aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/utils
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/utils')
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/Makefile.srcs29
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/axis_ctrlport_reg.v143
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/chdr_pad_packet.v132
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/chdr_trim_payload.v97
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/context_builder.v392
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/context_handler_sync.v110
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/context_parser.v230
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_clk_cross.v167
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_combiner.v222
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder.v151
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder_param.v169
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_ro.v181
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_rw.v247
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_resp_combine.v70
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_splitter.v114
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_terminator.v50
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_timer.v122
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/ctrlport_to_settings_bus.v241
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/noc_shell_generic_ctrlport_pyld_chdr.v273
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/timekeeper.v279
20 files changed, 3419 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs b/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs
new file mode 100644
index 000000000..c8fb9648f
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/Makefile.srcs
@@ -0,0 +1,29 @@
+#
+# Copyright 2018 Ettus Research, A National Instruments Company
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+
+##################################################
+# RFNoC Utility Sources
+##################################################
+RFNOC_UTIL_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/utils/, \
+chdr_trim_payload.v \
+chdr_pad_packet.v \
+context_handler_sync.v \
+context_builder.v \
+context_parser.v \
+ctrlport_timer.v \
+ctrlport_combiner.v \
+ctrlport_decoder.v \
+ctrlport_decoder_param.v \
+ctrlport_splitter.v \
+ctrlport_resp_combine.v \
+ctrlport_clk_cross.v \
+ctrlport_reg_rw.v \
+ctrlport_reg_ro.v \
+ctrlport_to_settings_bus.v \
+noc_shell_generic_ctrlport_pyld_chdr.v \
+timekeeper.v \
+ctrlport_terminator.v \
+))
diff --git a/fpga/usrp3/lib/rfnoc/utils/axis_ctrlport_reg.v b/fpga/usrp3/lib/rfnoc/utils/axis_ctrlport_reg.v
new file mode 100644
index 000000000..52a372a62
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/axis_ctrlport_reg.v
@@ -0,0 +1,143 @@
+//
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: axis_ctrlport_reg
+//
+// Description:
+//
+// Converts control port writes to an AXI-stream data stream. Flow control is
+// handled by pushing back on the ctrlport interface (i.e., by not
+// acknowledging ctrlport writes until the AXI-stream data is accepted).
+//
+// Parameters:
+//
+// ADDR : Writes to this address will makeup the payload of the
+// packet.
+//
+// USE_ADDR_LAST : Indicate if we the ADDR_LAST register generated. Set to 1
+// if TLAST is needed.
+//
+// ADDR_LAST : A write to this address will complete the packet (output
+// the last word with TLAST asserted).
+//
+// DWIDTH : Width of the AXI-stream data bus
+//
+// USE_FIFO : Indicate if you want a FIFO to be inserted before the output.
+//
+// FIFm_SIZE : The FIFO depth will be 2^FIFm_SIZE
+//
+// DATA_AT_RESET : Value of TDATA at reset.
+//
+// VALID_AT_RESET : State of TVALID at reset.
+//
+// LAST_AT_RESET : State of TLAST at reset.
+//
+
+module axis_ctrlport_reg #(
+ parameter ADDR = 0,
+ parameter USE_ADDR_LAST = 0,
+ parameter ADDR_LAST = ADDR+1,
+ parameter DWIDTH = 32,
+ parameter USE_FIFO = 0,
+ parameter FIFm_SIZE = 5,
+ parameter DATA_AT_RESET = 0,
+ parameter VALID_AT_RESET = 0,
+ parameter LAST_AT_RESET = 0
+) (
+ input clk,
+ input reset,
+
+ //---------------------------------------------------------------------------
+ // Control Port
+ //---------------------------------------------------------------------------
+
+ // Control Port Slave (Request)
+ input wire s_ctrlport_req_wr,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+
+ // Control Port Slave (Response)
+ output reg s_ctrlport_resp_ack,
+
+ //---------------------------------------------------------------------------
+ // AXI-Stream Master
+ //---------------------------------------------------------------------------
+
+ // AXI-Stream Output
+ output [DWIDTH-1:0] m_tdata,
+ output m_tlast,
+ output m_tvalid,
+ input m_tready
+);
+
+ reg [DWIDTH-1:0] m_tdata_int = DATA_AT_RESET;
+ reg m_tlast_int = VALID_AT_RESET;
+ reg m_tvalid_int = LAST_AT_RESET;
+ wire m_tready_int;
+
+
+ //---------------------------------------------------------------------------
+ // CtrlPort to AXI-Stream Logic
+ //---------------------------------------------------------------------------
+
+ always @(posedge clk) begin
+ if (reset) begin
+ m_tdata_int <= DATA_AT_RESET;
+ m_tvalid_int <= VALID_AT_RESET;
+ m_tlast_int <= LAST_AT_RESET;
+ s_ctrlport_resp_ack <= 1'b0;
+ end else begin
+ if (m_tvalid_int & m_tready_int) begin
+ s_ctrlport_resp_ack <= 1'b1;
+ m_tvalid_int <= 1'b0;
+ m_tlast_int <= 1'b0;
+ end else begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end
+
+ if (s_ctrlport_req_wr) begin
+ if (s_ctrlport_req_addr == ADDR) begin
+ m_tdata_int <= s_ctrlport_req_data;
+ m_tvalid_int <= 1'b1;
+ m_tlast_int <= 1'b0;
+ end else if (USE_ADDR_LAST && ADDR_LAST == s_ctrlport_req_addr) begin
+ m_tdata_int <= s_ctrlport_req_data;
+ m_tvalid_int <= 1'b1;
+ m_tlast_int <= 1'b1;
+ end
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Output FIFO
+ //---------------------------------------------------------------------------
+
+ if (USE_FIFO) begin : gen_fifo
+ axi_fifo #(
+ .DWIDTH (DWIDTH+1),
+ .SIZE (FIFm_SIZE)
+ ) axi_fifo (
+ .clk (clk),
+ .reset (reset),
+ .clear (1'b0),
+ .i_tdata ({m_tlast_int, m_tdata_int}),
+ .i_tvalid (m_tvalid_int),
+ .i_tready (m_tready_int),
+ .o_tdata ({m_tlast, m_tdata}),
+ .o_tvalid (m_tvalid),
+ .o_tready (m_tready),
+ .space (),
+ .occupied ()
+ );
+ end else begin : nm_gen_fifo
+ assign m_tdata = m_tdata_int;
+ assign m_tlast = m_tlast_int;
+ assign m_tvalid = m_tvalid_int;
+ assign m_tready_int = m_tready;
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/chdr_pad_packet.v b/fpga/usrp3/lib/rfnoc/utils/chdr_pad_packet.v
new file mode 100644
index 000000000..14d63fe74
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/chdr_pad_packet.v
@@ -0,0 +1,132 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: chdr_pad_packet
+// Description:
+// This module pads extra data on the AXI-Stream bus
+// to the requested packet size. This module is for
+// creating len-sized packets, for DMA engines that
+// do not support partial transfers.
+//
+// Parameters:
+// - CHDR_W: Width of the CHDR tdata bus in bits
+//
+// Signals:
+// - s_axis_* : Input AXI-Stream CHDR bus
+// - m_axis_* : Output AXI-Stream CHDR bus
+// - len : Requested number of CHDR_W lines in the packet (must be > 1)
+
+`default_nettype none
+module chdr_pad_packet #(
+ parameter CHDR_W = 256
+)(
+ input wire clk,
+ input wire rst,
+ input wire [15:0] len,
+ input wire [CHDR_W-1:0] s_axis_tdata,
+ input wire s_axis_tlast,
+ input wire s_axis_tvalid,
+ output reg s_axis_tready,
+ output wire [CHDR_W-1:0] m_axis_tdata,
+ output reg m_axis_tlast,
+ output reg m_axis_tvalid,
+ input wire m_axis_tready
+);
+
+ localparam [1:0] ST_HEADER = 2'd0;
+ localparam [1:0] ST_BODY = 2'd1;
+ localparam [1:0] ST_PAD = 2'd2;
+ localparam [1:0] ST_DROP = 2'd3;
+
+ reg [1:0] state;
+ reg [15:0] lines_left;
+
+ always @(posedge clk) begin
+ if (rst || (len <= 16'd1)) begin
+ state <= ST_HEADER;
+ end else begin
+ case(state)
+ ST_HEADER: begin
+ lines_left <= len - 16'd1;
+ if (s_axis_tvalid && m_axis_tready) begin
+ if (!s_axis_tlast) begin
+ // Packet is more than one line and length not reached
+ state <= ST_BODY;
+ end else begin
+ // Packet is only one line and length not reached
+ state <= ST_PAD;
+ end
+ end
+ end
+ ST_BODY: begin
+ if (s_axis_tvalid && m_axis_tready) begin
+ lines_left <= lines_left - 16'd1;
+ if (s_axis_tlast && (lines_left == 16'd1)) begin
+ // End of input and reached length
+ state <= ST_HEADER;
+ end else if (s_axis_tlast && (lines_left != 16'd1)) begin
+ // End of input, but length not reached
+ state <= ST_PAD;
+ end else if (!s_axis_tlast && (lines_left == 16'd1)) begin
+ // Reached length, but input continues...
+ state <= ST_DROP;
+ end
+ end
+ end
+ ST_PAD: begin
+ if (m_axis_tready) begin
+ lines_left <= lines_left - 16'd1;
+ if (lines_left == 16'd1) begin
+ state <= ST_HEADER;
+ end
+ end
+ end
+ ST_DROP: begin
+ if (s_axis_tvalid && s_axis_tlast) begin
+ state <= ST_HEADER;
+ end
+ end
+ default: begin
+ // We should never get here
+ state <= ST_HEADER;
+ end
+ endcase
+ end
+ end
+
+ assign m_axis_tdata = s_axis_tdata;
+
+ always @(*) begin
+ case(state)
+ ST_HEADER: begin
+ if (len <= 16'd1) begin
+ s_axis_tready <= 1'b0;
+ m_axis_tvalid <= 1'b0;
+ end else begin
+ s_axis_tready <= m_axis_tready;
+ m_axis_tvalid <= s_axis_tvalid;
+ end
+ m_axis_tlast <= 1'b0;
+ end
+ ST_BODY: begin
+ s_axis_tready <= m_axis_tready;
+ m_axis_tvalid <= s_axis_tvalid;
+ m_axis_tlast <= (lines_left == 16'd1);
+ end
+ ST_PAD: begin
+ s_axis_tready <= 1'b0;
+ m_axis_tvalid <= 1'b1;
+ m_axis_tlast <= (lines_left == 16'd1);
+ end
+ ST_DROP: begin
+ s_axis_tready <= 1'b1;
+ m_axis_tvalid <= 1'b0;
+ m_axis_tlast <= 1'b0;
+ end
+ endcase
+ end
+
+endmodule // chdr_pad_packet
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/utils/chdr_trim_payload.v b/fpga/usrp3/lib/rfnoc/utils/chdr_trim_payload.v
new file mode 100644
index 000000000..ffeec1437
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/chdr_trim_payload.v
@@ -0,0 +1,97 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: chdr_trim_payload
+// Description:
+// This module trims any extra data on the AXI-Stream
+// bus to the CHDR payload size. This ensures that the
+// line with tlast is the actual last line of the packet
+//
+// Parameters:
+// - CHDR_W: Width of the CHDR tdata bus in bits
+// - USER_W: Width of the tuser bus in bits
+//
+// Signals:
+// - s_axis_* : Input AXI-Stream CHDR bus
+// - m_axis_* : Output AXI-Stream CHDR bus
+
+module chdr_trim_payload #(
+ parameter CHDR_W = 256,
+ parameter USER_W = 16
+)(
+ input wire clk,
+ input wire rst,
+ input wire [CHDR_W-1:0] s_axis_tdata,
+ input wire [USER_W-1:0] s_axis_tuser,
+ input wire s_axis_tlast,
+ input wire s_axis_tvalid,
+ output wire s_axis_tready,
+ output wire [CHDR_W-1:0] m_axis_tdata,
+ output wire [USER_W-1:0] m_axis_tuser,
+ output wire m_axis_tlast,
+ output wire m_axis_tvalid,
+ input wire m_axis_tready
+);
+
+ `include "../core/rfnoc_chdr_utils.vh"
+
+ localparam LOG2_CHDR_W_BYTES = $clog2(CHDR_W/8);
+
+ localparam [1:0] ST_HEADER = 2'd0;
+ localparam [1:0] ST_BODY = 2'd1;
+ localparam [1:0] ST_DUMP = 2'd2;
+
+ reg [1:0] state;
+ reg [15:0] lines_left;
+
+ wire [15:0] pkt_length = chdr_get_length(s_axis_tdata[63:0]);
+ wire [15:0] lines_in_pkt = pkt_length[15:LOG2_CHDR_W_BYTES] + (|pkt_length[LOG2_CHDR_W_BYTES-1:0]);
+ wire last_line = (lines_left == 16'd0);
+
+ always @(posedge clk) begin
+ if (rst) begin
+ state <= ST_HEADER;
+ lines_left <= 16'd0;
+ end else if(s_axis_tvalid & s_axis_tready) begin
+ case(state)
+ ST_HEADER: begin
+ if ((lines_in_pkt == 16'd1) && !s_axis_tlast) begin
+ // First line is valid, dump rest
+ state <= ST_DUMP;
+ end else begin
+ lines_left <= lines_in_pkt - 16'd2;
+ state <= ST_BODY;
+ end
+ end
+ ST_BODY: begin
+ if (last_line && !s_axis_tlast) begin
+ state <= ST_DUMP;
+ end else if (s_axis_tlast) begin
+ state <= ST_HEADER;
+ end else begin
+ lines_left <= lines_left - 16'd1;
+ end
+ end
+ ST_DUMP: begin
+ if (s_axis_tlast)
+ state <= ST_HEADER;
+ end
+ default: begin
+ // We should never get here
+ state <= ST_HEADER;
+ end
+ endcase
+ end
+ end
+
+ assign m_axis_tdata = s_axis_tdata;
+ assign m_axis_tuser = s_axis_tuser;
+ assign m_axis_tlast = s_axis_tlast ||
+ ((state == ST_HEADER) && (lines_in_pkt == 16'd1)) ||
+ ((state == ST_BODY) && last_line);
+ assign m_axis_tvalid = s_axis_tvalid && (state != ST_DUMP);
+ assign s_axis_tready = m_axis_tready || (state == ST_DUMP);
+
+endmodule // chdr_trim_payload
diff --git a/fpga/usrp3/lib/rfnoc/utils/context_builder.v b/fpga/usrp3/lib/rfnoc/utils/context_builder.v
new file mode 100644
index 000000000..83171e831
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/context_builder.v
@@ -0,0 +1,392 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: context_builder
+//
+// Description:
+//
+// This module builds the payload and context data streams necessary for RFnoC
+// communication through an AXI-Stream Raw Data (Simple Interface). It takes as
+// input an AXI-Stream data bus and sideband buses containing the timestamp and
+// packet flags.
+//
+// For each AXI-Stream raw data packet that is input, the same data packet will
+// be output in the payload stream along with the context stream that's
+// necessary to create a CHDR packet for this data packet.
+//
+// The timestamp and flags must be input coincident with the AXI-Stream data
+// input. The timestamp and flag inputs will be sampled coincident with the
+// last word of data in the packet (i.e., when tlast is asserted).
+//
+// In order to determine the length of the packet, the entire packet is
+// buffered before the header in the context stream is generated. Therefore,
+// the internal FIFO size (configured by MTU) must be large enough to buffer
+// the maximum packet size.
+//
+// The maximum number of packets that can be simultaneously buffered in this
+// block is limited by INFO_FIFO_SIZE, where the maximum number of packets is
+// 2**INFO_FIFO_SIZE. This must be large enough to handle the expected worse
+// case, or data flow will stall.
+//
+// Parameters:
+//
+// CHDR_W : Width of the CHDR interface (width of context words)
+// ITEM_W : Number of samples/items per data word
+// NIPC : Number of samples/items per clock cycle
+// MTU : Log2 of maximum transfer unit (maximum packet size) in CHDR_W sized words.
+// INFO_FIFO_SIZE : Size of the internal packet info FIFO is 2**INFO_FIFO_SIZE
+//
+
+module context_builder #(
+ parameter CHDR_W = 64,
+ parameter ITEM_W = 32,
+ parameter NIPC = 2,
+ parameter MTU = 10,
+ parameter INFO_FIFO_SIZE = 5
+) (
+ input axis_data_clk,
+ input axis_data_rst,
+
+ // 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,
+ // Sideband info (sampled on the first cycle of the packet)
+ input wire [ 63:0] s_axis_ttimestamp,
+ input wire s_axis_thas_time,
+ input wire s_axis_teov,
+ input wire s_axis_teob,
+
+ // Data stream out (AXI-Stream Payload)
+ 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,
+
+ // Data stream out (AXI-Stream Context)
+ output reg [CHDR_W-1:0] m_axis_context_tdata,
+ output reg [ 3:0] m_axis_context_tuser,
+ output reg m_axis_context_tlast,
+ output reg m_axis_context_tvalid = 1'b0,
+ input wire m_axis_context_tready
+);
+ `include "../core/rfnoc_chdr_utils.vh"
+
+
+ reg packet_info_fifo_full;
+
+
+ //---------------------------------------------------------------------------
+ // Data FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This FIFO buffers packet data while we calculate each packet's length.
+ //
+ //---------------------------------------------------------------------------
+
+ wire s_axis_tvalid_df;
+ wire s_axis_tready_df;
+
+ // Compute MTU (maximum packet) size in data words from the CHDR word MTU.
+ localparam DATA_FIFO_SIZE = MTU + $clog2(CHDR_W) - $clog2(ITEM_W*NIPC);
+
+ axi_fifo #(
+ .WIDTH (NIPC + 1 + ITEM_W*NIPC),
+ .SIZE (DATA_FIFO_SIZE)
+ ) data_fifo (
+ .clk (axis_data_clk),
+ .reset (axis_data_rst),
+ .clear (1'b0),
+ .i_tdata ({s_axis_tkeep, s_axis_tlast, s_axis_tdata}),
+ .i_tvalid (s_axis_tvalid_df),
+ .i_tready (s_axis_tready_df),
+ .o_tdata ({m_axis_payload_tkeep, m_axis_payload_tlast, m_axis_payload_tdata}),
+ .o_tvalid (m_axis_payload_tvalid),
+ .o_tready (m_axis_payload_tready),
+ .space (),
+ .occupied ()
+ );
+
+ // To prevent the packet info FIFO from overflowing, we block the input of
+ // new packets to the data FIFO whenever the packet info FIFO fills up.
+ assign s_axis_tready = s_axis_tready_df & ~packet_info_fifo_full;
+ assign s_axis_tvalid_df = s_axis_tvalid & ~packet_info_fifo_full;
+
+
+ //---------------------------------------------------------------------------
+ // Timestamp and Flags Capture
+ //---------------------------------------------------------------------------
+ //
+ // The timestamp and flags that we use for each packet is that of the last
+ // data word. This maintains compatibility with how tuser was used on old
+ // RFnoC. Here, we capture this information at the start of the packet. At
+ // the end of the packet, when the length is known, this value will be
+ // inserted into the packet info FIFO.
+ //
+ //---------------------------------------------------------------------------
+
+ 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 Counter
+ //---------------------------------------------------------------------------
+ //
+ // Here We track the state of the incoming packet to determine its length.
+ //
+ //---------------------------------------------------------------------------
+
+ reg [15:0] packet_length, length_count;
+ reg packet_length_valid;
+
+ always @(posedge axis_data_clk) begin : length_counter
+ if (axis_data_rst) begin
+ length_count <= 0;
+ packet_length <= 0;
+ packet_length_valid <= 1'b0;
+ end else begin : 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
+ packet_length_valid <= 1'b0;
+ if (s_axis_tvalid & s_axis_tready) begin
+ length_count <= length_count + num_bytes;
+
+ if (s_axis_tlast) begin
+ length_count <= 0;
+ packet_length <= length_count + num_bytes;
+ packet_length_valid <= 1'b1;
+ end
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Packet Info FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This FIFO stores the packet info (length, timestamp, flags) for each fully
+ // received packet. Due to AXI-Stream flow control, we may end up with
+ // multiple packets being buffered in the data_fifo. The packet_info_fifo
+ // here stores each packet's info until the packet is ready to go out.
+ //
+ //---------------------------------------------------------------------------
+
+ wire [63:0] next_packet_timestamp;
+ wire next_packet_has_time;
+ wire next_packet_eob;
+ wire next_packet_eov;
+ wire [15:0] next_packet_length;
+ wire [15:0] packet_info_space;
+ wire packet_info_valid;
+ reg packet_info_ready = 1'b0;
+
+ axi_fifo #(
+ .WIDTH (3 + 64 + 16),
+ .SIZE (INFO_FIFO_SIZE)
+ ) packet_info_fifo (
+ .clk (axis_data_clk),
+ .reset (axis_data_rst),
+ .clear (1'b0),
+ .i_tdata ({packet_eov,
+ packet_eob,
+ packet_has_time,
+ packet_timestamp,
+ packet_length}),
+ .i_tvalid (packet_length_valid),
+ .i_tready (),
+ .o_tdata ({next_packet_eov,
+ next_packet_eob,
+ next_packet_has_time,
+ next_packet_timestamp,
+ next_packet_length}),
+ .o_tvalid (packet_info_valid),
+ .o_tready (packet_info_ready),
+ .space (packet_info_space),
+ .occupied ()
+ );
+
+
+ // Create a register to indicate when the FIFO is (almost) full. We leave
+ // some space so that we can accept a new packet during the delay before data
+ // transfer gets blocked.
+ always @(posedge axis_data_clk) begin
+ if (axis_data_rst) begin
+ packet_info_fifo_full <= 1'b0;
+ end else begin
+ if (packet_info_space < 4) begin
+ packet_info_fifo_full <= 1'b1;
+ end else begin
+ packet_info_fifo_full <= 1'b0;
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Context State Machine
+ //---------------------------------------------------------------------------
+ //
+ // This state machine controls generation of the context packets (containing
+ // the header and timestamp) that are output on m_axis_context, which will be
+ // needed to create the CHDR packet.
+ //
+ //---------------------------------------------------------------------------
+
+ localparam ST_IDLE = 0;
+ localparam ST_HEADER = 1;
+ localparam ST_TIMESTAMP = 2;
+
+ reg [ 1:0] state = ST_IDLE; // Current context FSM state
+ reg [15:0] seq_num = 0; // CHDR sequence number
+
+ reg [15:0] chdr_length;
+ reg [ 2:0] chdr_pkt_type;
+ reg [63:0] chdr_header;
+
+
+ always @(*) begin : calc_chdr_header
+ // Calculate byte length of the CHDR packet by adding the header and
+ // timestamp length to the length of the payload.
+ if (CHDR_W == 64) begin
+ // If CHDR_W is 64-bit, timestamp is in a separate word
+ if (next_packet_has_time) begin
+ chdr_length = next_packet_length + 16; // Add two 64-bit CHDR words
+ end else begin
+ chdr_length = next_packet_length + 8; // Add one 64-bit CHDR word
+ end
+ end else begin
+ // If CHDR_W is 128-bit or larger, timestamp is in the same word as the header
+ chdr_length = next_packet_length + CHDR_W/8; // Add one CHDR word
+ end
+
+ // Determine the packet type
+ if (next_packet_has_time) begin
+ chdr_pkt_type = CHDR_PKT_TYPE_DATA_TS;
+ end else begin
+ chdr_pkt_type = CHDR_PKT_TYPE_DATA;
+ end
+
+ // Build up header
+ chdr_header = chdr_build_header(
+ 6'b0, // vc
+ next_packet_eob, // eob
+ next_packet_eov, // eov
+ chdr_pkt_type, // pkt_type
+ 0, // num_mdata
+ seq_num, // seq_num
+ chdr_length, // length of CHDR packet in bytes
+ 0 // dst_epid
+ );
+ end
+
+
+ always @(posedge axis_data_clk) begin
+ if (axis_data_rst) begin
+ state <= ST_IDLE;
+ seq_num <= 'd0;
+ packet_info_ready <= 1'b0;
+ m_axis_context_tvalid <= 1'b0;
+ end else begin
+ packet_info_ready <= 1'b0;
+
+ if (CHDR_W == 64) begin : gen_ctx_fsm_64
+ // For 64-bit CHDR_W, we require two words, one for the header and one
+ // for the timestamp.
+ case (state)
+ ST_IDLE: begin
+ m_axis_context_tdata <= chdr_header;
+ m_axis_context_tuser <= CONTEXT_FIELD_HDR;
+ m_axis_context_tlast <= !next_packet_has_time;
+ if (packet_info_valid && !packet_info_ready) begin
+ m_axis_context_tvalid <= 1'b1;
+ seq_num <= seq_num + 1;
+ state <= ST_HEADER;
+ end
+ end
+
+ ST_HEADER : begin
+ // Wait for header to be accepted
+ if (m_axis_context_tready) begin
+ packet_info_ready <= 1'b1;
+ m_axis_context_tdata <= next_packet_timestamp;
+ if (next_packet_has_time) begin
+ m_axis_context_tlast <= 1'b1;
+ m_axis_context_tuser <= CONTEXT_FIELD_TS;
+ state <= ST_TIMESTAMP;
+ end else begin
+ m_axis_context_tlast <= 1'b0;
+ m_axis_context_tvalid <= 1'b0;
+ state <= ST_IDLE;
+ end
+ end
+ end
+
+ ST_TIMESTAMP : begin
+ // Wait for timestamp to be accepted
+ if (m_axis_context_tready) begin
+ m_axis_context_tvalid <= 1'b0;
+ state <= ST_IDLE;
+ end
+ end
+
+ default: state <= ST_IDLE;
+ endcase
+
+ end else begin : gen_ctx_fsm_128
+ // For 128-bit and larger CHDR_W, we need the header and timestamp in
+ // the same word.
+ case (state)
+ ST_IDLE: begin
+ m_axis_context_tdata <= { next_packet_timestamp, chdr_header };
+ m_axis_context_tuser <= next_packet_has_time ? CONTEXT_FIELD_HDR_TS :
+ CONTEXT_FIELD_HDR;
+ m_axis_context_tlast <= 1'b1;
+ if (packet_info_valid) begin
+ m_axis_context_tvalid <= 1'b1;
+ seq_num <= seq_num + 1;
+ packet_info_ready <= 1'b1;
+ state <= ST_HEADER;
+ end
+ end
+
+ ST_HEADER : begin
+ // Wait for header to be accepted
+ if (m_axis_context_tready) begin
+ m_axis_context_tvalid <= 1'b0;
+ state <= ST_IDLE;
+ end
+ end
+
+ default : state <= ST_IDLE;
+ endcase
+
+ end
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/context_handler_sync.v b/fpga/usrp3/lib/rfnoc/utils/context_handler_sync.v
new file mode 100644
index 000000000..c7f899ee9
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/context_handler_sync.v
@@ -0,0 +1,110 @@
+//
+// Copyright 2018-2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: context_handler_sync
+// Description:
+//
+// Parameters:
+// - CHDR_W: Width of the input CHDR bus in bits
+//
+// Signals:
+//
+
+module context_handler_sync #(
+ parameter CHDR_W = 256,
+ parameter ITEM_W = 32,
+ parameter NIPC = 2
+)(
+ // Clock and reset
+ input wire clk,
+ input wire rst,
+ // Context stream in (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,
+ // 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,
+ // Input payload stream monitor
+ input wire [NIPC-1:0] in_payload_tkeep,
+ input wire in_payload_tlast,
+ input wire in_payload_tvalid,
+ input wire in_payload_tready,
+ // Output payload stream monitor
+ input wire [NIPC-1:0] out_payload_tkeep,
+ input wire out_payload_tlast,
+ input wire out_payload_tvalid,
+ input wire out_payload_tready,
+ // Status
+ output reg length_err_stb,
+ output reg seq_err_stb
+);
+
+ `include "../core/rfnoc_chdr_utils.vh"
+
+ // Thermometer to binary decoder
+ // 4'b0000 => 3'd0
+ // 4'b0001 => 3'd1
+ // 4'b0011 => 3'd2
+ // 4'b0111 => 3'd3
+ // 4'b1111 => 3'd4
+ function [$clog2(NIPC):0] thermo2bin(input [NIPC-1:0] thermo);
+ reg [NIPC:0] onehot;
+ integer i;
+ begin
+ onehot = thermo + 1;
+ thermo2bin = 0;
+ for (i = 0; i <= NIPC; i=i+1)
+ if (onehot[i])
+ thermo2bin = thermo2bin | i;
+ end
+ endfunction
+
+ axi_fifo #(.WIDTH(CHDR_W+4+1), .SIZE(1)) ctxt_pipe_i (
+ .clk(clk), .reset(rst), .clear(1'b0),
+ .i_tdata({s_axis_context_tlast, s_axis_context_tuser, s_axis_context_tdata}),
+ .i_tvalid(s_axis_context_tvalid), .i_tready(s_axis_context_tready),
+ .o_tdata({m_axis_context_tlast, m_axis_context_tuser, m_axis_context_tdata}),
+ .o_tvalid(m_axis_context_tvalid), .o_tready(m_axis_context_tready),
+ .space(), .occupied()
+ );
+
+ wire is_ctxt_hdr = s_axis_context_tvalid && s_axis_context_tready &&
+ (s_axis_context_tuser == CONTEXT_FIELD_HDR ||
+ s_axis_context_tuser == CONTEXT_FIELD_HDR_TS);
+
+ reg [15:0] exp_pkt_len = 16'd0;
+ reg [15:0] exp_seq_num = 16'd0;
+ reg check_seq_num = 1'b0;
+ always @(posedge clk) begin
+ if (rst) begin
+ exp_pkt_len <= 16'd0;
+ check_seq_num <= 1'b0;
+ end else if (is_ctxt_hdr) begin
+ check_seq_num <= 1'b1;
+ exp_pkt_len <= chdr_get_length(s_axis_context_tdata[63:0]);
+ exp_seq_num <= chdr_get_seq_num(s_axis_context_tdata[63:0]) + 16'd1;
+ end
+ seq_err_stb <= is_ctxt_hdr && check_seq_num &&
+ (exp_seq_num != chdr_get_seq_num(s_axis_context_tdata[63:0]));
+ end
+
+ reg [15:0] pyld_pkt_len = 16'd0;
+ always @(posedge clk) begin
+ if (rst) begin
+ pyld_pkt_len <= 16'd0;
+ end else if (in_payload_tvalid && in_payload_tready) begin
+ pyld_pkt_len <= in_payload_tlast ? 16'd0 : (pyld_pkt_len + ((ITEM_W*NIPC)/8));
+ end
+ length_err_stb <= in_payload_tvalid && in_payload_tready && in_payload_tlast &&
+ (pyld_pkt_len + (thermo2bin(in_payload_tkeep)*(ITEM_W/8)) != exp_pkt_len);
+ end
+
+endmodule // context_handler_sync
diff --git a/fpga/usrp3/lib/rfnoc/utils/context_parser.v b/fpga/usrp3/lib/rfnoc/utils/context_parser.v
new file mode 100644
index 000000000..2d0759af7
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/context_parser.v
@@ -0,0 +1,230 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: context_parser
+//
+// Description:
+//
+// This module extracts the context information from the AXI-Stream Raw Data
+// (Simple Interface) in RFNoC and outputs it as sideband information for an
+// AXI-Stream data bus. This includes the timestamp, if present, and packet
+// flags (EOB, EOV).
+//
+// For each payload and context packet that is input, one data packet will be
+// output along with the sideband data.
+//
+// Parameters:
+//
+// CHDR_W : Width of the CHDR interface (width of context words)
+// ITEM_W : Width of each item/sample
+// NIPC : Number of items/samples per clock cycle
+//
+
+module context_parser #(
+ parameter CHDR_W = 64,
+ parameter ITEM_W = 32,
+ parameter NIPC = 2
+) (
+ input axis_data_clk,
+ input axis_data_rst,
+
+ // AXI-Stream Raw Data (Simple Interface) input
+ 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,
+ //
+ input wire [ CHDR_W-1:0] s_axis_context_tdata,
+ input wire s_axis_context_tlast,
+ input wire s_axis_context_tvalid,
+ output wire s_axis_context_tready,
+
+ // 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,
+ // Sideband information
+ output wire [ 63:0] m_axis_ttimestamp,
+ output wire m_axis_thas_time,
+ output wire [ 15:0] m_axis_tlength, // Payload length, in bytes
+ output wire m_axis_teov,
+ output wire m_axis_teob
+);
+
+ `include "../core/rfnoc_chdr_utils.vh"
+
+
+ // Sideband-FIFO signals
+ reg sideband_i_tvalid = 1'b0;
+ wire sideband_i_tready;
+ wire sideband_o_tvalid;
+ wire sideband_o_tready;
+
+ // Sideband data for next packet
+ reg [63:0] timestamp;
+ reg has_time;
+ reg [15:0] length;
+ reg eov;
+ reg eob;
+
+
+ //---------------------------------------------------------------------------
+ // Context State Machine
+ //---------------------------------------------------------------------------
+ //
+ // This state machine parses the context data so that it can be output as
+ // sideband information on the AXI-Stream output.
+ //
+ // This state machine assumes that the context packet is always properly
+ // formed (i.e., it doesn't explicitly check for and drop malformed packets).
+ //
+ //---------------------------------------------------------------------------
+
+ localparam ST_HEADER = 0;
+ localparam ST_TIMESTAMP = 1;
+ localparam ST_METADATA = 2;
+
+ reg [1:0] state = ST_HEADER;
+
+ always @(posedge axis_data_clk) begin
+ if (axis_data_rst) begin
+ state <= ST_HEADER;
+ sideband_i_tvalid <= 1'b0;
+ end else begin
+ sideband_i_tvalid <= 1'b0;
+
+ case(state)
+ ST_HEADER: begin
+ // Grab header information
+ eov <= chdr_get_eov(s_axis_context_tdata[63:0]);
+ eob <= chdr_get_eob(s_axis_context_tdata[63:0]);
+ has_time <= chdr_get_has_time(s_axis_context_tdata[63:0]);
+ length <= chdr_calc_payload_length(CHDR_W, s_axis_context_tdata[63:0]);
+
+ if (s_axis_context_tvalid && s_axis_context_tready) begin
+ if (CHDR_W > 64) begin
+ // When CHDR_W > 64, the timestamp is a part of the header word
+ if (chdr_get_has_time(s_axis_context_tdata[63:0])) begin
+ timestamp <= s_axis_context_tdata[127:64];
+ end
+
+ // Load the sideband data into the FIFO
+ sideband_i_tvalid <= 1'b1;
+
+ // Check if there's more context packet to wait for
+ if (!s_axis_context_tlast) begin
+ state <= ST_METADATA;
+ end
+
+ end else begin
+ // When CHDR_W == 64, the timestamp comes after the header word
+ if (s_axis_context_tlast) begin
+ // Context packet is ending. Load the sideband data into FIFO.
+ sideband_i_tvalid <= 1'b1;
+ end else begin
+ // More context packet to come
+ if (chdr_get_has_time(s_axis_context_tdata[63:0])) begin
+ state <= ST_TIMESTAMP;
+ end else begin
+ // Load the sideband data into the FIFO
+ sideband_i_tvalid <= 1'b1;
+ state <= ST_METADATA;
+ end
+ end
+ end
+ end
+ end
+
+ ST_TIMESTAMP: begin
+ // This state only applies when CHDR_W == 64
+ if (s_axis_context_tvalid && s_axis_context_tready) begin
+ timestamp <= s_axis_context_tdata;
+
+ // Load the sideband data into the FIFO
+ sideband_i_tvalid <= 1'b1;
+
+ // Check if there's more context packet to wait for
+ if (s_axis_context_tlast) begin
+ state <= ST_HEADER;
+ end else begin
+ state <= ST_METADATA;
+ end
+ end
+ end
+
+ ST_METADATA: begin
+ // This module doesn't handle metadata currently, so just ignore it
+ if (s_axis_context_tvalid && s_axis_context_tready) begin
+ if (s_axis_context_tlast) begin
+ state <= ST_HEADER;
+ end
+ end
+ end
+
+ default: state <= ST_HEADER;
+ endcase
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Sideband Data FIFO
+ //---------------------------------------------------------------------------
+ //
+ // Here we buffer the sideband information into a FIFO. The information will
+ // be output coincident with the corresponding data packet.
+ //
+ //---------------------------------------------------------------------------
+
+ axi_fifo_short #(
+ .WIDTH (83)
+ ) sideband_fifo (
+ .clk (axis_data_clk),
+ .reset (axis_data_rst),
+ .clear (1'b0),
+ .i_tdata ({length, eob, eov, has_time, timestamp}),
+ .i_tvalid (sideband_i_tvalid),
+ .i_tready (sideband_i_tready),
+ .o_tdata ({m_axis_tlength, m_axis_teob, m_axis_teov,
+ m_axis_thas_time, m_axis_ttimestamp}),
+ .o_tvalid (sideband_o_tvalid),
+ .o_tready (sideband_o_tready),
+ .space (),
+ .occupied ()
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Payload Transfer Logic
+ //---------------------------------------------------------------------------
+ //
+ // Here we handle the logic for AXI-Stream flow control. The data and
+ // sideband information are treated as a single AXI-Stream bus. The sideband
+ // information is output for the duration of the packet and is popped off of
+ // the sideband FIFO at the end of each packet.
+ //
+ //---------------------------------------------------------------------------
+
+ // We can only accept context info when there's room in the sideband FIFO.
+ assign s_axis_context_tready = sideband_i_tready;
+
+ // Allow payload transfer whenever the sideband info is valid
+ assign s_axis_payload_tready = (m_axis_tready & sideband_o_tvalid);
+ assign m_axis_tvalid = (s_axis_payload_tvalid & sideband_o_tvalid);
+
+ // Pop off the sideband info at the end of each packet
+ assign sideband_o_tready = (s_axis_payload_tready &
+ s_axis_payload_tvalid &
+ s_axis_payload_tlast);
+
+ // Other AXI-Stream signals pass through untouched
+ assign m_axis_tdata = s_axis_payload_tdata;
+ assign m_axis_tkeep = s_axis_payload_tkeep;
+ assign m_axis_tlast = s_axis_payload_tlast;
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_clk_cross.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_clk_cross.v
new file mode 100644
index 000000000..6aa74c74f
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_clk_cross.v
@@ -0,0 +1,167 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_clk_cross
+//
+// Description:
+//
+// Crosses a CTRL Port request and response between two clock domains.
+//
+
+
+module ctrlport_clk_cross (
+
+ input wire rst, // Can be either clock domain, but must be glitch-free
+
+ //---------------------------------------------------------------------------
+ // Input Clock Domain (Slave Interface)
+ //---------------------------------------------------------------------------
+
+ input wire s_ctrlport_clk,
+ 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,
+ output wire s_ctrlport_resp_ack,
+ output wire [ 1:0] s_ctrlport_resp_status,
+ output wire [31:0] s_ctrlport_resp_data,
+
+ //---------------------------------------------------------------------------
+ // Output Clock Domain (Master Interface)
+ //---------------------------------------------------------------------------
+
+ input wire m_ctrlport_clk,
+ output wire m_ctrlport_req_wr,
+ output wire m_ctrlport_req_rd,
+ output wire [19:0] m_ctrlport_req_addr,
+ output wire [ 9:0] m_ctrlport_req_portid,
+ output wire [15:0] m_ctrlport_req_rem_epid,
+ output wire [ 9:0] m_ctrlport_req_rem_portid,
+ 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,
+ input wire m_ctrlport_resp_ack,
+ input wire [ 1:0] m_ctrlport_resp_status,
+ input wire [31:0] m_ctrlport_resp_data
+);
+
+ //---------------------------------------------------------------------------
+ // Slave to Master Clock Crossing (Request)
+ //---------------------------------------------------------------------------
+
+ localparam REQ_W =
+ 1 + // ctrlport_req_wr
+ 1 + // ctrlport_req_rd
+ 20 + // ctrlport_req_addr
+ 10 + // ctrlport_req_portid
+ 16 + // ctrlport_req_rem_epid
+ 10 + // ctrlport_req_rem_portid
+ 32 + // ctrlport_req_data
+ 4 + // ctrlport_req_byte_en
+ 1 + // ctrlport_req_has_time
+ 64; // ctrlport_req_time
+
+ wire [ REQ_W-1:0] s_req_flat;
+ wire [ REQ_W-1:0] m_req_flat;
+ wire m_req_flat_valid;
+ wire m_ctrlport_req_wr_tmp;
+ wire m_ctrlport_req_rd_tmp;
+
+ assign s_req_flat = {
+ s_ctrlport_req_wr,
+ s_ctrlport_req_rd,
+ s_ctrlport_req_addr,
+ s_ctrlport_req_portid,
+ s_ctrlport_req_rem_epid,
+ s_ctrlport_req_rem_portid,
+ s_ctrlport_req_data,
+ s_ctrlport_req_byte_en,
+ s_ctrlport_req_has_time,
+ s_ctrlport_req_time
+ };
+
+ axi_fifo_2clk #(
+ .WIDTH (REQ_W),
+ .SIZE (3)
+ ) req_fifo (
+ .reset (rst),
+ .i_aclk (s_ctrlport_clk),
+ .i_tdata (s_req_flat),
+ .i_tvalid (s_ctrlport_req_wr | s_ctrlport_req_rd),
+ .i_tready (),
+ .o_aclk (m_ctrlport_clk),
+ .o_tdata (m_req_flat),
+ .o_tready (1'b1),
+ .o_tvalid (m_req_flat_valid)
+ );
+
+ assign {
+ m_ctrlport_req_wr_tmp,
+ m_ctrlport_req_rd_tmp,
+ m_ctrlport_req_addr,
+ m_ctrlport_req_portid,
+ m_ctrlport_req_rem_epid,
+ m_ctrlport_req_rem_portid,
+ m_ctrlport_req_data,
+ m_ctrlport_req_byte_en,
+ m_ctrlport_req_has_time,
+ m_ctrlport_req_time
+ } = m_req_flat;
+
+ assign m_ctrlport_req_wr = m_ctrlport_req_wr_tmp & m_req_flat_valid;
+ assign m_ctrlport_req_rd = m_ctrlport_req_rd_tmp & m_req_flat_valid;
+
+
+ //---------------------------------------------------------------------------
+ // Master to Slave Clock Crossing (Response)
+ //---------------------------------------------------------------------------
+
+ localparam RESP_W =
+ 1 + // ctrlport_resp_ack,
+ 2 + // ctrlport_resp_status,
+ 32; // ctrlport_resp_data
+
+ wire [RESP_W-1:0] m_resp_flat;
+ wire [RESP_W-1:0] s_resp_flat;
+ wire s_resp_flat_valid;
+ wire s_ctrlport_resp_ack_tmp;
+
+ assign m_resp_flat = {
+ m_ctrlport_resp_ack,
+ m_ctrlport_resp_status,
+ m_ctrlport_resp_data
+ };
+
+ axi_fifo_2clk #(
+ .WIDTH (RESP_W),
+ .SIZE (3)
+ ) resp_fifo (
+ .reset (rst),
+ .i_aclk (m_ctrlport_clk),
+ .i_tdata (m_resp_flat),
+ .i_tvalid (m_ctrlport_resp_ack),
+ .i_tready (),
+ .o_aclk (s_ctrlport_clk),
+ .o_tdata (s_resp_flat),
+ .o_tready (1'b1),
+ .o_tvalid (s_resp_flat_valid)
+ );
+
+ assign {
+ s_ctrlport_resp_ack_tmp,
+ s_ctrlport_resp_status,
+ s_ctrlport_resp_data
+ } = s_resp_flat;
+
+ assign s_ctrlport_resp_ack = s_ctrlport_resp_ack_tmp & s_resp_flat_valid;
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_combiner.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_combiner.v
new file mode 100644
index 000000000..591fadc27
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_combiner.v
@@ -0,0 +1,222 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_combiner
+//
+// Description:
+//
+// This block is an arbiter that merges control-port interfaces. This block is
+// used when you have multiple control-port masters that need to access a
+// single slave. For example, a NoC block with multiple submodules that each
+// need to read and/or write registers outside of themselves.
+//
+// This module combines the control-port requests from multiple masters into a
+// single request for one slave. Simultaneous requests are handled in the order
+// specified by PRIORITY. The responding ACK is routed back to the requester.
+//
+// Parameters:
+//
+// NUM_MASTERS : The number of control-port masters to connect to a single
+// control-port slave.
+// PRIORITY : Use PRIORITY = 0 for round robin arbitration, PRIORITY = 1
+// for priority arbitration (lowest number port serviced first).
+//
+
+
+module ctrlport_combiner #(
+ parameter NUM_MASTERS = 2,
+ parameter PRIORITY = 0
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ // Requests from multiple masters
+ input wire [ NUM_MASTERS-1:0] s_ctrlport_req_wr,
+ input wire [ NUM_MASTERS-1:0] s_ctrlport_req_rd,
+ input wire [20*NUM_MASTERS-1:0] s_ctrlport_req_addr,
+ input wire [10*NUM_MASTERS-1:0] s_ctrlport_req_portid,
+ input wire [16*NUM_MASTERS-1:0] s_ctrlport_req_rem_epid,
+ input wire [10*NUM_MASTERS-1:0] s_ctrlport_req_rem_portid,
+ input wire [32*NUM_MASTERS-1:0] s_ctrlport_req_data,
+ input wire [ 4*NUM_MASTERS-1:0] s_ctrlport_req_byte_en,
+ input wire [ NUM_MASTERS-1:0] s_ctrlport_req_has_time,
+ input wire [64*NUM_MASTERS-1:0] s_ctrlport_req_time,
+ // Responses to multiple masters
+ output reg [ NUM_MASTERS-1:0] s_ctrlport_resp_ack,
+ output reg [ 2*NUM_MASTERS-1:0] s_ctrlport_resp_status,
+ output reg [32*NUM_MASTERS-1:0] s_ctrlport_resp_data,
+
+ // Request to a single slave
+ output reg m_ctrlport_req_wr,
+ output reg m_ctrlport_req_rd,
+ output reg [19:0] m_ctrlport_req_addr,
+ output reg [ 9:0] m_ctrlport_req_portid,
+ output reg [15:0] m_ctrlport_req_rem_epid,
+ output reg [ 9:0] m_ctrlport_req_rem_portid,
+ output reg [31:0] m_ctrlport_req_data,
+ output reg [ 3:0] m_ctrlport_req_byte_en,
+ output reg m_ctrlport_req_has_time,
+ output reg [63:0] m_ctrlport_req_time,
+ // Response from a single slave
+ input wire m_ctrlport_resp_ack,
+ input wire [ 1:0] m_ctrlport_resp_status,
+ input wire [31:0] m_ctrlport_resp_data
+);
+
+ reg [$clog2(NUM_MASTERS)-1:0] slave_sel = 0; // Tracks which slave port is
+ // currently being serviced.
+ reg req_load_output = 1'b0;
+
+
+ //---------------------------------------------------------------------------
+ // Input Registers
+ //---------------------------------------------------------------------------
+ //
+ // Latch each request until it can be serviced. Only one request per slave
+ // can be in progress at a time.
+ //
+ //---------------------------------------------------------------------------
+
+ reg [ NUM_MASTERS-1:0] req_valid = 0;
+ reg [ NUM_MASTERS-1:0] req_wr;
+ reg [ NUM_MASTERS-1:0] req_rd;
+ reg [20*NUM_MASTERS-1:0] req_addr;
+ reg [10*NUM_MASTERS-1:0] req_portid;
+ reg [16*NUM_MASTERS-1:0] req_rem_epid;
+ reg [10*NUM_MASTERS-1:0] req_rem_portid;
+ reg [32*NUM_MASTERS-1:0] req_data;
+ reg [ 4*NUM_MASTERS-1:0] req_byte_en;
+ reg [ NUM_MASTERS-1:0] req_has_time;
+ reg [64*NUM_MASTERS-1:0] req_time;
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ req_valid <= 0;
+ end else begin : input_reg_gen
+ integer i;
+ for (i = 0; i < NUM_MASTERS; i = i + 1) begin
+ if (s_ctrlport_req_wr[i] | s_ctrlport_req_rd[i]) begin
+ // Mark this slave's request valid and save the request information
+ req_valid[i] <= 1'b1;
+ req_wr[i] <= s_ctrlport_req_wr[i];
+ req_rd[i] <= s_ctrlport_req_rd[i];
+ req_addr[20*i+:20] <= s_ctrlport_req_addr[20*i+:20];
+ req_portid[10*i+:10] <= s_ctrlport_req_portid[10*i+:10];
+ req_rem_epid[16*i+:16] <= s_ctrlport_req_rem_epid[16*i+:16];
+ req_rem_portid[10*i+:10] <= s_ctrlport_req_rem_portid[10*i+:10];
+ req_data[32*i+:32] <= s_ctrlport_req_data[32*i+:32];
+ req_byte_en[4*i+:4] <= s_ctrlport_req_byte_en[4*i+:4];
+ req_has_time[i] <= s_ctrlport_req_has_time[i];
+ req_time[64*i+:64] <= s_ctrlport_req_time[64*i+:64];
+ end
+ end
+
+ // Clear the active request when it gets output
+ if (req_load_output) begin
+ req_valid[slave_sel] <= 1'b0;
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Arbitration State Machine
+ //---------------------------------------------------------------------------
+ //
+ // This state machine tracks which slave port is being serviced and which to
+ // service next. This is done using a counter that simply checks each port in
+ // sequential order and then stops when it finds one that has a valid request.
+ //
+ //---------------------------------------------------------------------------
+
+ reg req_active = 0; // Indicates if there's a request being serviced
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ slave_sel <= 0;
+ req_active <= 1'b0;
+ req_load_output <= 1'b0;
+ end else begin
+ req_load_output <= 1'b0;
+
+ if (req_active) begin
+ // Wait until we get the response before we allow another request
+ if (m_ctrlport_resp_ack) begin
+ req_active <= 1'b0;
+
+ // Go to the next slave so we don't service the same slave again
+ if(PRIORITY == 1 || slave_sel == NUM_MASTERS-1)
+ slave_sel <= 0;
+ else
+ slave_sel <= slave_sel + 1;
+ end
+ end else begin
+ // No active request in progress, so check if there's a new request on
+ // the selected slave.
+ if (req_valid[slave_sel]) begin
+ req_active <= 1'b1;
+ req_load_output <= 1'b1;
+ end else begin
+ // Nothing from this slave, so move to the next slave.
+ if (slave_sel == NUM_MASTERS-1)
+ slave_sel <= 0;
+ else
+ slave_sel <= slave_sel + 1;
+ end
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Output Register
+ //---------------------------------------------------------------------------
+ //
+ // Here we load the active request for a single clock cycle and demultiplex
+ // the response back to the requesting master.
+ //
+ //---------------------------------------------------------------------------
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ m_ctrlport_req_wr <= 1'b0;
+ m_ctrlport_req_rd <= 1'b0;
+ end else begin : output_reg_gen
+ integer i;
+
+ // Load the active request
+ if (req_load_output) begin
+ m_ctrlport_req_wr <= req_wr [slave_sel];
+ m_ctrlport_req_rd <= req_rd [slave_sel];
+ m_ctrlport_req_addr <= req_addr [20*slave_sel +: 20];
+ m_ctrlport_req_portid <= req_portid [10*slave_sel +: 10];
+ m_ctrlport_req_rem_epid <= req_rem_epid [16*slave_sel +: 16];
+ m_ctrlport_req_rem_portid <= req_rem_portid[10*slave_sel +: 10];
+ m_ctrlport_req_data <= req_data [32*slave_sel +: 32];
+ m_ctrlport_req_byte_en <= req_byte_en [ 4*slave_sel +: 4];
+ m_ctrlport_req_has_time <= req_has_time [slave_sel];
+ m_ctrlport_req_time <= req_time [64*slave_sel +: 64];
+ end else begin
+ m_ctrlport_req_wr <= 1'b0;
+ m_ctrlport_req_rd <= 1'b0;
+ end
+
+ // Output any response to the master that made the request
+ for (i = 0; i < NUM_MASTERS; i = i + 1) begin
+ // Give the response data to all the slaves (no demux, to save logic)
+ s_ctrlport_resp_status[2*i +: 2] <= m_ctrlport_resp_status;
+ s_ctrlport_resp_data[32*i +: 32] <= m_ctrlport_resp_data;
+
+ // Give the ack only to the master that made the request (use a demux)
+ if (i == slave_sel && m_ctrlport_resp_ack) begin
+ s_ctrlport_resp_ack[i] <= 1'b1;
+ end else begin
+ s_ctrlport_resp_ack[i] <= 1'b0;
+ end
+ end
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder.v
new file mode 100644
index 000000000..74cdb307a
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder.v
@@ -0,0 +1,151 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_decoder
+//
+// Description:
+//
+// This block splits a single control port interface into multiple. It is used
+// when you have a single master that needs to access multiple slaves. For
+// example, a NoC block where the registers are implemented in multiple
+// submodules that must be read/written by a single NoC shell.
+//
+// This version also implements address decoding. The request is passed to a
+// slave only if the address falls within that slave's address space. Each
+// slave is given an address space of 2**ADDR_W and the first slave starts at
+// address BASE_ADDR. In other words, the request address is partitioned as
+// shown below.
+//
+// |---------------- 32-bit -----------------|
+// | Base | Port Num | Slave Addr |
+// |-----------------------------------------|
+//
+// When passed to the slave, the base address and port number bits are stripped
+// from the request address and only the SLAVE_ADDR_W-bit address is passed
+// through.
+//
+// Parameters:
+//
+// NUM_SLAVES : Number of slave devices that you want to connect to master.
+// BASE_ADDR : Base address for slave 0. This should be a power-of-2
+// multiple of the combined slave address spaces.
+// SLAVE_ADDR_W : Number of address bits to allocate to each slave.
+//
+
+module ctrlport_decoder #(
+ parameter NUM_SLAVES = 2,
+ parameter BASE_ADDR = 0,
+ parameter SLAVE_ADDR_W = 8
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ // Slave Interface
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ 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,
+ output reg s_ctrlport_resp_ack = 1'b0,
+ output reg [ 1:0] s_ctrlport_resp_status,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ // Master Interfaces
+ output reg [ NUM_SLAVES-1:0] m_ctrlport_req_wr = 0,
+ output reg [ NUM_SLAVES-1:0] m_ctrlport_req_rd = 0,
+ output reg [20*NUM_SLAVES-1:0] m_ctrlport_req_addr = 0,
+ output reg [32*NUM_SLAVES-1:0] m_ctrlport_req_data,
+ output reg [ 4*NUM_SLAVES-1:0] m_ctrlport_req_byte_en,
+ output reg [ NUM_SLAVES-1:0] m_ctrlport_req_has_time,
+ output reg [64*NUM_SLAVES-1:0] m_ctrlport_req_time,
+ input wire [ NUM_SLAVES-1:0] m_ctrlport_resp_ack,
+ input wire [ 2*NUM_SLAVES-1:0] m_ctrlport_resp_status,
+ input wire [32*NUM_SLAVES-1:0] m_ctrlport_resp_data
+);
+
+ localparam PORT_NUM_W = $clog2(NUM_SLAVES);
+ localparam PORT_NUM_POS = SLAVE_ADDR_W;
+ localparam BASE_ADDR_W = 20 - (SLAVE_ADDR_W + PORT_NUM_W);
+ localparam BASE_ADDR_POS = SLAVE_ADDR_W + PORT_NUM_W;
+ localparam [19:0] BASE_ADDR_MASK = { BASE_ADDR_W {1'b1}} << BASE_ADDR_POS;
+
+
+ //---------------------------------------------------------------------------
+ // Split the requests among the slaves
+ //---------------------------------------------------------------------------
+
+ wire [NUM_SLAVES-1:0] decoder;
+
+ genvar i;
+ for (i = 0; i < NUM_SLAVES; i = i+1) begin : gen_split
+ // Check if the upper bits of the request address match each slave. If the
+ // address matches, set the corresponding decoder[] bit.
+ if (PORT_NUM_W == 0) begin
+ // Only one port in this case, so there are no port number bits to check
+ assign decoder[i] = ((s_ctrlport_req_addr & BASE_ADDR_MASK) == BASE_ADDR);
+ end else begin
+ assign decoder[i] = ((s_ctrlport_req_addr & BASE_ADDR_MASK) == BASE_ADDR) &&
+ (s_ctrlport_req_addr[PORT_NUM_POS +: PORT_NUM_W] == i);
+ end
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ m_ctrlport_req_wr[i] <= 1'b0;
+ m_ctrlport_req_rd[i] <= 1'b0;
+ end else begin
+ // Mask WR and RD based on address decoding
+ m_ctrlport_req_wr[i] <= s_ctrlport_req_wr & decoder[i];
+ m_ctrlport_req_rd[i] <= s_ctrlport_req_rd & decoder[i];
+
+ // Other values pass through to all slaves, but should be ignored
+ // unless the corresponding WR or RD is not asserted.
+ m_ctrlport_req_data [32*i +: 32] <= s_ctrlport_req_data;
+ m_ctrlport_req_byte_en [4*i +: 4] <= s_ctrlport_req_byte_en;
+ m_ctrlport_req_has_time[i] <= s_ctrlport_req_has_time;
+ m_ctrlport_req_time [64*i +: 64] <= s_ctrlport_req_time;
+
+ // Pass through only the relevant slave bits
+ m_ctrlport_req_addr[20*i+:20] <= 20'b0;
+ m_ctrlport_req_addr[20*i+:SLAVE_ADDR_W] <= s_ctrlport_req_addr[SLAVE_ADDR_W-1:0];
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Decode the responses
+ //---------------------------------------------------------------------------
+
+ reg [31:0] data;
+ reg [ 1:0] status;
+ reg ack = 0;
+
+ // Take the responses and mask them with ack, then OR them together
+ always @(*) begin : comb_decode
+ integer s;
+ data = 0;
+ status = 0;
+ ack = 0;
+ for (s = 0; s < NUM_SLAVES; s = s+1) begin
+ data = data | (m_ctrlport_resp_data [s*32 +: 32] & {32{m_ctrlport_resp_ack[s]}});
+ status = status | (m_ctrlport_resp_status[s* 2 +: 2] & { 2{m_ctrlport_resp_ack[s]}});
+ ack = ack | m_ctrlport_resp_ack[s];
+ end
+ end
+
+ // Register the output to break combinatorial path
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_ack <= 0;
+ end else begin
+ s_ctrlport_resp_data <= data;
+ s_ctrlport_resp_status <= status;
+ s_ctrlport_resp_ack <= ack;
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder_param.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder_param.v
new file mode 100644
index 000000000..f2f4a438c
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_decoder_param.v
@@ -0,0 +1,169 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_decoder_param
+//
+// Description:
+//
+// This block splits a single control port interface into multiple. It is
+// used when you have a single master that needs to access multiple slaves.
+// For example, a NoC block where the registers are implemented in multiple
+// submodules that must be read/written by a single NoC shell.
+//
+// This version also implements address decoding. The request is passed to a
+// slave only if the address falls within that slave's address space. Each
+// slave can have a unique base address and address space size. The address
+// space is broken up as follows.
+//
+// PORT_BASE[0*20 +: 20] = Port 0 base address
+// │ ┐
+// │ ├── 2**PORT_ADDR_W[0*32 +: 32] bytes for slave 0
+// │ ┘
+// .
+// .
+// PORT_BASE[1*20 +: 20] = Port 1 base address
+// │ ┐
+// │ ├── 2**PORT_ADDR_W[1*32 +: 32] bytes for slave 1
+// │ ┘
+// .
+// .
+//
+// When passed to the slave, the base address is stripped from the request
+// address so that only the PORT_ADDR_W-bit address is passed through.
+//
+// Parameters:
+//
+// NUM_SLAVES : The number of slaves to connect to a master.
+//
+// PORT_BASE : Base addresses to use fore each slave. This is a
+// concatenation of 20-bit addresses, where the right-most
+// (least-significant) 20 bits corresponds to slave 0. Each
+// address must be a multiple of 2**PORT_ADDR_W, where
+// PORT_ADDR_W is the number of address bits allocated to that
+// slave.
+//
+// PORT_ADDR_W : Number of address bits to allocate to each slave. This is a
+// concatenation of 32-bit integers, where the right-most
+// (least-significant) 32 bits corresponds to the address space
+// for slave 0.
+//
+
+module ctrlport_decoder_param #(
+ parameter NUM_SLAVES = 4,
+ parameter PORT_BASE = { 20'h300, 20'h200, 20'h100, 20'h000 },
+ parameter PORT_ADDR_W = { 32'd8, 32'd8, 32'd8, 32'd8 }
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ // Slave Interface
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ 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,
+ output reg s_ctrlport_resp_ack = 1'b0,
+ output reg [ 1:0] s_ctrlport_resp_status,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ // Master Interfaces
+ output reg [ NUM_SLAVES-1:0] m_ctrlport_req_wr = 0,
+ output reg [ NUM_SLAVES-1:0] m_ctrlport_req_rd = 0,
+ output reg [20*NUM_SLAVES-1:0] m_ctrlport_req_addr = 0,
+ output reg [32*NUM_SLAVES-1:0] m_ctrlport_req_data,
+ output reg [ 4*NUM_SLAVES-1:0] m_ctrlport_req_byte_en,
+ output reg [ NUM_SLAVES-1:0] m_ctrlport_req_has_time,
+ output reg [64*NUM_SLAVES-1:0] m_ctrlport_req_time,
+ input wire [ NUM_SLAVES-1:0] m_ctrlport_resp_ack,
+ input wire [ 2*NUM_SLAVES-1:0] m_ctrlport_resp_status,
+ input wire [32*NUM_SLAVES-1:0] m_ctrlport_resp_data
+);
+
+ //---------------------------------------------------------------------------
+ // Address Decode Logic
+ //---------------------------------------------------------------------------
+ //
+ // Check if the upper bits of the request address match each slave. If the
+ // address matches, set the corresponding dec_mask[] bit.
+ //
+ //---------------------------------------------------------------------------
+
+ wire [NUM_SLAVES-1:0] dec_mask; // Address decoder mask
+
+ genvar i;
+
+ for (i = 0; i < NUM_SLAVES; i = i+1) begin : gen_dec_mask
+ localparam [19:0] BASE_ADDR = PORT_BASE [i*20 +: 20];
+ localparam [31:0] ADDR_W = PORT_ADDR_W[i*32 +: 32];
+ assign dec_mask[i] = ~|((s_ctrlport_req_addr ^ BASE_ADDR) & ((~0) << ADDR_W));
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Split the requests among the slaves
+ //---------------------------------------------------------------------------
+
+ for (i = 0; i < NUM_SLAVES; i = i+1) begin : gen_split
+ localparam [31:0] ADDR_W = PORT_ADDR_W[i*32 +: 32];
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ m_ctrlport_req_wr[i] <= 1'b0;
+ m_ctrlport_req_rd[i] <= 1'b0;
+ end else begin
+ // Mask WR and RD based on address decoding
+ m_ctrlport_req_wr[i] <= s_ctrlport_req_wr & dec_mask[i];
+ m_ctrlport_req_rd[i] <= s_ctrlport_req_rd & dec_mask[i];
+
+ // Other values pass through to all slaves, but should be ignored
+ // unless WR or RD is asserted.
+ m_ctrlport_req_data [32*i +: 32] <= s_ctrlport_req_data;
+ m_ctrlport_req_byte_en [4*i +: 4] <= s_ctrlport_req_byte_en;
+ m_ctrlport_req_has_time[i] <= s_ctrlport_req_has_time;
+ m_ctrlport_req_time [64*i +: 64] <= s_ctrlport_req_time;
+
+ // Mask the address bits to that of the slaves address space.
+ m_ctrlport_req_addr[20*i +: 20] <= 20'b0;
+ m_ctrlport_req_addr[20*i +: ADDR_W] <= s_ctrlport_req_addr[ADDR_W-1 : 0];
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Decode the responses
+ //---------------------------------------------------------------------------
+
+ reg [31:0] data;
+ reg [ 1:0] status;
+ reg ack = 0;
+
+ // Take the responses and mask them with ack, then OR them together
+ always @(*) begin : comb_decode
+ integer s;
+ data = 0;
+ status = 0;
+ ack = 0;
+ for (s = 0; s < NUM_SLAVES; s = s+1) begin
+ data = data | (m_ctrlport_resp_data [s*32 +: 32] & {32{m_ctrlport_resp_ack[s]}});
+ status = status | (m_ctrlport_resp_status[s* 2 +: 2] & { 2{m_ctrlport_resp_ack[s]}});
+ ack = ack | m_ctrlport_resp_ack[s];
+ end
+ end
+
+ // Register the output to break combinatorial path
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_ack <= 0;
+ end else begin
+ s_ctrlport_resp_data <= data;
+ s_ctrlport_resp_status <= status;
+ s_ctrlport_resp_ack <= ack;
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_ro.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_ro.v
new file mode 100644
index 000000000..c21667358
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_ro.v
@@ -0,0 +1,181 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_reg_ro
+//
+// Description:
+//
+// Implements a read-only register on a CTRL Port bus. The actual register
+// bits are driven from outside of this module and passed in through the
+// "value_in" input port. All input addresses are assumed to be 32-bit word
+// aligned.
+//
+// The width of the register is configurable. The register will take up the
+// full power-of-2 address region, with a minimum of a 4-byte region. For
+// example:
+//
+// WIDTH (Bits) │ Address Space (Bytes)
+// ──────────────┼───────────────────────
+// 1 to 32 │ 4
+// 33 to 64 │ 8
+// 64 to 128 │ 16
+// etc. │ etc.
+//
+// When COHERENT is true and the WIDTH is larger than a single CTRL Port word
+// (32 bits), reading the least-significant word of the register causes the
+// other words of the register to be read and saved in a cache register on
+// the same clock cycle. Reading the upper words of the register will always
+// read from the cached copy. This allows reads of large, multi-word
+// registers to be coherent. This is very important for registers in which
+// there is a relationship between the upper and lower bits, such as in a
+// counter which could change or roll over between 32-bit reads. The
+// least-significant word MUST always be read first when COHERENT is true.
+//
+// Parameters:
+//
+// ADDR : Byte address to use for this register. This address must be
+// aligned to the size of the register.
+// WIDTH : Width of register to implement in bits. This determines the
+// width of the "value_in" input and the amount of address space
+// used by the register, which is always a power of 2.
+// COHERENT : Setting to 1 implements additional logic so that register reads
+// maintain coherency. Setting to 0 removes this logic, so that
+// each 32-bit word of the register is treated independently.
+//
+// Ports:
+//
+// *ctrlport* : CTRL Port interface.
+// value_in : The current value of the register.
+//
+
+
+module ctrlport_reg_ro #(
+ parameter [ 19:0] ADDR = 0,
+ parameter WIDTH = 32,
+ parameter COHERENT = 0
+) (
+ input wire ctrlport_clk,
+
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ output reg s_ctrlport_resp_ack,
+ output wire [ 1:0] s_ctrlport_resp_status,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ input wire [WIDTH-1:0] value_in
+);
+
+ //---------------------------------------------------------------------------
+ // Functions
+ //---------------------------------------------------------------------------
+
+ function automatic integer max(input integer a, b);
+ max = a > b ? a : b;
+ endfunction
+
+
+ //---------------------------------------------------------------------------
+ // Local Parameters
+ //---------------------------------------------------------------------------
+
+ // Calculate the number of bytes of address space this register will take up.
+ // The minimum size is a 32-bit register (4 bytes).
+ localparam NUM_BYTES = max(4, 2**$clog2(WIDTH) / 8);
+
+ // Calculate the number of bits needed to index each byte of this register.
+ localparam BYTE_ADDR_W = $clog2(NUM_BYTES);
+
+ // Calculate the number of bits needed to index each 32-bit word of this
+ // register.
+ localparam WORD_ADDR_W = BYTE_ADDR_W-2;
+
+
+ //---------------------------------------------------------------------------
+ // Parameter Checking
+ //---------------------------------------------------------------------------
+
+ // Make sure WIDTH is valid
+ if (WIDTH < 1) begin
+ WIDTH_must_be_at_least_1();
+ end
+
+ // Make sure the address is word-aligned to the size of the register
+ if (ADDR[BYTE_ADDR_W-1:0] != 0) begin
+ ADDR_must_be_aligned_to_the_size_of_the_register();
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Resize Input Value
+ //---------------------------------------------------------------------------
+
+ // Use full size to simplify indexing. Unused bits will be optimized away.
+ reg [NUM_BYTES*8-1:0] reg_val = 0;
+
+ always @(*) begin
+ reg_val <= 0;
+ reg_val[WIDTH-1:0] <= value_in;
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Read Logic
+ //---------------------------------------------------------------------------
+
+ reg [WIDTH-1:0] cache_reg;
+
+ assign s_ctrlport_resp_status = 0; // Status is always "OK" (0)
+
+ //
+ // Coherent implementation
+ //
+ if (WIDTH > 32 && COHERENT) begin : gen_coherent
+ // In this case we want the upper bits, when read separately, to be
+ // coherent with the lower bits. So we register the upper bits when the
+ // least-significant word is read.
+
+ always @(posedge ctrlport_clk) begin
+ // Check if any part of this register is being addressed
+ if (s_ctrlport_req_addr[19 : BYTE_ADDR_W] == ADDR[19 : BYTE_ADDR_W] && s_ctrlport_req_rd) begin
+ s_ctrlport_resp_ack <= 1'b1;
+
+ // Check if we're reading the least-significant word
+ if (s_ctrlport_req_addr[BYTE_ADDR_W-1 : 2] == 0) begin
+ s_ctrlport_resp_data <= reg_val[31:0];
+ cache_reg <= reg_val; // Unused bits will be optimized away
+
+ // Otherwise, grab the word that's being addressed from the cached value
+ end else begin
+ s_ctrlport_resp_data <= cache_reg[s_ctrlport_req_addr[2 +: WORD_ADDR_W]*32 +: 32];
+ end
+ end else begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end
+ end
+
+ //
+ // Non-coherent implementation
+ //
+ end else begin : gen_no_coherent
+ // In this case, coherency is not required, so we just return the word
+ // that's being addressed.
+
+ always @(posedge ctrlport_clk) begin
+ // Check if any part of this register is being addressed
+ if (s_ctrlport_req_addr[19 : BYTE_ADDR_W] == ADDR[19 : BYTE_ADDR_W] && s_ctrlport_req_rd) begin
+ s_ctrlport_resp_ack <= 1'b1;
+ if (WORD_ADDR_W > 0) begin
+ // Read back only the word of the register being addressed
+ s_ctrlport_resp_data <= reg_val[s_ctrlport_req_addr[2 +: WORD_ADDR_W]*32 +: 32];
+ end else begin
+ s_ctrlport_resp_data <= reg_val;
+ end
+ end else begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_rw.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_rw.v
new file mode 100644
index 000000000..7e74b1422
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_reg_rw.v
@@ -0,0 +1,247 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_reg_rw
+//
+// Description:
+//
+// Implements a read/write register on a CTRL Port bus. CTRL Port byte
+// enables are supported on writes. All input addresses are assumed to be
+// 32-bit word aligned.
+//
+// The width of the register is configurable. The register will take up the
+// full power-of-2 address region, with a minimum of a 4-byte region. For
+// example:
+//
+// WIDTH (Bits) │ Address Space (Bytes)
+// ──────────────┼───────────────────────
+// 1 to 32 │ 4
+// 33 to 64 │ 8
+// 64 to 128 │ 16
+// etc. │ etc.
+//
+// When COHERENCY is true and the WIDTH is larger than a single CTRL Port
+// word (32 bits), writing the least-significant words of the register causes
+// them to be saved in a cache register and does not immediately update those
+// words in the register. Writing the most-significant word of the register
+// causes all the words to be simultaneously written to the register. This
+// allows writes of large, multi-word registers to be coherent. This is very
+// important for registers in which there is a relationship between the upper
+// and lower bits, such as in a counter value in which changing only part of
+// the word at a time could be seen as a large change when in fact the final
+// change is small. The most-significant word MUST always be written last
+// when COHERENCY is true.
+//
+// Parameters:
+//
+// ADDR : Byte address to use for this register. This address must be
+// aligned to the size of the register.
+// WIDTH : Width of register to implement in bits. This determines the
+// width of the "value_out" input and the amount of address space
+// used by the register, which is always a power of 2.
+// COHERENT : Setting to 1 implements additional logic so that register
+// writes maintain coherency. Setting to 0 removes this logic, so
+// that each 32-bit word of the register is treated independently.
+// RESET_VAL : Value to give the register at power-on and at reset.
+//
+// Ports:
+//
+// *ctrlport* : CTRL Port interface.
+// value_out : The current value of the register.
+// written : A strobe (single-cycle pulse) that indicates when the
+// register was written. The new value may or may not be the
+// same as the old value.
+//
+
+
+module ctrlport_reg_rw #(
+ parameter [ 19:0] ADDR = 0,
+ parameter WIDTH = 32,
+ parameter COHERENT = 0,
+ parameter [WIDTH-1:0] RESET_VAL = 'h0
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+ input wire [ 3:0] s_ctrlport_req_byte_en,
+ output wire s_ctrlport_resp_ack,
+ output wire [ 1:0] s_ctrlport_resp_status,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ output wire [WIDTH-1:0] value_out,
+ output reg written
+);
+
+ //---------------------------------------------------------------------------
+ // Functions
+ //---------------------------------------------------------------------------
+
+ function automatic integer max(input integer a, b);
+ max = a > b ? a : b;
+ endfunction
+
+
+ //---------------------------------------------------------------------------
+ // Local Parameters
+ //---------------------------------------------------------------------------
+
+ // Calculate the number of bytes of address space this register will take up.
+ // The minimum size is a 32-bit register (4 bytes).
+ localparam NUM_BYTES = max(4, 2**$clog2(WIDTH)/8);
+
+ // Calculate the number of bits needed to index each byte of this register.
+ localparam BYTE_ADDR_W = $clog2(NUM_BYTES);
+
+ // Calculate the number of bits needed to index each 32-bit word of this
+ // register.
+ localparam WORD_ADDR_W = BYTE_ADDR_W-2;
+
+
+ //---------------------------------------------------------------------------
+ // Parameter Checking
+ //---------------------------------------------------------------------------
+
+ // Make sure WIDTH is valid
+ if (WIDTH < 1) begin
+ WIDTH_must_be_at_least_1();
+ end
+
+ // Make sure the address is word-aligned to the size of the register
+ if (ADDR[BYTE_ADDR_W-1:0] != 0) begin
+ ADDR_must_be_aligned_to_the_size_of_the_register();
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Write Logic
+ //---------------------------------------------------------------------------
+
+ // Use full size to simplify indexing. Unused bits will be optimized away.
+ reg [8*NUM_BYTES-1:0] reg_val = 0;
+
+ reg [8*NUM_BYTES-1:0] write_cache_reg;
+ reg [ NUM_BYTES-1:0] write_en_cache_reg;
+
+ reg s_ctrlport_resp_ack_wr;
+
+ integer b, w;
+
+ //
+ // Coherent implementation
+ //
+ if (WIDTH > 32 && COHERENT) begin : gen_coherent
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ reg_val <= RESET_VAL;
+ written <= 1'b0;
+ end else begin
+ // Check if any part of this register is being written to
+ if (s_ctrlport_req_addr[19 : BYTE_ADDR_W] == ADDR[19 : BYTE_ADDR_W] && s_ctrlport_req_wr) begin
+ s_ctrlport_resp_ack_wr <= 1'b1;
+
+ // Check if we're writing the most-significant word
+ if (s_ctrlport_req_addr[BYTE_ADDR_W-1 : 2] == {BYTE_ADDR_W-2{1'b1}}) begin
+ written <= 1'b1;
+
+ // Iterate over the 4 bytes, updating each based on byte_en
+ for (b = 0; b < 4; b = b+1) begin
+ // Update the most-significant word from the input
+ if(s_ctrlport_req_byte_en[b]) begin
+ reg_val[32*(NUM_BYTES/4-1)+b*8 +: 8] <= s_ctrlport_req_data[8*b +: 8];
+ end
+
+ // Update the least-significant words from the cache
+ for (w = 0; w < NUM_BYTES/4; w = w+1) begin
+ if (write_en_cache_reg[b]) begin
+ reg_val[32*w+b*8 +: 8] <= write_cache_reg[32*w+b*8 +: 8];
+ end
+ end
+ end
+
+ // We're writing one of the least-significant words, so just cache
+ // the values written.
+ end else begin
+ w = s_ctrlport_req_addr[2 +: WORD_ADDR_W];
+ write_cache_reg[w*32 +: 32] <= s_ctrlport_req_data;
+ write_en_cache_reg[w*4 +: 4] <= s_ctrlport_req_byte_en;
+ end
+
+ end else begin
+ s_ctrlport_resp_ack_wr <= 1'b0;
+ written <= 1'b0;
+ end
+ end
+ end
+
+ //
+ // Non-coherent implementation
+ //
+ end else begin : gen_no_coherent
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ reg_val <= RESET_VAL;
+ written <= 1'b0;
+ end else begin
+ // Check if any part of the word is begin written to
+ if (s_ctrlport_req_addr[19 : BYTE_ADDR_W] == ADDR[19 : BYTE_ADDR_W] && s_ctrlport_req_wr) begin
+ for (b = 0; b < 4; b = b + 1) begin
+ if (s_ctrlport_req_byte_en[b]) begin
+ if (WORD_ADDR_W > 0) begin
+ // Update only the word of the register being addressed. "max"
+ // is needed by Vivado here to elaborate when WORD_ADDR_W is 0.
+ w = s_ctrlport_req_addr[2 +: max(1, WORD_ADDR_W)];
+ reg_val[w*32+b*8 +: 8] <= s_ctrlport_req_data[8*b +: 8];
+ end else begin
+ reg_val[b*8 +: 8] <= s_ctrlport_req_data[8*b +: 8];
+ end
+ end
+ end
+ s_ctrlport_resp_ack_wr <= 1'b1;
+ written <= 1'b1;
+ end else begin
+ s_ctrlport_resp_ack_wr <= 1'b0;
+ written <= 1'b0;
+ end
+ end
+ end
+
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Read Logic
+ //---------------------------------------------------------------------------
+
+ reg s_ctrlport_resp_ack_rd;
+
+ assign s_ctrlport_resp_status = 0; // Status is always "OK" (0)
+
+ assign value_out = reg_val[WIDTH-1:0];
+
+ // Because the register is only changed by software, read coherency is not
+ // required, so we just return the word that's being addressed.
+ always @(posedge ctrlport_clk) begin
+ // Check if any part of this register is being addressed
+ if (s_ctrlport_req_addr[19 : BYTE_ADDR_W] == ADDR[19 : BYTE_ADDR_W] && s_ctrlport_req_rd) begin
+ s_ctrlport_resp_ack_rd <= 1'b1;
+ if (WORD_ADDR_W > 0) begin
+ // Read back only the word of the register being addressed
+ s_ctrlport_resp_data <= reg_val[s_ctrlport_req_addr[2 +: WORD_ADDR_W]*32 +: 32];
+ end else begin
+ s_ctrlport_resp_data <= reg_val[31:0];
+ end
+ end else begin
+ s_ctrlport_resp_ack_rd <= 1'b0;
+ end
+ end
+
+ // Combine read/write ack
+ assign s_ctrlport_resp_ack = s_ctrlport_resp_ack_wr | s_ctrlport_resp_ack_rd;
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_resp_combine.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_resp_combine.v
new file mode 100644
index 000000000..e3461cb2c
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_resp_combine.v
@@ -0,0 +1,70 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_resp_combine
+//
+// Description:
+//
+// This module combines the control-port responses from multiple slave blocks
+// into a single response for the master. This is done by using ack bit to
+// mask all bits of the responses then ORing all the results together onto a
+// single response bus. This is valid because only one block is allowed to
+// respond to a single request.
+//
+// Note that no special logic is required to split the requests from the
+// master among multiple slaves. A single master request interface can be
+// directly connected to all the slaves without issue.
+//
+// Parameters:
+//
+// NUM_SLAVES : The number of slaves you want to connect to a master.
+//
+
+
+module ctrlport_resp_combine #(
+ parameter NUM_SLAVES = 2
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ // Responses from multiple slaves
+ input wire [ NUM_SLAVES-1:0] m_ctrlport_resp_ack,
+ input wire [ 2*NUM_SLAVES-1:0] m_ctrlport_resp_status,
+ input wire [32*NUM_SLAVES-1:0] m_ctrlport_resp_data,
+
+ // Response to a single master
+ output reg s_ctrlport_resp_ack,
+ output reg [ 1:0] s_ctrlport_resp_status,
+ output reg [31:0] s_ctrlport_resp_data
+);
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_data <= 0;
+ s_ctrlport_resp_ack <= 0;
+ end else begin : or_reg_resp
+ reg [31:0] data;
+ reg [ 1:0] status;
+ reg ack;
+ integer s;
+
+ // Take the responses and mask them with ack then OR them together
+ data = 0;
+ status = 0;
+ ack = 0;
+ for (s = 0; s < NUM_SLAVES; s = s+1) begin
+ data = data | (m_ctrlport_resp_data [s*32 +: 32] & {32{m_ctrlport_resp_ack[s]}});
+ status = status | (m_ctrlport_resp_status[s* 2 +: 2] & { 2{m_ctrlport_resp_ack[s]}});
+ ack = ack | m_ctrlport_resp_ack[s];
+ end
+
+ // Register the output to break combinatorial path
+ s_ctrlport_resp_data <= data;
+ s_ctrlport_resp_status <= status;
+ s_ctrlport_resp_ack <= ack;
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_splitter.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_splitter.v
new file mode 100644
index 000000000..23ef13585
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_splitter.v
@@ -0,0 +1,114 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_splitter
+//
+// Description:
+//
+// This block splits a single control port interface into multiple. It is used
+// when you have a single master that needs to access multiple slaves. For
+// example, a NoC block where the registers are implemented in multiple
+// submodules that must be read/written by a single NoC shell.
+//
+// Note that this block does not do any address decoding, so the connected
+// slaves must use non-overlapping address spaces.
+//
+// This module takes the request received by its single slave interface and
+// outputs it on all its master interfaces. In the opposite direction, it takes
+// the responses received by its multiple master interfaces and combines them
+// into a single response on its slave interface. This is done by using the ack
+// bit of each response to mask the other bits of the response, then OR'ing all
+// of the masked responses together onto a single response bus. This is valid
+// because only one block is allowed to respond to a single request.
+//
+// Parameters:
+//
+// NUM_SLAVES : The number of slaves you want to connect to a master.
+//
+
+
+module ctrlport_splitter #(
+ parameter NUM_SLAVES = 2
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ // Slave Interface
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ 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,
+ output reg s_ctrlport_resp_ack = 1'b0,
+ output reg [ 1:0] s_ctrlport_resp_status,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ // Master Interfaces
+ output wire [ NUM_SLAVES-1:0] m_ctrlport_req_wr,
+ output wire [ NUM_SLAVES-1:0] m_ctrlport_req_rd,
+ output wire [20*NUM_SLAVES-1:0] m_ctrlport_req_addr,
+ output wire [32*NUM_SLAVES-1:0] m_ctrlport_req_data,
+ output wire [ 4*NUM_SLAVES-1:0] m_ctrlport_req_byte_en,
+ output wire [ NUM_SLAVES-1:0] m_ctrlport_req_has_time,
+ output wire [64*NUM_SLAVES-1:0] m_ctrlport_req_time,
+ input wire [ NUM_SLAVES-1:0] m_ctrlport_resp_ack,
+ input wire [ 2*NUM_SLAVES-1:0] m_ctrlport_resp_status,
+ input wire [32*NUM_SLAVES-1:0] m_ctrlport_resp_data
+);
+
+ //---------------------------------------------------------------------------
+ // Split the requests among the slaves
+ //---------------------------------------------------------------------------
+
+ generate
+ genvar i;
+ for (i = 0; i < NUM_SLAVES; i = i+1) begin : gen_split
+ // No special logic is required to split the requests from the master among
+ // multiple slaves.
+ assign m_ctrlport_req_wr[i] = s_ctrlport_req_wr;
+ assign m_ctrlport_req_rd[i] = s_ctrlport_req_rd;
+ assign m_ctrlport_req_addr[20*i+:20] = s_ctrlport_req_addr;
+ assign m_ctrlport_req_data[32*i+:32] = s_ctrlport_req_data;
+ assign m_ctrlport_req_byte_en[4*i+:4] = s_ctrlport_req_byte_en;
+ assign m_ctrlport_req_has_time[i] = s_ctrlport_req_has_time;
+ assign m_ctrlport_req_time[64*i+:64] = s_ctrlport_req_time;
+ end
+ endgenerate
+
+ //---------------------------------------------------------------------------
+ // Decode the responses
+ //---------------------------------------------------------------------------
+
+ reg [31:0] data;
+ reg [ 1:0] status;
+ reg ack = 0;
+
+ // Take the responses and mask them with ack, then OR them together
+ always @(*) begin : comb_decode
+ integer s;
+ data = 0;
+ status = 0;
+ ack = 0;
+ for (s = 0; s < NUM_SLAVES; s = s+1) begin
+ data = data | (m_ctrlport_resp_data [s*32 +: 32] & {32{m_ctrlport_resp_ack[s]}});
+ status = status | (m_ctrlport_resp_status[s* 2 +: 2] & { 2{m_ctrlport_resp_ack[s]}});
+ ack = ack | m_ctrlport_resp_ack[s];
+ end
+ end
+
+ // Register the output to break combinatorial path
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_ack <= 0;
+ end else begin
+ s_ctrlport_resp_data <= data;
+ s_ctrlport_resp_status <= status;
+ s_ctrlport_resp_ack <= ack;
+ end
+ end
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_terminator.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_terminator.v
new file mode 100644
index 000000000..2d087e53e
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_terminator.v
@@ -0,0 +1,50 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_terminator.v
+// Description:
+// Returns an error for all ctrlport requests in given address range.
+
+module ctrlport_terminator #(
+ parameter START_ADDRESS = 0, // first address to generate response for
+ parameter LAST_ADDRESS = 32 // last address (including) to generate response for
+)(
+ //---------------------------------------------------------------
+ // ControlPort slave
+ //---------------------------------------------------------------
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+
+ output reg s_ctrlport_resp_ack,
+ output wire [ 1:0] s_ctrlport_resp_status,
+ output wire [31:0] s_ctrlport_resp_data
+);
+
+`include "../core/ctrlport.vh"
+//vhook_nowarn s_ctrlport_req_addr
+//vhook_nowarn s_ctrlport_req_data
+
+// drive acknowledgement on requests but not on reset
+always @(posedge ctrlport_clk) begin
+ if (ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end else if ((s_ctrlport_req_addr >= START_ADDRESS) && (s_ctrlport_req_addr <= LAST_ADDRESS)) begin
+ s_ctrlport_resp_ack <= s_ctrlport_req_wr | s_ctrlport_req_rd;
+ end else begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end
+ end
+end
+
+// other outputs are fixed
+assign s_ctrlport_resp_status = CTRL_STS_CMDERR;
+assign s_ctrlport_resp_data = { CTRLPORT_DATA_W {1'b0}};
+
+endmodule \ No newline at end of file
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_timer.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_timer.v
new file mode 100644
index 000000000..293ee6559
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_timer.v
@@ -0,0 +1,122 @@
+//
+// Copyright 2018 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_timer
+// Description:
+// The Control-Port timer module converts an asynchronous timed
+// transaction into a synchronous blocking transaction. This
+// module will use the input req_has_time and req_time fields and
+// produce an output transaction that will execute when the requested
+// time is current. The module does not pass the has_time and time
+// signals out because they are no longer relevant. The current time
+// is an input to this module, and must be a monotonic counter that
+// updates every time the time strobe is asserted.
+//
+// Parameters:
+// - PRECISION_BITS : The number of bits to ignore when performing a
+// time comparison to determine execution time.
+// - EXEC_LATE_CMDS : If a command is late, a TSERR response is sent.
+// If EXEC_LATE_CMDS = 1, then the late command will
+// be passed to the output regardless of the TSERR.
+//
+// Signals:
+// - time_now* : The time_now signal is the current time and the stb
+// signal indicates that the time_now is valid.
+// - s_ctrlport_* : The slave Control-Port bus.
+// This must have the has_time and time signals.
+// - m_ctrlport_* : The master Control-Port bus.
+// This will not have the has_time and time signals.
+
+module ctrlport_timer #(
+ parameter PRECISION_BITS = 0,
+ parameter [0:0] EXEC_LATE_CMDS = 0
+)(
+ // Clocks and Resets
+ input wire clk,
+ input wire rst,
+ // Timestamp (synchronous to clk)
+ input wire [63:0] time_now,
+ input wire time_now_stb,
+ // Control Port Master (Request)
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ 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,
+ // 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,
+ // 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
+);
+
+ `include "../core/rfnoc_chdr_utils.vh"
+ `include "../core/rfnoc_axis_ctrl_utils.vh"
+
+ // Control triggers:
+ // - pending: A command is waiting on the input port
+ // - ontime: The timed command is due for execution (on time)
+ // - late: The timed command is late
+ // - exec: Execute the command (pass it to the output)
+ // - consume: Consume the input command
+ wire pending, ontime, late, exec, consume;
+ // Cached values for input command
+ wire cached_req_wr, cached_req_rd;
+ wire [19:0] cached_req_addr;
+ wire [31:0] cached_req_data;
+ wire [3:0] cached_req_byte_en;
+ wire cached_req_has_time;
+ wire [63:0] cached_req_time;
+
+ axi_fifo_flop #(.WIDTH(1+1+20+32+4+1+64)) req_cache_i (
+ .clk(clk), .reset(rst), .clear(1'b0),
+ .i_tdata({s_ctrlport_req_wr, s_ctrlport_req_rd, s_ctrlport_req_addr, s_ctrlport_req_data,
+ s_ctrlport_req_byte_en, s_ctrlport_req_has_time, s_ctrlport_req_time}),
+ .i_tvalid(s_ctrlport_req_wr | s_ctrlport_req_rd), .i_tready(),
+ .o_tdata({cached_req_wr, cached_req_rd, cached_req_addr, cached_req_data,
+ cached_req_byte_en, cached_req_has_time, cached_req_time}),
+ .o_tvalid(pending), .o_tready(consume),
+ .occupied(), .space()
+ );
+
+ // Command is on time
+ assign ontime = cached_req_has_time && pending && time_now_stb &&
+ (cached_req_time[63:PRECISION_BITS] == time_now[63:PRECISION_BITS]);
+ // Command is late
+ assign late = cached_req_has_time && pending && time_now_stb &&
+ (cached_req_time[63:PRECISION_BITS] < time_now[63:PRECISION_BITS]);
+ // Logic to pass cmd forward
+ assign exec = pending && (!cached_req_has_time || ontime || (EXEC_LATE_CMDS && late));
+ assign consume = exec || late;
+
+ assign m_ctrlport_req_wr = cached_req_wr & exec;
+ assign m_ctrlport_req_rd = cached_req_rd & exec;
+ assign m_ctrlport_req_addr = cached_req_addr;
+ assign m_ctrlport_req_data = cached_req_data;
+ assign m_ctrlport_req_byte_en = cached_req_byte_en;
+
+ wire [1:0] resp_status = (late && !exec) ? AXIS_CTRL_STS_TSERR : m_ctrlport_resp_status;
+ axi_fifo_flop #(.WIDTH(2+32)) resp_cache_i (
+ .clk(clk), .reset(rst), .clear(1'b0),
+ .i_tdata({resp_status, m_ctrlport_resp_data}),
+ .i_tvalid(m_ctrlport_resp_ack || (late && !exec)), .i_tready(),
+ .o_tdata({s_ctrlport_resp_status, s_ctrlport_resp_data}),
+ .o_tvalid(s_ctrlport_resp_ack), .o_tready(s_ctrlport_resp_ack),
+ .occupied(), .space()
+ );
+
+endmodule // ctrlport_timer
+
diff --git a/fpga/usrp3/lib/rfnoc/utils/ctrlport_to_settings_bus.v b/fpga/usrp3/lib/rfnoc/utils/ctrlport_to_settings_bus.v
new file mode 100644
index 000000000..121b0ea40
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/ctrlport_to_settings_bus.v
@@ -0,0 +1,241 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: ctrlport_to_settings_bus
+//
+// Description:
+//
+// Converts CTRL port interface requests to a user register settings bus
+// access. This can be used to connect RFNoC block IP settings registers and
+// user read-back registers to a control port.
+//
+// There are a few key differences between control port and the settings bus
+// that need to be accounted for.
+//
+// * Control port uses byte address whereas the settings bus uses a
+// word address.
+// * Control port is 32-bit whereas the settings bus supports
+// 32-bit writes and 64-bit reads.
+// * The settings bus always does both a write and a read for each
+// transaction. If the intent is to read a register, then it writes the
+// address for the read to SR_RB_ADDR. If the intent is to write a
+// register, then the read result is ignored.
+//
+// This block handles these differences by allocating a 2048-byte address
+// space to each settings bus. Each word of the settings bus is treated like
+// a 64-bit word on an eight-byte boundary. To write to a settings register
+// N, simply write a 32-bit value to address N*8. To read read-back register
+// N, simply perform a 32-bit read from address N*8 followed by a 32-bit read
+// from address N*8+4 to get the full 64-bits. If only the lower 32-bits are
+// needed then it is not necessary to read the upper 32 bits. Note however,
+// that software must always read the lower 32-bits before trying to read the
+// upper 32-bits and that these reads should be atomic (no intervening reads
+// should occur).
+//
+// Parameters:
+//
+// NUM_PORTS : The number of settings buses you wish to connect
+//
+// SR_RB_ADDR : Address to use for the settings register that holds the
+// read-back address. Set to 124 to model register access to
+// user logic registers. Set to 127 to model access to internal
+// NoC shell registers.
+//
+// USE_TIME : When 0, timestamps are simply passed from ctrlport to
+// settings bus and the timestamp input is not used. When 1,
+// this block will wait until the indicated time to arrive on
+// the timestamp input before issuing the transaction on the
+// settings bus. In this case, the time must be provided on the
+// timestamp input.
+//
+
+module ctrlport_to_settings_bus #(
+ parameter NUM_PORTS = 1,
+ parameter SR_RB_ADDR = 124,
+ parameter USE_TIME = 0
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_rst,
+
+ //---------------------------------------------------------------------------
+ // CTRL Port Interface
+ //---------------------------------------------------------------------------
+
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+ input wire s_ctrlport_req_has_time,
+ input wire [63:0] s_ctrlport_req_time,
+ output reg s_ctrlport_resp_ack = 0,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ //---------------------------------------------------------------------------
+ // Settings Bus Interface
+ //---------------------------------------------------------------------------
+
+ output wire [NUM_PORTS*32-1:0] set_data,
+ output wire [ NUM_PORTS*8-1:0] set_addr,
+ output reg [ NUM_PORTS-1:0] set_stb = 0,
+ output wire [NUM_PORTS*64-1:0] set_time,
+ output wire [ NUM_PORTS-1:0] set_has_time,
+
+ input [NUM_PORTS-1:0] rb_stb,
+ output [NUM_PORTS*8-1:0] rb_addr,
+ input [NUM_PORTS*64-1:0] rb_data,
+
+ //---------------------------------------------------------------------------
+ // Timestamp
+ //---------------------------------------------------------------------------
+
+ // Current timestamp, synchronous to ctrlport_clk
+ input wire [63:0] timestamp
+);
+
+ localparam PORT_W = (NUM_PORTS > 1) ? $clog2(NUM_PORTS) : 1;
+
+ wire [PORT_W-1:0] port_num;
+ reg [PORT_W-1:0] port_num_reg;
+
+ wire msw_access;
+
+ reg [31:0] set_data_reg;
+ reg [ 7:0] set_addr_reg;
+ reg [63:0] set_time_reg;
+ reg set_has_time_reg;
+ reg [ 7:0] rb_addr_reg;
+
+ reg [31:0] upper_word;
+
+ // Extract the port index from the address (the bits above the lower 11 bits)
+ assign port_num = (NUM_PORTS > 1) ? s_ctrlport_req_addr[(PORT_W+11)-1:11] : 0;
+
+ // Determine if the upper word is being accessed
+ assign msw_access = s_ctrlport_req_addr[2];
+
+ localparam ST_IDLE = 0;
+ localparam ST_TIME_CHECK = 1;
+ localparam ST_STROBE_WAIT = 2;
+ localparam ST_WAIT_RESP = 3;
+
+ reg [2:0] state = ST_IDLE;
+
+
+ always @(posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_ack <= 0;
+ set_stb <= 0;
+ state <= ST_IDLE;
+ s_ctrlport_resp_data <= 32'hX;
+ set_addr_reg <= 8'hX;
+ rb_addr_reg <= 8'hX;
+ port_num_reg <= 8'hX;
+ upper_word <= 32'hX;
+ end else begin
+ // Default assignments
+ s_ctrlport_resp_ack <= 0;
+ set_stb <= 0;
+
+ case (state)
+ ST_IDLE : begin
+ if (s_ctrlport_req_rd && port_num < NUM_PORTS) begin
+ // Handle register reads (read-back registers)
+ if (msw_access) begin
+ // Reading the upper word always returns the cached upper-word value
+ // from the previous lower-word read.
+ s_ctrlport_resp_ack <= 1;
+ s_ctrlport_resp_data <= upper_word;
+ end else begin
+ // Handle register reads (read-back registers)
+ rb_addr_reg <= s_ctrlport_req_addr[10:3];
+
+ // Read-back of a user register on settings bus is always
+ // combined with a write to the SR_RB_ADDR address.
+ set_addr_reg <= SR_RB_ADDR;
+ set_data_reg <= 32'bX; // CtrlPort has no data in this case
+ set_time_reg <= s_ctrlport_req_time;
+ set_has_time_reg <= s_ctrlport_req_has_time;
+
+ // Save which port the read is for so that we only watch for
+ // acknowledgments from that port.
+ port_num_reg <= port_num;
+
+ if (USE_TIME) begin
+ state <= ST_TIME_CHECK;
+ end else begin
+ set_stb[port_num] <= 1;
+ state <= ST_STROBE_WAIT;
+ end
+ end
+
+ end else if (s_ctrlport_req_wr && port_num < NUM_PORTS) begin
+ // Handle register writes (settings registers)
+ set_addr_reg <= s_ctrlport_req_addr[10:3];
+ set_data_reg <= s_ctrlport_req_data;
+ set_time_reg <= s_ctrlport_req_time;
+ set_has_time_reg <= s_ctrlport_req_has_time;
+
+ // Save which port the write is for so that we only watch for
+ // acknowledgments from that port.
+ port_num_reg <= port_num;
+
+ if (USE_TIME) begin
+ state <= ST_TIME_CHECK;
+ end else begin
+ set_stb[port_num] <= 1;
+ state <= ST_STROBE_WAIT;
+ end
+ end
+ end
+
+ ST_TIME_CHECK : begin
+ // If the transaction is timed, wait until the time arrives before
+ // starting. This state is only reachable if USE_TIME is true.
+ if (set_has_time_reg) begin
+ if (timestamp >= set_time_reg) begin
+ set_stb[port_num_reg] <= 1;
+ state <= ST_STROBE_WAIT;
+ end
+ end else begin
+ set_stb[port_num_reg] <= 1;
+ state <= ST_STROBE_WAIT;
+ end
+ end
+
+ ST_STROBE_WAIT : begin
+ // Wait a cycle before checking for a response
+ state <= ST_WAIT_RESP;
+ end
+
+ ST_WAIT_RESP : begin
+ // Wait for read completion on settings bus, acknowledged by rb_stb.
+ // The read-back data will be ignored by ctrlport if this is a write.
+ upper_word <= rb_data[(64*port_num_reg + 32) +: 32];
+ s_ctrlport_resp_data <= rb_data[(64*port_num_reg + 0) +: 32];
+ if (rb_stb[port_num_reg] == 1) begin
+ s_ctrlport_resp_ack <= 1;
+ state <= ST_IDLE;
+ end
+ end
+
+ endcase
+ end
+ end
+
+
+ genvar i;
+ generate
+ for (i = 0; i < NUM_PORTS; i = i+1) begin : gen_settings_bus
+ // Drive all settings buses with the same values, except the strobe
+ assign rb_addr [ 8*i +: 8] = rb_addr_reg;
+ assign set_data [32*i +: 32] = set_data_reg;
+ assign set_addr [ 8*i +: 8] = set_addr_reg;
+ assign set_time [64*i +: 64] = set_time_reg;
+ assign set_has_time [ i] = set_has_time_reg;
+ end
+ endgenerate
+
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/utils/noc_shell_generic_ctrlport_pyld_chdr.v b/fpga/usrp3/lib/rfnoc/utils/noc_shell_generic_ctrlport_pyld_chdr.v
new file mode 100644
index 000000000..3a0e7fea7
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/noc_shell_generic_ctrlport_pyld_chdr.v
@@ -0,0 +1,273 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: noc_shell_generic_ctrlport_pyld_chdr
+// Description:
+//
+// Parameters:
+//
+// Signals:
+
+module noc_shell_generic_ctrlport_pyld_chdr #(
+ parameter [31:0] NOC_ID = 32'h0,
+ parameter [9:0] THIS_PORTID = 10'd0,
+ parameter CHDR_W = 64,
+ parameter [5:0] CTRL_FIFOSIZE = 0,
+ parameter [0:0] CTRLPORT_SLV_EN = 1,
+ parameter [5:0] NUM_DATA_I = 0,
+ parameter [5:0] NUM_DATA_O = 0,
+ parameter ITEM_W = 32,
+ parameter NIPC = 2,
+ parameter [5:0] MTU = 0,
+ parameter CTXT_FIFOSIZE = 1,
+ parameter PYLD_FIFOSIZE = 1
+)(
+ // Framework Interface
+ //------------------------------------------------------------
+ // RFNoC Framework Clocks and Resets
+ input wire rfnoc_chdr_clk,
+ output wire rfnoc_chdr_rst,
+ input wire rfnoc_ctrl_clk,
+ output wire rfnoc_ctrl_rst,
+ // RFNoC Backend Interface
+ input wire [511:0] rfnoc_core_config,
+ output wire [511:0] rfnoc_core_status,
+ // CHDR Input Ports (from framework)
+ input wire [(CHDR_W*NUM_DATA_I)-1:0] s_rfnoc_chdr_tdata,
+ input wire [NUM_DATA_I-1:0] s_rfnoc_chdr_tlast,
+ input wire [NUM_DATA_I-1:0] s_rfnoc_chdr_tvalid,
+ output wire [NUM_DATA_I-1:0] s_rfnoc_chdr_tready,
+ // CHDR Output Ports (to framework)
+ output wire [(CHDR_W*NUM_DATA_O)-1:0] m_rfnoc_chdr_tdata,
+ output wire [NUM_DATA_O-1:0] m_rfnoc_chdr_tlast,
+ output wire [NUM_DATA_O-1:0] m_rfnoc_chdr_tvalid,
+ input wire [NUM_DATA_O-1:0] m_rfnoc_chdr_tready,
+ // AXIS-Ctrl Input Port (from framework)
+ 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,
+ // AXIS-Ctrl Output Port (to framework)
+ 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,
+
+ // Client Interface
+ //------------------------------------------------------------
+ // 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,
+ 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,
+ output wire s_ctrlport_resp_ack,
+ output wire [1:0] s_ctrlport_resp_status,
+ output wire [31:0] s_ctrlport_resp_data,
+ // Payload stream out (to user logic)
+ output wire [(ITEM_W*NIPC*NUM_DATA_I)-1:0] m_axis_payload_tdata,
+ output wire [(NIPC*NUM_DATA_I)-1:0] m_axis_payload_tkeep,
+ output wire [NUM_DATA_I-1:0] m_axis_payload_tlast,
+ output wire [NUM_DATA_I-1:0] m_axis_payload_tvalid,
+ input wire [NUM_DATA_I-1:0] m_axis_payload_tready,
+ // Context stream out (to user logic)
+ output wire [(CHDR_W*NUM_DATA_I)-1:0] m_axis_context_tdata,
+ output wire [(4*NUM_DATA_I)-1:0] m_axis_context_tuser,
+ output wire [NUM_DATA_I-1:0] m_axis_context_tlast,
+ output wire [NUM_DATA_I-1:0] m_axis_context_tvalid,
+ input wire [NUM_DATA_I-1:0] m_axis_context_tready,
+ // Payload stream in (from user logic)
+ input wire [(ITEM_W*NIPC*NUM_DATA_O)-1:0] s_axis_payload_tdata,
+ input wire [(NIPC*NUM_DATA_O)-1:0] s_axis_payload_tkeep,
+ input wire [NUM_DATA_O-1:0] s_axis_payload_tlast,
+ input wire [NUM_DATA_O-1:0] s_axis_payload_tvalid,
+ output wire [NUM_DATA_O-1:0] s_axis_payload_tready,
+ // Context stream in (from user logic)
+ input wire [(CHDR_W*NUM_DATA_O)-1:0] s_axis_context_tdata,
+ input wire [(4*NUM_DATA_O)-1:0] s_axis_context_tuser,
+ input wire [NUM_DATA_O-1:0] s_axis_context_tlast,
+ input wire [NUM_DATA_O-1:0] s_axis_context_tvalid,
+ output wire [NUM_DATA_O-1:0] s_axis_context_tready
+);
+
+ // ---------------------------------------------------
+ // Backend Interface
+ // ---------------------------------------------------
+ wire data_i_flush_en;
+ wire [31:0] data_i_flush_timeout;
+ wire [63:0] data_i_flush_active;
+ wire [63:0] data_i_flush_done;
+ wire data_o_flush_en;
+ wire [31:0] data_o_flush_timeout;
+ wire [63:0] data_o_flush_active;
+ wire [63:0] data_o_flush_done;
+
+ backend_iface #(
+ .NOC_ID (NOC_ID ),
+ .NUM_DATA_I (NUM_DATA_I ),
+ .NUM_DATA_O (NUM_DATA_O ),
+ .CTRL_FIFOSIZE (CTRL_FIFOSIZE),
+ .MTU (MTU )
+ ) backend_iface_i (
+ .rfnoc_chdr_clk (rfnoc_chdr_clk ),
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk ),
+ .rfnoc_core_config (rfnoc_core_config ),
+ .rfnoc_core_status (rfnoc_core_status ),
+ .rfnoc_chdr_rst (rfnoc_chdr_rst ),
+ .rfnoc_ctrl_rst (rfnoc_ctrl_rst ),
+ .data_i_flush_en (data_i_flush_en ),
+ .data_i_flush_timeout (data_i_flush_timeout),
+ .data_i_flush_active (data_i_flush_active ),
+ .data_i_flush_done (data_i_flush_done ),
+ .data_o_flush_en (data_o_flush_en ),
+ .data_o_flush_timeout (data_o_flush_timeout),
+ .data_o_flush_active (data_o_flush_active ),
+ .data_o_flush_done (data_o_flush_done )
+ );
+
+ // ---------------------------------------------------
+ // Control Path
+ // ---------------------------------------------------
+
+ ctrlport_endpoint #(
+ .THIS_PORTID (THIS_PORTID ),
+ .SYNC_CLKS (0 ),
+ .AXIS_CTRL_MST_EN (CTRLPORT_SLV_EN),
+ .AXIS_CTRL_SLV_EN (1 ),
+ .SLAVE_FIFO_SIZE (CTRL_FIFOSIZE )
+ ) ctrlport_ep_i (
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk ),
+ .rfnoc_ctrl_rst (rfnoc_ctrl_rst ),
+ .ctrlport_clk (rfnoc_chdr_clk ),
+ .ctrlport_rst (rfnoc_chdr_rst ),
+ .s_rfnoc_ctrl_tdata (s_rfnoc_ctrl_tdata ),
+ .s_rfnoc_ctrl_tlast (s_rfnoc_ctrl_tlast ),
+ .s_rfnoc_ctrl_tvalid (s_rfnoc_ctrl_tvalid ),
+ .s_rfnoc_ctrl_tready (s_rfnoc_ctrl_tready ),
+ .m_rfnoc_ctrl_tdata (m_rfnoc_ctrl_tdata ),
+ .m_rfnoc_ctrl_tlast (m_rfnoc_ctrl_tlast ),
+ .m_rfnoc_ctrl_tvalid (m_rfnoc_ctrl_tvalid ),
+ .m_rfnoc_ctrl_tready (m_rfnoc_ctrl_tready ),
+ .m_ctrlport_req_wr (m_ctrlport_req_wr ),
+ .m_ctrlport_req_rd (m_ctrlport_req_rd ),
+ .m_ctrlport_req_addr (m_ctrlport_req_addr ),
+ .m_ctrlport_req_data (m_ctrlport_req_data ),
+ .m_ctrlport_req_byte_en (m_ctrlport_req_byte_en ),
+ .m_ctrlport_req_has_time (m_ctrlport_req_has_time ),
+ .m_ctrlport_req_time (m_ctrlport_req_time ),
+ .m_ctrlport_resp_ack (m_ctrlport_resp_ack ),
+ .m_ctrlport_resp_status (m_ctrlport_resp_status ),
+ .m_ctrlport_resp_data (m_ctrlport_resp_data ),
+ .s_ctrlport_req_wr (s_ctrlport_req_wr ),
+ .s_ctrlport_req_rd (s_ctrlport_req_rd ),
+ .s_ctrlport_req_addr (s_ctrlport_req_addr ),
+ .s_ctrlport_req_portid (s_ctrlport_req_portid ),
+ .s_ctrlport_req_rem_epid (s_ctrlport_req_rem_epid ),
+ .s_ctrlport_req_rem_portid(s_ctrlport_req_rem_portid),
+ .s_ctrlport_req_data (s_ctrlport_req_data ),
+ .s_ctrlport_req_byte_en (s_ctrlport_req_byte_en ),
+ .s_ctrlport_req_has_time (s_ctrlport_req_has_time ),
+ .s_ctrlport_req_time (s_ctrlport_req_time ),
+ .s_ctrlport_resp_ack (s_ctrlport_resp_ack ),
+ .s_ctrlport_resp_status (s_ctrlport_resp_status ),
+ .s_ctrlport_resp_data (s_ctrlport_resp_data )
+ );
+
+ // ---------------------------------------------------
+ // Data Path
+ // ---------------------------------------------------
+
+ genvar i;
+ generate
+ for (i = 0; i < NUM_DATA_I; i = i + 1) begin: in
+ chdr_to_axis_pyld_ctxt #(
+ .CHDR_W (CHDR_W ),
+ .ITEM_W (ITEM_W ),
+ .NIPC (NIPC ),
+ .SYNC_CLKS (0 ),
+ .CONTEXT_FIFO_SIZE (CTXT_FIFOSIZE),
+ .PAYLOAD_FIFO_SIZE (PYLD_FIFOSIZE),
+ .CONTEXT_PREFETCH_EN (1 )
+ ) chdr2raw_i (
+ .axis_chdr_clk (rfnoc_chdr_clk ),
+ .axis_chdr_rst (rfnoc_chdr_rst ),
+ .axis_data_clk (rfnoc_chdr_clk ),
+ .axis_data_rst (rfnoc_chdr_rst ),
+ .s_axis_chdr_tdata (s_rfnoc_chdr_tdata [(i*CHDR_W)+:CHDR_W] ),
+ .s_axis_chdr_tlast (s_rfnoc_chdr_tlast [i] ),
+ .s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid [i] ),
+ .s_axis_chdr_tready (s_rfnoc_chdr_tready [i] ),
+ .m_axis_payload_tdata (m_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]),
+ .m_axis_payload_tkeep (m_axis_payload_tkeep [(i*NIPC)+:NIPC] ),
+ .m_axis_payload_tlast (m_axis_payload_tlast [i] ),
+ .m_axis_payload_tvalid(m_axis_payload_tvalid[i] ),
+ .m_axis_payload_tready(m_axis_payload_tready[i] ),
+ .m_axis_context_tdata (m_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)] ),
+ .m_axis_context_tuser (m_axis_context_tuser [(i*4)+:4] ),
+ .m_axis_context_tlast (m_axis_context_tlast [i] ),
+ .m_axis_context_tvalid(m_axis_context_tvalid[i] ),
+ .m_axis_context_tready(m_axis_context_tready[i] ),
+ .flush_en (data_i_flush_en ),
+ .flush_timeout (data_i_flush_timeout ),
+ .flush_active (data_i_flush_active [i] ),
+ .flush_done (data_i_flush_done [i] )
+ );
+ end
+
+ for (i = 0; i < NUM_DATA_O; i = i + 1) begin: out
+ axis_pyld_ctxt_to_chdr #(
+ .CHDR_W (CHDR_W ),
+ .ITEM_W (ITEM_W ),
+ .NIPC (NIPC ),
+ .SYNC_CLKS (0 ),
+ .CONTEXT_FIFO_SIZE (CTXT_FIFOSIZE ),
+ .PAYLOAD_FIFO_SIZE (PYLD_FIFOSIZE ),
+ .CONTEXT_PREFETCH_EN (1 ),
+ .MTU (MTU )
+ ) raw2chdr_i (
+ .axis_chdr_clk (rfnoc_chdr_clk ),
+ .axis_chdr_rst (rfnoc_chdr_rst ),
+ .axis_data_clk (rfnoc_chdr_clk ),
+ .axis_data_rst (rfnoc_chdr_rst ),
+ .m_axis_chdr_tdata (m_rfnoc_chdr_tdata [(i*CHDR_W)+:CHDR_W] ),
+ .m_axis_chdr_tlast (m_rfnoc_chdr_tlast [i] ),
+ .m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid [i] ),
+ .m_axis_chdr_tready (m_rfnoc_chdr_tready [i] ),
+ .s_axis_payload_tdata (s_axis_payload_tdata [(i*ITEM_W*NIPC)+:(ITEM_W*NIPC)]),
+ .s_axis_payload_tkeep (s_axis_payload_tkeep [(i*NIPC)+:NIPC] ),
+ .s_axis_payload_tlast (s_axis_payload_tlast [i] ),
+ .s_axis_payload_tvalid(s_axis_payload_tvalid[i] ),
+ .s_axis_payload_tready(s_axis_payload_tready[i] ),
+ .s_axis_context_tdata (s_axis_context_tdata [(i*CHDR_W)+:(CHDR_W)] ),
+ .s_axis_context_tuser (s_axis_context_tuser [(i*4)+:4] ),
+ .s_axis_context_tlast (s_axis_context_tlast [i] ),
+ .s_axis_context_tvalid(s_axis_context_tvalid[i] ),
+ .s_axis_context_tready(s_axis_context_tready[i] ),
+ .framer_errors ( ),
+ .flush_en (data_o_flush_en ),
+ .flush_timeout (data_o_flush_timeout ),
+ .flush_active (data_o_flush_active [i] ),
+ .flush_done (data_o_flush_done [i] )
+ );
+ end
+ endgenerate
+
+endmodule // noc_shell_generic_ctrlport_raw
diff --git a/fpga/usrp3/lib/rfnoc/utils/timekeeper.v b/fpga/usrp3/lib/rfnoc/utils/timekeeper.v
new file mode 100644
index 000000000..404f45758
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/utils/timekeeper.v
@@ -0,0 +1,279 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: timekeeper
+//
+// Description: Timekeeper for RFNoC blocks. This block contains a 64-bit
+// counter to represent the current time in terms of sample clock cycles. The
+// counter can be updated and synchronized using the pps input.
+//
+// WARNING: All register larger than a single 32-bit word should be read and
+// written least significant word first to guarantee coherency.
+//
+
+module timekeeper #(
+ parameter BASE_ADDR = 'h00,
+ parameter TIME_INCREMENT = 1 // Amount by which to increment time on each sample clock cycle
+) (
+ input wire tb_clk, // Time-base clock
+ input wire tb_rst, // Time-base reset in tb_clk domain
+
+ //---------------------------------------------------------------------------
+ // Control Interface
+ //---------------------------------------------------------------------------
+
+ input wire s_ctrlport_clk,
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+ output wire s_ctrlport_resp_ack,
+ output wire [31:0] s_ctrlport_resp_data,
+
+ //---------------------------------------------------------------------------
+ // Time
+ //---------------------------------------------------------------------------
+
+ input wire sample_rx_stb, // Sample Rx strobe (data valid indicator)
+ input wire pps, // Pulse per second input
+ output reg [63:0] tb_timestamp, // 64-bit global timestamp synchronous to tb_clk
+ output reg [63:0] tb_timestamp_last_pps, // 64-bit global timestamp synchronous to tb_clk
+ output reg [63:0] tb_period_ns_q32 // Time Period of time-base in nanoseconds
+);
+
+ //---------------------------------------------------------------------------
+ // Register Logic
+ //---------------------------------------------------------------------------
+
+ reg set_time_pps;
+ reg set_time_now;
+ reg new_time_ctrl;
+ reg [63:0] time_at_next_event; // Time to load at next timed event
+
+ reg [31:0] tb_timestamp_hi; // Holding register for reading tb_timestamp
+ reg [31:0] time_at_next_event_lo; // Holding register for writing time_at_next_event
+ reg [31:0] time_at_next_event_hi; // Holding register for reading time_at_next_event
+ reg [31:0] tb_timestamp_last_pps_hi; // Holding register for reading tb_timestamp_last_pps
+
+ wire s_ctrlport_req_wr_tb;
+ wire s_ctrlport_req_rd_tb;
+ wire [19:0] s_ctrlport_req_addr_tb;
+ wire [31:0] s_ctrlport_req_data_tb;
+ reg s_ctrlport_resp_ack_tb;
+ reg [31:0] s_ctrlport_resp_data_tb;
+
+ // Clock crossing from ctrlport_clk to tb_clk domain
+
+ ctrlport_clk_cross ctrlport_clk_cross_tb_i (
+ .rst (tb_rst),
+ .s_ctrlport_clk (s_ctrlport_clk),
+ .s_ctrlport_req_wr (s_ctrlport_req_wr),
+ .s_ctrlport_req_rd (s_ctrlport_req_rd),
+ .s_ctrlport_req_addr (s_ctrlport_req_addr),
+ .s_ctrlport_req_portid (),
+ .s_ctrlport_req_rem_epid (),
+ .s_ctrlport_req_rem_portid (),
+ .s_ctrlport_req_data (s_ctrlport_req_data),
+ .s_ctrlport_req_byte_en (),
+ .s_ctrlport_req_has_time (),
+ .s_ctrlport_req_time (),
+ .s_ctrlport_resp_ack (s_ctrlport_resp_ack),
+ .s_ctrlport_resp_status (),
+ .s_ctrlport_resp_data (s_ctrlport_resp_data),
+ .m_ctrlport_clk (tb_clk),
+ .m_ctrlport_req_wr (s_ctrlport_req_wr_tb),
+ .m_ctrlport_req_rd (s_ctrlport_req_rd_tb),
+ .m_ctrlport_req_addr (s_ctrlport_req_addr_tb),
+ .m_ctrlport_req_portid (),
+ .m_ctrlport_req_rem_epid (),
+ .m_ctrlport_req_rem_portid (),
+ .m_ctrlport_req_data (s_ctrlport_req_data_tb),
+ .m_ctrlport_req_byte_en (),
+ .m_ctrlport_req_has_time (),
+ .m_ctrlport_req_time (),
+ .m_ctrlport_resp_ack (s_ctrlport_resp_ack_tb),
+ .m_ctrlport_resp_status (),
+ .m_ctrlport_resp_data (s_ctrlport_resp_data_tb)
+ );
+
+ //---------------------------------------------------------------------------
+ // Timekeeper Register Offsets
+ //---------------------------------------------------------------------------
+
+ localparam REG_TIME_NOW_LO = 'h00; // Current time count (low word)
+ localparam REG_TIME_NOW_HI = 'h04; // Current time count (high word)
+ localparam REG_TIME_EVENT_LO = 'h08; // Time for next event (low word)
+ localparam REG_TIME_EVENT_HI = 'h0C; // Time for next event (high word)
+ localparam REG_TIME_CTRL = 'h10; // Time control word
+ localparam REG_TIME_LAST_PPS_LO = 'h14; // Time of last PPS pulse edge (low word)
+ localparam REG_TIME_LAST_PPS_HI = 'h18; // Time of last PPS pulse edge (high word)
+ localparam REG_TIME_BASE_PERIOD_LO = 'h1C; // Time Period in nanoseconds (low word)
+ localparam REG_TIME_BASE_PERIOD_HI = 'h20; // Time Period in nanoseconds (high word)
+
+ // REG_TIME_CTRL bit fields
+ localparam TIME_NOW_POS = 0;
+ localparam TIME_PPS_POS = 1;
+
+ always @(posedge tb_clk) begin
+ if (tb_rst) begin
+ s_ctrlport_resp_ack_tb <= 0;
+ s_ctrlport_resp_data_tb <= 0;
+ new_time_ctrl <= 0;
+ set_time_pps <= 0;
+ set_time_now <= 0;
+ end else begin
+ // Default assignments
+ s_ctrlport_resp_ack_tb <= 0;
+ s_ctrlport_resp_data_tb <= 0;
+ new_time_ctrl <= 0;
+
+ // Handle register writes
+ if (s_ctrlport_req_wr_tb) begin
+ case (s_ctrlport_req_addr_tb)
+ BASE_ADDR + REG_TIME_EVENT_LO: begin
+ time_at_next_event_lo <= s_ctrlport_req_data_tb;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_EVENT_HI: begin
+ time_at_next_event[31: 0] <= time_at_next_event_lo;
+ time_at_next_event[63:32] <= s_ctrlport_req_data_tb;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_CTRL: begin
+ set_time_pps <= s_ctrlport_req_data_tb[TIME_PPS_POS];
+ set_time_now <= s_ctrlport_req_data_tb[TIME_NOW_POS];
+ new_time_ctrl <= 1;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_BASE_PERIOD_LO: begin
+ tb_period_ns_q32[31:0] <= s_ctrlport_req_data_tb;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_BASE_PERIOD_HI: begin
+ tb_period_ns_q32[63:32] <= s_ctrlport_req_data_tb;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ endcase
+ end
+
+ // Handle register reads
+ if (s_ctrlport_req_rd_tb) begin
+ case (s_ctrlport_req_addr_tb)
+ BASE_ADDR + REG_TIME_NOW_LO: begin
+ s_ctrlport_resp_data_tb <= tb_timestamp[31:0];
+ tb_timestamp_hi <= tb_timestamp[63:32];
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_NOW_HI: begin
+ s_ctrlport_resp_data_tb <= tb_timestamp_hi;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_EVENT_LO: begin
+ s_ctrlport_resp_data_tb <= time_at_next_event[31:0];
+ time_at_next_event_hi <= time_at_next_event[63:32];
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_EVENT_HI: begin
+ s_ctrlport_resp_data_tb <= time_at_next_event_hi;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_CTRL: begin
+ s_ctrlport_resp_data_tb <= 0;
+ s_ctrlport_resp_data_tb[TIME_PPS_POS] <= set_time_pps;
+ s_ctrlport_resp_data_tb[TIME_NOW_POS] <= set_time_now;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_LAST_PPS_LO: begin
+ s_ctrlport_resp_data_tb <= tb_timestamp_last_pps[31:0];
+ tb_timestamp_last_pps_hi <= tb_timestamp_last_pps[63:32];
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_LAST_PPS_HI: begin
+ s_ctrlport_resp_data_tb <= tb_timestamp_last_pps_hi;
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_BASE_PERIOD_LO: begin
+ s_ctrlport_resp_data_tb <= tb_period_ns_q32[31:0];
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ BASE_ADDR + REG_TIME_BASE_PERIOD_HI: begin
+ s_ctrlport_resp_data_tb <= tb_period_ns_q32[63:32];
+ s_ctrlport_resp_ack_tb <= 1;
+ end
+ endcase
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Pulse Per Second
+ //---------------------------------------------------------------------------
+
+ reg pps_del;
+ reg pps_edge;
+
+ always @(posedge tb_clk) begin
+ if (tb_rst) begin
+ pps_del <= 0;
+ pps_edge <= 0;
+ end else begin
+ pps_del <= pps;
+ pps_edge<= pps_del & ~pps;
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Time Tracker
+ //---------------------------------------------------------------------------
+
+ reg time_event_armed; // Boolean to indicate if we're expecting a timed event
+
+ wire time_event =
+ time_event_armed && (
+ set_time_now || (set_time_pps && pps_edge)
+ );
+
+ always @(posedge tb_clk) begin
+ if (tb_rst) begin
+ tb_timestamp <= 0;
+ time_event_armed <= 0;
+ end else begin
+ if (time_event) begin
+ // Load the timing info configured prior to the event
+ time_event_armed <= 0;
+ tb_timestamp <= time_at_next_event;
+ end else if (sample_rx_stb) begin
+ // Update time for each sample word received
+ tb_timestamp <= tb_timestamp + TIME_INCREMENT;
+ end
+
+ if (new_time_ctrl) begin
+ // Indicate that we're expecting a timed event because the time control
+ // register was updated.
+ time_event_armed <= 1;
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // PPS Tracker
+ //---------------------------------------------------------------------------
+
+ always @(posedge tb_clk) begin
+ if (tb_rst) begin
+ tb_timestamp_last_pps <= 64'h0;
+ end else if (pps_edge) begin
+ if (time_event) begin
+ tb_timestamp_last_pps <= time_at_next_event;
+ end else begin
+ tb_timestamp_last_pps <= tb_timestamp + TIME_INCREMENT;
+ end
+ end
+ end
+
+endmodule