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  | 
