diff --git a/rtl/controller_new/tests/Makefile b/rtl/controller_new/tests/Makefile new file mode 100644 index 0000000..e3af5e4 --- /dev/null +++ b/rtl/controller_new/tests/Makefile @@ -0,0 +1,40 @@ +TOPLEVEL_LANG = verilog +SIM ?= verilator + +PWD := $(shell pwd) + +RTL_DIR = $(PWD)/../src +LIBS_DIR = $(PWD)/../../../external/rtl_libs + +VERILOG_SOURCES += $(LIBS_DIR)/axi/rtl/axi_pkg.sv +VERILOG_SOURCES += $(LIBS_DIR)/axi/rtl/axi_if.sv +VERILOG_SOURCES += $(LIBS_DIR)/axi/rtl/axi4l_flat_to_if.sv +VERILOG_SOURCES += $(LIBS_DIR)/axi/rtl/axi4l_if_to_flat.sv +VERILOG_SOURCES += $(LIBS_DIR)/axi/axi_reg/axi4l_reg_map.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)/axi4l_reg_map_controller.sv +VERILOG_SOURCES += $(PWD)/tb_controller_wrapper_axil.sv + +TOPLEVEL = tb_controller_wrapper_axil +MODULE = test_controller + + +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/rtl/controller_new/tests/controller_tb.sv b/rtl/controller_new/tests/controller_tb.sv new file mode 100644 index 0000000..14119d9 --- /dev/null +++ b/rtl/controller_new/tests/controller_tb.sv @@ -0,0 +1,737 @@ +`timescale 1ns/1ps + +module tb_control; + + localparam int unsigned DAC_DATA_WIDTH = 12; + + //------------------------------------------------------------ + // Clocks / reset + //------------------------------------------------------------ + logic ctrl_clk; + logic dac_clk_in; + logic adc_clk_in; + logic rst_n; + + initial begin + ctrl_clk = 1'b0; + forever #(1 * 4.000) ctrl_clk = ~ctrl_clk; // 125 MHz + end + + initial begin + dac_clk_in = 1'b0; + forever #(1 * 3.846153846) dac_clk_in = ~dac_clk_in; // ~130 MHz + end + + initial begin + adc_clk_in = 1'b0; + forever #(1 * 7.692307692) adc_clk_in = ~adc_clk_in; // ~65 MHz + end + + //------------------------------------------------------------ + // DUT inputs + //------------------------------------------------------------ + + logic rst_soft; + + logic finish; + + logic [159:0] cfg_bus_input; + logic cfg_bus_valid; + + logic start; + + //------------------------------------------------------------ + // DUT outputs + //------------------------------------------------------------ + + logic busy; + + logic [31:0] dac_pulse_width; + logic [31:0] dac_pulse_period; + logic [DAC_DATA_WIDTH-1:0] dac_pulse_height; + logic [15:0] dac_pulse_num; + + logic [31:0] adc_pulse_period; + logic [15:0] adc_pulse_num; + logic [31:0] adc_window_size; + + logic dac_start; + logic adc_start; + logic dac_rst; + logic adc_rst; + + //------------------------------------------------------------ + // DUT + //------------------------------------------------------------ + + control #( + .DAC_DATA_WIDTH(DAC_DATA_WIDTH) + ) dut ( + .ctrl_clk (ctrl_clk), + .dac_clk_in (dac_clk_in), + .adc_clk_in (adc_clk_in), + + .rst_n (rst_n), + .rst_soft (rst_soft), + + .finish (finish), + + .cfg_bus_input (cfg_bus_input), + .cfg_bus_valid (cfg_bus_valid), + + .start (start), + + .busy (busy), + + .dac_pulse_width (dac_pulse_width), + .dac_pulse_period (dac_pulse_period), + .dac_pulse_height (dac_pulse_height), + .dac_pulse_num (dac_pulse_num), + + .adc_pulse_period (adc_pulse_period), + .adc_pulse_num (adc_pulse_num), + .adc_window_size (adc_window_size), + + .dac_start (dac_start), + .adc_start (adc_start), + .dac_rst (dac_rst), + .adc_rst (adc_rst) + ); + + //------------------------------------------------------------ + // pulse counters and monitors for testing + //------------------------------------------------------------ + + int dac_rst_count; + int adc_rst_count; + int dac_start_count; + int adc_start_count; + + always_ff @(posedge dac_clk_in) begin + if (!rst_n) begin + dac_rst_count <= 0; + dac_start_count <= 0; + end + else begin + if (dac_rst) + dac_rst_count <= dac_rst_count + 1; + + if (dac_start) + dac_start_count <= dac_start_count + 1; + end + end + + always_ff @(posedge adc_clk_in) begin + if (!rst_n) begin + adc_rst_count <= 0; + adc_start_count <= 0; + end + else begin + if (adc_rst) + adc_rst_count <= adc_rst_count + 1; + + if (adc_start) + adc_start_count <= adc_start_count + 1; + end + end + + //------------------------------------------------------------ + // helpers + //------------------------------------------------------------ + + task automatic send_cmd(input logic [7:0] cmd); + begin + @(negedge ctrl_clk); + + case (cmd) + + 8'h0F: begin + rst_soft <= 1'b1; + + @(posedge ctrl_clk); + + rst_soft <= 1'b0; + end + + 8'hF0: begin + start <= 1'b1; + + @(posedge ctrl_clk); + + start <= 1'b0; + end + + default: + $fatal(1, "Unsupported command %h", cmd); + + endcase + end + endtask + + task automatic send_set_data( + input logic [31:0] pulse_width, + input logic [31:0] pulse_period, + input logic [15:0] pulse_num, + input logic [15:0] pulse_height_raw, + input logic [31:0] pulse_period_adc, + input logic [31:0] window_size +); + +logic [159:0] payload; + +begin + + payload = { + window_size, + pulse_period_adc, + pulse_height_raw, + pulse_num, + pulse_period, + pulse_width + }; + + @(negedge ctrl_clk); + + cfg_bus_input <= payload; + cfg_bus_valid <= 1'b1; + + @(posedge ctrl_clk); + + cfg_bus_valid <= 1'b0; + cfg_bus_input <= '0; + +end +endtask + + task automatic pulse_finish; + begin + @(posedge adc_clk_in); + finish <= 1'b1; + + @(posedge adc_clk_in); + finish <= 1'b0; + end + endtask + + //------------------------------------------------------------ + // waiters + //------------------------------------------------------------ + + task automatic wait_dac_rst_count(input int expected, input int max_cycles = 100); + int i; + begin + for (i = 0; i < max_cycles; i++) begin + @(posedge dac_clk_in); + + if (dac_rst_count >= expected) + return; + end + + $fatal(1, + "Timeout waiting for dac_rst_count >= %0d, current=%0d", + expected, + dac_rst_count + ); + end + endtask + + task automatic wait_adc_rst_count(input int expected, input int max_cycles = 100); + int i; + begin + for (i = 0; i < max_cycles; i++) begin + @(posedge adc_clk_in); + + if (adc_rst_count >= expected) + return; + end + + $fatal(1, + "Timeout waiting for adc_rst_count >= %0d, current=%0d", + expected, + adc_rst_count + ); + end + endtask + + task automatic wait_dac_start_count(input int expected, input int max_cycles = 100); + int i; + begin + for (i = 0; i < max_cycles; i++) begin + @(posedge dac_clk_in); + + if (dac_start_count >= expected) + return; + end + + $fatal(1, + "Timeout waiting for dac_start_count >= %0d, current=%0d", + expected, + dac_start_count + ); + end + endtask + + task automatic wait_adc_start_count(input int expected, input int max_cycles = 100); + int i; + begin + for (i = 0; i < max_cycles; i++) begin + @(posedge adc_clk_in); + + if (adc_start_count >= expected) + return; + end + + $fatal(1, + "Timeout waiting for adc_start_count >= %0d, current=%0d", + expected, + adc_start_count + ); + end + endtask + + task automatic wait_cfg_applied( + + input logic [31:0] exp_pulse_width, + input logic [31:0] exp_pulse_period, + input logic [15:0] exp_pulse_num, + input logic [15:0] exp_pulse_height_raw, + input logic [31:0] exp_pulse_period_adc, + input logic [31:0] exp_window_size, + input int max_cycles = 200 +); + +logic [DAC_DATA_WIDTH-1:0] exp_height; + +int i; + +begin + + exp_height = exp_pulse_height_raw[DAC_DATA_WIDTH-1:0]; + + for(i=0;i 12'hCDE + test_pulse_period_adc = 32'h50607080; + test_window_size = 32'hABCDEF55; + + repeat (10) @(posedge ctrl_clk); + rst_n = 1'b1; + + repeat (10) @(posedge ctrl_clk); + + //-------------------------------------------------------- + // TEST 1: soft_reset + //-------------------------------------------------------- + + $display("[%0t] TEST 1: soft_reset", $time); + + send_cmd(8'h0F); + + wait_dac_rst_count(1); + wait_adc_rst_count(1); + + if (dac_rst_count != 1) begin + $fatal(1, + "Expected exactly one dac_rst pulse after first soft_reset, got %0d", + dac_rst_count + ); + end + + if (adc_rst_count != 1) begin + $fatal(1, + "Expected exactly one adc_rst pulse after first soft_reset, got %0d", + adc_rst_count + ); + end + + $display("[%0t] TEST 1 passed", $time); + + //-------------------------------------------------------- + // TEST 2: set_data + //-------------------------------------------------------- + + $display("[%0t] TEST 2: set_data", $time); + + send_set_data( + + test_pulse_width, + test_pulse_period, + test_pulse_num, + test_pulse_height_raw, + test_pulse_period_adc, + test_window_size + +); + + wait_cfg_applied( + + test_pulse_width, + test_pulse_period, + test_pulse_num, + test_pulse_height_raw, + test_pulse_period_adc, + test_window_size + +); + + if (dac_pulse_width !== 32'h11223344) begin + $fatal(1, + "dac_pulse_width mismatch: got %h expected %h", + dac_pulse_width, + 32'h11223344 + ); + end + + if (dac_pulse_period !== 32'h55667788) begin + $fatal(1, + "dac_pulse_period mismatch: got %h expected %h", + dac_pulse_period, + 32'h55667788 + ); + end + + if (dac_pulse_num !== 16'hA1B2) begin + $fatal(1, + "dac_pulse_num mismatch: got %h expected %h", + dac_pulse_num, + 16'hA1B2 + ); + end + + if (dac_pulse_height !== 12'hCDE) begin + $fatal(1, + "dac_pulse_height mismatch: got %h expected %h", + dac_pulse_height, + 12'hCDE + ); + end + + if (adc_pulse_period !== 32'h50607080) begin + $fatal(1, + "adc_pulse_period mismatch: got %h expected %h", + adc_pulse_period, + 32'h50607080 + ); + end + + if (adc_pulse_num !== 16'hA1B2) begin + $fatal(1, + "adc_pulse_num mismatch: got %h expected %h", + adc_pulse_num, + 16'hA1B2 + ); + end + + if(adc_window_size != test_window_size) + + $fatal( + "adc_window_size mismatch. got=%h expected=%h", + adc_window_size, + test_window_size + ); + + $display("[%0t] TEST 2 passed", $time); + + repeat (20) @(posedge ctrl_clk); + + //-------------------------------------------------------- + // TEST 3: start + //-------------------------------------------------------- + + $display("[%0t] TEST 3: start", $time); + + send_cmd(8'hF0); + + wait_dac_start_count(1); + wait_adc_start_count(1); + + if (dac_start_count != 1) begin + $fatal(1, + "Expected exactly one dac_start pulse after first start, got %0d", + dac_start_count + ); + end + + if (adc_start_count != 1) begin + $fatal(1, + "Expected exactly one adc_start pulse after first start, got %0d", + adc_start_count + ); + end + + if (!busy) begin + $fatal(1, + "busy must be asserted after start" + ); + end + + $display("[%0t] TEST 3 start pulses passed", $time); + + //-------------------------------------------------------- + // release busy by finish pulse from ADC domain + //-------------------------------------------------------- + + $display("[%0t] Sending finish pulse", $time); + + pulse_finish(); + + repeat (20) @(posedge ctrl_clk); + + if (busy) begin + $fatal(1, + "busy was not cleared after finish pulse" + ); + end + + //-------------------------------------------------------- + // sanity check that commands are accepted again after finish + //-------------------------------------------------------- + + $display("[%0t] TEST 4: soft_reset after finish", $time); + + send_cmd(8'h0F); + + wait_dac_rst_count(2); + wait_adc_rst_count(2); + + if (dac_rst_count != 2) begin + $fatal(1, + "Expected exactly two dac_rst pulses total, got %0d", + dac_rst_count + ); + end + + if (adc_rst_count != 2) begin + $fatal(1, + "Expected exactly two adc_rst pulses total, got %0d", + adc_rst_count + ); + end + + $display("[%0t] TEST 4 passed", $time); + + //-------------------------------------------------------- + + $display("=============================================="); + $display("ALL BASIC TESTS PASSED"); + $display("dac_rst_count = %0d", dac_rst_count); + $display("adc_rst_count = %0d", adc_rst_count); + $display("dac_start_count = %0d", dac_start_count); + $display("adc_start_count = %0d", adc_start_count); + $display("=============================================="); + + #50; + + //-------------------------------------------------------- + // TEST 2.1: set_data + //-------------------------------------------------------- + + test_pulse_width = 32'h12121212; + test_pulse_period = 32'h34343434; + test_pulse_num = 16'h1A2B; + test_pulse_height_raw = 16'hC0ED; // for DAC_DATA_WIDTH=12 => 12'hCDE + test_pulse_period_adc = 32'h56565656; + test_window_size = 32'h01020304; + + $display("[%0t] TEST 2.1: set_data", $time); + + send_set_data( + test_pulse_width, + test_pulse_period, + test_pulse_num, + test_pulse_height_raw, + test_pulse_period_adc, + test_window_size + ); + + wait_cfg_applied( + test_pulse_width, + test_pulse_period, + test_pulse_num, + test_pulse_height_raw, + test_pulse_period_adc, + test_window_size + ); + + if (dac_pulse_width !== 32'h12121212) begin + $fatal(1, + "dac_pulse_width mismatch: got %h expected %h", + dac_pulse_width, + 32'h12121212 + ); + end + + if (dac_pulse_period !== 32'h34343434) begin + $fatal(1, + "dac_pulse_period mismatch: got %h expected %h", + dac_pulse_period, + 32'h34343434 + ); + end + + if (dac_pulse_num !== 16'h1A2B) begin + $fatal(1, + "dac_pulse_num mismatch: got %h expected %h", + dac_pulse_num, + 16'h1A2B + ); + end + + if (dac_pulse_height !== 12'hC0ED) begin + $fatal(1, + "dac_pulse_height mismatch: got %h expected %h", + dac_pulse_height, + 12'hC0ED + ); + end + + if (adc_pulse_period !== 32'h56565656) begin + $fatal(1, + "adc_pulse_period mismatch: got %h expected %h", + adc_pulse_period, + 32'h56565656 + ); + end + + $display("[%0t] TEST 2.1 passed", $time); + + repeat (20) @(posedge ctrl_clk); + + //-------------------------------------------------------- + // TEST 3.1: start + //-------------------------------------------------------- + + $display("[%0t] TEST 3.1: start", $time); + + send_cmd(8'hF0); + + wait_dac_start_count(2); + wait_adc_start_count(2); + + if (dac_start_count != 2) begin + $fatal(1, + "Expected exactly one dac_start pulse after first start, got %0d", + dac_start_count + ); + end + + if (adc_start_count != 2) begin + $fatal(1, + "Expected exactly one adc_start pulse after first start, got %0d", + adc_start_count + ); + end + + if (!busy) begin + $fatal(1, + "busy must be asserted after start" + ); + end + + $display("[%0t] TEST 3.1 start pulses passed", $time); + + //-------------------------------------------------------- + // release busy by finish pulse from ADC domain + //-------------------------------------------------------- + + $display("[%0t] Sending finish pulse", $time); + + pulse_finish(); + + repeat (20) @(posedge ctrl_clk); + + if (busy) begin + $fatal(1, + "busy was not cleared after finish pulse" + ); + end + + //-------------------------------------------------------- + // sanity check that commands are accepted again after finish + //-------------------------------------------------------- + + $display("[%0t] TEST 4.1: soft_reset after finish", $time); + + send_cmd(8'h0F); + + wait_dac_rst_count(2); + wait_adc_rst_count(2); + + if (dac_rst_count != 2) begin + $fatal(1, + "Expected exactly two dac_rst pulses total, got %0d", + dac_rst_count + ); + end + + if (adc_rst_count != 2) begin + $fatal(1, + "Expected exactly two adc_rst pulses total, got %0d", + adc_rst_count + ); + end + + $display("[%0t] TEST 4.1 passed", $time); + + //-------------------------------------------------------- + + $display("=============================================="); + $display("ALL BASIC TESTS PASSED"); + $display("dac_rst_count = %0d", dac_rst_count); + $display("adc_rst_count = %0d", adc_rst_count); + $display("dac_start_count = %0d", dac_start_count); + $display("adc_start_count = %0d", adc_start_count); + $display("=============================================="); + $finish; + + end + +endmodule \ No newline at end of file diff --git a/rtl/controller_new/tests/tb_control_behav.wcfg b/rtl/controller_new/tests/tb_control_behav.wcfg new file mode 100644 index 0000000..16048c6 --- /dev/null +++ b/rtl/controller_new/tests/tb_control_behav.wcfg @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + dac_clk_in + dac_clk_in + #FFA500 + true + + + adc_clk_in + adc_clk_in + + + rst_n + rst_n + #800080 + true + + + rst_soft + rst_soft + #008080 + true + + + start + start + #E0FFFF + true + + + adc_start_pulse + adc_start_pulse + + + dac_start_pulse + dac_start_pulse + + + busy + busy + #E0FFFF + true + + + finish + finish + #FAAFBE + true + + + finish_pulse + finish_pulse + + + cfg_bus_valid + cfg_bus_valid + #FF0080 + true + + + cfg_bus_input[159:0] + cfg_bus_input[159:0] + #FF0080 + true + + + dac_pulse_width[31:0] + dac_pulse_width[31:0] + #FFA500 + true + + + dac_pulse_period[31:0] + dac_pulse_period[31:0] + #FFA500 + true + + + dac_pulse_height[11:0] + dac_pulse_height[11:0] + #FFA500 + true + HEXRADIX + + + dac_pulse_num[15:0] + dac_pulse_num[15:0] + #FFA500 + true + + + adc_pulse_period[31:0] + adc_pulse_period[31:0] + #FFA500 + true + + + adc_pulse_num[15:0] + adc_pulse_num[15:0] + #FFA500 + true + + + adc_window_size[31:0] + adc_window_size[31:0] + + + dac_start + dac_start + #FFA500 + true + + + adc_start + adc_start + + + dac_rst + dac_rst + #FFA500 + true + + + adc_rst + adc_rst + + + tb signals + label + + dac_rst_count[31:0] + dac_rst_count[31:0] + #F0E68C + true + + + adc_rst_count[31:0] + adc_rst_count[31:0] + #F0E68C + true + + + dac_start_count[31:0] + dac_start_count[31:0] + #F0E68C + true + + + adc_start_count[31:0] + adc_start_count[31:0] + #F0E68C + true + + + + cfg_req_toggle_adc + cfg_req_toggle_adc + #FFFF00 + true + + + cfg_req_toggle_dac + cfg_req_toggle_dac + #FFFF00 + true + + + cfg_ack_toggle_adc + cfg_ack_toggle_adc + #FF00FF + true + + + cfg_ack_toggle_dac + cfg_ack_toggle_dac + #FF00FF + true + + + cfg_wait_adc_ack + cfg_wait_adc_ack + #0000FF + true + + + cfg_wait_dac_ack + cfg_wait_dac_ack + #0000FF + true + + + cfg_req_pulse_adc + cfg_req_pulse_adc + #808000 + true + + + cfg_req_pulse_dac + cfg_req_pulse_dac + #808000 + true + + + cfg_ack_pulse_adc + cfg_ack_pulse_adc + #DCDCDC + true + + + cfg_ack_pulse_dac + cfg_ack_pulse_dac + #DCDCDC + true + + diff --git a/rtl/controller_new/tests/tb_controller_wrapper_axil.sv b/rtl/controller_new/tests/tb_controller_wrapper_axil.sv new file mode 100644 index 0000000..dd39a50 --- /dev/null +++ b/rtl/controller_new/tests/tb_controller_wrapper_axil.sv @@ -0,0 +1,338 @@ +module tb_controller_wrapper_axil #( + parameter int unsigned ADDR_W = 16, + parameter int unsigned DATA_W = 32, + parameter int unsigned USER_W = 1, + + parameter int unsigned DAC_DATA_WIDTH = 12, + + parameter int unsigned AXI_ADDR_WIDTH = 16, + parameter int unsigned AXIS_ID_WIDTH = 8, + parameter int unsigned LEN_WIDTH = 20, + parameter int unsigned AXIS_USER_WIDTH = 1, + parameter int unsigned AXIS_DEST_WIDTH = 8, + parameter int unsigned TAG_WIDTH = 8, + + parameter int unsigned READ_DESC_WIDTH = AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH + AXIS_ID_WIDTH + AXIS_DEST_WIDTH + AXIS_USER_WIDTH, + parameter int unsigned WRITE_DESC_WIDTH = AXI_ADDR_WIDTH + LEN_WIDTH + TAG_WIDTH, + + parameter int unsigned READ_STATUS_WIDTH = TAG_WIDTH + 4, + parameter int unsigned WRITE_STATUS_WIDTH = LEN_WIDTH + TAG_WIDTH + AXIS_ID_WIDTH + AXIS_DEST_WIDTH + AXIS_USER_WIDTH + 4, + + // ceil(width / 8), чтобы keep/strb не стали слишком узкими для нестандартных ширин типа 12/49/61. + parameter int unsigned READ_DESC_KEEP_W = (READ_DESC_WIDTH + 7) / 8, + parameter int unsigned WRITE_DESC_KEEP_W = (WRITE_DESC_WIDTH + 7) / 8, + parameter int unsigned READ_STATUS_KEEP_W = (READ_STATUS_WIDTH + 7) / 8, + parameter int unsigned WRITE_STATUS_KEEP_W = (WRITE_STATUS_WIDTH + 7) / 8 +)( + input logic ctrl_clk, + input logic rst, + + input logic [ADDR_W-1:0] s_axil_awaddr, + input logic [2:0] s_axil_awprot, + input logic s_axil_awvalid, + output logic s_axil_awready, + + input logic [DATA_W-1:0] s_axil_wdata, + input logic [DATA_W/8-1:0] s_axil_wstrb, + input logic s_axil_wvalid, + output logic s_axil_wready, + + output logic [1:0] s_axil_bresp, + output logic s_axil_bvalid, + input logic s_axil_bready, + + input logic [ADDR_W-1:0] s_axil_araddr, + input logic [2:0] s_axil_arprot, + input logic s_axil_arvalid, + output logic s_axil_arready, + + output logic [DATA_W-1:0] s_axil_rdata, + output logic [1:0] s_axil_rresp, + output logic s_axil_rvalid, + input logic s_axil_rready, + + // --------------------------------------------------------------------------- + // Минимальный flat-view для DMA AXIS портов. + // Наружу оставлены только tdata/tvalid/tready в старом стиле. + // Все остальные AXIS поля внутри обёртки завязаны на безопасные значения. + // --------------------------------------------------------------------------- + + // DUT master -> testbench: descriptor commands. + output logic [READ_DESC_WIDTH-1:0] desc_read_cmd_out, + output logic valid_desc_read, + input logic ready_desc_read, + + output logic [WRITE_DESC_WIDTH-1:0] desc_write_cmd_out, + output logic valid_desc_write, + input logic ready_desc_write, + + // testbench -> DUT slave: status commands. + input logic [READ_STATUS_WIDTH-1:0] status_read_cmd_in, + input logic valid_status_read, + output logic ready_status_read, + + input logic [WRITE_STATUS_WIDTH-1:0] status_write_cmd_in, + input logic valid_status_write, + output logic ready_status_write +); + + logic rst_n; + assign rst_n = ~rst; + + // Для минимального теста держим все clock domain-ы на одном clock. + logic dac_clk_in; + logic adc_clk_in; + assign dac_clk_in = ctrl_clk; + assign adc_clk_in = ctrl_clk; + + logic finish; + assign finish = 1'b0; + + // --------------------------------------------------------------------------- + // AXI-Lite flat -> axi4l_if + // --------------------------------------------------------------------------- + + axi4l_if #( + .ADDR_W(ADDR_W), + .DATA_W(DATA_W), + .USER_W(USER_W) + ) axil_bus ( + .aclk(ctrl_clk), + .aresetn(rst_n) + ); + + axi4l_flat_to_if #( + .ADDR_W(ADDR_W), + .DATA_W(DATA_W), + .USER_W(USER_W) + ) u_axil_flat_to_if ( + .s_axil_awaddr (s_axil_awaddr), + .s_axil_awprot (s_axil_awprot), + .s_axil_awvalid(s_axil_awvalid), + .s_axil_awready(s_axil_awready), + + .s_axil_wdata (s_axil_wdata), + .s_axil_wstrb (s_axil_wstrb), + .s_axil_wvalid (s_axil_wvalid), + .s_axil_wready (s_axil_wready), + + .s_axil_bresp (s_axil_bresp), + .s_axil_bvalid (s_axil_bvalid), + .s_axil_bready (s_axil_bready), + + .s_axil_araddr (s_axil_araddr), + .s_axil_arprot (s_axil_arprot), + .s_axil_arvalid(s_axil_arvalid), + .s_axil_arready(s_axil_arready), + + .s_axil_rdata (s_axil_rdata), + .s_axil_rresp (s_axil_rresp), + .s_axil_rvalid (s_axil_rvalid), + .s_axil_rready (s_axil_rready), + + .m_axil(axil_bus) + ); + + // --------------------------------------------------------------------------- + // AXIS interfaces for the updated controller_wrapper_axil + // --------------------------------------------------------------------------- + + axis_if #( + .DATA_W(READ_STATUS_WIDTH), + .KEEP_W(READ_STATUS_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) axis_status_read ( + .aclk(ctrl_clk), + .aresetn(rst_n) + ); + + axis_if #( + .DATA_W(WRITE_STATUS_WIDTH), + .KEEP_W(WRITE_STATUS_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) axis_status_write ( + .aclk(ctrl_clk), + .aresetn(rst_n) + ); + + axis_if #( + .DATA_W(READ_DESC_WIDTH), + .KEEP_W(READ_DESC_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) axis_desc_read ( + .aclk(ctrl_clk), + .aresetn(rst_n) + ); + + axis_if #( + .DATA_W(WRITE_DESC_WIDTH), + .KEEP_W(WRITE_DESC_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) axis_desc_write ( + .aclk(ctrl_clk), + .aresetn(rst_n) + ); + + // testbench flat status -> DUT AXIS slave ports + axis_flat_to_if #( + .DATA_W(READ_STATUS_WIDTH), + .KEEP_W(READ_STATUS_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) u_status_read_flat_to_if ( + .s_axis_tdata (status_read_cmd_in), + .s_axis_tkeep ({READ_STATUS_KEEP_W{1'b1}}), + .s_axis_tstrb ({READ_STATUS_KEEP_W{1'b1}}), + .s_axis_tlast (1'b1), + .s_axis_tid ('0), + .s_axis_tdest ('0), + .s_axis_tuser ('0), + .s_axis_tvalid(valid_status_read), + .s_axis_tready(ready_status_read), + + .m_axis(axis_status_read) + ); + + axis_flat_to_if #( + .DATA_W(WRITE_STATUS_WIDTH), + .KEEP_W(WRITE_STATUS_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) u_status_write_flat_to_if ( + .s_axis_tdata (status_write_cmd_in), + .s_axis_tkeep ({WRITE_STATUS_KEEP_W{1'b1}}), + .s_axis_tstrb ({WRITE_STATUS_KEEP_W{1'b1}}), + .s_axis_tlast (1'b1), + .s_axis_tid ('0), + .s_axis_tdest ('0), + .s_axis_tuser ('0), + .s_axis_tvalid(valid_status_write), + .s_axis_tready(ready_status_write), + + .m_axis(axis_status_write) + ); + + // DUT AXIS master ports -> testbench flat descriptor outputs + logic [READ_DESC_KEEP_W-1:0] unused_desc_read_tkeep; + logic [READ_DESC_KEEP_W-1:0] unused_desc_read_tstrb; + logic unused_desc_read_tlast; + logic [AXIS_ID_WIDTH-1:0] unused_desc_read_tid; + logic [AXIS_DEST_WIDTH-1:0] unused_desc_read_tdest; + logic [AXIS_USER_WIDTH-1:0] unused_desc_read_tuser; + + axis_if_to_flat #( + .DATA_W(READ_DESC_WIDTH), + .KEEP_W(READ_DESC_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) u_desc_read_if_to_flat ( + .s_axis(axis_desc_read), + + .m_axis_tdata (desc_read_cmd_out), + .m_axis_tkeep (unused_desc_read_tkeep), + .m_axis_tstrb (unused_desc_read_tstrb), + .m_axis_tlast (unused_desc_read_tlast), + .m_axis_tid (unused_desc_read_tid), + .m_axis_tdest (unused_desc_read_tdest), + .m_axis_tuser (unused_desc_read_tuser), + .m_axis_tvalid(valid_desc_read), + .m_axis_tready(ready_desc_read) + ); + + logic [WRITE_DESC_KEEP_W-1:0] unused_desc_write_tkeep; + logic [WRITE_DESC_KEEP_W-1:0] unused_desc_write_tstrb; + logic unused_desc_write_tlast; + logic [AXIS_ID_WIDTH-1:0] unused_desc_write_tid; + logic [AXIS_DEST_WIDTH-1:0] unused_desc_write_tdest; + logic [AXIS_USER_WIDTH-1:0] unused_desc_write_tuser; + + axis_if_to_flat #( + .DATA_W(WRITE_DESC_WIDTH), + .KEEP_W(WRITE_DESC_KEEP_W), + .ID_W(AXIS_ID_WIDTH), + .DEST_W(AXIS_DEST_WIDTH), + .USER_W(AXIS_USER_WIDTH) + ) u_desc_write_if_to_flat ( + .s_axis(axis_desc_write), + + .m_axis_tdata (desc_write_cmd_out), + .m_axis_tkeep (unused_desc_write_tkeep), + .m_axis_tstrb (unused_desc_write_tstrb), + .m_axis_tlast (unused_desc_write_tlast), + .m_axis_tid (unused_desc_write_tid), + .m_axis_tdest (unused_desc_write_tdest), + .m_axis_tuser (unused_desc_write_tuser), + .m_axis_tvalid(valid_desc_write), + .m_axis_tready(ready_desc_write) + ); + + // Controller ADC/DAC outputs. + logic [31:0] adc_window_size; + logic [31:0] dac_pulse_width; + logic [31:0] dac_pulse_period; + logic [DAC_DATA_WIDTH-1:0] dac_pulse_height; + logic [15:0] dac_pulse_num; + logic [31:0] adc_pulse_period; + logic [15:0] adc_pulse_num; + logic dac_start; + logic adc_start; + logic dac_rst; + logic adc_rst; + + controller_wrapper_axil #( + .ADDR_W(ADDR_W), + .DATA_W(DATA_W), + .USER_W(USER_W), + .DAC_DATA_WIDTH(DAC_DATA_WIDTH), + .AXI_ADDR_WIDTH(AXI_ADDR_WIDTH), + .AXIS_ID_WIDTH(AXIS_ID_WIDTH), + .LEN_WIDTH(LEN_WIDTH), + .AXIS_USER_WIDTH(AXIS_USER_WIDTH), + .AXIS_DEST_WIDTH(AXIS_DEST_WIDTH), + .TAG_WIDTH(TAG_WIDTH), + .READ_DESC_WIDTH(READ_DESC_WIDTH), + .WRITE_DESC_WIDTH(WRITE_DESC_WIDTH), + .READ_STATUS_WIDTH(READ_STATUS_WIDTH), + .WRITE_STATUS_WIDTH(WRITE_STATUS_WIDTH) + ) dut ( + .ctrl_clk(ctrl_clk), + .dac_clk_in(dac_clk_in), + .adc_clk_in(adc_clk_in), + .rst_n(rst_n), + .s_axil(axil_bus), + + .finish(finish), + .adc_window_size(adc_window_size), + + .dac_pulse_width(dac_pulse_width), + .dac_pulse_period(dac_pulse_period), + .dac_pulse_height(dac_pulse_height), + .dac_pulse_num(dac_pulse_num), + + .adc_pulse_period(adc_pulse_period), + .adc_pulse_num(adc_pulse_num), + + .dac_start(dac_start), + .adc_start(adc_start), + .dac_rst(dac_rst), + .adc_rst(adc_rst), + + .s_axis_status_read(axis_status_read), + .s_axis_status_write(axis_status_write), + + .m_axis_desc_read(axis_desc_read), + .m_axis_desc_write(axis_desc_write) + ); + +endmodule : tb_controller_wrapper_axil diff --git a/rtl/controller_new/tests/test_controller.py b/rtl/controller_new/tests/test_controller.py new file mode 100644 index 0000000..fa27942 --- /dev/null +++ b/rtl/controller_new/tests/test_controller.py @@ -0,0 +1,170 @@ +import cocotb +from cocotb.clock import Clock +from cocotb.handle import Immediate +from cocotb.triggers import RisingEdge +from cocotbext.axi import AxiLiteBus, AxiLiteMaster + + +# 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 = 0 +CTRL_RST_SOFT = 1 +CTRL_CFG_BUS_VALID = 2 +CTRL_SEND_DESC_READ = 3 +CTRL_SEND_DESC_WRITE = 4 +CTRL_TAKE_STATUS_READ = 5 +CTRL_TAKE_STATUS_WRITE = 6 + + +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()) + + self.axil = AxiLiteMaster( + AxiLiteBus.from_prefix(dut, "s_axil"), + 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") + + +@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_desc_status(dut): + tb = TB(dut) + await tb.reset() + + await tb.write_reg(REG_DESC_READ_ADDR, 0xdead) + await tb.write_reg(REG_DESC_READ_LEN, 64) + await tb.write_reg(REG_DESC_READ_CONFIG, 0x0000_0001) + + await tb.write_reg(REG_DESC_WRITE_ADDR, 0xbeef) + await tb.write_reg(REG_DESC_WRITE_LEN_AND_TAG, 0x0005555) + + # W1S pulse: bit 3 = send_desc_read_o. + await tb.write_reg(REG_CONTROL, 1 << CTRL_SEND_DESC_READ) + + await tb.write_reg(REG_CONTROL, 1 << CTRL_SEND_DESC_WRITE) + + for _ in range(20): + await RisingEdge(dut.ctrl_clk) + + assert int(dut.desc_read_cmd_out.value) == 0x000000100040DEAD + assert int(dut.desc_write_cmd_out.value) == 0x0005555BEEF + + # test status + dut.status_read_cmd_in.value = Immediate(123) + dut.status_write_cmd_in.value = Immediate(0xabcd90012345) + dut.valid_status_read.value = Immediate(1) + dut.valid_status_write.value = Immediate(1) + + for _ in range(10): + await RisingEdge(dut.ctrl_clk) + + # read status + await tb.write_reg(REG_CONTROL, 1 << CTRL_TAKE_STATUS_READ) + 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_READ_STATUS) == 123 + assert await tb.read_reg(REG_STATUS_WRITE_CONFIG) == 0xABCD900 + assert await tb.read_reg(REG_STATUS_WRITE_LEN) == 0x12345 diff --git a/rtl/controller_new/tests/test_timing.xdc b/rtl/controller_new/tests/test_timing.xdc new file mode 100644 index 0000000..2ecbc36 --- /dev/null +++ b/rtl/controller_new/tests/test_timing.xdc @@ -0,0 +1,13 @@ +# Primary clocks +create_clock -name eth_clk -period 8.000 [get_ports eth_clk_in] +create_clock -name dac_clk -period 7.692 [get_ports dac_clk_in] +create_clock -name adc_clk -period 15.385 [get_ports adc_clk_in] + + +# Asynchronous clock groups +# eth, dac, adc are independent domains + +set_clock_groups -name ASYNC_ETH_DAC_ADC -asynchronous \ + -group [get_clocks eth_clk] \ + -group [get_clocks dac_clk] \ + -group [get_clocks adc_clk]