Compare commits
4 Commits
dev/debug
...
8e46f965df
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e46f965df | |||
| 7f9ad95e68 | |||
| 4786d2d7f6 | |||
| 58500b7549 |
193
rtl/accum/src/out_axis_fifo.sv
Normal file
193
rtl/accum/src/out_axis_fifo.sv
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
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] s_axis_tdata,
|
||||||
|
output logic s_axis_tvalid,
|
||||||
|
input logic s_axis_tready,
|
||||||
|
output logic s_axis_tlast,
|
||||||
|
|
||||||
|
// 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
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 rd_unavail;
|
||||||
|
wire wr_unavail;
|
||||||
|
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!
|
||||||
|
|
||||||
|
// 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 or posedge rst) begin
|
||||||
|
if (rst) 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;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// wait until we can request a word
|
||||||
|
// depends on prog_full signal
|
||||||
|
WR_CHECK: begin
|
||||||
|
if (~wr_unavail) 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 (din_valid) 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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
finish <= 1;
|
||||||
|
wr_state <= WR_IDLE;
|
||||||
|
end else begin
|
||||||
|
// next word
|
||||||
|
wr_state <= WR_CHECK;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
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("1606"), // String
|
||||||
|
.WRITE_DATA_WIDTH(ACCUM_WIDTH),
|
||||||
|
.WR_DATA_COUNT_WIDTH(WDEPTH_BITS)
|
||||||
|
)
|
||||||
|
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
|
||||||
|
// output bus (dout).
|
||||||
|
|
||||||
|
.dout(s_axis_tdata),
|
||||||
|
.empty( ),
|
||||||
|
|
||||||
|
.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
|
||||||
|
// 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_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
|
||||||
|
// 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(acc_din), // 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(din_valid)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
52
rtl/accum/tests/Makefile
Normal file
52
rtl/accum/tests/Makefile
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# Copyright (c) 2025 FPGA Ninja, LLC
|
||||||
|
#
|
||||||
|
# Authors:
|
||||||
|
# - Alex Forencich
|
||||||
|
#
|
||||||
|
|
||||||
|
# FPGA settings
|
||||||
|
FPGA_PART = xc7a35tfgg484-1
|
||||||
|
FPGA_TOP = control
|
||||||
|
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 += controller_tb.sv
|
||||||
|
# SIM_TOP = control_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;
|
||||||
198
rtl/accum/tests/out_axis_fifo_tb.sv
Normal file
198
rtl/accum/tests/out_axis_fifo_tb.sv
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
module tb_out_axis_fifo;
|
||||||
|
|
||||||
|
localparam int ACCUM_WIDTH = 32;
|
||||||
|
localparam int WINDOW_SIZE = 65;
|
||||||
|
localparam int PACKET_SIZE = 1024;
|
||||||
|
|
||||||
|
logic eth_clk_in;
|
||||||
|
logic acc_clk_in;
|
||||||
|
logic rst;
|
||||||
|
|
||||||
|
logic [31:0] smp_num;
|
||||||
|
|
||||||
|
logic [7:0] s_axis_tdata;
|
||||||
|
logic s_axis_tvalid;
|
||||||
|
logic s_axis_tready;
|
||||||
|
logic s_axis_tlast;
|
||||||
|
|
||||||
|
logic [ACCUM_WIDTH-1:0] acc_din;
|
||||||
|
logic din_valid;
|
||||||
|
|
||||||
|
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),
|
||||||
|
|
||||||
|
.s_axis_tdata (s_axis_tdata),
|
||||||
|
.s_axis_tvalid (s_axis_tvalid),
|
||||||
|
.s_axis_tready (s_axis_tready),
|
||||||
|
.s_axis_tlast (s_axis_tlast),
|
||||||
|
|
||||||
|
.acc_din (acc_din),
|
||||||
|
.din_valid (din_valid),
|
||||||
|
|
||||||
|
.readout_begin (readout_begin),
|
||||||
|
|
||||||
|
.batch_req (batch_req),
|
||||||
|
.finish (finish)
|
||||||
|
);
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// clocks
|
||||||
|
// -----------------------------
|
||||||
|
initial begin
|
||||||
|
eth_clk_in = 0;
|
||||||
|
forever #4 eth_clk_in = ~eth_clk_in; // 125 MHz
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
acc_clk_in = 0;
|
||||||
|
forever #3 acc_clk_in = ~acc_clk_in; // ~166.7 MHz
|
||||||
|
end
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// simple AXIS sink
|
||||||
|
// -----------------------------
|
||||||
|
initial begin
|
||||||
|
s_axis_tready = 1'b1;
|
||||||
|
end
|
||||||
|
|
||||||
|
// У DUT нет своей логики rd_en, поэтому для теста подадим её force-ом.
|
||||||
|
initial begin
|
||||||
|
force dut.rd_en = dut.s_axis_tvalid && s_axis_tready;
|
||||||
|
end
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// helpers
|
||||||
|
// -----------------------------
|
||||||
|
task automatic do_reset();
|
||||||
|
begin
|
||||||
|
rst = 1'b1;
|
||||||
|
readout_begin = 1'b0;
|
||||||
|
din_valid = 1'b0;
|
||||||
|
acc_din = '0;
|
||||||
|
smp_num = '0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
begin
|
||||||
|
for (i = 0; i < n_words; i++) begin
|
||||||
|
@(posedge acc_clk_in);
|
||||||
|
din_valid <= 1'b1;
|
||||||
|
acc_din <= $urandom;
|
||||||
|
end
|
||||||
|
|
||||||
|
@(posedge acc_clk_in);
|
||||||
|
din_valid <= 1'b0;
|
||||||
|
acc_din <= '0;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// Один запуск:
|
||||||
|
// 1) задаём smp_num
|
||||||
|
// 2) даём readout_begin
|
||||||
|
// 3) каждый раз, когда DUT просит batch_req, отправляем PACKET_SIZE слов
|
||||||
|
// 4) ждём finish
|
||||||
|
task automatic run_case(input logic [31:0] smp_num_i);
|
||||||
|
int batch_count;
|
||||||
|
begin
|
||||||
|
batch_count = 0;
|
||||||
|
|
||||||
|
$display("[%0t] run_case start, smp_num=%0d", $time, smp_num_i);
|
||||||
|
|
||||||
|
pulse_readout_begin(smp_num_i);
|
||||||
|
|
||||||
|
while (finish !== 1'b1) begin
|
||||||
|
@(posedge acc_clk_in);
|
||||||
|
|
||||||
|
if (batch_req) begin
|
||||||
|
batch_count++;
|
||||||
|
$display("[%0t] batch_req #%0d -> send %0d words",
|
||||||
|
$time, batch_count, PACKET_SIZE / ACCUM_WIDTH * 8);
|
||||||
|
|
||||||
|
// send packets to accomplish 1kb packet.
|
||||||
|
send_random_words(PACKET_SIZE / ACCUM_WIDTH * 8);
|
||||||
|
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);
|
||||||
|
|
||||||
|
@(posedge acc_clk_in);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// monitor
|
||||||
|
// -----------------------------
|
||||||
|
int axis_byte_count;
|
||||||
|
|
||||||
|
always_ff @(posedge eth_clk_in or posedge rst) begin
|
||||||
|
if (rst) begin
|
||||||
|
axis_byte_count <= 0;
|
||||||
|
end else begin
|
||||||
|
if (s_axis_tvalid && s_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;
|
||||||
|
|
||||||
|
// 1-й запуск
|
||||||
|
do_reset();
|
||||||
|
run_case(32'd17);
|
||||||
|
|
||||||
|
// 2-й запуск
|
||||||
|
do_reset();
|
||||||
|
run_case(32'd1024);
|
||||||
|
|
||||||
|
// 3-й запуск
|
||||||
|
do_reset();
|
||||||
|
run_case(32'd77777);
|
||||||
|
|
||||||
|
do_reset();
|
||||||
|
repeat (50) @(posedge acc_clk_in);
|
||||||
|
$display("[%0t] ALL TESTS DONE", $time);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
Reference in New Issue
Block a user