aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/utils
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-01-23 16:10:22 -0800
committerMartin Braun <martin.braun@ettus.com>2020-01-28 09:35:36 -0800
commitbafa9d95453387814ef25e6b6256ba8db2df612f (patch)
tree39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/lib/rfnoc/utils
parent3075b981503002df3115d5f1d0b97d2619ba30f2 (diff)
downloaduhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.gz
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.bz2
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.zip
Merge FPGA repository back into UHD repository
The FPGA codebase was removed from the UHD repository in 2014 to reduce the size of the repository. However, over the last half-decade, the split between the repositories has proven more burdensome than it has been helpful. By merging the FPGA code back, it will be possible to create atomic commits that touch both FPGA and UHD codebases. Continuous integration testing is also simplified by merging the repositories, because it was previously difficult to automatically derive the correct UHD branch when testing a feature branch on the FPGA repository. This commit also updates the license files and paths therein. We are therefore merging the repositories again. Future development for FPGA code will happen in the same repository as the UHD host code and MPM code. == Original Codebase and Rebasing == The original FPGA repository will be hosted for the foreseeable future at its original local location: https://github.com/EttusResearch/fpga/ It can be used for bisecting, reference, and a more detailed history. The final commit from said repository to be merged here is 05003794e2da61cabf64dd278c45685a7abad7ec. This commit is tagged as v4.0.0.0-pre-uhd-merge. If you have changes in the FPGA repository that you want to rebase onto the UHD repository, simply run the following commands: - Create a directory to store patches (this should be an empty directory): mkdir ~/patches - Now make sure that your FPGA codebase is based on the same state as the code that was merged: cd src/fpga # Or wherever your FPGA code is stored git rebase v4.0.0.0-pre-uhd-merge Note: The rebase command may look slightly different depending on what exactly you're trying to rebase. - Create a patch set for your changes versus v4.0.0.0-pre-uhd-merge: git format-patch v4.0.0.0-pre-uhd-merge -o ~/patches Note: Make sure that only patches are stored in your output directory. It should otherwise be empty. Make sure that you picked the correct range of commits, and only commits you wanted to rebase were exported as patch files. - Go to the UHD repository and apply the patches: cd src/uhd # Or wherever your UHD repository is stored git am --directory fpga ~/patches/* rm -rf ~/patches # This is for cleanup == Contributors == The following people have contributed mainly to these files (this list is not complete): Co-authored-by: Alex Williams <alex.williams@ni.com> Co-authored-by: Andrej Rode <andrej.rode@ettus.com> Co-authored-by: Ashish Chaudhari <ashish@ettus.com> Co-authored-by: Ben Hilburn <ben.hilburn@ettus.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Daniel Jepson <daniel.jepson@ni.com> Co-authored-by: Derek Kozel <derek.kozel@ettus.com> Co-authored-by: EJ Kreinar <ej@he360.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Ian Buckley <ian.buckley@gmail.com> Co-authored-by: Jörg Hofrichter <joerg.hofrichter@ni.com> Co-authored-by: Jon Kiser <jon.kiser@ni.com> Co-authored-by: Josh Blum <josh@joshknows.com> Co-authored-by: Jonathon Pendlum <jonathan.pendlum@ettus.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Matt Ettus <matt@ettus.com> Co-authored-by: Michael West <michael.west@ettus.com> Co-authored-by: Moritz Fischer <moritz.fischer@ettus.com> Co-authored-by: Nick Foster <nick@ettus.com> Co-authored-by: Nicolas Cuervo <nicolas.cuervo@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Paul David <paul.david@ettus.com> Co-authored-by: Ryan Marlow <ryan.marlow@ettus.com> Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com> Co-authored-by: Sylvain Munaut <tnt@246tNt.com> Co-authored-by: Trung Tran <trung.tran@ettus.com> Co-authored-by: Vidush Vishwanath <vidush.vishwanath@ettus.com> Co-authored-by: Wade Fife <wade.fife@ettus.com>
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