Skip to content

PutFullData

Muhammad Hadir Khan edited this page Aug 13, 2020 · 7 revisions

This page deals with the elaboration of PutFullData operation. This operation is initiated by the host on channel A and expects a response of AccessAck from the device on channel D.

The main difference between PutFullData and PutPartialData is that the former operation must write all the data indicated by a_size while the latter can write all the data indicated by a_size or it can write less than it depending upon the configuration of a_mask.

The other difference that the specification indicates is that in PutFullData the a_mask must have contiguous bits set for example in a 2 byte data write, a_mask can only be 'b0011 or 'b1100 not 'b0101 or any other configuration like this, however there is no such contiguous bits restriction for the a_mask in the PutPartialData operation.

Note: For our implementation we make sure that PutPartialData also has contiguous bits of a_mask set.

For this overview we will be using this figure of memory for convenience:

Memory

When a_size = 0

This means that the host wants to write 2a_size = 20 = 1 byte of data. The address can be word aligned as well as subword aligned which means we do not need to check for address, what matters is a_mask. This is exactly the same as we discussed in the Get:(a_size = 0) operation earlier. Read the Get operation first before reading further as that section elaborates in detail what is happening. Following are the possibilities of a_address and a_mask as covered previously:

a_address[31:0] a_mask[3:0]
'd0 'b0001
'd1 'b0010
'd2 'b0100
'd3 'b1000
'd4 'b0001
'd5 'b0010
'd6 'b0100
'd7 'b1000
. .
. .

Note: The 'd in above table indicates decimal representation and the 'b indicates binary representation.

Example 1: Writing Byte FF to Memory at address 0.

For this, we will set the a_mask = 'b0001 and a_address = 'h0. This will write one byte of data to the memory at address 0 on the byte lane set by a_mask.

Here is a little animation to demonstrate what is happening:

PutFullData Write

Functional verification design

For PutFullData we just create one additional check fulldata_chk which is only used when the opcode is of PutFullData.

// Our own mask created using the a_address
// If data bus width (DBW) is 32-bits then 32/8 = 4 bytes so a_mask uses 4 bits to represent the active byte lane.

val mask = Wire(UInt((DBW/8).W))
mask := (1 << a_address(1,0))

// Creating two wires of Chisel.Bool type in order to set them `true.B` if the check is passed or `false.B` if the check is failed.
val addr_chk = Wire(Bool())
val mask_chk = Wire(Bool())
val fulldata_chk = Wire(Bool())    // This is the new line added

when(a_size === 0.U) {
  addr_chk := true.B
  mask_chk := ~((a_mask & ~mask).orR)
  fulldata_chk := (a_mask & mask).orR    // This is the new line added
}

Note: This is the code that is elaborated in the Get(a_size = 0) with additional code lines marked with the comments.

The logic (a_mask & mask).orR is the new one added for the PutFullData. This ensures that a_size matches with a_mask.

Test Case: Example 1 (Writing Byte FF to Memory)

Input 1:
a_address[31:0] a_size[1:0] a_mask[3:0]
'h0 'h0 'b0001

With these inputs incoming let's analyse what is happening in the wires mask and fulldata_chk.

mask

mask = (1 << a_address(1,0))

a_address bits are as follows:

a_address[31] a_address[30] ... a_address[1] a_address[0]
0 0 ... 0 0

Since we extract the first two bits of a_address we get 'b00

So 'b0001 << 'b00 results in the same 'b0001 or 'd1

Which means,

mask := 1.U
fulldata_chk

We are using the mask and a_mask to set this wire. Currently we have mask := 'b0001 and a_mask := 'b0001.

In the first step we do a bitwise AND operation between a_mask and mask as:

a_mask & mask

This results in

'b0001 & 'b0001 = 'b0001

The .orR is a Chisel function for OR Reduction. Which returns true if any bit is set. Here it checks if any bit is set or not. As seen above there is a bit set so,

'b0001.orR returns true. Which makes the fulldata_chk := true.B and passes the check. ✅

Input 2:
a_address[31:0] a_size[1:0] a_mask[3:0]
'h0 'h0 'b0100

With these inputs incoming let's analyse what is happening in the wires mask and fulldata_chk.

Since the a_address = 'h0 same as the above Input 1 the mask would be set as:

mask := 1.U
fulldata_chk

We are using the mask and a_mask to set this wire. Currently we have mask := 'b0001 and a_mask := 'b0100.

In the first step we do a bitwise AND operation between a_mask and mask as:

a_mask & mask

This results in

'b0001 & 'b0100 = 'b0000

The .orR is a Chisel function for OR Reduction. Which returns true if any bit is set. Here it checks if any bit is set or not. As seen above there is no bit set so,

