From 2094717ad333eee8106313ea4eaae36ad47114da Mon Sep 17 00:00:00 2001 From: otroubi Date: Mon, 10 Aug 2026 17:20:15 +0300 Subject: [PATCH] test and build the project to verify the functionality of the bundle accum-dma --- .../tests/dma_accum_connection/Makefile | 61 ++ .../test_controller_dma_accum.py | 568 ++++++++++++++++++ 2 files changed, 629 insertions(+) create mode 100644 designs/controller_dma_connective/tests/dma_accum_connection/Makefile create mode 100644 designs/controller_dma_connective/tests/dma_accum_connection/test_controller_dma_accum.py diff --git a/designs/controller_dma_connective/tests/dma_accum_connection/Makefile b/designs/controller_dma_connective/tests/dma_accum_connection/Makefile new file mode 100644 index 0000000..b523c58 --- /dev/null +++ b/designs/controller_dma_connective/tests/dma_accum_connection/Makefile @@ -0,0 +1,61 @@ +TOPLEVEL_LANG = verilog +SIM ?= verilator + +PWD := $(shell pwd) + +WRAP_DIR = $(PWD)/../src +RTL_DIR = $(PWD)/../../controller_new/src +RTL_ACCUM_DIR = $(PWD)/../src/accum/src + +LIBS_DIR = $(PWD)/../../../external/rtl_libs +XPM_DIR = /mnt/c/Xilinx/Vivado/2021.2/data/ip/xpm + +VERILOG_SOURCES += $(LIBS_DIR)/axi/rtl/axi_pkg.sv +VERILOG_SOURCES += $(RTL_DIR)/dma_reg_pkg.sv +VERILOG_SOURCES += $(LIBS_DIR)/axi/rtl/axi_if.sv +VERILOG_SOURCES += $(LIBS_DIR)/axi/axi_reg/axi4l_reg_map.sv +VERILOG_SOURCES += $(LIBS_DIR)/external/verilog-axi/rtl/axi_dma_rd.v +VERILOG_SOURCES += $(LIBS_DIR)/external/verilog-axi/rtl/axi_dma_wr.v +VERILOG_SOURCES += $(LIBS_DIR)/external/verilog-axi/rtl/axi_dma.v +VERILOG_SOURCES += $(LIBS_DIR)/external/verilog-axi/rtl/axi_ram.v +VERILOG_SOURCES += $(RTL_DIR)/axi_ram_wrapper.sv +VERILOG_SOURCES += $(RTL_DIR)/controller.sv +VERILOG_SOURCES += $(RTL_DIR)/dma_controller.sv +VERILOG_SOURCES += $(RTL_DIR)/shaper_axis_desc.sv +VERILOG_SOURCES += $(RTL_DIR)/shaper_axis_status.sv +VERILOG_SOURCES += $(RTL_DIR)/controller_wrapper_axil.sv +VERILOG_SOURCES += $(RTL_DIR)/axi4l_reg_map_controller_pkg.sv +VERILOG_SOURCES += $(RTL_DIR)/axis_defaults_helper.sv +VERILOG_SOURCES += $(RTL_DIR)/axi4l_reg_map_controller.sv +VERILOG_SOURCES += $(WRAP_DIR)/wrapper_controller_dma_accum.sv +VERILOG_SOURCES += $(WRAP_DIR)/axi_dma_wrapper_if.sv + +VERILOG_SOURCES += $(RTL_ACCUM_DIR)/adder.sv +VERILOG_SOURCES += $(RTL_ACCUM_DIR)/out_axis_fifo.sv +VERILOG_SOURCES += $(RTL_ACCUM_DIR)/accum.sv +VERILOG_SOURCES += $(RTL_ACCUM_DIR)/accum_top.sv + +VERILOG_SOURCES += $(XPM_DIR)/xpm_fifo/hdl/xpm_fifo.sv +VERILOG_SOURCES += $(XPM_DIR)/xpm_memory/hdl/xpm_memory.sv +VERILOG_SOURCES += $(XPM_DIR)/xpm_cdc/hdl/xpm_cdc.sv + +VERILOG_SOURCES += $(PWD)/tb_controller_dma_accum_wrapper_axil.sv + +TOPLEVEL = tb_controller_dma_accum_wrapper_axil +MODULE = test_controller_dma_accum + + +ifeq ($(SIM),verilator) + EXTRA_ARGS += --trace --trace-structs + EXTRA_ARGS += -I$(LIBS_DIR)/axi/rtl/ + COMPILE_ARGS += -Wno-fatal + COMPILE_ARGS += -I$(LIBS_DIR)/axi/rtl/ + EXTRA_ARGS += --trace + EXTRA_ARGS += --trace-structs + EXTRA_ARGS += --public-flat-rw + EXTRA_ARGS += -Wno-fatal + EXTRA_ARGS += --timing +endif + + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/designs/controller_dma_connective/tests/dma_accum_connection/test_controller_dma_accum.py b/designs/controller_dma_connective/tests/dma_accum_connection/test_controller_dma_accum.py new file mode 100644 index 0000000..2d8ff31 --- /dev/null +++ b/designs/controller_dma_connective/tests/dma_accum_connection/test_controller_dma_accum.py @@ -0,0 +1,568 @@ +import cocotb +import random +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge +from cocotbext.axi import AxiLiteBus, AxiLiteMaster +from cocotbext.axi import AxiBus, AxiRam, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamFrame + + +# Register indexes from axi4l_reg_map_controller_pkg.sv +REG_CONTROL = 0 +REG_STATUS = 1 +REG_DAC_WIDTH = 2 +REG_DAC_PERIOD = 3 +REG_DAC_PULSE_NUM = 4 +REG_DAC_PULSE_HEIGHT = 5 +REG_ADC_PERIOD = 6 +REG_WINDOW_SIZE = 7 +REG_ERROR = 8 +REG_DESC_READ_ADDR = 9 +REG_DESC_READ_LEN = 10 +REG_DESC_READ_CONFIG = 11 +REG_READ_STATUS = 12 +REG_DESC_WRITE_ADDR = 13 +REG_DESC_WRITE_LEN_AND_TAG = 14 +REG_STATUS_WRITE_LEN = 15 +REG_STATUS_WRITE_CONFIG = 16 + + +# REG_CONTROL pulse bits +CTRL_START = 1 << 0 +CTRL_RST_SOFT = 1 << 1 +CTRL_CFG_BUS_VALID = 1 << 2 +CTRL_SEND_DESC_READ = 1 << 3 +CTRL_SEND_DESC_WRITE = 1 << 4 +CTRL_TAKE_STATUS_READ = 1 << 5 +CTRL_TAKE_STATUS_WRITE = 1 << 6 + +# REG_STATUS bits +STATUS_BUSY = 1 << 0 +STATUS_PROCESSING_DONE = 1 << 1 + +STATUS_DESC_READ_BUSY = 1 << 2 +STATUS_DESC_WRITE_BUSY = 1 << 3 +STATUS_STATUS_READ_BUSY = 1 << 4 +STATUS_STATUS_WRITE_BUSY = 1 << 5 + +STATUS_DESC_READ_HS = 1 << 6 +STATUS_DESC_WRITE_HS = 1 << 7 +STATUS_STATUS_READ_HS = 1 << 8 +STATUS_STATUS_WRITE_HS = 1 << 9 + +# PARAMETERS for accumulator reference model + +DAC_DATA_WIDTH = 14 +ADC_DATA_WIDTH = 12 +PACK_FACTOR = 1 +PROCESS_MODE = 0 +ZERO_LEVEL = 8192 +ACCUM_WIDTH = 32 +N_MAX = 4096 +PACKET_SIZE = 1024 +RD_FIFO_WIDTH = 32 + + +def reg_addr(reg_index: int) -> int: + # AXI-Lite uses byte addresses, 32-bit registers are spaced by 4 bytes. + return reg_index * 4 + + +def u32(value: int) -> bytes: + return int(value & 0xFFFFFFFF).to_bytes(4, "little") + + +class TB: + def __init__(self, dut): + self.dut = dut + + cocotb.start_soon(Clock(dut.ctrl_clk, 10, units="ns").start()) + + cocotb.start_soon(Clock(dut.adc_clk_in, 15.3846, units="ns").start()) + + cocotb.start_soon(Clock(dut.dac_clk_in, 8.333, units="ns").start()) + + self.axil = AxiLiteMaster( + AxiLiteBus.from_prefix(dut, "s_axil"), + dut.ctrl_clk, + dut.rst + ) + + + self.axis_source = AxiStreamSource( + AxiStreamBus.from_prefix(dut, "s_axis_write_data"), + dut.ctrl_clk, + dut.rst + ) + + self.axis_sink = AxiStreamSink( + AxiStreamBus.from_prefix(dut, "m_axis_read_data"), + dut.ctrl_clk, + dut.rst + ) + + async def reset(self): + self.dut.rst.value = 1 + for _ in range(5): + await RisingEdge(self.dut.ctrl_clk) + + self.dut.rst.value = 0 + for _ in range(5): + await RisingEdge(self.dut.ctrl_clk) + + async def write_reg(self, reg_index: int, value: int): + await self.axil.write(reg_addr(reg_index), u32(value)) + + async def read_reg(self, reg_index: int) -> int: + resp = await self.axil.read(reg_addr(reg_index), 4) + return int.from_bytes(bytes(resp.data), "little") + + async def pulse_control(self, mask): + await self.write_reg( REG_CONTROL, mask ) + + # Reflectometer Driver + + async def configure_reflectometer ( + self, + pulse_width, + pulse_period, + pulse_num, + pulse_height, + adc_period, + window_size + ): + await self.write_reg(REG_DAC_WIDTH, pulse_width) + await self.write_reg(REG_DAC_PERIOD, pulse_period) + await self.write_reg(REG_DAC_PULSE_NUM, pulse_num) + await self.write_reg(REG_DAC_PULSE_HEIGHT, pulse_height) + await self.write_reg(REG_ADC_PERIOD, adc_period) + await self.write_reg(REG_WINDOW_SIZE, window_size) + + await self.pulse_control(CTRL_CFG_BUS_VALID) + + async def send_start(self): + await self.pulse_control(CTRL_START) + + async def soft_reset(self): + await self.pulse_control(CTRL_RST_SOFT) + + async def get_status(self): + status = await self.read_reg(REG_STATUS) + return { + "busy": bool(status & STATUS_BUSY), + "processing_done": bool(status & STATUS_PROCESSING_DONE), + "desc_read_busy": bool(status & STATUS_DESC_READ_BUSY), + "desc_write_busy": bool(status & STATUS_DESC_WRITE_BUSY), + "status_read_busy": bool(status & STATUS_STATUS_READ_BUSY), + "status_write_busy": bool(status & STATUS_STATUS_WRITE_BUSY), + "desc_read_hs": bool(status & STATUS_DESC_READ_HS), + "desc_write_hs": bool(status & STATUS_DESC_WRITE_HS), + "status_read_hs": bool(status & STATUS_STATUS_READ_HS), + "status_write_hs": bool(status & STATUS_STATUS_WRITE_HS) + } + + async def wait_status(self, field, value=True): + + while True: + + status = await self.get_status() + + if status[field] == value: + return + + await RisingEdge(self.dut.ctrl_clk) + + async def wait_processing_done(self): + await self.wait_status("processing_done") + + + async def wait_finish(self): + await self.wait_status("busy", False) + + # DMA Driver + + async def send_desc_write(self, addr, length_tag): + + await self.write_reg(REG_DESC_WRITE_ADDR, addr) + await self.write_reg(REG_DESC_WRITE_LEN_AND_TAG, length_tag) + await self.pulse_control(CTRL_SEND_DESC_WRITE) + + async def send_desc_read(self, addr, length, config): + + await self.write_reg(REG_DESC_READ_ADDR, addr) + await self.write_reg(REG_DESC_READ_LEN, length) + await self.write_reg(REG_DESC_READ_CONFIG, config) + await self.pulse_control( CTRL_SEND_DESC_READ) + + async def take_status_write(self): + await self.pulse_control(CTRL_TAKE_STATUS_WRITE) + + return ( + await self.read_reg(REG_STATUS_WRITE_LEN), + await self.read_reg(REG_STATUS_WRITE_CONFIG) ) + + async def take_read_status(self): + + await self.pulse_control( CTRL_TAKE_STATUS_READ) + return await self.read_reg(REG_READ_STATUS) + + async def wait_dma_write_done(self): + await self.wait_status("desc_write_busy", True) + await self.wait_status("desc_write_busy", False) + + async def wait_dma_read_done(self): + await self.wait_status("desc_read_busy", True) + await self.wait_status("desc_read_busy", False) + + async def wait_status_read_handshake(self): + await self.wait_status("status_read_hs") + + async def wait_status_write_handshake(self): + await self.wait_status("status_write_hs") + + # AxiStream Driver + + async def send_axis_data(self, data: bytes): + + await self.axis_source.send(AxiStreamFrame(data) ) + + async def receive_axis_data(self): + + frame = await self.axis_sink.recv() + return bytes(frame) + + # Accum transaction generator + + def generate_samples( + self, + seq_num: int, + smp_num: int, + data_width: int, + seed: int | None = None): + if seq_num <= 0: + raise ValueError(f"seq_num must be > 0, got {seq_num}") + + if smp_num <= 0: + raise ValueError(f"smp_num must be > 0, got {smp_num}") + + rng = random.Random(seed) + + max_value = (1 << data_width) - 1 + + samples = [] + + for _ in range(seq_num): + seq_samples = [] + + for _ in range(smp_num): + seq_samples.append( rng.randint(0, max_value)) + + samples.append(seq_samples) + + return samples + + def calculate_expected( + self, + samples, + window_size: int, + accum_width: int): + if window_size <= 0: + raise ValueError( f"window_size must be > 0, got {window_size}") + + if not samples: + raise ValueError("samples must not be empty") + + seq_num = len(samples) + smp_num = len(samples[0]) + + if smp_num == 0: + raise ValueError("samples must not contain empty sequences") + + for seq_idx, seq_samples in enumerate(samples): + if len(seq_samples) != smp_num: + raise ValueError(f"Sequence {seq_idx} has {len(seq_samples)} samples, " f"expected {smp_num}" ) + + if smp_num % window_size != 0: + raise ValueError(f"smp_num ({smp_num}) must be divisible " f"by window_size ({window_size})") + + exp_word_count = smp_num // window_size + accum_mask = (1 << accum_width) - 1 + + expected = [] + + for word_idx in range(exp_word_count): + local_sum = 0 + + for seq_idx in range(seq_num): + for k in range(window_size): + sample_idx = word_idx * window_size + k + local_sum += samples[seq_idx][sample_idx] + + expected.append(local_sum & accum_mask) + + return expected + + async def send_samples(self, samples): + self.dut.sampler_m_axis_tvalid.value = 0 + self.dut.sampler_m_axis_tdata.value = 0 + + for seq_samples in samples: + + for sample in seq_samples: + + self.dut.sampler_m_axis_tdata.value = sample + self.dut.sampler_m_axis_tvalid.value = 1 + + await RisingEdge(self.dut.adc_clk_in) + + self.dut.sampler_m_axis_tdata.value = 0 + self.dut.sampler_m_axis_tvalid.value = 0 + + await RisingEdge(self.dut.adc_clk_in) + await RisingEdge(self.dut.adc_clk_in) + + def bytes_to_words(self, data: bytes, word_width: int = 32): + word_bytes = word_width // 8 + + if len(data) % word_bytes != 0: + raise ValueError(f"Data length {len(data)} is not divisible " f"by word size {word_bytes}" ) + + words = [] + + for i in range(0, len(data), word_bytes): + word = int.from_bytes(data[i:i + word_bytes], byteorder="little" ) + words.append(word) + + return words + + def check_results(self, expected, received): + + assert len(received) == len(expected), ( + f"Number of words mismatch: " + f"expected={len(expected)}, " + f"received={len(received)}" ) + + for i, (exp, rec) in enumerate(zip(expected, received)): + assert rec == exp, ( + f"Payload mismatch at index {i}: " + f"expected=0x{exp:08X}, " + f"received=0x{rec:08X}" ) + + print( f"Payload check passed: " f"{len(expected)} words") + + + + +@cocotb.test() +async def simple_axil_write_read(dut): + tb = TB(dut) + await tb.reset() + + await tb.write_reg(REG_DAC_WIDTH, 0x0000_0123) + + value = await tb.read_reg(REG_DAC_WIDTH) + assert value == 0x0000_0123 + + +@cocotb.test() +async def simple_controller_config_write(dut): + tb = TB(dut) + await tb.reset() + + await tb.write_reg(REG_DAC_WIDTH, 0x10) + await tb.write_reg(REG_DAC_PERIOD, 0x40) + await tb.write_reg(REG_DAC_PULSE_NUM, 3) + await tb.write_reg(REG_DAC_PULSE_HEIGHT, 0x7FF) + await tb.write_reg(REG_ADC_PERIOD, 0x80) + await tb.write_reg(REG_WINDOW_SIZE, 16) + + assert await tb.read_reg(REG_DAC_WIDTH) == 0x10 + assert await tb.read_reg(REG_WINDOW_SIZE) == 16 + + # write data: set cfg_bus_valid signal + await tb.write_reg(REG_CONTROL, 1 << CTRL_CFG_BUS_VALID) + + # wait + for _ in range(30): + await RisingEdge(dut.ctrl_clk) + + # check config + assert int(dut.dac_pulse_width) == 0x10 + assert int(dut.dac_pulse_period) == 0x40 + assert int(dut.dac_pulse_num) == 3 + assert int(dut.dac_pulse_height) == 0x7FF + assert int(dut.adc_pulse_period) == 0x80 + assert int(dut.adc_window_size) == 16 + + await RisingEdge(dut.ctrl_clk) + + +@cocotb.test() +async def simple_controller_start_check(dut): + tb = TB(dut) + await tb.reset() + await tb.write_reg(REG_CONTROL, 1 << CTRL_START) + + await RisingEdge(dut.dac_start) + +@cocotb.test() +async def simple_dma_mem_to_mem(dut): + tb = TB(dut) + await tb.reset() + + SRC_ADDR = 0x1000 + LENGTH = 8 + + tx_data = bytes([ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 ]) + + ## write part + + await tb.write_reg(REG_DESC_WRITE_ADDR, SRC_ADDR) + await tb.write_reg(REG_DESC_WRITE_LEN_AND_TAG, LENGTH) + + await tb.write_reg(REG_CONTROL, 1 << CTRL_SEND_DESC_WRITE) + + await tb.axis_source.send(AxiStreamFrame(tx_data)) + + for _ in range(20): + await RisingEdge(dut.ctrl_clk) + + await tb.write_reg(REG_CONTROL, 1 << CTRL_TAKE_STATUS_WRITE) + + for _ in range(10): + await RisingEdge(dut.ctrl_clk) + + + assert await tb.read_reg(REG_STATUS_WRITE_CONFIG) == 0 + assert await tb.read_reg(REG_STATUS_WRITE_LEN) == 0x8 + + ## read part + + await tb.write_reg(REG_DESC_READ_ADDR, SRC_ADDR) + await tb.write_reg(REG_DESC_READ_LEN, LENGTH) + await tb.write_reg(REG_DESC_READ_CONFIG, 0x0000_0001) + + await tb.write_reg(REG_CONTROL, 1 << CTRL_SEND_DESC_READ) + + rx_frame = await tb.axis_sink.recv() + + for _ in range(20): + await RisingEdge(dut.ctrl_clk) + + await tb.write_reg(REG_CONTROL, 1 << CTRL_TAKE_STATUS_READ) + + for _ in range(10): + await RisingEdge(dut.ctrl_clk) + + assert await tb.read_reg(REG_READ_STATUS) == 0x1 + + assert bytes(rx_frame) == tx_data + + +@cocotb.test() +async def dma_accum_connection(dut): + tb = TB(dut) + await tb.reset() + + SEQ_NUM = 3 + SMP_NUM = 0x40 # 64 samples per sequence + WINDOW_SIZE = 16 + + PULSE_WIDTH = 0x10 + PULSE_PERIOD = 0x40 + PULSE_HEIGHT = 0x7FF + ADC_PERIOD = 0x80 + + RANDOM_SEED = 12345 + + await tb.configure_reflectometer( + pulse_width=PULSE_WIDTH, + pulse_period=PULSE_PERIOD, + pulse_num=SEQ_NUM, + pulse_height=PULSE_HEIGHT, + adc_period=ADC_PERIOD, + window_size=WINDOW_SIZE ) + + samples = tb.generate_samples( + seq_num=SEQ_NUM, + smp_num=SMP_NUM, + data_width=ADC_DATA_WIDTH, + seed=RANDOM_SEED ) + + + expected = tb.calculate_expected( + samples=samples, + window_size=WINDOW_SIZE, + accum_width=ACCUM_WIDTH + ) + + print("") + print("========================================") + print("ACCUMULATOR RANDOM TEST") + print("========================================") + print(f"seq_num = {SEQ_NUM}") + print(f"smp_num = {SMP_NUM}") + print(f"window_size = {WINDOW_SIZE}") + print(f"data_width = {ADC_DATA_WIDTH}") + print(f"accum_width = {ACCUM_WIDTH}") + print(f"expected words = {len(expected)}") + + RESULT_WORDS = len(expected) + RESULT_BYTES = RESULT_WORDS * (ACCUM_WIDTH // 8) + + print(f"result bytes = {RESULT_BYTES}") + assert RESULT_WORDS == 4 + assert RESULT_BYTES == 16 + + + await tb.send_start() + + await tb.send_samples(samples) + + await tb.wait_processing_done() + + RESULT_ADDR = 0x1000 + + await tb.send_desc_write( addr=RESULT_ADDR, length_tag=RESULT_BYTES ) + + await tb.wait_dma_write_done() + + status_write_len, status_write_config = \ + await tb.take_status_write() + + assert status_write_config == 0 + assert status_write_len == RESULT_BYTES + + await tb.send_desc_read(addr=RESULT_ADDR, length=RESULT_BYTES, config=0x0000_0001 ) + + received_data = await tb.receive_axis_data() + + await tb.wait_dma_read_done() + + read_status = await tb.take_read_status() + + assert read_status == 0x1 + + received = tb.bytes_to_words(received_data, RD_FIFO_WIDTH) + + print("") + print("Expected:") + for i, value in enumerate(expected): + print(f" [{i}] = 0x{value:08X}") + + print("") + print("Received:") + for i, value in enumerate(received): + print(f" [{i}] = 0x{value:08X}") + + tb.check_results( + expected=expected, + received=received + ) + + print("") + print("========================================") + print("ACCUMULATOR RANDOM TEST PASSED") + print("========================================") + + +