blob: 437c7b485e1de85b01414f2ba63bd3a1085decf2 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Filename: axi3reorder.v
// {{{
// Project: WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose: One of the challenges of working with AXI3 are the (potentially)
// out of order writes. This modules is designed to re-order write
// beats back into the proper order given by the AW* channel. Once done,
// write beats will always be contiguous--only changing ID's after WLAST.
// Indeed, once done, the WID field can be properly removed and ignored.
// it's left within for forms sake, but isn't required.
//
// Algorithms:
// The design currently contains one of three reordering algorithms.
//
// MTHD_NONE Is a basic pass-through. This would be appropriate
// for AXI3 streams that are known to be in order already.
//
// MTHD_SHIFT_REGISTER Uses a shift-register based "FIFO". (Not block
// RAM) All write data goes into this FIFO. Items
// are read from this FIFO out of order as required for
// the given ID. When any item is read from the middle,
// the shift register back around the item read.
//
// This is a compromise implementation that uses less
// logic than the PER/ID FIFOS, while still allowing some
// amount of reordering to take place.
//
// MTHD_PERID_FIFOS Uses an explicit FIFO for each ID. Data come
// into the core and go directly into the FIFO's.
// Data are read out of the FIFOs in write-address order
// until WLAST, where the write address may (potentially)
// change.
//
// For a small number of IDs, this solution should be
// *complete*, and lacking nothing. (Unless you fill the
// write data FIFO's while needing data from another ID ..)
//
// Implementation notes:
// This module is intended to be used side by side with other AW* channel
// processing, and following an (external) AW* skidbuffer. For this
// reason, it doesn't use an AW skid buffer, nor does it output AW*
// information. External AWREADY handling should therefore be:
//
// master_awskid_read = reorder_awready && any_other_awready;
//
// going into a skid buffer, with the proper AWREADY being the upstream
// skidbuffer's AWREADY output.
//
// Expected performance:
// One beat per clock, all methods.
//
// Status:
// This module passes both a Verilator lint check and a 15-20 step formal
// bounded model check.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
// }}}
// Copyright (C) 2020, 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
// }}}
module axi3reorder #(
// {{{
parameter C_AXI_ID_WIDTH = 4,
parameter C_AXI_DATA_WIDTH = 32,
parameter LGAWFIFO = 3, // # packets we can handle
parameter LGWFIFO = 4, // Full size of an AXI burst
parameter OPT_METHOD = 0,
parameter [0:0] OPT_LOWPOWER = 0,
parameter [0:0] OPT_LOW_LATENCY = 0,
localparam MTHD_NONE = 0,
localparam MTHD_SHIFT_REGISTER = 1,
localparam MTHD_PERID_FIFOS = 2,
localparam IW = C_AXI_ID_WIDTH,
localparam DW = C_AXI_DATA_WIDTH,
parameter NUM_FIFOS = (1<<IW)
// }}}
) (
// {{{
input wire S_AXI_ACLK,
input wire S_AXI_ARESETN,
// AXI3 (partial) slave interface
// {{{
input wire S_AXI3_AWVALID,
output wire S_AXI3_AWREADY,
input wire [IW-1:0] S_AXI3_AWID,
// input wire [AW-1:0] S_AXI3_AWADDR,
// input wire [3:0] S_AXI3_AWLEN,
// input wire [2:0] S_AXI3_AWSIZE,
// input wire [1:0] S_AXI3_AWBURST,
// input wire [1:0] S_AXI3_AWLOCK,
// input wire [3:0] S_AXI3_AWCACHE,
// input wire [2:0] S_AXI3_AWPROT,
// input wire [3:0] S_AXI3_AWQOS,
//
input wire S_AXI3_WVALID,
output wire S_AXI3_WREADY,
input wire [IW-1:0] S_AXI3_WID,
input wire [DW-1:0] S_AXI3_WDATA,
input wire [DW/8-1:0] S_AXI3_WSTRB,
input wire S_AXI3_WLAST,
// }}}
// Reordered write data port. WID may now be discarded.
// {{{
output reg M_AXI_WVALID,
input wire M_AXI_WREADY,
output reg [IW-1:0] M_AXI_WID,
output reg [DW-1:0] M_AXI_WDATA,
output reg [DW/8-1:0] M_AXI_WSTRB,
output reg M_AXI_WLAST
// }}}
// }}}
);
// Register declarations
// {{{
wire awfifo_full, awfifo_empty;
reg read_awid_fifo;
wire [IW-1:0] awfifo_id;
wire [LGAWFIFO:0] awfifo_fill;
genvar gk;
reg read_beat_fifo;
reg cid_valid;
reg [IW-1:0] current_id;
// }}}
////////////////////////////////////////////////////////////////////////
//
// Write address ID's go to an address ID FIFO
// {{{
////////////////////////////////////////////////////////////////////////
//
generate if (OPT_METHOD == MTHD_NONE)
begin : IGNORE_AW_CHANNEL
// No write address ID's to keep track of
// {{{
// These values are irrelevant. They are placeholders, put here
// to keep the linter from complaining.
assign awfifo_id = S_AXI3_AWID;
assign S_AXI3_AWREADY = 1'b1;
assign awfifo_full = 0;
assign awfifo_fill = 0;
assign awfifo_empty = !S_AXI3_AWVALID;
assign cid_valid = S_AXI3_AWVALID;
always @(*)
cid_valid = S_AXI3_AWVALID;
always @(*)
current_id= S_AXI3_AWID;
always @(*)
read_awid_fifo = 0;
// Verilator lint_off UNUSED
wire none_aw_unused;
assign none_aw_unused = &{ 1'b0, awfifo_full, awfifo_fill,
awfifo_empty, read_awid_fifo, awfifo_id };
// }}}
end else begin : AWFIFO
////////////////////////////////////////////////////////////////
//
// Write address ID FIFO
// {{{
////////////////////////////////////////////////////////////////
//
//
sfifo #(
// {{{
.BW(IW),
.LGFLEN(LGAWFIFO),
.OPT_READ_ON_EMPTY(OPT_LOW_LATENCY)
// }}}
) awidfifo(
// {{{
.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
.i_wr(S_AXI3_AWVALID && S_AXI3_AWREADY),
.i_data(S_AXI3_AWID),
.o_full(awfifo_full),
.o_fill(awfifo_fill),
.i_rd(read_awid_fifo),
.o_data( awfifo_id ),
.o_empty(awfifo_empty)
// }}}
);
assign S_AXI3_AWREADY = !awfifo_full;
// }}}
////////////////////////////////////////////////////////////////
//
// Current ID -- what ID shall we output next?
// {{{
////////////////////////////////////////////////////////////////
//
// The current ID is given by the write address channel
initial cid_valid = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
cid_valid <= 1'b0;
else if (read_awid_fifo)
cid_valid <= !awfifo_empty;
// The current ID is given by the write address channel
always @(posedge S_AXI_ACLK)
if (read_awid_fifo)
current_id <= awfifo_id;
// }}}
end endgenerate
// }}}
////////////////////////////////////////////////////////////////////////
//
// Write data channel processing and reordering
// {{{
////////////////////////////////////////////////////////////////////////
//
//
generate if (OPT_METHOD == MTHD_NONE)
begin : COPY_SW_TO_MW // Copy S_AXI3_W* values to M_AXIW*
// {{{
// {{{
always @(*)
begin
M_AXI_WVALID = S_AXI3_WVALID;
M_AXI_WID = S_AXI3_WID;
M_AXI_WDATA = S_AXI3_WDATA;
M_AXI_WSTRB = S_AXI3_WSTRB;
M_AXI_WLAST = S_AXI3_WLAST;
read_beat_fifo = 0;
end
// }}}
assign S_AXI3_WREADY = M_AXI_WREADY;
// Keep Verilator happy
// Verilator lint_off UNUSED
wire none_unused;
assign none_unused = &{ 1'b0, S_AXI_ACLK, S_AXI_ARESETN,
current_id, cid_valid, read_beat_fifo };
// Verilator lint_on UNUSED
// }}}
end else if (OPT_METHOD == MTHD_SHIFT_REGISTER)
begin : SHIFT_REGISTER
// {{{
////////////////////////////////////////////////////////////////
//
// Local declarations
// {{{
////////////////////////////////////////////////////////////////
//
//
localparam NSREG = (1<<LGWFIFO);
reg [NSREG-1:0] sr_advance;
reg [NSREG-1:0] sr_write;
reg [NSREG-1:0] sr_valid;
reg [IW-1:0] sr_id [0:NSREG];
reg [DW-1:0] sr_data [0:NSREG];
reg [DW/8-1:0] sr_strb [0:NSREG];
reg [NSREG-1:0] sr_last;
integer ik;
// }}}
////////////////////////////////////////////////////////////////
//
// Per-register-station logic
// {{{
////////////////////////////////////////////////////////////////
//
//
for (gk = 0; gk<NSREG; gk=gk+1)
begin
// sr_advance
// {{{
// Do we copy from the next station into this one or no?
always @(*)
begin
sr_advance[gk] = 0;
if ((gk > 0)&&(sr_advance[gk-1]))
sr_advance[gk] = 1;
if ((!M_AXI_WVALID || M_AXI_WREADY)
&& cid_valid && current_id == sr_id[gk])
sr_advance[gk] = 1;
if (!sr_valid[gk])
sr_advance[gk] = 0;
end
// }}}
// sw_write
// {{{
// Do we write new data into this station?
always @(*)
begin
sr_write[gk] = S_AXI3_WVALID && S_AXI3_WREADY;
if (sr_valid[gk] && (!sr_advance[gk]
||(gk < NSREG-1 && sr_valid[gk+1])))
sr_write[gk] = 0;
if (gk > 0 && (!sr_valid[gk-1]
|| (sr_advance[gk-1] && !sr_valid[gk])))
sr_write[gk] = 0;
end
// }}}
// sr_valid
// {{{
initial sr_valid[gk] = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
sr_valid[gk] <= 0;
else if (sr_write[gk])
sr_valid[gk] <= 1;
else if (sr_advance[gk] && gk<NSREG-1)
sr_valid[gk] <= sr_valid[gk+1];
else if (sr_advance[gk])
sr_valid[gk] <= 0;
// }}}
// sr_id, sr_data, sr_strb, sr_last
// {{{
always @(posedge S_AXI_ACLK)
if (OPT_LOWPOWER && !S_AXI_ARESETN)
begin
sr_id[gk] <= 0;
sr_data[gk] <= 0;
sr_strb[gk] <= 0;
sr_last[gk] <= 0;
end else if (sr_write[gk])
begin
sr_id[gk] <= S_AXI3_WID;
sr_data[gk] <= S_AXI3_WDATA;
sr_strb[gk] <= S_AXI3_WSTRB;
sr_last[gk] <= S_AXI3_WLAST;
end else if ((gk < NSREG-1) && sr_advance[gk])
begin
sr_id[gk] <= sr_id[gk+1];
sr_data[gk] <= sr_data[gk+1];
sr_strb[gk] <= sr_strb[gk+1];
sr_last[gk] <= sr_last[gk+1];
if (OPT_LOWPOWER && gk < NSREG-1 && !sr_valid[gk+1])
begin
// If we are running in low power,
// Keep every unused register == 0
sr_id[gk] <= 0;
sr_data[gk] <= 0;
sr_strb[gk] <= 0;
sr_last[gk] <= 0;
end
end else if ((!OPT_LOWPOWER && !sr_valid[gk])
|| sr_write[gk])
begin
sr_id[gk] <= S_AXI3_WID;
sr_data[gk] <= S_AXI3_WDATA;
sr_strb[gk] <= S_AXI3_WSTRB;
sr_last[gk] <= S_AXI3_WLAST;
end
// }}}
end
// }}}
////////////////////////////////////////////////////////////////
//
// Pick a register location to output
// {{{
////////////////////////////////////////////////////////////////
//
reg known_next;
reg [LGWFIFO-1:0] sr_next;
always @(*)
begin
known_next = 0;
sr_next = -1;
for(ik=0; ik<NSREG; ik=ik+1)
if (!known_next && current_id == sr_id[ik])
begin
sr_next = ik[LGWFIFO-1:0];
known_next = sr_valid[ik];
end
if (!cid_valid)
known_next = 0;
end
assign S_AXI3_WREADY = !sr_valid[NSREG-1];
// }}}
////////////////////////////////////////////////////////////////
//
//
// {{{
////////////////////////////////////////////////////////////////
//
//
always @(*)
read_awid_fifo = (!M_AXI_WVALID || M_AXI_WREADY)
&& (!cid_valid
|| (known_next && sr_last[sr_next]));
always @(*)
read_beat_fifo = (!M_AXI_WVALID || M_AXI_WREADY)
&&(known_next);
initial M_AXI_WVALID = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
M_AXI_WVALID <= 0;
else if (!M_AXI_WVALID || M_AXI_WREADY)
M_AXI_WVALID <= known_next;
initial M_AXI_WID = 0;
initial M_AXI_WDATA = 0;
initial M_AXI_WSTRB = 0;
initial M_AXI_WLAST = 0;
always @(posedge S_AXI_ACLK)
if (OPT_LOWPOWER && !S_AXI_ARESETN)
begin
// {{{
M_AXI_WID <= 0;
M_AXI_WDATA <= 0;
M_AXI_WSTRB <= 0;
M_AXI_WLAST <= 0;
// }}}
end else if (!M_AXI_WVALID || M_AXI_WREADY)
begin
if (OPT_LOWPOWER && !known_next)
begin
// {{{
M_AXI_WID <= 0;
M_AXI_WDATA <= 0;
M_AXI_WSTRB <= 0;
M_AXI_WLAST <= 0;
// }}}
end else begin
// {{{
M_AXI_WID <= current_id;
// Verilator lint_off WIDTH
M_AXI_WDATA <= sr_data[sr_next];
M_AXI_WSTRB <= sr_strb[sr_next];
M_AXI_WLAST <= sr_last[sr_next];
// Verilator lint_on WIDTH
// }}}
end
end
// }}}
// Verilator lint_off UNUSED
wire unused_sreg;
assign unused_sreg = &{ 1'b0, read_beat_fifo };
// Verilator lint_on UNUSED
`ifdef FORMAL
always @(*)
if (S_AXI3_WVALID && S_AXI3_WREADY)
assert($onehot(sr_write));
else if (S_AXI_ARESETN)
assert(sr_write == 0);
always @(*)
assert(((sr_write << 1) & sr_valid) == 0);
reg cvr_sreg_full;
initial cvr_sreg_full = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
cvr_sreg_full <= 0;
else if (!S_AXI3_WREADY)
cvr_sreg_full <= 1;
always @(*)
cover(cvr_sreg_full && sr_valid == 0);
`endif
// }}}
end else if (OPT_METHOD == MTHD_PERID_FIFOS)
begin : MULTIPLE_FIFOS
// {{{
wire [NUM_FIFOS-1:0] wbfifo_full, wbfifo_empty, wbfifo_last;
// wire [IW-1:0] wbfifo_id [0:NUM_FIFOS-1];
wire [DW-1:0] wbfifo_data [0:NUM_FIFOS-1];
wire [DW/8-1:0] wbfifo_strb [0:NUM_FIFOS-1];
////////////////////////////////////////////////////////////////
//
// The write beat FIFO
////////////////////////////////////////////////////////////////
// {{{
//
// Every write beat goes into a separate FIFO based on its ID
//
for (gk=0; gk < NUM_FIFOS; gk=gk+1)
begin
// {{{
wire [LGWFIFO:0] wbfifo_fill;
sfifo #(
.BW(DW+(DW/8)+1),
.LGFLEN(LGWFIFO),
.OPT_READ_ON_EMPTY(OPT_LOW_LATENCY)
) write_beat_fifo (
.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
.i_wr(S_AXI3_WVALID && S_AXI3_WREADY && S_AXI3_WID == gk),
.i_data({ S_AXI3_WDATA, S_AXI3_WSTRB,
S_AXI3_WLAST }),
.o_full(wbfifo_full[gk]), .o_fill(wbfifo_fill),
.i_rd(read_beat_fifo && current_id == gk),
.o_data({ wbfifo_data[gk],
wbfifo_strb[gk], wbfifo_last[gk] }),
.o_empty(wbfifo_empty[gk])
);
// Verilator lint_off UNUSED
wire unused;
assign unused = &{ 1'b0, wbfifo_fill };
// Verilator lint_on UNUSED
// }}}
end
assign S_AXI3_WREADY = !wbfifo_full[S_AXI3_WID];
// }}}
////////////////////////////////////////////////////////////////
//
// Outgoing write data channel
// {{{
////////////////////////////////////////////////////////////////
//
//
always @(*)
read_awid_fifo = (!M_AXI_WVALID || M_AXI_WREADY)
&& (!cid_valid
|| (read_beat_fifo && wbfifo_last[current_id]));
// Write packets associated with the current ID can move forward
always @(*)
read_beat_fifo = (!M_AXI_WVALID || M_AXI_WREADY)
&&(cid_valid)&&(!wbfifo_empty[current_id]);
initial M_AXI_WVALID = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
M_AXI_WVALID <= 0;
else if (!M_AXI_WVALID || M_AXI_WREADY)
M_AXI_WVALID <= read_beat_fifo;
initial M_AXI_WID = 0;
initial M_AXI_WDATA = 0;
initial M_AXI_WSTRB = 0;
initial M_AXI_WLAST = 0;
always @(posedge S_AXI_ACLK)
if (OPT_LOWPOWER && !S_AXI_ARESETN)
begin
// {{{
M_AXI_WID <= 0;
M_AXI_WDATA <= 0;
M_AXI_WSTRB <= 0;
M_AXI_WLAST <= 0;
// }}}
end else if (!M_AXI_WVALID || M_AXI_WREADY)
begin
// {{{
M_AXI_WID <= current_id;
M_AXI_WDATA <= wbfifo_data[current_id];
M_AXI_WSTRB <= wbfifo_strb[current_id];
M_AXI_WLAST <= wbfifo_last[current_id];
if (OPT_LOWPOWER && !read_beat_fifo)
begin
// {{{
M_AXI_WID <= 0;
M_AXI_WDATA <= 0;
M_AXI_WSTRB <= 0;
M_AXI_WLAST <= 0;
// }}}
end
// }}}
end
// }}}
// }}}
end endgenerate
// }}}
// Make Verilator happy
// {{{
// Verilator lint_off UNUSED
wire unused;
assign unused = &{ 1'b0, awfifo_fill };
// Verilator lint_on UNUSED
// }}}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Formal properties
// {{{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
`ifdef FORMAL
reg f_past_valid;
reg [LGAWFIFO+LGWFIFO-1:0] f_awid_count;
(* anyconst *) reg [IW-1:0] f_const_id;
initial f_past_valid = 0;
always @(posedge S_AXI_ACLK)
f_past_valid <= 1;
always @(*)
if (!f_past_valid)
assume(!S_AXI_ARESETN);
always @(posedge S_AXI_ACLK)
if (!f_past_valid || $past(!S_AXI_ARESETN))
begin
assume(!S_AXI3_AWVALID);
assume(!S_AXI3_WVALID);
assume(!M_AXI_WVALID);
end else begin
if ($past(S_AXI3_AWVALID && !S_AXI3_AWREADY))
begin
assume(S_AXI3_AWVALID);
assume($stable(S_AXI3_AWID));
end
if ($past(S_AXI3_WVALID && !S_AXI3_WREADY))
begin
assume(S_AXI3_WVALID);
assume($stable(S_AXI3_WID));
assume($stable(S_AXI3_WDATA));
assume($stable(S_AXI3_WSTRB));
assume($stable(S_AXI3_WLAST));
end
if ($past(M_AXI_WVALID && !M_AXI_WREADY))
begin
assert(M_AXI_WVALID);
assert($stable(M_AXI_WID));
assert($stable(M_AXI_WDATA));
assert($stable(M_AXI_WSTRB));
assert($stable(M_AXI_WLAST));
end
end
generate if (OPT_METHOD != MTHD_NONE)
begin : F_FIFO_CHECK
wire [LGWFIFO:0] f_ckfifo_fill;
wire [LGWFIFO:0] f_ckfifo_full, f_ckfifo_empty;
wire [IW-1:0] f_ckfifo_id;
wire [DW-1:0] f_ckfifo_data;
wire [DW/8-1:0] f_ckfifo_strb;
wire f_ckfifo_last;
sfifo #(
.BW(IW + DW + (DW/8) + 1),
.LGFLEN(LGWFIFO),
.OPT_READ_ON_EMPTY(OPT_LOW_LATENCY)
) f_checkfifo (
.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
.i_wr(S_AXI3_WVALID && S_AXI3_WREADY
&& S_AXI3_WID == f_const_id),
.i_data({ S_AXI3_WID, S_AXI3_WDATA, S_AXI3_WSTRB, S_AXI3_WLAST }),
.o_full(f_ckfifo_full), .o_fill(f_ckfifo_fill),
.i_rd(M_AXI_WVALID && M_AXI_WREADY
&& M_AXI_WID == f_const_id),
.o_data({ f_ckfifo_id, f_ckfifo_data, f_ckfifo_strb,
f_ckfifo_last }),
.o_empty(f_ckfifo_empty)
);
always @(*)
if (S_AXI_ARESETN && M_AXI_WVALID && M_AXI_WID == f_const_id)
begin
assert(!f_ckfifo_empty);
assert(f_ckfifo_id == M_AXI_WID);
assert(f_ckfifo_data == M_AXI_WDATA);
assert(f_ckfifo_strb == M_AXI_WSTRB);
assert(f_ckfifo_last == M_AXI_WLAST);
end
end endgenerate
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
f_awid_count = 0;
else case({S_AXI3_AWVALID&& S_AXI3_AWREADY && S_AXI3_AWID == f_const_id,
M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST
&& M_AXI_WID == f_const_id })
2'b01: f_awid_count <= f_awid_count - 1;
2'b10: f_awid_count <= f_awid_count + 1;
default begin end
endcase
always @(*)
if (M_AXI_WVALID && M_AXI_WID == f_const_id)
assert(f_awid_count > 0);
generate if (OPT_LOWPOWER)
begin : F_LOWPOWER_CHECK
always @(*)
if (!S_AXI3_AWVALID)
assume(S_AXI3_AWID == 0);
always @(*)
if (!S_AXI3_WVALID)
begin
assume(S_AXI3_WID == 0);
assume(S_AXI3_WDATA == 0);
assume(S_AXI3_WSTRB == 0);
assume(S_AXI3_WLAST == 0);
end
always @(*)
if (!M_AXI_WVALID)
begin
assert(M_AXI_WID == 0);
assert(M_AXI_WDATA == 0);
assert(M_AXI_WSTRB == 0);
assert(M_AXI_WLAST == 0);
end
end endgenerate
`endif
// }}}
endmodule