aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/rf/common
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/top/x400/rf/common')
-rw-r--r--fpga/usrp3/top/x400/rf/common/PkgRf.vhd220
-rw-r--r--fpga/usrp3/top/x400/rf/common/axis_mux.vhd98
-rw-r--r--fpga/usrp3/top/x400/rf/common/capture_sysref.v50
-rw-r--r--fpga/usrp3/top/x400/rf/common/clock_gates.vhd300
-rw-r--r--fpga/usrp3/top/x400/rf/common/gpio_to_axis_mux.vhd147
-rw-r--r--fpga/usrp3/top/x400/rf/common/rf_nco_reset.vhd228
-rw-r--r--fpga/usrp3/top/x400/rf/common/rf_reset.vhd216
-rw-r--r--fpga/usrp3/top/x400/rf/common/rf_reset_controller.vhd208
-rw-r--r--fpga/usrp3/top/x400/rf/common/scale_2x.vhd51
-rw-r--r--fpga/usrp3/top/x400/rf/common/sync_wrapper.v43
10 files changed, 1561 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/rf/common/PkgRf.vhd b/fpga/usrp3/top/x400/rf/common/PkgRf.vhd
new file mode 100644
index 000000000..9b31a7a02
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/PkgRf.vhd
@@ -0,0 +1,220 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: PkgRf
+--
+-- Description:
+--
+-- This package has some type definition and functions used in the RF data
+-- chain.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+package PkgRf is
+
+ -- DDC sample data out width.
+ constant kDdcDataOutWidth : natural := 17;
+ -- Each sample is padded in MSB with 7 extra bits of zero to byte align.
+ constant kDdcDataWordWidth : natural := kDdcDataOutWidth+7;
+ -- DUC sample data out width.
+ constant kDucDataOutWidth : natural := 18;
+ -- Each sample is padded in MSB with 6 extra bits of zero to byte align.
+ constant kDucDataWordWidth : natural := kDucDataOutWidth+6;
+ -- Saturated data output width.
+ constant kSatDataWidth : natural := 16;
+ -- ADC sample resolution.
+ constant kAdcSampleRes : natural := 16;
+
+ subtype Sample18_t is signed(17 downto 0);
+ subtype Sample17_t is signed(16 downto 0);
+ subtype Sample16_t is signed(15 downto 0);
+ subtype Sample16slv_t is std_logic_vector(15 downto 0);
+
+ type Samples16_t is array(natural range<>) of Sample16_t;
+ type Samples17_t is array(natural range<>) of Sample17_t;
+ type Samples18_t is array(natural range<>) of Sample18_t;
+
+ -- These constants have the largest and smallest 18-bit, 17-bit, and 16-bit
+ -- signed values.
+ constant kLargest18 : Sample18_t := to_signed(2**17 - 1, 18);
+ constant kSmallest18 : Sample18_t := to_signed(-2**17, 18);
+ constant kLargest17 : Sample17_t := to_signed(2**16 - 1, 17);
+ constant kSmallest17 : Sample17_t := to_signed(-2**16, 17);
+ constant kLargest16 : Sample16_t := to_signed(2**15 - 1, 16);
+ constant kSmallest16 : Sample16_t := to_signed(-2**15, 16);
+
+ function Saturate(s : signed ) return Sample16_t;
+ function to_stdlogicvector(d : Samples16_t) return std_logic_vector;
+ function to_Samples16(d : std_logic_vector) return Samples16_t;
+ function to_Samples17(d : std_logic_vector) return Samples17_t;
+ function to_Samples18(d : std_logic_vector) return Samples18_t;
+ -- Shift the ADC sample to the left by 1 bit.
+ function Gain2x(d : std_logic_vector) return std_logic_vector;
+ function Gain2x(s : Samples16_t) return Samples16_t;
+ --synopsys translate_off
+ function tb_saturate(s: std_logic_vector) return Sample16slv_t;
+ --synopsys translate_on
+
+end package PkgRf;
+
+
+package body PkgRf is
+
+ -- Function to saturate any signed number greater then 16 bits.
+ -- A saturated 16-bit data is returned.
+ function Saturate ( s : signed) return Sample16_t is
+ begin
+ if s > kLargest16 then
+ return kLargest16;
+ elsif s < kSmallest16 then
+ return kSmallest16;
+ else
+ return resize(s, 16);
+ end if;
+ end function Saturate;
+
+ -- This function will convert 16 bit signed array into a single
+ -- std_logic_vector.
+ function to_stdlogicvector(d : Samples16_t) return std_logic_vector is
+ -- This alias is used to normalize the input vector to [d'length-1 downto 0]
+ alias normalD : Samples16_t(d'length-1 downto 0) is d;
+ variable rval : std_logic_vector(d'length * 16 - 1 downto 0);
+ constant dataWidth : natural := Sample16_t'length;
+ begin
+ for i in normalD'range loop
+ rval(i*dataWidth + dataWidth-1 downto i*dataWidth)
+ := std_logic_vector(normalD(i));
+ end loop;
+ return rval;
+ end function to_stdlogicvector;
+
+ -- This function will convert a std_logic_vector into an array of 18 bit
+ -- signed array. The input std_logic_vector has data packed in 24 bits. But
+ -- only 18 bits has valid data and remaining 6 MSB bits are padded with
+ -- zeros.
+ function to_Samples18(d : std_logic_vector) return Samples18_t is
+ -- This alias is used to normalize the input vector to [d'length-1 downto 0]
+ alias normalD : std_logic_vector(d'length-1 downto 0) is d;
+ variable rval : Samples18_t(d'length / kDucDataWordWidth - 1 downto 0);
+ begin
+ --synopsys translate_off
+ assert (((d'length) mod kDucDataWordWidth) = 0)
+ report "Input to the function to_Samples18 must be a multiple of kDucDataWordWidth"
+ severity error;
+ --synopsys translate_on
+ for i in rval'range loop
+ rval(i) := Sample18_t(normalD(i*kDucDataWordWidth + Sample18_t'length-1
+ downto i*kDucDataWordWidth));
+ end loop;
+ return rval;
+ end function to_Samples18;
+
+ -- This function will convert a std_logic_vector into an array of 16 bit
+ -- signed array. The input std_logic_vector has data packed in 16 bits. But
+ -- only 15 bits has valid data and the uper two bits only have the signed
+ -- bit.
+ function to_Samples16(d : std_logic_vector) return Samples16_t is
+ -- This alias is used to normalize the input vector to [d'length-1 downto 0]
+ alias normalD : std_logic_vector(d'length-1 downto 0) is d;
+ variable rval : Samples16_t(d'length / kAdcSampleRes - 1 downto 0);
+ begin
+ --synopsys translate_off
+ assert (((d'length) mod kAdcSampleRes) = 0)
+ report "Input to the function to_Samples16 must be a multiple of kAdcSampleRes"
+ severity error;
+ --synopsys translate_on
+ for i in rval'range loop
+ rval(i) := Sample16_t(normalD(i*kAdcSampleRes + Sample16_t'length-1
+ downto i*kAdcSampleRes));
+ end loop;
+ return rval;
+ end function to_Samples16;
+
+ -- This function will convert a std_logic_vector into an array of 19 bit
+ -- signed array. The input std_logic_vector has data packed in 24 bits. But
+ -- only 17 bits has valid data and remaining 7 MSB bits are padded with
+ -- zeros.
+ function to_Samples17(d : std_logic_vector) return Samples17_t is
+ -- This alias is used to normalize the input vector to [d'length-1 downto 0]
+ alias normalD : std_logic_vector(d'length-1 downto 0) is d;
+ variable rval : Samples17_t(d'length / kDdcDataWordWidth - 1 downto 0);
+ begin
+ --synopsys translate_off
+ assert (((d'length) mod kDdcDataWordWidth) = 0)
+ report "Input to the function to_Samples17 must be a multiple of kDdcDataWordWidth"
+ severity error;
+ --synopsys translate_on
+ for i in rval'range loop
+ rval(i) := Sample17_t(normalD(i*kDdcDataWordWidth + Sample17_t'length-1
+ downto i*kDdcDataWordWidth));
+ end loop;
+ return rval;
+ end function to_Samples17;
+
+ -- Function to shift the sample to the left by one bit and effectively
+ -- multiply by 2.
+ function Gain2x(s : Samples16_t) return Samples16_t is
+ variable rval : Samples16_t(s'range);
+ begin
+ for i in rval'range loop
+ rval(i) := s(i)(kAdcSampleRes-2 downto 0) & '0';
+ end loop;
+ return rval;
+ end function Gain2x;
+
+ function Gain2x (d : std_logic_vector) return std_logic_vector is
+ begin
+ return to_stdlogicvector(Gain2x(to_Samples16(d)));
+ end function;
+
+ --synopsys translate_off
+ ---------------------------------------------------------------
+ -- Function below this comment is used only for testbench.
+ ---------------------------------------------------------------
+ -- This function does saturation of a signed number in std_logic_vector data
+ -- type. The current implementation supports only 17 or 18 bit signed
+ -- number.
+ function tb_saturate(s: std_logic_vector) return Sample16slv_t is
+ -- This alias is used to normalize the input vector to [s'length-1 downto 0]
+ alias normalS : std_logic_vector(s'length-1 downto 0) is s;
+ variable rval : Sample16slv_t;
+ constant len : integer := s'length;
+ begin
+
+ -- 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
+ if len = kDdcDataOutWidth then
+ if normalS(len-1 downto len-2) = "01" then
+ rval := "0111111111111111";
+ elsif normalS(len-1 downto len-2) = "10" then
+ rval := "1000000000000000";
+ else
+ rval := normalS(len-2 downto 0);
+ end if;
+
+ -- 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
+ else -- len = kDucDataOutWidth
+ if normalS(len-1) = '0' and normalS(len-2 downto len-3) /= "00" then
+ rval := "0111111111111111";
+ elsif (normalS(len-1 downto len-3) = "000") or
+ (normalS(len-1 downto len-3) = "111") then
+ rval := normalS(len-3 downto 0);
+ else
+ rval := "1000000000000000";
+ end if;
+ end if;
+ return rval;
+ end function tb_saturate;
+ --synopsys translate_on
+
+end package body;
diff --git a/fpga/usrp3/top/x400/rf/common/axis_mux.vhd b/fpga/usrp3/top/x400/rf/common/axis_mux.vhd
new file mode 100644
index 000000000..f0ead246f
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/axis_mux.vhd
@@ -0,0 +1,98 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: axis_mux
+--
+-- Description:
+--
+-- This module implements a data mux for a single AXIS bus. When
+-- mux_select='0' m_axis_tdata comes from s_axis_tdata. mux_select='1'
+-- chooses GPIO as the output data.
+--
+-- This module IS NOT useful for crossing clock domain boundaries s_axis_aclk
+-- and m_axis_mclk must be connected to the same clock.
+--
+-- This mux is intended for muxing in constant calibration data from gpio.
+-- gpio and mux_select are expected to be asynchronous to s_axis_aclk, but
+-- this module includes no synchronization logic. When mux_select or gpio
+-- change, m_axis_tvalid and m_axis_tdata are undefined in the first few
+-- clock cycles. You must wait for bad axis cycles to flush through the
+-- remainder of the pipeline before performing calibration and again after
+-- exiting calibration mode.
+--
+-- kAxiWidth must be an integer multiple of kGpioWidth. A concurrent assert
+-- statement checks this assumption and should produce a synthesis warning if
+-- that requirement is not met.
+--
+-- Parameters:
+--
+-- kGpioWidth : GPIO width.
+-- kAxiWidth : AXI bus width. Must be an integer multiple of kGpioWidth
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+
+entity axis_mux is
+ generic (
+ kGpioWidth : natural := 32;
+ kAxiWidth : natural := 256
+ );
+ port(
+ gpio : in std_logic_vector(kGpioWidth-1 downto 0);
+ mux_select : in std_logic;
+
+ -- s_axis_aclk MUST be the same as m_axis_aclk.
+ -- Declaring an unused clock allows the BD tool to identify the
+ -- synchronicity of the slave AXIS port signals.
+ s_axis_aclk : in std_logic;
+ s_axis_tdata : in std_logic_vector(kAxiWidth - 1 downto 0);
+ s_axis_tvalid : in std_logic;
+ s_axis_tready : out std_logic;
+ m_axis_aclk : in std_logic;
+ m_axis_tvalid : out std_logic;
+ m_axis_tdata : out std_logic_vector(kAxiWidth - 1 downto 0)
+ );
+end entity axis_mux;
+
+architecture RTL of axis_mux is
+
+ constant kWordSize : natural := gpio'length;
+ constant kWordCount : natural := kAxiWidth / kWordSize;
+
+ subtype AxiData_t is std_logic_vector(kAxiWidth - 1 downto 0);
+
+ impure function ConcatenatedData return AxiData_t is
+ variable rval : AxiData_t;
+ begin
+ for i in 0 to kWordCount - 1 loop
+ rval(i*kWordSize + kWordSize - 1 downto i*kWordSize) := gpio;
+ end loop;
+ return rval;
+ end function ConcatenatedData;
+
+begin
+
+ assert kWordSize * kWordCount = kAxiWidth
+ report "m_axis_tdata'length is not an integer multiple of gpio'length"
+ severity failure;
+
+ MuxOutputRegister:
+ process (m_axis_aclk) is
+ begin
+ if rising_edge(m_axis_aclk) then
+ if mux_select='1' then
+ m_axis_tdata <= ConcatenatedData;
+ m_axis_tvalid <= '1';
+ else
+ m_axis_tdata <= s_axis_tdata;
+ m_axis_tvalid <= s_axis_tvalid;
+ end if;
+ end if;
+ end process MuxOutputRegister;
+
+ s_axis_tready <= '1';
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/common/capture_sysref.v b/fpga/usrp3/top/x400/rf/common/capture_sysref.v
new file mode 100644
index 000000000..0beaa3b5c
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/capture_sysref.v
@@ -0,0 +1,50 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: capture_sysref
+//
+// Description:
+//
+// Capture SYSREF and transfer it to the higher clock domain. Module incurs
+// in 2 pll_ref_clk cycles + 1 rfdc_clk cycle of delay.
+//
+
+module capture_sysref (
+ // Clocks
+ input wire pll_ref_clk,
+ input wire rfdc_clk,
+
+ // SYSREF input and control
+ input wire sysref_in, // Single-ended SYSREF (previously buffered)
+ input wire enable_rclk, // Enables SYSREF output in the rfdc_clk domain.
+
+ // Captured SYSREF outputs
+ output wire sysref_out_pclk, // Debug output (Domain: pll_ref_clk).
+ output wire sysref_out_rclk // RFDC output (Domain: rfdc_clk).
+);
+
+ reg sysref_pclk_ms = 1'b0, sysref_pclk = 1'b0, sysref_rclk = 1'b0;
+
+ // Capture SYSREF synchronously with the pll_ref_clk, but double-sync it just
+ // in case static timing isn't met so as not to destroy downstream logic.
+ always @ (posedge pll_ref_clk) begin
+ sysref_pclk_ms <= sysref_in;
+ sysref_pclk <= sysref_pclk_ms;
+ end
+
+ assign sysref_out_pclk = sysref_pclk;
+
+ // Transfer to faster clock which is edge-aligned with the pll_ref_clk.
+ always @ (posedge rfdc_clk) begin
+ if (enable_rclk) begin
+ sysref_rclk <= sysref_pclk;
+ end else begin
+ sysref_rclk <= 1'b0;
+ end
+ end
+
+ assign sysref_out_rclk = sysref_rclk;
+
+endmodule
diff --git a/fpga/usrp3/top/x400/rf/common/clock_gates.vhd b/fpga/usrp3/top/x400/rf/common/clock_gates.vhd
new file mode 100644
index 000000000..c4df28d8f
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/clock_gates.vhd
@@ -0,0 +1,300 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: clock_gates
+--
+-- Description:
+--
+-- Gate propagation of DataClk and RfdcClk instances until the PLL lock
+-- status signal is stable and software has acknowledged it by asserting the
+-- pertinent controls.
+--
+-- RfdcClks are used on other Xilinx IP components in the Board Design, and
+-- Vivado fails to detect their frequency correctly their buffer is
+-- explicitly instantiated in the Block Design. Therefore, we only generate
+-- the buffer enable signals for these clocks within this component.
+--
+-- Since DataClk are only used in other Custom IP blocks within the Block
+-- design, it is possible to instantiate the clock buffers within this block
+-- for without running into IP generation failures.
+--
+-- Parameters:
+--
+-- kReliableClkPeriodNs: Clock period (ns) for ReliableClk.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.ALL;
+ use IEEE.numeric_std.ALL;
+
+library UNISIM;
+ use UNISIM.Vcomponents.ALL;
+
+library WORK;
+use WORK.PkgRFDC_REGS_REGMAP.all;
+
+
+entity clock_gates is
+ generic (
+ kReliableClkPeriodNs : integer := 25
+ );
+ port (
+ -- MMCM reset
+ -- This clock will be asserted via AXI access before any clocking
+ -- configuration done, signals coming into this component will not change
+ -- immediately after this reset is de-asserted.
+ rPllReset_n : in std_logic;
+
+ aPllLocked : in std_logic;
+
+ -- Input Clocks (from MMCM)
+ ReliableClk : in std_logic;
+ DataClk1xPll : in std_logic;
+ DataClk2xPll : in std_logic;
+
+ -- Buffered Clock Outputs (to design)
+ DataClk1x : out std_logic;
+ DataClk2x : out std_logic;
+
+ -- Buffers for these signals must be instantiated on Block design for clock
+ -- rates to be identified. The Utility Buffers instantiated on the Block
+ -- Design require signals to be of type std_logic_vector.
+ aEnableRfBufg1x : out std_logic_vector(0 downto 0);
+ aEnableRfBufg2x : out std_logic_vector(0 downto 0);
+
+ -- PLL Status Signals
+ rPllLocked : out std_logic;
+
+ -- Window Interface
+ rSafeToEnableGatedClks : in std_logic;
+ rGatedBaseClksValid : out std_logic;
+
+ -- AXI GPIO interface
+ rSoftwareControl : in std_logic_vector(31 downto 0);
+ rSoftwareStatus : out std_logic_vector(31 downto 0)
+ );
+end clock_gates;
+
+architecture STRUCT of clock_gates is
+
+ component sync_wrapper
+ generic (
+ WIDTH : integer := 1;
+ STAGES : integer := 2;
+ INITIAL_VAL : integer := 0;
+ FALSE_PATH_TO_IN : integer := 1);
+ port (
+ clk : in std_logic;
+ rst : in std_logic;
+ signal_in : in std_logic_vector((WIDTH-1) downto 0);
+ signal_out : out std_logic_vector((WIDTH-1) downto 0));
+ end component;
+
+ component BUFGCE
+ generic(
+ CE_TYPE : string);
+ port (
+ O : out std_ulogic;
+ CE : in std_ulogic;
+ I : in std_ulogic);
+ end component;
+
+ -- UltraScale MMCM max lock time = 100 us / 25 ns = 4,000 clk cycles. If the
+ -- division kPllLockTimeNs / kReliableClkPeriodNs does not evaluate to an
+ -- integer, Vivado could either round up or down. In case they round down, we
+ -- add '1' to the result to ensure we have the full lock time accounted for.
+ -- In this case, it is better to count 1 more than necessary than kill the
+ -- process prematurely.
+ constant kPllLockTimeNs : integer := 100000;
+ constant kMaxPllLockCount : integer := kPllLockTimeNs / kReliableClkPeriodNs + 1;
+ signal rLockedFilterCount : integer range 0 to kMaxPllLockCount-1 := kMaxPllLockCount-1;
+
+ signal rClearDataClkUnlockedSticky : std_logic;
+
+ -----------------------------------------------------------------------------
+ -- PLL locked signals
+ -----------------------------------------------------------------------------
+
+ -- Synchronizer signals
+ signal aPllLockedLcl : std_logic_vector(0 downto 0);
+ signal rPllLockedDs : std_logic_vector(0 downto 0) := (others => '0');
+
+ -- Lock status indicators
+ signal rPllLockedLcl : std_logic := '0';
+ signal rPllUnlockedSticky : std_logic := '0';
+
+ -- Safe BUFG enable signals
+ signal rEnableDataClk1x,
+ rEnableDataClk2x,
+ rEnableRfdcClk1x,
+ rEnableRfdcClk2x : std_logic;
+
+ signal rEnableDataBufg1x : std_logic := '0';
+ signal rEnableDataBufg2x : std_logic := '0';
+ signal rEnableRfdcBufg1xLcl : std_logic := '0';
+ signal rEnableRfdcBufg2xLcl : std_logic := '0';
+
+ -- Active high version of reset required for synchronizer blocks.
+ signal rPllReset : std_logic;
+
+ -- Since these signals control sensitive components (clock enables), we apply
+ -- a dont_touch attribute to preserve the signals through both synthesis and
+ -- P&R. Implementation of "dont_touch" has been confirmed after P&R.
+ attribute dont_touch : string;
+ attribute dont_touch of rEnableDataBufg1x : signal is "TRUE";
+ attribute dont_touch of rEnableDataBufg2x : signal is "TRUE";
+ attribute dont_touch of aEnableRfBufg1x : signal is "TRUE";
+ attribute dont_touch of aEnableRfBufg2x : signal is "TRUE";
+
+ attribute X_INTERFACE_INFO : string;
+ attribute X_INTERFACE_PARAMETER : string;
+
+ attribute X_INTERFACE_INFO of DataClk1xPll : signal is
+ "xilinx.com:signal:clock:1.0 DataClk1xPll CLK";
+ attribute X_INTERFACE_INFO of DataClk2xPll : signal is
+ "xilinx.com:signal:clock:1.0 DataClk2xPll CLK";
+
+begin
+
+ rPllReset <= not rPllReset_n;
+
+ -- Assert rGatedBaseClksValid once the PLL has been locked for the specified
+ -- time.
+ rGatedBaseClksValid <= rPllLockedLcl;
+
+ DataClkEnables : process(ReliableClk)
+ begin
+ if rising_edge(ReliableClk) then
+ if rPllReset_n = '0' then
+ rEnableDataBufg1x <= '0';
+ rEnableDataBufg2x <= '0';
+ rEnableRfdcBufg1xLcl <= '0';
+ rEnableRfdcBufg2xLcl <= '0';
+ else
+ rEnableDataBufg1x <=
+ rSafeToEnableGatedClks and
+ rEnableDataClk1x and
+ (not rPllUnlockedSticky);
+
+ rEnableDataBufg2x <=
+ rSafeToEnableGatedClks and
+ rEnableDataClk2x and
+ (not rPllUnlockedSticky);
+
+ rEnableRfdcBufg1xLcl <=
+ rSafeToEnableGatedClks and
+ rEnableRfdcClk1x and
+ (not rPllUnlockedSticky);
+
+ rEnableRfdcBufg2xLcl <=
+ rSafeToEnableGatedClks and
+ rEnableRfdcClk2x and
+ (not rPllUnlockedSticky);
+ end if;
+ end if;
+ end process DataClkEnables;
+
+ aEnableRfBufg1x(0) <= rEnableRfdcBufg1xLcl;
+ aEnableRfBufg2x(0) <= rEnableRfdcBufg2xLcl;
+
+ DataClk1xSafeBufg: BUFGCE
+ generic map(
+ CE_TYPE => "ASYNC"
+ )
+ port map (
+ I => DataClk1xPll,
+ CE => rEnableDataBufg1x,
+ O => DataClk1x
+ );
+
+ DataClk2xSafeBufg: BUFGCE
+ generic map(
+ CE_TYPE => "ASYNC"
+ )
+ port map (
+ I => DataClk2xPll,
+ CE => rEnableDataBufg2x,
+ O => DataClk2x
+ );
+
+
+ -----------------------------------------------------------------------------
+ -- Create PLL Lock Signal
+ -----------------------------------------------------------------------------
+ -- Double-sync the incoming aPllLocked signal from the PLL.
+
+ aPllLockedLcl(0) <= aPllLocked;
+
+ DataClkPllLockedDS: sync_wrapper
+ generic map (
+ WIDTH => 1,
+ STAGES => open,
+ INITIAL_VAL => open,
+ FALSE_PATH_TO_IN => open)
+ port map (
+ clk => ReliableClk,
+ rst => rPllReset,
+ signal_in => aPllLockedLcl,
+ signal_out => rPllLockedDs
+ );
+
+ -- Filter the Lock signal. Assert a lock when the PLL lock signal has been
+ -- asserted for kPllLockTimeNs
+ --
+ -- !!! SAFE COUNTER STARTUP !!!
+ -- rLockedFilterCount cannot start incrementing until rPllReset_n is
+ -- de-asserted. Once rPllReset_n is de-asserted through a AXI access, input
+ -- values for the registers in this state machine will not change until the
+ -- MMCM locks and the double synchronizer reflects a locked status, making
+ -- this start-up safe.
+ PllLockFilter: process (ReliableClk)
+ begin
+ if rising_edge(ReliableClk) then
+ if rPllReset_n = '0' then
+ rLockedFilterCount <= kMaxPllLockCount-1;
+ rPllLockedLcl <= '0';
+ else
+ if rPllLockedDs(0) = '1' then
+ if rLockedFilterCount = 0 then
+ rPllLockedLcl <= '1';
+ else
+ rPllLockedLcl <= '0';
+ rLockedFilterCount <= rLockedFilterCount - 1;
+ end if;
+ else
+ rLockedFilterCount <= kMaxPllLockCount-1;
+ rPllLockedLcl <= '0';
+ end if;
+ end if;
+ end if;
+ end process PllLockFilter;
+
+ -- Sticky bit to hold '1' if PLL ever comes unlocked
+ PllStickyBit: process (ReliableClk)
+ begin
+ if rising_edge(ReliableClk) then
+ if (not rPllReset_n or rClearDataClkUnlockedSticky) = '1' then
+ rPllUnlockedSticky <= '0';
+ else
+ if rPllLockedLcl = '1' and rPllLockedDs(0) = '0' then
+ rPllUnlockedSticky <= '1';
+ end if;
+ end if;
+ end if;
+ end process;
+
+ rPllLocked <= rPllLockedLcl;
+
+ -- AXI transaction decoding
+ rClearDataClkUnlockedSticky <= rSoftwareControl(kCLEAR_DATA_CLK_UNLOCKED);
+ rEnableDataClk1x <= rSoftwareControl(kENABLE_DATA_CLK);
+ rEnableDataClk2x <= rSoftwareControl(kENABLE_DATA_CLK_2X);
+ rEnableRfdcClk1x <= rSoftwareControl(kENABLE_RF_CLK);
+ rEnableRfdcClk2x <= rSoftwareControl(kENABLE_RF_CLK_2X);
+
+ rSoftwareStatus(kDATA_CLK_PLL_LOCKED) <= rPllLockedLcl;
+ rSoftwareStatus(kDATA_CLK_PLL_UNLOCKED_STICKY) <= rPllUnlockedSticky;
+
+end STRUCT;
diff --git a/fpga/usrp3/top/x400/rf/common/gpio_to_axis_mux.vhd b/fpga/usrp3/top/x400/rf/common/gpio_to_axis_mux.vhd
new file mode 100644
index 000000000..4a72cedf6
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/gpio_to_axis_mux.vhd
@@ -0,0 +1,147 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: gpio_to_axis_mux
+--
+-- Description:
+--
+-- This module either drives the AXIS outputs with the corresponding AXIS
+-- slave inputs, or it drives the output AXIS with data provided by the GPIO
+-- lines. This allows the calibration process to drive a constant value to
+-- the DAC's. Although every AXIS interface has its own clock, all the clocks
+-- must be connected to the same source. Independent clock inputs allows the
+-- block design editor to automatically detect the clock domain of the
+-- corresponding interface.
+--
+-- kAxiWidth must be an integer multiple of kGpioWidth. A concurrent assert
+-- statement in axis_mux checks this assumption and should produce a
+-- synthesis warning if that requirement is not met.
+--
+-- Parameters:
+--
+-- kGpioWidth : GPIO width.
+-- kAxiWidth : AXI bus width. Must be an integer multiple of kGpioWidth
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+
+entity gpio_to_axis_mux is
+ generic (
+ kGpioWidth : natural := 32;
+ kAxiWidth : natural := 256
+ );
+ port(
+ gpio : in std_logic_vector(kGpioWidth-1 downto 0);
+
+ -- mux_select(n) chooses the data source for AXIS interface n.
+ -- '0' chooses s_axis_tdata_n. '1' chooses gpio as the data source.
+ -- The only used bits are 0, 1, 4, 5. The remaining bits are reserved for
+ -- future expansion.
+ mux_select : in std_logic_vector(7 downto 0);
+
+ s_axis_0_aclk : in std_logic;
+ s_axis_tdata_0 : in std_logic_vector(kAxiWidth - 1 downto 0);
+ s_axis_tvalid_0 : in std_logic;
+ s_axis_tready_0 : out std_logic;
+ m_axis_0_aclk : in std_logic;
+ m_axis_tvalid_0 : out std_logic;
+ m_axis_tdata_0 : out std_logic_vector(kAxiWidth - 1 downto 0);
+
+ s_axis_1_aclk : in std_logic;
+ s_axis_tdata_1 : in std_logic_vector(kAxiWidth - 1 downto 0);
+ s_axis_tvalid_1 : in std_logic;
+ s_axis_tready_1 : out std_logic;
+ m_axis_1_aclk : in std_logic;
+ m_axis_tvalid_1 : out std_logic;
+ m_axis_tdata_1 : out std_logic_vector(kAxiWidth - 1 downto 0);
+
+ s_axis_2_aclk : in std_logic;
+ s_axis_tdata_2 : in std_logic_vector(kAxiWidth - 1 downto 0);
+ s_axis_tvalid_2 : in std_logic;
+ s_axis_tready_2 : out std_logic;
+ m_axis_2_aclk : in std_logic;
+ m_axis_tvalid_2 : out std_logic;
+ m_axis_tdata_2 : out std_logic_vector(kAxiWidth - 1 downto 0);
+
+ s_axis_3_aclk : in std_logic;
+ s_axis_tdata_3 : in std_logic_vector(kAxiWidth - 1 downto 0);
+ s_axis_tvalid_3 : in std_logic;
+ s_axis_tready_3 : out std_logic;
+ m_axis_3_aclk : in std_logic;
+ m_axis_tvalid_3 : out std_logic;
+ m_axis_tdata_3 : out std_logic_vector(kAxiWidth - 1 downto 0)
+ );
+end entity;
+
+architecture RTL of gpio_to_axis_mux is
+
+begin
+
+ axis_mux0: entity work.axis_mux (RTL)
+ generic map (
+ kGpioWidth => kGpioWidth,
+ kAxiWidth => kAxiWidth)
+ port map (
+ gpio => gpio,
+ mux_select => mux_select(0),
+ s_axis_aclk => s_axis_0_aclk,
+ s_axis_tdata => s_axis_tdata_0,
+ s_axis_tvalid => s_axis_tvalid_0,
+ s_axis_tready => s_axis_tready_0,
+ m_axis_aclk => m_axis_0_aclk,
+ m_axis_tvalid => m_axis_tvalid_0,
+ m_axis_tdata => m_axis_tdata_0
+ );
+
+ axis_mux1: entity work.axis_mux (RTL)
+ generic map (
+ kGpioWidth => kGpioWidth,
+ kAxiWidth => kAxiWidth)
+ port map (
+ gpio => gpio,
+ mux_select => mux_select(1),
+ s_axis_aclk => s_axis_1_aclk,
+ s_axis_tdata => s_axis_tdata_1,
+ s_axis_tvalid => s_axis_tvalid_1,
+ s_axis_tready => s_axis_tready_1,
+ m_axis_aclk => m_axis_1_aclk,
+ m_axis_tvalid => m_axis_tvalid_1,
+ m_axis_tdata => m_axis_tdata_1
+ );
+
+ axis_mux2: entity work.axis_mux (RTL)
+ generic map (
+ kGpioWidth => kGpioWidth,
+ kAxiWidth => kAxiWidth)
+ port map (
+ gpio => gpio,
+ mux_select => mux_select(4),
+ s_axis_aclk => s_axis_2_aclk,
+ s_axis_tdata => s_axis_tdata_2,
+ s_axis_tvalid => s_axis_tvalid_2,
+ s_axis_tready => s_axis_tready_2,
+ m_axis_aclk => m_axis_2_aclk,
+ m_axis_tvalid => m_axis_tvalid_2,
+ m_axis_tdata => m_axis_tdata_2
+ );
+
+ axis_mux3: entity work.axis_mux (RTL)
+ generic map (
+ kGpioWidth => kGpioWidth,
+ kAxiWidth => kAxiWidth)
+ port map (
+ gpio => gpio,
+ mux_select => mux_select(5),
+ s_axis_aclk => s_axis_3_aclk,
+ s_axis_tdata => s_axis_tdata_3,
+ s_axis_tvalid => s_axis_tvalid_3,
+ s_axis_tready => s_axis_tready_3,
+ m_axis_aclk => m_axis_3_aclk,
+ m_axis_tvalid => m_axis_tvalid_3,
+ m_axis_tdata => m_axis_tdata_3
+ );
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/common/rf_nco_reset.vhd b/fpga/usrp3/top/x400/rf/common/rf_nco_reset.vhd
new file mode 100644
index 000000000..d891a8722
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/rf_nco_reset.vhd
@@ -0,0 +1,228 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: rf_nco_reset
+--
+-- Description:
+--
+-- This entity has the logic needed to synchronously reset the NCO inside the
+-- RF section.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+entity rf_nco_reset is
+ port(
+ -- AXI-lite clock used for RFDC configuration.
+ ConfigClk : in std_logic;
+
+ -- Radio clock used in the converter data path.
+ DataClk : in std_logic;
+
+ -- PL SYSREF
+ dSysref : in std_logic;
+
+ --Strobe dNcoResetEn for one DataClk cycle to initiate NCO reset.
+ dStartNcoReset : in std_logic;
+
+ ---------------------------------------------------------------------------
+ -- NCO reset controls and status
+ ---------------------------------------------------------------------------
+ -- Port naming convention:
+ -- cDac<Tile Number><Converter Number><signal name>
+ -- cAdc<Tile Number><Converter Number><signal name>
+
+ -----------------------------------
+ -- DAC Tile 228
+ -----------------------------------
+ -- DAC common NCO update controls and status.
+ cDac0xNcoUpdateBusy : in std_logic_vector(1 downto 0);
+ cDac0xNcoUpdateReq : out std_logic := '0';
+ cDac0xSysrefIntGating : out std_logic := '0';
+ cDac0xSysrefIntReenable : out std_logic := '0';
+
+ -----------------------------------
+ -- DAC Tile 229
+ -----------------------------------
+ -- DAC common NCO update controls and status.
+ cDac1xNcoUpdateBusy : in std_logic;
+ cDac1xNcoUpdateReq : out std_logic := '0';
+
+ -----------------------------------
+ --ADC Tile 224
+ -----------------------------------
+ -- ADC common NCO update controls and status.
+ cAdc0xNcoUpdateBusy : in std_logic;
+ cAdc0xNcoUpdateReq : out std_logic := '0';
+
+ -----------------------------------
+ --ADC Tile 226
+ -----------------------------------
+ -- ADC common NCO update controls and status.
+ cAdc2xNcoUpdateBusy : in std_logic;
+ cAdc2xNcoUpdateReq : out std_logic := '0';
+
+ -- NCO reset can be initiated only when cNcoPhaseRst is set to '1' and
+ -- cNcoUpdateEn = 0x20. The FSM in this entity will set these values when
+ -- an NCO reset is initiated during synchronization. These ports are common
+ -- for all the converters. So, we will fan these signals out to each
+ -- converter outside this entity.
+ cNcoPhaseRst : out std_logic := '1';
+ cNcoUpdateEn : out std_logic_vector(5 downto 0) := "100000";
+
+ -- NCO reset status back to the user.
+ dNcoResetDone : out std_logic := '0'
+ );
+end rf_nco_reset;
+
+architecture RTL of rf_nco_reset is
+
+ -- State machine to sequence NCO reset across different RFDC tiles.
+ type ResetState_t is (Idle, ReqGating, CheckGating, CheckUpdateDone,
+ CheckResetDone, ResetDone);
+ signal cResetState : ResetState_t := Idle;
+
+ signal dNcoResetDone_ms, cNcoResetDone : std_logic := '0';
+ signal dStartNcoResetReg, cStartNcoReset_ms, cStartNcoReset : std_logic := '0';
+ signal cSysref_ms, cSysref, cSysrefDlyd : std_logic := '0';
+ signal cSysrefIntGating, dSysrefIntGating_ms,
+ dSysrefIntGating : std_logic := '0';
+begin
+
+ -- NCO start signal from the user is a one DataClk cycle strobe. In this
+ -- process, we register the NCO start request from the user. This NCO start
+ -- request register is cleared after the NCO reset sequence is initiated. We
+ -- used the signal used to gate SYSREF to clear this register.
+ RegNcoStart: process(DataClk)
+ begin
+ if rising_edge(DataClk) then
+ dSysrefIntGating_ms <= cSysrefIntGating;
+ dSysrefIntGating <= dSysrefIntGating_ms;
+ if dSysrefIntGating = '1' then
+ dStartNcoResetReg <= '0';
+ elsif dStartNcoReset = '1' then
+ dStartNcoResetReg <= '1';
+ end if;
+ end if;
+ end process RegNcoStart;
+
+ -- Irrespective of when NCO reset strobe is issued by the user, we need to
+ -- initiate NCO reset only on the rising edge of SYSREF. This is because, we
+ -- have to complete the reset within a SYSREF period.
+ ConfigClkCross: process(ConfigClk)
+ begin
+ if rising_edge(ConfigClk) then
+ cSysref_ms <= dSysref;
+ cSysref <= cSysref_ms;
+ cSysrefDlyd <= cSysref;
+ cStartNcoReset_ms <= dStartNcoResetReg;
+ cStartNcoReset <= cStartNcoReset_ms;
+ end if;
+ end process ConfigClkCross;
+
+ -- These signals can be set to a constant value as NCO phase reset is only
+ -- initiated by *NcoUpdateReq signal.
+ cNcoPhaseRst <= '1';
+ cNcoUpdateEn <= "100000";
+
+ -- ! STATE MACHINE STARTUP !
+ -- The state machine starts in Idle state and does not change state until
+ -- cStartNcoReset is set to '1'. cStartNcoReset signal and cSysref are based
+ -- of ConfigClock so changing state from Idle cannot go metastable. State
+ -- machine to initiate NCO reset on all enabled RFDC tiles. This state
+ -- machine was written based of the information provided in "NCO frequency
+ -- hopping" section in PG269 (v2.2). We use multi-mode for NCO reset.
+ ResetFsm: process(ConfigClk)
+ begin
+ if rising_edge(ConfigClk) then
+ cResetState <= Idle;
+ cNcoResetDone <= '0';
+ cDac0xNcoUpdateReq <= '0';
+ cSysrefIntGating <= '0';
+ cDac0xSysrefIntReenable <= '0';
+ cDac1xNcoUpdateReq <= '0';
+ cAdc0xNcoUpdateReq <= '0';
+ cAdc2xNcoUpdateReq <= '0';
+ case cResetState is
+ -- Stay in this state until NCO reset sequence is initiated. NCO reset
+ -- is initiated only on the rising edge of SYSREF.
+ when Idle =>
+ if cSysref = '1' and cSysrefDlyd = '0' and cStartNcoReset = '1' then
+ cResetState <= ReqGating;
+ cSysrefIntGating <= '1';
+ end if;
+
+ -- When NCO reset is initiated, gate the RFDC internal SYSREF. To gate
+ -- internal SYSREF set cSysrefIntGating to '1'. To request NCO reset
+ -- strobe cDac0xNcoUpdateReq for one ConfigClk period. At this point,
+ -- we can only request NCO reset for RF-DAC tile 228.
+ when ReqGating =>
+ cResetState <= CheckGating;
+ cDac0xNcoUpdateReq <= '1';
+ cSysrefIntGating <= '1';
+
+ -- Since we are gating SYSREF inside RFDC, we need to wait until SYSREF
+ -- is gated internally. RFDC sets cDac0xNcoUpdateBusy[0] to '1' when
+ -- SYSREF is gated. cDac0xNcoUpdateBusy[1] is also set to '1' to
+ -- indicate that NCO reset is still in progress. After the SYSREF is
+ -- gated request NCO reset on all other converter tiles.
+ when CheckGating =>
+ cSysrefIntGating <= '1';
+ cResetState <= CheckGating;
+ if cDac0xNcoUpdateBusy = "11" then
+ cResetState <= CheckUpdateDone;
+ cDac1xNcoUpdateReq <= '1';
+ cAdc0xNcoUpdateReq <= '1';
+ cAdc2xNcoUpdateReq <= '1';
+ end if;
+
+ -- In this state, we check if the RFDC block is ready for NCO reset.
+ -- This check is done using the *Busy signal from RFDC. Once RFDC is
+ -- ready for NCO reset, disable internal SYSREF gating.
+ when CheckUpdateDone =>
+ cSysrefIntGating <= '1';
+ cResetState <= CheckUpdateDone;
+ if cDac0xNcoUpdateBusy = "10" and cAdc0xNcoUpdateBusy = '0' and
+ cAdc2xNcoUpdateBusy = '0' and cDac1xNcoUpdateBusy = '0' and
+ cSysref = '1' and cSysrefDlyd = '0' then
+ cDac0xSysrefIntReenable <= '1';
+ cResetState <= CheckResetDone;
+ end if;
+
+ -- NCO reset is done when cDac0xNcoUpdateBusy[1] is set to '0'. RFDC is
+ -- programmed from software to reset the NCO on a SYSREF rising edge.
+ when CheckResetDone =>
+ cSysrefIntGating <= '1';
+ cResetState <= CheckResetDone;
+ if cDac0xNcoUpdateBusy = "00" then
+ cResetState <= ResetDone;
+ end if;
+
+ -- Wait in this state until another NCO reset request is issued.
+ when ResetDone =>
+ cNcoResetDone <= '1';
+ cResetState <= ResetDone;
+ if cSysref = '1' and cSysrefDlyd = '0' and cStartNcoReset = '1' then
+ cResetState <= ReqGating;
+ cSysrefIntGating <= '1';
+ end if;
+ end case;
+ end if;
+ end process ResetFsm;
+
+ cDac0xSysrefIntGating <= cSysrefIntGating;
+
+ -- Move the NCO reset done status to DataClk domain.
+ DataClkCrossing: process(DataClk)
+ begin
+ if rising_edge(DataClk) then
+ dNcoResetDone_ms <= cNcoResetDone;
+ dNcoResetDone <= dNcoResetDone_ms;
+ end if;
+ end process DataClkCrossing;
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/common/rf_reset.vhd b/fpga/usrp3/top/x400/rf/common/rf_reset.vhd
new file mode 100644
index 000000000..c1ef34d3c
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/rf_reset.vhd
@@ -0,0 +1,216 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: rf_reset
+--
+-- Description:
+--
+-- Control RFDC, ADC, and DAC resets.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+entity rf_reset is
+ port(
+ -- Clocks used in the data path.
+ DataClk : in std_logic;
+ PllRefClk : in std_logic;
+ RfClk : in std_logic;
+ RfClk2x : in std_logic;
+ DataClk2x : in std_logic;
+
+ -- Master resets from the Radio.
+ dTimedReset : in std_logic;
+ dSwReset : in std_logic;
+
+ -- Resets outputs.
+ dReset_n : out std_logic := '0';
+ d2Reset_n : out std_logic := '0';
+ r2Reset_n : out std_logic := '0';
+ rAxiReset_n : out std_logic := '0';
+ rReset_n : out std_logic := '0'
+ );
+end rf_reset;
+
+
+architecture RTL of rf_reset is
+
+ -- POR value for all resets are active high or low.
+ signal dResetPulseDly : std_logic_vector(2 downto 0) := "111";
+ signal dResetPulseStretch : std_logic := '1';
+ signal pResetPulseStretch : std_logic_vector(1 downto 0) := "11";
+ signal pResetPulse_n : std_logic := '0';
+ signal pAxiReset_n : std_logic := '0';
+
+
+begin
+
+ -----------------------------------------------------------------------------
+ -- Clock Phase Diagram
+ -----------------------------------------------------------------------------
+ -- Before we look into the details of the clock alignment, here is the clock
+ -- frequencies of all the synchronous clocks that is used in the design.
+ -- PllRefClk is the reference clock for the FPGA PLL and all other clocks are
+ -- derived from PllRefClk. PllRefClk for X410 is ~62.5 MHz
+ -- PllRefClk = ~62.5 MHz (Sample clock/48. This is the X410 configuration and
+ -- could be different for other x4xx variants.)
+ -- DataClk = PllRefClk*2
+ -- DataClkx2 = PllRefClk*4
+ -- RfClk = PllRefClk*3
+ -- RfClkx2 = PllRefClk*6
+ -- DataClk = PllRefClk*4 for legacy mode. In legacy mode, we will not use
+ -- DataClkx2 as the clock frequency will be too high to close timing.
+ -- Five clocks with five different frequencies, all related and occasionally
+ -- aligned. Rising edge of all clocks are aligned to the rising edge of
+ -- PllRefClk. We will use the rising edge of PllRefClk as the reference to
+ -- assert synchronous reset for all clock domains. The synchronous reset
+ -- pulse is in the DataClk domain. As we can see from the timing diagram, the
+ -- DataClk rising edge is not always aligned to the rising edge of all the
+ -- other clocks. But, it is guaranteed that the DataClk will be aligned to
+ -- all the other clock on the rising edge of PLL reference clock. In case 1,
+ -- the synchronous reset pulse is on the DataClk edge where the data clock is
+ -- not aligned to RfClk. We stretch the pulse from DataClk domain and send
+ -- the reset out on the rising edge of PllRefClk where all the clocks rising
+ -- edge is aligned. In case 2, the synchronous reset is received on the
+ -- DataClk cycle where all the clocks are aligned. This is because, in
+ -- case 2, the synchronous reset is received on the rising edge of PllRefClk.
+ -- For case 1 and case 2, all the output resets are asserted only on the
+ -- PllRefClk rising edge to guarantee a known relationship between the resets
+ -- in different clock domains.
+ --
+ -- Alignment * * *
+ -- ___________ ___________ ___________ ___________ ___________
+ -- PllRefClk __| |___________| |___________| |___________| |___________| |
+ -- _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ -- RfClk2x __| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_
+ -- ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
+ -- RfClk __| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___| |___|
+ -- __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __
+ -- DataClk2x __| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__| |__|
+ -- _____ _____ _____ _____ _____ _____ _____ _____ _____
+ -- DataClk __| |_____| |_____| |_____| |_____| |_____| |_____| |_____| |_____| |_____|
+ -- . : : : :
+ -- --------- Case 1 ---------.-- : : : :
+ -- ^ : : ^ :
+ -- Reset Strobe --> | : Aligned reset strobe -->| :
+ -- ____________ : : : :
+ -- dResetPulse________| |______________________________________ : :
+ -- : _____________________________________________________________________________
+ -- dResetPulseStretch ______________________| :
+ -- : ________________________________________________
+ -- pResetPulseStretch ____________________________________________| : : |___
+ -- _________________________________________________________________________ :
+ -- pResetPulse_n : |________________________________
+ -- : : : :
+ -- --------- Case 2 ----------- : : : :
+ -- ^ : ^ :
+ -- Reset Strobe --> | : | <-- Aligned reset strobe
+ -- ____________ : : :
+ -- dResetPulse(0) ________| |______________________________________________________________________________
+ -- _______________________________________________________________________________
+ -- dResetPulseStretch ______________________| :
+ -- ________________________________________________________
+ -- pResetPulseStretch ____________________________________________| :
+ -- _________________________________________________________________________
+ -- pResetPulse_n |________________________________
+ -- --------------------------------------------------------------------------
+
+ -----------------------------------------------------------------------------
+ -- Implementation
+ -----------------------------------------------------------------------------
+
+ -- Since the dTimedReset is asserted only for one DataClk cycle, we need to
+ -- stretch the strobe to four DataClk cycles, so the strobe is wide enough to
+ -- be sampled by PllRefClk which is four times the DataClk period. Pulse
+ -- stretch is done for 4 DataClk periods to support the legacy mode. We also
+ -- do a logical OR on resets from software. Software resets are from the
+ -- ConfigClock domain which is a slower clock than the PllRefClk. So, we
+ -- don't have to stretch the software reset.
+ PulseStretch: process(DataClk)
+ begin
+ if rising_edge(DataClk) then
+ dResetPulseDly <= dResetPulseDly(1 downto 0) & (dTimedReset or dSwReset);
+ dResetPulseStretch <= '0';
+ if (dResetPulseDly /= "000") or dTimedReset = '1' or dSwReset = '1' then
+ dResetPulseStretch <= '1';
+ end if;
+ end if;
+ end process PulseStretch;
+
+ -- Strobe reset pulse for 2 PllRefClk period to make sure we have the reset
+ -- asserted for longer period. The FIR filter is the only design that
+ -- requires reset to be asserted for 2 clock cycles. This requirement is
+ -- satisfied with one PllRefClk period. RFDC does not have any AXI stream
+ -- reset time requirement. We will reset all designs for two PllRefClk period
+ -- just to be on the safer side. The same strategy is used for DAC resets as
+ -- well.
+ ResetOut: process(PllRefClk)
+ begin
+ if rising_edge(PllRefClk) then
+ pResetPulseStretch <= pResetPulseStretch(0) & dResetPulseStretch;
+ pResetPulse_n <= not (pResetPulseStretch(1) or pResetPulseStretch(0));
+ end if;
+ end process ResetOut;
+
+ -- We are using PllRefClk as the reference and issuing resets to all the
+ -- other clock domains. We are not trying to align all the resets in
+ -- different clock domains. We are making sure that all resets will be
+ -- asserted with respect to each other at the same time from run to run.
+ DataClkReset: process(DataClk)
+ begin
+ if rising_edge(DataClk) then
+ dReset_n <= pResetPulse_n;
+ end if;
+ end process DataClkReset;
+
+ DataClk2xReset: process(DataClk2x)
+ begin
+ if rising_edge(DataClk2x) then
+ d2Reset_n <= pResetPulse_n;
+ end if;
+ end process DataClk2xReset;
+
+ Rfclk2xReset: process(RfClk2x)
+ begin
+ if rising_edge(RfClk2x) then
+ r2Reset_n <= pResetPulse_n;
+ end if;
+ end process Rfclk2xReset;
+
+ RfclkReset: process(RfClk)
+ begin
+ if rising_edge(RfClk) then
+ rReset_n <= pResetPulse_n;
+ end if;
+ end process RfclkReset;
+
+ -------------------------------------
+ -- RF Resets
+ -------------------------------------
+ -- RFDC resets are asserted only once and it should be done using the reset
+ -- from software. This is because we want the RFDC AXI-S interface in reset
+ -- until the RfClk is stable. The only way to know if the RfClk is stable is
+ -- by reading the lock status of sample clock PLL and MMCM used to generate
+ -- all clocks in the signal path. dSwReset is a software reset while is
+ -- asserted for a longer period of time and it does not require any pulse
+ -- stretch.
+
+ RfdcReset: process(PllRefClk)
+ begin
+ if rising_edge(PllRefClk) then
+ pAxiReset_n <= not dSwReset;
+ end if;
+ end process RfdcReset;
+
+ RfclkAxiReset: process(RfClk)
+ begin
+ if rising_edge(RfClk) then
+ rAxiReset_n <= pAxiReset_n;
+ end if;
+ end process RfclkAxiReset;
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/common/rf_reset_controller.vhd b/fpga/usrp3/top/x400/rf/common/rf_reset_controller.vhd
new file mode 100644
index 000000000..d69228a80
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/rf_reset_controller.vhd
@@ -0,0 +1,208 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: rf_reset_controller
+--
+-- Description:
+--
+-- Control RFDC, ADC, and DAC resets.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+library WORK;
+ use WORK.PkgRFDC_REGS_REGMAP.all;
+
+entity rf_reset_controller is
+ port(
+ -- Clocks
+ -- Config clock is async to all the others.
+ ConfigClk : in std_logic;
+ DataClk : in std_logic;
+ PllRefClk : in std_logic;
+ RfClk : in std_logic;
+ RfClk2x : in std_logic;
+ DataClk2x : in std_logic;
+
+ -- Master resets from the Radio
+ dAdcResetPulse : in std_logic;
+ dDacResetPulse : in std_logic;
+
+ -- ADC Resets
+ dAdcDataOutReset_n : out std_logic;
+ r2AdcFirReset_n : out std_logic;
+ rAdcRfdcAxiReset_n : out std_logic;
+ rAdcEnableData : out std_logic;
+ rAdcGearboxReset_n : out std_logic;
+
+ -- DAC Resets
+ dDacDataInReset_n : out std_logic;
+ r2DacFirReset_n : out std_logic;
+ d2DacFirReset_n : out std_logic;
+ rDacRfdcAxiReset_n : out std_logic;
+ rDacGearboxReset_n : out std_logic;
+
+ -- SW Control and Status
+ -- Control to initiate resets to RFDC and decimation block including the
+ -- gearboxes. The reset status is a sticky status of both ADC and DAC.
+ cSoftwareControl : in std_logic_vector(31 downto 0);
+ cSoftwareStatus : out std_logic_vector(31 downto 0)
+ );
+end rf_reset_controller;
+
+
+architecture RTL of rf_reset_controller is
+
+ -- POR value for all resets are high.
+ signal cTriggerAdcReset : std_logic := '1';
+ signal cTriggerAdcResetDlyd : std_logic := '1';
+ signal cTriggerDacReset : std_logic := '1';
+ signal cTriggerDacResetDlyd : std_logic := '1';
+
+ signal dTriggerAdcReset_ms : std_logic := '1';
+ signal dTriggerAdcReset : std_logic := '1';
+ signal dTriggerDacReset_ms : std_logic := '1';
+ signal dTriggerDacReset : std_logic := '1';
+
+ -- POR value of all reset done signals are set to low.
+ signal cTriggerAdcResetDone_ms : std_logic := '0';
+ signal cTriggerAdcResetDone : std_logic := '0';
+ signal cAdcResetDoneSticky : std_logic := '0';
+ signal cTriggerDacResetDone_ms : std_logic := '0';
+ signal cTriggerDacResetDone : std_logic := '0';
+ signal cDacResetDoneSticky : std_logic := '0';
+
+ attribute ASYNC_REG : string;
+ attribute ASYNC_REG of dTriggerAdcReset : signal is "TRUE";
+ attribute ASYNC_REG of dTriggerDacReset : signal is "TRUE";
+ attribute ASYNC_REG of cTriggerAdcResetDone : signal is "TRUE";
+ attribute ASYNC_REG of cTriggerDacResetDone : signal is "TRUE";
+ attribute ASYNC_REG of dTriggerAdcReset_ms : signal is "TRUE";
+ attribute ASYNC_REG of dTriggerDacReset_ms : signal is "TRUE";
+ attribute ASYNC_REG of cTriggerAdcResetDone_ms : signal is "TRUE";
+ attribute ASYNC_REG of cTriggerDacResetDone_ms : signal is "TRUE";
+
+begin
+
+ -- rAdcEnableData is set to '1' as we don't control the flow of RX data.
+ rAdcEnableData <= '1';
+
+ cTriggerAdcReset <= cSoftwareControl(kADC_RESET);
+ cTriggerDacReset <= cSoftwareControl(kDAC_RESET);
+
+ cSoftwareStatus <= (
+ kADC_SEQ_DONE => cAdcResetDoneSticky,
+ kDAC_SEQ_DONE => cDacResetDoneSticky,
+ others => '0'
+ );
+
+ -----------------------------------------------------------------------------
+ -- High-Level Resets Using ConfigClk
+ -----------------------------------------------------------------------------
+ -- Pass the master FSM reset around to the other clock domains and then
+ -- return them back to the ConfigClk domain. This is also a handy way to
+ -- prove all your clocks are toggling to some extent.
+ -----------------------------------------------------------------------------
+
+ SeqResetDataClk : process(DataClk)
+ begin
+ if rising_edge(DataClk) then
+ -- double-syncs have no sync reset!
+ dTriggerAdcReset_ms <= cTriggerAdcReset;
+ dTriggerAdcReset <= dTriggerAdcReset_ms;
+ dTriggerDacReset_ms <= cTriggerDacReset;
+ dTriggerDacReset <= dTriggerDacReset_ms;
+ end if;
+ end process;
+
+ -----------------------------------------------------------------------------
+ -- Reset Sequence Done Status
+ -----------------------------------------------------------------------------
+ -- Now back to ConfigClk! We provide the status for all software controlled
+ -- resets. We move the signal from ConfigClk to DataClk domain and move it
+ -- back to ConfigClk domain. This just proves that DataClk is toggling and
+ -- the reset requested by software is sampled in the DataClk.
+ -----------------------------------------------------------------------------
+
+ SeqResetDone : process(ConfigClk)
+ begin
+ if rising_edge(ConfigClk) then
+ -- double-syncs have no sync reset!
+ cTriggerAdcResetDone_ms <= dTriggerAdcReset;
+ cTriggerAdcResetDone <= cTriggerAdcResetDone_ms;
+ cTriggerDacResetDone_ms <= dTriggerDacReset;
+ cTriggerDacResetDone <= cTriggerDacResetDone_ms;
+ end if;
+ end process;
+
+ -- ADC reset done
+ SwAdcResetDone: process(ConfigClk)
+ begin
+ if rising_edge(ConfigClk) then
+ cTriggerAdcResetDlyd <= cTriggerAdcReset;
+ -- De-assert reset status on the rising edge of SW ADC reset.
+ if cTriggerAdcReset = '1' and cTriggerAdcResetDlyd = '0' then
+ cAdcResetDoneSticky <= '0';
+ -- Assert and hold the ADC reset status on ADC reset strobe.
+ elsif cTriggerAdcResetDone = '1' then
+ cAdcResetDoneSticky <= '1';
+ end if;
+ end if;
+ end process SwAdcResetDone;
+
+ -- DAC reset done
+ SwDacResetDone: process(ConfigClk)
+ begin
+ if rising_edge(ConfigClk) then
+ cTriggerDacResetDlyd <= cTriggerDacReset;
+ -- De-assert reset status on the rising edge of SW DAC reset.
+ if cTriggerDacReset = '1' and cTriggerDacResetDlyd = '0' then
+ cDacResetDoneSticky <= '0';
+ -- Assert and hold the DAC reset status on DAC reset strobe.
+ elsif cTriggerDacResetDone = '1' then
+ cDacResetDoneSticky <= '1';
+ end if;
+ end if;
+ end process SwDacResetDone;
+
+ -----------------------------------------------------------------------------
+ -- rf_reset Instances
+ -----------------------------------------------------------------------------
+
+ AdcResets: entity work.rf_reset (RTL)
+ port map (
+ DataClk => DataClk,
+ PllRefClk => PllRefClk,
+ RfClk => RfClk,
+ RfClk2x => RfClk2x,
+ DataClk2x => DataClk2x,
+ dTimedReset => dAdcResetPulse,
+ dSwReset => dTriggerAdcReset,
+ dReset_n => dAdcDataOutReset_n,
+ d2Reset_n => open,
+ r2Reset_n => r2AdcFirReset_n,
+ rAxiReset_n => rAdcRfdcAxiReset_n,
+ rReset_n => rAdcGearboxReset_n
+ );
+
+ DacResets: entity work.rf_reset (RTL)
+ port map (
+ DataClk => DataClk,
+ PllRefClk => PllRefClk,
+ RfClk => RfClk,
+ RfClk2x => RfClk2x,
+ DataClk2x => DataClk2x,
+ dTimedReset => dDacResetPulse,
+ dSwReset => dTriggerDacReset,
+ dReset_n => dDacDataInReset_n,
+ d2Reset_n => d2DacFirReset_n,
+ r2Reset_n => r2DacFirReset_n,
+ rAxiReset_n => rDacRfdcAxiReset_n,
+ rReset_n => rDacGearboxReset_n
+ );
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/common/scale_2x.vhd b/fpga/usrp3/top/x400/rf/common/scale_2x.vhd
new file mode 100644
index 000000000..919ae9474
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/scale_2x.vhd
@@ -0,0 +1,51 @@
+--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: scale_2x
+--
+-- Description:
+--
+-- This block does the scaling of IQ data by 2. The data from the mixer is
+-- 1/2 the full scale and the upper two bits will only have the signed bits,
+-- so it is okay to multiply the data by 2 and resize it back to 16 bits.
+--
+-- Parameters:
+--
+-- kDataWidth: Data width, should be a multiple of 16 bits.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+library WORK;
+ use WORK.PkgRf.all;
+
+entity scale_2x is
+ generic(
+ kDataWidth : integer range 1 to 256 := 32
+ );
+ port(
+ -- [..Q1,I1,Q0,I0] (I in LSBs). Each I and Q data is 16 bits wide, but
+ -- since the data is only 1/2 full scale. Useful information is only
+ -- in the lower 15 bits, with upper two bits used as a signed bit.
+ cDataIn : in std_logic_vector(kDataWidth-1 downto 0);
+ cDataValidIn : in std_logic;
+
+ -- [..Q1,I1,Q0,I0] (I in LSBs). 16 bit output with a gain of 2x.
+ cDataOut : out std_logic_vector(kDataWidth-1 downto 0);
+ cDataValidOut : out std_logic
+ );
+end scale_2x;
+
+architecture RTL of scale_2x is
+
+begin
+
+ -- Scale the date by 2 by shifting the data to the left by 1 bit.
+ cDataOut <= Gain2x(cDataIn);
+ cDataValidOut <= cDataValidIn;
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/common/sync_wrapper.v b/fpga/usrp3/top/x400/rf/common/sync_wrapper.v
new file mode 100644
index 000000000..797d19d5f
--- /dev/null
+++ b/fpga/usrp3/top/x400/rf/common/sync_wrapper.v
@@ -0,0 +1,43 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: sync_wrapper
+//
+// Description:
+//
+// As the original synchronizer component has port signal names that are
+// incompatible with VHDL (in, out), this modules provides an an interface to
+// instantiate the synchronizer block in VHDL.
+//
+
+`default_nettype none
+
+module sync_wrapper #(
+ parameter WIDTH = 1,
+ parameter STAGES = 2,
+ parameter INITIAL_VAL = 0,
+ parameter FALSE_PATH_TO_IN = 1
+)(
+ input wire clk,
+ input wire rst,
+ input wire [WIDTH-1:0] signal_in,
+ output wire [WIDTH-1:0] signal_out
+);
+
+synchronizer #(
+ .WIDTH (WIDTH),
+ .STAGES (STAGES),
+ .INITIAL_VAL (INITIAL_VAL),
+ .FALSE_PATH_TO_IN (FALSE_PATH_TO_IN)
+) synchronizer_i (
+ .clk (clk),
+ .rst (rst),
+ .in (signal_in),
+ .out (signal_out)
+);
+
+endmodule //sync_wrapper
+
+`default_nettype wire