diff options
-rw-r--r-- | fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile | 44 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile.srcs | 22 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/noc_shell_fosphor.v | 344 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor.v | 398 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_regs.vh | 186 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_tb.sv | 590 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/fosphor/fifo_srl.v | 2 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/blocks/fosphor.yml | 56 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/defaults.hpp | 2 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/fosphor_block_control.hpp | 383 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/fosphor_block_control.cpp | 418 |
13 files changed, 2446 insertions, 1 deletions
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile new file mode 100644 index 000000000..58a2cde0e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile @@ -0,0 +1,44 @@ +# +# 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 +BASE_DIR = $(abspath ../../../../top) +# Include viv_sim_preamble 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 Makefile.srcs + +DESIGN_SRCS += $(abspath \ +$(RFNOC_CORE_SRCS) \ +$(RFNOC_UTIL_SRCS) \ +$(RFNOC_OOT_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +SIM_TOP = rfnoc_block_fosphor_tb glbl +SIM_SRCS = \ +$(abspath rfnoc_block_fosphor_tb.sv) \ +$(VIVADO_PATH)/data/verilog/src/glbl.v \ + +#------------------------------------------------- +# 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_fosphor/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile.srcs new file mode 100644 index 000000000..620e993e2 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/Makefile.srcs @@ -0,0 +1,22 @@ +# +# 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)))), \ +rfnoc_block_fosphor.v \ +noc_shell_fosphor.v \ +) diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/noc_shell_fosphor.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/noc_shell_fosphor.v new file mode 100644 index 000000000..dd1845b40 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/noc_shell_fosphor.v @@ -0,0 +1,344 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: noc_shell_fosphor +// +// Description: +// +// This is a tool-generated NoC-shell for the fosphor 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_fosphor #( + parameter [9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter [5:0] MTU = 10 +) ( + //--------------------- + // 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 [(2)*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [(2)-1:0] m_rfnoc_chdr_tlast, + output wire [(2)-1:0] m_rfnoc_chdr_tvalid, + input wire [(2)-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: fft_in + output wire [32*1-1:0] m_fft_in_axis_tdata, + output wire [1-1:0] m_fft_in_axis_tkeep, + output wire m_fft_in_axis_tlast, + output wire m_fft_in_axis_tvalid, + input wire m_fft_in_axis_tready, + output wire [63:0] m_fft_in_axis_ttimestamp, + output wire m_fft_in_axis_thas_time, + output wire [15:0] m_fft_in_axis_tlength, + output wire m_fft_in_axis_teov, + output wire m_fft_in_axis_teob, + // Data Stream from User Logic: hist + input wire [8*4-1:0] s_hist_axis_tdata, + input wire [3:0] s_hist_axis_tkeep, + input wire s_hist_axis_tlast, + input wire s_hist_axis_tvalid, + output wire s_hist_axis_tready, + input wire [63:0] s_hist_axis_ttimestamp, + input wire s_hist_axis_thas_time, + input wire [15:0] s_hist_axis_tlength, + input wire s_hist_axis_teov, + input wire s_hist_axis_teob, + // Data Stream from User Logic: wf + input wire [8*4-1:0] s_wf_axis_tdata, + input wire [3:0] s_wf_axis_tkeep, + input wire s_wf_axis_tlast, + input wire s_wf_axis_tvalid, + output wire s_wf_axis_tready, + input wire [63:0] s_wf_axis_ttimestamp, + input wire s_wf_axis_thas_time, + input wire [15:0] s_wf_axis_tlength, + input wire s_wf_axis_teov, + input wire s_wf_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'h666F0000), + .NUM_DATA_I (1), + .NUM_DATA_O (2), + .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 + //--------------------- + + chdr_to_axis_data #( + .CHDR_W (CHDR_W), + .ITEM_W (32), + .NIPC (1), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE ($clog2(32)), + .PYLD_FIFO_SIZE ($clog2(32)) + ) chdr_to_axis_data_in_fft_in ( + .axis_chdr_clk (rfnoc_chdr_clk), + .axis_chdr_rst (rfnoc_chdr_rst), + .axis_data_clk (axis_data_clk), + .axis_data_rst (axis_data_rst), + .s_axis_chdr_tdata (s_rfnoc_chdr_tdata[(0)*CHDR_W+:CHDR_W]), + .s_axis_chdr_tlast (s_rfnoc_chdr_tlast[0]), + .s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid[0]), + .s_axis_chdr_tready (s_rfnoc_chdr_tready[0]), + .m_axis_tdata (m_fft_in_axis_tdata), + .m_axis_tkeep (m_fft_in_axis_tkeep), + .m_axis_tlast (m_fft_in_axis_tlast), + .m_axis_tvalid (m_fft_in_axis_tvalid), + .m_axis_tready (m_fft_in_axis_tready), + .m_axis_ttimestamp (m_fft_in_axis_ttimestamp), + .m_axis_thas_time (m_fft_in_axis_thas_time), + .m_axis_tlength (m_fft_in_axis_tlength), + .m_axis_teov (m_fft_in_axis_teov), + .m_axis_teob (m_fft_in_axis_teob), + .flush_en (data_i_flush_en), + .flush_timeout (data_i_flush_timeout), + .flush_active (data_i_flush_active[0]), + .flush_done (data_i_flush_done[0]) + ); + + //--------------------- + // Output Data Paths + //--------------------- + + axis_data_to_chdr #( + .CHDR_W (CHDR_W), + .ITEM_W (8), + .NIPC (4), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE ($clog2(32)), + .PYLD_FIFO_SIZE ($clog2(32)), + .MTU (MTU), + .SIDEBAND_AT_END (0) + ) axis_data_to_chdr_out_hist ( + .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)*CHDR_W+:CHDR_W]), + .m_axis_chdr_tlast (m_rfnoc_chdr_tlast[0]), + .m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid[0]), + .m_axis_chdr_tready (m_rfnoc_chdr_tready[0]), + .s_axis_tdata (s_hist_axis_tdata), + .s_axis_tkeep (s_hist_axis_tkeep), + .s_axis_tlast (s_hist_axis_tlast), + .s_axis_tvalid (s_hist_axis_tvalid), + .s_axis_tready (s_hist_axis_tready), + .s_axis_ttimestamp (s_hist_axis_ttimestamp), + .s_axis_thas_time (s_hist_axis_thas_time), + .s_axis_tlength (s_hist_axis_tlength), + .s_axis_teov (s_hist_axis_teov), + .s_axis_teob (s_hist_axis_teob), + .flush_en (data_o_flush_en), + .flush_timeout (data_o_flush_timeout), + .flush_active (data_o_flush_active[0]), + .flush_done (data_o_flush_done[0]) + ); + + axis_data_to_chdr #( + .CHDR_W (CHDR_W), + .ITEM_W (8), + .NIPC (4), + .SYNC_CLKS (0), + .INFO_FIFO_SIZE ($clog2(32)), + .PYLD_FIFO_SIZE ($clog2(32)), + .MTU (MTU), + .SIDEBAND_AT_END (0) + ) axis_data_to_chdr_out_wf ( + .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[(1)*CHDR_W+:CHDR_W]), + .m_axis_chdr_tlast (m_rfnoc_chdr_tlast[1]), + .m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid[1]), + .m_axis_chdr_tready (m_rfnoc_chdr_tready[1]), + .s_axis_tdata (s_wf_axis_tdata), + .s_axis_tkeep (s_wf_axis_tkeep), + .s_axis_tlast (s_wf_axis_tlast), + .s_axis_tvalid (s_wf_axis_tvalid), + .s_axis_tready (s_wf_axis_tready), + .s_axis_ttimestamp (s_wf_axis_ttimestamp), + .s_axis_thas_time (s_wf_axis_thas_time), + .s_axis_tlength (s_wf_axis_tlength), + .s_axis_teov (s_wf_axis_teov), + .s_axis_teob (s_wf_axis_teob), + .flush_en (data_o_flush_en), + .flush_timeout (data_o_flush_timeout), + .flush_active (data_o_flush_active[1]), + .flush_done (data_o_flush_done[1]) + ); + +endmodule // noc_shell_fosphor + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor.v new file mode 100644 index 000000000..40112b05e --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor.v @@ -0,0 +1,398 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fosphor +// +// Description: +// +// Fosphor RFNoC block. This block accepts packets containing FFT data (one +// FFT output per packet) and generates two output data streams, one +// containing histogram data and the other containing waterfall plot data. +// +// Each set of waterfall data is output as a single packet. The frequency of +// waterfall output depends on the waterfall decimation register setting. +// +// Each set of histogram data is output as a burst of 64 packets, followed by +// a single packet of max values and then a single packet of average values. +// The frequency of waterfall output bursts depends on the waterfall +// decimation register setting. +// +// For all outputs, the packets contain byte values, and the number of bytes +// in each packet matches the number of 4-byte sc16 samples in the input +// packets (i.e., the FFT size). In other words, the output packet size is +// 1/4th the input packet size. +// +// Many registers control the visual effects and behavior of the waterfall +// and histogram. See the register descriptions in +// rfnoc_block_fosphor_regs.vh 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). +// + +`default_nettype none + + +module rfnoc_block_fosphor #( + parameter [9:0] THIS_PORTID = 10'd0, + parameter CHDR_W = 64, + parameter [5:0] MTU = 10 +) ( + // 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 [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 [2*CHDR_W-1:0] m_rfnoc_chdr_tdata, + output wire [ (2)-1:0] m_rfnoc_chdr_tlast, + output wire [ (2)-1:0] m_rfnoc_chdr_tvalid, + input wire [ (2)-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 "../../core/rfnoc_chdr_utils.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; + reg m_ctrlport_resp_ack; + reg [31:0] m_ctrlport_resp_data; + // Data Stream to User Logic: in + wire [32*1-1:0] in_tdata; + wire in_tlast; + wire in_tvalid; + wire in_tready; + wire [15:0] in_tlength; + // Data Stream from User Logic: hist + wire [8*4-1:0] hist_tdata; + wire hist_tlast; + wire hist_tvalid; + wire hist_tready; + wire [15:0] hist_tlength; + wire hist_teob; + // Data Stream from User Logic: wf + wire [8*4-1:0] wf_tdata; + wire wf_tlast; + wire wf_tvalid; + wire wf_tready; + wire [15:0] wf_tlength; + + //--------------------------------------------------------------------------- + // NoC Shell + //--------------------------------------------------------------------------- + + wire ce_rst; + + noc_shell_fosphor #( + .CHDR_W (CHDR_W), + .THIS_PORTID (THIS_PORTID), + .MTU (MTU) + ) noc_shell_fosphor_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 to User Logic: in + .m_fft_in_axis_tdata (in_tdata), + .m_fft_in_axis_tkeep (), + .m_fft_in_axis_tlast (in_tlast), + .m_fft_in_axis_tvalid (in_tvalid), + .m_fft_in_axis_tready (in_tready), + .m_fft_in_axis_ttimestamp (), + .m_fft_in_axis_thas_time (), + .m_fft_in_axis_tlength (in_tlength), + .m_fft_in_axis_teov (), + .m_fft_in_axis_teob (), + // Data Stream from User Logic: hist + .s_hist_axis_tdata (hist_tdata), + .s_hist_axis_tkeep (4'hF), + .s_hist_axis_tlast (hist_tlast), + .s_hist_axis_tvalid (hist_tvalid), + .s_hist_axis_tready (hist_tready), + .s_hist_axis_ttimestamp (64'b0), + .s_hist_axis_thas_time (1'b0), + .s_hist_axis_tlength (hist_tlength), + .s_hist_axis_teov (1'b0), + .s_hist_axis_teob (hist_teob), + // Data Stream from User Logic: wf + .s_wf_axis_tdata (wf_tdata), + .s_wf_axis_tkeep (4'hF), + .s_wf_axis_tlast (wf_tlast), + .s_wf_axis_tvalid (wf_tvalid), + .s_wf_axis_tready (wf_tready), + .s_wf_axis_ttimestamp (64'b0), + .s_wf_axis_thas_time (1'b0), + .s_wf_axis_tlength (wf_tlength), + .s_wf_axis_teov (1'b0), + .s_wf_axis_teob (1'b0) + ); + + + //--------------------------------------------------------------------------- + // Registers + //--------------------------------------------------------------------------- + + `include "rfnoc_block_fosphor_regs.vh" + + // Configuration registers + reg [REG_ENABLE_LEN-1:0] cfg_enable; + reg clear_req; + reg fosphor_rst = 1; + reg [REG_RANDOM_LEN-1:0] cfg_random; + reg [REG_HIST_DECIM_LEN-1:0] cfg_hist_decim; + reg [REG_OFFSET_LEN-1:0] cfg_offset; + reg [REG_SCALE_LEN-1:0] cfg_scale; + reg [REG_TRISE_LEN-1:0] cfg_trise; + reg [REG_TDECAY_LEN-1:0] cfg_tdecay; + reg [REG_ALPHA_LEN-1:0] cfg_alpha; + reg [REG_EPSILON_LEN-1:0] cfg_epsilon; + reg [REG_WF_DIV_LEN-1:0] cfg_wf_div; + reg cfg_wf_mode; + reg [REG_WF_DECIM_LEN-1:0] cfg_wf_decim; + reg cfg_hist_decim_changed; + reg cfg_wf_decim_changed; + + always @(posedge ce_clk) begin + if (ce_rst) begin + m_ctrlport_resp_ack <= 0; + m_ctrlport_resp_data <= 'bX; + cfg_enable <= 0; + clear_req <= 0; + fosphor_rst <= 1; + cfg_random <= 0; + cfg_hist_decim <= 0; + cfg_hist_decim_changed <= 0; + cfg_offset <= 0; + cfg_scale <= 0; + cfg_trise <= 0; + cfg_tdecay <= 0; + cfg_alpha <= 0; + cfg_epsilon <= 0; + cfg_wf_div <= 0; + cfg_wf_mode <= 0; + cfg_wf_decim <= 0; + cfg_wf_decim_changed <= 0; + end else begin + // Default assignments + m_ctrlport_resp_ack <= 0; + m_ctrlport_resp_data <= 0; + cfg_hist_decim_changed <= 0; + cfg_wf_decim_changed <= 0; + clear_req <= 0; + fosphor_rst <= 0; + m_ctrlport_resp_data <= 0; + m_ctrlport_resp_ack <= 0; + + // Handle register writes + if (m_ctrlport_req_wr) begin + m_ctrlport_resp_ack <= 1; + case (m_ctrlport_req_addr) + REG_ENABLE : cfg_enable <= m_ctrlport_req_data[0+:REG_ENABLE_LEN]; + REG_CLEAR : begin + fosphor_rst <= m_ctrlport_req_data[REG_RESET_POS]; + clear_req <= m_ctrlport_req_data[REG_CLEAR_POS]; + end + REG_RANDOM : cfg_random <= m_ctrlport_req_data[0+:REG_RANDOM_LEN]; + REG_HIST_DECIM : begin + cfg_hist_decim <= m_ctrlport_req_data[0+:REG_HIST_DECIM_LEN]; + cfg_hist_decim_changed <= 1'b1; + end + REG_OFFSET : cfg_offset <= m_ctrlport_req_data[0+:REG_OFFSET_LEN]; + REG_SCALE : cfg_scale <= m_ctrlport_req_data[0+:REG_SCALE_LEN]; + REG_TRISE : cfg_trise <= m_ctrlport_req_data[0+:REG_TRISE_LEN]; + REG_TDECAY : cfg_tdecay <= m_ctrlport_req_data[0+:REG_TDECAY_LEN]; + REG_ALPHA : cfg_alpha <= m_ctrlport_req_data[0+:REG_ALPHA_LEN]; + REG_EPSILON : cfg_epsilon <= m_ctrlport_req_data[0+:REG_EPSILON_LEN]; + REG_WF_CTRL : begin + cfg_wf_mode <= m_ctrlport_req_data[REG_WF_MODE_POS]; + cfg_wf_div <= m_ctrlport_req_data[REG_WF_DIV_POS+:REG_WF_DIV_LEN]; + end + REG_WF_DECIM : begin + cfg_wf_decim <= m_ctrlport_req_data[0+:REG_WF_DECIM_LEN]; + cfg_wf_decim_changed <= 1'b1; + end + endcase + + // Handle register reads + end else if (m_ctrlport_req_rd) begin + m_ctrlport_resp_ack <= 1; + case (m_ctrlport_req_addr) + REG_ENABLE : m_ctrlport_resp_data[0+:REG_ENABLE_LEN] <= cfg_enable; + REG_RANDOM : m_ctrlport_resp_data[0+:REG_RANDOM_LEN] <= cfg_random; + REG_HIST_DECIM : m_ctrlport_resp_data[0+:REG_HIST_DECIM_LEN] <= cfg_hist_decim; + REG_OFFSET : m_ctrlport_resp_data[0+:REG_OFFSET_LEN] <= cfg_offset; + REG_SCALE : m_ctrlport_resp_data[0+:REG_SCALE_LEN] <= cfg_scale; + REG_TRISE : m_ctrlport_resp_data[0+:REG_TRISE_LEN] <= cfg_trise; + REG_TDECAY : m_ctrlport_resp_data[0+:REG_TDECAY_LEN] <= cfg_tdecay; + REG_ALPHA : m_ctrlport_resp_data[0+:REG_ALPHA_LEN] <= cfg_alpha; + REG_EPSILON : m_ctrlport_resp_data[0+:REG_EPSILON_LEN] <= cfg_epsilon; + REG_WF_CTRL : begin + m_ctrlport_resp_data[REG_WF_MODE_POS] <= cfg_wf_mode; + m_ctrlport_resp_data[REG_WF_DIV_POS+:REG_WF_DIV_LEN] <= cfg_wf_div; + end + REG_WF_DECIM : m_ctrlport_resp_data[0+:REG_WF_DECIM_LEN] <= cfg_wf_decim; + endcase + end + end + end + + + //--------------------------------------------------------------------------- + // Output Packet Length Register + //--------------------------------------------------------------------------- + + // The output length is always 1/4th the input length, since + // we output one byte for each sc16 input. + reg [15:0] out_packet_length; + reg start_of_packet = 1'b1; + + assign wf_tlength = out_packet_length; + assign hist_tlength = out_packet_length; + + always @(posedge ce_clk) begin + if (ce_rst) begin + start_of_packet <= 1'b1; + out_packet_length <= 'bX; + end else begin + if (in_tvalid && in_tready) begin + start_of_packet <= in_tlast; + if (start_of_packet) begin + out_packet_length <= in_tlength / 4; + end + end + end + end + + + //--------------------------------------------------------------------------- + // Fosphor Core + //--------------------------------------------------------------------------- + + wire hist_tvalid_tmp; + wire hist_tready_tmp; + wire wf_tvalid_tmp; + wire wf_tready_tmp; + + f15_core f15_core_inst ( + .clk (ce_clk), + .reset (fosphor_rst), + .clear_req (clear_req), + .cfg_random (cfg_random), + .cfg_offset (cfg_offset), + .cfg_scale (cfg_scale), + .cfg_trise (cfg_trise), + .cfg_tdecay (cfg_tdecay), + .cfg_alpha (cfg_alpha), + .cfg_epsilon (cfg_epsilon), + .cfg_decim (cfg_hist_decim), + .cfg_decim_changed (cfg_hist_decim_changed), + .cfg_wf_div (cfg_wf_div), + .cfg_wf_mode (cfg_wf_mode), + .cfg_wf_decim (cfg_wf_decim), + .cfg_wf_decim_changed (cfg_wf_decim_changed), + .i_tdata (in_tdata), + .i_tlast (in_tlast), + .i_tvalid (in_tvalid), + .i_tready (in_tready), + .o_hist_tdata (hist_tdata), + .o_hist_tlast (hist_tlast), + .o_hist_tvalid (hist_tvalid_tmp), + .o_hist_tready (hist_tready_tmp), + .o_hist_teob (hist_teob), + .o_wf_tdata (wf_tdata), + .o_wf_tlast (wf_tlast), + .o_wf_tvalid (wf_tvalid_tmp), + .o_wf_tready (wf_tready_tmp) + ); + + // Enable/disable logic. All we're doing here is discarding the output for + // the "disabled" output. It is still generated internally. + assign hist_tready_tmp = hist_tready | ~cfg_enable[0]; + assign hist_tvalid = hist_tvalid_tmp & cfg_enable[0]; + assign wf_tready_tmp = wf_tready | ~cfg_enable[1]; + assign wf_tvalid = wf_tvalid_tmp & cfg_enable[1]; + +endmodule // rfnoc_block_fosphor + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_regs.vh new file mode 100644 index 000000000..eca372169 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_regs.vh @@ -0,0 +1,186 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fosphor_vh (Header) +// +// Description: +// +// Fosphor RFNoC block register descriptions. See the block controller +// (fosphor_block_control.hpp) for additional documentation. +// + + +// REG_ENABLE (R/W) +// +// This register enables or disables the stream of histogram data from the +// block. The streams are always generated internally and this register causes +// them to be discarded or not. This register should only be updated when the +// Fosphor block is idle to avoid enabling it while packets are in flight. +// +// [1] : Enable waterfall output stream +// [0] : Enable histogram output stream +// +localparam REG_ENABLE = 'h00; +// +localparam REG_ENABLE_LEN = 2; + +// REG_CLEAR (W) +// +// Controls reset of the Fosphor IP and clearing of the accumulated history +// (average and max hold values). Note that reset is not a superset of clear, +// and both reset and clear should not be asserted in the same write. To reset +// and clear, set only the reset bit in the first write then set only the clear +// bit in a second write. +// +// [1] : Reset (strobe). This is a self-clearing strobe bit to reset the +// internal Fosphor core. +// [0] : Clear (strobe). This is a self-clearing strobe bit to clear the +// history of the fosphor core. +// +localparam REG_CLEAR = 'h04; +// +localparam REG_CLEAR_LEN = 2; +// +localparam REG_RESET_POS = 1; +localparam REG_CLEAR_POS = 0; + +// REG_RANDOM (R/W) +// +// Enables or disables the addition of random noise and/or dithering to the +// incoming signal. +// +// [1] : Noise enable. Adds random numbers to the signal. +// [0] : Dither enable. Randomizes the least-significant bits of the signal. +// +localparam REG_RANDOM = 'h08; +// +localparam REG_RANDOM_LEN = 2; + +// REG_HIST_DECIM (R/W) +// +// [11:0] : Histogram decimation. This determines the amount of histogram data +// that is output relative to the amount of input FFT data. The actual +// decimation is N:1 where N=VALUE+2. That is, you'll get 1 histogram +// output packet for ever N FFT packets received, on average. However, +// histogram data is always output as a burst of 66 packets (64 +// histogram, 1 maximum value, 1 average value). +// +localparam REG_HIST_DECIM = 'h0C; +// +localparam REG_HIST_DECIM_LEN = 12; + +// REG_OFFSET (R/W) +// +// Histogram offset to apply to the FFT power levels before determining the +// appropriate histogram bin. +// +// [15:0] : Offset +// +localparam REG_OFFSET = 'h10; +// +localparam REG_OFFSET_LEN = 16; + +// REG_SCALE (R/W) +// +// Histogram scaling factor. Controls the scaling factor to apply to FFT power +// levels before determining the appropriate histogram bin. The scaling factor +// is scale / 256. +// +// [15:0] : Scale +// +localparam REG_SCALE = 'h14; +// +localparam REG_SCALE_LEN = 16; + +// REG_TRISE (R/W) +// +// Histogram rise rate. Controls the rate at which the hit count in each +// frequency bin increases when there are hits in the particular bin. The +// higher the value, the more quickly the values increase. +// +// [15:0] : Rise time +// +localparam REG_TRISE = 'h18; +// +localparam REG_TRISE_LEN = 16; + +// REG_TDECAY (R/W) +// +// Histogram decay rate. Controls the rate at which the hit count in each +// frequency and power bin decreases when there are no hits in a particular +// bin. The higher the value, the more quickly the values decrease. +// +// [15:0] : Decay time +// +localparam REG_TDECAY = 'h1C; +// +localparam REG_TDECAY_LEN = 16; + +// REG_ALPHA (R/W) +// +// Controls the weighting to be applied to the average power level value for +// each FFT frequency bin. The higher the value, the higher the weight that is +// given to older samples and the more slowly the average values change over +// time in each bin. +// +// [15:0] : Alpha +// +localparam REG_ALPHA = 'h20; +// +localparam REG_ALPHA_LEN = 16; + +// REG_EPSILON (R/W) +// +// Controls the rate at which the maximum value for each FFT frequency bin +// decays. The higher the value, the faster the decay rate. A value of 0 +// retains the maximum values indefinitely. +// +// [15:0] : Epsilon +// +localparam REG_EPSILON = 'h24; +// +localparam REG_EPSILON_LEN = 16; + +// REG_WF_CTRL (R/W) +// +// Waterfall Control register +// +// [7] : Waterfall mode. Controls the source of the waterfall history data. +// When set to "Max Hold", the waterfall data is comprised of the max +// power values from each frequency bin. When set to "Average", the +// waterfall data is comprised of the accumulated average value from +// each frequency bin between waterfall output packets. Can take on the +// following values: +// +// 0 = Max Hold +// 1 = Average +// +// [1:0] : Waterfall pre-division. Controls the scaling factor applied to +// waterfall values. Can take on the following values: +// +// 0 = 1:1 +// 1 = 1:8 +// 2 = 1:64 +// 3 = 1:256 +// +localparam REG_WF_CTRL = 'h28; +// +localparam REG_WF_CTRL_LEN = 8; +// +localparam REG_WF_MODE_POS = 7; +// +localparam REG_WF_DIV_POS = 0; +localparam REG_WF_DIV_LEN = 2; + +// REG_WF_DECIM (R/W) +// +// [7:0] : Waterfall decimation. This controls the amount of waterfall data +// that is output relative to the amount of input FFT data. The actual +// decimation is N:1 where N=VALUE+2. That is, you'll get 1 waterfall +// output packet for ever N FFT packets received. +// +localparam REG_WF_DECIM = 'h2C; +// +localparam REG_WF_DECIM_LEN = 8; diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_tb.sv new file mode 100644 index 000000000..3f46e4383 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_fosphor/rfnoc_block_fosphor_tb.sv @@ -0,0 +1,590 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rfnoc_block_fosphor_tb +// +// Description: Testbench for the fosphor RFNoC block. +// + +`default_nettype none + + +module rfnoc_block_fosphor_tb; + + `include "test_exec.svh" + + import PkgTestExec::*; + import PkgChdrUtils::*; + import PkgRfnocBlockCtrlBfm::*; + import PkgRfnocItemUtils::*; + + `include "rfnoc_block_fosphor_regs.vh" + + //--------------------------------------------------------------------------- + // Testbench Configuration + //--------------------------------------------------------------------------- + + localparam [ 9:0] THIS_PORTID = 10'h123; + localparam [31:0] NOC_ID = 32'h666F0000; + localparam int CHDR_W = 64; + localparam int ITEM_W = 32; + localparam int NUM_PORTS_I = 1; + localparam int NUM_PORTS_O = 2; + localparam int MTU = 13; + localparam int SPP = 128; + localparam int PKT_SIZE_BYTES = SPP * (ITEM_W/8); + localparam int STALL_PROB = 60; // Default BFM stall probability + localparam real CHDR_CLK_PER = 5.0; // 200 MHz + localparam real CTRL_CLK_PER = 25.0; // 40 MHz + localparam real CE_CLK_PER = 5.0; // 200 MHz + + localparam int HIST_PKT_PER_BURST = 66; // Always 64 hist + 1 max + 1 avg + localparam int HIST_PORT = 0; + localparam int WF_PORT = 1; + + //--------------------------------------------------------------------------- + // Clocks and Resets + //--------------------------------------------------------------------------- + + bit rfnoc_chdr_clk; + bit rfnoc_ctrl_clk; + bit ce_clk; + + sim_clock_gen #(CHDR_CLK_PER) rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); + sim_clock_gen #(CTRL_CLK_PER) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); + sim_clock_gen #(CE_CLK_PER) 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_fosphor #( + .THIS_PORTID (THIS_PORTID), + .CHDR_W (CHDR_W), + .MTU (MTU) + ) 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 Functions + //--------------------------------------------------------------------------- + + typedef enum bit { WF_MAX_HOLD, WF_AVERAGE } wf_mode_t; + typedef enum bit [1:0] { WF_1_1, WF_1_8, WF_1_64, WF_1_256 } wf_div_t; + + // Data structure to hold the Fosphor configuration state + typedef struct packed { + bit en_wf; + bit en_hist; + bit en_noise; + bit en_dither; + bit [11:0] hist_decim; + bit [15:0] offset; + bit [15:0] scale; + bit [15:0] trise; + bit [15:0] tdecay; + bit [15:0] alpha; + bit [15:0] epsilon; + wf_mode_t wf_mode; + wf_div_t wf_div; + bit [ 7:0] wf_decim; + } fosphor_config_t; + + // Default configuration copied from GNURadio + const fosphor_config_t DEFAULT_CONFG = '{ + en_wf : 1, + en_hist : 1, + en_noise : 0, + en_dither : 0, + hist_decim : 2, + offset : 0, + scale : 256, + trise : 4096, + tdecay : 16384, + alpha : 65280, + epsilon : 2, + wf_mode : WF_MAX_HOLD, + wf_div : WF_1_8, + wf_decim : 2 + }; + + + // Rand#(WIDTH)::rand_logic() returns a WIDTH-bit random number. We avoid + // std::randomize() due to license requirements and limited tool support. + class Rand #(WIDTH = 32); + + static function logic [WIDTH-1:0] rand_bits(); + bit [WIDTH-1:0] result; + int num_rand32 = (WIDTH + 31) / 32; + for (int i = 0; i < num_rand32; i++) begin + result = {result, $urandom()}; + end + return result; + endfunction : rand_bits + + endclass : Rand + + + // Set all Fosphor registers based off the cfg data structure + task automatic set_registers(fosphor_config_t cfg); + blk_ctrl.reg_write(REG_ENABLE, (int'(cfg.en_wf) << 1) | + (int'(cfg.en_hist) << 0)); + blk_ctrl.reg_write(REG_RANDOM, (int'(cfg.en_noise) << 1) | + (int'(cfg.en_dither) << 0)); + blk_ctrl.reg_write(REG_HIST_DECIM, cfg.hist_decim); + blk_ctrl.reg_write(REG_OFFSET, cfg.offset); + blk_ctrl.reg_write(REG_SCALE, cfg.scale); + blk_ctrl.reg_write(REG_TRISE, cfg.trise); + blk_ctrl.reg_write(REG_TDECAY, cfg.tdecay); + blk_ctrl.reg_write(REG_ALPHA, cfg.alpha); + blk_ctrl.reg_write(REG_EPSILON, cfg.epsilon); + blk_ctrl.reg_write(REG_WF_CTRL, (int'(cfg.wf_mode) << 7) | int'(cfg.wf_div)); + blk_ctrl.reg_write(REG_WF_DECIM, cfg.wf_decim); + endtask : set_registers; + + + // Verify that all the Fosphor registers match the cfg data structure + task automatic verify_registers(fosphor_config_t cfg); + bit [31:0] value; + + blk_ctrl.reg_read(REG_ENABLE, value); + `ASSERT_ERROR(value[1] == cfg.en_wf, "REG_ENABLE[1] didn't have expected value"); + `ASSERT_ERROR(value[0] == cfg.en_hist, "REG_ENABLE[0] didn't have expected value"); + + blk_ctrl.reg_read(REG_CLEAR, value); + `ASSERT_ERROR(value == 0, "REG_CLEAR didn't have expected value"); + + blk_ctrl.reg_read(REG_RANDOM, value); + `ASSERT_ERROR(value[1] == cfg.en_noise, "REG_RANDOM[1] didn't have expected value"); + `ASSERT_ERROR(value[0] == cfg.en_dither, "REG_RANDOM[0] didn't have expected value"); + + blk_ctrl.reg_read(REG_HIST_DECIM, value); + `ASSERT_ERROR(value == cfg.hist_decim, "REG_HIST_DECIM didn't have expected value"); + + blk_ctrl.reg_read(REG_OFFSET, value); + `ASSERT_ERROR(value == cfg.offset, "REG_OFFSET didn't have expected value"); + + blk_ctrl.reg_read(REG_SCALE, value); + `ASSERT_ERROR(value == cfg.scale, "REG_SCALE didn't have expected value"); + + blk_ctrl.reg_read(REG_TRISE, value); + `ASSERT_ERROR(value == cfg.trise, "REG_TRISE didn't have expected value"); + + blk_ctrl.reg_read(REG_TDECAY, value); + `ASSERT_ERROR(value == cfg.tdecay, "REG_TDECAY didn't have expected value"); + + blk_ctrl.reg_read(REG_ALPHA, value); + `ASSERT_ERROR(value == cfg.alpha, "REG_ALPHA didn't have expected value"); + + blk_ctrl.reg_read(REG_EPSILON, value); + `ASSERT_ERROR(value == cfg.epsilon, "REG_EPSILON didn't have expected value"); + + blk_ctrl.reg_read(REG_WF_CTRL, value); + `ASSERT_ERROR(value[7] == cfg.wf_mode, "REG_WF_CTRL didn't have expected value"); + `ASSERT_ERROR(value[1:0] == cfg.wf_div, "REG_WF_CTRL didn't have expected value"); + + blk_ctrl.reg_read(REG_WF_DECIM, value); + `ASSERT_ERROR(value == cfg.wf_decim, "REG_WF_DECIM didn't have expected value"); + endtask : verify_registers; + + + // Generate a random Fosphor configuration to test + task automatic randomize_cfg(output fosphor_config_t cfg, output int spp); + // Chase a random SPP size, but make it a power of 2 (like the FFT) up to + // the define SPP value. + spp = 2**$urandom_range(4, $clog2(SPP)); + + // Start by randomizing the entire fosphor configuration, but then + cfg = Rand #($bits(cfg))::rand_bits(); + + // Keep decimation relatively small to decrease simulation time + cfg.hist_decim = $urandom_range(0, 8); + + // Make sure wf_mode and wf_div are valid values + cfg.wf_mode = wf_mode_t'($urandom_range(cfg.wf_mode.num()-1)); + cfg.wf_div = wf_div_t'($urandom_range(cfg.wf_div.num()-1)); + endtask : randomize_cfg + + + // Test the passed Fosphor configuration. This updates the registers, inputs + // num_packets of data (spp-samples each) and verifies the output. + task automatic test_config(fosphor_config_t cfg, int num_packets, int spp); + item_t fft_items[$]; + + $display("Testing . . ."); + $display(" packets: %0d", num_packets); + $display(" spp: %0d", spp); + $display(" en_wf %0d", cfg.en_wf); + $display(" en_hist %0d", cfg.en_hist); + $display(" hist_decim: %0d", cfg.hist_decim); + $display(" wf_decim: %0d", cfg.wf_decim); + + // Clear any existing data + blk_ctrl.reg_write(REG_CLEAR, 1); + + // Configure all the core's registers + set_registers(cfg); + + // Generate packets to send + fft_items = {}; + for (int i = 0; i < spp; i++) begin + fft_items.push_back({ + shortint'(i), + shortint'(0) + }); + end + + // Send the packets + for (int i = 0; i < num_packets; i++) begin + blk_ctrl.send_items(0, fft_items); + end + + fork + begin : fork_waterfall + item_t recv_items[$]; + int exp_num_packets; + + if (cfg.en_wf) begin + // Calculate expected number of packets + exp_num_packets = num_packets / (cfg.wf_decim + 2); + end else begin + exp_num_packets = 0; + end + + $display("Expecting %0d waterfall packets of length %0d bytes", + exp_num_packets, spp); + + if (exp_num_packets > 0) begin + for (int i = 0; i < exp_num_packets; i++) begin + string err_string; + blk_ctrl.recv_items(WF_PORT, recv_items); + + // We expect one byte output per sample input + $sformat( + err_string, + "Waterfall packet of %0d bytes didn't match expected length of %0d bytes", + recv_items.size()*4, spp + ); + `ASSERT_ERROR(recv_items.size()*4 == spp, err_string); + end + $display("All waterfall packets received!"); + end + end + + begin : fork_histogram + item_t recv_items[$]; + chdr_word_t mdata[$]; + int exp_num_packets; + packet_info_t pkt_info; + + if(cfg.en_hist) begin + // Calculate expected number of packets + exp_num_packets = num_packets / (cfg.hist_decim + 2); + // Round it down to a multiple of HIST_PKT_PER_BURST, since it always + // outputs HIST_PKT_PER_BURST packets at a time. + exp_num_packets = (exp_num_packets / HIST_PKT_PER_BURST) * HIST_PKT_PER_BURST; + end else begin + exp_num_packets = 0; + end + + $display("Expecting %0d histogram packets of length %0d bytes", + exp_num_packets, spp); + + if (exp_num_packets > 0) begin + for (int i = 0; i < exp_num_packets; i++) begin + string err_string; + blk_ctrl.recv_items_adv(HIST_PORT, recv_items, mdata, pkt_info); + //$display("Recvd hist pkt %0d", i); + + // We expect one byte output per sample input + $sformat( + err_string, + "Histogram packet of %0d bytes didn't match expected length of %0d bytes", + recv_items.size()*4, spp + ); + `ASSERT_ERROR(recv_items.size()*4 == spp, err_string); + + // Check that the last packet of each burst has EOB set + if ((i+1) % HIST_PKT_PER_BURST == 0) begin + `ASSERT_ERROR(pkt_info.eob == 1, "EOB was not set on last packet of histogram"); + end else begin + `ASSERT_ERROR(pkt_info.eob == 0, "EOB was set on middle packet histogram"); + end + end + $display("All histogram packets received!"); + end + end + join + + // Wait until all input packets were accepted before moving on, since we + // don't want any output from these packets to be confused with the next + // test. + blk_ctrl.wait_complete(0); + #(CE_CLK_PER * SPP * 2); + + // The current Fosphor core doesn't cleanly handle transitions between + // settings, so we reset the core before each test. + blk_ctrl.reg_write(REG_CLEAR, 2); + + endtask : test_config + + + //--------------------------------------------------------------------------- + // Test Sequences + //--------------------------------------------------------------------------- + + // Test that all the registers read/write as expected + task automatic test_registers(); + fosphor_config_t cfg; + + // All registers reset to 0 + test.start_test("Test Registers (reset values)", 50us); + cfg = '0; + verify_registers(cfg); + test.end_test(); + + test.start_test("Test Registers (max values)", 50us); + cfg = '{ + en_wf : 'h1, + en_hist : 'h1, + en_noise : 'h1, + en_dither : 'h1, + hist_decim : 'hFFF, + offset : 'hFFFF, + scale : 'hFFFF, + trise : 'hFFFF, + tdecay : 'hFFFF, + alpha : 'hFFFF, + epsilon : 'hFFFF, + wf_mode : wf_mode_t'('h1), + wf_div : wf_div_t'('h3), + wf_decim : 'hFF + }; + set_registers(cfg); + verify_registers(cfg); + test.end_test(); + + test.start_test("Test Registers (default values)", 50us); + cfg = DEFAULT_CONFG; + set_registers(cfg); + verify_registers(cfg); + test.end_test(); + endtask : test_registers; + + + // Test waterfall decimation settings + task automatic test_wf_decimation(); + const int spp = 16; + const int num_wf = 4; + fosphor_config_t cfg; + int num_packets; + + test.start_test("Test waterfall decimation", 1ms); + cfg = DEFAULT_CONFG; + cfg.en_hist = 0; + for (int wf_decim = 0; wf_decim < 5; wf_decim++) begin + cfg.wf_decim = wf_decim; + // Input enough packets to get num_wf packets out + num_packets = (wf_decim+2) * (num_wf+1) - 1; + test_config(cfg, num_packets, spp); + end + test.end_test(); + endtask : test_wf_decimation + + + // Test histogram decimation settings + task automatic test_hist_decimation(); + const int spp = 16; + const int num_hist = HIST_PKT_PER_BURST * 4; + fosphor_config_t cfg; + int num_packets; + + test.start_test("Test histogram decimation", 1ms); + cfg = DEFAULT_CONFG; + cfg.en_wf = 0; + for (int hist_decim = 0; hist_decim < 5; hist_decim++) begin + cfg.hist_decim = hist_decim; + // Input enough packets to get num_hist packets out + num_packets = (hist_decim+2) * (num_hist+HIST_PKT_PER_BURST/2); + test_config(cfg, num_packets, spp); + end + test.end_test(); + endtask : test_hist_decimation + + + // Choose num_iter random configurations and test each one + task automatic test_rand_config(int num_iter); + int num_packets, num_packets_wf, num_packets_hist; + int spp; + fosphor_config_t cfg; + const int num_wf = 2; // Get 2 waterfall packets + const int num_hist = HIST_PKT_PER_BURST * 2; // Get 2 histogram bursts + + test.start_test("Test random configurations", num_iter * 10ms); + for(int i = 0; i < num_iter; i++) begin + string str; + $display("<<<<<<<< RANDOM ITERATION %0d >>>>>>>>", i); + //test.current_test = $sformatf("%0d", i); + + // Choose a random configuration + randomize_cfg(cfg, spp); + + // Only allow the output of waterfall or histogram at one time. Because + // they operate independently and their outputs overlap, we only check + // one at a time. This way we can end testing cleanly between output + // packets without cutting off either the waterfall or histogram output. + if (($urandom() & 1) == 0) begin + cfg.en_wf = 1; + cfg.en_hist = 0; + num_packets = (cfg.wf_decim+2) * (num_wf+1) - 1; + end else begin + cfg.en_wf = 0; + cfg.en_hist = 1; + num_packets = (cfg.hist_decim+2) * (num_hist+HIST_PKT_PER_BURST/2); + end + test_config(cfg, num_packets, spp); + end + test.end_test(); + endtask : test_rand_config + + + //--------------------------------------------------------------------------- + // Main Test Process + //--------------------------------------------------------------------------- + + initial begin : tb_main + + // Initialize the test exec object for this testbench + test.start_tb("rfnoc_block_fosphor_tb"); + + // 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 + //-------------------------------- + + test_registers(); + test_wf_decimation(); + test_hist_decimation(); + test_rand_config(16); + + //-------------------------------- + // Finish Up + //-------------------------------- + + // Display final statistics and results + test.end_tb(); + end : tb_main + +endmodule : rfnoc_block_fosphor_tb + + +`default_nettype wire diff --git a/fpga/usrp3/lib/rfnoc/fosphor/fifo_srl.v b/fpga/usrp3/lib/rfnoc/fosphor/fifo_srl.v index 700da18d3..822a1b769 100644 --- a/fpga/usrp3/lib/rfnoc/fosphor/fifo_srl.v +++ b/fpga/usrp3/lib/rfnoc/fosphor/fifo_srl.v @@ -28,7 +28,7 @@ module fifo_srl #( output wire full, output wire afull, - output reg [WIDTH-1:0] do, + output reg [WIDTH-1:0] do = {WIDTH{1'b0}}, input wire rden, output reg empty, diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt index 6435ec2a6..098c79562 100644 --- a/host/include/uhd/rfnoc/CMakeLists.txt +++ b/host/include/uhd/rfnoc/CMakeLists.txt @@ -36,6 +36,7 @@ UHD_INSTALL(FILES dmafifo_block_control.hpp fft_block_control.hpp fir_filter_block_control.hpp + fosphor_block_control.hpp null_block_control.hpp radio_control.hpp diff --git a/host/include/uhd/rfnoc/blocks/fosphor.yml b/host/include/uhd/rfnoc/blocks/fosphor.yml new file mode 100644 index 000000000..d803dc602 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fosphor.yml @@ -0,0 +1,56 @@ +schema: rfnoc_modtool_args +module_name: fosphor +version: 1.0 +rfnoc_version: 1.0 +chdr_width: 64 +noc_id: 0x666F0000 + +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: + fft_in: + item_width: 32 + nipc: 1 + info_fifo_depth: 32 + payload_fifo_depth: 32 + format: sc16 + mdata_sig: ~ + outputs: + hist: + item_width: 8 + nipc: 4 + info_fifo_depth: 32 + payload_fifo_depth: 32 + format: u8 + mdata_sig: ~ + wf: + item_width: 8 + nipc: 4 + info_fifo_depth: 32 + payload_fifo_depth: 32 + format: u8 + mdata_sig: ~ + +registers: + +properties: diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp index bc97fd9f5..38ae57242 100644 --- a/host/include/uhd/rfnoc/defaults.hpp +++ b/host/include/uhd/rfnoc/defaults.hpp @@ -30,6 +30,7 @@ static const std::string NODE_ID_SEP("SEP"); using io_type_t = std::string; static const io_type_t IO_TYPE_SC16 = "sc16"; +static const io_type_t IO_TYPE_U8 = "u8"; static const std::string ACTION_KEY_STREAM_CMD("stream_cmd"); static const std::string ACTION_KEY_RX_EVENT("rx_event"); @@ -75,5 +76,6 @@ static const noc_id_t RADIO_BLOCK = 0x12AD1000; static const noc_id_t DUC_BLOCK = 0xD0C00000; static const noc_id_t DDC_BLOCK = 0xDDC00000; static const noc_id_t FIR_FILTER_BLOCK = 0xf1120000; +static const noc_id_t FOSPHOR_BLOCK = 0x666F0000; }} // namespace uhd::rfnoc diff --git a/host/include/uhd/rfnoc/fosphor_block_control.hpp b/host/include/uhd/rfnoc/fosphor_block_control.hpp new file mode 100644 index 000000000..965b733a7 --- /dev/null +++ b/host/include/uhd/rfnoc/fosphor_block_control.hpp @@ -0,0 +1,383 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/config.hpp> +#include <uhd/rfnoc/noc_block_base.hpp> + +namespace uhd { namespace rfnoc { + +enum class fosphor_waterfall_mode { MAX_HOLD, AVERAGE }; +enum class fosphor_waterfall_predivision_ratio { + RATIO_1_1, + RATIO_1_8, + RATIO_1_64, + RATIO_1_256 +}; + +/*! + * Fosphor Control Class + * + * The Fosphor Block is an RFNoC block that accepts FFT data as signed + * complex 16-bit data and produces two streams of eight-bit data, a + * stream of histogram data and a stream of waterfall data. + * + * \section Histogram + * + * Each time the Fosphor block receives an FFT input packet, the power values + * in each of the N frequency bins are quantized into one of 64 power bins + * (X axis represents individual FFT frequency bins; Y axis represents the + * power bins): + * + * 63 . + * : . . + * : . . . . . . + * 0 . . . . . . . . . . . . . . . . . . . . + * 0 1 2 3 4 5 - - - - - - - - - - - - - - - - - - - - - - N-1 + * + * Each time an FFT power value is quantized to a bin, the bin count + * is increased by one (illustrated by a '+'): + * + * 63 + + * : + + + * : + + + + + + + * 0 + + + + + + + + + + + + + + + + + + + + + * 0 1 2 3 4 5 - - - - - - - - - - - - - - - - - - - - - - N-1 + * + * As more FFT packets are received, the counts in each bin accumulate. + * Over time, the count in the 'closest' power bin to each sample in the FFT + * accumulates at the highest rate. However, adjacent power bins' counts may + * increase due to quantization noise and variances in the input FFT signal + * (highest counts illustrated by '*', followed by '+' and '.'): + * + * 63 . * + + * : . . . + * . * + + * : + + . . + + . + * * + + + * + * + + * * . + . + + . + * 0 * * * * * * * * + + * * * + + * * + + * * * * * * + * 0 1 2 3 4 5 - - - - - - - - - - - - - - - - - - - - - - N-1 + * + * The Fosphor block also calculates the average power level and maximum + * power level encountered in each FFT frequency bin. The rate at which + * the accumulated counts, average power level, and maximum power level + * values rise and fall over time is configurable. + * + * An instance of histogram data output consists of 66 packets: + * + * * 64 packets, one per quantized power level, of N values, representing the + * accumulated count for each frequency bin for that particular quantized + * power level; + * * One packet of N values, representing the average power level in each + * frequency bin; and + * * One packet of N values, representing the maximum power level in each + * frequency bin. + * + * \section Waterfall + * + * The waterfall stream consists of history data of either the average or + * maximum power level values in each bin, depending on the selected waterfall + * mode. In max hold mode, each waterfall packet consists of N values, + * representing the maximum power level in each frequency bin. The rate + * that packets are produced relative to the number of input FFT packets is + * configurable via the waterfall decimation parameter. + * + * In average mode, each waterfall packet consists of N values, representing + * the _sum_ of the average power level in each frequency bin accumulated + * between packets. (Thus, if the decimation rate is increased, the values + * returned are higher than if the decimation rate is decreased.) The + * waterfall predivision ratio parameter can be used to scale the values + * prior to accumulation to counteract this effect. + * + * These streams are intended to be inputs to the GNU Radio Fosphor + * display block, which renders the streams in a entertaining graphical + * format. + */ +class UHD_API fosphor_block_control : public noc_block_base +{ +public: + RFNOC_DECLARE_BLOCK(fosphor_block_control) + + // Block registers + static const uint32_t REG_ENABLE_ADDR; + static const uint32_t REG_CLEAR_ADDR; + static const uint32_t REG_RANDOM_ADDR; + static const uint32_t REG_DECIM_ADDR; + static const uint32_t REG_OFFSET_ADDR; + static const uint32_t REG_SCALE_ADDR; + static const uint32_t REG_TRISE_ADDR; + static const uint32_t REG_TDECAY_ADDR; + static const uint32_t REG_ALPHA_ADDR; + static const uint32_t REG_EPSILON_ADDR; + static const uint32_t REG_WF_CTRL_ADDR; + static const uint32_t REG_WF_DECIM_ADDR; + + /*! Set the histogram stream enable flag + * + * Enables or disables the stream of histogram data from the block. + * + * \param enable_histogram Histogram stream enable/disable flag + */ + virtual void set_enable_histogram(const bool enable_histogram) = 0; + + /*! Get the histogram stream enable flag + * + * Returns the current histogram enable value. + * + * \returns Histogram stream enable/disable flag + */ + virtual bool get_enable_histogram() const = 0; + + /*! Set the waterfall stream enable flag + * + * Enables or disables the stream of waterfall data from the block. + * + * \param enable_waterfall Histogram stream enable/disable flag + */ + virtual void set_enable_waterfall(const bool enable_waterfall) = 0; + + /*! Get the waterfall stream enable flag + * + * Returns the current waterfall enable value. + * + * \returns Histogram stream enable/disable flag + */ + virtual bool get_enable_waterfall() const = 0; + + /*! Clear the Fosphor block's stored history + * + * Clears the accumulated history in the Fosphor block, resetting + * average and max hold values. + */ + virtual void clear_history() = 0; + + /*! Set the dither enable flag + * + * Enables or disables dithering. Dithering adds quantization error + * to the incoming signal. + * + * \param enable_dither Dither enable/disable flag + */ + virtual void set_enable_dither(const bool enable_dither) = 0; + + /*! Get the dither enable flag + * + * Returns the current dither enable value. + * + * \returns Dither enable/disable flag + */ + virtual bool get_enable_dither() const = 0; + + /*! Set the noise enable flag + * + * Enables or disables the addition of random noise to the incoming + * signal. + * + * \param enable_noise Noise enable/disable flag + */ + virtual void set_enable_noise(const bool enable_noise) = 0; + + /*! Get the noise enable flag + * + * Returns the current noise enable value. + * + * \returns Noise enable/disable flag + */ + virtual bool get_enable_noise() const = 0; + + /*! Set the histogram decimation factor + * + * Sets the ratio of histogram outputs to FFT packet inputs. + * For every \p decimation FFT input packets, one histogram + * output cluster (64 histogram packets, plus a maximum and + * average values packet) is produced. The minimum value for + * \p decimation is 2. + * + * \param decimation Histogram decimation factor + */ + virtual void set_histogram_decimation(const uint16_t decimation) = 0; + + /*! Get the histogram decimation factor + * + * Returns the current histogram decimation factor. + * + * \returns Histogram decimation factor + */ + virtual uint16_t get_histogram_decimation() const = 0; + + /*! Set the histogram offset factor + * + * Sets the offset factor to apply to FFT power levels before determining + * the appropriate histogram bin. + * + * \param offset The histogram offset factor to apply + */ + virtual void set_histogram_offset(const uint16_t offset) = 0; + + /*! Get the histogram offset factor + * + * Returns the current histogram offset factor. + * + * \returns The histogram offset factor + */ + virtual uint16_t get_histogram_offset() const = 0; + + /*! Set the histogram scale factor + * + * Sets the scale factor to apply to FFT power levels before determining + * the appropriate histogram bin. The scaling factor is \p scale / 256. + * + * \param scale The histogram scale factor to apply + */ + virtual void set_histogram_scale(const uint16_t scale) = 0; + + /*! Get the history scale factor + * + * Returns the current histogram scale factor. + * + * \returns The histogram scale factor + */ + virtual uint16_t get_histogram_scale() const = 0; + + /*! Set the histogram rise rate factor + * + * Sets the rate at which the hit count in each frequency and power bin + * increases when accumulating (i.e., there are hits in the particular + * bin). The higher the value, the more quickly the values increase, + * leading to a phosphorescent-like effect on the Fosphor display similar + * to the gradual illumination of a CRT display in the area where the + * electron beam is pointing. + * + * \param rise_rate The histogram rise rate factor to apply + */ + virtual void set_histogram_rise_rate(const uint16_t rise_rate) = 0; + + /*! Get the histogram rise rate factor + * + * Returns the current histogram rise rate factor. + * + * \returns The histogram rise rate factor + */ + virtual uint16_t get_histogram_rise_rate() const = 0; + + /*! Set the histogram decay rate factor + * + * Sets the rate at which the hit count in each frequency and power bin + * decreases when not accumulating (i.e., there are no hits in the + * particular bin). The lower the value, the more slowly the values + * decrease, leading to a phosphorescent-like effect on the Fosphor + * display similar to the gradual fading of a CRT display when the + * electron beam is extinguished. + * + * \param decay_rate The histogram decay rate factor to apply + */ + virtual void set_histogram_decay_rate(const uint16_t decay_rate) = 0; + + /*! Get the histogram decay rate factor + * + * Returns the current histogram decay rate factor. + * + * \returns The histogram decay rate factor + */ + virtual uint16_t get_histogram_decay_rate() const = 0; + + /*! Set the power level moving average weighting + * + * Sets the weighing to be applied to the average power level value + * for each FFT frequency bin. The higher the value, the higher the + * weight is given to older samples (and thus the more slowly the average + * values change over time in each bin). + * + * \param alpha The power level moving average weighting to apply + */ + virtual void set_spectrum_alpha(const uint16_t alpha) = 0; + + /*! Get the power level moving average weighting + * + * Returns the weighting that is applied to older samples when calculating + * the average power level for each FFT frequency bin. + * + * \returns The power level moving average weighting + */ + virtual uint16_t get_spectrum_alpha() const = 0; + + /*! Set the maximum hold decay rate + * + * Sets the rate at which the maximum value for each FFT frequency + * bin decays. The higher the value, the faster the decay rate. + * A value of 0 retains the maximum values indefinitely. + * + * \param epsilon The histogram scale factor to apply + */ + virtual void set_spectrum_max_hold_decay(const uint16_t epsilon) = 0; + + /*! Get the maximum hold decay rate + * + * Returns the rate at which the maximum value for each FFT frequency + * bin decays. + * + * \returns The maximum hold decay rate + */ + virtual uint16_t get_spectrum_max_hold_decay() const = 0; + + /*! Set the waterfall predivision ratio + * + * Sets the scaling factor applied to waterfall values. + * + * \param waterfall_predivision The waterfall predivision ratio to apply + */ + virtual void set_waterfall_predivision( + const fosphor_waterfall_predivision_ratio waterfall_predivision) = 0; + + /*! Get the waterfall predivision ratio + * + * Returns the current waterfall predivision ratio. + * + * \returns The waterfall predivision ratio + */ + virtual fosphor_waterfall_predivision_ratio get_waterfall_predivision() const = 0; + + /*! Set the waterfall mode setting + * + * Sets the source of the waterfall history data. When \p waterfall_mode + * is set to `MAX_HOLD`, the waterfall data is comprised of the max + * power values from each FFT frequency bin. When \p waterfall_mode is set + * to `AVERAGE`, the waterfall data is comprised of the accumulated + * average value from each FFT frequency bin between waterfall output + * packets. + * + * \param waterfall_mode The waterfall mode setting + */ + virtual void set_waterfall_mode(const fosphor_waterfall_mode waterfall_mode) = 0; + + /*! Get the waterfall mode setting + * + * Returns the current waterfall mode setting. + * + * \returns The waterfall mode setting + */ + virtual fosphor_waterfall_mode get_waterfall_mode() const = 0; + + /*! Set the waterfall decimation factor + * + * Sets the ratio of waterfall outputs to FFT packet inputs. + * For every \p waterfall_decimation FFT input packets, one waterfall + * output packet is produced. The minimum value for + * \p waterfall_decimation is 2. + * + * \param waterfall_decimation The waterfall decimation factor to apply + */ + virtual void set_waterfall_decimation(const uint16_t waterfall_decimation) = 0; + + /*! Get the histogram decimation factor + * + * Returns the current waterfall decimation factor. + * + * \returns The waterfall decimation factor + */ + virtual uint16_t get_waterfall_decimation() const = 0; +}; + +}} // namespace uhd::rfnoc diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index 4edfe4009..6881a57e8 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -46,6 +46,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/dmafifo_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fft_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fir_filter_block_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fosphor_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/null_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_control_impl.cpp ) diff --git a/host/lib/rfnoc/fosphor_block_control.cpp b/host/lib/rfnoc/fosphor_block_control.cpp new file mode 100644 index 000000000..6bf621982 --- /dev/null +++ b/host/lib/rfnoc/fosphor_block_control.cpp @@ -0,0 +1,418 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <uhd/rfnoc/fosphor_block_control.hpp> +#include <uhd/rfnoc/property.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <string> + +using namespace uhd::rfnoc; + + +// Register offsets +const uint32_t fosphor_block_control::REG_ENABLE_ADDR = 0x00; +const uint32_t fosphor_block_control::REG_CLEAR_ADDR = 0x04; +const uint32_t fosphor_block_control::REG_RANDOM_ADDR = 0x08; +const uint32_t fosphor_block_control::REG_DECIM_ADDR = 0x0c; +const uint32_t fosphor_block_control::REG_OFFSET_ADDR = 0x10; +const uint32_t fosphor_block_control::REG_SCALE_ADDR = 0x14; +const uint32_t fosphor_block_control::REG_TRISE_ADDR = 0x18; +const uint32_t fosphor_block_control::REG_TDECAY_ADDR = 0x1c; +const uint32_t fosphor_block_control::REG_ALPHA_ADDR = 0x20; +const uint32_t fosphor_block_control::REG_EPSILON_ADDR = 0x24; +const uint32_t fosphor_block_control::REG_WF_CTRL_ADDR = 0x28; +const uint32_t fosphor_block_control::REG_WF_DECIM_ADDR = 0x2c; + +// Mask bits +constexpr uint32_t RESET_HISTORY_BIT = 0; +constexpr uint32_t RESET_HISTORY_MASK = (1 << RESET_HISTORY_BIT); +constexpr uint32_t RESET_CORE_BIT = 1; +constexpr uint32_t RESET_CORE_MASK = (1 << RESET_CORE_BIT); + +constexpr uint32_t HISTOGRAM_ENABLE_BIT = 0; +constexpr uint32_t HISTOGRAM_ENABLE_MASK = (1 << HISTOGRAM_ENABLE_BIT); +constexpr uint32_t WATERFALL_ENABLE_BIT = 1; +constexpr uint32_t WATERFALL_ENABLE_MASK = (1 << WATERFALL_ENABLE_BIT); + +constexpr uint32_t DITHER_ENABLE_BIT = 0; +constexpr uint32_t DITHER_ENABLE_MASK = (1 << DITHER_ENABLE_BIT); +constexpr uint32_t NOISE_ENABLE_BIT = 1; +constexpr uint32_t NOISE_ENABLE_MASK = (1 << NOISE_ENABLE_BIT); + +constexpr uint32_t WATERFALL_MODE_BIT = 7; +constexpr uint32_t WATERFALL_MODE_MASK = (1 << WATERFALL_MODE_BIT); +constexpr uint32_t PREDIV_RATIO_MASK = (1 << 0) | (1 << 1); + +// User property names +const char* const PROP_KEY_ENABLE_HISTOGRAM = "enable_histogram"; +const char* const PROP_KEY_ENABLE_WATERFALL = "enable_waterfall"; +const char* const PROP_KEY_CLEAR_HISTORY = "clear_history"; +const char* const PROP_KEY_ENABLE_DITHER = "enable_dither"; +const char* const PROP_KEY_ENABLE_NOISE = "enable_noise"; +const char* const PROP_KEY_HIST_DECIMATION = "hist_decimation"; +const char* const PROP_KEY_OFFSET = "offset"; +const char* const PROP_KEY_SCALE = "scale"; +const char* const PROP_KEY_RISE_TIME = "trise"; +const char* const PROP_KEY_DECAY_TIME = "tdecay"; +const char* const PROP_KEY_ALPHA = "alpha"; +const char* const PROP_KEY_EPSILON = "epsilon"; +const char* const PROP_KEY_WF_PREDIVISION_RATIO = "wf_predivision_ratio"; +const char* const PROP_KEY_WF_MODE = "wf_mode"; +const char* const PROP_KEY_WF_DECIMATION = "wf_decimation"; + +// Edge property details +constexpr uint32_t HISTOGRAM_PORT = 0; +constexpr uint32_t WATERFALL_PORT = 1; + + +class fosphor_block_control_impl : public fosphor_block_control +{ +public: + RFNOC_BLOCK_CONSTRUCTOR(fosphor_block_control) + { + // reset the core upon block construction + this->regs().poke32(REG_CLEAR_ADDR, RESET_CORE_MASK); + _register_props(); + } + + void set_enable_histogram(const bool enable_histogram) + { + set_property<bool>(PROP_KEY_ENABLE_HISTOGRAM, enable_histogram); + } + + bool get_enable_histogram() const + { + return _prop_enable_histogram.get(); + } + + void set_enable_waterfall(const bool enable_waterfall) + { + set_property<bool>(PROP_KEY_ENABLE_WATERFALL, enable_waterfall); + } + + bool get_enable_waterfall() const + { + return _prop_enable_waterfall.get(); + } + + void clear_history() + { + set_property<bool>(PROP_KEY_CLEAR_HISTORY, true); + } + + void set_enable_dither(const bool enable_dither) + { + set_property<bool>(PROP_KEY_ENABLE_DITHER, enable_dither); + } + + bool get_enable_dither() const + { + return _prop_enable_dither.get(); + } + + void set_enable_noise(const bool enable_noise) + { + set_property<bool>(PROP_KEY_ENABLE_NOISE, enable_noise); + } + + bool get_enable_noise() const + { + return _prop_enable_noise.get(); + } + + void set_histogram_decimation(const uint16_t decimation) + { + set_property<int>(PROP_KEY_HIST_DECIMATION, decimation); + } + + uint16_t get_histogram_decimation() const + { + return _prop_hist_decimation.get(); + } + + void set_histogram_offset(const uint16_t offset) + { + set_property<int>(PROP_KEY_OFFSET, offset); + } + + uint16_t get_histogram_offset() const + { + return _prop_offset.get(); + } + + void set_histogram_scale(const uint16_t scale) + { + set_property<int>(PROP_KEY_SCALE, scale); + } + + uint16_t get_histogram_scale() const + { + return _prop_scale.get(); + } + + void set_histogram_rise_rate(const uint16_t rise_rate) + { + set_property<int>(PROP_KEY_RISE_TIME, rise_rate); + } + + uint16_t get_histogram_rise_rate() const + { + return _prop_trise.get(); + } + + void set_histogram_decay_rate(const uint16_t decay_rate) + { + set_property<int>(PROP_KEY_DECAY_TIME, decay_rate); + } + + uint16_t get_histogram_decay_rate() const + { + return _prop_tdecay.get(); + } + + void set_spectrum_alpha(const uint16_t alpha) + { + set_property<int>(PROP_KEY_ALPHA, alpha); + } + + uint16_t get_spectrum_alpha() const + { + return _prop_alpha.get(); + } + + void set_spectrum_max_hold_decay(const uint16_t epsilon) + { + set_property<int>(PROP_KEY_EPSILON, epsilon); + } + + uint16_t get_spectrum_max_hold_decay() const + { + return _prop_epsilon.get(); + } + + void set_waterfall_predivision( + const fosphor_waterfall_predivision_ratio waterfall_predivision) + { + set_property<int>( + PROP_KEY_WF_PREDIVISION_RATIO, static_cast<int>(waterfall_predivision)); + } + + fosphor_waterfall_predivision_ratio get_waterfall_predivision() const + { + return static_cast<fosphor_waterfall_predivision_ratio>( + _prop_wf_prediv_ratio.get()); + } + + void set_waterfall_mode(const fosphor_waterfall_mode waterfall_mode) + { + set_property<int>(PROP_KEY_WF_MODE, static_cast<int>(waterfall_mode)); + } + + fosphor_waterfall_mode get_waterfall_mode() const + { + return static_cast<fosphor_waterfall_mode>(_prop_wf_mode.get()); + } + + void set_waterfall_decimation(const uint16_t waterfall_decimation) + { + set_property<int>(PROP_KEY_WF_DECIMATION, waterfall_decimation); + } + + uint16_t get_waterfall_decimation() const + { + return _prop_wf_decim.get(); + } + + /************************************************************************** + * Initialization + *************************************************************************/ +private: + void _register_props() + { + // register user properties + register_property(&_prop_enable_histogram, [this]() { _program_enables(); }); + register_property(&_prop_enable_waterfall, [this]() { _program_enables(); }); + register_property(&_prop_clear_history, + [this]() { this->regs().poke32(REG_CLEAR_ADDR, RESET_HISTORY_MASK); }); + register_property( + &_prop_enable_dither, [this]() { _program_randomness_enables(); }); + register_property( + &_prop_enable_noise, [this]() { _program_randomness_enables(); }); + register_property(&_prop_hist_decimation, [this]() { + int decim = _prop_hist_decimation.get(); + if (decim < 2 || decim > 1024) { + throw uhd::value_error( + "Histogram decimation value must be in [2, 1024]"); + } + this->regs().poke32(REG_DECIM_ADDR, uint32_t(decim - 2)); + }); + register_property(&_prop_offset, [this]() { + int offset = _prop_offset.get(); + if (offset < 0 || offset > 65535) { + throw uhd::value_error("Offset value must be in [0, 65535]"); + } + this->regs().poke32(REG_OFFSET_ADDR, uint32_t(offset)); + }); + register_property(&_prop_scale, [this]() { + int scale = _prop_scale.get(); + if (scale < 0 || scale > 65535) { + throw uhd::value_error("Scale value must be in [0, 65535]"); + } + this->regs().poke32(REG_SCALE_ADDR, uint32_t(scale)); + }); + register_property(&_prop_trise, [this]() { + int trise = _prop_trise.get(); + if (trise < 0 || trise > 65535) { + throw uhd::value_error("Rise rate value must be in [0, 65535]"); + } + this->regs().poke32(REG_TRISE_ADDR, uint32_t(trise)); + }); + register_property(&_prop_tdecay, [this]() { + int tdecay = _prop_tdecay.get(); + if (tdecay < 0 || tdecay > 65535) { + throw uhd::value_error("Decay rate value must be in [0, 65535]"); + } + this->regs().poke32(REG_TDECAY_ADDR, uint32_t(tdecay)); + }); + register_property(&_prop_alpha, [this]() { + int alpha = _prop_alpha.get(); + if (alpha < 0 || alpha > 65535) { + throw uhd::value_error("Alpha value must be in [0, 65535]"); + } + this->regs().poke32(REG_ALPHA_ADDR, uint32_t(alpha)); + }); + register_property(&_prop_epsilon, [this]() { + int epsilon = _prop_epsilon.get(); + if (epsilon < 0 || epsilon > 65535) { + throw uhd::value_error("Max hold decay rate must be in [0, 65535]"); + } + this->regs().poke32(REG_EPSILON_ADDR, uint32_t(epsilon)); + }); + register_property(&_prop_wf_prediv_ratio, [this]() { + int prediv_ratio = _prop_wf_prediv_ratio.get(); + if (prediv_ratio + < static_cast<int>(fosphor_waterfall_predivision_ratio::RATIO_1_1) + || prediv_ratio > static_cast<int>( + fosphor_waterfall_predivision_ratio::RATIO_1_256)) { + throw uhd::value_error( + "Waterfall predivision ratio value must be in [0, 3]"); + } + _program_waterfall_mode(); + }); + register_property(&_prop_wf_mode, [this]() { + int wf_mode = _prop_wf_mode.get(); + if (wf_mode < static_cast<int>(fosphor_waterfall_mode::MAX_HOLD) + || wf_mode > static_cast<int>(fosphor_waterfall_mode::AVERAGE)) { + throw uhd::value_error("Waterfall mode value must be 0 or 1"); + } + _program_waterfall_mode(); + }); + register_property(&_prop_wf_decim, [this]() { + int wf_decim = _prop_wf_decim.get(); + if (wf_decim < 2 || wf_decim > 257) { + throw uhd::value_error( + "Waterfall decimation value must be in [2, 257]"); + } + this->regs().poke32(REG_WF_DECIM_ADDR, uint32_t(wf_decim - 2)); + }); + + // register edge properties + register_property(&_prop_type_in); + register_property(&_prop_type_out_histogram); + register_property(&_prop_type_out_wf); + + // add resolvers for type + add_property_resolver({&_prop_type_in}, {&_prop_type_in}, [this]() { + _prop_type_in.set(IO_TYPE_SC16); + }); + add_property_resolver({&_prop_type_out_histogram}, + {&_prop_type_out_histogram}, + [this]() { _prop_type_out_histogram.set(IO_TYPE_U8); }); + add_property_resolver({&_prop_type_out_wf}, {&_prop_type_out_wf}, [this]() { + _prop_type_out_wf.set(IO_TYPE_U8); + }); + } + + void _program_enables() + { + uint32_t reg_value = this->regs().peek32(REG_ENABLE_ADDR) + & ~(HISTOGRAM_ENABLE_MASK | WATERFALL_ENABLE_MASK); + uint32_t histogram_enable_bit = + (_prop_enable_histogram.get()) ? HISTOGRAM_ENABLE_MASK : 0; + uint32_t waterfall_enable_bit = + (_prop_enable_waterfall.get()) ? WATERFALL_ENABLE_MASK : 0; + this->regs().poke32( + REG_ENABLE_ADDR, reg_value | histogram_enable_bit | waterfall_enable_bit); + } + + void _program_randomness_enables() + { + uint32_t reg_value = this->regs().peek32(REG_RANDOM_ADDR) + & ~(DITHER_ENABLE_MASK | NOISE_ENABLE_MASK); + uint32_t dither_enable_bit = (_prop_enable_dither.get()) ? DITHER_ENABLE_MASK : 0; + uint32_t noise_enable_bit = (_prop_enable_noise.get()) ? NOISE_ENABLE_MASK : 0; + this->regs().poke32( + REG_RANDOM_ADDR, reg_value | dither_enable_bit | noise_enable_bit); + } + + void _program_waterfall_mode() + { + uint32_t reg_value = this->regs().peek32(REG_WF_CTRL_ADDR) + & ~(WATERFALL_MODE_MASK | PREDIV_RATIO_MASK); + int prediv_ratio = _prop_wf_prediv_ratio.get(); + int wf_mode = _prop_wf_mode.get(); + uint32_t wf_mode_bits = (wf_mode << WATERFALL_MODE_BIT) | prediv_ratio; + this->regs().poke32(REG_WF_CTRL_ADDR, reg_value | wf_mode_bits); + } + + /************************************************************************** + * Attributes + *************************************************************************/ + property_t<std::string> _prop_type_in = property_t<std::string>{ + PROP_KEY_TYPE, IO_TYPE_SC16, {res_source_info::INPUT_EDGE}}; + property_t<std::string> _prop_type_out_histogram = property_t<std::string>{ + PROP_KEY_TYPE, IO_TYPE_U8, {res_source_info::OUTPUT_EDGE, HISTOGRAM_PORT}}; + property_t<std::string> _prop_type_out_wf = property_t<std::string>{ + PROP_KEY_TYPE, IO_TYPE_U8, {res_source_info::OUTPUT_EDGE, WATERFALL_PORT}}; + + property_t<bool> _prop_enable_histogram = + property_t<bool>{PROP_KEY_ENABLE_HISTOGRAM, true, {res_source_info::USER}}; + property_t<bool> _prop_enable_waterfall = + property_t<bool>{PROP_KEY_ENABLE_WATERFALL, true, {res_source_info::USER}}; + property_t<bool> _prop_clear_history = + property_t<bool>{PROP_KEY_CLEAR_HISTORY, false, {res_source_info::USER}}; + property_t<bool> _prop_enable_dither = + property_t<bool>{PROP_KEY_ENABLE_DITHER, true, {res_source_info::USER}}; + property_t<bool> _prop_enable_noise = + property_t<bool>{PROP_KEY_ENABLE_NOISE, true, {res_source_info::USER}}; + property_t<int> _prop_hist_decimation = + property_t<int>{PROP_KEY_HIST_DECIMATION, 2, {res_source_info::USER}}; + property_t<int> _prop_offset = + property_t<int>{PROP_KEY_OFFSET, 0, {res_source_info::USER}}; + property_t<int> _prop_scale = + property_t<int>{PROP_KEY_SCALE, 256, {res_source_info::USER}}; + property_t<int> _prop_trise = + property_t<int>{PROP_KEY_RISE_TIME, 4096, {res_source_info::USER}}; + property_t<int> _prop_tdecay = + property_t<int>{PROP_KEY_DECAY_TIME, 16384, {res_source_info::USER}}; + property_t<int> _prop_alpha = + property_t<int>{PROP_KEY_ALPHA, 65280, {res_source_info::USER}}; + property_t<int> _prop_epsilon = + property_t<int>{PROP_KEY_EPSILON, 1, {res_source_info::USER}}; + property_t<int> _prop_wf_prediv_ratio = property_t<int>{PROP_KEY_WF_PREDIVISION_RATIO, + static_cast<int>(fosphor_waterfall_predivision_ratio::RATIO_1_1), + {res_source_info::USER}}; + property_t<int> _prop_wf_mode = property_t<int>{PROP_KEY_WF_MODE, + static_cast<int>(fosphor_waterfall_mode::MAX_HOLD), + {res_source_info::USER}}; + property_t<int> _prop_wf_decim = + property_t<int>{PROP_KEY_WF_DECIMATION, 8, {res_source_info::USER}}; +}; + +UHD_RFNOC_BLOCK_REGISTER_DIRECT( + fosphor_block_control, FOSPHOR_BLOCK, "Fosphor", CLOCK_KEY_GRAPH, "bus_clk") |