diff --git a/dds_test.srcs/sim_1/new/timetagging_discriminating_buffer_test.sv b/dds_test.srcs/sim_1/new/timetagging_discriminating_buffer_test.sv index e69de29..61b059e 100644 --- a/dds_test.srcs/sim_1/new/timetagging_discriminating_buffer_test.sv +++ b/dds_test.srcs/sim_1/new/timetagging_discriminating_buffer_test.sv @@ -0,0 +1,233 @@ +import sim_util_pkg::*; + +`timescale 1ns / 1ps +module timetagging_discriminating_buffer_test (); + +logic clk = 0; +localparam CLK_RATE_HZ = 100_000_000; +always #(0.5s/CLK_RATE_HZ) clk = ~clk; + +logic reset; + +int error_count = 0; + +localparam int N_CHANNELS = 8; +localparam int TSTAMP_BUFFER_DEPTH = 128; +localparam int DATA_BUFFER_DEPTH = 1024; +localparam int AXI_MM_WIDTH = 128; +localparam int PARALLEL_SAMPLES = 1; +localparam int SAMPLE_WIDTH = 16; +localparam int APPROX_CLOCK_WIDTH = 48; + +Axis_Parallel_If #(.DWIDTH(PARALLEL_SAMPLES*SAMPLE_WIDTH), .PARALLEL_CHANNELS(N_CHANNELS)) data_in (); +Axis_If #(.DWIDTH(AXI_MM_WIDTH)) data_out (); +Axis_If #(.DWIDTH(2+$clog2($clog2(N_CHANNELS)+1))) buffer_config_in (); +Axis_If #(.DWIDTH(N_CHANNELS*SAMPLE_WIDTH*2)) discriminator_config_in(); +logic [31:0] timestamp_width; + +logic start, stop; +logic [$clog2($clog2(N_CHANNELS)+1)-1:0] banking_mode; +logic [N_CHANNELS-1:0][SAMPLE_WIDTH-1:0] threshold_high, threshold_low; + +always_comb begin + for (int i = 0; i < N_CHANNELS; i++) begin + discriminator_config_in.data[2*SAMPLE_WIDTH*i+:2*SAMPLE_WIDTH] = {threshold_high[i], threshold_low[i]}; + end +end + +assign buffer_config_in.data = {banking_mode, start, stop}; + +timetagging_discriminating_buffer #( + .N_CHANNELS(N_CHANNELS), + .TSTAMP_BUFFER_DEPTH(TSTAMP_BUFFER_DEPTH), + .DATA_BUFFER_DEPTH(DATA_BUFFER_DEPTH), + .AXI_MM_WIDTH(AXI_MM_WIDTH), + .PARALLEL_SAMPLES(PARALLEL_SAMPLES), + .SAMPLE_WIDTH(SAMPLE_WIDTH), + .APPROX_CLOCK_WIDTH(APPROX_CLOCK_WIDTH) +) dut_i ( + .clk, + .reset, + .timestamp_width, + .data_in, + .data_out, + .discriminator_config_in, + .buffer_cfg_in, +); + +logic [PARALLEL_SAMPLES*SAMPLE_WIDTH-1:0] data_sent [N_CHANNELS][$]; +logic [AXI_MM_WIDTH-1:0] data_received [$]; + +// send data to DUT and save sent/received data +always @(posedge clk) begin + for (int i = 0; i < N_CHANNELS; i++) begin + if (reset) begin + data_in.data[i] <= '0; + end else begin + if (data_in.ok[i]) begin + // send new data + for (int j = 0; j < PARALLEL_SAMPLES; j++) begin + data_in.data[i][j*SAMPLE_WIDTH+:SAMPLE_WIDTH] <= $urandom_range({SAMPLE_WIDTH{1'b1}}); + end + // save data that was sent + data_sent[i].push_front(data_in.data[i]); + end + end + end + // save all data in the same buffer and postprocess it later + if (data_out.ok) begin + data_received.push_front(data_out.data); + end +end + +task check_results(input int banking_mode, input bit missing_ok); + logic [SAMPLE_WIDTH*PARALLEL_SAMPLES:0] temp_sample; + int current_channel, n_samples; + for (int i = 0; i < N_CHANNELS; i++) begin + $display("data_sent[%0d].size() = %0d", i, data_sent[i].size()); + end + $display("data_received.size() = %0d", data_received.size()); + while (data_received.size() > 0) begin + current_channel = data_received.pop_back(); + n_samples = data_received.pop_back(); + $display("processing new bank with %0d samples from channel %0d", n_samples, current_channel); + for (int i = 0; i < n_samples; i++) begin + if (data_sent[current_channel][$] != data_received[$]) begin + $display("data mismatch error (channel = %0d, sample = %0d, received %x, sent %x)", current_channel, i, data_received[$], data_sent[current_channel][$]); + error_count = error_count + 1; + end + data_sent[current_channel].pop_back(); + data_received.pop_back(); + end + end + for (int i = 0; i < (1 << banking_mode); i++) begin + // make sure there are no remaining samples in data_sent queues + // corresponding to channels which are enabled as per banking_mode + // caveat: if one of the channels filled up, then it's okay for there to + // be missing samples in the other channels + if ((data_sent[i].size() > 0) & (!missing_ok)) begin + $warning("leftover samples in data_sent[%0d]: %0d", i, data_sent[i].size()); + error_count = error_count + 1; + end + while (data_sent[i].size() > 0) data_sent[i].pop_back(); + end + for (int i = (1 << banking_mode); i < N_CHANNELS; i++) begin + // flush out any remaining samples in data_sent queue + $display("removing %0d samples from data_sent[%0d]", data_sent[i].size(), i); + while (data_sent[i].size() > 0) data_sent[i].pop_back(); + end +endtask + +task start_acq_with_banking_mode(input int mode); + start <= 1'b1; + banking_mode <= mode; + buffer_config_inconfig_in.valid <= 1'b1; + @(posedge clk); + start <= 1'b0; + config_in.valid <= 1'b0; +endtask + +task stop_acq(); + stop <= 1'b1; + start <= 1'b0; + config_in.valid <= 1'b1; + @(posedge clk); + config_in.valid <= 1'b0; + start <= 1'b0; + stop <= 1'b0; +endtask + +initial begin + reset <= 1'b1; + start <= 1'b0; + stop <= 1'b0; + banking_mode <= '0; // only enable channel 0 + data_in.valid <= '0; + repeat (100) @(posedge clk); + reset <= 1'b0; + repeat (50) @(posedge clk); + + for (int in_valid_rand = 0; in_valid_rand < 2; in_valid_rand++) begin + for (int bank_mode = 0; bank_mode < 4; bank_mode++) begin + for (int amplitude_mode = 0; amplitude_mode < 4; amplitude_mode++) begin + repeat (10) @(posedge clk); + unique case (amplitude_mode) + 0: begin + // save everything + for (int i = 0; i < N_CHANNELS; i++) begin + data_range_low[i] <= 16'h0000; + data_range_high[i] <= 16'h04ff; + threshold_low[i] <= 16'h03c0; + threshold_high[i] <= 16'h0400; + end + end + 1: begin + // send stuff straddling the threshold with strong hysteresis + for (int i = 0; i < N_CHANNELS; i++) begin + data_range_low[i] <= 16'h00ff; + data_range_high[i] <= 16'h04ff; + threshold_low[i] <= 16'h01c0; + threshold_high[i] <= 16'h0400; + end + end + 2: begin + // send stuff below the threshold + for (int i = 0; i < N_CHANNELS; i++) begin + data_range_low[i] <= 16'h0000; + data_range_high[i] <= 16'h01ff; + threshold_low[i] <= 16'h0200; + threshold_high[i] <= 16'h0200; + end + end + 3: begin + // send stuff straddling the threshold with weak hysteresis + for (int i = 0; i < N_CHANNELS; i++) begin + data_range_low[i] <= 16'h0000; + data_range_high[i] <= 16'h04ff; + threshold_low[i] <= 16'h03c0; + threshold_high[i] <= 16'h0400; + end + end + endcase + disc_config_in.valid <= 1'b1; + @(posedge clk); + disc_config_in.valid <= 1'b0; + + repeat (10) @(posedge clk); + start_acq_with_banking_mode(bank_mode); + + data_in.send_samples(clk, $urandom_range(50,500), in_valid_rand & 1'b1, 1'b1); + repeat (10) @(posedge clk); + stop_acq(); + data_out.do_readout(clk, 1'b1, 100000); + $display("######################################################"); + $display("# checking results n_samples = %d", samples_to_send); + $display("# banking mode = %d", bank_mode); + $display("# samples sent with rand_valid = %d", in_valid_rand); + $display("######################################################"); + // The second argument of check_results is if it's okay for there to + // be missing samples that weren't stored. + // When data_in.valid is randomly toggled on and off and enough samples + // are sent to fill up all the banks, one of the banks will likely + // fill up before the others are done, triggering a stop condition for + // the other banks before they are full. + // This results in "missing" samples that aren't saved + check_results(bank_mode, (samp_count == 2) & (in_valid_rand == 1)); + end + end + end + + $display("#################################################"); + if (error_count == 0) begin + $display("# finished with zero errors"); + end else begin + $error("# finished with %0d errors", error_count); + $display("#################################################"); + end + $display("#################################################"); + $finish; +end + +endmodule + + diff --git a/dds_test.srcs/sources_1/new/timetagging_discriminating_buffer.sv b/dds_test.srcs/sources_1/new/timetagging_discriminating_buffer.sv index ce9a4c1..bb7be8e 100644 --- a/dds_test.srcs/sources_1/new/timetagging_discriminating_buffer.sv +++ b/dds_test.srcs/sources_1/new/timetagging_discriminating_buffer.sv @@ -1,43 +1,45 @@ // timetagging_discriminating_buffer - Reed Foster // performs threshold-based sample discrimination module timetagging_discriminating_buffer #( - parameter int N_CHANNELS = 2, - parameter int TSTAMP_BUFFER_DEPTH = 1024, - parameter int DATA_BUFFER_DEPTH = 32768, - parameter int AXI_MM_WIDTH = 128, - parameter int PARALLEL_SAMPLES = 1, - parameter int SAMPLE_WIDTH = 16, - parameter int APPROX_CLOCK_WIDTH = 48 + parameter int N_CHANNELS = 2, // number of input channels + parameter int TSTAMP_BUFFER_DEPTH = 1024, // depth of timestamp buffer + parameter int DATA_BUFFER_DEPTH = 32768, // depth of data/sample buffer + parameter int AXI_MM_WIDTH = 128, // width of DMA AXI-stream interface + parameter int PARALLEL_SAMPLES = 1, // number of parallel samples per clock cycle per channel + parameter int SAMPLE_WIDTH = 16, // width in bits of each sample + parameter int APPROX_CLOCK_WIDTH = 48 // requested width of timestamp ) ( input wire clk, reset, + output logic [31:0] timestamp_width, Axis_Parallel_If.Slave_Simple data_in, // all channels in parallel Axis_If.Master_Full data_out, - Axis_If.Slave_Simple disc_cfg_in, // {threshold_high, threshold_low} for each channel - Axis_If.Slave_Simple buffer_cfg_in // {banking_mode, start, stop} + Axis_If.Slave_Simple discriminator_config_in, // {threshold_high, threshold_low} for each channel + Axis_If.Slave_Simple buffer_config_in // {banking_mode, start, stop} ); localparam int SAMPLE_INDEX_WIDTH = $clog2(DATA_BUFFER_DEPTH*N_CHANNELS); localparam int TIMESTAMP_WIDTH = SAMPLE_WIDTH * ((SAMPLE_INDEX_WIDTH + APPROX_CLOCK_WIDTH + (SAMPLE_WIDTH - 1)) / SAMPLE_WIDTH); +assign timestamps_width = TIMESTAMP_WIDTH; // when either buffer fills up, it triggers a stop on the other with the stop_aux input logic [1:0] buffer_full; // axi-stream interfaces -Axis_Parallel_If #(.DWIDTH(TIMESTAMP_WIDTH), .PARALLEL_CHANNELS(N_CHANNELS)) disc_tstamps(); +Axis_Parallel_If #(.DWIDTH(TIMESTAMP_WIDTH), .PARALLEL_CHANNELS(N_CHANNELS)) disc_timestamps(); Axis_Parallel_If #(.DWIDTH(SAMPLE_WIDTH*PARALLEL_SAMPLES), .PARALLEL_CHANNELS(N_CHANNELS)) disc_data(); -Axis_If #(.DWIDTH($clog2($clog2(N_CHANNELS)+1)+2)) buf_tstamp_cfg (); -Axis_If #(.DWIDTH($clog2($clog2(N_CHANNELS)+1)+2)) buf_data_cfg (); -Axis_If #(.DWIDTH(TIMESTAMP_WIDTH)) buf_tstamp_out (); -Axis_If #(.DWIDTH(SAMPLE_WIDTH*PARALLEL_SAMPLES)) buf_data_out (); -Axis_If #(.DWIDTH(AXI_MM_WIDTH)) buf_tstamp_out_resized (); -Axis_If #(.DWIDTH(AXI_MM_WIDTH)) buf_data_out_resized (); - -// share buffer_cfg_in between both buffers so their configuration is synchronized -assign buf_tstamp_cfg.data = buffer_cfg_in.data; -assign buf_tstamp_cfg.valid = buffer_cfg_in.valid; -assign buf_data_cfg.data = buffer_cfg_in.data; -assign buf_data_cfg.valid = buffer_cfg_in.valid; -assign buffer_cfg_in.ready = 1'b1; // doesn't matter what we do here, since both modules hold ready = 1'b1 +Axis_If #(.DWIDTH($clog2($clog2(N_CHANNELS)+1)+2)) buffer_timestamp_config (); +Axis_If #(.DWIDTH($clog2($clog2(N_CHANNELS)+1)+2)) buffer_data_config (); +Axis_If #(.DWIDTH(TIMESTAMP_WIDTH)) buffer_timestamp_out (); +Axis_If #(.DWIDTH(SAMPLE_WIDTH*PARALLEL_SAMPLES)) buffer_data_out (); +Axis_If #(.DWIDTH(AXI_MM_WIDTH)) buffer_timestamp_out_resized (); +Axis_If #(.DWIDTH(AXI_MM_WIDTH)) buffer_data_out_resized (); + +// share buffer_config_in between both buffers so their configuration is synchronized +assign buffer_timestamp_config.data = buffer_config_in.data; +assign buffer_timestamp_config.valid = buffer_config_in.valid; +assign buffer_data_config.data = buffer_config_in.data; +assign buffer_data_config.valid = buffer_config_in.valid; +assign buffer_config_in.ready = 1'b1; // doesn't matter what we do here, since both modules hold ready = 1'b1 logic start, start_d; always_ff @(posedge clk) begin @@ -46,8 +48,8 @@ always_ff @(posedge clk) begin start_d <= '0; end else begin start_d <= start; - if (buffer_cfg_in.ok) begin - start <= buffer_cfg_in.data[1]; + if (buffer_config_in.ok) begin + start <= buffer_config_in.data[1]; end end end @@ -63,8 +65,8 @@ sample_discriminator #( .reset, .data_in, .data_out(disc_data), - .timestamps_out(disc_tstamps), - .config_in(disc_cfg_in), + .timestamps_out(disc_timestamps), + .config_in(discriminator_config_in), .sample_index_reset(start & ~start_d) // reset sample_index count whenever a new capture is started ); @@ -77,8 +79,8 @@ banked_sample_buffer #( .clk, .reset, .data_in(disc_data), - .data_out(buf_data_out), - .config_in(buf_data_cfg), + .data_out(buffer_data_out), + .config_in(buffer_data_config), .stop_aux(buffer_full[0]), // stop saving data when timestamp buffer is full .capture_started(), .buffer_full(buffer_full[1]) @@ -92,9 +94,9 @@ banked_sample_buffer #( ) timestamp_buffer_i ( .clk, .reset, - .data_in(disc_tstamps), - .data_out(buf_tstamp_out), - .config_in(buf_tstamp_cfg), + .data_in(disc_timestamps), + .data_out(buffer_timestamp_out), + .config_in(buffer_timestamp_config), .stop_aux(buffer_full[1]), // stop saving timestamps when data buffer is full .capture_started(), .buffer_full(buffer_full[0]) @@ -125,8 +127,8 @@ axis_width_converter #( ) data_width_converter_i ( .clk, .reset, - .data_in(buf_data_out), - .data_out(buf_data_out_resized) + .data_in(buffer_data_out), + .data_out(buffer_data_out_resized) ); axis_width_converter #( @@ -136,8 +138,8 @@ axis_width_converter #( ) timestamp_width_converter_i ( .clk, .reset, - .data_in(buf_tstamp_out), - .data_out(buf_tstamp_out_resized) + .data_in(buffer_timestamp_out), + .data_out(buffer_timestamp_out_resized) ); // mux the two outputs @@ -150,8 +152,8 @@ always_ff @(posedge clk) begin buffer_select <= TIMESTAMP; end else begin unique case (buffer_select) - TIMESTAMP: if (buf_tstamp_out_resized.last && buf_tstamp_out_resized.ok) buffer_select <= DATA; - DATA: if (buf_data_out_resized.last && buf_data_out_resized.ok) buffer_select <= TIMESTAMP; + TIMESTAMP: if (buffer_timestamp_out_resized.last && buffer_timestamp_out_resized.ok) buffer_select <= DATA; + DATA: if (buffer_data_out_resized.last && buffer_data_out_resized.ok) buffer_select <= TIMESTAMP; endcase end end @@ -160,19 +162,19 @@ end always_comb begin unique case (buffer_select) TIMESTAMP: begin - data_out.data = buf_tstamp_out_resized.data; - data_out.valid = buf_tstamp_out_resized.valid; + data_out.data = buffer_timestamp_out_resized.data; + data_out.valid = buffer_timestamp_out_resized.valid; data_out.last = 1'b0; // don't send last for timestamp data end DATA: begin - data_out.data = buf_data_out_resized.data; - data_out.valid = buf_data_out_resized.valid; - data_out.last = buf_data_out_resized.last; + data_out.data = buffer_data_out_resized.data; + data_out.valid = buffer_data_out_resized.valid; + data_out.last = buffer_data_out_resized.last; end endcase end -assign buf_tstamp_out_resized.ready = (buffer_select == TIMESTAMP) ? data_out.ready : 1'b0; -assign buf_data_out_resized.ready = (buffer_select == DATA) ? data_out.ready : 1'b0; +assign buffer_timestamp_out_resized.ready = (buffer_select == TIMESTAMP) ? data_out.ready : 1'b0; +assign buffer_data_out_resized.ready = (buffer_select == DATA) ? data_out.ready : 1'b0; endmodule