This is the first of a series of posts about useful patterns I use with Cocotb. It’s probably documented somewhere in the Cocotb documentation, but I am writing them here for my own reference.

Anyway, this one is about a simple but important topic: how to deal with double-words, bytes, bits and endianness. It’s simple but easy to get wrong, which can lead to hours of wasted debugging (well, I did waste hours on this once).

Starting with a list of DW of a packet of some sort, dw_to_bytes takes a DW and generates a list of bytes depending on endianness. bytes_to_signal takes a list of bytes and flattens them starting from byte 0 (the MSB in the original DW).

Now, we have a signal that can be assigned to cocotb’s .value. Note this works as the SystemVerilog data_in range is [1023:0] which is the norm I guess. If the range is reversed, that adds another gotcha.

import cocotb
from cocotb.triggers import Timer


def dw_to_bytes(dw, endian='little'):
    b = []
    if endian == 'big':
        b = [(dw >> (8 * (3 - i))) & 0xFF for i in range(4)]
    else:
        b = [(dw >> (8 * i)) & 0xFF for i in range(4)]
    return b

def bytes_to_signal(bytes):
    sig = 0
    for i in range(len(bytes)):
        sig |= (bytes[i] << (8 * i))
    return sig

@cocotb.test()
async def test_bytes(dut):

    packet = [0x40AABBDD, 0x50000002]
    bytes = []

    for dw in packet:
        bytes.extend(dw_to_bytes(dw, endian='big'))
    print(f"Bytes: {[f'0x{b:02X}' for b in bytes]}")
    
    dut.data_in.value = bytes_to_signal(bytes)

    await Timer(10, unit='ns')
    
    print(f"Data in: {hex(dut.data_in.value)}")
Bytes: ['0x40', '0xAA', '0xBB', '0xDD', '0x50', '0x00', '0x00', '0x02']
Data in: 0x2000050ddbbaa40

Bits Link to heading

The built-in types (int/bytes) can be tricky when accessing bits, as we have to use shift/mask operations and probably use bin or hex for printing.

dw0 = packet[0]
print(f'DW0: 0x{dw0:08X}')
print(f"bin(Bit[15:8]): {bin(dw0 >> 8 & 0xff)}") # bin() returns str
print(f"hex(Bit[15:8]): {hex(dw0 >> 8 & 0xff)}") # hex() returns str
DW0: 0x40AABBDD
bin(Bit[15:8]): 0b10111011
hex(Bit[15:8]): 0xbb

On the other hand, Cocotb’s value uses Logic and LogicArray which are indexable.

print(dut.data_in.value[7:0])  # Byte 0
print(type(dut.data_in.value[7:0]))
01000000
<class 'cocotb.types.LogicArray'>