diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/core')
27 files changed, 8581 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/core/Makefile.srcs b/fpga/usrp3/lib/rfnoc/core/Makefile.srcs new file mode 100644 index 000000000..0a646f98b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/Makefile.srcs @@ -0,0 +1,39 @@ +# +# Copyright 2018 Ettus Research, A National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Core Sources +################################################## +RFNOC_CORE_HEADERS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/core/, \ +rfnoc_chdr_utils.vh \ +rfnoc_axis_ctrl_utils.vh \ +rfnoc_chdr_internal_utils.vh \ +ctrlport.vh \ +)) + +RFNOC_CORE_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/core/, \ +axis_ctrl_endpoint.v \ +axis_ctrl_master.v \ +axis_ctrl_slave.v \ +chdr_compute_tkeep.v \ +chdr_to_chdr_data.v \ +chdr_to_axis_pyld_ctxt.v \ +chdr_to_axis_data_mdata.v \ +chdr_to_axis_data.v \ +axis_pyld_ctxt_to_chdr.v \ +axis_data_mdata_to_chdr.v \ +axis_data_to_chdr.v \ +chdr_ingress_fifo.v \ +chdr_mgmt_pkt_handler.v \ +chdr_data_swapper.v \ +chdr_stream_endpoint.v \ +chdr_stream_input.v \ +chdr_stream_output.v \ +chdr_to_axis_ctrl.v \ +ctrlport_endpoint.v \ +backend_iface.v \ +rfnoc_core_kernel.v \ +)) diff --git a/fpga/usrp3/lib/rfnoc/core/axis_ctrl_endpoint.v b/fpga/usrp3/lib/rfnoc/core/axis_ctrl_endpoint.v new file mode 100644 index 000000000..e1ded42aa --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/axis_ctrl_endpoint.v @@ -0,0 +1,116 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_ctrl_endpoint +// Description: +// A bidirectional AXIS-Control to AXIS-Control converter. +// Use this module in noc_shell to interface between the user +// logic and the rfnoc infrastructure when both interfaces use +// AXIS-Control. +// +// Parameters: +// - SYNC_CLKS: Is rfnoc_ctrl_clk and axis_ctrl_clk the same clock? +// - SLAVE_FIFO_SIZE: The depth of the slave FIFO. Note that the +// slave FIFO will also buffer master responses. +// +// Signals: +// - *_rfnoc_ctrl_* : Input/output AXIS-Control from/to the framework +// - *_axis_ctrl_* : Input/output AXIS-Control from/to the user + +module axis_ctrl_endpoint #( + parameter SYNC_CLKS = 0, + parameter SLAVE_FIFO_SIZE = 5 +)( + // Clocks, Resets, Misc + input wire rfnoc_ctrl_clk, + input wire rfnoc_ctrl_rst, + input wire axis_ctrl_clk, + input wire axis_ctrl_rst, + // AXIS-Control Bus (RFNoC infrastructure) + input wire [31:0] s_rfnoc_ctrl_tdata, + input wire s_rfnoc_ctrl_tlast, + input wire s_rfnoc_ctrl_tvalid, + output wire s_rfnoc_ctrl_tready, + output wire [31:0] m_rfnoc_ctrl_tdata, + output wire m_rfnoc_ctrl_tlast, + output wire m_rfnoc_ctrl_tvalid, + input wire m_rfnoc_ctrl_tready, + // AXIS-Control Bus (User logic) + input wire [31:0] s_axis_ctrl_tdata, + input wire s_axis_ctrl_tlast, + input wire s_axis_ctrl_tvalid, + output wire s_axis_ctrl_tready, + output wire [31:0] m_axis_ctrl_tdata, + output wire m_axis_ctrl_tlast, + output wire m_axis_ctrl_tvalid, + input wire m_axis_ctrl_tready +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Clock Crossing + // --------------------------------------------------- + + wire [31:0] i_ctrl_tdata; + wire i_ctrl_tlast, i_ctrl_tvalid, i_ctrl_tready; + + generate + if (SYNC_CLKS) begin + axi_fifo #(.WIDTH(32+1), .SIZE(SLAVE_FIFO_SIZE)) in_fifo_i ( + .clk(axis_ctrl_clk), .reset(axis_ctrl_rst), .clear(1'b0), + .i_tdata({s_rfnoc_ctrl_tlast, s_rfnoc_ctrl_tdata}), + .i_tvalid(s_rfnoc_ctrl_tvalid), .i_tready(s_rfnoc_ctrl_tready), + .o_tdata({i_ctrl_tlast, i_ctrl_tdata}), + .o_tvalid(i_ctrl_tvalid), .o_tready(i_ctrl_tready), + .space(), .occupied() + ); + + axi_fifo #(.WIDTH(32+1), .SIZE(1)) out_fifo_i ( + .clk(axis_ctrl_clk), .reset(axis_ctrl_rst), .clear(1'b0), + .i_tdata({s_axis_ctrl_tlast, s_axis_ctrl_tdata}), + .i_tvalid(s_axis_ctrl_tvalid), .i_tready(s_axis_ctrl_tready), + .o_tdata({m_rfnoc_ctrl_tlast, m_rfnoc_ctrl_tdata}), + .o_tvalid(m_rfnoc_ctrl_tvalid), .o_tready(m_rfnoc_ctrl_tready), + .space(), .occupied() + ); + end else begin + axi_fifo_2clk #(.WIDTH(32+1), .SIZE(SLAVE_FIFO_SIZE), .PIPELINE("NONE")) in_fifo_i ( + .reset(rfnoc_ctrl_rst), + .i_aclk(rfnoc_ctrl_clk), + .i_tdata({s_rfnoc_ctrl_tlast, s_rfnoc_ctrl_tdata}), + .i_tvalid(s_rfnoc_ctrl_tvalid), .i_tready(s_rfnoc_ctrl_tready), + .o_aclk(axis_ctrl_clk), + .o_tdata({i_ctrl_tlast, i_ctrl_tdata}), + .o_tvalid(i_ctrl_tvalid), .o_tready(i_ctrl_tready) + ); + + axi_fifo_2clk #(.WIDTH(32+1), .SIZE(1), .PIPELINE("NONE")) out_fifo_i ( + .reset(axis_ctrl_rst), + .i_aclk(axis_ctrl_clk), + .i_tdata({s_axis_ctrl_tlast, s_axis_ctrl_tdata}), + .i_tvalid(s_axis_ctrl_tvalid), .i_tready(s_axis_ctrl_tready), + .o_aclk(rfnoc_ctrl_clk), + .o_tdata({m_rfnoc_ctrl_tlast, m_rfnoc_ctrl_tdata}), + .o_tvalid(m_rfnoc_ctrl_tvalid), .o_tready(m_rfnoc_ctrl_tready) + ); + end + endgenerate + + axi_fifo #(.WIDTH(32+1), .SIZE(1)) slv_pipe_i ( + .clk(axis_ctrl_clk), .reset(axis_ctrl_rst), .clear(1'b0), + .i_tdata({i_ctrl_tlast, i_ctrl_tdata}), + .i_tvalid(i_ctrl_tvalid), .i_tready(i_ctrl_tready), + .o_tdata({m_axis_ctrl_tlast, m_axis_ctrl_tdata}), + .o_tvalid(m_axis_ctrl_tvalid), .o_tready(m_axis_ctrl_tready), + .space(), .occupied() + ); + +endmodule // axis_ctrl_endpoint + diff --git a/fpga/usrp3/lib/rfnoc/core/axis_ctrl_master.v b/fpga/usrp3/lib/rfnoc/core/axis_ctrl_master.v new file mode 100644 index 000000000..19ae98f52 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/axis_ctrl_master.v @@ -0,0 +1,316 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_ctrl_master +// Description: +// This module implements an AXIS-Control master (and a Control-Port +// slave). Requests are accepted on the slave Control-Port, converted +// to AXIS-Control requests, then sent over the master AXI-Stream port. +// Responses are received on the AXI-Stream slave port, and converted +// to Control-Port responses. +// NOTE: Transactions are not buffered so there is no need for flow +// control or throttling. +// +// Parameters: +// - THIS_PORTID : The local port-ID of this control port +// +// Signals: +// - s_axis_ctrl_* : Input control stream (AXI-Stream) for responses +// - m_axis_ctrl_* : Output control stream (AXI-Stream) for requests +// - ctrlport_req_* : Control-port master request port +// - ctrlport_resp_* : Control-port master response port + +module axis_ctrl_master #( + parameter [9:0] THIS_PORTID = 10'd0 +)( + // Clock and reset + input wire clk, + input wire rst, + // AXIS-Control Bus (Response) + input wire [31:0] s_axis_ctrl_tdata, + input wire s_axis_ctrl_tlast, + input wire s_axis_ctrl_tvalid, + output wire s_axis_ctrl_tready, + // AXIS-Control Bus (Request) + output reg [31:0] m_axis_ctrl_tdata, + output wire m_axis_ctrl_tlast, + output wire m_axis_ctrl_tvalid, + input wire m_axis_ctrl_tready, + // Control Port Endpoint (Request) + input wire ctrlport_req_wr, + input wire ctrlport_req_rd, + input wire [19:0] ctrlport_req_addr, + input wire [9:0] ctrlport_req_portid, + input wire [15:0] ctrlport_req_rem_epid, + input wire [9:0] ctrlport_req_rem_portid, + input wire [31:0] ctrlport_req_data, + input wire [3:0] ctrlport_req_byte_en, + input wire ctrlport_req_has_time, + input wire [63:0] ctrlport_req_time, + // Control Port Endpoint (Response) + output wire ctrlport_resp_ack, + output wire [1:0] ctrlport_resp_status, + output wire [31:0] ctrlport_resp_data +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // State Machine + // --------------------------------------------------- + localparam [3:0] ST_IDLE = 4'd0; // Waiting for a request on slave ctrlport + localparam [3:0] ST_REQ_HDR_LO = 4'd1; // Sending AXIS-Control request header (low bits) + localparam [3:0] ST_REQ_HDR_HI = 4'd2; // Sending AXIS-Control request header (high bits) + localparam [3:0] ST_REQ_TS_LO = 4'd3; // Sending AXIS-Control request timestamp (low bits) + localparam [3:0] ST_REQ_TS_HI = 4'd4; // Sending AXIS-Control request timestamp (high bits) + localparam [3:0] ST_REQ_OP_WORD = 4'd5; // Sending AXIS-Control request operation word + localparam [3:0] ST_REQ_OP_DATA = 4'd6; // Sending AXIS-Control request data word + localparam [3:0] ST_RESP_HDR_LO = 4'd7; // Receiving AXIS-Control response header (low bits) + localparam [3:0] ST_RESP_HDR_HI = 4'd8; // Receiving AXIS-Control response header (high bits) + localparam [3:0] ST_RESP_TS_LO = 4'd9; // Receiving AXIS-Control response timestamp (low bits) + localparam [3:0] ST_RESP_TS_HI = 4'd10; // Receiving AXIS-Control response timestamp (high bits) + localparam [3:0] ST_RESP_OP_WORD = 4'd11; // Receiving AXIS-Control response operation word + localparam [3:0] ST_RESP_OP_DATA = 4'd12; // Receiving AXIS-Control response data word + localparam [3:0] ST_SHORT_PKT_ERR = 4'd13; // Response was too short. Send a dummy response on ctrlport + localparam [3:0] ST_DROP_LONG_PKT = 4'd14; // Response was too long. Dump the rest of the packet + + // State variables + reg [3:0] state = ST_IDLE; // Current state for FSM + reg [5:0] seq_num = 6'd0; // Expected seqnum for response + // Request state + reg [3:0] req_opcode; // Cached opcode for transaction request + reg [19:0] req_addr; // Cached address for transaction request + reg [9:0] req_portid; // Cached port ID for transaction request + reg [15:0] req_rem_epid; // Cached remote endpoint ID for transaction request + reg [9:0] req_rem_portid; // Cached remote port ID for transaction request + reg [31:0] req_data; // Cached data word for transaction request + reg [3:0] req_byte_en; // Cached byte enable for transaction request + reg req_has_time; // Cached has_time bit for transaction request + reg [63:0] req_time; // Cached timestamp for transaction request + // Response state + reg resp_has_time; // Does the response have a timestamp? + reg [1:0] resp_status; // The status in the response + reg resp_seq_err, resp_cmd_err; // Error bits for the response + + always @(posedge clk) begin + if (rst) begin + state <= ST_IDLE; + seq_num <= 6'd0; + end else begin + case (state) + + // Ready to receive a request on ctrlport + // ------------------------------------ + ST_IDLE: begin + if (ctrlport_req_wr | ctrlport_req_rd) begin + // A transaction was posted on the slave ctrlport... + // Cache the opcode + if (ctrlport_req_wr & ctrlport_req_rd) + req_opcode <= AXIS_CTRL_OPCODE_WRITE_READ; + else if (ctrlport_req_rd) + req_opcode <= AXIS_CTRL_OPCODE_READ; + else + req_opcode <= AXIS_CTRL_OPCODE_WRITE; + // Cache transaction info + req_addr <= ctrlport_req_addr; + req_portid <= ctrlport_req_portid; + req_rem_epid <= ctrlport_req_rem_epid; + req_rem_portid <= ctrlport_req_rem_portid; + req_data <= ctrlport_req_data; + req_byte_en <= ctrlport_req_byte_en; + req_has_time <= ctrlport_req_has_time; + req_time <= ctrlport_req_time; + // Start sending out AXIS-Ctrl packet + state <= ST_REQ_HDR_LO; + end + end + + // Send a request AXIS comand + // (a state for each stage in the packet) + // ------------------------------------ + ST_REQ_HDR_LO: begin + if (m_axis_ctrl_tready) + state <= ST_REQ_HDR_HI; + end + ST_REQ_HDR_HI: begin + if (m_axis_ctrl_tready) + state <= req_has_time ? ST_REQ_TS_LO : ST_REQ_OP_WORD; + end + ST_REQ_TS_LO: begin + if (m_axis_ctrl_tready) + state <= ST_REQ_TS_HI; + end + ST_REQ_TS_HI: begin + if (m_axis_ctrl_tready) + state <= ST_REQ_OP_WORD; + end + ST_REQ_OP_WORD: begin + if (m_axis_ctrl_tready) + state <= ST_REQ_OP_DATA; + end + ST_REQ_OP_DATA: begin + if (m_axis_ctrl_tready) + state <= ST_RESP_HDR_LO; + end + + // Receive a response AXIS comand + // (a state for each stage in the packet) + // ------------------------------------ + ST_RESP_HDR_LO: begin + if (s_axis_ctrl_tvalid) begin + // Remeber if the packet is supposed to have a timestamp + resp_has_time <= axis_ctrl_get_has_time(s_axis_ctrl_tdata); + // Check for a sequence error + resp_seq_err <= (axis_ctrl_get_seq_num(s_axis_ctrl_tdata) != seq_num); + // Assert a command error if: + // - The port ID does not match + // - The response was too short (the next check) + resp_cmd_err <= (axis_ctrl_get_dst_port(s_axis_ctrl_tdata) != THIS_PORTID); + if (!s_axis_ctrl_tlast) begin + state <= ST_RESP_HDR_HI; + end else begin + // Response was too short + resp_cmd_err <= 1'b1; + state <= ST_SHORT_PKT_ERR; + end + end + end + ST_RESP_HDR_HI: begin + if (s_axis_ctrl_tvalid) begin + if (!s_axis_ctrl_tlast) begin + state <= resp_has_time ? ST_RESP_TS_LO : ST_RESP_OP_WORD; + end else begin + // Response was too short + resp_cmd_err <= 1'b1; + state <= ST_SHORT_PKT_ERR; + end + end + end + ST_RESP_TS_LO: begin + if (s_axis_ctrl_tvalid) begin + if (!s_axis_ctrl_tlast) begin + state <= ST_RESP_TS_HI; + end else begin + // Response was too short + resp_cmd_err <= 1'b1; + state <= ST_SHORT_PKT_ERR; + end + end + end + ST_RESP_TS_HI: begin + if (s_axis_ctrl_tvalid) begin + if (!s_axis_ctrl_tlast) begin + state <= ST_RESP_OP_WORD; + end else begin + // Response was too short + resp_cmd_err <= 1'b1; + state <= ST_SHORT_PKT_ERR; + end + end + end + ST_RESP_OP_WORD: begin + if (s_axis_ctrl_tvalid) begin + if (!s_axis_ctrl_tlast) begin + // Assert a command error if opcode and addr in request does not match response + resp_cmd_err <= resp_cmd_err || + (axis_ctrl_get_opcode(s_axis_ctrl_tdata) != req_opcode) || + (axis_ctrl_get_address(s_axis_ctrl_tdata) != req_addr); + resp_status <= axis_ctrl_get_status(s_axis_ctrl_tdata); + state <= ST_RESP_OP_DATA; + end else begin + // Response was too short + resp_cmd_err <= 1'b1; + state <= ST_SHORT_PKT_ERR; + end + end + end + ST_RESP_OP_DATA: begin + if (s_axis_ctrl_tvalid) begin + // If the packet was too long then just drop the rest without complaining + state <= s_axis_ctrl_tlast ? ST_IDLE : ST_DROP_LONG_PKT; + seq_num <= seq_num + 6'd1; + end + end + + // Error handling states + // ------------------------------------ + ST_SHORT_PKT_ERR: begin + state <= ST_IDLE; + end + ST_DROP_LONG_PKT: begin + if (s_axis_ctrl_tvalid && s_axis_ctrl_tlast) + state <= ST_IDLE; + end + + default: begin + // We should never get here + state <= ST_IDLE; + end + endcase + end + end + + // Logic to drive m_axis_ctrl_* + // ------------------------------------ + always @(*) begin + case (state) + ST_REQ_HDR_LO: begin + m_axis_ctrl_tdata = axis_ctrl_build_hdr_lo( + 1'b0 /* is_ack*/, req_has_time, seq_num, + 4'd1 /* num_data */, THIS_PORTID, req_portid); + end + ST_REQ_HDR_HI: begin + m_axis_ctrl_tdata = axis_ctrl_build_hdr_hi( + req_rem_portid, req_rem_epid); + end + ST_REQ_TS_LO: begin + m_axis_ctrl_tdata = req_time[31:0]; + end + ST_REQ_TS_HI: begin + m_axis_ctrl_tdata = req_time[63:32]; + end + ST_REQ_OP_WORD: begin + m_axis_ctrl_tdata = axis_ctrl_build_op_word( + AXIS_CTRL_STS_OKAY, req_opcode, req_byte_en, req_addr); + end + ST_REQ_OP_DATA: begin + m_axis_ctrl_tdata = req_data; + end + default: begin + m_axis_ctrl_tdata = 32'h0; + end + endcase + end + assign m_axis_ctrl_tvalid = (state == ST_REQ_HDR_LO) || + (state == ST_REQ_HDR_HI) || + (state == ST_REQ_TS_LO) || + (state == ST_REQ_TS_HI) || + (state == ST_REQ_OP_WORD) || + (state == ST_REQ_OP_DATA); + assign m_axis_ctrl_tlast = (state == ST_REQ_OP_DATA); + + // Logic to backpressure responses + // ------------------------------------ + assign s_axis_ctrl_tready = (state == ST_RESP_HDR_LO) || + (state == ST_RESP_HDR_HI) || + (state == ST_RESP_TS_LO) || + (state == ST_RESP_TS_HI) || + (state == ST_RESP_OP_WORD) || + (state == ST_RESP_OP_DATA) || + (state == ST_DROP_LONG_PKT); + + // Logic to drive Control-port response + // ------------------------------------ + assign ctrlport_resp_ack = (state == ST_RESP_OP_DATA && s_axis_ctrl_tvalid) || + (state == ST_SHORT_PKT_ERR); + assign ctrlport_resp_status = resp_cmd_err ? AXIS_CTRL_STS_CMDERR : + (resp_seq_err ? AXIS_CTRL_STS_WARNING : resp_status); + assign ctrlport_resp_data = (state == ST_SHORT_PKT_ERR) ? 32'h0 : s_axis_ctrl_tdata; + +endmodule // axis_ctrl_master diff --git a/fpga/usrp3/lib/rfnoc/core/axis_ctrl_slave.v b/fpga/usrp3/lib/rfnoc/core/axis_ctrl_slave.v new file mode 100644 index 000000000..558d21be2 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/axis_ctrl_slave.v @@ -0,0 +1,333 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_ctrl_slave +// Description: +// This module implements an AXIS-Control slave (and a Control-Port +// master). Requests are accepted on the slave axis port and responses +// are sent out on the master axis port. This module implements the +// following operations: {SLEEP, READ, WRITE}. All other operations +// will be treated as a nop and the output will throw a CMDERR. +// +// Parameters: +// None +// +// Signals: +// - s_axis_ctrl_* : Input control stream (AXI-Stream) for requests +// - m_axis_ctrl_* : Output control stream (AXI-Stream) for responses +// - ctrlport_req_* : Control-port master request port +// - ctrlport_resp_* : Control-port master response port + +module axis_ctrl_slave ( + // CHDR Bus (master and slave) + input wire clk, + input wire rst, + // AXIS-Control Bus (Request) + input wire [31:0] s_axis_ctrl_tdata, + input wire s_axis_ctrl_tlast, + input wire s_axis_ctrl_tvalid, + output wire s_axis_ctrl_tready, + // AXIS-Control Bus (Response) + output wire [31:0] m_axis_ctrl_tdata, + output wire m_axis_ctrl_tlast, + output wire m_axis_ctrl_tvalid, + input wire m_axis_ctrl_tready, + // Control Port Endpoint (Request) + output wire ctrlport_req_wr, + output wire ctrlport_req_rd, + output wire [19:0] ctrlport_req_addr, + output wire [31:0] ctrlport_req_data, + output wire [3:0] ctrlport_req_byte_en, + output wire ctrlport_req_has_time, + output wire [63:0] ctrlport_req_time, + // Control Port Endpoint (Response) + input wire ctrlport_resp_ack, + input wire [1:0] ctrlport_resp_status, + input wire [31:0] ctrlport_resp_data +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Width converters + // --------------------------------------------------- + // Convert 32-bit messages to 64 bits for ease of handling + // and buffering. Convert back to 32 bits. + + wire [63:0] in64_tdata; + wire [1:0] in64_tkeep; + wire in64_tlast, in64_tvalid; + reg in64_tready; + + axis_width_conv #( + .WORD_W(32), .IN_WORDS(1), .OUT_WORDS(2), + .SYNC_CLKS(1), .PIPELINE("OUT") + ) upsizer_i ( + .s_axis_aclk(clk), .s_axis_rst(rst), + .s_axis_tdata(s_axis_ctrl_tdata), .s_axis_tkeep(1'b1), + .s_axis_tlast(s_axis_ctrl_tlast), + .s_axis_tvalid(s_axis_ctrl_tvalid), .s_axis_tready(s_axis_ctrl_tready), + .m_axis_aclk(clk), .m_axis_rst(rst), + .m_axis_tdata(in64_tdata), .m_axis_tkeep(in64_tkeep), + .m_axis_tlast(in64_tlast), + .m_axis_tvalid(in64_tvalid), .m_axis_tready(in64_tready) + ); + + reg [63:0] out64_tdata; + wire [1:0] out64_tkeep; + reg out64_tvalid; + wire out64_tlast, out64_terror, out64_tready; + + wire [63:0] out64_gt_tdata; + wire [1:0] out64_gt_tkeep; + wire out64_gt_tlast, out64_gt_tvalid, out64_gt_tready; + + // The header of the response packet is generated + // immediately when a request is received but the data + // comes much later. The packet gate will smooth out the + // outgoing responses. + + axi_packet_gate #( + .WIDTH(66), .SIZE(4) + ) gate_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata({out64_tkeep, out64_tdata}), .i_tlast(out64_tlast), + .i_terror(out64_terror), + .i_tvalid(out64_tvalid), .i_tready(out64_tready), + .o_tdata({out64_gt_tkeep, out64_gt_tdata}), .o_tlast(out64_gt_tlast), + .o_tvalid(out64_gt_tvalid), .o_tready(out64_gt_tready) + ); + + axis_width_conv #( + .WORD_W(32), .IN_WORDS(2), .OUT_WORDS(1), + .SYNC_CLKS(1), .PIPELINE("IN") + ) downsizer_i ( + .s_axis_aclk(clk), .s_axis_rst(rst), + .s_axis_tdata(out64_gt_tdata), .s_axis_tkeep(out64_gt_tkeep), + .s_axis_tlast(out64_gt_tlast), + .s_axis_tvalid(out64_gt_tvalid), .s_axis_tready(out64_gt_tready), + .m_axis_aclk(clk), .m_axis_rst(rst), + .m_axis_tdata(m_axis_ctrl_tdata), .m_axis_tkeep(/*unused*/), + .m_axis_tlast(m_axis_ctrl_tlast), + .m_axis_tvalid(m_axis_ctrl_tvalid), .m_axis_tready(m_axis_ctrl_tready) + ); + + // --------------------------------------------------- + // Transaction Processor + // --------------------------------------------------- + + localparam [2:0] ST_IN_HDR = 3'd0; // Transferring input header to output + localparam [2:0] ST_IN_TS = 3'd1; // Transferring input timestamp to output + localparam [2:0] ST_IN_OP_WORD = 3'd2; // Processing input control word + localparam [2:0] ST_WAIT_FOR_ACK = 3'd3; // Waiting for a ctrlport response + localparam [2:0] ST_SLEEP = 3'd4; // Idle state for sleep operation + localparam [2:0] ST_OUT_OP_WORD = 3'd5; // Outputing control word after respose receipt + localparam [2:0] ST_MORE_DATA = 3'd6; // Control word is too long. Passing extra data forward + localparam [2:0] ST_DROP = 3'd7; // Something went wrong. Drop the current packet + + // State variables + reg [2:0] state = ST_IN_HDR; // Current state of FSM + reg [31:0] sleep_cntr = 32'd0; // Counter to count sleep cycles + reg cached_has_time = 1'b0; // Cached "has_time" bit for input transaction request + reg [63:0] cached_time; // Cached timestamp for input transaction request + reg [1:0] resp_status; // Status for outgoing response + reg [31:0] resp_data; // Data for outgoing response + + // Sleep is an internal operation + wire ctrlport_req_sleep; + + // Shortcuts (transaction request header) + wire is_ack = axis_ctrl_get_is_ack (in64_tdata[31:0] ); + wire has_time = axis_ctrl_get_has_time (in64_tdata[31:0] ); + wire [5:0] seq_num = axis_ctrl_get_seq_num (in64_tdata[31:0] ); + wire [3:0] num_data = axis_ctrl_get_num_data (in64_tdata[31:0] ); + wire [9:0] src_port = axis_ctrl_get_src_port (in64_tdata[31:0] ); + wire [9:0] dst_port = axis_ctrl_get_dst_port (in64_tdata[31:0] ); + wire [9:0] rem_dst_port = axis_ctrl_get_rem_dst_port(in64_tdata[63:32]); + wire [15:0] rem_dst_epid = axis_ctrl_get_rem_dst_epid(in64_tdata[63:32]); + wire malformed = (is_ack || num_data == 4'd0); + // Shortcuts (transaction request op-word) + wire [19:0] xact_address = axis_ctrl_get_address(in64_tdata[31:0]); + wire [3:0] xact_byte_en = axis_ctrl_get_byte_en(in64_tdata[31:0]); + wire [3:0] xact_opcode = axis_ctrl_get_opcode (in64_tdata[31:0]); + wire [31:0] xact_data = in64_tdata[63:32]; + + always @(posedge clk) begin + if (rst) begin + state <= ST_IN_HDR; + end else begin + case (state) + + // Receive an AXIS-Control request + // (a state for each stage in the packet) + // Except for the OP_WORD stage, the appropriate response + // line is also pushed to the output + // ------------------------------------ + ST_IN_HDR: begin + if (in64_tvalid && in64_tready) begin + cached_has_time <= has_time; + if (!in64_tlast) begin + if (malformed) // Malformed packet. Drop. + state <= ST_DROP; + else if (has_time) // Pkt has a timestamp + state <= ST_IN_TS; + else // Pkt has no timestamp + state <= ST_IN_OP_WORD; + end else begin + // Premature termination + // out64_terror will be asserted to cancel the outgoing response + state <= ST_IN_HDR; + end + end + end + ST_IN_TS: begin + if (in64_tvalid && in64_tready) begin + cached_time <= in64_tdata; + if (!in64_tlast) begin + state <= ST_IN_OP_WORD; + end else begin + // Premature termination + // out64_terror will be asserted to cancel the outgoing response + state <= ST_IN_HDR; + end + end + end + ST_IN_OP_WORD: begin + if (in64_tvalid) begin + if (ctrlport_req_sleep) begin + state <= ST_SLEEP; + sleep_cntr <= xact_data; + end else if (ctrlport_req_rd | ctrlport_req_wr) begin + state <= ST_WAIT_FOR_ACK; + end else begin + // Treat all other operations as a NOP (1 cycle sleep) + state <= ST_SLEEP; + sleep_cntr <= 32'd0; + resp_status <= AXIS_CTRL_STS_CMDERR; + end + end + end + + // Hold the input bus to implement a sleep + // ------------------------------------ + ST_SLEEP: begin + if (sleep_cntr == 32'd0) begin + state <= ST_OUT_OP_WORD; + resp_data <= xact_data; + // We could get to this state for an invalid opcode so + // only update the status if this is a legit sleep op + if (xact_opcode == AXIS_CTRL_OPCODE_SLEEP) + resp_status <= AXIS_CTRL_STS_OKAY; + end else begin + sleep_cntr <= sleep_cntr - 32'd1; + end + end + + // Wait for a response on the master ctrlport + // ------------------------------------ + ST_WAIT_FOR_ACK: begin + if (ctrlport_resp_ack) begin + resp_status <= ctrlport_resp_status; + if (xact_opcode == AXIS_CTRL_OPCODE_READ || + xact_opcode == AXIS_CTRL_OPCODE_WRITE_READ) + resp_data <= ctrlport_resp_data; + else + resp_data <= xact_data; + state <= ST_OUT_OP_WORD; + end + end + + // Send the AXIS-Control response data + // ------------------------------------ + ST_OUT_OP_WORD: begin + if (in64_tvalid && in64_tready) begin + state <= in64_tlast ? ST_IN_HDR : ST_MORE_DATA; + end + end + + // Framing error handlers + // ------------------------------------ + ST_MORE_DATA: begin + if (in64_tvalid && in64_tready && in64_tlast) + state <= ST_IN_HDR; + end + ST_DROP: begin + if (in64_tvalid && in64_tready && in64_tlast) + state <= ST_IN_HDR; + end + + default: begin + // We should never get here + state <= ST_IN_HDR; + end + endcase + end + end + + always @(*) begin + case (state) + ST_IN_HDR: begin // Swap src/dst and add resp flag when passing header + in64_tready = out64_tready; + out64_tdata = { + axis_ctrl_build_hdr_hi(rem_dst_port, rem_dst_epid), + axis_ctrl_build_hdr_lo(1'b1, has_time, seq_num, num_data, dst_port, src_port) + }; + out64_tvalid = in64_tvalid && !malformed; + end + ST_IN_TS: begin // Pass input to the output without modification + in64_tready = out64_tready; + out64_tdata = in64_tdata; + out64_tvalid = in64_tvalid; + end + ST_OUT_OP_WORD: begin // Update status and data when passing op-word + in64_tready = out64_tready; + out64_tdata = { + resp_data, + axis_ctrl_build_op_word(resp_status, xact_opcode, xact_byte_en, xact_address) + }; + out64_tvalid = in64_tvalid; + end + ST_MORE_DATA: begin // Pass input to the output without modification + in64_tready = out64_tready; + out64_tdata = in64_tdata; + out64_tvalid = in64_tvalid; + end + ST_DROP: begin // Consume input but don't produce output + in64_tready = 1'b1; + out64_tdata = 64'h0; + out64_tvalid = 1'b0; + end + default: begin // State machine is waiting. Don't produce output + in64_tready = 1'b0; + out64_tdata = 64'h0; + out64_tvalid = 1'b0; + end + endcase + end + + assign out64_tlast = in64_tlast; + assign out64_tkeep = in64_tkeep; + assign out64_terror = (state == ST_IN_HDR || state == ST_IN_TS) && in64_tlast; //Premature termination + + // Control-port request signals + assign ctrlport_req_sleep = in64_tvalid && (state == ST_IN_OP_WORD) && + (xact_opcode == AXIS_CTRL_OPCODE_SLEEP); + assign ctrlport_req_wr = in64_tvalid && (state == ST_IN_OP_WORD) && + (xact_opcode == AXIS_CTRL_OPCODE_WRITE || + xact_opcode == AXIS_CTRL_OPCODE_WRITE_READ); + assign ctrlport_req_rd = in64_tvalid && (state == ST_IN_OP_WORD) && + (xact_opcode == AXIS_CTRL_OPCODE_READ || + xact_opcode == AXIS_CTRL_OPCODE_WRITE_READ); + assign ctrlport_req_addr = xact_address; + assign ctrlport_req_byte_en = xact_byte_en; + assign ctrlport_req_data = xact_data; + assign ctrlport_req_has_time = cached_has_time; + assign ctrlport_req_time = cached_time; + +endmodule // axis_ctrl_slave diff --git a/fpga/usrp3/lib/rfnoc/core/axis_data_mdata_to_chdr.v b/fpga/usrp3/lib/rfnoc/core/axis_data_mdata_to_chdr.v new file mode 100644 index 000000000..dbeb35d08 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/axis_data_mdata_to_chdr.v @@ -0,0 +1,603 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_data_mdata_to_chdr +// +// Description: +// +// A framer module for CHDR data packets. It accepts an input data stream +// (with sideband information for packet flags and timestamp) and a separate +// metadata stream. A data packet and a metadata packet are required to be +// input in order for a single CHDR packet to be generated. If no metadata is +// associated with the payload, then an empty metadata packet must be input +// along with the data packet (i.e., input a metadata packet with +// s_axis_mdata_tkeep set to 0). +// +// The sideband information (e.g., timestamp, flags) must be input coincident +// with the AXI-Stream data input and will be sampled coincident with the +// last word of data in the packet (i.e., when tlast is asserted). +// +// This module also performs an optional clock crossing and data width +// conversion from a user requested width for the payload bus to CHDR_W. +// +// In order to guarantee a gapless CHDR data stream, the metadata packet +// should be input before the end of the data packet, although this is not +// required. +// +// Parameters: +// +// CHDR_W : Width of the input CHDR bus in bits +// ITEM_W : Width of the output item bus in bits +// NIPC : The number of output items delivered per cycle +// SYNC_CLKS : Are the CHDR and data clocks synchronous to each other? +// MTU : Log2 of the maximum packet size in CHDR words +// INFO_FIFO_SIZE : Log2 of the info FIFO size. This determines the number of +// packets that can be simultaneously buffered in the +// payload FIFO. +// PYLD_FIFO_SIZE : Log2 of the payload FIFO size. The actual FIFO size will +// be the maximum of 2**MTU or 2**PYLD_FIFO_SIZE, since the +// FIFO must be at least one MTU so that we can calculate +// the packet length in the header. +// +// Signals: +// +// m_axis_chdr_* : Output CHDR stream +// s_axis_* : Input data stream (AXI-Stream) +// s_axis_mdata_* : Input metadata stream (AXI-Stream) +// flush_* : Signals for flush control and status +// + +module axis_data_mdata_to_chdr #( + parameter CHDR_W = 256, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter SYNC_CLKS = 0, + parameter MTU = 10, + parameter INFO_FIFO_SIZE = 4, + parameter PYLD_FIFO_SIZE = MTU +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + input wire axis_data_clk, + input wire axis_data_rst, + // CHDR out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Payload data stream in (AXI-Stream) + input wire [(ITEM_W*NIPC)-1:0] s_axis_tdata, + input wire [NIPC-1:0] s_axis_tkeep, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + // Payload sideband info + input wire [63:0] s_axis_ttimestamp, + input wire s_axis_thas_time, + input wire s_axis_teov, + input wire s_axis_teob, + // Metadata stream in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_mdata_tdata, + input wire s_axis_mdata_tlast, + input wire s_axis_mdata_tkeep, + input wire s_axis_mdata_tvalid, + output wire s_axis_mdata_tready, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + // Make sure the metadata FIFO is large enough to store an entire packet's + // worth of metadata (32 words). + localparam MDATA_FIFO_SIZE = 5; + + // Make sure the payload FIFO is large enough to store an entire packet's + // worth of payload data. This will ensure that we can buffer the entire + // packet to calculate its length. + localparam PAYLOAD_FIFO_SIZE = PYLD_FIFO_SIZE > MTU ? + PYLD_FIFO_SIZE : MTU; + + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + + //--------------------------------------------------------------------------- + // Timestamp and Flags Capture + //--------------------------------------------------------------------------- + // + // The timestamp and flags that we use for each packet is that of the last + // data word. Here, we capture this information at the end of the packet. + // + //--------------------------------------------------------------------------- + + reg [63:0] packet_timestamp; + reg packet_has_time; + reg packet_eov; + reg packet_eob; + + always @(posedge axis_data_clk) begin + if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin + packet_timestamp <= s_axis_ttimestamp; + packet_has_time <= s_axis_thas_time; + packet_eov <= s_axis_teov; + packet_eob <= s_axis_teob; + end + end + + + //--------------------------------------------------------------------------- + // Length Counters + //--------------------------------------------------------------------------- + // + // Here We track the state of the incoming packet to determine the payload + // and mdata length. + // + //--------------------------------------------------------------------------- + + localparam HDR_LEN = CHDR_W/8; // Length of CHDR header word in bytes + + reg [15:0] packet_length; + reg [15:0] length_count = HDR_LEN; + reg in_pkt_info_tvalid = 0; + wire in_pkt_info_tready; + + always @(posedge axis_data_clk) begin : pkt_length_counter + if (axis_data_rst) begin + length_count <= HDR_LEN; + in_pkt_info_tvalid <= 1'b0; + end else begin : pkt_length_counter_main + // Calculate the length of this word in bytes, taking tkeep into account + integer i; + integer num_bytes; + num_bytes = 0; + for (i = 0; i < NIPC; i = i + 1) begin + num_bytes = num_bytes + (s_axis_tkeep[i]*(ITEM_W/8)); + end + + // Update the packet length if the word is accepted + in_pkt_info_tvalid <= 1'b0; + if (s_axis_tvalid && s_axis_tready) begin + if (s_axis_tlast) begin + length_count <= HDR_LEN; + packet_length <= length_count + num_bytes; + in_pkt_info_tvalid <= 1'b1; + end else begin + length_count <= length_count + num_bytes; + end + end + end + end + + + reg [4:0] num_mdata = 0; + reg [4:0] mdata_count = 0; + reg in_mdata_info_tvalid = 0; + wire in_mdata_info_tready; + + always @(posedge axis_data_clk) begin : num_mdata_counter + if (axis_data_rst) begin + mdata_count <= 0; + num_mdata <= 0; + in_mdata_info_tvalid <= 1'b0; + end else begin : num_mdata_counter_main + // Update the mdata length if the word is accepted + in_mdata_info_tvalid <= 1'b0; + if (s_axis_mdata_tvalid && s_axis_mdata_tready) begin + if (s_axis_mdata_tlast) begin + mdata_count <= 0; + num_mdata <= mdata_count + s_axis_mdata_tkeep; + in_mdata_info_tvalid <= 1'b1; + end else begin + mdata_count <= mdata_count + s_axis_mdata_tkeep; + end + end + end + end + + + //--------------------------------------------------------------------------- + // Data Width Converter (ITEM_W*NIPC => CHDR_W) + //--------------------------------------------------------------------------- + + wire [CHDR_W-1:0] in_pyld_tdata; + wire in_pyld_tlast; + wire in_pyld_tvalid; + wire in_pyld_tready; + wire width_conv_tready; + + assign width_conv_tready = in_pyld_tready & in_pkt_info_tready; + + generate + if (NIPC != CHDR_W/ITEM_W) begin : gen_axis_width_conv + axis_width_conv #( + .WORD_W (ITEM_W), + .IN_WORDS (NIPC), + .OUT_WORDS (CHDR_W/ITEM_W), + .SYNC_CLKS (1), + .PIPELINE ("IN") + ) payload_width_conv_i ( + .s_axis_aclk (axis_data_clk), + .s_axis_rst (axis_data_rst), + .s_axis_tdata (s_axis_tdata), + .s_axis_tkeep ({NIPC{1'b1}}), + .s_axis_tlast (s_axis_tlast), + .s_axis_tvalid (s_axis_tvalid), + .s_axis_tready (s_axis_tready), + .m_axis_aclk (axis_data_clk), + .m_axis_rst (axis_data_rst), + .m_axis_tdata (in_pyld_tdata), + .m_axis_tkeep (), + .m_axis_tlast (in_pyld_tlast), + .m_axis_tvalid (in_pyld_tvalid), + .m_axis_tready (width_conv_tready) + ); + end else begin : no_gen_axis_width_conv + assign in_pyld_tdata = s_axis_tdata; + assign in_pyld_tlast = s_axis_tlast; + assign in_pyld_tvalid = s_axis_tvalid; + assign s_axis_tready = width_conv_tready; + end + endgenerate + + + //--------------------------------------------------------------------------- + // Input FIFOs + //--------------------------------------------------------------------------- + // + // Buffer the data, packet info, metadata, and cross it into the CHDR clock + // domain, if needed. The payload FIFO is sized to match the MTU so that an + // entire packet can be buffered while the length is calculated. + // + //--------------------------------------------------------------------------- + + wire [CHDR_W-1:0] out_mdata_tdata, out_pyld_tdata; + wire out_mdata_tlast, out_pyld_tlast; + wire out_mdata_tvalid, out_pyld_tvalid; + reg out_mdata_tready, out_pyld_tready; + + wire out_pkt_info_tvalid; + reg out_pkt_info_tready; + wire out_eob, out_eov, out_has_time; + wire [63:0] out_timestamp; + wire [15:0] out_length; + + wire [4:0] out_num_mdata; + reg out_mdata_info_tready; + wire out_mdata_info_tvalid; + + wire in_mdata_tready; + + + assign s_axis_mdata_tready = in_mdata_tready & in_mdata_info_tready; + + generate if (SYNC_CLKS) begin : gen_sync_fifo + axi_fifo #( + .WIDTH (CHDR_W+1), + .SIZE (PAYLOAD_FIFO_SIZE) + ) pyld_fifo ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .clear (1'b0), + .i_tdata ({in_pyld_tlast, in_pyld_tdata}), + .i_tvalid (in_pyld_tvalid), + .i_tready (in_pyld_tready), + .o_tdata ({out_pyld_tlast, out_pyld_tdata}), + .o_tvalid (out_pyld_tvalid), + .o_tready (out_pyld_tready), + .space (), + .occupied () + ); + axi_fifo #( + .WIDTH (CHDR_W + 1), + .SIZE (MDATA_FIFO_SIZE) + ) mdata_fifo ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .clear (1'b0), + .i_tdata ({s_axis_mdata_tlast, s_axis_mdata_tdata}), + .i_tvalid (s_axis_mdata_tvalid), + .i_tready (in_mdata_tready), + .o_tdata ({out_mdata_tlast, out_mdata_tdata}), + .o_tvalid (out_mdata_tvalid), + .o_tready (out_mdata_tready), + .space (), + .occupied () + ); + axi_fifo #( + .WIDTH (3 + 64 + 16), + .SIZE (INFO_FIFO_SIZE) + ) pkt_info_fifo ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .clear (1'b0), + .i_tdata ({packet_eob, packet_eov, packet_has_time,packet_timestamp, packet_length}), + .i_tvalid (in_pkt_info_tvalid), + .i_tready (in_pkt_info_tready), + .o_tdata ({out_eob, out_eov, out_has_time, out_timestamp, out_length}), + .o_tvalid (out_pkt_info_tvalid), + .o_tready (out_pkt_info_tready), + .space (), + .occupied () + ); + axi_fifo #( + .WIDTH (5), + .SIZE (INFO_FIFO_SIZE) + ) mdata_info_fifo ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .clear (1'b0), + .i_tdata (num_mdata), + .i_tvalid (in_mdata_info_tvalid), + .i_tready (in_mdata_info_tready), + .o_tdata (out_num_mdata), + .o_tvalid (out_mdata_info_tvalid), + .o_tready (out_mdata_info_tready), + .space (), + .occupied () + ); + + end else begin : gen_async_fifo + axi_fifo_2clk #( + .WIDTH (CHDR_W + 1), + .SIZE (PAYLOAD_FIFO_SIZE) + ) pyld_fifo ( + .reset (axis_data_rst), + .i_aclk (axis_data_clk), + .i_tdata ({in_pyld_tlast, in_pyld_tdata}), + .i_tvalid (in_pyld_tvalid), + .i_tready (in_pyld_tready), + .o_aclk (axis_chdr_clk), + .o_tdata ({out_pyld_tlast, out_pyld_tdata}), + .o_tvalid (out_pyld_tvalid), + .o_tready (out_pyld_tready) + ); + axi_fifo_2clk #( + .WIDTH (CHDR_W + 1), + .SIZE (MDATA_FIFO_SIZE) + ) mdata_fifo ( + .reset (axis_data_rst), + .i_aclk (axis_data_clk), + .i_tdata ({s_axis_mdata_tlast, s_axis_mdata_tdata}), + .i_tvalid (s_axis_mdata_tvalid), + .i_tready (in_mdata_tready), + .o_aclk (axis_chdr_clk), + .o_tdata ({out_mdata_tlast, out_mdata_tdata}), + .o_tvalid (out_mdata_tvalid), + .o_tready (out_mdata_tready) + ); + axi_fifo_2clk #( + .WIDTH (3 + 64 + 16), + .SIZE (INFO_FIFO_SIZE) + ) pkt_info_fifo ( + .reset (axis_data_rst), + .i_aclk (axis_data_clk), + .i_tdata ({packet_eob, packet_eov, packet_has_time,packet_timestamp, packet_length}), + .i_tvalid (in_pkt_info_tvalid), + .i_tready (in_pkt_info_tready), + .o_aclk (axis_chdr_clk), + .o_tdata ({out_eob, out_eov, out_has_time, out_timestamp, out_length}), + .o_tvalid (out_pkt_info_tvalid), + .o_tready (out_pkt_info_tready) + ); + axi_fifo_2clk #( + .WIDTH (5), + .SIZE (INFO_FIFO_SIZE) + ) mdata_info_fifo ( + .reset (axis_data_rst), + .i_aclk (axis_data_clk), + .i_tdata (num_mdata), + .i_tvalid (in_mdata_info_tvalid), + .i_tready (in_mdata_info_tready), + .o_aclk (axis_chdr_clk), + .o_tdata (out_num_mdata), + .o_tvalid (out_mdata_info_tvalid), + .o_tready (out_mdata_info_tready) + ); + end endgenerate + + + //--------------------------------------------------------------------------- + // Output State Machine + //--------------------------------------------------------------------------- + + reg [CHDR_W-1:0] chdr_pf_tdata; + reg chdr_pf_tlast, chdr_pf_tvalid; + wire chdr_pf_tready; + + localparam [1:0] ST_HDR = 0; // Processing the output CHDR header + localparam [1:0] ST_TS = 1; // Processing the output CHDR timestamp + localparam [1:0] ST_MDATA = 2; // Processing the output CHDR metadata word + localparam [1:0] ST_PYLD = 3; // Processing the output CHDR payload word + + reg [1:0] state = ST_HDR; + + reg [15:0] seq_num = 0; + + wire [63:0] header; + reg [63:0] timestamp; + wire [15:0] length; + reg has_mdata; + + // Some the payload, metadata, and timestamp lengths (out_length already + // includes the header). + assign length = (CHDR_W > 64) ? + out_length + out_num_mdata * (CHDR_W/8) : + out_length + out_num_mdata * (CHDR_W/8) + 8*out_has_time; + + // Build the header word + assign header = chdr_build_header( + 6'b0, // vc + out_eob, // eob + out_eov, // eov + out_has_time ? CHDR_PKT_TYPE_DATA_TS : + CHDR_PKT_TYPE_DATA, // pkt_type + out_num_mdata, // num_mdata + seq_num, // seq_num + length, // length + 16'b0 // dst_epid + ); + + always @(posedge axis_chdr_clk) begin + if (axis_chdr_rst) begin + state <= ST_HDR; + seq_num <= 0; + end else begin + case (state) + + // ST_HDR: CHDR Header + // ------------------- + ST_HDR: begin + timestamp <= out_timestamp; + has_mdata <= (out_num_mdata != CHDR_NO_MDATA); + + if (out_pkt_info_tvalid && out_mdata_info_tvalid && chdr_pf_tready) begin + if (CHDR_W > 64) begin + // When CHDR_W > 64, the timestamp is a part of the header word. + // If this is a data packet (with or without a TS), we skip the + // timestamp state move directly to metadata/body. + if (out_num_mdata == CHDR_NO_MDATA) begin + state <= ST_PYLD; + end else begin + state <= ST_MDATA; + end + end else begin + // When CHDR_W == 64, the timestamp comes after the header. Check + // if this is a data packet with a timestamp or metadata to + // figure out the next state. + if (out_has_time) begin + state <= ST_TS; + end else if (out_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_PYLD; + end + end + end + end + + // ST_TS: Timestamp (CHDR_W == 64 only) + // ------------------------------------ + ST_TS: begin + if (chdr_pf_tready) begin + state <= has_mdata ? ST_MDATA : ST_PYLD; + end + end + + // ST_MDATA: Metadata word + // ----------------------- + ST_MDATA: begin + if (out_mdata_tvalid && out_mdata_tready && out_mdata_tlast) begin + state <= ST_PYLD; + end + end + + // ST_PYLD: Payload word + // --------------------- + ST_PYLD: begin + if (out_pyld_tvalid && out_pyld_tready && out_pyld_tlast) begin + state <= ST_HDR; + seq_num <= seq_num + 1; + end + end + + default: begin + // We should never get here + state <= ST_HDR; + end + endcase + end + end + + always @(*) begin + case (state) + ST_HDR: begin + // Insert header word + chdr_pf_tdata = (CHDR_W > 64) ? { out_timestamp, header } : header; + chdr_pf_tvalid = out_pkt_info_tvalid & out_mdata_info_tvalid; + chdr_pf_tlast = 1'b0; + out_mdata_tready = chdr_pf_tready & // Remove empty mdata packet from FIFO + (out_num_mdata == CHDR_NO_MDATA); + out_mdata_info_tready = chdr_pf_tready; // Remove mdata info word from FIFO + out_pyld_tready = 1'b0; + out_pkt_info_tready = chdr_pf_tready; // Remove packet info word from FIFO + end + ST_TS: begin + // Insert timestamp + chdr_pf_tdata[63:0] = timestamp; + chdr_pf_tvalid = 1'b1; // Timestamp register is always valid in this state + chdr_pf_tlast = 1'b0; + out_mdata_tready = 1'b0; + out_mdata_info_tready = 1'b0; + out_pyld_tready = 1'b0; + out_pkt_info_tready = 1'b0; + end + ST_MDATA: begin + // Insert mdata words + chdr_pf_tdata = out_mdata_tdata; + chdr_pf_tvalid = out_mdata_tvalid; + chdr_pf_tlast = 1'b0; + out_mdata_tready = chdr_pf_tready; + out_mdata_info_tready = 1'b0; + out_pyld_tready = 1'b0; + out_pkt_info_tready = 1'b0; + end + ST_PYLD: begin + // Insert payload words + chdr_pf_tdata = out_pyld_tdata; + chdr_pf_tvalid = out_pyld_tvalid; + chdr_pf_tlast = out_pyld_tlast; + out_mdata_tready = 1'b0; + out_mdata_info_tready = 1'b0; + out_pyld_tready = chdr_pf_tready; + out_pkt_info_tready = 1'b0; + end + default: begin + chdr_pf_tdata = out_pyld_tdata; + chdr_pf_tvalid = 1'b0; + chdr_pf_tlast = 1'b0; + out_mdata_tready = 1'b0; + out_mdata_info_tready = 1'b0; + out_pyld_tready = 1'b0; + out_pkt_info_tready = 1'b0; + end + endcase + end + + + //--------------------------------------------------------------------------- + // Flushing Logic + //--------------------------------------------------------------------------- + + axis_packet_flush #( + .WIDTH (CHDR_W), + .FLUSH_PARTIAL_PKTS (0), + .TIMEOUT_W (32), + .PIPELINE ("IN") + ) chdr_flusher_i ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .enable (flush_en), + .timeout (flush_timeout), + .flushing (flush_active), + .done (flush_done), + .s_axis_tdata (chdr_pf_tdata), + .s_axis_tlast (chdr_pf_tlast), + .s_axis_tvalid (chdr_pf_tvalid), + .s_axis_tready (chdr_pf_tready), + .m_axis_tdata (m_axis_chdr_tdata), + .m_axis_tlast (m_axis_chdr_tlast), + .m_axis_tvalid (m_axis_chdr_tvalid), + .m_axis_tready (m_axis_chdr_tready) + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v b/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v new file mode 100644 index 000000000..6a5b3ce05 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v @@ -0,0 +1,452 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_data_to_chdr +// +// Description: +// +// A framer module for CHDR data packets. It accepts an input data stream +// with sideband information for packet flags and timestamp). A CHDR packet +// will be generated for each data packet that is input. +// +// The sideband information (e.g., timestamp, flags) must be input coincident +// with the AXI-Stream data input and will be sampled coincident with the +// last word of data in the packet (i.e., when tlast is asserted). +// +// This module also performs an optional clock crossing and data width +// conversion from a user requested width for the payload bus to CHDR_W. +// +// Parameters: +// +// CHDR_W : Width of the input CHDR bus in bits +// ITEM_W : Width of the output item bus in bits +// NIPC : The number of output items delivered per cycle +// SYNC_CLKS : Are the CHDR and data clocks synchronous to each other? +// MTU : Log2 of the maximum packet size in CHDR words +// INFO_FIFO_SIZE : Log2 of the info FIFO size. This determines the number of +// packets that can be simultaneously buffered in the +// payload FIFO. +// PYLD_FIFO_SIZE : Log2 of the payload FIFO size. The actual FIFO size will +// be the maximum of 2**MTU or 2**PYLD_FIFO_SIZE, since the +// FIFO must be at least one MTU so that we can calculate +// the packet length in the header. +// +// Signals: +// +// m_axis_chdr_* : Output CHDR stream +// s_axis_* : Input data stream (AXI-Stream) +// flush_* : Signals for flush control and status +// + +module axis_data_to_chdr #( + parameter CHDR_W = 256, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter SYNC_CLKS = 0, + parameter MTU = 10, + parameter INFO_FIFO_SIZE = 5, + parameter PYLD_FIFO_SIZE = MTU +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + input wire axis_data_clk, + input wire axis_data_rst, + // CHDR out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Payload data stream in (AXI-Stream) + input wire [(ITEM_W*NIPC)-1:0] s_axis_tdata, + input wire [NIPC-1:0] s_axis_tkeep, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + // Payload sideband info + input wire [63:0] s_axis_ttimestamp, + input wire s_axis_thas_time, + input wire s_axis_teov, + input wire s_axis_teob, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + // Make sure the payload FIFO is large enough to store an entire packet's + // worth of payload data. This will ensure that we can buffer the entire + // packet to calculate its length. + localparam PAYLOAD_FIFO_SIZE = PYLD_FIFO_SIZE > MTU ? + PYLD_FIFO_SIZE : MTU; + + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + + //--------------------------------------------------------------------------- + // Timestamp and Flags Capture + //--------------------------------------------------------------------------- + // + // The timestamp and flags that we use for each packet is that of the last + // data word. Here, we capture this information at the end of the packet. + // + //--------------------------------------------------------------------------- + + reg [63:0] packet_timestamp; + reg packet_has_time; + reg packet_eov; + reg packet_eob; + + always @(posedge axis_data_clk) begin + if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin + packet_timestamp <= s_axis_ttimestamp; + packet_has_time <= s_axis_thas_time; + packet_eov <= s_axis_teov; + packet_eob <= s_axis_teob; + end + end + + + //--------------------------------------------------------------------------- + // Length Counters + //--------------------------------------------------------------------------- + // + // Here We track the state of the incoming packet to determine the payload + // length. + // + //--------------------------------------------------------------------------- + + localparam HDR_LEN = CHDR_W/8; // Length of CHDR header word in bytes + + reg [15:0] packet_length; + reg [15:0] length_count = HDR_LEN; + reg in_pkt_info_tvalid = 0; + wire in_pkt_info_tready; + + always @(posedge axis_data_clk) begin : pkt_length_counter + if (axis_data_rst) begin + length_count <= HDR_LEN; + in_pkt_info_tvalid <= 1'b0; + end else begin : pkt_length_counter_main + // Calculate the length of this word in bytes, taking tkeep into account + integer i; + integer num_bytes; + num_bytes = 0; + for (i = 0; i < NIPC; i = i + 1) begin + num_bytes = num_bytes + (s_axis_tkeep[i]*(ITEM_W/8)); + end + + // Update the packet length if the word is accepted + in_pkt_info_tvalid <= 1'b0; + if (s_axis_tvalid && s_axis_tready) begin + if (s_axis_tlast) begin + length_count <= HDR_LEN; + packet_length <= length_count + num_bytes; + in_pkt_info_tvalid <= 1'b1; + end else begin + length_count <= length_count + num_bytes; + end + end + end + end + + + //--------------------------------------------------------------------------- + // Data Width Converter (ITEM_W*NIPC => CHDR_W) + //--------------------------------------------------------------------------- + + wire [CHDR_W-1:0] in_pyld_tdata; + wire in_pyld_tlast; + wire in_pyld_tvalid; + wire in_pyld_tready; + wire width_conv_tready; + + assign width_conv_tready = in_pyld_tready & in_pkt_info_tready; + + generate + if (NIPC != CHDR_W/ITEM_W) begin : gen_axis_width_conv + axis_width_conv #( + .WORD_W (ITEM_W), + .IN_WORDS (NIPC), + .OUT_WORDS (CHDR_W/ITEM_W), + .SYNC_CLKS (1), + .PIPELINE ("IN") + ) payload_width_conv_i ( + .s_axis_aclk (axis_data_clk), + .s_axis_rst (axis_data_rst), + .s_axis_tdata (s_axis_tdata), + .s_axis_tkeep ({NIPC{1'b1}}), + .s_axis_tlast (s_axis_tlast), + .s_axis_tvalid (s_axis_tvalid), + .s_axis_tready (s_axis_tready), + .m_axis_aclk (axis_data_clk), + .m_axis_rst (axis_data_rst), + .m_axis_tdata (in_pyld_tdata), + .m_axis_tkeep (), + .m_axis_tlast (in_pyld_tlast), + .m_axis_tvalid (in_pyld_tvalid), + .m_axis_tready (width_conv_tready) + ); + end else begin : no_gen_axis_width_conv + assign in_pyld_tdata = s_axis_tdata; + assign in_pyld_tlast = s_axis_tlast; + assign in_pyld_tvalid = s_axis_tvalid; + assign s_axis_tready = width_conv_tready; + end + endgenerate + + + //--------------------------------------------------------------------------- + // Input FIFOs + //--------------------------------------------------------------------------- + // + // Buffer the data, packet info, metadata, and cross it into the CHDR clock + // domain, if needed. The payload FIFO is sized to match the MTU so that an + // entire packet can be buffered while the length is calculated. + // + //--------------------------------------------------------------------------- + + wire [CHDR_W-1:0] out_pyld_tdata; + wire out_pyld_tlast; + wire out_pyld_tvalid; + reg out_pyld_tready; + + wire out_pkt_info_tvalid; + reg out_pkt_info_tready; + wire out_eob, out_eov, out_has_time; + wire [63:0] out_timestamp; + wire [15:0] out_length; + + generate if (SYNC_CLKS) begin : gen_sync_fifo + axi_fifo #( + .WIDTH (CHDR_W+1), + .SIZE (PAYLOAD_FIFO_SIZE) + ) pyld_fifo ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .clear (1'b0), + .i_tdata ({in_pyld_tlast, in_pyld_tdata}), + .i_tvalid (in_pyld_tvalid), + .i_tready (in_pyld_tready), + .o_tdata ({out_pyld_tlast, out_pyld_tdata}), + .o_tvalid (out_pyld_tvalid), + .o_tready (out_pyld_tready), + .space (), + .occupied () + ); + axi_fifo #( + .WIDTH (3 + 64 + 16), + .SIZE (INFO_FIFO_SIZE) + ) pkt_info_fifo ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .clear (1'b0), + .i_tdata ({packet_eob, packet_eov, packet_has_time,packet_timestamp, packet_length}), + .i_tvalid (in_pkt_info_tvalid), + .i_tready (in_pkt_info_tready), + .o_tdata ({out_eob, out_eov, out_has_time, out_timestamp, out_length}), + .o_tvalid (out_pkt_info_tvalid), + .o_tready (out_pkt_info_tready), + .space (), + .occupied () + ); + + end else begin : gen_async_fifo + axi_fifo_2clk #( + .WIDTH (CHDR_W + 1), + .SIZE (PAYLOAD_FIFO_SIZE) + ) pyld_fifo ( + .reset (axis_data_rst), + .i_aclk (axis_data_clk), + .i_tdata ({in_pyld_tlast, in_pyld_tdata}), + .i_tvalid (in_pyld_tvalid), + .i_tready (in_pyld_tready), + .o_aclk (axis_chdr_clk), + .o_tdata ({out_pyld_tlast, out_pyld_tdata}), + .o_tvalid (out_pyld_tvalid), + .o_tready (out_pyld_tready) + ); + axi_fifo_2clk #( + .WIDTH (3 + 64 + 16), + .SIZE (INFO_FIFO_SIZE) + ) pkt_info_fifo ( + .reset (axis_data_rst), + .i_aclk (axis_data_clk), + .i_tdata ({packet_eob, packet_eov, packet_has_time,packet_timestamp, packet_length}), + .i_tvalid (in_pkt_info_tvalid), + .i_tready (in_pkt_info_tready), + .o_aclk (axis_chdr_clk), + .o_tdata ({out_eob, out_eov, out_has_time, out_timestamp, out_length}), + .o_tvalid (out_pkt_info_tvalid), + .o_tready (out_pkt_info_tready) + ); + end endgenerate + + + //--------------------------------------------------------------------------- + // Output State Machine + //--------------------------------------------------------------------------- + + reg [CHDR_W-1:0] chdr_pf_tdata; + reg chdr_pf_tlast, chdr_pf_tvalid; + wire chdr_pf_tready; + + localparam [1:0] ST_HDR = 0; // Processing the output CHDR header + localparam [1:0] ST_TS = 1; // Processing the output CHDR timestamp + localparam [1:0] ST_PYLD = 2; // Processing the output CHDR payload word + + reg [1:0] state = ST_HDR; + + reg [15:0] seq_num = 0; + + wire [63:0] header; + reg [63:0] timestamp; + wire [15:0] length; + + // Some the payload, metadata, and timestamp lengths (out_length already + // includes the header). + assign length = (CHDR_W > 64) ? out_length : out_length + 8*out_has_time; + + // Build the header word + assign header = chdr_build_header( + 6'b0, // vc + out_eob, // eob + out_eov, // eov + out_has_time ? CHDR_PKT_TYPE_DATA_TS : + CHDR_PKT_TYPE_DATA, // pkt_type + 0, // num_mdata + seq_num, // seq_num + length, // length + 16'b0 // dst_epid + ); + + always @(posedge axis_chdr_clk) begin + if (axis_chdr_rst) begin + state <= ST_HDR; + seq_num <= 0; + end else begin + case (state) + + // ST_HDR: CHDR Header + // ------------------- + ST_HDR: begin + timestamp <= out_timestamp; + + if (out_pkt_info_tvalid && chdr_pf_tready) begin + seq_num <= seq_num + 1; + + if (CHDR_W > 64) begin + // When CHDR_W > 64, the timestamp is a part of the header word. + // If this is a data packet (with or without a TS), we skip the + // timestamp state move directly to the payload. + state <= ST_PYLD; + end else begin + // When CHDR_W == 64, the timestamp comes after the header. Check + // if this is a data packet with a timestamp to figure out the + // next state. + if (out_has_time) begin + state <= ST_TS; + end else begin + state <= ST_PYLD; + end + end + end + end + + // ST_TS: Timestamp (CHDR_W == 64 only) + // ------------------------------------ + ST_TS: begin + if (chdr_pf_tready) begin + state <= ST_PYLD; + end + end + + // ST_PYLD: Payload word + // --------------------- + ST_PYLD: begin + if (out_pyld_tvalid && out_pyld_tready && out_pyld_tlast) begin + state <= ST_HDR; + end + end + + default: begin + // We should never get here + state <= ST_HDR; + end + endcase + end + end + + always @(*) begin + case (state) + ST_HDR: begin + // Insert header word + chdr_pf_tdata = (CHDR_W > 64) ? { out_timestamp, header } : header; + chdr_pf_tvalid = out_pkt_info_tvalid; + chdr_pf_tlast = 1'b0; + out_pyld_tready = 1'b0; + out_pkt_info_tready = chdr_pf_tready; // Remove packet info word from FIFO + end + ST_TS: begin + // Insert timestamp + chdr_pf_tdata[63:0] = timestamp; + chdr_pf_tvalid = 1'b1; // Timestamp register is always valid in this state + chdr_pf_tlast = 1'b0; + out_pyld_tready = 1'b0; + out_pkt_info_tready = 1'b0; + end + ST_PYLD: begin + // Insert payload words + chdr_pf_tdata = out_pyld_tdata; + chdr_pf_tvalid = out_pyld_tvalid; + chdr_pf_tlast = out_pyld_tlast; + out_pyld_tready = chdr_pf_tready; + out_pkt_info_tready = 1'b0; + end + default: begin + chdr_pf_tdata = out_pyld_tdata; + chdr_pf_tvalid = 1'b0; + chdr_pf_tlast = 1'b0; + out_pyld_tready = 1'b0; + out_pkt_info_tready = 1'b0; + end + endcase + end + + + //--------------------------------------------------------------------------- + // Flushing Logic + //--------------------------------------------------------------------------- + + axis_packet_flush #( + .WIDTH (CHDR_W), + .FLUSH_PARTIAL_PKTS (0), + .TIMEOUT_W (32), + .PIPELINE ("IN") + ) chdr_flusher_i ( + .clk (axis_chdr_clk), + .reset (axis_chdr_rst), + .enable (flush_en), + .timeout (flush_timeout), + .flushing (flush_active), + .done (flush_done), + .s_axis_tdata (chdr_pf_tdata), + .s_axis_tlast (chdr_pf_tlast), + .s_axis_tvalid (chdr_pf_tvalid), + .s_axis_tready (chdr_pf_tready), + .m_axis_tdata (m_axis_chdr_tdata), + .m_axis_tlast (m_axis_chdr_tlast), + .m_axis_tvalid (m_axis_chdr_tvalid), + .m_axis_tready (m_axis_chdr_tready) + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/core/axis_pyld_ctxt_to_chdr.v b/fpga/usrp3/lib/rfnoc/core/axis_pyld_ctxt_to_chdr.v new file mode 100644 index 000000000..c73d7f365 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/axis_pyld_ctxt_to_chdr.v @@ -0,0 +1,463 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_pyld_ctxt_to_chdr +// Description: +// A header framer module for CHDR data packets. +// Accepts an input payload and context stream, and produces an +// output CHDR stream. +// This module also performs an optional clock crossing and data +// width convertion from a user requested width for the +// payload bus to CHDR_W. +// Context and data packets must be interleaved i.e. a context packet +// must arrive before its corresponding data packet. However, if +// context prefetching is enabled, the context for the next packet +// may arrive before the data for the current packet has been +// consumed. In the case of a rate reduction, this allows the module +// to sustain a gapless stream of payload items and a bursty +// sideband context path. +// +// Parameters: +// - CHDR_W: Width of the input CHDR bus in bits +// - ITEM_W: Width of the output item bus in bits +// - NIPC: The number of output items delievered per cycle +// - SYNC_CLKS: Are the CHDR and data clocks synchronous to each other? +// - CONTEXT_FIFO_SIZE: FIFO size for the context path +// - PAYLOAD_FIFO_SIZE: FIFO size for the payload path +// - MTU: Log2 of the maximum packet size in words +// - CONTEXT_PREFETCH_EN: Is context prefetching enabled? +// +// Signals: +// - s_axis_payload_* : Input payload stream (AXI-Stream) +// - s_axis_context_* : Input context stream (AXI-Stream) +// - s_axis_chdr_* : Output CHDR stream (AXI-Stream) +// - framer_errors : Number of framer errors (dropped packets) +// - flush_* : Signals for flush control and status +// + +module axis_pyld_ctxt_to_chdr #( + parameter CHDR_W = 256, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter SYNC_CLKS = 0, + parameter CONTEXT_FIFO_SIZE = 1, + parameter PAYLOAD_FIFO_SIZE = 1, + parameter MTU = 9, + parameter CONTEXT_PREFETCH_EN = 1 +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + input wire axis_data_clk, + input wire axis_data_rst, + // CHDR in (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Payload stream out (AXI-Stream) + input wire [(ITEM_W*NIPC)-1:0] s_axis_payload_tdata, + input wire [NIPC-1:0] s_axis_payload_tkeep, + input wire s_axis_payload_tlast, + input wire s_axis_payload_tvalid, + output wire s_axis_payload_tready, + // Context stream out (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_context_tdata, + input wire [3:0] s_axis_context_tuser, + input wire s_axis_context_tlast, + input wire s_axis_context_tvalid, + output wire s_axis_context_tready, + // Status + output reg [31:0] framer_errors, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Intput State Machine + // --------------------------------------------------- + reg [2:0] ctxt_pkt_cnt = 3'd0, pyld_pkt_cnt = 3'd0; + // A payload packet can pass only if it is preceeded by a context packet + wire pass_pyld = ((ctxt_pkt_cnt - pyld_pkt_cnt) > 3'd0); + // A context packet has to be blocked if its corresponding payload packet hasn't passed except + // when prefetching is enabled. In that case one additional context packet is allowed to pass + wire pass_ctxt = ((ctxt_pkt_cnt - pyld_pkt_cnt) < (CONTEXT_PREFETCH_EN == 1 ? 3'd2 : 3'd1)); + + always @(posedge axis_data_clk) begin + if (axis_data_rst) begin + ctxt_pkt_cnt <= 3'd0; + pyld_pkt_cnt <= 3'd0; + end else begin + if (s_axis_context_tvalid && s_axis_context_tready && s_axis_context_tlast) + ctxt_pkt_cnt <= ctxt_pkt_cnt + 3'd1; + if (s_axis_payload_tvalid && s_axis_payload_tready && s_axis_payload_tlast) + pyld_pkt_cnt <= pyld_pkt_cnt + 3'd1; + end + end + + wire tmp_ctxt_tvalid, tmp_ctxt_tready; + wire tmp_pyld_tvalid, tmp_pyld_tready; + + assign tmp_ctxt_tvalid = s_axis_context_tvalid && pass_ctxt; + assign tmp_pyld_tvalid = s_axis_payload_tvalid && pass_pyld; + assign s_axis_context_tready = tmp_ctxt_tready && pass_ctxt; + assign s_axis_payload_tready = tmp_pyld_tready && pass_pyld; + + // --------------------------------------------------- + // Data Width Converter: ITEM_W*NIPC => CHDR_W + // --------------------------------------------------- + wire [CHDR_W-1:0] in_pyld_tdata; + wire in_pyld_tlast; + wire in_pyld_tvalid; + wire in_pyld_tready; + + axis_width_conv #( + .WORD_W(ITEM_W), .IN_WORDS(NIPC), .OUT_WORDS(CHDR_W/ITEM_W), + .SYNC_CLKS(1), .PIPELINE("IN") + ) payload_width_conv_i ( + .s_axis_aclk(axis_data_clk), .s_axis_rst(axis_data_rst), + .s_axis_tdata(s_axis_payload_tdata), + .s_axis_tkeep(s_axis_payload_tkeep), + .s_axis_tlast(s_axis_payload_tlast), + .s_axis_tvalid(tmp_pyld_tvalid), + .s_axis_tready(tmp_pyld_tready), + .m_axis_aclk(axis_data_clk), .m_axis_rst(axis_data_rst), + .m_axis_tdata(in_pyld_tdata), + .m_axis_tkeep(/* unused */), + .m_axis_tlast(in_pyld_tlast), + .m_axis_tvalid(in_pyld_tvalid), + .m_axis_tready(in_pyld_tready) + ); + + // --------------------------------------------------- + // Payload and Context FIFOs + // --------------------------------------------------- + wire [CHDR_W-1:0] out_ctxt_tdata , out_pyld_tdata ; + wire [3:0] out_ctxt_tuser; + wire out_ctxt_tlast , out_pyld_tlast ; + wire out_ctxt_tvalid, out_pyld_tvalid; + reg out_ctxt_tready, out_pyld_tready; + + generate if (SYNC_CLKS) begin + axi_fifo #(.WIDTH(CHDR_W+4+1), .SIZE(CONTEXT_FIFO_SIZE)) ctxt_fifo_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), .clear(1'b0), + .i_tdata({s_axis_context_tlast, s_axis_context_tuser, s_axis_context_tdata}), + .i_tvalid(tmp_ctxt_tvalid), .i_tready(tmp_ctxt_tready), + .o_tdata({out_ctxt_tlast, out_ctxt_tuser, out_ctxt_tdata}), + .o_tvalid(out_ctxt_tvalid), .o_tready(out_ctxt_tready), + .space(), .occupied() + ); + axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(PAYLOAD_FIFO_SIZE)) pyld_fifo_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), .clear(1'b0), + .i_tdata({in_pyld_tlast, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_tdata({out_pyld_tlast, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready), + .space(), .occupied() + ); + end else begin + axi_fifo_2clk #(.WIDTH(CHDR_W+4+1), .SIZE(CONTEXT_FIFO_SIZE)) ctxt_fifo_i ( + .reset(axis_data_rst), + .i_aclk(axis_data_clk), + .i_tdata({s_axis_context_tlast, s_axis_context_tuser, s_axis_context_tdata}), + .i_tvalid(tmp_ctxt_tvalid), .i_tready(tmp_ctxt_tready), + .o_aclk(axis_chdr_clk), + .o_tdata({out_ctxt_tlast, out_ctxt_tuser, out_ctxt_tdata}), + .o_tvalid(out_ctxt_tvalid), .o_tready(out_ctxt_tready) + ); + axi_fifo_2clk #(.WIDTH(CHDR_W+1), .SIZE(PAYLOAD_FIFO_SIZE)) pyld_fifo_i ( + .reset(axis_data_rst), + .i_aclk(axis_data_clk), + .i_tdata({in_pyld_tlast, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_aclk(axis_chdr_clk), + .o_tdata({out_pyld_tlast, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready) + ); + end endgenerate + + // --------------------------------------------------- + // Output State Machine + // --------------------------------------------------- + wire [CHDR_W-1:0] chdr_pg_tdata; + reg chdr_pg_tlast, chdr_pg_tvalid; + wire chdr_pg_terror, chdr_pg_tready; + + localparam [2:0] ST_HDR = 3'd0; // Processing the output CHDR header + localparam [2:0] ST_TS = 3'd1; // Processing the output CHDR timestamp + localparam [2:0] ST_MDATA = 3'd2; // Processing the output CHDR metadata word + localparam [2:0] ST_BODY = 3'd3; // Processing the output CHDR payload word + localparam [2:0] ST_DROP_CTXT = 3'd4; // Something went wrong... Dropping context packet + localparam [2:0] ST_DROP_PYLD = 3'd5; // Something went wrong... Dropping payload packet + localparam [2:0] ST_TERMINATE = 3'd6; // Something went wrong... Rejecting output packet + + reg [2:0] state = ST_HDR; + reg [4:0] mdata_pending = 5'd0; + + // Shortcuts: CHDR header + wire [2:0] out_pkt_type = chdr_get_pkt_type(out_ctxt_tdata[63:0]); + wire [4:0] out_num_mdata = chdr_get_num_mdata(out_ctxt_tdata[63:0]); + + always @(posedge axis_chdr_clk) begin + if (axis_chdr_rst) begin + state <= ST_HDR; + framer_errors <= 32'd0; + end else begin + case (state) + + // ST_HDR: CHDR Header + // ------------------- + ST_HDR: begin + if (out_ctxt_tvalid && out_ctxt_tready) begin + mdata_pending <= out_num_mdata; + if (CHDR_W > 64) begin + // When CHDR_W > 64, the timestamp is a part of the header word. + // If this is a data packet (with/without a TS), we skip the TS state + // move directly to metadata/body + if (out_num_mdata != CHDR_NO_MDATA) begin + if (!out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_HDR_TS) + state <= ST_MDATA; // tlast should be low. Move to metadata. + else + state <= ST_DROP_CTXT; // Malformed packet: Wrong tuser. Drop ctxt+pyld + else + state <= ST_DROP_PYLD; // Premature tlast. Drop pyld + end else begin + if (out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_HDR_TS) + state <= ST_BODY; // tlast should be high. Move to payload. + else + state <= ST_DROP_PYLD; // Malformed packet: Wrong tuser. Drop pyld + else + state <= ST_DROP_CTXT; // Malformed packet: extra context lines. Drop ctxt+pyld + end + end else begin + // When CHDR_W == 64, the timestamp comes after the header. Check if this is a data + // packet with a TS to figure out the next state. If no TS, then check for metadata + // to move to the next state. Drop any non-data packets. + if (out_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + if (!out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_HDR) + state <= ST_TS; // tlast should be low. Move to timestamp. + else + state <= ST_DROP_CTXT; // Malformed packet: Wrong tuser. Drop ctxt+pyld + else + state <= ST_DROP_PYLD; // Premature tlast. Drop pyld + end else begin + if (out_num_mdata != CHDR_NO_MDATA) begin + if (!out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_HDR) + state <= ST_MDATA; // tlast should be low. Move to metadata. + else + state <= ST_DROP_CTXT; // Malformed packet: Wrong tuser. Drop ctxt+pyld + else + state <= ST_DROP_PYLD; // Premature tlast. Drop pyld + end else begin + if (out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_HDR) + state <= ST_BODY; // tlast should be high. Move to payload. + else + state <= ST_DROP_PYLD; // Malformed packet: Wrong tuser. Drop pyld + else + state <= ST_DROP_CTXT; // Malformed packet: extra context lines. Drop ctxt+pyld + end + end + end + end + end + + // ST_TS: Timestamp (CHDR_W == 64 only) + // ------------------------------------ + ST_TS: begin + if (out_ctxt_tvalid && out_ctxt_tready) begin + if (mdata_pending != CHDR_NO_MDATA) begin + if (!out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_TS) + state <= ST_MDATA; // tlast should be low. Move to metadata. + else + state <= ST_DROP_CTXT; // Malformed packet: Wrong tuser. Drop ctxt+pyld + else + state <= ST_DROP_PYLD; // Premature tlast. Drop pyld + end else begin + if (out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_TS) + state <= ST_BODY; // tlast should be high. Move to payload. + else + state <= ST_DROP_PYLD; // Malformed packet: Wrong tuser. Drop pyld + else + state <= ST_DROP_CTXT; // Malformed packet: extra context lines. Drop ctxt+pyld + end + end + end + + // ST_MDATA: Metadata word + // ----------------------- + ST_MDATA: begin + if (out_ctxt_tvalid && out_ctxt_tready) begin + if (mdata_pending != 5'd1) begin + mdata_pending <= mdata_pending - 'd1; + if (!out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_MDATA) + state <= ST_MDATA; // tlast should be low. Continue processing metadata. + else + state <= ST_DROP_CTXT; // Malformed packet: Wrong tuser. Drop ctxt+pyld + else + state <= ST_DROP_PYLD; // Premature tlast. Drop pyld + end else begin + if (out_ctxt_tlast) + if (out_ctxt_tuser == CONTEXT_FIELD_MDATA) + state <= ST_BODY; // tlast should be high. Move to payload. + else + state <= ST_DROP_PYLD; // Malformed packet: Wrong tuser. Drop pyld + else + state <= ST_DROP_CTXT; // Malformed packet: extra context lines. Drop ctxt+pyld + end + end + end + + // ST_BODY: Payload word + // --------------------- + ST_BODY: begin + if (out_pyld_tvalid && out_pyld_tready) begin + state <= out_pyld_tlast ? ST_HDR : ST_BODY; + end + end + + // ST_DROP_CTXT: Drop current context packet + // ----------------------------------------- + ST_DROP_CTXT: begin + if (out_ctxt_tvalid && out_ctxt_tready) begin + state <= out_ctxt_tlast ? ST_DROP_PYLD : ST_DROP_CTXT; + end + end + + // ST_DROP_PYLD: Drop current payload packet + // ----------------------------------------- + ST_DROP_PYLD: begin + if (out_pyld_tvalid && out_pyld_tready) begin + state <= out_pyld_tlast ? ST_TERMINATE : ST_DROP_PYLD; + end + end + + // ST_TERMINATE: Drop partial output packet + // ---------------------------------------- + ST_TERMINATE: begin + if (chdr_pg_tready) begin + state <= ST_HDR; + framer_errors <= framer_errors + 32'd1; + end + end + + default: begin + // We should never get here + state <= ST_HDR; + end + endcase + end + end + + + always @(*) begin + case (state) + ST_HDR: begin + // A context word passes fwd to the CHDR output + chdr_pg_tvalid = out_ctxt_tvalid; + chdr_pg_tlast = 1'b0; // tlast is inherited from the data stream + out_ctxt_tready = chdr_pg_tready; + out_pyld_tready = 1'b0; + end + ST_TS: begin + // A context word passes fwd to the CHDR output + chdr_pg_tvalid = out_ctxt_tvalid; + chdr_pg_tlast = 1'b0; // tlast is inherited from the data stream + out_ctxt_tready = chdr_pg_tready; + out_pyld_tready = 1'b0; + end + ST_MDATA: begin + // A context word passes fwd to the CHDR output + chdr_pg_tvalid = out_ctxt_tvalid; + chdr_pg_tlast = 1'b0; // tlast is inherited from the data stream + out_ctxt_tready = chdr_pg_tready; + out_pyld_tready = 1'b0; + end + ST_BODY: begin + // A payload word passes fwd to the CHDR output + chdr_pg_tvalid = out_pyld_tvalid; + chdr_pg_tlast = out_pyld_tlast; + out_ctxt_tready = 1'b0; + out_pyld_tready = chdr_pg_tready; + end + ST_DROP_CTXT: begin + // A context word is consumed without passing fwd + chdr_pg_tvalid = 1'b0; + chdr_pg_tlast = 1'b0; + out_ctxt_tready = 1'b1; + out_pyld_tready = 1'b0; + end + ST_DROP_PYLD: begin + // A payload word is consumed without passing fwd + chdr_pg_tvalid = 1'b0; + chdr_pg_tlast = 1'b0; + out_ctxt_tready = 1'b0; + out_pyld_tready = 1'b1; + end + ST_TERMINATE: begin + // A dummy word with a tlast and terror is passed fwd + // to evacuate the current packet from the packet_gate + chdr_pg_tvalid = 1'b1; + chdr_pg_tlast = 1'b1; + out_ctxt_tready = 1'b0; + out_pyld_tready = 1'b0; + end + default: begin + chdr_pg_tvalid = 1'b0; + chdr_pg_tlast = 1'b0; + out_ctxt_tready = 1'b0; + out_pyld_tready = 1'b0; + end + endcase + end + + assign chdr_pg_tdata = (state == ST_BODY) ? out_pyld_tdata : out_ctxt_tdata; + assign chdr_pg_terror = (state == ST_TERMINATE); + + // --------------------------------------------------- + // Packet gate + // --------------------------------------------------- + + wire [CHDR_W-1:0] chdr_flush_tdata; + wire chdr_flush_tlast, chdr_flush_tvalid; + wire chdr_flush_terror, chdr_flush_tready; + + axis_packet_flush #( + .WIDTH(CHDR_W+1), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("IN") + ) chdr_flusher_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), + .enable(flush_en), .timeout(flush_timeout), + .flushing(flush_active), .done(flush_done), + .s_axis_tdata({chdr_pg_terror, chdr_pg_tdata}), .s_axis_tlast(chdr_pg_tlast), + .s_axis_tvalid(chdr_pg_tvalid), .s_axis_tready(chdr_pg_tready), + .m_axis_tdata({chdr_flush_terror, chdr_flush_tdata}), .m_axis_tlast(chdr_flush_tlast), + .m_axis_tvalid(chdr_flush_tvalid), .m_axis_tready(chdr_flush_tready) + ); + + axi_packet_gate #( .WIDTH(CHDR_W), .SIZE(MTU), .USE_AS_BUFF(0) ) out_gate_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), .clear(flush_active), + .i_tdata(chdr_flush_tdata), .i_tlast(chdr_flush_tlast), .i_terror(chdr_flush_terror), + .i_tvalid(chdr_flush_tvalid), .i_tready(chdr_flush_tready), + .o_tdata(m_axis_chdr_tdata), .o_tlast(m_axis_chdr_tlast), + .o_tvalid(m_axis_chdr_tvalid), .o_tready(m_axis_chdr_tready) + ); + +endmodule // axis_pyld_ctxt_to_chdr diff --git a/fpga/usrp3/lib/rfnoc/core/backend_iface.v b/fpga/usrp3/lib/rfnoc/core/backend_iface.v new file mode 100644 index 000000000..59429ea6b --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/backend_iface.v @@ -0,0 +1,142 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: backend_iface +// Description: +// A noc_shell interface to the backend infrastructure +// + +module backend_iface #( + parameter [31:0] NOC_ID = 32'h0, + parameter [5:0] NUM_DATA_I = 0, + parameter [5:0] NUM_DATA_O = 0, + parameter [5:0] CTRL_FIFOSIZE = 0, + parameter [7:0] CTRL_MAX_ASYNC_MSGS = 0, + parameter [5:0] MTU = 0 +)( + // Input clock + input wire rfnoc_chdr_clk, + input wire rfnoc_ctrl_clk, + // Backend interface (sync. to rfnoc_ctrl_clk) + input wire [511:0] rfnoc_core_config, + output wire [511:0] rfnoc_core_status, + // Output reset + output wire rfnoc_chdr_rst, + output wire rfnoc_ctrl_rst, + // Flush interface (sync. to rfnoc_chdr_clk) + output wire data_i_flush_en, + output wire [31:0] data_i_flush_timeout, + input wire [63:0] data_i_flush_active, + input wire [63:0] data_i_flush_done, + output wire data_o_flush_en, + output wire [31:0] data_o_flush_timeout, + input wire [63:0] data_o_flush_active, + input wire [63:0] data_o_flush_done +); + localparam RESET_LENGTH = 32; + + `include "rfnoc_backend_iface.vh" + + // ----------------------------------- + // CONFIG: Infrastructure => Block + // ----------------------------------- + wire [BEC_TOTAL_WIDTH-1:0] rfnoc_core_config_trim = rfnoc_core_config[BEC_TOTAL_WIDTH-1:0]; + + reg [31:0] flush_timeout_ctclk = 32'd0; + reg flush_en_ctclk = 1'b0; + reg soft_ctrl_rst_ctclk = 1'b0; + reg soft_chdr_rst_ctclk = 1'b0; + + // Register logic before synchronizer + always @(posedge rfnoc_ctrl_clk) begin + flush_timeout_ctclk <= rfnoc_core_config_trim[BEC_FLUSH_TIMEOUT_OFFSET +: BEC_FLUSH_TIMEOUT_WIDTH]; + flush_en_ctclk <= rfnoc_core_config_trim[BEC_FLUSH_EN_OFFSET +: BEC_FLUSH_EN_WIDTH ]; + soft_ctrl_rst_ctclk <= rfnoc_core_config_trim[BEC_SOFT_CTRL_RST_OFFSET +: BEC_SOFT_CTRL_RST_WIDTH]; + soft_chdr_rst_ctclk <= rfnoc_core_config_trim[BEC_SOFT_CHDR_RST_OFFSET +: BEC_SOFT_CHDR_RST_WIDTH]; + end + + // Synchronizer + wire [31:0] flush_timeout_chclk; + wire flush_en_chclk; + + // Note: We are using a synchronizer to cross the 32-bit timeout bus + // into a different clock domain. Typically we would use a 2clk FIFO + // but it's OK to have the bits unsynchronized here because the value + // is static and is set from SW long before it is actually used. + + synchronizer #(.WIDTH(33), .INITIAL_VAL(33'd0)) sync_ctrl_i ( + .clk(rfnoc_chdr_clk), .rst(1'b0), + .in({flush_en_ctclk, flush_timeout_ctclk}), + .out({flush_en_chclk, flush_timeout_chclk}) + ); + + // Synchronize the reset to the CHDR and CTRL clock domains, and extend the + // reset pulse to make it long enough for most IP to reset correctly. + + wire rfnoc_ctrl_rst_pulse; + wire rfnoc_chdr_rst_pulse; + + pulse_synchronizer #(.MODE("POSEDGE")) soft_ctrl_rst_sync_i ( + .clk_a(rfnoc_ctrl_clk), .rst_a(1'b0), .pulse_a(soft_ctrl_rst_ctclk), .busy_a(), + .clk_b(rfnoc_ctrl_clk), .pulse_b(rfnoc_ctrl_rst_pulse) + ); + + pulse_synchronizer #(.MODE("POSEDGE")) soft_chdr_rst_sync_i ( + .clk_a(rfnoc_ctrl_clk), .rst_a(1'b0), .pulse_a(soft_chdr_rst_ctclk), .busy_a(), + .clk_b(rfnoc_chdr_clk), .pulse_b(rfnoc_chdr_rst_pulse) + ); + + pulse_stretch_min #(.LENGTH(RESET_LENGTH)) soft_ctrl_rst_stretch_i ( + .clk(rfnoc_ctrl_clk), .rst(1'b0), + .pulse_in(rfnoc_ctrl_rst_pulse), .pulse_out(rfnoc_ctrl_rst) + ); + + pulse_stretch_min #(.LENGTH(RESET_LENGTH)) soft_chdr_rst_stretch_i ( + .clk(rfnoc_chdr_clk), .rst(1'b0), + .pulse_in(rfnoc_chdr_rst_pulse), .pulse_out(rfnoc_chdr_rst) + ); + + assign data_i_flush_timeout = flush_timeout_chclk; + assign data_o_flush_timeout = flush_timeout_chclk; + assign data_i_flush_en = flush_en_chclk; + assign data_o_flush_en = flush_en_chclk; + + // ----------------------------------- + // STATUS: Block => Infrastructure + // ----------------------------------- + + reg flush_active_chclk = 1'b0; + reg flush_done_chclk = 1'b0; + + // Register logic before synchronizer + wire flush_active_ctclk; + wire flush_done_ctclk; + + always @(posedge rfnoc_chdr_clk) begin + flush_active_chclk <= (|data_i_flush_active[NUM_DATA_I-1:0]) | (|data_o_flush_active[NUM_DATA_O-1:0]); + flush_done_chclk <= (&data_i_flush_done [NUM_DATA_I-1:0]) & (&data_o_flush_done [NUM_DATA_O-1:0]); + end + + // Synchronizer + synchronizer #(.WIDTH(2), .INITIAL_VAL(2'd0)) sync_status_i ( + .clk(rfnoc_ctrl_clk), .rst(1'b0), + .in({flush_active_chclk, flush_done_chclk}), + .out({flush_active_ctclk, flush_done_ctclk}) + ); + + assign rfnoc_core_status[BES_PROTO_VER_OFFSET +:BES_PROTO_VER_WIDTH ] = BACKEND_PROTO_VER; + assign rfnoc_core_status[BES_NUM_DATA_I_OFFSET +:BES_NUM_DATA_I_WIDTH ] = NUM_DATA_I; + assign rfnoc_core_status[BES_NUM_DATA_O_OFFSET +:BES_NUM_DATA_O_WIDTH ] = NUM_DATA_O; + assign rfnoc_core_status[BES_CTRL_FIFOSIZE_OFFSET +:BES_CTRL_FIFOSIZE_WIDTH ] = CTRL_FIFOSIZE; + assign rfnoc_core_status[BES_CTRL_MAX_ASYNC_MSGS_OFFSET+:BES_CTRL_MAX_ASYNC_MSGS_WIDTH] = CTRL_MAX_ASYNC_MSGS; + assign rfnoc_core_status[BES_NOC_ID_OFFSET +:BES_NOC_ID_WIDTH ] = NOC_ID; + assign rfnoc_core_status[BES_FLUSH_ACTIVE_OFFSET +:BES_FLUSH_ACTIVE_WIDTH ] = flush_active_ctclk; + assign rfnoc_core_status[BES_FLUSH_DONE_OFFSET +:BES_FLUSH_DONE_WIDTH ] = flush_done_ctclk; + assign rfnoc_core_status[BES_DATA_MTU_OFFSET +:BES_DATA_MTU_WIDTH ] = MTU; + // Assign the rest to 0 + assign rfnoc_core_status[511:BES_TOTAL_WIDTH] = {(512-BES_TOTAL_WIDTH){1'b0}}; + +endmodule // backend_iface + diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_compute_tkeep.v b/fpga/usrp3/lib/rfnoc/core/chdr_compute_tkeep.v new file mode 100644 index 000000000..72c5bab13 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_compute_tkeep.v @@ -0,0 +1,86 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_compute_tkeep +// Description: +// This module monitors an AXI-Stream CHDR bus and uses the +// packet size field in the CHDR header to compute a tkeep +// trailer signal to indicate the the valid bytes when +// tlast is asserted. +// +// Parameters: +// - CHDR_W: Width of the CHDR bus in bits +// - ITEM_W: Width of the item bus in bits (must be a multiple of 8) +// +// Signals: +// - axis_* : AXI-Stream CHDR bus + +module chdr_compute_tkeep #( + parameter CHDR_W = 256, + parameter ITEM_W = 32 +)( + input wire clk, + input wire rst, + input wire [CHDR_W-1:0] axis_tdata, + input wire axis_tlast, + input wire axis_tvalid, + input wire axis_tready, + output wire [(CHDR_W/ITEM_W)-1:0] axis_tkeep +); + + `include "rfnoc_chdr_utils.vh" + + generate if (CHDR_W > ITEM_W) begin + + localparam CHDR_W_BYTES = CHDR_W/8; + localparam ITEM_W_BYTES = ITEM_W/8; + localparam KEEP_W = CHDR_W_BYTES/ITEM_W_BYTES; + + // Binary to thermometer decoder + // 2'd0 => 4'b1111 (special case) + // 2'd1 => 4'b0001 + // 2'd2 => 4'b0011 + // 2'd3 => 4'b0111 + function [KEEP_W-1:0] bin2thermo; + input [$clog2(KEEP_W)-1:0] bin; + bin2thermo = ~((~1)<<((bin-1)%KEEP_W)); + endfunction + + // Read the packet length and figure out the number + // of trailing items + wire [15:0] pkt_len = chdr_get_length(axis_tdata[63:0]); + wire [KEEP_W-1:0] len_thermo = bin2thermo(pkt_len[$clog2(CHDR_W_BYTES)-1:$clog2(ITEM_W_BYTES)]); + reg [KEEP_W-1:0] reg_len_thermo = 'h0; + reg is_header = 1'b1; + + always @(posedge clk) begin + if (rst) begin + is_header <= 1'b1; + end else if (axis_tvalid & axis_tready) begin + is_header <= axis_tlast; + if (is_header) begin + reg_len_thermo <= len_thermo; + end + end + end + + // tkeep indicates trailing items, so for lines with tlast == 0, + // tkeep is all 1's. + assign axis_tkeep = (~axis_tlast) ? {KEEP_W{1'b1}} : + (is_header ? len_thermo : reg_len_thermo); + + end else if (CHDR_W == ITEM_W) begin + + // Only one item per CHDR word. So always keep it. + assign axis_tkeep = 1'b1; + + end else begin + + // Illegal. A item must be smaller than the CHDR_W + illegal_parameter_value item_w_cannot_be_larger_than_chdr_w(); + + end endgenerate + +endmodule // chdr_compute_tkeep diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_data_swapper.v b/fpga/usrp3/lib/rfnoc/core/chdr_data_swapper.v new file mode 100644 index 000000000..9b5e96bca --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_data_swapper.v @@ -0,0 +1,227 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_data_swapper +// Description: +// A module to adapt a CHDR stream to correctly sequence in +// a software buffer of a user-specified type. Here are the +// the swapping assumptions: +// - The CHDR header, timestamp and metadata for all packet +// types must be interpreted as a uint64_t. +// - All Control, Stream Status/Cmd, Management packet payloads +// must reside in a uint64_t* buffer. +// - The buffer type for the data packet payload and metadata +// is user configurable +// +// Parameters: +// - CHDR_W: Width of the tdata bus in bits +// +// Signals: +// - payload_sw_buff: SW buffer mode for payload (0=u64, 1=u32, 2=u16, 3=u8) +// - mdata_sw_buff : SW buffer mode for metadata (0=u64, 1=u32, 2=u16, 3=u8) +// - s_axis_* : The input AXI stream +// - m_axis_* : The output AXI stream +// + +module chdr_data_swapper #( + parameter CHDR_W = 256 +)( + // Clock and Reset + input wire clk, + input wire rst, + // Software Buffer Mode + input wire [1:0] payload_sw_buff, + input wire [1:0] mdata_sw_buff, + input wire swap_endianness, + // Input AXIS + input wire [CHDR_W-1:0] s_axis_tdata, + input wire s_axis_tlast, + input wire s_axis_tvalid, + output wire s_axis_tready, + // Output AXIS + output wire [CHDR_W-1:0] m_axis_tdata, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready +); + + `include "../core/rfnoc_chdr_utils.vh" + + // *_sw_buff values + localparam [1:0] SW_BUFF_UINT64 = 2'd0; + localparam [1:0] SW_BUFF_UINT32 = 2'd1; + localparam [1:0] SW_BUFF_UINT16 = 2'd2; + localparam [1:0] SW_BUFF_UINT8 = 2'd3; + + localparam SWAP_W = $clog2(CHDR_W); + + // Packet states + localparam [2:0] ST_HDR = 3'd0; + localparam [2:0] ST_TS = 3'd1; + localparam [2:0] ST_MDATA = 3'd2; + localparam [2:0] ST_DATA_BODY = 3'd3; + localparam [2:0] ST_OTHER = 3'd4; + + reg [2:0] state = ST_HDR; + reg [4:0] mdata_pending = CHDR_NO_MDATA; + reg [SWAP_W-2:0] pyld_tswap = 'h0, mdata_tswap = 'h0; + + // Shortcuts: CHDR header + wire [2:0] pkt_type = chdr_get_pkt_type(s_axis_tdata[63:0]); + wire [4:0] num_mdata = chdr_get_num_mdata(s_axis_tdata[63:0]); + + // State machine to determine packet state + always @(posedge clk) begin + if (rst) begin + state <= ST_HDR; + end else if (s_axis_tvalid & s_axis_tready) begin + case (state) + ST_HDR: begin + mdata_pending <= num_mdata; + if (!s_axis_tlast) begin + if (CHDR_W > 64) begin + if (pkt_type == CHDR_PKT_TYPE_DATA || pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + if (num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_DATA_BODY; + end + end else begin + state <= ST_OTHER; + end + end else begin + if (pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + state <= ST_TS; + end else if (pkt_type == CHDR_PKT_TYPE_DATA) begin + if (num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_DATA_BODY; + end + end else begin + state <= ST_OTHER; + end + end + end else begin + state <= ST_HDR; + end + end + ST_TS: begin + if (!s_axis_tlast) begin + if (mdata_pending != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_DATA_BODY; + end + end else begin + state <= ST_HDR; + end + end + ST_MDATA: begin + if (!s_axis_tlast) begin + if (mdata_pending == 5'd1) begin + state <= ST_DATA_BODY; + end else begin + mdata_pending <= mdata_pending - 5'd1; + end + end else begin + state <= ST_HDR; + end + end + ST_DATA_BODY: begin + if (s_axis_tlast) begin + state <= ST_HDR; + end + end + ST_OTHER: begin + if (s_axis_tlast) begin + state <= ST_HDR; + end + end + default: begin + state <= ST_HDR; + end + endcase + end + end + + // Convert SW buff size to swap-lane map + always @(posedge clk) begin + pyld_tswap <= 'h0; + mdata_tswap <= 'h0; + case (payload_sw_buff) + SW_BUFF_UINT8: + pyld_tswap[4:2] <= 3'b111; + SW_BUFF_UINT16: + pyld_tswap[4:2] <= 3'b110; + SW_BUFF_UINT32: + pyld_tswap[4:2] <= 3'b100; + default: + pyld_tswap[4:2] <= 3'b000; + endcase + case (mdata_sw_buff) + SW_BUFF_UINT8: + mdata_tswap[4:2] <= 3'b111; + SW_BUFF_UINT16: + mdata_tswap[4:2] <= 3'b110; + SW_BUFF_UINT32: + mdata_tswap[4:2] <= 3'b100; + default: + mdata_tswap[4:2] <= 3'b000; + endcase + end + + wire [SWAP_W-2:0] s_axis_tswap_dyn = + (state == ST_DATA_BODY) ? pyld_tswap : ( + (state == ST_MDATA) ? mdata_tswap : {(SWAP_W-1){1'b0}} + ); + wire s_axis_tswap_end = swap_endianness && + (state == ST_DATA_BODY || state == ST_MDATA); + + // Swapper that re-aligns items in a buffer for software + wire [CHDR_W-1:0] out_swap_tdata, out_swap_tdata_pre; + wire out_swap_tswap_end, out_swap_tlast, out_swap_tvalid, out_swap_tready; + + axis_data_swap #( + .DATA_W(CHDR_W), .USER_W(1'b1), + .STAGES_EN({{(SWAP_W-6){1'b0}}, 6'b111100}), .DYNAMIC(1) + ) chdr_dyn_swap_i ( + .clk (clk ), + .rst (rst ), + .s_axis_tdata (s_axis_tdata ), + .s_axis_tswap (s_axis_tswap_dyn ), + .s_axis_tuser (s_axis_tswap_end ), + .s_axis_tlast (s_axis_tlast ), + .s_axis_tvalid(s_axis_tvalid ), + .s_axis_tready(s_axis_tready ), + .m_axis_tdata (out_swap_tdata_pre), + .m_axis_tuser (out_swap_tswap_end), + .m_axis_tlast (out_swap_tlast ), + .m_axis_tvalid(out_swap_tvalid ), + .m_axis_tready(out_swap_tready ) + ); + + // Swapper that pre-corrects for transport endianness + genvar i; + generate for (i = 0; i < CHDR_W/8; i=i+1) begin + assign out_swap_tdata[i*8 +: 8] = out_swap_tswap_end ? + out_swap_tdata_pre[((CHDR_W/8)-i-1)*8 +: 8] : out_swap_tdata_pre[i*8 +: 8]; + end endgenerate + + axi_fifo_flop2 #(.WIDTH(CHDR_W+1)) out_reg_i ( + .clk (clk ), + .reset (rst ), + .clear (1'b0 ), + .i_tdata ({out_swap_tlast, out_swap_tdata}), + .i_tvalid(out_swap_tvalid ), + .i_tready(out_swap_tready ), + .o_tdata ({m_axis_tlast, m_axis_tdata} ), + .o_tvalid(m_axis_tvalid ), + .o_tready(m_axis_tready ), + .occupied( ), + .space ( ) + ); + +endmodule // chdr_data_swapper diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_ingress_fifo.v b/fpga/usrp3/lib/rfnoc/core/chdr_ingress_fifo.v new file mode 100644 index 000000000..e2660426f --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_ingress_fifo.v @@ -0,0 +1,95 @@ +// +// Copyright 2016 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module chdr_ingress_fifo #( + parameter WIDTH = 64, + parameter SIZE = 12, + parameter DEVICE = "7SERIES" +) ( + input clk, + input reset, + input clear, + + input [WIDTH-1:0] i_tdata, + input i_tlast, + input i_tvalid, + output i_tready, + + output [WIDTH-1:0] o_tdata, + output o_tlast, + output o_tvalid, + input o_tready +); + + localparam SIZE_THRESHOLD = ( + (DEVICE == "7SERIES") ? 14 : ( + (DEVICE == "VIRTEX6") ? 14 : ( + (DEVICE == "SPARTAN6") ? 12 : ( + 12 + )))); + + wire [WIDTH-1:0] i_tdata_pre; + wire i_tlast_pre, i_tvalid_pre, i_tready_pre; + + // SRL based FIFO to break timing paths to BRAM resources + axi_fifo_flop2 #(.WIDTH(WIDTH+1)) pre_fifo ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata({i_tlast, i_tdata}), .i_tvalid(i_tvalid), .i_tready(i_tready), + .o_tdata({i_tlast_pre, i_tdata_pre}), .o_tvalid(i_tvalid_pre), .o_tready(i_tready_pre), + .space(), .occupied() + ); + + generate + if (SIZE <= SIZE_THRESHOLD) begin + wire [WIDTH-1:0] o_tdata_int; + wire o_tlast_int, o_tvalid_int, o_tready_int; + // Instantiate a single axi_fifo if size is not larger than threshold + axi_fifo #(.WIDTH(WIDTH+1), .SIZE(SIZE)) main_fifo ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata({i_tlast_pre, i_tdata_pre}), .i_tvalid(i_tvalid_pre), .i_tready(i_tready_pre), + .o_tdata({o_tlast_int, o_tdata_int}), .o_tvalid(o_tvalid_int), .o_tready(o_tready_int), + .space(), .occupied() + ); + axi_fifo_flop2 #(.WIDTH(WIDTH+1)) fifo_flop2 ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata({o_tlast_int, o_tdata_int}), .i_tvalid(o_tvalid_int), .i_tready(o_tready_int), + .o_tdata({o_tlast, o_tdata}), .o_tvalid(o_tvalid), .o_tready(o_tready), + .space(), .occupied() + ); + end else begin + // Instantiate a cascade of axi_fifos if size is larger than threshold + localparam CDEPTH = 2**(SIZE - SIZE_THRESHOLD); //Cascade Depth + wire [WIDTH-1:0] c_tdata[CDEPTH:0], int_tdata[CDEPTH-1:0]; + wire c_tlast[CDEPTH:0], c_tvalid[CDEPTH:0], c_tready[CDEPTH:0]; + wire int_tlast[CDEPTH-1:0], int_tvalid[CDEPTH-1:0], int_tready[CDEPTH-1:0]; + + //Connect input to first cascade state + assign {c_tdata[0], c_tlast[0], c_tvalid[0]} = {i_tdata_pre, i_tlast_pre, i_tvalid_pre}; + assign i_tready_pre = c_tready[0]; + //Connect output to last cascade state + assign {o_tdata, o_tlast, o_tvalid} = {c_tdata[CDEPTH], c_tlast[CDEPTH], c_tvalid[CDEPTH]}; + assign c_tready[CDEPTH] = o_tready; + + genvar i; + for (i=0; i<CDEPTH; i=i+1) begin: fifo_stages + axi_fifo #(.WIDTH(WIDTH+1), .SIZE(SIZE_THRESHOLD)) main_fifo ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata({c_tlast[i], c_tdata[i]}), .i_tvalid(c_tvalid[i]), .i_tready(c_tready[i]), + .o_tdata({int_tlast[i], int_tdata[i]}), .o_tvalid(int_tvalid[i]), .o_tready(int_tready[i]), + .space(), .occupied() + ); + axi_fifo_flop2 #(.WIDTH(WIDTH+1)) fifo_flop2 ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata({int_tlast[i], int_tdata[i]}), .i_tvalid(int_tvalid[i]), .i_tready(int_tready[i]), + .o_tdata({c_tlast[i+1], c_tdata[i+1]}), .o_tvalid(c_tvalid[i+1]), .o_tready(c_tready[i+1]), + .space(), .occupied() + ); + end + end + endgenerate + +endmodule // axi_fifo_large diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_mgmt_pkt_handler.v b/fpga/usrp3/lib/rfnoc/core/chdr_mgmt_pkt_handler.v new file mode 100644 index 000000000..f9c56a6e1 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_mgmt_pkt_handler.v @@ -0,0 +1,617 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_mgmt_pkt_handler +// Description: +// This module sits inline on a CHDR stream and adds a management +// node that is discoverable and configurable by software. As a +// management node, a control-port master to configure any slave. +// The output CHDR stream has an additional tdest and tid which can +// be used to make routing decisions for management packets only. +// tid will be CHDR_MGMT_ROUTE_TDEST when tdest should be used. +// +// Parameters: +// - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>} +// - CHDR_W: Width of the CHDR bus in bits +// - USER_W: Width of the user/data bits that accompany an advertisement op +// - RESP_FIFO_SIZE: Log2 of the depth of the response FIFO +// Maximum value = 8 +// +// Signals: +// - s_axis_chdr_* : Input CHDR stream (AXI-Stream) +// - m_axis_chdr_* : Output CHDR stream (AXI-Stream) +// - node_info: Info about the node that contains this management slave +// - ctrlport_* : Control-port master for management peripheral +// - op_*: Strobe and info signals for a mgmt advertisement + +module chdr_mgmt_pkt_handler #( + parameter [15:0] PROTOVER = {8'd1, 8'd0}, + parameter CHDR_W = 256, + parameter USER_W = 1, + parameter [0:0] MGMT_ONLY = 0, + parameter RESP_FIFO_SIZE = 5 +)( + // Clock, reset and settings + input wire clk, + input wire rst, + // Node Info + input wire [47:0] node_info, + // CHDR Data In (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + input wire [USER_W-1:0] s_axis_chdr_tuser, + // CHDR Data Out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire [1:0] m_axis_chdr_tid, // Routing mode. Values defined in rfnoc_chdr_internal_utils.vh + output wire [9:0] m_axis_chdr_tdest, // Manual routing destination (only valid for tid = CHDR_MGMT_ROUTE_TDEST) + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Control port endpoint + output reg ctrlport_req_wr, + output reg ctrlport_req_rd, + output reg [15:0] ctrlport_req_addr, + output reg [31:0] ctrlport_req_data, + input wire ctrlport_resp_ack, + input wire [31:0] ctrlport_resp_data, + // Mgmt packet advertisement strobe + output wire [USER_W-1:0] op_data, + output wire op_stb, + output wire [15:0] op_dst_epid, + output wire [15:0] op_src_epid +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_chdr_internal_utils.vh" + + // --------------------------------------------------- + // Instantiate input demux and output mux to allow + // non-management packets to be bypassed + // --------------------------------------------------- + + localparam CHDR_W_BYTES = CHDR_W / 8; + localparam LOG2_CHDR_W_BYTES = $clog2(CHDR_W_BYTES); + + wire [CHDR_W-1:0] s_mgmt_tdata, m_mgmt_tdata; + wire [USER_W-1:0] s_mgmt_tuser; + wire [9:0] m_mgmt_tdest; + wire [1:0] m_mgmt_tid; + wire s_mgmt_tlast, s_mgmt_tvalid, s_mgmt_tready; + wire m_mgmt_tlast, m_mgmt_tvalid, m_mgmt_tready; + + generate if (!MGMT_ONLY) begin + // Instantiate MUX and DEMUX to segregate management and non-management packets. + // Management packets go to the main state machine, all others get bypassed to + // the output. + wire [CHDR_W-1:0] bypass_tdata; + wire [9:0] bypass_tdest; + wire [1:0] bypass_tid; + wire bypass_tlast, bypass_tvalid, bypass_tready; + wire [CHDR_W-1:0] s_header; + + // We consume the management packet only if it is actually a management packet and we + // don't know where it's going. If the packet has a valid EPID, it is a response that + // is capable of being routed. + wire consume_mgmt_pkt = (chdr_get_pkt_type(s_header[63:0]) == CHDR_PKT_TYPE_MGMT) && + (chdr_get_dst_epid(s_header[63:0]) == NULL_EPID); + + axi_demux #( + .WIDTH(CHDR_W), .SIZE(2), .PRE_FIFO_SIZE(1), .POST_FIFO_SIZE(0) + ) mgmt_demux_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .header(s_header), .dest(consume_mgmt_pkt ? 1'b1 : 1'b0), + .i_tdata(s_axis_chdr_tdata), .i_tlast(s_axis_chdr_tlast), + .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready), + .o_tdata({s_mgmt_tdata, bypass_tdata}), .o_tlast({s_mgmt_tlast, bypass_tlast}), + .o_tvalid({s_mgmt_tvalid, bypass_tvalid}), .o_tready({s_mgmt_tready, bypass_tready}) + ); + + // Only one cycle of delay, so can skip past the demux with the tuser bits + // Packets are longer than the latency through the axi_demux + assign s_mgmt_tuser = s_axis_chdr_tuser; + + assign {bypass_tid, bypass_tdest} = {CHDR_MGMT_ROUTE_EPID, 10'h0}; + + axi_mux #( + .WIDTH(CHDR_W+10+2), .SIZE(2), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(1) + ) mgmt_mux_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata({m_mgmt_tid, m_mgmt_tdest, m_mgmt_tdata, bypass_tid, bypass_tdest, bypass_tdata}), + .i_tlast({m_mgmt_tlast, bypass_tlast}), + .i_tvalid({m_mgmt_tvalid, bypass_tvalid}), .i_tready({m_mgmt_tready, bypass_tready}), + .o_tdata({m_axis_chdr_tid, m_axis_chdr_tdest, m_axis_chdr_tdata}), + .o_tlast(m_axis_chdr_tlast), + .o_tvalid(m_axis_chdr_tvalid), .o_tready(m_axis_chdr_tready) + ); + end else begin + // We are assuming that only management packets come into this module so we don't + // instantiate a bypass path to save resources. + assign s_mgmt_tdata = s_axis_chdr_tdata; + assign s_mgmt_tlast = s_axis_chdr_tlast; + assign s_mgmt_tvalid = s_axis_chdr_tvalid; + assign s_mgmt_tuser = s_axis_chdr_tuser; + assign s_axis_chdr_tready = s_mgmt_tready; + + assign m_axis_chdr_tdata = m_mgmt_tdata; + assign m_axis_chdr_tdest = m_mgmt_tdest; + assign m_axis_chdr_tid = m_mgmt_tid; + assign m_axis_chdr_tlast = m_mgmt_tlast; + assign m_axis_chdr_tvalid = m_mgmt_tvalid; + assign m_mgmt_tready = m_axis_chdr_tready; + end endgenerate + + // --------------------------------------------------- + // Convert management packets to 64-bit + // For CHDR_W > 64, only the bottom 64 bits are used + // --------------------------------------------------- + wire [63:0] i64_tdata; + wire [USER_W-1:0] i64_tuser; + wire i64_tlast, i64_tvalid; + reg i64_tready; + reg [63:0] o64_tdata; + reg [9:0] o64_tdest; + reg [1:0] o64_tid; + reg o64_tlast, o64_tvalid; + wire o64_tready; + + axi_fifo #(.WIDTH(USER_W+65), .SIZE(1)) in_flop_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata({s_mgmt_tuser, s_mgmt_tlast, s_mgmt_tdata[63:0]}), + .i_tvalid(s_mgmt_tvalid), .i_tready(s_mgmt_tready), + .o_tdata({i64_tuser, i64_tlast, i64_tdata}), + .o_tvalid(i64_tvalid), .o_tready(i64_tready), + .space(), .occupied() + ); + + axi_fifo #(.WIDTH(64+10+2+1), .SIZE(1)) out_flop_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata({o64_tlast, o64_tdest, o64_tid, o64_tdata}), + .i_tvalid(o64_tvalid), .i_tready(o64_tready), + .o_tdata({m_mgmt_tlast, m_mgmt_tdest, m_mgmt_tid, m_mgmt_tdata[63:0]}), + .o_tvalid(m_mgmt_tvalid), .o_tready(m_mgmt_tready), + .space(), .occupied() + ); + + generate + if (CHDR_W > 64) + assign m_mgmt_tdata[CHDR_W-1:CHDR_W-64] = 'h0; + endgenerate + + // --------------------------------------------------- + // Parse management packet + // --------------------------------------------------- + localparam [3:0] ST_CHDR_IN_HDR = 4'd0; // Consuming input CHDR header + localparam [3:0] ST_CHDR_IN_MDATA = 4'd1; // Discarding input CHDR metadata + localparam [3:0] ST_MGMT_IN_HDR = 4'd2; // Consuming input management header + localparam [3:0] ST_MGMT_OP_EXEC = 4'd3; // Management operation started + localparam [3:0] ST_MGMT_OP_WAIT = 4'd4; // Waiting for management op to finish + localparam [3:0] ST_MGMT_OP_DONE = 4'd5; // Consuming management op line + localparam [3:0] ST_CHDR_OUT_HDR = 4'd6; // Outputing a CHDR header + localparam [3:0] ST_MGMT_OUT_HDR = 4'd7; // Outputing a managment header + localparam [3:0] ST_PASS_PAYLOAD = 4'd8; // Passing payload for downstream hops + localparam [3:0] ST_MOD_LAST_HOP = 4'd9; // Processing last hop + localparam [3:0] ST_POP_RESPONSE = 4'd10; // Popping response from response FIFO + localparam [3:0] ST_APPEND_LAST_HOP = 4'd11; // Appending response to last hop + localparam [3:0] ST_FAILSAFE_DROP = 4'd12; // Something went wrong. Flushing input. + + // Pieces of state maintained by this state machine + reg [3:0] pkt_state = ST_CHDR_IN_HDR; // The state variable + reg [4:0] num_mdata; // Number of metadata lines in packet + reg [63:0] cached_chdr_hdr, cached_mgmt_hdr; // Cached copies of the CHDR and mgmt headers + reg [15:0] stripped_len; // The new CHDR length after ops are stripped + reg [9:0] hops_remaining; // Number of hops remaining until pkt is consumed + reg [7:0] resp_op_code; // Opcode for the response + reg [47:0] resp_op_payload; // Payload for the response + reg [USER_W-1:0] cached_tuser; // Cached copy of the tuser bits (for the advertise op) + + // Shortcuts + wire [7:0] op_code = chdr_mgmt_get_op_code(i64_tdata); + wire [47:0] op_payload = chdr_mgmt_get_op_payload(i64_tdata); + + // Inputs and outputs for the response FIFO + wire [55:0] resp_i_tdata, resp_o_tdata; + wire resp_i_tvalid, resp_o_tvalid; + wire [7:0] num_resp_pending; + + // The massive state machine + // ------------------------- + always @(posedge clk) begin + if (rst) begin + // We just need to initialize pkt_state here. + // All other registers are initialized in states before their usage + pkt_state <= ST_CHDR_IN_HDR; + end else begin + case (pkt_state) + + // ST_CHDR_IN_HDR + // ------------------ + // - Cache and consume the CHDR header. It will be modified + // later before the packet is sent out. + // - Initialize CHDR specific state + ST_CHDR_IN_HDR: begin + if (i64_tvalid && i64_tready) begin + cached_chdr_hdr <= i64_tdata; + cached_tuser <= i64_tuser; + stripped_len <= chdr_get_length(i64_tdata); + num_mdata <= chdr_get_num_mdata(i64_tdata) - 5'd1; + if (!i64_tlast) begin + if (chdr_get_pkt_type(i64_tdata) != CHDR_PKT_TYPE_MGMT) + pkt_state <= ST_FAILSAFE_DROP; // Drop non-mgmt packets + else if (chdr_get_num_mdata(i64_tdata) != CHDR_NO_MDATA) + pkt_state <= ST_CHDR_IN_MDATA; // Skip over metadata + else + pkt_state <= ST_MGMT_IN_HDR; // Start processing packet + end else begin + pkt_state <= ST_CHDR_IN_HDR; // Premature termination + end + end + end + + // ST_CHDR_IN_MDATA + // ------------------ + // - Discard incoming CHDR metadata + ST_CHDR_IN_MDATA: begin + if (i64_tvalid && i64_tready) begin + num_mdata <= num_mdata - 5'd1; + if (!i64_tlast) + pkt_state <= (num_mdata == CHDR_NO_MDATA) ? ST_MGMT_IN_HDR : ST_CHDR_IN_MDATA; + else + pkt_state <= ST_CHDR_IN_HDR; // Premature termination + end + end + + // ST_MGMT_IN_HDR + // ------------------ + // - Cache and consume the managment header. It will be modified + // later before the packet is sent out. + // - Initialize management specific state + ST_MGMT_IN_HDR: begin + if (i64_tvalid && i64_tready) begin + cached_mgmt_hdr <= i64_tdata; + hops_remaining <= chdr_mgmt_get_num_hops(i64_tdata); + pkt_state <= (!i64_tlast) ? ST_MGMT_OP_EXEC : ST_CHDR_IN_HDR; + end + end + + // ST_MGMT_OP_EXEC + // ------------------ + // - We are processing a management operation for this hop + // - Launch the requested action be looking at the op_code + ST_MGMT_OP_EXEC: begin + if (i64_tvalid) begin + // Assume that the packet is getting routed normally + // unless some operation changes that + o64_tid <= CHDR_MGMT_ROUTE_EPID; + o64_tdest <= 10'd0; + case (op_code) + // Operation: Do nothing + CHDR_MGMT_OP_NOP: begin + // No-op. Jump to the finish state + pkt_state <= ST_MGMT_OP_DONE; + end + // Operation: Advertise this management packet to outside logic + CHDR_MGMT_OP_ADVERTISE: begin + // Pretty much a no-op. Jump to the finish state + pkt_state <= ST_MGMT_OP_DONE; + end + // Operation: Select a destination (tdest and tid) for the output CHDR stream + CHDR_MGMT_OP_SEL_DEST: begin + o64_tid <= CHDR_MGMT_ROUTE_TDEST; + o64_tdest <= chdr_mgmt_sel_dest_get_tdest(op_payload); + pkt_state <= ST_MGMT_OP_DONE; // Single cycle op + end + // Operation: Return the packet to source (turn it around) + CHDR_MGMT_OP_RETURN: begin + o64_tid <= CHDR_MGMT_RETURN_TO_SRC; + pkt_state <= ST_MGMT_OP_DONE; // Single cycle op + end + // Operation: Handle a node information request. + // Send the info as a response + CHDR_MGMT_OP_INFO_REQ: begin + pkt_state <= ST_MGMT_OP_DONE; // Single cycle op + end + // Operation: Handle a node information response. + // Treat as a no-op because this is a slave + CHDR_MGMT_OP_INFO_RESP: begin + pkt_state <= ST_MGMT_OP_DONE; + end + // Operation: Post a write on the outgoing control-port + CHDR_MGMT_OP_CFG_WR_REQ: begin + // ctrlport_req_* signals are assigned below + pkt_state <= ST_MGMT_OP_WAIT; // Wait until ACKed + end + // Operation: Post a read on the outgoing control-port + CHDR_MGMT_OP_CFG_RD_REQ: begin + // ctrlport_req_* signals are assigned below + pkt_state <= ST_MGMT_OP_WAIT; // Wait until ACKed + end + // Operation: Handle a read response. + // Treat as a no-op because this is a slave + CHDR_MGMT_OP_CFG_RD_RESP: begin + pkt_state <= ST_MGMT_OP_DONE; + end + default: begin + // We should never get here + pkt_state <= ST_CHDR_IN_HDR; + end + endcase + end + end + + // ST_MGMT_OP_WAIT + // ------------------ + // - A management operation has started. We are waiting for it to finish + ST_MGMT_OP_WAIT: begin + if (i64_tvalid) begin + if (op_code == CHDR_MGMT_OP_CFG_WR_REQ || + op_code == CHDR_MGMT_OP_CFG_RD_REQ) begin + // Wait for an control-port transaction to finish + if (ctrlport_resp_ack) begin + pkt_state <= ST_MGMT_OP_DONE; + end + end else begin + // All other operations should not get here + pkt_state <= ST_MGMT_OP_DONE; + end + end + end + + // ST_MGMT_OP_DONE + // ------------------ + // - The management operation has finished + // - Consume a word on the input CHDR stream and update interal state + ST_MGMT_OP_DONE: begin + if (i64_tvalid && i64_tready) begin + if (!i64_tlast) begin + // We just consumed 8-bytes from the incoming packet + stripped_len <= stripped_len - CHDR_W_BYTES; + // Check if this was the last op for this hop. If so start + // to output a packet, otherwise start the next op. + if (chdr_mgmt_get_ops_pending(i64_tdata) == 8'd0) begin + hops_remaining <= hops_remaining - 10'd1; + pkt_state <= ST_CHDR_OUT_HDR; + end else begin + pkt_state <= ST_MGMT_OP_EXEC; + end + end else begin + // Premature termination or this is the last operation + // Either way, move back to the beginning of the next pkt + pkt_state <= ST_CHDR_IN_HDR; + end + end + end + + // ST_CHDR_OUT_HDR + // ------------------ + // - We are outputing the CHDR header + ST_CHDR_OUT_HDR: begin + if (o64_tvalid && o64_tready) + pkt_state <= ST_MGMT_OUT_HDR; + end + + // ST_CHDR_OUT_HDR + // ------------------ + // - We are outputing the management header + ST_MGMT_OUT_HDR: begin + if (o64_tvalid && o64_tready) + if (resp_o_tvalid && (hops_remaining == 10'd1)) + pkt_state <= ST_MOD_LAST_HOP; // Special state to append responses to last hod + else + pkt_state <= ST_PASS_PAYLOAD; // Just pass the data as-is + end + + // ST_PASS_PAYLOAD + // ------------------ + // - We are passing the payload for the downstream hops as-is + ST_PASS_PAYLOAD: begin + if (o64_tvalid && o64_tready) begin + if (!i64_tlast) begin + // Check if this was the last op for this hop. If so update + // the hop count. If this is the last hop then enter the next + // state to process it. We might need to append responses for our + // management operations. + if (chdr_mgmt_get_ops_pending(i64_tdata) == 8'd0) begin + hops_remaining <= hops_remaining - 10'd1; + if (resp_o_tvalid && (hops_remaining == 10'd1)) + pkt_state <= ST_MOD_LAST_HOP; // Special state to append responses to last hod + else + pkt_state <= ST_PASS_PAYLOAD; // Just pass the data as-is + end else begin + pkt_state <= ST_PASS_PAYLOAD; + end + end else begin + pkt_state <= ST_CHDR_IN_HDR; + end + end + end + + // ST_MOD_LAST_HOP + // ------------------ + // - We are processing the last hop. We need a special state because we + // need to update the "ops_pending" field if we have responses to tack + // on to the end of the hop. + // - We continue to pass the input to the output while modifying ops_pending + // - For the last op, we move to the APPEND state if we need to add responses + ST_MOD_LAST_HOP: begin + if (o64_tvalid && o64_tready) begin + // Check if this was the last op for this hop. + if (chdr_mgmt_get_ops_pending(i64_tdata) == 8'd0) begin + if (resp_o_tvalid) + pkt_state <= ST_POP_RESPONSE; // We have pending responses + else + pkt_state <= i64_tlast ? ST_CHDR_IN_HDR : ST_FAILSAFE_DROP; + end + end + end + + // ST_POP_RESPONSE + // ------------------ + // - Pop a response word from the FIFO + ST_POP_RESPONSE: begin + if (resp_o_tvalid) begin + resp_op_code <= resp_o_tdata[7:0]; + resp_op_payload <= resp_o_tdata[55:8]; + pkt_state <= ST_APPEND_LAST_HOP; + end + end + + // ST_APPEND_LAST_HOP + // ------------------ + // - Append the popped response to the output packet here + // - Keep doing so until the response FIFO is empty + ST_APPEND_LAST_HOP: begin + if (o64_tvalid && o64_tready) + pkt_state <= resp_o_tvalid ? ST_POP_RESPONSE : ST_CHDR_IN_HDR; + end + + // ST_FAILSAFE_DROP + // ------------------ + // - Something went wrong. Discard the packet and re-arm the state machine + ST_FAILSAFE_DROP: begin + if (i64_tvalid && i64_tready) + pkt_state <= i64_tlast ? ST_CHDR_IN_HDR : ST_FAILSAFE_DROP; + end + + default: begin + // We should never get here + pkt_state <= ST_CHDR_IN_HDR; + end + endcase + end + end + + // Logic to determine when to consume a word from the input CHDR stream + always @(*) begin + case (pkt_state) + ST_CHDR_IN_HDR: + i64_tready = 1'b1; // Unconditionally consume header + ST_CHDR_IN_MDATA: + i64_tready = 1'b1; // Unconditionally discard header + ST_MGMT_IN_HDR: + i64_tready = 1'b1; // Unconditionally consume header + ST_MGMT_OP_DONE: + i64_tready = 1'b1; // Operation is done. Consume op-word + ST_PASS_PAYLOAD: + i64_tready = o64_tready; // We are passing input -> output + ST_MOD_LAST_HOP: + i64_tready = o64_tready; // We are passing input -> output + ST_FAILSAFE_DROP: + i64_tready = 1'b1; // Unconditionally consume to drop + default: + i64_tready = 1'b0; // Hold the input. We are processing it + endcase + end + + // Swap src/dst EPIDs if returning packet to source + wire [15:0] o64_dst_epid = (o64_tid == CHDR_MGMT_RETURN_TO_SRC) ? + chdr_mgmt_get_src_epid(cached_mgmt_hdr) : chdr_get_dst_epid(cached_chdr_hdr); + wire [15:0] o64_src_epid = (o64_tid == CHDR_MGMT_RETURN_TO_SRC) ? + chdr_get_dst_epid(cached_chdr_hdr) : chdr_mgmt_get_src_epid(cached_mgmt_hdr); + + // Logic to drive the output CHDR stream + always @(*) begin + case (pkt_state) + ST_CHDR_OUT_HDR: begin + // We are generating new data using cached values. + // Output header = Input header with new length + o64_tdata = chdr_set_length( + chdr_set_dst_epid(cached_chdr_hdr, o64_dst_epid), + (stripped_len + (num_resp_pending << LOG2_CHDR_W_BYTES))); + o64_tvalid = 1'b1; + o64_tlast = 1'b0; + end + ST_MGMT_OUT_HDR: begin + // We are generating new data using cached values. + // Output header = Input header with new num_hops and some protocol info + o64_tdata = chdr_mgmt_build_hdr(PROTOVER, chdr_w_to_enum(CHDR_W), + chdr_mgmt_get_num_hops(cached_mgmt_hdr) - 10'd1, o64_src_epid); + o64_tvalid = 1'b1; + o64_tlast = 1'b0; + end + ST_PASS_PAYLOAD: begin + // Input -> Output without modification + o64_tdata = i64_tdata; + o64_tvalid = i64_tvalid; + o64_tlast = i64_tlast; + end + ST_MOD_LAST_HOP: begin + // Input -> Output but update the ops_pending field + o64_tdata = chdr_mgmt_build_op(chdr_mgmt_get_op_payload(i64_tdata), + chdr_mgmt_get_op_code(i64_tdata), + chdr_mgmt_get_ops_pending(i64_tdata) + num_resp_pending); + o64_tvalid = i64_tvalid; + o64_tlast = i64_tlast && !resp_o_tvalid; + end + ST_APPEND_LAST_HOP: begin + // We are generating new data using cached values. + o64_tdata = chdr_mgmt_build_op(resp_op_payload, resp_op_code, num_resp_pending); + o64_tvalid = 1'b1; + o64_tlast = !resp_o_tvalid; + end + default: begin + // We are processing something. Don't output + o64_tdata = 64'h0; + o64_tvalid = 1'b0; + o64_tlast = 1'b0; + end + endcase + end + + // CHDR_MGMT_OP_ADVERTISE + // ---------------------- + assign op_stb = i64_tvalid && (pkt_state == ST_MGMT_OP_DONE) && + (op_code == CHDR_MGMT_OP_ADVERTISE); + assign op_dst_epid = chdr_get_dst_epid(cached_chdr_hdr); + assign op_src_epid = chdr_mgmt_get_src_epid(cached_mgmt_hdr); + assign op_data = cached_tuser; + + // CHDR_MGMT_OP_CFG_WR_REQ + // CHDR_MGMT_OP_CFG_RD_REQ + // ----------------------- + // The request is sent out in the ST_MGMT_OP_EXEC state and we wait for a response + // in the ST_MGMT_OP_WAIT state + always @(posedge clk) begin + if (rst) begin + ctrlport_req_wr <= 1'b0; + ctrlport_req_rd <= 1'b0; + end else begin + ctrlport_req_wr <= i64_tvalid && (pkt_state == ST_MGMT_OP_EXEC) && + (op_code == CHDR_MGMT_OP_CFG_WR_REQ); + ctrlport_req_rd <= i64_tvalid && (pkt_state == ST_MGMT_OP_EXEC) && + (op_code == CHDR_MGMT_OP_CFG_RD_REQ); + ctrlport_req_addr <= chdr_mgmt_cfg_reg_get_addr(op_payload); + ctrlport_req_data <= chdr_mgmt_cfg_reg_get_data(op_payload); + end + end + + // CHDR_MGMT_OP_CFG_RD_REQ + // CHDR_MGMT_OP_INFO_REQ + // ----------------------- + // Collect the response for these operations and push to the response FIFO + assign resp_i_tvalid = i64_tvalid && ( + ((pkt_state == ST_MGMT_OP_WAIT) && (op_code == CHDR_MGMT_OP_CFG_RD_REQ) && ctrlport_resp_ack) || + ((pkt_state == ST_MGMT_OP_DONE) && (op_code == CHDR_MGMT_OP_INFO_REQ))); + assign resp_i_tdata = (op_code == CHDR_MGMT_OP_CFG_RD_REQ) ? + {ctrlport_resp_data, ctrlport_req_addr, CHDR_MGMT_OP_CFG_RD_RESP} : // Ctrlport response + {node_info, CHDR_MGMT_OP_INFO_RESP}; // NodeInfo + + // The response FIFO should be deep enough to store all the responses + wire [15:0] resp_fifo_occ; + axi_fifo #(.WIDTH(56), .SIZE(RESP_FIFO_SIZE)) resp_fifo_i ( + .clk(clk), .reset(rst), .clear(pkt_state == ST_CHDR_IN_HDR), + .i_tdata(resp_i_tdata), .i_tvalid(resp_i_tvalid), + .i_tready(/* Must be high. Responses will be dropped if FIFO is full */), + .o_tdata(resp_o_tdata), .o_tvalid(resp_o_tvalid), + .o_tready(resp_o_tvalid && (pkt_state == ST_POP_RESPONSE)), + .space(), .occupied(resp_fifo_occ) + ); + assign num_resp_pending = resp_fifo_occ[7:0]; + +endmodule // chdr_mgmt_pkt_handler diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_stream_endpoint.v b/fpga/usrp3/lib/rfnoc/core/chdr_stream_endpoint.v new file mode 100644 index 000000000..9c824a0af --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_stream_endpoint.v @@ -0,0 +1,621 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_stream_endpoint +// Description: +// The implementation of a stream endpoint. This module serves as +// an endpoint for a bidirectional stream. It implement a control +// and a data path, both of which can be individually enabled using +// parameters. The control path contains a bidirectional CHDR to +// AXIS-Control converter. The data path has a stream input and +// output port. +// +// Parameters: +// - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>} +// - CHDR_W: Width of the CHDR bus in bits +// - INST_NUM: The instance number of this module +// - CTRL_XBAR_PORT: The port index on the control crossbar that +// this module's control path will connect to +// - AXIS_CTRL_EN: Enable control traffic (axis_ctrl port) +// - AXIS_DATA_EN: Enable data traffic (axis_data port) +// - NUM_DATA_I: Number of AXIS data slave ports +// - NUM_DATA_O: Number of AXIS data master ports +// - INGRESS_BUFF_SIZE: Buffer size in log2 of the number of words +// in the ingress buffer for the stream +// - MTU: Log2 of the maximum packet size in words +// - REPORT_STRM_ERRS: Report data stream errors upstream +// - SIM_SPEEDUP: Set to 1 in simultion, and 0 otherwise +// +// Signals: +// - device_id : The ID of the device that has instantiated this module +// - *_axis_chdr_* : Input/output CHDR stream (AXI-Stream) +// - *_axis_ctrl_* : Input/output AXIS-Control streams (AXI-Stream) +// - *_axis_data_* : Input/output CHDR Data streams (AXI-Stream) +// - strm_*_err_stb: The stream encountered an error +// - signal_*_err : Notify upstream that we encountered an error + +module chdr_stream_endpoint #( + parameter [15:0] PROTOVER = {8'd1, 8'd0}, + parameter CHDR_W = 64, + parameter [9:0] INST_NUM = 0, + parameter [9:0] CTRL_XBAR_PORT = 0, + parameter [0:0] AXIS_CTRL_EN = 1, + parameter [0:0] AXIS_DATA_EN = 1, + parameter [5:0] NUM_DATA_I = 1, + parameter [5:0] NUM_DATA_O = 1, + parameter [5:0] INGRESS_BUFF_SIZE = 12, + parameter [5:0] MTU = 10, + parameter [0:0] REPORT_STRM_ERRS = 1, + parameter [0:0] SIM_SPEEDUP = 0 +)( + // Clock, reset and settings + input wire rfnoc_chdr_clk, + input wire rfnoc_chdr_rst, + input wire rfnoc_ctrl_clk, + input wire rfnoc_ctrl_rst, + // Device info + input wire [15:0] device_id, + // CHDR in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + // CHDR out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Flow controlled data in (AXI-Stream) + input wire [(CHDR_W*NUM_DATA_I)-1:0] s_axis_data_tdata, + input wire [NUM_DATA_I-1:0] s_axis_data_tlast, + input wire [NUM_DATA_I-1:0] s_axis_data_tvalid, + output wire [NUM_DATA_I-1:0] s_axis_data_tready, + // Flow controlled data out (AXI-Stream) + output wire [(CHDR_W*NUM_DATA_O)-1:0] m_axis_data_tdata, + output wire [NUM_DATA_O-1:0] m_axis_data_tlast, + output wire [NUM_DATA_O-1:0] m_axis_data_tvalid, + input wire [NUM_DATA_O-1:0] m_axis_data_tready, + // Control in (AXI-Stream) + input wire [31:0] s_axis_ctrl_tdata, + input wire s_axis_ctrl_tlast, + input wire s_axis_ctrl_tvalid, + output wire s_axis_ctrl_tready, + // Control out (AXI-Stream) + output wire [31:0] m_axis_ctrl_tdata, + output wire m_axis_ctrl_tlast, + output wire m_axis_ctrl_tvalid, + input wire m_axis_ctrl_tready, + // Stream status specfic + output wire strm_seq_err_stb, + output wire strm_data_err_stb, + output wire strm_route_err_stb, + input wire signal_data_err +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_chdr_internal_utils.vh" + + // --------------------------------------------------- + // Filter packets by type + // --------------------------------------------------- + wire [CHDR_W-1:0] ctrl_i_tdata, ctrl_o_tdata; + wire ctrl_i_tlast, ctrl_o_tlast; + wire ctrl_i_tvalid, ctrl_o_tvalid; + wire ctrl_i_tready, ctrl_o_tready; + + wire [CHDR_W-1:0] data_i_tdata, data_o_tdata; + wire data_i_tlast, data_o_tlast; + wire data_i_tvalid, data_o_tvalid; + wire data_i_tready, data_o_tready; + + wire [CHDR_W-1:0] strs_i_tdata, strs_o_tdata; + wire strs_i_tlast, strs_o_tlast; + wire strs_i_tvalid, strs_o_tvalid; + wire strs_i_tready, strs_o_tready; + + wire [CHDR_W-1:0] mgmt_i_tdata, mgmt_o_tdata; + wire mgmt_i_tlast, mgmt_o_tlast; + wire mgmt_i_tvalid, mgmt_o_tvalid; + wire mgmt_i_tready, mgmt_o_tready; + + function [1:0] compute_demux_dest; + input [63:0] hdr; + if (chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_CTRL) + // Control + compute_demux_dest = 2'd2; + else if (chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_STRC || + chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_DATA || + chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_DATA_TS) + // Data and stream command + compute_demux_dest = 2'd1; + else if (chdr_get_pkt_type(hdr) == CHDR_PKT_TYPE_STRS) + // Stream status + compute_demux_dest = 2'd0; + else + // Management (all packets must return to sender) + compute_demux_dest = 2'd3; + endfunction + + // We give the demux a FIFO large enough to buffer short packets + // Flow control will ensure that data does not back up through + // this demux but we might have the other packet types block + // each other. + localparam DEMUX_FIFO_SIZE = 5; + + wire [CHDR_W-1:0] chdr_header; + axi_demux #( + .WIDTH(CHDR_W), .SIZE(4), .PRE_FIFO_SIZE(DEMUX_FIFO_SIZE), .POST_FIFO_SIZE(1) + ) mgmt_demux_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst), .clear(1'b0), + .header(chdr_header), .dest(compute_demux_dest(chdr_header[63:0])), + .i_tdata (s_axis_chdr_tdata ), + .i_tlast (s_axis_chdr_tlast ), + .i_tvalid(s_axis_chdr_tvalid), + .i_tready(s_axis_chdr_tready), + .o_tdata ({mgmt_i_tdata, ctrl_i_tdata, data_i_tdata, strs_i_tdata }), + .o_tlast ({mgmt_i_tlast, ctrl_i_tlast, data_i_tlast, strs_i_tlast }), + .o_tvalid({mgmt_i_tvalid, ctrl_i_tvalid, data_i_tvalid, strs_i_tvalid}), + .o_tready({mgmt_i_tready, ctrl_i_tready, data_i_tready, strs_i_tready}) + ); + + axi_mux #( + .WIDTH(CHDR_W), .SIZE(4), .PRIO(1), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(1) + ) mgmt_mux_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst), .clear(1'b0), + .i_tdata ({mgmt_o_tdata, data_o_tdata, strs_o_tdata, ctrl_o_tdata }), + .i_tlast ({mgmt_o_tlast, data_o_tlast, strs_o_tlast, ctrl_o_tlast }), + .i_tvalid({mgmt_o_tvalid, data_o_tvalid, strs_o_tvalid, ctrl_o_tvalid}), + .i_tready({mgmt_o_tready, data_o_tready, strs_o_tready, ctrl_o_tready}), + .o_tdata (m_axis_chdr_tdata ), + .o_tlast (m_axis_chdr_tlast ), + .o_tvalid(m_axis_chdr_tvalid), + .o_tready(m_axis_chdr_tready) + ); + + // --------------------------------------------------- + // Management Path + // --------------------------------------------------- + wire ctrlport_req_wr, ctrlport_req_rd; + reg ctrlport_resp_ack = 1'b0; + wire [15:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + reg [31:0] ctrlport_resp_data; + + localparam [17:0] EXTENDED_INFO = { + 3'b0, REPORT_STRM_ERRS, NUM_DATA_O, NUM_DATA_I, AXIS_DATA_EN, AXIS_CTRL_EN}; + + // Handle management packets here + chdr_mgmt_pkt_handler #( + .PROTOVER(PROTOVER), .CHDR_W(CHDR_W), .MGMT_ONLY(1) + ) mgmt_ep_i ( + .clk(rfnoc_chdr_clk), .rst(rfnoc_chdr_rst), + .node_info(chdr_mgmt_build_node_info(EXTENDED_INFO, INST_NUM, NODE_TYPE_STREAM_EP, device_id)), + .s_axis_chdr_tdata(mgmt_i_tdata), .s_axis_chdr_tlast(mgmt_i_tlast), + .s_axis_chdr_tvalid(mgmt_i_tvalid), .s_axis_chdr_tready(mgmt_i_tready), + .s_axis_chdr_tuser('d0), + .m_axis_chdr_tdata(mgmt_o_tdata), .m_axis_chdr_tlast(mgmt_o_tlast), + .m_axis_chdr_tdest(/* unused */), .m_axis_chdr_tid(/* unused */), + .m_axis_chdr_tvalid(mgmt_o_tvalid), .m_axis_chdr_tready(mgmt_o_tready), + .ctrlport_req_wr(ctrlport_req_wr), .ctrlport_req_rd(ctrlport_req_rd), + .ctrlport_req_addr(ctrlport_req_addr), .ctrlport_req_data(ctrlport_req_data), + .ctrlport_resp_ack(ctrlport_resp_ack), .ctrlport_resp_data(ctrlport_resp_data), + .op_stb(/* unused */), .op_dst_epid(/* unused */), .op_src_epid(/* unused */), + .op_data(/* unused */) + ); + + // ============================== REGISTERS ============================== + // * REG_EPID_SELF (Read-Write): + // The endpoint ID of this stream endpoint + // - [15:0]: Endpoint ID + // * REG_RESET_AND_FLUSH (Write-Only): + // Reset and flush register + // - [0]: Flush data path + // - [1]: Flush control path + // * REG_OSTRM_CTRL_STATUS (Read-Write): + // Control and status register for the output stream + // - [0] : Configuration start (strobe) + // - [1] : Is this transport lossy? + // - [3:2]: Payload SW buff (0=u64, 1=u32, 2=u16, 3=u8) + // - [5:4]: Metadata SW buff (0=u64, 1=u32, 2=u16, 3=u8) + // - [6] : Swap endianness + // * REG_OSTRM_DST_EPID (Write-Only): + // The endpoint ID of a downstream stream endpoint + // - [15:0]: Endpoint ID + // * REG_OSTRM_FC_FREQ_BYTES_LO, REG_OSTRM_FC_FREQ_BYTES_HI (Write-Only): + // Number of bytes between flow control status messages + // * REG_OSTRM_FC_FREQ_PKTS (Write-Only): + // Number of packets between flow control status messages + // * REG_OSTRM_FC_HEADROOM (Write-Only): + // Flow control headroom register + // - [15:0]: Bytes of headroom + // - [23:16]: Packets of headroom + // * REG_OSTRM_BUFF_CAP_BYTES_LO, REG_OSTRM_BUFF_CAP_BYTES_HI (Read-Only): + // Number of bytes in the downstream buffer + // * REG_OSTRM_BUFF_CAP_PKTS (Read-Only): + // Number of packets in the downstream buffer + // * REG_OSTRM_SEQ_ERR_CNT (Read-Only): + // Number of sequence errors since initialization + // * REG_OSTRM_DATA_ERR_CNT (Read-Only): + // Number of data integrity errors since initialization + // * REG_OSTRM_ROUTE_ERR_CNT (Read-Only): + // Number of routing errors since initialization + // * REG_ISTRM_CTRL_STATUS (Read-Write): + // Control and status register for the input stream + // - [0] : Reserved + // - [1] : Reserved + // - [3:2]: Payload SW buff (0=u64, 1=u32, 2=u16, 3=u8) + // - [5:4]: Metadata SW buff (0=u64, 1=u32, 2=u16, 3=u8) + // - [6] : Swap endianness + // ======================================================================= + + localparam [15:0] REG_EPID_SELF = 16'h00; //RW + localparam [15:0] REG_RESET_AND_FLUSH = 16'h04; //W + localparam [15:0] REG_OSTRM_CTRL_STATUS = 16'h08; //RW + localparam [15:0] REG_OSTRM_DST_EPID = 16'h0C; //W + localparam [15:0] REG_OSTRM_FC_FREQ_BYTES_LO = 16'h10; //W + localparam [15:0] REG_OSTRM_FC_FREQ_BYTES_HI = 16'h14; //W + localparam [15:0] REG_OSTRM_FC_FREQ_PKTS = 16'h18; //W + localparam [15:0] REG_OSTRM_FC_HEADROOM = 16'h1C; //W + localparam [15:0] REG_OSTRM_BUFF_CAP_BYTES_LO = 16'h20; //R + localparam [15:0] REG_OSTRM_BUFF_CAP_BYTES_HI = 16'h24; //R + localparam [15:0] REG_OSTRM_BUFF_CAP_PKTS = 16'h28; //R + localparam [15:0] REG_OSTRM_SEQ_ERR_CNT = 16'h2C; //R + localparam [15:0] REG_OSTRM_DATA_ERR_CNT = 16'h30; //R + localparam [15:0] REG_OSTRM_ROUTE_ERR_CNT = 16'h34; //R + localparam [15:0] REG_ISTRM_CTRL_STATUS = 16'h38; //RW + + // Configurable registers + reg [15:0] reg_epid_self = 16'h0; + reg reg_ctrl_reset = 1'b0; + reg reg_istrm_reset = 1'b0; + reg reg_ostrm_reset = 1'b0; + reg reg_ostrm_cfg_start = 1'b0; + wire reg_ostrm_cfg_pending; + wire reg_ostrm_cfg_failed; + reg reg_ostrm_cfg_lossy_xport = 1'b0; + reg [1:0] reg_ostrm_cfg_pyld_sw_buff = 2'd0; + reg [1:0] reg_ostrm_cfg_mdata_sw_buff = 2'd0; + reg reg_ostrm_cfg_swap_endian = 1'b0; + reg [15:0] reg_ostrm_dst_epid = 16'h0; + reg [39:0] reg_fc_freq_bytes = 40'h0; + reg [23:0] reg_fc_freq_pkts = 24'h0; + reg [15:0] reg_fc_headroom_bytes = 16'd0; + reg [7:0] reg_fc_headroom_pkts = 8'd0; + reg [1:0] reg_istrm_cfg_pyld_sw_buff = 2'd0; + reg [1:0] reg_istrm_cfg_mdata_sw_buff = 2'd0; + reg reg_istrm_cfg_swap_endian = 1'b0; + wire reg_fc_enabled; + wire [39:0] reg_buff_cap_bytes; + wire [23:0] reg_buff_cap_pkts; + wire [31:0] reg_seq_err_cnt; + wire [31:0] reg_data_err_cnt; + wire [31:0] reg_route_err_cnt; + + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst) begin + ctrlport_resp_ack <= 1'b0; + end else begin + // All transactions finish in 1 cycle + ctrlport_resp_ack <= ctrlport_req_wr | ctrlport_req_rd; + // Handle register writes + if (ctrlport_req_wr) begin + case(ctrlport_req_addr) + REG_EPID_SELF: + reg_epid_self <= ctrlport_req_data[15:0]; + REG_RESET_AND_FLUSH: + {reg_ctrl_reset, reg_istrm_reset, reg_ostrm_reset} <= ctrlport_req_data[2:0]; + REG_OSTRM_CTRL_STATUS: + {reg_ostrm_cfg_swap_endian, reg_ostrm_cfg_mdata_sw_buff, reg_ostrm_cfg_pyld_sw_buff, + reg_ostrm_cfg_lossy_xport, reg_ostrm_cfg_start} <= ctrlport_req_data[6:0]; + REG_OSTRM_DST_EPID: + reg_ostrm_dst_epid <= ctrlport_req_data[15:0]; + REG_OSTRM_FC_FREQ_BYTES_LO: + reg_fc_freq_bytes[31:0] <= ctrlport_req_data[31:0]; + REG_OSTRM_FC_FREQ_BYTES_HI: + reg_fc_freq_bytes[39:32] <= ctrlport_req_data[7:0]; + REG_OSTRM_FC_FREQ_PKTS: + reg_fc_freq_pkts <= ctrlport_req_data[23:0]; + REG_OSTRM_FC_HEADROOM: + {reg_fc_headroom_pkts, reg_fc_headroom_bytes} <= ctrlport_req_data[23:0]; + REG_ISTRM_CTRL_STATUS: + {reg_istrm_cfg_swap_endian, reg_istrm_cfg_mdata_sw_buff, reg_istrm_cfg_pyld_sw_buff} + <= ctrlport_req_data[6:2]; + endcase + end else begin + // Strobed registers + reg_ostrm_cfg_start <= 1'b0; + reg_ctrl_reset <= 1'b0; + reg_ostrm_reset <= 1'b0; + reg_istrm_reset <= 1'b0; + end + // Handle register reads + if (ctrlport_req_rd) begin + case(ctrlport_req_addr) + REG_EPID_SELF: + ctrlport_resp_data <= {16'h0, reg_epid_self}; + REG_OSTRM_CTRL_STATUS: + ctrlport_resp_data <= { + reg_fc_enabled, reg_ostrm_cfg_failed, reg_ostrm_cfg_pending, 23'h0, + reg_ostrm_cfg_mdata_sw_buff, reg_ostrm_cfg_pyld_sw_buff, + reg_ostrm_cfg_lossy_xport, 1'b0}; + REG_OSTRM_BUFF_CAP_BYTES_LO: + ctrlport_resp_data <= reg_buff_cap_bytes[31:0]; + REG_OSTRM_BUFF_CAP_BYTES_HI: + ctrlport_resp_data <= {24'h0, reg_buff_cap_bytes[39:32]}; + REG_OSTRM_BUFF_CAP_PKTS: + ctrlport_resp_data <= {8'h0, reg_buff_cap_pkts}; + REG_OSTRM_SEQ_ERR_CNT: + ctrlport_resp_data <= reg_seq_err_cnt; + REG_OSTRM_DATA_ERR_CNT: + ctrlport_resp_data <= reg_data_err_cnt; + REG_OSTRM_ROUTE_ERR_CNT: + ctrlport_resp_data <= reg_route_err_cnt; + REG_ISTRM_CTRL_STATUS: + ctrlport_resp_data <= {26'h0, + reg_istrm_cfg_mdata_sw_buff, reg_istrm_cfg_pyld_sw_buff, 2'b0}; + default: + ctrlport_resp_data <= 32'h0; + endcase + end + end + end + + // --------------------------------------------------- + // Data and Flow Control Path + // --------------------------------------------------- + genvar i; + generate if (AXIS_DATA_EN) begin: datapath + localparam INPUT_FLUSH_TIMEOUT_W = SIM_SPEEDUP ? 6 : 14; + + // Data => CHDR + //------------- + wire [CHDR_W-1:0] axis_di_tdata, axis_dis_tdata, axis_di_tdata_pre; + wire [5:0] axis_di_tdest; + wire axis_di_tlast, axis_dis_tlast; + wire axis_di_tvalid, axis_dis_tvalid; + wire axis_di_tready, axis_dis_tready; + + // Optional MUX to combine multiple input data ports into a single one + if (NUM_DATA_I == 6'd1) begin + axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(1)) axis_s_reg_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_ostrm_reset), .clear(1'b0), + .i_tdata({s_axis_data_tlast, s_axis_data_tdata}), + .i_tvalid(s_axis_data_tvalid), .i_tready(s_axis_data_tready), + .o_tdata({axis_di_tlast, axis_di_tdata_pre}), + .o_tvalid(axis_di_tvalid), .o_tready(axis_di_tready), + .space(), .occupied() + ); + assign axis_di_tdest = 6'd0; + end else begin + wire [((CHDR_W+6)*NUM_DATA_I)-1:0] s_axis_data_tdata_tmp; + for (i = 0; i < NUM_DATA_I; i=i+1) begin + assign s_axis_data_tdata_tmp[(i*(CHDR_W+6))+:(CHDR_W+6)] = {i[5:0], s_axis_data_tdata[(i*CHDR_W)+:CHDR_W]}; + end + + axi_mux #( + .WIDTH(CHDR_W+6), .SIZE(NUM_DATA_I), .PRIO(0), .PRE_FIFO_SIZE(1), .POST_FIFO_SIZE(1) + ) axis_s_mux_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_ostrm_reset), .clear(1'b0), + .i_tdata(s_axis_data_tdata_tmp), .i_tlast(s_axis_data_tlast), + .i_tvalid(s_axis_data_tvalid), .i_tready(s_axis_data_tready), + .o_tdata({axis_di_tdest, axis_di_tdata_pre}), .o_tlast(axis_di_tlast), + .o_tvalid(axis_di_tvalid), .o_tready(axis_di_tready) + ); + end + + // Logic to correctly fill in the VC field in the CHDR header + reg axis_di_hdr = 1'b1; + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst | reg_ostrm_reset) + axis_di_hdr <= 1'b1; + else if (axis_di_tvalid && axis_di_tready) + axis_di_hdr <= axis_di_tlast; + end + assign axis_di_tdata[63:0] = axis_di_hdr ? chdr_set_vc(axis_di_tdata_pre[63:0], axis_di_tdest) : + axis_di_tdata_pre[63:0]; + if (CHDR_W > 64) begin + assign axis_di_tdata[CHDR_W-1:64] = axis_di_tdata_pre[CHDR_W-1:64]; + end + + // Module to swap words in the payload and metadata depending on SW settings + chdr_data_swapper #( .CHDR_W(CHDR_W)) di_swap_i ( + .clk (rfnoc_chdr_clk), + .rst (rfnoc_chdr_rst | reg_ostrm_reset), + .payload_sw_buff(reg_ostrm_cfg_pyld_sw_buff), + .mdata_sw_buff (reg_ostrm_cfg_mdata_sw_buff), + .swap_endianness(reg_ostrm_cfg_swap_endian), + .s_axis_tdata (axis_di_tdata), + .s_axis_tlast (axis_di_tlast), + .s_axis_tvalid (axis_di_tvalid), + .s_axis_tready (axis_di_tready), + .m_axis_tdata (axis_dis_tdata), + .m_axis_tlast (axis_dis_tlast), + .m_axis_tvalid (axis_dis_tvalid), + .m_axis_tready (axis_dis_tready) + ); + + // Stream endpoint flow-control output module + chdr_stream_output #( + .CHDR_W(CHDR_W), .MTU(MTU) + ) strm_output_i ( + .clk (rfnoc_chdr_clk), + .rst (rfnoc_chdr_rst | reg_ostrm_reset), + .m_axis_chdr_tdata (data_o_tdata), + .m_axis_chdr_tlast (data_o_tlast), + .m_axis_chdr_tvalid (data_o_tvalid), + .m_axis_chdr_tready (data_o_tready), + .s_axis_data_tdata (axis_dis_tdata), + .s_axis_data_tlast (axis_dis_tlast), + .s_axis_data_tvalid (axis_dis_tvalid), + .s_axis_data_tready (axis_dis_tready), + .s_axis_strs_tdata (strs_i_tdata), + .s_axis_strs_tlast (strs_i_tlast), + .s_axis_strs_tvalid (strs_i_tvalid), + .s_axis_strs_tready (strs_i_tready), + .cfg_start (reg_ostrm_cfg_start), + .cfg_pending (reg_ostrm_cfg_pending), + .cfg_failed (reg_ostrm_cfg_failed), + .cfg_lossy_xport (reg_ostrm_cfg_lossy_xport), + .cfg_dst_epid (reg_ostrm_dst_epid), + .cfg_this_epid (reg_epid_self), + .cfg_fc_freq_bytes (reg_fc_freq_bytes), + .cfg_fc_freq_pkts (reg_fc_freq_pkts), + .cfg_fc_headroom_bytes(reg_fc_headroom_bytes), + .cfg_fc_headroom_pkts (reg_fc_headroom_pkts), + .fc_enabled (reg_fc_enabled), + .capacity_bytes (reg_buff_cap_bytes), + .capacity_pkts (reg_buff_cap_pkts), + .seq_err_stb (strm_seq_err_stb), + .seq_err_cnt (reg_seq_err_cnt), + .data_err_stb (strm_data_err_stb), + .data_err_cnt (reg_data_err_cnt), + .route_err_stb (strm_route_err_stb), + .route_err_cnt (reg_route_err_cnt) + ); + + // CHDR => Data + //------------- + wire [CHDR_W-1:0] axis_do_tdata, axis_dos_tdata; + wire axis_do_tlast, axis_dos_tlast; + wire axis_do_tvalid, axis_dos_tvalid; + wire axis_do_tready, axis_dos_tready; + + // Stream endpoint flow-control input module + chdr_stream_input #( + .CHDR_W(CHDR_W), .BUFF_SIZE(INGRESS_BUFF_SIZE), + .FLUSH_TIMEOUT_W(INPUT_FLUSH_TIMEOUT_W), + .MONITOR_EN(0), .SIGNAL_ERRS(REPORT_STRM_ERRS) + ) strm_input_i ( + .clk (rfnoc_chdr_clk), + .rst (rfnoc_chdr_rst | reg_istrm_reset), + .s_axis_chdr_tdata (data_i_tdata), + .s_axis_chdr_tlast (data_i_tlast), + .s_axis_chdr_tvalid(data_i_tvalid), + .s_axis_chdr_tready(data_i_tready), + .m_axis_data_tdata (axis_do_tdata), + .m_axis_data_tlast (axis_do_tlast), + .m_axis_data_tvalid(axis_do_tvalid), + .m_axis_data_tready(axis_do_tready), + .m_axis_strs_tdata (strs_o_tdata), + .m_axis_strs_tlast (strs_o_tlast), + .m_axis_strs_tvalid(strs_o_tvalid), + .m_axis_strs_tready(strs_o_tready), + .data_err_stb (signal_data_err) + ); + + // Module to swap words in the payload and metadata depending on SW settings + chdr_data_swapper #( .CHDR_W(CHDR_W)) do_swap_i ( + .clk (rfnoc_chdr_clk), + .rst (rfnoc_chdr_rst | reg_istrm_reset), + .payload_sw_buff(reg_istrm_cfg_pyld_sw_buff), + .mdata_sw_buff (reg_istrm_cfg_mdata_sw_buff), + .swap_endianness(reg_istrm_cfg_swap_endian), + .s_axis_tdata (axis_do_tdata), + .s_axis_tlast (axis_do_tlast), + .s_axis_tvalid (axis_do_tvalid), + .s_axis_tready (axis_do_tready), + .m_axis_tdata (axis_dos_tdata), + .m_axis_tlast (axis_dos_tlast), + .m_axis_tvalid (axis_dos_tvalid), + .m_axis_tready (axis_dos_tready) + ); + + // Optional DEMUX to split multiple single stream into multiple outputs + // Packets with an invalid (out of bounds) VC goes to port 0 + if (NUM_DATA_O == 6'd1) begin + axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(1)) axis_m_reg_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_istrm_reset), .clear(1'b0), + .i_tdata({axis_dos_tlast, axis_dos_tdata}), + .i_tvalid(axis_dos_tvalid), .i_tready(axis_dos_tready), + .o_tdata({m_axis_data_tlast, m_axis_data_tdata}), + .o_tvalid(m_axis_data_tvalid), .o_tready(m_axis_data_tready), + .space(), .occupied() + ); + end else begin + wire [CHDR_W-1:0] data_header; + wire [5:0] data_vc = chdr_get_vc(data_header[63:0]); + axi_demux #( + .WIDTH(CHDR_W), .SIZE(NUM_DATA_O), .PRE_FIFO_SIZE(1), .POST_FIFO_SIZE(1) + ) axis_m_demux_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst | reg_istrm_reset), .clear(1'b0), + .header(data_header), + .dest((data_vc < NUM_DATA_O) ? data_vc[$clog2(NUM_DATA_O)-1:0] : {$clog2(NUM_DATA_O){1'b0}}), + .i_tdata(axis_dos_tdata), .i_tlast(axis_dos_tlast), + .i_tvalid(axis_dos_tvalid), .i_tready(axis_dos_tready), + .o_tdata(m_axis_data_tdata), .o_tlast(m_axis_data_tlast), + .o_tvalid(m_axis_data_tvalid), .o_tready(m_axis_data_tready) + ); + end + + end else begin // if (AXIS_DATA_EN) + + assign data_i_tready = 1'b1; + assign data_o_tdata = {CHDR_W{1'b0}}; + assign data_o_tlast = 1'b0; + assign data_o_tvalid = 1'b0; + + assign strs_i_tready = 1'b1; + assign strs_o_tdata = {CHDR_W{1'b0}}; + assign strs_o_tlast = 1'b0; + assign strs_o_tvalid = 1'b0; + + assign s_axis_data_tready = {NUM_DATA_I{1'b0}}; + assign m_axis_data_tdata = {(CHDR_W*NUM_DATA_O){1'b0}}; + assign m_axis_data_tlast = {NUM_DATA_O{1'b0}}; + assign m_axis_data_tvalid = {NUM_DATA_O{1'b0}}; + + end endgenerate + + // --------------------------------------------------- + // Control Path + // --------------------------------------------------- + generate if (AXIS_CTRL_EN) begin: ctrlpath + + // Convert from a CHDR control packet to an AXIS control packet + chdr_to_axis_ctrl #( + .CHDR_W(CHDR_W), .THIS_PORTID(CTRL_XBAR_PORT) + ) chdr_ctrl_adapter_i ( + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .rfnoc_chdr_rst (rfnoc_chdr_rst | reg_ctrl_reset), + .this_epid (reg_epid_self), + .s_rfnoc_chdr_tdata (ctrl_i_tdata), + .s_rfnoc_chdr_tlast (ctrl_i_tlast), + .s_rfnoc_chdr_tvalid(ctrl_i_tvalid), + .s_rfnoc_chdr_tready(ctrl_i_tready), + .m_rfnoc_chdr_tdata (ctrl_o_tdata), + .m_rfnoc_chdr_tlast (ctrl_o_tlast), + .m_rfnoc_chdr_tvalid(ctrl_o_tvalid), + .m_rfnoc_chdr_tready(ctrl_o_tready), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .rfnoc_ctrl_rst (rfnoc_ctrl_rst), + .s_rfnoc_ctrl_tdata (s_axis_ctrl_tdata), + .s_rfnoc_ctrl_tlast (s_axis_ctrl_tlast), + .s_rfnoc_ctrl_tvalid(s_axis_ctrl_tvalid), + .s_rfnoc_ctrl_tready(s_axis_ctrl_tready), + .m_rfnoc_ctrl_tdata (m_axis_ctrl_tdata), + .m_rfnoc_ctrl_tlast (m_axis_ctrl_tlast), + .m_rfnoc_ctrl_tvalid(m_axis_ctrl_tvalid), + .m_rfnoc_ctrl_tready(m_axis_ctrl_tready) + ); + + end else begin // if (AXIS_CTRL_EN) + + assign ctrl_i_tready = 1'b1; + assign ctrl_o_tdata = {CHDR_W{1'b0}}; + assign ctrl_o_tlast = 1'b0; + assign ctrl_o_tvalid = 1'b0; + + assign s_axis_ctrl_tready = 1'b1; + assign m_axis_ctrl_tdata = 32'h0; + assign m_axis_ctrl_tlast = 1'b0; + assign m_axis_ctrl_tvalid = 1'b0; + + end endgenerate + +endmodule // chdr_stream_endpoint + diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_stream_input.v b/fpga/usrp3/lib/rfnoc/core/chdr_stream_input.v new file mode 100644 index 000000000..2a8a9c628 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_stream_input.v @@ -0,0 +1,569 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_stream_input +// Description: +// Implements the CHDR input port for a stream endpoint. +// The module accepts stream command and data packets and +// emits stream status packets. Flow control and error state +// is communicated using stream status packets. There are no +// external config interfaces because all configuration is done +// using stream command packets. +// +// Parameters: +// - CHDR_W: Width of the CHDR bus in bits +// - BUFF_SIZE: Buffer size in log2 of the number of words in the +// ingress buffer for the stream +// - FLUSH_TIMEOUT_W: log2 of the number of cycles to wait in order +// to flush the input stream +// - SIGNAL_ERRS: If set to 1 then all stream errors will be notified +// upstream, otherwise ALL errors are ignored +// +// Signals: +// - s_axis_chdr_* : Input CHDR stream (AXI-Stream) +// - m_axis_chdr_* : Output flow-controlled CHDR stream (AXI-Stream) +// - m_axis_strs_* : Output stream status (AXI-Stream) +// - data_err_stb : If asserted, a data error notification is sent upstream +// + +module chdr_stream_input #( + parameter CHDR_W = 256, + parameter BUFF_SIZE = 14, + parameter FLUSH_TIMEOUT_W = 14, + parameter MONITOR_EN = 1, + parameter SIGNAL_ERRS = 1 +)( + // Clock, reset and settings + input wire clk, + input wire rst, + // CHDR in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + // Flow controlled data out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_data_tdata, + output wire m_axis_data_tlast, + output wire m_axis_data_tvalid, + input wire m_axis_data_tready, + // Stream status out (AXI-Stream) + output reg [CHDR_W-1:0] m_axis_strs_tdata, + output wire m_axis_strs_tlast, + output wire m_axis_strs_tvalid, + input wire m_axis_strs_tready, + // External stream error signal + input wire data_err_stb +); + + // The buffer size depends on the BUFF_SIZE parameter + localparam [40:0] BUFF_SIZE_BYTES = ((41'h1 << BUFF_SIZE) * (CHDR_W / 8)) - 41'h1; + // This is a flit-buffer. No packet limits + localparam [23:0] BUFF_SIZE_PKTS = 24'hFFFFFF; + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_chdr_internal_utils.vh" + + // --------------------------------------------------- + // Ingress Buffer and Flow Control Logic + // --------------------------------------------------- + wire [CHDR_W-1:0] buff_tdata; + wire buff_tlast, buff_tvalid; + reg buff_tready; + wire [15:0] buff_info; + + chdr_ingress_fifo #( + .WIDTH(CHDR_W), .SIZE(BUFF_SIZE) + ) ingress_fifo_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata(s_axis_chdr_tdata), .i_tlast(s_axis_chdr_tlast), + .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready), + .o_tdata(buff_tdata), .o_tlast(buff_tlast), + .o_tvalid(buff_tvalid), .o_tready(buff_tready) + ); + + generate if (MONITOR_EN) begin + wire [BUFF_SIZE:0] occ_lines; + axis_fifo_monitor #( .COUNT_W(BUFF_SIZE+1) ) fifo_mon_i ( + .clk(clk), .reset(rst), + .i_tlast(s_axis_chdr_tlast), .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready), + .o_tlast(buff_tlast), .o_tvalid(buff_tvalid), .o_tready(buff_tready), + .i_sop(), .i_eop(), .o_sop(), .o_eop(), + .occupied(occ_lines), .occupied_pkts() + ); + // buff_info represents a fraction of the fullness of the buffer + // fullness percentage = (buff_info / 32768) * 100 + if (BUFF_SIZE + 1 >= 16) + assign buff_info = occ_lines[BUFF_SIZE:(BUFF_SIZE-15)]; + else + assign buff_info = {occ_lines, {(15-BUFF_SIZE){1'b0}}}; + end else begin + assign buff_info = 16'd0; + end endgenerate + + // Flow Control State + // xfer_cnt: Total transfer count since fc_enabled = 1 + // accum: Transfer count since last FC response + // fc_freq: The threshold for sending an FC response + reg [63:0] xfer_cnt_bytes = 64'd0; + reg [39:0] xfer_cnt_pkts = 40'd0; + reg [63:0] accum_bytes = 64'd0; + reg [39:0] accum_pkts = 40'd0; + reg [63:0] fc_freq_bytes = 64'd0; + reg [39:0] fc_freq_pkts = 40'd0; + + // State machine transition signals info + reg fc_enabled = 1'b0; // Is flow control enabled? + wire fc_ping; // A flow control response was requested + wire fc_first_resp; // Send the first flow control response + wire fc_refresh; // Refresh accumulated values + wire fc_override; // Override total xfer counts + reg fc_override_del = 1'b0; + reg [3:0] fc_due_shreg = 4'h0; // Is a response due? (shift register) + + // Endpoint IDs of this endpoint and the stream source + reg [15:0] this_epid = 16'd0, return_epid = 16'd0; + + // Cached values from a stream command + reg [63:0] strc_num_bytes; + reg [39:0] strc_num_pkts; + reg [3:0] strc_op_data; // Unused for now + reg [3:0] strc_op_code; + + // Total transfer count updater + always @(posedge clk) begin + if (rst || !fc_enabled) begin + // Reset + xfer_cnt_bytes <= 64'd0; + xfer_cnt_pkts <= 40'd0; + end else if (fc_override) begin + // Override + xfer_cnt_bytes <= strc_num_bytes; + xfer_cnt_pkts <= strc_num_pkts; + end else if (buff_tvalid && buff_tready) begin + // Count + xfer_cnt_bytes <= xfer_cnt_bytes + (CHDR_W/8); + if (buff_tlast) + xfer_cnt_pkts <= xfer_cnt_pkts + 40'd1; + end + end + + // Accumulated transfer count updater + always @(posedge clk) begin + if (rst || !fc_enabled || fc_refresh) begin + // Reset + accum_bytes <= 64'd0; + accum_pkts <= 40'd0; + end else if (buff_tvalid && buff_tready) begin + // Count + accum_bytes <= accum_bytes + (CHDR_W/8); + if (buff_tlast) + accum_pkts <= accum_pkts + 40'd1; + end + end + + // Flow control trigger + // Why a shift-register here? + // 1. For edge detection + // 2. To allow the tools to re-time the wide comparators. + // We don't care about the latency here because stream + // status messages are asynchronous wrt the input. + always @(posedge clk) begin + if (rst || !fc_enabled) begin + fc_due_shreg <= 4'h0; + end else begin + fc_due_shreg <= { + fc_due_shreg[2:0], + (accum_bytes >= fc_freq_bytes) || (accum_pkts >= fc_freq_pkts) + }; + end + end + wire fc_resp_due = fc_due_shreg[2] && !fc_due_shreg[3]; + + // --------------------------------------------------- + // Stream Command Handler + // --------------------------------------------------- + localparam [2:0] ST_IN_HDR = 3'd0; // The CHDR header of an input pkt + localparam [2:0] ST_IN_DATA = 3'd1; // The CHDR body (incl. mdata) of an input pkt + localparam [2:0] ST_STRC_W0 = 3'd2; // The first word of a stream command + localparam [2:0] ST_STRC_W1 = 3'd3; // The second word of a stream command + localparam [2:0] ST_STRC_EXEC = 3'd4; // A stream command is executing + localparam [2:0] ST_FLUSH = 3'd5; // Input is flushing + localparam [2:0] ST_DROP = 3'd6; // Current packet is being dropped + + reg [2:0] state = ST_IN_HDR; // State of the input state machine + reg pkt_too_long = 1'b0; // Error case. Packet is too long + reg is_first_data_pkt = 1'b1; // Is this the first data pkt after fc_enabled = 1? + reg is_first_strc_pkt = 1'b1; // Is this the strm cmd data pkt after fc_enabled = 1? + reg [15:0] exp_data_seq_num = 16'd0; // Expected sequence number for the next data pkt + reg [15:0] exp_strc_seq_num = 16'd0; // Expected sequence number for the next stream cmd pkt + reg [15:0] strc_dst_epid = 16'd0; // EPID in CHDR header of STRC packet + + reg [FLUSH_TIMEOUT_W-1:0] flush_counter = {FLUSH_TIMEOUT_W{1'b0}}; + + // Shortcuts + wire is_data_pkt = + chdr_get_pkt_type(buff_tdata[63:0]) == CHDR_PKT_TYPE_DATA || + chdr_get_pkt_type(buff_tdata[63:0]) == CHDR_PKT_TYPE_DATA_TS; + wire is_strc_pkt = + chdr_get_pkt_type(buff_tdata[63:0]) == CHDR_PKT_TYPE_STRC; + + // Error Logic + wire data_seq_err_stb = (state == ST_IN_HDR) && is_data_pkt && !is_first_data_pkt && + (chdr_get_seq_num(buff_tdata[63:0]) != exp_data_seq_num); + wire strc_seq_err_stb = (state == ST_IN_HDR) && is_strc_pkt && !is_first_strc_pkt && + (chdr_get_seq_num(buff_tdata[63:0]) != exp_strc_seq_num); + wire seq_err_stb = (data_seq_err_stb || strc_seq_err_stb) && buff_tvalid && buff_tready; + + wire route_err_stb = buff_tvalid && buff_tready && (state == ST_IN_HDR) && + (chdr_get_dst_epid(buff_tdata[63:0]) != this_epid); + + // Break critical paths to response FIFO + reg [47:0] stream_err_info = 48'h0; + reg stream_err_stb = 1'b0; + reg [3:0] stream_err_status = CHDR_STRS_STATUS_OKAY; + + always @(posedge clk) begin + if (rst || (SIGNAL_ERRS == 0)) begin + stream_err_stb <= 1'b0; + end else begin + stream_err_stb <= seq_err_stb | route_err_stb | data_err_stb; + if (seq_err_stb) begin + stream_err_status <= CHDR_STRS_STATUS_SEQERR; + // The extended info has the packet type (to detect which stream + // had an error), the expected and actual sequence number. + stream_err_info <= {13'h0, chdr_get_pkt_type(buff_tdata[63:0]), + data_seq_err_stb ? exp_data_seq_num : exp_strc_seq_num, + chdr_get_seq_num(buff_tdata[63:0])}; + end else if (route_err_stb) begin + stream_err_status <= CHDR_STRS_STATUS_RTERR; + // The extended info has the expected and actual destination EPID. + stream_err_info <= {16'd0, this_epid, chdr_get_dst_epid(buff_tdata[63:0])}; + end else begin + stream_err_status <= CHDR_STRS_STATUS_DATAERR; + // The extended info has the expected and actual destination EPID. + stream_err_info <= {16'd0, this_epid, chdr_get_dst_epid(buff_tdata[63:0])}; + end + end + end + + // Input State Machine + // - Pass data packets forward + // - Consume stream cmd packets + always @(posedge clk) begin + if (rst) begin + state <= ST_IN_HDR; + pkt_too_long <= 1'b0; + fc_enabled <= 1'b0; + end else begin + case (state) + ST_IN_HDR: begin + if (buff_tvalid && buff_tready) begin + if (!buff_tlast) begin + // Classify packet and... + if (is_strc_pkt) begin + // ...consume if it is a stream command or... + state <= ST_STRC_W0; + end else if (is_data_pkt) begin + // ...pass to output if it is a data packet... + state <= ST_IN_DATA; + end else begin + // ... otherwise drop. + state <= ST_DROP; + end + end + // Update other state vars + pkt_too_long <= 1'b0; + if (is_strc_pkt) begin + is_first_strc_pkt <= 1'b0; + strc_dst_epid <= chdr_get_dst_epid(buff_tdata[63:0]); + exp_strc_seq_num <= chdr_get_seq_num(buff_tdata[63:0]) + 16'd1; + end else if (is_data_pkt) begin + is_first_data_pkt <= 1'b0; + exp_data_seq_num <= chdr_get_seq_num(buff_tdata[63:0]) + 16'd1; + end + end + end + ST_IN_DATA: begin + // Pass the data packet forward + if (buff_tvalid && buff_tready && buff_tlast) + state <= ST_IN_HDR; + end + ST_STRC_W0: begin + if (buff_tvalid && buff_tready) begin + // Consume the first word of a stream command packet + if (CHDR_W > 64) begin + strc_num_bytes <= chdr128_strc_get_num_bytes(buff_tdata[127:0]); + strc_num_pkts <= chdr128_strc_get_num_pkts (buff_tdata[127:0]); + strc_op_data <= chdr128_strc_get_op_data (buff_tdata[127:0]); + strc_op_code <= chdr128_strc_get_op_code (buff_tdata[127:0]); + return_epid <= chdr128_strs_get_src_epid (buff_tdata[127:0]); + state <= ST_STRC_EXEC; + pkt_too_long <= ~buff_tlast; + end else begin + strc_num_pkts <= chdr64_strc_get_num_pkts(buff_tdata[63:0]); + strc_op_data <= chdr64_strc_get_op_data (buff_tdata[63:0]); + strc_op_code <= chdr64_strc_get_op_code (buff_tdata[63:0]); + return_epid <= chdr64_strs_get_src_epid(buff_tdata[63:0]); + state <= ST_STRC_W1; + end + end + end + ST_STRC_W1: begin + if (buff_tvalid && buff_tready) begin + // Consume the second word of a stream command packet + strc_num_bytes <= chdr64_strc_get_num_bytes(buff_tdata[63:0]); + state <= ST_STRC_EXEC; + pkt_too_long <= ~buff_tlast; + end + end + ST_STRC_EXEC: begin + case (strc_op_code) + CHDR_STRC_OPCODE_INIT: begin + // Configure FC but disable it temporarily + fc_freq_bytes <= strc_num_bytes; + fc_freq_pkts <= strc_num_pkts; + this_epid <= strc_dst_epid; + fc_enabled <= 1'b0; + // Flush the input + state <= ST_FLUSH; + flush_counter <= {FLUSH_TIMEOUT_W{1'b1}}; + end + CHDR_STRC_OPCODE_PING: begin + // Ping can complete in 1 cycle + state <= pkt_too_long ? ST_DROP : ST_IN_HDR; + end + CHDR_STRC_OPCODE_RESYNC: begin + // Resync can complete in 1 cycle + state <= pkt_too_long ? ST_DROP : ST_IN_HDR; + end + default: begin + state <= pkt_too_long ? ST_DROP : ST_IN_HDR; + end + endcase + end + ST_FLUSH: begin + // Drop until the next packet arrives + if (buff_tvalid && buff_tready) begin + flush_counter <= {FLUSH_TIMEOUT_W{1'b1}}; + end else begin + flush_counter <= flush_counter - 'd1; + if (flush_counter == {FLUSH_TIMEOUT_W{1'b0}}) begin + // Done flushing. Re-arm flow control and reset packet + // sequence check info. + fc_enabled <= 1'b1; + is_first_data_pkt <= 1'b1; + is_first_strc_pkt <= 1'b1; + state <= ST_IN_HDR; + end + end + end + ST_DROP: begin + // Drop until the next packet arrives + if (buff_tvalid && buff_tready && buff_tlast) + state <= ST_IN_HDR; + end + default: begin + // We should never get here + state <= ST_IN_HDR; + end + endcase + end + end + + always @(*) begin + case (state) + ST_IN_HDR: + buff_tready = m_axis_data_tready || !is_data_pkt; + ST_IN_DATA: + buff_tready = m_axis_data_tready; + ST_STRC_W0: + buff_tready = 1'b1; + ST_STRC_W1: + buff_tready = 1'b1; + ST_FLUSH: + buff_tready = 1'b1; + ST_DROP: + buff_tready = 1'b1; + default: + buff_tready = 1'b0; + endcase + end + + // Logic to drive output port + assign m_axis_data_tdata = buff_tdata; + assign m_axis_data_tlast = buff_tlast; + assign m_axis_data_tvalid = buff_tvalid && + ((state == ST_IN_HDR && is_data_pkt) || state == ST_IN_DATA); + + // Logic to drive triggers + assign fc_ping = (state == ST_STRC_EXEC) && (strc_op_code == CHDR_STRC_OPCODE_PING); + assign fc_first_resp = (state == ST_FLUSH) && (flush_counter == {FLUSH_TIMEOUT_W{1'b0}}); + assign fc_override = (state == ST_STRC_EXEC) && (strc_op_code == CHDR_STRC_OPCODE_RESYNC); + always @(posedge clk) fc_override_del <= fc_override; + + wire [51:0] resp_o_tdata; + wire resp_o_tvalid; + reg [51:0] resp_i_tdata; + reg resp_i_tvalid = 1'b0; + + // Send a stream status packet for the following cases: + // - Immediately after initialization + // - If a response is explicitly requested (ping) + // - If a response is due i.e. we have exceeded the frequency + // - If FC is resynchronized via a stream cmd + // - If an error is detected in the stream + always @(posedge clk) begin + if (rst) begin + resp_i_tvalid <= 1'b0; + resp_i_tdata <= 52'h0; + end else begin + resp_i_tvalid <= fc_first_resp || fc_ping || fc_resp_due || fc_override_del || stream_err_stb; + resp_i_tdata <= stream_err_stb ? {stream_err_info, stream_err_status} : {48'h0, CHDR_STRS_STATUS_OKAY}; + end + end + + // --------------------------------------------------- + // Stream Status Responder + // --------------------------------------------------- + localparam [2:0] ST_STRS_IDLE = 3'd0; // Waiting for response to post + localparam [2:0] ST_STRS_HDR = 3'd1; // Sending response CHDR header + localparam [2:0] ST_STRS_W0 = 3'd2; // Sending first response word + localparam [2:0] ST_STRS_W1 = 3'd3; // Sending second response word + localparam [2:0] ST_STRS_W2 = 3'd4; // Sending third response word + localparam [2:0] ST_STRS_W3 = 3'd5; // Sending fourth response word + localparam [2:0] ST_STRS_DONE = 3'd6; // Consuming response + + reg [2:0] resp_state = ST_STRS_IDLE; // State of the responder + reg [15:0] resp_seq_num = 16'd0; // Current sequence number of response + + assign fc_refresh = (resp_state == ST_STRS_DONE); + + // A FIFO that holds up to 32 posted responses and status information + // NOTE: This is a lossy FIFO. If the downstream response port is clogged + // then we will drop responses. That should never happen in a normal operating + // scenario. + axi_fifo #(.WIDTH(48 + 4), .SIZE(5)) resp_fifo_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata(resp_i_tdata), .i_tvalid(resp_i_tvalid), .i_tready(/* Lossy FIFO */), + .o_tdata(resp_o_tdata), .o_tvalid(resp_o_tvalid), .o_tready(resp_state == ST_STRS_DONE || !fc_enabled), + .space(), .occupied() + ); + + // Responder State Machine + // - Wait for response to appear in FIFO + // - Output a full packet (different # of xfers depending on CHDR_W) + always @(posedge clk) begin + if (rst || !fc_enabled) begin + resp_state <= ST_STRS_IDLE; + resp_seq_num <= 16'd0; + end else begin + case (resp_state) + ST_STRS_IDLE: begin + if (resp_o_tvalid) + resp_state <= ST_STRS_HDR; + end + ST_STRS_HDR: begin + if (m_axis_strs_tready) + resp_state <= ST_STRS_W0; + end + ST_STRS_W0: begin + if (m_axis_strs_tready) + if (CHDR_W < 256) + resp_state <= ST_STRS_W1; + else + resp_state <= ST_STRS_DONE; + end + ST_STRS_W1: begin + if (m_axis_strs_tready) + if (CHDR_W < 128) + resp_state <= ST_STRS_W2; + else + resp_state <= ST_STRS_DONE; + end + ST_STRS_W2: begin + if (m_axis_strs_tready) + resp_state <= ST_STRS_W3; + end + ST_STRS_W3: begin + if (m_axis_strs_tready) + resp_state <= ST_STRS_DONE; + end + ST_STRS_DONE: begin + resp_state <= ST_STRS_IDLE; + resp_seq_num <= resp_seq_num + 16'd1; + end + default: begin + // We should never get here + resp_state <= ST_STRS_IDLE; + end + endcase + end + end + + // Output data. Header and Payload + wire [63:0] strs_header = chdr_build_header( + /*VC*/ 6'd0, /*eob*/ 1'b0, /*eov*/ 1'b0, CHDR_PKT_TYPE_STRS, CHDR_NO_MDATA, + resp_seq_num, 16'd32+(CHDR_W/8), return_epid); + wire [255:0] strs_payload = chdr256_strs_build( + /*statusinfo*/ resp_o_tdata[51:4], buff_info, + xfer_cnt_bytes, xfer_cnt_pkts, + BUFF_SIZE_PKTS[23:0], BUFF_SIZE_BYTES[39:0], + resp_o_tdata[3:0], this_epid); + + // m_axis_strs_* signal values depend on CHDR_W + generate + if (CHDR_W == 64) begin + // Response spans 5 transfers (header + 4 words) + assign m_axis_strs_tlast = (resp_state == ST_STRS_W3); + always @(*) begin + case (resp_state) + ST_STRS_W0: + m_axis_strs_tdata = strs_payload[63:0]; + ST_STRS_W1: + m_axis_strs_tdata = strs_payload[127:64]; + ST_STRS_W2: + m_axis_strs_tdata = strs_payload[191:128]; + ST_STRS_W3: + m_axis_strs_tdata = strs_payload[255:192]; + default: + m_axis_strs_tdata = strs_header; + endcase + end + end else if (CHDR_W == 128) begin + // Response spans 3 transfers (header + 2 words) + assign m_axis_strs_tlast = (resp_state == ST_STRS_W1); + always @(*) begin + case (resp_state) + ST_STRS_W0: + m_axis_strs_tdata = strs_payload[127:0]; + ST_STRS_W1: + m_axis_strs_tdata = strs_payload[255:128]; + default: + m_axis_strs_tdata = {64'h0, strs_header}; + endcase + end + end else begin + // Response spans 2 transfers (header + word) + assign m_axis_strs_tlast = (resp_state == ST_STRS_W0); + always @(*) begin + case (resp_state) + ST_STRS_W0: + m_axis_strs_tdata[255:0] = strs_payload; + default: + m_axis_strs_tdata[255:0] = {192'h0, strs_header}; + endcase + if (CHDR_W > 256) begin + m_axis_strs_tdata[CHDR_W-1:256] = 'h0; + end + end + end + endgenerate + + assign m_axis_strs_tvalid = (resp_state != ST_STRS_IDLE) && (resp_state != ST_STRS_DONE); + +endmodule // chdr_stream_input diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_stream_output.v b/fpga/usrp3/lib/rfnoc/core/chdr_stream_output.v new file mode 100644 index 000000000..271c7fccc --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_stream_output.v @@ -0,0 +1,557 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_stream_output +// Description: +// Implements the CHDR output port for a stream endpoint. +// The module generates stream command packets to setup +// a downstream endpoint module (chdr_stream_input). Once +// a stream is setup, the CHDR data on the axis_data port +// can be sent downstream with full flow control. Stream +// status messages are recieved from the downstream node +// to update flow control state. This module has an external +// configuration bus to initiate stream creation. +// +// Parameters: +// - CHDR_W: Width of the CHDR bus in bits +// - MTU: Log2 of the maximum number of lines in a packet +// +// Signals: +// - m_axis_chdr_* : Output CHDR stream (AXI-Stream) +// - s_axis_data_* : Input CHDR Data stream (AXI-Stream) before flow control +// - s_axis_strs_* : Input stream status (AXI-Stream) + +module chdr_stream_output #( + parameter CHDR_W = 256, + parameter MTU = 10 +)( + // Clock, reset and settings + input wire clk, + input wire rst, + // CHDR out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Data packets in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_data_tdata, + input wire s_axis_data_tlast, + input wire s_axis_data_tvalid, + output wire s_axis_data_tready, + // Stream status in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_strs_tdata, + input wire s_axis_strs_tlast, + input wire s_axis_strs_tvalid, + output wire s_axis_strs_tready, + // Configuration port + input wire cfg_start, + output reg cfg_pending = 1'b0, + output reg cfg_failed = 1'b0, + input wire cfg_lossy_xport, + input wire [15:0] cfg_dst_epid, + input wire [15:0] cfg_this_epid, + input wire [39:0] cfg_fc_freq_bytes, + input wire [23:0] cfg_fc_freq_pkts, + input wire [15:0] cfg_fc_headroom_bytes, + input wire [7:0] cfg_fc_headroom_pkts, + // Flow control status + output reg fc_enabled = 1'b0, + output reg [39:0] capacity_bytes = 40'd0, + output reg [23:0] capacity_pkts = 24'd0, + // Stream status + output wire seq_err_stb, + output reg [31:0] seq_err_cnt = 32'd0, + output wire data_err_stb, + output reg [31:0] data_err_cnt = 32'd0, + output wire route_err_stb, + output reg [31:0] route_err_cnt = 32'd0 +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_chdr_internal_utils.vh" + + localparam CHDR_W_LOG2 = $clog2(CHDR_W); + + // --------------------------------------------------- + // Output packet gate + // --------------------------------------------------- + reg [CHDR_W-1:0] chdr_out_tdata; + reg chdr_out_tlast, chdr_out_tvalid; + wire chdr_out_tready; + + axi_packet_gate #( + .WIDTH(CHDR_W), .SIZE(MTU), .USE_AS_BUFF(0) + ) chdr_pkt_gate_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata(chdr_out_tdata), .i_tlast(chdr_out_tlast), .i_terror(1'b0), + .i_tvalid(chdr_out_tvalid), .i_tready(chdr_out_tready), + .o_tdata(m_axis_chdr_tdata), .o_tlast(m_axis_chdr_tlast), + .o_tvalid(m_axis_chdr_tvalid), .o_tready(m_axis_chdr_tready) + ); + + // --------------------------------------------------- + // Flow Control State + // --------------------------------------------------- + + // send_cnt: Total transfer count at the sender (here) + // recv_cnt: Total transfer count at the receiver + // accum: Transfer count since last FC resynchronization request + // headroom: Total headroom to keep in the downstream buffer + // adj_cap: The adjusted capacity (after headroom) of the downstream buffer + // strc_cnt: Saved count for the STRC packet (prevents mid-packet updates) + reg [63:0] send_cnt_bytes = 64'd0; + reg [39:0] send_cnt_pkts = 40'd0; + reg [63:0] recv_cnt_bytes = 64'd0; + reg [39:0] recv_cnt_pkts = 40'd0; + reg [39:0] accum_bytes = 40'd0; + reg [23:0] accum_pkts = 24'd0; + reg [15:0] headroom_bytes = 16'd0; + reg [ 7:0] headroom_pkts = 8'd0; + reg [39:0] adj_cap_bytes = 40'd0; + reg [23:0] adj_cap_pkts = 24'd0; + reg [63:0] strc_cnt_bytes = 64'd0; + + // Output transfer count + always @(posedge clk) begin + if (rst || !fc_enabled) begin + send_cnt_bytes <= 64'd0; + send_cnt_pkts <= 40'd0; + end else if (chdr_out_tvalid && chdr_out_tready) begin + send_cnt_bytes <= send_cnt_bytes + (CHDR_W/8); + if (chdr_out_tlast) + send_cnt_pkts <= send_cnt_pkts + 40'd1; + end + end + + // Buffer occupied counts + // TODO: Need better overflow handling + wire signed [64:0] occupied_bytes = + $signed({1'b0, send_cnt_bytes}) - $signed({1'b0, recv_cnt_bytes}); + wire signed [40:0] occupied_pkts = + $signed({1'b0, send_cnt_pkts}) - $signed({1'b0, recv_cnt_pkts}); + + // OK-to-Send shift register + // - Why a shift-register here? + // To allow the tools to re-time the wide comparators. + // - We don't care about the latency here because stream + // status messages are asynchronous wrt the data + reg [3:0] ok_shreg = 4'b1111; // OK to send? (shift register) + always @(posedge clk) begin + if (rst || !fc_enabled) begin + ok_shreg <= 4'b1111; + end else begin + ok_shreg <= {ok_shreg[2:0], ( + (occupied_bytes[40:0] < $signed({1'b0, adj_cap_bytes})) && + (occupied_pkts [24:0] < $signed({1'b0, adj_cap_pkts })) + )}; + end + end + wire ok_to_send = ok_shreg[3]; + + // Accumulated transfer count updater for FC resync + reg lossy_xport = 1'b0; + reg [3:0] fc_resync_req_shreg = 4'h0; + wire fc_resync_req, fc_resync_ack; + + always @(posedge clk) begin + if (rst || !fc_enabled || !lossy_xport || fc_resync_ack) begin + // Reset + accum_bytes <= 40'd0; + accum_pkts <= 24'd0; + fc_resync_req_shreg <= 4'b0000; + end else begin + if (chdr_out_tvalid && chdr_out_tready) begin + // Count + accum_bytes <= accum_bytes + (CHDR_W/8); + if (chdr_out_tlast) + accum_pkts <= accum_pkts + 24'd1; + end + // FC resync request + fc_resync_req_shreg <= {fc_resync_req_shreg[2:0], + (accum_bytes > capacity_bytes) || (accum_pkts > capacity_pkts)}; + end + end + assign fc_resync_req = fc_resync_req_shreg[3]; + + // --------------------------------------------------- + // Stream Status Parser + // --------------------------------------------------- + + wire [3:0] msg_i_tdata, msg_o_tdata; + wire msg_i_tvalid, msg_o_tvalid; + wire msg_i_tready, msg_o_tready; + + axi_fifo #(.WIDTH(4), .SIZE(1)) msg_fifo_i ( + .clk(clk), .reset(rst), .clear(1'b0), + .i_tdata(msg_i_tdata), .i_tvalid(msg_i_tvalid), .i_tready(msg_i_tready), + .o_tdata(msg_o_tdata), .o_tvalid(msg_o_tvalid), .o_tready(msg_o_tready), + .space(), .occupied() + ); + + localparam [2:0] ST_STRS_HDR = 3'd0; // Receiving the CHDR header of a stream status msg + localparam [2:0] ST_STRS_W0 = 3'd1; // Receiving the first word of a stream status msg + localparam [2:0] ST_STRS_W1 = 3'd2; // Receiving the second word of a stream status msg + localparam [2:0] ST_STRS_W2 = 3'd3; // Receiving the third word of a stream status msg + localparam [2:0] ST_STRS_W3 = 3'd4; // Receiving the fourth word of a stream status msg + localparam [2:0] ST_STRS_LATCH = 3'd5; // Atomically updating and posting the status msg + localparam [2:0] ST_STRS_DROP = 3'd6; // Something went wrong dropping current packet + + reg [2:0] strs_state = ST_STRS_HDR; + reg strs_too_long = 1'b0; + reg [15:0] cached_dst_epid = 16'd0; + reg [255:0] cached_strs_msg; + + always @(posedge clk) begin + if (rst) begin + strs_state <= ST_STRS_HDR; + strs_too_long <= 1'b0; + end else begin + case (strs_state) + + // ST_STRS_HDR + // ------------------ + ST_STRS_HDR: begin + if (s_axis_strs_tvalid) begin + // Only accept stream status packets. Drop everything else + if (chdr_get_pkt_type(s_axis_strs_tdata[63:0]) == CHDR_PKT_TYPE_STRS) + strs_state <= ST_STRS_W0; + else + strs_state <= ST_STRS_DROP; + strs_too_long <= 1'b0; + end + end + + // ST_STRS_W0 + // ------------------ + // - Cache the first word of the stream status + // - For CHDR_W == 64, this is one of 4 words. + // - For CHDR_W == 128, this is one of 2 words. + // - For CHDR_W >= 256, this is the only word. + ST_STRS_W0: begin + if (s_axis_strs_tvalid) begin + if (CHDR_W == 64) begin + cached_strs_msg[63:0] <= s_axis_strs_tdata[63:0]; + strs_state <= !s_axis_strs_tlast ? ST_STRS_W1 : ST_STRS_HDR; + end else if (CHDR_W == 128) begin + cached_strs_msg[127:0] <= s_axis_strs_tdata[127:0]; + strs_state <= !s_axis_strs_tlast ? ST_STRS_W1 : ST_STRS_HDR; + end else begin //CHDR_W >= 256 + cached_strs_msg[255:0] <= s_axis_strs_tdata[255:0]; + strs_state <= ST_STRS_LATCH; + strs_too_long <= !s_axis_strs_tlast; + end + end + end + + // ST_STRS_W1 + // ------------------ + // - Cache the second word of the stream status + ST_STRS_W1: begin + if (s_axis_strs_tvalid) begin + if (CHDR_W == 64) begin + cached_strs_msg[127:64] <= s_axis_strs_tdata[63:0]; + strs_state <= !s_axis_strs_tlast ? ST_STRS_W2 : ST_STRS_HDR; + end else begin //CHDR_W >= 128 + cached_strs_msg[255:128] <= s_axis_strs_tdata[127:0]; + strs_state <= ST_STRS_LATCH; + strs_too_long <= !s_axis_strs_tlast; + end + end + end + + // ST_STRS_W2 + // ------------------ + // - Cache the third word of the stream status + ST_STRS_W2: begin + if (s_axis_strs_tvalid) begin + cached_strs_msg[191:128] <= s_axis_strs_tdata[63:0]; + strs_state <= !s_axis_strs_tlast ? ST_STRS_W3 : ST_STRS_HDR; + end + end + + // ST_STRS_W3 + // ------------------ + // - Cache the fourth word of the stream status + ST_STRS_W3: begin + if (s_axis_strs_tvalid) begin + cached_strs_msg[255:192] <= s_axis_strs_tdata[63:0]; + strs_state <= ST_STRS_LATCH; + strs_too_long <= !s_axis_strs_tlast; + end + end + + // ST_STRS_LATCH + // ------------------ + // - Act on the received stream status + ST_STRS_LATCH: begin + capacity_bytes <= chdr256_strs_get_capacity_bytes(cached_strs_msg); + capacity_pkts <= chdr256_strs_get_capacity_pkts(cached_strs_msg); + recv_cnt_bytes <= chdr256_strs_get_xfercnt_bytes(cached_strs_msg); + recv_cnt_pkts <= chdr256_strs_get_xfercnt_pkts(cached_strs_msg); + adj_cap_bytes <= chdr256_strs_get_capacity_bytes(cached_strs_msg) - + {24'd0, headroom_bytes[15:(CHDR_W_LOG2-3)], {(CHDR_W_LOG2-3){1'b0}}}; + adj_cap_pkts <= chdr256_strs_get_capacity_pkts(cached_strs_msg) - + {16'd0, headroom_pkts}; + if (msg_i_tready) begin + strs_state <= strs_too_long ? ST_STRS_DROP : ST_STRS_HDR; + end + end + + // ST_STRS_DROP + // ------------------ + ST_STRS_DROP: begin + if (s_axis_strs_tvalid && s_axis_strs_tlast) + strs_state <= ST_STRS_HDR; + end + default: begin + // We should never get here + strs_state <= ST_STRS_HDR; + end + endcase + end + end + + assign s_axis_strs_tready = (strs_state != ST_STRS_LATCH); + + assign msg_i_tvalid = (strs_state == ST_STRS_LATCH); + assign msg_i_tdata = (chdr256_strs_get_src_epid(cached_strs_msg) != cached_dst_epid) ? + CHDR_STRS_STATUS_CMDERR : chdr256_strs_get_status(cached_strs_msg); + + + // --------------------------------------------------- + // Main State Machine + // --------------------------------------------------- + + localparam [2:0] ST_PASS_DATA = 3'd0; // Passing input axis_data out + localparam [2:0] ST_STRC_HDR = 3'd1; // Sending CHDR header for stream cmd + localparam [2:0] ST_STRC_W0 = 3'd2; // Sending first word of stream cmd + localparam [2:0] ST_STRC_W1 = 3'd3; // Sending second word of stream cmd + localparam [2:0] ST_STRC_WAIT = 3'd4; // Waiting for response (stream status) + localparam [2:0] ST_INIT_DLY = 3'd5; // Finishing command execution + + reg [2:0] state = ST_PASS_DATA; + reg mid_pkt = 1'b0; + reg [15:0] data_seq_num = 16'd0; + reg [15:0] strc_seq_num = 16'd0; + reg [2:0] cfg_delay = 3'd0; + + always @(posedge clk) begin + if (rst) begin + state <= ST_PASS_DATA; + mid_pkt <= 1'b0; + data_seq_num <= 16'd0; + strc_seq_num <= 16'd0; + cfg_pending <= 1'b0; + cfg_failed <= 1'b0; + end else begin + case (state) + + // ST_PASS_DATA + // ------------------ + // This is the default state where input data is passed to the + // output port. Flow control is enforced in this state. + // This state also serves as the launch state for a configuration + // operation (using cfg_start) + ST_PASS_DATA: begin + // Update the mid_pkt flag and sequence number + if (chdr_out_tvalid && chdr_out_tready) begin + mid_pkt <= !chdr_out_tlast; + if (chdr_out_tlast) + data_seq_num <= data_seq_num + 16'd1; + end + // Launch a configuration operation + if (cfg_start) begin + // Latch cfg command + cfg_pending <= 1'b1; + cfg_failed <= 1'b0; + // Disable flow control + fc_enabled <= 1'b0; + // Cache relevant data from the cfg cmd + lossy_xport <= cfg_lossy_xport; + cached_dst_epid <= cfg_dst_epid; + headroom_bytes <= cfg_fc_headroom_bytes; + headroom_pkts <= cfg_fc_headroom_pkts; + end + // Wait for current packet to transfer then begin the + // configuration process or stream command + if (cfg_start || cfg_pending || fc_resync_req) begin + if (mid_pkt) begin + if (chdr_out_tvalid && chdr_out_tready && chdr_out_tlast) + state <= ST_STRC_HDR; + end else begin + if (!(chdr_out_tvalid && chdr_out_tready)) + state <= ST_STRC_HDR; + end + end + end + + // ST_STRC_HDR + // ------------------ + // Send the CHDR header for a stream command + ST_STRC_HDR: begin + if (chdr_out_tvalid && chdr_out_tready) begin + state <= ST_STRC_W0; + // Update seqnum for the next packet + strc_seq_num <= strc_seq_num + 16'd1; + end + // Update byte count for stream command + strc_cnt_bytes <= send_cnt_bytes; + end + + // ST_STRC_W0 + // ------------------ + // Send the first line of a stream command + ST_STRC_W0: begin + if (chdr_out_tvalid && chdr_out_tready) + if (CHDR_W < 128) + state <= ST_STRC_W1; + else + state <= ST_STRC_WAIT; + end + + // ST_STRC_W1 + // ------------------ + // Send the second line of a stream command + ST_STRC_W1: begin + if (chdr_out_tvalid && chdr_out_tready) + state <= fc_resync_req ? ST_PASS_DATA : ST_STRC_WAIT; + end + + // ST_STRC_WAIT + // ------------------ + // Done sending stream command. Wait for a response + ST_STRC_WAIT: begin + // Wait for a new response to arrive + if (msg_o_tvalid) begin + if (msg_o_tdata == CHDR_STRS_STATUS_OKAY) begin + state <= ST_INIT_DLY; + cfg_delay <= 3'd4; + fc_enabled <= 1'b1; + data_seq_num <= 16'd0; + strc_seq_num <= 16'd0; + end else begin + state <= ST_PASS_DATA; + cfg_failed <= 1'b1; + cfg_pending <= 1'b0; + end + end + end + + // ST_INIT_DLY + // ------------------ + // Delay matching state for ok_shreg + ST_INIT_DLY: begin + if (cfg_delay == 3'd0) begin + state <= ST_PASS_DATA; + cfg_pending <= 1'b0; + end else begin + cfg_delay <= cfg_delay - 3'd1; + end + end + + // We should never get here + default: begin + state <= ST_PASS_DATA; + end + endcase + end + end + + // Header for output CHDR data + wire [CHDR_W-1:0] data_header; + assign data_header[63:0] = chdr_set_seq_num( + chdr_set_dst_epid(s_axis_data_tdata[63:0], cached_dst_epid), + data_seq_num); + generate if (CHDR_W > 64) + assign data_header[CHDR_W-1:64] = s_axis_data_tdata[CHDR_W-1:64]; + endgenerate + + // Header for stream command + wire [CHDR_W-1:0] strc_header; + assign strc_header[63:0] = chdr_build_header( + /*VC*/ 6'd0, /*eob*/ 1'b0, /*eov*/ 1'b0, CHDR_PKT_TYPE_STRC, CHDR_NO_MDATA, + strc_seq_num, 16'd16+(CHDR_W/8), cached_dst_epid); + generate if (CHDR_W > 64) + assign strc_header[CHDR_W-1:64] = {(CHDR_W-64){1'b0}}; + endgenerate + + // Payload for stream command + wire [127:0] strc_init_payload = chdr128_strc_build( + {24'h0, cfg_fc_freq_bytes}, {16'h0, cfg_fc_freq_pkts}, + /*op_data*/ 4'h0, CHDR_STRC_OPCODE_INIT, cfg_this_epid); + wire [127:0] strc_resync_payload = chdr128_strc_build( + strc_cnt_bytes, send_cnt_pkts, + /*op_data*/ 4'h0, CHDR_STRC_OPCODE_RESYNC, cfg_this_epid); + wire [127:0] strc_payload = fc_resync_req ? strc_resync_payload : strc_init_payload; + + always @(*) begin + case (state) + ST_PASS_DATA: begin + chdr_out_tdata = mid_pkt ? s_axis_data_tdata : data_header; + chdr_out_tlast = s_axis_data_tlast; + chdr_out_tvalid = s_axis_data_tvalid && ok_to_send; + end + ST_STRC_HDR: begin + chdr_out_tdata = strc_header; + chdr_out_tlast = 1'b0; + chdr_out_tvalid = ok_to_send; + end + ST_STRC_W0: begin + chdr_out_tdata = strc_payload; + chdr_out_tlast = (CHDR_W < 128) ? 1'b0 : 1'b1; + chdr_out_tvalid = ok_to_send; + end + ST_STRC_W1: begin + // We will enter this state only if CHDR_W = 64 + chdr_out_tdata = strc_payload[127:64]; + chdr_out_tlast = 1'b1; + chdr_out_tvalid = ok_to_send; + end + default: begin + chdr_out_tdata = {CHDR_W{1'b0}}; + chdr_out_tlast = 1'b0; + chdr_out_tvalid = 1'b0; + end + endcase + end + assign s_axis_data_tready = (state == ST_PASS_DATA) && chdr_out_tready && ok_to_send; + + // Consume all messages when passing data forward. The flow control state is automatically + // updated outside the message FIFO. When a stream command is issued, we wait for the + // "wait" state to consume responses. + assign msg_o_tready = msg_o_tvalid && (state == ST_PASS_DATA || state == ST_STRC_WAIT); + + // Acknowledge a flow control resync command + assign fc_resync_ack = fc_resync_req && (state == ST_STRC_W1) && + chdr_out_tvalid && chdr_out_tready && chdr_out_tlast; + + // --------------------------------------------------- + // Stream Status Reporting + // --------------------------------------------------- + + wire runtime_err_stb = msg_o_tvalid && msg_o_tready && (state == ST_PASS_DATA); + assign seq_err_stb = runtime_err_stb && (msg_o_tdata == CHDR_STRS_STATUS_SEQERR); + assign data_err_stb = runtime_err_stb && (msg_o_tdata == CHDR_STRS_STATUS_DATAERR); + assign route_err_stb = runtime_err_stb && (msg_o_tdata == CHDR_STRS_STATUS_RTERR); + + always @(posedge clk) begin + if (rst || !fc_enabled) begin + seq_err_cnt <= 32'd0; + data_err_cnt <= 32'd0; + route_err_cnt <= 32'd0; + end else begin + if (seq_err_stb) + seq_err_cnt <= seq_err_cnt + 32'd1; + if (data_err_stb) + data_err_cnt <= data_err_cnt + 32'd1; + if (route_err_stb) + route_err_cnt <= route_err_cnt + 32'd1; + end + end + +endmodule // chdr_stream_output diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_ctrl.v b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_ctrl.v new file mode 100644 index 000000000..1f9dba2eb --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_ctrl.v @@ -0,0 +1,319 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_to_axis_ctrl +// Description: +// Converts from CHDR to AXIS-Control and vice versa. +// This module has to handle remote control transactions +// correctly. The CHDR frame has/needs the DstEPID, DstPort +// SrcEPID and SrcPort and the AXIS-Ctrl frame has/needs +// the DstPort, SrcPort, RemDstEPID and RemDstPort. +// +// Parameters: +// - CHDR_W: Width of the CHDR bus in bits +// - THIS_PORTID: The port number of the control xbar +// that this module is connected to. +// +// Signals: +// - s_rfnoc_chdr_* : Input CHDR stream (AXI-Stream) +// - m_rfnoc_chdr_* : Output CHDR stream (AXI-Stream) +// - s_rfnoc_ctrl_* : Input control stream (AXI-Stream) +// - m_rfnoc_ctrl_* : Output control stream (AXI-Stream) + +module chdr_to_axis_ctrl #( + parameter CHDR_W = 256, + parameter [9:0] THIS_PORTID = 10'd0 +)( + // CHDR Bus (master and slave) + input wire rfnoc_chdr_clk, + input wire rfnoc_chdr_rst, + input wire [15:0] this_epid, + input wire [CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire s_rfnoc_chdr_tlast, + input wire s_rfnoc_chdr_tvalid, + output wire s_rfnoc_chdr_tready, + output wire [CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire m_rfnoc_chdr_tlast, + output wire m_rfnoc_chdr_tvalid, + input wire m_rfnoc_chdr_tready, + // AXIS-Control Bus (master and slave) + input wire rfnoc_ctrl_clk, + input wire rfnoc_ctrl_rst, + input wire [31:0] s_rfnoc_ctrl_tdata, + input wire s_rfnoc_ctrl_tlast, + input wire s_rfnoc_ctrl_tvalid, + output wire s_rfnoc_ctrl_tready, + output wire [31:0] m_rfnoc_ctrl_tdata, + output wire m_rfnoc_ctrl_tlast, + output wire m_rfnoc_ctrl_tvalid, + input wire m_rfnoc_ctrl_tready +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + localparam [1:0] ST_CHDR_HDR = 2'd0; // Processing the CHDR header + localparam [1:0] ST_CHDR_MDATA = 2'd1; // Processing the CHDR metadata + localparam [1:0] ST_CTRL_HDR = 2'd2; // Processing the CHDR control header + localparam [1:0] ST_CTRL_BODY = 2'd3; // Processing the CHDR control body + + // --------------------------------------------------- + // Input/output register slices + // --------------------------------------------------- + // - ch2ct: CHDR to Ctrl + // - ct2ch: Ctrl to CHDR + + wire [CHDR_W-1:0] ch2ct_tdata, ct2ch_tdata; + wire ch2ct_tlast, ct2ch_tlast; + wire ch2ct_tvalid, ct2ch_tvalid; + wire ch2ct_tready, ct2ch_tready; + + axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(1)) ch2ct_reg_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst), .clear(1'b0), + .i_tdata({s_rfnoc_chdr_tlast, s_rfnoc_chdr_tdata}), + .i_tvalid(s_rfnoc_chdr_tvalid), .i_tready(s_rfnoc_chdr_tready), + .o_tdata({ch2ct_tlast, ch2ct_tdata}), + .o_tvalid(ch2ct_tvalid), .o_tready(ch2ct_tready), + .space(), .occupied() + ); + + axi_fifo #(.WIDTH(CHDR_W+1), .SIZE(1)) ct2ch_reg_i ( + .clk(rfnoc_chdr_clk), .reset(rfnoc_chdr_rst), .clear(1'b0), + .i_tdata({ct2ch_tlast, ct2ch_tdata}), + .i_tvalid(ct2ch_tvalid), .i_tready(ct2ch_tready), + .o_tdata({m_rfnoc_chdr_tlast, m_rfnoc_chdr_tdata}), + .o_tvalid(m_rfnoc_chdr_tvalid), .o_tready(m_rfnoc_chdr_tready), + .space(), .occupied() + ); + + // --------------------------------------------------- + // CH2CT: CHDR => Ctrl path + // --------------------------------------------------- + // When converting CHDR => Ctrl we know we are dealing with + // a remote control transaction so we need to perform + // the following transformations to ensure that the packet + // has all the info to route downstream and has enough info + // to return to the master (of the transaction). + // - Use the CHDR DstPort as the Ctrl DstPort (forward the master's request) + // - Use THIS_PORTID as the Ctrl SrcPort (for the return path back here) + // - Use the CHDR SrcEPID as the Ctrl RemDstEPID (return path for CHDR packet) + // - Use the CHDR SrcPort as the Ctrl RemDstPort (return path in the downstream EP) + // - Ignore the CHDR DstEPID because packet is already here + + reg [1:0] ch2ct_state = ST_CHDR_HDR; + reg [4:0] ch2ct_nmdata = CHDR_NO_MDATA; + + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst) begin + ch2ct_state <= ST_CHDR_HDR; + end else if (ch2ct_tvalid && ch2ct_tready) begin + case (ch2ct_state) + ST_CHDR_HDR: begin + ch2ct_nmdata <= chdr_get_num_mdata(ch2ct_tdata[63:0]) - 5'd1; + if (!ch2ct_tlast) + ch2ct_state <= (chdr_get_num_mdata(ch2ct_tdata[63:0]) == 5'd0) ? + ST_CTRL_HDR : ST_CHDR_MDATA; + else + ch2ct_state <= ST_CHDR_HDR; // Premature termination + end + ST_CHDR_MDATA: begin + ch2ct_nmdata <= ch2ct_nmdata - 5'd1; + if (!ch2ct_tlast) + ch2ct_state <= (ch2ct_nmdata == CHDR_NO_MDATA) ? ST_CTRL_HDR : ST_CHDR_MDATA; + else + ch2ct_state <= ST_CHDR_HDR; // Premature termination + end + ST_CTRL_HDR: begin + ch2ct_state <= ch2ct_tlast ? ST_CHDR_HDR : ST_CTRL_BODY; + end + ST_CTRL_BODY: begin + if (ch2ct_tlast) + ch2ct_state <= ST_CHDR_HDR; + end + default: begin + // We should never get here + ch2ct_state <= ST_CHDR_HDR; + end + endcase + end + end + + wire [(CHDR_W/32)-1:0] ch2ct_tkeep; + chdr_compute_tkeep #(.CHDR_W(CHDR_W), .ITEM_W(32)) chdr_tkeep_gen_i ( + .clk(rfnoc_chdr_clk), .rst(rfnoc_chdr_rst), + .axis_tdata(ch2ct_tdata), .axis_tlast(ch2ct_tlast), + .axis_tvalid(ch2ct_tvalid), .axis_tready(ch2ct_tready), + .axis_tkeep(ch2ct_tkeep) + ); + + // Create the first two lines of the Ctrl word (wide) + // using data from CHDR packet + wire [CHDR_W-1:0] ch2ct_new_ctrl_hdr; + assign ch2ct_new_ctrl_hdr[63:0] = { + axis_ctrl_build_hdr_hi( + axis_ctrl_get_src_port(ch2ct_tdata[31:0]), + axis_ctrl_get_rem_dst_epid(ch2ct_tdata[63:32]) + ), + axis_ctrl_build_hdr_lo( + axis_ctrl_get_is_ack (ch2ct_tdata[31:0]), + axis_ctrl_get_has_time(ch2ct_tdata[31:0]), + axis_ctrl_get_seq_num (ch2ct_tdata[31:0]), + axis_ctrl_get_num_data(ch2ct_tdata[31:0]), + THIS_PORTID, + axis_ctrl_get_dst_port(ch2ct_tdata[31:0]) + ) + }; + generate if (CHDR_W > 64) begin + assign ch2ct_new_ctrl_hdr[CHDR_W-1:64] = ch2ct_tdata[CHDR_W-1:64]; + end endgenerate + + wire [CHDR_W-1:0] ch2ct_wctrl_tdata = + (ch2ct_state == ST_CTRL_HDR) ? ch2ct_new_ctrl_hdr : ch2ct_tdata; + + axis_width_conv #( + .WORD_W(32), .IN_WORDS(CHDR_W/32), .OUT_WORDS(1), + .SYNC_CLKS(0), .PIPELINE("OUT") + ) ctrl_downsizer_i ( + .s_axis_aclk(rfnoc_chdr_clk), .s_axis_rst(rfnoc_chdr_rst), + .s_axis_tdata(ch2ct_wctrl_tdata), + .s_axis_tkeep(ch2ct_tkeep), + .s_axis_tlast(ch2ct_tlast), + .s_axis_tvalid(ch2ct_tvalid && (ch2ct_state == ST_CTRL_HDR || ch2ct_state == ST_CTRL_BODY)), + .s_axis_tready(ch2ct_tready), + .m_axis_aclk(rfnoc_ctrl_clk), .m_axis_rst(rfnoc_ctrl_rst), + .m_axis_tdata(m_rfnoc_ctrl_tdata), + .m_axis_tkeep(/* Unused: OUT_WORDS=1 */), + .m_axis_tlast(m_rfnoc_ctrl_tlast), + .m_axis_tvalid(m_rfnoc_ctrl_tvalid), + .m_axis_tready(m_rfnoc_ctrl_tready) + ); + + // --------------------------------------------------- + // CT2CH: Ctrl => CHDR path + // --------------------------------------------------- + // When converting Ctrl => CHDR we know we are dealing with + // a remote control transaction so we need to perform + // the following transformations to ensure that the packet + // has all the info to route downstream and has enough info + // to return to the initiator of the transaction. + // - Use the Ctrl RemDstEPID as the CHDR DstEPID (forward the master's request) + // - Use the Ctrl RemDstPort as the CHDR DstPort (forward the master's request) + // - Use the this_epid as CHDR SrcEPID (return path for the CHDR packet) + // - Use the Ctrl SrcPort as the CHDR SrcPort (return path to the master) + // - Ignore the Ctrl DstPort because the packet has already been routed + + wire [CHDR_W-1:0] ct2ch_wctrl_tdata; + wire ct2ch_wctrl_tlast, ct2ch_wctrl_tvalid, ct2ch_wctrl_tready; + + axis_width_conv #( + .WORD_W(32), .IN_WORDS(1), .OUT_WORDS(CHDR_W/32), + .SYNC_CLKS(0), .PIPELINE("IN") + ) ctrl_upsizer_i ( + .s_axis_aclk(rfnoc_ctrl_clk), .s_axis_rst(rfnoc_ctrl_rst), + .s_axis_tdata(s_rfnoc_ctrl_tdata), + .s_axis_tkeep(/* Unused: IN_WORDS=1 */), + .s_axis_tlast(s_rfnoc_ctrl_tlast), + .s_axis_tvalid(s_rfnoc_ctrl_tvalid), + .s_axis_tready(s_rfnoc_ctrl_tready), + .m_axis_aclk(rfnoc_chdr_clk), .m_axis_rst(rfnoc_chdr_rst), + .m_axis_tdata(ct2ch_wctrl_tdata), + .m_axis_tkeep(/* Unused: We are updating the CHDR length */), + .m_axis_tlast(ct2ch_wctrl_tlast), + .m_axis_tvalid(ct2ch_wctrl_tvalid), + .m_axis_tready(ct2ch_wctrl_tready) + ); + + reg [1:0] ct2ch_state = ST_CHDR_HDR; + reg [15:0] ct2ch_seqnum = 16'd0; + + always @(posedge rfnoc_chdr_clk) begin + if (rfnoc_chdr_rst) begin + ct2ch_state <= ST_CHDR_HDR; + ct2ch_seqnum <= 16'd0; + end else if (ct2ch_tvalid && ct2ch_tready) begin + case (ct2ch_state) + ST_CHDR_HDR: begin + if (!ct2ch_tlast) + ct2ch_state <= ST_CTRL_HDR; + end + ST_CTRL_HDR: begin + if (ct2ch_tlast) + ct2ch_state <= ST_CHDR_HDR; + else + ct2ch_state <= ST_CTRL_BODY; + end + ST_CTRL_BODY: begin + if (ct2ch_tlast) + ct2ch_state <= ST_CHDR_HDR; + end + default: begin + // We should never get here + ct2ch_state <= ST_CHDR_HDR; + end + endcase + if (ct2ch_tlast) + ct2ch_seqnum <= ct2ch_seqnum + 16'd1; + end + end + + // Hold the first line to generate info for the outgoing CHDR header + assign ct2ch_wctrl_tready = (ct2ch_state == ST_CTRL_HDR || ct2ch_state == ST_CTRL_BODY) ? ct2ch_tready : 1'b0; + + wire [7:0] ct2ch_32bit_lines = 8'd3 + // Header + OpWord + (axis_ctrl_get_has_time(ct2ch_wctrl_tdata[31:0]) ? 8'd2 : 8'd0) + // Timestamp + ({4'h0, axis_ctrl_get_num_data(ct2ch_wctrl_tdata[31:0])}); // Data words + + wire [15:0] ct2ch_chdr_lines = 16'd1 + // CHDR header + ct2ch_32bit_lines[7:$clog2(CHDR_W/32)] + // Convert 32-bit lines to CHDR_W + (|ct2ch_32bit_lines[$clog2(CHDR_W/32)-1:0]); // Residue + + reg [63:0] ct2ch_chdr_tdata; + always @(*) begin + case (ct2ch_state) + ST_CHDR_HDR: begin + ct2ch_chdr_tdata = chdr_build_header( + 6'd0, /* VC */ + 1'b0, 1'b0, /* eob, eov */ + CHDR_PKT_TYPE_CTRL, + CHDR_NO_MDATA, + ct2ch_seqnum, + (ct2ch_chdr_lines << $clog2(CHDR_W/8)), /* length in bytes */ + axis_ctrl_get_rem_dst_epid(ct2ch_wctrl_tdata[63:32]) + ); + end + ST_CTRL_HDR: begin + ct2ch_chdr_tdata = { + axis_ctrl_build_hdr_hi( + 10'd0, /* Unused in CHDR Control payload */ + this_epid /* This is the SrcEPID */ + ), + axis_ctrl_build_hdr_lo( + axis_ctrl_get_is_ack (ct2ch_wctrl_tdata[31:0]), + axis_ctrl_get_has_time(ct2ch_wctrl_tdata[31:0]), + axis_ctrl_get_seq_num (ct2ch_wctrl_tdata[31:0]), + axis_ctrl_get_num_data(ct2ch_wctrl_tdata[31:0]), + axis_ctrl_get_src_port(ct2ch_wctrl_tdata[31:0]), + axis_ctrl_get_rem_dst_port(ct2ch_wctrl_tdata[63:32]) + ) + }; + end + default: begin + ct2ch_chdr_tdata = ct2ch_wctrl_tdata[63:0]; + end + endcase + end + + // Output signals + assign ct2ch_tdata[63:0] = ct2ch_chdr_tdata; + assign ct2ch_tlast = ct2ch_wctrl_tlast; + assign ct2ch_tvalid = ct2ch_wctrl_tvalid; + generate if (CHDR_W > 64) begin + assign ct2ch_tdata[CHDR_W-1:64] = ct2ch_wctrl_tdata[CHDR_W-1:64]; + end endgenerate + +endmodule // chdr_to_axis_ctrl diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_data.v b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_data.v new file mode 100644 index 000000000..a00a9952c --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_data.v @@ -0,0 +1,422 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_to_axis_data +// +// Description: +// +// A deframer module for CHDR data packets. It accepts an input CHDR stream +// and produces an output data stream that includes the payload of the +// packet, as well as timestamp and packet flags presented as sideband +// information. +// +// This module also performs an optional clock crossing and data width +// conversion from CHDR_W to a user requested width for the payload data bus. +// +// Parameters: +// - CHDR_W : Width of the input CHDR bus in bits +// - ITEM_W : Width of the output item bus in bits +// - NIPC : The number of output items delivered per cycle +// - SYNC_CLKS : Are the CHDR and data clocks synchronous to each other? +// - INFO_FIFO_SIZE : Log2 of the FIFO size for the packet info data path +// - PYLD_FIFO_SIZE : Log2 of the FIFO size for the payload data path +// +// Signals: +// - s_axis_chdr_* : Input CHDR stream (AXI-Stream) +// - m_axis_* : Output payload data stream (AXI-Stream) +// - m_axis_mdata_* : Output mdata stream (AXI-Stream) +// - flush_* : Signals for flush control and status +// + +module chdr_to_axis_data #( + parameter CHDR_W = 256, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter SYNC_CLKS = 0, + parameter INFO_FIFO_SIZE = 5, + parameter PYLD_FIFO_SIZE = 5 +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + input wire axis_data_clk, + input wire axis_data_rst, + // CHDR in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + // Payload data stream out (AXI-Stream) + output wire [(ITEM_W*NIPC)-1:0] m_axis_tdata, + output wire [NIPC-1:0] m_axis_tkeep, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready, + // Payload sideband information + output wire [63:0] m_axis_ttimestamp, + output wire m_axis_thas_time, + output wire [15:0] m_axis_tlength, + output wire m_axis_teob, + output wire m_axis_teov, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Pipeline + // --------------------------------------------------- + localparam CHDR_KEEP_W = CHDR_W/ITEM_W; + + wire [CHDR_W-1:0] in_chdr_tdata; + wire [CHDR_KEEP_W-1:0] in_chdr_tkeep; + wire in_chdr_tlast, in_chdr_tvalid; + reg in_chdr_tready; + + axi_fifo_flop2 #(.WIDTH(CHDR_W+1)) in_pipe_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), .clear(1'b0), + .i_tdata({s_axis_chdr_tlast, s_axis_chdr_tdata}), + .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready), + .o_tdata({in_chdr_tlast, in_chdr_tdata}), + .o_tvalid(in_chdr_tvalid), .o_tready(in_chdr_tready), + .space(), .occupied() + ); + + chdr_compute_tkeep #(.CHDR_W(CHDR_W), .ITEM_W(ITEM_W)) tkeep_gen_i ( + .clk(axis_chdr_clk), .rst(axis_chdr_rst), + .axis_tdata(in_chdr_tdata), .axis_tlast(in_chdr_tlast), + .axis_tvalid(in_chdr_tvalid), .axis_tready(in_chdr_tready), + .axis_tkeep(in_chdr_tkeep) + ); + + // --------------------------------------------------- + // Input State Machine + // --------------------------------------------------- + localparam INFO_W = 64+1+16+1+1; // timestamp, has_time, length, eob, eov + + wire [CHDR_W-1:0] in_pyld_tdata; + wire [CHDR_KEEP_W-1:0] in_pyld_tkeep; + wire in_pyld_tlast, in_pyld_tvalid, in_pyld_tready; + + reg [INFO_W-1:0] in_info_tdata; + reg in_info_tvalid; + wire in_info_tready; + + localparam [2:0] ST_HDR = 3'd0; // Processing the input CHDR header + localparam [2:0] ST_TS = 3'd1; // Processing the input CHDR timestamp + localparam [2:0] ST_MDATA = 3'd2; // Processing the input CHDR metadata word + localparam [2:0] ST_BODY = 3'd3; // Processing the input CHDR payload word + localparam [2:0] ST_DROP = 3'd4; // Something went wrong... Dropping packet + + reg [2:0] state = ST_HDR; + reg [4:0] mdata_pending = CHDR_NO_MDATA; + + reg [15:0] chdr_length_reg; + reg chdr_eob_reg, chdr_eov_reg; + + // Shortcuts: CHDR header + wire [2:0] in_pkt_type = chdr_get_pkt_type(in_chdr_tdata[63:0]); + wire [4:0] in_num_mdata = chdr_get_num_mdata(in_chdr_tdata[63:0]); + + always @(posedge axis_chdr_clk) begin + if (axis_chdr_rst) begin + state <= ST_HDR; + end else if (in_chdr_tvalid & in_chdr_tready) begin + case (state) + + // ST_HDR: CHDR Header + // ------------------- + ST_HDR: begin + // Always cache the number of metadata words + mdata_pending <= in_num_mdata; + // Figure out the next state + if (!in_chdr_tlast) begin + if (CHDR_W > 64) begin + // When CHDR_W > 64, the timestamp is a part of the header word. + // If this is a data packet (with/without a TS), we move on to the metadata/body + // state otherwise we drop it. Non-data packets should never reach here. + if (in_pkt_type == CHDR_PKT_TYPE_DATA || in_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + if (in_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin + state <= ST_DROP; + end + end else begin + // When CHDR_W == 64, the timestamp comes after the header. Check if this is a data + // packet with a TS to figure out the next state. If no TS, then check for metadata + // to move to the next state. Drop any non-data packets. + chdr_length_reg <= chdr_calc_payload_length(CHDR_W, in_chdr_tdata); + chdr_eob_reg <= chdr_get_eob(in_chdr_tdata); + chdr_eov_reg <= chdr_get_eov(in_chdr_tdata); + if (in_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + state <= ST_TS; + end else if (in_pkt_type == CHDR_PKT_TYPE_DATA) begin + if (in_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin + state <= ST_DROP; + end + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_TS: Timestamp (CHDR_W == 64 only) + // ------------------------------------ + ST_TS: begin + if (!in_chdr_tlast) begin + if (mdata_pending != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_MDATA: Metadata word + // ----------------------- + ST_MDATA: begin + if (!in_chdr_tlast) begin + // Count down metadata and stop at 1 + if (mdata_pending == 5'd1) begin + state <= ST_BODY; + end else begin + mdata_pending <= mdata_pending - 5'd1; + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_BODY: Payload word + // --------------------- + ST_BODY: begin + if (in_chdr_tlast) begin + state <= ST_HDR; + end + end + + // ST_DROP: Drop current packet + // ---------------------------- + ST_DROP: begin + if (in_chdr_tlast) begin + state <= ST_HDR; + end + end + + default: begin + // We should never get here + state <= ST_HDR; + end + endcase + end + end + + // CHDR data goes to the payload stream only in the BODY state. Packets are + // expected to have at least one payload word so the CHDR tlast can be used + // as the payload tlast. + assign in_pyld_tdata = in_chdr_tdata; + assign in_pyld_tkeep = in_chdr_tkeep; + assign in_pyld_tlast = in_chdr_tlast; + assign in_pyld_tvalid = in_chdr_tvalid && (state == ST_BODY); + + always @(*) begin + // Packet timestamp and flags go into the info FIFO, but only if it's a + // data packet since non-data packets will be discarded. + if (CHDR_W > 64) begin + // When CHDR_W > 64, all info will be in the first word of the CHDR packet + in_info_tdata = { in_chdr_tdata[127:64], + chdr_get_has_time(in_chdr_tdata), + chdr_calc_payload_length(CHDR_W, in_chdr_tdata), + chdr_get_eob(in_chdr_tdata), + chdr_get_eov(in_chdr_tdata) }; + in_info_tvalid = in_chdr_tvalid && (state == ST_HDR && + (in_pkt_type == CHDR_PKT_TYPE_DATA || in_pkt_type == CHDR_PKT_TYPE_DATA_TS)); + end else begin + // When CHDR_W == 64, the flags will be in the first word of the packet, + // but the timestamp will be in the second word, if there is a timestamp. + if (state == ST_HDR && in_pkt_type == CHDR_PKT_TYPE_DATA) begin + // No timestamp in this case + in_info_tdata = { in_chdr_tdata[63:0], 1'b0, + chdr_calc_payload_length(CHDR_W, in_chdr_tdata), + chdr_get_eob(in_chdr_tdata), chdr_get_eov(in_chdr_tdata) }; + in_info_tvalid = in_chdr_tvalid; + end else begin + // Assuming timestamp is present, so use flags from previous clock cycle + in_info_tdata = { in_chdr_tdata[63:0], 1'b1, chdr_length_reg, + chdr_eob_reg, chdr_eov_reg }; + in_info_tvalid = in_chdr_tvalid && (state == ST_TS); + end + end + + case (state) + ST_HDR : in_chdr_tready = in_info_tready; + ST_TS : in_chdr_tready = in_info_tready; + ST_MDATA : in_chdr_tready = 1'b1; + ST_BODY : in_chdr_tready = in_pyld_tready; + ST_DROP : in_chdr_tready = 1'b1; + default : in_chdr_tready = 1'b0; + endcase + end + + // --------------------------------------------------- + // Payload and mdata FIFOs + // --------------------------------------------------- + wire [CHDR_W-1:0] out_pyld_tdata; + wire [CHDR_KEEP_W-1:0] out_pyld_tkeep; + wire out_pyld_tlast, out_pyld_tvalid, out_pyld_tready; + + wire [INFO_W-1:0] out_info_tdata; + wire out_info_tvalid, out_info_tready; + + wire [(ITEM_W*NIPC)-1:0] conv_pyld_tdata; + wire [NIPC-1:0] conv_pyld_tkeep; + wire conv_pyld_tlast, conv_pyld_tvalid, conv_pyld_tready; + + + generate if (SYNC_CLKS) begin : gen_sync_fifo + axi_fifo #(.WIDTH(INFO_W), .SIZE(INFO_FIFO_SIZE)) info_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata(in_info_tdata), + .i_tvalid(in_info_tvalid), .i_tready(in_info_tready), + .o_tdata(out_info_tdata), + .o_tvalid(out_info_tvalid), .o_tready(out_info_tready), + .space(), .occupied() + ); + axi_fifo #(.WIDTH(CHDR_W+CHDR_KEEP_W+1), .SIZE(PYLD_FIFO_SIZE)) pyld_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata({in_pyld_tlast, in_pyld_tkeep, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_tdata({out_pyld_tlast, out_pyld_tkeep, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready), + .space(), .occupied() + ); + end else begin : gen_async_fifo + axi_fifo_2clk #(.WIDTH(INFO_W), .SIZE(INFO_FIFO_SIZE)) info_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata(in_info_tdata), + .i_tvalid(in_info_tvalid), .i_tready(in_info_tready), + .o_aclk(axis_data_clk), + .o_tdata(out_info_tdata), + .o_tvalid(out_info_tvalid), .o_tready(out_info_tready) + ); + axi_fifo_2clk #(.WIDTH(CHDR_W+CHDR_KEEP_W+1), .SIZE(PYLD_FIFO_SIZE)) pyld_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({in_pyld_tlast, in_pyld_tkeep, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_aclk(axis_data_clk), + .o_tdata({out_pyld_tlast, out_pyld_tkeep, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready) + ); + end endgenerate + + // --------------------------------------------------- + // Data Width Converter: CHDR_W => ITEM_W*NIPC + // --------------------------------------------------- + generate + if (CHDR_W != ITEM_W*NIPC) begin : gen_axis_width_conv + axis_width_conv #( + .WORD_W(ITEM_W), .IN_WORDS(CHDR_W/ITEM_W), .OUT_WORDS(NIPC), + .SYNC_CLKS(1), .PIPELINE("NONE") + ) payload_width_conv_i ( + .s_axis_aclk(axis_data_clk), .s_axis_rst(axis_data_rst), + .s_axis_tdata(out_pyld_tdata), .s_axis_tkeep(out_pyld_tkeep), + .s_axis_tlast(out_pyld_tlast), .s_axis_tvalid(out_pyld_tvalid), + .s_axis_tready(out_pyld_tready), + .m_axis_aclk(axis_data_clk), .m_axis_rst(axis_data_rst), + .m_axis_tdata(conv_pyld_tdata), .m_axis_tkeep(conv_pyld_tkeep), + .m_axis_tlast(conv_pyld_tlast), .m_axis_tvalid(conv_pyld_tvalid), + .m_axis_tready(conv_pyld_tready) + ); + end else begin : no_gen_axis_width_conv + assign conv_pyld_tdata = out_pyld_tdata; + assign conv_pyld_tkeep = out_pyld_tkeep; + assign conv_pyld_tlast = out_pyld_tlast; + assign conv_pyld_tvalid = out_pyld_tvalid; + assign out_pyld_tready = conv_pyld_tready; + end + endgenerate + + // --------------------------------------------------- + // Merge payload and info streams + // --------------------------------------------------- + // There should be one info word for each payload packet. + wire [INFO_W+(ITEM_W+1)*NIPC-1:0] flush_tdata; + wire flush_tlast; + wire flush_tvalid; + wire flush_tready; + + assign flush_tdata = { out_info_tdata, conv_pyld_tkeep, conv_pyld_tdata }; + assign flush_tlast = conv_pyld_tlast; + assign flush_tvalid = conv_pyld_tvalid && out_info_tvalid; + assign conv_pyld_tready = flush_tready && out_info_tvalid; + assign out_info_tready = conv_pyld_tready && conv_pyld_tlast && conv_pyld_tvalid; + + // --------------------------------------------------- + // Flushing Logic + // --------------------------------------------------- + wire [31:0] flush_timeout_dclk; + wire flush_en_dclk; + wire flush_active_pyld_cclk; + wire flush_done_pyld_cclk; + wire flush_active_pyld; + wire flush_done_pyld; + + synchronizer #(.WIDTH(2), .INITIAL_VAL(4'd0)) flush_2clk_rb_i ( + .clk(axis_chdr_clk), .rst(1'b0), + .in({flush_active_pyld, flush_done_pyld}), + .out({flush_active_pyld_cclk, flush_done_pyld_cclk}) + ); + assign flush_active = flush_active_pyld_cclk; + assign flush_done = flush_done_pyld_cclk; + + axi_fifo_2clk #(.WIDTH(33), .SIZE(1)) flush_2clk_ctrl_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({flush_en, flush_timeout}), .i_tvalid(1'b1), .i_tready(), + .o_aclk(axis_data_clk), + .o_tdata({flush_en_dclk, flush_timeout_dclk}), .o_tvalid(), .o_tready(1'b1) + ); + + axis_packet_flush #( + .WIDTH(INFO_W+(ITEM_W+1)*NIPC), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("OUT") + ) pyld_flusher_i ( + .clk(axis_data_clk), .reset(axis_data_rst), + .enable(flush_en_dclk), .timeout(flush_timeout_dclk), + .flushing(flush_active_pyld), .done(flush_done_pyld), + .s_axis_tdata(flush_tdata), + .s_axis_tlast(flush_tlast), + .s_axis_tvalid(flush_tvalid), + .s_axis_tready(flush_tready), + .m_axis_tdata({m_axis_ttimestamp, m_axis_thas_time, m_axis_tlength, + m_axis_teob, m_axis_teov, m_axis_tkeep, m_axis_tdata}), + .m_axis_tlast(m_axis_tlast), + .m_axis_tvalid(m_axis_tvalid), + .m_axis_tready(m_axis_tready) + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_data_mdata.v b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_data_mdata.v new file mode 100644 index 000000000..90eb5c767 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_data_mdata.v @@ -0,0 +1,538 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_to_axis_data_mdata +// +// Description: +// +// A deframer module for CHDR data packets. It accepts an input CHDR stream, +// and produces two output streams: +// +// 1) Payload data, which includes the payload of the packet, as well as +// timestamp and packet flags presented as sideband information. +// 2) Metadata (mdata), which contains only the metadata of the packet. +// +// This module also performs an optional clock crossing and data width +// conversion from CHDR_W to a user requested width for the payload data bus. +// +// The metadata and data packets are interleaved, i.e., a mdata packet will +// arrive before its corresponding data packet. However, if mdata prefetching +// is enabled, the mdata for the next packet might arrive before the data for +// the current packet has been consumed. In the case of a rate reduction, +// this allows the module to sustain a gapless stream of payload items and a +// bursty sideband mdata path. If there is no metadata in a packet, then an +// empty packet is output on m_axis_mdata_* (i.e., m_axis_mdata_tkeep will be +// set to 0). +// +// Parameters: +// +// - CHDR_W : Width of the input CHDR bus in bits +// - ITEM_W : Width of the output item bus in bits +// - NIPC : The number of output items delivered per cycle +// - SYNC_CLKS : Are the CHDR and data clocks synchronous to each other? +// - MDATA_FIFO_SIZE : FIFO size for the mdata path +// - INFO_FIFO_SIZE : FIFO size for the packet info path +// - PAYLOAD_FIFO_SIZE : FIFO size for the payload path +// - MDATA_PREFETCH_EN : Is mdata prefetching enabled? +// +// Signals: +// +// - s_axis_chdr_* : Input CHDR stream (AXI-Stream) +// - m_axis_* : Output payload data stream (AXI-Stream) +// - m_axis_mdata_* : Output mdata stream (AXI-Stream) +// - flush_* : Signals for flush control and status +// + +module chdr_to_axis_data_mdata #( + parameter CHDR_W = 256, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter SYNC_CLKS = 0, + parameter MDATA_FIFO_SIZE = 1, + parameter INFO_FIFO_SIZE = 1, + parameter PAYLOAD_FIFO_SIZE = 1, + parameter MDATA_PREFETCH_EN = 1 +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + input wire axis_data_clk, + input wire axis_data_rst, + // CHDR in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + // Payload data stream out (AXI-Stream) + output wire [(ITEM_W*NIPC)-1:0] m_axis_tdata, + output wire [NIPC-1:0] m_axis_tkeep, + output wire m_axis_tlast, + output wire m_axis_tvalid, + input wire m_axis_tready, + // Payload sideband information + output wire [63:0] m_axis_ttimestamp, + output wire m_axis_thas_time, + output wire [15:0] m_axis_tlength, + output wire m_axis_teob, + output wire m_axis_teov, + // Metadata stream out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_mdata_tdata, + output wire m_axis_mdata_tlast, + output wire m_axis_mdata_tkeep, + output wire m_axis_mdata_tvalid, + input wire m_axis_mdata_tready, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Pipeline + // --------------------------------------------------- + localparam CHDR_KEEP_W = CHDR_W/ITEM_W; + + wire [CHDR_W-1:0] in_chdr_tdata; + wire [CHDR_KEEP_W-1:0] in_chdr_tkeep; + wire in_chdr_tlast, in_chdr_tvalid; + reg in_chdr_tready; + + axi_fifo_flop2 #(.WIDTH(CHDR_W+1)) in_pipe_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), .clear(1'b0), + .i_tdata({s_axis_chdr_tlast, s_axis_chdr_tdata}), + .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready), + .o_tdata({in_chdr_tlast, in_chdr_tdata}), + .o_tvalid(in_chdr_tvalid), .o_tready(in_chdr_tready), + .space(), .occupied() + ); + + chdr_compute_tkeep #(.CHDR_W(CHDR_W), .ITEM_W(ITEM_W)) tkeep_gen_i ( + .clk(axis_chdr_clk), .rst(axis_chdr_rst), + .axis_tdata(in_chdr_tdata), .axis_tlast(in_chdr_tlast), + .axis_tvalid(in_chdr_tvalid), .axis_tready(in_chdr_tready), + .axis_tkeep(in_chdr_tkeep) + ); + + // --------------------------------------------------- + // Input State Machine + // --------------------------------------------------- + localparam INFO_W = 64+1+16+1+1; // timestamp, has_time, length, eob, eov + + wire [CHDR_W-1:0] in_pyld_tdata; + wire [CHDR_KEEP_W-1:0] in_pyld_tkeep; + wire in_pyld_tlast, in_pyld_tvalid, in_pyld_tready; + + reg [INFO_W-1:0] in_info_tdata; + reg in_info_tvalid; + wire in_info_tready; + + wire [CHDR_W-1:0] in_mdata_tdata; + wire in_mdata_tkeep; + wire in_mdata_tlast, in_mdata_tvalid, in_mdata_tready; + + + localparam [2:0] ST_HDR = 3'd0; // Processing the input CHDR header + localparam [2:0] ST_TS = 3'd1; // Processing the input CHDR timestamp + localparam [2:0] ST_MDATA = 3'd2; // Processing the input CHDR metadata word + localparam [2:0] ST_BODY = 3'd3; // Processing the input CHDR payload word + localparam [2:0] ST_DROP = 3'd4; // Something went wrong... Dropping packet + + reg [2:0] state = ST_HDR; + reg [4:0] mdata_pending = CHDR_NO_MDATA; + reg last_mdata_line; + + reg [15:0] chdr_length_reg; + reg chdr_eob_reg, chdr_eov_reg; + + // Shortcuts: CHDR header + wire [2:0] in_pkt_type = chdr_get_pkt_type(in_chdr_tdata[63:0]); + wire [4:0] in_num_mdata = chdr_get_num_mdata(in_chdr_tdata[63:0]); + + always @(posedge axis_chdr_clk) begin + if (axis_chdr_rst) begin + state <= ST_HDR; + end else if (in_chdr_tvalid & in_chdr_tready) begin + case (state) + + // ST_HDR: CHDR Header + // ------------------- + ST_HDR: begin + // Always cache the number of metadata words + mdata_pending <= in_num_mdata; + // Figure out the next state + if (!in_chdr_tlast) begin + if (CHDR_W > 64) begin + // When CHDR_W > 64, the timestamp is a part of the header word. + // If this is a data packet (with/without a TS), we move on to the metadata/body + // state otherwise we drop it. Non-data packets should never reach here. + if (in_pkt_type == CHDR_PKT_TYPE_DATA || in_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + if (in_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin + state <= ST_DROP; + end + end else begin + // When CHDR_W == 64, the timestamp comes after the header. Check if this is a data + // packet with a TS to figure out the next state. If no TS, then check for metadata + // to move to the next state. Drop any non-data packets. + chdr_length_reg <= chdr_calc_payload_length(CHDR_W, in_chdr_tdata); + chdr_eob_reg <= chdr_get_eob(in_chdr_tdata); + chdr_eov_reg <= chdr_get_eov(in_chdr_tdata); + if (in_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + state <= ST_TS; + end else if (in_pkt_type == CHDR_PKT_TYPE_DATA) begin + if (in_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin + state <= ST_DROP; + end + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_TS: Timestamp (CHDR_W == 64 only) + // ------------------------------------ + ST_TS: begin + if (!in_chdr_tlast) begin + if (mdata_pending != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_MDATA: Metadata word + // ----------------------- + ST_MDATA: begin + if (!in_chdr_tlast) begin + // Count down metadata and stop at 1 + if (mdata_pending == 5'd1) begin + state <= ST_BODY; + end else begin + mdata_pending <= mdata_pending - 5'd1; + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_BODY: Payload word + // --------------------- + ST_BODY: begin + if (in_chdr_tlast) begin + state <= ST_HDR; + end + end + + // ST_DROP: Drop current packet + // ---------------------------- + ST_DROP: begin + if (in_chdr_tlast) begin + state <= ST_HDR; + end + end + + default: begin + // We should never get here + state <= ST_HDR; + end + endcase + end + end + + // CHDR data goes to the payload stream only in the BODY state. + // Packets are expected to have at least one payload word so the + // CHDR tlast can be used as the payload tlast + assign in_pyld_tdata = in_chdr_tdata; + assign in_pyld_tkeep = in_chdr_tkeep; + assign in_pyld_tlast = in_chdr_tlast; + assign in_pyld_tvalid = in_chdr_tvalid && (state == ST_BODY); + + // Only metadata goes into the mdata FIFO. However, if there is no metadata, + // then we want an empty packet to go into the mdata FIFO. We check the + // packet type because non-data packets will be discarded. + assign in_mdata_tdata = in_chdr_tdata; + assign in_mdata_tlast = in_chdr_tlast || last_mdata_line; + assign in_mdata_tkeep = (state == ST_MDATA); + assign in_mdata_tvalid = in_chdr_tvalid && ( + (state == ST_MDATA) || + (state == ST_HDR && in_num_mdata == CHDR_NO_MDATA && + (in_pkt_type == CHDR_PKT_TYPE_DATA || in_pkt_type == CHDR_PKT_TYPE_DATA_TS))); + + always @(*) begin + // Packet timestamp and flags go into the info FIFO, but only if it's a + // data packet since non-data packets will be discarded. + if (CHDR_W > 64) begin + // When CHDR_W > 64, all info will be in the first word of the CHDR packet + in_info_tdata = { in_chdr_tdata[127:64], + chdr_get_has_time(in_chdr_tdata), + chdr_calc_payload_length(CHDR_W, in_chdr_tdata), + chdr_get_eob(in_chdr_tdata), + chdr_get_eov(in_chdr_tdata) }; + in_info_tvalid = in_chdr_tvalid && (state == ST_HDR && + (in_pkt_type == CHDR_PKT_TYPE_DATA || in_pkt_type == CHDR_PKT_TYPE_DATA_TS)); + end else begin + // When CHDR_W == 64, the flags will be in the first word of the packet, + // but the timestamp will be in the second word, if there is a timestamp. + if (state == ST_HDR && in_pkt_type == CHDR_PKT_TYPE_DATA) begin + // No timestamp in this case + in_info_tdata = { in_chdr_tdata[63:0], 1'b0, + chdr_calc_payload_length(CHDR_W, in_chdr_tdata), + chdr_get_eob(in_chdr_tdata), chdr_get_eov(in_chdr_tdata) }; + in_info_tvalid = in_chdr_tvalid; + end else begin + // Assuming timestamp is present, so use flags from previous clock cycle + in_info_tdata = { in_chdr_tdata[63:0], 1'b1, chdr_length_reg, + chdr_eob_reg, chdr_eov_reg }; + in_info_tvalid = in_chdr_tvalid && (state == ST_TS); + end + end + + case (state) + ST_HDR: begin + in_chdr_tready = in_info_tready && in_mdata_tready; + last_mdata_line = (in_num_mdata == CHDR_NO_MDATA); + end + ST_TS: begin + in_chdr_tready = in_info_tready && in_mdata_tready; + last_mdata_line = 1'b0; + end + ST_MDATA: begin + in_chdr_tready = in_mdata_tready; + last_mdata_line = (mdata_pending == 5'd1); + end + ST_BODY: begin + in_chdr_tready = in_pyld_tready; + last_mdata_line = 1'b0; + end + ST_DROP: begin + in_chdr_tready = 1'b1; + last_mdata_line = 1'b0; + end + default: begin + in_chdr_tready = 1'b0; + last_mdata_line = 1'b0; + end + endcase + end + + // --------------------------------------------------- + // Payload and mdata FIFOs + // --------------------------------------------------- + wire [CHDR_W-1:0] out_pyld_tdata; + wire [CHDR_KEEP_W-1:0] out_pyld_tkeep; + wire out_pyld_tlast, out_pyld_tvalid, out_pyld_tready; + + wire tmp_mdata_tvalid, tmp_mdata_tready; + wire tmp_info_tready; + + wire [(ITEM_W*NIPC)-1:0] flush_pyld_tdata; + wire [NIPC-1:0] flush_pyld_tkeep; + wire flush_pyld_tlast, flush_pyld_tvalid, flush_pyld_tready; + wire [INFO_W-1:0] flush_info_tdata; + wire [CHDR_W-1:0] flush_mdata_tdata; + wire flush_mdata_tkeep; + wire flush_mdata_tlast, flush_mdata_tvalid, flush_mdata_tready; + + generate if (SYNC_CLKS) begin : gen_sync_fifo + axi_fifo #(.WIDTH(CHDR_W+2), .SIZE(MDATA_FIFO_SIZE)) mdata_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata({in_mdata_tkeep, in_mdata_tlast, in_mdata_tdata}), + .i_tvalid(in_mdata_tvalid), .i_tready(in_mdata_tready), + .o_tdata({flush_mdata_tkeep, flush_mdata_tlast, flush_mdata_tdata}), + .o_tvalid(tmp_mdata_tvalid), .o_tready(tmp_mdata_tready), + .space(), .occupied() + ); + axi_fifo #(.WIDTH(INFO_W), .SIZE(INFO_FIFO_SIZE)) info_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata(in_info_tdata), + .i_tvalid(in_info_tvalid), .i_tready(in_info_tready), + .o_tdata(flush_info_tdata), + .o_tvalid(), .o_tready(tmp_info_tready), + .space(), .occupied() + ); + axi_fifo #(.WIDTH(CHDR_W+CHDR_KEEP_W+1), .SIZE(PAYLOAD_FIFO_SIZE)) pyld_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata({in_pyld_tlast, in_pyld_tkeep, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_tdata({out_pyld_tlast, out_pyld_tkeep, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready), + .space(), .occupied() + ); + end else begin : gen_async_fifo + axi_fifo_2clk #(.WIDTH(CHDR_W+2), .SIZE(MDATA_FIFO_SIZE)) mdata_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({in_mdata_tkeep, in_mdata_tlast, in_mdata_tdata}), + .i_tvalid(in_mdata_tvalid), .i_tready(in_mdata_tready), + .o_aclk(axis_data_clk), + .o_tdata({flush_mdata_tkeep, flush_mdata_tlast, flush_mdata_tdata}), + .o_tvalid(tmp_mdata_tvalid), .o_tready(tmp_mdata_tready) + ); + axi_fifo_2clk #(.WIDTH(INFO_W), .SIZE(INFO_FIFO_SIZE)) info_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata(in_info_tdata), + .i_tvalid(in_info_tvalid), .i_tready(in_info_tready), + .o_aclk(axis_data_clk), + .o_tdata(flush_info_tdata), + .o_tvalid(), .o_tready(tmp_info_tready) + ); + axi_fifo_2clk #(.WIDTH(CHDR_W+CHDR_KEEP_W+1), .SIZE(PAYLOAD_FIFO_SIZE)) pyld_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({in_pyld_tlast, in_pyld_tkeep, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_aclk(axis_data_clk), + .o_tdata({out_pyld_tlast, out_pyld_tkeep, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready) + ); + end endgenerate + + // --------------------------------------------------- + // Data Width Converter: CHDR_W => ITEM_W*NIPC + // --------------------------------------------------- + wire tmp_pyld_tvalid, tmp_pyld_tready; + + generate + if (CHDR_W != ITEM_W*NIPC) begin : gen_axis_width_conv + axis_width_conv #( + .WORD_W(ITEM_W), .IN_WORDS(CHDR_W/ITEM_W), .OUT_WORDS(NIPC), + .SYNC_CLKS(1), .PIPELINE("NONE") + ) payload_width_conv_i ( + .s_axis_aclk(axis_data_clk), .s_axis_rst(axis_data_rst), + .s_axis_tdata(out_pyld_tdata), .s_axis_tkeep(out_pyld_tkeep), + .s_axis_tlast(out_pyld_tlast), .s_axis_tvalid(out_pyld_tvalid), + .s_axis_tready(out_pyld_tready), + .m_axis_aclk(axis_data_clk), .m_axis_rst(axis_data_rst), + .m_axis_tdata(flush_pyld_tdata), .m_axis_tkeep(flush_pyld_tkeep), + .m_axis_tlast(flush_pyld_tlast), .m_axis_tvalid(tmp_pyld_tvalid), + .m_axis_tready(tmp_pyld_tready) + ); + end else begin : no_gen_axis_width_conv + assign flush_pyld_tdata = out_pyld_tdata; + assign flush_pyld_tkeep = out_pyld_tkeep; + assign flush_pyld_tlast = out_pyld_tlast; + assign tmp_pyld_tvalid = out_pyld_tvalid; + assign out_pyld_tready = tmp_pyld_tready; + end + endgenerate + + + // --------------------------------------------------- + // Output State Machine + // --------------------------------------------------- + reg [2:0] mdata_pkt_cnt = 3'd0, pyld_pkt_cnt = 3'd0; + + // A payload packet can pass only if it is preceded by a mdata packet + wire pass_pyld = ((mdata_pkt_cnt - pyld_pkt_cnt) > 3'd0); + // A mdata packet has to be blocked if its corresponding payload packet hasn't passed except + // when prefetching is enabled. In that case one additional mdata packet is allowed to pass + wire pass_mdata = ((mdata_pkt_cnt - pyld_pkt_cnt) < (MDATA_PREFETCH_EN == 1 ? 3'd2 : 3'd1)); + + always @(posedge axis_data_clk) begin + if (axis_data_rst) begin + mdata_pkt_cnt <= 3'd0; + pyld_pkt_cnt <= 3'd0; + end else begin + if (flush_mdata_tvalid && flush_mdata_tready && flush_mdata_tlast) + mdata_pkt_cnt <= mdata_pkt_cnt + 3'd1; + if (flush_pyld_tvalid && flush_pyld_tready && flush_pyld_tlast) + pyld_pkt_cnt <= pyld_pkt_cnt + 3'd1; + end + end + + assign flush_pyld_tvalid = tmp_pyld_tvalid && pass_pyld; + assign tmp_pyld_tready = flush_pyld_tready && pass_pyld; + + // Only read the info FIFO once per packet + assign tmp_info_tready = tmp_pyld_tready && flush_pyld_tlast && tmp_pyld_tvalid; + + assign flush_mdata_tvalid = tmp_mdata_tvalid && pass_mdata; + assign tmp_mdata_tready = flush_mdata_tready && pass_mdata; + + // --------------------------------------------------- + // Flushing Logic + // --------------------------------------------------- + wire [31:0] flush_timeout_dclk; + wire flush_en_dclk; + wire flush_active_pyld_cclk, flush_active_mdata_cclk; + wire flush_done_pyld_cclk, flush_done_mdata_cclk; + wire flush_active_pyld, flush_active_mdata; + wire flush_done_pyld, flush_done_mdata; + + synchronizer #(.WIDTH(4), .INITIAL_VAL(4'd0)) flush_2clk_rb_i ( + .clk(axis_chdr_clk), .rst(1'b0), + .in({flush_active_pyld, flush_done_pyld, + flush_active_mdata, flush_done_mdata}), + .out({flush_active_pyld_cclk, flush_done_pyld_cclk, + flush_active_mdata_cclk, flush_done_mdata_cclk}) + ); + assign flush_active = flush_active_pyld_cclk | flush_active_mdata_cclk; + assign flush_done = flush_done_pyld_cclk & flush_done_mdata_cclk; + + axi_fifo_2clk #(.WIDTH(33), .SIZE(1)) flush_2clk_ctrl_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({flush_en, flush_timeout}), .i_tvalid(1'b1), .i_tready(), + .o_aclk(axis_data_clk), + .o_tdata({flush_en_dclk, flush_timeout_dclk}), .o_tvalid(), .o_tready(1'b1) + ); + + axis_packet_flush #( + .WIDTH(INFO_W+(ITEM_W+1)*NIPC), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("OUT") + ) pyld_flusher_i ( + .clk(axis_data_clk), .reset(axis_data_rst), + .enable(flush_en_dclk), .timeout(flush_timeout_dclk), + .flushing(flush_active_pyld), .done(flush_done_pyld), + .s_axis_tdata({flush_info_tdata, flush_pyld_tkeep, flush_pyld_tdata}), + .s_axis_tlast(flush_pyld_tlast), + .s_axis_tvalid(flush_pyld_tvalid), + .s_axis_tready(flush_pyld_tready), + .m_axis_tdata({m_axis_ttimestamp, m_axis_thas_time, m_axis_tlength, + m_axis_teob, m_axis_teov, m_axis_tkeep, m_axis_tdata}), + .m_axis_tlast(m_axis_tlast), + .m_axis_tvalid(m_axis_tvalid), + .m_axis_tready(m_axis_tready) + ); + + axis_packet_flush #( + .WIDTH(CHDR_W+1), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("OUT") + ) mdata_flusher_i ( + .clk(axis_data_clk), .reset(axis_data_rst), + .enable(flush_en_dclk), .timeout(flush_timeout_dclk), + .flushing(flush_active_mdata), .done(flush_done_mdata), + .s_axis_tdata({flush_mdata_tkeep, flush_mdata_tdata}), + .s_axis_tlast(flush_mdata_tlast), + .s_axis_tvalid(flush_mdata_tvalid), + .s_axis_tready(flush_mdata_tready), + .m_axis_tdata({m_axis_mdata_tkeep, m_axis_mdata_tdata}), + .m_axis_tlast(m_axis_mdata_tlast), + .m_axis_tvalid(m_axis_mdata_tvalid), + .m_axis_tready(m_axis_mdata_tready) + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_pyld_ctxt.v b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_pyld_ctxt.v new file mode 100644 index 000000000..f604584e8 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_to_axis_pyld_ctxt.v @@ -0,0 +1,458 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: chdr_to_axis_pyld_ctxt +// Description: +// A header deframer module for CHDR data packets. +// Accepts an input CHDR stream, and produces two output streams: +// 1) Payload, which contains the payload of the packet +// 2) Context, which contains the header info in the packet i.e. +// CHDR header, timestamp and metadata (marked with a tuser) +// This module also performs an optional clock crossing and data +// width convertion from CHDR_W to a user requested width for the +// payload bus. +// Context and data packets are interleaved i.e. a context packet +// will arrive before its corresponding data packet. However, if +// context prefetching is enabled, the context for the next packet +// might arrive before the data for the current packet has been +// consumed. In the case of a rate reduction, this allows the module +// to sustain a gapless stream of payload items and a bursty +// sideband context path. +// +// Parameters: +// - CHDR_W: Width of the input CHDR bus in bits +// - ITEM_W: Width of the output item bus in bits +// - NIPC: The number of output items delievered per cycle +// - SYNC_CLKS: Are the CHDR and data clocks synchronous to each other? +// - CONTEXT_FIFO_SIZE: FIFO size for the context path +// - PAYLOAD_FIFO_SIZE: FIFO size for the payload path +// - CONTEXT_PREFETCH_EN: Is context prefetching enabled? +// +// Signals: +// - s_axis_chdr_* : Input CHDR stream (AXI-Stream) +// - m_axis_payload_* : Output payload stream (AXI-Stream) +// - m_axis_context_* : Output context stream (AXI-Stream) +// - flush_* : Signals for flush control and status +// + +module chdr_to_axis_pyld_ctxt #( + parameter CHDR_W = 256, + parameter ITEM_W = 32, + parameter NIPC = 2, + parameter SYNC_CLKS = 0, + parameter CONTEXT_FIFO_SIZE = 1, + parameter PAYLOAD_FIFO_SIZE = 1, + parameter CONTEXT_PREFETCH_EN = 1 +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + input wire axis_data_clk, + input wire axis_data_rst, + // CHDR in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + // Payload stream out (AXI-Stream) + output wire [(ITEM_W*NIPC)-1:0] m_axis_payload_tdata, + output wire [NIPC-1:0] m_axis_payload_tkeep, + output wire m_axis_payload_tlast, + output wire m_axis_payload_tvalid, + input wire m_axis_payload_tready, + // Context stream out (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_context_tdata, + output wire [3:0] m_axis_context_tuser, + output wire m_axis_context_tlast, + output wire m_axis_context_tvalid, + input wire m_axis_context_tready, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Pipeline + // --------------------------------------------------- + localparam CHDR_KEEP_W = CHDR_W/ITEM_W; + + wire [CHDR_W-1:0] in_chdr_tdata; + wire [CHDR_KEEP_W-1:0] in_chdr_tkeep; + wire in_chdr_tlast, in_chdr_tvalid; + reg in_chdr_tready; + + axi_fifo_flop2 #(.WIDTH(CHDR_W+1)) in_pipe_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), .clear(1'b0), + .i_tdata({s_axis_chdr_tlast, s_axis_chdr_tdata}), + .i_tvalid(s_axis_chdr_tvalid), .i_tready(s_axis_chdr_tready), + .o_tdata({in_chdr_tlast, in_chdr_tdata}), + .o_tvalid(in_chdr_tvalid), .o_tready(in_chdr_tready), + .space(), .occupied() + ); + + chdr_compute_tkeep #(.CHDR_W(CHDR_W), .ITEM_W(ITEM_W)) tkeep_gen_i ( + .clk(axis_chdr_clk), .rst(axis_chdr_rst), + .axis_tdata(in_chdr_tdata), .axis_tlast(in_chdr_tlast), + .axis_tvalid(in_chdr_tvalid), .axis_tready(in_chdr_tready), + .axis_tkeep(in_chdr_tkeep) + ); + + // --------------------------------------------------- + // Input State Machine + // --------------------------------------------------- + wire [CHDR_W-1:0] in_pyld_tdata; + wire [CHDR_KEEP_W-1:0] in_pyld_tkeep; + wire in_pyld_tlast, in_pyld_tvalid, in_pyld_tready; + + wire [CHDR_W-1:0] in_ctxt_tdata; + reg [3:0] in_ctxt_tuser; + wire in_ctxt_tlast, in_ctxt_tvalid, in_ctxt_tready; + + + localparam [2:0] ST_HDR = 3'd0; // Processing the input CHDR header + localparam [2:0] ST_TS = 3'd1; // Processing the input CHDR timestamp + localparam [2:0] ST_MDATA = 3'd2; // Processing the input CHDR metadata word + localparam [2:0] ST_BODY = 3'd3; // Processing the input CHDR payload word + localparam [2:0] ST_DROP = 3'd4; // Something went wrong... Dropping packet + + reg [2:0] state = ST_HDR; + reg [4:0] mdata_pending = CHDR_NO_MDATA; + reg last_ctxt_line; + + // Shortcuts: CHDR header + wire [2:0] in_pkt_type = chdr_get_pkt_type(in_chdr_tdata[63:0]); + wire [4:0] in_num_mdata = chdr_get_num_mdata(in_chdr_tdata[63:0]); + + always @(posedge axis_chdr_clk) begin + if (axis_chdr_rst) begin + state <= ST_HDR; + end else if (in_chdr_tvalid & in_chdr_tready) begin + case (state) + + // ST_HDR: CHDR Header + // ------------------- + ST_HDR: begin + // Always cache the number of metadata words + mdata_pending <= in_num_mdata; + // Figure out the next state + if (!in_chdr_tlast) begin + if (CHDR_W > 64) begin + // When CHDR_W > 64, the timestamp is a part of the header word. + // If this is a data packet (with/without a TS), we move on to the metadata/body + // state otherwise we drop it. Non-data packets should never reach here. + if (in_pkt_type == CHDR_PKT_TYPE_DATA || in_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + if (in_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin + state <= ST_DROP; + end + end else begin + // When CHDR_W == 64, the timestamp comes after the header. Check if this is a data + // packet with a TS to figure out the next state. If no TS, then check for metadata + // to move to the next state. Drop any non-data packets. + if (in_pkt_type == CHDR_PKT_TYPE_DATA_TS) begin + state <= ST_TS; + end else if (in_pkt_type == CHDR_PKT_TYPE_DATA) begin + if (in_num_mdata != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin + state <= ST_DROP; + end + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_TS: Timestamp (CHDR_W == 64 only) + // ------------------------------------ + ST_TS: begin + if (!in_chdr_tlast) begin + if (mdata_pending != CHDR_NO_MDATA) begin + state <= ST_MDATA; + end else begin + state <= ST_BODY; + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_MDATA: Metadata word + // ----------------------- + ST_MDATA: begin + if (!in_chdr_tlast) begin + // Count down metadata and stop at 1 + if (mdata_pending == 5'd1) begin + state <= ST_BODY; + end else begin + mdata_pending <= mdata_pending - 5'd1; + end + end else begin // Premature termination + // Packets must have at least one payload line + state <= ST_HDR; + end + end + + // ST_BODY: Payload word + // --------------------- + ST_BODY: begin + if (in_chdr_tlast) begin + state <= ST_HDR; + end + end + + // ST_DROP: Drop current packet + // ---------------------------- + ST_DROP: begin + if (in_chdr_tlast) begin + state <= ST_HDR; + end + end + + default: begin + // We should never get here + state <= ST_HDR; + end + endcase + end + end + + // CHDR data goes to the payload stream only in the BODY state. + // Packets are expected to have at least one payload word so the + // CHDR tlast can be used as the payload tlast + assign in_pyld_tdata = in_chdr_tdata; + assign in_pyld_tkeep = in_chdr_tkeep; + assign in_pyld_tlast = in_chdr_tlast; + assign in_pyld_tvalid = in_chdr_tvalid && (state == ST_BODY); + + // CHDR data goes to the context stream in the HDR,TS,MDATA state. + // tlast has to be recomputed for the context stream, however, we + // still need to correctly handle an errant packet without a payload + assign in_ctxt_tdata = in_chdr_tdata; + assign in_ctxt_tlast = in_chdr_tlast || last_ctxt_line; + assign in_ctxt_tvalid = in_chdr_tvalid && (state != ST_BODY && state != ST_DROP); + + always @(*) begin + case (state) + ST_HDR: begin + // The header goes to the context stream + in_chdr_tready <= in_ctxt_tready; + in_ctxt_tuser <= (CHDR_W > 64) ? CONTEXT_FIELD_HDR_TS : CONTEXT_FIELD_HDR; + last_ctxt_line <= (in_num_mdata == 7'd0) && ( + in_pkt_type == CHDR_PKT_TYPE_DATA || + (in_pkt_type == CHDR_PKT_TYPE_DATA_TS && CHDR_W > 64)); + end + ST_TS: begin + // The timestamp goes to the context stream + in_chdr_tready <= in_ctxt_tready; + in_ctxt_tuser <= CONTEXT_FIELD_TS; + last_ctxt_line <= (mdata_pending == CHDR_NO_MDATA); + end + ST_MDATA: begin + // The metadata goes to the context stream + in_chdr_tready <= in_ctxt_tready; + in_ctxt_tuser <= CONTEXT_FIELD_MDATA; + last_ctxt_line <= (mdata_pending == 5'd1); + end + ST_BODY: begin + // The body goes to the payload stream + in_chdr_tready <= in_pyld_tready; + in_ctxt_tuser <= 4'h0; + last_ctxt_line <= 1'b0; + end + ST_DROP: begin + // Errant packets get dropped + in_chdr_tready <= 1'b1; + in_ctxt_tuser <= 4'h0; + last_ctxt_line <= 1'b0; + end + default: begin + in_chdr_tready <= 1'b0; + in_ctxt_tuser <= 4'h0; + last_ctxt_line <= 1'b0; + end + endcase + end + + // --------------------------------------------------- + // Payload and Context FIFOs + // --------------------------------------------------- + wire [CHDR_W-1:0] out_pyld_tdata; + wire [CHDR_KEEP_W-1:0] out_pyld_tkeep; + wire out_pyld_tlast, out_pyld_tvalid, out_pyld_tready; + + wire tmp_ctxt_tvalid, tmp_ctxt_tready; + + wire [(ITEM_W*NIPC)-1:0] flush_pyld_tdata; + wire [NIPC-1:0] flush_pyld_tkeep; + wire flush_pyld_tlast, flush_pyld_tvalid, flush_pyld_tready; + wire [CHDR_W-1:0] flush_ctxt_tdata; + wire [3:0] flush_ctxt_tuser; + wire flush_ctxt_tlast, flush_ctxt_tvalid, flush_ctxt_tready; + + generate if (SYNC_CLKS) begin : gen_sync_fifo + axi_fifo #(.WIDTH(CHDR_W+4+1), .SIZE(CONTEXT_FIFO_SIZE)) ctxt_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata({in_ctxt_tlast, in_ctxt_tuser, in_ctxt_tdata}), + .i_tvalid(in_ctxt_tvalid), .i_tready(in_ctxt_tready), + .o_tdata({flush_ctxt_tlast, flush_ctxt_tuser, flush_ctxt_tdata}), + .o_tvalid(tmp_ctxt_tvalid), .o_tready(tmp_ctxt_tready), + .space(), .occupied() + ); + axi_fifo #(.WIDTH(CHDR_W+CHDR_KEEP_W+1), .SIZE(PAYLOAD_FIFO_SIZE)) pyld_fifo_i ( + .clk(axis_data_clk), .reset(axis_data_rst), .clear(1'b0), + .i_tdata({in_pyld_tlast, in_pyld_tkeep, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_tdata({out_pyld_tlast, out_pyld_tkeep, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready), + .space(), .occupied() + ); + end else begin : gen_async_fifo + axi_fifo_2clk #(.WIDTH(CHDR_W+4+1), .SIZE(CONTEXT_FIFO_SIZE)) ctxt_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({in_ctxt_tlast, in_ctxt_tuser, in_ctxt_tdata}), + .i_tvalid(in_ctxt_tvalid), .i_tready(in_ctxt_tready), + .o_aclk(axis_data_clk), + .o_tdata({flush_ctxt_tlast, flush_ctxt_tuser, flush_ctxt_tdata}), + .o_tvalid(tmp_ctxt_tvalid), .o_tready(tmp_ctxt_tready) + ); + axi_fifo_2clk #(.WIDTH(CHDR_W+CHDR_KEEP_W+1), .SIZE(PAYLOAD_FIFO_SIZE)) pyld_fifo_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({in_pyld_tlast, in_pyld_tkeep, in_pyld_tdata}), + .i_tvalid(in_pyld_tvalid), .i_tready(in_pyld_tready), + .o_aclk(axis_data_clk), + .o_tdata({out_pyld_tlast, out_pyld_tkeep, out_pyld_tdata}), + .o_tvalid(out_pyld_tvalid), .o_tready(out_pyld_tready) + ); + end endgenerate + + // --------------------------------------------------- + // Data Width Converter: CHDR_W => ITEM_W*NIPC + // --------------------------------------------------- + wire tmp_pyld_tvalid, tmp_pyld_tready; + + axis_width_conv #( + .WORD_W(ITEM_W), .IN_WORDS(CHDR_W/ITEM_W), .OUT_WORDS(NIPC), + .SYNC_CLKS(1), .PIPELINE("NONE") + ) payload_width_conv_i ( + .s_axis_aclk(axis_data_clk), .s_axis_rst(axis_data_rst), + .s_axis_tdata(out_pyld_tdata), .s_axis_tkeep(out_pyld_tkeep), + .s_axis_tlast(out_pyld_tlast), .s_axis_tvalid(out_pyld_tvalid), + .s_axis_tready(out_pyld_tready), + .m_axis_aclk(axis_data_clk), .m_axis_rst(axis_data_rst), + .m_axis_tdata(flush_pyld_tdata), .m_axis_tkeep(flush_pyld_tkeep), + .m_axis_tlast(flush_pyld_tlast), .m_axis_tvalid(tmp_pyld_tvalid), + .m_axis_tready(tmp_pyld_tready) + ); + + // --------------------------------------------------- + // Output State Machine + // --------------------------------------------------- + reg [2:0] ctxt_pkt_cnt = 3'd0, pyld_pkt_cnt = 3'd0; + + // A payload packet can pass only if it is preceeded by a context packet + wire pass_pyld = ((ctxt_pkt_cnt - pyld_pkt_cnt) > 3'd0); + // A context packet has to be blocked if its corresponding payload packet hasn't passed except + // when prefetching is enabled. In that case one additional context packet is allowed to pass + wire pass_ctxt = ((ctxt_pkt_cnt - pyld_pkt_cnt) < (CONTEXT_PREFETCH_EN == 1 ? 3'd2 : 3'd1)); + + always @(posedge axis_data_clk) begin + if (axis_data_rst) begin + ctxt_pkt_cnt <= 3'd0; + pyld_pkt_cnt <= 3'd0; + end else begin + if (flush_ctxt_tvalid && flush_ctxt_tready && flush_ctxt_tlast) + ctxt_pkt_cnt <= ctxt_pkt_cnt + 3'd1; + if (flush_pyld_tvalid && flush_pyld_tready && flush_pyld_tlast) + pyld_pkt_cnt <= pyld_pkt_cnt + 3'd1; + end + end + + assign flush_pyld_tvalid = tmp_pyld_tvalid && pass_pyld; + assign tmp_pyld_tready = flush_pyld_tready && pass_pyld; + + assign flush_ctxt_tvalid = tmp_ctxt_tvalid && pass_ctxt; + assign tmp_ctxt_tready = flush_ctxt_tready && pass_ctxt; + + // --------------------------------------------------- + // Flushing Logic + // --------------------------------------------------- + wire [31:0] flush_timeout_dclk; + wire flush_en_dclk; + wire flush_active_pyld_cclk, flush_active_ctxt_cclk; + wire flush_done_pyld_cclk, flush_done_ctxt_cclk; + wire flush_active_pyld, flush_active_ctxt; + wire flush_done_pyld, flush_done_ctxt; + + synchronizer #(.WIDTH(4), .INITIAL_VAL(4'd0)) flush_2clk_rb_i ( + .clk(axis_chdr_clk), .rst(1'b0), + .in({flush_active_pyld, flush_done_pyld, + flush_active_ctxt, flush_done_ctxt}), + .out({flush_active_pyld_cclk, flush_done_pyld_cclk, + flush_active_ctxt_cclk, flush_done_ctxt_cclk}) + ); + assign flush_active = flush_active_pyld_cclk | flush_active_ctxt_cclk; + assign flush_done = flush_done_pyld_cclk & flush_done_ctxt_cclk; + + axi_fifo_2clk #(.WIDTH(33), .SIZE(1)) flush_2clk_ctrl_i ( + .reset(axis_chdr_rst), + .i_aclk(axis_chdr_clk), + .i_tdata({flush_en, flush_timeout}), .i_tvalid(1'b1), .i_tready(), + .o_aclk(axis_data_clk), + .o_tdata({flush_en_dclk, flush_timeout_dclk}), .o_tvalid(), .o_tready(1'b1) + ); + + axis_packet_flush #( + .WIDTH((ITEM_W+1)*NIPC), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("OUT") + ) pyld_flusher_i ( + .clk(axis_data_clk), .reset(axis_data_rst), + .enable(flush_en_dclk), .timeout(flush_timeout_dclk), + .flushing(flush_active_pyld), .done(flush_done_pyld), + .s_axis_tdata({flush_pyld_tkeep, flush_pyld_tdata}), + .s_axis_tlast(flush_pyld_tlast), + .s_axis_tvalid(flush_pyld_tvalid), + .s_axis_tready(flush_pyld_tready), + .m_axis_tdata({m_axis_payload_tkeep, m_axis_payload_tdata}), + .m_axis_tlast(m_axis_payload_tlast), + .m_axis_tvalid(m_axis_payload_tvalid), + .m_axis_tready(m_axis_payload_tready) + ); + + axis_packet_flush #( + .WIDTH(CHDR_W+4), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("OUT") + ) ctxt_flusher_i ( + .clk(axis_data_clk), .reset(axis_data_rst), + .enable(flush_en_dclk), .timeout(flush_timeout_dclk), + .flushing(flush_active_ctxt), .done(flush_done_ctxt), + .s_axis_tdata({flush_ctxt_tuser, flush_ctxt_tdata}), + .s_axis_tlast(flush_ctxt_tlast), + .s_axis_tvalid(flush_ctxt_tvalid), + .s_axis_tready(flush_ctxt_tready), + .m_axis_tdata({m_axis_context_tuser, m_axis_context_tdata}), + .m_axis_tlast(m_axis_context_tlast), + .m_axis_tvalid(m_axis_context_tvalid), + .m_axis_tready(m_axis_context_tready) + ); + +endmodule // chdr_to_axis_pyld_ctxt diff --git a/fpga/usrp3/lib/rfnoc/core/chdr_to_chdr_data.v b/fpga/usrp3/lib/rfnoc/core/chdr_to_chdr_data.v new file mode 100644 index 000000000..390d77bca --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/chdr_to_chdr_data.v @@ -0,0 +1,55 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_raw_data_to_chdr +// Description: +// A simple adapter for when CHDR data is requested as an +// interface to user logic. +// +// Parameters: +// - CHDR_W: Width of the input CHDR bus in bits +// +// Signals: +// - s_axis_chdr_* : Input CHDR stream (AXI-Stream) +// - m_axis_chdr_* : Output CHDR stream (AXI-Stream) +// - flush_* : Signals for flush control and status +// + +module chdr_to_chdr_data #( + parameter CHDR_W = 256 +)( + // Clock, reset and settings + input wire axis_chdr_clk, + input wire axis_chdr_rst, + // CHDR in (AXI-Stream) + input wire [CHDR_W-1:0] s_axis_chdr_tdata, + input wire s_axis_chdr_tlast, + input wire s_axis_chdr_tvalid, + output wire s_axis_chdr_tready, + // CHDR in (AXI-Stream) + output wire [CHDR_W-1:0] m_axis_chdr_tdata, + output wire m_axis_chdr_tlast, + output wire m_axis_chdr_tvalid, + input wire m_axis_chdr_tready, + // Flush signals + input wire flush_en, + input wire [31:0] flush_timeout, + output wire flush_active, + output wire flush_done +); + + axis_packet_flush #( + .WIDTH(CHDR_W), .FLUSH_PARTIAL_PKTS(0), .TIMEOUT_W(32), .PIPELINE("OUT") + ) chdr_flusher_i ( + .clk(axis_chdr_clk), .reset(axis_chdr_rst), + .enable(flush_en), .timeout(flush_timeout), + .flushing(flush_active), .done(flush_done), + .s_axis_tdata(s_axis_chdr_tdata), .s_axis_tlast(s_axis_chdr_tlast), + .s_axis_tvalid(s_axis_chdr_tvalid), .s_axis_tready(s_axis_chdr_tready), + .m_axis_tdata(m_axis_chdr_tdata), .m_axis_tlast(m_axis_chdr_tlast), + .m_axis_tvalid(m_axis_chdr_tvalid), .m_axis_tready(m_axis_chdr_tready) + ); + +endmodule // chdr_to_chdr_data diff --git a/fpga/usrp3/lib/rfnoc/core/ctrlport.vh b/fpga/usrp3/lib/rfnoc/core/ctrlport.vh new file mode 100644 index 000000000..7b5f9fcaa --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/ctrlport.vh @@ -0,0 +1,26 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: ctrlport.vh +// Description: +// Defines constants for the control port interface. +// +// Requires rfnoc_axis_ctrl_utils.vh in same directory to be +// included first. + +//--------------------------------------------------------------- +// Signal widths +//--------------------------------------------------------------- +localparam CTRLPORT_ADDR_W = 20; +localparam CTRLPORT_DATA_W = 32; +localparam CTRLPORT_STS_W = 2; + +//--------------------------------------------------------------- +// Status values +//--------------------------------------------------------------- +localparam [1:0] CTRL_STS_OKAY = 2'b00; +localparam [1:0] CTRL_STS_CMDERR = 2'b01; +localparam [1:0] CTRL_STS_TSERR = 2'b10; +localparam [1:0] CTRL_STS_WARNING = 2'b11; diff --git a/fpga/usrp3/lib/rfnoc/core/ctrlport_endpoint.v b/fpga/usrp3/lib/rfnoc/core/ctrlport_endpoint.v new file mode 100644 index 000000000..4a7d7302a --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/ctrlport_endpoint.v @@ -0,0 +1,284 @@ +// +// Copyright 2018-2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: ctrlport_endpoint +// Description: +// A bidirectional AXIS-Control to Control-Port converter. +// Use this module in noc_shell to interface between the user +// logic (using ctrlport) and the rfnoc infrastructure (axis_ctrl) +// +// Parameters: +// - THIS_PORTID: The 10-bit ID of the control XB port that is +// connected to this converter. +// - SYNC_CLKS: Is rfnoc_ctrl_clk and ctrlport_clk the same clock? +// - AXIS_CTRL_MST_EN: Enable an AXIS-Ctrl master +// - AXIS_CTRL_SLV_EN: Enable an AXIS-Ctrl slave +// - SLAVE_FIFO_SIZE: FIFO depth for the slave port +// +// Signals: +// - *_rfnoc_ctrl_* : Input/output AXIS-Control stream (AXI-Stream) +// - *_ctrlport_* : Input/output control-port bus + +module ctrlport_endpoint #( + parameter [9:0] THIS_PORTID = 10'd0, + parameter SYNC_CLKS = 0, + parameter [0:0] AXIS_CTRL_MST_EN = 1, + parameter [0:0] AXIS_CTRL_SLV_EN = 1, + parameter SLAVE_FIFO_SIZE = 5 +)( + // Clocks, Resets, Misc + input wire rfnoc_ctrl_clk, + input wire rfnoc_ctrl_rst, + input wire ctrlport_clk, + input wire ctrlport_rst, + // AXIS-Control Bus + input wire [31:0] s_rfnoc_ctrl_tdata, + input wire s_rfnoc_ctrl_tlast, + input wire s_rfnoc_ctrl_tvalid, + output wire s_rfnoc_ctrl_tready, + output wire [31:0] m_rfnoc_ctrl_tdata, + output wire m_rfnoc_ctrl_tlast, + output wire m_rfnoc_ctrl_tvalid, + input wire m_rfnoc_ctrl_tready, + // Control Port Master (Request) + output wire m_ctrlport_req_wr, + output wire m_ctrlport_req_rd, + output wire [19:0] m_ctrlport_req_addr, + output wire [31:0] m_ctrlport_req_data, + output wire [3:0] m_ctrlport_req_byte_en, + output wire m_ctrlport_req_has_time, + output wire [63:0] m_ctrlport_req_time, + // Control Port Master (Response) + input wire m_ctrlport_resp_ack, + input wire [1:0] m_ctrlport_resp_status, + input wire [31:0] m_ctrlport_resp_data, + // Control Port Slave (Request) + input wire s_ctrlport_req_wr, + input wire s_ctrlport_req_rd, + input wire [19:0] s_ctrlport_req_addr, + input wire [9:0] s_ctrlport_req_portid, + input wire [15:0] s_ctrlport_req_rem_epid, + input wire [9:0] s_ctrlport_req_rem_portid, + input wire [31:0] s_ctrlport_req_data, + input wire [3:0] s_ctrlport_req_byte_en, + input wire s_ctrlport_req_has_time, + input wire [63:0] s_ctrlport_req_time, + // Control Port Slave (Response) + output wire s_ctrlport_resp_ack, + output wire [1:0] s_ctrlport_resp_status, + output wire [31:0] s_ctrlport_resp_data +); + + // --------------------------------------------------- + // RFNoC Includes + // --------------------------------------------------- + `include "rfnoc_chdr_utils.vh" + `include "rfnoc_axis_ctrl_utils.vh" + + // --------------------------------------------------- + // Clock Crossing + // --------------------------------------------------- + + wire [31:0] i_ctrl_tdata, o_ctrl_tdata; + wire i_ctrl_tlast, o_ctrl_tlast; + wire i_ctrl_tvalid, o_ctrl_tvalid; + wire i_ctrl_tready, o_ctrl_tready; + + generate + if (SYNC_CLKS) begin : gen_sync_fifos + axi_fifo #(.WIDTH(32+1), .SIZE(1)) in_fifo_i ( + .clk(ctrlport_clk), .reset(ctrlport_rst), .clear(1'b0), + .i_tdata({s_rfnoc_ctrl_tlast, s_rfnoc_ctrl_tdata}), + .i_tvalid(s_rfnoc_ctrl_tvalid), .i_tready(s_rfnoc_ctrl_tready), + .o_tdata({i_ctrl_tlast, i_ctrl_tdata}), + .o_tvalid(i_ctrl_tvalid), .o_tready(i_ctrl_tready), + .space(), .occupied() + ); + + axi_fifo #(.WIDTH(32+1), .SIZE(1)) out_fifo_i ( + .clk(ctrlport_clk), .reset(ctrlport_rst), .clear(1'b0), + .i_tdata({o_ctrl_tlast, o_ctrl_tdata}), + .i_tvalid(o_ctrl_tvalid), .i_tready(o_ctrl_tready), + .o_tdata({m_rfnoc_ctrl_tlast, m_rfnoc_ctrl_tdata}), + .o_tvalid(m_rfnoc_ctrl_tvalid), .o_tready(m_rfnoc_ctrl_tready), + .space(), .occupied() + ); + end else begin : gen_async_fifos + axi_fifo_2clk #(.WIDTH(32+1), .SIZE(1), .PIPELINE("IN")) in_fifo_i ( + .reset(rfnoc_ctrl_rst), + .i_aclk(rfnoc_ctrl_clk), + .i_tdata({s_rfnoc_ctrl_tlast, s_rfnoc_ctrl_tdata}), + .i_tvalid(s_rfnoc_ctrl_tvalid), .i_tready(s_rfnoc_ctrl_tready), + .o_aclk(ctrlport_clk), + .o_tdata({i_ctrl_tlast, i_ctrl_tdata}), + .o_tvalid(i_ctrl_tvalid), .o_tready(i_ctrl_tready) + ); + + axi_fifo_2clk #(.WIDTH(32+1), .SIZE(1), .PIPELINE("OUT")) out_fifo_i ( + .reset(ctrlport_rst), + .i_aclk(ctrlport_clk), + .i_tdata({o_ctrl_tlast, o_ctrl_tdata}), + .i_tvalid(o_ctrl_tvalid), .i_tready(o_ctrl_tready), + .o_aclk(rfnoc_ctrl_clk), + .o_tdata({m_rfnoc_ctrl_tlast, m_rfnoc_ctrl_tdata}), + .o_tvalid(m_rfnoc_ctrl_tvalid), .o_tready(m_rfnoc_ctrl_tready) + ); + + end + endgenerate + + // --------------------------------------------------- + // MUXING + // --------------------------------------------------- + wire [31:0] mst_req_tdata, mst_resp_tdata ; + wire mst_req_tlast, mst_resp_tlast ; + wire mst_req_tvalid, mst_resp_tvalid; + wire mst_req_tready, mst_resp_tready; + + wire [31:0] slv_req_tdata, slv_req_fifo_tdata, slv_resp_tdata ; + wire slv_req_tlast, slv_req_fifo_tlast, slv_resp_tlast ; + wire slv_req_tvalid, slv_req_fifo_tvalid, slv_resp_tvalid; + wire slv_req_tready, slv_req_fifo_tready, slv_resp_tready; + + generate + if (AXIS_CTRL_MST_EN == 1'b1 && AXIS_CTRL_SLV_EN == 1'b1) begin : gen_mst_slv_muxing + wire [31:0] in_hdr; + axi_demux #( + .WIDTH(32), .SIZE(2), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(0) + ) demux_i ( + .clk(ctrlport_clk), .reset(ctrlport_rst), .clear(1'b0), + .header(in_hdr), .dest(axis_ctrl_get_is_ack(in_hdr)), + .i_tdata (i_ctrl_tdata ), + .i_tlast (i_ctrl_tlast ), + .i_tvalid(i_ctrl_tvalid), + .i_tready(i_ctrl_tready), + .o_tdata ({mst_resp_tdata, slv_req_tdata }), + .o_tlast ({mst_resp_tlast, slv_req_tlast }), + .o_tvalid({mst_resp_tvalid, slv_req_tvalid}), + .o_tready({mst_resp_tready, slv_req_tready}) + ); + + axi_mux #( + .WIDTH(32), .SIZE(2), .PRIO(0), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(0) + ) mux_i ( + .clk(ctrlport_clk), .reset(ctrlport_rst), .clear(1'b0), + .i_tdata ({mst_req_tdata, slv_resp_tdata }), + .i_tlast ({mst_req_tlast, slv_resp_tlast }), + .i_tvalid({mst_req_tvalid, slv_resp_tvalid}), + .i_tready({mst_req_tready, slv_resp_tready}), + .o_tdata (o_ctrl_tdata ), + .o_tlast (o_ctrl_tlast ), + .o_tvalid(o_ctrl_tvalid), + .o_tready(o_ctrl_tready) + ); + + end else if (AXIS_CTRL_MST_EN == 1'b1) begin : gen_mst_muxing + + assign mst_resp_tdata = i_ctrl_tdata; + assign mst_resp_tlast = i_ctrl_tlast; + assign mst_resp_tvalid = i_ctrl_tvalid; + assign i_ctrl_tready = mst_resp_tready; + + assign o_ctrl_tdata = mst_req_tdata; + assign o_ctrl_tlast = mst_req_tlast; + assign o_ctrl_tvalid = mst_req_tvalid; + assign mst_req_tready = o_ctrl_tready; + + end else begin : gen_no_mst_muxing + + assign slv_req_tdata = i_ctrl_tdata; + assign slv_req_tlast = i_ctrl_tlast; + assign slv_req_tvalid = i_ctrl_tvalid; + assign i_ctrl_tready = slv_req_tready; + + assign o_ctrl_tdata = slv_resp_tdata; + assign o_ctrl_tlast = slv_resp_tlast; + assign o_ctrl_tvalid = slv_resp_tvalid; + assign slv_resp_tready = o_ctrl_tready; + + end + endgenerate + + // --------------------------------------------------- + // AXIS Control Master and Slave + // --------------------------------------------------- + + generate + if (AXIS_CTRL_MST_EN == 1'b1) begin : gen_ctrl_master + axis_ctrl_master #( .THIS_PORTID(THIS_PORTID) ) axis_ctrl_mst_i ( + .clk (ctrlport_clk), + .rst (ctrlport_rst), + .s_axis_ctrl_tdata (mst_resp_tdata), + .s_axis_ctrl_tlast (mst_resp_tlast), + .s_axis_ctrl_tvalid (mst_resp_tvalid), + .s_axis_ctrl_tready (mst_resp_tready), + .m_axis_ctrl_tdata (mst_req_tdata), + .m_axis_ctrl_tlast (mst_req_tlast), + .m_axis_ctrl_tvalid (mst_req_tvalid), + .m_axis_ctrl_tready (mst_req_tready), + .ctrlport_req_wr (s_ctrlport_req_wr), + .ctrlport_req_rd (s_ctrlport_req_rd), + .ctrlport_req_addr (s_ctrlport_req_addr), + .ctrlport_req_portid (s_ctrlport_req_portid), + .ctrlport_req_rem_epid (s_ctrlport_req_rem_epid), + .ctrlport_req_rem_portid(s_ctrlport_req_rem_portid), + .ctrlport_req_data (s_ctrlport_req_data), + .ctrlport_req_byte_en (s_ctrlport_req_byte_en), + .ctrlport_req_has_time (s_ctrlport_req_has_time), + .ctrlport_req_time (s_ctrlport_req_time), + .ctrlport_resp_ack (s_ctrlport_resp_ack), + .ctrlport_resp_status (s_ctrlport_resp_status), + .ctrlport_resp_data (s_ctrlport_resp_data) + ); + end else begin : gen_no_ctrl_master + assign mst_resp_tready = 1'b1; + assign mst_req_tlast = 1'b0; + assign mst_req_tvalid = 1'b0; + assign s_ctrlport_resp_ack = 1'b0; + end + + if (AXIS_CTRL_SLV_EN == 1'b1) begin : gen_ctrl_slave + axi_fifo #(.WIDTH(32+1), .SIZE(SLAVE_FIFO_SIZE)) slv_fifo_i ( + .clk(ctrlport_clk), .reset(ctrlport_rst), .clear(1'b0), + .i_tdata({slv_req_tlast, slv_req_tdata}), + .i_tvalid(slv_req_tvalid), .i_tready(slv_req_tready), + .o_tdata({slv_req_fifo_tlast, slv_req_fifo_tdata}), + .o_tvalid(slv_req_fifo_tvalid), .o_tready(slv_req_fifo_tready), + .space(), .occupied() + ); + + axis_ctrl_slave axis_ctrl_slv_i ( + .clk (ctrlport_clk), + .rst (ctrlport_rst), + .s_axis_ctrl_tdata (slv_req_fifo_tdata), + .s_axis_ctrl_tlast (slv_req_fifo_tlast), + .s_axis_ctrl_tvalid (slv_req_fifo_tvalid), + .s_axis_ctrl_tready (slv_req_fifo_tready), + .m_axis_ctrl_tdata (slv_resp_tdata), + .m_axis_ctrl_tlast (slv_resp_tlast), + .m_axis_ctrl_tvalid (slv_resp_tvalid), + .m_axis_ctrl_tready (slv_resp_tready), + .ctrlport_req_wr (m_ctrlport_req_wr), + .ctrlport_req_rd (m_ctrlport_req_rd), + .ctrlport_req_addr (m_ctrlport_req_addr), + .ctrlport_req_data (m_ctrlport_req_data), + .ctrlport_req_byte_en (m_ctrlport_req_byte_en), + .ctrlport_req_has_time(m_ctrlport_req_has_time), + .ctrlport_req_time (m_ctrlport_req_time), + .ctrlport_resp_ack (m_ctrlport_resp_ack), + .ctrlport_resp_status (m_ctrlport_resp_status), + .ctrlport_resp_data (m_ctrlport_resp_data) + ); + end else begin : gen_no_ctrl_slave + assign slv_req_fifo_tready = 1'b1; + assign slv_resp_tlast = 1'b0; + assign slv_resp_tvalid = 1'b0; + assign m_ctrlport_req_wr = 1'b0; + assign m_ctrlport_req_rd = 1'b0; + end + endgenerate + +endmodule // ctrlport_endpoint + diff --git a/fpga/usrp3/lib/rfnoc/core/rfnoc_axis_ctrl_utils.vh b/fpga/usrp3/lib/rfnoc/core/rfnoc_axis_ctrl_utils.vh new file mode 100644 index 000000000..5c3dab8ac --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/rfnoc_axis_ctrl_utils.vh @@ -0,0 +1,154 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// ============================================================= +// AXIS-Ctrl Bitfields +// ============================================================= + +// ----------------------- +// Line 0: HDR_0 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 31 is_ack Is this an acknowledgment to a transaction? +// 30 has_time Does the transaction have a timestamp? +// 29:24 seq_num Sequence number +// 23:20 num_data Number of data words +// 19:10 src_port Ctrl XB port that the source block is on +// 9:0 dst_port Ctrl XB port that the destination block is on + +// ----------------------- +// Line 1: HDR_1 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 31:26 <Reserved> +// 25:16 rem_dst_port Ctrl XB port that the remote dest block is on +// 15:0 rem_dst_epid Endpoint ID of the remote dest of this msg + +// ----------------------- +// Line 2: TS_LO (Optional) +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 31:0 timestamp Lower 32 bits of the timestamp + +// ----------------------- +// Line 3: TS_HI (Optional) +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 31:0 timestamp Upper 32 bits of the timestamp + +// ----------------------- +// Line 4: OP Word +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 31:30 status The status of the ack +// 29:28 <Reserved> +// 27:24 opcode Operation Code +// 23:20 byte_en Byte enable strobe +// 19:0 address Address for transaction + +// AXIS-Ctrl Status +// +localparam [1:0] AXIS_CTRL_STS_OKAY = 2'b00; +localparam [1:0] AXIS_CTRL_STS_CMDERR = 2'b01; +localparam [1:0] AXIS_CTRL_STS_TSERR = 2'b10; +localparam [1:0] AXIS_CTRL_STS_WARNING = 2'b11; + +// AXIS-Ctrl Opcode Definitions +// +localparam [3:0] AXIS_CTRL_OPCODE_SLEEP = 4'd0; +localparam [3:0] AXIS_CTRL_OPCODE_WRITE = 4'd1; +localparam [3:0] AXIS_CTRL_OPCODE_READ = 4'd2; +localparam [3:0] AXIS_CTRL_OPCODE_WRITE_READ = 4'd3; + +// AXIS-Ctrl Getter Functions +// +function [0:0] axis_ctrl_get_is_ack(input [31:0] header); + axis_ctrl_get_is_ack = header[31]; +endfunction + +function [0:0] axis_ctrl_get_has_time(input [31:0] header); + axis_ctrl_get_has_time = header[30]; +endfunction + +function [5:0] axis_ctrl_get_seq_num(input [31:0] header); + axis_ctrl_get_seq_num = header[29:24]; +endfunction + +function [3:0] axis_ctrl_get_num_data(input [31:0] header); + axis_ctrl_get_num_data = header[23:20]; +endfunction + +function [9:0] axis_ctrl_get_src_port(input [31:0] header); + axis_ctrl_get_src_port = header[19:10]; +endfunction + +function [9:0] axis_ctrl_get_dst_port(input [31:0] header); + axis_ctrl_get_dst_port = header[9:0]; +endfunction + +function [15:0] axis_ctrl_get_rem_dst_epid(input [31:0] header); + axis_ctrl_get_rem_dst_epid = header[15:0]; +endfunction + +function [9:0] axis_ctrl_get_rem_dst_port(input [31:0] header); + axis_ctrl_get_rem_dst_port = header[25:16]; +endfunction + +function [1:0] axis_ctrl_get_status(input [31:0] header); + axis_ctrl_get_status = header[31:30]; +endfunction + +function [3:0] axis_ctrl_get_opcode(input [31:0] header); + axis_ctrl_get_opcode = header[27:24]; +endfunction + +function [3:0] axis_ctrl_get_byte_en(input [31:0] header); + axis_ctrl_get_byte_en = header[23:20]; +endfunction + +function [19:0] axis_ctrl_get_address(input [31:0] header); + axis_ctrl_get_address = header[19:0]; +endfunction + +// AXIS-Ctrl Setter Functions +// +function [31:0] axis_ctrl_build_hdr_lo( + input [0:0] is_ack, + input [0:0] has_time, + input [5:0] seq_num, + input [3:0] num_data, + input [9:0] src_port, + input [9:0] dst_port +); + axis_ctrl_build_hdr_lo = {is_ack, has_time, seq_num, num_data, src_port, dst_port}; +endfunction + +function [31:0] axis_ctrl_build_hdr_hi( + input [9:0] rem_dst_port, + input [15:0] rem_dst_epid +); + axis_ctrl_build_hdr_hi = {6'h0, rem_dst_port, rem_dst_epid}; +endfunction + +function [31:0] chdr_ctrl_build_hdr_hi( + input [15:0] src_epid +); + chdr_ctrl_build_hdr_hi = {16'h0, src_epid}; +endfunction + +function [31:0] axis_ctrl_build_op_word( + input [1:0] status, + input [3:0] opcode, + input [3:0] byte_en, + input [19:0] address +); + axis_ctrl_build_op_word = {status, 2'b00, opcode, byte_en, address}; +endfunction diff --git a/fpga/usrp3/lib/rfnoc/core/rfnoc_backend_iface.vh b/fpga/usrp3/lib/rfnoc/core/rfnoc_backend_iface.vh new file mode 100644 index 000000000..ec5c152f6 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/rfnoc_backend_iface.vh @@ -0,0 +1,52 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// Each block has a backed interface that is 512 bits wide. This bus +// is split into 16 32-bit registers to it is preferable to have fields +// aligned at 32-bit boundaries + +// Backend Config +localparam BEC_FLUSH_TIMEOUT_OFFSET = 0; +localparam BEC_FLUSH_TIMEOUT_WIDTH = 32; +localparam BEC_FLUSH_EN_OFFSET = BEC_FLUSH_TIMEOUT_OFFSET + BEC_FLUSH_TIMEOUT_WIDTH; +localparam BEC_FLUSH_EN_WIDTH = 1; +localparam BEC_SOFT_CTRL_RST_OFFSET = BEC_FLUSH_EN_OFFSET + BEC_FLUSH_EN_WIDTH; +localparam BEC_SOFT_CTRL_RST_WIDTH = 1; +localparam BEC_SOFT_CHDR_RST_OFFSET = BEC_SOFT_CTRL_RST_OFFSET + BEC_SOFT_CTRL_RST_WIDTH; +localparam BEC_SOFT_CHDR_RST_WIDTH = 1; +localparam BEC_TOTAL_WIDTH = BEC_SOFT_CHDR_RST_OFFSET + BEC_SOFT_CHDR_RST_WIDTH; + +localparam [511:0] BEC_DEFAULT_VAL = { + {(512-BEC_TOTAL_WIDTH){1'b0}}, + 1'b1, // BEC_SOFT_CHDR_RST + 1'b1, // BEC_SOFT_CTRL_RST + 1'b0, // BEC_FLUSH_EN + 32'd0 // BEC_FLUSH_TIMEOUT +}; + +// Backend Status +localparam BES_PROTO_VER_OFFSET = 0; +localparam BES_PROTO_VER_WIDTH = 6; +localparam BES_NUM_DATA_I_OFFSET = BES_PROTO_VER_OFFSET + BES_PROTO_VER_WIDTH; +localparam BES_NUM_DATA_I_WIDTH = 6; +localparam BES_NUM_DATA_O_OFFSET = BES_NUM_DATA_I_OFFSET + BES_NUM_DATA_I_WIDTH; +localparam BES_NUM_DATA_O_WIDTH = 6; +localparam BES_CTRL_FIFOSIZE_OFFSET = BES_NUM_DATA_O_OFFSET + BES_NUM_DATA_O_WIDTH; +localparam BES_CTRL_FIFOSIZE_WIDTH = 6; +localparam BES_CTRL_MAX_ASYNC_MSGS_OFFSET = BES_CTRL_FIFOSIZE_OFFSET + BES_CTRL_FIFOSIZE_WIDTH; +localparam BES_CTRL_MAX_ASYNC_MSGS_WIDTH = 8; +localparam BES_NOC_ID_OFFSET = BES_CTRL_MAX_ASYNC_MSGS_OFFSET + BES_CTRL_MAX_ASYNC_MSGS_WIDTH; +localparam BES_NOC_ID_WIDTH = 32; +localparam BES_FLUSH_ACTIVE_OFFSET = BES_NOC_ID_OFFSET + BES_NOC_ID_WIDTH; +localparam BES_FLUSH_ACTIVE_WIDTH = 1; +localparam BES_FLUSH_DONE_OFFSET = BES_FLUSH_ACTIVE_OFFSET + BES_FLUSH_ACTIVE_WIDTH; +localparam BES_FLUSH_DONE_WIDTH = 1; +localparam BES_DATA_MTU_OFFSET = BES_FLUSH_DONE_OFFSET + BES_FLUSH_DONE_WIDTH; +localparam BES_DATA_MTU_WIDTH = 6; +localparam BES_TOTAL_WIDTH = BES_DATA_MTU_OFFSET + BES_DATA_MTU_WIDTH; + +// Protocol version for this definition +localparam [5:0] BACKEND_PROTO_VER = 6'd1;
\ No newline at end of file diff --git a/fpga/usrp3/lib/rfnoc/core/rfnoc_chdr_internal_utils.vh b/fpga/usrp3/lib/rfnoc/core/rfnoc_chdr_internal_utils.vh new file mode 100644 index 000000000..1d70c0f1c --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/rfnoc_chdr_internal_utils.vh @@ -0,0 +1,452 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// ============================================================= +// Stream Status Bitfields +// ============================================================= + +// ----------------------- +// Line 0 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:24 capacity_bytes Downstream buffer capacity in bytes +// 23:20 <reserved> +// 19:16 status Stream status code (enumeration) +// 15:0 src_epid Endpoint ID of the source of this msg + +// ----------------------- +// Line 1 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:24 xfercnt_pkts Transfer count in packets +// 23:0 capacity_pkts Downstream buffer capacity in packets + +// ----------------------- +// Line 2 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:0 xfercnt_bytes Transfer count in bytes + +// ----------------------- +// Line 3 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:16 status_info Extended information about status (diagnostic only) +// 15:0 buff_info Extended information about buffer state (diagnostic only) + +localparam [3:0] CHDR_STRS_STATUS_OKAY = 4'd0; // No error +localparam [3:0] CHDR_STRS_STATUS_CMDERR = 4'd1; // Cmd execution failed +localparam [3:0] CHDR_STRS_STATUS_SEQERR = 4'd2; // Sequence number discontinuity +localparam [3:0] CHDR_STRS_STATUS_DATAERR = 4'd3; // Data integrity check failed +localparam [3:0] CHDR_STRS_STATUS_RTERR = 4'd4; // Unexpected destination + +// 64-bit fields +function [39:0] chdr64_strs_get_capacity_bytes(input [63:0] header); + chdr64_strs_get_capacity_bytes = header[63:24]; +endfunction + +function [3:0] chdr64_strs_get_status(input [63:0] header); + chdr64_strs_get_status = header[19:16]; +endfunction + +function [15:0] chdr64_strs_get_src_epid(input [63:0] header); + chdr64_strs_get_src_epid = header[15:0]; +endfunction + +function [39:0] chdr64_strs_get_xfercnt_pkts(input [63:0] header); + chdr64_strs_get_xfercnt_pkts = header[63:24]; +endfunction + +function [23:0] chdr64_strs_get_capacity_pkts(input [63:0] header); + chdr64_strs_get_capacity_pkts = header[23:0]; +endfunction + +function [63:0] chdr64_strs_get_xfercnt_bytes(input [63:0] header); + chdr64_strs_get_xfercnt_bytes = header[63:0]; +endfunction + +function [47:0] chdr64_strs_get_status_info(input [63:0] header); + chdr64_strs_get_status_info = header[63:16]; +endfunction + +function [15:0] chdr64_strs_get_buff_info(input [63:0] header); + chdr64_strs_get_buff_info = header[15:0]; +endfunction + + +// 128-bit fields +function [39:0] chdr128_strs_get_capacity_bytes(input [127:0] header); + chdr128_strs_get_capacity_bytes = chdr64_strs_get_capacity_bytes(header[63:0]); +endfunction + +function [3:0] chdr128_strs_get_status(input [127:0] header); + chdr128_strs_get_status = chdr64_strs_get_status(header[63:0]); +endfunction + +function [15:0] chdr128_strs_get_src_epid(input [127:0] header); + chdr128_strs_get_src_epid = chdr64_strs_get_src_epid(header[63:0]); +endfunction + +function [23:0] chdr128_strs_get_capacity_pkts(input [127:0] header); + chdr128_strs_get_capacity_pkts = chdr64_strs_get_capacity_pkts(header[127:64]); +endfunction + +function [39:0] chdr128_strs_get_xfercnt_pkts(input [127:0] header); + chdr128_strs_get_xfercnt_pkts = chdr64_strs_get_xfercnt_pkts(header[127:64]); +endfunction + +function [63:0] chdr128_strs_get_xfercnt_bytes(input [127:0] header); + chdr128_strs_get_xfercnt_bytes = chdr64_strs_get_xfercnt_bytes(header[63:0]); +endfunction + +function [47:0] chdr128_strs_get_status_info(input [127:0] header); + chdr128_strs_get_status_info = chdr64_strs_get_status_info(header[127:64]); +endfunction + +function [15:0] chdr128_strs_get_buff_info(input [127:0] header); + chdr128_strs_get_buff_info = chdr64_strs_get_buff_info(header[127:64]); +endfunction + + +// 256-bit fields +function [39:0] chdr256_strs_get_capacity_bytes(input [255:0] header); + chdr256_strs_get_capacity_bytes = chdr64_strs_get_capacity_bytes(header[63:0]); +endfunction + +function [3:0] chdr256_strs_get_status(input [255:0] header); + chdr256_strs_get_status = chdr64_strs_get_status(header[63:0]); +endfunction + +function [15:0] chdr256_strs_get_src_epid(input [255:0] header); + chdr256_strs_get_src_epid = chdr64_strs_get_src_epid(header[63:0]); +endfunction + +function [23:0] chdr256_strs_get_capacity_pkts(input [255:0] header); + chdr256_strs_get_capacity_pkts = chdr64_strs_get_capacity_pkts(header[127:64]); +endfunction + +function [39:0] chdr256_strs_get_xfercnt_pkts(input [255:0] header); + chdr256_strs_get_xfercnt_pkts = chdr64_strs_get_xfercnt_pkts(header[127:64]); +endfunction + +function [63:0] chdr256_strs_get_xfercnt_bytes(input [255:0] header); + chdr256_strs_get_xfercnt_bytes = chdr64_strs_get_xfercnt_bytes(header[191:128]); +endfunction + +function [47:0] chdr256_strs_get_status_info(input [255:0] header); + chdr256_strs_get_status_info = chdr64_strs_get_status_info(header[255:192]); +endfunction + +function [15:0] chdr256_strs_get_buff_info(input [255:0] header); + chdr256_strs_get_buff_info = chdr64_strs_get_buff_info(header[255:192]); +endfunction + +// Stream Status Setter Functions +// + +// 64-bit fields +function [63:0] chdr64_strs_build_w0( + input [39:0] capacity_bytes, + input [3:0] status, + input [15:0] src_epid +); + chdr64_strs_build_w0 = {capacity_bytes, 4'h0, status, src_epid}; +endfunction + +function [63:0] chdr64_strs_build_w1( + input [39:0] xfercnt_pkts, + input [23:0] capacity_pkts +); + chdr64_strs_build_w1 = {xfercnt_pkts, capacity_pkts}; +endfunction + +function [63:0] chdr64_strs_build_w2( + input [63:0] xfercnt_bytes +); + chdr64_strs_build_w2 = xfercnt_bytes; +endfunction + +function [63:0] chdr64_strs_build_w3( + input [47:0] status_info, + input [15:0] buff_info +); + chdr64_strs_build_w3 = {status_info, buff_info}; +endfunction + +// 128-bit fields +function [127:0] chdr128_strs_build_w0( + input [39:0] xfercnt_pkts, + input [23:0] capacity_pkts, + input [39:0] capacity_bytes, + input [3:0] status, + input [15:0] src_epid +); + chdr128_strs_build_w0 = { + chdr64_strs_build_w1(xfercnt_pkts, capacity_pkts), + chdr64_strs_build_w0(capacity_bytes, status, src_epid)}; +endfunction + +function [127:0] chdr128_strs_build_w1( + input [47:0] status_info, + input [15:0] buff_info, + input [63:0] xfercnt_bytes +); + chdr128_strs_build_w1 = { + chdr64_strs_build_w3(status_info, buff_info), + chdr64_strs_build_w2(xfercnt_bytes)}; +endfunction + +// 256-bit fields +function [255:0] chdr256_strs_build( + input [47:0] status_info, + input [15:0] buff_info, + input [63:0] xfercnt_bytes, + input [39:0] xfercnt_pkts, + input [23:0] capacity_pkts, + input [39:0] capacity_bytes, + input [3:0] status, + input [15:0] src_epid +); + chdr256_strs_build = { + chdr64_strs_build_w3(status_info, buff_info), + chdr64_strs_build_w2(xfercnt_bytes), + chdr64_strs_build_w1(xfercnt_pkts, capacity_pkts), + chdr64_strs_build_w0(capacity_bytes, status, src_epid)}; +endfunction + +// ============================================================= +// Stream Command Bitfields +// ============================================================= + +// ----------------------- +// Line 0 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:24 num_pkts Downstream buffer capacity in bytes +// 23:20 op_data Payload for command +// 19:16 op_code Command operation code (enumeration) +// 15:0 src_epid Endpoint ID of the source of this msg + +// ----------------------- +// Line 1 +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:0 num_bytes Transfer count in packets + +localparam [3:0] CHDR_STRC_OPCODE_INIT = 4'd0; +localparam [3:0] CHDR_STRC_OPCODE_PING = 4'd1; +localparam [3:0] CHDR_STRC_OPCODE_RESYNC = 4'd2; + +// 64-bit fields +function [39:0] chdr64_strc_get_num_pkts(input [63:0] header); + chdr64_strc_get_num_pkts = header[63:24]; +endfunction + +function [3:0] chdr64_strc_get_op_data(input [63:0] header); + chdr64_strc_get_op_data = header[23:20]; +endfunction + +function [3:0] chdr64_strc_get_op_code(input [63:0] header); + chdr64_strc_get_op_code = header[19:16]; +endfunction + +function [15:0] chdr64_strc_get_src_epid(input [63:0] header); + chdr64_strc_get_src_epid = header[15:0]; +endfunction + +function [63:0] chdr64_strc_get_num_bytes(input [63:0] header); + chdr64_strc_get_num_bytes = header[63:0]; +endfunction + +// 128-bit fields +function [39:0] chdr128_strc_get_num_pkts(input [127:0] header); + chdr128_strc_get_num_pkts = chdr64_strc_get_num_pkts(header[63:0]); +endfunction + +function [3:0] chdr128_strc_get_op_data(input [127:0] header); + chdr128_strc_get_op_data = chdr64_strc_get_op_data(header[63:0]); +endfunction + +function [3:0] chdr128_strc_get_op_code(input [127:0] header); + chdr128_strc_get_op_code = chdr64_strc_get_op_code(header[63:0]); +endfunction + +function [15:0] chdr128_strc_get_src_epid(input [127:0] header); + chdr128_strc_get_src_epid = chdr64_strc_get_src_epid(header[63:0]); +endfunction + +function [63:0] chdr128_strc_get_num_bytes(input [127:0] header); + chdr128_strc_get_num_bytes = chdr64_strc_get_num_bytes(header[127:64]); +endfunction + +// Stream Command Setter Functions +// + +// 64-bit fields + +function [63:0] chdr64_strc_build_w0( + input [39:0] num_pkts, + input [3:0] op_data, + input [3:0] op_code, + input [15:0] src_epid +); + chdr64_strc_build_w0 = {num_pkts, op_data, op_code, src_epid}; +endfunction + +function [63:0] chdr64_strc_build_w1( + input [63:0] num_bytes +); + chdr64_strc_build_w1 = num_bytes; +endfunction + +// 128-bit fields +function [127:0] chdr128_strc_build( + input [63:0] num_bytes, + input [39:0] num_pkts, + input [3:0] op_data, + input [3:0] op_code, + input [15:0] src_epid +); + chdr128_strc_build = { + chdr64_strc_build_w1(num_bytes), + chdr64_strc_build_w0(num_pkts, op_data, op_code, src_epid)}; +endfunction + +// ============================================================= +// Management Packet Bitfields +// ============================================================= + +// ----------------------- +// HDR +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:48 proto_ver Protocol Version +// 47:45 chdr_w Bitwidth of the CHDR interface +// 44:26 <Reserved> +// 25:16 num_hops Number of hops that this message will take (TTL) +// 15:0 src_epid Endpoint ID of the source of this msg + +// ----------------------- +// OP +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:16 op_payload Operation Payload +// 15:8 op_code Operation code +// 7:0 ops_pending Number of operations pending in this hop + +localparam [2:0] CHDR_MGMT_WIDTH_64 = 3'd0; +localparam [2:0] CHDR_MGMT_WIDTH_128 = 3'd1; +localparam [2:0] CHDR_MGMT_WIDTH_256 = 3'd2; +localparam [2:0] CHDR_MGMT_WIDTH_512 = 3'd3; + +function [2:0] chdr_w_to_enum(input integer bits); + if (bits == 512) + chdr_w_to_enum = CHDR_MGMT_WIDTH_512; + else if (bits == 256) + chdr_w_to_enum = CHDR_MGMT_WIDTH_256; + else if (bits == 128) + chdr_w_to_enum = CHDR_MGMT_WIDTH_128; + else + chdr_w_to_enum = CHDR_MGMT_WIDTH_64; +endfunction + +localparam [7:0] CHDR_MGMT_OP_NOP = 8'd0; +localparam [7:0] CHDR_MGMT_OP_ADVERTISE = 8'd1; +localparam [7:0] CHDR_MGMT_OP_SEL_DEST = 8'd2; +localparam [7:0] CHDR_MGMT_OP_RETURN = 8'd3; +localparam [7:0] CHDR_MGMT_OP_INFO_REQ = 8'd4; +localparam [7:0] CHDR_MGMT_OP_INFO_RESP = 8'd5; +localparam [7:0] CHDR_MGMT_OP_CFG_WR_REQ = 8'd6; +localparam [7:0] CHDR_MGMT_OP_CFG_RD_REQ = 8'd7; +localparam [7:0] CHDR_MGMT_OP_CFG_RD_RESP = 8'd8; + +function [15:0] chdr_mgmt_get_proto_ver(input [63:0] header); + chdr_mgmt_get_proto_ver = header[63:48]; +endfunction + +function [2:0] chdr_mgmt_get_chdr_w(input [63:0] header); + chdr_mgmt_get_chdr_w = header[47:45]; +endfunction + +function [9:0] chdr_mgmt_get_num_hops(input [63:0] header); + chdr_mgmt_get_num_hops = header[25:16]; +endfunction + +function [15:0] chdr_mgmt_get_src_epid(input [63:0] header); + chdr_mgmt_get_src_epid = header[15:0]; +endfunction + +function [47:0] chdr_mgmt_get_op_payload(input [63:0] header); + chdr_mgmt_get_op_payload = header[63:16]; +endfunction + +function [7:0] chdr_mgmt_get_op_code(input [63:0] header); + chdr_mgmt_get_op_code = header[15:8]; +endfunction + +function [7:0] chdr_mgmt_get_ops_pending(input [63:0] header); + chdr_mgmt_get_ops_pending = header[7:0]; +endfunction + +function [63:0] chdr_mgmt_build_hdr( + input [15:0] proto_ver, + input [2:0] chdr_w, + input [9:0] num_hops, + input [15:0] src_epid +); + chdr_mgmt_build_hdr = {proto_ver, chdr_w, 19'h0, num_hops, src_epid}; +endfunction + +function [63:0] chdr_mgmt_build_op( + input [47:0] op_payload, + input [7:0] op_code, + input [7:0] ops_pending +); + chdr_mgmt_build_op = {op_payload, op_code, ops_pending}; +endfunction + +// Definition for the TID field for the output of chdr_mgmt_pkt_handler +localparam [1:0] CHDR_MGMT_ROUTE_EPID = 2'd0; // Route based on EPID +localparam [1:0] CHDR_MGMT_ROUTE_TDEST = 2'd1; // Route based on tdest field +localparam [1:0] CHDR_MGMT_RETURN_TO_SRC = 2'd2; // Return packet to sender + +// ----------------------- +// OP specific fields +// ----------------------- + +localparam [3:0] NODE_TYPE_INVALID = 4'd0; +localparam [3:0] NODE_TYPE_XBAR = 4'd1; +localparam [3:0] NODE_TYPE_STREAM_EP = 4'd2; +localparam [3:0] NODE_TYPE_TRANSPORT = 4'd3; + +function [47:0] chdr_mgmt_build_node_info( + input [17:0] ext_info, + input [9:0] node_inst, + input [3:0] node_type, + input [15:0] device_id +); + chdr_mgmt_build_node_info = {ext_info, node_inst, node_type, device_id}; +endfunction + +function [9:0] chdr_mgmt_sel_dest_get_tdest(input [47:0] payload); + chdr_mgmt_sel_dest_get_tdest = payload[9:0]; +endfunction + +function [15:0] chdr_mgmt_cfg_reg_get_addr(input [47:0] payload); + chdr_mgmt_cfg_reg_get_addr = payload[15:0]; +endfunction + +function [31:0] chdr_mgmt_cfg_reg_get_data(input [47:0] payload); + chdr_mgmt_cfg_reg_get_data = payload[47:16]; +endfunction diff --git a/fpga/usrp3/lib/rfnoc/core/rfnoc_chdr_utils.vh b/fpga/usrp3/lib/rfnoc/core/rfnoc_chdr_utils.vh new file mode 100644 index 000000000..047d58bc0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/rfnoc_chdr_utils.vh @@ -0,0 +1,200 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// ============================================================= +// CHDR Bitfields +// ============================================================= +// +// The Condensed Hierarchical Datagram for RFNoC (CHDR) is +// a protocol that defines the fundamental unit of data transfer +// in an RFNoC network. +// +// ----------------------- +// Header +// ----------------------- +// Bits Name Meaning +// ---- ---- ------- +// 63:58 vc Virtual Channel +// 57 eob End of Burst Delimiter +// 56 eov End of Vector Delimiter +// 55:53 pkt_type Packet Type (enumeration) +// 52:48 num_mdata Number of lines of metadata +// 47:32 seq_num Sequence number for the packet +// 31:16 length Length of the datagram in bytes +// 15:0 dst_epid Destination Endpoint ID +// +// Field: Packet Type +// ----------------------- +// 3'd0 Management +// 3'd1 Stream Status +// 3'd2 Stream Command +// 3'd3 <Reserved> +// 3'd4 Control Transaction +// 3'd5 <Reserved> +// 3'd6 Data (without timestamp) +// 3'd7 Data (with timestamp) +// + +// Special CHDR Values +// + +// Packet Type +localparam [2:0] CHDR_PKT_TYPE_MGMT = 3'd0; +localparam [2:0] CHDR_PKT_TYPE_STRS = 3'd1; +localparam [2:0] CHDR_PKT_TYPE_STRC = 3'd2; +//localparam [2:0] RESERVED = 3'd3; +localparam [2:0] CHDR_PKT_TYPE_CTRL = 3'd4; +//localparam [2:0] RESERVED = 3'd5; +localparam [2:0] CHDR_PKT_TYPE_DATA = 3'd6; +localparam [2:0] CHDR_PKT_TYPE_DATA_TS = 3'd7; + +// Metadata +localparam [4:0] CHDR_NO_MDATA = 5'd0; + +// EPID +localparam [15:0] NULL_EPID = 16'd0; + +// CHDR Getter Functions +// +function [5:0] chdr_get_vc(input [63:0] header); + chdr_get_vc = header[63:58]; +endfunction + +function [0:0] chdr_get_eob(input [63:0] header); + chdr_get_eob = header[57]; +endfunction + +function [0:0] chdr_get_eov(input [63:0] header); + chdr_get_eov = header[56]; +endfunction + +function [2:0] chdr_get_pkt_type(input [63:0] header); + chdr_get_pkt_type = header[55:53]; +endfunction + +function [4:0] chdr_get_num_mdata(input [63:0] header); + chdr_get_num_mdata = header[52:48]; +endfunction + +function [15:0] chdr_get_seq_num(input [63:0] header); + chdr_get_seq_num = header[47:32]; +endfunction + +function [15:0] chdr_get_length(input [63:0] header); + chdr_get_length = header[31:16]; +endfunction + +function [15:0] chdr_get_dst_epid(input [63:0] header); + chdr_get_dst_epid = header[15:0]; +endfunction + +// CHDR Setter Functions +// +function [63:0] chdr_build_header( + input [5:0] vc, + input [0:0] eob, + input [0:0] eov, + input [2:0] pkt_type, + input [4:0] num_mdata, + input [15:0] seq_num, + input [15:0] length, + input [15:0] dst_epid +); + chdr_build_header = {vc, eob, eov, pkt_type, num_mdata, seq_num, length, dst_epid}; +endfunction + +function [63:0] chdr_set_vc( + input [63:0] base_hdr, + input [5:0] vc +); + chdr_set_vc = {vc, base_hdr[57:0]}; +endfunction + +function [63:0] chdr_set_eob( + input [63:0] base_hdr, + input [0:0] eob +); + chdr_set_eob = {base_hdr[63:58], eob, base_hdr[56:0]}; +endfunction + +function [63:0] chdr_set_eov( + input [63:0] base_hdr, + input [0:0] eov +); + chdr_set_eov = {base_hdr[63:57], eov, base_hdr[55:0]}; +endfunction + +function [63:0] chdr_set_delims( + input [63:0] base_hdr, + input [0:0] eob, + input [0:0] eov +); + chdr_set_delims = {base_hdr[63:58], eob, eov, base_hdr[55:0]}; +endfunction + +function [63:0] chdr_set_pkt_type( + input [63:0] base_hdr, + input [2:0] pkt_type +); + chdr_set_pkt_type = {base_hdr[63:56], pkt_type, base_hdr[52:0]}; +endfunction + +function [63:0] chdr_set_num_mdata( + input [63:0] base_hdr, + input [4:0] num_mdata +); + chdr_set_num_mdata = {base_hdr[63:53], num_mdata, base_hdr[47:0]}; +endfunction + +function [63:0] chdr_set_seq_num( + input [63:0] base_hdr, + input [15:0] seq_num +); + chdr_set_seq_num = {base_hdr[63:48], seq_num, base_hdr[31:0]}; +endfunction + +function [63:0] chdr_set_length( + input [63:0] base_hdr, + input [15:0] length +); + chdr_set_length = {base_hdr[63:32], length, base_hdr[15:0]}; +endfunction + +function [63:0] chdr_set_dst_epid( + input [63:0] base_hdr, + input [15:0] dst_epid +); + chdr_set_dst_epid = {base_hdr[63:16], dst_epid}; +endfunction + +// ============================================================= +// Data Packet Specific +// ============================================================= + +localparam [3:0] CONTEXT_FIELD_HDR = 4'd0; +localparam [3:0] CONTEXT_FIELD_HDR_TS = 4'd1; +localparam [3:0] CONTEXT_FIELD_TS = 4'd2; +localparam [3:0] CONTEXT_FIELD_MDATA = 4'd3; + +function [0:0] chdr_get_has_time(input [63:0] header); + chdr_get_has_time = (chdr_get_pkt_type(header) == CHDR_PKT_TYPE_DATA_TS); +endfunction + +// Calculate the payload length in bytes based on the CHDR_W and header +function [15:0] chdr_calc_payload_length(input [31:0] chdr_w, input [63:0] header); + reg [15:0] payload_length, mdata_length, header_length; + begin + if (chdr_w == 64) begin + header_length = chdr_get_has_time(header) ? 2*(chdr_w/8) : (chdr_w/8); + end else begin + header_length = chdr_w/8; + end + mdata_length = chdr_get_num_mdata(header) * (chdr_w/8); + payload_length = chdr_get_length(header) - mdata_length - header_length; + + chdr_calc_payload_length = payload_length; + end +endfunction diff --git a/fpga/usrp3/lib/rfnoc/core/rfnoc_core_kernel.v b/fpga/usrp3/lib/rfnoc/core/rfnoc_core_kernel.v new file mode 100644 index 000000000..15a7940a4 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/core/rfnoc_core_kernel.v @@ -0,0 +1,385 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_core_kernel +// Description: +// The main utility and software interface module for an +// assembled rfnoc design +// +// Parameters: +// - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>} +// - DEVICE_TYPE: The device type to use in the Device Info register +// - DEVICE_FAMILY: The device family (to pass to Xilinx primitives) +// - SAFE_START_CLKS: Instantiate logic to ensure that all output +// clocks are glitch-free and startup safely +// - NUM_BLOCKS: Number of blocks instantiated in the design +// - NUM_STREAM_ENDPOINTS: Number of stream EPs instantiated in the design +// - NUM_ENDPOINTS_CTRL: Number of stream EPs connected to the ctrl crossbar +// - NUM_TRANSPORTS: Number of transports instantiated in the design +// - NUM_EDGES: Number of edges of static connection in the design +// - CHDR_XBAR_PRESENT: 1 if the CHDR crossbar is present. If 0 then +// transports are directly connected to SEPs +// - EDGE_TBL_FILE: The memory init file for the static connection +// adjacency list +// +// Signals: +// - chdr_aclk : The input CHDR clock (may be unbuffered if SAFE_START_CLKS=1) +// - chdr_aclk_locked : The PLL locked pin for the input CHDR clock (unused if SAFE_START_CLKS=0) +// - ctrl_aclk : The input Control clock (may be unbuffered if SAFE_START_CLKS=1) +// - ctrl_aclk_locked : The PLL locked pin for the input Control clock (unused if SAFE_START_CLKS=0) +// - core_chdr_clk: Output stable CHDR clock for the rest of the design +// - core_chdr_rst: Output sync CHDR reset for all infrastructure modules (not blocks) +// - core_ctrl_clk: Output stable Control clock for the rest of the design +// - core_ctrl_rst: Output sync Control reset for all infrastructure modules (not blocks) +// - s_axis_ctrl_* : Slave AXIS-Ctrl for the primary (zero'th) control endpoint +// - m_axis_ctrl_* : Master AXIS-Ctrl for the primary (zero'th) control endpoint +// - device_id: The dynamic device_id to read through the Device Info register (domain: core_chdr_clk) +// - rfnoc_core_config: The backend config port for all blocks in the design (domain: core_ctrl_clk) +// - rfnoc_core_status: The backend status port for all blocks in the design (domain: core_ctrl_clk) + +module rfnoc_core_kernel #( + parameter [15:0] PROTOVER = {8'd1, 8'd0}, + parameter [15:0] DEVICE_TYPE = 16'd0, + parameter DEVICE_FAMILY = "7SERIES", + parameter SAFE_START_CLKS = 0, + parameter [9:0] NUM_BLOCKS = 0, + parameter [9:0] NUM_STREAM_ENDPOINTS = 0, + parameter [9:0] NUM_ENDPOINTS_CTRL = 0, + parameter [9:0] NUM_TRANSPORTS = 0, + parameter [11:0] NUM_EDGES = 0, + parameter [0:0] CHDR_XBAR_PRESENT = 1, + parameter EDGE_TBL_FILE = "" +)( + // Input clocks and resets + input wire chdr_aclk, + input wire chdr_aclk_locked, + input wire ctrl_aclk, + input wire ctrl_aclk_locked, + input wire core_arst, + // Output clocks and resets + output wire core_chdr_clk, + output wire core_chdr_rst, + output wire core_ctrl_clk, + output wire core_ctrl_rst, + // AXIS-Control Bus + input wire [31:0] s_axis_ctrl_tdata, + input wire s_axis_ctrl_tlast, + input wire s_axis_ctrl_tvalid, + output wire s_axis_ctrl_tready, + output wire [31:0] m_axis_ctrl_tdata, + output wire m_axis_ctrl_tlast, + output wire m_axis_ctrl_tvalid, + input wire m_axis_ctrl_tready, + // Global info (domain: core_chdr_clk) + input wire [15:0] device_id, + // Backend config/status for each block (domain: core_ctrl_clk) + output wire [(512*NUM_BLOCKS)-1:0] rfnoc_core_config, + input wire [(512*NUM_BLOCKS)-1:0] rfnoc_core_status +); + + `include "rfnoc_axis_ctrl_utils.vh" + `include "rfnoc_backend_iface.vh" + + // ----------------------------------- + // Clocking and Resets + // ----------------------------------- + + generate if (SAFE_START_CLKS == 1) begin + // Safe startup logic for the CHDR and Control clocks: + // chdr_aclk and ctrl_aclk can be unbuffered. + // Use a BUFGCE to disable the clock until the upstream + // PLLs have locked. + + wire chdr_ce_clk, ctrl_ce_clk; + (* keep = "true" *) (* async_reg = "true" *) reg [7:0] chdr_clk_ce_shreg = 8'h0; + (* keep = "true" *) (* async_reg = "true" *) reg [7:0] ctrl_clk_ce_shreg = 8'h0; + + // A glitch-free clock buffer with an enable + BUFGCE chdr_clk_buf_i ( + .I (chdr_aclk), + .CE(chdr_clk_ce_shreg[7]), + .O (core_chdr_clk) + ); + // A separate clock buffer for the CE signal + // We instantiate this manually to prevent the tools from instantiating + // the more scare BUFG here. There are a lot more BUFHs than BUFGs + BUFH chdr_ce_buf_i ( + .I(chdr_aclk), + .O(chdr_ce_clk) + ); + always @(posedge chdr_ce_clk) begin + chdr_clk_ce_shreg <= {chdr_clk_ce_shreg[6:0], chdr_aclk_locked}; + end + + // A glitch-free clock buffer with an enable + BUFGCE ctrl_clk_buf_i ( + .I (ctrl_aclk), + .CE(ctrl_clk_ce_shreg[7]), + .O (core_ctrl_clk) + ); + // A separate clock buffer for the CE signal + // We instantiate this manually to prevent the tools from instantiating + // the more scare BUFG here. There are a lot more BUFHs than BUFGs + BUFH ctrl_ce_buf_i ( + .I(ctrl_aclk), + .O(ctrl_ce_clk) + ); + always @(posedge ctrl_ce_clk) begin + ctrl_clk_ce_shreg <= {ctrl_clk_ce_shreg[6:0], ctrl_aclk_locked}; + end + end else begin + // We assume that chdr_aclk and ctrl_aclk start safely and are glitch-free + assign core_chdr_clk = chdr_aclk; + assign core_ctrl_clk = ctrl_aclk; + end endgenerate + + reset_sync rst_sync_chdr_i ( + .clk(core_chdr_clk), .reset_in(core_arst), .reset_out(core_chdr_rst) + ); + reset_sync rst_sync_ctrl_i ( + .clk(core_ctrl_clk), .reset_in(core_arst), .reset_out(core_ctrl_rst) + ); + + // ----------------------------------- + // AXIS-Ctrl Slave + // ----------------------------------- + + wire ctrlport_req_wr; + wire ctrlport_req_rd; + wire [19:0] ctrlport_req_addr; + wire [31:0] ctrlport_req_data; + reg ctrlport_resp_ack; + reg [31:0] ctrlport_resp_data; + + // The port ID of this endpoint must be zero + localparam [9:0] RFNOC_CORE_PORT_ID = 10'd0; + + ctrlport_endpoint #( + .THIS_PORTID(RFNOC_CORE_PORT_ID), .SYNC_CLKS(1), + .AXIS_CTRL_MST_EN(0), .AXIS_CTRL_SLV_EN(1), + .SLAVE_FIFO_SIZE(5) + ) ctrlport_ep_i ( + .rfnoc_ctrl_clk (core_ctrl_clk ), + .rfnoc_ctrl_rst (core_ctrl_rst ), + .ctrlport_clk (core_ctrl_clk ), + .ctrlport_rst (core_ctrl_rst ), + .s_rfnoc_ctrl_tdata (s_axis_ctrl_tdata ), + .s_rfnoc_ctrl_tlast (s_axis_ctrl_tlast ), + .s_rfnoc_ctrl_tvalid (s_axis_ctrl_tvalid ), + .s_rfnoc_ctrl_tready (s_axis_ctrl_tready ), + .m_rfnoc_ctrl_tdata (m_axis_ctrl_tdata ), + .m_rfnoc_ctrl_tlast (m_axis_ctrl_tlast ), + .m_rfnoc_ctrl_tvalid (m_axis_ctrl_tvalid ), + .m_rfnoc_ctrl_tready (m_axis_ctrl_tready ), + .m_ctrlport_req_wr (ctrlport_req_wr ), + .m_ctrlport_req_rd (ctrlport_req_rd ), + .m_ctrlport_req_addr (ctrlport_req_addr ), + .m_ctrlport_req_data (ctrlport_req_data ), + .m_ctrlport_req_byte_en (/* not supported */), + .m_ctrlport_req_has_time (/* not supported */), + .m_ctrlport_req_time (/* not supported */), + .m_ctrlport_resp_ack (ctrlport_resp_ack ), + .m_ctrlport_resp_status (AXIS_CTRL_STS_OKAY ), + .m_ctrlport_resp_data (ctrlport_resp_data ), + .s_ctrlport_req_wr (1'b0 ), + .s_ctrlport_req_rd (1'b0 ), + .s_ctrlport_req_addr (20'd0 ), + .s_ctrlport_req_portid (10'd0 ), + .s_ctrlport_req_rem_epid (16'd0 ), + .s_ctrlport_req_rem_portid(10'd0 ), + .s_ctrlport_req_data (32'h0 ), + .s_ctrlport_req_byte_en (4'h0 ), + .s_ctrlport_req_has_time (1'b0 ), + .s_ctrlport_req_time (1'b0 ), + .s_ctrlport_resp_ack (/* unused */ ), + .s_ctrlport_resp_status (/* unused */ ), + .s_ctrlport_resp_data (/* unused */ ) + ); + + // ------------------------------------------------ + // Segment Address space into the three functions: + // - Block Specific (incl. global regs) + // - Connections + // ------------------------------------------------ + + reg [15:0] req_addr = 16'h0; + reg [31:0] req_data = 32'h0; + reg blk_req_wr = 1'b0; + reg blk_req_rd = 1'b0; + reg blk_resp_ack = 1'b0; + reg [31:0] blk_resp_data = 32'h0; + reg con_req_wr = 1'b0; + reg con_req_rd = 1'b0; + reg con_resp_ack = 1'b0; + reg [31:0] con_resp_data = 32'h0; + + // Shortcuts + wire blk_addr_space = (ctrlport_req_addr[19:16] == 4'd0); + wire con_addr_space = (ctrlport_req_addr[19:16] == 4'd1); + + // ControlPort MUX + always @(posedge core_ctrl_clk) begin + // Write strobe + blk_req_wr <= ctrlport_req_wr & blk_addr_space; + con_req_wr <= ctrlport_req_wr & con_addr_space; + // Read strobe + blk_req_rd <= ctrlport_req_rd & blk_addr_space; + con_req_rd <= ctrlport_req_rd & con_addr_space; + // Address and Data (shared) + req_addr <= ctrlport_req_addr[15:0]; + req_data <= ctrlport_req_data; + // Response + ctrlport_resp_ack <= blk_resp_ack | con_resp_ack; + if (blk_resp_ack) + ctrlport_resp_data <= blk_resp_data; + else + ctrlport_resp_data <= con_resp_data; + end + + // ----------------------------------- + // Block Address Space + // ----------------------------------- + + // Arrange the backend block wires into a 2-d array where the + // outer index represents the slot number and the inner index represents + // a register index for that slot. We have 512 bits of read/write + // data which translates to 16 32-bit registers per slot. The first slot + // belongs to this endpoint, the next N slots map to the instantiated + // stream endpoints and the remaining slots map to block control and + // status endpoint. The slot number has a 1-to-1 mapping to the port + // number on the control crossbar. + localparam NUM_REGS_PER_SLOT = 512/32; + localparam NUM_SLOTS = 1 /*this*/ + NUM_STREAM_ENDPOINTS + NUM_BLOCKS; + localparam BLOCK_OFFSET = 1 /*this*/ + NUM_STREAM_ENDPOINTS; + + reg [31:0] config_arr_2d [0:NUM_SLOTS-1][0:NUM_REGS_PER_SLOT-1]; + wire [31:0] status_arr_2d [0:NUM_SLOTS-1][0:NUM_REGS_PER_SLOT-1]; + + genvar b, i; + generate + for (b = 0; b < NUM_BLOCKS; b=b+1) begin + for (i = 0; i < NUM_REGS_PER_SLOT; i=i+1) begin + assign rfnoc_core_config[(b*512)+(i*32) +: 32] = config_arr_2d[b+BLOCK_OFFSET][i]; + assign status_arr_2d[b+BLOCK_OFFSET][i] = rfnoc_core_status[(b*512)+(i*32) +: 32]; + end + end + endgenerate + + integer m, n; + always @(posedge core_ctrl_clk) begin + if (core_ctrl_rst) begin + blk_resp_ack <= 1'b0; + for (m = 0; m < NUM_SLOTS; m = m + 1) begin + for (n = 0; n < NUM_REGS_PER_SLOT; n = n + 1) begin + config_arr_2d[m][n] <= BEC_DEFAULT_VAL[(n*32)+:32]; + end + end + end else begin + // All transactions finish in 1 cycle + blk_resp_ack <= blk_req_wr | blk_req_rd; + // Handle register writes + if (blk_req_wr) begin + config_arr_2d[req_addr[$clog2(NUM_SLOTS)+5:6]][req_addr[5:2]] <= req_data; + end + // Handle register reads + if (blk_req_rd) begin + blk_resp_data <= status_arr_2d[req_addr[$clog2(NUM_SLOTS)+5:6]][req_addr[5:2]]; + end + end + end + + // Global Registers + localparam [3:0] REG_GLOBAL_PROTOVER = 4'd0; // Offset = 0x00 + localparam [3:0] REG_GLOBAL_PORT_CNT = 4'd1; // Offset = 0x04 + localparam [3:0] REG_GLOBAL_EDGE_CNT = 4'd2; // Offset = 0x08 + localparam [3:0] REG_GLOBAL_DEVICE_INFO = 4'd3; // Offset = 0x0C + localparam [3:0] REG_GLOBAL_ENDPOINT_CTRL_CNT = 4'd4; // Offset = 0x10 + + // Clock-crossing for device_id. + // FIFO going from core_chdr_clk domain to core_ctrl_clk. + wire device_id_fifo_ovalid; + wire [15:0] device_id_fifo_odata; + axi_fifo_2clk # ( + .WIDTH (16), + .SIZE (2) + ) device_id_fifo_i ( + .reset (1'b0), + .i_aclk (core_chdr_clk), + .i_tdata (device_id), + .i_tvalid (1'b1), + .i_tready (), + .o_aclk (core_ctrl_clk), + .o_tdata (device_id_fifo_odata), + .o_tvalid (device_id_fifo_ovalid), + .o_tready (1'b1) + ); + // Register the FIFO's output to always have valid data available. + reg [15:0] device_id_ctrl_clk = 16'h0; + always @(posedge core_ctrl_clk) begin + if (device_id_fifo_ovalid) begin + device_id_ctrl_clk <= device_id_fifo_odata; + end + end + + // Signature and protocol version + assign status_arr_2d[RFNOC_CORE_PORT_ID][REG_GLOBAL_PROTOVER] = {16'h12C6, PROTOVER[15:0]}; + + // Global port count register + localparam [0:0] STATIC_ROUTER_PRESENT = (NUM_EDGES == 12'd0) ? 1'b0 : 1'b1; + assign status_arr_2d[RFNOC_CORE_PORT_ID][REG_GLOBAL_PORT_CNT] = + {STATIC_ROUTER_PRESENT, CHDR_XBAR_PRESENT, + NUM_TRANSPORTS[9:0], NUM_BLOCKS[9:0], NUM_STREAM_ENDPOINTS[9:0]}; + // Global edge count register + assign status_arr_2d[RFNOC_CORE_PORT_ID][REG_GLOBAL_EDGE_CNT] = {20'd0, NUM_EDGES[11:0]}; + // Device information + assign status_arr_2d[RFNOC_CORE_PORT_ID][REG_GLOBAL_DEVICE_INFO] = {DEVICE_TYPE, device_id_ctrl_clk}; + // Number of stream endpoint connected to the ctrl crossbar + assign status_arr_2d[RFNOC_CORE_PORT_ID][REG_GLOBAL_ENDPOINT_CTRL_CNT] = {22'b0, NUM_ENDPOINTS_CTRL[9:0]}; + + // ----------------------------------- + // Connections Address Space + // ----------------------------------- + + // All inter-block static connections must be stored in a memory + // file which will be used to initialize a ROM that can be read + // by software for topology discovery. The format of the memory + // must be as follows: + // * Word Width: 32 bits + // * Maximum Depth: 16384 entries + // * Layout: + // - 0x000 : HEADER + // - 0x001 : EDGE_0_DEF + // - 0x002 : EDGE_1_DEF + // ... + // - 0xFFF : EDGE_4094_DEF + // + // where: + // * HEADER = {18'd0, NumEntries[13:0]} + // * EDGE_<N>_DEF = {SrcBlkIndex[9:0], SrcBlkPort[5:0], DstBlkIndex[9:0], DstBlkPort[5:0]} + // + // The BlkIndex is the port number of the block on the control crossbar amd the BlkPort is + // the index of the input or output port of the block. + + generate if (EDGE_TBL_FILE == "" || NUM_EDGES == 0) begin + // If no file is specified or if the number of edges is zero + // then just return zero for all transactions + always @(posedge core_ctrl_clk) begin + con_resp_ack <= (con_req_wr | con_req_rd); + con_resp_data <= 32'h0; + end + end else begin + // Initialize ROM from file and read it during a reg transaction + reg [31:0] edge_tbl_rom[0:NUM_EDGES]; + initial begin + $readmemh(EDGE_TBL_FILE, edge_tbl_rom, 0, NUM_EDGES); + end + always @(posedge core_ctrl_clk) begin + con_resp_ack <= (con_req_wr | con_req_rd); + con_resp_data <= edge_tbl_rom[req_addr[$clog2(NUM_EDGES+1)+1:2]]; + end + end endgenerate + +endmodule // rfnoc_core_kernel + |