def packets_write(dut, axis_writer, axilite_writer, axilite_reader, pkts, latencies, inter_packet_times): """Apply packets on DuT input.""" # start the module yield axilite_writer.write(CPUREG_OFFSET_CTRL_ACTIVE, 0x1) # wait a little bit yield wait_n_cycles(dut.clk, 10) # iterate over all packets for i, pkt in enumerate(pkts): # convert packet to AXI4-Stream data (tdata, tkeep) = packet_to_axis_data(pkt, AXIS_BIT_WIDTH) # include latency and inter-packet time in last TUSER word tuser = len(tdata) * [0] tuser[-1] = latencies[i] | (1 << 24) | (inter_packet_times[i] << 25) # write data yield axis_writer.write(tdata, tkeep, tuser) # wait random number of cycles before applying the next packet yield wait_n_cycles(dut.clk, random.randint(0, 10)) # stop the module yield axilite_writer.write(CPUREG_OFFSET_CTRL_ACTIVE, 0x0)
def nt_timestamp_test(dut): """Main test bench function. TODO: no automatic validation of DUT output yet. Look at waveforms. """ # start the clock cocotb.fork(clk_gen(dut.clk156, CLK_FREQ_MHZ)) # reset dut yield rstn(dut.clk156, dut.rstn156) # create axi lite writer, connect and reset axi_writer = AXI_Lite_Writer() axi_writer.connect(dut, dut.clk156, AXI_DATA_WIDTH, "ctrl") yield axi_writer.rst() # run simulation for a while yield wait_n_cycles(dut.clk156, 1000) # set number of clock cycles which shall pass until counter is incremented # to 10 yield axi_writer.write(CPUREG_OFFSET_CTRL_CYCLES_PER_TICK, 10) # run simulation for a while yield wait_n_cycles(dut.clk156, 1000)
def apply_input(dut, meta, data): """Apply meta data and packet data as DUT stimulus. Meta data is written to the input meta data FIFO, packet data is written to the input packet data FIFO. """ # data word index i = 0 # iterate over meta data words for m in meta: # get the capture length len_capture = (m >> 64) & 0x7FF # calculate the number of 64 bit data words if len_capture % 8 == 0: len_capture_words = len_capture / 8 else: len_capture_words = len_capture / 8 + 1 # apply data words at DUT input for _ in range(len_capture_words): # get data word and increment index d = data[i] i += 1 # make sure input data fifo is not full check_value("fifo_data_full_i", dut.fifo_data_full_o, 0x0) # apply data word dut.fifo_data_din_i <= d dut.fifo_data_wr_en_i <= 1 yield RisingEdge(dut.clk) # done writing data dut.fifo_data_wr_en_i <= 0 # wait a random number of cycles until applying the meta data word yield wait_n_cycles(dut.clk, random.randint(0, 10)) # make sure input meta data fifo is not full check_value("fifo_meta_full_o", dut.fifo_meta_full_o, 0x0) # apply meta data word dut.fifo_meta_din_i <= m dut.fifo_meta_wr_en_i <= 1 yield RisingEdge(dut.clk) # done applying meta data dut.fifo_meta_wr_en_i <= 0 # wait a random number of cycles again yield wait_n_cycles(dut.clk, random.randint(0, 10))
def packets_write(dut, axis_writer, pkts): """Apply packets on DuT input.""" # iterate over all packets for pkt in pkts: # convert packet to AXI4-Stream data (tdata, tkeep) = packet_to_axis_data(pkt, AXIS_BIT_WIDTH) # apply data yield axis_writer.write(tdata, tkeep) # wait a random number of cycles before writing next packet yield wait_n_cycles(dut.clk156, randint(0, 10))
def frames_write(dut, axis_writer, frames): """Apply generated frames on DuT input.""" # iterate over frames for frame in frames: # convert frame to AXI4-Stream data (tdata, tkeep) = packet_to_axis_data(frame, AXIS_BIT_WIDTH) # write frame yield axis_writer.write(tdata, tkeep, []) # wait a random number of cycles before writing next frame yield wait_n_cycles(dut.clk, randint(0, 10))
def perform_test(dut, axis_writer, pkts, latencies, inter_packet_times, active, max_len_capture): """Perform a test run for specific capture parameters.""" # enable/disable capture dut.active_i <= active # set per-packet capture length dut.max_len_capture_i <= max_len_capture # wait a few cycles yield wait_n_cycles(dut.clk, 5) # if capture is enabled, module shall be active now. otherwise it should # remain deactivated check_value("active_o", dut.active_o, active) # start writing packets to DuT input coroutine_pkts_write = cocotb.fork(packets_write(dut, axis_writer, pkts, latencies, inter_packet_times)) # start coroutine that checks module output coroutine_check_output = cocotb.fork(check_output(dut, pkts, latencies, inter_packet_times)) # wait for coroutines to complete yield coroutine_pkts_write.join() yield coroutine_check_output.join() # disable module dut.active_i <= 0 # wait a few cycles yield wait_n_cycles(dut.clk, 3) # make sure module is now inactive check_value("active_o", dut.active_o, 0x0)
def packets_write(dut, axis_writer, pkts, latencies, inter_packet_times): """Apply packets on DuT input.""" # iterate over all packets for i, pkt in enumerate(pkts): # convert packet to AXI4-Stream data (tdata, tkeep) = packet_to_axis_data(pkt, DATAPATH_BIT_WIDTH) # include latency and inter-packet time in last TUSER word tuser = len(tdata) * [0] tuser[-1] = latencies[i] | (1 << 24) | (inter_packet_times[i] << 25) # write data yield axis_writer.write(tdata, tkeep, tuser) # wait random number of cycles before writing the next packet yield wait_n_cycles(dut.clk, randint(0, 10))
def packets_write(dut, axis_writer, pkts): """Apply packets on DuT input.""" # iterate over all packets for pkt in pkts: # convert packet to axi stream data (tdata, tkeep) = packet_to_axis_data(pkt, AXIS_BIT_WIDTH) # if TLAST is low, TUSER input must be zero. for the last AXI4-Stream # transfer of the packet, TUSER is set to a predetermined random value tuser = len(tdata) * [0] tuser[-1] = TUSER_LSB # write data on AXI4-Stream slave interface yield axis_writer.write(tdata, tkeep, tuser) # wait a random number of cycles before applying next packet yield wait_n_cycles(dut.clk156, randint(0, 100))
def nt_recv_capture_mem_write_fifo_wrapper_test(dut): """Test bench main function.""" # start the clock cocotb.fork(clk_gen(dut.clk, CLK_FREQ_MHZ)) # do not issue software reset dut.rst_sw <= 0 # initially we do not read or write data to the FIFO dut.rd_en_i <= 0 dut.wr_en_i <= 0 # initially do not add alignment data dut.align_i <= 0 # reset the dut yield rstn(dut.clk, dut.rstn) # wait a few cycles yield wait_n_cycles(dut.clk, 10) for i, n_words in enumerate(N_INPUT_WORDS): print("Test %d/%d" % (i + 1, len(N_INPUT_WORDS))) # generate 64 bit input data data64 = gen_input_data(n_words) # convert 64 bit input data to 512 bit output data data512 = convert_data_64_to_512(data64) # start input coroutine coroutine_in = cocotb.fork(apply_input(dut, data64)) # start ouput coroutine coroutine_out = cocotb.fork(check_output(dut, data512)) # start one coroutine for triggering aligment coroutine_align = cocotb.fork(trigger_align(dut, data64)) # wait for coroutines to complete yield coroutine_in.join() yield coroutine_out.join() yield coroutine_align.join()
def nt_recv_capture_fifo_merge(dut): """Test bench main function.""" # start the clock cocotb.fork(clk_gen(dut.clk, CLK_FREQ_MHZ)) # do not issue software reset dut.rst_sw <= 0 # output fifo is never full dut.fifo_full_i <= 0 # intially not writing to input fifos dut.fifo_meta_wr_en_i <= 0 dut.fifo_data_wr_en_i <= 0 # reset the dut yield rstn(dut.clk, dut.rstn) # wait a few cycles yield wait_n_cycles(dut.clk, 5) for i in range(N_REPEATS): # print out some status print("Test %d/%d" % (i+1, N_REPEATS)) # determine random maximum capture length max_len_capture = random.randint(0, 1514) # generate packet and meta data (meta, data) = gen_input_data(max_len_capture) # start stimulus coroutine cocotb.fork(apply_input(dut, meta, data)) # start coroutine checking output coroutine_chk = cocotb.fork(check_output(dut, meta, data, max_len_capture)) # wat for checking coroutine to complete yield coroutine_chk.join()
def nt_gen_rate_ctrl_top_test(dut): """Test bench main function.""" # initially module is inactive dut.active_i <= 0 # no software reset dut.rst_sw156 <= 0 # start the clock cocotb.fork(clk_gen(dut.clk156, CLK_FREQ_MHZ)) # reset the DuT yield rstn(dut.clk156, dut.rstn156) # create AXI4-Stream writer and reader axis_writer = AXIS_Writer() axis_writer.connect(dut, dut.clk156, AXIS_BIT_WIDTH) yield axis_writer.rst() axis_reader = AXIS_Reader() axis_reader.connect(dut, dut.clk156, AXIS_BIT_WIDTH) yield axis_reader.rst() # create AXI4-Lite writer and connect to DuT axi_lite_writer = AXI_Lite_Writer() axi_lite_writer.connect(dut, dut.clk156, AXI_CTRL_BIT_WIDTH, "ctrl") yield axi_lite_writer.rst() # create AXI4-Lite reader and connect to DuT axi_lite_reader = AXI_Lite_Reader() axi_lite_reader.connect(dut, dut.clk156, AXI_CTRL_BIT_WIDTH, "ctrl") yield axi_lite_reader.rst() # initially we are always ready to receive dut.m_axis_tready <= 1 # start a coroutine that counts the number of cycles between two packets # on the output axi stream cocotb.fork(count_cycles_between_axis_transmission(dut)) print("Test 1/4") # generate some packets and inter-packet times. we start with a constant # inter-packet time of 200 cycles, more than enough to transmit each packet pkts = [] for _ in range(N_PACKETS): pkts.append((gen_packet(), 200)) # write packet data cocotb.fork(packets_write(dut, axis_writer, pkts)) # wait a little yield wait_n_cycles(dut.clk156, 500) # start the module dut.active_i <= 1 # start coroutine that checks output and wait until it completes yield cocotb.fork(packets_read(dut, axis_reader, pkts, True)) # deactive the module dut.active_i <= 0 # make sure no warning was flagged status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) assert status == 0x0 print("Test 2/4") # now generate packets of fixed size of 800 bytes. Since the datapath is # 8 byte wide, it will take exactly 100 cycles to send them. Also select # a fixed inter-packet time of 100 cycles. packets should be sent # back-to-back pkts = [] for _ in range(N_PACKETS): pkt = Ether(src="53:00:00:00:00:01", dst="53:00:00:00:00:02") pkt /= ''.join( chr(random.randint(0, 255)) for _ in range(800 - len(pkt))) pkts.append((pkt, 100)) # apply packet data cocotb.fork(packets_write(dut, axis_writer, pkts)) # wait a little yield wait_n_cycles(dut.clk156, 1000) # start the module dut.active_i <= 1 # start coroutine that checks output and wait until it completes yield cocotb.fork(packets_read(dut, axis_reader, pkts, True)) # deactive the module dut.active_i <= 0 # make sure no warning was flagged status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) assert status == 0x0 print("Test 3/4") # repeat the experiment, but decrement inter-packet time to 99 cycles. # since inter-packet time is smaller than the packet transmit time, we # should get a warning pkts = [] for _ in range(N_PACKETS): pkt = Ether(src="53:00:00:00:00:01", dst="53:00:00:00:00:02") pkt /= ''.join( chr(random.randint(0, 255)) for _ in range(800 - len(pkt))) pkts.append((pkt, 99)) # apply packet data cocotb.fork(packets_write(dut, axis_writer, pkts)) # wait a little yield wait_n_cycles(dut.clk156, 500) # start the module dut.active_i <= 1 # start coroutine that checks output and wait until it completes yield cocotb.fork(packets_read(dut, axis_reader, pkts, False)) # deactive the module dut.active_i <= 0 # make sure a warning was flagged status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) assert status == 0x1 # perform software reset to clear warning flag yield axi_lite_writer.write(CPUREG_OFFSET_RST, 0x1) # now start toggeling tready cocotb.fork(toggle_signal(dut.clk156, dut.m_axis_tready)) print("Test 4/4") # repeat the experiment, inter-packet time back at 100 cycles. since the # slave is not always ready to receive, this should cause a warning pkts = [] for _ in range(N_PACKETS): pkt = Ether(src="53:00:00:00:00:01", dst="53:00:00:00:00:02") pkt /= ''.join( chr(random.randint(0, 255)) for _ in range(800 - len(pkt))) pkts.append((pkt, 100)) # apply packet data cocotb.fork(packets_write(dut, axis_writer, pkts)) # wait a little yield wait_n_cycles(dut.clk156, 500) # start the module dut.active_i <= 1 # start coroutine that checks output and wait until it completes yield cocotb.fork(packets_read(dut, axis_reader, pkts, False)) # deactive the module dut.active_i <= 0 # make sure no warning was flagged status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) assert status == 0x1
def nt_recv_capture_mem_write_test(dut): """Test bench main function.""" # open file with random content try: f = File("files/random.file") except IOError: raise cocotb.result.TestFailure("Generate input data by calling " + "'./create_random.py' in 'files' " + "folder!") # file size must be a multiple of AXI data width if f.size() % (BIT_WIDTH_MEM_WRITE / 8) != 0: raise cocotb.result.TestFailure("invalid input data size") # create a ring buffer memory (initially of size 0) and connect it to the # DuT ring_buff = Mem(0) ring_buff.connect(dut) # start the clock cocotb.fork(clk_gen(dut.clk, CLK_FREQ_MHZ)) # deassert sw reset dut.rst_sw <= 0 # initially module is disabled dut.active_i <= 0 # initially no FIFO flush dut.flush_i <= 0 # reset DuT yield rstn(dut.clk, dut.rstn) # start the ring buffer memory main routine cocotb.fork(ring_buff.main()) # wait some more clock cycles yield wait_n_cycles(dut.clk, 5) # iterate over all ring buffer sizes for i, ring_buff_size in enumerate(RING_BUFF_SIZES): # set ring buffer size ring_buff.set_size(ring_buff_size) # iterate over all adderesses where ring buffer shall be located in # memory for j, ring_buff_addr in enumerate(RING_BUFF_ADDRS): # print status print("Test %d/%d" % (i * len(RING_BUFF_ADDRS) + j + 1, len(RING_BUFF_ADDRS) * len(RING_BUFF_SIZES))) # we have a total of 8 GByte of memory. Make sure the ring buffer # fits at the desired address if ring_buff_addr + ring_buff_size > 0x1FFFFFFFF: raise cocotb.result.TestFailure("ring buffer is too large") # to reduce the simulation memory footprint, provide the memory # module the first memory address that we actually care about ring_buff.set_offset(ring_buff_addr) # apply ring buffer memory location to dut dut.mem_addr_hi_i <= ring_buff_addr >> 32 dut.mem_addr_lo_i <= ring_buff_addr & 0xFFFFFFFF # apply ring buffer address range to dut dut.mem_range_i <= ring_buff_size - 1 # reset read address pointer dut.addr_rd_i <= 0 # start a couroutine that applies input data cocotb.fork(apply_input(dut, f)) # wait a few clock cycles yield wait_n_cycles(dut.clk, 10) # start the ring buffer read coroutine and wait until it completes yield ring_buff_read(dut, ring_buff, f) # clear the ring buffer contents ring_buff.clear() # close file f.close()
def nt_recv_capture_top_test(dut): """Test bench main function.""" # start the clock cocotb.fork(clk_gen(dut.clk, CLK_FREQ_MHZ)) # no software reset dut.rst_sw <= 0 # reset DuT yield rstn(dut.clk, dut.rstn) # create AXI4-Lite writer, connect and reset it axilite_writer = AXI_Lite_Writer() axilite_writer.connect(dut, dut.clk, AXI_CTRL_BIT_WIDTH, "ctrl") yield axilite_writer.rst() # create AXI4-Lite reader, connect and reset it axilite_reader = AXI_Lite_Reader() axilite_reader.connect(dut, dut.clk, AXI_CTRL_BIT_WIDTH, "ctrl") yield axilite_reader.rst() # create AXI4-Stream writer, connect and reset it axis_writer = AXIS_Writer() axis_writer.connect(dut, dut.clk, AXIS_BIT_WIDTH) yield axis_writer.rst() # create a ring buffer memory (initially of size 0) and connect it to the # DuT ring_buff = Mem(0) ring_buff.connect(dut, "ddr3") # generate a couple of random Ethernet packets. For each packet, generate # a 16 bit latency value and a 26 bit inter-packet time value pkts = [] latencies = [] inter_packet_times = [] for _ in range(N_PACKETS): pkts.append(gen_packet()) latencies.append(random.randint(0, 2**24 - 1)) inter_packet_times.append(random.randint(0, 2**28 - 1)) # start the ring buffer memory main routine cocotb.fork(ring_buff.main()) # wait some more clock cycles yield wait_n_cycles(dut.clk, 5) # iterate over all ring buffer sizes for i, ring_buff_size in enumerate(RING_BUFF_SIZES): # set ring buffer size ring_buff.set_size(ring_buff_size) # iterate over all adderesses where ring buffer shall be located in # memory for j, ring_buff_addr in enumerate(RING_BUFF_ADDRS): # print status print("Test %d/%d (this will take a while)" % (i * len(RING_BUFF_ADDRS) + j + 1, len(RING_BUFF_ADDRS) * len(RING_BUFF_SIZES))) # we have a total of 8 GByte of memory. Make sure the ring buffer # fits at the desired address if ring_buff_addr + ring_buff_size > 0x1FFFFFFFF: raise cocotb.result.TestFailure("ring buffer is too large") # to reduce the simulation memory footprint, provide the memory # module the first memory address that we actually care about ring_buff.set_offset(ring_buff_addr) # write ring buffer memory location and address range yield axilite_writer.write(CPUREG_OFFSET_CTRL_MEM_ADDR_HI, ring_buff_addr >> 32) yield axilite_writer.write(CPUREG_OFFSET_CTRL_MEM_ADDR_LO, ring_buff_addr & 0xFFFFFFFF) yield axilite_writer.write(CPUREG_OFFSET_CTRL_MEM_RANGE, ring_buff_size - 1) # itererate over all capture lengths for max_len_capture in MAX_CAPTURE_LENS: # reset read address pointer yield axilite_writer.write(CPUREG_OFFSET_CTRL_ADDR_RD, 0x0) # set max capture length yield axilite_writer.write(CPUREG_OFFSET_CTRL_MAX_LEN_CAPTURE, max_len_capture) # start couroutine that applies packets at input cocotb.fork( packets_write(dut, axis_writer, axilite_writer, axilite_reader, pkts, latencies, inter_packet_times)) # wait a bit yield wait_n_cycles(dut.clk, 50) # start the ring buffer read coroutine and wait until it # completes yield ring_buff_read(dut, axilite_writer, axilite_reader, ring_buff, ring_buff_addr, max_len_capture, pkts, latencies, inter_packet_times) # make sure no error occured errs = yield axilite_reader.read(CPUREG_OFFSET_STATUS_ERRS) assert errs == 0x0 # make sure packet count is correct pkt_cnt = \ yield axilite_reader.read(CPUREG_OFFSET_STATUS_PKT_CNT) assert pkt_cnt == len(pkts) # make sure module is deactivated now active = yield axilite_reader.read(CPUREG_OFFSET_STATUS_ACTIVE) assert active == 0 # clear the ring buffer contents ring_buff.clear()
def ring_buff_read(dut, axilite_writer, axilite_reader, ring_buff, ring_buff_addr, max_len_capture, pkts_ref, latencies_ref, inter_packet_times_ref): """Read data from the ring buffer and check it for correctness. The coroutines monitors the ring buffer write pointer and reads data from the buffer if sufficient data is available. It ensures that the read data matches the expected one. """ # get ring buffer size ring_buff_size = ring_buff.size() # ring buffer must be larger than 16384 bytes if ring_buff_size <= 16384: raise cocotb.result.TestFailure("ring buffer size too small") # ring buffer size must be a multiple of 16384 bytes if ring_buff_size % 16384 != 0: raise cocotb.result.TestFailure("ring buffer size invalid") # transfer size must be smaller than ring buffer if RD_TRANSFER_SIZE_MAX >= ring_buff_size: raise cocotb.result.TestFailure("transfer size too large") # determine the number of bytes that we are expecting to read in total size_outstanding = 0 # iterate over packets for pkt in pkts_ref: # for each packet we need to read 8 byte of meta information size_outstanding += 8 # determine data capture length len_capture = min(len(pkt), max_len_capture) # data is captured at the granularity of 8 byte words if len_capture % 8 == 0: size_outstanding += len_capture else: size_outstanding += 8 * (len_capture / 8 + 1) # total capture data is 64 byte aligned if size_outstanding % 64 != 0: size_outstanding = 64 * (size_outstanding / 64 + 1) # read pointer has been reset and currently is zero rd = 0 data = [] while True: # number of outstanding bytes that still need to be read must never be # negative assert size_outstanding >= 0 # abort if there is no more data to be read if size_outstanding == 0: break # read error register errs = yield axilite_reader.read(CPUREG_OFFSET_STATUS_ERRS) # make sure there was no error assert errs == 0x0 # get the write pointer wr = yield axilite_reader.read(CPUREG_OFFSET_CTRL_ADDR_WR) # get memory size from current read pointer position until the end of # the ring buffer memory location ring_buff_size_end = ring_buff_size - rd # calculate the desired memory transfer size transfer_size = min(ring_buff_size_end, min(size_outstanding, RD_TRANSFER_SIZE_MAX)) # calculated memory transfer size must always be positive assert transfer_size > 0 # ... and it must always be a multiple of 64 bytes assert transfer_size % 64 == 0 if rd == wr: # ring buffer is empty -> nothing to transfer do_transfer = False elif rd < wr: # we can read if the difference between both pointers is at least # the desired transfer size do_transfer = (wr - rd) >= transfer_size elif wr < rd: # we can read until the end of the ring buffer do_transfer = True if not do_transfer: # no data transfer shall take place now, do nothing continue # read data from the ring buffer data_ring_buff = ring_buff.read(ring_buff_addr + rd, transfer_size) # write data to list in 8 byte words for i in range(transfer_size / 8): d = data_ring_buff >> ( (transfer_size / 8 - i - 1) * 64) & 2**64 - 1 data.append(d) # update read pointer if (rd + transfer_size) == ring_buff_size: # end of memory reached, wrap around rd = 0 else: assert (rd + transfer_size) < ring_buff_size rd = rd + transfer_size # write read pointer to DuT yield axilite_writer.write(CPUREG_OFFSET_CTRL_ADDR_RD, rd) # decrement number of bytes that still remain to be written to memory size_outstanding -= transfer_size # wait a little bit yield wait_n_cycles(dut.clk, 100) # check data for correctness check_data(pkts_ref, latencies_ref, inter_packet_times_ref, data, max_len_capture)
def nt_gen_replay_mem_read_test(dut): """Test bench main function.""" # open trace file trace = File("files/random.file") # get trace file size trace_size = trace.size() # trace file size must be a multiple of AXI data width if trace.size() % (AXI_BIT_WIDTH / 8) != 0: raise cocotb.result.TestFailure("invalid trace size") # calculate ring buffer sizes ring_buff_sizes = [] for ring_buff_size in RING_BUFF_SIZES: # size of ring buffer is determined by multiplying the size factor by # the size of the trace ring_buff_size = int(ring_buff_size * trace_size) # make sure that the ring buffer size is multiple of AXI data width if ring_buff_size % (AXI_BIT_WIDTH / 8) != 0: ring_buff_size += AXI_BIT_WIDTH/8 - ring_buff_size % \ (AXI_BIT_WIDTH/8) ring_buff_sizes.append(ring_buff_size) # create a ring buffer memory (initially of size 0) and connect it to the # DUT ring_buff = Mem(0) ring_buff.connect(dut) # start the clock cocotb.fork(clk_gen(dut.clk, CLK_FREQ_MHZ)) # deassert sw reset dut.rst_sw <= 0 # initially module start is not triggered dut.ctrl_start_i <= 0 # reset dut yield rstn(dut.clk, dut.rstn) # start the ring buffer memory main routine cocotb.fork(ring_buff.main()) # wait some more clock cycles yield wait_n_cycles(dut.clk, 5) # randomly toggle fifo_prog_full input signal dut.fifo_prog_full_i <= 0 cocotb.fork(toggle_signal(dut.clk, dut.fifo_prog_full_i)) # iterate over all ring buffer sizes for i, ring_buff_size in enumerate(ring_buff_sizes): # set ring buffer size ring_buff.set_size(ring_buff_size) # iterate over all adderesses where ring buffer shall be located in # memory for j, ring_buff_addr in enumerate(RING_BUFF_ADDRS): # print status print("Test %d/%d" % (i * len(RING_BUFF_ADDRS) + j + 1, len(RING_BUFF_ADDRS) * len(RING_BUFF_SIZES))) # we have a total of 8 GByte of memory. Make sure the ring buffer # fits at the desired address if ring_buff_addr + ring_buff_size > 0x1FFFFFFFF: raise cocotb.result.TestFailure("ring buffer is too large") # to reduce the simulation memory footprint, provide the memory # module the first memory address that we actually care about ring_buff.set_offset(ring_buff_addr) # apply ring buffer memory location to dut dut.ctrl_mem_addr_hi_i <= ring_buff_addr >> 32 dut.ctrl_mem_addr_lo_i <= ring_buff_addr & 0xFFFFFFFF # apply ring buffer address range to dut dut.ctrl_mem_range_i <= ring_buff_size - 1 # apply trace size to dut dut.ctrl_trace_size_hi_i <= trace_size >> 32 dut.ctrl_trace_size_lo_i <= trace_size & 0xFFFFFFFF # reset write address pointer dut.ctrl_addr_wr_i <= 0 # start reading from the ring buffer dut.ctrl_start_i <= 1 yield RisingEdge(dut.clk) dut.ctrl_start_i <= 0 yield RisingEdge(dut.clk) # start writing the ring buffer cocotb.fork(ring_buff_write(dut, ring_buff, trace)) # start checking dut output and wait until it completes yield cocotb.fork(check_output(dut, trace)).join() # clear the ring buffer contents ring_buff.clear() # close trace file trace.close()
def ring_buff_write(dut, ring_buff, trace, ring_buff_addr, axi_lite_reader, axi_lite_writer): """Coroutine writes trace data to the ring buffer in memory. The coroutine monitors the ring buffer read pointer (set by the DUT) and writes data to the buffer when a sufficient amount of storage is available. """ # get the ring buffer size ring_buff_size = ring_buff.size() # get trace size trace_size = trace.size() # transfer size must be smaller than ring buffer size if WR_TRANSFER_SIZE_MAX >= ring_buff_size: raise cocotb.result.TestFailure("transfer size too large") # initialize number of bytes that still need to be transfered to memory trace_size_outstanding = trace_size # initialize write pointer wr = 0x0 while True: # number of outstanding bytes for transfer must never be negative assert trace_size_outstanding >= 0 # abort if there is no more trace data to be transfered if trace_size_outstanding == 0: break # get the current read pointer rd = yield axi_lite_reader.read(CPUREG_OFFSET_CTRL_ADDR_RD) # get memory size from current write pointer position until the end # of the ring buffer memory location ring_buff_size_end = ring_buff_size - wr # calculate the desired transfer size transfer_size = \ min(ring_buff_size_end, min(trace_size_outstanding, WR_TRANSFER_SIZE_MAX)) # calculated memory transfer size must always be positive assert transfer_size > 0 if rd == wr: # ring buffer is empty --> write data do_transfer = True elif rd < wr: # as long as ring buffer contains valid data, read and write # pointers must never become equal. If the read pointer is smaller # than the write pointer, we may fill up the memory until the end. # This means that the write pointer will may wrap around and have a # value of 0. Now if the read pointer is currently 0 as well, this # would result in an error situation in which the memory would be # assumed to be empty. Thus, special attention is necessary here. do_transfer = (rd != 0) or (wr + transfer_size) != ring_buff_size elif rd > wr: # to make sure that the read pointer does not have the same value # as the write pointer (which would mean that ring buffer is # empty), only transfer data if difference between both pointer is # larger than the transfer size do_transfer = (rd - wr) > transfer_size if not do_transfer: # no data transfer shall take place now, do nothing continue # read trace file data data = trace.read(trace_size - trace_size_outstanding, transfer_size) # write data to the ring buffer ring_buff.write(ring_buff_addr + wr, data, transfer_size) # update the write pointer if (wr + transfer_size) == ring_buff_size: # end of memory reached, wrap around wr = 0x0 else: assert (wr + transfer_size) < ring_buff_size wr += transfer_size # write the write pointer to the DUT yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_ADDR_WR, wr) # decrement number of bytes that still remain to be written to memory trace_size_outstanding -= transfer_size # wait a little bit yield wait_n_cycles(dut.clk, 100)
def nt_gen_replay_top_test(dut): """Test bench main function.""" # start the clock cocotb.fork(clk_gen(dut.clk, CLK_FREQ_MHZ)) # no software reset dut.rst_sw <= 0 # reset dut yield rstn(dut.clk, dut.rstn) # open trace file trace = File("files/random.file") # get trace file size trace_size = trace.size() # trace file must be a multiple of the AXI data width if trace.size() % (AXI_MEM_BIT_WIDTH / 8) != 0: raise cocotb.result.TestFailure("invalid trace size") # calculate ring buffer sizes ring_buff_sizes = [] for ring_buff_size in RING_BUFF_SIZES: # size of ring buffer is determined by multiplying the size factor by # the size of the trace ring_buff_size = int(ring_buff_size * trace_size) # make sure that the ring buffer size is multiple of AXI data width if ring_buff_size % (AXI_MEM_BIT_WIDTH / 8) != 0: ring_buff_size += AXI_MEM_BIT_WIDTH/8 - \ ring_buff_size % (AXI_MEM_BIT_WIDTH/8) ring_buff_sizes.append(ring_buff_size) # create a ring buffer memory (initially of size 0) and connect it to the # DUT ring_buff = Mem(0) ring_buff.connect(dut, "ddr3") # create axi lite writer, connect and reset axi_lite_writer = AXI_Lite_Writer() axi_lite_writer.connect(dut, dut.clk, AXI_LITE_BIT_WIDTH, "ctrl") yield axi_lite_writer.rst() # create axi lite reader, connect and reset axi_lite_reader = AXI_Lite_Reader() axi_lite_reader.connect(dut, dut.clk, AXI_LITE_BIT_WIDTH, "ctrl") yield axi_lite_reader.rst() # create axi stream reader, connect and reset axis_reader = AXIS_Reader() axis_reader.connect(dut, dut.clk, AXIS_BIT_WIDTH) yield axis_reader.rst() # start the ring buffer memory main routine cocotb.fork(ring_buff.main()) # toggle m_axis_tready cocotb.fork(toggle_signal(dut.clk, dut.m_axis_tready)) # iterate over all ring buffer sizes for i, ring_buff_size in enumerate(ring_buff_sizes): # set ring buffer size ring_buff.set_size(ring_buff_size) # iterate over all addresses where ring buffer shall be located in # memory for j, ring_buff_addr in enumerate(RING_BUFF_ADDRS): # print status print("Test %d/%d" % (i * len(RING_BUFF_ADDRS) + j + 1, len(RING_BUFF_ADDRS) * len(RING_BUFF_SIZES))) print("Ring Buff Addr: 0x%x, Size: %d" % (ring_buff_addr, ring_buff_size)) # we have a total of 8 GByte of memory. Make sure the ring buffer # fits at the desired address if ring_buff_addr + ring_buff_size > 0x1FFFFFFFF: raise cocotb.result.TestFailure("ring buffer is too large") # to reduce the simulation memory footprint, provide the memory # module the first memory address that we acutally care about ring_buff.set_offset(ring_buff_addr) # configure ring buffer memory location yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_MEM_ADDR_HI, ring_buff_addr >> 32) yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_MEM_ADDR_LO, ring_buff_addr & 0xFFFFFFFF) # configure ring buffer address range yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_MEM_RANGE, ring_buff_size - 1) # configure trace size yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_TRACE_SIZE_HI, trace_size >> 32) yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_TRACE_SIZE_LO, trace_size & 0xFFFFFFFF) # reset write address pointer yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_ADDR_WR, 0x0) # make sure module initially is inactive status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) if status & 0x3 != 0: raise cocotb.reset.TestFailure("module is active") # start the module yield axi_lite_writer.write(CPUREG_OFFSET_CTRL_START, 0x1) # wait a few cycles yield wait_n_cycles(dut.clk, 10) # start writing the ring buffer cocotb.fork( ring_buff_write(dut, ring_buff, trace, ring_buff_addr, axi_lite_reader, axi_lite_writer)) # start coroutine that checks dut output coroutine_chk_out = cocotb.fork( check_output(dut, trace, axis_reader)) # wait a few cycles and make sure module is active yield wait_n_cycles(dut.clk, 10) status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) if status & 0x1 == 0x0: raise cocotb.result.TestFailure("mem read not active") if status & 0x2 == 0x0: raise cocotb.result.TestFailure("packet assembly not active") # wait for output check to complete yield coroutine_chk_out.join() # wait a few cycles yield wait_n_cycles(dut.clk, 10) # make sure module is now inactive status = yield axi_lite_reader.read(CPUREG_OFFSET_STATUS) if status & 0x3 != 0x0: raise cocotb.result.TestFailure("module does not become " + "inactive") # clear the ring buffer contents ring_buff.clear() # close the trace file trace.close()
def ring_buff_read(dut, ring_buff, f): """Read data from the ring buffer and check it for correctness. The coroutines monitors the ring buffer write pointer and reads data from the buffer if sufficient data is available. It ensures that the read data matches the data that has originally been written from the input file to the input FIFO. """ # get ring buffer size ring_buff_size = ring_buff.size() # get file size f_size = f.size() # ring buffer must be larger than 16384 bytes if ring_buff_size <= 16384: raise cocotb.result.TestFailure("ring buffer size too small") # ring buffer size must be a multiple of 16384 bytes if ring_buff_size % 16384 != 0: raise cocotb.result.TestFailure("ring buffer size invalid") # transfer size must be smaller than ring buffer if RD_TRANSFER_SIZE_MAX >= ring_buff_size: raise cocotb.result.TestFailure("transfer size too large") # memory address at which ring buffer is located ring_buff_addr = (int(dut.mem_addr_hi_i) << 32) | int(dut.mem_addr_lo_i) # initialize number of bytes that still need to be read from memory size_outstanding = f_size # make sure module is active if int(dut.active_o) == 0: raise cocotb.result.TestFailure("DuT became inactive") while True: # number of outstanding bytes that still need to be read must never be # negative assert size_outstanding >= 0 # abort if there is no more data to be read if size_outstanding == 0: break yield RisingEdge(dut.clk) # get read and write pointers rd = int(dut.addr_rd_i) wr = int(dut.addr_wr_o) # get memory size from current read pointer position until the end of # the ring buffer memory location ring_buff_size_end = ring_buff_size - rd # calculate the desired memory transfer size transfer_size = min(ring_buff_size_end, min(size_outstanding, RD_TRANSFER_SIZE_MAX)) # calculated memory transfer size must always be positive assert transfer_size > 0 if rd == wr: # ring buffer is empty -> nothing to transfer do_transfer = False elif rd < wr: # we can read if the difference between both pointers is at least # the desired transfer size do_transfer = (wr - rd) >= transfer_size elif wr < rd: # we can read until the end of the ring buffer do_transfer = True if not do_transfer: # no data transfer shall take place now, do nothing continue # read data from the ring buffer data_ring_buff = ring_buff.read(ring_buff_addr + rd, transfer_size) # read data from file data_file = f.read(f_size - size_outstanding, transfer_size) # make sure that data read from ring buffer matches the data we are # expecting if data_ring_buff != data_file: raise cocotb.result.TestFailure("ring buffer data does not " + "match expected data") # update read pointer if (rd + transfer_size) == ring_buff_size: # end of memory reached, wrap around dut.addr_rd_i <= 0 else: assert (rd + transfer_size) < ring_buff_size dut.addr_rd_i <= rd + transfer_size # decrement number of bytes that still remain to be written to memory size_outstanding -= transfer_size # wait a little bit yield wait_n_cycles(dut.clk, 100) # make sure module is now inactive if int(dut.active_o) != 0: raise cocotb.result.TestFailure("DuT does not become inactive")