diff options
Diffstat (limited to 'fpga/usrp3/top/x400/rf/common')
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/PkgRf.vhd | 220 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/axis_mux.vhd | 98 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/capture_sysref.v | 50 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/clock_gates.vhd | 300 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/gpio_to_axis_mux.vhd | 147 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/rf_nco_reset.vhd | 228 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/rf_reset.vhd | 216 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/rf_reset_controller.vhd | 208 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/scale_2x.vhd | 51 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/common/sync_wrapper.v | 43 |
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 |