Files
b210-k7-fpga/lib/rfnoc/crossbar/chdr_crossbar_nxn.v
T
Wade Fife 9ed8bb22d6 fpga: lib: Fix Vivado warnings
Original-commit: ad3d614426e068a067692df411412ce3ed936c39
2023-06-09 09:01:04 -05:00

692 lines
32 KiB
Verilog

//
// Copyright 2023 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: chdr_crossbar_nxn
//
// Description:
//
// This module implements a full-bandwidth NxN crossbar with N input and
// output ports for CHDR traffic. It supports multiple optimization
// strategies for performance, area and timing trade-offs. It uses AXI-Stream
// for all of its links. The crossbar has a dynamic routing table based on a
// Content Addressable Memory (CAM). The SID is used to determine the
// destination of a packet and the routing table contains a re-programmable
// SID to crossbar port mapping. The table is programmed using special route
// config packets on the data input ports or using an optional management
// port.
//
// The topology, routing algorithms and the router architecture is described
// in README.pdf in this directory.
//
// This crossbar also supports multiple port sizes. By default, each port
// will be PORT_W bits wide. This can be changed using the CHDR_WIDTHS
// parameter. This parameter allows the CHDR width of each port to be
// specified. When using multiple CHDR widths, the PORT_W parameter should be
// the size of the widest port. The CHDR_W value reported by the management
// port will be the value specified for that port in CHDR_WIDTHS.
//
// Parameters:
//
// PORT_W : Width of the AXI-Stream data buses s_axis and m_axis. If
// using multiple port widths, this should be set to the
// width of the widest port.
// NPORTS : Number of ports to instantiate.
// CHDR_WIDTHS : Descending array of NUM_PORT integers representing the
// width of each crossbar port. The width of port n is given
// by CHDR_WIDTHS[(N+1)*32-1 : N*32].
// ROUTES : Descending array representing which crossbar routes to
// enable. This is an NPORTS*NPORTS-bit array where bit
// [NPORTS*A + B] corresponds to the path from input port A
// to output port B. A '1' indicates the logic for that route
// is included. All routes are enabled by default.
// EN_ROUTE_FIFO : Set to 1 to include a FIFO on all routes going from a wide
// port to a narrow port. This may improve performance when a
// single wide input port streams to multiple narrow output
// ports by buffering the input data while it's resized for
// the slower output port. This helps to avoid congestion on
// the input port.
// EN_ROUTE_GATE : Set to 1 to include a packet gate on all routes going from
// a narrow port to a wide port. This may improve performance
// when multiple narrow input ports stream to a single wide
// output port by removing idle transfer cycles caused by the
// slower rate of the narrow input port. This helps to avoid
// congestion on the output port.
// DEFAULT_PORT : The fail-safe port to forward a packet to if SID mapping
// is missing.
// BYTE_MTU : log2 of the max packet size in bytes.
// ROUTE_TBL_SIZE: log2 of the number of mappings that the routing table can
// hold at any time. Mapping values are maintained in a FIFO
// fashion.
// MUX_ALLOC : Algorithm to allocate the egress MUX. Possible values:
// * "PRIO": Priority based. Lower port numbers have a
// higher priority
// * "ROUND-ROBIN": Round robin input port allocation
// OPTIMIZE : Optimization strategy for performance vs area vs timing
// trade-offs. Possible values:
// * "AREA": Attempt to minimize area at the cost of
// performance (throughput) and/or timing.
// * "PERFORMANCE": Attempt to maximize performance at the
// cost of area and/or timing.
// * "TIMING": Attempt to maximize Fmax at the cost of area
// and/or performance.
// NPORTS_MGMT : Number of ports with management endpoint. The first
// NPORTS_MGMT ports will have the management port
// instantiated.
// EXT_RTCFG_PORT: Enable a side-channel AXI-Stream management port to
// configure the routing table.
//
// CHDR_WIDTHS Bit Mapping Example (4x4):
//
// Port #: 3 2 1 0
// ↓ ↓ ↓ ↓
// {32'd64, 32'd64, 32'd64, 32'd64}
//
// ROUTES Bit Mapping Example (4x4):
//
// Output Port: 3210
// ↓↓↓↓
// Input Port 3 → {4'b1111,
// Input Port 2 → 4'b1111,
// Input Port 1 → 4'b1111,
// Input Port 0 → 4'b1111}
//
// Ports:
//
// s_axis_* : Slave port for router (flattened)
// m_axis_* : Master port for router (flattened)
// s_axis_mgmt_*: Management slave port
// device_id : The ID of the device that has instantiated this module
//
module chdr_crossbar_nxn #(
parameter [15:0] PROTOVER = {8'd1, 8'd0},
parameter [31:0] PORT_W = 64,
parameter [7:0] NPORTS = 8,
parameter [NPORTS*32-1:0] CHDR_WIDTHS = {NPORTS{PORT_W}},
parameter EN_ROUTE_FIFO = 0,
parameter EN_ROUTE_GATE = 0,
parameter [7:0] DEFAULT_PORT = 0,
parameter [NPORTS**2-1:0] ROUTES = {NPORTS*NPORTS{1'b1}},
parameter BYTE_MTU = $clog2(8192),
parameter ROUTE_TBL_SIZE = 6,
parameter MUX_ALLOC = "ROUND-ROBIN",
parameter OPTIMIZE = "AREA",
parameter [7:0] NPORTS_MGMT = NPORTS,
parameter EXT_RTCFG_PORT = 0
) (
input wire clk,
input wire reset,
// Device info
input wire [15:0] device_id,
// Inputs
input wire [(PORT_W*NPORTS)-1:0] s_axis_tdata,
input wire [NPORTS-1:0] s_axis_tlast,
input wire [NPORTS-1:0] s_axis_tvalid,
output wire [NPORTS-1:0] s_axis_tready,
// Output
output wire [(PORT_W*NPORTS)-1:0] m_axis_tdata,
output wire [NPORTS-1:0] m_axis_tlast,
output wire [NPORTS-1:0] m_axis_tvalid,
input wire [NPORTS-1:0] m_axis_tready,
// Router config management port
input wire ext_rtcfg_stb,
input wire [15:0] ext_rtcfg_addr,
input wire [31:0] ext_rtcfg_data,
output wire ext_rtcfg_ack
);
//---------------------------------------------------------------------------
// RFNoC Includes
//---------------------------------------------------------------------------
`include "../core/rfnoc_chdr_utils.vh"
`include "../core/rfnoc_chdr_internal_utils.vh"
//---------------------------------------------------------------------------
// Parameters
//---------------------------------------------------------------------------
localparam NPORTS_W = $clog2(NPORTS);
localparam EPID_W = 16;
localparam [17:0] EXT_INFO = {1'b0, EXT_RTCFG_PORT, NPORTS_MGMT, NPORTS};
localparam [0:0] PKT_ST_HEAD = 1'b0;
localparam [0:0] PKT_ST_BODY = 1'b1;
//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------
// The compute_mux_alloc function is the switch allocation function for the
// MUX. That is, it chooses which input port reserves the output MUX for
// packet transfer.
function [NPORTS_W-1:0] compute_mux_alloc(
input [ NPORTS-1:0] pkt_waiting,
input [NPORTS_W-1:0] last_alloc
);
reg signed [NPORTS_W:0] i;
begin
compute_mux_alloc = last_alloc;
for (i = NPORTS-1; i >= 0; i=i-1) begin
if (MUX_ALLOC == "PRIO") begin
// Priority. Lower port index gets a higher priority.
if (pkt_waiting[i]) begin
compute_mux_alloc = i;
end
end else begin
// Round-robin
if (pkt_waiting[(last_alloc + i + 1) % NPORTS]) begin
compute_mux_alloc = (last_alloc + i + 1) % NPORTS;
end
end
end
end
endfunction
// Return the CHDR width of the given port.
function [31:0] CHDR_W(input integer n);
CHDR_W = CHDR_WIDTHS[32*n +: 32];
endfunction
// Return the MTU size for the given port in terms of its CHDR width.
function [31:0] WORD_MTU(input integer n);
WORD_MTU = BYTE_MTU - $clog2(CHDR_W(n)/8);
endfunction
// Return bit indicating if the route between input port i and output port j
// is enabled.
function [0:0] ROUTE_ENABLED(input integer i, j);
ROUTE_ENABLED = ROUTES[NPORTS*i + j];
endfunction
// Return bit indicating if the given input port has any routes connected to
// it.
function [0:0] INPUT_HAS_ROUTES(input integer i);
INPUT_HAS_ROUTES = |ROUTES[NPORTS*i +: NPORTS];
endfunction
// Return bit indicating if the given output port has any routes connected to
// it.
function automatic [0:0] OUTPUT_HAS_ROUTES(input integer j);
integer i;
begin
OUTPUT_HAS_ROUTES = 1'b0;
for (i = 0; i < NPORTS; i = i+1) begin
OUTPUT_HAS_ROUTES = OUTPUT_HAS_ROUTES | ROUTES[NPORTS*i + j];
end
end
endfunction
//---------------------------------------------------------------------------
// CHDR Routing Table
//---------------------------------------------------------------------------
wire [NPORTS-1:0] rtcfg_req_wr;
wire [(16*NPORTS)-1:0] rtcfg_req_addr;
wire [(32*NPORTS)-1:0] rtcfg_req_data;
wire [NPORTS-1:0] rtcfg_resp_ack;
wire [(EPID_W*NPORTS)-1:0] find_tdata;
wire [NPORTS-1:0] find_tvalid;
wire [NPORTS-1:0] find_tready;
wire [(NPORTS_W*NPORTS)-1:0] result_tdata;
wire [NPORTS-1:0] result_tkeep;
wire [NPORTS-1:0] result_tvalid;
wire [NPORTS-1:0] result_tready;
// Instantiate a single CAM-based routing table that will be shared between
// all input ports. Configuration and lookup is performed using an AXI-Stream
// interface. If multiple packets arrive simultaneously, only the headers of
// those packets will be serialized in order to arbitrate this map. Selection
// is done round-robin.
chdr_xb_routing_table #(
.SIZE(ROUTE_TBL_SIZE), .NPORTS(NPORTS),
.EXT_INS_PORT_EN(EXT_RTCFG_PORT)
) chdr_xb_routing_table_i (
.clk (clk ),
.reset (reset ),
.port_req_wr (rtcfg_req_wr ),
.port_req_addr (rtcfg_req_addr),
.port_req_data (rtcfg_req_data),
.port_resp_ack (rtcfg_resp_ack),
.ext_req_wr (ext_rtcfg_stb ),
.ext_req_addr (ext_rtcfg_addr),
.ext_req_data (ext_rtcfg_data),
.ext_resp_ack (ext_rtcfg_ack ),
.axis_find_tdata (find_tdata ),
.axis_find_tvalid (find_tvalid ),
.axis_find_tready (find_tready ),
.axis_result_tdata (result_tdata ),
.axis_result_tkeep (result_tkeep ),
.axis_result_tvalid(result_tvalid ),
.axis_result_tready(result_tready )
);
wire [PORT_W-1:0] i_tdata [0:NPORTS-1];
wire [9:0] i_tdest [0:NPORTS-1];
wire [1:0] i_tid [0:NPORTS-1];
wire i_tlast [0:NPORTS-1];
wire i_tvalid [0:NPORTS-1];
wire i_tready [0:NPORTS-1];
wire [PORT_W-1:0] buf_tdata [0:NPORTS-1];
wire [NPORTS_W-1:0] buf_tdest [0:NPORTS-1], buf_tdest_tmp[0:NPORTS-1];
wire buf_tkeep [0:NPORTS-1];
wire buf_tlast [0:NPORTS-1];
wire buf_tvalid[0:NPORTS-1];
wire buf_tready[0:NPORTS-1];
wire [PORT_W-1:0] swi_tdata [0:NPORTS-1];
wire [NPORTS_W-1:0] swi_tdest [0:NPORTS-1];
wire swi_tlast [0:NPORTS-1];
wire swi_tvalid[0:NPORTS-1];
wire swi_tready[0:NPORTS-1];
wire [(PORT_W*NPORTS)-1:0] swo_tdata [0:NPORTS-1], muxi_tdata [0:NPORTS-1];
wire [NPORTS-1:0] swo_tlast [0:NPORTS-1], muxi_tlast [0:NPORTS-1];
wire [NPORTS-1:0] swo_tvalid[0:NPORTS-1], muxi_tvalid[0:NPORTS-1];
wire [NPORTS-1:0] swo_tready[0:NPORTS-1], muxi_tready[0:NPORTS-1];
//---------------------------------------------------------------------------
// Port Generation
//---------------------------------------------------------------------------
genvar n, i, j, port;
generate
for (n = 0; n < NPORTS; n = n + 1) begin: gen_in_ports
// Only generate the input logic for this input port if it has routes
if (INPUT_HAS_ROUTES(n)) begin : gen_in_port
//-----------------------------------------------------------------------
// Assertions
//-----------------------------------------------------------------------
// Make sure the width of this port does not exceed the given maximum
// port width.
if (CHDR_W(n) > PORT_W) begin : gen_chdr_w_too_large
ERROR__CHDR_W_must_not_exceed_PORT_W_parameter();
end
// Make sure the port's CHDR width is a valid CHDR width (a power of 2
// and at least 64 bits).
if (2**$clog2(CHDR_W(n)) != CHDR_W(n) || CHDR_W(n) < 64) begin : gen_invalid_chdr_w
ERROR__CHDR_W_is_not_a_valid_CHDR_width();
end
// Make sure the maximum port width is a valid CHDR width (a power of 2
// and at least 64 bits).
if (2**$clog2(PORT_W) != PORT_W || PORT_W < 64) begin : gen_invalid_port_w
ERROR__PORT_W_is_not_a_valid_CHDR_width();
end
//-----------------------------------------------------------------------
// Management Ports
//-----------------------------------------------------------------------
wire [47:0] node_info =
chdr_mgmt_build_node_info(EXT_INFO, n, NODE_TYPE_XBAR, device_id);
// For each input port, first check if we have a management packet
// arriving. If it arrives, the top config commands are extracted, sent
// to the routing table for configuration, and the rest of the packet is
// forwarded down to the router. the router.
if (n < NPORTS_MGMT) begin : gen_mgmt
chdr_mgmt_pkt_handler #(
.PROTOVER (PROTOVER ),
.CHDR_W (CHDR_W(n)),
.MGMT_ONLY (0 )
) chdr_mgmt_pkt_handler_i (
.clk (clk ),
.rst (reset ),
.node_info (node_info ),
.s_axis_chdr_tdata (s_axis_tdata [(n*PORT_W)+:CHDR_W(n)]),
.s_axis_chdr_tlast (s_axis_tlast [n] ),
.s_axis_chdr_tvalid (s_axis_tvalid[n] ),
.s_axis_chdr_tready (s_axis_tready[n] ),
.s_axis_chdr_tuser (1'd0 ),
.m_axis_chdr_tdata (i_tdata [n][CHDR_W(n)-1:0] ),
.m_axis_chdr_tdest (i_tdest [n] ),
.m_axis_chdr_tid (i_tid [n] ),
.m_axis_chdr_tlast (i_tlast [n] ),
.m_axis_chdr_tvalid (i_tvalid [n] ),
.m_axis_chdr_tready (i_tready [n] ),
.ctrlport_req_wr (rtcfg_req_wr [n] ),
.ctrlport_req_rd (/* unused */ ),
.ctrlport_req_addr (rtcfg_req_addr[(n*16)+:16] ),
.ctrlport_req_data (rtcfg_req_data[(n*32)+:32] ),
.ctrlport_resp_ack (rtcfg_resp_ack[n] ),
.ctrlport_resp_data (32'h0 /* unused */ ),
.op_stb (/* unused */ ),
.op_dst_epid (/* unused */ ),
.op_src_epid (/* unused */ ),
.op_data (/* unused */ )
);
end else begin : gen_no_mgmt
assign i_tdata [n] = s_axis_tdata [(n*PORT_W)+:CHDR_W(n)];
assign i_tid [n] = CHDR_MGMT_ROUTE_EPID;
assign i_tdest [n] = 10'd0; // Unused
assign i_tlast [n] = s_axis_tlast [n];
assign i_tvalid [n] = s_axis_tvalid[n];
assign s_axis_tready[n] = i_tready [n];
assign rtcfg_req_wr [n] = 1'b0;
assign rtcfg_req_addr[(n*16)+:16] = 16'h0;
assign rtcfg_req_data[(n*32)+:32] = 32'h0;
end
//-----------------------------------------------------------------------
// Port Ingress Buffer
//-----------------------------------------------------------------------
// Ingress buffer module that does the following:
// - Stores and gates an incoming packet
// - Looks up destination in routing table and attaches a tdest for the packet
chdr_xb_ingress_buff #(
.WIDTH (CHDR_W(n) ),
.MTU (WORD_MTU(n)),
.DEST_W (NPORTS_W ),
.NODE_ID(n )
) chdr_xb_ingress_buff_i (
.clk (clk ),
.reset (reset ),
.s_axis_chdr_tdata (i_tdata [n][CHDR_W(n)-1:0] ),
.s_axis_chdr_tdest (i_tdest [n][NPORTS_W-1:0] ),
.s_axis_chdr_tid (i_tid [n] ),
.s_axis_chdr_tlast (i_tlast [n] ),
.s_axis_chdr_tvalid (i_tvalid [n] ),
.s_axis_chdr_tready (i_tready [n] ),
.m_axis_chdr_tdata (buf_tdata [n][CHDR_W(n)-1:0] ),
.m_axis_chdr_tdest (buf_tdest_tmp[n] ),
.m_axis_chdr_tkeep (buf_tkeep [n] ),
.m_axis_chdr_tlast (buf_tlast [n] ),
.m_axis_chdr_tvalid (buf_tvalid [n] ),
.m_axis_chdr_tready (buf_tready [n] ),
.m_axis_find_tdata (find_tdata [(n*EPID_W)+:EPID_W] ),
.m_axis_find_tvalid (find_tvalid [n] ),
.m_axis_find_tready (find_tready [n] ),
.s_axis_result_tdata (result_tdata [(n*NPORTS_W)+:NPORTS_W]),
.s_axis_result_tkeep (result_tkeep [n] ),
.s_axis_result_tvalid(result_tvalid[n] ),
.s_axis_result_tready(result_tready[n] )
);
assign buf_tdest[n] = buf_tkeep[n] ? buf_tdest_tmp[n] : DEFAULT_PORT[NPORTS_W-1:0];
// Pipeline stage
axi_fifo #(
.WIDTH(CHDR_W(n)+1+NPORTS_W),
.SIZE (1 )
) axi_fifo_i (
.clk (clk ),
.reset (reset ),
.clear (1'b0 ),
.i_tdata ({buf_tlast[n], buf_tdest[n], buf_tdata[n][CHDR_W(n)-1:0]}),
.i_tvalid(buf_tvalid[n] ),
.i_tready(buf_tready[n] ),
.o_tdata ({swi_tlast[n], swi_tdest[n], swi_tdata[n][CHDR_W(n)-1:0]}),
.o_tvalid(swi_tvalid[n] ),
.o_tready(swi_tready[n] ),
.space (/* Unused */ ),
.occupied(/* Unused */ )
);
//-----------------------------------------------------------------------
// Ingress Switch (De-multiplexers)
//-----------------------------------------------------------------------
wire [CHDR_W(n)*NPORTS-1:0] swo_tdata_packed;
// Ingress de-mux. Use the tdest field to determine packet destination.
axis_switch #(
.DATA_W (CHDR_W(n)),
.DEST_W (1 ),
.IN_PORTS (1 ),
.OUT_PORTS(NPORTS ),
.PIPELINE (1 )
) axis_switch_demux (
.clk (clk ),
.reset (reset ),
.s_axis_tdata (swi_tdata[n][CHDR_W(n)-1:0]),
.s_axis_tdest ({1'b0, swi_tdest[n]} ),
.s_axis_tlast (swi_tlast [n] ),
.s_axis_tvalid(swi_tvalid[n] ),
.s_axis_tready(swi_tready[n] ),
.s_axis_alloc (1'b0 ),
.m_axis_tdata (swo_tdata_packed ),
.m_axis_tdest (/* Unused */ ),
.m_axis_tlast (swo_tlast [n] ),
.m_axis_tvalid(swo_tvalid[n] ),
.m_axis_tready(swo_tready[n] )
);
// Unpack the switch output to handle the case where this port's CHDR_W
// is narrower than PORT_W.
for (port = 0; port < NPORTS; port = port+1) begin : gen_switch_output
assign swo_tdata[n][PORT_W*port +: CHDR_W(n)] =
swo_tdata_packed[CHDR_W(n)*port +: CHDR_W(n)];
end
end // gen_in_port
end // gen_in_ports
//-------------------------------------------------------------------------
// Crossbar Routing
//-------------------------------------------------------------------------
// Generate the routing for a full NxN crossbar where i is the input port
// number and j is the output port number. Some paths are resized,
// depending on CHDR_WIDTHS, or excluded, depending on ROUTES.
for (i = 0; i < NPORTS; i = i + 1) begin : gen_for_i
for (j = 0; j < NPORTS; j = j + 1) begin : gen_for_j
if (ROUTE_ENABLED(i,j)) begin : gen_enabled_route
wire [CHDR_W(i)-1:0] rs_i_tdata;
wire rs_i_tlast;
wire rs_i_tvalid;
wire rs_i_tready;
wire [CHDR_W(j)-1:0] rs_o_tdata;
wire rs_o_tlast;
wire rs_o_tvalid;
wire rs_o_tready;
// Connect output j of ingress port i to input i of egress port j.
// Resize the bus if the ports have different widths, otherwise
// directly connect them.
if (CHDR_W(i) != CHDR_W(j)) begin : gen_port_resize
if (CHDR_W(i) > CHDR_W(j) && EN_ROUTE_FIFO) begin : gen_input_fifo
// If we're downsizing, we need a wide FIFO on the input to the
// resize block to buffer the fast incoming packet.
axi_fifo #(
.WIDTH(CHDR_W(i)+1),
.SIZE (WORD_MTU(i))
) axi_fifo_i (
.clk (clk ),
.reset (reset ),
.clear (1'b0 ),
.i_tdata ({swo_tlast[i][j], swo_tdata[i][j*PORT_W+:CHDR_W(i)]}),
.i_tvalid(swo_tvalid[i][j] ),
.i_tready(swo_tready[i][j] ),
.o_tdata ({rs_i_tlast, rs_i_tdata} ),
.o_tvalid(rs_i_tvalid ),
.o_tready(rs_i_tready ),
.space ( ),
.occupied( )
);
end else begin : gen_no_input_fifo
assign rs_i_tdata = swo_tdata[i][j*PORT_W+:CHDR_W(i)];
assign rs_i_tlast = swo_tlast[i][j];
assign rs_i_tvalid = swo_tvalid[i][j];
assign swo_tready[i][j] = rs_i_tready;
end
chdr_resize #(
.I_CHDR_W(CHDR_W(i)),
.O_CHDR_W(CHDR_W(j)),
.I_DATA_W(CHDR_W(i)),
.O_DATA_W(CHDR_W(j)),
.USER_W (1 ),
.PIPELINE("OUT" )
) chdr_resize_i (
.clk (clk ),
.rst (reset ),
.i_chdr_tdata (rs_i_tdata ),
.i_chdr_tuser (1'b0 ),
.i_chdr_tlast (rs_i_tlast ),
.i_chdr_tvalid(rs_i_tvalid),
.i_chdr_tready(rs_i_tready),
.o_chdr_tdata (rs_o_tdata ),
.o_chdr_tuser ( ),
.o_chdr_tlast (rs_o_tlast ),
.o_chdr_tvalid(rs_o_tvalid),
.o_chdr_tready(rs_o_tready)
);
if (CHDR_W(i) < CHDR_W(j) && EN_ROUTE_GATE) begin : gen_output_pkt_gate
// If we are up-sizing, then there will be idle cycles on the
// wider output bus that will waste time on the output mux. To
// maximize throughput on the output port, we gate packets here
// so that we can output a continuous stream of data without idle
// cycles.
axi_packet_gate #(
.WIDTH(CHDR_W(j) ),
.SIZE (WORD_MTU(j))
) axi_packet_gate_i (
.clk (clk ),
.reset (reset ),
.clear (1'b0 ),
.i_tdata (rs_o_tdata ),
.i_tlast (rs_o_tlast ),
.i_terror(1'b0 ),
.i_tvalid(rs_o_tvalid ),
.i_tready(rs_o_tready ),
.o_tdata (muxi_tdata[j][i*PORT_W+:CHDR_W(j)]),
.o_tlast (muxi_tlast[j][i] ),
.o_tvalid(muxi_tvalid[j][i] ),
.o_tready(muxi_tready[j][i] )
);
end else begin : gen_no_output_pkt_gate
assign muxi_tdata[j][i*PORT_W+:CHDR_W(j)] = rs_o_tdata;
assign muxi_tlast[j][i] = rs_o_tlast;
assign muxi_tvalid[j][i] = rs_o_tvalid;
assign rs_o_tready = muxi_tready[j][i];
end
end else begin : gen_port_same_size
assign muxi_tdata[j][i*PORT_W+:CHDR_W(j)] = swo_tdata [i][j*PORT_W+:CHDR_W(i)];
assign muxi_tlast[j][i] = swo_tlast [i][j];
assign muxi_tvalid[j][i] = swo_tvalid [i][j];
assign swo_tready[i][j] = muxi_tready[j][i];
end
end else begin : gen_disabled_route
// Tie off these unused paths so they can be optimized out.
assign muxi_tdata[j][i*PORT_W+:PORT_W] = { PORT_W {1'b0} };
assign muxi_tlast[j][i] = 1'b0;
assign muxi_tvalid[j][i] = 1'b0;
assign swo_tready[i][j] = 1'b1;
end
end
end
//-------------------------------------------------------------------------
// Egress Switch (Multiplexers)
//-------------------------------------------------------------------------
for (n = 0; n < NPORTS; n = n + 1) begin: gen_out_ports
// Only generate egress logic for this output port if it has routes
if (OUTPUT_HAS_ROUTES(n)) begin : gen_out_port
wire [CHDR_W(n)*NPORTS-1:0] muxi_tdata_repacked;
// Repack the mux input to handle the case where this port's CHDR_W is
// narrower than PORT_W.
for (port = 0; port < NPORTS; port = port+1) begin : gen_mux_input
assign muxi_tdata_repacked[CHDR_W(n)*port +: CHDR_W(n)] =
muxi_tdata[n][PORT_W*port +: CHDR_W(n)];
end
if (OPTIMIZE == "PERFORMANCE") begin : gen_performance
// Use the axis_switch module when optimizing for performance
// This logic has some extra levels of logic to ensure
// that the switch allocation happens in 0 clock cycles which
// means that Fmax for this implementation will be lower.
wire mux_ready = |muxi_tready[n]; // Max 1 bit should be high
wire mux_valid = |muxi_tvalid[n];
wire mux_last = |(muxi_tvalid[n] & muxi_tlast[n]);
// Track the input packet state
reg [0:0] pkt_state = PKT_ST_HEAD;
always @(posedge clk) begin
if (reset) begin
pkt_state <= PKT_ST_HEAD;
end else if (mux_valid & mux_ready) begin
pkt_state <= mux_last ? PKT_ST_HEAD : PKT_ST_BODY;
end
end
// The switch requires the allocation to stay valid until the
// end of the packet. We also might need to keep the previous
// packet's allocation to compute the current one
reg [NPORTS_W-1:0] prev_sw_alloc = {NPORTS_W{1'b0}};
reg [NPORTS_W-1:0] pkt_sw_alloc = {NPORTS_W{1'b0}};
wire [NPORTS_W-1:0] muxi_sw_alloc = (mux_valid && pkt_state == PKT_ST_HEAD) ?
compute_mux_alloc(muxi_tvalid[n], prev_sw_alloc) : pkt_sw_alloc;
always @(posedge clk) begin
if (reset) begin
prev_sw_alloc <= {NPORTS_W{1'b0}};
pkt_sw_alloc <= {NPORTS_W{1'b0}};
end else if (mux_valid & mux_ready) begin
if (pkt_state == PKT_ST_HEAD)
pkt_sw_alloc <= muxi_sw_alloc;
if (mux_last)
prev_sw_alloc <= muxi_sw_alloc;
end
end
axis_switch #(
.DATA_W (CHDR_W(n)),
.DEST_W (1 ),
.IN_PORTS (NPORTS ),
.OUT_PORTS (1 ),
.PIPELINE (0 )
) axis_switch_mux (
.clk (clk ),
.reset (reset ),
.s_axis_tdata (muxi_tdata_repacked ),
.s_axis_tdest ({NPORTS{1'b0}} /* Unused */ ),
.s_axis_tlast (muxi_tlast [n] ),
.s_axis_tvalid (muxi_tvalid[n] ),
.s_axis_tready (muxi_tready[n] ),
.s_axis_alloc (muxi_sw_alloc ),
.m_axis_tdata (m_axis_tdata [(n*PORT_W)+:CHDR_W(n)]),
.m_axis_tdest (/* Unused */ ),
.m_axis_tlast (m_axis_tlast [n] ),
.m_axis_tvalid (m_axis_tvalid[n] ),
.m_axis_tready (m_axis_tready[n] )
);
end else begin : gen_not_performance
// axi_mux has an additional bubble cycle but the logic
// to allocate an input port has fewer levels and takes
// up fewer resources.
axi_mux #(
.PRIO (MUX_ALLOC == "PRIO" ),
.WIDTH (CHDR_W(n) ),
.SIZE (NPORTS ),
.PRE_FIFO_SIZE (OPTIMIZE == "TIMING" ? 1 : 0),
.POST_FIFO_SIZE(1 )
) axi_mux_i (
.clk (clk ),
.reset (reset ),
.clear (1'b0 ),
.i_tdata (muxi_tdata_repacked ),
.i_tlast (muxi_tlast [n] ),
.i_tvalid(muxi_tvalid [n] ),
.i_tready(muxi_tready [n] ),
.o_tdata (m_axis_tdata [(n*PORT_W)+:CHDR_W(n)]),
.o_tlast (m_axis_tlast [n] ),
.o_tvalid(m_axis_tvalid[n] ),
.o_tready(m_axis_tready[n] )
);
end
end
end
endgenerate
endmodule