6 Commits

Author SHA1 Message Date
b54e69dec0 chore: add clocks for accum_fifo impl test 2026-04-21 19:48:20 +03:00
7be26d9d1a chore: update tb files 2026-04-21 19:47:56 +03:00
3dcaaf8ea5 fix: better sync for accum fifo 2026-04-21 19:47:46 +03:00
dfccc01225 tests: auto tb for out_axis_fifo 2026-04-21 19:47:27 +03:00
4eb937e13f infra: make default sim longer 2026-04-21 19:46:51 +03:00
21785aaac7 rtl: send part of out_axis_fifo 2026-04-21 17:26:02 +03:00
5 changed files with 341 additions and 108 deletions

View File

@ -13,6 +13,10 @@ module out_axis_fifo #(
output logic s_axis_tvalid, output logic s_axis_tvalid,
input logic s_axis_tready, input logic s_axis_tready,
output logic s_axis_tlast, output logic s_axis_tlast,
// eth handshake
input logic req_ready,
output logic send_req,
output logic [15:0] udp_data_length,
// data from acc // data from acc
input logic [ACCUM_WIDTH-1:0] acc_din, input logic [ACCUM_WIDTH-1:0] acc_din,
@ -25,6 +29,32 @@ module out_axis_fifo #(
output logic batch_req, output logic batch_req,
output logic finish 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 // fifo params calc
// round up to be enough for 2xPACKET_SIZE storage // round up to be enough for 2xPACKET_SIZE storage
@ -37,9 +67,8 @@ module out_axis_fifo #(
localparam int FIFO_RDEPTH = FIFO_WDEPTH * ACCUM_WIDTH / 8; localparam int FIFO_RDEPTH = FIFO_WDEPTH * ACCUM_WIDTH / 8;
localparam int RDEPTH_BITS = $clog2(FIFO_RDEPTH) + 1; localparam int RDEPTH_BITS = $clog2(FIFO_RDEPTH) + 1;
wire rd_unavail;
wire wr_unavail; wire wr_unavail;
wire wr_rst_busy;
reg rd_en; reg rd_en;
@ -57,14 +86,16 @@ module out_axis_fifo #(
reg [31:0] wr_batch_tgt; // next 'target' that should be written from batch reg [31:0] wr_batch_tgt; // next 'target' that should be written from batch
reg [31:0] wr_total; // total BITS to be sent! reg [31:0] wr_total; // total BITS to be sent!
wire [WDEPTH_BITS:0] wr_data_count;
// NOTE: // NOTE:
// each written "acc_din" ACCUM_WIDTH word // each written "acc_din" ACCUM_WIDTH word
// is counted as WINDOWS_SIZE samples actually // is counted as WINDOWS_SIZE samples actually
// because hw division for counters is painful // because hw division for counters is painful
// so we just increased the counter sizes // so we just increased the counter sizes
always_ff @(posedge acc_clk_in or posedge rst) begin always_ff @(posedge acc_clk_in) begin
if (rst) begin if (rst_acc) begin
wr_state <= WR_IDLE; wr_state <= WR_IDLE;
wr_cnt <= 32'b0; wr_cnt <= 32'b0;
wr_batch_tgt <= 32'b0; wr_batch_tgt <= 32'b0;
@ -82,13 +113,15 @@ module out_axis_fifo #(
wr_state <= WR_CHECK; wr_state <= WR_CHECK;
wr_total <= smp_num * ACCUM_WIDTH; wr_total <= smp_num * ACCUM_WIDTH;
wr_batch_tgt <= 32'b0; wr_batch_tgt <= 32'b0;
batch_req <= 0;
finish <= 0;
end end
end end
// wait until we can request a word // wait until we can request a word
// depends on prog_full signal // depends on prog_full signal
WR_CHECK: begin WR_CHECK: begin
if (~wr_unavail) begin if (~wr_unavail && ~wr_rst_busy) begin
batch_req <= 1; batch_req <= 1;
// should give us exactly PACKET_SIZE * 8 bits // should give us exactly PACKET_SIZE * 8 bits
// multiplied by WINDOW_SIZE, because we count // multiplied by WINDOW_SIZE, because we count
@ -103,11 +136,7 @@ module out_axis_fifo #(
// wait until all requested packet is written // wait until all requested packet is written
WR_RUN: begin WR_RUN: begin
batch_req <= 0; batch_req <= 0;
if (din_valid) begin if (wr_cnt == wr_batch_tgt) begin
// data supplied
// count as we got WINDOW_SIZE samples
wr_cnt <= wr_cnt + ACCUM_WIDTH * WINDOW_SIZE;
end else if (wr_cnt == wr_batch_tgt) begin
// got enough words // got enough words
wr_state <= WR_END; wr_state <= WR_END;
end else if (wr_cnt > wr_batch_tgt) begin end else if (wr_cnt > wr_batch_tgt) begin
@ -116,6 +145,12 @@ module out_axis_fifo #(
wr_cnt <= 32'hffffffff; // sort of signal for sim/ila wr_cnt <= 32'hffffffff; // sort of signal for sim/ila
wr_state <= WR_END; wr_state <= WR_END;
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 end
// check if this was last data batch // check if this was last data batch
@ -123,8 +158,11 @@ module out_axis_fifo #(
// here we check that we sent enough data // here we check that we sent enough data
// wr_cnt should be by design PACKET_SIZE-aligned // wr_cnt should be by design PACKET_SIZE-aligned
if (wr_cnt >= wr_total) begin if (wr_cnt >= wr_total) begin
// wait until all data is sent
if (wr_data_count == 0) begin
finish <= 1; finish <= 1;
wr_state <= WR_IDLE; wr_state <= WR_IDLE;
end
end else begin end else begin
// next word // next word
wr_state <= WR_CHECK; wr_state <= WR_CHECK;
@ -134,6 +172,99 @@ module out_axis_fifo #(
end end
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;
s_axis_tlast <= 1'b0;
s_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;
s_axis_tlast <= 1'b0;
s_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 (s_axis_tready && rd_valid) begin
rd_en <= 1'b1;
s_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;
s_axis_tlast <= 1'b1;
end
end else begin
rd_en <= 1'b0;
s_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 // xpm_fifo_async: Asynchronous FIFO
// Xilinx Parameterized Macro, version 2025.1 // Xilinx Parameterized Macro, version 2025.1
@ -148,13 +279,13 @@ module out_axis_fifo #(
.READ_DATA_WIDTH(8), // always 8 bit for eth .READ_DATA_WIDTH(8), // always 8 bit for eth
.READ_MODE("fwft"), .READ_MODE("fwft"),
.SIM_ASSERT_CHK(1), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages .SIM_ASSERT_CHK(1), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
.USE_ADV_FEATURES("1606"), // String .USE_ADV_FEATURES("1616"), // String
.WRITE_DATA_WIDTH(ACCUM_WIDTH), .WRITE_DATA_WIDTH(ACCUM_WIDTH),
.WR_DATA_COUNT_WIDTH(WDEPTH_BITS) .WR_DATA_COUNT_WIDTH(WDEPTH_BITS+1)
) )
xpm_fifo_async_inst ( xpm_fifo_async_inst (
.data_valid(s_axis_tvalid), // 1-bit output: Read Data Valid: When asserted, this signal indicates that valid data is available on the .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). // output bus (dout).
.dout(s_axis_tdata), .dout(s_axis_tdata),
@ -162,17 +293,13 @@ module out_axis_fifo #(
.full( ), .full( ),
.prog_empty(rd_unavail), // 1-bit output: Programmable Empty: This signal is asserted when the number of words in the FIFO is less than
// or equal to the programmable empty threshold value. It is de-asserted when the number of words in the FIFO
// exceeds the programmable empty threshold value.
.prog_full(wr_unavail), // 1-bit output: Programmable Full: This signal is asserted when the number of words in the FIFO is greater than .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 // 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. // less than the programmable full threshold value.
.rd_data_count( ), // RD_DATA_COUNT_WIDTH-bit output: Read Data Count: This bus indicates the number of words read from the FIFO. .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_WIDTH-bit output: Write Data Count: This bus indicates the number of words written into the .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. // FIFO.
@ -184,9 +311,11 @@ module out_axis_fifo #(
.rst(rst), .rst(rst),
.din(acc_din), // WRITE_DATA_WIDTH-bit input: Write Data: The input data bus used when writing the FIFO. .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_clk(acc_clk_in), // 1-bit input: Write clock: Used for write operation. wr_clk must be a free running clock.
.wr_en(din_valid) .wr_en(fifo_wr_en_r),
.wr_rst_busy(wr_rst_busy)
); );

