module out_axis_fifo #( parameter ACCUM_WIDTH = 32, parameter RW_WIDTH = 32, parameter PACKET_SIZE = 256 ) ( input logic dma_clk_in, input logic acc_clk_in, input logic rst, input logic [31:0] smp_num, input logic [31:0] window_size, // AXI stream master for output, dma_clk_in domain output logic [RW_WIDTH-1: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 / RW_WIDTH; localparam int RDEPTH_BITS = $clog2(FIFO_RDEPTH) + 1; localparam int WR_WORD_BYTES = ACCUM_WIDTH/8; localparam int RD_WORD_BYTES = RW_WIDTH/8; localparam int PACKET_WR_WORDS = PACKET_SIZE / WR_WORD_BYTES; localparam int PACKET_RD_WORDS = PACKET_SIZE / RD_WORD_BYTES; 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! logic [31:0] window_size_reg; wire [31:0] window_size_safe = (window_size == 32'd0) ? 32'd1 : window_size; wire empty; wire [WDEPTH_BITS:0] wr_data_count; // NOTE: // each written "acc_din" ACCUM_WIDTH word // is counted as window_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; window_size_reg <= 32'd1; 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; window_size_reg <= window_size_safe; 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_WR_WORDS)) && ~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 window_size samples !!! wr_batch_tgt <= wr_batch_tgt + (8 * window_size_reg * 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_reg; 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 dma_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_RD_WORDS) 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_RD_WORDS-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_RD_WORDS), .PROG_FULL_THRESH(PACKET_WR_WORDS), .RD_DATA_COUNT_WIDTH(RDEPTH_BITS), .READ_DATA_WIDTH(RW_WIDTH), // 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(dma_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