diff --git a/rtl/accum/src/accum_top.sv b/rtl/accum/src/accum_top.sv new file mode 100644 index 0000000..bf1616b --- /dev/null +++ b/rtl/accum/src/accum_top.sv @@ -0,0 +1,92 @@ +`timescale 1ns / 1ps + +module accumulator_top +#( + parameter DATA_WIDTH = 12, + parameter ACCUM_WIDTH = 32, + parameter N_MAX = 4096, + parameter WINDOW_SIZE = 4, + parameter PACKET_SIZE = 8, + parameter READ_BATCH_SIZE =(PACKET_SIZE*8)/(ACCUM_WIDTH) + ) +( + // main clk + input clk_in, + input rst, + + // input data + input [DATA_WIDTH-1:0] s_axis_tdata, + input s_axis_tvalid, + + // parameters + input start, + input [31:0] smp_num, + input [15:0] seq_num, + + // eth signals + input eth_clk_in, + input req_ready, + output send_req, + + // output axis + output logic [7:0] m_axis_tdata, + output logic m_axis_tvalid, + input logic m_axis_tready, + output logic m_axis_tlast, + + output logic finish + ); + + wire [ACCUM_WIDTH-1:0] out_data; + wire out_valid; + wire readout_begin; + wire batch_req; + + accumulator #( + .DATA_WIDTH(DATA_WIDTH), + .ACCUM_WIDTH(ACCUM_WIDTH), + .N_MAX(N_MAX), + .WINDOW_SIZE(WINDOW_SIZE), + .PACKET_SIZE(PACKET_SIZE) + ) accum_main ( + .clk_in(clk_in), + .rst(rst), + .s_axis_tdata(s_axis_tdata), + .s_axis_tvalid(s_axis_tvalid), + .start(start), + .smp_num(smp_num), + .seq_num(seq_num), + .out_data(out_data), + .out_valid(out_valid), + .readout_begin(readout_begin), + .batch_req(batch_req), + .finish(finish) + ); + + out_axis_fifo #( + .ACCUM_WIDTH(ACCUM_WIDTH), + .WINDOW_SIZE(WINDOW_SIZE), + .PACKET_SIZE(PACKET_SIZE) + ) output_async_fifo ( + .eth_clk_in (eth_clk_in), + .acc_clk_in (clk_in), + .rst (rst), + .smp_num (smp_num), + + .m_axis_tdata (m_axis_tdata), + .m_axis_tvalid (m_axis_tvalid), + .m_axis_tready (m_axis_tready), + .m_axis_tlast (m_axis_tlast), + + .acc_din (out_data), + .din_valid (out_valid), + + .readout_begin (readout_begin), + + .req_ready (req_ready), + .send_req (send_req), + + .batch_req (batch_req), + .finish (finish) + ); +endmodule \ No newline at end of file diff --git a/rtl/accum/tests/Makefile b/rtl/accum/tests/Makefile index b45b6bc..056aad0 100644 --- a/rtl/accum/tests/Makefile +++ b/rtl/accum/tests/Makefile @@ -8,7 +8,7 @@ # FPGA settings FPGA_PART = xc7a35tfgg484-1 -FPGA_TOP = accum +FPGA_TOP = accumulator_top FPGA_ARCH = artix7 RTL_DIR = ../src @@ -24,7 +24,8 @@ XDC_FILES += ../../../constraints/ax7a035b.xdc XDC_FILES += test_timing.xdc SYN_FILES += out_axis_fifo_tb.sv -SIM_TOP = control_tb +SYN_FILE += accum_full_tb.sv +SIM_TOP = tb_accumulator_top program: $(PROJECT).bit diff --git a/rtl/accum/tests/accum_full_tb.sv b/rtl/accum/tests/accum_full_tb.sv new file mode 100644 index 0000000..1b6d1d5 --- /dev/null +++ b/rtl/accum/tests/accum_full_tb.sv @@ -0,0 +1,344 @@ +`timescale 1ns / 1ps + +module tb_accumulator_top; + + localparam DATA_WIDTH = 12; + localparam ACCUM_WIDTH = 32; + localparam N_MAX = 256; + localparam WINDOW_SIZE = 4; + localparam PACKET_SIZE = 128; + localparam READ_BATCH_SIZE = (PACKET_SIZE*8)/ACCUM_WIDTH; + localparam MAX_WORDS = N_MAX / WINDOW_SIZE; + localparam MAX_SEQ_NUM = 64; + + logic clk_in; + logic eth_clk_in; + logic rst; + + logic [DATA_WIDTH-1:0] s_axis_tdata; + logic s_axis_tvalid; + logic start; + logic [31:0] smp_num; + logic [15:0] seq_num; + + logic req_ready; + wire send_req; + + wire [7:0] m_axis_tdata; + wire m_axis_tvalid; + logic m_axis_tready; + wire m_axis_tlast; + + wire finish; + + integer seed; + integer total_errors; + integer tests_total; + integer tests_failed; + integer tests_passed; + + integer packets_seen; + integer current_packet_byte_count; + integer total_words_captured; + + byte packet_bytes [0:PACKET_SIZE-1]; + logic [ACCUM_WIDTH-1:0] expected_words [0:MAX_WORDS-1]; + logic [ACCUM_WIDTH-1:0] captured_words_le[0:MAX_WORDS-1]; + logic [ACCUM_WIDTH-1:0] captured_words_be[0:MAX_WORDS-1]; + + accumulator_top #( + .DATA_WIDTH(DATA_WIDTH), + .ACCUM_WIDTH(ACCUM_WIDTH), + .N_MAX(N_MAX), + .WINDOW_SIZE(WINDOW_SIZE), + .PACKET_SIZE(PACKET_SIZE) + ) dut ( + .clk_in(clk_in), + .rst(rst), + .s_axis_tdata(s_axis_tdata), + .s_axis_tvalid(s_axis_tvalid), + .start(start), + .smp_num(smp_num), + .seq_num(seq_num), + .eth_clk_in(eth_clk_in), + .req_ready(req_ready), + .send_req(send_req), + .m_axis_tdata(m_axis_tdata), + .m_axis_tvalid(m_axis_tvalid), + .m_axis_tready(m_axis_tready), + .m_axis_tlast(m_axis_tlast), + .finish(finish) + ); + + initial begin + clk_in = 1'b0; + forever #5 clk_in = ~clk_in; + end + + initial begin + eth_clk_in = 1'b0; + forever #4 eth_clk_in = ~eth_clk_in; + end + + task automatic clear_scoreboard; + integer i; + begin + packets_seen = 0; + current_packet_byte_count = 0; + total_words_captured = 0; + for (i = 0; i < MAX_WORDS; i = i + 1) begin + expected_words[i] = '0; + captured_words_le[i] = '0; + captured_words_be[i] = '0; + end + for (i = 0; i < PACKET_SIZE; i = i + 1) + packet_bytes[i] = 8'h00; + end + endtask + + task automatic reset_dut; + begin + rst = 1'b1; + start = 1'b0; + s_axis_tdata = '0; + s_axis_tvalid = 1'b0; + smp_num = '0; + seq_num = '0; + req_ready = 1'b0; + m_axis_tready = 1'b1; + clear_scoreboard(); + + repeat(12) @(posedge clk_in); + rst = 1'b0; + repeat(8) @(posedge clk_in); + end + endtask + + task automatic pulse_start; + begin + @(posedge clk_in); + start <= 1'b1; + @(posedge clk_in); + start <= 1'b0; + end + endtask + + task automatic send_one_sample(input logic [DATA_WIDTH-1:0] val); + begin + @(posedge clk_in); + s_axis_tdata <= val; + s_axis_tvalid <= 1'b1; + end + endtask + + task automatic stop_stream; + begin + @(posedge clk_in); + s_axis_tdata <= '0; + s_axis_tvalid <= 1'b0; + end + endtask + + task automatic run_test( + input integer test_id, + input integer seq_num_i, + input integer smp_num_i, + input bit randomize_data, + input integer base_value, + input string test_name + ); + logic [DATA_WIDTH-1:0] sample_mem [0:MAX_SEQ_NUM-1][0:N_MAX-1]; + integer seq_idx; + integer sample_idx; + integer word_idx; + integer k; + integer exp_word_count; + integer exp_packet_count; + integer sample_value; + integer local_sum; + integer timeout_cnt; + bit le_ok; + bit be_ok; + integer errors_before; + integer i; + begin + tests_total = tests_total + 1; + errors_before = total_errors; + + if (smp_num_i <= 0 || smp_num_i > N_MAX || (smp_num_i % WINDOW_SIZE) != 0) + $fatal(1, "[%0s] invalid smp_num=%0d", test_name, smp_num_i); + if (seq_num_i <= 0 || seq_num_i > MAX_SEQ_NUM) + $fatal(1, "[%0s] invalid seq_num=%0d", test_name, seq_num_i); + + $display("\n========================================"); + $display("TEST %0d: %0s", test_id, test_name); + $display("seq_num=%0d smp_num=%0d randomize=%0d", seq_num_i, smp_num_i, randomize_data); + $display("========================================"); + + reset_dut(); + smp_num = smp_num_i; + seq_num = seq_num_i; + req_ready = 1'b1; // приемник готов заранее + + exp_word_count = smp_num_i / WINDOW_SIZE; + exp_packet_count = (exp_word_count + READ_BATCH_SIZE - 1) / READ_BATCH_SIZE; + + for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin + for (sample_idx = 0; sample_idx < smp_num_i; sample_idx = sample_idx + 1) begin + if (randomize_data) + sample_value = $unsigned($random(seed)) % (1 << DATA_WIDTH); + else + sample_value = (base_value + seq_idx * smp_num_i + sample_idx) % (1 << DATA_WIDTH); + sample_mem[seq_idx][sample_idx] = sample_value[DATA_WIDTH-1:0]; + end + end + + for (word_idx = 0; word_idx < exp_word_count; word_idx = word_idx + 1) begin + local_sum = 0; + for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin + for (k = 0; k < WINDOW_SIZE; k = k + 1) + local_sum = local_sum + sample_mem[seq_idx][word_idx * WINDOW_SIZE + k]; + end + expected_words[word_idx] = local_sum[ACCUM_WIDTH-1:0]; + $display(" expected[%0d] = %0d (0x%08x)", word_idx, expected_words[word_idx], expected_words[word_idx]); + end + + pulse_start(); + + for (seq_idx = 0; seq_idx < seq_num_i; seq_idx = seq_idx + 1) begin + for (sample_idx = 0; sample_idx < smp_num_i; sample_idx = sample_idx + 1) + send_one_sample(sample_mem[seq_idx][sample_idx]); + stop_stream(); + repeat(2) @(posedge clk_in); + end + + timeout_cnt = 0; + while (packets_seen < exp_packet_count && timeout_cnt < 30000) begin + @(posedge eth_clk_in); + timeout_cnt = timeout_cnt + 1; + end + if (packets_seen < exp_packet_count) begin + $display("[%0s] ERROR: timeout waiting packets, got=%0d exp=%0d", + test_name, packets_seen, exp_packet_count); + total_errors = total_errors + 1; + end + + timeout_cnt = 0; + while (finish !== 1'b1 && timeout_cnt < 30000) begin + @(posedge clk_in); + timeout_cnt = timeout_cnt + 1; + end + if (finish !== 1'b1) begin + $display("[%0s] ERROR: timeout waiting finish", test_name); + total_errors = total_errors + 1; + end + + le_ok = 1'b1; + be_ok = 1'b1; + for (i = 0; i < exp_word_count; i = i + 1) begin + if (captured_words_le[i] !== expected_words[i]) le_ok = 1'b0; + if (captured_words_be[i] !== expected_words[i]) be_ok = 1'b0; + end + + if (!le_ok && !be_ok) begin + $display("[%0s] ERROR: payload mismatch", test_name); + for (i = 0; i < exp_word_count; i = i + 1) + $display(" idx=%0d exp=0x%08x le=0x%08x be=0x%08x", + i, expected_words[i], captured_words_le[i], captured_words_be[i]); + total_errors = total_errors + 1; + end else if (le_ok) begin + $display("[%0s] payload check passed in little-endian", test_name); + end else begin + $display("[%0s] payload check passed in big-endian", test_name); + end + + if (total_errors == errors_before) begin + tests_passed = tests_passed + 1; + $display("TEST %0d PASSED: %0s", test_id, test_name); + end else begin + tests_failed = tests_failed + 1; + $display("TEST %0d FAILED: %0s", test_id, test_name); + end + + req_ready = 1'b0; + repeat(10) @(posedge clk_in); + end + endtask + + always @(posedge eth_clk_in) begin : CAPTURE_AXIS + integer idx; + logic [31:0] tmp_le; + logic [31:0] tmp_be; + if (rst) begin + current_packet_byte_count = 0; + end else if (m_axis_tvalid && m_axis_tready) begin + if (current_packet_byte_count < PACKET_SIZE) + packet_bytes[current_packet_byte_count] = m_axis_tdata; + current_packet_byte_count = current_packet_byte_count + 1; + + if (m_axis_tlast) begin + packets_seen = packets_seen + 1; + + if (current_packet_byte_count != PACKET_SIZE) begin + $display("[packet] ERROR: packet size=%0d expected=%0d", current_packet_byte_count, PACKET_SIZE); + total_errors = total_errors + 1; + end + + for (idx = 0; idx < READ_BATCH_SIZE; idx = idx + 1) begin + tmp_le = { + packet_bytes[idx*4 + 3], + packet_bytes[idx*4 + 2], + packet_bytes[idx*4 + 1], + packet_bytes[idx*4 + 0] + }; + tmp_be = { + packet_bytes[idx*4 + 0], + packet_bytes[idx*4 + 1], + packet_bytes[idx*4 + 2], + packet_bytes[idx*4 + 3] + }; + if (total_words_captured + idx < MAX_WORDS) begin + captured_words_le[total_words_captured + idx] = tmp_le; + captured_words_be[total_words_captured + idx] = tmp_be; + end + end + + total_words_captured = total_words_captured + READ_BATCH_SIZE; + current_packet_byte_count = 0; + end + end + end + + initial begin + seed = 32'h1badf00d; + total_errors = 0; + tests_total = 0; + tests_failed = 0; + tests_passed = 0; + + reset_dut(); + + run_test(1, 2, 8, 1'b0, 1, "deterministic_small"); + run_test(2, 3, 8, 1'b1, 0, "random_seq3_smp8"); + run_test(3, 5, 16, 1'b1, 0, "random_seq5_smp16_multi_packet"); + // run_test(4, 1, 4, 1'b1, 0, "random_single_window"); + run_test(5, 7, 12, 1'b1, 0, "random_seq7_smp12"); + run_test(6, 4, 256, 1'b1, 0, "random_max_smpnum"); + + $display("\n========================================"); + $display("ALL TESTS COMPLETED"); + $display("tests_total = %0d", tests_total); + $display("tests_passed = %0d", tests_passed); + $display("tests_failed = %0d", tests_failed); + $display("total_errors = %0d", total_errors); + $display("========================================"); + + if (total_errors != 0) + $fatal(1, "TB FAILED with %0d error(s)", total_errors); + else + $display("TB PASSED"); + + $finish; + end + +endmodule diff --git a/rtl/accum/tests/accum_tb.sv b/rtl/accum/tests/accum_tb.sv new file mode 100644 index 0000000..d8d6385 --- /dev/null +++ b/rtl/accum/tests/accum_tb.sv @@ -0,0 +1,183 @@ +`timescale 1ns / 1ps + +module tb_accumulator; + + localparam DATA_WIDTH = 12; + localparam ACCUM_WIDTH = 32; + localparam N_MAX = 64; + localparam WINDOW_SIZE = 4; + localparam PACKET_SIZE = 8; // bytes + localparam READ_BATCH_SIZE = (PACKET_SIZE*8)/ACCUM_WIDTH; // = 2 + + reg clk_in; + reg rst; + reg [DATA_WIDTH-1:0] s_axis_tdata; + reg s_axis_tvalid; + reg start; + reg [31:0] smp_num; + reg [15:0] seq_num; + wire [ACCUM_WIDTH-1:0] out_data; + wire out_valid; + wire readout_begin; + reg batch_req; + reg finish; + + integer i; + integer out_count; + + reg [ACCUM_WIDTH-1:0] expected [0:READ_BATCH_SIZE-1]; + reg [ACCUM_WIDTH-1:0] got [0:READ_BATCH_SIZE-1]; + + accumulator #( + .DATA_WIDTH(DATA_WIDTH), + .ACCUM_WIDTH(ACCUM_WIDTH), + .N_MAX(N_MAX), + .WINDOW_SIZE(WINDOW_SIZE), + .PACKET_SIZE(PACKET_SIZE) + ) dut ( + .clk_in(clk_in), + .rst(rst), + .s_axis_tdata(s_axis_tdata), + .s_axis_tvalid(s_axis_tvalid), + .start(start), + .smp_num(smp_num), + .seq_num(seq_num), + .out_data(out_data), + .out_valid(out_valid), + .readout_begin(readout_begin), + .batch_req(batch_req), + .finish(finish) + ); + + // clock 100 MHz + initial begin + clk_in = 0; + forever #5 clk_in = ~clk_in; + end + + // send one sample + task send_sample(input [DATA_WIDTH-1:0] val); + begin + @(posedge clk_in); + s_axis_tdata <= val; + s_axis_tvalid <= 1'b1; + end + endtask + + // one idle cycle after valid stream + task end_stream; + begin + @(posedge clk_in); + s_axis_tvalid <= 1'b0; + s_axis_tdata <= '0; + end + endtask + + // pulse start + task pulse_start; + begin + @(posedge clk_in); + start <= 1'b1; + @(posedge clk_in); + start <= 1'b0; + end + endtask + + // pulse batch request + task pulse_batch_req; + begin + @(posedge clk_in); + batch_req <= 1'b1; + @(posedge clk_in); + batch_req <= 1'b0; + end + endtask + + initial begin + repeat(100) @(posedge clk_in); + // init + rst = 1'b1; + s_axis_tdata = '0; + s_axis_tvalid= 1'b0; + start = 1'b0; + smp_num = 32'd8; + seq_num = 16'd2; + batch_req = 1'b0; + finish = 1'b0; + + expected[0] = 32'd60; + expected[1] = 32'd92; + + repeat(50) @(posedge clk_in); + rst = 1'b0; + repeat(50) @(posedge clk_in); + + $display("=== TEST START ==="); + + pulse_start(); + + // seq 0: [1..8] + send_sample(12'd1); + send_sample(12'd2); + send_sample(12'd3); + send_sample(12'd4); + send_sample(12'd5); + send_sample(12'd6); + send_sample(12'd7); + send_sample(12'd8); + end_stream(); + + // небольшой зазор + repeat(5) @(posedge clk_in); + + // seq 1: [11..18] + send_sample(12'd11); + send_sample(12'd12); + send_sample(12'd13); + send_sample(12'd14); + send_sample(12'd15); + send_sample(12'd16); + send_sample(12'd17); + send_sample(12'd18); + end_stream(); + + $display("[%0t] all input data sent, waiting readout_begin...", $time); + + wait(readout_begin == 1'b1); + $display("[%0t] readout_begin asserted", $time); + repeat(22) @(posedge clk_in); + pulse_batch_req(); + + out_count = 0; + + // ждём два слова + while (out_count < READ_BATCH_SIZE) begin + @(posedge clk_in); + if (out_valid) begin + got[out_count] = out_data; + $display("[%0t] out_valid: got[%0d] = %0d", $time, out_count, out_data); + out_count = out_count + 1; + end + end + + // проверка + for (i = 0; i < READ_BATCH_SIZE; i = i + 1) begin + if (got[i] !== expected[i]) begin + $error("Mismatch at index %0d: got=%0d expected=%0d", i, got[i], expected[i]); + end else begin + $display("OK index %0d: %0d", i, got[i]); + end + end + + // завершаем readout + @(posedge clk_in); + finish <= 1'b1; + + + repeat(10) @(posedge clk_in); + + $display("=== TEST PASSED ==="); + $finish; + end + +endmodule \ No newline at end of file