| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 |