mirror of
https://github.com/alexforencich/verilog-axi.git
synced 2025-01-14 06:42:55 +08:00
Add AXI virtual FIFO
Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
parent
99ff2e81b7
commit
59d37ee850
607
rtl/axi_vfifo.v
Normal file
607
rtl/axi_vfifo.v
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* AXI4 virtual FIFO
|
||||
*/
|
||||
module axi_vfifo #
|
||||
(
|
||||
// AXI channel count
|
||||
parameter AXI_CH = 1,
|
||||
// Width of AXI data bus in bits
|
||||
parameter AXI_DATA_WIDTH = 32,
|
||||
// Width of AXI address bus in bits
|
||||
parameter AXI_ADDR_WIDTH = 16,
|
||||
// Width of AXI wstrb (width of data bus in words)
|
||||
parameter AXI_STRB_WIDTH = (AXI_DATA_WIDTH/8),
|
||||
// Width of AXI ID signal
|
||||
parameter AXI_ID_WIDTH = 8,
|
||||
// Maximum AXI burst length to generate
|
||||
parameter AXI_MAX_BURST_LEN = 16,
|
||||
// Width of AXI stream interfaces in bits
|
||||
parameter AXIS_DATA_WIDTH = AXI_DATA_WIDTH*AXI_CH/2,
|
||||
// Use AXI stream tkeep signal
|
||||
parameter AXIS_KEEP_ENABLE = (AXIS_DATA_WIDTH>8),
|
||||
// AXI stream tkeep signal width (words per cycle)
|
||||
parameter AXIS_KEEP_WIDTH = (AXIS_DATA_WIDTH/8),
|
||||
// Use AXI stream tlast signal
|
||||
parameter AXIS_LAST_ENABLE = 1,
|
||||
// Propagate AXI stream tid signal
|
||||
parameter AXIS_ID_ENABLE = 0,
|
||||
// AXI stream tid signal width
|
||||
parameter AXIS_ID_WIDTH = 8,
|
||||
// Propagate AXI stream tdest signal
|
||||
parameter AXIS_DEST_ENABLE = 0,
|
||||
// AXI stream tdest signal width
|
||||
parameter AXIS_DEST_WIDTH = 8,
|
||||
// Propagate AXI stream tuser signal
|
||||
parameter AXIS_USER_ENABLE = 1,
|
||||
// AXI stream tuser signal width
|
||||
parameter AXIS_USER_WIDTH = 1,
|
||||
// Width of length field
|
||||
parameter LEN_WIDTH = AXI_ADDR_WIDTH,
|
||||
// Maximum segment width
|
||||
parameter MAX_SEG_WIDTH = 256,
|
||||
// Input FIFO depth for AXI write data (full-width words)
|
||||
parameter WRITE_FIFO_DEPTH = 64,
|
||||
// Max AXI write burst length
|
||||
parameter WRITE_MAX_BURST_LEN = WRITE_FIFO_DEPTH/4,
|
||||
// Output FIFO depth for AXI read data (full-width words)
|
||||
parameter READ_FIFO_DEPTH = 128,
|
||||
// Max AXI read burst length
|
||||
parameter READ_MAX_BURST_LEN = WRITE_MAX_BURST_LEN
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* AXI stream data input
|
||||
*/
|
||||
input wire s_axis_clk,
|
||||
input wire s_axis_rst,
|
||||
output wire s_axis_rst_out,
|
||||
input wire [AXIS_DATA_WIDTH-1:0] s_axis_tdata,
|
||||
input wire [AXIS_KEEP_WIDTH-1:0] s_axis_tkeep,
|
||||
input wire s_axis_tvalid,
|
||||
output wire s_axis_tready,
|
||||
input wire s_axis_tlast,
|
||||
input wire [AXIS_ID_WIDTH-1:0] s_axis_tid,
|
||||
input wire [AXIS_DEST_WIDTH-1:0] s_axis_tdest,
|
||||
input wire [AXIS_USER_WIDTH-1:0] s_axis_tuser,
|
||||
|
||||
/*
|
||||
* AXI stream data output
|
||||
*/
|
||||
input wire m_axis_clk,
|
||||
input wire m_axis_rst,
|
||||
output wire m_axis_rst_out,
|
||||
output wire [AXIS_DATA_WIDTH-1:0] m_axis_tdata,
|
||||
output wire [AXIS_KEEP_WIDTH-1:0] m_axis_tkeep,
|
||||
output wire m_axis_tvalid,
|
||||
input wire m_axis_tready,
|
||||
output wire m_axis_tlast,
|
||||
output wire [AXIS_ID_WIDTH-1:0] m_axis_tid,
|
||||
output wire [AXIS_DEST_WIDTH-1:0] m_axis_tdest,
|
||||
output wire [AXIS_USER_WIDTH-1:0] m_axis_tuser,
|
||||
|
||||
/*
|
||||
* AXI master interfaces
|
||||
*/
|
||||
input wire [AXI_CH-1:0] m_axi_clk,
|
||||
input wire [AXI_CH-1:0] m_axi_rst,
|
||||
output wire [AXI_CH*AXI_ID_WIDTH-1:0] m_axi_awid,
|
||||
output wire [AXI_CH*AXI_ADDR_WIDTH-1:0] m_axi_awaddr,
|
||||
output wire [AXI_CH*8-1:0] m_axi_awlen,
|
||||
output wire [AXI_CH*3-1:0] m_axi_awsize,
|
||||
output wire [AXI_CH*2-1:0] m_axi_awburst,
|
||||
output wire [AXI_CH-1:0] m_axi_awlock,
|
||||
output wire [AXI_CH*4-1:0] m_axi_awcache,
|
||||
output wire [AXI_CH*3-1:0] m_axi_awprot,
|
||||
output wire [AXI_CH-1:0] m_axi_awvalid,
|
||||
input wire [AXI_CH-1:0] m_axi_awready,
|
||||
output wire [AXI_CH*AXI_DATA_WIDTH-1:0] m_axi_wdata,
|
||||
output wire [AXI_CH*AXI_STRB_WIDTH-1:0] m_axi_wstrb,
|
||||
output wire [AXI_CH-1:0] m_axi_wlast,
|
||||
output wire [AXI_CH-1:0] m_axi_wvalid,
|
||||
input wire [AXI_CH-1:0] m_axi_wready,
|
||||
input wire [AXI_CH*AXI_ID_WIDTH-1:0] m_axi_bid,
|
||||
input wire [AXI_CH*2-1:0] m_axi_bresp,
|
||||
input wire [AXI_CH-1:0] m_axi_bvalid,
|
||||
output wire [AXI_CH-1:0] m_axi_bready,
|
||||
output wire [AXI_CH*AXI_ID_WIDTH-1:0] m_axi_arid,
|
||||
output wire [AXI_CH*AXI_ADDR_WIDTH-1:0] m_axi_araddr,
|
||||
output wire [AXI_CH*8-1:0] m_axi_arlen,
|
||||
output wire [AXI_CH*3-1:0] m_axi_arsize,
|
||||
output wire [AXI_CH*2-1:0] m_axi_arburst,
|
||||
output wire [AXI_CH-1:0] m_axi_arlock,
|
||||
output wire [AXI_CH*4-1:0] m_axi_arcache,
|
||||
output wire [AXI_CH*3-1:0] m_axi_arprot,
|
||||
output wire [AXI_CH-1:0] m_axi_arvalid,
|
||||
input wire [AXI_CH-1:0] m_axi_arready,
|
||||
input wire [AXI_CH*AXI_ID_WIDTH-1:0] m_axi_rid,
|
||||
input wire [AXI_CH*AXI_DATA_WIDTH-1:0] m_axi_rdata,
|
||||
input wire [AXI_CH*2-1:0] m_axi_rresp,
|
||||
input wire [AXI_CH-1:0] m_axi_rlast,
|
||||
input wire [AXI_CH-1:0] m_axi_rvalid,
|
||||
output wire [AXI_CH-1:0] m_axi_rready,
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
input wire [AXI_CH*AXI_ADDR_WIDTH-1:0] cfg_fifo_base_addr,
|
||||
input wire [LEN_WIDTH-1:0] cfg_fifo_size_mask,
|
||||
input wire cfg_enable,
|
||||
input wire cfg_reset,
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
output wire [AXI_CH*(LEN_WIDTH+1)-1:0] sts_fifo_occupancy,
|
||||
output wire [AXI_CH-1:0] sts_fifo_empty,
|
||||
output wire [AXI_CH-1:0] sts_fifo_full,
|
||||
output wire [AXI_CH-1:0] sts_reset,
|
||||
output wire [AXI_CH-1:0] sts_active,
|
||||
output wire sts_hdr_parity_err
|
||||
);
|
||||
|
||||
parameter CH_SEG_CNT = AXI_DATA_WIDTH > MAX_SEG_WIDTH ? AXI_DATA_WIDTH / MAX_SEG_WIDTH : 1;
|
||||
parameter SEG_CNT = CH_SEG_CNT * AXI_CH;
|
||||
parameter SEG_WIDTH = AXI_DATA_WIDTH / CH_SEG_CNT;
|
||||
|
||||
wire [AXI_CH-1:0] ch_input_rst_out;
|
||||
wire [AXI_CH-1:0] ch_input_watermark;
|
||||
wire [SEG_CNT*SEG_WIDTH-1:0] ch_input_data;
|
||||
wire [SEG_CNT-1:0] ch_input_valid;
|
||||
wire [SEG_CNT-1:0] ch_input_ready;
|
||||
|
||||
wire [AXI_CH-1:0] ch_output_rst_out;
|
||||
wire [SEG_CNT*SEG_WIDTH-1:0] ch_output_data;
|
||||
wire [SEG_CNT-1:0] ch_output_valid;
|
||||
wire [SEG_CNT-1:0] ch_output_ready;
|
||||
wire [SEG_CNT*SEG_WIDTH-1:0] ch_output_ctrl_data;
|
||||
wire [SEG_CNT-1:0] ch_output_ctrl_valid;
|
||||
wire [SEG_CNT-1:0] ch_output_ctrl_ready;
|
||||
|
||||
wire [AXI_CH-1:0] ch_rst_req;
|
||||
|
||||
// config management
|
||||
reg [AXI_CH*AXI_ADDR_WIDTH-1:0] cfg_fifo_base_addr_reg = 0;
|
||||
reg [LEN_WIDTH-1:0] cfg_fifo_size_mask_reg = 0;
|
||||
reg cfg_enable_reg = 0;
|
||||
reg cfg_reset_reg = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (cfg_enable_reg) begin
|
||||
if (cfg_reset) begin
|
||||
cfg_enable_reg <= 1'b0;
|
||||
end
|
||||
end else begin
|
||||
if (cfg_enable) begin
|
||||
cfg_enable_reg <= 1'b1;
|
||||
end
|
||||
cfg_fifo_base_addr_reg <= cfg_fifo_base_addr;
|
||||
cfg_fifo_size_mask_reg <= cfg_fifo_size_mask;
|
||||
end
|
||||
|
||||
cfg_reset_reg <= cfg_reset;
|
||||
|
||||
if (rst) begin
|
||||
cfg_enable_reg <= 0;
|
||||
cfg_reset_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
// status sync
|
||||
wire [AXI_CH*(LEN_WIDTH+1)-1:0] sts_fifo_occupancy_int;
|
||||
wire [AXI_CH-1:0] sts_fifo_empty_int;
|
||||
wire [AXI_CH-1:0] sts_fifo_full_int;
|
||||
wire [AXI_CH-1:0] sts_reset_int;
|
||||
wire [AXI_CH-1:0] sts_active_int;
|
||||
wire sts_hdr_parity_err_int;
|
||||
reg [3:0] sts_hdr_parity_err_cnt_reg = 0;
|
||||
reg sts_hdr_parity_err_reg = 1'b0;
|
||||
|
||||
reg [2:0] sts_sync_count_reg = 0;
|
||||
reg sts_sync_flag_reg = 1'b0;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg [AXI_CH*(LEN_WIDTH+1)-1:0] sts_fifo_occupancy_sync_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [AXI_CH-1:0] sts_fifo_empty_sync_1_reg = 0, sts_fifo_empty_sync_2_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [AXI_CH-1:0] sts_fifo_full_sync_1_reg = 0, sts_fifo_full_sync_2_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [AXI_CH-1:0] sts_reset_sync_1_reg = 0, sts_reset_sync_2_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [AXI_CH-1:0] sts_active_sync_1_reg = 0, sts_active_sync_2_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg sts_hdr_parity_err_sync_1_reg = 0, sts_hdr_parity_err_sync_2_reg = 0;
|
||||
|
||||
assign sts_fifo_occupancy = sts_fifo_occupancy_sync_reg;
|
||||
assign sts_fifo_empty = sts_fifo_empty_sync_2_reg;
|
||||
assign sts_fifo_full = sts_fifo_full_sync_2_reg;
|
||||
assign sts_reset = sts_reset_sync_2_reg;
|
||||
assign sts_active = sts_active_sync_2_reg;
|
||||
assign sts_hdr_parity_err = sts_hdr_parity_err_sync_2_reg;
|
||||
|
||||
always @(posedge m_axis_clk) begin
|
||||
sts_hdr_parity_err_reg <= 1'b0;
|
||||
|
||||
if (sts_hdr_parity_err_cnt_reg) begin
|
||||
sts_hdr_parity_err_reg <= 1'b1;
|
||||
sts_hdr_parity_err_cnt_reg <= sts_hdr_parity_err_cnt_reg - 1;
|
||||
end
|
||||
|
||||
if (sts_hdr_parity_err_int) begin
|
||||
sts_hdr_parity_err_cnt_reg <= 4'hf;
|
||||
end
|
||||
|
||||
if (m_axis_rst) begin
|
||||
sts_hdr_parity_err_cnt_reg <= 4'h0;
|
||||
sts_hdr_parity_err_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
sts_sync_count_reg <= sts_sync_count_reg + 1;
|
||||
|
||||
if (sts_sync_count_reg == 0) begin
|
||||
sts_sync_flag_reg <= !sts_sync_flag_reg;
|
||||
sts_fifo_occupancy_sync_reg <= sts_fifo_occupancy_int;
|
||||
end
|
||||
|
||||
sts_fifo_empty_sync_1_reg <= sts_fifo_empty_int;
|
||||
sts_fifo_empty_sync_2_reg <= sts_fifo_empty_sync_1_reg;
|
||||
sts_fifo_full_sync_1_reg <= sts_fifo_full_int;
|
||||
sts_fifo_full_sync_2_reg <= sts_fifo_full_sync_1_reg;
|
||||
sts_reset_sync_1_reg <= sts_reset_int;
|
||||
sts_reset_sync_2_reg <= sts_reset_sync_1_reg;
|
||||
sts_active_sync_1_reg <= sts_active_int;
|
||||
sts_active_sync_2_reg <= sts_active_sync_1_reg;
|
||||
sts_hdr_parity_err_sync_1_reg <= sts_hdr_parity_err_reg;
|
||||
sts_hdr_parity_err_sync_2_reg <= sts_hdr_parity_err_sync_1_reg;
|
||||
end
|
||||
|
||||
assign s_axis_rst_out = |ch_input_rst_out;
|
||||
|
||||
axi_vfifo_enc #(
|
||||
.SEG_WIDTH(SEG_WIDTH),
|
||||
.SEG_CNT(SEG_CNT),
|
||||
.AXIS_DATA_WIDTH(AXIS_DATA_WIDTH),
|
||||
.AXIS_KEEP_ENABLE(AXIS_KEEP_ENABLE),
|
||||
.AXIS_KEEP_WIDTH(AXIS_KEEP_WIDTH),
|
||||
.AXIS_LAST_ENABLE(AXIS_LAST_ENABLE),
|
||||
.AXIS_ID_ENABLE(AXIS_ID_ENABLE),
|
||||
.AXIS_ID_WIDTH(AXIS_ID_WIDTH),
|
||||
.AXIS_DEST_ENABLE(AXIS_DEST_ENABLE),
|
||||
.AXIS_DEST_WIDTH(AXIS_DEST_WIDTH),
|
||||
.AXIS_USER_ENABLE(AXIS_USER_ENABLE),
|
||||
.AXIS_USER_WIDTH(AXIS_USER_WIDTH)
|
||||
)
|
||||
axi_vfifo_enc_inst (
|
||||
.clk(s_axis_clk),
|
||||
.rst(s_axis_rst),
|
||||
|
||||
/*
|
||||
* AXI stream data input
|
||||
*/
|
||||
.s_axis_tdata(s_axis_tdata),
|
||||
.s_axis_tkeep(s_axis_tkeep),
|
||||
.s_axis_tvalid(s_axis_tvalid),
|
||||
.s_axis_tready(s_axis_tready),
|
||||
.s_axis_tlast(s_axis_tlast),
|
||||
.s_axis_tid(s_axis_tid),
|
||||
.s_axis_tdest(s_axis_tdest),
|
||||
.s_axis_tuser(s_axis_tuser),
|
||||
|
||||
/*
|
||||
* Segmented data output (to virtual FIFO channel)
|
||||
*/
|
||||
.fifo_rst_in(s_axis_rst_out),
|
||||
.output_data(ch_input_data),
|
||||
.output_valid(ch_input_valid),
|
||||
.fifo_watermark_in(|ch_input_watermark)
|
||||
);
|
||||
|
||||
generate
|
||||
|
||||
genvar n;
|
||||
|
||||
for (n = 0; n < AXI_CH; n = n + 1) begin : axi_ch
|
||||
|
||||
wire ch_clk = m_axi_clk[1*n +: 1];
|
||||
wire ch_rst = m_axi_rst[1*n +: 1];
|
||||
|
||||
wire [AXI_ID_WIDTH-1:0] ch_axi_awid;
|
||||
wire [AXI_ADDR_WIDTH-1:0] ch_axi_awaddr;
|
||||
wire [7:0] ch_axi_awlen;
|
||||
wire [2:0] ch_axi_awsize;
|
||||
wire [1:0] ch_axi_awburst;
|
||||
wire ch_axi_awlock;
|
||||
wire [3:0] ch_axi_awcache;
|
||||
wire [2:0] ch_axi_awprot;
|
||||
wire ch_axi_awvalid;
|
||||
wire ch_axi_awready;
|
||||
wire [AXI_DATA_WIDTH-1:0] ch_axi_wdata;
|
||||
wire [AXI_STRB_WIDTH-1:0] ch_axi_wstrb;
|
||||
wire ch_axi_wlast;
|
||||
wire ch_axi_wvalid;
|
||||
wire ch_axi_wready;
|
||||
wire [AXI_ID_WIDTH-1:0] ch_axi_bid;
|
||||
wire [1:0] ch_axi_bresp;
|
||||
wire ch_axi_bvalid;
|
||||
wire ch_axi_bready;
|
||||
wire [AXI_ID_WIDTH-1:0] ch_axi_arid;
|
||||
wire [AXI_ADDR_WIDTH-1:0] ch_axi_araddr;
|
||||
wire [7:0] ch_axi_arlen;
|
||||
wire [2:0] ch_axi_arsize;
|
||||
wire [1:0] ch_axi_arburst;
|
||||
wire ch_axi_arlock;
|
||||
wire [3:0] ch_axi_arcache;
|
||||
wire [2:0] ch_axi_arprot;
|
||||
wire ch_axi_arvalid;
|
||||
wire ch_axi_arready;
|
||||
wire [AXI_ID_WIDTH-1:0] ch_axi_rid;
|
||||
wire [AXI_DATA_WIDTH-1:0] ch_axi_rdata;
|
||||
wire [1:0] ch_axi_rresp;
|
||||
wire ch_axi_rlast;
|
||||
wire ch_axi_rvalid;
|
||||
wire ch_axi_rready;
|
||||
|
||||
assign m_axi_awid[AXI_ID_WIDTH*n +: AXI_ID_WIDTH] = ch_axi_awid;
|
||||
assign m_axi_awaddr[AXI_ADDR_WIDTH*n +: AXI_ADDR_WIDTH] = ch_axi_awaddr;
|
||||
assign m_axi_awlen[8*n +: 8] = ch_axi_awlen;
|
||||
assign m_axi_awsize[3*n +: 3] = ch_axi_awsize;
|
||||
assign m_axi_awburst[2*n +: 2] = ch_axi_awburst;
|
||||
assign m_axi_awlock[1*n +: 1] = ch_axi_awlock;
|
||||
assign m_axi_awcache[4*n +: 4] = ch_axi_awcache;
|
||||
assign m_axi_awprot[3*n +: 3] = ch_axi_awprot;
|
||||
assign m_axi_awvalid[1*n +: 1] = ch_axi_awvalid;
|
||||
assign ch_axi_awready = m_axi_awready[1*n +: 1];
|
||||
assign m_axi_wdata[AXI_DATA_WIDTH*n +: AXI_DATA_WIDTH] = ch_axi_wdata;
|
||||
assign m_axi_wstrb[AXI_STRB_WIDTH*n +: AXI_STRB_WIDTH] = ch_axi_wstrb;
|
||||
assign m_axi_wlast[1*n +: 1] = ch_axi_wlast;
|
||||
assign m_axi_wvalid[1*n +: 1] = ch_axi_wvalid;
|
||||
assign ch_axi_wready = m_axi_wready[1*n +: 1];
|
||||
assign ch_axi_bid = m_axi_bid[AXI_ID_WIDTH*n +: AXI_ID_WIDTH];
|
||||
assign ch_axi_bresp = m_axi_bresp[2*n +: 2];
|
||||
assign ch_axi_bvalid = m_axi_bvalid[1*n +: 1];
|
||||
assign m_axi_bready[1*n +: 1] = ch_axi_bready;
|
||||
assign m_axi_arid[AXI_ID_WIDTH*n +: AXI_ID_WIDTH] = ch_axi_arid;
|
||||
assign m_axi_araddr[AXI_ADDR_WIDTH*n +: AXI_ADDR_WIDTH] = ch_axi_araddr;
|
||||
assign m_axi_arlen[8*n +: 8] = ch_axi_arlen;
|
||||
assign m_axi_arsize[3*n +: 3] = ch_axi_arsize;
|
||||
assign m_axi_arburst[2*n +: 2] = ch_axi_arburst;
|
||||
assign m_axi_arlock[1*n +: 1] = ch_axi_arlock;
|
||||
assign m_axi_arcache[4*n +: 4] = ch_axi_arcache;
|
||||
assign m_axi_arprot[3*n +: 3] = ch_axi_arprot;
|
||||
assign m_axi_arvalid[1*n +: 1] = ch_axi_arvalid;
|
||||
assign ch_axi_arready = m_axi_arready[1*n +: 1];
|
||||
assign ch_axi_rid = m_axi_rid[AXI_ID_WIDTH*n +: AXI_ID_WIDTH];
|
||||
assign ch_axi_rdata = m_axi_rdata[AXI_DATA_WIDTH*n +: AXI_DATA_WIDTH];
|
||||
assign ch_axi_rresp = m_axi_rresp[2*n +: 2];
|
||||
assign ch_axi_rlast = m_axi_rlast[1*n +: 1];
|
||||
assign ch_axi_rvalid = m_axi_rvalid[1*n +: 1];
|
||||
assign m_axi_rready[1*n +: 1] = ch_axi_rready;
|
||||
|
||||
// control sync
|
||||
(* shreg_extract = "no" *)
|
||||
reg ch_cfg_enable_sync_1_reg = 1'b0, ch_cfg_enable_sync_2_reg = 1'b0;
|
||||
|
||||
always @(posedge ch_clk) begin
|
||||
ch_cfg_enable_sync_1_reg <= cfg_enable_reg;
|
||||
ch_cfg_enable_sync_2_reg <= ch_cfg_enable_sync_1_reg;
|
||||
end
|
||||
|
||||
// status sync
|
||||
wire [LEN_WIDTH+1-1:0] ch_sts_fifo_occupancy;
|
||||
reg [LEN_WIDTH+1-1:0] ch_sts_fifo_occupancy_reg;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg ch_sts_flag_sync_1_reg = 1'b0, ch_sts_flag_sync_2_reg = 1'b0, ch_sts_flag_sync_3_reg = 1'b0;
|
||||
|
||||
assign sts_fifo_occupancy_int[(LEN_WIDTH+1)*n +: LEN_WIDTH+1] = ch_sts_fifo_occupancy_reg;
|
||||
|
||||
always @(posedge ch_clk) begin
|
||||
ch_sts_flag_sync_1_reg <= sts_sync_flag_reg;
|
||||
ch_sts_flag_sync_2_reg <= ch_sts_flag_sync_1_reg;
|
||||
ch_sts_flag_sync_3_reg <= ch_sts_flag_sync_2_reg;
|
||||
|
||||
if (ch_sts_flag_sync_3_reg ^ ch_sts_flag_sync_2_reg) begin
|
||||
ch_sts_fifo_occupancy_reg <= ch_sts_fifo_occupancy;
|
||||
end
|
||||
end
|
||||
|
||||
axi_vfifo_raw #(
|
||||
.SEG_WIDTH(SEG_WIDTH),
|
||||
.SEG_CNT(CH_SEG_CNT),
|
||||
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
|
||||
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH),
|
||||
.AXI_STRB_WIDTH(AXI_STRB_WIDTH),
|
||||
.AXI_ID_WIDTH(AXI_ID_WIDTH),
|
||||
.AXI_MAX_BURST_LEN(AXI_MAX_BURST_LEN),
|
||||
.LEN_WIDTH(LEN_WIDTH),
|
||||
.WRITE_FIFO_DEPTH(WRITE_FIFO_DEPTH),
|
||||
.WRITE_MAX_BURST_LEN(WRITE_MAX_BURST_LEN),
|
||||
.READ_FIFO_DEPTH(READ_FIFO_DEPTH),
|
||||
.READ_MAX_BURST_LEN(READ_MAX_BURST_LEN),
|
||||
.WATERMARK_LEVEL(WRITE_FIFO_DEPTH-4),
|
||||
.CTRL_OUT_EN(1)
|
||||
)
|
||||
axi_vfifo_raw_inst (
|
||||
.clk(ch_clk),
|
||||
.rst(ch_rst),
|
||||
|
||||
/*
|
||||
* Segmented data input (from encode logic)
|
||||
*/
|
||||
.input_clk(s_axis_clk),
|
||||
.input_rst(s_axis_rst),
|
||||
.input_rst_out(ch_input_rst_out[n]),
|
||||
.input_watermark(ch_input_watermark[n]),
|
||||
.input_data(ch_input_data[SEG_WIDTH*CH_SEG_CNT*n +: SEG_WIDTH*CH_SEG_CNT]),
|
||||
.input_valid(ch_input_valid[CH_SEG_CNT*n +: CH_SEG_CNT]),
|
||||
.input_ready(ch_input_ready[CH_SEG_CNT*n +: CH_SEG_CNT]),
|
||||
|
||||
/*
|
||||
* Segmented data output (to decode logic)
|
||||
*/
|
||||
.output_clk(m_axis_clk),
|
||||
.output_rst(m_axis_rst),
|
||||
.output_rst_out(ch_output_rst_out[n]),
|
||||
.output_data(ch_output_data[SEG_WIDTH*CH_SEG_CNT*n +: SEG_WIDTH*CH_SEG_CNT]),
|
||||
.output_valid(ch_output_valid[CH_SEG_CNT*n +: CH_SEG_CNT]),
|
||||
.output_ready(ch_output_ready[CH_SEG_CNT*n +: CH_SEG_CNT]),
|
||||
.output_ctrl_data(ch_output_ctrl_data[SEG_WIDTH*CH_SEG_CNT*n +: SEG_WIDTH*CH_SEG_CNT]),
|
||||
.output_ctrl_valid(ch_output_ctrl_valid[CH_SEG_CNT*n +: CH_SEG_CNT]),
|
||||
.output_ctrl_ready(ch_output_ctrl_ready[CH_SEG_CNT*n +: CH_SEG_CNT]),
|
||||
|
||||
/*
|
||||
* AXI master interface
|
||||
*/
|
||||
.m_axi_awid(ch_axi_awid),
|
||||
.m_axi_awaddr(ch_axi_awaddr),
|
||||
.m_axi_awlen(ch_axi_awlen),
|
||||
.m_axi_awsize(ch_axi_awsize),
|
||||
.m_axi_awburst(ch_axi_awburst),
|
||||
.m_axi_awlock(ch_axi_awlock),
|
||||
.m_axi_awcache(ch_axi_awcache),
|
||||
.m_axi_awprot(ch_axi_awprot),
|
||||
.m_axi_awvalid(ch_axi_awvalid),
|
||||
.m_axi_awready(ch_axi_awready),
|
||||
.m_axi_wdata(ch_axi_wdata),
|
||||
.m_axi_wstrb(ch_axi_wstrb),
|
||||
.m_axi_wlast(ch_axi_wlast),
|
||||
.m_axi_wvalid(ch_axi_wvalid),
|
||||
.m_axi_wready(ch_axi_wready),
|
||||
.m_axi_bid(ch_axi_bid),
|
||||
.m_axi_bresp(ch_axi_bresp),
|
||||
.m_axi_bvalid(ch_axi_bvalid),
|
||||
.m_axi_bready(ch_axi_bready),
|
||||
.m_axi_arid(ch_axi_arid),
|
||||
.m_axi_araddr(ch_axi_araddr),
|
||||
.m_axi_arlen(ch_axi_arlen),
|
||||
.m_axi_arsize(ch_axi_arsize),
|
||||
.m_axi_arburst(ch_axi_arburst),
|
||||
.m_axi_arlock(ch_axi_arlock),
|
||||
.m_axi_arcache(ch_axi_arcache),
|
||||
.m_axi_arprot(ch_axi_arprot),
|
||||
.m_axi_arvalid(ch_axi_arvalid),
|
||||
.m_axi_arready(ch_axi_arready),
|
||||
.m_axi_rid(ch_axi_rid),
|
||||
.m_axi_rdata(ch_axi_rdata),
|
||||
.m_axi_rresp(ch_axi_rresp),
|
||||
.m_axi_rlast(ch_axi_rlast),
|
||||
.m_axi_rvalid(ch_axi_rvalid),
|
||||
.m_axi_rready(ch_axi_rready),
|
||||
|
||||
/*
|
||||
* Reset sync
|
||||
*/
|
||||
.rst_req_out(ch_rst_req[n]),
|
||||
.rst_req_in(|ch_rst_req),
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
.cfg_fifo_base_addr(cfg_fifo_base_addr_reg[AXI_ADDR_WIDTH*n +: AXI_ADDR_WIDTH]),
|
||||
.cfg_fifo_size_mask(cfg_fifo_size_mask_reg),
|
||||
.cfg_enable(ch_cfg_enable_sync_2_reg),
|
||||
.cfg_reset(cfg_reset_reg),
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
.sts_fifo_occupancy(ch_sts_fifo_occupancy),
|
||||
.sts_fifo_empty(sts_fifo_empty_int[n]),
|
||||
.sts_fifo_full(sts_fifo_full_int[n]),
|
||||
.sts_reset(sts_reset_int[n]),
|
||||
.sts_active(sts_active_int[n]),
|
||||
.sts_write_active(),
|
||||
.sts_read_active()
|
||||
);
|
||||
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
assign m_axis_rst_out = |ch_output_rst_out;
|
||||
|
||||
axi_vfifo_dec #(
|
||||
.SEG_WIDTH(SEG_WIDTH),
|
||||
.SEG_CNT(SEG_CNT),
|
||||
.AXIS_DATA_WIDTH(AXIS_DATA_WIDTH),
|
||||
.AXIS_KEEP_ENABLE(AXIS_KEEP_ENABLE),
|
||||
.AXIS_KEEP_WIDTH(AXIS_KEEP_WIDTH),
|
||||
.AXIS_LAST_ENABLE(AXIS_LAST_ENABLE),
|
||||
.AXIS_ID_ENABLE(AXIS_ID_ENABLE),
|
||||
.AXIS_ID_WIDTH(AXIS_ID_WIDTH),
|
||||
.AXIS_DEST_ENABLE(AXIS_DEST_ENABLE),
|
||||
.AXIS_DEST_WIDTH(AXIS_DEST_WIDTH),
|
||||
.AXIS_USER_ENABLE(AXIS_USER_ENABLE),
|
||||
.AXIS_USER_WIDTH(AXIS_USER_WIDTH)
|
||||
)
|
||||
axi_vfifo_dec_inst (
|
||||
.clk(m_axis_clk),
|
||||
.rst(m_axis_rst),
|
||||
|
||||
/*
|
||||
* Segmented data input (from virtual FIFO channel)
|
||||
*/
|
||||
.fifo_rst_in(m_axis_rst_out),
|
||||
.input_data(ch_output_data),
|
||||
.input_valid(ch_output_valid),
|
||||
.input_ready(ch_output_ready),
|
||||
.input_ctrl_data(ch_output_ctrl_data),
|
||||
.input_ctrl_valid(ch_output_ctrl_valid),
|
||||
.input_ctrl_ready(ch_output_ctrl_ready),
|
||||
|
||||
/*
|
||||
* AXI stream data output
|
||||
*/
|
||||
.m_axis_tdata(m_axis_tdata),
|
||||
.m_axis_tkeep(m_axis_tkeep),
|
||||
.m_axis_tvalid(m_axis_tvalid),
|
||||
.m_axis_tready(m_axis_tready),
|
||||
.m_axis_tlast(m_axis_tlast),
|
||||
.m_axis_tid(m_axis_tid),
|
||||
.m_axis_tdest(m_axis_tdest),
|
||||
.m_axis_tuser(m_axis_tuser),
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
.sts_hdr_parity_err(sts_hdr_parity_err_int)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
720
rtl/axi_vfifo_dec.v
Normal file
720
rtl/axi_vfifo_dec.v
Normal file
@ -0,0 +1,720 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* AXI4 virtual FIFO (decoder)
|
||||
*/
|
||||
module axi_vfifo_dec #
|
||||
(
|
||||
// Width of input segment
|
||||
parameter SEG_WIDTH = 32,
|
||||
// Segment count
|
||||
parameter SEG_CNT = 2,
|
||||
// Width of AXI stream interfaces in bits
|
||||
parameter AXIS_DATA_WIDTH = SEG_WIDTH*SEG_CNT/2,
|
||||
// Use AXI stream tkeep signal
|
||||
parameter AXIS_KEEP_ENABLE = (AXIS_DATA_WIDTH>8),
|
||||
// AXI stream tkeep signal width (words per cycle)
|
||||
parameter AXIS_KEEP_WIDTH = (AXIS_DATA_WIDTH/8),
|
||||
// Use AXI stream tlast signal
|
||||
parameter AXIS_LAST_ENABLE = 1,
|
||||
// Propagate AXI stream tid signal
|
||||
parameter AXIS_ID_ENABLE = 0,
|
||||
// AXI stream tid signal width
|
||||
parameter AXIS_ID_WIDTH = 8,
|
||||
// Propagate AXI stream tdest signal
|
||||
parameter AXIS_DEST_ENABLE = 0,
|
||||
// AXI stream tdest signal width
|
||||
parameter AXIS_DEST_WIDTH = 8,
|
||||
// Propagate AXI stream tuser signal
|
||||
parameter AXIS_USER_ENABLE = 1,
|
||||
// AXI stream tuser signal width
|
||||
parameter AXIS_USER_WIDTH = 1
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* Segmented data input (from virtual FIFO channel)
|
||||
*/
|
||||
input wire fifo_rst_in,
|
||||
input wire [SEG_CNT*SEG_WIDTH-1:0] input_data,
|
||||
input wire [SEG_CNT-1:0] input_valid,
|
||||
output wire [SEG_CNT-1:0] input_ready,
|
||||
input wire [SEG_CNT*SEG_WIDTH-1:0] input_ctrl_data,
|
||||
input wire [SEG_CNT-1:0] input_ctrl_valid,
|
||||
output wire [SEG_CNT-1:0] input_ctrl_ready,
|
||||
|
||||
/*
|
||||
* AXI stream data output
|
||||
*/
|
||||
output wire [AXIS_DATA_WIDTH-1:0] m_axis_tdata,
|
||||
output wire [AXIS_KEEP_WIDTH-1:0] m_axis_tkeep,
|
||||
output wire m_axis_tvalid,
|
||||
input wire m_axis_tready,
|
||||
output wire m_axis_tlast,
|
||||
output wire [AXIS_ID_WIDTH-1:0] m_axis_tid,
|
||||
output wire [AXIS_DEST_WIDTH-1:0] m_axis_tdest,
|
||||
output wire [AXIS_USER_WIDTH-1:0] m_axis_tuser,
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
output wire sts_hdr_parity_err
|
||||
);
|
||||
|
||||
parameter AXIS_KEEP_WIDTH_INT = AXIS_KEEP_ENABLE ? AXIS_KEEP_WIDTH : 1;
|
||||
parameter AXIS_BYTE_LANES = AXIS_KEEP_WIDTH_INT;
|
||||
parameter AXIS_BYTE_SIZE = AXIS_DATA_WIDTH/AXIS_BYTE_LANES;
|
||||
parameter AXIS_BYTE_IDX_WIDTH = $clog2(AXIS_BYTE_LANES);
|
||||
|
||||
parameter BYTE_SIZE = AXIS_BYTE_SIZE;
|
||||
|
||||
parameter SEG_BYTE_LANES = SEG_WIDTH / BYTE_SIZE;
|
||||
|
||||
parameter EXPAND_INPUT = SEG_CNT < 2;
|
||||
|
||||
parameter SEG_CNT_INT = EXPAND_INPUT ? SEG_CNT*2 : SEG_CNT;
|
||||
|
||||
parameter SEG_IDX_WIDTH = $clog2(SEG_CNT_INT);
|
||||
parameter SEG_BYTE_IDX_WIDTH = $clog2(SEG_BYTE_LANES);
|
||||
|
||||
parameter AXIS_SEG_CNT = (AXIS_DATA_WIDTH + SEG_WIDTH-1) / SEG_WIDTH;
|
||||
parameter AXIS_SEG_IDX_WIDTH = AXIS_SEG_CNT > 1 ? $clog2(AXIS_SEG_CNT) : 1;
|
||||
parameter AXIS_LEN_MASK = AXIS_BYTE_LANES-1;
|
||||
|
||||
parameter OUT_OFFS_WIDTH = AXIS_SEG_IDX_WIDTH;
|
||||
|
||||
parameter META_ID_OFFSET = 0;
|
||||
parameter META_DEST_OFFSET = META_ID_OFFSET + (AXIS_ID_ENABLE ? AXIS_ID_WIDTH : 0);
|
||||
parameter META_USER_OFFSET = META_DEST_OFFSET + (AXIS_DEST_ENABLE ? AXIS_DEST_WIDTH : 0);
|
||||
parameter META_WIDTH = META_USER_OFFSET + (AXIS_USER_ENABLE ? AXIS_USER_WIDTH : 0);
|
||||
parameter HDR_SIZE = (16 + META_WIDTH + BYTE_SIZE-1) / BYTE_SIZE;
|
||||
parameter HDR_WIDTH = HDR_SIZE * BYTE_SIZE;
|
||||
|
||||
parameter HDR_LEN_WIDTH = 12;
|
||||
parameter HDR_SEG_LEN_WIDTH = HDR_LEN_WIDTH-SEG_BYTE_IDX_WIDTH;
|
||||
|
||||
parameter CTRL_FIFO_ADDR_WIDTH = 5;
|
||||
parameter OUTPUT_FIFO_ADDR_WIDTH = 5;
|
||||
|
||||
parameter CTRL_FIFO_PTR_WIDTH = CTRL_FIFO_ADDR_WIDTH + SEG_IDX_WIDTH;
|
||||
|
||||
// validate parameters
|
||||
initial begin
|
||||
if (AXIS_BYTE_SIZE * AXIS_KEEP_WIDTH_INT != AXIS_DATA_WIDTH) begin
|
||||
$error("Error: AXI stream data width not evenly divisible (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (AXIS_SEG_CNT * SEG_WIDTH != AXIS_DATA_WIDTH) begin
|
||||
$error("Error: AXI stream data width not evenly divisible into segments (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (SEG_WIDTH < HDR_WIDTH) begin
|
||||
$error("Error: Segment smaller than header (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
reg frame_reg = 1'b0, frame_next, frame_cyc;
|
||||
reg last_reg = 1'b0, last_next, last_cyc;
|
||||
reg extra_cycle_reg = 1'b0, extra_cycle_next, extra_cycle_cyc;
|
||||
reg last_straddle_reg = 1'b0, last_straddle_next, last_straddle_cyc;
|
||||
reg [HDR_SEG_LEN_WIDTH-1:0] seg_cnt_reg = 0, seg_cnt_next, seg_cnt_cyc;
|
||||
reg hdr_parity_err_reg = 1'b0, hdr_parity_err_next, hdr_parity_err_cyc;
|
||||
|
||||
reg out_frame_reg = 1'b0, out_frame_next, out_frame_cyc;
|
||||
reg [SEG_IDX_WIDTH-1:0] out_seg_offset_reg = 0, out_seg_offset_next, out_seg_offset_cyc;
|
||||
reg [OUT_OFFS_WIDTH-1:0] output_offset_reg = 0, output_offset_next, output_offset_cyc;
|
||||
reg [SEG_CNT_INT-1:0] out_seg_consumed;
|
||||
reg [SEG_CNT_INT-1:0] out_seg_consumed_reg = 0, out_seg_consumed_next;
|
||||
reg out_valid, out_valid_straddle, out_frame, out_last, out_abort, out_done;
|
||||
|
||||
reg [SEG_CNT_INT-1:0] seg_valid;
|
||||
reg [SEG_CNT_INT-1:0] seg_valid_straddle;
|
||||
reg [SEG_CNT_INT-1:0] seg_hdr_start_pkt;
|
||||
reg [SEG_CNT_INT-1:0] seg_hdr_last;
|
||||
reg [SEG_CNT_INT-1:0] seg_hdr_last_straddle;
|
||||
reg [SEG_CNT_INT-1:0] seg_hdr_parity_err;
|
||||
reg [HDR_LEN_WIDTH-1:0] seg_hdr_len[SEG_CNT_INT-1:0];
|
||||
reg [HDR_SEG_LEN_WIDTH-1:0] seg_hdr_seg_cnt[SEG_CNT_INT-1:0];
|
||||
|
||||
reg [SEG_CNT_INT-1:0] shift_out_seg_valid;
|
||||
reg [SEG_CNT_INT-1:0] shift_out_seg_valid_straddle;
|
||||
reg [SEG_CNT_INT-1:0] shift_out_seg_sop;
|
||||
reg [SEG_CNT_INT-1:0] shift_out_seg_eop;
|
||||
reg [SEG_CNT_INT-1:0] shift_out_seg_end;
|
||||
reg [SEG_CNT_INT-1:0] shift_out_seg_last;
|
||||
|
||||
reg [SEG_CNT-1:0] input_ready_cmb;
|
||||
reg [SEG_CNT-1:0] input_ctrl_ready_cmb;
|
||||
|
||||
reg [SEG_CNT*SEG_WIDTH-1:0] input_data_int_reg = 0, input_data_int_next;
|
||||
reg [SEG_CNT-1:0] input_valid_int_reg = 0, input_valid_int_next;
|
||||
|
||||
wire [SEG_CNT_INT*SEG_WIDTH*2-1:0] input_data_full = EXPAND_INPUT ? {2{{input_data, input_data_int_reg}}} : {2{input_data}};
|
||||
wire [SEG_CNT_INT-1:0] input_valid_full = EXPAND_INPUT ? {input_valid, input_valid_int_reg} : input_valid;
|
||||
|
||||
reg out_ctrl_en_reg = 0, out_ctrl_en_next;
|
||||
reg out_ctrl_hdr_reg = 0, out_ctrl_hdr_next;
|
||||
reg out_ctrl_last_reg = 0, out_ctrl_last_next;
|
||||
reg [AXIS_BYTE_IDX_WIDTH-1:0] out_ctrl_last_len_reg = 0, out_ctrl_last_len_next;
|
||||
reg [SEG_IDX_WIDTH-1:0] out_ctrl_seg_offset_reg = 0, out_ctrl_seg_offset_next;
|
||||
|
||||
reg [AXIS_ID_WIDTH-1:0] axis_tid_reg = 0, axis_tid_next;
|
||||
reg [AXIS_DEST_WIDTH-1:0] axis_tdest_reg = 0, axis_tdest_next;
|
||||
reg [AXIS_USER_WIDTH-1:0] axis_tuser_reg = 0, axis_tuser_next;
|
||||
|
||||
// internal datapath
|
||||
reg [AXIS_DATA_WIDTH-1:0] m_axis_tdata_int;
|
||||
reg [AXIS_KEEP_WIDTH-1:0] m_axis_tkeep_int;
|
||||
reg m_axis_tvalid_int;
|
||||
wire m_axis_tready_int;
|
||||
reg m_axis_tlast_int;
|
||||
reg [AXIS_ID_WIDTH-1:0] m_axis_tid_int;
|
||||
reg [AXIS_DEST_WIDTH-1:0] m_axis_tdest_int;
|
||||
reg [AXIS_USER_WIDTH-1:0] m_axis_tuser_int;
|
||||
|
||||
assign input_ready = input_ready_cmb;
|
||||
assign input_ctrl_ready = input_ctrl_ready_cmb;
|
||||
|
||||
assign sts_hdr_parity_err = hdr_parity_err_reg;
|
||||
|
||||
// segmented control FIFO
|
||||
reg [CTRL_FIFO_PTR_WIDTH+1-1:0] ctrl_fifo_wr_ptr_reg = 0, ctrl_fifo_wr_ptr_next;
|
||||
reg [CTRL_FIFO_PTR_WIDTH+1-1:0] ctrl_fifo_rd_ptr_reg = 0, ctrl_fifo_rd_ptr_next;
|
||||
|
||||
reg [SEG_CNT-1:0] ctrl_mem_rd_data_valid_reg = 0, ctrl_mem_rd_data_valid_next;
|
||||
|
||||
reg [SEG_CNT-1:0] ctrl_fifo_wr_sop;
|
||||
reg [SEG_CNT-1:0] ctrl_fifo_wr_eop;
|
||||
reg [SEG_CNT-1:0] ctrl_fifo_wr_end;
|
||||
reg [SEG_CNT-1:0] ctrl_fifo_wr_last;
|
||||
reg [SEG_CNT*AXIS_BYTE_IDX_WIDTH-1:0] ctrl_fifo_wr_last_len;
|
||||
reg [SEG_CNT-1:0] ctrl_fifo_wr_en;
|
||||
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_rd_sop;
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_rd_eop;
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_rd_end;
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_rd_last;
|
||||
wire [SEG_CNT*AXIS_BYTE_IDX_WIDTH-1:0] ctrl_fifo_rd_last_len;
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_rd_valid;
|
||||
reg [SEG_CNT-1:0] ctrl_fifo_rd_en;
|
||||
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_seg_full;
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_seg_half_full;
|
||||
wire [SEG_CNT-1:0] ctrl_fifo_seg_empty;
|
||||
|
||||
wire ctrl_fifo_full = |ctrl_fifo_seg_full;
|
||||
wire ctrl_fifo_half_full = |ctrl_fifo_seg_half_full;
|
||||
wire ctrl_fifo_empty = |ctrl_fifo_seg_empty;
|
||||
|
||||
generate
|
||||
|
||||
genvar n;
|
||||
|
||||
for (n = 0; n < SEG_CNT; n = n + 1) begin : ctrl_fifo_seg
|
||||
|
||||
reg [CTRL_FIFO_ADDR_WIDTH+1-1:0] seg_wr_ptr_reg = 0;
|
||||
reg [CTRL_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_reg = 0;
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg seg_mem_sop[2**CTRL_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg seg_mem_eop[2**CTRL_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg seg_mem_end[2**CTRL_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg seg_mem_last[2**CTRL_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [AXIS_BYTE_IDX_WIDTH-1:0] seg_mem_last_len[2**CTRL_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
reg seg_rd_sop_reg = 0;
|
||||
reg seg_rd_eop_reg = 0;
|
||||
reg seg_rd_end_reg = 0;
|
||||
reg seg_rd_last_reg = 0;
|
||||
reg [AXIS_BYTE_IDX_WIDTH-1:0] seg_rd_last_len_reg = 0;
|
||||
reg seg_rd_valid_reg = 0;
|
||||
|
||||
reg seg_half_full_reg = 1'b0;
|
||||
|
||||
assign ctrl_fifo_rd_sop[n] = seg_rd_sop_reg;
|
||||
assign ctrl_fifo_rd_eop[n] = seg_rd_eop_reg;
|
||||
assign ctrl_fifo_rd_end[n] = seg_rd_end_reg;
|
||||
assign ctrl_fifo_rd_last[n] = seg_rd_last_reg;
|
||||
assign ctrl_fifo_rd_last_len[AXIS_BYTE_IDX_WIDTH*n +: AXIS_BYTE_IDX_WIDTH] = seg_rd_last_len_reg;
|
||||
assign ctrl_fifo_rd_valid[n] = seg_rd_valid_reg;
|
||||
|
||||
wire seg_full = seg_wr_ptr_reg == (seg_rd_ptr_reg ^ {1'b1, {CTRL_FIFO_ADDR_WIDTH{1'b0}}});
|
||||
wire seg_empty = seg_wr_ptr_reg == seg_rd_ptr_reg;
|
||||
|
||||
assign ctrl_fifo_seg_full[n] = seg_full;
|
||||
assign ctrl_fifo_seg_half_full[n] = seg_half_full_reg;
|
||||
assign ctrl_fifo_seg_empty[n] = seg_empty;
|
||||
|
||||
always @(posedge clk) begin
|
||||
seg_rd_valid_reg <= seg_rd_valid_reg && !ctrl_fifo_rd_en[n];
|
||||
|
||||
seg_half_full_reg <= $unsigned(seg_wr_ptr_reg - seg_rd_ptr_reg) >= 2**(CTRL_FIFO_ADDR_WIDTH-1);
|
||||
|
||||
if (ctrl_fifo_wr_en[n]) begin
|
||||
seg_mem_sop[seg_wr_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]] <= ctrl_fifo_wr_sop[n];
|
||||
seg_mem_eop[seg_wr_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]] <= ctrl_fifo_wr_eop[n];
|
||||
seg_mem_end[seg_wr_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]] <= ctrl_fifo_wr_end[n];
|
||||
seg_mem_last[seg_wr_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]] <= ctrl_fifo_wr_last[n];
|
||||
seg_mem_last_len[seg_wr_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]] <= ctrl_fifo_wr_last_len[AXIS_BYTE_IDX_WIDTH*n +: AXIS_BYTE_IDX_WIDTH];
|
||||
|
||||
seg_wr_ptr_reg <= seg_wr_ptr_reg + 1;
|
||||
end
|
||||
|
||||
if (!seg_empty && (!seg_rd_valid_reg || ctrl_fifo_rd_en[n])) begin
|
||||
seg_rd_sop_reg <= seg_mem_sop[seg_rd_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_eop_reg <= seg_mem_eop[seg_rd_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_end_reg <= seg_mem_end[seg_rd_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_last_reg <= seg_mem_last[seg_rd_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_last_len_reg <= seg_mem_last_len[seg_rd_ptr_reg[CTRL_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_valid_reg <= 1'b1;
|
||||
|
||||
seg_rd_ptr_reg <= seg_rd_ptr_reg + 1;
|
||||
end
|
||||
|
||||
if (rst || fifo_rst_in) begin
|
||||
seg_wr_ptr_reg <= 0;
|
||||
seg_rd_ptr_reg <= 0;
|
||||
seg_rd_valid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
// parse segment headers
|
||||
integer seg;
|
||||
|
||||
always @* begin
|
||||
input_ctrl_ready_cmb = 0;
|
||||
|
||||
frame_next = frame_reg;
|
||||
frame_cyc = frame_reg;
|
||||
last_next = last_reg;
|
||||
last_cyc = last_reg;
|
||||
extra_cycle_next = extra_cycle_reg;
|
||||
extra_cycle_cyc = extra_cycle_reg;
|
||||
last_straddle_next = last_straddle_reg;
|
||||
last_straddle_cyc = last_straddle_reg;
|
||||
seg_cnt_next = seg_cnt_reg;
|
||||
seg_cnt_cyc = seg_cnt_reg;
|
||||
hdr_parity_err_next = 1'b0;
|
||||
hdr_parity_err_cyc = 1'b0;
|
||||
|
||||
ctrl_fifo_wr_sop = 0;
|
||||
ctrl_fifo_wr_eop = 0;
|
||||
ctrl_fifo_wr_end = 0;
|
||||
ctrl_fifo_wr_last = 0;
|
||||
ctrl_fifo_wr_last_len = 0;
|
||||
ctrl_fifo_wr_en = 0;
|
||||
|
||||
// decode segment headers
|
||||
for (seg = 0; seg < SEG_CNT; seg = seg + 1) begin
|
||||
seg_valid[seg] = input_ctrl_valid[seg];
|
||||
seg_hdr_start_pkt[seg] = input_ctrl_data[SEG_WIDTH*seg + 0 +: 1];
|
||||
seg_hdr_last[seg] = input_ctrl_data[SEG_WIDTH*seg + 1 +: 1];
|
||||
seg_hdr_len[seg] = input_ctrl_data[SEG_WIDTH*seg + 4 +: 12];
|
||||
seg_hdr_seg_cnt[seg] = (seg_hdr_len[seg] + SEG_BYTE_LANES) >> SEG_BYTE_IDX_WIDTH;
|
||||
seg_hdr_last_straddle[seg] = ((seg_hdr_len[seg] & (SEG_BYTE_LANES-1)) + HDR_SIZE) >> SEG_BYTE_IDX_WIDTH != 0;
|
||||
seg_hdr_parity_err[seg] = ^input_ctrl_data[SEG_WIDTH*seg + 0 +: 3] || ^input_ctrl_data[SEG_WIDTH*seg + 3 +: 13];
|
||||
end
|
||||
seg_valid_straddle = {2{seg_valid}} >> 1;
|
||||
|
||||
for (seg = 0; seg < SEG_CNT; seg = seg + 1) begin
|
||||
if (!frame_cyc) begin
|
||||
if (seg_valid[seg]) begin
|
||||
if (seg_hdr_start_pkt[seg]) begin
|
||||
// start of frame
|
||||
last_cyc = seg_hdr_last[seg];
|
||||
extra_cycle_cyc = 1'b0;
|
||||
last_straddle_cyc = seg_hdr_last_straddle[seg];
|
||||
seg_cnt_cyc = seg_hdr_seg_cnt[seg];
|
||||
|
||||
ctrl_fifo_wr_sop[seg] = 1'b1;
|
||||
ctrl_fifo_wr_last_len[AXIS_BYTE_IDX_WIDTH*seg +: AXIS_BYTE_IDX_WIDTH] = seg_hdr_len[seg];
|
||||
|
||||
frame_cyc = 1'b1;
|
||||
end else begin
|
||||
// consume null segment
|
||||
end
|
||||
|
||||
if (seg_hdr_parity_err[seg]) begin
|
||||
hdr_parity_err_cyc = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (frame_cyc) begin
|
||||
if (extra_cycle_cyc) begin
|
||||
// extra cycle
|
||||
frame_cyc = 0;
|
||||
extra_cycle_cyc = 0;
|
||||
|
||||
ctrl_fifo_wr_eop[seg] = 1'b1;
|
||||
end else if (seg_cnt_cyc == 1) begin
|
||||
// last output cycle
|
||||
if (last_cyc) begin
|
||||
ctrl_fifo_wr_last[seg] = 1'b1;
|
||||
end
|
||||
|
||||
if (last_straddle_cyc) begin
|
||||
// last output cycle, with segment straddle
|
||||
extra_cycle_cyc = 1'b1;
|
||||
|
||||
ctrl_fifo_wr_end[seg] = 1'b1;
|
||||
end else begin
|
||||
// last output cycle, no segment straddle
|
||||
frame_cyc = 0;
|
||||
|
||||
ctrl_fifo_wr_eop[seg] = 1'b1;
|
||||
ctrl_fifo_wr_end[seg] = 1'b1;
|
||||
end
|
||||
end else begin
|
||||
// middle cycle
|
||||
end
|
||||
end
|
||||
|
||||
seg_cnt_cyc = seg_cnt_cyc - 1;
|
||||
end
|
||||
|
||||
if (&seg_valid && !ctrl_fifo_half_full) begin
|
||||
input_ctrl_ready_cmb = {SEG_CNT{1'b1}};
|
||||
|
||||
ctrl_fifo_wr_en = {SEG_CNT{1'b1}};
|
||||
|
||||
frame_next = frame_cyc;
|
||||
last_next = last_cyc;
|
||||
extra_cycle_next = extra_cycle_cyc;
|
||||
last_straddle_next = last_straddle_cyc;
|
||||
seg_cnt_next = seg_cnt_cyc;
|
||||
hdr_parity_err_next = hdr_parity_err_cyc;
|
||||
end
|
||||
end
|
||||
|
||||
// re-pack data
|
||||
integer out_seg;
|
||||
reg [SEG_IDX_WIDTH-1:0] out_cur_seg;
|
||||
|
||||
always @* begin
|
||||
input_ready_cmb = 0;
|
||||
|
||||
out_frame_next = out_frame_reg;
|
||||
out_frame_cyc = out_frame_reg;
|
||||
out_seg_offset_next = out_seg_offset_reg;
|
||||
out_seg_offset_cyc = out_seg_offset_reg;
|
||||
output_offset_next = output_offset_reg;
|
||||
// output_offset_cyc = output_offset_reg;
|
||||
output_offset_cyc = 0;
|
||||
out_seg_consumed_next = 0;
|
||||
|
||||
|
||||
out_ctrl_en_next = 0;
|
||||
out_ctrl_hdr_next = 0;
|
||||
out_ctrl_last_next = 0;
|
||||
out_ctrl_last_len_next = out_ctrl_last_len_reg;
|
||||
out_ctrl_seg_offset_next = out_ctrl_seg_offset_reg;
|
||||
|
||||
axis_tid_next = axis_tid_reg;
|
||||
axis_tdest_next = axis_tdest_reg;
|
||||
axis_tuser_next = axis_tuser_reg;
|
||||
|
||||
input_data_int_next = input_data_int_reg;
|
||||
input_valid_int_next = input_valid_int_reg;
|
||||
|
||||
ctrl_fifo_rd_en = 0;
|
||||
|
||||
// apply segment offset
|
||||
shift_out_seg_valid = {2{ctrl_fifo_rd_valid}} >> out_seg_offset_reg;
|
||||
shift_out_seg_valid_straddle = {2{ctrl_fifo_rd_valid}} >> (out_seg_offset_reg+1);
|
||||
shift_out_seg_valid_straddle[SEG_CNT-1] = 1'b0; // wrapped, so cannot be consumed
|
||||
shift_out_seg_sop = {2{ctrl_fifo_rd_sop}} >> out_seg_offset_reg;
|
||||
shift_out_seg_eop = {2{ctrl_fifo_rd_eop}} >> out_seg_offset_reg;
|
||||
shift_out_seg_end = {2{ctrl_fifo_rd_end}} >> out_seg_offset_reg;
|
||||
shift_out_seg_last = {2{ctrl_fifo_rd_last}} >> out_seg_offset_reg;
|
||||
|
||||
// extract data
|
||||
out_valid = 0;
|
||||
out_valid_straddle = 0;
|
||||
out_frame = out_frame_cyc;
|
||||
out_abort = 0;
|
||||
out_done = 0;
|
||||
out_seg_consumed = 0;
|
||||
|
||||
out_ctrl_seg_offset_next = out_seg_offset_reg;
|
||||
|
||||
out_cur_seg = out_seg_offset_reg;
|
||||
for (out_seg = 0; out_seg < SEG_CNT; out_seg = out_seg + 1) begin
|
||||
out_seg_offset_cyc = out_seg_offset_cyc + 1;
|
||||
|
||||
// check for contiguous valid segments
|
||||
out_valid = (~shift_out_seg_valid & ({SEG_CNT{1'b1}} >> (SEG_CNT-1 - out_seg))) == 0;
|
||||
out_valid_straddle = shift_out_seg_valid_straddle[0];
|
||||
|
||||
if (!out_frame_cyc) begin
|
||||
if (out_valid) begin
|
||||
if (shift_out_seg_sop[0]) begin
|
||||
// start of frame
|
||||
out_frame_cyc = 1'b1;
|
||||
|
||||
if (!out_done) begin
|
||||
out_ctrl_hdr_next = 1'b1;
|
||||
out_ctrl_last_len_next = ctrl_fifo_rd_last_len[AXIS_BYTE_IDX_WIDTH*out_cur_seg +: AXIS_BYTE_IDX_WIDTH];
|
||||
out_ctrl_seg_offset_next = out_cur_seg;
|
||||
end
|
||||
end else if (!out_abort) begin
|
||||
// consume null segment
|
||||
out_seg_consumed[out_cur_seg] = 1'b1;
|
||||
out_seg_consumed_next = out_seg_consumed;
|
||||
ctrl_fifo_rd_en = out_seg_consumed;
|
||||
|
||||
out_seg_offset_next = out_seg_offset_cyc;
|
||||
end
|
||||
end
|
||||
end
|
||||
out_frame = out_frame_cyc;
|
||||
|
||||
if (out_frame && !out_done) begin
|
||||
if (shift_out_seg_end[0]) begin
|
||||
// last output cycle
|
||||
out_frame_cyc = 0;
|
||||
out_done = 1;
|
||||
|
||||
if (shift_out_seg_last[0]) begin
|
||||
out_ctrl_last_next = 1'b1;
|
||||
end
|
||||
|
||||
if (out_valid && (out_valid_straddle || shift_out_seg_eop[0]) && m_axis_tready_int) begin
|
||||
out_ctrl_en_next = 1'b1;
|
||||
out_seg_consumed[out_cur_seg] = 1'b1;
|
||||
out_seg_consumed_next = out_seg_consumed;
|
||||
ctrl_fifo_rd_en = out_seg_consumed;
|
||||
out_frame_next = out_frame_cyc;
|
||||
out_seg_offset_next = out_seg_offset_cyc;
|
||||
end else begin
|
||||
out_abort = 1'b1;
|
||||
end
|
||||
end else if (output_offset_cyc == AXIS_SEG_CNT-1) begin
|
||||
// output full
|
||||
out_done = 1;
|
||||
|
||||
if (out_valid && out_valid_straddle && m_axis_tready_int) begin
|
||||
out_ctrl_en_next = 1'b1;
|
||||
out_seg_consumed[out_cur_seg] = 1'b1;
|
||||
out_seg_consumed_next = out_seg_consumed;
|
||||
ctrl_fifo_rd_en = out_seg_consumed;
|
||||
out_frame_next = out_frame_cyc;
|
||||
out_seg_offset_next = out_seg_offset_cyc;
|
||||
end else begin
|
||||
out_abort = 1'b1;
|
||||
end
|
||||
end else begin
|
||||
// middle cycle
|
||||
|
||||
if (out_valid && out_valid_straddle && m_axis_tready_int) begin
|
||||
out_seg_consumed[out_cur_seg] = 1'b1;
|
||||
end else begin
|
||||
out_abort = 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
if (output_offset_cyc == AXIS_SEG_CNT-1) begin
|
||||
output_offset_cyc = 0;
|
||||
end else begin
|
||||
output_offset_cyc = output_offset_cyc + 1;
|
||||
end
|
||||
end
|
||||
|
||||
out_cur_seg = out_cur_seg + 1;
|
||||
|
||||
// shift_out_seg_valid = shift_out_seg_valid >> 1;
|
||||
shift_out_seg_valid_straddle = shift_out_seg_valid_straddle >> 1;
|
||||
shift_out_seg_sop = shift_out_seg_sop >> 1;
|
||||
shift_out_seg_eop = shift_out_seg_eop >> 1;
|
||||
shift_out_seg_end = shift_out_seg_end >> 1;
|
||||
shift_out_seg_last = shift_out_seg_last >> 1;
|
||||
end
|
||||
|
||||
// construct output
|
||||
input_ready_cmb = out_seg_consumed_reg;
|
||||
|
||||
m_axis_tdata_int = input_data_full >> (SEG_WIDTH*out_ctrl_seg_offset_reg + HDR_WIDTH);
|
||||
|
||||
if (out_ctrl_last_reg) begin
|
||||
m_axis_tkeep_int = {AXIS_KEEP_WIDTH{1'b1}} >> (AXIS_KEEP_WIDTH-1 - out_ctrl_last_len_reg);
|
||||
end else begin
|
||||
m_axis_tkeep_int = {AXIS_KEEP_WIDTH{1'b1}};
|
||||
end
|
||||
m_axis_tlast_int = out_ctrl_last_reg;
|
||||
|
||||
if (out_ctrl_hdr_reg) begin
|
||||
axis_tid_next = input_data_full >> (SEG_WIDTH*out_ctrl_seg_offset_reg + 16 + META_ID_OFFSET);
|
||||
axis_tdest_next = input_data_full >> (SEG_WIDTH*out_ctrl_seg_offset_reg + 16 + META_DEST_OFFSET);
|
||||
axis_tuser_next = input_data_full >> (SEG_WIDTH*out_ctrl_seg_offset_reg + 16 + META_USER_OFFSET);
|
||||
end
|
||||
|
||||
m_axis_tvalid_int = out_ctrl_en_reg;
|
||||
|
||||
m_axis_tid_int = axis_tid_next;
|
||||
m_axis_tdest_int = axis_tdest_next;
|
||||
m_axis_tuser_int = axis_tuser_next;
|
||||
|
||||
if (EXPAND_INPUT) begin
|
||||
for (seg = 0; seg < SEG_CNT; seg = seg + 1) begin
|
||||
if (input_ready[seg] && input_valid[seg]) begin
|
||||
input_data_int_next[SEG_WIDTH*seg +: SEG_WIDTH] = input_data[SEG_WIDTH*seg +: SEG_WIDTH];
|
||||
input_valid_int_next[seg] = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
frame_reg <= frame_next;
|
||||
last_reg <= last_next;
|
||||
extra_cycle_reg <= extra_cycle_next;
|
||||
last_straddle_reg <= last_straddle_next;
|
||||
seg_cnt_reg <= seg_cnt_next;
|
||||
hdr_parity_err_reg <= hdr_parity_err_next;
|
||||
|
||||
out_frame_reg <= out_frame_next;
|
||||
out_seg_offset_reg <= out_seg_offset_next;
|
||||
output_offset_reg <= output_offset_next;
|
||||
out_seg_consumed_reg <= out_seg_consumed_next;
|
||||
|
||||
input_data_int_reg <= input_data_int_next;
|
||||
input_valid_int_reg <= input_valid_int_next;
|
||||
|
||||
out_ctrl_en_reg <= out_ctrl_en_next;
|
||||
out_ctrl_hdr_reg <= out_ctrl_hdr_next;
|
||||
out_ctrl_last_reg <= out_ctrl_last_next;
|
||||
out_ctrl_last_len_reg <= out_ctrl_last_len_next;
|
||||
out_ctrl_seg_offset_reg <= out_ctrl_seg_offset_next;
|
||||
|
||||
axis_tid_reg <= axis_tid_next;
|
||||
axis_tdest_reg <= axis_tdest_next;
|
||||
axis_tuser_reg <= axis_tuser_next;
|
||||
|
||||
if (rst || fifo_rst_in) begin
|
||||
frame_reg <= 1'b0;
|
||||
hdr_parity_err_reg <= 1'b0;
|
||||
out_frame_reg <= 1'b0;
|
||||
out_seg_offset_reg <= 0;
|
||||
output_offset_reg <= 0;
|
||||
out_seg_consumed_reg <= 0;
|
||||
input_valid_int_next <= 1'b0;
|
||||
out_ctrl_en_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// output datapath logic
|
||||
reg [AXIS_DATA_WIDTH-1:0] m_axis_tdata_reg = {AXIS_DATA_WIDTH{1'b0}};
|
||||
reg [AXIS_KEEP_WIDTH-1:0] m_axis_tkeep_reg = {AXIS_KEEP_WIDTH{1'b0}};
|
||||
reg m_axis_tvalid_reg = 1'b0;
|
||||
reg m_axis_tlast_reg = 1'b0;
|
||||
reg [AXIS_ID_WIDTH-1:0] m_axis_tid_reg = {AXIS_ID_WIDTH{1'b0}};
|
||||
reg [AXIS_DEST_WIDTH-1:0] m_axis_tdest_reg = {AXIS_DEST_WIDTH{1'b0}};
|
||||
reg [AXIS_USER_WIDTH-1:0] m_axis_tuser_reg = {AXIS_USER_WIDTH{1'b0}};
|
||||
|
||||
reg [OUTPUT_FIFO_ADDR_WIDTH+1-1:0] out_fifo_wr_ptr_reg = 0;
|
||||
reg [OUTPUT_FIFO_ADDR_WIDTH+1-1:0] out_fifo_rd_ptr_reg = 0;
|
||||
reg out_fifo_half_full_reg = 1'b0;
|
||||
|
||||
wire out_fifo_full = out_fifo_wr_ptr_reg == (out_fifo_rd_ptr_reg ^ {1'b1, {OUTPUT_FIFO_ADDR_WIDTH{1'b0}}});
|
||||
wire out_fifo_empty = out_fifo_wr_ptr_reg == out_fifo_rd_ptr_reg;
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [AXIS_DATA_WIDTH-1:0] out_fifo_tdata[2**OUTPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [AXIS_KEEP_WIDTH-1:0] out_fifo_tkeep[2**OUTPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg out_fifo_tlast[2**OUTPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [AXIS_ID_WIDTH-1:0] out_fifo_tid[2**OUTPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [AXIS_DEST_WIDTH-1:0] out_fifo_tdest[2**OUTPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [AXIS_USER_WIDTH-1:0] out_fifo_tuser[2**OUTPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
assign m_axis_tready_int = !out_fifo_half_full_reg;
|
||||
|
||||
assign m_axis_tdata = m_axis_tdata_reg;
|
||||
assign m_axis_tkeep = AXIS_KEEP_ENABLE ? m_axis_tkeep_reg : {AXIS_KEEP_WIDTH{1'b1}};
|
||||
assign m_axis_tvalid = m_axis_tvalid_reg;
|
||||
assign m_axis_tlast = AXIS_LAST_ENABLE ? m_axis_tlast_reg : 1'b1;
|
||||
assign m_axis_tid = AXIS_ID_ENABLE ? m_axis_tid_reg : {AXIS_ID_WIDTH{1'b0}};
|
||||
assign m_axis_tdest = AXIS_DEST_ENABLE ? m_axis_tdest_reg : {AXIS_DEST_WIDTH{1'b0}};
|
||||
assign m_axis_tuser = AXIS_USER_ENABLE ? m_axis_tuser_reg : {AXIS_USER_WIDTH{1'b0}};
|
||||
|
||||
always @(posedge clk) begin
|
||||
m_axis_tvalid_reg <= m_axis_tvalid_reg && !m_axis_tready;
|
||||
|
||||
out_fifo_half_full_reg <= $unsigned(out_fifo_wr_ptr_reg - out_fifo_rd_ptr_reg) >= 2**(OUTPUT_FIFO_ADDR_WIDTH-1);
|
||||
|
||||
if (!out_fifo_full && m_axis_tvalid_int) begin
|
||||
out_fifo_tdata[out_fifo_wr_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]] <= m_axis_tdata_int;
|
||||
out_fifo_tkeep[out_fifo_wr_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]] <= m_axis_tkeep_int;
|
||||
out_fifo_tlast[out_fifo_wr_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]] <= m_axis_tlast_int;
|
||||
out_fifo_tid[out_fifo_wr_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]] <= m_axis_tid_int;
|
||||
out_fifo_tdest[out_fifo_wr_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]] <= m_axis_tdest_int;
|
||||
out_fifo_tuser[out_fifo_wr_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]] <= m_axis_tuser_int;
|
||||
out_fifo_wr_ptr_reg <= out_fifo_wr_ptr_reg + 1;
|
||||
end
|
||||
|
||||
if (!out_fifo_empty && (!m_axis_tvalid_reg || m_axis_tready)) begin
|
||||
m_axis_tdata_reg <= out_fifo_tdata[out_fifo_rd_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]];
|
||||
m_axis_tkeep_reg <= out_fifo_tkeep[out_fifo_rd_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]];
|
||||
m_axis_tvalid_reg <= 1'b1;
|
||||
m_axis_tlast_reg <= out_fifo_tlast[out_fifo_rd_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]];
|
||||
m_axis_tid_reg <= out_fifo_tid[out_fifo_rd_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]];
|
||||
m_axis_tdest_reg <= out_fifo_tdest[out_fifo_rd_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]];
|
||||
m_axis_tuser_reg <= out_fifo_tuser[out_fifo_rd_ptr_reg[OUTPUT_FIFO_ADDR_WIDTH-1:0]];
|
||||
out_fifo_rd_ptr_reg <= out_fifo_rd_ptr_reg + 1;
|
||||
end
|
||||
|
||||
if (rst || fifo_rst_in) begin
|
||||
out_fifo_wr_ptr_reg <= 0;
|
||||
out_fifo_rd_ptr_reg <= 0;
|
||||
m_axis_tvalid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
794
rtl/axi_vfifo_enc.v
Normal file
794
rtl/axi_vfifo_enc.v
Normal file
@ -0,0 +1,794 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* AXI4 virtual FIFO (encoder)
|
||||
*/
|
||||
module axi_vfifo_enc #
|
||||
(
|
||||
// Width of input segment
|
||||
parameter SEG_WIDTH = 32,
|
||||
// Segment count
|
||||
parameter SEG_CNT = 2,
|
||||
// Width of AXI stream interfaces in bits
|
||||
parameter AXIS_DATA_WIDTH = SEG_WIDTH*SEG_CNT/2,
|
||||
// Use AXI stream tkeep signal
|
||||
parameter AXIS_KEEP_ENABLE = (AXIS_DATA_WIDTH>8),
|
||||
// AXI stream tkeep signal width (words per cycle)
|
||||
parameter AXIS_KEEP_WIDTH = (AXIS_DATA_WIDTH/8),
|
||||
// Use AXI stream tlast signal
|
||||
parameter AXIS_LAST_ENABLE = 1,
|
||||
// Propagate AXI stream tid signal
|
||||
parameter AXIS_ID_ENABLE = 0,
|
||||
// AXI stream tid signal width
|
||||
parameter AXIS_ID_WIDTH = 8,
|
||||
// Propagate AXI stream tdest signal
|
||||
parameter AXIS_DEST_ENABLE = 0,
|
||||
// AXI stream tdest signal width
|
||||
parameter AXIS_DEST_WIDTH = 8,
|
||||
// Propagate AXI stream tuser signal
|
||||
parameter AXIS_USER_ENABLE = 1,
|
||||
// AXI stream tuser signal width
|
||||
parameter AXIS_USER_WIDTH = 1
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* AXI stream data input
|
||||
*/
|
||||
input wire [AXIS_DATA_WIDTH-1:0] s_axis_tdata,
|
||||
input wire [AXIS_KEEP_WIDTH-1:0] s_axis_tkeep,
|
||||
input wire s_axis_tvalid,
|
||||
output wire s_axis_tready,
|
||||
input wire s_axis_tlast,
|
||||
input wire [AXIS_ID_WIDTH-1:0] s_axis_tid,
|
||||
input wire [AXIS_DEST_WIDTH-1:0] s_axis_tdest,
|
||||
input wire [AXIS_USER_WIDTH-1:0] s_axis_tuser,
|
||||
|
||||
/*
|
||||
* Segmented data output (to virtual FIFO channel)
|
||||
*/
|
||||
input wire fifo_rst_in,
|
||||
output wire [SEG_CNT*SEG_WIDTH-1:0] output_data,
|
||||
output wire [SEG_CNT-1:0] output_valid,
|
||||
input wire fifo_watermark_in
|
||||
);
|
||||
|
||||
parameter AXIS_KEEP_WIDTH_INT = AXIS_KEEP_ENABLE ? AXIS_KEEP_WIDTH : 1;
|
||||
parameter AXIS_BYTE_LANES = AXIS_KEEP_WIDTH_INT;
|
||||
parameter AXIS_BYTE_SIZE = AXIS_DATA_WIDTH/AXIS_BYTE_LANES;
|
||||
parameter CL_AXIS_BYTE_LANES = $clog2(AXIS_BYTE_LANES);
|
||||
|
||||
parameter BYTE_SIZE = AXIS_BYTE_SIZE;
|
||||
|
||||
parameter SEG_BYTE_LANES = SEG_WIDTH / BYTE_SIZE;
|
||||
|
||||
parameter EXPAND_OUTPUT = SEG_CNT < 2;
|
||||
|
||||
parameter SEG_CNT_INT = EXPAND_OUTPUT ? SEG_CNT*2 : SEG_CNT;
|
||||
|
||||
parameter SEG_IDX_WIDTH = $clog2(SEG_CNT_INT);
|
||||
parameter SEG_BYTE_IDX_WIDTH = $clog2(SEG_BYTE_LANES);
|
||||
|
||||
parameter AXIS_SEG_CNT = (AXIS_DATA_WIDTH + SEG_WIDTH-1) / SEG_WIDTH;
|
||||
parameter AXIS_SEG_IDX_WIDTH = AXIS_SEG_CNT > 1 ? $clog2(AXIS_SEG_CNT) : 1;
|
||||
parameter AXIS_LEN_MASK = AXIS_BYTE_LANES-1;
|
||||
|
||||
parameter IN_OFFS_WIDTH = AXIS_SEG_IDX_WIDTH;
|
||||
|
||||
parameter META_ID_OFFSET = 0;
|
||||
parameter META_DEST_OFFSET = META_ID_OFFSET + (AXIS_ID_ENABLE ? AXIS_ID_WIDTH : 0);
|
||||
parameter META_USER_OFFSET = META_DEST_OFFSET + (AXIS_DEST_ENABLE ? AXIS_DEST_WIDTH : 0);
|
||||
parameter META_WIDTH = META_USER_OFFSET + (AXIS_USER_ENABLE ? AXIS_USER_WIDTH : 0);
|
||||
parameter HDR_SIZE = (16 + META_WIDTH + BYTE_SIZE-1) / BYTE_SIZE;
|
||||
parameter HDR_WIDTH = HDR_SIZE * BYTE_SIZE;
|
||||
|
||||
parameter HDR_LEN_WIDTH = 12;
|
||||
parameter HDR_SEG_LEN_WIDTH = HDR_LEN_WIDTH-SEG_BYTE_IDX_WIDTH;
|
||||
|
||||
parameter INPUT_FIFO_ADDR_WIDTH = 5;
|
||||
parameter HDR_FIFO_ADDR_WIDTH = INPUT_FIFO_ADDR_WIDTH + SEG_IDX_WIDTH;
|
||||
|
||||
parameter INPUT_FIFO_PTR_WIDTH = INPUT_FIFO_ADDR_WIDTH + SEG_IDX_WIDTH;
|
||||
parameter HDR_FIFO_PTR_WIDTH = HDR_FIFO_ADDR_WIDTH;
|
||||
|
||||
parameter INPUT_FIFO_SIZE = SEG_BYTE_LANES * SEG_CNT_INT * 2**INPUT_FIFO_ADDR_WIDTH;
|
||||
|
||||
parameter MAX_BLOCK_LEN = INPUT_FIFO_SIZE / 2 > 4096 ? 4096 : INPUT_FIFO_SIZE / 2;
|
||||
|
||||
// validate parameters
|
||||
initial begin
|
||||
if (AXIS_BYTE_SIZE * AXIS_KEEP_WIDTH_INT != AXIS_DATA_WIDTH) begin
|
||||
$error("Error: AXI stream data width not evenly divisible (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (AXIS_SEG_CNT * SEG_WIDTH != AXIS_DATA_WIDTH) begin
|
||||
$error("Error: AXI stream data width not evenly divisible into segments (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (SEG_WIDTH < HDR_SIZE*BYTE_SIZE) begin
|
||||
$error("Error: Segment smaller than header (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
reg [INPUT_FIFO_PTR_WIDTH+1-1:0] input_fifo_wr_ptr_reg = 0, input_fifo_wr_ptr_next;
|
||||
reg [INPUT_FIFO_PTR_WIDTH+1-1:0] input_fifo_rd_ptr_reg = 0, input_fifo_rd_ptr_next;
|
||||
reg [HDR_FIFO_PTR_WIDTH+1-1:0] hdr_fifo_wr_ptr_reg = 0, hdr_fifo_wr_ptr_next;
|
||||
reg [HDR_FIFO_PTR_WIDTH+1-1:0] hdr_fifo_rd_ptr_reg = 0, hdr_fifo_rd_ptr_next;
|
||||
|
||||
reg [SEG_CNT_INT-1:0] mem_rd_data_valid_reg = 0, mem_rd_data_valid_next;
|
||||
reg hdr_mem_rd_data_valid_reg = 0, hdr_mem_rd_data_valid_next;
|
||||
|
||||
reg [AXIS_DATA_WIDTH-1:0] int_seg_data;
|
||||
reg [AXIS_SEG_CNT-1:0] int_seg_valid;
|
||||
|
||||
reg [SEG_CNT_INT*SEG_WIDTH-1:0] seg_mem_wr_data;
|
||||
reg [SEG_CNT_INT-1:0] seg_mem_wr_valid;
|
||||
reg [SEG_CNT_INT*INPUT_FIFO_ADDR_WIDTH-1:0] seg_mem_wr_addr_reg = 0, seg_mem_wr_addr_next;
|
||||
reg [SEG_CNT_INT-1:0] seg_mem_wr_en;
|
||||
reg [SEG_CNT_INT*SEG_IDX_WIDTH-1:0] seg_mem_wr_sel;
|
||||
|
||||
wire [SEG_CNT_INT*SEG_WIDTH-1:0] seg_mem_rd_data;
|
||||
reg [SEG_CNT_INT*INPUT_FIFO_ADDR_WIDTH-1:0] seg_mem_rd_addr_reg = 0, seg_mem_rd_addr_next;
|
||||
reg [SEG_CNT_INT-1:0] seg_mem_rd_en;
|
||||
|
||||
reg [HDR_LEN_WIDTH-1:0] hdr_mem_wr_len;
|
||||
reg hdr_mem_wr_last;
|
||||
reg [META_WIDTH-1:0] hdr_mem_wr_meta;
|
||||
reg [HDR_FIFO_ADDR_WIDTH-1:0] hdr_mem_wr_addr;
|
||||
reg hdr_mem_wr_en;
|
||||
|
||||
wire [HDR_LEN_WIDTH-1:0] hdr_mem_rd_len;
|
||||
wire hdr_mem_rd_last;
|
||||
wire [META_WIDTH-1:0] hdr_mem_rd_meta;
|
||||
reg [HDR_FIFO_ADDR_WIDTH-1:0] hdr_mem_rd_addr_reg = 0, hdr_mem_rd_addr_next;
|
||||
reg hdr_mem_rd_en;
|
||||
|
||||
reg input_fifo_full_reg = 1'b0;
|
||||
reg input_fifo_half_full_reg = 1'b0;
|
||||
reg input_fifo_empty_reg = 1'b1;
|
||||
reg [INPUT_FIFO_PTR_WIDTH+1-1:0] input_fifo_count_reg = 0;
|
||||
reg hdr_fifo_full_reg = 1'b0;
|
||||
reg hdr_fifo_half_full_reg = 1'b0;
|
||||
reg hdr_fifo_empty_reg = 1'b1;
|
||||
reg [HDR_FIFO_PTR_WIDTH+1-1:0] hdr_fifo_count_reg = 0;
|
||||
|
||||
reg [SEG_CNT*SEG_WIDTH-1:0] output_data_reg = 0, output_data_next;
|
||||
reg [SEG_CNT-1:0] output_valid_reg = 0, output_valid_next;
|
||||
|
||||
assign s_axis_tready = !input_fifo_full_reg && !hdr_fifo_full_reg && !fifo_rst_in;
|
||||
|
||||
assign output_data = output_data_reg;
|
||||
assign output_valid = output_valid_reg;
|
||||
|
||||
generate
|
||||
|
||||
genvar n;
|
||||
|
||||
for (n = 0; n < SEG_CNT_INT; n = n + 1) begin : seg_ram
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [SEG_WIDTH-1:0] seg_mem_data[2**INPUT_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
wire wr_en = seg_mem_wr_en[n];
|
||||
wire [INPUT_FIFO_ADDR_WIDTH-1:0] wr_addr = seg_mem_wr_addr_reg[n*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH];
|
||||
wire [SEG_WIDTH-1:0] wr_data = seg_mem_wr_data[n*SEG_WIDTH +: SEG_WIDTH];
|
||||
|
||||
wire rd_en = seg_mem_rd_en[n];
|
||||
wire [INPUT_FIFO_ADDR_WIDTH-1:0] rd_addr = seg_mem_rd_addr_reg[n*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH];
|
||||
reg [SEG_WIDTH-1:0] rd_data_reg = 0;
|
||||
|
||||
assign seg_mem_rd_data[n*SEG_WIDTH +: SEG_WIDTH] = rd_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (wr_en) begin
|
||||
seg_mem_data[wr_addr] <= wr_data;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rd_en) begin
|
||||
rd_data_reg <= seg_mem_data[rd_addr];
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [HDR_LEN_WIDTH-1:0] hdr_mem_len[2**HDR_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg hdr_mem_last[2**HDR_FIFO_ADDR_WIDTH-1:0];
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [META_WIDTH-1:0] hdr_mem_meta[2**HDR_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
reg [HDR_LEN_WIDTH-1:0] hdr_mem_rd_len_reg = 0;
|
||||
reg hdr_mem_rd_last_reg = 1'b0;
|
||||
reg [META_WIDTH-1:0] hdr_mem_rd_meta_reg = 0;
|
||||
|
||||
assign hdr_mem_rd_len = hdr_mem_rd_len_reg;
|
||||
assign hdr_mem_rd_last = hdr_mem_rd_last_reg;
|
||||
assign hdr_mem_rd_meta = hdr_mem_rd_meta_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (hdr_mem_wr_en) begin
|
||||
hdr_mem_len[hdr_mem_wr_addr] <= hdr_mem_wr_len;
|
||||
hdr_mem_last[hdr_mem_wr_addr] <= hdr_mem_wr_last;
|
||||
hdr_mem_meta[hdr_mem_wr_addr] <= hdr_mem_wr_meta;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (hdr_mem_rd_en) begin
|
||||
hdr_mem_rd_len_reg <= hdr_mem_len[hdr_mem_rd_addr_reg];
|
||||
hdr_mem_rd_last_reg <= hdr_mem_last[hdr_mem_rd_addr_reg];
|
||||
hdr_mem_rd_meta_reg <= hdr_mem_meta[hdr_mem_rd_addr_reg];
|
||||
end
|
||||
end
|
||||
|
||||
// limits
|
||||
always @(posedge clk) begin
|
||||
input_fifo_full_reg <= $unsigned(input_fifo_wr_ptr_reg - input_fifo_rd_ptr_reg) >= (2**INPUT_FIFO_ADDR_WIDTH*SEG_CNT_INT)-SEG_CNT_INT*2;
|
||||
input_fifo_half_full_reg <= $unsigned(input_fifo_wr_ptr_reg - input_fifo_rd_ptr_reg) >= (2**INPUT_FIFO_ADDR_WIDTH*SEG_CNT_INT)/2;
|
||||
hdr_fifo_full_reg <= $unsigned(hdr_fifo_wr_ptr_reg - hdr_fifo_rd_ptr_reg) >= 2**HDR_FIFO_ADDR_WIDTH-4;
|
||||
hdr_fifo_half_full_reg <= $unsigned(hdr_fifo_wr_ptr_reg - hdr_fifo_rd_ptr_reg) >= 2**HDR_FIFO_ADDR_WIDTH/2;
|
||||
|
||||
if (rst) begin
|
||||
input_fifo_full_reg <= 1'b0;
|
||||
input_fifo_half_full_reg <= 1'b0;
|
||||
hdr_fifo_full_reg <= 1'b0;
|
||||
hdr_fifo_half_full_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// Split input segments
|
||||
integer si;
|
||||
|
||||
always @* begin
|
||||
int_seg_data = s_axis_tdata;
|
||||
int_seg_valid = 0;
|
||||
|
||||
if (s_axis_tvalid) begin
|
||||
if (s_axis_tlast) begin
|
||||
for (si = 0; si < AXIS_SEG_CNT; si = si + 1) begin
|
||||
int_seg_valid[si] = s_axis_tkeep[SEG_BYTE_LANES*si +: SEG_BYTE_LANES] != 0;
|
||||
end
|
||||
end else begin
|
||||
int_seg_valid = {AXIS_SEG_CNT{1'b1}};
|
||||
end
|
||||
end else begin
|
||||
int_seg_valid = 0;
|
||||
end
|
||||
end
|
||||
|
||||
// Write logic
|
||||
integer seg, k;
|
||||
reg [SEG_IDX_WIDTH+1-1:0] seg_count;
|
||||
reg [SEG_IDX_WIDTH-1:0] cur_seg;
|
||||
|
||||
reg frame_reg = 1'b0, frame_next;
|
||||
reg [HDR_LEN_WIDTH-1:0] len_reg = 0, len_next;
|
||||
|
||||
reg cycle_valid_reg = 1'b0, cycle_valid_next;
|
||||
reg cycle_last_reg = 1'b0, cycle_last_next;
|
||||
reg [CL_AXIS_BYTE_LANES+1-1:0] cycle_len_reg = 0, cycle_len_next;
|
||||
reg [META_WIDTH-1:0] cycle_meta_reg = 0, cycle_meta_next;
|
||||
|
||||
reg [CL_AXIS_BYTE_LANES+1-1:0] cycle_len;
|
||||
|
||||
reg [HDR_LEN_WIDTH-1:0] hdr_len_reg = 0, hdr_len_next;
|
||||
reg [META_WIDTH-1:0] hdr_meta_reg = 0, hdr_meta_next;
|
||||
reg hdr_last_reg = 0, hdr_last_next;
|
||||
reg hdr_commit_reg = 0, hdr_commit_next;
|
||||
reg hdr_commit_prev_reg = 0, hdr_commit_prev_next;
|
||||
reg hdr_valid_reg = 0, hdr_valid_next;
|
||||
|
||||
wire [META_WIDTH-1:0] s_axis_meta;
|
||||
|
||||
generate
|
||||
|
||||
if (AXIS_ID_ENABLE) assign s_axis_meta[META_ID_OFFSET +: AXIS_ID_WIDTH] = s_axis_tid;
|
||||
if (AXIS_DEST_ENABLE) assign s_axis_meta[META_DEST_OFFSET +: AXIS_DEST_WIDTH] = s_axis_tdest;
|
||||
if (AXIS_USER_ENABLE) assign s_axis_meta[META_USER_OFFSET +: AXIS_USER_WIDTH] = s_axis_tuser;
|
||||
|
||||
endgenerate
|
||||
|
||||
always @* begin
|
||||
input_fifo_wr_ptr_next = input_fifo_wr_ptr_reg;
|
||||
hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg;
|
||||
|
||||
if (AXIS_KEEP_ENABLE) begin
|
||||
cycle_len = 0;
|
||||
for (k = 0; k < AXIS_BYTE_LANES; k = k + 1) begin
|
||||
cycle_len = cycle_len + s_axis_tkeep[k];
|
||||
end
|
||||
end else begin
|
||||
cycle_len = AXIS_BYTE_LANES;
|
||||
end
|
||||
|
||||
// pack segments
|
||||
seg_mem_wr_valid = 0;
|
||||
seg_mem_wr_sel = 0;
|
||||
cur_seg = input_fifo_wr_ptr_reg[SEG_IDX_WIDTH-1:0];
|
||||
seg_count = 0;
|
||||
for (seg = 0; seg < AXIS_SEG_CNT; seg = seg + 1) begin
|
||||
if (int_seg_valid[seg]) begin
|
||||
seg_mem_wr_valid[cur_seg +: 1] = 1'b1;
|
||||
seg_mem_wr_sel[cur_seg*SEG_IDX_WIDTH +: SEG_IDX_WIDTH] = seg;
|
||||
cur_seg = cur_seg + 1;
|
||||
seg_count = seg_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
for (seg = 0; seg < SEG_CNT_INT; seg = seg + 1) begin
|
||||
seg_mem_wr_data[seg*SEG_WIDTH +: SEG_WIDTH] = int_seg_data[seg_mem_wr_sel[seg*SEG_IDX_WIDTH +: SEG_IDX_WIDTH]*SEG_WIDTH +: SEG_WIDTH];
|
||||
end
|
||||
|
||||
seg_mem_wr_addr_next = seg_mem_wr_addr_reg;
|
||||
seg_mem_wr_en = 0;
|
||||
|
||||
hdr_mem_wr_len = hdr_len_reg;
|
||||
hdr_mem_wr_last = hdr_last_reg;
|
||||
hdr_mem_wr_meta = hdr_meta_reg;
|
||||
hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg;
|
||||
hdr_mem_wr_en = 1'b0;
|
||||
|
||||
frame_next = frame_reg;
|
||||
len_next = len_reg;
|
||||
|
||||
cycle_valid_next = 1'b0;
|
||||
cycle_last_next = cycle_last_reg;
|
||||
cycle_len_next = cycle_len_reg;
|
||||
cycle_meta_next = cycle_meta_reg;
|
||||
|
||||
hdr_len_next = len_reg;
|
||||
hdr_meta_next = cycle_meta_reg;
|
||||
hdr_last_next = cycle_last_reg;
|
||||
hdr_commit_next = 1'b0;
|
||||
hdr_commit_prev_next = 1'b0;
|
||||
hdr_valid_next = 1'b0;
|
||||
|
||||
if (s_axis_tvalid && s_axis_tready) begin
|
||||
// transfer data
|
||||
seg_mem_wr_en = seg_mem_wr_valid;
|
||||
input_fifo_wr_ptr_next = input_fifo_wr_ptr_reg + seg_count;
|
||||
for (seg = 0; seg < SEG_CNT_INT; seg = seg + 1) begin
|
||||
seg_mem_wr_addr_next[seg*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH] = (input_fifo_wr_ptr_next + (SEG_CNT_INT-1 - seg)) >> SEG_IDX_WIDTH;
|
||||
end
|
||||
|
||||
cycle_valid_next = 1'b1;
|
||||
cycle_last_next = s_axis_tlast;
|
||||
cycle_len_next = cycle_len;
|
||||
cycle_meta_next = s_axis_meta;
|
||||
end
|
||||
|
||||
if (cycle_valid_reg) begin
|
||||
// process packets
|
||||
if (!frame_reg) begin
|
||||
frame_next = 1'b1;
|
||||
|
||||
if (cycle_last_reg) begin
|
||||
len_next = cycle_len_reg;
|
||||
end else begin
|
||||
len_next = AXIS_BYTE_LANES;
|
||||
end
|
||||
|
||||
hdr_len_next = len_next-1;
|
||||
hdr_meta_next = cycle_meta_reg;
|
||||
hdr_last_next = cycle_last_reg;
|
||||
hdr_valid_next = 1'b1;
|
||||
|
||||
if (cycle_last_reg) begin
|
||||
// end of frame
|
||||
|
||||
hdr_commit_next = 1'b1;
|
||||
|
||||
frame_next = 1'b0;
|
||||
end
|
||||
end else begin
|
||||
if (cycle_meta_reg != hdr_meta_reg) begin
|
||||
if (cycle_last_reg) begin
|
||||
len_next = cycle_len_reg;
|
||||
end else begin
|
||||
len_next = AXIS_BYTE_LANES;
|
||||
end
|
||||
end else begin
|
||||
if (cycle_last_reg) begin
|
||||
len_next = len_reg + cycle_len_reg;
|
||||
end else begin
|
||||
len_next = len_reg + AXIS_BYTE_LANES;
|
||||
end
|
||||
end
|
||||
|
||||
hdr_len_next = len_next-1;
|
||||
hdr_meta_next = cycle_meta_reg;
|
||||
hdr_last_next = cycle_last_reg;
|
||||
hdr_valid_next = 1'b1;
|
||||
|
||||
if (cycle_meta_reg != hdr_meta_reg) begin
|
||||
// meta changed
|
||||
|
||||
hdr_commit_prev_next = 1'b1;
|
||||
|
||||
if (cycle_last_reg) begin
|
||||
hdr_commit_next = 1'b1;
|
||||
frame_next = 1'b0;
|
||||
end
|
||||
end else if (cycle_last_reg || len_next >= MAX_BLOCK_LEN) begin
|
||||
// end of frame or block is full
|
||||
|
||||
hdr_commit_next = 1'b1;
|
||||
|
||||
frame_next = 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (hdr_valid_reg) begin
|
||||
hdr_mem_wr_len = hdr_len_reg;
|
||||
hdr_mem_wr_last = hdr_last_reg;
|
||||
hdr_mem_wr_meta = hdr_meta_reg;
|
||||
hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg;
|
||||
hdr_mem_wr_en = 1'b1;
|
||||
|
||||
if (hdr_commit_prev_reg) begin
|
||||
if (hdr_commit_reg) begin
|
||||
hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg + 2;
|
||||
hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg + 1;
|
||||
end else begin
|
||||
hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg + 1;
|
||||
hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg + 1;
|
||||
end
|
||||
end else begin
|
||||
if (hdr_commit_reg) begin
|
||||
hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg + 1;
|
||||
hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
input_fifo_wr_ptr_reg <= input_fifo_wr_ptr_next;
|
||||
hdr_fifo_wr_ptr_reg <= hdr_fifo_wr_ptr_next;
|
||||
|
||||
seg_mem_wr_addr_reg <= seg_mem_wr_addr_next;
|
||||
|
||||
frame_reg <= frame_next;
|
||||
len_reg <= len_next;
|
||||
|
||||
cycle_valid_reg <= cycle_valid_next;
|
||||
cycle_last_reg <= cycle_last_next;
|
||||
cycle_len_reg <= cycle_len_next;
|
||||
cycle_meta_reg <= cycle_meta_next;
|
||||
|
||||
hdr_len_reg <= hdr_len_next;
|
||||
hdr_meta_reg <= hdr_meta_next;
|
||||
hdr_last_reg <= hdr_last_next;
|
||||
hdr_commit_reg <= hdr_commit_next;
|
||||
hdr_commit_prev_reg <= hdr_commit_prev_next;
|
||||
hdr_valid_reg <= hdr_valid_next;
|
||||
|
||||
if (rst || fifo_rst_in) begin
|
||||
input_fifo_wr_ptr_reg <= 0;
|
||||
hdr_fifo_wr_ptr_reg <= 0;
|
||||
|
||||
seg_mem_wr_addr_reg <= 0;
|
||||
|
||||
frame_reg <= 1'b0;
|
||||
|
||||
cycle_valid_reg <= 1'b0;
|
||||
hdr_valid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// Read logic
|
||||
integer rd_seg;
|
||||
reg [SEG_IDX_WIDTH-1:0] cur_rd_seg;
|
||||
reg rd_valid;
|
||||
|
||||
reg out_frame_reg = 1'b0, out_frame_next;
|
||||
reg [HDR_LEN_WIDTH-1:0] out_len_reg = 0, out_len_next;
|
||||
reg out_split1_reg = 1'b0, out_split1_next;
|
||||
reg [HDR_SEG_LEN_WIDTH-1:0] out_seg_cnt_in_reg = 0, out_seg_cnt_in_next;
|
||||
reg out_seg_last_straddle_reg = 1'b0, out_seg_last_straddle_next;
|
||||
reg [SEG_IDX_WIDTH-1:0] out_seg_offset_reg = 0, out_seg_offset_next;
|
||||
reg [SEG_IDX_WIDTH-1:0] out_seg_fifo_offset_reg = 0, out_seg_fifo_offset_next;
|
||||
reg [SEG_IDX_WIDTH+1-1:0] out_seg_count_reg = 0, out_seg_count_next;
|
||||
|
||||
reg [HDR_WIDTH-1:0] out_hdr_reg = 0, out_hdr_next;
|
||||
|
||||
reg [SEG_CNT_INT-1:0] out_ctl_seg_hdr_reg = 0, out_ctl_seg_hdr_next, out_ctl_seg_hdr_raw;
|
||||
reg [SEG_CNT_INT-1:0] out_ctl_seg_split1_reg = 0, out_ctl_seg_split1_next, out_ctl_seg_split1_raw;
|
||||
reg [SEG_CNT_INT-1:0] out_ctl_seg_en_reg = 0, out_ctl_seg_en_next, out_ctl_seg_en_raw;
|
||||
reg [SEG_IDX_WIDTH-1:0] out_ctl_seg_idx_reg[SEG_CNT_INT-1:0], out_ctl_seg_idx_next[SEG_CNT_INT-1:0];
|
||||
reg [SEG_IDX_WIDTH-1:0] out_ctl_seg_offset_reg = 0, out_ctl_seg_offset_next;
|
||||
|
||||
reg [HDR_WIDTH-1:0] out_shift_reg = 0, out_shift_next;
|
||||
|
||||
reg [7:0] block_timeout_count_reg = 0, block_timeout_count_next;
|
||||
reg block_timeout_reg = 0, block_timeout_next;
|
||||
|
||||
always @* begin
|
||||
input_fifo_rd_ptr_next = input_fifo_rd_ptr_reg;
|
||||
hdr_fifo_rd_ptr_next = hdr_fifo_rd_ptr_reg;
|
||||
|
||||
mem_rd_data_valid_next = mem_rd_data_valid_reg;
|
||||
hdr_mem_rd_data_valid_next = hdr_mem_rd_data_valid_reg;
|
||||
|
||||
output_data_next = output_data_reg;
|
||||
output_valid_next = 0;
|
||||
|
||||
seg_mem_rd_addr_next = seg_mem_rd_addr_reg;
|
||||
seg_mem_rd_en = 0;
|
||||
|
||||
hdr_mem_rd_addr_next = hdr_mem_rd_addr_reg;
|
||||
hdr_mem_rd_en = 0;
|
||||
|
||||
out_frame_next = out_frame_reg;
|
||||
out_len_next = out_len_reg;
|
||||
out_split1_next = out_split1_reg;
|
||||
out_seg_cnt_in_next = out_seg_cnt_in_reg;
|
||||
out_seg_last_straddle_next = out_seg_last_straddle_reg;
|
||||
out_seg_offset_next = out_seg_offset_reg;
|
||||
out_seg_fifo_offset_next = out_seg_fifo_offset_reg;
|
||||
|
||||
out_hdr_next = out_hdr_reg;
|
||||
|
||||
out_ctl_seg_hdr_raw = 0;
|
||||
out_ctl_seg_hdr_next = 0;
|
||||
out_ctl_seg_split1_raw = 0;
|
||||
out_ctl_seg_split1_next = 0;
|
||||
out_ctl_seg_en_raw = 0;
|
||||
out_ctl_seg_en_next = 0;
|
||||
out_ctl_seg_offset_next = out_seg_offset_reg;
|
||||
|
||||
for (seg = 0; seg < SEG_CNT_INT; seg = seg + 1) begin
|
||||
out_ctl_seg_idx_next[seg] = out_seg_fifo_offset_reg - out_seg_offset_reg + seg;
|
||||
end
|
||||
|
||||
// partial block timeout handling
|
||||
block_timeout_count_next = block_timeout_count_reg;
|
||||
block_timeout_next = block_timeout_count_reg == 0;
|
||||
if (output_valid || out_seg_offset_reg == 0) begin
|
||||
block_timeout_count_next = 8'hff;
|
||||
block_timeout_next = 1'b0;
|
||||
end else if (block_timeout_count_reg > 0) begin
|
||||
block_timeout_count_next = block_timeout_count_reg - 1;
|
||||
end
|
||||
|
||||
// process headers and generate output commands
|
||||
if (!fifo_watermark_in) begin
|
||||
if (out_frame_reg) begin
|
||||
if (out_seg_cnt_in_next >= SEG_CNT_INT) begin
|
||||
out_frame_next = out_seg_last_straddle_next || out_seg_cnt_in_next > SEG_CNT_INT;
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
|
||||
out_seg_offset_next = out_seg_offset_reg + SEG_CNT_INT;
|
||||
out_seg_fifo_offset_next = out_seg_fifo_offset_reg + SEG_CNT_INT;
|
||||
end else begin
|
||||
out_frame_next = 1'b0;
|
||||
if (out_seg_last_straddle_next) begin
|
||||
out_ctl_seg_split1_raw = 1 << out_seg_cnt_in_next;
|
||||
if (out_seg_cnt_in_next == SEG_CNT_INT-1) begin
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
|
||||
end else begin
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - (out_seg_cnt_in_next+1));
|
||||
end
|
||||
out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next+1;
|
||||
end else begin
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - out_seg_cnt_in_next);
|
||||
out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next;
|
||||
end
|
||||
out_seg_fifo_offset_next = out_seg_fifo_offset_reg + out_seg_cnt_in_next;
|
||||
end
|
||||
|
||||
out_seg_cnt_in_next = out_seg_cnt_in_next - SEG_CNT_INT;
|
||||
end else begin
|
||||
out_len_next = hdr_mem_rd_len;
|
||||
out_seg_cnt_in_next = (hdr_mem_rd_len + SEG_BYTE_LANES) >> SEG_BYTE_IDX_WIDTH;
|
||||
out_seg_last_straddle_next = ((hdr_mem_rd_len & (SEG_BYTE_LANES-1)) + HDR_SIZE) >> SEG_BYTE_IDX_WIDTH != 0;
|
||||
out_hdr_next = 0;
|
||||
out_hdr_next[0] = 1'b1;
|
||||
out_hdr_next[1] = hdr_mem_rd_last;
|
||||
out_hdr_next[2] = !hdr_mem_rd_last;
|
||||
out_hdr_next[15:4] = hdr_mem_rd_len;
|
||||
out_hdr_next[3] = ^hdr_mem_rd_len;
|
||||
if (META_WIDTH > 0) begin
|
||||
out_hdr_next[16 +: META_WIDTH] = hdr_mem_rd_meta;
|
||||
end
|
||||
|
||||
out_ctl_seg_hdr_raw = 1;
|
||||
|
||||
if (hdr_mem_rd_data_valid_reg) begin
|
||||
if (out_seg_cnt_in_next >= SEG_CNT_INT) begin
|
||||
out_frame_next = out_seg_last_straddle_next || out_seg_cnt_in_next > SEG_CNT_INT;
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
|
||||
out_seg_offset_next = out_seg_offset_reg + SEG_CNT_INT;
|
||||
out_seg_fifo_offset_next = out_seg_fifo_offset_reg + SEG_CNT_INT;
|
||||
end else begin
|
||||
out_frame_next = 1'b0;
|
||||
if (out_seg_last_straddle_next) begin
|
||||
out_ctl_seg_split1_raw = 1 << out_seg_cnt_in_next;
|
||||
if (out_seg_cnt_in_next == SEG_CNT_INT-1) begin
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
|
||||
end else begin
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - (out_seg_cnt_in_next+1));
|
||||
end
|
||||
out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next+1;
|
||||
end else begin
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - out_seg_cnt_in_next);
|
||||
out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next;
|
||||
end
|
||||
out_seg_fifo_offset_next = out_seg_fifo_offset_reg + out_seg_cnt_in_next;
|
||||
end
|
||||
|
||||
out_seg_cnt_in_next = out_seg_cnt_in_next - SEG_CNT_INT;
|
||||
|
||||
hdr_mem_rd_data_valid_next = 1'b0;
|
||||
end else if (block_timeout_reg && out_seg_offset_reg) begin
|
||||
// insert padding
|
||||
out_hdr_next[15:0] = 0;
|
||||
|
||||
out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> out_seg_offset_reg;
|
||||
out_ctl_seg_hdr_raw = {SEG_CNT_INT{1'b1}};
|
||||
out_ctl_seg_split1_raw = {SEG_CNT_INT{1'b1}};
|
||||
|
||||
out_seg_offset_next = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
out_ctl_seg_hdr_next = {2{out_ctl_seg_hdr_raw}} >> (SEG_CNT_INT - out_seg_offset_reg);
|
||||
out_ctl_seg_split1_next = {2{out_ctl_seg_split1_raw}} >> (SEG_CNT_INT - out_seg_offset_reg);
|
||||
out_ctl_seg_en_next = {2{out_ctl_seg_en_raw}} >> (SEG_CNT_INT - out_seg_offset_reg);
|
||||
|
||||
out_shift_next = out_shift_reg;
|
||||
|
||||
// mux segments
|
||||
cur_rd_seg = out_ctl_seg_offset_reg;
|
||||
for (rd_seg = 0; rd_seg < SEG_CNT_INT; rd_seg = rd_seg + 1) begin
|
||||
output_data_next[cur_rd_seg*SEG_WIDTH +: SEG_WIDTH] = out_shift_next;
|
||||
output_data_next[cur_rd_seg*SEG_WIDTH+HDR_WIDTH +: SEG_WIDTH-HDR_WIDTH] = seg_mem_rd_data[out_ctl_seg_idx_reg[cur_rd_seg]*SEG_WIDTH +: SEG_WIDTH-HDR_WIDTH];
|
||||
|
||||
if (out_ctl_seg_hdr_reg[cur_rd_seg]) begin
|
||||
output_data_next[cur_rd_seg*SEG_WIDTH +: HDR_WIDTH] = out_hdr_reg;
|
||||
end
|
||||
|
||||
output_valid_next[cur_rd_seg] = out_ctl_seg_en_reg[cur_rd_seg];
|
||||
|
||||
if (out_ctl_seg_en_reg[cur_rd_seg] && !out_ctl_seg_split1_reg[cur_rd_seg]) begin
|
||||
mem_rd_data_valid_next[out_ctl_seg_idx_reg[cur_rd_seg]] = 1'b0;
|
||||
end
|
||||
|
||||
if (out_ctl_seg_en_reg[cur_rd_seg]) begin
|
||||
out_shift_next = seg_mem_rd_data[(out_ctl_seg_idx_reg[cur_rd_seg]+1)*SEG_WIDTH-HDR_WIDTH +: HDR_WIDTH];
|
||||
end
|
||||
|
||||
cur_rd_seg = cur_rd_seg + 1;
|
||||
end
|
||||
|
||||
// read segments
|
||||
cur_rd_seg = input_fifo_rd_ptr_reg[SEG_IDX_WIDTH-1:0];
|
||||
rd_valid = 1;
|
||||
for (rd_seg = 0; rd_seg < SEG_CNT_INT; rd_seg = rd_seg + 1) begin
|
||||
if (!mem_rd_data_valid_next[cur_rd_seg] && input_fifo_count_reg > rd_seg && rd_valid) begin
|
||||
input_fifo_rd_ptr_next = input_fifo_rd_ptr_reg + rd_seg+1;
|
||||
seg_mem_rd_en[cur_rd_seg] = 1'b1;
|
||||
seg_mem_rd_addr_next[cur_rd_seg*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH] = ((input_fifo_rd_ptr_reg + rd_seg) >> SEG_IDX_WIDTH) + 1;
|
||||
mem_rd_data_valid_next[cur_rd_seg] = 1'b1;
|
||||
end else begin
|
||||
rd_valid = 0;
|
||||
end
|
||||
cur_rd_seg = cur_rd_seg + 1;
|
||||
end
|
||||
|
||||
// read header
|
||||
if (!hdr_mem_rd_data_valid_next && !hdr_fifo_empty_reg) begin
|
||||
hdr_fifo_rd_ptr_next = hdr_fifo_rd_ptr_reg + 1;
|
||||
hdr_mem_rd_en = 1'b1;
|
||||
hdr_mem_rd_addr_next = hdr_fifo_rd_ptr_next;
|
||||
hdr_mem_rd_data_valid_next = 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
integer i;
|
||||
|
||||
always @(posedge clk) begin
|
||||
input_fifo_rd_ptr_reg <= input_fifo_rd_ptr_next;
|
||||
input_fifo_count_reg <= input_fifo_wr_ptr_next - input_fifo_rd_ptr_next;
|
||||
input_fifo_empty_reg <= input_fifo_wr_ptr_next == input_fifo_rd_ptr_next;
|
||||
hdr_fifo_rd_ptr_reg <= hdr_fifo_rd_ptr_next;
|
||||
hdr_fifo_count_reg <= hdr_fifo_wr_ptr_next - hdr_fifo_rd_ptr_next;
|
||||
hdr_fifo_empty_reg <= hdr_fifo_wr_ptr_next == hdr_fifo_rd_ptr_next;
|
||||
|
||||
seg_mem_rd_addr_reg <= seg_mem_rd_addr_next;
|
||||
hdr_mem_rd_addr_reg <= hdr_mem_rd_addr_next;
|
||||
|
||||
mem_rd_data_valid_reg <= mem_rd_data_valid_next;
|
||||
hdr_mem_rd_data_valid_reg <= hdr_mem_rd_data_valid_next;
|
||||
|
||||
output_data_reg <= output_data_next;
|
||||
output_valid_reg <= output_valid_next;
|
||||
|
||||
out_frame_reg <= out_frame_next;
|
||||
out_len_reg <= out_len_next;
|
||||
out_split1_reg <= out_split1_next;
|
||||
out_seg_cnt_in_reg <= out_seg_cnt_in_next;
|
||||
out_seg_last_straddle_reg <= out_seg_last_straddle_next;
|
||||
out_seg_offset_reg <= out_seg_offset_next;
|
||||
out_seg_fifo_offset_reg <= out_seg_fifo_offset_next;
|
||||
|
||||
out_hdr_reg <= out_hdr_next;
|
||||
|
||||
out_ctl_seg_hdr_reg <= out_ctl_seg_hdr_next;
|
||||
out_ctl_seg_split1_reg <= out_ctl_seg_split1_next;
|
||||
out_ctl_seg_en_reg <= out_ctl_seg_en_next;
|
||||
for (i = 0; i < SEG_CNT_INT; i = i + 1) begin
|
||||
out_ctl_seg_idx_reg[i] <= out_ctl_seg_idx_next[i];
|
||||
end
|
||||
out_ctl_seg_offset_reg <= out_ctl_seg_offset_next;
|
||||
|
||||
out_shift_reg <= out_shift_next;
|
||||
|
||||
block_timeout_count_reg <= block_timeout_count_next;
|
||||
block_timeout_reg <= block_timeout_next;
|
||||
|
||||
if (rst || fifo_rst_in) begin
|
||||
input_fifo_rd_ptr_reg <= 0;
|
||||
input_fifo_count_reg <= 0;
|
||||
input_fifo_empty_reg <= 1'b1;
|
||||
hdr_fifo_rd_ptr_reg <= 0;
|
||||
hdr_fifo_count_reg <= 0;
|
||||
hdr_fifo_empty_reg <= 1'b1;
|
||||
|
||||
seg_mem_rd_addr_reg <= 0;
|
||||
hdr_mem_rd_addr_reg <= 0;
|
||||
|
||||
mem_rd_data_valid_reg <= 0;
|
||||
hdr_mem_rd_data_valid_reg <= 0;
|
||||
|
||||
out_frame_reg <= 1'b0;
|
||||
out_len_reg <= 0;
|
||||
out_split1_reg <= 0;
|
||||
out_seg_offset_reg <= 0;
|
||||
out_seg_fifo_offset_reg <= 0;
|
||||
out_seg_count_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
381
rtl/axi_vfifo_raw.v
Normal file
381
rtl/axi_vfifo_raw.v
Normal file
@ -0,0 +1,381 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* AXI4 virtual FIFO (raw)
|
||||
*/
|
||||
module axi_vfifo_raw #
|
||||
(
|
||||
// Width of input segment
|
||||
parameter SEG_WIDTH = 32,
|
||||
// Segment count
|
||||
parameter SEG_CNT = 2,
|
||||
// Width of AXI data bus in bits
|
||||
parameter AXI_DATA_WIDTH = SEG_WIDTH*SEG_CNT,
|
||||
// Width of AXI address bus in bits
|
||||
parameter AXI_ADDR_WIDTH = 16,
|
||||
// Width of AXI wstrb (width of data bus in words)
|
||||
parameter AXI_STRB_WIDTH = (AXI_DATA_WIDTH/8),
|
||||
// Width of AXI ID signal
|
||||
parameter AXI_ID_WIDTH = 8,
|
||||
// Maximum AXI burst length to generate
|
||||
parameter AXI_MAX_BURST_LEN = 16,
|
||||
// Width of length field
|
||||
parameter LEN_WIDTH = AXI_ADDR_WIDTH,
|
||||
// Input FIFO depth for AXI write data (full-width words)
|
||||
parameter WRITE_FIFO_DEPTH = 64,
|
||||
// Max AXI write burst length
|
||||
parameter WRITE_MAX_BURST_LEN = WRITE_FIFO_DEPTH/4,
|
||||
// Output FIFO depth for AXI read data (full-width words)
|
||||
parameter READ_FIFO_DEPTH = 128,
|
||||
// Max AXI read burst length
|
||||
parameter READ_MAX_BURST_LEN = WRITE_MAX_BURST_LEN,
|
||||
// Watermark level
|
||||
parameter WATERMARK_LEVEL = WRITE_FIFO_DEPTH/2,
|
||||
// Use control output
|
||||
parameter CTRL_OUT_EN = 0
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* Segmented data input (from encode logic)
|
||||
*/
|
||||
input wire input_clk,
|
||||
input wire input_rst,
|
||||
output wire input_rst_out,
|
||||
output wire input_watermark,
|
||||
input wire [SEG_CNT*SEG_WIDTH-1:0] input_data,
|
||||
input wire [SEG_CNT-1:0] input_valid,
|
||||
output wire [SEG_CNT-1:0] input_ready,
|
||||
|
||||
/*
|
||||
* Segmented data output (to decode logic)
|
||||
*/
|
||||
input wire output_clk,
|
||||
input wire output_rst,
|
||||
output wire output_rst_out,
|
||||
output wire [SEG_CNT*SEG_WIDTH-1:0] output_data,
|
||||
output wire [SEG_CNT-1:0] output_valid,
|
||||
input wire [SEG_CNT-1:0] output_ready,
|
||||
output wire [SEG_CNT*SEG_WIDTH-1:0] output_ctrl_data,
|
||||
output wire [SEG_CNT-1:0] output_ctrl_valid,
|
||||
input wire [SEG_CNT-1:0] output_ctrl_ready,
|
||||
|
||||
/*
|
||||
* AXI master interface
|
||||
*/
|
||||
output wire [AXI_ID_WIDTH-1:0] m_axi_awid,
|
||||
output wire [AXI_ADDR_WIDTH-1:0] m_axi_awaddr,
|
||||
output wire [7:0] m_axi_awlen,
|
||||
output wire [2:0] m_axi_awsize,
|
||||
output wire [1:0] m_axi_awburst,
|
||||
output wire m_axi_awlock,
|
||||
output wire [3:0] m_axi_awcache,
|
||||
output wire [2:0] m_axi_awprot,
|
||||
output wire m_axi_awvalid,
|
||||
input wire m_axi_awready,
|
||||
output wire [AXI_DATA_WIDTH-1:0] m_axi_wdata,
|
||||
output wire [AXI_STRB_WIDTH-1:0] m_axi_wstrb,
|
||||
output wire m_axi_wlast,
|
||||
output wire m_axi_wvalid,
|
||||
input wire m_axi_wready,
|
||||
input wire [AXI_ID_WIDTH-1:0] m_axi_bid,
|
||||
input wire [1:0] m_axi_bresp,
|
||||
input wire m_axi_bvalid,
|
||||
output wire m_axi_bready,
|
||||
output wire [AXI_ID_WIDTH-1:0] m_axi_arid,
|
||||
output wire [AXI_ADDR_WIDTH-1:0] m_axi_araddr,
|
||||
output wire [7:0] m_axi_arlen,
|
||||
output wire [2:0] m_axi_arsize,
|
||||
output wire [1:0] m_axi_arburst,
|
||||
output wire m_axi_arlock,
|
||||
output wire [3:0] m_axi_arcache,
|
||||
output wire [2:0] m_axi_arprot,
|
||||
output wire m_axi_arvalid,
|
||||
input wire m_axi_arready,
|
||||
input wire [AXI_ID_WIDTH-1:0] m_axi_rid,
|
||||
input wire [AXI_DATA_WIDTH-1:0] m_axi_rdata,
|
||||
input wire [1:0] m_axi_rresp,
|
||||
input wire m_axi_rlast,
|
||||
input wire m_axi_rvalid,
|
||||
output wire m_axi_rready,
|
||||
|
||||
/*
|
||||
* Reset sync
|
||||
*/
|
||||
output wire rst_req_out,
|
||||
input wire rst_req_in,
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
input wire [AXI_ADDR_WIDTH-1:0] cfg_fifo_base_addr,
|
||||
input wire [LEN_WIDTH-1:0] cfg_fifo_size_mask,
|
||||
input wire cfg_enable,
|
||||
input wire cfg_reset,
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
output wire [LEN_WIDTH+1-1:0] sts_fifo_occupancy,
|
||||
output wire sts_fifo_empty,
|
||||
output wire sts_fifo_full,
|
||||
output wire sts_reset,
|
||||
output wire sts_active,
|
||||
output wire sts_write_active,
|
||||
output wire sts_read_active
|
||||
);
|
||||
|
||||
localparam ADDR_MASK = {AXI_ADDR_WIDTH{1'b1}} << $clog2(AXI_STRB_WIDTH);
|
||||
|
||||
reg fifo_reset_reg = 1'b1, fifo_reset_next;
|
||||
reg fifo_enable_reg = 1'b0, fifo_enable_next;
|
||||
reg [AXI_ADDR_WIDTH-1:0] fifo_base_addr_reg = 0, fifo_base_addr_next;
|
||||
reg [LEN_WIDTH-1:0] fifo_size_mask_reg = 0, fifo_size_mask_next;
|
||||
|
||||
assign sts_reset = fifo_reset_reg;
|
||||
assign sts_active = fifo_enable_reg;
|
||||
|
||||
wire [LEN_WIDTH+1-1:0] wr_start_ptr;
|
||||
wire [LEN_WIDTH+1-1:0] wr_finish_ptr;
|
||||
wire [LEN_WIDTH+1-1:0] rd_start_ptr;
|
||||
wire [LEN_WIDTH+1-1:0] rd_finish_ptr;
|
||||
|
||||
axi_vfifo_raw_wr #(
|
||||
.SEG_WIDTH(SEG_WIDTH),
|
||||
.SEG_CNT(SEG_CNT),
|
||||
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
|
||||
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH),
|
||||
.AXI_STRB_WIDTH(AXI_STRB_WIDTH),
|
||||
.AXI_ID_WIDTH(AXI_ID_WIDTH),
|
||||
.AXI_MAX_BURST_LEN(AXI_MAX_BURST_LEN),
|
||||
.LEN_WIDTH(LEN_WIDTH),
|
||||
.WRITE_FIFO_DEPTH(WRITE_FIFO_DEPTH),
|
||||
.WRITE_MAX_BURST_LEN(WRITE_MAX_BURST_LEN),
|
||||
.WATERMARK_LEVEL(WATERMARK_LEVEL)
|
||||
)
|
||||
axi_vfifo_raw_wr_inst (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
|
||||
/*
|
||||
* Segmented data input (from encode logic)
|
||||
*/
|
||||
.input_clk(input_clk),
|
||||
.input_rst(input_rst),
|
||||
.input_rst_out(input_rst_out),
|
||||
.input_watermark(input_watermark),
|
||||
.input_data(input_data),
|
||||
.input_valid(input_valid),
|
||||
.input_ready(input_ready),
|
||||
|
||||
/*
|
||||
* AXI master interface
|
||||
*/
|
||||
.m_axi_awid(m_axi_awid),
|
||||
.m_axi_awaddr(m_axi_awaddr),
|
||||
.m_axi_awlen(m_axi_awlen),
|
||||
.m_axi_awsize(m_axi_awsize),
|
||||
.m_axi_awburst(m_axi_awburst),
|
||||
.m_axi_awlock(m_axi_awlock),
|
||||
.m_axi_awcache(m_axi_awcache),
|
||||
.m_axi_awprot(m_axi_awprot),
|
||||
.m_axi_awvalid(m_axi_awvalid),
|
||||
.m_axi_awready(m_axi_awready),
|
||||
.m_axi_wdata(m_axi_wdata),
|
||||
.m_axi_wstrb(m_axi_wstrb),
|
||||
.m_axi_wlast(m_axi_wlast),
|
||||
.m_axi_wvalid(m_axi_wvalid),
|
||||
.m_axi_wready(m_axi_wready),
|
||||
.m_axi_bid(m_axi_bid),
|
||||
.m_axi_bresp(m_axi_bresp),
|
||||
.m_axi_bvalid(m_axi_bvalid),
|
||||
.m_axi_bready(m_axi_bready),
|
||||
|
||||
/*
|
||||
* FIFO control
|
||||
*/
|
||||
.wr_start_ptr_out(wr_start_ptr),
|
||||
.wr_finish_ptr_out(wr_finish_ptr),
|
||||
.rd_start_ptr_in(rd_start_ptr),
|
||||
.rd_finish_ptr_in(rd_finish_ptr),
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
.cfg_fifo_base_addr(fifo_base_addr_reg),
|
||||
.cfg_fifo_size_mask(fifo_size_mask_reg),
|
||||
.cfg_enable(fifo_enable_reg),
|
||||
.cfg_reset(fifo_reset_reg),
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
.sts_fifo_occupancy(sts_fifo_occupancy),
|
||||
.sts_fifo_empty(sts_fifo_empty),
|
||||
.sts_fifo_full(sts_fifo_full),
|
||||
.sts_write_active(sts_write_active)
|
||||
);
|
||||
|
||||
axi_vfifo_raw_rd #(
|
||||
.SEG_WIDTH(SEG_WIDTH),
|
||||
.SEG_CNT(SEG_CNT),
|
||||
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
|
||||
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH),
|
||||
.AXI_STRB_WIDTH(AXI_STRB_WIDTH),
|
||||
.AXI_ID_WIDTH(AXI_ID_WIDTH),
|
||||
.AXI_MAX_BURST_LEN(AXI_MAX_BURST_LEN),
|
||||
.LEN_WIDTH(LEN_WIDTH),
|
||||
.READ_FIFO_DEPTH(READ_FIFO_DEPTH),
|
||||
.READ_MAX_BURST_LEN(READ_MAX_BURST_LEN),
|
||||
.CTRL_OUT_EN(CTRL_OUT_EN)
|
||||
)
|
||||
axi_vfifo_raw_rd_inst (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
|
||||
/*
|
||||
* Segmented data output (to decode logic)
|
||||
*/
|
||||
.output_clk(output_clk),
|
||||
.output_rst(output_rst),
|
||||
.output_rst_out(output_rst_out),
|
||||
.output_data(output_data),
|
||||
.output_valid(output_valid),
|
||||
.output_ready(output_ready),
|
||||
.output_ctrl_data(output_ctrl_data),
|
||||
.output_ctrl_valid(output_ctrl_valid),
|
||||
.output_ctrl_ready(output_ctrl_ready),
|
||||
|
||||
/*
|
||||
* AXI master interface
|
||||
*/
|
||||
.m_axi_arid(m_axi_arid),
|
||||
.m_axi_araddr(m_axi_araddr),
|
||||
.m_axi_arlen(m_axi_arlen),
|
||||
.m_axi_arsize(m_axi_arsize),
|
||||
.m_axi_arburst(m_axi_arburst),
|
||||
.m_axi_arlock(m_axi_arlock),
|
||||
.m_axi_arcache(m_axi_arcache),
|
||||
.m_axi_arprot(m_axi_arprot),
|
||||
.m_axi_arvalid(m_axi_arvalid),
|
||||
.m_axi_arready(m_axi_arready),
|
||||
.m_axi_rid(m_axi_rid),
|
||||
.m_axi_rdata(m_axi_rdata),
|
||||
.m_axi_rresp(m_axi_rresp),
|
||||
.m_axi_rlast(m_axi_rlast),
|
||||
.m_axi_rvalid(m_axi_rvalid),
|
||||
.m_axi_rready(m_axi_rready),
|
||||
|
||||
/*
|
||||
* FIFO control
|
||||
*/
|
||||
.wr_start_ptr_in(wr_start_ptr),
|
||||
.wr_finish_ptr_in(wr_finish_ptr),
|
||||
.rd_start_ptr_out(rd_start_ptr),
|
||||
.rd_finish_ptr_out(rd_finish_ptr),
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
.cfg_fifo_base_addr(fifo_base_addr_reg),
|
||||
.cfg_fifo_size_mask(fifo_size_mask_reg),
|
||||
.cfg_enable(fifo_enable_reg),
|
||||
.cfg_reset(fifo_reset_reg),
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
.sts_read_active(sts_read_active)
|
||||
);
|
||||
|
||||
// reset synchronization
|
||||
assign rst_req_out = rst | input_rst | output_rst | cfg_reset;
|
||||
|
||||
wire rst_req_int = rst_req_in | rst_req_out;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg rst_sync_1_reg = 1'b1, rst_sync_2_reg = 1'b1, rst_sync_3_reg = 1'b1;
|
||||
|
||||
always @(posedge clk or posedge rst_req_int) begin
|
||||
if (rst_req_int) begin
|
||||
rst_sync_1_reg <= 1'b1;
|
||||
end else begin
|
||||
rst_sync_1_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
rst_sync_2_reg <= rst_sync_1_reg;
|
||||
rst_sync_3_reg <= rst_sync_2_reg;
|
||||
end
|
||||
|
||||
// reset and enable logic
|
||||
always @* begin
|
||||
fifo_reset_next = 1'b0;
|
||||
fifo_enable_next = fifo_enable_reg;
|
||||
fifo_base_addr_next = fifo_base_addr_reg;
|
||||
fifo_size_mask_next = fifo_size_mask_reg;
|
||||
|
||||
if (cfg_reset || rst_sync_3_reg) begin
|
||||
fifo_reset_next = 1'b1;
|
||||
end
|
||||
|
||||
if (fifo_reset_reg) begin
|
||||
fifo_enable_next = 1'b0;
|
||||
// hold reset until everything is flushed
|
||||
if (sts_write_active || sts_read_active) begin
|
||||
fifo_reset_next = 1'b1;
|
||||
end
|
||||
end else if (!fifo_enable_reg && cfg_enable) begin
|
||||
fifo_base_addr_next = cfg_fifo_base_addr & ADDR_MASK;
|
||||
fifo_size_mask_next = cfg_fifo_size_mask | ~ADDR_MASK;
|
||||
|
||||
fifo_enable_next = 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
fifo_reset_reg <= fifo_reset_next;
|
||||
fifo_enable_reg <= fifo_enable_next;
|
||||
fifo_base_addr_reg <= fifo_base_addr_next;
|
||||
fifo_size_mask_reg <= fifo_size_mask_next;
|
||||
|
||||
if (rst) begin
|
||||
fifo_reset_reg <= 1'b1;
|
||||
fifo_enable_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
580
rtl/axi_vfifo_raw_rd.v
Normal file
580
rtl/axi_vfifo_raw_rd.v
Normal file
@ -0,0 +1,580 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* AXI4 virtual FIFO (raw, read side)
|
||||
*/
|
||||
module axi_vfifo_raw_rd #
|
||||
(
|
||||
// Width of input segment
|
||||
parameter SEG_WIDTH = 32,
|
||||
// Segment count
|
||||
parameter SEG_CNT = 2,
|
||||
// Width of AXI data bus in bits
|
||||
parameter AXI_DATA_WIDTH = SEG_WIDTH*SEG_CNT,
|
||||
// Width of AXI address bus in bits
|
||||
parameter AXI_ADDR_WIDTH = 16,
|
||||
// Width of AXI wstrb (width of data bus in words)
|
||||
parameter AXI_STRB_WIDTH = (AXI_DATA_WIDTH/8),
|
||||
// Width of AXI ID signal
|
||||
parameter AXI_ID_WIDTH = 8,
|
||||
// Maximum AXI burst length to generate
|
||||
parameter AXI_MAX_BURST_LEN = 16,
|
||||
// Width of length field
|
||||
parameter LEN_WIDTH = AXI_ADDR_WIDTH,
|
||||
// Output FIFO depth for AXI read data (full-width words)
|
||||
parameter READ_FIFO_DEPTH = 128,
|
||||
// Max AXI read burst length
|
||||
parameter READ_MAX_BURST_LEN = READ_FIFO_DEPTH/4,
|
||||
// Use control output
|
||||
parameter CTRL_OUT_EN = 0
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* Segmented data output (to decode logic)
|
||||
*/
|
||||
input wire output_clk,
|
||||
input wire output_rst,
|
||||
output wire output_rst_out,
|
||||
output wire [SEG_CNT*SEG_WIDTH-1:0] output_data,
|
||||
output wire [SEG_CNT-1:0] output_valid,
|
||||
input wire [SEG_CNT-1:0] output_ready,
|
||||
output wire [SEG_CNT*SEG_WIDTH-1:0] output_ctrl_data,
|
||||
output wire [SEG_CNT-1:0] output_ctrl_valid,
|
||||
input wire [SEG_CNT-1:0] output_ctrl_ready,
|
||||
|
||||
/*
|
||||
* AXI master interface
|
||||
*/
|
||||
output wire [AXI_ID_WIDTH-1:0] m_axi_arid,
|
||||
output wire [AXI_ADDR_WIDTH-1:0] m_axi_araddr,
|
||||
output wire [7:0] m_axi_arlen,
|
||||
output wire [2:0] m_axi_arsize,
|
||||
output wire [1:0] m_axi_arburst,
|
||||
output wire m_axi_arlock,
|
||||
output wire [3:0] m_axi_arcache,
|
||||
output wire [2:0] m_axi_arprot,
|
||||
output wire m_axi_arvalid,
|
||||
input wire m_axi_arready,
|
||||
input wire [AXI_ID_WIDTH-1:0] m_axi_rid,
|
||||
input wire [AXI_DATA_WIDTH-1:0] m_axi_rdata,
|
||||
input wire [1:0] m_axi_rresp,
|
||||
input wire m_axi_rlast,
|
||||
input wire m_axi_rvalid,
|
||||
output wire m_axi_rready,
|
||||
|
||||
/*
|
||||
* FIFO control
|
||||
*/
|
||||
input wire [LEN_WIDTH+1-1:0] wr_start_ptr_in,
|
||||
input wire [LEN_WIDTH+1-1:0] wr_finish_ptr_in,
|
||||
output wire [LEN_WIDTH+1-1:0] rd_start_ptr_out,
|
||||
output wire [LEN_WIDTH+1-1:0] rd_finish_ptr_out,
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
input wire [AXI_ADDR_WIDTH-1:0] cfg_fifo_base_addr,
|
||||
input wire [LEN_WIDTH-1:0] cfg_fifo_size_mask,
|
||||
input wire cfg_enable,
|
||||
input wire cfg_reset,
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
output wire sts_read_active
|
||||
);
|
||||
|
||||
localparam AXI_BYTE_LANES = AXI_STRB_WIDTH;
|
||||
localparam AXI_BYTE_SIZE = AXI_DATA_WIDTH/AXI_BYTE_LANES;
|
||||
localparam AXI_BURST_SIZE = $clog2(AXI_STRB_WIDTH);
|
||||
localparam AXI_MAX_BURST_SIZE = AXI_MAX_BURST_LEN << AXI_BURST_SIZE;
|
||||
|
||||
localparam OFFSET_ADDR_WIDTH = AXI_STRB_WIDTH > 1 ? $clog2(AXI_STRB_WIDTH) : 1;
|
||||
localparam OFFSET_ADDR_MASK = AXI_STRB_WIDTH > 1 ? {OFFSET_ADDR_WIDTH{1'b1}} : 0;
|
||||
localparam ADDR_MASK = {AXI_ADDR_WIDTH{1'b1}} << $clog2(AXI_STRB_WIDTH);
|
||||
localparam CYCLE_COUNT_WIDTH = LEN_WIDTH - AXI_BURST_SIZE + 1;
|
||||
|
||||
localparam READ_FIFO_ADDR_WIDTH = $clog2(READ_FIFO_DEPTH);
|
||||
|
||||
// mask(x) = (2**$clog2(x))-1
|
||||
// log2(min(x, y, z)) = (mask & mask & mask)+1
|
||||
// floor(log2(x)) = $clog2(x+1)-1
|
||||
// floor(log2(min(AXI_MAX_BURST_LEN, READ_MAX_BURST_LEN, 2**(READ_FIFO_ADDR_WIDTH-1), 4096/AXI_BYTE_LANES)))
|
||||
localparam READ_MAX_BURST_LEN_INT = ((2**($clog2(AXI_MAX_BURST_LEN+1)-1)-1) & (2**($clog2(READ_MAX_BURST_LEN+1)-1)-1) & (2**(READ_FIFO_ADDR_WIDTH-1)-1) & ((4096/AXI_BYTE_LANES)-1)) + 1;
|
||||
localparam READ_MAX_BURST_SIZE_INT = READ_MAX_BURST_LEN_INT << AXI_BURST_SIZE;
|
||||
localparam READ_BURST_LEN_WIDTH = $clog2(READ_MAX_BURST_LEN_INT);
|
||||
localparam READ_BURST_ADDR_WIDTH = $clog2(READ_MAX_BURST_SIZE_INT);
|
||||
localparam READ_BURST_ADDR_MASK = READ_BURST_ADDR_WIDTH > 1 ? {READ_BURST_ADDR_WIDTH{1'b1}} : 0;
|
||||
|
||||
// validate parameters
|
||||
initial begin
|
||||
if (AXI_BYTE_SIZE * AXI_STRB_WIDTH != AXI_DATA_WIDTH) begin
|
||||
$error("Error: AXI data width not evenly divisible (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (2**$clog2(AXI_BYTE_LANES) != AXI_BYTE_LANES) begin
|
||||
$error("Error: AXI byte lane count must be even power of two (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (AXI_MAX_BURST_LEN < 1 || AXI_MAX_BURST_LEN > 256) begin
|
||||
$error("Error: AXI_MAX_BURST_LEN must be between 1 and 256 (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (SEG_CNT * SEG_WIDTH != AXI_DATA_WIDTH) begin
|
||||
$error("Error: Width mismatch (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
localparam [1:0]
|
||||
AXI_RESP_OKAY = 2'b00,
|
||||
AXI_RESP_EXOKAY = 2'b01,
|
||||
AXI_RESP_SLVERR = 2'b10,
|
||||
AXI_RESP_DECERR = 2'b11;
|
||||
|
||||
reg [AXI_ADDR_WIDTH-1:0] m_axi_araddr_reg = {AXI_ADDR_WIDTH{1'b0}}, m_axi_araddr_next;
|
||||
reg [7:0] m_axi_arlen_reg = 8'd0, m_axi_arlen_next;
|
||||
reg m_axi_arvalid_reg = 1'b0, m_axi_arvalid_next;
|
||||
|
||||
assign m_axi_arid = {AXI_ID_WIDTH{1'b0}};
|
||||
assign m_axi_araddr = m_axi_araddr_reg;
|
||||
assign m_axi_arlen = m_axi_arlen_reg;
|
||||
assign m_axi_arsize = AXI_BURST_SIZE;
|
||||
assign m_axi_arburst = 2'b01;
|
||||
assign m_axi_arlock = 1'b0;
|
||||
assign m_axi_arcache = 4'b0011;
|
||||
assign m_axi_arprot = 3'b010;
|
||||
assign m_axi_arvalid = m_axi_arvalid_reg;
|
||||
|
||||
// reset synchronization
|
||||
wire rst_req_int = cfg_reset;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg rst_sync_1_reg = 1'b1, rst_sync_2_reg = 1'b1, rst_sync_3_reg = 1'b1;
|
||||
|
||||
assign output_rst_out = rst_sync_3_reg;
|
||||
|
||||
always @(posedge output_clk or posedge rst_req_int) begin
|
||||
if (rst_req_int) begin
|
||||
rst_sync_1_reg <= 1'b1;
|
||||
end else begin
|
||||
rst_sync_1_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge output_clk) begin
|
||||
rst_sync_2_reg <= rst_sync_1_reg;
|
||||
rst_sync_3_reg <= rst_sync_2_reg;
|
||||
end
|
||||
|
||||
// output datapath logic (read data)
|
||||
reg [AXI_DATA_WIDTH-1:0] m_axis_tdata_reg = {AXI_DATA_WIDTH{1'b0}};
|
||||
reg m_axis_tvalid_reg = 1'b0;
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH-1:0] read_fifo_read_start_cnt = 0;
|
||||
reg read_fifo_read_start_en = 1'b0;
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_read_start_ptr_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_wr_ptr_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_wr_ptr_gray_reg = 0;
|
||||
wire [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_rd_ptr;
|
||||
wire [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_rd_ptr_gray;
|
||||
wire [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_ctrl_rd_ptr;
|
||||
wire [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_ctrl_rd_ptr_gray;
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_wr_ptr_temp;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_wr_ptr_gray_sync_1_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_wr_ptr_gray_sync_2_reg = 0;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_rd_ptr_gray_sync_1_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_rd_ptr_gray_sync_2_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_rd_ptr_sync_reg = 0;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_ctrl_rd_ptr_gray_sync_1_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_ctrl_rd_ptr_gray_sync_2_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_ctrl_rd_ptr_sync_reg = 0;
|
||||
|
||||
reg read_fifo_half_full_reg = 1'b0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_occupancy_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] read_fifo_occupancy_lookahead_reg = 0;
|
||||
|
||||
wire read_fifo_full = read_fifo_wr_ptr_gray_reg == (read_fifo_rd_ptr_gray_sync_2_reg ^ {2'b11, {READ_FIFO_ADDR_WIDTH-1{1'b0}}});
|
||||
wire read_fifo_empty = read_fifo_rd_ptr_gray == read_fifo_wr_ptr_gray_sync_2_reg;
|
||||
|
||||
wire read_fifo_ctrl_full = read_fifo_wr_ptr_gray_reg == (read_fifo_ctrl_rd_ptr_gray_sync_2_reg ^ {2'b11, {READ_FIFO_ADDR_WIDTH-1{1'b0}}});
|
||||
wire read_fifo_ctrl_empty = read_fifo_ctrl_rd_ptr_gray == read_fifo_wr_ptr_gray_sync_2_reg;
|
||||
|
||||
assign m_axi_rready = (!read_fifo_full && (!CTRL_OUT_EN || !read_fifo_ctrl_full)) || cfg_reset;
|
||||
|
||||
genvar n;
|
||||
integer k;
|
||||
|
||||
generate
|
||||
|
||||
for (n = 0; n < SEG_CNT; n = n + 1) begin : read_fifo_seg
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_gray_reg = 0;
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_temp;
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [SEG_WIDTH-1:0] seg_mem_data[2**READ_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
reg [SEG_WIDTH-1:0] seg_rd_data_reg = 0;
|
||||
reg seg_rd_data_valid_reg = 0;
|
||||
|
||||
wire seg_empty = seg_rd_ptr_gray_reg == read_fifo_wr_ptr_gray_sync_2_reg;
|
||||
|
||||
assign output_data[n*SEG_WIDTH +: SEG_WIDTH] = seg_rd_data_reg;
|
||||
assign output_valid[n] = seg_rd_data_valid_reg;
|
||||
|
||||
if (n == SEG_CNT-1) begin
|
||||
assign read_fifo_rd_ptr = seg_rd_ptr_reg;
|
||||
assign read_fifo_rd_ptr_gray = seg_rd_ptr_gray_reg;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!read_fifo_full && m_axi_rready && m_axi_rvalid) begin
|
||||
seg_mem_data[read_fifo_wr_ptr_reg[READ_FIFO_ADDR_WIDTH-1:0]] <= m_axi_rdata[n*SEG_WIDTH +: SEG_WIDTH];
|
||||
end
|
||||
end
|
||||
|
||||
// per-segment read logic
|
||||
always @(posedge output_clk) begin
|
||||
seg_rd_data_valid_reg <= seg_rd_data_valid_reg && !output_ready[n];
|
||||
|
||||
if (!seg_empty && (!seg_rd_data_valid_reg || output_ready[n])) begin
|
||||
seg_rd_data_reg <= seg_mem_data[seg_rd_ptr_reg[READ_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_data_valid_reg <= 1'b1;
|
||||
|
||||
seg_rd_ptr_temp = seg_rd_ptr_reg + 1;
|
||||
seg_rd_ptr_reg <= seg_rd_ptr_temp;
|
||||
seg_rd_ptr_gray_reg <= seg_rd_ptr_temp ^ (seg_rd_ptr_temp >> 1);
|
||||
end
|
||||
|
||||
if (output_rst || output_rst_out) begin
|
||||
seg_rd_ptr_reg <= 0;
|
||||
seg_rd_ptr_gray_reg <= 0;
|
||||
seg_rd_data_valid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
// write logic
|
||||
always @(posedge clk) begin
|
||||
read_fifo_occupancy_reg <= read_fifo_wr_ptr_reg - read_fifo_rd_ptr_sync_reg;
|
||||
read_fifo_half_full_reg <= $unsigned(read_fifo_wr_ptr_reg - read_fifo_rd_ptr_sync_reg) >= 2**(READ_FIFO_ADDR_WIDTH-1);
|
||||
|
||||
if (read_fifo_read_start_en) begin
|
||||
read_fifo_read_start_ptr_reg <= read_fifo_read_start_ptr_reg + read_fifo_read_start_cnt;
|
||||
read_fifo_occupancy_lookahead_reg <= read_fifo_read_start_ptr_reg + read_fifo_read_start_cnt - read_fifo_rd_ptr_sync_reg;
|
||||
end else begin
|
||||
read_fifo_occupancy_lookahead_reg <= read_fifo_read_start_ptr_reg - read_fifo_rd_ptr_sync_reg;
|
||||
end
|
||||
|
||||
if (!read_fifo_full && m_axi_rready && m_axi_rvalid) begin
|
||||
read_fifo_wr_ptr_temp = read_fifo_wr_ptr_reg + 1;
|
||||
read_fifo_wr_ptr_reg <= read_fifo_wr_ptr_temp;
|
||||
read_fifo_wr_ptr_gray_reg <= read_fifo_wr_ptr_temp ^ (read_fifo_wr_ptr_temp >> 1);
|
||||
|
||||
read_fifo_occupancy_reg <= read_fifo_wr_ptr_temp - read_fifo_rd_ptr_sync_reg;
|
||||
end
|
||||
|
||||
if (rst || cfg_reset) begin
|
||||
read_fifo_read_start_ptr_reg <= 0;
|
||||
read_fifo_wr_ptr_reg <= 0;
|
||||
read_fifo_wr_ptr_gray_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
// pointer synchronization
|
||||
always @(posedge clk) begin
|
||||
read_fifo_rd_ptr_gray_sync_1_reg <= read_fifo_rd_ptr_gray;
|
||||
read_fifo_rd_ptr_gray_sync_2_reg <= read_fifo_rd_ptr_gray_sync_1_reg;
|
||||
|
||||
for (k = 0; k < READ_FIFO_ADDR_WIDTH+1; k = k + 1) begin
|
||||
read_fifo_rd_ptr_sync_reg[k] <= ^(read_fifo_rd_ptr_gray_sync_2_reg >> k);
|
||||
end
|
||||
|
||||
if (rst || cfg_reset) begin
|
||||
read_fifo_rd_ptr_gray_sync_1_reg <= 0;
|
||||
read_fifo_rd_ptr_gray_sync_2_reg <= 0;
|
||||
read_fifo_rd_ptr_sync_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
read_fifo_ctrl_rd_ptr_gray_sync_1_reg <= read_fifo_ctrl_rd_ptr_gray;
|
||||
read_fifo_ctrl_rd_ptr_gray_sync_2_reg <= read_fifo_ctrl_rd_ptr_gray_sync_1_reg;
|
||||
|
||||
for (k = 0; k < READ_FIFO_ADDR_WIDTH+1; k = k + 1) begin
|
||||
read_fifo_ctrl_rd_ptr_sync_reg[k] <= ^(read_fifo_ctrl_rd_ptr_gray_sync_2_reg >> k);
|
||||
end
|
||||
|
||||
if (rst || cfg_reset) begin
|
||||
read_fifo_ctrl_rd_ptr_gray_sync_1_reg <= 0;
|
||||
read_fifo_ctrl_rd_ptr_gray_sync_2_reg <= 0;
|
||||
read_fifo_ctrl_rd_ptr_sync_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge output_clk) begin
|
||||
read_fifo_wr_ptr_gray_sync_1_reg <= read_fifo_wr_ptr_gray_reg;
|
||||
read_fifo_wr_ptr_gray_sync_2_reg <= read_fifo_wr_ptr_gray_sync_1_reg;
|
||||
|
||||
if (output_rst || output_rst_out) begin
|
||||
read_fifo_wr_ptr_gray_sync_1_reg <= 0;
|
||||
read_fifo_wr_ptr_gray_sync_2_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
generate
|
||||
|
||||
if (CTRL_OUT_EN) begin
|
||||
|
||||
for (n = 0; n < SEG_CNT; n = n + 1) begin : read_fifo_ctrl_seg
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_reg = 0;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_gray_reg = 0;
|
||||
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] seg_rd_ptr_temp;
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [SEG_WIDTH-1:0] seg_mem_data[2**READ_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
reg [SEG_WIDTH-1:0] seg_rd_data_reg = 0;
|
||||
reg seg_rd_data_valid_reg = 0;
|
||||
|
||||
reg seg_output_ready_reg = 1'b0;
|
||||
|
||||
wire seg_empty = seg_rd_ptr_gray_reg == read_fifo_wr_ptr_gray_sync_2_reg;
|
||||
|
||||
if (n == SEG_CNT-1) begin
|
||||
assign read_fifo_ctrl_rd_ptr = seg_rd_ptr_reg;
|
||||
assign read_fifo_ctrl_rd_ptr_gray = seg_rd_ptr_gray_reg;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!read_fifo_full && m_axi_rready && m_axi_rvalid) begin
|
||||
seg_mem_data[read_fifo_wr_ptr_reg[READ_FIFO_ADDR_WIDTH-1:0]] <= m_axi_rdata[n*SEG_WIDTH +: SEG_WIDTH];
|
||||
end
|
||||
end
|
||||
|
||||
// per-segment read logic
|
||||
always @(posedge output_clk) begin
|
||||
seg_rd_data_valid_reg <= seg_rd_data_valid_reg && !seg_output_ready_reg;
|
||||
|
||||
if (!seg_empty && (!seg_rd_data_valid_reg || seg_output_ready_reg)) begin
|
||||
seg_rd_data_reg <= seg_mem_data[seg_rd_ptr_reg[READ_FIFO_ADDR_WIDTH-1:0]];
|
||||
seg_rd_data_valid_reg <= 1'b1;
|
||||
|
||||
seg_rd_ptr_temp = seg_rd_ptr_reg + 1;
|
||||
seg_rd_ptr_reg <= seg_rd_ptr_temp;
|
||||
seg_rd_ptr_gray_reg <= seg_rd_ptr_temp ^ (seg_rd_ptr_temp >> 1);
|
||||
end
|
||||
|
||||
if (output_rst || output_rst_out) begin
|
||||
seg_rd_ptr_reg <= 0;
|
||||
seg_rd_ptr_gray_reg <= 0;
|
||||
seg_rd_data_valid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// skid buffer
|
||||
reg [SEG_WIDTH-1:0] seg_output_data_reg = 0;
|
||||
reg seg_output_valid_reg = 1'b0;
|
||||
|
||||
reg [SEG_WIDTH-1:0] temp_seg_output_data_reg = 0;
|
||||
reg temp_seg_output_valid_reg = 1'b0;
|
||||
|
||||
assign output_ctrl_data[n*SEG_WIDTH +: SEG_WIDTH] = seg_output_data_reg;
|
||||
assign output_ctrl_valid[n] = seg_output_valid_reg;
|
||||
|
||||
always @(posedge output_clk) begin
|
||||
// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input)
|
||||
seg_output_ready_reg <= output_ctrl_ready[n] || (!temp_seg_output_valid_reg && (!seg_output_valid_reg || !seg_rd_data_valid_reg));
|
||||
|
||||
if (seg_output_ready_reg) begin
|
||||
// input is ready
|
||||
if (output_ctrl_ready[n] || !seg_output_valid_reg) begin
|
||||
// output is ready or currently not valid, transfer data to output
|
||||
seg_output_data_reg <= seg_rd_data_reg;
|
||||
seg_output_valid_reg <= seg_rd_data_valid_reg;
|
||||
end else begin
|
||||
// output is not ready, store input in temp
|
||||
temp_seg_output_data_reg <= seg_rd_data_reg;
|
||||
temp_seg_output_valid_reg <= seg_rd_data_valid_reg;
|
||||
end
|
||||
end else if (output_ctrl_ready[n]) begin
|
||||
// input is not ready, but output is ready
|
||||
seg_output_data_reg <= temp_seg_output_data_reg;
|
||||
seg_output_valid_reg <= temp_seg_output_valid_reg;
|
||||
temp_seg_output_valid_reg <= 1'b0;
|
||||
end
|
||||
|
||||
if (output_rst || output_rst_out) begin
|
||||
seg_output_ready_reg <= 1'b0;
|
||||
seg_output_valid_reg <= 1'b0;
|
||||
temp_seg_output_valid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
reg [READ_BURST_LEN_WIDTH+1-1:0] rd_burst_len;
|
||||
reg [READ_BURST_LEN_WIDTH+1-1:0] rd_outstanding_inc;
|
||||
reg rd_outstanding_dec;
|
||||
reg [READ_FIFO_ADDR_WIDTH+1-1:0] rd_outstanding_reg = 0, rd_outstanding_next;
|
||||
reg [LEN_WIDTH+1-1:0] rd_start_ptr;
|
||||
reg [7:0] rd_timeout_count_reg = 0, rd_timeout_count_next;
|
||||
reg rd_timeout_reg = 0, rd_timeout_next;
|
||||
|
||||
reg [LEN_WIDTH+1-1:0] rd_start_ptr_reg = 0, rd_start_ptr_next;
|
||||
reg [LEN_WIDTH+1-1:0] rd_finish_ptr_reg = 0, rd_finish_ptr_next;
|
||||
|
||||
assign rd_start_ptr_out = rd_start_ptr_reg;
|
||||
assign rd_finish_ptr_out = rd_finish_ptr_reg;
|
||||
|
||||
assign sts_read_active = rd_outstanding_reg != 0;
|
||||
|
||||
// read logic
|
||||
always @* begin
|
||||
rd_start_ptr_next = rd_start_ptr_reg;
|
||||
rd_finish_ptr_next = rd_finish_ptr_reg;
|
||||
|
||||
rd_outstanding_inc = 0;
|
||||
rd_outstanding_dec = 0;
|
||||
rd_outstanding_next = rd_outstanding_reg;
|
||||
rd_timeout_count_next = rd_timeout_count_reg;
|
||||
rd_timeout_next = rd_timeout_reg;
|
||||
|
||||
m_axi_araddr_next = m_axi_araddr_reg;
|
||||
m_axi_arlen_next = m_axi_arlen_reg;
|
||||
m_axi_arvalid_next = m_axi_arvalid_reg && !m_axi_arready;
|
||||
|
||||
// partial burst timeout handling
|
||||
rd_timeout_next = rd_timeout_count_reg == 0;
|
||||
if (wr_finish_ptr_in == rd_start_ptr_reg || m_axi_arvalid) begin
|
||||
rd_timeout_count_next = 8'hff;
|
||||
rd_timeout_next = 1'b0;
|
||||
end else if (rd_timeout_count_reg > 0) begin
|
||||
rd_timeout_count_next = rd_timeout_count_reg - 1;
|
||||
end
|
||||
|
||||
// compute length based on DRAM occupancy
|
||||
if ((wr_finish_ptr_in ^ rd_start_ptr_reg) >> READ_BURST_ADDR_WIDTH != 0) begin
|
||||
// crosses burst boundary, read up to burst boundary
|
||||
rd_burst_len = READ_MAX_BURST_LEN_INT - ((rd_start_ptr_reg & READ_BURST_ADDR_MASK) >> AXI_BURST_SIZE);
|
||||
rd_start_ptr = (rd_start_ptr_reg & ~READ_BURST_ADDR_MASK) + (1 << READ_BURST_ADDR_WIDTH);
|
||||
end else begin
|
||||
// does not cross burst boundary, read available data
|
||||
rd_burst_len = (wr_finish_ptr_in - rd_start_ptr_reg) >> AXI_BURST_SIZE;
|
||||
rd_start_ptr = wr_finish_ptr_in;
|
||||
end
|
||||
|
||||
read_fifo_read_start_cnt = rd_burst_len;
|
||||
read_fifo_read_start_en = 1'b0;
|
||||
|
||||
// generate AXI read bursts
|
||||
if (!m_axi_arvalid_reg) begin
|
||||
// ready to start new burst
|
||||
|
||||
m_axi_araddr_next = cfg_fifo_base_addr + (rd_start_ptr_reg & cfg_fifo_size_mask);
|
||||
m_axi_arlen_next = rd_burst_len - 1;
|
||||
|
||||
if (cfg_enable && (wr_finish_ptr_in ^ rd_start_ptr_reg) != 0 && read_fifo_occupancy_lookahead_reg < 2**READ_FIFO_ADDR_WIDTH - READ_MAX_BURST_LEN_INT) begin
|
||||
// enabled, have data to write, have space for data
|
||||
if ((wr_finish_ptr_in ^ rd_start_ptr_reg) >> READ_BURST_ADDR_WIDTH != 0 || rd_timeout_reg) begin
|
||||
// have full burst or timed out
|
||||
read_fifo_read_start_en = 1'b1;
|
||||
rd_outstanding_inc = rd_burst_len;
|
||||
m_axi_arvalid_next = 1'b1;
|
||||
rd_start_ptr_next = rd_start_ptr;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// handle AXI read completions
|
||||
if (m_axi_rready && m_axi_rvalid) begin
|
||||
rd_finish_ptr_next = rd_finish_ptr_reg + AXI_BYTE_LANES;
|
||||
rd_outstanding_dec = 1;
|
||||
end
|
||||
|
||||
rd_outstanding_next = rd_outstanding_reg + rd_outstanding_inc - rd_outstanding_dec;
|
||||
|
||||
if (cfg_reset) begin
|
||||
rd_start_ptr_next = 0;
|
||||
rd_finish_ptr_next = 0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
rd_start_ptr_reg <= rd_start_ptr_next;
|
||||
rd_finish_ptr_reg <= rd_finish_ptr_next;
|
||||
|
||||
rd_outstanding_reg <= rd_outstanding_next;
|
||||
rd_timeout_count_reg <= rd_timeout_count_next;
|
||||
rd_timeout_reg <= rd_timeout_next;
|
||||
|
||||
m_axi_araddr_reg <= m_axi_araddr_next;
|
||||
m_axi_arlen_reg <= m_axi_arlen_next;
|
||||
m_axi_arvalid_reg <= m_axi_arvalid_next;
|
||||
|
||||
if (rst) begin
|
||||
rd_outstanding_reg <= 0;
|
||||
m_axi_arvalid_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
567
rtl/axi_vfifo_raw_wr.v
Normal file
567
rtl/axi_vfifo_raw_wr.v
Normal file
@ -0,0 +1,567 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Language: Verilog 2001
|
||||
|
||||
`resetall
|
||||
`timescale 1ns / 1ps
|
||||
`default_nettype none
|
||||
|
||||
/*
|
||||
* AXI4 virtual FIFO (raw, write side)
|
||||
*/
|
||||
module axi_vfifo_raw_wr #
|
||||
(
|
||||
// Width of input segment
|
||||
parameter SEG_WIDTH = 32,
|
||||
// Segment count
|
||||
parameter SEG_CNT = 2,
|
||||
// Width of AXI data bus in bits
|
||||
parameter AXI_DATA_WIDTH = SEG_WIDTH*SEG_CNT,
|
||||
// Width of AXI address bus in bits
|
||||
parameter AXI_ADDR_WIDTH = 16,
|
||||
// Width of AXI wstrb (width of data bus in words)
|
||||
parameter AXI_STRB_WIDTH = (AXI_DATA_WIDTH/8),
|
||||
// Width of AXI ID signal
|
||||
parameter AXI_ID_WIDTH = 8,
|
||||
// Maximum AXI burst length to generate
|
||||
parameter AXI_MAX_BURST_LEN = 16,
|
||||
// Width of length field
|
||||
parameter LEN_WIDTH = AXI_ADDR_WIDTH,
|
||||
// Input FIFO depth for AXI write data (full-width words)
|
||||
parameter WRITE_FIFO_DEPTH = 64,
|
||||
// Max AXI write burst length
|
||||
parameter WRITE_MAX_BURST_LEN = WRITE_FIFO_DEPTH/4,
|
||||
// Watermark level
|
||||
parameter WATERMARK_LEVEL = WRITE_FIFO_DEPTH/2
|
||||
)
|
||||
(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/*
|
||||
* Segmented data input (from encode logic)
|
||||
*/
|
||||
input wire input_clk,
|
||||
input wire input_rst,
|
||||
output wire input_rst_out,
|
||||
output wire input_watermark,
|
||||
input wire [SEG_CNT*SEG_WIDTH-1:0] input_data,
|
||||
input wire [SEG_CNT-1:0] input_valid,
|
||||
output wire [SEG_CNT-1:0] input_ready,
|
||||
|
||||
/*
|
||||
* AXI master interface
|
||||
*/
|
||||
output wire [AXI_ID_WIDTH-1:0] m_axi_awid,
|
||||
output wire [AXI_ADDR_WIDTH-1:0] m_axi_awaddr,
|
||||
output wire [7:0] m_axi_awlen,
|
||||
output wire [2:0] m_axi_awsize,
|
||||
output wire [1:0] m_axi_awburst,
|
||||
output wire m_axi_awlock,
|
||||
output wire [3:0] m_axi_awcache,
|
||||
output wire [2:0] m_axi_awprot,
|
||||
output wire m_axi_awvalid,
|
||||
input wire m_axi_awready,
|
||||
output wire [AXI_DATA_WIDTH-1:0] m_axi_wdata,
|
||||
output wire [AXI_STRB_WIDTH-1:0] m_axi_wstrb,
|
||||
output wire m_axi_wlast,
|
||||
output wire m_axi_wvalid,
|
||||
input wire m_axi_wready,
|
||||
input wire [AXI_ID_WIDTH-1:0] m_axi_bid,
|
||||
input wire [1:0] m_axi_bresp,
|
||||
input wire m_axi_bvalid,
|
||||
output wire m_axi_bready,
|
||||
|
||||
/*
|
||||
* FIFO control
|
||||
*/
|
||||
output wire [LEN_WIDTH+1-1:0] wr_start_ptr_out,
|
||||
output wire [LEN_WIDTH+1-1:0] wr_finish_ptr_out,
|
||||
input wire [LEN_WIDTH+1-1:0] rd_start_ptr_in,
|
||||
input wire [LEN_WIDTH+1-1:0] rd_finish_ptr_in,
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
input wire [AXI_ADDR_WIDTH-1:0] cfg_fifo_base_addr,
|
||||
input wire [LEN_WIDTH-1:0] cfg_fifo_size_mask,
|
||||
input wire cfg_enable,
|
||||
input wire cfg_reset,
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
output wire [LEN_WIDTH+1-1:0] sts_fifo_occupancy,
|
||||
output wire sts_fifo_empty,
|
||||
output wire sts_fifo_full,
|
||||
output wire sts_write_active
|
||||
);
|
||||
|
||||
localparam AXI_BYTE_LANES = AXI_STRB_WIDTH;
|
||||
localparam AXI_BYTE_SIZE = AXI_DATA_WIDTH/AXI_BYTE_LANES;
|
||||
localparam AXI_BURST_SIZE = $clog2(AXI_STRB_WIDTH);
|
||||
localparam AXI_MAX_BURST_SIZE = AXI_MAX_BURST_LEN << AXI_BURST_SIZE;
|
||||
|
||||
localparam OFFSET_ADDR_WIDTH = AXI_STRB_WIDTH > 1 ? $clog2(AXI_STRB_WIDTH) : 1;
|
||||
localparam OFFSET_ADDR_MASK = AXI_STRB_WIDTH > 1 ? {OFFSET_ADDR_WIDTH{1'b1}} : 0;
|
||||
localparam ADDR_MASK = {AXI_ADDR_WIDTH{1'b1}} << $clog2(AXI_STRB_WIDTH);
|
||||
localparam CYCLE_COUNT_WIDTH = LEN_WIDTH - AXI_BURST_SIZE + 1;
|
||||
|
||||
localparam WRITE_FIFO_ADDR_WIDTH = $clog2(WRITE_FIFO_DEPTH);
|
||||
localparam RESP_FIFO_ADDR_WIDTH = 5;
|
||||
|
||||
// mask(x) = (2**$clog2(x))-1
|
||||
// log2(min(x, y, z)) = (mask & mask & mask)+1
|
||||
// floor(log2(x)) = $clog2(x+1)-1
|
||||
// floor(log2(min(AXI_MAX_BURST_LEN, WRITE_MAX_BURST_LEN, 2**(WRITE_FIFO_ADDR_WIDTH-1), 4096/AXI_BYTE_LANES)))
|
||||
localparam WRITE_MAX_BURST_LEN_INT = ((2**($clog2(AXI_MAX_BURST_LEN+1)-1)-1) & (2**($clog2(WRITE_MAX_BURST_LEN+1)-1)-1) & (2**(WRITE_FIFO_ADDR_WIDTH-1)-1) & ((4096/AXI_BYTE_LANES)-1)) + 1;
|
||||
localparam WRITE_MAX_BURST_SIZE_INT = WRITE_MAX_BURST_LEN_INT << AXI_BURST_SIZE;
|
||||
localparam WRITE_BURST_LEN_WIDTH = $clog2(WRITE_MAX_BURST_LEN_INT);
|
||||
localparam WRITE_BURST_ADDR_WIDTH = $clog2(WRITE_MAX_BURST_SIZE_INT);
|
||||
localparam WRITE_BURST_ADDR_MASK = WRITE_BURST_ADDR_WIDTH > 1 ? {WRITE_BURST_ADDR_WIDTH{1'b1}} : 0;
|
||||
|
||||
// validate parameters
|
||||
initial begin
|
||||
if (AXI_BYTE_SIZE * AXI_STRB_WIDTH != AXI_DATA_WIDTH) begin
|
||||
$error("Error: AXI data width not evenly divisible (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (2**$clog2(AXI_BYTE_LANES) != AXI_BYTE_LANES) begin
|
||||
$error("Error: AXI byte lane count must be even power of two (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (AXI_MAX_BURST_LEN < 1 || AXI_MAX_BURST_LEN > 256) begin
|
||||
$error("Error: AXI_MAX_BURST_LEN must be between 1 and 256 (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (SEG_CNT * SEG_WIDTH != AXI_DATA_WIDTH) begin
|
||||
$error("Error: Width mismatch (instance %m)");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
localparam [1:0]
|
||||
AXI_RESP_OKAY = 2'b00,
|
||||
AXI_RESP_EXOKAY = 2'b01,
|
||||
AXI_RESP_SLVERR = 2'b10,
|
||||
AXI_RESP_DECERR = 2'b11;
|
||||
|
||||
reg [AXI_ADDR_WIDTH-1:0] m_axi_awaddr_reg = {AXI_ADDR_WIDTH{1'b0}}, m_axi_awaddr_next;
|
||||
reg [7:0] m_axi_awlen_reg = 8'd0, m_axi_awlen_next;
|
||||
reg m_axi_awvalid_reg = 1'b0, m_axi_awvalid_next;
|
||||
reg [AXI_DATA_WIDTH-1:0] m_axi_wdata_reg = {AXI_DATA_WIDTH{1'b0}}, m_axi_wdata_next;
|
||||
reg [AXI_STRB_WIDTH-1:0] m_axi_wstrb_reg = {AXI_STRB_WIDTH{1'b0}}, m_axi_wstrb_next;
|
||||
reg m_axi_wlast_reg = 1'b0, m_axi_wlast_next;
|
||||
reg m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next;
|
||||
reg m_axi_bready_reg = 1'b0, m_axi_bready_next;
|
||||
|
||||
assign m_axi_awid = {AXI_ID_WIDTH{1'b0}};
|
||||
assign m_axi_awaddr = m_axi_awaddr_reg;
|
||||
assign m_axi_awlen = m_axi_awlen_reg;
|
||||
assign m_axi_awsize = AXI_BURST_SIZE;
|
||||
assign m_axi_awburst = 2'b01;
|
||||
assign m_axi_awlock = 1'b0;
|
||||
assign m_axi_awcache = 4'b0011;
|
||||
assign m_axi_awprot = 3'b010;
|
||||
assign m_axi_awvalid = m_axi_awvalid_reg;
|
||||
assign m_axi_wdata = m_axi_wdata_reg;
|
||||
assign m_axi_wstrb = m_axi_wstrb_reg;
|
||||
assign m_axi_wvalid = m_axi_wvalid_reg;
|
||||
assign m_axi_wlast = m_axi_wlast_reg;
|
||||
assign m_axi_bready = m_axi_bready_reg;
|
||||
|
||||
// reset synchronization
|
||||
wire rst_req_int = cfg_reset;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg rst_sync_1_reg = 1'b1, rst_sync_2_reg = 1'b1, rst_sync_3_reg = 1'b1;
|
||||
|
||||
assign input_rst_out = rst_sync_3_reg;
|
||||
|
||||
always @(posedge input_clk or posedge rst_req_int) begin
|
||||
if (rst_req_int) begin
|
||||
rst_sync_1_reg <= 1'b1;
|
||||
end else begin
|
||||
rst_sync_1_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge input_clk) begin
|
||||
rst_sync_2_reg <= rst_sync_1_reg;
|
||||
rst_sync_3_reg <= rst_sync_2_reg;
|
||||
end
|
||||
|
||||
// input datapath logic (write data)
|
||||
wire [AXI_DATA_WIDTH-1:0] input_data_int;
|
||||
reg input_valid_int_reg = 1'b0;
|
||||
|
||||
reg input_read_en;
|
||||
|
||||
wire [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_wr_ptr;
|
||||
wire [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_wr_ptr_gray;
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_rd_ptr_reg = 0;
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_rd_ptr_gray_reg = 0;
|
||||
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_rd_ptr_temp;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_wr_ptr_gray_sync_1_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_wr_ptr_gray_sync_2_reg = 0;
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_wr_ptr_sync_reg = 0;
|
||||
|
||||
(* shreg_extract = "no" *)
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_rd_ptr_gray_sync_1_reg = 0;
|
||||
(* shreg_extract = "no" *)
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_rd_ptr_gray_sync_2_reg = 0;
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_rd_ptr_sync_reg = 0;
|
||||
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] write_fifo_occupancy_reg = 0;
|
||||
|
||||
wire [SEG_CNT-1:0] write_fifo_seg_full;
|
||||
wire [SEG_CNT-1:0] write_fifo_seg_empty;
|
||||
wire [SEG_CNT-1:0] write_fifo_seg_watermark;
|
||||
|
||||
wire write_fifo_full = |write_fifo_seg_full;
|
||||
wire write_fifo_empty = |write_fifo_seg_empty;
|
||||
|
||||
assign input_watermark = |write_fifo_seg_watermark | input_rst_out;
|
||||
|
||||
genvar n;
|
||||
integer k;
|
||||
|
||||
generate
|
||||
|
||||
for (n = 0; n < SEG_CNT; n = n + 1) begin : write_fifo_seg
|
||||
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] seg_wr_ptr_reg = 0;
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] seg_wr_ptr_gray_reg = 0;
|
||||
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] seg_wr_ptr_temp;
|
||||
|
||||
reg [WRITE_FIFO_ADDR_WIDTH+1-1:0] seg_occupancy_reg = 0;
|
||||
|
||||
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
|
||||
reg [SEG_WIDTH-1:0] seg_mem_data[2**WRITE_FIFO_ADDR_WIDTH-1:0];
|
||||
|
||||
reg [SEG_WIDTH-1:0] seg_rd_data_reg = 0;
|
||||
|
||||
wire seg_full = seg_wr_ptr_gray_reg == (write_fifo_rd_ptr_gray_sync_2_reg ^ {2'b11, {WRITE_FIFO_ADDR_WIDTH-1{1'b0}}});
|
||||
wire seg_empty = write_fifo_rd_ptr_reg == write_fifo_wr_ptr_sync_reg;
|
||||
wire seg_watermark = seg_occupancy_reg > WATERMARK_LEVEL;
|
||||
|
||||
assign input_data_int[n*SEG_WIDTH +: SEG_WIDTH] = seg_rd_data_reg;
|
||||
|
||||
assign input_ready[n] = !seg_full && !input_rst_out;
|
||||
|
||||
assign write_fifo_seg_full[n] = seg_full;
|
||||
assign write_fifo_seg_empty[n] = seg_empty;
|
||||
assign write_fifo_seg_watermark[n] = seg_watermark;
|
||||
|
||||
if (n == SEG_CNT-1) begin
|
||||
assign write_fifo_wr_ptr = seg_wr_ptr_reg;
|
||||
assign write_fifo_wr_ptr_gray = seg_wr_ptr_gray_reg;
|
||||
end
|
||||
|
||||
// per-segment write logic
|
||||
always @(posedge input_clk) begin
|
||||
seg_occupancy_reg <= seg_wr_ptr_reg - write_fifo_rd_ptr_sync_reg;
|
||||
|
||||
if (input_ready[n] && input_valid[n]) begin
|
||||
seg_mem_data[seg_wr_ptr_reg[WRITE_FIFO_ADDR_WIDTH-1:0]] <= input_data[n*SEG_WIDTH +: SEG_WIDTH];
|
||||
|
||||
seg_wr_ptr_temp = seg_wr_ptr_reg + 1;
|
||||
seg_wr_ptr_reg <= seg_wr_ptr_temp;
|
||||
seg_wr_ptr_gray_reg <= seg_wr_ptr_temp ^ (seg_wr_ptr_temp >> 1);
|
||||
end
|
||||
|
||||
if (input_rst || input_rst_out) begin
|
||||
seg_wr_ptr_reg <= 0;
|
||||
seg_wr_ptr_gray_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!write_fifo_empty && (!input_valid_int_reg || input_read_en)) begin
|
||||
seg_rd_data_reg <= seg_mem_data[write_fifo_rd_ptr_reg[WRITE_FIFO_ADDR_WIDTH-1:0]];
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
endgenerate
|
||||
|
||||
// pointer synchronization
|
||||
always @(posedge input_clk) begin
|
||||
write_fifo_rd_ptr_gray_sync_1_reg <= write_fifo_rd_ptr_gray_reg;
|
||||
write_fifo_rd_ptr_gray_sync_2_reg <= write_fifo_rd_ptr_gray_sync_1_reg;
|
||||
|
||||
for (k = 0; k < WRITE_FIFO_ADDR_WIDTH+1; k = k + 1) begin
|
||||
write_fifo_rd_ptr_sync_reg[k] <= ^(write_fifo_rd_ptr_gray_sync_2_reg >> k);
|
||||
end
|
||||
|
||||
if (input_rst || input_rst_out) begin
|
||||
write_fifo_rd_ptr_gray_sync_1_reg <= 0;
|
||||
write_fifo_rd_ptr_gray_sync_2_reg <= 0;
|
||||
write_fifo_rd_ptr_sync_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
write_fifo_wr_ptr_gray_sync_1_reg <= write_fifo_wr_ptr_gray;
|
||||
write_fifo_wr_ptr_gray_sync_2_reg <= write_fifo_wr_ptr_gray_sync_1_reg;
|
||||
|
||||
for (k = 0; k < WRITE_FIFO_ADDR_WIDTH+1; k = k + 1) begin
|
||||
write_fifo_wr_ptr_sync_reg[k] <= ^(write_fifo_wr_ptr_gray_sync_2_reg >> k);
|
||||
end
|
||||
|
||||
if (rst || cfg_reset) begin
|
||||
write_fifo_wr_ptr_gray_sync_1_reg <= 0;
|
||||
write_fifo_wr_ptr_gray_sync_2_reg <= 0;
|
||||
write_fifo_wr_ptr_sync_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
// read logic
|
||||
always @(posedge clk) begin
|
||||
write_fifo_occupancy_reg <= write_fifo_wr_ptr_sync_reg - write_fifo_rd_ptr_reg + input_valid_int_reg;
|
||||
|
||||
if (input_read_en) begin
|
||||
input_valid_int_reg <= 1'b0;
|
||||
write_fifo_occupancy_reg <= write_fifo_wr_ptr_sync_reg - write_fifo_rd_ptr_reg;
|
||||
end
|
||||
|
||||
if (!write_fifo_empty && (!input_valid_int_reg || input_read_en)) begin
|
||||
input_valid_int_reg <= 1'b1;
|
||||
|
||||
write_fifo_rd_ptr_temp = write_fifo_rd_ptr_reg + 1;
|
||||
write_fifo_rd_ptr_reg <= write_fifo_rd_ptr_temp;
|
||||
write_fifo_rd_ptr_gray_reg <= write_fifo_rd_ptr_temp ^ (write_fifo_rd_ptr_temp >> 1);
|
||||
|
||||
write_fifo_occupancy_reg <= write_fifo_wr_ptr_sync_reg - write_fifo_rd_ptr_reg;
|
||||
end
|
||||
|
||||
if (rst || cfg_reset) begin
|
||||
write_fifo_rd_ptr_reg <= 0;
|
||||
write_fifo_rd_ptr_gray_reg <= 0;
|
||||
input_valid_int_reg <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
reg [WRITE_BURST_LEN_WIDTH+1-1:0] wr_burst_len;
|
||||
reg [LEN_WIDTH+1-1:0] wr_start_ptr;
|
||||
reg [LEN_WIDTH+1-1:0] wr_start_ptr_blk_adj;
|
||||
reg wr_burst_reg = 1'b0, wr_burst_next;
|
||||
reg [WRITE_BURST_LEN_WIDTH-1:0] wr_burst_len_reg = 0, wr_burst_len_next;
|
||||
reg [7:0] wr_timeout_count_reg = 0, wr_timeout_count_next;
|
||||
reg wr_timeout_reg = 0, wr_timeout_next;
|
||||
reg fifo_full_wr_blk_adj_reg = 1'b0, fifo_full_wr_blk_adj_next;
|
||||
|
||||
reg [LEN_WIDTH+1-1:0] wr_start_ptr_reg = 0, wr_start_ptr_next;
|
||||
reg [LEN_WIDTH+1-1:0] wr_start_ptr_blk_adj_reg = 0, wr_start_ptr_blk_adj_next;
|
||||
reg [LEN_WIDTH+1-1:0] wr_finish_ptr_reg = 0, wr_finish_ptr_next;
|
||||
|
||||
reg resp_fifo_we_reg = 1'b0, resp_fifo_we_next;
|
||||
reg [RESP_FIFO_ADDR_WIDTH+1-1:0] resp_fifo_wr_ptr_reg = 0;
|
||||
reg [RESP_FIFO_ADDR_WIDTH+1-1:0] resp_fifo_rd_ptr_reg = 0, resp_fifo_rd_ptr_next;
|
||||
reg [WRITE_BURST_LEN_WIDTH+1-1:0] resp_fifo_burst_len[(2**RESP_FIFO_ADDR_WIDTH)-1:0];
|
||||
reg [WRITE_BURST_LEN_WIDTH+1-1:0] resp_fifo_wr_burst_len_reg = 0, resp_fifo_wr_burst_len_next;
|
||||
|
||||
assign wr_start_ptr_out = wr_start_ptr_reg;
|
||||
assign wr_finish_ptr_out = wr_finish_ptr_reg;
|
||||
|
||||
// FIFO occupancy using adjusted write start pointer
|
||||
wire [LEN_WIDTH+1-1:0] fifo_occupancy_wr_blk_adj = wr_start_ptr_blk_adj_reg - rd_finish_ptr_in;
|
||||
// FIFO full indication - no space to start writing a complete block
|
||||
wire fifo_full_wr_blk_adj = (fifo_occupancy_wr_blk_adj & ~cfg_fifo_size_mask) || ((~fifo_occupancy_wr_blk_adj & cfg_fifo_size_mask & ~WRITE_BURST_ADDR_MASK) == 0 && (fifo_occupancy_wr_blk_adj & WRITE_BURST_ADDR_MASK));
|
||||
|
||||
// FIFO occupancy (including all in-progress reads and writes)
|
||||
assign sts_fifo_occupancy = wr_start_ptr_reg - rd_finish_ptr_in;
|
||||
// FIFO empty (including all in-progress reads and writes)
|
||||
assign sts_fifo_empty = wr_start_ptr_reg == rd_finish_ptr_in;
|
||||
// FIFO full
|
||||
assign sts_fifo_full = fifo_full_wr_blk_adj_reg;
|
||||
|
||||
assign sts_write_active = wr_burst_reg || resp_fifo_we_reg || (resp_fifo_wr_ptr_reg != resp_fifo_rd_ptr_reg);
|
||||
|
||||
// write logic
|
||||
always @* begin
|
||||
wr_start_ptr_next = wr_start_ptr_reg;
|
||||
wr_start_ptr_blk_adj_next = wr_start_ptr_blk_adj_reg;
|
||||
wr_finish_ptr_next = wr_finish_ptr_reg;
|
||||
|
||||
wr_burst_next = wr_burst_reg;
|
||||
wr_burst_len_next = wr_burst_len_reg;
|
||||
wr_timeout_count_next = wr_timeout_count_reg;
|
||||
wr_timeout_next = wr_timeout_reg;
|
||||
|
||||
fifo_full_wr_blk_adj_next = fifo_full_wr_blk_adj;
|
||||
|
||||
resp_fifo_we_next = 1'b0;
|
||||
resp_fifo_rd_ptr_next = resp_fifo_rd_ptr_reg;
|
||||
resp_fifo_wr_burst_len_next = wr_burst_len_reg;
|
||||
|
||||
input_read_en = 1'b0;
|
||||
|
||||
m_axi_awaddr_next = m_axi_awaddr_reg;
|
||||
m_axi_awlen_next = m_axi_awlen_reg;
|
||||
m_axi_awvalid_next = m_axi_awvalid_reg && !m_axi_awready;
|
||||
|
||||
m_axi_wdata_next = m_axi_wdata_reg;
|
||||
m_axi_wstrb_next = m_axi_wstrb_reg;
|
||||
m_axi_wlast_next = m_axi_wlast_reg;
|
||||
m_axi_wvalid_next = m_axi_wvalid_reg && !m_axi_wready;
|
||||
|
||||
m_axi_bready_next = 1'b0;
|
||||
|
||||
// partial burst timeout handling
|
||||
wr_timeout_next = wr_timeout_count_reg == 0;
|
||||
if (!input_valid_int_reg || m_axi_awvalid) begin
|
||||
wr_timeout_count_next = 8'hff;
|
||||
wr_timeout_next = 1'b0;
|
||||
end else if (wr_timeout_count_reg > 0) begin
|
||||
wr_timeout_count_next = wr_timeout_count_reg - 1;
|
||||
end
|
||||
|
||||
// compute length based on input FIFO occupancy
|
||||
if ((((wr_start_ptr_reg & WRITE_BURST_ADDR_MASK) >> AXI_BURST_SIZE) + write_fifo_occupancy_reg) >> WRITE_BURST_LEN_WIDTH != 0) begin
|
||||
// crosses burst boundary, write up to burst boundary
|
||||
wr_burst_len = WRITE_MAX_BURST_LEN_INT-1 - ((wr_start_ptr_reg & WRITE_BURST_ADDR_MASK) >> AXI_BURST_SIZE);
|
||||
wr_start_ptr = (wr_start_ptr_reg & ~WRITE_BURST_ADDR_MASK) + (1 << WRITE_BURST_ADDR_WIDTH);
|
||||
wr_start_ptr_blk_adj = (wr_start_ptr_reg & ~WRITE_BURST_ADDR_MASK) + (1 << WRITE_BURST_ADDR_WIDTH);
|
||||
end else begin
|
||||
// does not cross burst boundary, write available data
|
||||
wr_burst_len = write_fifo_occupancy_reg-1;
|
||||
wr_start_ptr = wr_start_ptr_reg + (write_fifo_occupancy_reg << AXI_BURST_SIZE);
|
||||
wr_start_ptr_blk_adj = (wr_start_ptr_reg & ~WRITE_BURST_ADDR_MASK) + (1 << WRITE_BURST_ADDR_WIDTH);
|
||||
end
|
||||
|
||||
resp_fifo_wr_burst_len_next = wr_burst_len;
|
||||
|
||||
// generate AXI write bursts
|
||||
if (!m_axi_awvalid_reg && !wr_burst_reg) begin
|
||||
// ready to start new burst
|
||||
|
||||
wr_burst_len_next = wr_burst_len;
|
||||
|
||||
m_axi_awaddr_next = cfg_fifo_base_addr + (wr_start_ptr_reg & cfg_fifo_size_mask);
|
||||
m_axi_awlen_next = wr_burst_len;
|
||||
|
||||
if (cfg_enable && input_valid_int_reg && !fifo_full_wr_blk_adj_reg) begin
|
||||
// enabled, have data to write, have space for data
|
||||
if ((write_fifo_occupancy_reg) >> WRITE_BURST_LEN_WIDTH != 0 || wr_timeout_reg) begin
|
||||
// have full burst or timed out
|
||||
wr_burst_next = 1'b1;
|
||||
m_axi_awvalid_next = 1'b1;
|
||||
resp_fifo_we_next = 1'b1;
|
||||
wr_start_ptr_next = wr_start_ptr;
|
||||
wr_start_ptr_blk_adj_next = wr_start_ptr_blk_adj;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (!m_axi_wvalid_reg || m_axi_wready) begin
|
||||
// transfer data
|
||||
m_axi_wdata_next = input_data_int;
|
||||
m_axi_wlast_next = wr_burst_len_reg == 0;
|
||||
|
||||
if (wr_burst_reg) begin
|
||||
m_axi_wstrb_next = {AXI_STRB_WIDTH{1'b1}};
|
||||
if (cfg_reset) begin
|
||||
m_axi_wstrb_next = 0;
|
||||
m_axi_wvalid_next = 1'b1;
|
||||
wr_burst_len_next = wr_burst_len_reg - 1;
|
||||
wr_burst_next = wr_burst_len_reg != 0;
|
||||
end else if (input_valid_int_reg) begin
|
||||
input_read_en = 1'b1;
|
||||
m_axi_wvalid_next = 1'b1;
|
||||
wr_burst_len_next = wr_burst_len_reg - 1;
|
||||
wr_burst_next = wr_burst_len_reg != 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// handle AXI write completions
|
||||
m_axi_bready_next = 1'b1;
|
||||
if (m_axi_bvalid) begin
|
||||
wr_finish_ptr_next = wr_finish_ptr_reg + ((resp_fifo_burst_len[resp_fifo_rd_ptr_reg[RESP_FIFO_ADDR_WIDTH-1:0]]+1) << AXI_BURST_SIZE);
|
||||
resp_fifo_rd_ptr_next = resp_fifo_rd_ptr_reg + 1;
|
||||
end
|
||||
|
||||
if (cfg_reset) begin
|
||||
wr_start_ptr_next = 0;
|
||||
wr_start_ptr_blk_adj_next = 0;
|
||||
wr_finish_ptr_next = 0;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
wr_start_ptr_reg <= wr_start_ptr_next;
|
||||
wr_start_ptr_blk_adj_reg <= wr_start_ptr_blk_adj_next;
|
||||
wr_finish_ptr_reg <= wr_finish_ptr_next;
|
||||
|
||||
wr_burst_reg <= wr_burst_next;
|
||||
wr_burst_len_reg <= wr_burst_len_next;
|
||||
wr_timeout_count_reg <= wr_timeout_count_next;
|
||||
wr_timeout_reg <= wr_timeout_next;
|
||||
fifo_full_wr_blk_adj_reg <= fifo_full_wr_blk_adj_next;
|
||||
|
||||
m_axi_awaddr_reg <= m_axi_awaddr_next;
|
||||
m_axi_awlen_reg <= m_axi_awlen_next;
|
||||
m_axi_awvalid_reg <= m_axi_awvalid_next;
|
||||
|
||||
m_axi_wdata_reg <= m_axi_wdata_next;
|
||||
m_axi_wstrb_reg <= m_axi_wstrb_next;
|
||||
m_axi_wlast_reg <= m_axi_wlast_next;
|
||||
m_axi_wvalid_reg <= m_axi_wvalid_next;
|
||||
|
||||
m_axi_bready_reg <= m_axi_bready_next;
|
||||
|
||||
resp_fifo_we_reg <= resp_fifo_we_next;
|
||||
resp_fifo_wr_burst_len_reg <= resp_fifo_wr_burst_len_next;
|
||||
|
||||
if (resp_fifo_we_reg) begin
|
||||
resp_fifo_burst_len[resp_fifo_wr_ptr_reg[RESP_FIFO_ADDR_WIDTH-1:0]] <= resp_fifo_wr_burst_len_reg;
|
||||
resp_fifo_wr_ptr_reg <= resp_fifo_wr_ptr_reg + 1;
|
||||
end
|
||||
resp_fifo_rd_ptr_reg <= resp_fifo_rd_ptr_next;
|
||||
|
||||
if (rst) begin
|
||||
wr_burst_reg <= 1'b0;
|
||||
m_axi_awvalid_reg <= 1'b0;
|
||||
m_axi_wvalid_reg <= 1'b0;
|
||||
m_axi_bready_reg <= 1'b0;
|
||||
resp_fifo_we_reg <= 1'b0;
|
||||
resp_fifo_wr_ptr_reg <= 0;
|
||||
resp_fifo_rd_ptr_reg <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`resetall
|
86
syn/vivado/axi_vfifo.tcl
Normal file
86
syn/vivado/axi_vfifo.tcl
Normal file
@ -0,0 +1,86 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# AXI virtual FIFO timing constraints
|
||||
|
||||
foreach inst [get_cells -hier -filter {(ORIG_REF_NAME == axi_vfifo || REF_NAME == axi_vfifo)}] {
|
||||
puts "Inserting timing constraints for axil_vfifo instance $inst"
|
||||
|
||||
proc constrain_sync_chain {inst driver args} {
|
||||
set sync_ffs [get_cells -hier [concat $driver $args] -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set src_clk [get_clocks -of_objects [get_cells "$inst/$driver"]]
|
||||
|
||||
set src_clk_period [if {[llength $src_clk]} {get_property -min PERIOD $src_clk} {expr 1.0}]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/$driver"] -to [get_cells "$inst/[lindex $args 0]"] -datapath_only $src_clk_period
|
||||
}
|
||||
}
|
||||
|
||||
proc constrain_sync_chain_async {inst driver args} {
|
||||
set sync_ffs [get_cells -hier [concat $driver $args] -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_false_path -to [get_pins "$inst/$driver/D"]
|
||||
}
|
||||
}
|
||||
|
||||
# control
|
||||
constrain_sync_chain $inst "cfg_enable_reg_reg" "axi_ch[*].ch_cfg_enable_sync_1_reg_reg" "axi_ch[*].ch_cfg_enable_sync_2_reg_reg"
|
||||
|
||||
set sync_ffs [get_cells "$inst/cfg_fifo_base_addr_reg_reg[*] $inst/axi_ch[*].axi_vfifo_raw_inst/fifo_base_addr_reg_reg[*]"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set src_clk [get_clocks -of_objects [get_cells "$inst/cfg_fifo_base_addr_reg_reg[*]"]]
|
||||
|
||||
set src_clk_period [if {[llength $src_clk]} {get_property -min PERIOD $src_clk} {expr 1.0}]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/cfg_fifo_base_addr_reg_reg[*]"] -to [get_cells "$inst/axi_ch[*].axi_vfifo_raw_inst/fifo_base_addr_reg_reg[*]"] -datapath_only $src_clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells "$inst/cfg_fifo_size_mask_reg_reg[*] $inst/axi_ch[*].axi_vfifo_raw_inst/fifo_size_mask_reg_reg[*]"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set src_clk [get_clocks -of_objects [get_cells "$inst/cfg_fifo_size_mask_reg_reg[*]"]]
|
||||
|
||||
set src_clk_period [if {[llength $src_clk]} {get_property -min PERIOD $src_clk} {expr 1.0}]
|
||||
|
||||
set_max_delay -from [get_cells "$inst/cfg_fifo_size_mask_reg_reg[*]"] -to [get_cells "$inst/axi_ch[*].axi_vfifo_raw_inst/fifo_size_mask_reg_reg[*]"] -datapath_only $src_clk_period
|
||||
}
|
||||
|
||||
# status
|
||||
constrain_sync_chain $inst "sts_sync_flag_reg_reg" "axi_ch[*].ch_sts_flag_sync_1_reg_reg" "axi_ch[*].ch_sts_flag_sync_2_reg_reg"
|
||||
constrain_sync_chain_async $inst "sts_fifo_occupancy_sync_reg_reg[*]"
|
||||
|
||||
constrain_sync_chain_async $inst "sts_fifo_empty_sync_1_reg_reg[*]" "sts_fifo_empty_sync_2_reg_reg[*]"
|
||||
constrain_sync_chain_async $inst "sts_fifo_full_sync_1_reg_reg[*]" "sts_fifo_full_sync_2_reg_reg[*]"
|
||||
constrain_sync_chain_async $inst "sts_reset_sync_1_reg_reg[*]" "sts_reset_sync_2_reg_reg[*]"
|
||||
constrain_sync_chain_async $inst "sts_active_sync_1_reg_reg[*]" "sts_active_sync_2_reg_reg[*]"
|
||||
constrain_sync_chain $inst "sts_hdr_parity_err_reg_reg" "sts_hdr_parity_err_sync_1_reg_reg" "sts_hdr_parity_err_sync_2_reg_reg"
|
||||
}
|
31
syn/vivado/axi_vfifo_raw.tcl
Normal file
31
syn/vivado/axi_vfifo_raw.tcl
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# AXI virtual FIFO (raw) timing constraints
|
||||
|
||||
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "axi_vfifo_raw(__xdcDup__\d+)?" || REF_NAME =~ "axi_vfifo_raw(__xdcDup__\d+)?")}] {
|
||||
puts "Inserting timing constraints for axi_vfifo_raw instance $inst"
|
||||
|
||||
# reset synchronization
|
||||
set reset_ffs [get_cells -quiet -hier -regexp ".*/rst_sync_\[123\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
set_property ASYNC_REG TRUE $reset_ffs
|
||||
set_false_path -to [get_pins -of_objects $reset_ffs -filter {IS_PRESET || IS_RESET}]
|
||||
}
|
77
syn/vivado/axi_vfifo_raw_rd.tcl
Normal file
77
syn/vivado/axi_vfifo_raw_rd.tcl
Normal file
@ -0,0 +1,77 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# AXI virtual FIFO (raw, read) timing constraints
|
||||
|
||||
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "axi_vfifo_raw_rd(__xdcDup__\d+)?" || REF_NAME =~ "axi_vfifo_raw_rd(__xdcDup__\d+)?")}] {
|
||||
puts "Inserting timing constraints for axi_vfifo_raw_rd instance $inst"
|
||||
|
||||
# get clock periods
|
||||
set clk [get_clocks -of_objects [get_cells "$inst/rd_start_ptr_reg_reg[*]"]]
|
||||
set output_clk [get_clocks -of_objects [get_cells "$inst/read_fifo_wr_ptr_gray_sync_1_reg_reg[*]"]]
|
||||
|
||||
set clk_period [if {[llength $clk]} {get_property -min PERIOD $clk} {expr 1.0}]
|
||||
set output_clk_period [if {[llength $output_clk]} {get_property -min PERIOD $output_clk} {expr 1.0}]
|
||||
|
||||
set min_clk_period [expr min($clk_period, $output_clk_period)]
|
||||
|
||||
# reset synchronization
|
||||
set reset_ffs [get_cells -quiet -hier -regexp ".*/rst_sync_\[123\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $reset_ffs]} {
|
||||
set_property ASYNC_REG TRUE $reset_ffs
|
||||
set_false_path -to [get_pins -of_objects $reset_ffs -filter {IS_PRESET || IS_RESET}]
|
||||
}
|
||||
|
||||
# read FIFO pointer synchronization
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/read_fifo_wr_ptr_gray_sync_\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/read_fifo_wr_ptr_reg_reg[*] $inst/read_fifo_wr_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/read_fifo_wr_ptr_gray_sync_1_reg_reg[*]"] -datapath_only $clk_period
|
||||
set_bus_skew -from [get_cells "$inst/read_fifo_wr_ptr_reg_reg[*] $inst/read_fifo_wr_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/read_fifo_wr_ptr_gray_sync_1_reg_reg[*]"] $output_clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/read_fifo_rd_ptr_gray_sync_\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/read_fifo_seg[*].seg_rd_ptr_reg_reg[*] $inst/read_fifo_seg[*].seg_rd_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/read_fifo_rd_ptr_gray_sync_1_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/read_fifo_seg[*].seg_rd_ptr_reg_reg[*] $inst/read_fifo_seg[*].seg_rd_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/read_fifo_rd_ptr_gray_sync_1_reg_reg[*]"] $clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/read_fifo_ctrl_rd_ptr_gray_sync_\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/read_fifo_ctrl_seg[*].seg_rd_ptr_reg_reg[*] $inst/read_fifo_ctrl_seg[*].seg_rd_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/read_fifo_ctrl_rd_ptr_gray_sync_1_reg_reg[*]"] -datapath_only $output_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/read_fifo_ctrl_seg[*].seg_rd_ptr_reg_reg[*] $inst/read_fifo_ctrl_seg[*].seg_rd_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/read_fifo_ctrl_rd_ptr_gray_sync_1_reg_reg[*]"] $clk_period
|
||||
}
|
||||
|
||||
# read FIFO output register (needed for distributed RAM sync write/async read)
|
||||
set output_reg_ffs [get_cells -quiet "$inst/read_fifo_seg[*].seg_rd_data_reg_reg[*] $inst/read_fifo_ctrl_seg[*].seg_rd_data_reg_reg[*]"]
|
||||
|
||||
if {[llength $output_reg_ffs] && [llength $clk]} {
|
||||
set_false_path -from $clk -to $output_reg_ffs
|
||||
}
|
||||
}
|
68
syn/vivado/axi_vfifo_raw_wr.tcl
Normal file
68
syn/vivado/axi_vfifo_raw_wr.tcl
Normal file
@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# AXI virtual FIFO (raw, write) timing constraints
|
||||
|
||||
foreach inst [get_cells -hier -regexp -filter {(ORIG_REF_NAME =~ "axi_vfifo_raw_wr(__xdcDup__\d+)?" || REF_NAME =~ "axi_vfifo_raw_wr(__xdcDup__\d+)?")}] {
|
||||
puts "Inserting timing constraints for axi_vfifo_raw_wr instance $inst"
|
||||
|
||||
# get clock periods
|
||||
set clk [get_clocks -of_objects [get_cells "$inst/wr_start_ptr_reg_reg[*]"]]
|
||||
set input_clk [get_clocks -of_objects [get_cells "$inst/write_fifo_rd_ptr_gray_sync_1_reg_reg[*]"]]
|
||||
|
||||
set clk_period [if {[llength $clk]} {get_property -min PERIOD $clk} {expr 1.0}]
|
||||
set input_clk_period [if {[llength $input_clk]} {get_property -min PERIOD $input_clk} {expr 1.0}]
|
||||
|
||||
set min_clk_period [expr min($clk_period, $input_clk_period)]
|
||||
|
||||
# reset synchronization
|
||||
set reset_ffs [get_cells -quiet -hier -regexp ".*/rst_sync_\[123\]_reg_reg" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $reset_ffs]} {
|
||||
set_property ASYNC_REG TRUE $reset_ffs
|
||||
set_false_path -to [get_pins -of_objects $reset_ffs -filter {IS_PRESET || IS_RESET}]
|
||||
}
|
||||
|
||||
# write FIFO pointer synchronization
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/write_fifo_wr_ptr_gray_sync_\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/write_fifo_seg[*].seg_wr_ptr_reg_reg[*] $inst/write_fifo_seg[*].seg_wr_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/write_fifo_wr_ptr_gray_sync_1_reg_reg[*]"] -datapath_only $input_clk_period
|
||||
set_bus_skew -from [get_cells "$inst/write_fifo_seg[*].seg_wr_ptr_reg_reg[*] $inst/write_fifo_seg[*].seg_wr_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/write_fifo_wr_ptr_gray_sync_1_reg_reg[*]"] $clk_period
|
||||
}
|
||||
|
||||
set sync_ffs [get_cells -quiet -hier -regexp ".*/write_fifo_rd_ptr_gray_sync_\[12\]_reg_reg\\\[\\d+\\\]" -filter "PARENT == $inst"]
|
||||
|
||||
if {[llength $sync_ffs]} {
|
||||
set_property ASYNC_REG TRUE $sync_ffs
|
||||
|
||||
set_max_delay -from [get_cells "$inst/write_fifo_rd_ptr_reg_reg[*] $inst/write_fifo_rd_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/write_fifo_rd_ptr_gray_sync_1_reg_reg[*]"] -datapath_only $clk_period
|
||||
set_bus_skew -from [get_cells "$inst/write_fifo_rd_ptr_reg_reg[*] $inst/write_fifo_rd_ptr_gray_reg_reg[*]"] -to [get_cells "$inst/write_fifo_rd_ptr_gray_sync_1_reg_reg[*]"] $input_clk_period
|
||||
}
|
||||
|
||||
# write FIFO output register (needed for distributed RAM sync write/async read)
|
||||
set output_reg_ffs [get_cells -quiet "$inst/write_fifo_seg[*].seg_rd_data_reg_reg[*]"]
|
||||
|
||||
if {[llength $output_reg_ffs] && [llength $input_clk]} {
|
||||
set_false_path -from $input_clk -to $output_reg_ffs
|
||||
}
|
||||
}
|
94
tb/axi_vfifo/Makefile
Normal file
94
tb/axi_vfifo/Makefile
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= icarus
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
DUT = axi_vfifo
|
||||
TOPLEVEL = $(DUT)
|
||||
MODULE = test_$(DUT)
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT).v
|
||||
VERILOG_SOURCES += ../../rtl/axi_vfifo_raw.v
|
||||
VERILOG_SOURCES += ../../rtl/axi_vfifo_raw_wr.v
|
||||
VERILOG_SOURCES += ../../rtl/axi_vfifo_raw_rd.v
|
||||
VERILOG_SOURCES += ../../rtl/axi_vfifo_enc.v
|
||||
VERILOG_SOURCES += ../../rtl/axi_vfifo_dec.v
|
||||
|
||||
# module parameters
|
||||
export PARAM_AXI_CH := 2
|
||||
export PARAM_AXI_DATA_WIDTH := 512
|
||||
export PARAM_AXI_ADDR_WIDTH := 16
|
||||
export PARAM_AXI_STRB_WIDTH := $(shell expr $(PARAM_AXI_DATA_WIDTH) / 8 )
|
||||
export PARAM_AXI_ID_WIDTH := 8
|
||||
export PARAM_AXI_MAX_BURST_LEN := 16
|
||||
export PARAM_AXIS_DATA_WIDTH := $(PARAM_AXI_DATA_WIDTH)
|
||||
export PARAM_AXIS_KEEP_ENABLE := $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 )
|
||||
export PARAM_AXIS_KEEP_WIDTH := $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 )
|
||||
export PARAM_AXIS_LAST_ENABLE := 1
|
||||
export PARAM_AXIS_ID_ENABLE := 1
|
||||
export PARAM_AXIS_ID_WIDTH := 8
|
||||
export PARAM_AXIS_DEST_ENABLE := 1
|
||||
export PARAM_AXIS_DEST_WIDTH := 8
|
||||
export PARAM_AXIS_USER_ENABLE := 1
|
||||
export PARAM_AXIS_USER_WIDTH := 1
|
||||
export PARAM_LEN_WIDTH := $(PARAM_AXI_ADDR_WIDTH)
|
||||
export PARAM_MAX_SEG_WIDTH := 256
|
||||
export PARAM_WRITE_FIFO_DEPTH := 64
|
||||
export PARAM_WRITE_MAX_BURST_LEN := $(shell expr $(PARAM_WRITE_FIFO_DEPTH) / 4 )
|
||||
export PARAM_READ_FIFO_DEPTH := 128
|
||||
export PARAM_READ_MAX_BURST_LEN := $(PARAM_WRITE_MAX_BURST_LEN)
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
COMPILE_ARGS += -s iverilog_dump
|
||||
endif
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||
echo 'end' >> $@
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
685
tb/axi_vfifo/test_axi_vfifo.py
Normal file
685
tb/axi_vfifo/test_axi_vfifo.py
Normal file
@ -0,0 +1,685 @@
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
|
||||
import cocotb_test.simulator
|
||||
import pytest
|
||||
|
||||
import cocotb
|
||||
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotb.regression import TestFactory
|
||||
|
||||
from cocotbext.axi import AxiBus, AxiRam
|
||||
from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSource, AxiStreamSink
|
||||
|
||||
|
||||
class TB(object):
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 8, units="ns").start())
|
||||
|
||||
# streaming data in
|
||||
cocotb.start_soon(Clock(dut.s_axis_clk, 6, units="ns").start())
|
||||
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.s_axis_clk, dut.s_axis_rst_out)
|
||||
|
||||
# streaming data out
|
||||
cocotb.start_soon(Clock(dut.m_axis_clk, 6, units="ns").start())
|
||||
self.sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "m_axis"), dut.m_axis_clk, dut.m_axis_rst_out)
|
||||
|
||||
# AXI interfaces
|
||||
self.axi_ram = []
|
||||
for ch in dut.axi_ch:
|
||||
cocotb.start_soon(Clock(ch.ch_clk, 3, units="ns").start())
|
||||
ram = AxiRam(AxiBus.from_prefix(ch.axi_vfifo_raw_inst, "m_axi"), ch.ch_clk, ch.ch_rst, size=2**16)
|
||||
self.axi_ram.append(ram)
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(0)
|
||||
dut.cfg_enable.setimmediatevalue(0)
|
||||
dut.cfg_reset.setimmediatevalue(0)
|
||||
|
||||
def set_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.source.set_pause_generator(generator())
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.b_channel.set_pause_generator(generator())
|
||||
ram.read_if.r_channel.set_pause_generator(generator())
|
||||
|
||||
def set_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.sink.set_pause_generator(generator())
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.aw_channel.set_pause_generator(generator())
|
||||
ram.write_if.w_channel.set_pause_generator(generator())
|
||||
ram.read_if.ar_channel.set_pause_generator(generator())
|
||||
|
||||
def set_stream_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.source.set_pause_generator(generator())
|
||||
|
||||
def set_stream_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.sink.set_pause_generator(generator())
|
||||
|
||||
def set_axi_0_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.axi_ram[0].write_if.b_channel.set_pause_generator(generator())
|
||||
self.axi_ram[0].read_if.r_channel.set_pause_generator(generator())
|
||||
|
||||
def set_axi_0_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.axi_ram[0].write_if.aw_channel.set_pause_generator(generator())
|
||||
self.axi_ram[0].write_if.w_channel.set_pause_generator(generator())
|
||||
self.axi_ram[0].read_if.ar_channel.set_pause_generator(generator())
|
||||
|
||||
def set_axi_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.b_channel.set_pause_generator(generator())
|
||||
ram.read_if.r_channel.set_pause_generator(generator())
|
||||
|
||||
def set_axi_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.aw_channel.set_pause_generator(generator())
|
||||
ram.write_if.w_channel.set_pause_generator(generator())
|
||||
ram.read_if.ar_channel.set_pause_generator(generator())
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
self.dut.s_axis_rst.setimmediatevalue(0)
|
||||
self.dut.m_axis_rst.setimmediatevalue(0)
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.reset.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
self.dut.s_axis_rst.value = 1
|
||||
self.dut.m_axis_rst.value = 1
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.reset.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
self.dut.s_axis_rst.value = 0
|
||||
self.dut.m_axis_rst.value = 0
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.reset.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_source(self):
|
||||
self.dut.s_axis_rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.s_axis_clk)
|
||||
self.dut.s_axis_rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.s_axis_clk)
|
||||
self.dut.s_axis_rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.s_axis_clk)
|
||||
|
||||
async def reset_sink(self):
|
||||
self.dut.m_axis_rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.m_axis_clk)
|
||||
self.dut.m_axis_rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.m_axis_clk)
|
||||
self.dut.m_axis_rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.m_axis_clk)
|
||||
|
||||
async def reset_axi_0(self):
|
||||
self.axi_ram[0].write_if.reset.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.axi_ram[0].write_if.reset.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.axi_ram[0].write_if.reset.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_axi(self):
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.reset.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.reset.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
for ram in self.axi_ram:
|
||||
ram.write_if.reset.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_cfg(self):
|
||||
self.dut.cfg_reset.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.cfg_reset.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.cfg_reset.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
|
||||
async def run_test(dut, payload_lengths=None, payload_data=None, space=False,
|
||||
stream_idle_inserter=None, stream_backpressure_inserter=None,
|
||||
axi_0_idle_inserter=None, axi_0_backpressure_inserter=None,
|
||||
axi_idle_inserter=None, axi_backpressure_inserter=None):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
id_count = 2**len(tb.source.bus.tid)
|
||||
|
||||
cur_id = 1
|
||||
|
||||
await tb.reset()
|
||||
|
||||
tb.set_stream_idle_generator(stream_idle_inserter)
|
||||
tb.set_stream_backpressure_generator(stream_backpressure_inserter)
|
||||
tb.set_axi_idle_generator(axi_idle_inserter)
|
||||
tb.set_axi_backpressure_generator(axi_backpressure_inserter)
|
||||
tb.set_axi_0_backpressure_generator(axi_0_backpressure_inserter)
|
||||
tb.set_axi_0_idle_generator(axi_0_idle_inserter)
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_frames = []
|
||||
|
||||
for test_data in [payload_data(x) for x in payload_lengths()]:
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
test_frame.tid = cur_id
|
||||
test_frame.tdest = cur_id
|
||||
|
||||
test_frames.append(test_frame)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
cur_id = (cur_id + 1) % id_count
|
||||
|
||||
if space:
|
||||
for k in range(1000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
if dut.m_axis_tvalid.value.integer and dut.m_axis_tready.value.integer and dut.m_axis_tlast.value.integer:
|
||||
break
|
||||
|
||||
for test_frame in test_frames:
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_frame.tdata
|
||||
assert rx_frame.tid == test_frame.tid
|
||||
assert rx_frame.tdest == test_frame.tdest
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_test_tuser_assert(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 32*byte_lanes))
|
||||
test_frame = AxiStreamFrame(test_data, tuser=1)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_data
|
||||
assert rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_test_init_sink_pause(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.sink.pause = True
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
for k in range(256):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
tb.sink.pause = False
|
||||
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_data
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_test_init_sink_pause_reset(dut, reset_type=TB.reset):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.sink.pause = True
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
for k in range(256):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
await reset_type(tb)
|
||||
|
||||
tb.sink.pause = False
|
||||
|
||||
for k in range(2048):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
assert tb.sink.idle()
|
||||
assert tb.sink.empty()
|
||||
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_data
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_test_shift_in_reset(dut, reset_type=TB.reset):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
for k in range(256):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
await reset_type(tb)
|
||||
|
||||
for k in range(2048):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
assert tb.sink.idle()
|
||||
assert tb.sink.empty()
|
||||
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_data
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_test_shift_out_reset(dut, reset_type=TB.reset):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
await RisingEdge(dut.m_axis_tvalid)
|
||||
|
||||
for k in range(8):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
await reset_type(tb)
|
||||
|
||||
for k in range(2048):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
assert tb.sink.idle()
|
||||
assert tb.sink.empty()
|
||||
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_data
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_test_overflow(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.sink.pause = True
|
||||
|
||||
ram_size = 2**tb.axi_ram[0].write_if.address_width*len(tb.axi_ram)
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 2*ram_size))
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
for k in range(2048):
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
tb.sink.pause = False
|
||||
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_data
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
async def run_stress_test(dut, space=False,
|
||||
stream_idle_inserter=None, stream_backpressure_inserter=None,
|
||||
axi_0_idle_inserter=None, axi_0_backpressure_inserter=None,
|
||||
axi_idle_inserter=None, axi_backpressure_inserter=None):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
id_count = 2**len(tb.source.bus.tid)
|
||||
|
||||
cur_id = 1
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**16-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.set_stream_idle_generator(stream_idle_inserter)
|
||||
tb.set_stream_backpressure_generator(stream_backpressure_inserter)
|
||||
tb.set_axi_idle_generator(axi_idle_inserter)
|
||||
tb.set_axi_backpressure_generator(axi_backpressure_inserter)
|
||||
tb.set_axi_0_backpressure_generator(axi_0_backpressure_inserter)
|
||||
tb.set_axi_0_idle_generator(axi_0_idle_inserter)
|
||||
|
||||
test_frames = []
|
||||
|
||||
for k in range(128):
|
||||
length = random.randint(1, byte_lanes*16)
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
test_frame.tid = cur_id
|
||||
test_frame.tdest = cur_id
|
||||
|
||||
test_frames.append(test_frame)
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
cur_id = (cur_id + 1) % id_count
|
||||
|
||||
if space:
|
||||
for k in range(1000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
if dut.m_axis_tvalid.value.integer and dut.m_axis_tready.value.integer and dut.m_axis_tlast.value.integer:
|
||||
break
|
||||
|
||||
for test_frame in test_frames:
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_frame.tdata
|
||||
assert rx_frame.tid == test_frame.tid
|
||||
assert rx_frame.tdest == test_frame.tdest
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
await RisingEdge(dut.s_axis_clk)
|
||||
|
||||
|
||||
def cycle_pause():
|
||||
return itertools.cycle([1, 1, 1, 0])
|
||||
|
||||
|
||||
def size_list():
|
||||
data_width = len(cocotb.top.m_axis_tdata)
|
||||
byte_width = data_width // 8
|
||||
return list(range(1, byte_width*4+1))+list(range(byte_width, byte_width*32, byte_width))+[2**14]+[1]*64
|
||||
|
||||
|
||||
def incrementing_payload(length):
|
||||
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||
|
||||
|
||||
if cocotb.SIM_NAME:
|
||||
|
||||
factory = TestFactory(run_test)
|
||||
factory.add_option("payload_lengths", [size_list])
|
||||
factory.add_option("payload_data", [incrementing_payload])
|
||||
factory.add_option(("space",
|
||||
"stream_idle_inserter", "stream_backpressure_inserter",
|
||||
"axi_0_idle_inserter", "axi_0_backpressure_inserter",
|
||||
"axi_idle_inserter", "axi_backpressure_inserter"), [
|
||||
(False, None, None, None, None, None, None),
|
||||
(False, cycle_pause, None, None, None, None, None),
|
||||
(False, None, cycle_pause, None, None, None, None),
|
||||
(False, None, None, cycle_pause, None, None, None),
|
||||
(False, None, None, None, cycle_pause, None, None),
|
||||
(False, None, None, None, None, cycle_pause, None),
|
||||
(False, None, None, None, None, None, cycle_pause),
|
||||
(True, None, None, None, None, None, None),
|
||||
(True, cycle_pause, None, None, None, None, None),
|
||||
(True, None, cycle_pause, None, None, None, None),
|
||||
(True, None, None, cycle_pause, None, None, None),
|
||||
(True, None, None, None, cycle_pause, None, None),
|
||||
(True, None, None, None, None, cycle_pause, None),
|
||||
(True, None, None, None, None, None, cycle_pause),
|
||||
])
|
||||
factory.generate_tests()
|
||||
|
||||
for test in [
|
||||
run_test_tuser_assert,
|
||||
run_test_init_sink_pause,
|
||||
run_test_overflow
|
||||
]:
|
||||
|
||||
factory = TestFactory(test)
|
||||
factory.generate_tests()
|
||||
|
||||
for test in [
|
||||
run_test_init_sink_pause_reset,
|
||||
run_test_shift_in_reset,
|
||||
run_test_shift_out_reset,
|
||||
]:
|
||||
|
||||
factory = TestFactory(test)
|
||||
factory.add_option("reset_type", [TB.reset, TB.reset_source,
|
||||
TB.reset_sink, TB.reset_axi_0, TB.reset_axi, TB.reset_cfg])
|
||||
factory.generate_tests()
|
||||
|
||||
factory = TestFactory(run_stress_test)
|
||||
factory.add_option(("space",
|
||||
"stream_idle_inserter", "stream_backpressure_inserter",
|
||||
"axi_0_idle_inserter", "axi_0_backpressure_inserter",
|
||||
"axi_idle_inserter", "axi_backpressure_inserter"), [
|
||||
(False, None, None, None, None, None, None),
|
||||
(False, cycle_pause, None, None, None, None, None),
|
||||
(False, None, cycle_pause, None, None, None, None),
|
||||
(False, None, None, cycle_pause, None, None, None),
|
||||
(False, None, None, None, cycle_pause, None, None),
|
||||
(False, None, None, None, None, cycle_pause, None),
|
||||
(False, None, None, None, None, None, cycle_pause),
|
||||
(True, None, None, None, None, None, None),
|
||||
(True, cycle_pause, None, None, None, None, None),
|
||||
(True, None, cycle_pause, None, None, None, None),
|
||||
(True, None, None, cycle_pause, None, None, None),
|
||||
(True, None, None, None, cycle_pause, None, None),
|
||||
(True, None, None, None, None, cycle_pause, None),
|
||||
(True, None, None, None, None, None, cycle_pause),
|
||||
])
|
||||
factory.generate_tests()
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("axis_data_width", "axi_ch", "axi_data_width"), [
|
||||
# (32, 1, 32),
|
||||
# (32, 2, 32),
|
||||
(512, 2, 512),
|
||||
])
|
||||
def test_axi_vfifo(request, axis_data_width, axi_ch, axi_data_width):
|
||||
dut = "axi_vfifo"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.v"),
|
||||
os.path.join(rtl_dir, "axi_vfifo_raw.v"),
|
||||
os.path.join(rtl_dir, "axi_vfifo_raw_wr.v"),
|
||||
os.path.join(rtl_dir, "axi_vfifo_raw_rd.v"),
|
||||
os.path.join(rtl_dir, "axi_vfifo_enc.v"),
|
||||
os.path.join(rtl_dir, "axi_vfifo_dec.v"),
|
||||
]
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['AXI_CH'] = axi_ch
|
||||
parameters['AXI_DATA_WIDTH'] = axi_data_width
|
||||
parameters['AXI_ADDR_WIDTH'] = 16
|
||||
parameters['AXI_STRB_WIDTH'] = parameters['AXI_DATA_WIDTH'] // 8
|
||||
parameters['AXI_ID_WIDTH'] = 8
|
||||
parameters['AXI_MAX_BURST_LEN'] = 16
|
||||
parameters['AXIS_DATA_WIDTH'] = axis_data_width
|
||||
parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8)
|
||||
parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8
|
||||
parameters['AXIS_LAST_ENABLE'] = 1
|
||||
parameters['AXIS_ID_ENABLE'] = 1
|
||||
parameters['AXIS_ID_WIDTH'] = 8
|
||||
parameters['AXIS_DEST_ENABLE'] = 1
|
||||
parameters['AXIS_DEST_WIDTH'] = 8
|
||||
parameters['AXIS_USER_ENABLE'] = 1
|
||||
parameters['AXIS_USER_WIDTH'] = 1
|
||||
parameters['LEN_WIDTH'] = parameters['AXI_ADDR_WIDTH']
|
||||
parameters['MAX_SEG_WIDTH'] = 256
|
||||
parameters['WRITE_FIFO_DEPTH'] = 64
|
||||
parameters['WRITE_MAX_BURST_LEN'] = parameters['WRITE_FIFO_DEPTH'] // 4
|
||||
parameters['READ_FIFO_DEPTH'] = 128
|
||||
parameters['READ_MAX_BURST_LEN'] = parameters['WRITE_MAX_BURST_LEN']
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
79
tb/axi_vfifo_dec/Makefile
Normal file
79
tb/axi_vfifo_dec/Makefile
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= icarus
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
DUT = axi_vfifo_dec
|
||||
TOPLEVEL = $(DUT)
|
||||
MODULE = test_$(DUT)
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT).v
|
||||
|
||||
# module parameters
|
||||
export PARAM_SEG_WIDTH := 256
|
||||
export PARAM_SEG_CNT := 4
|
||||
export PARAM_AXIS_DATA_WIDTH := $(shell expr $(PARAM_SEG_WIDTH) \* $(PARAM_SEG_CNT) / 2 )
|
||||
export PARAM_AXIS_KEEP_ENABLE := $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 )
|
||||
export PARAM_AXIS_KEEP_WIDTH := $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 )
|
||||
export PARAM_AXIS_LAST_ENABLE := 1
|
||||
export PARAM_AXIS_ID_ENABLE := 1
|
||||
export PARAM_AXIS_ID_WIDTH := 8
|
||||
export PARAM_AXIS_DEST_ENABLE := 1
|
||||
export PARAM_AXIS_DEST_WIDTH := 8
|
||||
export PARAM_AXIS_USER_ENABLE := 1
|
||||
export PARAM_AXIS_USER_WIDTH := 1
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
COMPILE_ARGS += -s iverilog_dump
|
||||
endif
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||
echo 'end' >> $@
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
394
tb/axi_vfifo_dec/test_axi_vfifo_dec.py
Normal file
394
tb/axi_vfifo_dec/test_axi_vfifo_dec.py
Normal file
@ -0,0 +1,394 @@
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
|
||||
import cocotb_test.simulator
|
||||
import pytest
|
||||
|
||||
import cocotb
|
||||
|
||||
from cocotb.queue import Queue
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotb.regression import TestFactory
|
||||
from cocotb_bus.bus import Bus
|
||||
|
||||
from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSink
|
||||
|
||||
|
||||
class BaseBus(Bus):
|
||||
|
||||
_signals = ["data"]
|
||||
_optional_signals = []
|
||||
|
||||
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
return cls(entity, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
return cls(entity, prefix, **kwargs)
|
||||
|
||||
|
||||
class DataBus(BaseBus):
|
||||
_signals = ["data", "valid", "ready"]
|
||||
|
||||
|
||||
class DataSource:
|
||||
|
||||
def __init__(self, bus, clock, reset=None, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.width = len(self.bus.data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.seg_count = len(self.bus.valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
|
||||
# queue per segment
|
||||
self.queue = [Queue() for x in range(self.seg_count)]
|
||||
|
||||
self.bus.data.setimmediatevalue(0)
|
||||
self.bus.valid.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
def empty(self):
|
||||
for queue in self.queue:
|
||||
if not queue.empty():
|
||||
return False
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
for queue in self.queue:
|
||||
while not queue.empty():
|
||||
_ = queue.get_nowait()
|
||||
|
||||
async def write(self, data):
|
||||
self.write_nowait(data)
|
||||
|
||||
def write_nowait(self, data):
|
||||
data = bytearray(data)
|
||||
|
||||
# pad to interface width
|
||||
if len(data) % self.byte_lanes:
|
||||
data.extend(b'\x00'*(self.byte_lanes - (len(data) % self.byte_lanes)))
|
||||
|
||||
# stripe across segment queues
|
||||
index = 0
|
||||
for offset in range(0, len(data), self.seg_byte_lanes):
|
||||
self.queue[index].put_nowait(data[offset:offset+self.seg_byte_lanes])
|
||||
index = (index + 1) % self.seg_count
|
||||
|
||||
async def _run(self):
|
||||
data = 0
|
||||
valid = 0
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
ready_sample = self.bus.ready.value.integer
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
self.bus.valid.setimmediatevalue(0)
|
||||
valid = 0
|
||||
continue
|
||||
|
||||
# process segments
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
if ((ready_sample & seg_mask) or not (valid & seg_mask)):
|
||||
if not self.queue[seg].empty() and not self.pause:
|
||||
d = self.queue[seg].get_nowait()
|
||||
data &= ~(self.seg_data_mask << self.seg_data_width*seg)
|
||||
data |= int.from_bytes(d, 'little') << self.seg_data_width*seg
|
||||
valid |= seg_mask
|
||||
|
||||
self.log.info("TX seg: %d data: %s", seg, d)
|
||||
else:
|
||||
valid = valid & ~seg_mask
|
||||
|
||||
self.bus.data.value = data
|
||||
self.bus.valid.value = valid
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class TB(object):
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
|
||||
# streaming data in
|
||||
self.source = DataSource(DataBus.from_prefix(dut, "input"), dut.clk, dut.rst)
|
||||
self.source_ctrl = DataSource(DataBus.from_prefix(dut, "input_ctrl"), dut.clk, dut.rst)
|
||||
|
||||
# streaming data out
|
||||
self.sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "m_axis"), dut.clk, dut.rst)
|
||||
|
||||
dut.fifo_rst_in.setimmediatevalue(0)
|
||||
|
||||
def set_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.source_ctrl.set_pause_generator(generator())
|
||||
|
||||
def set_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.sink.set_pause_generator(generator())
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_fifo(self):
|
||||
self.dut.fifo_rst_in.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.fifo_rst_in.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.fifo_rst_in.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
|
||||
async def run_test(dut, payload_lengths=None, payload_data=None, pack=False, idle_inserter=None, backpressure_inserter=None):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
id_width = len(tb.sink.bus.tid)
|
||||
dest_width = len(tb.sink.bus.tdest)
|
||||
user_width = len(tb.sink.bus.tuser)
|
||||
|
||||
seg_cnt = tb.source.seg_count
|
||||
seg_byte_lanes = tb.source.seg_byte_lanes
|
||||
|
||||
max_block_size = seg_byte_lanes*seg_cnt*16
|
||||
|
||||
meta_id_offset = 0
|
||||
meta_dest_offset = meta_id_offset + id_width
|
||||
meta_user_offset = meta_dest_offset + dest_width
|
||||
meta_width = meta_user_offset + user_width
|
||||
hdr_size = (16 + meta_width + 7) // 8
|
||||
|
||||
id_count = 2**id_width
|
||||
|
||||
cur_id = 1
|
||||
|
||||
await tb.reset()
|
||||
|
||||
tb.set_idle_generator(idle_inserter)
|
||||
tb.set_backpressure_generator(backpressure_inserter)
|
||||
|
||||
test_frames = []
|
||||
|
||||
packed_data = bytearray()
|
||||
|
||||
for test_data in [payload_data(x) for x in payload_lengths()]:
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
test_frame.tid = cur_id
|
||||
test_frame.tdest = cur_id
|
||||
test_frame.tuser = 0
|
||||
|
||||
# encode frame
|
||||
test_frame_enc = bytearray()
|
||||
|
||||
for offset in range(0, len(test_data), max_block_size):
|
||||
block = test_data[offset:offset+max_block_size]
|
||||
block_enc = bytearray()
|
||||
|
||||
meta = test_frame.tid << meta_id_offset
|
||||
meta |= (test_frame.tdest) << meta_dest_offset
|
||||
meta |= (test_frame.tuser) << meta_user_offset
|
||||
|
||||
# pack header
|
||||
hdr = 0x1
|
||||
if offset+len(block) >= len(test_data):
|
||||
# last block
|
||||
hdr |= 0x2
|
||||
hdr |= (len(block)-1) << 4
|
||||
hdr |= (bin(hdr & 0x0003).count("1") & 1) << 2
|
||||
hdr |= (bin(hdr & 0xfff0).count("1") & 1) << 3
|
||||
hdr |= meta << 16
|
||||
|
||||
# pack data
|
||||
block_enc.extend(hdr.to_bytes(hdr_size, 'little'))
|
||||
block_enc.extend(block)
|
||||
|
||||
# zero pad to segment size
|
||||
if len(block_enc) % seg_byte_lanes:
|
||||
block_enc.extend(b'\x00'*(seg_byte_lanes - (len(block_enc) % seg_byte_lanes)))
|
||||
|
||||
test_frame_enc.extend(block_enc)
|
||||
|
||||
if pack:
|
||||
packed_data.extend(test_frame_enc)
|
||||
else:
|
||||
await tb.source.write(test_frame_enc)
|
||||
await tb.source_ctrl.write(test_frame_enc)
|
||||
|
||||
test_frames.append(test_frame)
|
||||
|
||||
cur_id = (cur_id + 1) % id_count
|
||||
|
||||
if pack:
|
||||
await tb.source.write(packed_data)
|
||||
await tb.source_ctrl.write(packed_data)
|
||||
|
||||
for test_frame in test_frames:
|
||||
rx_frame = await tb.sink.recv()
|
||||
|
||||
assert rx_frame.tdata == test_frame.tdata
|
||||
assert rx_frame.tid == test_frame.tid
|
||||
assert rx_frame.tdest == test_frame.tdest
|
||||
assert not rx_frame.tuser
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
def cycle_pause():
|
||||
return itertools.cycle([1, 1, 1, 0])
|
||||
|
||||
|
||||
def size_list():
|
||||
data_width = len(cocotb.top.m_axis_tdata)
|
||||
byte_width = data_width // 8
|
||||
return list(range(1, byte_width*4+1))+list(range(byte_width, 2**14, byte_width))+[1]*64
|
||||
|
||||
|
||||
def incrementing_payload(length):
|
||||
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||
|
||||
|
||||
if cocotb.SIM_NAME:
|
||||
|
||||
factory = TestFactory(run_test)
|
||||
factory.add_option("payload_lengths", [size_list])
|
||||
factory.add_option("payload_data", [incrementing_payload])
|
||||
factory.add_option("pack", [False, True])
|
||||
factory.add_option("idle_inserter", [None, cycle_pause])
|
||||
factory.add_option("backpressure_inserter", [None, cycle_pause])
|
||||
factory.generate_tests()
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("axis_data_width", "seg_width", "seg_cnt"), [
|
||||
# (32, 32, 2),
|
||||
# (64, 256, 4),
|
||||
(512, 256, 4),
|
||||
])
|
||||
def test_axi_vfifo_dec(request, axis_data_width, seg_width, seg_cnt):
|
||||
dut = "axi_vfifo_dec"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.v"),
|
||||
]
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['SEG_WIDTH'] = seg_width
|
||||
parameters['SEG_CNT'] = seg_cnt
|
||||
parameters['AXIS_DATA_WIDTH'] = axis_data_width
|
||||
parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8)
|
||||
parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8
|
||||
parameters['AXIS_LAST_ENABLE'] = 1
|
||||
parameters['AXIS_ID_ENABLE'] = 1
|
||||
parameters['AXIS_ID_WIDTH'] = 8
|
||||
parameters['AXIS_DEST_ENABLE'] = 1
|
||||
parameters['AXIS_DEST_WIDTH'] = 8
|
||||
parameters['AXIS_USER_ENABLE'] = 1
|
||||
parameters['AXIS_USER_WIDTH'] = 1
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
79
tb/axi_vfifo_enc/Makefile
Normal file
79
tb/axi_vfifo_enc/Makefile
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= icarus
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
DUT = axi_vfifo_enc
|
||||
TOPLEVEL = $(DUT)
|
||||
MODULE = test_$(DUT)
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT).v
|
||||
|
||||
# module parameters
|
||||
export PARAM_SEG_WIDTH := 256
|
||||
export PARAM_SEG_CNT := 4
|
||||
export PARAM_AXIS_DATA_WIDTH := $(shell expr $(PARAM_SEG_WIDTH) \* $(PARAM_SEG_CNT) / 2)
|
||||
export PARAM_AXIS_KEEP_ENABLE := $(shell expr $(PARAM_AXIS_DATA_WIDTH) \> 8 )
|
||||
export PARAM_AXIS_KEEP_WIDTH := $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 )
|
||||
export PARAM_AXIS_LAST_ENABLE := 1
|
||||
export PARAM_AXIS_ID_ENABLE := 1
|
||||
export PARAM_AXIS_ID_WIDTH := 8
|
||||
export PARAM_AXIS_DEST_ENABLE := 1
|
||||
export PARAM_AXIS_DEST_WIDTH := 8
|
||||
export PARAM_AXIS_USER_ENABLE := 1
|
||||
export PARAM_AXIS_USER_WIDTH := 1
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
COMPILE_ARGS += -s iverilog_dump
|
||||
endif
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||
echo 'end' >> $@
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
426
tb/axi_vfifo_enc/test_axi_vfifo_enc.py
Normal file
426
tb/axi_vfifo_enc/test_axi_vfifo_enc.py
Normal file
@ -0,0 +1,426 @@
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
|
||||
import cocotb_test.simulator
|
||||
import pytest
|
||||
|
||||
import cocotb
|
||||
|
||||
from cocotb.queue import Queue
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Event
|
||||
from cocotb.regression import TestFactory
|
||||
from cocotb_bus.bus import Bus
|
||||
|
||||
from cocotbext.axi import AxiStreamBus, AxiStreamFrame, AxiStreamSource
|
||||
|
||||
|
||||
class BaseBus(Bus):
|
||||
|
||||
_signals = ["data"]
|
||||
_optional_signals = []
|
||||
|
||||
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
return cls(entity, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
return cls(entity, prefix, **kwargs)
|
||||
|
||||
|
||||
class DataBus(BaseBus):
|
||||
_signals = ["data", "valid", "ready"]
|
||||
|
||||
|
||||
class DataSink:
|
||||
|
||||
def __init__(self, bus, clock, reset=None, watermark=None, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.watermark = watermark
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.enqueue_event = Event()
|
||||
|
||||
self.watermark_level = 0
|
||||
|
||||
self.width = len(self.bus.data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.seg_count = len(self.bus.valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
|
||||
# queue per segment
|
||||
self.queue = [Queue() for x in range(self.seg_count)]
|
||||
|
||||
self.read_queue = bytearray()
|
||||
|
||||
self.bus.data.setimmediatevalue(0)
|
||||
self.bus.valid.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
def empty(self):
|
||||
for queue in self.queue:
|
||||
if not queue.empty():
|
||||
return False
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
for queue in self.queue:
|
||||
while not queue.empty():
|
||||
_ = queue.get_nowait()
|
||||
self.read_queue.clear()
|
||||
|
||||
def _read_queues(self):
|
||||
while True:
|
||||
for queue in self.queue:
|
||||
if queue.empty():
|
||||
return
|
||||
for queue in self.queue:
|
||||
self.read_queue.extend(queue.get_nowait())
|
||||
|
||||
async def read(self, count=-1):
|
||||
self._read_queues()
|
||||
while not self.read_queue:
|
||||
self.enqueue_event.clear()
|
||||
await self.enqueue_event.wait()
|
||||
self._read_queues()
|
||||
return self.read_nowait(count)
|
||||
|
||||
def read_nowait(self, count=-1):
|
||||
self._read_queues()
|
||||
if count < 0:
|
||||
count = len(self.read_queue)
|
||||
data = self.read_queue[:count]
|
||||
del self.read_queue[:count]
|
||||
return data
|
||||
|
||||
async def _run(self):
|
||||
data_sample = 0
|
||||
valid_sample = 0
|
||||
ready = 0
|
||||
watermark = 0
|
||||
|
||||
has_ready = self.bus.ready is not None
|
||||
has_watermark = self.watermark is not None
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
valid_sample = self.bus.valid.value.integer
|
||||
|
||||
if valid_sample:
|
||||
data_sample = self.bus.data.value.integer
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
if has_ready:
|
||||
self.bus.ready.setimmediatevalue(0)
|
||||
ready = 0
|
||||
continue
|
||||
|
||||
# process segments
|
||||
watermark = 0
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
if (ready & seg_mask or not has_ready) and (valid_sample & seg_mask):
|
||||
data = (data_sample >> self.seg_data_width*seg) & self.seg_data_mask
|
||||
|
||||
data = data.to_bytes(self.seg_byte_lanes, 'little')
|
||||
|
||||
self.queue[seg].put_nowait(data)
|
||||
self.enqueue_event.set()
|
||||
|
||||
self.log.info("RX seg: %d data: %s", seg, data)
|
||||
|
||||
if has_watermark and self.watermark_level > 0 and self.queue[seg].qsize() > self.watermark_level:
|
||||
watermark = 1
|
||||
|
||||
ready = 2**self.seg_count-1
|
||||
|
||||
if self.pause:
|
||||
ready = 0
|
||||
watermark = 1
|
||||
|
||||
if has_ready:
|
||||
self.bus.ready.value = ready
|
||||
if has_watermark:
|
||||
self.watermark.value = watermark
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class TB(object):
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
|
||||
# streaming data in
|
||||
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.clk, dut.rst)
|
||||
|
||||
# streaming data out
|
||||
self.sink = DataSink(DataBus.from_prefix(dut, "output"), dut.clk, dut.rst, dut.fifo_watermark_in)
|
||||
|
||||
dut.fifo_rst_in.value = 0
|
||||
|
||||
def set_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.source.set_pause_generator(generator())
|
||||
|
||||
def set_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.sink.set_pause_generator(generator())
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_fifo(self):
|
||||
self.dut.fifo_rst_in.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.fifo_rst_in.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.fifo_rst_in.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
|
||||
async def run_test(dut, payload_lengths=None, payload_data=None, space=False, idle_inserter=None, backpressure_inserter=None):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
id_width = len(tb.source.bus.tid)
|
||||
dest_width = len(tb.source.bus.tdest)
|
||||
user_width = len(tb.source.bus.tuser)
|
||||
|
||||
seg_cnt = tb.sink.seg_count
|
||||
seg_byte_lanes = tb.sink.seg_byte_lanes
|
||||
|
||||
meta_id_offset = 0
|
||||
meta_dest_offset = meta_id_offset + id_width
|
||||
meta_user_offset = meta_dest_offset + dest_width
|
||||
meta_width = meta_user_offset + user_width
|
||||
hdr_size = (16 + meta_width + 7) // 8
|
||||
|
||||
id_count = 2**id_width
|
||||
|
||||
cur_id = 1
|
||||
|
||||
await tb.reset()
|
||||
|
||||
tb.set_idle_generator(idle_inserter)
|
||||
tb.set_backpressure_generator(backpressure_inserter)
|
||||
|
||||
test_frames = []
|
||||
|
||||
for test_data in [payload_data(x) for x in payload_lengths()]:
|
||||
test_frame = AxiStreamFrame(test_data)
|
||||
test_frame.tid = cur_id
|
||||
test_frame.tdest = cur_id
|
||||
|
||||
await tb.source.send(test_frame)
|
||||
|
||||
test_frames.append(test_frame)
|
||||
|
||||
cur_id = (cur_id + 1) % id_count
|
||||
|
||||
if space:
|
||||
for k in range(1000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for test_frame in test_frames:
|
||||
rx_frame = AxiStreamFrame()
|
||||
while True:
|
||||
# read block
|
||||
block = await tb.sink.read(seg_byte_lanes)
|
||||
# print(block)
|
||||
|
||||
# extract header
|
||||
hdr = int.from_bytes(block[0:hdr_size], 'little')
|
||||
# print(hex(hdr))
|
||||
|
||||
# check parity bits
|
||||
assert bool(hdr & 0x4) == bool(bin(hdr & 0x0003).count("1") & 1)
|
||||
assert bool(hdr & 0x8) == bool(bin(hdr & 0xfff0).count("1") & 1)
|
||||
|
||||
if not hdr & 1:
|
||||
# null block, skip
|
||||
continue
|
||||
|
||||
length = ((hdr >> 4) & 0xfff)+1
|
||||
meta = hdr >> 16
|
||||
|
||||
rx_frame.tid = (meta >> meta_id_offset) & (2**id_width-1)
|
||||
rx_frame.tdest = (meta >> meta_dest_offset) & (2**dest_width-1)
|
||||
rx_frame.tuser = (meta >> meta_user_offset) & (2**user_width-1)
|
||||
|
||||
data = block[hdr_size:]
|
||||
|
||||
while len(data) < length:
|
||||
block = await tb.sink.read(seg_byte_lanes)
|
||||
data.extend(block)
|
||||
|
||||
if len(data) >= length:
|
||||
rx_frame.tdata.extend(data[0:length])
|
||||
|
||||
if hdr & 0x2:
|
||||
break
|
||||
|
||||
print(rx_frame)
|
||||
|
||||
assert rx_frame == test_frame
|
||||
|
||||
# assert tb.sink.empty()
|
||||
|
||||
for k in range(1000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
def cycle_pause():
|
||||
return itertools.cycle([1, 1, 1, 0])
|
||||
|
||||
|
||||
def size_list():
|
||||
data_width = len(cocotb.top.s_axis_tdata)
|
||||
byte_width = data_width // 8
|
||||
return list(range(1, byte_width*4+1))+list(range(byte_width, 2**14, byte_width))+[1]*64
|
||||
|
||||
|
||||
def incrementing_payload(length):
|
||||
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||
|
||||
|
||||
if cocotb.SIM_NAME:
|
||||
|
||||
factory = TestFactory(run_test)
|
||||
factory.add_option("payload_lengths", [size_list])
|
||||
factory.add_option("payload_data", [incrementing_payload])
|
||||
factory.add_option("space", [False, True])
|
||||
factory.add_option("idle_inserter", [None, cycle_pause])
|
||||
factory.add_option("backpressure_inserter", [None, cycle_pause])
|
||||
factory.generate_tests()
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("axis_data_width", "seg_width", "seg_cnt"), [
|
||||
# (32, 32, 2),
|
||||
# (64, 256, 4),
|
||||
(512, 256, 4),
|
||||
])
|
||||
def test_axi_vfifo_enc(request, axis_data_width, seg_width, seg_cnt):
|
||||
dut = "axi_vfifo_enc"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.v"),
|
||||
]
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['SEG_WIDTH'] = seg_width
|
||||
parameters['SEG_CNT'] = seg_cnt
|
||||
parameters['AXIS_DATA_WIDTH'] = axis_data_width
|
||||
parameters['AXIS_KEEP_ENABLE'] = int(parameters['AXIS_DATA_WIDTH'] > 8)
|
||||
parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8
|
||||
parameters['AXIS_LAST_ENABLE'] = 1
|
||||
parameters['AXIS_ID_ENABLE'] = 1
|
||||
parameters['AXIS_ID_WIDTH'] = 8
|
||||
parameters['AXIS_DEST_ENABLE'] = 1
|
||||
parameters['AXIS_DEST_WIDTH'] = 8
|
||||
parameters['AXIS_USER_ENABLE'] = 1
|
||||
parameters['AXIS_USER_WIDTH'] = 1
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
83
tb/axi_vfifo_raw/Makefile
Normal file
83
tb/axi_vfifo_raw/Makefile
Normal file
@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2023 Alex Forencich
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
TOPLEVEL_LANG = verilog
|
||||
|
||||
SIM ?= icarus
|
||||
WAVES ?= 0
|
||||
|
||||
COCOTB_HDL_TIMEUNIT = 1ns
|
||||
COCOTB_HDL_TIMEPRECISION = 1ps
|
||||
|
||||
DUT = axi_vfifo_raw
|
||||
TOPLEVEL = $(DUT)
|
||||
MODULE = test_$(DUT)
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT).v
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT)_wr.v
|
||||
VERILOG_SOURCES += ../../rtl/$(DUT)_rd.v
|
||||
|
||||
# module parameters
|
||||
export PARAM_SEG_WIDTH := 32
|
||||
export PARAM_SEG_CNT := 1
|
||||
export PARAM_AXI_DATA_WIDTH := $(shell expr $(PARAM_SEG_WIDTH) \* $(PARAM_SEG_CNT) )
|
||||
export PARAM_AXI_ADDR_WIDTH := 16
|
||||
export PARAM_AXI_STRB_WIDTH := $(shell expr $(PARAM_AXI_DATA_WIDTH) / 8 )
|
||||
export PARAM_AXI_ID_WIDTH := 8
|
||||
export PARAM_AXI_MAX_BURST_LEN := 16
|
||||
export PARAM_LEN_WIDTH := $(PARAM_AXI_ADDR_WIDTH)
|
||||
export PARAM_WRITE_FIFO_DEPTH := 64
|
||||
export PARAM_WRITE_MAX_BURST_LEN := $(shell expr $(PARAM_WRITE_FIFO_DEPTH) / 4 )
|
||||
export PARAM_READ_FIFO_DEPTH := 128
|
||||
export PARAM_READ_MAX_BURST_LEN := $(PARAM_WRITE_MAX_BURST_LEN)
|
||||
export PARAM_WATERMARK_LEVEL := $(shell expr $(PARAM_WRITE_FIFO_DEPTH) / 2 )
|
||||
export PARAM_CTRL_OUT_EN := 0
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
COMPILE_ARGS += -s iverilog_dump
|
||||
endif
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
||||
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||
echo 'end' >> $@
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
804
tb/axi_vfifo_raw/test_axi_vfifo_raw.py
Normal file
804
tb/axi_vfifo_raw/test_axi_vfifo_raw.py
Normal file
@ -0,0 +1,804 @@
|
||||
"""
|
||||
|
||||
Copyright (c) 2023 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
|
||||
import cocotb_test.simulator
|
||||
import pytest
|
||||
|
||||
import cocotb
|
||||
|
||||
from cocotb.queue import Queue
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Event
|
||||
from cocotb.regression import TestFactory
|
||||
from cocotb_bus.bus import Bus
|
||||
|
||||
from cocotbext.axi import AxiBus, AxiRam
|
||||
|
||||
|
||||
class BaseBus(Bus):
|
||||
|
||||
_signals = ["data"]
|
||||
_optional_signals = []
|
||||
|
||||
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
return cls(entity, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
return cls(entity, prefix, **kwargs)
|
||||
|
||||
|
||||
class DataBus(BaseBus):
|
||||
_signals = ["data", "valid", "ready"]
|
||||
|
||||
|
||||
class DataSource:
|
||||
|
||||
def __init__(self, bus, clock, reset=None, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.width = len(self.bus.data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.seg_count = len(self.bus.valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
|
||||
# queue per segment
|
||||
self.queue = [Queue() for x in range(self.seg_count)]
|
||||
|
||||
self.bus.data.setimmediatevalue(0)
|
||||
self.bus.valid.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
def empty(self):
|
||||
for queue in self.queue:
|
||||
if not queue.empty():
|
||||
return False
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
for queue in self.queue:
|
||||
while not queue.empty():
|
||||
_ = queue.get_nowait()
|
||||
|
||||
async def write(self, data):
|
||||
self.write_nowait(data)
|
||||
|
||||
def write_nowait(self, data):
|
||||
data = bytearray(data)
|
||||
|
||||
# pad to interface width
|
||||
if len(data) % self.byte_lanes:
|
||||
data.extend(b'\x00'*(self.byte_lanes - (len(data) % self.byte_lanes)))
|
||||
|
||||
# stripe across segment queues
|
||||
index = 0
|
||||
for offset in range(0, len(data), self.seg_byte_lanes):
|
||||
self.queue[index].put_nowait(data[offset:offset+self.seg_byte_lanes])
|
||||
index = (index + 1) % self.seg_count
|
||||
|
||||
async def _run(self):
|
||||
data = 0
|
||||
valid = 0
|
||||
ready_sample = 0
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
ready_sample = self.bus.ready.value.integer
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
self.bus.valid.setimmediatevalue(0)
|
||||
valid = 0
|
||||
self.clear()
|
||||
continue
|
||||
|
||||
# process segments
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
if ((ready_sample & seg_mask) or not (valid & seg_mask)):
|
||||
if not self.queue[seg].empty() and not self.pause:
|
||||
d = self.queue[seg].get_nowait()
|
||||
data &= ~(self.seg_data_mask << self.seg_data_width*seg)
|
||||
data |= int.from_bytes(d, 'little') << self.seg_data_width*seg
|
||||
valid |= seg_mask
|
||||
|
||||
self.log.info("TX seg: %d data: %s", seg, d)
|
||||
else:
|
||||
valid = valid & ~seg_mask
|
||||
|
||||
self.bus.data.value = data
|
||||
self.bus.valid.value = valid
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class DataSink:
|
||||
|
||||
def __init__(self, bus, clock, reset=None, watermark=None, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.watermark = watermark
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.enqueue_event = Event()
|
||||
|
||||
self.watermark_level = 0
|
||||
|
||||
self.width = len(self.bus.data)
|
||||
self.byte_size = 8
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.seg_count = len(self.bus.valid)
|
||||
self.seg_data_width = self.width // self.seg_count
|
||||
self.seg_byte_lanes = self.seg_data_width // self.byte_size
|
||||
|
||||
self.seg_data_mask = 2**self.seg_data_width-1
|
||||
|
||||
# queue per segment
|
||||
self.queue = [Queue() for x in range(self.seg_count)]
|
||||
|
||||
self.read_queue = bytearray()
|
||||
|
||||
self.bus.data.setimmediatevalue(0)
|
||||
self.bus.valid.setimmediatevalue(0)
|
||||
|
||||
cocotb.start_soon(self._run())
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
|
||||
self._pause_generator = generator
|
||||
|
||||
if self._pause_generator is not None:
|
||||
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||
|
||||
def clear_pause_generator(self):
|
||||
self.set_pause_generator(None)
|
||||
|
||||
def empty(self):
|
||||
for queue in self.queue:
|
||||
if not queue.empty():
|
||||
return False
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
for queue in self.queue:
|
||||
while not queue.empty():
|
||||
_ = queue.get_nowait()
|
||||
self.read_queue.clear()
|
||||
|
||||
def _read_queues(self):
|
||||
while True:
|
||||
for queue in self.queue:
|
||||
if queue.empty():
|
||||
return
|
||||
for queue in self.queue:
|
||||
self.read_queue.extend(queue.get_nowait())
|
||||
|
||||
async def read(self, count=-1):
|
||||
self._read_queues()
|
||||
while not self.read_queue:
|
||||
self.enqueue_event.clear()
|
||||
await self.enqueue_event.wait()
|
||||
self._read_queues()
|
||||
return self.read_nowait(count)
|
||||
|
||||
def read_nowait(self, count=-1):
|
||||
self._read_queues()
|
||||
if count < 0:
|
||||
count = len(self.read_queue)
|
||||
data = self.read_queue[:count]
|
||||
del self.read_queue[:count]
|
||||
return data
|
||||
|
||||
async def _run(self):
|
||||
data_sample = 0
|
||||
valid_sample = 0
|
||||
ready = 0
|
||||
watermark = 0
|
||||
|
||||
has_ready = self.bus.ready is not None
|
||||
has_watermark = self.watermark is not None
|
||||
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
valid_sample = self.bus.valid.value.integer
|
||||
|
||||
if valid_sample:
|
||||
data_sample = self.bus.data.value.integer
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
if has_ready:
|
||||
self.bus.ready.setimmediatevalue(0)
|
||||
ready = 0
|
||||
continue
|
||||
|
||||
# process segments
|
||||
watermark = 0
|
||||
for seg in range(self.seg_count):
|
||||
seg_mask = 1 << seg
|
||||
if ready & valid_sample & seg_mask:
|
||||
data = (data_sample >> self.seg_data_width*seg) & self.seg_data_mask
|
||||
|
||||
data = data.to_bytes(self.seg_byte_lanes, 'little')
|
||||
|
||||
self.queue[seg].put_nowait(data)
|
||||
self.enqueue_event.set()
|
||||
|
||||
self.log.info("RX seg: %d data: %s", seg, data)
|
||||
|
||||
if has_watermark and self.watermark_level > 0 and self.queue[seg].qsize() > self.watermark_level:
|
||||
watermark = 1
|
||||
|
||||
ready = 2**self.seg_count-1
|
||||
|
||||
if self.pause:
|
||||
ready = 0
|
||||
watermark = 1
|
||||
|
||||
if has_ready:
|
||||
self.bus.ready.value = ready
|
||||
if has_watermark:
|
||||
self.watermark.value = watermark
|
||||
|
||||
async def _run_pause(self):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await clock_edge_event
|
||||
|
||||
|
||||
class TB(object):
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
self.log = logging.getLogger("cocotb.tb")
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
|
||||
# streaming data in
|
||||
cocotb.start_soon(Clock(dut.input_clk, 10, units="ns").start())
|
||||
self.source = DataSource(DataBus.from_prefix(dut, "input"), dut.input_clk, dut.input_rst_out)
|
||||
|
||||
# streaming data out
|
||||
cocotb.start_soon(Clock(dut.output_clk, 10, units="ns").start())
|
||||
self.sink = DataSink(DataBus.from_prefix(dut, "output"), dut.output_clk, dut.output_rst_out)
|
||||
|
||||
# AXI interface
|
||||
self.axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**16)
|
||||
|
||||
dut.rst_req_in.setimmediatevalue(0)
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(0)
|
||||
dut.cfg_enable.setimmediatevalue(0)
|
||||
dut.cfg_reset.setimmediatevalue(0)
|
||||
|
||||
def set_stream_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.source.set_pause_generator(generator())
|
||||
|
||||
def set_stream_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.sink.set_pause_generator(generator())
|
||||
|
||||
def set_axi_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
self.axi_ram.write_if.b_channel.set_pause_generator(generator())
|
||||
self.axi_ram.read_if.r_channel.set_pause_generator(generator())
|
||||
|
||||
def set_axi_backpressure_generator(self, generator=None):
|
||||
if generator:
|
||||
self.axi_ram.write_if.aw_channel.set_pause_generator(generator())
|
||||
self.axi_ram.write_if.w_channel.set_pause_generator(generator())
|
||||
self.axi_ram.read_if.ar_channel.set_pause_generator(generator())
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
self.dut.input_rst.setimmediatevalue(0)
|
||||
self.dut.output_rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
self.dut.input_rst.value = 1
|
||||
self.dut.output_rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
self.dut.input_rst.value = 0
|
||||
self.dut.output_rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_axi(self):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
async def reset_source(self):
|
||||
self.dut.input_rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.input_clk)
|
||||
self.dut.input_rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.input_clk)
|
||||
self.dut.input_rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.input_clk)
|
||||
|
||||
async def reset_sink(self):
|
||||
self.dut.output_rst.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.output_clk)
|
||||
self.dut.output_rst.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.output_clk)
|
||||
self.dut.output_rst.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.output_clk)
|
||||
|
||||
async def reset_cfg(self):
|
||||
self.dut.cfg_reset.setimmediatevalue(0)
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.cfg_reset.value = 1
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.cfg_reset.value = 0
|
||||
for k in range(10):
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
|
||||
async def run_test(dut, payload_lengths=None, payload_data=None, space=False,
|
||||
stream_idle_inserter=None, stream_backpressure_inserter=None,
|
||||
axi_idle_inserter=None, axi_backpressure_inserter=None):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
tb.set_stream_idle_generator(stream_idle_inserter)
|
||||
tb.set_stream_backpressure_generator(stream_backpressure_inserter)
|
||||
tb.set_axi_idle_generator(axi_idle_inserter)
|
||||
tb.set_axi_backpressure_generator(axi_backpressure_inserter)
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**len(dut.m_axi_awaddr)-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_frames = []
|
||||
|
||||
for test_data in [payload_data(x) for x in payload_lengths()]:
|
||||
if len(test_data) % byte_lanes:
|
||||
test_data.extend(b'\x00'*(byte_lanes - (len(test_data) % byte_lanes)))
|
||||
|
||||
test_frames.append(test_data)
|
||||
await tb.source.write(test_data)
|
||||
|
||||
if space:
|
||||
for k in range(1000):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for test_data in test_frames:
|
||||
rx_data = bytearray()
|
||||
while len(rx_data) < len(test_data):
|
||||
d = await tb.sink.read(len(test_data) - len(rx_data))
|
||||
rx_data.extend(d)
|
||||
|
||||
assert rx_data == test_data
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
async def run_test_init_sink_pause(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**len(dut.m_axi_awaddr)-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.sink.pause = True
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
|
||||
if len(test_data) % byte_lanes:
|
||||
test_data.extend(b'\x00'*(byte_lanes - (len(test_data) % byte_lanes)))
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
for k in range(256):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.sink.pause = False
|
||||
|
||||
rx_data = bytearray()
|
||||
while len(rx_data) < len(test_data):
|
||||
d = await tb.sink.read(len(test_data) - len(rx_data))
|
||||
rx_data.extend(d)
|
||||
|
||||
assert rx_data == test_data
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
async def run_test_init_sink_pause_reset(dut, reset_type=TB.reset):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**len(dut.m_axi_awaddr)-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.sink.pause = True
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
|
||||
if len(test_data) % byte_lanes:
|
||||
test_data.extend(b'\x00'*(byte_lanes - (len(test_data) % byte_lanes)))
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
for k in range(256):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await reset_type(tb)
|
||||
tb.sink.clear()
|
||||
|
||||
tb.sink.pause = False
|
||||
|
||||
for k in range(1024):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
rx_data = bytearray()
|
||||
while len(rx_data) < len(test_data):
|
||||
d = await tb.sink.read(len(test_data) - len(rx_data))
|
||||
rx_data.extend(d)
|
||||
|
||||
assert rx_data == test_data
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
async def run_test_shift_in_reset(dut, reset_type=TB.reset):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**len(dut.m_axi_awaddr)-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
|
||||
if len(test_data) % byte_lanes:
|
||||
test_data.extend(b'\x00'*(byte_lanes - (len(test_data) % byte_lanes)))
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
for k in range(256):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await reset_type(tb)
|
||||
tb.sink.clear()
|
||||
|
||||
for k in range(2048):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
rx_data = bytearray()
|
||||
while len(rx_data) < len(test_data):
|
||||
d = await tb.sink.read(len(test_data) - len(rx_data))
|
||||
rx_data.extend(d)
|
||||
|
||||
assert rx_data == test_data
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
async def run_test_shift_out_reset(dut, reset_type=TB.reset):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**len(dut.m_axi_awaddr)-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 1024*byte_lanes))
|
||||
|
||||
if len(test_data) % byte_lanes:
|
||||
test_data.extend(b'\x00'*(byte_lanes - (len(test_data) % byte_lanes)))
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
while not dut.output_valid:
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
for k in range(8):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
await reset_type(tb)
|
||||
tb.sink.clear()
|
||||
|
||||
for k in range(2048):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
rx_data = bytearray()
|
||||
while len(rx_data) < len(test_data):
|
||||
d = await tb.sink.read(len(test_data) - len(rx_data))
|
||||
rx_data.extend(d)
|
||||
|
||||
assert rx_data == test_data
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
async def run_test_overflow(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_lanes = tb.source.byte_lanes
|
||||
|
||||
await tb.reset()
|
||||
|
||||
dut.cfg_fifo_base_addr.setimmediatevalue(0)
|
||||
dut.cfg_fifo_size_mask.setimmediatevalue(2**len(dut.m_axi_awaddr)-1)
|
||||
dut.cfg_enable.setimmediatevalue(1)
|
||||
|
||||
tb.sink.pause = True
|
||||
|
||||
test_data = bytearray(itertools.islice(itertools.cycle(range(256)), 2*2**len(dut.m_axi_awaddr)))
|
||||
|
||||
if len(test_data) % byte_lanes:
|
||||
test_data.extend(b'\x00'*(byte_lanes - (len(test_data) % byte_lanes)))
|
||||
|
||||
await tb.source.write(test_data)
|
||||
|
||||
for k in range(2*2**len(dut.m_axi_awaddr)//len(dut.m_axi_wstrb)):
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
tb.sink.pause = False
|
||||
|
||||
rx_data = bytearray()
|
||||
while len(rx_data) < len(test_data):
|
||||
d = await tb.sink.read(len(test_data) - len(rx_data))
|
||||
rx_data.extend(d)
|
||||
|
||||
assert rx_data == test_data
|
||||
|
||||
assert tb.sink.empty()
|
||||
|
||||
await RisingEdge(dut.clk)
|
||||
await RisingEdge(dut.clk)
|
||||
|
||||
|
||||
def cycle_pause():
|
||||
return itertools.cycle([1, 1, 1, 0])
|
||||
|
||||
|
||||
def size_list():
|
||||
data_width = len(cocotb.top.input_data)
|
||||
byte_width = data_width // 8
|
||||
return list(range(byte_width, byte_width*64, byte_width))+[1]*64
|
||||
|
||||
|
||||
def incrementing_payload(length):
|
||||
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||
|
||||
|
||||
if cocotb.SIM_NAME:
|
||||
|
||||
factory = TestFactory(run_test)
|
||||
factory.add_option("payload_lengths", [size_list])
|
||||
factory.add_option("payload_data", [incrementing_payload])
|
||||
factory.add_option(("space",
|
||||
"stream_idle_inserter", "stream_backpressure_inserter",
|
||||
"axi_idle_inserter", "axi_backpressure_inserter"), [
|
||||
(False, None, None, None, None),
|
||||
(False, cycle_pause, None, None, None),
|
||||
(False, None, cycle_pause, None, None),
|
||||
(False, None, None, cycle_pause, None),
|
||||
(False, None, None, None, cycle_pause),
|
||||
(True, None, None, None, None),
|
||||
(True, cycle_pause, None, None, None),
|
||||
(True, None, cycle_pause, None, None),
|
||||
(True, None, None, cycle_pause, None),
|
||||
(True, None, None, None, cycle_pause),
|
||||
])
|
||||
factory.generate_tests()
|
||||
|
||||
for test in [
|
||||
run_test_init_sink_pause,
|
||||
run_test_overflow
|
||||
]:
|
||||
|
||||
factory = TestFactory(test)
|
||||
factory.generate_tests()
|
||||
|
||||
for test in [
|
||||
run_test_init_sink_pause_reset,
|
||||
run_test_shift_in_reset,
|
||||
run_test_shift_out_reset,
|
||||
]:
|
||||
|
||||
factory = TestFactory(test)
|
||||
factory.add_option("reset_type", [TB.reset, TB.reset_source,
|
||||
TB.reset_sink, TB.reset_axi, TB.reset_cfg])
|
||||
factory.generate_tests()
|
||||
|
||||
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("seg_width", "seg_cnt"), [
|
||||
(32, 1),
|
||||
(16, 2),
|
||||
])
|
||||
def test_axi_vfifo_raw(request, seg_width, seg_cnt):
|
||||
dut = "axi_vfifo_raw"
|
||||
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||
toplevel = dut
|
||||
|
||||
verilog_sources = [
|
||||
os.path.join(rtl_dir, f"{dut}.v"),
|
||||
os.path.join(rtl_dir, f"{dut}_wr.v"),
|
||||
os.path.join(rtl_dir, f"{dut}_rd.v"),
|
||||
]
|
||||
|
||||
parameters = {}
|
||||
|
||||
parameters['SEG_WIDTH'] = seg_width
|
||||
parameters['SEG_CNT'] = seg_cnt
|
||||
parameters['AXI_DATA_WIDTH'] = seg_width*seg_cnt
|
||||
parameters['AXI_ADDR_WIDTH'] = 16
|
||||
parameters['AXI_STRB_WIDTH'] = parameters['AXI_DATA_WIDTH'] // 8
|
||||
parameters['AXI_ID_WIDTH'] = 8
|
||||
parameters['AXI_MAX_BURST_LEN'] = 16
|
||||
parameters['LEN_WIDTH'] = parameters['AXI_ADDR_WIDTH']
|
||||
parameters['WRITE_FIFO_DEPTH'] = 64
|
||||
parameters['WRITE_MAX_BURST_LEN'] = parameters['WRITE_FIFO_DEPTH'] // 4
|
||||
parameters['READ_FIFO_DEPTH'] = 128
|
||||
parameters['READ_MAX_BURST_LEN'] = parameters['WRITE_MAX_BURST_LEN']
|
||||
parameters['WATERMARK_LEVEL'] = parameters['WRITE_FIFO_DEPTH'] // 2
|
||||
parameters['CTRL_OUT_EN'] = 0
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
verilog_sources=verilog_sources,
|
||||
toplevel=toplevel,
|
||||
module=module,
|
||||
parameters=parameters,
|
||||
sim_build=sim_build,
|
||||
extra_env=extra_env,
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user