//////////////////////////////////////////////////////////////////////////////// // // Filename: axivfifo.v // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: A virtual FIFO, using an AXI based memory on the back end. Data // written via the AXI stream interface is written to an external // memory once enough is available to fill a burst. It's then copied from // this external memory to a FIFO from which it can drive an outgoing // stream. // // Registers: // This core is simple--providing no control interface nor registers // whereby it may be controlled. To place it in a particular region of // SDRAM, limit the address width and fill the rest of the address with // the region you want. Note: THIS CORE DEPENDS UPON ALIGNED MEMORY // ACCESSES. Hence, it must be aligned to the memory to keep these // accesses aligned. // // Performance goals: // 100% throughput // Stay off the bus until you can drive it hard // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // //////////////////////////////////////////////////////////////////////////////// // }}} // Copyright (C) 2020-2024, Gisselquist Technology, LLC // {{{ // This file is part of the WB2AXIP project. // // The WB2AXIP project contains free software and gateware, licensed under the // Apache License, Version 2.0 (the "License"). You may not use this project, // or this file, except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // //////////////////////////////////////////////////////////////////////////////// // `default_nettype none // // `define AXI3 // }}} module axivfifo #( // {{{ parameter C_AXI_ID_WIDTH = 1, parameter C_AXI_ADDR_WIDTH = 32, parameter C_AXI_DATA_WIDTH = 32, // // This core requires that the stream data width be identical // to the bus data width. Use an upstream core if you need to // pack a smaller width into your bus's width, or a downstream // core if you need to unpack it. localparam C_AXIS_DATA_WIDTH = C_AXI_DATA_WIDTH, // // LGMAXBURST determines the size of the maximum AXI burst. // In AXI4, the maximum burst size is 256 beats the log_2() // of which is 8. In AXI3, it's a 16 beat burst of which the // log_2() is 4. Smaller numbers are also permissible here, // although not verified. I expect problems if LGMAXBURST is // ever set to zero (no bursting). An upgrade should fix that. // Lower LGMAXBURST values will decrease the latency in this // core while possibly causing throughput to be decreased // (in the rest of the system--this core can handle 100% // throughput either way.) // // Beware of the AXI requirement that bursts cannot cross // 4kB boundaries. If your bus is larger than 128 bits wide, // you'll need to lower this maximum burst size to meet that // requirement. `ifdef AXI3 parameter LGMAXBURST=4, // 16 beats max `else parameter LGMAXBURST=8, // 256 beats `endif // // LGFIFO: This is the (log-based-2) size of the internal FIFO. // Hence if LGFIFO=8, the internal FIFO will have 256 elements // (words) in it. High throughput transfers are accomplished // by first storing data into a FIFO, then once a full burst // size is available bursting that data over the bus. In // order to be able to keep receiving data while bursting it // out, the FIFO size must be at least twice the size of the // maximum burst size--that is LGFIFO must be at least one more // than LGMAXBURST. Larger sizes are possible as well. parameter LGFIFO = LGMAXBURST+1, // 512 element FIFO // // AXI uses ID's to transfer information. This core rather // ignores them. Instead, it uses a constant ID for all // transfers. The following two parameters control that ID. parameter [C_AXI_ID_WIDTH-1:0] AXI_READ_ID = 0, parameter [C_AXI_ID_WIDTH-1:0] AXI_WRITE_ID = 0, // localparam ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3 // }}} ) ( // {{{ input wire S_AXI_ACLK, input wire S_AXI_ARESETN, // // The AXI4-stream interface // {{{ // This core does not support TLAST, TKEEP, or TSTRB. If you // want to support these extra values, expand the width of // TDATA, and unpack them on the output of the FIFO. // // The incoming stream input wire S_AXIS_TVALID, output wire S_AXIS_TREADY, input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, // // The outgoing stream output wire M_AXIS_TVALID, input wire M_AXIS_TREADY, output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA, // }}} // // The AXI Master (DMA) interface // {{{ // First to write data to the (external) AXI buffer output wire M_AXI_AWVALID, input wire M_AXI_AWREADY, output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, `ifdef AXI3 output wire [3:0] M_AXI_AWLEN, `else output wire [7:0] M_AXI_AWLEN, `endif output wire [2:0] M_AXI_AWSIZE, output wire [1:0] M_AXI_AWBURST, `ifdef AXI3 output wire [1:0] M_AXI_AWLOCK, `else output wire M_AXI_AWLOCK, `endif output wire [3:0] M_AXI_AWCACHE, output wire [2:0] M_AXI_AWPROT, output wire [3:0] M_AXI_AWQOS, // // output wire M_AXI_WVALID, input wire M_AXI_WREADY, `ifdef AXI3 output wire [C_AXI_ID_WIDTH-1:0] M_AXI_WID, `endif output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, output wire M_AXI_WLAST, // // input wire M_AXI_BVALID, output wire M_AXI_BREADY, input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, input wire [1:0] M_AXI_BRESP, // // Then the read interface to read the data back output wire M_AXI_ARVALID, input wire M_AXI_ARREADY, output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, `ifdef AXI3 output wire [3:0] M_AXI_ARLEN, `else output wire [7:0] M_AXI_ARLEN, `endif output wire [2:0] M_AXI_ARSIZE, output wire [1:0] M_AXI_ARBURST, `ifdef AXI3 output wire [1:0] M_AXI_ARLOCK, `else output wire M_AXI_ARLOCK, `endif output wire [3:0] M_AXI_ARCACHE, output wire [2:0] M_AXI_ARPROT, output wire [3:0] M_AXI_ARQOS, // input wire M_AXI_RVALID, output wire M_AXI_RREADY, input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, input wire M_AXI_RLAST, input wire [1:0] M_AXI_RRESP, // }}} // // {{{ // Request a soft-reset: reset the FIFO without resetting th bus input wire i_reset, // o_err is a hard error. If ever true, the core will come // to a hard stop. output reg o_err, // o_overflow is an indication of data changing before it is // accepted. output reg o_overflow, // o_mm2s_full is a reference to the last FIFO in our // processing pipeline. It's true if a burst of (1<= (1< 8) ? (C_AXI_ADDR_WIDTH+1) : 9; reg f_past_valid; initial f_past_valid = 0; always @(posedge S_AXI_ACLK) f_past_valid <= 1; // // Wires necessary for interacting with the formal property file // {{{ // ... // }}} // Other registers to keep simplify keeping track of our progress // {{{ reg [C_AXI_ADDR_WIDTH-1:0] faxi_rd_cknext, faxi_wr_cknext, f_read_beat_addr, f_write_beat_addr, faxi_read_beat_addr; reg [C_AXI_ADDR_WIDTH-1:0] f_read_ckbeat_addr; reg [FAXI_DEPTH-1:0] f_rd_bursts_after_check; reg [C_AXI_ADDR_WIDTH:0] f_vfill; reg [C_AXI_ADDR_WIDTH:0] f_space_available, f_data_available; reg [C_AXI_ADDR_WIDTH-1:0] f_read_checksum; reg [C_AXI_ADDR_WIDTH:0] mem_space_available, mem_data_available; // }}} //////////////////////////////////////////////////////////////////////// // // The main AXI data interface bus property check // //////////////////////////////////////////////////////////////////////// // // // The number of beats in the maximum burst size is // automatically determined from LGMAXBURST, and so its // forced to be a power of two this way. localparam MAXBURST=(1<= writes_pending); // }}} // Bus reads aren't allowed to overflow the return FIFO // {{{ always @(posedge S_AXI_ACLK) if (!soft_reset && M_AXI_RVALID) assert(!ofifo_full); // }}} //////////////////////////////////////////////////////////////////////// // // Write checks // // Make sure the skid buffer only reads when either there's nothing // held in the buffer, or the write channel has accepted data. Indeed, // the write channel should never be active unless the skid buffer is // full. // {{{ always @(*) if (!soft_reset && !skd_valid) assert(!M_AXI_WVALID); always @(*) if (M_AXI_WVALID && M_AXI_WREADY) begin assert(read_from_fifo); end else if (!skd_valid && !ififo_empty) assert(read_from_fifo); // }}} //////////////////////////////////////////////////////////////////////// // // Read checks // {{{ // No read checks in addition to the ones above // }}} //////////////////////////////////////// // // Errors or resets -- do we properly end gracefully // // {{{ // Set o_err on any bus error. Only clear it on reset // {{{ always @(posedge S_AXI_ACLK) if (f_past_valid && $past(S_AXI_ARESETN)) begin if ($past(M_AXI_BVALID && M_AXI_BRESP[1])) assert(o_err); if ($past(M_AXI_RVALID && M_AXI_RRESP[1])) assert(o_err); if ($past(!M_AXI_BVALID || !M_AXI_BRESP[1]) && $past(!M_AXI_RVALID || !M_AXI_RRESP[1])) assert(!$rose(o_err)); end // Only release soft_reset once the bus is idle // {{{ always @(posedge S_AXI_ACLK) if (f_past_valid && $past(S_AXI_ARESETN) && $fell(soft_reset)) begin // // ... // assert(!M_AXI_AWVALID); assert(!M_AXI_WVALID); assert(!M_AXI_ARVALID); end // }}} // }}} // }}} //////////////////////////////////////////////////////////////////////// // // FIFO checks // {{{ // Check the global fill/emtpy values // {{{ always @(*) assert(vfifo_full == (vfifo_fill == (1<<(C_AXI_ADDR_WIDTH-ADDRLSB)))); always @(*) assert(vfifo_fill <= (1<<(C_AXI_ADDR_WIDTH-ADDRLSB))); always @(*) assert(vfifo_empty == (vfifo_fill == 0)); // }}} // An equation for vfill based upon our property checker's counters // {{{ always @(*) begin f_vfill = M_AXI_AWADDR - M_AXI_ARADDR; f_vfill[C_AXI_ADDR_WIDTH] = 0; if (!M_AXI_AWVALID) f_vfill = f_vfill - (writes_pending << ADDRLSB); // ... if (skd_valid) f_vfill = f_vfill + (1<> ADDRLSB)); assert(f_vfill <= (1<<(C_AXI_ADDR_WIDTH))); // Before the equation check, double check that we // don't overflow any of our arithmetic. Then check our // virtual FIFO's fill counter against the various internal // FIFO fills and read counters. // These are just inequality checks, however, so we'll still // need a full equation check elsewhere // // ... // end // Make certain we aren't overflowing in our subtraction above always @(*) if (M_AXI_ARVALID && !soft_reset) assert(M_AXI_ARADDR != M_AXI_AWADDR); // }}} // Check mem_space_available // {{{ always @(*) begin f_space_available = (M_AXI_AWADDR - M_AXI_ARADDR); f_space_available[C_AXI_ADDR_WIDTH] = 0; f_space_available = (1< 0); assume(M_AXIS_TREADY); assume(M_AXI_WREADY); assume(M_AXI_ARREADY); end initial cvr_write_bursts = 0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN || soft_reset || o_err || o_overflow) cvr_write_bursts <= 0; else if (M_AXI_BVALID && !cvr_write_bursts[2]) cvr_write_bursts <= cvr_write_bursts + 1; initial cvr_read_bursts = 0; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN || soft_reset || o_err || o_overflow) cvr_read_bursts <= 0; else if (M_AXI_RVALID && M_AXI_RLAST && !cvr_read_bursts[2]) cvr_read_bursts <= cvr_read_bursts + 1; // // ... // always @(posedge S_AXI_ACLK) if (S_AXI_ARESETN && !soft_reset) cover(cvr_read_bursts > 1 && cvr_write_bursts > 1); always @(posedge S_AXI_ACLK) if (S_AXI_ARESETN && !soft_reset) cover(cvr_read_bursts > 1 && cvr_write_bursts > 1 && cvr_high_speed); // }}} //////////////////////////////////////////////////////////////////////// // // Careless assumptions (i.e. constraints) // {{{ //////////////////////////////////////////////////////////////////////// // // // No careless assumptions. Indeed, no assumptions at all beyond // what's in the bus property file, and some optional cover assumptions. // }}} // }}} `endif endmodule