'b0000.orR returns false. Which makes the fulldata_chk := false.B and fails the check. ❌

This check rightly fails since the a_address and a_mask are not aligned. If a_address = 'h0 then a_mask must only be 'b0001 to write the first byte of data on to the first address location. According to the tilelink specification a_address, a_size and a_mask must correspond with one another. (See page 56)

When a_size = 1

This means that the host wants to write 2a_size = 21 = 2 bytes of data. The a_address will only be even this time. It can have subword addressing but must be even like (0, 2, 4, 6, 8, 10, 12 ...). Correspondingly, the a_mask will have contiguous bits in the form of 'b0011 or 'b1100. Complete elaboration and details can be found in the Get: (a_size = 1) page where we go in detail. For PutFullData the address_chk and mask_chk will be performed the same way as discussed previously in the Get: (a_size = 1) page. The additional thing here will be the fulldata_chk.

Functional verification design

// Our own mask created using the a_address
// If data bus width (DBW) is 32-bits then 32/8 = 4 bytes so a_mask uses 4 bits to represent the active byte lane.

val mask = Wire(UInt((DBW/8).W))
mask := (1 << a_address(1,0))

// Creating two wires of Chisel.Bool type in order to set them `true.B` if the check is passed or `false.B` if the check is failed.
val addr_chk = Wire(Bool())
val mask_chk = Wire(Bool())
val fulldata_chk = Wire(Bool())

when(a_size === 0.U) {    // 1 Byte covered in Get: (a_size = 0)
  addr_chk := true.B
  mask_chk := ~((a_mask & ~mask).orR)
  fulldata_chk := (a_mask & mask).orR
} .elsewhen(a_size === 1.U) {    // 2 Bytes covered in Get: (a_size = 1)
  addr_chk := ~a_address(0)
  mask_chk := Mux(a_address(1), ~((a_mask & "b0011".U).orR), ~((a_mask & "b1100".U).orR))
  fulldata_chk := Mux(a_address(1), a_mask(3,2).andR, a_mask(1,0).andR)    // New line added here
}

Note: This is the code that is elaborated in the Get(a_size = 1) with additional code lines marked with the comments.

Here we simply check if a_address first bit is set it means the address will be even and subword aligned (2, 6, 10, 14, 18 ...) therefore the a_mask must have two MSB bits set to access the byte lanes 'b1100. This is what we are checking with the logic a_mask(3,2).andR. The .andR is AND reduction in Chisel which returns true.B if all bits are set otherwise returns false.B. Consequently, if the first bit is not set it means the addressing is word aligned (0, 4, 8, 12 ...) therefore a_mask must have LSB byte lanes active there it's value must be 'b0011 and this is what we check with the logic a_mask(1,0).andR. You must already know why a_mask must have two MSB bits or LSB bits set depending upon the a_address if you read the Get operation overview first. We will not be going into the details here.

When a_size = 2

This means that the host wants to write 2a_size = 22 = 4 bytes of data. The a_address is only allowed to be word aligned in this configuration i.e (0, 4, 8, 12, 16, 20 ...). The a_mask must have all bits active 'b1111 to select all active byte lanes to write.

Functional verification design

// Our own mask created using the a_address
// If data bus width (DBW) is 32-bits then 32/8 = 4 bytes so a_mask uses 4 bits to represent the active byte lane.

val mask = Wire(UInt((DBW/8).W))
mask := (1 << a_address(1,0))

// Creating two wires of Chisel.Bool type in order to set them `true.B` if the check is passed or `false.B` if the check is failed.
val addr_chk = Wire(Bool())
val mask_chk = Wire(Bool())
val fulldata_chk = Wire(Bool())
when(a_size === 0.U) {    // 1 Byte covered in Get: (a_size = 0)
  addr_chk := true.B
  mask_chk := ~((a_mask & ~mask).orR)  
} .elsewhen(a_size === 1.U) {    // 2 Bytes covered in Get: (a_size = 1)
  addr_chk := ~a_address(0)
  mask_chk := Mux(a_address(1), ~((a_mask & "b0011".U).orR), ~((a_mask & "b1100".U).orR))
  fulldata_chk := Mux(a_address(1), a_mask(3,2).andR, a_mask(1,0).andR)
} .elsewhen(a_size === 2.U) {    // 4 Bytes
  addr_chk := ~(a_address(2,0).orR)
  mask_chk := true.B
  fulldata_chk := a_mask(3,0).andR    // New line added here
}

Note: This is the code that is elaborated in the Get(a_size = 2) with additional code lines marked with the comments.

This is as simple as it gets. The fulldata_chk is wired with this logic a_mask(3,0).andR which just ensures that all bits of a_mask are set in this configuration otherwise fulldata_chk := false.B and the check fails which will generate an error.