`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 [127: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 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), .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 ); logic [127:0] payload; begin payload = { 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 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 ctrl_clk); 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) && (adc_pulse_num === exp_pulse_num)) return; 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; logic [31:0] test_pulse_period_adc; initial begin // defaults rst_n = 1'b0; rst_soft = 1'b0; start = 1'b0; finish = 1'b0; cfg_bus_input = '0; cfg_bus_valid = 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 test_pulse_period_adc = 32'h50607080; 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 ); wait_cfg_applied( test_pulse_width, test_pulse_period, test_pulse_num, test_pulse_height_raw, test_pulse_period_adc ); 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 $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; $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 ); wait_cfg_applied( test_pulse_width, test_pulse_period, test_pulse_num, test_pulse_height_raw, test_pulse_period_adc ); 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(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.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