diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/utils')
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 |