View File

@ -8,7 +8,7 @@
# FPGA settings # FPGA settings
FPGA_PART = xc7a35tfgg484-1 FPGA_PART = xc7a35tfgg484-1
FPGA_TOP = control FPGA_TOP = accum
FPGA_ARCH = artix7 FPGA_ARCH = artix7
RTL_DIR = ../src RTL_DIR = ../src
@ -21,10 +21,10 @@ SYN_FILES += $(sort $(shell find ../src -type f \( -name '*.v' -o -name '*.sv' \
XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci')) XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci'))
XDC_FILES += ../../../constraints/ax7a035b.xdc XDC_FILES += ../../../constraints/ax7a035b.xdc
# XDC_FILES += test_timing.xdc XDC_FILES += test_timing.xdc
# SYN_FILES += controller_tb.sv SYN_FILES += out_axis_fifo_tb.sv
# SIM_TOP = control_tb SIM_TOP = control_tb
program: $(PROJECT).bit program: $(PROJECT).bit

View File

@ -5,6 +5,8 @@ module tb_out_axis_fifo;
localparam int ACCUM_WIDTH = 32; localparam int ACCUM_WIDTH = 32;
localparam int WINDOW_SIZE = 65; localparam int WINDOW_SIZE = 65;
localparam int PACKET_SIZE = 1024; localparam int PACKET_SIZE = 1024;
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 eth_clk_in;
logic acc_clk_in; logic acc_clk_in;
@ -20,6 +22,9 @@ module tb_out_axis_fifo;
logic [ACCUM_WIDTH-1:0] acc_din; logic [ACCUM_WIDTH-1:0] acc_din;
logic din_valid; logic din_valid;
logic send_req;
logic req_ready;
logic readout_begin; logic readout_begin;
logic batch_req; logic batch_req;
@ -45,38 +50,89 @@ module tb_out_axis_fifo;
.readout_begin (readout_begin), .readout_begin (readout_begin),
.req_ready (req_ready),
.send_req (send_req),
.batch_req (batch_req), .batch_req (batch_req),
.finish (finish) .finish (finish)
); );
// -----------------------------
// clocks // clocks
// -----------------------------
initial begin initial begin
eth_clk_in = 0; eth_clk_in = 0;
forever #4 eth_clk_in = ~eth_clk_in; // 125 MHz forever #6 eth_clk_in = ~eth_clk_in; // 125
end end
initial begin initial begin
acc_clk_in = 0; acc_clk_in = 0;
forever #3 acc_clk_in = ~acc_clk_in; // ~166.7 MHz forever #7.692307692 acc_clk_in = ~acc_clk_in; // 65
end end
// ----------------------------- // scoreboard
// simple AXIS sink byte expected_bytes[$];
// ----------------------------- int unsigned compared_bytes;
initial begin int unsigned mismatch_count;
s_axis_tready = 1'b1; 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 (s_axis_tvalid && s_axis_tready) begin
if (expected_bytes.size() == 0) begin
$error("[%0t] AXIS produced unexpected byte 0x%02x: expected queue is empty",
$time, s_axis_tdata);
mismatch_count <= mismatch_count + 1;
end else begin
exp_byte = expected_bytes.pop_front();
compared_bytes <= compared_bytes + 1;
if (s_axis_tdata !== exp_byte) begin
$error("[%0t] AXIS mismatch at byte #%0d: got=0x%02x expected=0x%02x",
$time, compared_bytes, s_axis_tdata, exp_byte);
mismatch_count <= mismatch_count + 1;
end
end
end
end
end end
// У DUT нет своей логики rd_en, поэтому для теста подадим её force-ом.
initial begin
force dut.rd_en = dut.s_axis_tvalid && s_axis_tready;
end
// -----------------------------
// helpers // helpers
// -----------------------------
task automatic do_reset(); task automatic do_reset();
begin begin
rst = 1'b1; rst = 1'b1;
@ -85,6 +141,8 @@ module tb_out_axis_fifo;
acc_din = '0; acc_din = '0;
smp_num = '0; smp_num = '0;
scoreboard_reset();
repeat (10) @(posedge acc_clk_in); repeat (10) @(posedge acc_clk_in);
rst = 1'b0; rst = 1'b0;
@ -104,11 +162,17 @@ module tb_out_axis_fifo;
task automatic send_random_words(input int unsigned n_words); task automatic send_random_words(input int unsigned n_words);
int unsigned i; int unsigned i;
logic [ACCUM_WIDTH-1:0] rand_word;
begin begin
for (i = 0; i < n_words; i++) begin for (i = 0; i < n_words; i++) begin
rand_word = $urandom;
@(posedge acc_clk_in); @(posedge acc_clk_in);
din_valid <= 1'b1; din_valid <= 1'b1;
acc_din <= $urandom; acc_din <= rand_word;
// expected result
push_expected_word(rand_word);
end end
@(posedge acc_clk_in); @(posedge acc_clk_in);
@ -117,17 +181,20 @@ module tb_out_axis_fifo;
end end
endtask endtask
// Один запуск:
// 1) задаём smp_num // 1. set smp_num
// 2) даём readout_begin // 2. pulse readout_begon
// 3) каждый раз, когда DUT просит batch_req, отправляем PACKET_SIZE слов // 3. send 1KB (PACKET_SIZE) after each batch_req pulse
// 4) ждём finish // 4. wait for finish
// 5. compare axis result
task automatic run_case(input logic [31:0] smp_num_i); task automatic run_case(input logic [31:0] smp_num_i);
int batch_count; int batch_count;
string case_name;
begin begin
batch_count = 0; batch_count = 0;
case_name = $sformatf("run_case(smp_num=%0d)", smp_num_i);
$display("[%0t] run_case start, smp_num=%0d", $time, smp_num_i); $display("[%0t] %s start", $time, case_name);
pulse_readout_begin(smp_num_i); pulse_readout_begin(smp_num_i);
@ -136,39 +203,57 @@ module tb_out_axis_fifo;
if (batch_req) begin if (batch_req) begin
batch_count++; batch_count++;
$display("[%0t] batch_req #%0d -> send %0d words", $display("[%0t] %s: batch_req #%0d -> send %0d words",
$time, batch_count, PACKET_SIZE / ACCUM_WIDTH * 8); $time, case_name, batch_count, WORDS_PER_BATCH);
// send packets to accomplish 1kb packet. send_random_words(WORDS_PER_BATCH);
send_random_words(PACKET_SIZE / ACCUM_WIDTH * 8);
end end
end end
$display("[%0t] run_case done, smp_num=%0d, batches=%0d, wr_cnt=%0d, wr_total=%0d",
$time, smp_num_i, batch_count, dut.wr_cnt, dut.wr_total); 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); @(posedge acc_clk_in);
end end
endtask endtask
// ----------------------------- // eth beh simulator
// monitor
// -----------------------------
int axis_byte_count; int axis_byte_count;
always_ff @(posedge eth_clk_in or posedge rst) begin always_ff @(posedge eth_clk_in or posedge rst) begin
if (rst) begin if (rst) begin
axis_byte_count <= 0; axis_byte_count <= 0;
req_ready <= 0;
s_axis_tready <= 1'b0;
end else begin end else begin
req_ready <= 1;
// request send
if (send_req) begin
s_axis_tready <= 1'b1;
req_ready <= 0;
end
if (s_axis_tvalid && s_axis_tready) begin if (s_axis_tvalid && s_axis_tready) begin
axis_byte_count <= axis_byte_count + 1; axis_byte_count <= axis_byte_count + 1;
end end
end end
end end
// -----------------------------
// main // main
// -----------------------------
initial begin initial begin
// init // init
rst = 1'b0; rst = 1'b0;
@ -177,20 +262,27 @@ module tb_out_axis_fifo;
acc_din = '0; acc_din = '0;
smp_num = '0; smp_num = '0;
// 1-й запуск repeat (500) @(posedge acc_clk_in);
do_reset();
run_case(32'd17);
// 2-й запуск // 1
do_reset();
repeat (500) @(posedge acc_clk_in);
run_case(32'd17);
repeat (20) @(posedge acc_clk_in);
// 2
do_reset(); do_reset();
run_case(32'd1024); run_case(32'd1024);
repeat (20) @(posedge acc_clk_in);
// 3-й запуск // 3
do_reset(); do_reset();
run_case(32'd77777); run_case(32'd77777);
repeat (20) @(posedge acc_clk_in);
do_reset(); do_reset();
repeat (50) @(posedge acc_clk_in); repeat (20) @(posedge acc_clk_in);
$display("[%0t] ALL TESTS DONE", $time); $display("[%0t] ALL TESTS DONE", $time);
$finish; $finish;
end end

View File

@ -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 acc_clk_in]
# Asynchronous clock groups
set_clock_groups -name ASYNC_ETH_ACC -asynchronous \
-group [get_clocks eth_clk] \
-group [get_clocks acc_clk]

View File

@ -180,6 +180,8 @@ sim: $(PROJECT).xpr gen_ip
echo "update_compile_order -fileset sources_1" >> run_sim.tcl echo "update_compile_order -fileset sources_1" >> run_sim.tcl
echo "update_compile_order -fileset sim_1" >> run_sim.tcl echo "update_compile_order -fileset sim_1" >> run_sim.tcl
echo "launch_simulation" >> run_sim.tcl echo "launch_simulation" >> run_sim.tcl
echo "run 1000 us" >> run_sim.tcl
echo "quit" >> run_sim.tcl
vivado -mode batch -source run_sim.tcl vivado -mode batch -source run_sim.tcl
simclean: simclean: