blob: 02229f106d5ca730e104c3e7a0c2843760a924db [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Filename: axisswitch.v
// {{{
// Project: WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose: Switch from among several AXI streams based upon an AXI-lite
// controlled index. All streams must have the same width.
// The switch will use TLAST to guarantee that it will not change
// mid-packet. If TLAST is unused for a particular input, simply set it
// to 1'b1.
//
// 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 axisswitch #(
// {{{
//
// Size of the AXI-lite bus. These are fixed, since 1) AXI-lite
// is fixed at a width of 32-bits by Xilinx def'n, and 2) since
// we only ever have a single configuration words.
parameter C_AXI_ADDR_WIDTH = 2,
localparam C_AXI_DATA_WIDTH = 32,
//
parameter NUM_STREAMS = 4,
parameter C_AXIS_DATA_WIDTH = 32,
parameter [0:0] OPT_LOWPOWER = 0,
//
localparam LGNS = $clog2(NUM_STREAMS),
localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3
// }}}
) (
// {{{
input wire S_AXI_ACLK,
input wire S_AXI_ARESETN,
// AXI-Lite control
// {{{
input wire S_AXI_AWVALID,
output wire S_AXI_AWREADY,
input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR,
input wire [2:0] S_AXI_AWPROT,
//
input wire S_AXI_WVALID,
output wire S_AXI_WREADY,
input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA,
input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB,
//
output wire S_AXI_BVALID,
input wire S_AXI_BREADY,
output wire [1:0] S_AXI_BRESP,
//
input wire S_AXI_ARVALID,
output wire S_AXI_ARREADY,
input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR,
input wire [2:0] S_AXI_ARPROT,
//
output wire S_AXI_RVALID,
input wire S_AXI_RREADY,
output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,
output wire [1:0] S_AXI_RRESP,
// }}}
// AXI stream inputs to be switched
// {{{
input wire [NUM_STREAMS-1:0] S_AXIS_TVALID,
output wire [NUM_STREAMS-1:0] S_AXIS_TREADY,
input wire [NUM_STREAMS*C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA,
input wire [NUM_STREAMS-1:0] S_AXIS_TLAST,
// }}}
// AXI stream output result
// {{{
output reg M_AXIS_TVALID,
input wire M_AXIS_TREADY,
output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA,
output reg M_AXIS_TLAST
// }}}
// }}}
);
////////////////////////////////////////////////////////////////////////
//
// Register/wire signal declarations
// {{{
////////////////////////////////////////////////////////////////////////
//
//
wire i_reset = !S_AXI_ARESETN;
wire axil_write_ready;
wire [0:0] awskd_addr; // UNUSED
//
wire [C_AXI_DATA_WIDTH-1:0] wskd_data;
wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb;
reg axil_bvalid;
//
wire axil_read_ready;
wire [0:0] arskd_addr; // UNUSED
reg [C_AXI_DATA_WIDTH-1:0] axil_read_data;
reg axil_read_valid;
reg [LGNS-1:0] r_index;
wire [31:0] wskd_index;
genvar gk;
reg [NUM_STREAMS-1:0] skd_switch_ready;
reg [LGNS-1:0] switch_index;
wire [C_AXIS_DATA_WIDTH-1:0] skd_data [0:NUM_STREAMS-1];
wire [NUM_STREAMS-1:0] skd_valid, skd_last;
reg mid_packet, r_mid_packet;
// }}}
////////////////////////////////////////////////////////////////////////
//
// AXI-lite signaling
// {{{
////////////////////////////////////////////////////////////////////////
//
//
//
// Write signaling
//
// {{{
wire awskd_valid, wskd_valid;
skidbuffer #(.OPT_OUTREG(0),
.OPT_LOWPOWER(OPT_LOWPOWER), .DW(1))
axilawskid(//
.i_clk(S_AXI_ACLK), .i_reset(i_reset),
.i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY),
.i_data(1'b0),
.o_valid(awskd_valid), .i_ready(axil_write_ready),
.o_data(awskd_addr));
skidbuffer #(.OPT_OUTREG(0),
.OPT_LOWPOWER(OPT_LOWPOWER),
.DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8))
axilwskid(//
.i_clk(S_AXI_ACLK), .i_reset(i_reset),
.i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY),
.i_data({ S_AXI_WDATA, S_AXI_WSTRB }),
.o_valid(wskd_valid), .i_ready(axil_write_ready),
.o_data({ wskd_data, wskd_strb }));
assign axil_write_ready = awskd_valid && wskd_valid
&& (!S_AXI_BVALID || S_AXI_BREADY);
initial axil_bvalid = 0;
always @(posedge S_AXI_ACLK)
if (i_reset)
axil_bvalid <= 0;
else if (axil_write_ready)
axil_bvalid <= 1;
else if (S_AXI_BREADY)
axil_bvalid <= 0;
assign S_AXI_BVALID = axil_bvalid;
assign S_AXI_BRESP = 2'b00;
// }}}
//
// Read signaling
//
// {{{
wire arskd_valid;
skidbuffer #(.OPT_OUTREG(0),
.OPT_LOWPOWER(OPT_LOWPOWER),
.DW(1))
axilarskid(//
.i_clk(S_AXI_ACLK), .i_reset(i_reset),
.i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY),
.i_data(1'b0),
.o_valid(arskd_valid), .i_ready(axil_read_ready),
.o_data(arskd_addr));
assign axil_read_ready = arskd_valid
&& (!axil_read_valid || S_AXI_RREADY);
initial axil_read_valid = 1'b0;
always @(posedge S_AXI_ACLK)
if (i_reset)
axil_read_valid <= 1'b0;
else if (axil_read_ready)
axil_read_valid <= 1'b1;
else if (S_AXI_RREADY)
axil_read_valid <= 1'b0;
assign S_AXI_RVALID = axil_read_valid;
assign S_AXI_RDATA = axil_read_data;
assign S_AXI_RRESP = 2'b00;
// }}}
// }}}
////////////////////////////////////////////////////////////////////////
//
// AXI-lite register logic
// {{{
////////////////////////////////////////////////////////////////////////
//
//
// apply_wstrb(old_data, new_data, write_strobes)
// r_index, (wskd_index)
// {{{
assign wskd_index = apply_wstrb(
{ {(C_AXI_DATA_WIDTH-LGNS){1'b0}}, r_index },
wskd_data, wskd_strb);
// r_index
initial r_index = 0;
always @(posedge S_AXI_ACLK)
if (i_reset)
r_index <= 0;
else if (axil_write_ready)
r_index <= wskd_index[LGNS-1:0];
// }}}
// axil_read_data
// {{{
initial axil_read_data = 0;
always @(posedge S_AXI_ACLK)
if (OPT_LOWPOWER && !S_AXI_ARESETN)
axil_read_data <= 0;
else if (!S_AXI_RVALID || S_AXI_RREADY)
begin
axil_read_data <= 0;
axil_read_data[LGNS-1:0] <= r_index;
if (OPT_LOWPOWER && !axil_read_ready)
axil_read_data <= 0;
end
// }}}
// function apply_wstrb
// {{{
function [C_AXI_DATA_WIDTH-1:0] apply_wstrb;
input [C_AXI_DATA_WIDTH-1:0] prior_data;
input [C_AXI_DATA_WIDTH-1:0] new_data;
input [C_AXI_DATA_WIDTH/8-1:0] wstrb;
integer k;
for(k=0; k<C_AXI_DATA_WIDTH/8; k=k+1)
begin
apply_wstrb[k*8 +: 8]
= wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8];
end
endfunction
// }}}
// }}}
////////////////////////////////////////////////////////////////////////
//
// The actual AXI switch
// {{{
////////////////////////////////////////////////////////////////////////
//
//
// Place a skidbuffer on every incoming stream input
// {{{
generate for(gk=0; gk<NUM_STREAMS; gk=gk+1)
begin
skidbuffer #(.OPT_OUTREG(0),
.OPT_LOWPOWER(OPT_LOWPOWER),
.DW(C_AXIS_DATA_WIDTH+1))
skdswitch(//
.i_clk(S_AXI_ACLK), .i_reset(i_reset),
.i_valid(S_AXIS_TVALID[gk]),.o_ready(S_AXIS_TREADY[gk]),
.i_data({ S_AXIS_TDATA[gk*C_AXIS_DATA_WIDTH
+: C_AXIS_DATA_WIDTH], S_AXIS_TLAST[gk] }),
.o_valid(skd_valid[gk]), .i_ready(skd_switch_ready[gk]),
.o_data({ skd_data[gk], skd_last[gk] }));
end endgenerate
// }}}
// skd_switch_ready
// {{{
always @(*)
begin
skd_switch_ready = (1<<switch_index);
if (M_AXIS_TVALID && !M_AXIS_TREADY)
skd_switch_ready = 0;
if (!mid_packet && r_index != switch_index)
skd_switch_ready = 0;
end
// }}}
// mid_packet -- are we currently within a packet or not
// {{{
initial r_mid_packet = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
r_mid_packet <= 0;
else if (M_AXIS_TVALID)
r_mid_packet <= !M_AXIS_TLAST;
always @(*)
if (M_AXIS_TVALID)
mid_packet = !M_AXIS_TLAST;
else
mid_packet = r_mid_packet;
// }}}
// switch_index -- the current index of the skidbuffer switch
// {{{
initial switch_index = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
switch_index <= 0;
else if (!mid_packet)
switch_index <= r_index;
// }}}
// M_AXIS_TVALID
// {{{
initial M_AXIS_TVALID = 1'b0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
M_AXIS_TVALID <= 1'b0;
else if (!M_AXIS_TVALID || M_AXIS_TREADY)
M_AXIS_TVALID <= |(skd_valid & skd_switch_ready);
// }}}
// M_AXIS_TDATA, M_AXIS_TLAST
// {{{
initial M_AXIS_TDATA = 0;
initial M_AXIS_TLAST = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN && OPT_LOWPOWER)
begin
M_AXIS_TDATA <= 0;
M_AXIS_TLAST <= 0;
end else if (!M_AXIS_TVALID || M_AXIS_TREADY)
begin
M_AXIS_TDATA <= skd_data[switch_index];
M_AXIS_TLAST <= skd_last[switch_index];
if (OPT_LOWPOWER && (skd_valid | skd_switch_ready) == 0)
begin
M_AXIS_TDATA <= 0;
M_AXIS_TLAST <= 0;
end
end
// }}}
// }}}
// Make Verilator happy
// {{{
// Verilator lint_off UNUSED
wire unused;
assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT,
S_AXI_ARADDR[ADDRLSB-1:0], awskd_addr, arskd_addr,
S_AXI_AWADDR[ADDRLSB-1:0], wskd_index };
// Verilator lint_on UNUSED
// }}}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Formal properties used in verfiying this core
// {{{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
`ifdef FORMAL
reg f_past_valid;
initial f_past_valid = 0;
always @(posedge S_AXI_ACLK)
f_past_valid <= 1;
////////////////////////////////////////////////////////////////////////
//
// The AXI-lite control interface
// {{{
////////////////////////////////////////////////////////////////////////
//
//
localparam F_AXIL_LGDEPTH = 4;
wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding,
faxil_wr_outstanding,
faxil_awr_outstanding;
faxil_slave #(
// {{{
.C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH),
.C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH),
.F_LGDEPTH(F_AXIL_LGDEPTH),
.F_AXI_MAXWAIT(2),
.F_AXI_MAXDELAY(2),
.F_AXI_MAXRSTALL(3),
.F_OPT_COVER_BURST(4)
// }}}
) faxil(
// {{{
.i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN),
//
.i_axi_awvalid(S_AXI_AWVALID),
.i_axi_awready(S_AXI_AWREADY),
.i_axi_awaddr( S_AXI_AWADDR),
.i_axi_awcache(4'h0),
.i_axi_awprot( S_AXI_AWPROT),
//
.i_axi_wvalid(S_AXI_WVALID),
.i_axi_wready(S_AXI_WREADY),
.i_axi_wdata( S_AXI_WDATA),
.i_axi_wstrb( S_AXI_WSTRB),
//
.i_axi_bvalid(S_AXI_BVALID),
.i_axi_bready(S_AXI_BREADY),
.i_axi_bresp( S_AXI_BRESP),
//
.i_axi_arvalid(S_AXI_ARVALID),
.i_axi_arready(S_AXI_ARREADY),
.i_axi_araddr( S_AXI_ARADDR),
.i_axi_arcache(4'h0),
.i_axi_arprot( S_AXI_ARPROT),
//
.i_axi_rvalid(S_AXI_RVALID),
.i_axi_rready(S_AXI_RREADY),
.i_axi_rdata( S_AXI_RDATA),
.i_axi_rresp( S_AXI_RRESP),
//
.f_axi_rd_outstanding(faxil_rd_outstanding),
.f_axi_wr_outstanding(faxil_wr_outstanding),
.f_axi_awr_outstanding(faxil_awr_outstanding)
// }}}
);
always @(*)
begin
assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0)
+(S_AXI_AWREADY ? 0:1));
assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0)
+(S_AXI_WREADY ? 0:1));
assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0)
+(S_AXI_ARREADY ? 0:1));
end
always @(*)
assert(S_AXI_RDATA[C_AXI_DATA_WIDTH-1:LGNS] == 0);
always @(posedge S_AXI_ACLK)
if (f_past_valid && $past(S_AXI_ARESETN
&& axil_read_ready))
begin
assert(S_AXI_RVALID);
assert(S_AXI_RDATA[LGNS-1:0] == $past(r_index));
end
//
// Check that our low-power only logic works by verifying that anytime
// S_AXI_RVALID is inactive, then the outgoing data is also zero.
//
always @(*)
if (OPT_LOWPOWER && !S_AXI_RVALID)
assert(S_AXI_RDATA == 0);
// }}}
////////////////////////////////////////////////////////////////////////
//
// AXI Stream properties
// {{{
////////////////////////////////////////////////////////////////////////
//
//
// Stream properties are captured by the skid buffers, and so don't
// need to be repeated here.
always @(posedge S_AXI_ACLK)
if (!f_past_valid || $past(!S_AXI_ARESETN))
assert(!M_AXIS_TVALID);
else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY))
begin
assert(M_AXIS_TVALID);
assert($stable(M_AXIS_TDATA));
assert($stable(M_AXIS_TLAST));
end
always @(*)
if (OPT_LOWPOWER && !M_AXIS_TVALID)
begin
assert(M_AXIS_TDATA == 0);
assert(M_AXIS_TLAST == 0);
end
(* anyconst *) reg [LGNS-1:0] f_const_index;
(* anyconst *) reg [C_AXIS_DATA_WIDTH-1:0] f_never_data;
reg [LGNS-1:0] f_this_index;
reg [3:0] f_count, f_recount;
reg f_accepts, f_delivers;
always @(*)
assume(f_const_index < NUM_STREAMS);
// f_this_index
// {{{
initial f_this_index = 1'b0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
f_this_index <= 1'b0;
else if (!M_AXIS_TVALID || M_AXIS_TREADY)
f_this_index <= switch_index;
always @(*)
assert(f_this_index < NUM_STREAMS);
always @(*)
assert(switch_index < NUM_STREAMS);
// }}}
// f_count
// {{{
always @(*)
begin
f_accepts = S_AXIS_TVALID[f_const_index]
&& S_AXIS_TREADY[f_const_index];
f_delivers = M_AXIS_TVALID && M_AXIS_TREADY
&& f_this_index == f_const_index;
end
initial f_count = 0;
always @(posedge S_AXI_ACLK)
if (!S_AXI_ARESETN)
f_count <= 0;
else case( { f_accepts, f_delivers })
2'b01: f_count <= f_count - 1;
2'b10: f_count <= f_count + 1;
default: begin end
endcase
// }}}
// f_count induction
// {{{
always @(*)
begin
f_recount = 0;
if (!S_AXIS_TREADY[f_const_index])
f_recount = 1;
if (M_AXIS_TVALID && f_this_index == f_const_index)
f_recount = f_recount + 1;
assert(f_recount == f_count);
end
// }}}
// f_this_index induction
always @(*)
if (M_AXIS_TVALID && !M_AXIS_TLAST)
assert(f_this_index == switch_index);
// assume != f_never_data
// {{{
always @(posedge S_AXI_ACLK)
if (S_AXIS_TVALID[f_const_index])
assume({ S_AXIS_TDATA[f_const_index * C_AXIS_DATA_WIDTH +: C_AXIS_DATA_WIDTH], S_AXIS_TLAST[f_const_index] } != f_never_data);
// }}}
// Never data induction
// {{{
always @(*)
begin
if (skd_valid[f_const_index])
assert({ skd_data[f_const_index], skd_last[f_const_index] } != f_never_data);
if (M_AXIS_TVALID && f_this_index == f_const_index)
assert({ M_AXIS_TDATA, M_AXIS_TLAST } != f_never_data);
end
// }}}
// }}}
////////////////////////////////////////////////////////////////////////
//
// Cover checks
// {{{
////////////////////////////////////////////////////////////////////////
//
//
// While there are already cover properties in the formal property
// set above, you'll probably still want to cover something
// application specific here
// }}}
`endif
// }}}
endmodule