diff options
author | Wade Fife <wade.fife@ettus.com> | 2020-06-19 15:40:12 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-07-30 12:51:41 -0500 |
commit | 1e94f85b8bafc3f9acab7ef35d2675fa7e61f6f4 (patch) | |
tree | 12ba30a59c8057e355971797d5cc7bf6910f520b | |
parent | b0b3849a18e1f2d3cb255a507b01ac5e7a9416a0 (diff) | |
download | uhd-1e94f85b8bafc3f9acab7ef35d2675fa7e61f6f4.tar.gz uhd-1e94f85b8bafc3f9acab7ef35d2675fa7e61f6f4.tar.bz2 uhd-1e94f85b8bafc3f9acab7ef35d2675fa7e61f6f4.zip |
fpga: rfnoc: Add Signal Generator RFNoC block
13 files changed, 1980 insertions, 18 deletions
diff --git a/fpga/usrp3/lib/axi/axis_packetize.v b/fpga/usrp3/lib/axi/axis_packetize.v index 6ee018d9a..bb1297e5c 100644 --- a/fpga/usrp3/lib/axi/axis_packetize.v +++ b/fpga/usrp3/lib/axi/axis_packetize.v @@ -69,7 +69,7 @@ module axis_packetize #( reg [SIZE_W-1:0] word_count = 0; // Count of output words reg [SIZE_W-1:0] current_size = DEFAULT_SIZE; // Current packet size - reg gating = 1'b1; // Indicate if output is blocked + reg gating = 1'b0; // Indicate if output is blocked reg mid_packet = 1'b0; // Indicate if we're in the middle of a packet //--------------------------------------------------------------------------- @@ -80,27 +80,27 @@ module axis_packetize #( always @(posedge clk) begin if (rst) begin - start_of_packet <= 1; - word_count <= 0; + start_of_packet <= 1'b1; current_size <= DEFAULT_SIZE; - o_tlast <= 0; + word_count <= 0; + o_tlast <= (DEFAULT_SIZE == 1); end else begin if (gating) begin // Wait until we're enabled. Setup for the start of the next packet. - start_of_packet <= 1; + start_of_packet <= 1'b1; current_size <= size; - word_count <= 0; + word_count <= size; o_tlast <= (size == 1); end else if (o_tvalid && o_tready) begin start_of_packet <= 1'b0; - word_count <= word_count + 1; + word_count <= word_count - 1; if (o_tlast) begin // This is the last sample, so restart everything for a new packet. - o_tlast <= (size == 1); - current_size <= size; - word_count <= 0; start_of_packet <= 1'b1; - end else if (word_count == current_size-2) begin + current_size <= size; + word_count <= size; + o_tlast <= (size == 1); + end else if (word_count == 2) begin // This is the second to last sample, so we assert tlast for the // last sample. o_tlast <= 1'b1; @@ -109,7 +109,7 @@ module axis_packetize #( // We're waiting for the start of the next packet. Keep checking the // size input until the next packet starts. current_size <= size; - word_count <= 0; + word_count <= size; o_tlast <= (size == 1); end end @@ -119,13 +119,11 @@ module axis_packetize #( // Handshake Monitor //--------------------------------------------------------------------------- - // We start out gated to allow a clock cycle for the length to be loaded. - // Monitor the state of the handshake so we know when it's OK to // enable/disable data transfer. always @(posedge clk) begin if (rst) begin - gating = 1'b1; + gating = 1'b0; mid_packet = 1'b0; end else begin // Keep track of if we are in the middle of a packet or not. Note that @@ -142,8 +140,9 @@ module axis_packetize #( // We can stop gating any time if (!gate) gating <= 0; end else begin - // Only start gating between packets or at the end of a packet - if ((mid_packet && !o_tvalid) || (o_tvalid && o_tready && o_tlast)) begin + // Only start gating between packets when the output is idle, or after + // the output transfer completes at the end of packet. + if ((!mid_packet && !o_tvalid) || (o_tvalid && o_tready && o_tlast)) begin gating <= gate; end end @@ -154,6 +153,11 @@ module axis_packetize #( // Data Pass-Through //--------------------------------------------------------------------------- + // Note that "gating" only asserts when a transfer completes at the end of a + // packet, or between packets when the output is idle. This ensures that + // o_tvalid won't deassert during a transfer and cause a handshake protocol + // violation. + assign o_tdata = i_tdata; assign o_tvalid = i_tvalid && !gating; assign i_tready = FLUSH ? (o_tready || gating) : (o_tready && !gating); diff --git a/fpga/usrp3/lib/control/axi_setting_reg.v b/fpga/usrp3/lib/control/axi_setting_reg.v index 9d419ec32..c231540aa 100644 --- a/fpga/usrp3/lib/control/axi_setting_reg.v +++ b/fpga/usrp3/lib/control/axi_setting_reg.v @@ -40,7 +40,7 @@ module axi_setting_reg #( reg init; reg [WIDTH-1:0] o_tdata_int; - reg o_tlast_int, o_tvalid_int; + reg o_tlast_int, o_tvalid_int = VALID_AT_RESET; wire o_tready_int; always @(posedge clk) begin diff --git a/fpga/usrp3/lib/rfnoc/Makefile.srcs b/fpga/usrp3/lib/rfnoc/Makefile.srcs index cb97542ce..d6e079f67 100644 --- a/fpga/usrp3/lib/rfnoc/Makefile.srcs +++ b/fpga/usrp3/lib/rfnoc/Makefile.srcs @@ -102,6 +102,7 @@ ddc.v \ duc.v \ cic_decimate.v \ cic_interpolate.v \ +sine_tone.v \ axi_fir_filter.v \ fir_filter_slice.v \ axi_fir_filter_dec.v \ diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/Makefile new file mode 100644 index 000000000..8fffd1c2e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/Makefile @@ -0,0 +1,49 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir. Note: +# UHD_FPGA_DIR must be passed into this Makefile. +BASE_DIR = ../../../../top +# Include viv_sim_preample after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its +# dependencies. +include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs +include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs +include $(LIB_IP_DIR)/cordic_rotator/Makefile.inc +include Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +$(LIB_IP_CORDIC_ROTATOR_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_siggen_all_tb glbl +SIM_SRCS = \ +$(abspath $(IP_BUILD_DIR)/cordic_rotator/sim/cordic_rotator.vhd) \ +$(VIVADO_PATH)/data/verilog/src/glbl.v \ +$(abspath rfnoc_block_siggen_tb.sv) \ +$(abspath rfnoc_block_siggen_all_tb.sv) \ + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/Makefile.srcs new file mode 100644 index 000000000..336cc8cfa --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/Makefile.srcs @@ -0,0 +1,24 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Block Sources +################################################## +# Here, list all the files that are necessary to synthesize this block. Don't +# include testbenches! +# Make sure that the source files are nicely detectable by a regex. Best to put +# one on each line. +# The first argument to addprefix is the current path to this Makefile, so the +# path list is always absolute, regardless of from where we're including or +# calling this file. RFNOC_OOT_SRCS needs to be a simply expanded variable +# (not a recursively expanded variable), and we take care of that in the build +# infrastructure. +RFNOC_OOT_SRCS += $(addprefix $(dir $(abspath $(lastword $(MAKEFILE_LIST)))), \ +noc_shell_siggen.v \ +rfnoc_siggen_core.v \ +rfnoc_block_siggen_regs.vh \ +rfnoc_block_siggen.v \ +) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/noc_shell_siggen.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/noc_shell_siggen.v new file mode 100644 index 000000000..6f14430b7 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/noc_shell_siggen.v @@ -0,0 +1,263 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_siggen +// +// Description: +// +// This is a tool-generated NoC-shell for the siggen block. +// See the RFNoC specification for more information about NoC shells. +// +// Parameters: +// +// THIS_PORTID : Control crossbar port to which this block is connected +// CHDR_W : AXIS-CHDR data bus width +// MTU : Maximum transmission unit (i.e., maximum packet size in +// + +`default_nettype none + + +module noc_shell_siggen #( + parameter [9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter [5:0] MTU = 10, + parameter NUM_PORTS = 1 +) ( + //--------------------- + // Framework Interface + //--------------------- + + // RFNoC Framework Clocks + input wire rfnoc_chdr_clk, + input wire rfnoc_ctrl_clk, + input wire ce_clk, + + // NoC Shell Generated Resets + output wire rfnoc_chdr_rst, + output wire rfnoc_ctrl_rst, + output wire ce_rst, + + // RFNoC Backend Interface + input wire [511:0] rfnoc_core_config, + output wire [511:0] rfnoc_core_status, + + // AXIS-CHDR Input Ports (from framework) + input wire [(1)*CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire [(1)-1:0] s_rfnoc_chdr_tlast, + input wire [(1)-1:0] s_rfnoc_chdr_tvalid, + output wire [(1)-1:0] s_rfnoc_chdr_tready, + // AXIS-CHDR Output Ports (to framework) + output wire [(0+NUM_PORTS)*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tlast, + output wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tvalid, + input wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tready, + + // AXIS-Ctrl Control 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 Control 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 + //--------------------- + + // CtrlPort Clock and Reset + output wire ctrlport_clk, + output wire ctrlport_rst, + // CtrlPort Master + 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, + input wire m_ctrlport_resp_ack, + input wire [31:0] m_ctrlport_resp_data, + + // AXI-Stream Data Clock and Reset + output wire axis_data_clk, + output wire axis_data_rst, + // Data Stream to User Logic: out + input wire [NUM_PORTS*32*1-1:0] s_out_axis_tdata, + input wire [NUM_PORTS*1-1:0] s_out_axis_tkeep, + input wire [NUM_PORTS-1:0] s_out_axis_tlast, + input wire [NUM_PORTS-1:0] s_out_axis_tvalid, + output wire [NUM_PORTS-1:0] s_out_axis_tready, + input wire [NUM_PORTS*64-1:0] s_out_axis_ttimestamp, + input wire [NUM_PORTS-1:0] s_out_axis_thas_time, + input wire [NUM_PORTS*16-1:0] s_out_axis_tlength, + input wire [NUM_PORTS-1:0] s_out_axis_teov, + input wire [NUM_PORTS-1:0] s_out_axis_teob +); + + //--------------------------------------------------------------------------- + // 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 (32'h51663110), + .NUM_DATA_I (1), + .NUM_DATA_O (0+NUM_PORTS), + .CTRL_FIFOSIZE ($clog2(32)), + .MTU (MTU) + ) backend_iface_i ( + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .rfnoc_chdr_rst (rfnoc_chdr_rst), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .rfnoc_ctrl_rst (rfnoc_ctrl_rst), + .rfnoc_core_config (rfnoc_core_config), + .rfnoc_core_status (rfnoc_core_status), + .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) + ); + + //--------------------------------------------------------------------------- + // Reset Generation + //--------------------------------------------------------------------------- + + wire ce_rst_pulse; + + pulse_synchronizer #(.MODE ("POSEDGE")) pulse_synchronizer_ce ( + .clk_a(rfnoc_chdr_clk), .rst_a(1'b0), .pulse_a (rfnoc_chdr_rst), .busy_a (), + .clk_b(ce_clk), .pulse_b (ce_rst_pulse) + ); + + pulse_stretch_min #(.LENGTH(32)) pulse_stretch_min_ce ( + .clk(ce_clk), .rst(1'b0), + .pulse_in(ce_rst_pulse), .pulse_out(ce_rst) + ); + + //--------------------------------------------------------------------------- + // Control Path + //--------------------------------------------------------------------------- + + assign ctrlport_clk = ce_clk; + assign ctrlport_rst = ce_rst; + + ctrlport_endpoint #( + .THIS_PORTID (THIS_PORTID), + .SYNC_CLKS (0), + .AXIS_CTRL_MST_EN (0), + .AXIS_CTRL_SLV_EN (1), + .SLAVE_FIFO_SIZE ($clog2(32)) + ) ctrlport_endpoint_i ( + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .rfnoc_ctrl_rst (rfnoc_ctrl_rst), + .ctrlport_clk (ctrlport_clk), + .ctrlport_rst (ctrlport_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_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (m_ctrlport_resp_ack), + .m_ctrlport_resp_status (2'b0), + .m_ctrlport_resp_data (m_ctrlport_resp_data), + .s_ctrlport_req_wr (1'b0), + .s_ctrlport_req_rd (1'b0), + .s_ctrlport_req_addr (20'b0), + .s_ctrlport_req_portid (10'b0), + .s_ctrlport_req_rem_epid (16'b0), + .s_ctrlport_req_rem_portid (10'b0), + .s_ctrlport_req_data (32'b0), + .s_ctrlport_req_byte_en (4'hF), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data () + ); + + //--------------------------------------------------------------------------- + // Data Path + //--------------------------------------------------------------------------- + + genvar i; + + assign axis_data_clk = ce_clk; + assign axis_data_rst = ce_rst; + + //--------------------- + // Input Data Paths + //--------------------- + + // No input data paths for this block + assign s_rfnoc_chdr_tready[0] = 1'b1; + assign data_i_flush_done[0] = 1'b1; + + //--------------------- + // Output Data Paths + //--------------------- + + for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_output_out + axis_data_to_chdr #( + .CHDR_W (CHDR_W), + .ITEM_W (32), + .NIPC (1), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE ($clog2(32)), + .PYLD_FIFO_SIZE ($clog2(32)), + .MTU (MTU), + .SIDEBAND_AT_END (0) + ) axis_data_to_chdr_out_out ( + .axis_chdr_clk (rfnoc_chdr_clk), + .axis_chdr_rst (rfnoc_chdr_rst), + .axis_data_clk (axis_data_clk), + .axis_data_rst (axis_data_rst), + .m_axis_chdr_tdata (m_rfnoc_chdr_tdata[(0+i)*CHDR_W+:CHDR_W]), + .m_axis_chdr_tlast (m_rfnoc_chdr_tlast[0+i]), + .m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid[0+i]), + .m_axis_chdr_tready (m_rfnoc_chdr_tready[0+i]), + .s_axis_tdata (s_out_axis_tdata[(32*1)*i+:(32*1)]), + .s_axis_tkeep (s_out_axis_tkeep[1*i+:1]), + .s_axis_tlast (s_out_axis_tlast[i]), + .s_axis_tvalid (s_out_axis_tvalid[i]), + .s_axis_tready (s_out_axis_tready[i]), + .s_axis_ttimestamp (s_out_axis_ttimestamp[64*i+:64]), + .s_axis_thas_time (s_out_axis_thas_time[i]), + .s_axis_tlength (s_out_axis_tlength[16*i+:16]), + .s_axis_teov (s_out_axis_teov[i]), + .s_axis_teob (s_out_axis_teob[i]), + .flush_en (data_o_flush_en), + .flush_timeout (data_o_flush_timeout), + .flush_active (data_o_flush_active[0+i]), + .flush_done (data_o_flush_done[0+i]) + ); + end + +endmodule // noc_shell_siggen + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen.v new file mode 100644 index 000000000..8a90c3e28 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen.v @@ -0,0 +1,242 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_siggen +// +// Description: +// +// Signal generator RFNoC block. This block outputs packets of one of three +// output types based on the REG_WAVEFORM register setting. Supported modes +// include constant, sinusoidal, and noise/random. The output is also run +// through a gain stage that is configurable using the REG_GAIN register. +// See the register descriptions in rfnoc_block_siggen_regs.vh for details. +// +// The sine output is based on the Xilinx CORDIC IP, configured for the +// rotate function, with scaled radians as the units. See the CORDIC user +// guide (PG105) and register descriptions for details. +// +// Parameters: +// +// THIS_PORTID : Control crossbar port to which this block is connected +// CHDR_W : AXIS-CHDR data bus width +// MTU : Maximum transmission unit (i.e., maximum packet size in +// CHDR words is 2**MTU). +// NUM_PORTS : Number of siggen cores to instantiate. +// + +`default_nettype none + + +module rfnoc_block_siggen #( + parameter [9:0] THIS_PORTID = 10 'd0, + parameter CHDR_W = 64, + parameter [5:0] MTU = 10, + parameter NUM_PORTS = 1 +) ( + // RFNoC Framework Clocks and Resets + input wire rfnoc_chdr_clk, + input wire rfnoc_ctrl_clk, + input wire ce_clk, + // RFNoC Backend Interface + input wire [ 511:0] rfnoc_core_config, + output wire [ 511:0] rfnoc_core_status, + // AXIS-CHDR Input Ports (from framework) + input wire [ CHDR_W-1:0] s_rfnoc_chdr_tdata, + input wire s_rfnoc_chdr_tlast, + input wire s_rfnoc_chdr_tvalid, + output wire s_rfnoc_chdr_tready, + // AXIS-CHDR Output Ports (to framework) + output wire [NUM_PORTS*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast, + output wire [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid, + input wire [ NUM_PORTS-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 +); + + `include "rfnoc_block_siggen_regs.vh" + + + //--------------------------------------------------------------------------- + // Signal Declarations + //--------------------------------------------------------------------------- + + // CtrlPort Master + wire m_ctrlport_req_wr; + wire m_ctrlport_req_rd; + wire [19:0] m_ctrlport_req_addr; + wire [31:0] m_ctrlport_req_data; + wire m_ctrlport_resp_ack; + wire [31:0] m_ctrlport_resp_data; + // Data Stream to User Logic: out + wire [NUM_PORTS*32*1-1:0] s_out_axis_tdata; + wire [ NUM_PORTS-1:0] s_out_axis_tlast; + wire [ NUM_PORTS-1:0] s_out_axis_tvalid; + wire [ NUM_PORTS-1:0] s_out_axis_tready; + wire [ NUM_PORTS*16-1:0] s_out_axis_tlength; + + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + wire ce_rst; + + noc_shell_siggen #( + .CHDR_W (CHDR_W), + .THIS_PORTID (THIS_PORTID), + .MTU (MTU), + .NUM_PORTS (NUM_PORTS) + ) noc_shell_siggen_i ( + //--------------------- + // Framework Interface + //--------------------- + + // Clock Inputs + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .ce_clk (ce_clk), + // Reset Outputs + .rfnoc_chdr_rst (), + .rfnoc_ctrl_rst (), + .ce_rst (ce_rst), + // RFNoC Backend Interface + .rfnoc_core_config (rfnoc_core_config), + .rfnoc_core_status (rfnoc_core_status), + // CHDR Input Ports (from framework) + .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata), + .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast), + .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid), + .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready), + // CHDR Output Ports (to framework) + .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata), + .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast), + .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid), + .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready), + // AXIS-Ctrl Input Port (from framework) + .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), + // AXIS-Ctrl Output Port (to framework) + .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), + + //--------------------- + // Client Interface + //--------------------- + + // CtrlPort Clock and Reset + .ctrlport_clk (), + .ctrlport_rst (), + // CtrlPort Master + .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_resp_ack (m_ctrlport_resp_ack), + .m_ctrlport_resp_data (m_ctrlport_resp_data), + + // AXI-Stream Clock and Reset + .axis_data_clk (), + .axis_data_rst (), + // Data Stream from User Logic: out + .s_out_axis_tdata (s_out_axis_tdata), + .s_out_axis_tkeep ({NUM_PORTS{1'b1}}), + .s_out_axis_tlast (s_out_axis_tlast), + .s_out_axis_tvalid (s_out_axis_tvalid), + .s_out_axis_tready (s_out_axis_tready), + .s_out_axis_ttimestamp ({NUM_PORTS{64'b0}}), + .s_out_axis_thas_time ({NUM_PORTS{1'b0}}), + .s_out_axis_tlength (s_out_axis_tlength), + .s_out_axis_teov ({NUM_PORTS{1'b0}}), + .s_out_axis_teob ({NUM_PORTS{1'b0}}) + ); + + + //--------------------------------------------------------------------------- + // CtrlPort Splitter + //--------------------------------------------------------------------------- + + // Create a CtrlPort bus for each port instance + + wire [ 1*NUM_PORTS-1:0] ctrlport_req_wr; + wire [ 1*NUM_PORTS-1:0] ctrlport_req_rd; + wire [20*NUM_PORTS-1:0] ctrlport_req_addr; + wire [32*NUM_PORTS-1:0] ctrlport_req_data; + wire [ 1*NUM_PORTS-1:0] ctrlport_resp_ack; + wire [32*NUM_PORTS-1:0] ctrlport_resp_data; + + ctrlport_decoder #( + .NUM_SLAVES (NUM_PORTS), + .BASE_ADDR (0), + .SLAVE_ADDR_W (SIGGEN_ADDR_W) + ) ctrlport_decoder_i ( + .ctrlport_clk (ce_clk), + .ctrlport_rst (ce_rst), + .s_ctrlport_req_wr (m_ctrlport_req_wr), + .s_ctrlport_req_rd (m_ctrlport_req_rd), + .s_ctrlport_req_addr (m_ctrlport_req_addr), + .s_ctrlport_req_data (m_ctrlport_req_data), + .s_ctrlport_req_byte_en (4'hF), + .s_ctrlport_req_has_time (1'b0), + .s_ctrlport_req_time (64'b0), + .s_ctrlport_resp_ack (m_ctrlport_resp_ack), + .s_ctrlport_resp_status (), + .s_ctrlport_resp_data (m_ctrlport_resp_data), + .m_ctrlport_req_wr (ctrlport_req_wr), + .m_ctrlport_req_rd (ctrlport_req_rd), + .m_ctrlport_req_addr (ctrlport_req_addr), + .m_ctrlport_req_data (ctrlport_req_data), + .m_ctrlport_req_byte_en (), + .m_ctrlport_req_has_time (), + .m_ctrlport_req_time (), + .m_ctrlport_resp_ack (ctrlport_resp_ack), + .m_ctrlport_resp_status ({NUM_PORTS{2'b0}}), + .m_ctrlport_resp_data (ctrlport_resp_data) + ); + + + //--------------------------------------------------------------------------- + // Port Instances + //--------------------------------------------------------------------------- + + genvar port; + generate + for (port = 0; port < NUM_PORTS; port = port+1) begin : gen_ports + + rfnoc_siggen_core rfnoc_siggen_core_i ( + .clk (ce_clk), + .rst (ce_rst), + .s_ctrlport_req_wr (ctrlport_req_wr [port* 1 +: 1]), + .s_ctrlport_req_rd (ctrlport_req_rd [port* 1 +: 1]), + .s_ctrlport_req_addr (ctrlport_req_addr [port*20 +: 20]), + .s_ctrlport_req_data (ctrlport_req_data [port*32 +: 32]), + .s_ctrlport_resp_ack (ctrlport_resp_ack [port* 1 +: 1]), + .s_ctrlport_resp_data (ctrlport_resp_data [port*32 +: 32]), + .m_tdata (s_out_axis_tdata [port*32 +: 32]), + .m_tlast (s_out_axis_tlast [port* 1 +: 1]), + .m_tvalid (s_out_axis_tvalid [port* 1 +: 1]), + .m_tready (s_out_axis_tready [port* 1 +: 1]), + .m_tlength (s_out_axis_tlength [port*16 +: 16]) + ); + + end + endgenerate + +endmodule // rfnoc_block_siggen + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_all_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_all_tb.sv new file mode 100644 index 000000000..0b23dcd0f --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_all_tb.sv @@ -0,0 +1,28 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_logpwr_all_tb +// +// Description: Top-level testbench for the Signal Generator RFNoC block. This +// instantiates rfnoc_block_siggen_tb with different parameters to test +// multiple configurations. +// + +`default_nettype none + + +module rfnoc_block_siggen_all_tb; + + // Test multiple CHDR widths + rfnoc_block_siggen_tb #(.CHDR_W(64), .NUM_PORTS(1)) test_siggen_0(); + rfnoc_block_siggen_tb #(.CHDR_W(64), .NUM_PORTS(2)) test_siggen_1(); + rfnoc_block_siggen_tb #(.CHDR_W(64), .NUM_PORTS(3)) test_siggen_2(); + rfnoc_block_siggen_tb #(.CHDR_W(128), .NUM_PORTS(2)) test_siggen_3(); + rfnoc_block_siggen_tb #(.CHDR_W(256), .NUM_PORTS(1)) test_siggen_4(); + +endmodule : rfnoc_block_siggen_all_tb + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_regs.vh new file mode 100644 index 000000000..10cda5425 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_regs.vh @@ -0,0 +1,134 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_siggen_regs (Header) +// +// Description: RFNoC Signal Generator block register descriptions +// + + +// Address space size, per signal generator core. That is, each signal +// generator core's address space is separated in the CtrlPort address space +// by 2^SIGGEN_ADDR_W bytes. +localparam SIGGEN_ADDR_W = 5; + + + +// REG_ENABLE (R/W) +// +// Starts or stops the waveform output. Write a 1 to enable waveform output, 0 +// to disable waveform output. Starting and stopping occurs on packet +// boundaries. +// +// [31:1] Reserved +// [0] Enable bit +// +localparam REG_ENABLE = 'h00; +// +localparam REG_ENABLE_LEN = 1; + + +// REG_SPP (R/W) +// +// The number of samples per packet to output for the selected waveform. This +// is read at the start of each new packet. +// +localparam REG_SPP = 'h04; +// +localparam REG_SPP_LEN = 14; + + +// REG_WAVEFORM (R/W) +// +// Selects the type of waveform to output. The possible values are: +// +// 0 : (WAVE_CONST) Constant data +// 1 : (WAVE_SINE) Sine wave +// 2 : (WAVE_NOISE) Noise / random data +// +localparam REG_WAVEFORM = 'h08; +// +localparam REG_WAVEFORM_LEN = 2; +// +localparam WAVE_CONST = 2'h0; +localparam WAVE_SINE = 2'h1; +localparam WAVE_NOISE = 2'h2; + + +// REG_GAIN (R/W) +// +// Sets the gain for the output. This is a 16-bit signed fixed point value +// with 15 fractional bits. The gain is applied to both the real and imaginary +// parts of each output sample. This gain is applied to all waveform output +// types. +// +localparam REG_GAIN = 'h0C; +// +localparam REG_GAIN_LEN = 16; + + +// REG_CONSTANT (R/W) +// +// Sets the value for the sample to output for the constant waveform. Both the +// real and imaginary components are treated as 16-bit signed fixed point +// values with 15 fractional bits. +// +// [31:16] Real/I component +// [15: 0] Imaginary/Q component +// +localparam REG_CONSTANT = 'h10; +// +localparam REG_CONSTANT_LEN = 32; + + +// REG_PHASE_INC (R/W) +// +// Sets the phase increment, in "scaled radians", for the sine waveform +// generator. This is the amount by which REG_CARTESIAN is rotated each clock +// cycle. In other words, it controls the rate of rotation, or the frequency, +// of the sine wave. The range of the phase value is -1.0 to +1.0. In scaled +// radians, the value range -1 to +1 corresponds to -Pi to Pi in radians. +// +// In other words, the normalized frequency (in cycles/sample) of the +// sinusoidal output is equal to 0.5*REG_PHASE_INC. +// +// [31:16] : Reserved +// [15: 0] : Signed fixed-point phase value with 3 integer bits and 13 +// fractional bits. +// +localparam REG_PHASE_INC = 'h14; +// +localparam REG_PHASE_INC_LEN = 16; + + +// REG_CARTESIAN (R/W) +// +// Sets the (X,Y) Cartesian coordinate that will be rotated to generate the +// sine output. The rate of rotation is controlled by REG_PHASE_INC. Note that +// this input vector is also scaled by a "CORDIC scale factor" that equals +// about 1.16444 (the product of sqrt(1 + 2^(-2i)) for i = 1 to n, where n = +// 14, the number of fractional bits used by the CORDIC IP). +// +// Both the X and Y coordinates are signed fixed-point values with 15 +// fractional bits. +// +// For example, supposed you wanted a sinusoidal output with an amplitude of +// about 0.9. In that case, you could set the Y coordinate to 0 and the X +// coordinate to 0.9/1.16444 = 0.7729. In fixed-point, that's 0.7729 * 2^15 = +// 0x62EE. +// +// NOTE: The Xilinx CORDIC IP describes the input and output as 16-bit signed +// fixed point with 2 integer and 14 fractional bits, which is accurate. +// However, since we treat the output as sc16 (15 fractional bits), we need to +// double the value of the CARTESIAN inputs to get the output we want for sc16. +// This is mathematically inequivalent to simply saying the CARTESIAN inputs +// have 15 fractional bits instead of 14. +// +// [31:16] : Y (Imaginary) component +// [15: 0] : X (Real) component +// +localparam REG_CARTESIAN = 'h18; +// +localparam REG_CARTESIAN_LEN = 32; diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_tb.sv new file mode 100644 index 000000000..4cb701aaf --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_block_siggen_tb.sv @@ -0,0 +1,719 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_siggen_tb +// +// Description: Testbench for the siggen RFNoC block. +// + +`default_nettype none + + +module rfnoc_block_siggen_tb #( + parameter CHDR_W = 64, + parameter NUM_PORTS = 1 +); + + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + import PkgRfnocItemUtils::*; + + `include "rfnoc_block_siggen_regs.vh" + + + //--------------------------------------------------------------------------- + // Testbench Configuration + //--------------------------------------------------------------------------- + + localparam [31:0] NOC_ID = 32'h51663110; + localparam [ 9:0] THIS_PORTID = 10'h123; + localparam int MTU = 10; // Log2 of max transmission unit in CHDR words + localparam int NUM_PORTS_I = 1; + localparam int NUM_PORTS_O = 0+NUM_PORTS; + localparam int ITEM_W = 32; // Sample size in bits + localparam int SPP = 64; // Samples per packet + localparam int PKT_SIZE_BYTES = SPP * (ITEM_W/8); + localparam int STALL_PROB = 25; // Default BFM stall probability + localparam real CHDR_CLK_PER = 5.0; // 200 MHz + localparam real CTRL_CLK_PER = 8.0; // 125 MHz + localparam real CE_CLK_PER = 4.0; // 250 MHz + + localparam real PI = 2*$acos(0); + + // Number of fractional bits used for fixed point values of the different + // settings (derived from the DUT). + localparam int GAIN_FRAC = 15; + localparam int CONST_FRAC = 15; + localparam int PHASE_FRAC = 13; + localparam int CART_FRAC = 14; + + // Maximum real (floating point) values allowed for the different fixed + // point formats (for range checking). All of the fixed point values are + // signed 16-bit. + localparam real MAX_GAIN_R = (2.0**15-1) / (2.0**GAIN_FRAC); + localparam real MIN_GAIN_R = -(2.0**15) / (2.0**GAIN_FRAC); + localparam real MAX_CONST_R = (2.0**15-1) / (2.0**CONST_FRAC); + localparam real MIN_CONST_R = -(2.0**15) / (2.0**CONST_FRAC); + localparam real MAX_CART_R = (2.0**15-1) / (2.0**CART_FRAC); + localparam real MIN_CART_R = -(2.0**15) / (2.0**CART_FRAC); + // Note that the CORDIC only supports phase values from -1.0 to +1.0. + localparam real MAX_PHASE_R = +1.0; + localparam real MIN_PHASE_R = -1.0; + + + //--------------------------------------------------------------------------- + // Clocks and Resets + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + bit ce_clk; + + sim_clock_gen #(.PERIOD(CHDR_CLK_PER), .AUTOSTART(0)) + rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0)) + rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + sim_clock_gen #(.PERIOD(CE_CLK_PER), .AUTOSTART(0)) + ce_clk_gen (.clk(ce_clk), .rst()); + + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + + // Backend Interface + RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk); + + // AXIS-Ctrl Interface + AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0); + AxiStreamIf #(32) s_ctrl (rfnoc_ctrl_clk, 1'b0); + + // AXIS-CHDR Interfaces + AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS_I] (rfnoc_chdr_clk, 1'b0); + AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS_O] (rfnoc_chdr_clk, 1'b0); + + // Block Controller BFM + RfnocBlockCtrlBfm #(CHDR_W, ITEM_W) blk_ctrl = new(backend, m_ctrl, s_ctrl); + + // CHDR word and item/sample data types + typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t; + typedef ChdrData #(CHDR_W, ITEM_W)::item_t item_t; + + // Connect block controller to BFMs + for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_bfm_input_connections + initial begin + blk_ctrl.connect_master_data_port(i, m_chdr[i], PKT_SIZE_BYTES); + blk_ctrl.set_master_stall_prob(i, STALL_PROB); + end + end + for (genvar i = 0; i < NUM_PORTS_O; i++) begin : gen_bfm_output_connections + initial begin + blk_ctrl.connect_slave_data_port(i, s_chdr[i]); + blk_ctrl.set_slave_stall_prob(i, STALL_PROB); + end + end + + + //--------------------------------------------------------------------------- + // Device Under Test (DUT) + //--------------------------------------------------------------------------- + + // DUT Slave (Input) Port Signals + logic [CHDR_W*NUM_PORTS_I-1:0] s_rfnoc_chdr_tdata; + logic [ NUM_PORTS_I-1:0] s_rfnoc_chdr_tlast; + logic [ NUM_PORTS_I-1:0] s_rfnoc_chdr_tvalid; + logic [ NUM_PORTS_I-1:0] s_rfnoc_chdr_tready; + + // DUT Master (Output) Port Signals + logic [CHDR_W*NUM_PORTS_O-1:0] m_rfnoc_chdr_tdata; + logic [ NUM_PORTS_O-1:0] m_rfnoc_chdr_tlast; + logic [ NUM_PORTS_O-1:0] m_rfnoc_chdr_tvalid; + logic [ NUM_PORTS_O-1:0] m_rfnoc_chdr_tready; + + // Map the array of BFMs to a flat vector for the DUT connections + for (genvar i = 0; i < NUM_PORTS_I; i++) begin : gen_dut_input_connections + // Connect BFM master to DUT slave port + assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata; + assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast; + assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid; + assign m_chdr[i].tready = s_rfnoc_chdr_tready[i]; + end + for (genvar i = 0; i < NUM_PORTS_O; i++) begin : gen_dut_output_connections + // Connect BFM slave to DUT master port + assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W]; + assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i]; + assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i]; + assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready; + end + + rfnoc_block_siggen #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .MTU (MTU), + .NUM_PORTS (NUM_PORTS) + ) dut ( + .rfnoc_chdr_clk (rfnoc_chdr_clk), + .rfnoc_ctrl_clk (rfnoc_ctrl_clk), + .ce_clk (ce_clk), + .rfnoc_core_config (backend.cfg), + .rfnoc_core_status (backend.sts), + .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata), + .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast), + .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid), + .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready), + .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata), + .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast), + .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid), + .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready), + .s_rfnoc_ctrl_tdata (m_ctrl.tdata), + .s_rfnoc_ctrl_tlast (m_ctrl.tlast), + .s_rfnoc_ctrl_tvalid (m_ctrl.tvalid), + .s_rfnoc_ctrl_tready (m_ctrl.tready), + .m_rfnoc_ctrl_tdata (s_ctrl.tdata), + .m_rfnoc_ctrl_tlast (s_ctrl.tlast), + .m_rfnoc_ctrl_tvalid (s_ctrl.tvalid), + .m_rfnoc_ctrl_tready (s_ctrl.tready) + ); + + + //--------------------------------------------------------------------------- + // Helper Tasks + //--------------------------------------------------------------------------- + + // Write a 32-bit register + task automatic write_reg(int port, bit [19:0] addr, bit [31:0] value); + blk_ctrl.reg_write(port * (2**SIGGEN_ADDR_W) + addr, value); + endtask : write_reg + + // Read a 32-bit register + task automatic read_reg(int port, bit [19:0] addr, output logic [31:0] value); + blk_ctrl.reg_read(port * (2**SIGGEN_ADDR_W) + addr, value); + endtask : read_reg + + + // Check if two samples are within a given distance from each other (i.e., + // check if the Cartesian distance is < threshold). + function bit samples_are_close( + logic [31:0] samp_a, samp_b, + real threshold = 3.0 + ); + real ax, ay, bx, by; + real distance; + + // Treat the samples and signed 16-bit numbers (not fixed point) + ax = signed'(samp_a[31:16]); + ay = signed'(samp_a[15: 0]); + bx = signed'(samp_b[31:16]); + by = signed'(samp_b[15: 0]); + + distance = $sqrt( (ax-bx)*(ax-bx) + (ay-by)*(ay-by) ); + + return distance <= threshold; + endfunction : samples_are_close + + + // Convert real to signed 16-bit fixed point with "frac" fractional bits + function automatic logic [15:0] real_to_fixed(real value, int frac = 15); + // Convert to fixed point value + value = value * 2.0**frac; + + // Round + value = $floor(value + 0.5); + + // Saturate + if (value > 16'sh7FFF) value = 16'sh7FFF; + if (value < 16'sh8000) value = 16'sh8000; + return int'(value); + endfunction : real_to_fixed + + + // Convert signed 16-bit fixed point to real, where the fixed point has + // "frac" fractional bits. + function automatic real fixed_to_real( + logic signed [15:0] value, + int frac = 15 + ); + return real'(value) / (2.0 ** frac); + endfunction : fixed_to_real + + + // Compute the next sine value we expect based on the previous + function automatic logic [31:0] next_sine_value( + logic [31:0] sample, + logic [15:0] phase_inc + ); + real x, y, phase, new_x, new_y; + x = fixed_to_real(sample[31:16], CART_FRAC); + y = fixed_to_real(sample[15: 0], CART_FRAC); + phase = fixed_to_real(phase_inc, PHASE_FRAC) * PI; + + // Compute the rotated coordinates + new_x = x*$cos(phase) + y*$sin(phase); + new_y = -x*$sin(phase) + y*$cos(phase); + + return { real_to_fixed(new_x, CART_FRAC), real_to_fixed(new_y, CART_FRAC) }; + endfunction : next_sine_value + + + // Apply a gain to an input value, then round and clip the same way the DUT + // does. + function automatic logic [15:0] apply_gain( + logic signed [15:0] gain, + logic signed [15:0] value + ); + logic signed [31:0] result; + bit round; + + // Apply gain + result = gain * value; + + // Now we "round and clip". The round and clip block is configured with + // 32-bit input, 16-bit output, and one "clip_bit". This means it takes + // the upper 17-bits of the result, rounded, then converts that to a + // 16-bit result, saturated. + + // Round the value in the upper 17 bits to nearest (biased towards +inf, + // but don't allow overflow). + if (result[31:15] != 17'h0FFFF) begin + round = result[14]; + end else begin + round = 0; + end + result = result >>> 15; // Arithmetic right shift + result = result + round; // Round the result + + // Saturate to 16-bit number + if (result < 16'sh8000) begin + result = 16'sh8000; + end else if (result > 16'sh7FFF) begin + result = 16'sh7FFF; + end + + return result[15:0]; + endfunction : apply_gain + + + // Flush (drop) any queued up packets on the output + task automatic flush_output(int port, timeout = 100); + item_t items[$]; + + forever begin + fork + begin : wait_for_data_fork + // Wait for tvalid to rise for up to "timeout" clock cycles + if (m_rfnoc_chdr_tvalid[port]) + wait(!m_rfnoc_chdr_tvalid[port]); + wait(m_rfnoc_chdr_tvalid[port]); + end + begin : wait_for_timeout_fork + #(CHDR_CLK_PER*timeout); + end + join_any + + // Check if we timed out or if new data arrived + if (!m_rfnoc_chdr_tvalid[port]) break; + end + + // Dump all the packets that were received + while (blk_ctrl.num_received(port)) begin + blk_ctrl.recv_items(port, items); + end + endtask : flush_output + + + // Test a read/write register for correct functionality + // + // port : Replay block port to use + // addr : Register byte address + // mask : Mask of the bits we expect to be writable + // initial_value : Value we expect to read initially + // + task automatic test_read_write_reg( + int port, + bit [19:0] addr, + bit [31:0] mask = 32'hFFFFFFFF, + logic [31:0] initial_value = '0 + ); + string err_msg; + logic [31:0] value; + logic [31:0] expected; + + err_msg = $sformatf("Register 0x%X failed read/write test: ", addr); + + // Check initial value + expected = initial_value; + read_reg(port, addr, value); + `ASSERT_ERROR(value === expected, {err_msg, "initial value"}); + + // Write maximum value + expected = (initial_value & ~mask) | mask; + write_reg(port, addr, '1); + read_reg(port, addr, value); + `ASSERT_ERROR(value === expected, {err_msg, "write max value"}); + + // Test writing 0 + expected = (initial_value & ~mask); + write_reg(port, addr, '0); + read_reg(port, addr, value); + `ASSERT_ERROR(value === expected, {err_msg, "write zero"}); + + // Restore original value + write_reg(port, addr, initial_value); + endtask : test_read_write_reg + + + // Run the block using the indicated settings and verify the output. + task automatic run_waveform( + int port, + logic signed [15:0] gain = 16'h7FFF, // 0.99997 + logic [2:0] mode = WAVE_CONST, + int num_packets = 1, + int spp = SPP, + logic signed [15:0] const_re = 16'h7FFF, // 0.99997 + logic signed [15:0] const_im = 16'h7FFF, // 0.99997 + logic signed [15:0] phase_inc = real_to_fixed(0.5, 13), //real_to_fixed(2.0/16, 13), // 2*pi/16 radians + logic signed [15:0] cart_x = real_to_fixed(1.0, 14), + logic signed [15:0] cart_y = real_to_fixed(0.0, 14) + ); + write_reg(port, REG_SPP, spp); + write_reg(port, REG_WAVEFORM, mode); + write_reg(port, REG_GAIN, gain); + if (mode == WAVE_CONST) begin + write_reg(port, REG_CONSTANT, {const_re, const_im}); + end else if (mode == WAVE_SINE) begin + write_reg(port, REG_PHASE_INC, phase_inc); + write_reg(port, REG_CARTESIAN, {cart_y, cart_x}); + end + write_reg(port, REG_ENABLE, 1); + + for (int packet_count = 0; packet_count < num_packets; packet_count++) begin + item_t items[$]; + item_t expected_const, expected_sine, actual; + + // Receive the next packet + blk_ctrl.recv_items(port, items); + + // Verify the length + `ASSERT_ERROR( + items.size() == spp, + "Packet length didn't match configured SPP" + ); + + // Verify the payload + foreach (items[i]) begin + actual = items[i]; + + // Determine the expected constant output + expected_const[31:16] = apply_gain(gain, const_re); + expected_const[15: 0] = apply_gain(gain, const_im); + + // Determine the expected sine output + if (i == 0) begin + // We have no basis for comparison on the first sample, so don't + // check it. It will be used to compute the next output. + expected_sine = actual; + end else begin + expected_sine = next_sine_value(items[i-1], phase_inc); + end + + // Check the output + if (mode == WAVE_CONST) begin + // For the constant, we expect the output to match exactly + `ASSERT_ERROR( + actual == expected_const, + $sformatf("Incorrect constant sample on packet %0d. Expected 0x%X, received 0x%X.", + packet_count, expected_const, actual) + ); + end else if (mode == WAVE_SINE) begin + // For sine, it's hard to reproduce the rounding behavior of the IP + // exactly, so we just check if we're close to the expected answer. + `ASSERT_ERROR( + samples_are_close(actual, expected_sine), + $sformatf("Incorrect sine sample on packet %0d. Expected 0x%X, received 0x%X.", + packet_count, expected_sine, actual) + ); + end else if (mode == WAVE_NOISE) begin + if (i != 0) begin + // For noise, it's hard to even estimate the output, so make sure + // it's changing. + `ASSERT_ERROR(items[i] !== items[i-1], + $sformatf("Noise output didn't update on packet %0d.Received 0x%X.", + packet_count, actual) + ); + end + end + + end + end + + // Disable the output and flush any output + write_reg(port, REG_ENABLE, 0); + flush_output(port); + endtask : run_waveform + + + // Run the block using the "constant" waveform mode using the indicated + // settings and verify the output. + task automatic run_const( + int port, + int num_packets = 50, + int spp = SPP, + real gain, + real re, + real im + ); + logic signed [15:0] fgain, fre, fim; // Fixed-point versions + + // Check the ranges + `ASSERT_FATAL(gain <= MAX_GAIN_R || gain >= MIN_GAIN_R, "Gain out of range"); + `ASSERT_FATAL(re <= MAX_CONST_R || re >= MIN_CONST_R, "Real out of range"); + `ASSERT_FATAL(im <= MAX_CONST_R || im >= MIN_CONST_R, "Imag out of range"); + + // Convert arguments to fixed point + fgain = real_to_fixed(gain, GAIN_FRAC); + fre = real_to_fixed(re, CONST_FRAC); + fim = real_to_fixed(im, CONST_FRAC); + + // Test the waveform + run_waveform( + .port(port), + .gain(fgain), + .mode(WAVE_CONST), + .num_packets(num_packets), + .spp(spp), + .const_re(fre), + .const_im(fim) + ); + endtask : run_const + + + // Run the block using the "sine" waveform mode using the indicated settings + // and verify the output. + task automatic run_sine( + int port, + int num_packets = 50, + int spp = SPP, + real gain, + real x, + real y, + real phase + ); + logic signed [15:0] fgain, fx, fy, fphase; // Fixed-point versions + + // Check the ranges + `ASSERT_FATAL(gain <= MAX_GAIN_R || gain >= MIN_GAIN_R, "Gain out of range"); + `ASSERT_FATAL(x <= MAX_CART_R || x >= MIN_CART_R, "X out of range"); + `ASSERT_FATAL(y <= MAX_CART_R || y >= MIN_CART_R, "Y out of range"); + `ASSERT_FATAL(phase <= MAX_PHASE_R || phase >= MIN_PHASE_R, "Phase out of range"); + + // Convert arguments to fixed point. + fgain = real_to_fixed(gain, GAIN_FRAC); + fx = real_to_fixed(x, CART_FRAC); + fy = real_to_fixed(y, CART_FRAC); + fphase = real_to_fixed(phase, PHASE_FRAC); + + // Test the waveform + run_waveform( + .port(port), + .gain(fgain), + .mode(WAVE_SINE), + .num_packets(num_packets), + .spp(spp), + .cart_x(fx), + .cart_y(fy), + .phase_inc(fphase) + ); + endtask : run_sine + + + // Run the block using the "noise" waveform mode using the indicated + // settings and verify the output. + task automatic run_noise( + int port, + int num_packets = 50, + int spp = SPP, + real gain + ); + logic signed [15:0] fgain; // Fixed-point versions + + // Check the ranges + `ASSERT_FATAL(gain <= MAX_GAIN_R || gain >= MIN_GAIN_R, "Gain out of range"); + + // Convert arguments to fixed point + fgain = real_to_fixed(gain, GAIN_FRAC); + + // Test the waveform + run_waveform( + .port(port), + .gain(fgain), + .mode(WAVE_NOISE), + .num_packets(num_packets), + .spp(spp) + ); + endtask : run_noise + + + //--------------------------------------------------------------------------- + // Test Procedures + //--------------------------------------------------------------------------- + + // Test the min and max allowed values on all registers + task automatic test_registers(int port); + test.start_test($sformatf("Test registers (port %0d)", port), 1ms); + // REG_ENABLE and REG_WAVEFORM will be tested during the other tests + test_read_write_reg(port, REG_SPP, {REG_SPP_LEN{1'b1}}, 32'd16); + test_read_write_reg(port, REG_GAIN, {REG_GAIN_LEN{1'b1}}, 32'h7FFF); + test_read_write_reg(port, REG_CONSTANT, {REG_CONSTANT_LEN{1'b1}}, 32'h0); + test_read_write_reg(port, REG_PHASE_INC, {REG_PHASE_INC_LEN{1'b1}}, {REG_PHASE_INC_LEN{1'bX}}); + test_read_write_reg(port, REG_CARTESIAN, {REG_CARTESIAN_LEN{1'b1}}, {REG_CARTESIAN_LEN{1'bX}}); + test.end_test(); + endtask : test_registers + + + // Run through all the waveform modes to make sure they work as expected + task automatic test_waveforms(int port); + test.start_test($sformatf("Test waveforms (port %0d)", port), 1ms); + run_const(.port(port), .gain(0.5), .re(0.25), .im(0.5)); + run_sine(.port(port), .gain(0.75), .x(0.25), .y(0.5), .phase(2.0/64)); + run_noise(.port(port), .gain(0.999)); + test.end_test(); + endtask : test_waveforms + + + // Use the constant waveform to test the gain. The gain logic is shared by + // all modes, but using "const" waveform makes it easy to control the values + // we're testing. + task automatic test_gain(int port); + logic signed [15:0] min_val; + logic signed [15:0] max_val; + + test.start_test($sformatf("Test gain (port %0d)", port), 1ms); + + max_val = 16'sh7FFF; + min_val = 16'sh8000; + + // Test max gain with min and max sample values + run_waveform(.port(port), .mode(WAVE_CONST), .gain(max_val), + .const_re(max_val), .const_im(min_val)); + // Test min gain with max and min sample values + run_waveform(.port(port), .mode(WAVE_CONST), .gain(min_val), + .const_re(min_val), .const_im(max_val)); + // Test zero + run_waveform(.port(port), .mode(WAVE_CONST), .gain(0), + .const_re(max_val), .const_im(min_val)); + // Test 0.5 * 0.5 = 0.25 and 0.25 * 0.5 = 0.125 + run_waveform( + .port(port), + .mode(WAVE_CONST), + .const_re(real_to_fixed(0.5, CONST_FRAC)), + .const_im(real_to_fixed(0.25, CONST_FRAC)), + .gain(real_to_fixed(0.5, GAIN_FRAC)) + ); + test.end_test(); + endtask : test_gain + + + // Test the phase setting for the sine waveform + task automatic test_phase(int port); + test.start_test($sformatf("Test phase (port %0d)", port), 1ms); + // Test typical phase + run_sine(.port(port), .gain(0.5), .x(1.0), .y(0.0), .phase(2.0/16), .num_packets(2)); + // Test max phase + run_sine(.port(port), .gain(0.5), .x(1.0), .y(0.0), .phase(MAX_PHASE_R), .num_packets(2)); + // Test min phase + run_sine(.port(port), .gain(0.5), .x(1.0), .y(0.0), .phase(MIN_PHASE_R), .num_packets(2)); + test.end_test(); + endtask : test_phase + + + // Use constant waveform to test min and max packet lengths + task automatic test_packet_length(int port); + test.start_test($sformatf("Test packet length (port %0d)", port), 1ms); + run_waveform(.port(port), .spp(2)); + run_waveform(.port(port), .spp(SPP)); + run_waveform(.port(port), .spp((2**MTU-1) * (CHDR_W / ITEM_W))); // Test MTU size + test.end_test(); + endtask : test_packet_length + + + //--------------------------------------------------------------------------- + // Main Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + int port; + + // Initialize the test exec object for this testbench + test.start_tb( + $sformatf("rfnoc_block_siggen_tb (CHDR_W = %0d, NUM_PORTS = %0d)", + CHDR_W, NUM_PORTS)); + + // Don't start the clocks until after start_tb() returns. This ensures that + // the clocks aren't toggling while other instances of this testbench are + // running, which speeds up simulation time. + rfnoc_chdr_clk_gen.start(); + rfnoc_ctrl_clk_gen.start(); + ce_clk_gen.start(); + + // Start the BFMs running + blk_ctrl.run(); + + //-------------------------------- + // Reset + //-------------------------------- + + test.start_test("Flush block then reset it", 10us); + blk_ctrl.flush_and_reset(); + test.end_test(); + + //-------------------------------- + // Verify Block Info + //-------------------------------- + + test.start_test("Verify Block Info", 2us); + `ASSERT_ERROR(blk_ctrl.get_noc_id() == NOC_ID, "Incorrect NOC_ID Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS_I, "Incorrect NUM_DATA_I Value"); + `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS_O, "Incorrect NUM_DATA_O Value"); + `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value"); + test.end_test(); + + //-------------------------------- + // Test Sequences + //-------------------------------- + + // Run basic test all ports + for(port = 0; port < NUM_PORTS; port++) begin + test_registers(port); + test_waveforms(port); + end + + // Run remaining tests on single port + port = 0; + test_gain(port); + test_packet_length(port); + test_phase(port); + + //-------------------------------- + // Finish Up + //-------------------------------- + + // Display final statistics and results, but don't call $finish, since we + // don't want to kill other instances of this testbench that may be + // running. + test.end_tb(0); + + // Kill the clocks to end this instance of the testbench + rfnoc_chdr_clk_gen.kill(); + rfnoc_ctrl_clk_gen.kill(); + ce_clk_gen.kill(); + end : tb_main + +endmodule : rfnoc_block_siggen_tb + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_siggen_core.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_siggen_core.v new file mode 100644 index 000000000..bcb664fe0 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_siggen/rfnoc_siggen_core.v @@ -0,0 +1,284 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_siggen_core +// +// Description: +// +// This module contains the registers and core logic for a single RFNoC +// Signal Generator module instance. +// + + +module rfnoc_siggen_core ( + input wire clk, + input wire rst, + + // CtrlPort Slave + 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 reg [31:0] s_ctrlport_resp_data, + + // Output data stream + output wire [31:0] m_tdata, + output wire m_tlast, + output wire m_tvalid, + input wire m_tready, + output wire [15:0] m_tlength +); + + `include "rfnoc_block_siggen_regs.vh" + + + //--------------------------------------------------------------------------- + // Registers + //--------------------------------------------------------------------------- + + // Define maximum fixed point value for the gain, equal to about 0.9999 + localparam MAX_GAIN = {REG_GAIN_LEN-1{1'b1}}; + + reg [ REG_ENABLE_LEN-1:0] reg_enable = 0; + reg [ REG_SPP_LEN-1:0] reg_spp = 16; + reg [ REG_WAVEFORM_LEN-1:0] reg_waveform = WAVE_CONST; + reg [ REG_GAIN_LEN-1:0] reg_gain = MAX_GAIN; + reg [ REG_CONSTANT_LEN-1:0] reg_constant = 0; + reg [REG_PHASE_INC_LEN-1:0] reg_phase_inc; + reg [REG_CARTESIAN_LEN-1:0] reg_cartesian; + + reg reg_phase_inc_stb; + reg reg_cartesian_stb; + + always @(posedge clk) begin + if (rst) begin + reg_enable <= 0; + reg_spp <= 16; + reg_waveform <= WAVE_CONST; + reg_gain <= MAX_GAIN; + reg_constant <= 0; + reg_phase_inc <= 'bX; + reg_cartesian <= 'bX; + s_ctrlport_resp_ack <= 1'b0; + s_ctrlport_resp_data <= 'bX; + reg_phase_inc_stb <= 1'b0; + reg_cartesian_stb <= 1'b0; + end else begin + + // Default assignments + s_ctrlport_resp_ack <= 1'b0; + s_ctrlport_resp_data <= 0; + reg_phase_inc_stb <= 1'b0; + reg_cartesian_stb <= 1'b0; + + // Handle register writes + if (s_ctrlport_req_wr) begin + s_ctrlport_resp_ack <= 1; + case (s_ctrlport_req_addr) + REG_ENABLE : reg_enable <= s_ctrlport_req_data[REG_ENABLE_LEN-1:0]; + REG_SPP : reg_spp <= s_ctrlport_req_data[REG_SPP_LEN-1:0]; + REG_WAVEFORM : reg_waveform <= s_ctrlport_req_data[REG_WAVEFORM_LEN-1:0]; + REG_GAIN : reg_gain <= s_ctrlport_req_data[REG_GAIN_LEN-1:0]; + REG_CONSTANT : reg_constant <= s_ctrlport_req_data[REG_CONSTANT_LEN-1:0]; + REG_PHASE_INC : begin + reg_phase_inc <= s_ctrlport_req_data[REG_PHASE_INC_LEN-1:0]; + reg_phase_inc_stb <= 1'b1; + end + REG_CARTESIAN : begin + reg_cartesian <= s_ctrlport_req_data[REG_CARTESIAN_LEN-1:0]; + reg_cartesian_stb <= 1'b1; + end + endcase + end + + // Handle register reads + if (s_ctrlport_req_rd) begin + s_ctrlport_resp_ack <= 1; + case (s_ctrlport_req_addr) + REG_ENABLE : s_ctrlport_resp_data[REG_ENABLE_LEN-1:0] <= reg_enable; + REG_SPP : s_ctrlport_resp_data[REG_SPP_LEN-1:0] <= reg_spp; + REG_WAVEFORM : s_ctrlport_resp_data[REG_WAVEFORM_LEN-1:0] <= reg_waveform; + REG_GAIN : s_ctrlport_resp_data[REG_GAIN_LEN-1:0] <= reg_gain; + REG_CONSTANT : s_ctrlport_resp_data[REG_CONSTANT_LEN-1:0] <= reg_constant; + REG_PHASE_INC : s_ctrlport_resp_data[REG_PHASE_INC_LEN-1:0] <= reg_phase_inc; + REG_CARTESIAN : s_ctrlport_resp_data[REG_CARTESIAN_LEN-1:0] <= reg_cartesian; + endcase + end + end + end + + + //--------------------------------------------------------------------------- + // Waveform Generation + //--------------------------------------------------------------------------- + + wire [31:0] axis_sine_tdata; + wire axis_sine_tvalid; + wire axis_sine_tready; + wire [31:0] axis_const_tdata; + wire axis_const_tvalid; + wire axis_const_tready; + wire [31:0] axis_noise_tdata; + wire axis_noise_tvalid; + wire axis_noise_tready; + + //------------------------------------ + // Sine waveform generation + //------------------------------------ + + // Convert the registers writes to settings bus transactions. Only one + // register strobe will assert at a time. + wire sine_set_stb = reg_cartesian_stb | reg_phase_inc_stb; + wire [31:0] sine_set_data = reg_cartesian_stb ? reg_cartesian : reg_phase_inc; + wire [ 7:0] sine_set_addr = reg_cartesian_stb; + + sine_tone #( + .WIDTH (32), + .SR_PHASE_INC_ADDR (0), + .SR_CARTESIAN_ADDR (1) + ) sine_tone_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .enable (1'b1), + .set_stb (sine_set_stb), + .set_data (sine_set_data), + .set_addr (sine_set_addr), + .o_tdata (axis_sine_tdata), + .o_tlast (), + .o_tvalid (axis_sine_tvalid), + .o_tready (axis_sine_tready) + ); + + //------------------------------------ + // Constant waveform generation + //------------------------------------ + + assign axis_const_tdata = reg_constant; + assign axis_const_tvalid = 1'b1; + + //------------------------------------ + // Noise waveform generation + //------------------------------------ + + assign axis_noise_tvalid = 1'b1; + + // Random number generator + rng rng_i ( + .clk (clk), + .rst (rst), + .out (axis_noise_tdata) + ); + + + //--------------------------------------------------------------------------- + // Waveform Selection + //--------------------------------------------------------------------------- + + wire [31:0] axis_mux_tdata; + wire axis_mux_tvalid; + wire axis_mux_tready; + + axi_mux_select #( + .WIDTH (32), + .SIZE (3), + .SWITCH_ON_LAST (0) + ) axi_mux_select_i ( + .clk (clk), + .reset (rst), + .clear (1'b0), + .select (reg_waveform), + .i_tdata ({axis_noise_tdata, axis_sine_tdata, axis_const_tdata}), + .i_tlast ({3'd0}), // Length controlled by SPP register + .i_tvalid ({axis_noise_tvalid, axis_sine_tvalid, axis_const_tvalid}), + .i_tready ({axis_noise_tready, axis_sine_tready, axis_const_tready}), + .o_tdata (axis_mux_tdata), + .o_tlast (), + .o_tvalid (axis_mux_tvalid), + .o_tready (axis_mux_tready) + ); + + + //--------------------------------------------------------------------------- + // Gain + //--------------------------------------------------------------------------- + + wire [63:0] axis_gain_tdata; + wire axis_gain_tvalid; + wire axis_gain_tready; + wire [31:0] axis_round_tdata; + wire axis_round_tvalid; + wire axis_round_tready; + + mult_rc #( + .WIDTH_REAL (16), + .WIDTH_CPLX (16), + .WIDTH_P (32), + .DROP_TOP_P (5), + .LATENCY (4) + ) mult_rc_i ( + .clk (clk), + .reset (rst), + .real_tdata (reg_gain), + .real_tlast (1'b0), + .real_tvalid (1'b1), + .real_tready (), + .cplx_tdata (axis_mux_tdata), + .cplx_tlast (1'b0), + .cplx_tvalid (axis_mux_tvalid), + .cplx_tready (axis_mux_tready), + .p_tdata (axis_gain_tdata), + .p_tlast (), + .p_tvalid (axis_gain_tvalid), + .p_tready (axis_gain_tready) + ); + + axi_round_and_clip_complex #( + .WIDTH_IN (32), + .WIDTH_OUT (16), + .CLIP_BITS (1) + ) axi_round_and_clip_complex_i ( + .clk (clk), + .reset (rst), + .i_tdata (axis_gain_tdata), + .i_tlast (1'b0), + .i_tvalid (axis_gain_tvalid), + .i_tready (axis_gain_tready), + .o_tdata (axis_round_tdata), + .o_tlast (), + .o_tvalid (axis_round_tvalid), + .o_tready (axis_round_tready) + ); + + + //--------------------------------------------------------------------------- + // Packet Length Control + //--------------------------------------------------------------------------- + + wire [REG_SPP_LEN-1:0] m_tlength_samples; + + assign m_tlength = { m_tlength_samples, 2'b0 }; // 4 bytes per sample + + axis_packetize #( + .DATA_W (32), + .SIZE_W (REG_SPP_LEN), + .FLUSH (1) + ) axis_packetize_i ( + .clk (clk), + .rst (rst), + .gate (~reg_enable), + .size (reg_spp), + .i_tdata (axis_round_tdata), + .i_tvalid (axis_round_tvalid), + .i_tready (axis_round_tready), + .o_tdata (m_tdata), + .o_tlast (m_tlast), + .o_tvalid (m_tvalid), + .o_tready (m_tready), + .o_tuser (m_tlength_samples) + ); + +endmodule diff --git a/fpga/usrp3/lib/rfnoc/sine_tone.v b/fpga/usrp3/lib/rfnoc/sine_tone.v new file mode 100644 index 000000000..a687472eb --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/sine_tone.v @@ -0,0 +1,159 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: sine_tone +// +// Description: +// +// Sine tone generator. This block uses the Xilinx CORDIC IP configured to +// perform the rotate function in units of scaled radians. See the CORDIC IP +// Product Guide (PG105) for details. +// +// The SR_PHASE_INC register controls the phase increment, in scaled +// radians, for the sine waveform generator. It is a 16-bit signed +// fixed-point phase value with 3 integer bits and 13 fractional bits. This +// is the amount by which REG_CARTESIAN is rotated each clock cycle. In +// other words, it controls the rate of rotation, or the frequency, of the +// sine wave. In scaled radians, the phase value range -1 to +1 corresponds +// to -Pi to Pi in radians. +// +// The SR_CARTESIAN register sets the sets the (X,Y) Cartesian coordinate +// that will be rotated to generate the sine output. Both X and Y are 16-bit +// signed fixed-point values with 2 integer bits and 14 fractional bits. Y +// is in the upper 16-bits and X is in the lower 16-bits. +// +// In addition to rotation, the SR_CARTESIAN input vector is also scaled by +// a "CORDIC scale factor" that equals about 1.1644 (that is, the product of +// sqrt(1 + 2^(-2i)) for i = 1 to n, where n = 14, the number of fractional +// bits). +// +// Parameters: +// +// SR_PHASE_INC_ADDR : The address to use for SR_PHASE_INC. +// SR_CARTESIAN_ADDR : The address to use for SR_CARTESIAN. +// + + +module sine_tone #( + parameter WIDTH = 32, + parameter SR_PHASE_INC_ADDR = 129, + parameter SR_CARTESIAN_ADDR = 130 +) ( + input clk, + input reset, + input clear, + input enable, + + // Settings bus + input set_stb, + input [WIDTH-1:0] set_data, + input [ 7:0] set_addr, + + // Output sinusoid + output [WIDTH-1:0] o_tdata, + output o_tlast, + output o_tvalid, + input o_tready +); + + wire [15:0] phase_in_tdata; + wire phase_in_tlast; + wire phase_in_tvalid; + wire phase_in_tready; + + wire [15:0] phase_out_tdata; + wire phase_out_tlast; + wire phase_out_tvalid; + wire phase_out_tready; + + wire [WIDTH-1:0] cartesian_tdata; + wire cartesian_tlast; + wire cartesian_tvalid; + wire cartesian_tready; + + wire [WIDTH-1:0] sine_out_tdata; + wire sine_out_tlast; + wire sine_out_tvalid; + wire sine_out_tready; + + // AXI settings bus for phase values + axi_setting_reg #( + .ADDR (SR_PHASE_INC_ADDR), + .AWIDTH (8), + .WIDTH (16), + .STROBE_LAST (1), + .REPEATS (1) + ) set_phase_acc ( + .clk (clk), + .reset (reset), + .error_stb (), + .set_stb (set_stb), + .set_addr (set_addr), + .set_data (set_data), + .o_tdata (phase_in_tdata), + .o_tlast (phase_in_tlast), + .o_tvalid (phase_in_tvalid), + .o_tready (phase_in_tready & enable) + ); + + // AXI settings bus for Cartesian values + axi_setting_reg #( + .ADDR (SR_CARTESIAN_ADDR), + .AWIDTH (8), + .WIDTH (32), + .REPEATS (1) + ) set_axis_cartesian ( + .clk (clk), + .reset (reset), + .error_stb (), + .set_stb (set_stb), + .set_addr (set_addr), + .set_data (set_data), + .o_tdata (cartesian_tdata), + .o_tlast (), + .o_tvalid (cartesian_tvalid), + .o_tready (cartesian_tready & enable) + ); + + assign cartesian_tlast = 1; + + // Phase accumulator + phase_accum phase_acc ( + .clk (clk), + .reset (reset), + .clear (clear), + .i_tdata (phase_in_tdata), + .i_tlast (phase_in_tlast), + .i_tvalid (1'b1), + .i_tready (phase_in_tready), + .o_tdata (phase_out_tdata), + .o_tlast (phase_out_tlast), + .o_tvalid (phase_out_tvalid), + .o_tready (phase_out_tready & enable) + ); + + // CORDIC + cordic_rotator cordic_inst ( + .aclk (clk), + .aresetn (~(reset|clear)), + .s_axis_phase_tdata (phase_out_tdata), + .s_axis_phase_tvalid (phase_out_tvalid & cartesian_tvalid & enable), + .s_axis_phase_tready (phase_out_tready), + .s_axis_cartesian_tdata (cartesian_tdata), + .s_axis_cartesian_tlast (cartesian_tlast), + .s_axis_cartesian_tvalid (phase_out_tvalid & cartesian_tvalid & enable), + .s_axis_cartesian_tready (cartesian_tready), + .m_axis_dout_tdata (sine_out_tdata), + .m_axis_dout_tlast (sine_out_tlast), + .m_axis_dout_tvalid (sine_out_tvalid), + .m_axis_dout_tready (sine_out_tready & enable) + ); + + assign o_tdata = sine_out_tdata; + assign o_tlast = sine_out_tlast; + assign o_tvalid = sine_out_tvalid; + assign sine_out_tready = o_tready; + +endmodule // sine_tone diff --git a/host/include/uhd/rfnoc/blocks/siggen.yml b/host/include/uhd/rfnoc/blocks/siggen.yml new file mode 100644 index 000000000..78a778479 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/siggen.yml @@ -0,0 +1,55 @@ +schema: rfnoc_modtool_args +module_name: siggen +version: 1.0 +rfnoc_version: 1.0 +chdr_width: 64 +noc_id: 0x51663110 +makefile_srcs: "${fpga_lib_dir}/blocks/rfnoc_block_siggen/Makefile.srcs" + +parameters: + NUM_PORTS: 1 + +clocks: + - name: rfnoc_chdr + freq: "[]" + - name: rfnoc_ctrl + freq: "[]" + - name: ce + freq: "[]" + +control: + sw_iface: nocscript + fpga_iface: ctrlport + interface_direction: slave + fifo_depth: 32 + clk_domain: ce + ctrlport: + byte_mode: False + timed: False + has_status: False + +data: + fpga_iface: axis_data + clk_domain: ce + inputs: + unused: + item_width: 32 + nipc: 1 + info_fifo_depth: 1 + payload_fifo_depth: 1 + format: sc16 + mdata_sig: ~ + outputs: + out: + num_ports: NUM_PORTS + item_width: 32 + nipc: 1 + info_fifo_depth: 32 + payload_fifo_depth: 32 + sideband_at_end: 0 + format: sc16 + mdata_sig: ~ + +registers: + +properties: |