diff --git a/rtl/accum/README.md b/rtl/accum/README.md
new file mode 100644
index 0000000..c3c518b
--- /dev/null
+++ b/rtl/accum/README.md
@@ -0,0 +1,39 @@
+# Аккумулятор
+Модуль аккумуляции данных для последующего усреднения. Принимает данные с входного потока АХI-Stream фиксированной ширины (задается параметрически), суммируя их сначала по окнам, а затем со значениями из предыдущей последовательности.
+
+## Список парамтеров:
+- DATA_WIDTH - ширина входных данных, получаемых с АЦП
+- ACCUM_WIDTH - размер данных для аккумуляции, должен быть степенью числа 2. По умолчанию - 32
+- N_MAX - максимальное число окон в последовательности. Должно быть степенью числа 2. Влияет на размер используемой памяти.
+- WINDOW_SIZE - размер окна усреднения
+- PACKET_SIZE - размер выходного пакета
+
+## Иерархия:
+```
+├── accum_top - полная сборка аккумулятора
+│ ├── accum - основная логика аккумуляции по окнам и последовательностям
+│ │ ├── adder - модуль сложения по окнам
+│ ├── out_axis_fifo - модуль для выдачи данных наружу в другом частотном домене
+```
+## Список входных портов:
+- clk_in - частота входных данных
+- rst - сброс всего
+- [DATA_WIDTH-1:0] s_axis_tdata - входные данные
+- s_axis_tvalid - валидность входных данных
+- start - начало аккумуляции
+- [31:0] smp_num - число сэмплов (должно быть кратно WINDOW_SIZE)
+- [15:0] seq_num - число последовательностей аккумуляции
+- eth_clk_in - частота для выходных данных на ethernet
+- req_ready - готовность отправителя начать принимать данные
+- m_axis_tready - готовность выходного axis
+
+
+## Список выходных портов:
+- send_req - сигнал начала отправки данных
+- [7:0] m_axis_tdata - данные выходного axis
+- m_axis_tvalid - валидность выходного axis
+- m_axis_tlast - последний пакет в axis
+- finish - конец отправки всех данных, полный цикл работы завершен
+
+## Логика работы:
+Модуль начинает работу при получении сигнала start. Сразу после начала работы можно подавать данные на входной axis, они будут суммироваться по WINDOW_SIZE штук и отправляться на хранение. Так будет сделано для последовательности длиной smp_num чисел, затем начинается новая последовательность - всего таких будет seq_num штук. Каждая последующая последовательность также суммируется по окнам, а затем полученные значения прибавляются к тем же значениям предыдущей последовательности. Таким образом, выполняется суммирование по двум осям, и из исходных данных seq_num по smp_num чисел остается вектор длиной 1 x (smp_num / WINDOW_SIZE). После накопления всех данных начинается выдача. Выдача осуществляется на выходной AXI stream, работающий в домене eth_clk, и имеющий ширину 8 бит - предполагается, что выдача пойдет на ethernet-udp. Когда поднят сигнал req_ready, модуль будет отправлять send_req (запрос отправки пакета), и по готовности m_axis_tready начнет выдавать пакет размер PACKET_SIZE байт. Если данные нельзя ровно разложить по пакетам, то в последнем пакете могут быть отправлены рандомные данные из памяти. После окончания отправки всех пакетов будет поднят сигнал finish.
\ No newline at end of file
diff --git a/rtl/accum/src/accum.sv b/rtl/accum/src/accum.sv
new file mode 100644
index 0000000..121acdc
--- /dev/null
+++ b/rtl/accum/src/accum.sv
@@ -0,0 +1,271 @@
+`timescale 1ns / 1ps
+
+module accumulator
+#(
+ 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)
+ )
+(
+ input clk_in,
+ input rst,
+ input [DATA_WIDTH-1:0] s_axis_tdata,
+ input s_axis_tvalid,
+ input start,
+ input [31:0] smp_num,
+ input [15:0] seq_num,
+
+ output [ACCUM_WIDTH-1:0] out_data,
+ output out_valid,
+ output readout_begin,
+ input batch_req,
+ input finish
+ );
+
+ logic [31:0] smp_num_reg, cnt_smp_num;
+ logic [15:0] seq_num_reg, cnt_seq_num;
+ logic [15:0] cnt_addr, addra, addrb;
+
+ logic [ACCUM_WIDTH-1:0] data;
+ logic valid_data;
+ logic [ACCUM_WIDTH-1:0] data_bram_in, data_bram_out;
+ logic wea, enb;
+
+ logic readout_begin_reg;
+ logic [ACCUM_WIDTH-1:0] out_data_reg;
+ logic out_valid_reg;
+ logic finish_reg, finish_buf;
+
+ // registers for port b data request
+ reg req_data_b;
+ reg [15:0] req_addr_b;
+
+ typedef enum logic [3:0] {
+ IDLE,
+ INIT_MEM,
+ BEGIN_SEQ,
+ REQ_WORD_B,
+ ACCUM,
+ READOUT_START,
+ READOUT_AWAIT,
+ READOUT_DELAY,
+ READOUT_PUT,
+ READOUT_LAST,
+ FINISH
+ } wr_state_t;
+ (* MARK_DEBUG="true" *) wr_state_t wr_state;
+
+ always @(posedge clk_in) begin
+ if (rst) begin
+ smp_num_reg <= '0;
+ cnt_smp_num <= '0;
+ seq_num_reg <= '0;
+ cnt_seq_num <= '0;
+ cnt_addr <= '0;
+ wea <= 0;
+ enb <= 0;
+ wr_state <= IDLE;
+ finish_reg <= 0;
+ out_valid_reg <= 0;
+ end else begin
+ finish_buf <= finish;
+
+ // FSM
+ case(wr_state)
+
+ IDLE: begin
+ // wait for start signal
+ wea <= 0;
+ enb <= 0;
+ readout_begin_reg <= 0;
+ finish_reg <= 0;
+ out_valid_reg <= 0;
+ if (start) begin
+ smp_num_reg <= smp_num;
+ seq_num_reg <= seq_num;
+ wr_state <= INIT_MEM;
+ end
+
+ end
+ INIT_MEM: begin
+ // first run to initialize memory with first batch of values
+ wea <= 0;
+ if (valid_data) begin
+ data_bram_in <= data;
+ addra <= cnt_addr;
+ wea <= 1;
+ cnt_addr <= cnt_addr + 1;
+ cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
+
+ end
+ if (cnt_smp_num >= smp_num_reg) begin
+ wr_state <= BEGIN_SEQ;
+ end
+
+ end
+ BEGIN_SEQ: begin
+ // start new acc seq
+ wea <= 0;
+ enb <= 0;
+ if (cnt_seq_num == seq_num_reg - 1) begin
+ cnt_seq_num <= '0;
+ cnt_smp_num <= '0;
+ cnt_addr <= '0;
+ wr_state <= READOUT_START;
+ addrb <= '0;
+ enb <= 0;
+ end else begin
+ // beginning of new data sequence
+ cnt_seq_num <= cnt_seq_num + 1;
+ cnt_smp_num <= '0;
+ cnt_addr <= '0;
+ wea <= 0;
+ addrb <= 0;
+ wr_state <= REQ_WORD_B;
+ end
+ end
+
+ REQ_WORD_B: begin
+ // pre-request data for port b
+ wea <= 0;
+ enb <= 1;
+ addrb <= cnt_addr;
+ wr_state <= ACCUM;
+ end
+
+ ACCUM: begin
+ // sum mem+input
+ enb <= 0;
+ if (valid_data) begin
+ addra <= cnt_addr;
+ wea <= 1;
+ data_bram_in <= data + data_bram_out;
+ cnt_smp_num <= cnt_smp_num + WINDOW_SIZE;
+ if (cnt_smp_num + WINDOW_SIZE >= smp_num_reg) begin
+ wr_state <= BEGIN_SEQ;
+ end else begin
+ cnt_addr <= cnt_addr + 1;
+ wr_state <= REQ_WORD_B;
+ end
+ end
+ end
+ READOUT_START: begin
+ readout_begin_reg <= 1'b1;
+ wr_state <= READOUT_AWAIT;
+ enb <= 0;
+ end
+
+ READOUT_AWAIT: begin
+ // req await + delay for every-clock readout.
+ if (batch_req) begin
+ enb <= 1;
+ wr_state <= READOUT_DELAY;
+ end else if (finish_buf) begin
+ wr_state <= FINISH;
+ end else begin
+ enb <= 0;
+ out_valid_reg <= 0;
+ end
+ end
+
+ READOUT_DELAY: begin
+ // wait for mem latency
+ addrb <= addrb + 1;
+ wr_state <= READOUT_PUT;
+ end
+
+ READOUT_PUT: begin
+ // main data output
+ if ((addrb % READ_BATCH_SIZE) == 0) begin
+ wr_state <= READOUT_LAST;
+ enb <= 0;
+ end else addrb <= addrb + 1;
+
+ out_valid_reg <= 1;
+ out_data_reg <= data_bram_out;
+ end
+
+ READOUT_LAST: begin
+ // last word of packet
+ out_valid_reg <= 0;
+ out_data_reg <= data_bram_out;
+ wr_state <= READOUT_START;
+ end
+
+ FINISH: begin
+ out_valid_reg <= 0;
+ enb <= 0;
+ wr_state <= IDLE;
+ end
+
+ default: wr_state <= IDLE;
+ endcase
+ end
+ end
+
+
+ adder
+ #(
+ .DATA_WIDTH(DATA_WIDTH),
+ .WINDOW_SIZE(WINDOW_SIZE),
+ .ACCUM_WIDTH(ACCUM_WIDTH)
+ ) adder_dut
+ (
+ .clk_in(clk_in),
+ .rst(rst),
+ .s_axis_tdata(s_axis_tdata),
+ .s_axis_tvalid(s_axis_tvalid),
+ .sum_data(data),
+ .sum_valid(valid_data)
+ );
+
+ xpm_memory_sdpram #(
+ .ADDR_WIDTH_A(16), // DECIMAL
+ .ADDR_WIDTH_B(16), // DECIMAL
+ .AUTO_SLEEP_TIME(0), // DECIMAL
+ .BYTE_WRITE_WIDTH_A(ACCUM_WIDTH), // DECIMAL
+ .CASCADE_HEIGHT(0), // DECIMAL
+ .CLOCKING_MODE("common_clock"), // String
+ .ECC_MODE("no_ecc"), // String
+ .MEMORY_INIT_FILE("none"), // String
+ .MEMORY_INIT_PARAM("0"), // String
+ .MEMORY_OPTIMIZATION("true"), // String
+ .MEMORY_PRIMITIVE("auto"), // String
+ .MEMORY_SIZE(N_MAX*ACCUM_WIDTH), // DECIMAL
+ .MESSAGE_CONTROL(0), // DECIMAL
+ .READ_DATA_WIDTH_B(ACCUM_WIDTH), // DECIMAL
+ .READ_LATENCY_B(1), // DECIMAL
+ .READ_RESET_VALUE_B("0"), // String
+ .RST_MODE_A("SYNC"), // String
+ .RST_MODE_B("SYNC"), // String
+ .SIM_ASSERT_CHK(0), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
+ .USE_EMBEDDED_CONSTRAINT(0), // DECIMAL
+ .USE_MEM_INIT(1), // DECIMAL
+ .USE_MEM_INIT_MMI(0), // DECIMAL
+ .WAKEUP_TIME("disable_sleep"), // String
+ .WRITE_DATA_WIDTH_A(ACCUM_WIDTH), // DECIMAL
+ .WRITE_MODE_B("no_change"), // String
+ .WRITE_PROTECT(1) // DECIMAL
+ )
+ xpm_memory_sdpram_inst (
+
+ .doutb(data_bram_out),
+
+ .addra(addra),
+ .addrb(addrb),
+ .clka(clk_in),
+ .clkb(clk_in),
+ .dina(data_bram_in),
+ .ena(1'b1),
+ .enb(enb),
+ .wea(wea)
+ );
+
+ assign readout_begin = readout_begin_reg;
+ assign out_data = out_data_reg;
+ assign out_valid = out_valid_reg;
+
+endmodule
diff --git a/rtl/accum/src/accum_top.sv b/rtl/accum/src/accum_top.sv
new file mode 100644
index 0000000..3ae2f62
--- /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 = 65,
+ parameter PACKET_SIZE = 1024,
+ 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/src/adder.sv b/rtl/accum/src/adder.sv
new file mode 100644
index 0000000..dd1c3c9
--- /dev/null
+++ b/rtl/accum/src/adder.sv
@@ -0,0 +1,52 @@
+`timescale 1ns / 1ps
+
+
+module adder
+#(
+ parameter DATA_WIDTH = 12,
+ parameter WINDOW_SIZE = 4,
+ parameter ACCUM_WIDTH = 32
+ )
+(
+ input clk_in,
+ input rst,
+ input [DATA_WIDTH-1:0] s_axis_tdata,
+ input s_axis_tvalid,
+
+ output [ACCUM_WIDTH-1:0] sum_data,
+ output sum_valid
+ );
+
+ logic [ACCUM_WIDTH-1:0] accum, res;
+ logic [DATA_WIDTH-1:0] axis_data;
+ logic res_valid, axis_valid;
+ (* MARK_DEBUG = "TRUE" *) logic [15:0] cnt;
+
+ always @(posedge clk_in) begin
+ if (rst) begin
+ accum <= '0;
+ cnt <= '0;
+ res <= '0;
+ res_valid <= 0;
+ end else begin
+ res_valid <= 0;
+ axis_data <= s_axis_tdata;
+ axis_valid <= s_axis_tvalid;
+ if ( axis_valid) begin
+ if (cnt == WINDOW_SIZE-1) begin
+ res <= accum + axis_data;
+ res_valid <= 1;
+ accum <= '0;
+ cnt <= '0;
+ end else begin
+ accum <= accum + axis_data;
+ cnt <= cnt + 1;
+ end
+ end
+ end
+ end
+
+ assign sum_valid = res_valid;
+ assign sum_data = res;
+
+endmodule
diff --git a/rtl/accum/src/out_axis_fifo.sv b/rtl/accum/src/out_axis_fifo.sv
new file mode 100644
index 0000000..0deb4f5
--- /dev/null
+++ b/rtl/accum/src/out_axis_fifo.sv
@@ -0,0 +1,324 @@
+module out_axis_fifo #(
+ parameter ACCUM_WIDTH = 32,
+ parameter WINDOW_SIZE = 65,
+ parameter PACKET_SIZE = 1024
+) (
+ input logic eth_clk_in,
+ input logic acc_clk_in,
+ input logic rst,
+ input logic [31:0] smp_num,
+
+ // AXI stream master for output, eth_clk_in domain
+ output logic [7:0] m_axis_tdata,
+ output logic m_axis_tvalid,
+ input logic m_axis_tready,
+ output logic m_axis_tlast,
+ // eth handshake
+ input logic req_ready,
+ output logic send_req,
+ output logic [15:0] udp_data_length,
+
+ // data from acc
+ input logic [ACCUM_WIDTH-1:0] acc_din,
+ input logic din_valid,
+
+ // input pulse
+ input logic readout_begin,
+
+ // output pulses
+ output logic batch_req,
+ output logic finish
+);
+ // sync reset
+ reg [1:0] rst_sync_ff;
+ reg rst_eth;
+
+ always @(posedge acc_clk_in or posedge rst) begin
+ if (rst) begin
+ rst_sync_ff <= 2'b11;
+ end else begin
+ rst_sync_ff <= {rst_sync_ff[0], 1'b0};
+ end
+ end
+
+ assign rst_eth = rst_sync_ff[1];
+
+ logic [1:0] rst_acc_ff;
+ logic rst_acc;
+
+ always_ff @(posedge acc_clk_in or posedge rst) begin
+ if (rst)
+ rst_acc_ff <= 2'b11;
+ else
+ rst_acc_ff <= {rst_acc_ff[0], 1'b0};
+ end
+
+ assign rst_acc = rst_acc_ff[1];
+
+
+ // fifo params calc
+ // round up to be enough for 2xPACKET_SIZE storage
+ localparam int MIN_BYTES = 2 * PACKET_SIZE;
+ localparam int MIN_BITS = MIN_BYTES * 8;
+ localparam int MIN_WR_WORDS = (MIN_BITS + ACCUM_WIDTH - 1) / ACCUM_WIDTH; // ceil div
+ localparam int WDEPTH_BITS = $clog2(MIN_WR_WORDS);
+ localparam int FIFO_WDEPTH = 1 << WDEPTH_BITS;
+
+ localparam int FIFO_RDEPTH = FIFO_WDEPTH * ACCUM_WIDTH / 8;
+ localparam int RDEPTH_BITS = $clog2(FIFO_RDEPTH) + 1;
+
+ wire wr_unavail;
+ wire wr_rst_busy;
+ reg rd_en;
+
+
+ typedef enum logic [2:0] {
+ WR_IDLE = 3'd0,
+ WR_CHECK = 3'd1,
+ WR_RUN = 3'd2,
+ WR_END = 3'd3
+ } wr_state_t;
+
+ (* MARK_DEBUG="true" *) wr_state_t wr_state;
+
+ // Write FSM
+ reg [31:0] wr_cnt; // current BIT mem ptr
+ reg [31:0] wr_batch_tgt; // next 'target' that should be written from batch
+ reg [31:0] wr_total; // total BITS to be sent!
+
+ wire empty;
+
+ wire [WDEPTH_BITS:0] wr_data_count;
+
+ // NOTE:
+ // each written "acc_din" ACCUM_WIDTH word
+ // is counted as WINDOWS_SIZE samples actually
+ // because hw division for counters is painful
+ // so we just increased the counter sizes
+
+ always_ff @(posedge acc_clk_in) begin
+ if (rst_acc) begin
+ wr_state <= WR_IDLE;
+ wr_cnt <= 32'b0;
+ wr_batch_tgt <= 32'b0;
+ wr_total <= 32'b0;
+ batch_req <= 0;
+ finish <= 0;
+
+ end else begin
+
+ case (wr_state)
+ // wait until readout is requested
+ WR_IDLE: begin
+ if (readout_begin) begin
+ wr_cnt <= 32'b0;
+ wr_state <= WR_CHECK;
+ wr_total <= smp_num * ACCUM_WIDTH;
+ wr_batch_tgt <= 32'b0;
+ batch_req <= 0;
+ finish <= 0;
+ end
+ end
+
+ // wait until we can request a word
+ // depends on prog_full signal
+ WR_CHECK: begin
+ if ((wr_data_count < (FIFO_WDEPTH - (PACKET_SIZE / (ACCUM_WIDTH / 8)))) && ~wr_rst_busy) begin
+ batch_req <= 1;
+ // should give us exactly PACKET_SIZE * 8 bits
+ // multiplied by WINDOW_SIZE, because we count
+ // each given ACCUM_WIDTH word as WINDOWS_SIZE samples !!!
+ wr_batch_tgt <= wr_batch_tgt + (8 * WINDOW_SIZE * PACKET_SIZE);
+ wr_state <= WR_RUN;
+ end else begin
+ batch_req <= 0;
+ end
+ end
+
+ // wait until all requested packet is written
+ WR_RUN: begin
+ batch_req <= 0;
+ if (wr_cnt == wr_batch_tgt) begin
+ // got enough words
+ wr_state <= WR_END;
+ end else if (wr_cnt > wr_batch_tgt) begin
+ // weird case when accum gave us too much words
+ // block resets
+ wr_cnt <= 32'hffffffff; // sort of signal for sim/ila
+ wr_state <= WR_END;
+ end
+
+ if (din_valid) begin
+ // data supplied
+ // count as we got WINDOW_SIZE samples
+ wr_cnt <= wr_cnt + ACCUM_WIDTH * WINDOW_SIZE;
+ end
+ end
+
+ // check if this was last data batch
+ WR_END: begin
+ // here we check that we sent enough data
+ // wr_cnt should be by design PACKET_SIZE-aligned
+ if (wr_cnt >= wr_total) begin
+ // wait until all data is sent
+ if (empty) begin
+ finish <= 1;
+ wr_state <= WR_IDLE;
+ end
+ end else begin
+ // next word
+ wr_state <= WR_CHECK;
+ end
+ end
+ endcase
+ end
+ end
+
+ // Readout FSM with ethernet request
+
+ assign udp_data_length = PACKET_SIZE; // fixed packet size
+ reg [15:0] sent_cnt;
+
+ typedef enum logic [2:0] {
+ RD_IDLE = 3'd0,
+ RD_CHECK = 3'd1,
+ RD_SEND = 3'd2
+ } rd_state_t;
+
+ (* MARK_DEBUG="true" *) rd_state_t rd_state;
+
+ wire rd_valid;
+ wire [RDEPTH_BITS-1:0] rd_data_count;
+
+ always_ff @(posedge eth_clk_in) begin
+ if (rst_eth) begin
+ rd_state <= RD_IDLE;
+ send_req <= 1'b0;
+ sent_cnt <= 16'd0;
+ m_axis_tlast <= 1'b0;
+ m_axis_tvalid <= 1'b0;
+ rd_en <= 1'b0;
+
+ end else begin
+
+ case (rd_state)
+ // wait until fifo has enough data to send
+ RD_IDLE: begin
+ if (rd_data_count == PACKET_SIZE) begin
+ // enough data to send packet, begin
+ rd_state <= RD_CHECK;
+ end
+ send_req <= 1'b0;
+ sent_cnt <= 16'd0;
+ rd_en <= 1'b0;
+ m_axis_tlast <= 1'b0;
+ m_axis_tvalid <= 1'b0;
+ end
+
+ // await udp ready
+ RD_CHECK: begin
+ if (req_ready) begin
+ send_req <= 1'b1;
+ rd_state <= RD_SEND;
+ end
+ end
+
+ // send data
+ RD_SEND: begin
+ // udp is ready and fifo is ready = sent
+ send_req <= 1'b0;
+ if (m_axis_tready && rd_valid) begin
+ rd_en <= 1'b1;
+ m_axis_tvalid <= 1'b1;
+ sent_cnt <= sent_cnt + 1;
+ // final packet of the batch
+ if (sent_cnt == PACKET_SIZE - 1) begin
+ rd_state <= RD_IDLE;
+ m_axis_tlast <= 1'b1;
+ end
+ end else begin
+ rd_en <= 1'b0;
+ m_axis_tvalid <= 1'b0;
+ end
+ end
+ endcase
+ end
+ end
+
+ logic [ACCUM_WIDTH-1:0] fifo_din_r, acc_din_reg, din_valid_reg;
+ logic fifo_wr_en_r;
+
+ always_ff @(posedge acc_clk_in) begin
+ if (rst_acc) begin
+ fifo_din_r <= '0;
+ fifo_wr_en_r <= 1'b0;
+
+ din_valid_reg <= 1'b0;
+ end else begin
+ fifo_wr_en_r <= 1'b0;
+ acc_din_reg <= acc_din;
+
+ if (!wr_rst_busy && din_valid_reg) begin
+ fifo_din_r <= acc_din_reg;
+ fifo_wr_en_r <= 1'b1;
+ end
+
+ din_valid_reg <= din_valid;
+ end
+ end
+
+ // xpm_fifo_async: Asynchronous FIFO
+ // Xilinx Parameterized Macro, version 2025.1
+
+ xpm_fifo_async #(
+ .DOUT_RESET_VALUE("0"), // String
+ .FIFO_READ_LATENCY(1), // DECIMAL
+ .FIFO_WRITE_DEPTH(FIFO_WDEPTH),
+ .FULL_RESET_VALUE(0),
+ .PROG_EMPTY_THRESH(PACKET_SIZE),
+ .PROG_FULL_THRESH(PACKET_SIZE / (ACCUM_WIDTH / 8)),
+ .RD_DATA_COUNT_WIDTH(RDEPTH_BITS),
+ .READ_DATA_WIDTH(8), // always 8 bit for eth
+ .READ_MODE("fwft"),
+ .SIM_ASSERT_CHK(1), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
+ .USE_ADV_FEATURES("1616"), // String
+ .WRITE_DATA_WIDTH(ACCUM_WIDTH),
+ .WR_DATA_COUNT_WIDTH(WDEPTH_BITS+1)
+ )
+ xpm_fifo_async_inst (
+
+ .data_valid(rd_valid), // 1-bit output: Read Data Valid: When asserted, this signal indicates that valid data is available on the
+ // output bus (dout).
+
+ .dout(m_axis_tdata),
+ .empty(empty),
+
+ .full( ),
+
+ .prog_full(wr_unavail), // 1-bit output: Programmable Full: This signal is asserted when the number of words in the FIFO is greater than
+ // or equal to the programmable full threshold value. It is de-asserted when the number of words in the FIFO is
+ // less than the programmable full threshold value.
+
+ .rd_data_count(rd_data_count), // RD_DATA_COUNT_WIDTH-bit output: Read Data Count: This bus indicates the number of words read from the FIFO.
+
+ .wr_data_count(wr_data_count), // WR_DATA_COUNT_WIDTH-bit output: Write Data Count: This bus indicates the number of words written into the
+ // FIFO.
+
+
+
+
+ .rd_clk(eth_clk_in), // 1-bit input: Read clock: Used for read operation. rd_clk must be a free running clock.
+ .rd_en(rd_en), // 1-bit input: Read Enable: If the FIFO is not empty, asserting this signal causes data (on dout) to be read
+ // from the FIFO. Must be held active-low when rd_rst_busy is active high.
+
+ .rst(rst),
+
+ .din(fifo_din_r), // WRITE_DATA_WIDTH-bit input: Write Data: The input data bus used when writing the FIFO.
+ .wr_clk(acc_clk_in), // 1-bit input: Write clock: Used for write operation. wr_clk must be a free running clock.
+ .wr_en(fifo_wr_en_r),
+
+ .wr_rst_busy(wr_rst_busy)
+
+ );
+
+endmodule
\ No newline at end of file
diff --git a/rtl/accum/tests/Makefile b/rtl/accum/tests/Makefile
new file mode 100644
index 0000000..45f5626
--- /dev/null
+++ b/rtl/accum/tests/Makefile
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: MIT
+#
+# Copyright (c) 2025 FPGA Ninja, LLC
+#
+# Authors:
+# - Alex Forencich
+#
+
+# FPGA settings
+FPGA_PART = xc7a35tfgg484-1
+FPGA_TOP = accumulator_top
+FPGA_ARCH = artix7
+
+RTL_DIR = ../src
+
+
+include ../../../scripts/vivado.mk
+
+SYN_FILES += $(sort $(shell find ../src -type f \( -name '*.v' -o -name '*.sv' \)))
+
+XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci'))
+
+XDC_FILES += ../../../constraints/ax7a035b.xdc
+XDC_FILES += test_timing.xdc
+
+SYN_FILES += out_axis_fifo_tb.sv
+SYN_FILES += accum_full_tb.sv
+SIM_TOP = tb_accumulator_top
+
+
+program: $(PROJECT).bit
+ echo "open_hw_manager" > program.tcl
+ echo "connect_hw_server" >> program.tcl
+ echo "open_hw_target" >> program.tcl
+ echo "current_hw_device [lindex [get_hw_devices] 0]" >> program.tcl
+ echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> program.tcl
+ echo "set_property PROGRAM.FILE {$(PROJECT).bit} [current_hw_device]" >> program.tcl
+ echo "program_hw_devices [current_hw_device]" >> program.tcl
+ echo "exit" >> program.tcl
+ vivado -nojournal -nolog -mode batch -source program.tcl
+
+$(PROJECT).mcs $(PROJECT).prm: $(PROJECT).bit
+ echo "write_cfgmem -force -format mcs -size 16 -interface SPIx4 -loadbit {up 0x0000000 $*.bit} -checksum -file $*.mcs" > generate_mcs.tcl
+ echo "exit" >> generate_mcs.tcl
+ vivado -nojournal -nolog -mode batch -source generate_mcs.tcl
+ mkdir -p rev
+ COUNT=100; \
+ while [ -e rev/$*_rev$$COUNT.bit ]; \
+ do COUNT=$$((COUNT+1)); done; \
+ COUNT=$$((COUNT-1)); \
+ for x in .mcs .prm; \
+ do cp $*$$x rev/$*_rev$$COUNT$$x; \
+ echo "Output: rev/$*_rev$$COUNT$$x"; done;
diff --git a/rtl/accum/tests/accum_full_tb.sv b/rtl/accum/tests/accum_full_tb.sv
new file mode 100644
index 0000000..66d0071
--- /dev/null
+++ b/rtl/accum/tests/accum_full_tb.sv
@@ -0,0 +1,345 @@
+`timescale 1ns / 1ps
+
+module tb_accumulator_top;
+
+ localparam DATA_WIDTH = 12;
+ localparam ACCUM_WIDTH = 32;
+ localparam N_MAX = 4096;
+ localparam WINDOW_SIZE = 65;
+ localparam PACKET_SIZE = 1024;
+ localparam READ_BATCH_SIZE = (PACKET_SIZE*8)/ACCUM_WIDTH;
+ localparam MAX_WORDS = N_MAX;
+ 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*WINDOW_SIZE)-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 * WINDOW_SIZE || (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 < 50 * PACKET_SIZE) 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, 1, 1 * WINDOW_SIZE, 1'b0, 1, "deterministic_small");
+// $finish;
+ run_test(2, 2, 1 * WINDOW_SIZE, 1'b1, 0, "random_seq3_smp8");
+ run_test(3, 1, 16 * WINDOW_SIZE, 1'b1, 0, "random_seq5_smp16_multi_packet");
+ run_test(4, 2, 12 * WINDOW_SIZE, 1'b1, 0, "random_seq7_smp12");
+ run_test(5, 4, 256 * WINDOW_SIZE, 1'b1, 0, "random_max_smpnum");
+ run_test(6, 2, 1500 * WINDOW_SIZE, 1'b1, 0, "random_max_smpnum2");
+
+ $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
diff --git a/rtl/accum/tests/out_axis_fifo_tb.sv b/rtl/accum/tests/out_axis_fifo_tb.sv
new file mode 100644
index 0000000..56135ff
--- /dev/null
+++ b/rtl/accum/tests/out_axis_fifo_tb.sv
@@ -0,0 +1,290 @@
+`timescale 1ns/1ps
+
+module tb_out_axis_fifo;
+
+ localparam int ACCUM_WIDTH = 32;
+ localparam int WINDOW_SIZE = 65;
+ localparam int PACKET_SIZE = 8;
+ localparam int BYTES_PER_WORD = ACCUM_WIDTH / 8;
+ localparam int WORDS_PER_BATCH = PACKET_SIZE / BYTES_PER_WORD; // 1024 / 4 = 256 слов
+
+ logic eth_clk_in;
+ logic acc_clk_in;
+ logic rst;
+
+ logic [31:0] smp_num;
+
+ logic [7:0] m_axis_tdata;
+ logic m_axis_tvalid;
+ logic m_axis_tready;
+ logic m_axis_tlast;
+
+ logic [ACCUM_WIDTH-1:0] acc_din;
+ logic din_valid;
+
+ logic send_req;
+ logic req_ready;
+
+ logic readout_begin;
+
+ logic batch_req;
+ logic finish;
+
+ out_axis_fifo #(
+ .ACCUM_WIDTH(ACCUM_WIDTH),
+ .WINDOW_SIZE(WINDOW_SIZE),
+ .PACKET_SIZE(PACKET_SIZE)
+ ) dut (
+ .eth_clk_in (eth_clk_in),
+ .acc_clk_in (acc_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 (acc_din),
+ .din_valid (din_valid),
+
+ .readout_begin (readout_begin),
+
+ .req_ready (req_ready),
+ .send_req (send_req),
+
+ .batch_req (batch_req),
+ .finish (finish)
+ );
+
+ // clocks
+ initial begin
+ eth_clk_in = 0;
+ forever #6 eth_clk_in = ~eth_clk_in; // 125
+ end
+
+ initial begin
+ acc_clk_in = 0;
+ forever #7.692307692 acc_clk_in = ~acc_clk_in; // 65
+ end
+
+ // scoreboard
+ byte expected_bytes[$];
+ int unsigned compared_bytes;
+ int unsigned mismatch_count;
+ int unsigned total_pushed_words;
+
+ task automatic scoreboard_reset();
+ begin
+ expected_bytes.delete();
+ compared_bytes = 0;
+ mismatch_count = 0;
+ total_pushed_words = 0;
+ end
+ endtask
+
+ task automatic push_expected_word(input logic [ACCUM_WIDTH-1:0] word);
+ begin
+ // queue push
+ expected_bytes.push_back(word[7:0]);
+ expected_bytes.push_back(word[15:8]);
+ expected_bytes.push_back(word[23:16]);
+ expected_bytes.push_back(word[31:24]);
+ total_pushed_words++;
+ end
+ endtask
+
+ task automatic check_expected_empty(string case_name);
+ begin
+ if (expected_bytes.size() != 0) begin
+ $error("[%0t] %s: expected_bytes is not empty, remaining=%0d",
+ $time, case_name, expected_bytes.size());
+ end else begin
+ $display("[%0t] %s: scoreboard queue empty, all expected bytes were transmitted",
+ $time, case_name);
+ end
+ end
+ endtask
+
+ // axis check
+ always_ff @(posedge eth_clk_in or posedge rst) begin
+ byte exp_byte;
+ if (rst) begin
+ compared_bytes <= 0;
+ mismatch_count <= 0;
+ end else begin
+ if (m_axis_tvalid && m_axis_tready) begin
+ if (expected_bytes.size() == 0) begin
+ $error("[%0t] AXIS produced unexpected byte 0x%02x: expected queue is empty",
+ $time, m_axis_tdata);
+ mismatch_count <= mismatch_count + 1;
+ end else begin
+ exp_byte = expected_bytes.pop_front();
+ compared_bytes <= compared_bytes + 1;
+
+ if (m_axis_tdata !== exp_byte) begin
+ $error("[%0t] AXIS mismatch at byte #%0d: got=0x%02x expected=0x%02x",
+ $time, compared_bytes, m_axis_tdata, exp_byte);
+ mismatch_count <= mismatch_count + 1;
+ end
+ end
+ end
+ end
+ end
+
+ // helpers
+ task automatic do_reset();
+ begin
+ rst = 1'b1;
+ readout_begin = 1'b0;
+ din_valid = 1'b0;
+ acc_din = '0;
+ smp_num = '0;
+
+ scoreboard_reset();
+
+ repeat (10) @(posedge acc_clk_in);
+ rst = 1'b0;
+
+ repeat (10) @(posedge acc_clk_in);
+ end
+ endtask
+
+ task automatic pulse_readout_begin(input logic [31:0] smp_num_i);
+ begin
+ smp_num = smp_num_i;
+ @(posedge acc_clk_in);
+ readout_begin <= 1'b1;
+ @(posedge acc_clk_in);
+ readout_begin <= 1'b0;
+ end
+ endtask
+
+ task automatic send_random_words(input int unsigned n_words);
+ int unsigned i;
+ logic [ACCUM_WIDTH-1:0] rand_word;
+ begin
+ for (i = 0; i < n_words; i++) begin
+ rand_word = $urandom;
+
+ @(posedge acc_clk_in);
+ din_valid <= 1'b1;
+ acc_din <= rand_word;
+
+ // expected result
+ push_expected_word(rand_word);
+ end
+
+ @(posedge acc_clk_in);
+ din_valid <= 1'b0;
+ acc_din <= '0;
+ end
+ endtask
+
+
+ // 1. set smp_num
+ // 2. pulse readout_begon
+ // 3. send 1KB (PACKET_SIZE) after each batch_req pulse
+ // 4. wait for finish
+ // 5. compare axis result
+ task automatic run_case(input logic [31:0] smp_num_i);
+ int batch_count;
+ string case_name;
+ begin
+ batch_count = 0;
+ case_name = $sformatf("run_case(smp_num=%0d)", smp_num_i);
+
+ $display("[%0t] %s start", $time, case_name);
+
+ pulse_readout_begin(smp_num_i);
+
+ while (finish !== 1'b1) begin
+ @(posedge acc_clk_in);
+
+ if (batch_req) begin
+ batch_count++;
+ $display("[%0t] %s: batch_req #%0d -> send %0d words",
+ $time, case_name, batch_count, WORDS_PER_BATCH);
+
+ send_random_words(WORDS_PER_BATCH);
+ end
+ end
+
+
+ repeat (200) @(posedge eth_clk_in);
+
+ $display("[%0t] %s done: batches=%0d, pushed_words=%0d, compared_bytes=%0d, mismatches=%0d, wr_cnt=%0d, wr_total=%0d",
+ $time, case_name, batch_count, total_pushed_words, compared_bytes, mismatch_count,
+ dut.wr_cnt, dut.wr_total);
+
+ check_expected_empty(case_name);
+
+ if (mismatch_count != 0) begin
+ $fatal(1, "[%0t] %s FAILED: mismatches=%0d", $time, case_name, mismatch_count);
+ end else begin
+ $display("[%0t] %s PASSED", $time, case_name);
+ end
+
+ @(posedge acc_clk_in);
+ end
+ endtask
+
+ // eth beh simulator
+ int axis_byte_count;
+
+ always_ff @(posedge eth_clk_in or posedge rst) begin
+ if (rst) begin
+ axis_byte_count <= 0;
+ req_ready <= 0;
+ m_axis_tready <= 1'b0;
+ end else begin
+ req_ready <= 1;
+
+ // request send
+ if (send_req) begin
+ m_axis_tready <= 1'b1;
+ req_ready <= 0;
+ end
+
+ if (m_axis_tvalid && m_axis_tready) begin
+ axis_byte_count <= axis_byte_count + 1;
+ end
+ end
+ end
+
+
+ // main
+ initial begin
+ // init
+ rst = 1'b0;
+ readout_begin = 1'b0;
+ din_valid = 1'b0;
+ acc_din = '0;
+ smp_num = '0;
+
+ repeat (500) @(posedge acc_clk_in);
+
+ // 1
+ do_reset();
+ repeat (500) @(posedge acc_clk_in);
+ run_case(32'd17);
+ repeat (20) @(posedge acc_clk_in);
+
+ // 2
+ do_reset();
+ run_case(32'd1024);
+ repeat (20) @(posedge acc_clk_in);
+
+ // 3
+ do_reset();
+ run_case(32'd77777);
+ repeat (20) @(posedge acc_clk_in);
+
+ do_reset();
+ repeat (20) @(posedge acc_clk_in);
+
+ $display("[%0t] ALL TESTS DONE", $time);
+ $finish;
+ end
+
+endmodule
\ No newline at end of file
diff --git a/rtl/accum/tests/tb_accumulator_top_behav.wcfg b/rtl/accum/tests/tb_accumulator_top_behav.wcfg
new file mode 100644
index 0000000..931e9f1
--- /dev/null
+++ b/rtl/accum/tests/tb_accumulator_top_behav.wcfg
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ clk_in
+ clk_in
+
+
+ eth_clk_in
+ eth_clk_in
+
+
+ rst
+ rst
+
+
+ s_axis_tdata[11:0]
+ s_axis_tdata[11:0]
+
+
+ s_axis_tvalid
+ s_axis_tvalid
+
+
+ start
+ start
+
+
+ smp_num[31:0]
+ smp_num[31:0]
+
+
+ seq_num[15:0]
+ seq_num[15:0]
+
+
+ req_ready
+ req_ready
+ #E0FFFF
+ true
+
+
+ send_req
+ send_req
+ #E0FFFF
+ true
+
+
+ m_axis_tdata[7:0]
+ m_axis_tdata[7:0]
+ #008080
+ true
+
+
+ m_axis_tready
+ m_axis_tready
+ #00FFFF
+ true
+
+
+ m_axis_tlast
+ m_axis_tlast
+ #008080
+ true
+
+
+ finish
+ finish
+ #FAAFBE
+ true
+
+
+ batch_req
+ batch_req
+ #00FFFF
+ true
+
+
+ readout_begin
+ readout_begin
+ #00FFFF
+ true
+
+
+ acc
+ label
+
+
+ PACKET_SIZE[31:0]
+ PACKET_SIZE[31:0]
+
+
+ READ_BATCH_SIZE[31:0]
+ READ_BATCH_SIZE[31:0]
+
+
+ addrb[15:0]
+ addrb[15:0]
+
+
+
+ fifo
+ label
+
+
+ acc_din[31:0]
+ acc_din[31:0]
+ #FF0080
+ true
+
+
+ din_valid
+ din_valid
+ #FF0080
+ true
+
+
+ batch_req
+ batch_req
+
+
+ wr_state[2:0]
+ wr_state[2:0]
+
+
+ rd_state[2:0]
+ rd_state[2:0]
+
+
+ wr_unavail
+ wr_unavail
+ #FFFF00
+ true
+
+
+ wr_rst_busy
+ wr_rst_busy
+ #FFFF00
+ true
+
+
+ empty
+ empty
+
+
+ PROG_FULL_THRESH[31:0]
+ PROG_FULL_THRESH[31:0]
+
+
+ wr_data_count[4:0]
+ wr_data_count[4:0]
+ UNSIGNEDDECRADIX
+
+
+ rd_data_count[6:0]
+ rd_data_count[6:0]
+ UNSIGNEDDECRADIX
+
+
+
diff --git a/rtl/accum/tests/tb_out_axis_fifo_behav.wcfg b/rtl/accum/tests/tb_out_axis_fifo_behav.wcfg
new file mode 100644
index 0000000..aaed3f4
--- /dev/null
+++ b/rtl/accum/tests/tb_out_axis_fifo_behav.wcfg
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ eth_clk_in
+ eth_clk_in
+
+
+ acc_clk_in
+ acc_clk_in
+
+
+ rst
+ rst
+
+
+ smp_num[31:0]
+ smp_num[31:0]
+ UNSIGNEDDECRADIX
+
+
+ acc_din[31:0]
+ acc_din[31:0]
+ #FF0080
+ true
+
+
+ din_valid
+ din_valid
+ #FF0080
+ true
+
+
+ fifo_din_r[31:0]
+ fifo_din_r[31:0]
+ #FFA500
+ true
+
+
+ fifo_wr_en_r
+ fifo_wr_en_r
+ #FFA500
+ true
+
+
+ readout_begin
+ readout_begin
+ #FFFF00
+ true
+
+
+ batch_req
+ batch_req
+
+
+ finish
+ finish
+ #00FFFF
+ true
+
+
+ m_axis_tdata[7:0]
+ m_axis_tdata[7:0]
+
+
+ m_axis_tvalid
+ m_axis_tvalid
+
+
+ m_axis_tready
+ m_axis_tready
+
+
+ m_axis_tlast
+ m_axis_tlast
+
+
+ axis_byte_count[31:0]
+ axis_byte_count[31:0]
+ UNSIGNEDDECRADIX
+ #F0E68C
+ true
+
+
+ ACCUM_WIDTH[31:0]
+ ACCUM_WIDTH[31:0]
+ UNSIGNEDDECRADIX
+
+
+ WINDOW_SIZE[31:0]
+ WINDOW_SIZE[31:0]
+ UNSIGNEDDECRADIX
+
+
+ PACKET_SIZE[31:0]
+ PACKET_SIZE[31:0]
+ UNSIGNEDDECRADIX
+
+
+ wr_state[2:0]
+ wr_state[2:0]
+
+
+ wr_cnt[31:0]
+ wr_cnt[31:0]
+ UNSIGNEDDECRADIX
+
+
+ wr_batch_tgt[31:0]
+ wr_batch_tgt[31:0]
+ UNSIGNEDDECRADIX
+
+
+ wr_total[31:0]
+ wr_total[31:0]
+ UNSIGNEDDECRADIX
+
+
+ prog_empty
+ prog_empty
+
+
+ prog_full
+ prog_full
+
+
+ wr_ack
+ wr_ack
+
+
+ wr_data_count[2:0]
+ wr_data_count[2:0]
+ UNSIGNEDDECRADIX
+
+
+ wr_data_count[2:0]
+ wr_data_count[2:0]
+ UNSIGNEDDECRADIX
+
+
+ rd_data_count[4:0]
+ rd_data_count[4:0]
+ UNSIGNEDDECRADIX
+
+
+ rst_sync_ff[1:0]
+ rst_sync_ff[1:0]
+
+
+ rd_state[2:0]
+ rd_state[2:0]
+
+
+ rd_en
+ rd_en
+
+
+ rd_valid
+ rd_valid
+
+
+ overflow
+ overflow
+
+
+ wr_rst_busy
+ wr_rst_busy
+
+
+ send_req
+ send_req
+
+
+ req_ready
+ req_ready
+
+
diff --git a/rtl/accum/tests/test_timing.xdc b/rtl/accum/tests/test_timing.xdc
new file mode 100644
index 0000000..29312a3
--- /dev/null
+++ b/rtl/accum/tests/test_timing.xdc
@@ -0,0 +1,10 @@
+# Primary clocks
+create_clock -name eth_clk -period 8.000 [get_ports eth_clk_in]
+create_clock -name acc_clk -period 15.385 [get_ports clk_in]
+
+
+# Asynchronous clock groups
+
+set_clock_groups -name ASYNC_ETH_ACC -asynchronous \
+ -group [get_clocks eth_clk] \
+ -group [get_clocks acc_clk]
diff --git a/rtl/sampler/README.md b/rtl/sampler/README.md
new file mode 100644
index 0000000..751865c
--- /dev/null
+++ b/rtl/sampler/README.md
@@ -0,0 +1,28 @@
+# Сэмплер
+Модуль выполняет задачу сбора данных с выхода АЦП, их обработку, упаковку, и передачу дальше с помощью AXI Stream интерфейса.
+
+## Cписок параметров
+DATA_WIDTH - ширина входных данных, получаемых с АЦП.
+PACK_FACTOR - количество отсчетов, собираемых в один выходной пакет.
+PROCESS_MODE - режим интерпретации входного кода. 0 - прямой код, 1 - дополнительный код.
+
+## Список входных портов
+clk_in - сигнал тактирования выходного интерфейса.
+rst - сброс модуля и остановка подачи импульсов.
+[DATA_WIDTH-1:0] data_in - входной сигнал с АЦП.
+out_of_range - флаг выхода значений данных за допустимый диапазон. 0 - валидны, 1 - не валидны.
+
+## Список выходных портов
+[DATA_WIDTH*PACK_FACTOR-1:0] m_axis_tdata - урезанный axis формат, выходные данные. Ширина шины считается исходя из битности данных и фактора упаковки.
+m_axis_tvalid - урезанный axis формат, валидность выходных данных.
+
+## Логика работы
+На каждом такте принимаются data_in (значение АЦП) и out_of_range (флаг выхода значений данных за допустимый диапазон). Если out_of_range = 1, то данные игнорируются и не попадают во внутренний буффер. В противном случае, модуль накапливает данные во внутреннем буффере, идет его заполнение до количества данных, равное PACK_FACTOR. Когда буффер оказывается заполненным, он выдает пакет упакованных данных, сопровождая их импульсом m_axis_tvalid (готовность пакета). Если PROCESS_MODE = 1, данные выдаются в дополнительном коде, если PROCESS_MODE = 0 - в прямом.
+
+## Симуляция
+Тесты запускаются автоматически через make.
+```
+cd tests
+make sim
+```
+При успешном завершении теста высвечивается "ALL PASSED".
\ No newline at end of file
diff --git a/rtl/sampler/src/sampler.sv b/rtl/sampler/src/sampler.sv
index d12d24e..18a533c 100644
--- a/rtl/sampler/src/sampler.sv
+++ b/rtl/sampler/src/sampler.sv
@@ -5,8 +5,8 @@
module sampler
#(
parameter DATA_WIDTH = 12,
- parameter PACK_FACTOR = 3,
- parameter PROCESS_MODE = 1
+ parameter PACK_FACTOR = 1,
+ parameter PROCESS_MODE = 0
)
(
input clk_in,
@@ -19,52 +19,103 @@ module sampler
);
logic [DATA_WIDTH-1:0] data_converted;
logic out_of_range_reg;
+
+ generate
+ if (PROCESS_MODE) begin
- always @( posedge clk_in) begin
- if (rst) begin
- data_converted <= '0;
- end else begin
- out_of_range_reg <= out_of_range;
- if (PROCESS_MODE) begin
- if (data_in == 12'b100000000000) begin
+ always @(posedge clk_in) begin
+ if (rst) begin
+ data_converted <= '0;
+ out_of_range_reg <= 0;
+ end
+ else begin
+ out_of_range_reg <= out_of_range;
+ if (data_in == {1'b1, {(DATA_WIDTH-1){1'b0}}})
data_converted <= data_in;
- end else begin
- data_converted <= data_in[DATA_WIDTH-1] ? {1'b1, (~data_in[DATA_WIDTH-2:0] + 1'b1)} : data_in;
-
- end
- end else begin
- data_converted <= data_in;
+ else
+ data_converted <= data_in[DATA_WIDTH-1] ?{1'b1, (~data_in[DATA_WIDTH-2:0] + 1'b1)}:data_in;
end
end
- end
-
+ end else begin
+ always @(posedge clk_in) begin
+ if (rst) begin
+ data_converted <= '0;
+ out_of_range_reg <= 0;
+ end
+ else begin
+ out_of_range_reg <= out_of_range;
+ data_converted <= data_in;
+ end
+ end
+ end
+ endgenerate
logic [DATA_WIDTH*PACK_FACTOR-1:0] buffer;
logic buffer_ready;
logic [$clog2(PACK_FACTOR):0] cnt;
- always @(posedge clk_in) begin
- if (rst) begin
- buffer <= '0;
- cnt <= -1; //
- buffer_ready <= 0;
- end
- else begin
- buffer_ready <= 0;
- if (!out_of_range_reg) begin
- buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted};
- if (cnt == PACK_FACTOR-1) begin
- cnt <= 0;
+ generate
+ if (PACK_FACTOR == 1) begin
+ always @(posedge clk_in) begin
+ if (rst) begin
+ buffer <= '0;
+ buffer_ready <= 0;
+ end
+ else begin
+ buffer_ready <= 0;
+ if (!out_of_range_reg) begin
+ buffer <= data_converted;
buffer_ready <= 1;
- buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted};
- end
- else begin
- cnt <= cnt + 1;
end
end
end
- end
+ end else begin
+ always @(posedge clk_in) begin
+ if (rst) begin
+ buffer <= '0;
+ cnt <= '0; //
+ buffer_ready <= 0;
+ end
+ else begin
+ buffer_ready <= 0;
+ if (!out_of_range_reg) begin
+ buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted};
+ if (cnt == PACK_FACTOR-1) begin
+ cnt <= 0;
+ buffer_ready <= 1;
+ buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted};
+ end
+ else begin
+ cnt <= cnt + 1;
+ end
+ end
+ end
+ end
+ end
+ endgenerate
+
+// always @(posedge clk_in) begin
+// if (rst) begin
+// buffer <= '0;
+// cnt <= '0; //
+// buffer_ready <= 0;
+// end
+// else begin
+// buffer_ready <= 0;
+// if (!out_of_range_reg) begin
+// buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted};
+// if (cnt == PACK_FACTOR-1) begin
+// cnt <= 0;
+// buffer_ready <= 1;
+// buffer <= {buffer[DATA_WIDTH*(PACK_FACTOR-1)-1:0], data_converted};
+// end
+// else begin
+// cnt <= cnt + 1;
+// end
+// end
+// end
+// end
assign m_axis_tdata = buffer;
assign m_axis_tvalid = buffer_ready;
diff --git a/rtl/sampler/tests/Makefile b/rtl/sampler/tests/Makefile
new file mode 100644
index 0000000..2226f93
--- /dev/null
+++ b/rtl/sampler/tests/Makefile
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: MIT
+#
+# Copyright (c) 2025 FPGA Ninja, LLC
+#
+# Authors:
+# - Alex Forencich
+#
+
+# FPGA settings
+FPGA_PART = xc7a35tfgg484-1
+FPGA_TOP = sampler
+FPGA_ARCH = artix7
+
+RTL_DIR = ../src
+
+
+include ../../../scripts/vivado.mk
+
+SYN_FILES += $(sort $(shell find ../src -type f \( -name '*.v' -o -name '*.sv' \)))
+
+XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci'))
+
+XDC_FILES += ../../../constraints/ax7a035b.xdc
+
+SYN_FILES += sampler_main_tb.sv
+SIM_TOP = sampler_tb
+
+
+program: $(PROJECT).bit
+ echo "open_hw_manager" > program.tcl
+ echo "connect_hw_server" >> program.tcl
+ echo "open_hw_target" >> program.tcl
+ echo "current_hw_device [lindex [get_hw_devices] 0]" >> program.tcl
+ echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> program.tcl
+ echo "set_property PROGRAM.FILE {$(PROJECT).bit} [current_hw_device]" >> program.tcl
+ echo "program_hw_devices [current_hw_device]" >> program.tcl
+ echo "exit" >> program.tcl
+ vivado -nojournal -nolog -mode batch -source program.tcl
+
+$(PROJECT).mcs $(PROJECT).prm: $(PROJECT).bit
+ echo "write_cfgmem -force -format mcs -size 16 -interface SPIx4 -loadbit {up 0x0000000 $*.bit} -checksum -file $*.mcs" > generate_mcs.tcl
+ echo "exit" >> generate_mcs.tcl
+ vivado -nojournal -nolog -mode batch -source generate_mcs.tcl
+ mkdir -p rev
+ COUNT=100; \
+ while [ -e rev/$*_rev$$COUNT.bit ]; \
+ do COUNT=$$((COUNT+1)); done; \
+ COUNT=$$((COUNT-1)); \
+ for x in .mcs .prm; \
+ do cp $*$$x rev/$*_rev$$COUNT$$x; \
+ echo "Output: rev/$*_rev$$COUNT$$x"; done;
diff --git a/rtl/sampler/tests/sampler_main_tb.sv b/rtl/sampler/tests/sampler_main_tb.sv
new file mode 100644
index 0000000..6ee619a
--- /dev/null
+++ b/rtl/sampler/tests/sampler_main_tb.sv
@@ -0,0 +1,132 @@
+`timescale 1ns / 1ps
+
+module sampler_tb;
+
+ parameter DATA_WIDTH = 12;
+ parameter PROCESS_MODE = 0;
+ parameter CLK_PERIOD = 15.3846;
+ parameter TEST_NUM = 1000;
+
+ logic clk;
+ logic rst;
+
+ logic [DATA_WIDTH-1:0] data_in;
+ logic out_of_range;
+
+ logic [DATA_WIDTH-1:0] m_axis_tdata;
+ logic m_axis_tvalid;
+
+ integer errors = 0;
+
+ sampler #(
+ .DATA_WIDTH(DATA_WIDTH),
+ .PROCESS_MODE(PROCESS_MODE)
+ ) dut (
+ .clk_in(clk),
+ .rst(rst),
+ .data_in(data_in),
+ .out_of_range(out_of_range),
+ .m_axis_tdata(m_axis_tdata),
+ .m_axis_tvalid(m_axis_tvalid)
+ );
+
+ initial begin
+ clk = 0;
+ forever #(CLK_PERIOD/2) clk = ~clk;
+ end
+
+ function automatic [DATA_WIDTH-1:0] ref_convert(input [DATA_WIDTH-1:0] din);
+ if (PROCESS_MODE == 0)
+ return din;
+ else if (din == {1'b1, {(DATA_WIDTH-1){1'b0}}})
+ return din;
+ else
+ return din[DATA_WIDTH-1] ?
+ {1'b1, (~din[DATA_WIDTH-2:0] + 1'b1)} :
+ din;
+ endfunction
+
+ task send(input [DATA_WIDTH-1:0] word, input bit oor);
+ @(posedge clk);
+ data_in <= word;
+ out_of_range <= oor;
+ endtask
+
+ logic [DATA_WIDTH-1:0] exp_d0, exp_d1, exp_d2;
+ logic oor_d0, oor_d1, oor_d2;
+
+ initial begin
+ $display("\n=== RANDOM SAMPLER TEST===\n");
+
+ rst = 1;
+ data_in = 0;
+ out_of_range = 0;
+
+ exp_d0 = 0;
+ exp_d1 = 0;
+ exp_d2 = 0;
+
+ oor_d0 = 1;
+ oor_d1 = 1;
+ oor_d2 = 1;
+
+ repeat(5) @(posedge clk);
+ rst = 0;
+ repeat(2) @(posedge clk);
+
+ repeat (TEST_NUM) begin
+ logic [DATA_WIDTH-1:0] rand_data;
+ bit rand_oor;
+
+ rand_data = $urandom_range(0, (1 << DATA_WIDTH) - 1);
+ rand_oor = ($urandom_range(0, 99) < 20);
+
+ @(negedge clk);
+
+ if (!oor_d2) begin
+ if (m_axis_tvalid !== 1) begin
+ $display("ERROR: valid=0");
+ errors++;
+ end
+
+ if (m_axis_tdata !== exp_d2) begin
+ $display("ERROR: data mismatch");
+ $display(" expected = %h", exp_d2);
+ $display(" got = %h", m_axis_tdata);
+ errors++;
+ end
+ end
+
+ send(rand_data, rand_oor);
+
+ exp_d2 = exp_d1;
+ exp_d1 = exp_d0;
+ exp_d0 = ref_convert(rand_data);
+
+ oor_d2 = oor_d1;
+ oor_d1 = oor_d0;
+ oor_d0 = rand_oor;
+ end
+
+ @(posedge clk);
+
+
+ if (!oor_d2) begin
+
+ if (m_axis_tdata !== exp_d2) begin
+ $display("ERROR: final mismatch");
+ $display(" expected = %h", exp_d2);
+ $display(" got = %h", m_axis_tdata);
+ errors++;
+ end
+ end
+
+ if (errors == 0)
+ $display("\n========== ALL PASSED ==========\n");
+ else
+ $display("\n========== FAILED: %0d errors ==========\n", errors);
+
+ $finish;
+ end
+
+endmodule
\ No newline at end of file
diff --git a/rtl/sampler/tests/sampler_tb_advanced.sv b/rtl/sampler/tests/sampler_tb_advanced.sv
deleted file mode 100644
index 601f0fd..0000000
--- a/rtl/sampler/tests/sampler_tb_advanced.sv
+++ /dev/null
@@ -1,120 +0,0 @@
-`timescale 1ns / 1ps
-
-module sampler_tb;
-
- parameter DATA_WIDTH = 12;
- parameter PACK_FACTOR = 3;
- parameter PROCESS_MODE = 1;
- parameter CLK_PERIOD = 15.3846; // 65 MHz
-
- logic clk;
- logic rst;
- logic [DATA_WIDTH-1:0] data_in;
- logic out_of_range;
- logic [DATA_WIDTH*PACK_FACTOR-1:0] m_axis_tdata;
- logic m_axis_tvalid;
-
- sampler #(
- .DATA_WIDTH(DATA_WIDTH),
- .PACK_FACTOR(PACK_FACTOR),
- .PROCESS_MODE(PROCESS_MODE)
- ) dut (
- .clk_in(clk),
- .rst(rst),
- .data_in(data_in),
- .out_of_range(out_of_range),
- .m_axis_tdata(m_axis_tdata),
- .m_axis_tvalid(m_axis_tvalid)
- );
-
- initial begin
- clk = 0;
- forever #(CLK_PERIOD/2) clk = ~clk;
- end
-
- task send(input [DATA_WIDTH-1:0] word, input bit oor);
- @(posedge clk);
- data_in <= word;
- out_of_range <= oor;
- $display("Send: %h (%0d) OOR=%b", word, word, oor);
- endtask
-
- initial begin
- $display("\n=== SAMPLER TEST (MODE=%0d) ===\n", PROCESS_MODE);
-
- // Reset
- rst = 1;
- out_of_range = 0;
- data_in = 0;
-// send(12'h001, 0);
- repeat(5) @(posedge clk);
- rst = 0;
- send(12'h001, 0);
- repeat(1) @(posedge clk);
-
- // 1. Positive
- $display("\n--- Positive numbers ---");
-// send(12'h001, 0);
- send(12'h002, 0);
- send(12'h003, 0);
-
- send(12'h004, 0);
- send(12'h005, 0);
- send(12'h806, 0);
-
- // 2. Negative
- $display("\n--- Negative numbers ---");
- send(12'hFFF, 0); // -1
- send(12'hFFE, 0); // -2
- send(12'hFFD, 0); // -3
-
- send(12'h800, 0); // -2048
- send(12'h801, 0); // -2047
- send(12'h802, 0); // -2046
-
- // 3. Boundary
- $display("\n--- Boundary values ---");
- send(12'h000, 0); // 0
- send(12'h001, 0); // 1
- send(12'h7FF, 0); // 2047 (max positive)
-
- send(12'h7FE, 0); // 2046
- send(12'h800, 0); // -2048 (min negative)
- send(12'hFFF, 0); // -1
-
- // 4. Out of range tests
- $display("\n--- Out of range tests ---");
-
-
- send(12'h00A, 0);
- send(12'h00B, 1); //
- send(12'h00C, 0);
- send(12'h00D, 0);
- send(12'h00E, 0);
- send(12'h00F, 0);
-
- send(12'h010, 0);
- send(12'h011, 0);
- send(12'h012, 1); //
-
- send(12'h013, 0);
- send(12'h014, 0);
- send(12'h015, 0);
-
- repeat(10) @(posedge clk);
- $display("\n=== TEST FINISHED ===");
- $finish;
- end
-
- // Results
- always @(posedge clk) begin
- if (m_axis_tvalid) begin
- $display("\n>>> PACKET RECEIVED at %0t ns:", $time);
- $display(" Full: %h", m_axis_tdata);
- $display(" Word0: %h", m_axis_tdata[11:0]);
- $display(" Word1: %h", m_axis_tdata[23:12]);
- $display(" Word2: %h\n", m_axis_tdata[35:24]);
- end
- end
-
-endmodule
\ No newline at end of file
diff --git a/rtl/sampler/tests/sampler_tb_basic.sv b/rtl/sampler/tests/sampler_tb_basic.sv
deleted file mode 100644
index f574ce3..0000000
--- a/rtl/sampler/tests/sampler_tb_basic.sv
+++ /dev/null
@@ -1,67 +0,0 @@
-`timescale 1ns / 1ps
-
-module sampler_tb;
-
- parameter DATA_WIDTH = 12;
- parameter PACK_FACTOR = 3;
- parameter PROCESS_MODE = 0;
-
- parameter CLK_PERIOD = 15.3846;
-
- logic clk;
- logic rst;
- logic [DATA_WIDTH-1:0] data_in;
- logic out_of_range;
-
- logic [DATA_WIDTH*PACK_FACTOR-1:0] m_axis_tdata;
- logic m_axis_tvalid;
-
- // DUT
- sampler #(
- .DATA_WIDTH(DATA_WIDTH),
- .PACK_FACTOR(PACK_FACTOR),
- .PROCESS_MODE(PROCESS_MODE)
- ) dut (
- .clk_in(clk),
- .rst(rst),
- .data_in(data_in),
- .out_of_range(out_of_range),
- .m_axis_tdata(m_axis_tdata),
- .m_axis_tvalid(m_axis_tvalid)
- );
-
- // clock
- initial begin
- clk = 0;
- forever #(CLK_PERIOD/2) clk = ~clk;
- end
-
- integer i;
-
- initial begin
- clk = 0;
- rst = 1;
- data_in = 0;
- out_of_range = 0;
-
- #20;
- rst = 0;
- repeat(5) @(posedge clk);
-
- for (i = 1; i < 20; i++) begin
- @(posedge clk);
-
- data_in <= i;
- end
-
- #50;
- $finish;
- end
-
- always @(posedge clk) begin
- if (m_axis_tvalid) begin
- $display("TIME=%0t PACKED DATA = %h", $time, m_axis_tdata);
- end
- end
-
-endmodule
\ No newline at end of file
diff --git a/scripts/vivado.mk b/scripts/vivado.mk
index 358973e..ec67ca6 100644
--- a/scripts/vivado.mk
+++ b/scripts/vivado.mk
@@ -180,6 +180,8 @@ sim: $(PROJECT).xpr gen_ip
echo "update_compile_order -fileset sources_1" >> run_sim.tcl
echo "update_compile_order -fileset sim_1" >> run_sim.tcl
echo "launch_simulation" >> run_sim.tcl
+ echo "run 10000 us" >> run_sim.tcl
+ echo "quit" >> run_sim.tcl
vivado -mode batch -source run_sim.tcl
simclean: