diff options
author | Wade Fife <wade.fife@ettus.com> | 2021-06-08 19:40:46 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 11:56:58 -0500 |
commit | 6d3765605262016a80f71e36357f749ea35cbe5a (patch) | |
tree | 7d62d6622befd4132ac1ee085effa1426f7f53e5 /fpga/usrp3/top/x400/rf/100m | |
parent | f706b89e6974e28ce76aadeeb06169becc86acba (diff) | |
download | uhd-6d3765605262016a80f71e36357f749ea35cbe5a.tar.gz uhd-6d3765605262016a80f71e36357f749ea35cbe5a.tar.bz2 uhd-6d3765605262016a80f71e36357f749ea35cbe5a.zip |
fpga: x400: Add support for X410 motherboard FPGA
Co-authored-by: Andrew Moch <Andrew.Moch@ni.com>
Co-authored-by: Daniel Jepson <daniel.jepson@ni.com>
Co-authored-by: Javier Valenzuela <javier.valenzuela@ni.com>
Co-authored-by: Joerg Hofrichter <joerg.hofrichter@ni.com>
Co-authored-by: Kumaran Subramoniam <kumaran.subramoniam@ni.com>
Co-authored-by: Max Köhler <max.koehler@ni.com>
Co-authored-by: Michael Auchter <michael.auchter@ni.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Hector Rubio <hrubio@ni.com>
Diffstat (limited to 'fpga/usrp3/top/x400/rf/100m')
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd | 114 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v | 120 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd | 143 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd | 118 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd | 83 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd | 87 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/100m/rf_core_100m.v | 362 |
7 files changed, 1027 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd b/fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd new file mode 100644 index 000000000..a941fcb8f --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd @@ -0,0 +1,114 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: adc_3_1_clk_converter +-- +-- Description: +-- +-- This module transfers data from s_axis_aclk to m_axis_aclk. s_axis_aclk +-- must be three times the frequency of m_axis_aclk, and the two clocks must +-- be related (this module requires timing closure across the clock domain +-- boundary). +-- + +library IEEE; + use IEEE.std_logic_1164.all; + +entity adc_3_1_clk_converter is + port( + s_axis_clk : in std_logic; + s_axis_resetn : in std_logic; + s_axis_tdata : in std_logic_vector(47 downto 0); + s_axis_tvalid : in std_logic; + + m_axis_clk : in std_logic; + m_axis_resetn : in std_logic; + m_axis_tvalid : out std_logic; + m_axis_tdata : out std_logic_vector(47 downto 0) + ); +end entity; + +architecture RTL of adc_3_1_clk_converter is + + -- To keep the implementation simple, this module does not implement a + -- correct AXIS handshake - it ignores m_axis_tready. adc_100m_bd already had + -- an assumption that the AXIS handshake is unneeded: ddc_saturate does not + -- accept _tready from the following component. + subtype Word_t is std_logic_vector(s_axis_tdata'range); + signal s_axis_tvalid_pipe : std_logic_vector(2 downto 0); + signal s_axis_tdata_reg : Word_t; + + -- These _CDC signals will be sampled in the destination clock domain, but + -- will not produce any metastability because the input clocks must be + -- synchronous. + -- + -- These signals must be driven by registers not to prevent glitches (as in + -- an asynchronous CDC), but to improve timing closure. + signal s_axis_tvalid_CDC : std_logic; + signal s_axis_tdata_CDC : Word_t; + + -- m_axis_clk and s_axis_clk are nominally aligned by their rising edges. + -- Because m_axis_clk is more heavily loaded than s_axis_clk, m_axis_clk has + -- a larger distribution delay, which causes a large hold violation using + -- post-place timing estimates. The Ultrafast method (UG 949) recommends + -- addressing such hold violations when WHS < -0.5 ns. By resampling on the + -- falling edge of the destination clock, we get nominally half a period of + -- setup and half a period of hold. The destination clock delay reduces the + -- hold margin, and increases the setup margin. + signal m_axis_tvalid_fall : std_logic; + signal m_axis_tdata_fall : Word_t; + +begin + + -- In the source clock domain, we capture incoming valid data and keep a + -- history of _tvalid over the last three clock cycles. If s_axis_tvalid has + -- been asserted once in the last three clock cycles, assert + -- s_axis_tvalid_CDC to be sampled in the output clock domain. The length of + -- s_axis_tvalid_pipe must match the ratio of the clock frequencies (3:1). + InputSampling: + process (s_axis_clk) is + begin + if rising_edge(s_axis_clk) then + if s_axis_tvalid='1' then + s_axis_tdata_reg <= s_axis_tdata; + end if; + s_axis_tdata_CDC <= s_axis_tdata_reg; + if s_axis_resetn='0' then + s_axis_tvalid_pipe <= (others => '0'); + s_axis_tvalid_CDC <= '0'; + else + s_axis_tvalid_pipe <= s_axis_tvalid_pipe(1 downto 0) & s_axis_tvalid; + if (s_axis_tvalid_pipe /= "000") then + s_axis_tvalid_CDC <= '1'; + else + s_axis_tvalid_CDC <= '0'; + end if; + end if; + end if; + end process InputSampling; + + FallingEdgeSampling: + process (m_axis_clk) is + begin + if falling_edge(m_axis_clk) then + m_axis_tvalid_fall <= s_axis_tvalid_CDC; + m_axis_tdata_fall <= s_axis_tdata_CDC; + end if; + end process FallingEdgeSampling; + + OutputRegisters: + process (m_axis_clk) is + begin + if rising_edge(m_axis_clk) then + m_axis_tdata <= m_axis_tdata_fall; + if m_axis_resetn='0' then + m_axis_tvalid <= '0'; + else + m_axis_tvalid <= m_axis_tvalid_fall; + end if; + end if; + end process OutputRegisters; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v b/fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v new file mode 100644 index 000000000..604c04f50 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v @@ -0,0 +1,120 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: adc_gearbox_2x1 +// +// Description: +// +// Gearbox ADC data from 2 SPC to 1 SPC and corresponding 2x clock to 1x +// clock. Also implement data swapping to format packets to fit the FIR +// filter input requirements. +// +// This modules incurs one clk1x cycle of delay on the data and valid signals +// from input on the 1x domain to output on the 2x domain. +// + +`default_nettype none + +module adc_gearbox_2x1 ( + input wire clk1x, + input wire reset_n_1x, + // Data is _presumed_ to be packed [Sample1, Sample0] (Sample0 in LSBs). + input wire [31:0] adc_q_in_1x, + input wire [31:0] adc_i_in_1x, + input wire valid_in_1x, + // De-assert enable_1x to clear the data synchronously from this module. + input wire enable_1x, + + input wire clk2x, + // Data is packed [Q,I] (I in LSBs) when swap_iq_1x is '0'. + input wire swap_iq_2x, + output wire [31:0] adc_out_2x, + output wire valid_out_2x +); + + // Re-create the 1x clock in the 2x domain to produce a deterministic + // crossing. + reg toggle_1x, toggle_2x = 1'b0, toggle_2x_dly = 1'b0, valid_2x = 1'b0, valid_dly_2x = 1'b0; + reg [31:0] data_out_2x = 32'b0, adc_q_data_in_2x = 32'b0, adc_i_data_in_2x = 32'b0; + + // Create a toggle in the 1x clock domain (clock divider /2). + always @(posedge clk1x or negedge reset_n_1x) begin + if ( ! reset_n_1x) begin + toggle_1x <= 1'b0; + end else begin + toggle_1x <= ! toggle_1x; + end + end + + // clk1x and clk2x are nominally aligned on their rising edges, but clk2x is + // more heavily loaded, which results in a later arrival time. That late + // arrival causes large estimated hold violations after place. The Ultrafast + // method (UG 949) suggests fixing post-place hold violations that are worse + // than -0.5 ns. + // Resampling 1x signals on the falling edge of clk2x provides nominally half + // a period of setup and half a period of hold. The late arrival of clk2x + // shifts some of that margin away from hold slack and into setup slack. + reg toggle_2x_fall = 1'b0; + reg [31:0] adc_q_in_2x_fall = 32'b0; + reg [31:0] adc_i_in_2x_fall = 32'b0; + reg valid_in_2x_fall = 1'b0; + reg enable_2x_fall = 1'b0; + + always @(negedge clk2x) begin + toggle_2x_fall <= toggle_1x; + adc_q_in_2x_fall <= adc_q_in_1x; + adc_i_in_2x_fall <= adc_i_in_1x; + valid_in_2x_fall <= valid_in_1x; + enable_2x_fall <= enable_1x; + end + + // Transfer the toggle from the 1x to the 2x domain. Delay the toggle in the + // 2x domain by one cycle and compare it to the non-delayed version. When + // they differ, push data_in[15:0] onto the output. When the match, push + // [31:16] onto the output. The datasheet is unclear on the exact + // implementation. + // + // It is safe to not reset this domain because all of the input signals will + // be cleared by the 1x reset. Safe default values are assigned to all these + // registers. + always @(posedge clk2x) begin + toggle_2x <= toggle_2x_fall; + toggle_2x_dly <= toggle_2x; + adc_q_data_in_2x <= adc_q_in_2x_fall; + adc_i_data_in_2x <= adc_i_in_2x_fall; + // Place Q in the MSBs, I in the LSBs by default, unless swapped = 1. + if (valid_2x) begin + if (swap_iq_2x) begin + if (toggle_2x != toggle_2x_dly) begin + data_out_2x[31:16] <= adc_i_data_in_2x[15:0]; + data_out_2x[15: 0] <= adc_q_data_in_2x[15:0]; + end else begin + data_out_2x[31:16] <= adc_i_data_in_2x[31:16]; + data_out_2x[15: 0] <= adc_q_data_in_2x[31:16]; + end + end else begin + if (toggle_2x != toggle_2x_dly) begin + data_out_2x[31:16] <= adc_q_data_in_2x[15:0]; + data_out_2x[15: 0] <= adc_i_data_in_2x[15:0]; + end else begin + data_out_2x[31:16] <= adc_q_data_in_2x[31:16]; + data_out_2x[15: 0] <= adc_i_data_in_2x[31:16]; + end + end + end else begin + data_out_2x <= 32'b0; + end + // Valid is simply a transferred version of the 1x clock's valid. Delay it + // one more cycle to align outputs. + valid_2x <= valid_in_2x_fall && enable_2x_fall; + valid_dly_2x <= valid_2x; + end + + assign adc_out_2x = data_out_2x; + assign valid_out_2x = valid_dly_2x; + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd b/fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd new file mode 100644 index 000000000..b5df20a08 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd @@ -0,0 +1,143 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_1_3_clk_converter +-- +-- Description: +-- +-- This module transfers data from s_axis_aclk to m_axis_aclk. m_axis_aclk +-- must be three times the frequency of s_axis_aclk, and the two clocks must +-- be related (this module requires timing closure across the clock domain +-- boundary). +-- + +library IEEE; + use IEEE.std_logic_1164.all; + +entity dac_1_3_clk_converter is + port( + s_axis_aclk : in std_logic; + s_axis_aresetn : in std_logic; + s_axis_tvalid : in std_logic; + s_axis_tdata : in std_logic_vector(31 downto 0); + s_axis_tready : out std_logic := '1'; + + m_axis_aclk : in std_logic; + m_axis_aresetn : in std_logic; + m_axis_tready : in std_logic; + m_axis_tdata : out std_logic_vector(31 downto 0); + m_axis_tvalid : out std_logic + ); +end entity dac_1_3_clk_converter; + +architecture RTL of dac_1_3_clk_converter is + + -- I was unable to think of a simple implementation that implements a correct + -- AXIS handshake on both ports. All my ideas became equivalent to a two + -- clock FIFO (although the clocks are synchronous, so the write-to-read + -- latency would have been certain). + -- + -- We don't expect the DAC to ever hold off incoming data, and dac_100m_bd + -- already has the AXIS handshake disconnected: the FIR is configured to + -- disallow back pressure - it has no m_axis_data_tready pin. + -- + -- I'm going with the simple, but not strictly correct, implementation. + -- s_axis_tready will be constantly true, even when it shouldn't be. The + -- bottom line is this component is likely useless for any application but + -- dac_100m_bd. + + type output_fsm is ( + idle, + got_data, + -- The recovery state of delay ensures that we don't re-use an old input + -- valid signal (remember the output clock is 3x the frequency of the input + -- clock) + recovery + ); + + subtype word is std_logic_vector(s_axis_tdata'range); + signal output_state_mclk : output_fsm; + signal axis_tdata_sclk : word; + signal axis_tvalid_sclk : std_logic; + + signal axis_tdata_mclk : word; + signal axis_tvalid_mclk : std_logic; + +begin + + s_axis_tready <= '1'; + + input_valid_register: + process(s_axis_aclk, s_axis_aresetn) is + begin + if s_axis_aresetn='0' then + axis_tvalid_sclk <= '0'; + elsif rising_edge(s_axis_aclk) then + axis_tvalid_sclk <= s_axis_tvalid; + end if; + end process; + + input_data_register: + process (s_axis_aclk) is + begin + if rising_edge(s_axis_aclk) then + axis_tdata_sclk <= s_axis_tdata; + end if; + end process input_data_register; + + -- These CDC registers will not become metastable because the two clock + -- domains are related. + cdc_input_valid_register: + process (m_axis_aclk, m_axis_aresetn) is + begin + if m_axis_aresetn='0' then + axis_tvalid_mclk <= '0'; + elsif rising_edge(m_axis_aclk) then + axis_tvalid_mclk <= axis_tvalid_sclk; + end if; + end process cdc_input_valid_register; + + cdc_input_data_register: + process (m_axis_aclk) is + begin + if rising_edge(m_axis_aclk) then + axis_tdata_mclk <= axis_tdata_sclk; + end if; + end process cdc_input_data_register; + + output_data_register: + process (m_axis_aclk) is + begin + if rising_edge(m_axis_aclk) then + if output_state_mclk=idle then + m_axis_tdata <= axis_tdata_mclk; + end if; + end if; + end process output_data_register; + + fsm: process(m_axis_aresetn, m_axis_aclk) is + begin + if m_axis_aresetn='0' then + output_state_mclk <= idle; + m_axis_tvalid <= '0'; + elsif rising_edge(m_axis_aclk) then + m_axis_tvalid <= '0'; + case output_state_mclk is + when idle => + if axis_tvalid_mclk='1' then + output_state_mclk <= got_data; + end if; + + when got_data => + m_axis_tvalid <= '1'; + output_state_mclk <= recovery; + + when recovery => + output_state_mclk <= idle; + end case; + end if; + end process fsm; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd b/fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd new file mode 100644 index 000000000..fcef4b4e0 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd @@ -0,0 +1,118 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_2_1_clk_converter +-- +-- Description: +-- +-- This module transfers data from s_axis_aclk to m_axis_aclk. s_axis_aclk +-- must be two times the frequency of m_axis_aclk, and the two clocks must be +-- related (this module requires timing closure across the clock domain +-- boundary). +-- + +library IEEE; + use IEEE.std_logic_1164.all; + +entity dac_2_1_clk_converter is + port ( + s_axis_aclk : in std_logic; + s_axis_aresetn : in std_logic; + s_axis_tvalid : in std_logic; + s_axis_tdata : in std_logic_vector(63 downto 0); + + m_axis_aclk : in std_logic; + m_axis_aresetn : in std_logic; + m_axis_tready : in std_logic; + m_axis_tvalid : out std_logic; + m_axis_tdata : out std_logic_vector(63 downto 0) + ); +end entity dac_2_1_clk_converter; + +architecture RTL of dac_2_1_clk_converter is + + -- To keep the implementation simple, this module does not implement a + -- correct AXIS handshake - it ignores m_axis_tready. dac_100m_bd already had + -- an assumption that the AXIS handshake is unneeded: duc_saturate does not + -- accept _tready from the following component. Also, registered_dac_data has + -- never accepted _tready from dac_2_1_clk_converter, so dac_100m_bd has + -- never supported complete AXIS dataflow. + + subtype Word_t is std_logic_vector(s_axis_tdata'range); + signal s_axis_tvalid_pipe : std_logic_vector(1 downto 0); + signal s_axis_tdata_reg : Word_t; + + -- These _CDC signals will be sampled in the destination clock domain, but + -- will not produce any metastability because the input clocks must be + -- synchronous. + -- + -- These signals must be driven by registers not to prevent glitches (as in + -- an asynchronous CDC), but to improve timing closure. + signal s_axis_tvalid_CDC : std_logic; + signal s_axis_tdata_CDC : Word_t; + + -- m_axis_aclk and s_axis_aclk are nominally aligned by their rising edges. + -- Because m_axis_aclk is more heavily loaded than s_axis_aclk, m_axis_aclk + -- has a larger distribution delay, which causes a large hold violation using + -- post-place timing estimates. The Ultrafast method (UG 949) recommends + -- addressing such hold violations when WHS < -0.5 ns. By resampling on the + -- falling edge of the destination clock, we get nominally half a period of + -- setup and half a period of hold. The destination clock delay reduces the + -- hold margin, and increases the setup margin. + signal m_axis_tvalid_fall : std_logic; + signal m_axis_tdata_fall : Word_t; + +begin + + -- In the source clock domain, we capture incoming valid data and keep a + -- history of _tvalid over the last three clock cycles. If s_axis_tvalid has + -- been asserted once in the last three clock cycles, assert + -- s_axis_tvalid_CDC to be sampled in the output clock domain. The length of + -- s_axis_tvalid_pipe must match the ratio of the clock frequencies (2:1). + InputSampling: + process (s_axis_aclk) is + begin + if rising_edge(s_axis_aclk) then + if s_axis_tvalid='1' then + s_axis_tdata_reg <= s_axis_tdata; + end if; + s_axis_tdata_CDC <= s_axis_tdata_reg; + if s_axis_aresetn='0' then + s_axis_tvalid_pipe <= (others => '0'); + s_axis_tvalid_CDC <= '0'; + else + s_axis_tvalid_pipe <= s_axis_tvalid_pipe(0) & s_axis_tvalid; + if (s_axis_tvalid_pipe /= "00") then + s_axis_tvalid_CDC <= '1'; + else + s_axis_tvalid_CDC <= '0'; + end if; + end if; + end if; + end process InputSampling; + + FallingEdgeSampling: + process (m_axis_aclk) is + begin + if falling_edge(m_axis_aclk) then + m_axis_tvalid_fall <= s_axis_tvalid_CDC; + m_axis_tdata_fall <= s_axis_tdata_CDC; + end if; + end process FallingEdgeSampling; + + OutputRegisters: + process (m_axis_aclk) is + begin + if rising_edge(m_axis_aclk) then + m_axis_tdata <= m_axis_tdata_fall; + if m_axis_aresetn='0' then + m_axis_tvalid <= '0'; + else + m_axis_tvalid <= m_axis_tvalid_fall; + end if; + end if; + end process OutputRegisters; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd b/fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd new file mode 100644 index 000000000..9235f4fec --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd @@ -0,0 +1,83 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: ddc_saturate +-- +-- Description: +-- +-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See +-- comments below for full description. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity ddc_saturate is + port( + Clk : in std_logic; + cDataIn : in std_logic_vector(47 downto 0); -- [Q,I] (I in LSBs) + cDataValidIn : in std_logic; + cDataOut : out std_logic_vector(31 downto 0); -- [Q,I] (I in LSBs) + cDataValidOut : out std_logic + ); +end ddc_saturate; + +architecture RTL of ddc_saturate is + + signal cDataOutI : std_logic_vector(15 downto 0) := (others => '0'); + signal cDataOutQ : std_logic_vector(15 downto 0) := (others => '0'); + +begin + + + ----------------------------------------------------------------------------- + -- Saturation + -- + -- The output of the Xilinx FIR Compiler has already been rounded on the LSB + -- side, but hasn't been saturated on the MSB side. + -- Coefficients = 18 bit, 1 integer bit (1.17) + -- Data In = 16 bits, 1 integer bit (1.15) + -- 1.17 * 1.15 = 2.32, and the Xilinx FIR core rounds to 2.15 + -- Data Out = 17 bits, 2 integer bits (2.15), with 17 LSBs already rounded + -- off inside the FIR core. + -- We need to manually saturate the 2.15 number back to a 1.15 number + -- + -- If 2 MSBs = 00, output <= input without MSB, e.g. positive number < 1 + -- If 2 MSBs = 01, output <= 0.111111111111111, e.g. positive number >= 1 + -- If 2 MSBs = 10, output <= 1.000000000000000, e.g. negative number < -1 + -- If 2 MSBs = 11, output <= input without MSB, e.g. negative number >= -1 + ----------------------------------------------------------------------------- + Saturation: + process(Clk) + begin + if rising_edge(Clk) then + -- Pipeline data valid to match the data + cDataValidOut <= cDataValidIn; + + -- I, from cDataIn(16 downto 0) + if cDataIn(16 downto 15) = "01" then + cDataOutI <= "0111111111111111"; + elsif cDataIn(16 downto 15) = "10" then + cDataOutI <= "1000000000000000"; + else + cDataOutI <= cDataIn(15 downto 0); + end if; + + -- Q, from cDataIn(40 downto 24) + if cDataIn(40 downto 39) = "01" then + cDataOutQ <= "0111111111111111"; + elsif cDataIn(40 downto 39) = "10" then + cDataOutQ <= "1000000000000000"; + else + cDataOutQ <= cDataIn(39 downto 24); + end if; + + end if; + end process Saturation; + + cDataOut <= cDataOutQ & cDataOutI; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd b/fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd new file mode 100644 index 000000000..5cb1bc1fc --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd @@ -0,0 +1,87 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: duc_saturate +-- +-- Description: +-- +-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See +-- comments below for full description. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity duc_saturate is + port( + Clk : in std_logic; + cDataIn : in std_logic_vector(47 downto 0); + cDataValidIn : in std_logic; + cReadyForInput : out std_logic; + cDataOut : out std_logic_vector(31 downto 0); + cDataValidOut : out std_logic := '0' + ); +end duc_saturate; + +architecture RTL of duc_saturate is + + signal cDataOutI : std_logic_vector(15 downto 0) := (others => '0'); + signal cDataOutQ : std_logic_vector(15 downto 0) := (others => '0'); + +begin + + ----------------------------------------------------------------------------- + -- Saturation + -- + -- The output of the Xilinx FIR Compiler has already been rounded on the LSB + -- side, but hasn't been saturated on the MSB side. + -- Coefficients = 18 bit, 1 integer bit (1.17) + -- Data In = 16 bits, 1 integer bit (1.15) + -- Xilinx FIR core rounds to output to 3.31. The filter coefficients has a + -- gain of 3 to compensate for the amplitude loss in interpolation, the + -- Xilinx FIR core rounds the output to 3.15. + -- Data Out = 18 bits, 3 integer bits (3.15), with 16 LSBs already rounded + -- off inside the FIR core. + -- We need to manually saturate the 3.15 number back to a 1.15 number + -- + -- If 3 MSBs = 000, output <= input without MSB, e.g. positive number < 1 + -- If 3 MSBs = 0x1/01x, output <= 0.111111111111111, e.g. positive number >= 1 + -- If 3 MSBs = 1x0/10x, output <= 1.000000000000000, e.g. negative number < -1 + -- If 3 MSBs = 111, output <= input without MSB, e.g. negative number >= -1 + ----------------------------------------------------------------------------- +Saturation: + process(Clk) + begin + if rising_edge(Clk) then + -- Pipeline data valid to match the data + cDataValidOut <= cDataValidIn; + + -- I, from cDataIn(17 downto 0) + if cDataIn(17) = '0' and cDataIn(16 downto 15) /= "00" then + cDataOutI <= "0111111111111111"; + elsif cDataIn(17) = '1' and cDataIn(16) /= cDataIn(15) then + cDataOutI <= "1000000000000000"; + else + cDataOutI <= cDataIn(15 downto 0); + end if; + + -- Q, from cDataIn(41 downto 24) + if cDataIn(41) = '0' and cDataIn(40 downto 39) /= "00" then + cDataOutQ <= "0111111111111111"; + elsif cDataIn(41) = '1' and + (not (cDataIn(40 downto 39) = "11")) then + cDataOutQ <= "1000000000000000"; + else + cDataOutQ <= cDataIn(39 downto 24); + end if; + + end if; + end process Saturation; + + cDataOut <= cDataOutQ & cDataOutI; + cReadyForInput <= '1'; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/rf_core_100m.v b/fpga/usrp3/top/x400/rf/100m/rf_core_100m.v new file mode 100644 index 000000000..9ca837e47 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/rf_core_100m.v @@ -0,0 +1,362 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rf_core_100m +// +// Description: +// +// Top-level wrapper for the ADC/DAC processing logic. One of these wrappers +// exists for every supported Data Rate. An instance of this core should +// exist per dboard. +// +// Data/RF Specs: +// DBs: 1 +// RX/DB: 2 +// TX/DB: 2 +// Data Rate: 122.88 or 125 MSps @ 1 SPC +// +// Input Clocks, all aligned to one another and coming from same MMCM +// rfdc_clk: 184.32 or 187.5 MHz (3x pll_ref_clk) +// rfdc_clk_2x: 368.64 or 375 MHz (6x pll_ref_clk) +// data_clk: 122.88 or 125 MHz (2x pll_ref_clk) +// + +`default_nettype none + +module rf_core_100m ( + + //--------------------------------------------------------------------------- + // Clocking + //--------------------------------------------------------------------------- + + // Main Clock Inputs + input wire rfdc_clk, + input wire rfdc_clk_2x, + input wire data_clk, + input wire data_clk_2x, // Unused, kept for rf_core_* interface consistency. + + // AXI4-Lite Configuration Clock + // This clock is used to synchronize status bits for the RFDC registers in + // the AXI-S clock domain. + input wire s_axi_config_clk, + + //--------------------------------------------------------------------------- + // RFDC Data Interfaces + //--------------------------------------------------------------------------- + // All ports here are in the rfdc_clk domain. + + // ADC + input wire [31:0] adc_data_in_i_tdata_0, + output wire adc_data_in_i_tready_0, + input wire adc_data_in_i_tvalid_0, + input wire [31:0] adc_data_in_q_tdata_0, + output wire adc_data_in_q_tready_0, + input wire adc_data_in_q_tvalid_0, + input wire [31:0] adc_data_in_i_tdata_1, + output wire adc_data_in_i_tready_1, + input wire adc_data_in_i_tvalid_1, + input wire [31:0] adc_data_in_q_tdata_1, + output wire adc_data_in_q_tready_1, + input wire adc_data_in_q_tvalid_1, + + // DAC + output wire [63:0] dac_data_out_tdata_0, + input wire dac_data_out_tready_0, + output wire dac_data_out_tvalid_0, + output wire [63:0] dac_data_out_tdata_1, + input wire dac_data_out_tready_1, + output wire dac_data_out_tvalid_1, + + //--------------------------------------------------------------------------- + // User Data Interfaces + //--------------------------------------------------------------------------- + // All ports here are in the data_clk domain. + + // ADC + output wire [31:0] adc_data_out_tdata_0, // Packed [Q,I] with Q in MSBs + output wire adc_data_out_tvalid_0, + output wire [31:0] adc_data_out_tdata_1, // Packed [Q,I] with Q in MSBs + output wire adc_data_out_tvalid_1, + + // DAC + input wire [31:0] dac_data_in_tdata_0, // Packed [Q,I] with Q in MSBs + output wire dac_data_in_tready_0, + input wire dac_data_in_tvalid_0, + input wire [31:0] dac_data_in_tdata_1, // Packed [Q,I] with Q in MSBs + output wire dac_data_in_tready_1, + input wire dac_data_in_tvalid_1, + + //--------------------------------------------------------------------------- + // Miscellaneous + //--------------------------------------------------------------------------- + + // Invert I/Q control signals from RFDC to DSP chain. + input wire [3:0] invert_adc_iq_rclk2, + input wire [3:0] invert_dac_iq_rclk2, + + // Control/status vectors from/to RFDC. + // Notice these are all in the s_axi_config_clk domain. + output wire [15:0] dsp_info_sclk, + output wire [15:0] axi_status_sclk, + + // Resets. + input wire adc_data_out_resetn_dclk, + input wire adc_enable_data_rclk, + input wire adc_rfdc_axi_resetn_rclk, + input wire dac_data_in_resetn_dclk, + input wire dac_data_in_resetn_rclk, + input wire dac_data_in_resetn_rclk2x, + input wire fir_resetn_rclk2x, + + // Version (Constant) + output wire [95:0] version_info +); + + `include "../../regmap/rfdc_regs_regmap_utils.vh" + `include "../../regmap/versioning_regs_regmap_utils.vh" + `include "../../regmap/versioning_utils.vh" + + // Fixed for this implementation. + localparam NUM_ADC_CHANNELS = 2; + localparam NUM_DAC_CHANNELS = 2; + + // ADC data interface from RFDC. + wire [31:0] adc_data_in_i_tdata [0:7]; // 2 SPC (I) + wire [31:0] adc_data_in_q_tdata [0:7]; // 2 SPC (Q) + wire [ 7:0] adc_data_in_i_tready; + wire [ 7:0] adc_data_in_q_tready; + wire [ 7:0] adc_data_in_i_tvalid; + wire [ 7:0] adc_data_in_q_tvalid; + // DAC data interface to RFDC. + wire [63:0] dac_data_out_tdata [0:7]; // 2 SPC (I + Q) + wire [ 7:0] dac_data_out_tready; + wire [ 7:0] dac_data_out_tvalid; + + // ADC data interface to user. + wire [31:0] adc_data_out_tdata [0:7]; // 1 SPC (I + Q) + wire [ 7:0] adc_data_out_tready; + wire [ 7:0] adc_data_out_tvalid; + // DAC data interface from user. + wire [31:0] dac_data_in_tdata_preswap [0:7]; // 1 SPC (I + Q) + wire [31:0] dac_data_in_tdata [0:7]; // 1 SPC (I + Q) + wire [ 7:0] dac_data_in_tready; + wire [ 7:0] dac_data_in_tvalid; + + wire [ 7:0] invert_dac_iq_dclk; + wire [15:0] axi_status; + + //--------------------------------------------------------------------------- + // Resets, Debug and Misc. + //--------------------------------------------------------------------------- + + // Group all these status bits together. They don't toggle frequently so data + // coherency is not an issue here. + // Using constants for DB0 since the bits are the 16 LSBs in a 32-bit vector. + // DB1 simply uses the 16 MSBs when wiring the status vector. + assign axi_status[USER_ADC_TREADY_MSB :USER_ADC_TREADY ] = adc_data_out_tready [1:0]; + assign axi_status[USER_ADC_TVALID_MSB :USER_ADC_TVALID ] = adc_data_out_tvalid [1:0]; + assign axi_status[RFDC_ADC_I_TVALID_MSB:RFDC_ADC_I_TVALID] = adc_data_in_i_tvalid[1:0]; + assign axi_status[RFDC_ADC_Q_TVALID_MSB:RFDC_ADC_Q_TVALID] = adc_data_in_q_tvalid[1:0]; + assign axi_status[RFDC_ADC_I_TREADY_MSB:RFDC_ADC_I_TREADY] = adc_data_in_i_tready[1:0]; + assign axi_status[RFDC_ADC_Q_TREADY_MSB:RFDC_ADC_Q_TREADY] = adc_data_in_q_tready[1:0]; + assign axi_status[RFDC_DAC_TVALID_MSB :RFDC_DAC_TVALID ] = dac_data_out_tvalid [1:0]; + assign axi_status[RFDC_DAC_TREADY_MSB :RFDC_DAC_TREADY ] = dac_data_out_tready [1:0]; + + synchronizer #( + .WIDTH (16), + .STAGES (2), + .INITIAL_VAL (0), + .FALSE_PATH_TO_IN (1) + ) synchronizer_axis_status ( + .clk (s_axi_config_clk), + .rst (1'b0), + .in (axi_status), + .out (axi_status_sclk) + ); + + // Drive the DSP info vector with information on this specific DSP chain. + assign dsp_info_sclk[FABRIC_DSP_BW_MSB :FABRIC_DSP_BW] = FABRIC_DSP_BW_100M; + assign dsp_info_sclk[FABRIC_DSP_RX_CNT_MSB:FABRIC_DSP_RX_CNT] = NUM_ADC_CHANNELS; + assign dsp_info_sclk[FABRIC_DSP_TX_CNT_MSB:FABRIC_DSP_TX_CNT] = NUM_DAC_CHANNELS; + + //--------------------------------------------------------------------------- + // ADC Post-Processing + //--------------------------------------------------------------------------- + + // Data comes from the RFDC as 2 SPC, separate streams for each channel and + // I/Q. + assign adc_data_in_i_tdata[0] = adc_data_in_i_tdata_0; + assign adc_data_in_q_tdata[0] = adc_data_in_q_tdata_0; + assign adc_data_in_i_tdata[1] = adc_data_in_i_tdata_1; + assign adc_data_in_q_tdata[1] = adc_data_in_q_tdata_1; + + assign adc_data_in_i_tready_0 = adc_data_in_i_tready[0]; + assign adc_data_in_i_tvalid[0] = adc_data_in_i_tvalid_0; + assign adc_data_in_q_tready_0 = adc_data_in_q_tready[0]; + assign adc_data_in_q_tvalid[0] = adc_data_in_q_tvalid_0; + assign adc_data_in_i_tready_1 = adc_data_in_i_tready[1]; + assign adc_data_in_i_tvalid[1] = adc_data_in_i_tvalid_1; + assign adc_data_in_q_tready_1 = adc_data_in_q_tready[1]; + assign adc_data_in_q_tvalid[1] = adc_data_in_q_tvalid_1; + + // ADC Data from the RFDC arrives here as 2 SPC with separate I and Q + // streams. It leaves the adc_100m_bd as 1 SPC with I and Q packed into a + // single 32 bit word. + genvar adc_num; + generate + for (adc_num=0; adc_num < (NUM_ADC_CHANNELS); adc_num = adc_num + 1) + begin : adc_gen + adc_100m_bd adc_100m_bd_gen ( + .adc_data_out_resetn_dclk (adc_data_out_resetn_dclk), + .data_clk (data_clk), + .enable_data_to_fir_rclk (adc_enable_data_rclk), + .fir_resetn_rclk2x (fir_resetn_rclk2x), + .rfdc_adc_axi_resetn_rclk (adc_rfdc_axi_resetn_rclk), + .rfdc_clk (rfdc_clk), + .rfdc_clk_2x (rfdc_clk_2x), + .swap_iq_2x (invert_adc_iq_rclk2 [adc_num]), + .adc_data_out_tvalid (adc_data_out_tvalid [adc_num]), + .adc_data_out_tdata (adc_data_out_tdata [adc_num]), + .adc_i_data_in_tvalid (adc_data_in_i_tvalid[adc_num]), + .adc_i_data_in_tready (adc_data_in_i_tready[adc_num]), + .adc_i_data_in_tdata (adc_data_in_i_tdata [adc_num]), + .adc_q_data_in_tvalid (adc_data_in_q_tvalid[adc_num]), + .adc_q_data_in_tready (adc_data_in_q_tready[adc_num]), + .adc_q_data_in_tdata (adc_data_in_q_tdata [adc_num]) + ); + end + endgenerate + + // Data is released to the user as 1 SPC, separate streams for each channel. + assign adc_data_out_tdata_0 = adc_data_out_tdata[0]; + assign adc_data_out_tdata_1 = adc_data_out_tdata[1]; + + // There is no tready going to the ADC (one has to be always ready for ADC + // data), but it is still a component of the axi_status vector as a generic + // AXI stream status. Report 1'b1 to the status vector consistent with being + // always ready + assign adc_data_out_tready[0] = 1'b1; + assign adc_data_out_tvalid_0 = adc_data_out_tvalid[0]; + assign adc_data_out_tready[1] = 1'b1; + assign adc_data_out_tvalid_1 = adc_data_out_tvalid[1]; + + //--------------------------------------------------------------------------- + // DAC Pre-Processing + //--------------------------------------------------------------------------- + + // Data comes from the user as 1 SPC, separate streams for each channel. + assign dac_data_in_tdata_preswap[0] = dac_data_in_tdata_0; + assign dac_data_in_tdata_preswap[1] = dac_data_in_tdata_1; + + assign dac_data_in_tready_0 = dac_data_in_tready[0]; + assign dac_data_in_tvalid[0] = dac_data_in_tvalid_0; + assign dac_data_in_tready_1 = dac_data_in_tready[1]; + assign dac_data_in_tvalid[1] = dac_data_in_tvalid_1; + + // Optionally swap IQ data positions in the vector. First cross the swap + // vector over to the data_clk domain. + synchronizer #( + .WIDTH (8), + .STAGES (2), + .INITIAL_VAL (0), + .FALSE_PATH_TO_IN (1) + ) synchronizer_invert_dac_iq ( + .clk (data_clk), + .rst (1'b0), + .in (invert_dac_iq_rclk2), + .out (invert_dac_iq_dclk) + ); + + genvar dac_num; + generate + for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1) + begin : dac_swap_gen + assign dac_data_in_tdata[dac_num][15:00] = invert_dac_iq_dclk[dac_num] ? + (dac_data_in_tdata_preswap[dac_num][31:16]) : (dac_data_in_tdata_preswap[dac_num][15:0]); + assign dac_data_in_tdata[dac_num][31:16] = invert_dac_iq_dclk[dac_num] ? + (dac_data_in_tdata_preswap[dac_num][15:00]) : (dac_data_in_tdata_preswap[dac_num][31:16]); + end + endgenerate + + // These streams are then interpolated by dac_100m_bd, and form a single + // stream per channel, 2 SPC, packed: MSB [Sample1Q, Sample1I, Sample0Q, + // Sample0I] LSB. + generate + for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1) + begin : dac_gen + dac_100m_bd dac_100m_bd_gen ( + .dac_data_in_resetn_dclk (dac_data_in_resetn_dclk), + .dac_data_in_resetn_rclk (dac_data_in_resetn_rclk), + .dac_data_in_resetn_rclk2x (dac_data_in_resetn_rclk2x), + .data_clk (data_clk), + .rfdc_clk (rfdc_clk), + .rfdc_clk_2x (rfdc_clk_2x), + .dac_data_out_tdata (dac_data_out_tdata [dac_num]), + .dac_data_out_tvalid (dac_data_out_tvalid[dac_num]), + .dac_data_out_tready (dac_data_out_tready[dac_num]), + .dac_data_in_tdata (dac_data_in_tdata [dac_num]), + .dac_data_in_tvalid (dac_data_in_tvalid [dac_num]), + .dac_data_in_tready (dac_data_in_tready [dac_num]) + ); + end + endgenerate + + // Data is released to the RFDC as 2 SPC, separate streams per channel (I/Q + // together). + assign dac_data_out_tdata_0 = dac_data_out_tdata[0]; + assign dac_data_out_tdata_1 = dac_data_out_tdata[1]; + + assign dac_data_out_tready[0] = dac_data_out_tready_0; + assign dac_data_out_tvalid_0 = dac_data_out_tvalid[0]; + assign dac_data_out_tready[1] = dac_data_out_tready_1; + assign dac_data_out_tvalid_1 = dac_data_out_tvalid[1]; + + //--------------------------------------------------------------------------- + // Version + //--------------------------------------------------------------------------- + + // Version metadata, constants come from auto-generated + // versioning_regs_regmap_utils.vh + assign version_info = build_component_versions( + RF_CORE_100M_VERSION_LAST_MODIFIED_TIME, + build_version( + RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MAJOR, + RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MINOR, + RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_BUILD + ), + build_version( + RF_CORE_100M_CURRENT_VERSION_MAJOR, + RF_CORE_100M_CURRENT_VERSION_MINOR, + RF_CORE_100M_CURRENT_VERSION_BUILD + ) + ); + +endmodule + +`default_nettype wire + +//XmlParse xml_on +//<regmap name="VERSIONING_REGS_REGMAP"> +// <group name="VERSIONING_CONSTANTS"> +// <enumeratedtype name="RF_CORE_100M_VERSION" showhex="true"> +// <info> +// 100 MHz RF core.{BR/} +// For guidance on when to update these revision numbers, +// please refer to the register map documentation accordingly: +// <li> Current version: @.VERSIONING_REGS_REGMAP..CURRENT_VERSION +// <li> Oldest compatible version: @.VERSIONING_REGS_REGMAP..OLDEST_COMPATIBLE_VERSION +// <li> Version last modified: @.VERSIONING_REGS_REGMAP..VERSION_LAST_MODIFIED +// </info> +// <value name="RF_CORE_100M_CURRENT_VERSION_MAJOR" integer="1"/> +// <value name="RF_CORE_100M_CURRENT_VERSION_MINOR" integer="0"/> +// <value name="RF_CORE_100M_CURRENT_VERSION_BUILD" integer="0"/> +// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MAJOR" integer="1"/> +// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MINOR" integer="0"/> +// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_BUILD" integer="0"/> +// <value name="RF_CORE_100M_VERSION_LAST_MODIFIED_TIME" integer="0x20102617"/> +// </enumeratedtype> +// </group> +//</regmap> +//XmlParse xml_off |