async def test_4kB_boundary(dut): """ Check that the driver raises an error when trying to cross a 4kB boundary """ axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, ram_stop = get_parameters(dut) await setup_dut(dut) for rw in ("Read", "Write"): burst_length = randint(2, 256) base = randrange(ram_start, ram_stop - 4096, 4096) offset = randrange(-(burst_length - 1) * data_width, 0, data_width) address = base + offset write_values = \ [randrange(0, 2**(data_width * 8)) for i in range(burst_length)] try: if rw == "Read": await axim.read(address, burst_length) else: await axim.write(address, write_values) raise TestFailure( "{} from {:#x} to {:#x} crosses a 4kB boundary, but the " "driver allowed it".format( rw, address, address + (burst_length - 1) * data_width)) except ValueError: pass
async def test_incr_burst(dut, size, return_rresp): """Test burst reads/writes""" axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, ram_stop = get_parameters(dut) size = size if size else data_width await setup_dut(dut) burst_length = randrange(2, 256) base = randrange(ram_start, ram_stop, 4096) offset = randrange(0, 4096 - (burst_length - 1) * size, size) address = base + offset write_values = [randrange(0, 2**(size * 8)) for i in range(burst_length)] # Make strobe one item less than data, to test also that driver behavior strobes = [randrange(0, 2**size) for i in range(len(write_values) - 1)] previous_values = await axim.read(address, len(write_values), size=size) await axim.write(address, write_values, byte_enable=strobes, size=size) read_values = await axim.read(address, len(write_values), return_rresp=return_rresp, size=size) if return_rresp: read_values, rresp_list = zip(*read_values) for i, rresp in enumerate(rresp_list): if rresp is not AXIxRESP.OKAY: raise TestFailure( "Read at beat {}/{} with starting address {:#x} failed " "with RRESP {} ({})".format(i + 1, len(rresp_list), address, rresp.value, rresp.name)) strobes += [strobes[-1]] expected_values = \ [add_wstrb_mask(size, previous_value, write_value, wstrb) for previous_value, write_value, wstrb in zip(previous_values, write_values, strobes)] for i in range(len(read_values)): if expected_values[i] != read_values[i]: raise TestFailure( "Read {:#x} at beat {}/{} with starting address {:#x}, but " "was expecting {:#x} ({:#x} with {:#x} as strobe and {:#x} as " "previous value)".format(read_values[i].integer, i + 1, burst_length, address, expected_values[i], write_values[i], strobes[i], previous_values[i].integer))
async def test_simultaneous(dut, sync, num=5): """Test simultaneous reads/writes""" axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, _ = get_parameters(dut) await setup_dut(dut) # Avoid crossing the 4kB boundary by using just the first 4kB block base_address = randrange(ram_start, ram_start + 4096 - 2 * num * data_width, data_width) # Clear the memory cells await axim.write(base_address, [0] * num) addresses = [base_address + i * data_width for i in range(num)] write_values = [randrange(0, 2**(data_width * 8)) for i in range(num)] writers = [ axim.write(address, value, sync=sync) for address, value in zip(addresses, write_values) ] await Combine(*writers) readers = [ cocotb.fork(axim.read(address, sync=sync)) for address in addresses ] dummy_addrs = [base_address + (num + i) * data_width for i in range(num)] dummy_writers = [ cocotb.fork(axim.write(address, value, sync=sync)) for address, value in zip(dummy_addrs, write_values) ] read_values = [] for reader in readers: read_values.append((await Join(reader))[0]) await Combine(*[Join(writer) for writer in dummy_writers]) for i, (written, read) in enumerate(zip(write_values, read_values)): if written != read: raise TestFailure("#{}: wrote {:#x} but read back {:#x}".format( i, written, read.integer))
async def test_illegal_operations(dut): """Test whether illegal operations are correctly refused by the driver""" axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, _ = get_parameters(dut) illegal_operations = ( (AXIBurst.INCR, -1, None), (AXIBurst.INCR, 0, None), (AXIBurst.FIXED, 17, None), (AXIBurst.INCR, 257, None), (AXIBurst.WRAP, 3, None), (AXIBurst.INCR, 4, -1), (AXIBurst.INCR, 4, data_width - 1), (AXIBurst.INCR, 4, data_width * 2), ) await setup_dut(dut) for rw in ("Read", "Write"): for burst, length, size in illegal_operations: try: if rw == "Read": await axim.read(ram_start, length, size=size, burst=burst) else: values = [ randrange(0, 2**(data_width * 8)) for i in range(length) ] await axim.write(ram_start, values, size=size, burst=burst) raise TestFailure( "{} with length={}, size={} and burst={} has been " "performed by the driver, but it is an illegal " "operation".format(rw, length, size, burst.name)) except ValueError: pass
async def test_fixed_wrap_burst(dut, size, burst_length=16): """Test FIXED and WRAP read/writes""" axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, ram_stop = get_parameters(dut) size = size if size else data_width await setup_dut(dut) base = randrange(ram_start, ram_stop, 4096) offset = randrange(0, 4096 - (burst_length - 1) * size, size) address = base + offset for burst in (AXIBurst.FIXED, AXIBurst.WRAP): write_values = \ [randrange(0, 2**(size * 8)) for i in range(burst_length)] await axim.write(address, write_values, burst=burst, size=size) if burst is AXIBurst.FIXED: # A FIXED write on a memory is like writing the last element, # reading it with a FIXED burst returns always the same value. expected_values = [write_values[-1]] * burst_length else: # Regardless of the boundary, reading with a WRAP from the same # address with the same length returns the same sequence. # This RAM implementation does not support WRAP bursts and treats # them as INCR. expected_values = write_values read_values = await axim.read(address, burst_length, burst=burst, size=size) compare_read_values(expected_values, read_values, burst, burst_length, address)
async def test_narrow_burst(dut): """Test that narrow writes and full reads match""" axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, ram_stop = get_parameters(dut) await setup_dut(dut) burst_length = (randrange(2, 256) // 2) * 2 address = ram_start write_values = \ [randrange(0, 2**(data_width * 8 // 2)) for i in range(burst_length)] await axim.write(address, write_values, size=data_width // 2) read_values = await axim.read(address, burst_length // 2) expected_values = [ write_values[i + 1] << 16 | write_values[i] for i in range(0, burst_length // 2, 2) ] compare_read_values(expected_values, read_values, AXIBurst.INCR, burst_length // 2, address)
async def test_unaligned(dut, size, burst): """Test that unaligned read and writes are performed correctly""" def get_random_words(length, size, num_bits): r_bytes = getrandbits(num_bits).to_bytes(size * length, 'little') r_words = [r_bytes[i * size:(i + 1) * size] for i in range(length)] r_ints = [int.from_bytes(w, 'little') for w in r_words] return r_bytes, r_ints axim = AXI4Master(dut, AXI_PREFIX, dut.clk) _, data_width, ram_start, ram_stop = get_parameters(dut) size = size if size else data_width await setup_dut(dut) burst_length = randrange(2, 16 if burst is AXIBurst.FIXED else 256) if burst is AXIBurst.FIXED: address = randrange(ram_start, ram_stop, size) else: base = randrange(ram_start, ram_stop, 4096) offset = randrange(0, 4096 - (burst_length - 1) * size, size) address = base + offset unaligned_addr = address + randrange(1, size) shift = unaligned_addr % size # Write aligned, read unaligned write_bytes, write_values = \ get_random_words(burst_length, size, size * burst_length * 8) await axim.write(address, write_values, burst=burst, size=size) read_values = await axim.read(unaligned_addr, burst_length, burst=burst, size=size) if burst is AXIBurst.FIXED: mask = 2**((size - shift) * 8) - 1 expected_values = \ [(write_values[-1] >> (shift * 8)) & mask] * burst_length else: expected_bytes = write_bytes[shift:] expected_words = [ expected_bytes[i * size:(i + 1) * size] for i in range(burst_length) ] expected_values = [int.from_bytes(w, 'little') for w in expected_words] compare_read_values(expected_values, read_values, burst, burst_length, unaligned_addr) # Write unaligned, read aligned write_bytes, write_values = \ get_random_words(burst_length, size, (size * burst_length - shift) * 8) first_word = randrange(0, 2**(size * 8)) await axim.write(address, first_word, burst=burst, size=size) await axim.write(unaligned_addr, write_values, burst=burst, size=size) read_values = await axim.read(address, burst_length, burst=burst, size=size) mask_low = 2**(shift * 8) - 1 mask_high = (2**((size - shift) * 8) - 1) << (shift * 8) if burst is AXIBurst.FIXED: last_val = \ first_word & mask_low | (write_values[-1] << shift * 8) & mask_high expected_values = [last_val] * burst_length else: first_bytes = (first_word & mask_low).to_bytes(shift, 'little') expected_bytes = first_bytes + write_bytes expected_words = [ expected_bytes[i * size:(i + 1) * size] for i in range(burst_length - 1) ] expected_values = [int.from_bytes(w, 'little') for w in expected_words] compare_read_values(expected_values, read_values, burst, burst_length, unaligned_addr)