From 23f82b9445f9016bdfcc6f2739e1ed0dfc604293 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 15 Apr 2026 13:30:56 +0300 Subject: [PATCH] tests: add controller tb --- rtl/controller/tests/controller_tb.sv | 377 ++++++++++++++++++++++++++ rtl/controller/tests/test_timing.xdc | 13 + 2 files changed, 390 insertions(+) create mode 100644 rtl/controller/tests/controller_tb.sv create mode 100644 rtl/controller/tests/test_timing.xdc diff --git a/rtl/controller/tests/controller_tb.sv b/rtl/controller/tests/controller_tb.sv new file mode 100644 index 0000000..8bbce57 --- /dev/null +++ b/rtl/controller/tests/controller_tb.sv @@ -0,0 +1,377 @@ +`timescale 1ns/1ps + +module tb_control; + + localparam int unsigned DAC_DATA_WIDTH = 12; + + + // Clocks / reset + logic eth_clk_in; + logic dac_clk_in; + logic adc_clk_in; + logic rst_n; + + // axi stream (input) + logic [7:0] s_axis_tdata; + logic s_axis_tvalid; + logic s_axis_tready; + logic s_axis_tlast; + + // ADC side input + logic finish; + + // DUT outputs + 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; + + + // DUT + control #( + .DAC_DATA_WIDTH(DAC_DATA_WIDTH) + ) dut ( + .eth_clk_in (eth_clk_in), + .dac_clk_in (dac_clk_in), + .adc_clk_in (adc_clk_in), + .rst_n (rst_n), + + .s_axis_tdata (s_axis_tdata), + .s_axis_tvalid (s_axis_tvalid), + .s_axis_tready (s_axis_tready), + .s_axis_tlast (s_axis_tlast), + + .finish (finish), + + .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) + ); + + + // Clock generation + initial begin + eth_clk_in = 1'b0; + forever #(1 * 4.000) eth_clk_in = ~eth_clk_in; // 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 + + + // 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 + + + // some helpers for axi + task automatic axis_send_byte(input logic [7:0] data, input logic last); + begin + @(negedge eth_clk_in); + s_axis_tdata <= data; + s_axis_tvalid <= 1'b1; + s_axis_tlast <= last; + + @(posedge eth_clk_in); + while (!s_axis_tready) begin + @(posedge eth_clk_in); + end + + s_axis_tvalid <= 1'b0; + s_axis_tlast <= 1'b0; + s_axis_tdata <= '0; + end + endtask + + task automatic send_cmd(input logic [7:0] cmd); + begin + axis_send_byte(cmd, 1'b1); + 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 + ); + logic [95:0] payload; + int i; + begin + // little-endian payload layout: + // [31:0] pulse_width + // [63:32] pulse_period + // [79:64] pulse_num + // [95:80] pulse_height_raw + payload = {pulse_height_raw, pulse_num, pulse_period, pulse_width}; + + axis_send_byte(8'h88, 1'b0); // CMD_SET_DATA + + for (i = 0; i < 12; i++) begin + axis_send_byte(payload[i*8 +: 8], (i == 11)); + end + 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 int max_cycles = 200 + ); + logic [DAC_DATA_WIDTH-1:0] exp_dac_height; + int i; + begin + exp_dac_height = exp_pulse_height_raw[DAC_DATA_WIDTH-1:0]; + + for (i = 0; i < max_cycles; i++) begin + @(posedge eth_clk_in); + if ((dac_pulse_width === exp_pulse_width ) && + (dac_pulse_period === exp_pulse_period) && + (dac_pulse_num === exp_pulse_num ) && + (dac_pulse_height === exp_dac_height ) && + (adc_pulse_period === exp_pulse_period) && + (adc_pulse_num === exp_pulse_num )) begin + return; + end + end + + $fatal(1, + "Timeout waiting config outputs. Got: dac_width=%h dac_period=%h dac_num=%h dac_height=%h adc_period=%h adc_num=%h", + dac_pulse_width, dac_pulse_period, dac_pulse_num, dac_pulse_height, + adc_pulse_period, adc_pulse_num + ); + end + endtask + + + // Test sequence + logic [31:0] test_pulse_width; + logic [31:0] test_pulse_period; + logic [15:0] test_pulse_num; + logic [15:0] test_pulse_height_raw; + + initial begin + // defaults + rst_n = 1'b0; + s_axis_tdata = '0; + s_axis_tvalid = 1'b0; + s_axis_tlast = 1'b0; + finish = 1'b0; + + test_pulse_width = 32'h11223344; + test_pulse_period = 32'h55667788; + test_pulse_num = 16'hA1B2; + test_pulse_height_raw = 16'h0CDE; // for DAC_DATA_WIDTH=12 => 12'hCDE + + repeat (10) @(posedge eth_clk_in); + rst_n = 1'b1; + + repeat (10) @(posedge eth_clk_in); + + $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); + + $display("[%0t] TEST 2: set_data", $time); + send_set_data( + test_pulse_width, + test_pulse_period, + test_pulse_num, + test_pulse_height_raw + ); + + wait_cfg_applied( + test_pulse_width, + test_pulse_period, + test_pulse_num, + test_pulse_height_raw + ); + + 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'h55667788) begin + $fatal(1, "adc_pulse_period mismatch: got %h expected %h", adc_pulse_period, 32'h55667788); + end + if (adc_pulse_num !== 16'hA1B2) begin + $fatal(1, "adc_pulse_num mismatch: got %h expected %h", adc_pulse_num, 16'hA1B2); + end + + $display("[%0t] TEST 2 passed", $time); + + repeat (20) @(posedge eth_clk_in); + + $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 + + $display("[%0t] TEST 3 start pulses passed", $time); + + // release busy by finish pulse from ADC domain + $display("[%0t] Sending finish pulse", $time); + pulse_finish(); + + // a bit of wait for finish CDC back to ETH + repeat (20) @(posedge eth_clk_in); + + // 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("=============================================="); + + #100; + $finish; + end + +endmodule \ No newline at end of file diff --git a/rtl/controller/tests/test_timing.xdc b/rtl/controller/tests/test_timing.xdc new file mode 100644 index 0000000..2ecbc36 --- /dev/null +++ b/rtl/controller/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]