diff options
author | Wade Fife <wade.fife@ettus.com> | 2021-06-08 19:40:46 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 11:56:58 -0500 |
commit | 6d3765605262016a80f71e36357f749ea35cbe5a (patch) | |
tree | 7d62d6622befd4132ac1ee085effa1426f7f53e5 /fpga/usrp3/top/x400/rf | |
parent | f706b89e6974e28ce76aadeeb06169becc86acba (diff) | |
download | uhd-6d3765605262016a80f71e36357f749ea35cbe5a.tar.gz uhd-6d3765605262016a80f71e36357f749ea35cbe5a.tar.bz2 uhd-6d3765605262016a80f71e36357f749ea35cbe5a.zip |
fpga: x400: Add support for X410 motherboard FPGA
Co-authored-by: Andrew Moch <Andrew.Moch@ni.com>
Co-authored-by: Daniel Jepson <daniel.jepson@ni.com>
Co-authored-by: Javier Valenzuela <javier.valenzuela@ni.com>
Co-authored-by: Joerg Hofrichter <joerg.hofrichter@ni.com>
Co-authored-by: Kumaran Subramoniam <kumaran.subramoniam@ni.com>
Co-authored-by: Max Köhler <max.koehler@ni.com>
Co-authored-by: Michael Auchter <michael.auchter@ni.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Hector Rubio <hrubio@ni.com>
Diffstat (limited to 'fpga/usrp3/top/x400/rf')
40 files changed, 6678 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd b/fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd new file mode 100644 index 000000000..a941fcb8f --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/adc_3_1_clk_converter.vhd @@ -0,0 +1,114 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: adc_3_1_clk_converter +-- +-- Description: +-- +-- This module transfers data from s_axis_aclk to m_axis_aclk. s_axis_aclk +-- must be three times the frequency of m_axis_aclk, and the two clocks must +-- be related (this module requires timing closure across the clock domain +-- boundary). +-- + +library IEEE; + use IEEE.std_logic_1164.all; + +entity adc_3_1_clk_converter is + port( + s_axis_clk : in std_logic; + s_axis_resetn : in std_logic; + s_axis_tdata : in std_logic_vector(47 downto 0); + s_axis_tvalid : in std_logic; + + m_axis_clk : in std_logic; + m_axis_resetn : in std_logic; + m_axis_tvalid : out std_logic; + m_axis_tdata : out std_logic_vector(47 downto 0) + ); +end entity; + +architecture RTL of adc_3_1_clk_converter is + + -- To keep the implementation simple, this module does not implement a + -- correct AXIS handshake - it ignores m_axis_tready. adc_100m_bd already had + -- an assumption that the AXIS handshake is unneeded: ddc_saturate does not + -- accept _tready from the following component. + subtype Word_t is std_logic_vector(s_axis_tdata'range); + signal s_axis_tvalid_pipe : std_logic_vector(2 downto 0); + signal s_axis_tdata_reg : Word_t; + + -- These _CDC signals will be sampled in the destination clock domain, but + -- will not produce any metastability because the input clocks must be + -- synchronous. + -- + -- These signals must be driven by registers not to prevent glitches (as in + -- an asynchronous CDC), but to improve timing closure. + signal s_axis_tvalid_CDC : std_logic; + signal s_axis_tdata_CDC : Word_t; + + -- m_axis_clk and s_axis_clk are nominally aligned by their rising edges. + -- Because m_axis_clk is more heavily loaded than s_axis_clk, m_axis_clk has + -- a larger distribution delay, which causes a large hold violation using + -- post-place timing estimates. The Ultrafast method (UG 949) recommends + -- addressing such hold violations when WHS < -0.5 ns. By resampling on the + -- falling edge of the destination clock, we get nominally half a period of + -- setup and half a period of hold. The destination clock delay reduces the + -- hold margin, and increases the setup margin. + signal m_axis_tvalid_fall : std_logic; + signal m_axis_tdata_fall : Word_t; + +begin + + -- In the source clock domain, we capture incoming valid data and keep a + -- history of _tvalid over the last three clock cycles. If s_axis_tvalid has + -- been asserted once in the last three clock cycles, assert + -- s_axis_tvalid_CDC to be sampled in the output clock domain. The length of + -- s_axis_tvalid_pipe must match the ratio of the clock frequencies (3:1). + InputSampling: + process (s_axis_clk) is + begin + if rising_edge(s_axis_clk) then + if s_axis_tvalid='1' then + s_axis_tdata_reg <= s_axis_tdata; + end if; + s_axis_tdata_CDC <= s_axis_tdata_reg; + if s_axis_resetn='0' then + s_axis_tvalid_pipe <= (others => '0'); + s_axis_tvalid_CDC <= '0'; + else + s_axis_tvalid_pipe <= s_axis_tvalid_pipe(1 downto 0) & s_axis_tvalid; + if (s_axis_tvalid_pipe /= "000") then + s_axis_tvalid_CDC <= '1'; + else + s_axis_tvalid_CDC <= '0'; + end if; + end if; + end if; + end process InputSampling; + + FallingEdgeSampling: + process (m_axis_clk) is + begin + if falling_edge(m_axis_clk) then + m_axis_tvalid_fall <= s_axis_tvalid_CDC; + m_axis_tdata_fall <= s_axis_tdata_CDC; + end if; + end process FallingEdgeSampling; + + OutputRegisters: + process (m_axis_clk) is + begin + if rising_edge(m_axis_clk) then + m_axis_tdata <= m_axis_tdata_fall; + if m_axis_resetn='0' then + m_axis_tvalid <= '0'; + else + m_axis_tvalid <= m_axis_tvalid_fall; + end if; + end if; + end process OutputRegisters; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v b/fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v new file mode 100644 index 000000000..604c04f50 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/adc_gearbox_2x1.v @@ -0,0 +1,120 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: adc_gearbox_2x1 +// +// Description: +// +// Gearbox ADC data from 2 SPC to 1 SPC and corresponding 2x clock to 1x +// clock. Also implement data swapping to format packets to fit the FIR +// filter input requirements. +// +// This modules incurs one clk1x cycle of delay on the data and valid signals +// from input on the 1x domain to output on the 2x domain. +// + +`default_nettype none + +module adc_gearbox_2x1 ( + input wire clk1x, + input wire reset_n_1x, + // Data is _presumed_ to be packed [Sample1, Sample0] (Sample0 in LSBs). + input wire [31:0] adc_q_in_1x, + input wire [31:0] adc_i_in_1x, + input wire valid_in_1x, + // De-assert enable_1x to clear the data synchronously from this module. + input wire enable_1x, + + input wire clk2x, + // Data is packed [Q,I] (I in LSBs) when swap_iq_1x is '0'. + input wire swap_iq_2x, + output wire [31:0] adc_out_2x, + output wire valid_out_2x +); + + // Re-create the 1x clock in the 2x domain to produce a deterministic + // crossing. + reg toggle_1x, toggle_2x = 1'b0, toggle_2x_dly = 1'b0, valid_2x = 1'b0, valid_dly_2x = 1'b0; + reg [31:0] data_out_2x = 32'b0, adc_q_data_in_2x = 32'b0, adc_i_data_in_2x = 32'b0; + + // Create a toggle in the 1x clock domain (clock divider /2). + always @(posedge clk1x or negedge reset_n_1x) begin + if ( ! reset_n_1x) begin + toggle_1x <= 1'b0; + end else begin + toggle_1x <= ! toggle_1x; + end + end + + // clk1x and clk2x are nominally aligned on their rising edges, but clk2x is + // more heavily loaded, which results in a later arrival time. That late + // arrival causes large estimated hold violations after place. The Ultrafast + // method (UG 949) suggests fixing post-place hold violations that are worse + // than -0.5 ns. + // Resampling 1x signals on the falling edge of clk2x provides nominally half + // a period of setup and half a period of hold. The late arrival of clk2x + // shifts some of that margin away from hold slack and into setup slack. + reg toggle_2x_fall = 1'b0; + reg [31:0] adc_q_in_2x_fall = 32'b0; + reg [31:0] adc_i_in_2x_fall = 32'b0; + reg valid_in_2x_fall = 1'b0; + reg enable_2x_fall = 1'b0; + + always @(negedge clk2x) begin + toggle_2x_fall <= toggle_1x; + adc_q_in_2x_fall <= adc_q_in_1x; + adc_i_in_2x_fall <= adc_i_in_1x; + valid_in_2x_fall <= valid_in_1x; + enable_2x_fall <= enable_1x; + end + + // Transfer the toggle from the 1x to the 2x domain. Delay the toggle in the + // 2x domain by one cycle and compare it to the non-delayed version. When + // they differ, push data_in[15:0] onto the output. When the match, push + // [31:16] onto the output. The datasheet is unclear on the exact + // implementation. + // + // It is safe to not reset this domain because all of the input signals will + // be cleared by the 1x reset. Safe default values are assigned to all these + // registers. + always @(posedge clk2x) begin + toggle_2x <= toggle_2x_fall; + toggle_2x_dly <= toggle_2x; + adc_q_data_in_2x <= adc_q_in_2x_fall; + adc_i_data_in_2x <= adc_i_in_2x_fall; + // Place Q in the MSBs, I in the LSBs by default, unless swapped = 1. + if (valid_2x) begin + if (swap_iq_2x) begin + if (toggle_2x != toggle_2x_dly) begin + data_out_2x[31:16] <= adc_i_data_in_2x[15:0]; + data_out_2x[15: 0] <= adc_q_data_in_2x[15:0]; + end else begin + data_out_2x[31:16] <= adc_i_data_in_2x[31:16]; + data_out_2x[15: 0] <= adc_q_data_in_2x[31:16]; + end + end else begin + if (toggle_2x != toggle_2x_dly) begin + data_out_2x[31:16] <= adc_q_data_in_2x[15:0]; + data_out_2x[15: 0] <= adc_i_data_in_2x[15:0]; + end else begin + data_out_2x[31:16] <= adc_q_data_in_2x[31:16]; + data_out_2x[15: 0] <= adc_i_data_in_2x[31:16]; + end + end + end else begin + data_out_2x <= 32'b0; + end + // Valid is simply a transferred version of the 1x clock's valid. Delay it + // one more cycle to align outputs. + valid_2x <= valid_in_2x_fall && enable_2x_fall; + valid_dly_2x <= valid_2x; + end + + assign adc_out_2x = data_out_2x; + assign valid_out_2x = valid_dly_2x; + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd b/fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd new file mode 100644 index 000000000..b5df20a08 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/dac_1_3_clk_converter.vhd @@ -0,0 +1,143 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_1_3_clk_converter +-- +-- Description: +-- +-- This module transfers data from s_axis_aclk to m_axis_aclk. m_axis_aclk +-- must be three times the frequency of s_axis_aclk, and the two clocks must +-- be related (this module requires timing closure across the clock domain +-- boundary). +-- + +library IEEE; + use IEEE.std_logic_1164.all; + +entity dac_1_3_clk_converter is + port( + s_axis_aclk : in std_logic; + s_axis_aresetn : in std_logic; + s_axis_tvalid : in std_logic; + s_axis_tdata : in std_logic_vector(31 downto 0); + s_axis_tready : out std_logic := '1'; + + m_axis_aclk : in std_logic; + m_axis_aresetn : in std_logic; + m_axis_tready : in std_logic; + m_axis_tdata : out std_logic_vector(31 downto 0); + m_axis_tvalid : out std_logic + ); +end entity dac_1_3_clk_converter; + +architecture RTL of dac_1_3_clk_converter is + + -- I was unable to think of a simple implementation that implements a correct + -- AXIS handshake on both ports. All my ideas became equivalent to a two + -- clock FIFO (although the clocks are synchronous, so the write-to-read + -- latency would have been certain). + -- + -- We don't expect the DAC to ever hold off incoming data, and dac_100m_bd + -- already has the AXIS handshake disconnected: the FIR is configured to + -- disallow back pressure - it has no m_axis_data_tready pin. + -- + -- I'm going with the simple, but not strictly correct, implementation. + -- s_axis_tready will be constantly true, even when it shouldn't be. The + -- bottom line is this component is likely useless for any application but + -- dac_100m_bd. + + type output_fsm is ( + idle, + got_data, + -- The recovery state of delay ensures that we don't re-use an old input + -- valid signal (remember the output clock is 3x the frequency of the input + -- clock) + recovery + ); + + subtype word is std_logic_vector(s_axis_tdata'range); + signal output_state_mclk : output_fsm; + signal axis_tdata_sclk : word; + signal axis_tvalid_sclk : std_logic; + + signal axis_tdata_mclk : word; + signal axis_tvalid_mclk : std_logic; + +begin + + s_axis_tready <= '1'; + + input_valid_register: + process(s_axis_aclk, s_axis_aresetn) is + begin + if s_axis_aresetn='0' then + axis_tvalid_sclk <= '0'; + elsif rising_edge(s_axis_aclk) then + axis_tvalid_sclk <= s_axis_tvalid; + end if; + end process; + + input_data_register: + process (s_axis_aclk) is + begin + if rising_edge(s_axis_aclk) then + axis_tdata_sclk <= s_axis_tdata; + end if; + end process input_data_register; + + -- These CDC registers will not become metastable because the two clock + -- domains are related. + cdc_input_valid_register: + process (m_axis_aclk, m_axis_aresetn) is + begin + if m_axis_aresetn='0' then + axis_tvalid_mclk <= '0'; + elsif rising_edge(m_axis_aclk) then + axis_tvalid_mclk <= axis_tvalid_sclk; + end if; + end process cdc_input_valid_register; + + cdc_input_data_register: + process (m_axis_aclk) is + begin + if rising_edge(m_axis_aclk) then + axis_tdata_mclk <= axis_tdata_sclk; + end if; + end process cdc_input_data_register; + + output_data_register: + process (m_axis_aclk) is + begin + if rising_edge(m_axis_aclk) then + if output_state_mclk=idle then + m_axis_tdata <= axis_tdata_mclk; + end if; + end if; + end process output_data_register; + + fsm: process(m_axis_aresetn, m_axis_aclk) is + begin + if m_axis_aresetn='0' then + output_state_mclk <= idle; + m_axis_tvalid <= '0'; + elsif rising_edge(m_axis_aclk) then + m_axis_tvalid <= '0'; + case output_state_mclk is + when idle => + if axis_tvalid_mclk='1' then + output_state_mclk <= got_data; + end if; + + when got_data => + m_axis_tvalid <= '1'; + output_state_mclk <= recovery; + + when recovery => + output_state_mclk <= idle; + end case; + end if; + end process fsm; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd b/fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd new file mode 100644 index 000000000..fcef4b4e0 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/dac_2_1_clk_converter.vhd @@ -0,0 +1,118 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_2_1_clk_converter +-- +-- Description: +-- +-- This module transfers data from s_axis_aclk to m_axis_aclk. s_axis_aclk +-- must be two times the frequency of m_axis_aclk, and the two clocks must be +-- related (this module requires timing closure across the clock domain +-- boundary). +-- + +library IEEE; + use IEEE.std_logic_1164.all; + +entity dac_2_1_clk_converter is + port ( + s_axis_aclk : in std_logic; + s_axis_aresetn : in std_logic; + s_axis_tvalid : in std_logic; + s_axis_tdata : in std_logic_vector(63 downto 0); + + m_axis_aclk : in std_logic; + m_axis_aresetn : in std_logic; + m_axis_tready : in std_logic; + m_axis_tvalid : out std_logic; + m_axis_tdata : out std_logic_vector(63 downto 0) + ); +end entity dac_2_1_clk_converter; + +architecture RTL of dac_2_1_clk_converter is + + -- To keep the implementation simple, this module does not implement a + -- correct AXIS handshake - it ignores m_axis_tready. dac_100m_bd already had + -- an assumption that the AXIS handshake is unneeded: duc_saturate does not + -- accept _tready from the following component. Also, registered_dac_data has + -- never accepted _tready from dac_2_1_clk_converter, so dac_100m_bd has + -- never supported complete AXIS dataflow. + + subtype Word_t is std_logic_vector(s_axis_tdata'range); + signal s_axis_tvalid_pipe : std_logic_vector(1 downto 0); + signal s_axis_tdata_reg : Word_t; + + -- These _CDC signals will be sampled in the destination clock domain, but + -- will not produce any metastability because the input clocks must be + -- synchronous. + -- + -- These signals must be driven by registers not to prevent glitches (as in + -- an asynchronous CDC), but to improve timing closure. + signal s_axis_tvalid_CDC : std_logic; + signal s_axis_tdata_CDC : Word_t; + + -- m_axis_aclk and s_axis_aclk are nominally aligned by their rising edges. + -- Because m_axis_aclk is more heavily loaded than s_axis_aclk, m_axis_aclk + -- has a larger distribution delay, which causes a large hold violation using + -- post-place timing estimates. The Ultrafast method (UG 949) recommends + -- addressing such hold violations when WHS < -0.5 ns. By resampling on the + -- falling edge of the destination clock, we get nominally half a period of + -- setup and half a period of hold. The destination clock delay reduces the + -- hold margin, and increases the setup margin. + signal m_axis_tvalid_fall : std_logic; + signal m_axis_tdata_fall : Word_t; + +begin + + -- In the source clock domain, we capture incoming valid data and keep a + -- history of _tvalid over the last three clock cycles. If s_axis_tvalid has + -- been asserted once in the last three clock cycles, assert + -- s_axis_tvalid_CDC to be sampled in the output clock domain. The length of + -- s_axis_tvalid_pipe must match the ratio of the clock frequencies (2:1). + InputSampling: + process (s_axis_aclk) is + begin + if rising_edge(s_axis_aclk) then + if s_axis_tvalid='1' then + s_axis_tdata_reg <= s_axis_tdata; + end if; + s_axis_tdata_CDC <= s_axis_tdata_reg; + if s_axis_aresetn='0' then + s_axis_tvalid_pipe <= (others => '0'); + s_axis_tvalid_CDC <= '0'; + else + s_axis_tvalid_pipe <= s_axis_tvalid_pipe(0) & s_axis_tvalid; + if (s_axis_tvalid_pipe /= "00") then + s_axis_tvalid_CDC <= '1'; + else + s_axis_tvalid_CDC <= '0'; + end if; + end if; + end if; + end process InputSampling; + + FallingEdgeSampling: + process (m_axis_aclk) is + begin + if falling_edge(m_axis_aclk) then + m_axis_tvalid_fall <= s_axis_tvalid_CDC; + m_axis_tdata_fall <= s_axis_tdata_CDC; + end if; + end process FallingEdgeSampling; + + OutputRegisters: + process (m_axis_aclk) is + begin + if rising_edge(m_axis_aclk) then + m_axis_tdata <= m_axis_tdata_fall; + if m_axis_aresetn='0' then + m_axis_tvalid <= '0'; + else + m_axis_tvalid <= m_axis_tvalid_fall; + end if; + end if; + end process OutputRegisters; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd b/fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd new file mode 100644 index 000000000..9235f4fec --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/ddc_saturate.vhd @@ -0,0 +1,83 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: ddc_saturate +-- +-- Description: +-- +-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See +-- comments below for full description. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity ddc_saturate is + port( + Clk : in std_logic; + cDataIn : in std_logic_vector(47 downto 0); -- [Q,I] (I in LSBs) + cDataValidIn : in std_logic; + cDataOut : out std_logic_vector(31 downto 0); -- [Q,I] (I in LSBs) + cDataValidOut : out std_logic + ); +end ddc_saturate; + +architecture RTL of ddc_saturate is + + signal cDataOutI : std_logic_vector(15 downto 0) := (others => '0'); + signal cDataOutQ : std_logic_vector(15 downto 0) := (others => '0'); + +begin + + + ----------------------------------------------------------------------------- + -- Saturation + -- + -- The output of the Xilinx FIR Compiler has already been rounded on the LSB + -- side, but hasn't been saturated on the MSB side. + -- Coefficients = 18 bit, 1 integer bit (1.17) + -- Data In = 16 bits, 1 integer bit (1.15) + -- 1.17 * 1.15 = 2.32, and the Xilinx FIR core rounds to 2.15 + -- Data Out = 17 bits, 2 integer bits (2.15), with 17 LSBs already rounded + -- off inside the FIR core. + -- We need to manually saturate the 2.15 number back to a 1.15 number + -- + -- If 2 MSBs = 00, output <= input without MSB, e.g. positive number < 1 + -- If 2 MSBs = 01, output <= 0.111111111111111, e.g. positive number >= 1 + -- If 2 MSBs = 10, output <= 1.000000000000000, e.g. negative number < -1 + -- If 2 MSBs = 11, output <= input without MSB, e.g. negative number >= -1 + ----------------------------------------------------------------------------- + Saturation: + process(Clk) + begin + if rising_edge(Clk) then + -- Pipeline data valid to match the data + cDataValidOut <= cDataValidIn; + + -- I, from cDataIn(16 downto 0) + if cDataIn(16 downto 15) = "01" then + cDataOutI <= "0111111111111111"; + elsif cDataIn(16 downto 15) = "10" then + cDataOutI <= "1000000000000000"; + else + cDataOutI <= cDataIn(15 downto 0); + end if; + + -- Q, from cDataIn(40 downto 24) + if cDataIn(40 downto 39) = "01" then + cDataOutQ <= "0111111111111111"; + elsif cDataIn(40 downto 39) = "10" then + cDataOutQ <= "1000000000000000"; + else + cDataOutQ <= cDataIn(39 downto 24); + end if; + + end if; + end process Saturation; + + cDataOut <= cDataOutQ & cDataOutI; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd b/fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd new file mode 100644 index 000000000..5cb1bc1fc --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/duc_saturate.vhd @@ -0,0 +1,87 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: duc_saturate +-- +-- Description: +-- +-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See +-- comments below for full description. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity duc_saturate is + port( + Clk : in std_logic; + cDataIn : in std_logic_vector(47 downto 0); + cDataValidIn : in std_logic; + cReadyForInput : out std_logic; + cDataOut : out std_logic_vector(31 downto 0); + cDataValidOut : out std_logic := '0' + ); +end duc_saturate; + +architecture RTL of duc_saturate is + + signal cDataOutI : std_logic_vector(15 downto 0) := (others => '0'); + signal cDataOutQ : std_logic_vector(15 downto 0) := (others => '0'); + +begin + + ----------------------------------------------------------------------------- + -- Saturation + -- + -- The output of the Xilinx FIR Compiler has already been rounded on the LSB + -- side, but hasn't been saturated on the MSB side. + -- Coefficients = 18 bit, 1 integer bit (1.17) + -- Data In = 16 bits, 1 integer bit (1.15) + -- Xilinx FIR core rounds to output to 3.31. The filter coefficients has a + -- gain of 3 to compensate for the amplitude loss in interpolation, the + -- Xilinx FIR core rounds the output to 3.15. + -- Data Out = 18 bits, 3 integer bits (3.15), with 16 LSBs already rounded + -- off inside the FIR core. + -- We need to manually saturate the 3.15 number back to a 1.15 number + -- + -- If 3 MSBs = 000, output <= input without MSB, e.g. positive number < 1 + -- If 3 MSBs = 0x1/01x, output <= 0.111111111111111, e.g. positive number >= 1 + -- If 3 MSBs = 1x0/10x, output <= 1.000000000000000, e.g. negative number < -1 + -- If 3 MSBs = 111, output <= input without MSB, e.g. negative number >= -1 + ----------------------------------------------------------------------------- +Saturation: + process(Clk) + begin + if rising_edge(Clk) then + -- Pipeline data valid to match the data + cDataValidOut <= cDataValidIn; + + -- I, from cDataIn(17 downto 0) + if cDataIn(17) = '0' and cDataIn(16 downto 15) /= "00" then + cDataOutI <= "0111111111111111"; + elsif cDataIn(17) = '1' and cDataIn(16) /= cDataIn(15) then + cDataOutI <= "1000000000000000"; + else + cDataOutI <= cDataIn(15 downto 0); + end if; + + -- Q, from cDataIn(41 downto 24) + if cDataIn(41) = '0' and cDataIn(40 downto 39) /= "00" then + cDataOutQ <= "0111111111111111"; + elsif cDataIn(41) = '1' and + (not (cDataIn(40 downto 39) = "11")) then + cDataOutQ <= "1000000000000000"; + else + cDataOutQ <= cDataIn(39 downto 24); + end if; + + end if; + end process Saturation; + + cDataOut <= cDataOutQ & cDataOutI; + cReadyForInput <= '1'; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/100m/rf_core_100m.v b/fpga/usrp3/top/x400/rf/100m/rf_core_100m.v new file mode 100644 index 000000000..9ca837e47 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/100m/rf_core_100m.v @@ -0,0 +1,362 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rf_core_100m +// +// Description: +// +// Top-level wrapper for the ADC/DAC processing logic. One of these wrappers +// exists for every supported Data Rate. An instance of this core should +// exist per dboard. +// +// Data/RF Specs: +// DBs: 1 +// RX/DB: 2 +// TX/DB: 2 +// Data Rate: 122.88 or 125 MSps @ 1 SPC +// +// Input Clocks, all aligned to one another and coming from same MMCM +// rfdc_clk: 184.32 or 187.5 MHz (3x pll_ref_clk) +// rfdc_clk_2x: 368.64 or 375 MHz (6x pll_ref_clk) +// data_clk: 122.88 or 125 MHz (2x pll_ref_clk) +// + +`default_nettype none + +module rf_core_100m ( + + //--------------------------------------------------------------------------- + // Clocking + //--------------------------------------------------------------------------- + + // Main Clock Inputs + input wire rfdc_clk, + input wire rfdc_clk_2x, + input wire data_clk, + input wire data_clk_2x, // Unused, kept for rf_core_* interface consistency. + + // AXI4-Lite Configuration Clock + // This clock is used to synchronize status bits for the RFDC registers in + // the AXI-S clock domain. + input wire s_axi_config_clk, + + //--------------------------------------------------------------------------- + // RFDC Data Interfaces + //--------------------------------------------------------------------------- + // All ports here are in the rfdc_clk domain. + + // ADC + input wire [31:0] adc_data_in_i_tdata_0, + output wire adc_data_in_i_tready_0, + input wire adc_data_in_i_tvalid_0, + input wire [31:0] adc_data_in_q_tdata_0, + output wire adc_data_in_q_tready_0, + input wire adc_data_in_q_tvalid_0, + input wire [31:0] adc_data_in_i_tdata_1, + output wire adc_data_in_i_tready_1, + input wire adc_data_in_i_tvalid_1, + input wire [31:0] adc_data_in_q_tdata_1, + output wire adc_data_in_q_tready_1, + input wire adc_data_in_q_tvalid_1, + + // DAC + output wire [63:0] dac_data_out_tdata_0, + input wire dac_data_out_tready_0, + output wire dac_data_out_tvalid_0, + output wire [63:0] dac_data_out_tdata_1, + input wire dac_data_out_tready_1, + output wire dac_data_out_tvalid_1, + + //--------------------------------------------------------------------------- + // User Data Interfaces + //--------------------------------------------------------------------------- + // All ports here are in the data_clk domain. + + // ADC + output wire [31:0] adc_data_out_tdata_0, // Packed [Q,I] with Q in MSBs + output wire adc_data_out_tvalid_0, + output wire [31:0] adc_data_out_tdata_1, // Packed [Q,I] with Q in MSBs + output wire adc_data_out_tvalid_1, + + // DAC + input wire [31:0] dac_data_in_tdata_0, // Packed [Q,I] with Q in MSBs + output wire dac_data_in_tready_0, + input wire dac_data_in_tvalid_0, + input wire [31:0] dac_data_in_tdata_1, // Packed [Q,I] with Q in MSBs + output wire dac_data_in_tready_1, + input wire dac_data_in_tvalid_1, + + //--------------------------------------------------------------------------- + // Miscellaneous + //--------------------------------------------------------------------------- + + // Invert I/Q control signals from RFDC to DSP chain. + input wire [3:0] invert_adc_iq_rclk2, + input wire [3:0] invert_dac_iq_rclk2, + + // Control/status vectors from/to RFDC. + // Notice these are all in the s_axi_config_clk domain. + output wire [15:0] dsp_info_sclk, + output wire [15:0] axi_status_sclk, + + // Resets. + input wire adc_data_out_resetn_dclk, + input wire adc_enable_data_rclk, + input wire adc_rfdc_axi_resetn_rclk, + input wire dac_data_in_resetn_dclk, + input wire dac_data_in_resetn_rclk, + input wire dac_data_in_resetn_rclk2x, + input wire fir_resetn_rclk2x, + + // Version (Constant) + output wire [95:0] version_info +); + + `include "../../regmap/rfdc_regs_regmap_utils.vh" + `include "../../regmap/versioning_regs_regmap_utils.vh" + `include "../../regmap/versioning_utils.vh" + + // Fixed for this implementation. + localparam NUM_ADC_CHANNELS = 2; + localparam NUM_DAC_CHANNELS = 2; + + // ADC data interface from RFDC. + wire [31:0] adc_data_in_i_tdata [0:7]; // 2 SPC (I) + wire [31:0] adc_data_in_q_tdata [0:7]; // 2 SPC (Q) + wire [ 7:0] adc_data_in_i_tready; + wire [ 7:0] adc_data_in_q_tready; + wire [ 7:0] adc_data_in_i_tvalid; + wire [ 7:0] adc_data_in_q_tvalid; + // DAC data interface to RFDC. + wire [63:0] dac_data_out_tdata [0:7]; // 2 SPC (I + Q) + wire [ 7:0] dac_data_out_tready; + wire [ 7:0] dac_data_out_tvalid; + + // ADC data interface to user. + wire [31:0] adc_data_out_tdata [0:7]; // 1 SPC (I + Q) + wire [ 7:0] adc_data_out_tready; + wire [ 7:0] adc_data_out_tvalid; + // DAC data interface from user. + wire [31:0] dac_data_in_tdata_preswap [0:7]; // 1 SPC (I + Q) + wire [31:0] dac_data_in_tdata [0:7]; // 1 SPC (I + Q) + wire [ 7:0] dac_data_in_tready; + wire [ 7:0] dac_data_in_tvalid; + + wire [ 7:0] invert_dac_iq_dclk; + wire [15:0] axi_status; + + //--------------------------------------------------------------------------- + // Resets, Debug and Misc. + //--------------------------------------------------------------------------- + + // Group all these status bits together. They don't toggle frequently so data + // coherency is not an issue here. + // Using constants for DB0 since the bits are the 16 LSBs in a 32-bit vector. + // DB1 simply uses the 16 MSBs when wiring the status vector. + assign axi_status[USER_ADC_TREADY_MSB :USER_ADC_TREADY ] = adc_data_out_tready [1:0]; + assign axi_status[USER_ADC_TVALID_MSB :USER_ADC_TVALID ] = adc_data_out_tvalid [1:0]; + assign axi_status[RFDC_ADC_I_TVALID_MSB:RFDC_ADC_I_TVALID] = adc_data_in_i_tvalid[1:0]; + assign axi_status[RFDC_ADC_Q_TVALID_MSB:RFDC_ADC_Q_TVALID] = adc_data_in_q_tvalid[1:0]; + assign axi_status[RFDC_ADC_I_TREADY_MSB:RFDC_ADC_I_TREADY] = adc_data_in_i_tready[1:0]; + assign axi_status[RFDC_ADC_Q_TREADY_MSB:RFDC_ADC_Q_TREADY] = adc_data_in_q_tready[1:0]; + assign axi_status[RFDC_DAC_TVALID_MSB :RFDC_DAC_TVALID ] = dac_data_out_tvalid [1:0]; + assign axi_status[RFDC_DAC_TREADY_MSB :RFDC_DAC_TREADY ] = dac_data_out_tready [1:0]; + + synchronizer #( + .WIDTH (16), + .STAGES (2), + .INITIAL_VAL (0), + .FALSE_PATH_TO_IN (1) + ) synchronizer_axis_status ( + .clk (s_axi_config_clk), + .rst (1'b0), + .in (axi_status), + .out (axi_status_sclk) + ); + + // Drive the DSP info vector with information on this specific DSP chain. + assign dsp_info_sclk[FABRIC_DSP_BW_MSB :FABRIC_DSP_BW] = FABRIC_DSP_BW_100M; + assign dsp_info_sclk[FABRIC_DSP_RX_CNT_MSB:FABRIC_DSP_RX_CNT] = NUM_ADC_CHANNELS; + assign dsp_info_sclk[FABRIC_DSP_TX_CNT_MSB:FABRIC_DSP_TX_CNT] = NUM_DAC_CHANNELS; + + //--------------------------------------------------------------------------- + // ADC Post-Processing + //--------------------------------------------------------------------------- + + // Data comes from the RFDC as 2 SPC, separate streams for each channel and + // I/Q. + assign adc_data_in_i_tdata[0] = adc_data_in_i_tdata_0; + assign adc_data_in_q_tdata[0] = adc_data_in_q_tdata_0; + assign adc_data_in_i_tdata[1] = adc_data_in_i_tdata_1; + assign adc_data_in_q_tdata[1] = adc_data_in_q_tdata_1; + + assign adc_data_in_i_tready_0 = adc_data_in_i_tready[0]; + assign adc_data_in_i_tvalid[0] = adc_data_in_i_tvalid_0; + assign adc_data_in_q_tready_0 = adc_data_in_q_tready[0]; + assign adc_data_in_q_tvalid[0] = adc_data_in_q_tvalid_0; + assign adc_data_in_i_tready_1 = adc_data_in_i_tready[1]; + assign adc_data_in_i_tvalid[1] = adc_data_in_i_tvalid_1; + assign adc_data_in_q_tready_1 = adc_data_in_q_tready[1]; + assign adc_data_in_q_tvalid[1] = adc_data_in_q_tvalid_1; + + // ADC Data from the RFDC arrives here as 2 SPC with separate I and Q + // streams. It leaves the adc_100m_bd as 1 SPC with I and Q packed into a + // single 32 bit word. + genvar adc_num; + generate + for (adc_num=0; adc_num < (NUM_ADC_CHANNELS); adc_num = adc_num + 1) + begin : adc_gen + adc_100m_bd adc_100m_bd_gen ( + .adc_data_out_resetn_dclk (adc_data_out_resetn_dclk), + .data_clk (data_clk), + .enable_data_to_fir_rclk (adc_enable_data_rclk), + .fir_resetn_rclk2x (fir_resetn_rclk2x), + .rfdc_adc_axi_resetn_rclk (adc_rfdc_axi_resetn_rclk), + .rfdc_clk (rfdc_clk), + .rfdc_clk_2x (rfdc_clk_2x), + .swap_iq_2x (invert_adc_iq_rclk2 [adc_num]), + .adc_data_out_tvalid (adc_data_out_tvalid [adc_num]), + .adc_data_out_tdata (adc_data_out_tdata [adc_num]), + .adc_i_data_in_tvalid (adc_data_in_i_tvalid[adc_num]), + .adc_i_data_in_tready (adc_data_in_i_tready[adc_num]), + .adc_i_data_in_tdata (adc_data_in_i_tdata [adc_num]), + .adc_q_data_in_tvalid (adc_data_in_q_tvalid[adc_num]), + .adc_q_data_in_tready (adc_data_in_q_tready[adc_num]), + .adc_q_data_in_tdata (adc_data_in_q_tdata [adc_num]) + ); + end + endgenerate + + // Data is released to the user as 1 SPC, separate streams for each channel. + assign adc_data_out_tdata_0 = adc_data_out_tdata[0]; + assign adc_data_out_tdata_1 = adc_data_out_tdata[1]; + + // There is no tready going to the ADC (one has to be always ready for ADC + // data), but it is still a component of the axi_status vector as a generic + // AXI stream status. Report 1'b1 to the status vector consistent with being + // always ready + assign adc_data_out_tready[0] = 1'b1; + assign adc_data_out_tvalid_0 = adc_data_out_tvalid[0]; + assign adc_data_out_tready[1] = 1'b1; + assign adc_data_out_tvalid_1 = adc_data_out_tvalid[1]; + + //--------------------------------------------------------------------------- + // DAC Pre-Processing + //--------------------------------------------------------------------------- + + // Data comes from the user as 1 SPC, separate streams for each channel. + assign dac_data_in_tdata_preswap[0] = dac_data_in_tdata_0; + assign dac_data_in_tdata_preswap[1] = dac_data_in_tdata_1; + + assign dac_data_in_tready_0 = dac_data_in_tready[0]; + assign dac_data_in_tvalid[0] = dac_data_in_tvalid_0; + assign dac_data_in_tready_1 = dac_data_in_tready[1]; + assign dac_data_in_tvalid[1] = dac_data_in_tvalid_1; + + // Optionally swap IQ data positions in the vector. First cross the swap + // vector over to the data_clk domain. + synchronizer #( + .WIDTH (8), + .STAGES (2), + .INITIAL_VAL (0), + .FALSE_PATH_TO_IN (1) + ) synchronizer_invert_dac_iq ( + .clk (data_clk), + .rst (1'b0), + .in (invert_dac_iq_rclk2), + .out (invert_dac_iq_dclk) + ); + + genvar dac_num; + generate + for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1) + begin : dac_swap_gen + assign dac_data_in_tdata[dac_num][15:00] = invert_dac_iq_dclk[dac_num] ? + (dac_data_in_tdata_preswap[dac_num][31:16]) : (dac_data_in_tdata_preswap[dac_num][15:0]); + assign dac_data_in_tdata[dac_num][31:16] = invert_dac_iq_dclk[dac_num] ? + (dac_data_in_tdata_preswap[dac_num][15:00]) : (dac_data_in_tdata_preswap[dac_num][31:16]); + end + endgenerate + + // These streams are then interpolated by dac_100m_bd, and form a single + // stream per channel, 2 SPC, packed: MSB [Sample1Q, Sample1I, Sample0Q, + // Sample0I] LSB. + generate + for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1) + begin : dac_gen + dac_100m_bd dac_100m_bd_gen ( + .dac_data_in_resetn_dclk (dac_data_in_resetn_dclk), + .dac_data_in_resetn_rclk (dac_data_in_resetn_rclk), + .dac_data_in_resetn_rclk2x (dac_data_in_resetn_rclk2x), + .data_clk (data_clk), + .rfdc_clk (rfdc_clk), + .rfdc_clk_2x (rfdc_clk_2x), + .dac_data_out_tdata (dac_data_out_tdata [dac_num]), + .dac_data_out_tvalid (dac_data_out_tvalid[dac_num]), + .dac_data_out_tready (dac_data_out_tready[dac_num]), + .dac_data_in_tdata (dac_data_in_tdata [dac_num]), + .dac_data_in_tvalid (dac_data_in_tvalid [dac_num]), + .dac_data_in_tready (dac_data_in_tready [dac_num]) + ); + end + endgenerate + + // Data is released to the RFDC as 2 SPC, separate streams per channel (I/Q + // together). + assign dac_data_out_tdata_0 = dac_data_out_tdata[0]; + assign dac_data_out_tdata_1 = dac_data_out_tdata[1]; + + assign dac_data_out_tready[0] = dac_data_out_tready_0; + assign dac_data_out_tvalid_0 = dac_data_out_tvalid[0]; + assign dac_data_out_tready[1] = dac_data_out_tready_1; + assign dac_data_out_tvalid_1 = dac_data_out_tvalid[1]; + + //--------------------------------------------------------------------------- + // Version + //--------------------------------------------------------------------------- + + // Version metadata, constants come from auto-generated + // versioning_regs_regmap_utils.vh + assign version_info = build_component_versions( + RF_CORE_100M_VERSION_LAST_MODIFIED_TIME, + build_version( + RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MAJOR, + RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MINOR, + RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_BUILD + ), + build_version( + RF_CORE_100M_CURRENT_VERSION_MAJOR, + RF_CORE_100M_CURRENT_VERSION_MINOR, + RF_CORE_100M_CURRENT_VERSION_BUILD + ) + ); + +endmodule + +`default_nettype wire + +//XmlParse xml_on +//<regmap name="VERSIONING_REGS_REGMAP"> +// <group name="VERSIONING_CONSTANTS"> +// <enumeratedtype name="RF_CORE_100M_VERSION" showhex="true"> +// <info> +// 100 MHz RF core.{BR/} +// For guidance on when to update these revision numbers, +// please refer to the register map documentation accordingly: +// <li> Current version: @.VERSIONING_REGS_REGMAP..CURRENT_VERSION +// <li> Oldest compatible version: @.VERSIONING_REGS_REGMAP..OLDEST_COMPATIBLE_VERSION +// <li> Version last modified: @.VERSIONING_REGS_REGMAP..VERSION_LAST_MODIFIED +// </info> +// <value name="RF_CORE_100M_CURRENT_VERSION_MAJOR" integer="1"/> +// <value name="RF_CORE_100M_CURRENT_VERSION_MINOR" integer="0"/> +// <value name="RF_CORE_100M_CURRENT_VERSION_BUILD" integer="0"/> +// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MAJOR" integer="1"/> +// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MINOR" integer="0"/> +// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_BUILD" integer="0"/> +// <value name="RF_CORE_100M_VERSION_LAST_MODIFIED_TIME" integer="0x20102617"/> +// </enumeratedtype> +// </group> +//</regmap> +//XmlParse xml_off diff --git a/fpga/usrp3/top/x400/rf/200m/rf_core_200m.v b/fpga/usrp3/top/x400/rf/200m/rf_core_200m.v new file mode 100644 index 000000000..9a08e7e57 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/200m/rf_core_200m.v @@ -0,0 +1,220 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rf_core_200m +// +// Description: +// +// Implementation of rf_core with 200 MHz bandwidth. It presents an interface +// that inputs/outputs 2 samples per cycle. This version is implemented by +// instantiating rf_core_400m and adding up-conversion and down-conversion +// filters. +// + +`default_nettype none + +module rf_core_200m ( + + //--------------------------------------------------------------------------- + // Clocking + //--------------------------------------------------------------------------- + + // Main Clock Inputs + input wire rfdc_clk, + input wire rfdc_clk_2x, + input wire data_clk, + input wire data_clk_2x, + + // AXI4-Lite Config Clock + // This clock is used to synchronize status bits for the RFDC + // registers in the AXI-S clock domain. + input wire s_axi_config_clk, + + //--------------------------------------------------------------------------- + // RFDC Data Interfaces + //--------------------------------------------------------------------------- + // All ports here are in the rfdc_clk domain. + + // ADC + input wire [127:0] adc_data_in_i_tdata_0, + output wire adc_data_in_i_tready_0, + input wire adc_data_in_i_tvalid_0, + input wire [127:0] adc_data_in_q_tdata_0, + output wire adc_data_in_q_tready_0, + input wire adc_data_in_q_tvalid_0, + input wire [127:0] adc_data_in_i_tdata_1, + output wire adc_data_in_i_tready_1, + input wire adc_data_in_i_tvalid_1, + input wire [127:0] adc_data_in_q_tdata_1, + output wire adc_data_in_q_tready_1, + input wire adc_data_in_q_tvalid_1, + + // DAC + output wire [255:0] dac_data_out_tdata_0, + input wire dac_data_out_tready_0, + output wire dac_data_out_tvalid_0, + output wire [255:0] dac_data_out_tdata_1, + input wire dac_data_out_tready_1, + output wire dac_data_out_tvalid_1, + + //--------------------------------------------------------------------------- + // User Data Interface + //--------------------------------------------------------------------------- + // All ports here are in the data_clk domain. + + // ADC + output wire [63:0] adc_data_out_tdata_0, // Packed {Q1,I1,Q0,I0} + output wire adc_data_out_tvalid_0, + output wire [63:0] adc_data_out_tdata_1, // Packed {Q1,I1,Q0,I0} + output wire adc_data_out_tvalid_1, + + // DAC + input wire [63:0] dac_data_in_tdata_0, // Packed {Q1,I1,Q0,I0} with Q in MSBs + output wire dac_data_in_tready_0, + input wire dac_data_in_tvalid_0, + input wire [63:0] dac_data_in_tdata_1, // Packed {Q1,I1,Q0,I0} with Q in MSBs + output wire dac_data_in_tready_1, + input wire dac_data_in_tvalid_1, + + + //--------------------------------------------------------------------------- + // Miscellaneous + //--------------------------------------------------------------------------- + + // Invert I/Q control signals from RFDC to DSP chain. + input wire [3:0] invert_adc_iq_rclk2, + input wire [3:0] invert_dac_iq_rclk2, + + // Control/status vectors from/to RFDC. + // Notice these are all in the s_axi_config_clk domain. + output reg [15:0] dsp_info_sclk, + output wire [15:0] axi_status_sclk, + + // Resets. + input wire adc_data_out_resetn_dclk, + input wire adc_enable_data_rclk, + input wire adc_rfdc_axi_resetn_rclk, + input wire dac_data_in_resetn_dclk, + input wire dac_data_in_resetn_dclk2x, + input wire dac_data_in_resetn_rclk, + input wire fir_resetn_rclk2x, + + // Version (Constant) + output wire [95:0] version_info +); + + `include "../../regmap/rfdc_regs_regmap_utils.vh" + + //--------------------------------------------------------------------------- + // 400 MHz RF Core + //--------------------------------------------------------------------------- + + wire [127:0] adc_400m_tdata_0; + wire adc_400m_tvalid_0; + wire [127:0] adc_400m_tdata_1; + wire adc_400m_tvalid_1; + wire [127:0] dac_400m_tdata_0; + wire dac_400m_tvalid_0; + wire [127:0] dac_400m_tdata_1; + wire dac_400m_tvalid_1; + + wire [ 15:0] dsp_info_sclk_400m; + + rf_core_400m rf_core_400m_i ( + .rfdc_clk (rfdc_clk), + .rfdc_clk_2x (rfdc_clk_2x), + .data_clk (data_clk), + .data_clk_2x (data_clk_2x), + .s_axi_config_clk (s_axi_config_clk), + .adc_data_in_i_tdata_0 (adc_data_in_i_tdata_0), + .adc_data_in_i_tready_0 (adc_data_in_i_tready_0), + .adc_data_in_i_tvalid_0 (adc_data_in_i_tvalid_0), + .adc_data_in_q_tdata_0 (adc_data_in_q_tdata_0), + .adc_data_in_q_tready_0 (adc_data_in_q_tready_0), + .adc_data_in_q_tvalid_0 (adc_data_in_q_tvalid_0), + .adc_data_in_i_tdata_1 (adc_data_in_i_tdata_1), + .adc_data_in_i_tready_1 (adc_data_in_i_tready_1), + .adc_data_in_i_tvalid_1 (adc_data_in_i_tvalid_1), + .adc_data_in_q_tdata_1 (adc_data_in_q_tdata_1), + .adc_data_in_q_tready_1 (adc_data_in_q_tready_1), + .adc_data_in_q_tvalid_1 (adc_data_in_q_tvalid_1), + .dac_data_out_tdata_0 (dac_data_out_tdata_0), + .dac_data_out_tready_0 (dac_data_out_tready_0), + .dac_data_out_tvalid_0 (dac_data_out_tvalid_0), + .dac_data_out_tdata_1 (dac_data_out_tdata_1), + .dac_data_out_tready_1 (dac_data_out_tready_1), + .dac_data_out_tvalid_1 (dac_data_out_tvalid_1), + .adc_data_out_tdata_0 (adc_400m_tdata_0), + .adc_data_out_tvalid_0 (adc_400m_tvalid_0), + .adc_data_out_tdata_1 (adc_400m_tdata_1), + .adc_data_out_tvalid_1 (adc_400m_tvalid_1), + .dac_data_in_tdata_0 (dac_400m_tdata_0), + .dac_data_in_tready_0 (), + .dac_data_in_tvalid_0 (dac_400m_tvalid_0), + .dac_data_in_tdata_1 (dac_400m_tdata_1), + .dac_data_in_tready_1 (), + .dac_data_in_tvalid_1 (dac_400m_tvalid_1), + .invert_adc_iq_rclk2 (invert_adc_iq_rclk2), + .invert_dac_iq_rclk2 (invert_dac_iq_rclk2), + .dsp_info_sclk (dsp_info_sclk_400m), + .axi_status_sclk (axi_status_sclk), + .adc_data_out_resetn_dclk (adc_data_out_resetn_dclk), + .adc_enable_data_rclk (adc_enable_data_rclk), + .adc_rfdc_axi_resetn_rclk (adc_rfdc_axi_resetn_rclk), + .dac_data_in_resetn_dclk (dac_data_in_resetn_dclk), + .dac_data_in_resetn_dclk2x (dac_data_in_resetn_dclk2x), + .dac_data_in_resetn_rclk (dac_data_in_resetn_rclk), + .fir_resetn_rclk2x (fir_resetn_rclk2x), + .version_info (version_info) + ); + + // Change reported bandwidth 200 MHz + always @(*) begin + dsp_info_sclk <= dsp_info_sclk_400m; + dsp_info_sclk[FABRIC_DSP_BW_MSB : FABRIC_DSP_BW] <= FABRIC_DSP_BW_200M; + end + + + //--------------------------------------------------------------------------- + // ADC Down-conversion + //--------------------------------------------------------------------------- + + rf_down_4to2 #( + .NUM_CHANNELS (2) + ) rf_down_4to2_i ( + .clk (data_clk), + .clk_2x (data_clk_2x), + .rst (~adc_data_out_resetn_dclk), + .rst_2x (~adc_data_out_resetn_dclk), // 1x clk reset is safe to use + .i_tdata ({ adc_400m_tdata_1, adc_400m_tdata_0 }), + .i_tvalid ({ adc_400m_tvalid_1, adc_400m_tvalid_0 }), + .o_tdata ({ adc_data_out_tdata_1, adc_data_out_tdata_0 }), + .o_tvalid ({ adc_data_out_tvalid_1, adc_data_out_tvalid_0 }) + ); + + + //--------------------------------------------------------------------------- + // DAC Up-conversion + //--------------------------------------------------------------------------- + + assign dac_data_in_tready_0 = 1'b1; + assign dac_data_in_tready_1 = 1'b1; + + rf_up_2to4 #( + .NUM_CHANNELS (2) + ) rf_up_2to4_i ( + .clk (data_clk), + .clk_2x (data_clk_2x), + .rst (~dac_data_in_resetn_dclk), + .rst_2x (~dac_data_in_resetn_dclk2x), + .i_tdata ({ dac_data_in_tdata_1, dac_data_in_tdata_0 }), + .i_tvalid ({ dac_data_in_tvalid_1, dac_data_in_tvalid_0 }), + .o_tdata ({ dac_400m_tdata_1, dac_400m_tdata_0 }), + .o_tvalid ({ dac_400m_tvalid_1, dac_400m_tvalid_0 }) + ); + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/200m/rf_down_4to2.v b/fpga/usrp3/top/x400/rf/200m/rf_down_4to2.v new file mode 100644 index 000000000..bc1ed177e --- /dev/null +++ b/fpga/usrp3/top/x400/rf/200m/rf_down_4to2.v @@ -0,0 +1,149 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rf_down_4to2 +// +// Description: +// +// Implements a down-sampling filter that accepts 4 samples per cycle on the +// input and outputs 2 samples per cycle. A 2x speed clock is used to perform +// the DSP computation, so that less logic can be used to implement the +// half-band filter. +// +// Data Path : In --> Gearbox --> Filter --> Gearbox --> Out +// SPC : 4 2 1 2 +// Clock Rate : 1x 2x 2x 1x +// + +`default_nettype none + +module rf_down_4to2 #( + parameter NUM_CHANNELS = 1 +) ( + input wire clk, + input wire clk_2x, + + // Synchronous resets + input wire rst, + input wire rst_2x, + + // Input - 4 SPC, synchronous to "clk" + input wire [NUM_CHANNELS*128-1:0] i_tdata, + input wire [NUM_CHANNELS* 1-1:0] i_tvalid, + + // Output - 2 SPC, synchronous to "clk" + output wire [NUM_CHANNELS*64-1:0] o_tdata, + output wire [NUM_CHANNELS* 1-1:0] o_tvalid +); + + generate + genvar ch; + for (ch = 0; ch < NUM_CHANNELS; ch = ch + 1) begin : gen_channel + + //----------------------------------------------------------------------- + // Input Gearbox + //----------------------------------------------------------------------- + // + // Convert from 4 SPC on clk to 2 SPC on clk_2x. + // + //----------------------------------------------------------------------- + + wire [63:0] gear_to_filt_tdata; + wire gear_to_filt_tvalid; + + gearbox_2x1 #( + .WORD_W (32), + .IN_WORDS (4), + .OUT_WORDS (2), + .BIG_ENDIAN (0) + ) gearbox_2x1_in ( + .i_clk (clk), + .i_rst (rst), + .i_tdata (i_tdata[ch*128 +: 128]), + .i_tvalid (i_tvalid[ch]), + .o_clk (clk_2x), + .o_rst (rst_2x), + .o_tdata (gear_to_filt_tdata), + .o_tvalid (gear_to_filt_tvalid) + ); + + + //----------------------------------------------------------------------- + // Interpolating Filter + //----------------------------------------------------------------------- + + wire [47:0] filt_to_clip_tdata; + wire filt_to_clip_tvalid; + + hb47_2to1 hb47_2to1_i ( + .aresetn (~rst_2x), + .aclk (clk_2x), + .s_axis_data_tvalid (gear_to_filt_tvalid), + .s_axis_data_tready (), + .s_axis_data_tdata (gear_to_filt_tdata), + .m_axis_data_tvalid (filt_to_clip_tvalid), + .m_axis_data_tuser (), + .m_axis_data_tdata (filt_to_clip_tdata) + ); + + + //----------------------------------------------------------------------- + // Saturation + //----------------------------------------------------------------------- + + wire [31:0] clip_to_gear_tdata; + wire clip_to_gear_tvalid; + + genvar word; + for (word = 0; word < 2; word = word+1) begin : gen_sat + axi_clip #( + .WIDTH_IN (24), + .WIDTH_OUT (16), + .FIFOSIZE (0) + ) axi_clip_i ( + .clk (clk_2x), + .reset (rst_2x), + .i_tdata (filt_to_clip_tdata[word*24 +: 24]), + .i_tlast (1'b0), + .i_tvalid (filt_to_clip_tvalid), + .i_tready (), + .o_tdata (clip_to_gear_tdata[word*16 +: 16]), + .o_tlast (), + .o_tvalid (clip_to_gear_tvalid), + .o_tready (1'b1) + ); + end + + + //----------------------------------------------------------------------- + // Output Gearbox + //----------------------------------------------------------------------- + // + // Convert from 1 SPC on clk_2x to 2 SPC on clk. + // + //----------------------------------------------------------------------- + + gearbox_2x1 #( + .WORD_W (32), + .IN_WORDS (1), + .OUT_WORDS (2), + .BIG_ENDIAN (0) + ) gearbox_2x1_out ( + .i_clk (clk_2x), + .i_rst (rst_2x), + .i_tdata (clip_to_gear_tdata), + .i_tvalid (clip_to_gear_tvalid), + .o_clk (clk), + .o_rst (rst), + .o_tdata (o_tdata[ch*64 +: 64]), + .o_tvalid (o_tvalid[ch]) + ); + + end // for + endgenerate + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/200m/rf_up_2to4.v b/fpga/usrp3/top/x400/rf/200m/rf_up_2to4.v new file mode 100644 index 000000000..0cabf346e --- /dev/null +++ b/fpga/usrp3/top/x400/rf/200m/rf_up_2to4.v @@ -0,0 +1,149 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: rf_up_2to4 +// +// Description: +// +// Implements an up-sampling filter that accepts 2 samples per cycle on the +// input and outputs 4 samples per cycle. A 2x speed clock is used to perform +// the DSP computation, so that less logic can be used to implement the +// half-band filter. +// +// Data Path : In --> Gearbox --> Filter --> Gearbox --> Out +// SPC : 2 1 2 4 +// Clock Rate : 1x 2x 2x 1x +// + +`default_nettype none + +module rf_up_2to4 #( + parameter NUM_CHANNELS = 1 +) ( + input wire clk, + input wire clk_2x, + + // Synchronous resets + input wire rst, + input wire rst_2x, + + // Input - 2 SPC, synchronous to "clk" + input wire [NUM_CHANNELS*64-1:0] i_tdata, + input wire [NUM_CHANNELS* 1-1:0] i_tvalid, + + // Output - 4 SPC, synchronous to "clk" + output wire [NUM_CHANNELS*128-1:0] o_tdata, + output wire [NUM_CHANNELS* 1-1:0] o_tvalid +); + + generate + genvar ch; + for (ch = 0; ch < NUM_CHANNELS; ch = ch + 1) begin : gen_channel + + //----------------------------------------------------------------------- + // Input Gearbox + //----------------------------------------------------------------------- + // + // Convert from 2 SPC on clk to 1 SPC on clk_2x. + // + //----------------------------------------------------------------------- + + wire [31:0] gear_to_filt_tdata; + wire gear_to_filt_tvalid; + + gearbox_2x1 #( + .WORD_W (32), + .IN_WORDS (2), + .OUT_WORDS (1), + .BIG_ENDIAN (0) + ) gearbox_2x1_in ( + .i_clk (clk), + .i_rst (rst), + .i_tdata (i_tdata[ch*64 +: 64]), + .i_tvalid (i_tvalid[ch]), + .o_clk (clk_2x), + .o_rst (rst_2x), + .o_tdata (gear_to_filt_tdata), + .o_tvalid (gear_to_filt_tvalid) + ); + + + //----------------------------------------------------------------------- + // Interpolating Filter + //----------------------------------------------------------------------- + + wire [95:0] filt_to_clip_tdata; + wire filt_to_clip_tvalid; + + hb47_1to2 hb47_1to2_i ( + .aresetn (~rst_2x), + .aclk (clk_2x), + .s_axis_data_tvalid (gear_to_filt_tvalid), + .s_axis_data_tready (), + .s_axis_data_tdata (gear_to_filt_tdata), + .m_axis_data_tvalid (filt_to_clip_tvalid), + .m_axis_data_tuser (), + .m_axis_data_tdata (filt_to_clip_tdata) + ); + + + //----------------------------------------------------------------------- + // Saturation + //----------------------------------------------------------------------- + + wire [63:0] clip_to_gear_tdata; + wire clip_to_gear_tvalid; + + genvar word; + for (word = 0; word < 4; word = word+1) begin : gen_sat + axi_clip #( + .WIDTH_IN (24), + .WIDTH_OUT (16), + .FIFOSIZE (0) + ) axi_clip_i ( + .clk (clk_2x), + .reset (rst_2x), + .i_tdata (filt_to_clip_tdata[word*24 +: 24]), + .i_tlast (1'b0), + .i_tvalid (filt_to_clip_tvalid), + .i_tready (), + .o_tdata (clip_to_gear_tdata[word*16 +: 16]), + .o_tlast (), + .o_tvalid (clip_to_gear_tvalid), + .o_tready (1'b1) + ); + end + + + //----------------------------------------------------------------------- + // Output Gearbox + //----------------------------------------------------------------------- + // + // Convert from 2 SPC on clk_2x to 4 SPC on clk. + // + //----------------------------------------------------------------------- + + gearbox_2x1 #( + .WORD_W (32), + .IN_WORDS (2), + .OUT_WORDS (4), + .BIG_ENDIAN (0) + ) gearbox_2x1_out ( + .i_clk (clk_2x), + .i_rst (rst_2x), + .i_tdata (clip_to_gear_tdata), + .i_tvalid (clip_to_gear_tvalid), + .o_clk (clk), + .o_rst (rst), + .o_tdata (o_tdata[ch*128 +: 128]), + .o_tvalid (o_tvalid[ch]) + ); + + end // for + endgenerate + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/400m/adc_gearbox_2x4.vhd b/fpga/usrp3/top/x400/rf/400m/adc_gearbox_2x4.vhd new file mode 100644 index 000000000..67f4eb448 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/adc_gearbox_2x4.vhd @@ -0,0 +1,142 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: adc_gearbox_2x4 +-- +-- Description: +-- +-- Gearbox to expand the data width from 2 SPC to 4 SPC. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity adc_gearbox_2x4 is + port( + Clk1x : in std_logic; + Clk3x : in std_logic; + -- Resets with synchronous de-assertion. + ac1Reset_n : in std_logic; + ac3Reset_n : in std_logic; + -- Data packing: [Q1,I1,Q0,I0] (I in LSBs). + c3DataIn : in std_logic_vector(95 downto 0); + c3DataValidIn : in std_logic; + -- Data packing: [Q3,I3,Q2,I2,Q1,I1,Q0,I0] (I in LSBs). + c1DataOut : out std_logic_vector(191 downto 0); + c1DataValidOut : out std_logic + ); +end adc_gearbox_2x4; + +architecture RTL of adc_gearbox_2x4 is + + signal c1DataValidInDly, c3DataValidInDly + : std_logic_vector(3 downto 0) := (others => '0'); + + subtype Word_t is std_logic_vector(95 downto 0); + type Words_t is array(natural range<>) of Word_t; + + signal c3DataInDly, c1DataInDly : Words_t(3 downto 0); + +begin + + -- Pipeline input data. We will need four pipeline stages to account for the + -- three possible Clk1x and Clk3x phases and the nature of data packing done + -- in the DDC filter. The DDC asserts data valid for two clock cycles and + -- de-asserted for one clock cycle. This requires us to have shift register + -- that is 4 sample words (each sample word is 2 SPC) deep. + InputValidPipeline: process(Clk3x, ac3Reset_n) + begin + if ac3Reset_n = '0' then + c3DataValidInDly <= (others => '0'); + -- These registers are on the falling edge to prevent a hold violation at + -- the input to the following Clk1x FF (which may arrive late when more + -- heavily loaded than Clk3x) + elsif falling_edge(Clk3x) then + c3DataValidInDly <= c3DataValidInDly(c3DataValidInDly'left-1 downto 0) & + c3DataValidIn; + end if; + end process; + + InputDataPipeline: process(Clk3x) + begin + -- These registers are on the falling edge to prevent a hold violation at + -- the input to the following Clk1x FF (which may arrive late when more + -- heavily loaded than Clk3x). + if falling_edge(Clk3x) then + c3DataInDly <= c3DataInDly(c3DataInDly'high-1 downto 0) & c3DataIn; + end if; + end process InputDataPipeline; + + -- Data valid clock crossing from Clk3x to Clk1x + Clk3xToClk1xValidCrossing: process(Clk1x, ac1Reset_n) + begin + if ac1Reset_n = '0' then + c1DataValidInDly <= (others => '0'); + elsif rising_edge(Clk1x) then + c1DataValidInDly <= c3DataValidInDly; + end if; + end process; + + -- Data clock crossing from Clk3x to Clk1x + Clk3xToClk1xDataCrossing: process(Clk1x) + begin + if rising_edge(Clk1x) then + c1DataInDly <= c3DataInDly; + end if; + end process; + + ----------------------------------------------------------------------------- + -- + -- p0 p1 p2 p0 + -- Clk3x _______/¯¯¯¯¯¯¯\_______/¯¯¯¯¯¯¯\_______/¯¯¯¯¯¯¯\_______/¯¯¯ + -- + -- Clk1x _______/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\_______________________/¯¯¯ + -- + -- c3DataValidIn _/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\_______________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- + -- This gearbox connect the DDC filter output to the remaining RX data path. + -- For efficient use of DSP slices we run the DDC at 3x clock rate. Both + -- Clk3x and Clk1x are sourced from the same PLL and is phase locked as shown + -- in the above timing diagram. The output of DDC filter is asserted for two + -- clock cycles and is de-asserted for one clock cycle. The remaining part of + -- the design cannot run at 3x clock rate. So, we increase the number of + -- samples per clock cycle and decrease the clock frequency to 1x. Depending + -- upon the pipeline delay through the filter and RF section, the phase of + -- data valid assertion could be on either p0, p1, or p2 edge. And depending + -- upon the phase, data packing to Clk1x domain will vary. Since there are + -- three possible phase, we will need three different data packing options. + -- + -- Data packing is done by looking for two consecutive ones in the data valid + -- shift register (c1DataValidInDly).This pattern can be used only because of + -- the way output data is packed in the filter. If we see two consecutive + -- ones, then we know that we have enough data to be packed for the output of + -- this gearbox. This is because, we need two Clk3x cycles of 2 SPC data to + -- pack a 4 SPC data output on Clk1x. The location of two consecutive ones in + -- the data valid shift register will provide the location of valid data in + -- data shift register (c1DataInDly). + DataPacker: process(Clk1x) + begin + if rising_edge(Clk1x) then + -- Data valid is asserted when both Clk1x and Clk3x are phase aligned + -- (p0). In this case, c1DataValidInDly will have consecutive ones in + -- index 1 and 2. + c1DataValidOut <= c1DataValidInDly(1) and c1DataValidInDly(2); + c1DataOut <= c1DataInDly(1) & c1DataInDly(2); + + -- Data valid asserted on phase p1. + if c1DataValidInDly(1 downto 0) = "11" then + c1DataOut <= c1DataInDly(0) & c1DataInDly(1); + c1DataValidOut <= '1'; + + -- Data valid asserted on phase p2. + elsif c1DataValidInDly(3 downto 2) = "11" then + c1DataOut <= c1DataInDly(2) & c1DataInDly(3); + c1DataValidOut <= '1'; + end if; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/400m/adc_gearbox_8x4.v b/fpga/usrp3/top/x400/rf/400m/adc_gearbox_8x4.v new file mode 100644 index 000000000..02e2684ad --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/adc_gearbox_8x4.v @@ -0,0 +1,105 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: adc_gearbox_8x4 +// +// Description: +// +// Gearbox ADC data from 8 SPC to 4 SPC and corresponding 2x clock to 1x +// clock. Also implement data swapping to format packets to fit the FIR +// filter input requirements. +// +// This modules incurs one clk1x cycle of delay on the data and valid signals +// from input on the 1x domain to output on the 2x domain. +// + +`default_nettype none + +module adc_gearbox_8x4 ( + input wire clk1x, + input wire reset_n_1x, + // Data is _presumed_ to be packed [Sample7, ..., Sample0] (Sample0 in LSBs). + input wire [127:0] adc_q_in_1x, + input wire [127:0] adc_i_in_1x, + input wire valid_in_1x, + // De-assert enable_1x to clear the data valid output synchronously. + input wire enable_1x, + + input wire clk2x, + // Data is packed [Q3,I3, ... , Q0, I0] (I in LSBs) when swap_iq_1x is '0' + input wire swap_iq_2x, + output wire [127:0] adc_out_2x, + output wire valid_out_2x +); + + // Re-create the 1x clock in the 2x domain to produce a deterministic + // crossing. + reg toggle_1x, toggle_2x = 1'b0, toggle_2x_dly = 1'b0, valid_2x = 1'b0, valid_dly_2x = 1'b0; + reg [127:0] data_out_2x = 128'b0, adc_q_data_in_2x = 128'b0, adc_i_data_in_2x = 128'b0; + + // Create a toggle in the 1x clock domain (clock divider /2). + always @(posedge clk1x or negedge reset_n_1x) begin + if ( ! reset_n_1x) begin + toggle_1x <= 1'b0; + end else begin + toggle_1x <= ! toggle_1x; + end + end + + // Transfer the toggle from the 1x to the 2x domain. Delay the toggle in the + // 2x domain by one cycle and compare it to the non-delayed version. When + // they differ, push data_in[63:0] onto the output. When the match, push + // [127:64] onto the output. The datasheet is unclear on the exact + // implementation. + // + // It is safe to not reset this domain because all of the input signals will + // be cleared by the 1x reset. Safe default values are assigned to all these + // registers. + always @(posedge clk2x) begin + toggle_2x <= toggle_1x; + toggle_2x_dly <= toggle_2x; + adc_q_data_in_2x <= adc_q_in_1x; + adc_i_data_in_2x <= adc_i_in_1x; + data_out_2x <= 128'b0; + // Place Q in the MSBs, I in the LSBs by default, unless swapped = 1. + if (valid_2x) begin + if (swap_iq_2x) begin + if (toggle_2x != toggle_2x_dly) begin + data_out_2x <= {adc_i_data_in_2x[63:48], adc_q_data_in_2x[63:48], + adc_i_data_in_2x[47:32], adc_q_data_in_2x[47:32], + adc_i_data_in_2x[31:16], adc_q_data_in_2x[31:16], + adc_i_data_in_2x[15: 0], adc_q_data_in_2x[15: 0]}; + end else begin + data_out_2x <= {adc_i_data_in_2x[127:112], adc_q_data_in_2x[127:112], + adc_i_data_in_2x[111: 96], adc_q_data_in_2x[111: 96], + adc_i_data_in_2x[95 : 80], adc_q_data_in_2x[95 : 80], + adc_i_data_in_2x[79 : 64], adc_q_data_in_2x[79 : 64]}; + end + end else begin + if (toggle_2x != toggle_2x_dly) begin + data_out_2x <= {adc_q_data_in_2x[63:48], adc_i_data_in_2x[63:48], + adc_q_data_in_2x[47:32], adc_i_data_in_2x[47:32], + adc_q_data_in_2x[31:16], adc_i_data_in_2x[31:16], + adc_q_data_in_2x[15: 0], adc_i_data_in_2x[15: 0]}; + end else begin + data_out_2x <= {adc_q_data_in_2x[127:112], adc_i_data_in_2x[127:112], + adc_q_data_in_2x[111: 96], adc_i_data_in_2x[111: 96], + adc_q_data_in_2x[95 : 80], adc_i_data_in_2x[95 : 80], + adc_q_data_in_2x[79 : 64], adc_i_data_in_2x[79 : 64]}; + end + end + end + // Valid is simply a transferred version of the 1x clock's valid. Delay it one + // more cycle to align outputs. + valid_2x <= valid_in_1x && enable_1x; + valid_dly_2x <= valid_2x; + end + + assign adc_out_2x = data_out_2x; + assign valid_out_2x = valid_dly_2x; + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/400m/dac_gearbox_12x8.vhd b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_12x8.vhd new file mode 100644 index 000000000..80f519aa6 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_12x8.vhd @@ -0,0 +1,233 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_gearbox_12x8 +-- +-- Description: +-- +-- Gearbox to expand the data width from 12 SPC to 8 SPC. +-- Input Clocks, all aligned to one another and coming from same MMCM. +-- PLL reference clock = 61.44 or 62.5 MHz. +-- RfClk: 184.32 or 187.5 MHz (3x PLL reference clock) +-- Clk1x: 122.88 or 125 MHz (2x PLL reference clock) +-- Clk2x: 245.76 or 250 MHz (4x PLL reference clock) +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity dac_gearbox_12x8 is + port( + Clk1x : in std_logic; + RfClk : in std_logic; + ac1Reset_n : in std_logic; + arReset_n : in std_logic; + -- Data packing: [Q11,I11,Q10,I10,...,Q3,I3,Q2,I2,Q1,I1,Q0,I0] (I in LSBs) + c1DataIn : in std_logic_vector(383 downto 0); + c1DataValidIn : in std_logic; + -- Data packing: [Q7,I7,Q6,I6,...,Q3,I3,Q2,I2,Q1,I1,Q0,I0] (I in LSBs) + rDataOut : out std_logic_vector(255 downto 0) := (others => '0'); + rReadyForOutput : in std_logic; + rDataValidOut : out std_logic + ); +end dac_gearbox_12x8; + +architecture RTL of dac_gearbox_12x8 is + + constant kDataWidth : natural := 16; + + constant kDataI0Lsb : natural := 0; + constant kDataI0Msb : natural := kDataWidth-1; + constant kDataQ0Lsb : natural := kDataI0Msb+1; + constant kDataQ0Msb : natural := kDataQ0Lsb+kDataWidth-1; + constant kDataI1Lsb : natural := kDataQ0Msb+1; + constant kDataI1Msb : natural := kDataI1Lsb+kDataWidth-1; + constant kDataQ1Lsb : natural := kDataI1Msb+1; + constant kDataQ1Msb : natural := kDataQ1Lsb+kDataWidth-1; + constant kDataI2Lsb : natural := kDataQ1Msb+1; + constant kDataI2Msb : natural := kDataI2Lsb+kDataWidth-1; + constant kDataQ2Lsb : natural := kDataI2Msb+1; + constant kDataQ2Msb : natural := kDataQ2Lsb+kDataWidth-1; + constant kDataI3Lsb : natural := kDataQ2Msb+1; + constant kDataI3Msb : natural := kDataI3Lsb+kDataWidth-1; + constant kDataQ3Lsb : natural := kDataI3Msb+1; + constant kDataQ3Msb : natural := kDataQ3Lsb+kDataWidth-1; + constant kDataI4Lsb : natural := kDataQ3Msb+1; + constant kDataI4Msb : natural := kDataI4Lsb+kDataWidth-1; + constant kDataQ4Lsb : natural := kDataI4Msb+1; + constant kDataQ4Msb : natural := kDataQ4Lsb+kDataWidth-1; + constant kDataI5Lsb : natural := kDataQ4Msb+1; + constant kDataI5Msb : natural := kDataI5Lsb+kDataWidth-1; + constant kDataQ5Lsb : natural := kDataI5Msb+1; + constant kDataQ5Msb : natural := kDataQ5Lsb+kDataWidth-1; + constant kDataI6Lsb : natural := kDataQ5Msb+1; + constant kDataI6Msb : natural := kDataI6Lsb+kDataWidth-1; + constant kDataQ6Lsb : natural := kDataI6Msb+1; + constant kDataQ6Msb : natural := kDataQ6Lsb+kDataWidth-1; + constant kDataI7Lsb : natural := kDataQ6Msb+1; + constant kDataI7Msb : natural := kDataI7Lsb+kDataWidth-1; + constant kDataQ7Lsb : natural := kDataI7Msb+1; + constant kDataQ7Msb : natural := kDataQ7Lsb+kDataWidth-1; + + subtype Word_t is std_logic_vector(383 downto 0); + type Words_t is array(natural range<>) of Word_t; + + signal rDataInDly : Words_t(3 downto 0); + + signal rDataValidDly : std_logic_vector(3 downto 0) := (others => '0'); + + signal c1PhaseCount, c1DataValidInDly : std_logic := '0'; + signal rPhaseShiftReg : std_logic_vector(2 downto 0); + +begin + + ----------------------------------------------------------------------------- + -- Data Packing 12 SPC to 8 SPC + ----------------------------------------------------------------------------- + + Clk1xDataCount: process(ac1Reset_n, Clk1x) + begin + if ac1Reset_n = '0' then + c1PhaseCount <= '0'; + c1DataValidInDly <= '0'; + elsif rising_edge(Clk1x) then + c1DataValidInDly <= c1DataValidIn; + c1PhaseCount <= (not c1PhaseCount) and (c1DataValidIn or c1DataValidInDly); + end if; + end process; + + DataClkCrossing: process(RfClk) + begin + if rising_edge(RfClk) then + rDataInDly <= rDataInDly(rDataInDly'high-1 downto 0) & c1DataIn; + end if; + end process; + + -- Store clock phase information in a shift register. The shift register + -- is a 3 bit register and it used in output data packer. + PhaseClkCrossing: process(arReset_n,RfClk) + begin + if arReset_n = '0' then + rPhaseShiftReg <= (others => '0'); + elsif rising_edge(RfClk) then + rPhaseShiftReg(2 downto 1) <= rPhaseShiftReg(1 downto 0); + rPhaseShiftReg(0) <= c1PhaseCount; + end if; + end process; + + ----------------------------------------------------------------------------- + -- + -- Timing diagram: Data valid is asserted when both clock are edge aligned. + -- + -- | | | + -- v <-Clocks edge aligned v v + -- Clk1x ¯¯\____/¯¯¯¯¯\_____/¯¯¯¯¯\_____/¯¯¯¯¯\_____/¯¯¯¯¯\_____/¯¯¯¯¯\___ + -- | + -- v <- O/p data valid assertion + -- RfClk ¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯ + -- | | | + -- c1DataValid _/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- | | | + -- c1DValidDly _________/¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- | | | + -- c1PhaseCount _______/¯¯¯¯¯¯¯¯¯¯¯¯\|_______|__/¯¯¯¯|¯¯¯¯¯¯¯\__________/¯¯ + -- | | | + -- v <- rPhaseSR= "001" + -- rPhaseSR(0) ________________/¯¯¯¯¯¯¯¯\_____|_______|_/¯¯¯¯¯¯¯\_________________ + -- | | + -- v <- rPhaseSR= "010" + -- rPhaseSR(1) _________________________/¯¯¯¯¯¯¯¯\____|__________/¯¯¯¯¯¯¯\____________ + -- | + -- v <- rPhaseSR= "100" + -- rPhaseSR(2) __________________________________/¯¯¯¯¯¯¯¯\_______________/¯¯¯¯¯¯¯\___ + -- + -- rDValidDly0 _________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- + -- rDValidDly1 _________________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- + -- rDValidDly2 __________________________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- + -- In this design use a single bit counter on the input clock (Clk1x) domain + -- and pass it to the RfClk domain. When data valid is asserted when both + -- clocks are rising edge aligned, only one bit in rPhaseSR high, the + -- remaining bits are zero. We use the position of the bit counter in the + -- shift register to do data packing. + -- + -- + -- Timing diagram: When data valid is asserted when both clock are NOT edge + -- aligned. + -- + -- | | | + -- v <-Clocks edge aligned v v + -- Clk1x ¯¯\____/¯¯¯¯¯\_____/¯¯¯¯¯\_____/¯¯¯¯¯\_____/¯¯¯¯¯\_____/¯¯¯¯¯\___ + -- | + -- v <- O/p data valid assertion + -- RfClk ¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯¯¯\___/¯ + -- | | | | + -- c1DataValid ________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- | | | | + -- c1DValidDly ___________________/¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- | | | | + -- c1PhaseCount ___________________/¯¯¯¯¯¯¯¯¯¯|¯\_____|_____/¯|¯¯¯¯¯¯¯|¯¯\__________/¯¯ + -- | | | | + -- v <- rPhaseSR= "001" | + -- rPhaseSR(0) ________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯\_____|_/¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯ + -- | | | + -- v <- rPhaseSR= "011" + -- rPhaseSR(1) ________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯\_____|_/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- | | + -- v <- rPhaseSR= "110" + -- rPhaseSR(2) ________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\_______/¯¯¯¯¯¯¯¯¯¯ + -- ^ + -- | <- rPhaseSR= "101" + -- + -- rDValidDly0 _________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- + -- rDValidDly1 _________________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ + -- + -- The above timing diagram is when input data valid is asserted when both + -- clocks rising edges are not aligned. In this case the more than one bit in + -- rPhaseSR is asserted which is unique to this case. As mentioned in the + -- above case, we use rPhaseSR value to determine data packing. + + -- Output Data Packer + DataOut: process(RfClk) + begin + if rising_edge(RfClk) then + -- rPhaseShiftReg = "011" + rDataOut <= rDataInDly(2)(kDataQ7Msb downto kDataI0Lsb); + if rPhaseShiftReg = "110" or rPhaseShiftReg = "100" then + rDataOut <= rDataInDly(2)(kDataQ3Msb downto kDataI0Lsb) & + rDataInDly(3)(c1DataIn'length-1 downto kDataQ7Msb+1); + elsif rPhaseShiftReg = "101" or rPhaseShiftReg = "001" then + rDataOut <= rDataInDly(3)(c1DataIn'length-1 downto kDataI4Lsb); + elsif rPhaseShiftReg = "010" then + rDataOut <= rDataInDly(3)(kDataQ7Msb downto kDataI0Lsb); + end if; + end if; + end process; + + DataValidOut: process(RfClk, arReset_n) + begin + if arReset_n = '0' then + rDataValidDly <= (others => '0'); + rDataValidOut <= '0'; + elsif rising_edge(RfClk) then + rDataValidDly <= rDataValidDly(rDataValidDly'left-1 downto 0) & + c1DataValidIn; + + -- Data valid out asserting based on phase alignment RfClk and Clk1x. + -- When RfClk and Clk1x are not phase aligned. + rDataValidOut <= rDataValidDly(2) and rReadyForOutput; + + -- When RfClk and Clk1x are phase aligned. + if (rPhaseShiftReg(2) xor rPhaseShiftReg(1) xor rPhaseShiftReg(0)) = '1' then + rDataValidOut <= rDataValidDly(2) and rDataValidDly(3) and rReadyForOutput; + end if; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/400m/dac_gearbox_4x2.v b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_4x2.v new file mode 100644 index 000000000..43fb39ec4 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_4x2.v @@ -0,0 +1,80 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: dac_gearbox_4x2 +// +// Description: +// +// Gearbox DAC data from 4 SPC to 2 SPC and corresponding 2x clock to 1x +// clock. +// This module incurs in one clk1x cycle of delay on the data and valid +// signals from input on the 1x domain to output on the 2x domain. +// + +`default_nettype none + +module dac_gearbox_4x2 ( + input wire clk1x, + input wire reset_n_1x, + // Data is _presumed_ to be packed [Q3,I3,Q2,I2,Q1,I1,Q0,I0] + input wire [127:0] data_in_1x, + input wire valid_in_1x, + output wire ready_out_1x, + + input wire clk2x, + // Data is packed [Q1,I1,Q0,I0] (I in LSBs) + output wire [ 63:0] data_out_2x, + output wire valid_out_2x +); + + // Re-create the 1x clock in the 2x domain to produce a deterministic + // crossing. + reg toggle_1x, toggle_2x = 1'b0, toggle_2x_dly = 1'b0, valid_2x = 1'b0, valid_dly_2x = 1'b0; + reg [127:0] data_in_2x_dly0 = 128'b0, data_in_2x_dly1 = 32'b0; + reg [63 :0] data_2x_dly = 64'b0; + + // Create a toggle in the 1x clock domain (clock divider /2). + always @(posedge clk1x or negedge reset_n_1x) begin + if ( ! reset_n_1x) begin + toggle_1x <= 1'b0; + end else begin + toggle_1x <= ! toggle_1x; + end + end + + // Transfer the toggle from the 1x to the 2x domain. Delay the toggle in the + // 2x domain by one cycle and compare it to the non-delayed version. When + // they differ, push data_in[63:0] onto the output. When they match, push + // [127:64] onto the output. + // + // It is safe to not reset this domain because all of the input signals will + // be cleared by the 1x reset. Safe default values are assigned to all these + // registers. + always @(posedge clk2x) begin + toggle_2x <= toggle_1x; + toggle_2x_dly <= toggle_2x; + data_in_2x_dly0 <= data_in_1x; + data_in_2x_dly1 <= data_in_2x_dly0 ; + data_2x_dly <= 64'b0; + + if (valid_2x) begin + data_2x_dly <= data_in_2x_dly1[127:64]; + if (toggle_2x != toggle_2x_dly) begin + data_2x_dly <= data_in_2x_dly0[63:0]; + end + end + // Valid is simply a transferred version of the 1x clock's valid. Delay it + // one more cycle to align outputs. + valid_2x <= valid_in_1x; + valid_dly_2x <= valid_2x; + end + + assign valid_out_2x = valid_dly_2x; + assign data_out_2x = data_2x_dly; + assign ready_out_1x = 1'b1; + +endmodule + +`default_nettype wire diff --git a/fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x12.vhd b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x12.vhd new file mode 100644 index 000000000..878720c8d --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x12.vhd @@ -0,0 +1,124 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_gearbox_6x12 +-- +-- Description: +-- +-- Gearbox to expand the data width from 6 SPC to 12 SPC. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity dac_gearbox_6x12 is + port( + Clk1x : in std_logic; + Clk2x : in std_logic; + ac1Reset_n : in std_logic; + ac2Reset_n : in std_logic; + -- 16 bit data packing: [Q5,I5,Q4,I4,Q3,I3,Q2,I2,Q1,I1,Q0,I0] (I in LSBs) + c2DataIn : in std_logic_vector(191 downto 0); + c2DataValidIn : in std_logic; + -- 16 bit data packing: [Q11,I11,Q10,I10,..,Q2,I2,Q1,I1,Q0,I0] (I in LSBs) + c1DataOut : out std_logic_vector(383 downto 0) := (others => '0'); + c1DataValidOut : out std_logic := '0' + ); +end dac_gearbox_6x12; + +architecture RTL of dac_gearbox_6x12 is + + subtype Word_t is std_logic_vector(191 downto 0); + type Words_t is array(natural range<>) of Word_t; + + signal c1DataInDly, c2DataInDly : Words_t(2 downto 0); + + signal c2DataValidInDly : std_logic_vector(1 downto 0) := (others => '0'); + signal c1PhaseCount, c2PhaseCount : std_logic := '0'; + signal c1DataValidIn, c1DataValidDly0 : std_logic := '0'; + +begin + + -- Input data pipeline. + InputValidPipeline: process(Clk2x, ac2Reset_n) + begin + if ac2Reset_n = '0' then + c2DataValidInDly <= (others => '0'); + elsif rising_edge(Clk2x) then + c2DataValidInDly <= c2DataValidInDly(c2DataValidInDly'left-1 downto 0) & + c2DataValidIn; + end if; + end process; + + InputDataPipeline: process(Clk2x) + begin + if rising_edge(Clk2x) then + c2DataInDly <= c2DataInDly(c2DataInDly'high-1 downto 0) & c2DataIn; + end if; + end process; + + -- Process to determine if data valid was asserted when both clocks were + -- in-phase. Since we are crossing a 2x clock domain to a 1x clock domain, + -- there are only two possible phase. One is data valid assertion when both + -- clocks rising edges are aligned. The other case is data valid assertion + -- when Clk2x is aligned to the falling edge. + Clock2xPhaseCount: process(ac2Reset_n, Clk2x) + begin + if ac2Reset_n = '0' then + c2PhaseCount <= '0'; + elsif rising_edge(Clk2x) then + -- This is a single bit counter. This counter is enabled for an extra + -- clock cycle to account for the output pipeline delay. + c2PhaseCount <= (not c2PhaseCount) and + (c2DataValidInDly(1) or c2DataValidInDly(0)); + end if; + end process; + + -- Crossing clock from Clk2x to Clk1x. + Clk2xToClk1xCrossing: process(Clk1x) + begin + if rising_edge(Clk1x) then + c1DataInDly <= c2DataInDly; + c1PhaseCount <= c2PhaseCount; + c1DataValidIn <= c2DataValidInDly(0); + end if; + end process; + + -- Output data packing is determined based on when input data valid was + -- asserted. c1PhaseCount is '1' when input data valid was asserted when both + -- clocks are rising edge aligned. In this case, we can send data from the + -- with 1 and 2 pipeline delays. + -- When data valid is asserted when the two clock are not rising edge + -- aligned, we will use data from 2 and 3 pipeline delays. + DataOut: process(Clk1x) + begin + if rising_edge(Clk1x) then + c1DataOut <= c1DataInDly(1) & c1DataInDly(2); + if c1PhaseCount = '1' then + c1DataOut <= c1DataInDly(0) & c1DataInDly(1); + end if; + end if; + end process; + + -- Similar to data output, when input data valid is asserted and both clocks + -- are rising edge aligned, the output data valid is asserted with a single + -- pipeline stage. If not, output data valid is asserted with two pipeline + -- stages. + DataValidOut: process(Clk1x, ac1Reset_n) + begin + if ac1Reset_n = '0' then + c1DataValidDly0 <= '0'; + c1DataValidOut <= '0'; + elsif rising_edge(Clk1x) then + c1DataValidDly0 <= c1DataValidIn; + c1DataValidOut <= c1DataValidDly0; + if c1PhaseCount = '1' then + c1DataValidOut <= c1DataValidIn; + end if; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x8.vhd b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x8.vhd new file mode 100644 index 000000000..c673cd732 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x8.vhd @@ -0,0 +1,99 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: dac_gearbox_6x8 +-- +-- Description: +-- +-- Gearbox to expand the data width from 6 SPC to 8 SPC. +-- Input Clocks, all aligned to one another and coming from same MMCM +-- PLL reference clock = 61.44 or 62.5 MHz. +-- RfClk: 184.32 or 187.5 MHz (3x PLL reference clock) +-- Clk1x: 122.88 or 125 MHz (2x PLL reference clock) +-- Clk2x: 245.76 or 250 MHz (4x PLL reference clock) +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity dac_gearbox_6x8 is + port( + Clk1x : in std_logic; + Clk2x : in std_logic; + RfClk : in std_logic; + ac1Reset_n : in std_logic; + ac2Reset_n : in std_logic; + arReset_n : in std_logic; + -- 16 bit data packing: [Q5,I5,Q4,I4,Q3,I3,Q2,I2,Q1,I1,Q0,I0] (I in LSBs) + c2DataIn : in std_logic_vector(191 downto 0); + c2DataValidIn : in std_logic; + -- 16 bit data packing: [Q7,I7,Q6,I6,..,Q2,I2,Q1,I1,Q0,I0] (I in LSBs) + rDataOut : out std_logic_vector(255 downto 0) := (others => '0'); + rReadyForOutput : in std_logic; + rDataValidOut : out std_logic := '0' + ); +end dac_gearbox_6x8; + +architecture struct of dac_gearbox_6x8 is + + signal c1DataOut : std_logic_vector(383 downto 0); + signal c1DataValidOut : std_logic; + +begin + + -- Clk1x, Clk2x, and RfClk are source from the same PLL and have a known + -- phase relationship between power cycles. Since, they have known phase + -- relationship, clock crossing as be done without a dual clock FIFO or any + -- other handshaking mechanism. We cannot move data from Clk2x to RfClk + -- because of the clock relation between these two clocks will make it almost + -- impossible to close timing. So, we move data from Clk2x to Clk1x and then + -- to RfClk domain. Since, we need deterministic delay in the data path, we + -- cannot use a FIFO to do data crossing. + -- + -- Clk1x = Sample clock/24 + -- Clk2x = Sample clock/12 + -- RfClk = Sample clock/16 + -- + -- Clk1x __/-----\_____/-----\_____/-----\_____/-----\_____/-----\___ + -- | | + -- Clk2x __/--\__/--\__/--\__/--\__/--\ | | + -- | | | | + -- | | <- Setup relationship | | <- Setup relationship + -- | | | | + -- RfClk __/---\___/---\___/---\___/---\___/---\___/---\___/---\___/- + -- + -- As you can see the setup relationship for passing data synchronously from + -- Clk2x to RfClk is very small (Sample clock period * 4). It is not possible + -- to close timing with this requirement. For passing data from Clk1x to + -- RfClk the setup relationship is (Sample clock period * 8) which is + -- relatively easy to close timing. + + dac_gearbox_6x12_i: entity work.dac_gearbox_6x12 (RTL) + port map ( + Clk1x => Clk1x, + Clk2x => Clk2x, + ac1Reset_n => ac1Reset_n, + ac2Reset_n => ac2Reset_n, + c2DataIn => c2DataIn, + c2DataValidIn => c2DataValidIn, + c1DataOut => c1DataOut, + c1DataValidOut => c1DataValidOut + ); + + dac_gearbox_12x8_i: entity work.dac_gearbox_12x8 (RTL) + port map ( + Clk1x => Clk1x, + RfClk => RfClk, + ac1Reset_n => ac1Reset_n, + arReset_n => arReset_n, + c1DataIn => c1DataOut, + c1DataValidIn => c1DataValidOut, + rDataOut => rDataOut, + rReadyForOutput => rReadyForOutput, + rDataValidOut => rDataValidOut + ); + +end struct; diff --git a/fpga/usrp3/top/x400/rf/400m/ddc_400m_saturate.vhd b/fpga/usrp3/top/x400/rf/400m/ddc_400m_saturate.vhd new file mode 100644 index 000000000..76104bdb4 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/ddc_400m_saturate.vhd @@ -0,0 +1,81 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: ddc_400m_saturate +-- +-- Description: +-- +-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See +-- comments below for full description. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +library work; + use work.PkgRf.all; + +entity ddc_400m_saturate is + port( + Clk : in std_logic; + -- This data is from the DDC with a sample width of 17 bits and 7 bits of + -- padding. Data format is, [Q3,I3, ... , Q0,I0] (I in LSBs) + cDataIn : in std_logic_vector(191 downto 0); + cDataValidIn : in std_logic; + -- 16 bits saturated data. Data format is [Q3,I3, ... , Q0,I0] (I in LSBs) + cDataOut : out std_logic_vector(127 downto 0); + cDataValidOut : out std_logic ); +end ddc_400m_saturate; + +architecture RTL of ddc_400m_saturate is + + signal cDataOutSamples : Samples16_t(7 downto 0) := (others => (others => '0')); + signal cDataInSamples : Samples17_t(cDataOutSamples'range); + +begin + + ----------------------------------------------------------------------------- + -- Saturation + -- + -- The output of the Xilinx FIR Compiler has already been rounded on the LSB + -- side, but hasn't been saturated on the MSB side. + -- Coefficients = 18 bit, 1 integer bit (1.17) + -- Data In = 16 bits, 1 integer bit (1.15) + -- 1.17 * 1.15 = 2.32, and the Xilinx FIR core rounds to 2.15 + -- Data Out = 17 bits, 2 integer bits (2.15), with 17 LSBs already rounded + -- off inside the FIR core. + -- We need to manually saturate the 2.15 number back to a 1.15 number + -- + -- If 2 MSBs = 00, output <= input without MSB, e.g. positive number < 1 + -- If 2 MSBs = 01, output <= 0.111111111111111, e.g. positive number >= 1 + -- If 2 MSBs = 10, output <= 1.000000000000000, e.g. negative number < -1 + -- If 2 MSBs = 11, output <= input without MSB, e.g. negative number >= -1 + ----------------------------------------------------------------------------- + + -- Logic to saturate input data to 16-bit signed value. Information on DDC + -- data packer is in PkgRf.vhd. + cDataInSamples <= to_Samples17(cDataIn); + GenSat: for i in cDataOutSamples'range generate + Saturation: + process(Clk) + begin + if rising_edge(Clk) then + cDataOutSamples(i) <= Saturate(cDataInSamples(i)); + end if; + end process; + end generate GenSat; + + DValidPipeline: process(Clk) + begin + if rising_edge(Clk) then + -- Pipeline data valid to match the data. + cDataValidOut <= cDataValidIn; + end if; + end process; + + cDataOut <= to_stdlogicvector(cDataOutSamples); + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/400m/duc_400m_saturate.vhd b/fpga/usrp3/top/x400/rf/400m/duc_400m_saturate.vhd new file mode 100644 index 000000000..f2c1072bd --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/duc_400m_saturate.vhd @@ -0,0 +1,86 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: duc_400m_saturate +-- +-- Description: +-- +-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See +-- comments below for full description. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +library work; + use work.PkgRf.all; + +entity duc_400m_saturate is + port( + Clk : in std_logic; + -- This data is from the DDC with a sample width of 18 bits and 6 bits of + -- padding. Data format is, [Q5,I5, ... , Q0,I0] (I in LSBs) + cDataIn : in std_logic_vector(287 downto 0); + cDataValidIn : in std_logic; + cReadyForInput : out std_logic; + -- 16 bits saturated data. Data format is [Q5,I5, ... , Q0,I0] (I in LSBs) + cDataOut : out std_logic_vector(191 downto 0); + cDataValidOut : out std_logic := '0'); +end duc_400m_saturate; + +architecture RTL of duc_400m_saturate is + + signal cDataOutSamples : Samples16_t(11 downto 0) := (others => (others => '0')); + signal cDataInputSamples : Samples18_t(cDataOutSamples'range); + +begin + + ----------------------------------------------------------------------------- + -- Saturation + -- + -- The output of the Xilinx FIR Compiler has already been rounded on the LSB + -- side, but hasn't been saturated on the MSB side. + -- Coefficients = 18 bit, 1 integer bit (1.17) + -- Data In = 16 bits, 1 integer bit (1.15) + -- Xilinx FIR core rounds to output to 3.31. The filter coefficients has a + -- gain of 3 to compensate for the amplitude loss in interpolation, the + -- Xilinx FIR core rounds the output to 3.15. + -- Data Out = 18 bits, 3 integer bits (3.15), with 16 LSBs already rounded + -- off inside the FIR core. + -- We need to manually saturate the 3.15 number back to a 1.15 number + -- + -- If 3 MSBs = 000, output <= input without MSB, e.g. positive number < 1 + -- If 3 MSBs = 0x1/01x, output <= 0.111111111111111, e.g. positive number >= 1 + -- If 3 MSBs = 1x0/10x, output <= 1.000000000000000, e.g. negative number < -1 + -- If 3 MSBs = 111, output <= input without MSB, e.g. negative number >= -1 + ----------------------------------------------------------------------------- + + -- Logic to saturate input data to 16-bit signed value. Information on DUC data packer is in + -- PkgRf.vhd. + cDataInputSamples <= to_Samples18(cDataIn); + GenSat: for i in cDataOutSamples'range generate + Saturation: + process(Clk) + begin + if rising_edge(Clk) then + cDataOutSamples(i) <= Saturate(cDataInputSamples(i)); + end if; + end process; + end generate GenSat; + + DValidPipeline: process(Clk) + begin + if rising_edge(Clk) then + -- Pipeline data valid to match the data. + cDataValidOut <= cDataValidIn; + end if; + end process; + + cDataOut <= to_stdlogicvector(cDataOutSamples); + + cReadyForInput <= '1'; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/400m/rf_core_400m.v b/fpga/usrp3/top/x400/rf/400m/rf_core_400m.v new file mode 100644 index 000000000..ef4556b35 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/400m/rf_core_400m.v @@ -0,0 +1,387 @@ +//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: rf_core_400m
+//
+// Description:
+//
+// Top-level wrapper for the ADC/DAC processing logic. One of these wrappers
+// exists for every supported Data Rate. An instance of this core should
+// exist per dboard.
+//
+// Data/RF Specs:
+// DBs: 1
+// RX/DB: 2
+// TX/DB: 2
+// Data Rate: 122.88 or 125 MSps @ 4 SPC
+//
+// Input Clocks, all aligned to one another and coming from same MMCM
+// rfdc_clk: 184.32 or 187.5 MHz (3x pll_ref_clk)
+// rfdc_clk_2x: 368.64 or 375 MHz (6x pll_ref_clk)
+// data_clk: 122.88 or 125 MHz (2x pll_ref_clk)
+// data_clk_2x: 245.76 or 250 MHz (4x pll_ref_clk)
+//
+
+`default_nettype none
+
+module rf_core_400m (
+
+ //---------------------------------------------------------------------------
+ // Clocking
+ //---------------------------------------------------------------------------
+
+ // Main Clock Inputs
+ input wire rfdc_clk,
+ input wire rfdc_clk_2x,
+ input wire data_clk,
+ input wire data_clk_2x,
+
+ // AXI4-Lite Config Clock
+ // This clock is used to synchronize status bits for the RFDC
+ // registers in the AXI-S clock domain.
+ input wire s_axi_config_clk,
+
+
+ //---------------------------------------------------------------------------
+ // RFDC Data Interfaces
+ //---------------------------------------------------------------------------
+ // All ports here are in the rfdc_clk domain.
+
+ // ADC
+ input wire [127:0] adc_data_in_i_tdata_0,
+ output wire adc_data_in_i_tready_0,
+ input wire adc_data_in_i_tvalid_0,
+ input wire [127:0] adc_data_in_q_tdata_0,
+ output wire adc_data_in_q_tready_0,
+ input wire adc_data_in_q_tvalid_0,
+ input wire [127:0] adc_data_in_i_tdata_1,
+ output wire adc_data_in_i_tready_1,
+ input wire adc_data_in_i_tvalid_1,
+ input wire [127:0] adc_data_in_q_tdata_1,
+ output wire adc_data_in_q_tready_1,
+ input wire adc_data_in_q_tvalid_1,
+
+ // DAC
+ output wire [255:0] dac_data_out_tdata_0,
+ input wire dac_data_out_tready_0,
+ output wire dac_data_out_tvalid_0,
+ output wire [255:0] dac_data_out_tdata_1,
+ input wire dac_data_out_tready_1,
+ output wire dac_data_out_tvalid_1,
+
+
+ //---------------------------------------------------------------------------
+ // User Data Interfaces
+ //---------------------------------------------------------------------------
+ // All ports here are in the data_clk domain.
+
+ // ADC
+ output wire [127:0] adc_data_out_tdata_0, // Packed [Q3,I3, ... , Q0,I0] with Q in MSBs
+ output wire adc_data_out_tvalid_0,
+ output wire [127:0] adc_data_out_tdata_1, // Packed [Q3,I3, ... , Q0,I0] with Q in MSBs
+ output wire adc_data_out_tvalid_1,
+
+ // DAC
+ input wire [127:0] dac_data_in_tdata_0, // Packed [Q3,I3, ... , Q0,I0] with Q in MSBs
+ output wire dac_data_in_tready_0,
+ input wire dac_data_in_tvalid_0,
+ input wire [127:0] dac_data_in_tdata_1, // Packed [Q3,I3, ... , Q0,I0] with Q in MSBs
+ output wire dac_data_in_tready_1,
+ input wire dac_data_in_tvalid_1,
+
+ //---------------------------------------------------------------------------
+ // Miscellaneous
+ //---------------------------------------------------------------------------
+
+ // Invert I/Q control signals from RFDC to DSP chain.
+ input wire [3:0] invert_adc_iq_rclk2,
+ input wire [3:0] invert_dac_iq_rclk2,
+
+ // Control/status vectors from/to RFDC.
+ // Notice these are all in the s_axi_config_clk domain.
+ output wire [15:0] dsp_info_sclk,
+ output wire [15:0] axi_status_sclk,
+
+ // Resets.
+ input wire adc_data_out_resetn_dclk,
+ input wire adc_enable_data_rclk,
+ input wire adc_rfdc_axi_resetn_rclk,
+ input wire dac_data_in_resetn_dclk,
+ input wire dac_data_in_resetn_dclk2x,
+ input wire dac_data_in_resetn_rclk,
+ input wire fir_resetn_rclk2x,
+
+ // Version (Constant)
+ output wire [95:0] version_info
+);
+
+ `include "../../regmap/rfdc_regs_regmap_utils.vh"
+ `include "../../regmap/versioning_regs_regmap_utils.vh"
+ `include "../../regmap/versioning_utils.vh"
+
+ // Fixed for this implementation
+ localparam NUM_ADC_CHANNELS = 2;
+ localparam NUM_DAC_CHANNELS = 2;
+
+ // ADC data interface from RFDC.
+ wire [127:0] adc_data_in_i_tdata [0:7]; // 8 SPC (I)
+ wire [127:0] adc_data_in_q_tdata [0:7]; // 8 SPC (Q)
+ wire [ 7:0] adc_data_in_i_tready;
+ wire [ 7:0] adc_data_in_q_tready;
+ wire [ 7:0] adc_data_in_i_tvalid;
+ wire [ 7:0] adc_data_in_q_tvalid;
+ // DAC data interface to RFDC.
+ wire [255:0] dac_data_out_tdata [0:7]; // 8 SPC (I + Q)
+ wire [ 7:0] dac_data_out_tready;
+ wire [ 7:0] dac_data_out_tvalid;
+
+ // ADC data interface to user.
+ wire [127:0] adc_data_out_tdata [0:7]; // 4 SPC (I + Q)
+ wire [ 7:0] adc_data_out_tready;
+ wire [ 7:0] adc_data_out_tvalid;
+ // DAC data interface from user.
+ wire [127:0] dac_data_in_tdata_preswap [0:7]; // 4 SPC (I + Q)
+ wire [127:0] dac_data_in_tdata [0:7]; // 4 SPC (I + Q)
+ wire [ 7:0] dac_data_in_tready;
+ wire [ 7:0] dac_data_in_tvalid;
+
+ wire [ 7:0] invert_dac_iq_dclk;
+ wire [15:0] axi_status;
+
+
+ //---------------------------------------------------------------------------
+ // Resets, Debug and Misc.
+ //---------------------------------------------------------------------------
+
+ // Group all these status bits together. They don't toggle frequently so data
+ // coherency is not an issue here.
+ // Using constants for DB0 since the bits are the 16 LSBs in a 32-bit vector.
+ // DB1 simply uses the 16 MSBs when wiring the status vector.
+ assign axi_status[USER_ADC_TREADY_MSB :USER_ADC_TREADY ] = adc_data_out_tready[1:0];
+ assign axi_status[USER_ADC_TVALID_MSB :USER_ADC_TVALID ] = adc_data_out_tvalid[1:0];
+ assign axi_status[RFDC_ADC_I_TVALID_MSB:RFDC_ADC_I_TVALID] = adc_data_in_i_tvalid[1:0];
+ assign axi_status[RFDC_ADC_Q_TVALID_MSB:RFDC_ADC_Q_TVALID] = adc_data_in_q_tvalid[1:0];
+ assign axi_status[RFDC_ADC_I_TREADY_MSB:RFDC_ADC_I_TREADY] = adc_data_in_i_tready[1:0];
+ assign axi_status[RFDC_ADC_Q_TREADY_MSB:RFDC_ADC_Q_TREADY] = adc_data_in_q_tready[1:0];
+ assign axi_status[RFDC_DAC_TVALID_MSB :RFDC_DAC_TVALID ] = dac_data_out_tvalid[1:0];
+ assign axi_status[RFDC_DAC_TREADY_MSB :RFDC_DAC_TREADY ] = dac_data_out_tready[1:0];
+
+ synchronizer #(
+ .WIDTH (16),
+ .STAGES (2),
+ .INITIAL_VAL (0),
+ .FALSE_PATH_TO_IN (1)
+ ) synchronizer_axis_status (
+ .clk (s_axi_config_clk),
+ .rst (1'b0),
+ .in (axi_status),
+ .out (axi_status_sclk)
+ );
+
+ // Drive the DSP info vector with information on this specific DSP chain.
+ assign dsp_info_sclk[FABRIC_DSP_BW_MSB :FABRIC_DSP_BW] = FABRIC_DSP_BW_400M;
+ assign dsp_info_sclk[FABRIC_DSP_RX_CNT_MSB:FABRIC_DSP_RX_CNT] = NUM_ADC_CHANNELS;
+ assign dsp_info_sclk[FABRIC_DSP_TX_CNT_MSB:FABRIC_DSP_TX_CNT] = NUM_DAC_CHANNELS;
+
+ //---------------------------------------------------------------------------
+ // ADC Post-Processing
+ //---------------------------------------------------------------------------
+
+ // Data comes from the RFDC as 8 SPC, separate streams for each channel and
+ // I/Q.
+ assign adc_data_in_i_tdata[0] = adc_data_in_i_tdata_0;
+ assign adc_data_in_q_tdata[0] = adc_data_in_q_tdata_0;
+ assign adc_data_in_i_tdata[1] = adc_data_in_i_tdata_1;
+ assign adc_data_in_q_tdata[1] = adc_data_in_q_tdata_1;
+
+ assign adc_data_in_i_tready_0 = adc_data_in_i_tready[0];
+ assign adc_data_in_i_tvalid[0] = adc_data_in_i_tvalid_0;
+ assign adc_data_in_q_tready_0 = adc_data_in_q_tready[0];
+ assign adc_data_in_q_tvalid[0] = adc_data_in_q_tvalid_0;
+ assign adc_data_in_i_tready_1 = adc_data_in_i_tready[1];
+ assign adc_data_in_i_tvalid[1] = adc_data_in_i_tvalid_1;
+ assign adc_data_in_q_tready_1 = adc_data_in_q_tready[1];
+ assign adc_data_in_q_tvalid[1] = adc_data_in_q_tvalid_1;
+
+ // ADC Data from the RFDC arrives here as 8 SPC with separate I and Q
+ // streams. It leaves the adc_100m_bd as 4 SPC with I and Q packed into a
+ // single 128 bit word.
+ genvar adc_num;
+ generate
+ for (adc_num=0; adc_num < (NUM_ADC_CHANNELS); adc_num = adc_num + 1)
+ begin : adc_gen
+ adc_400m_bd adc_400m_bd_gen (
+ .adc_data_out_resetn_dclk (adc_data_out_resetn_dclk),
+ .data_clk (data_clk),
+ .enable_data_to_fir_rclk (adc_enable_data_rclk),
+ .fir_resetn_rclk2x (fir_resetn_rclk2x),
+ .rfdc_adc_axi_resetn_rclk (adc_rfdc_axi_resetn_rclk),
+ .rfdc_clk (rfdc_clk),
+ .rfdc_clk_2x (rfdc_clk_2x),
+ .swap_iq_2x (invert_adc_iq_rclk2 [adc_num]),
+ .adc_q_data_in_tvalid (adc_data_in_q_tvalid[adc_num]),
+ .adc_q_data_in_tready (adc_data_in_q_tready[adc_num]),
+ .adc_q_data_in_tdata (adc_data_in_q_tdata [adc_num]),
+ .adc_i_data_in_tvalid (adc_data_in_i_tvalid[adc_num]),
+ .adc_i_data_in_tready (adc_data_in_i_tready[adc_num]),
+ .adc_i_data_in_tdata (adc_data_in_i_tdata [adc_num]),
+ .adc_data_out_tvalid (adc_data_out_tvalid [adc_num]),
+ .adc_data_out_tdata (adc_data_out_tdata [adc_num])
+ );
+ end
+ endgenerate
+
+ // Data is released to the user as 4 SPC, separate streams for each channel.
+ assign adc_data_out_tdata_0 = adc_data_out_tdata[0];
+ assign adc_data_out_tdata_1 = adc_data_out_tdata[1];
+
+ // There is no tready going to the ADC (one has to be always ready for ADC
+ // data), but it is still a component of the axi_status vector as a generic
+ // AXI stream status. Report 1'b1 to the status vector consistent with being
+ // always ready
+ assign adc_data_out_tready[0] = 1'b1;
+ assign adc_data_out_tvalid_0 = adc_data_out_tvalid[0];
+ assign adc_data_out_tready[1] = 1'b1;
+ assign adc_data_out_tvalid_1 = adc_data_out_tvalid[1];
+
+ //---------------------------------------------------------------------------
+ // DAC Pre-Processing
+ //---------------------------------------------------------------------------
+
+ // Data comes from the user as 4 SPC, separate streams for each channel.
+ assign dac_data_in_tdata_preswap[0] = dac_data_in_tdata_0;
+ assign dac_data_in_tdata_preswap[1] = dac_data_in_tdata_1;
+
+ assign dac_data_in_tready_0 = dac_data_in_tready[0];
+ assign dac_data_in_tvalid[0] = dac_data_in_tvalid_0;
+ assign dac_data_in_tready_1 = dac_data_in_tready[1];
+ assign dac_data_in_tvalid[1] = dac_data_in_tvalid_1;
+
+ // Optionally swap IQ data positions in the vector. First cross the swap
+ // vector over to the data_clk domain.
+ synchronizer #(
+ .WIDTH (8),
+ .STAGES (2),
+ .INITIAL_VAL (0),
+ .FALSE_PATH_TO_IN (1)
+ ) synchronizer_invert_dac_iq (
+ .clk (data_clk),
+ .rst (1'b0),
+ .in (invert_dac_iq_rclk2),
+ .out (invert_dac_iq_dclk)
+ );
+
+ genvar dac_num;
+ generate
+ for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1)
+ begin : dac_swap_gen
+ //IO and Q0 swap
+ assign dac_data_in_tdata[dac_num][15:00] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][31:16]) : (dac_data_in_tdata_preswap[dac_num][15:0]);
+ assign dac_data_in_tdata[dac_num][31:16] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][15:00]) : (dac_data_in_tdata_preswap[dac_num][31:16]);
+
+ //I1 and Q1 swap
+ assign dac_data_in_tdata[dac_num][47:32] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][63:48]) : (dac_data_in_tdata_preswap[dac_num][47:32]);
+ assign dac_data_in_tdata[dac_num][63:48] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][47:32]) : (dac_data_in_tdata_preswap[dac_num][63:48]);
+
+ //I2 and Q2 swap
+ assign dac_data_in_tdata[dac_num][79:64] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][95:80]) : (dac_data_in_tdata_preswap[dac_num][79:64]);
+ assign dac_data_in_tdata[dac_num][95:80] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][79:64]) : (dac_data_in_tdata_preswap[dac_num][95:80]);
+
+ //I3 and Q3 swap
+ assign dac_data_in_tdata[dac_num][111:96] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][127:112]) : (dac_data_in_tdata_preswap[dac_num][111:96]);
+ assign dac_data_in_tdata[dac_num][127:112] = invert_dac_iq_dclk[dac_num] ?
+ (dac_data_in_tdata_preswap[dac_num][111:96]) : (dac_data_in_tdata_preswap[dac_num][127:112]);
+
+ end
+ endgenerate
+
+ // These streams are then interpolated by dac_400m_bd, and form a single
+ // stream per channel, 8 SPC, packed: MSB [Sample7Q, Sample7I, ... ,
+ // Sample0Q, Sample0I] LSB.
+ generate
+ for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1)
+ begin : dac_gen
+ dac_400m_bd dac_400m_bd_gen (
+ .dac_data_in_resetn_dclk (dac_data_in_resetn_dclk),
+ .dac_data_in_resetn_dclk2x (dac_data_in_resetn_dclk2x),
+ .dac_data_in_resetn_rclk (dac_data_in_resetn_rclk),
+ .dac_data_in_tdata (dac_data_in_tdata [dac_num]),
+ .dac_data_in_tready (dac_data_in_tready [dac_num]),
+ .dac_data_in_tvalid (dac_data_in_tvalid [dac_num]),
+ .dac_data_out_tdata (dac_data_out_tdata [dac_num]),
+ .dac_data_out_tready (dac_data_out_tready[dac_num]),
+ .dac_data_out_tvalid (dac_data_out_tvalid[dac_num]),
+ .data_clk (data_clk),
+ .data_clk_2x (data_clk_2x),
+ .rfdc_clk (rfdc_clk)
+ );
+ end
+ endgenerate
+
+ // Data is released to the RFDC as 8 SPC, separate streams per channel (I/Q
+ // together).
+ assign dac_data_out_tdata_0 = dac_data_out_tdata[0];
+ assign dac_data_out_tdata_1 = dac_data_out_tdata[1];
+
+ assign dac_data_out_tready[0] = dac_data_out_tready_0;
+ assign dac_data_out_tvalid_0 = dac_data_out_tvalid[0];
+ assign dac_data_out_tready[1] = dac_data_out_tready_1;
+ assign dac_data_out_tvalid_1 = dac_data_out_tvalid[1];
+
+
+ //---------------------------------------------------------------------------
+ // Version
+ //---------------------------------------------------------------------------
+
+ // Version metadata, constants come from auto-generated
+ // versioning_regs_regmap_utils.vh
+ assign version_info = build_component_versions(
+ RF_CORE_400M_VERSION_LAST_MODIFIED_TIME,
+ build_version(
+ RF_CORE_400M_OLDEST_COMPATIBLE_VERSION_MAJOR,
+ RF_CORE_400M_OLDEST_COMPATIBLE_VERSION_MINOR,
+ RF_CORE_400M_OLDEST_COMPATIBLE_VERSION_BUILD
+ ),
+ build_version(
+ RF_CORE_400M_CURRENT_VERSION_MAJOR,
+ RF_CORE_400M_CURRENT_VERSION_MINOR,
+ RF_CORE_400M_CURRENT_VERSION_BUILD
+ )
+ );
+
+endmodule
+
+`default_nettype wire
+
+//XmlParse xml_on
+//<regmap name="VERSIONING_REGS_REGMAP">
+// <group name="VERSIONING_CONSTANTS">
+// <enumeratedtype name="RF_CORE_400M_VERSION" showhex="true">
+// <info>
+// 400 MHz RF core.{BR/}
+// For guidance on when to update these revision numbers,
+// please refer to the register map documentation accordingly:
+// <li> Current version: @.VERSIONING_REGS_REGMAP..CURRENT_VERSION
+// <li> Oldest compatible version: @.VERSIONING_REGS_REGMAP..OLDEST_COMPATIBLE_VERSION
+// <li> Version last modified: @.VERSIONING_REGS_REGMAP..VERSION_LAST_MODIFIED
+// </info>
+// <value name="RF_CORE_400M_CURRENT_VERSION_MAJOR" integer="1"/>
+// <value name="RF_CORE_400M_CURRENT_VERSION_MINOR" integer="0"/>
+// <value name="RF_CORE_400M_CURRENT_VERSION_BUILD" integer="0"/>
+// <value name="RF_CORE_400M_OLDEST_COMPATIBLE_VERSION_MAJOR" integer="1"/>
+// <value name="RF_CORE_400M_OLDEST_COMPATIBLE_VERSION_MINOR" integer="0"/>
+// <value name="RF_CORE_400M_OLDEST_COMPATIBLE_VERSION_BUILD" integer="0"/>
+// <value name="RF_CORE_400M_VERSION_LAST_MODIFIED_TIME" integer="0x20102617"/>
+// </enumeratedtype>
+// </group>
+//</regmap>
+//XmlParse xml_off
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 diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_2x1.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_2x1.vhd new file mode 100644 index 000000000..f5c58ccf5 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_2x1.vhd @@ -0,0 +1,186 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_adc_gearbox_2x1 +-- +-- Description: +-- +-- Self-checking testbench for adc_gearbox_2x1. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_adc_gearbox_2x1 is +end tb_adc_gearbox_2x1; + + +architecture RTL of tb_adc_gearbox_2x1 is + + component adc_gearbox_2x1 + port ( + clk1x : in std_logic; + reset_n_1x : in std_logic; + adc_q_in_1x : in std_logic_vector(31 downto 0); + adc_i_in_1x : in std_logic_vector(31 downto 0); + valid_in_1x : in std_logic; + enable_1x : in std_logic; + clk2x : in std_logic; + swap_iq_2x : in std_logic; + adc_out_2x : out std_logic_vector(31 downto 0); + valid_out_2x : out std_logic); + end component; + + signal cDataCheckNxtLo, cDataCheckLo : std_logic_vector(31 downto 0); + signal cDataCheckNxtHi, cDataCheckHi1, cDataCheckHi2 : std_logic_vector(31 downto 0); + + signal adc_i_in_1x : std_logic_vector(31 downto 0); + signal adc_out_2x : std_logic_vector(31 downto 0); + signal adc_q_in_1x : std_logic_vector(31 downto 0); + signal enable_1x : std_logic; + signal reset_n_1x : std_logic; + signal swap_iq_2x : std_logic; + signal valid_in_1x : std_logic; + signal valid_out_2x : std_logic; + + signal StopSim : boolean; + constant kPer : time := 10 ns; + + signal Clk : std_logic := '1'; + signal Clk2x : std_logic := '1'; + + procedure ClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk); + end loop; + end procedure ClkWait; + +begin + + Clk <= not Clk after kPer/2 when not StopSim else '0'; + Clk2x <= not Clk2x after kPer/4 when not StopSim else '0'; + + dut: adc_gearbox_2x1 + port map ( + clk1x => Clk, + reset_n_1x => reset_n_1x, + adc_q_in_1x => adc_q_in_1x, + adc_i_in_1x => adc_i_in_1x, + valid_in_1x => valid_in_1x, + enable_1x => enable_1x, + clk2x => Clk2x, + swap_iq_2x => swap_iq_2x, + adc_out_2x => adc_out_2x, + valid_out_2x => valid_out_2x + ); + + main: process + begin + swap_iq_2x <= '0'; + valid_in_1x <= '0'; + enable_1x <= '0'; + reset_n_1x <= '0'; + ClkWait(5); + reset_n_1x <= '1'; + ClkWait(5); + + -- Ensure the outputs are quiet. + ClkWait(20); + assert valid_out_2x'stable(kPer*20) and valid_out_2x = '0' + report "valid not stable at de-asserted at startup" + severity error; + assert adc_out_2x'stable(kPer*20) and (adc_out_2x = x"00000000") + report "data not stable at zero at startup" + severity error; + + -- Valid asserted, Enable asserted, Enable de-asserted, Valid de-asserted. + + ClkWait(10); + valid_in_1x <= '1'; + ClkWait(10); + enable_1x <= '1'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '1' + report "valid not stable at asserted" + severity error; + + ClkWait(10); + enable_1x <= '0'; + ClkWait(10); + valid_in_1x <= '0'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '0' + report "valid not stable at de-asserted" + severity error; + + -- Enable asserted, Valid asserted, Enable de-asserted, Valid de-asserted. + + ClkWait(10); + enable_1x <= '1'; + ClkWait(10); + valid_in_1x <= '1'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '1' + report "valid not stable at asserted" + severity error; + + ClkWait(10); + enable_1x <= '0'; + ClkWait(10); + valid_in_1x <= '0'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '0' + report "valid not stable at de-asserted" + severity error; + + StopSim <= true; + wait; + end process; + + + driver: process(Clk) + variable tempQdata : integer := 1; + variable tempIdata : integer := 128; + begin + if rising_edge(Clk) then + adc_q_in_1x <= std_logic_vector(to_unsigned(tempQdata+1,16)) & std_logic_vector(to_unsigned(tempQdata, 16)); + adc_i_in_1x <= std_logic_vector(to_unsigned(tempIdata+1,16)) & std_logic_vector(to_unsigned(tempIdata, 16)); + cDataCheckNxtLo <= std_logic_vector(to_unsigned(tempQdata,16)) & std_logic_vector(to_unsigned(tempIdata, 16)); + cDataCheckNxtHi <= std_logic_vector(to_unsigned(tempQdata+1,16)) & std_logic_vector(to_unsigned(tempIdata+1,16)); + tempQdata := tempQdata+2; + tempIdata := tempIdata+2; + end if; + end process; + + + checker: process(Clk2x) + variable tempout : integer := 1; + variable ExpectedData : std_logic_vector(31 downto 0) := (others => '0'); + begin + if falling_edge(Clk2x) then + if Clk = '1' then + ExpectedData := cDataCheckLo; + else + ExpectedData := cDataCheckHi2; + end if; + if valid_out_2x = '1' then + assert adc_out_2x = ExpectedData + report "ADC data out mismatch from expected" + severity error; + tempout := tempout +1; + end if; + cDataCheckLo <= cDataCheckNxtLo; + cDataCheckHi1 <= cDataCheckNxtHi; + cDataCheckHi2 <= cDataCheckHi1; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_2x4.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_2x4.vhd new file mode 100644 index 000000000..b36f7de54 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_2x4.vhd @@ -0,0 +1,197 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_adc_gearbox_2x4 +-- +-- Description: +-- +-- Self-checking testbench for the gearbox that expands the data width from 2 +-- SPC to 4 SPC. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_adc_gearbox_2x4 is +end tb_adc_gearbox_2x4; + + +architecture RTL of tb_adc_gearbox_2x4 is + + component adc_gearbox_2x4 + port ( + Clk1x : in std_logic; + Clk3x : in std_logic; + ac1Reset_n : in std_logic; + ac3Reset_n : in std_logic; + c3DataIn : in std_logic_vector(95 downto 0); + c3DataValidIn : in std_logic; + c1DataOut : out std_logic_vector(191 downto 0); + c1DataValidOut : out std_logic); + end component; + + signal aTestReset : boolean; + + signal ac1Reset_n : std_logic := '1'; + signal ac3Reset_n : std_logic := '1'; + signal c3DataIn : std_logic_vector( 95 downto 0) := (others => '0'); + signal c3DataValidIn : std_logic := '0'; + signal c1ExpectedData : std_logic_vector(191 downto 0) := (others => '0'); + signal c1DataOut : std_logic_vector(191 downto 0) := (others => '0'); + signal c1DataValidOut : std_logic; + + signal StopSim : boolean; + constant kPer : time := 12 ns; + + signal Clk1x : std_logic := '1'; + signal Clk3x : std_logic := '1'; + + procedure Clk3xWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk3x); + end loop; + end procedure Clk3xWait; + + procedure Clk1xWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk1x); + end loop; + end procedure Clk1xWait; + +begin + + Clk1x <= not Clk1x after kPer/2 when not StopSim else '0'; + Clk3x <= not Clk3x after kPer/6 when not StopSim else '0'; + + dut: adc_gearbox_2x4 + port map ( + Clk1x => Clk1x, + Clk3x => Clk3x, + ac1Reset_n => ac1Reset_n, + ac3Reset_n => ac3Reset_n, + c3DataIn => c3DataIn, + c3DataValidIn => c3DataValidIn, + c1DataOut => c1DataOut, + c1DataValidOut => c1DataValidOut + ); + + main: process + procedure PhaseTest(WaitCycles : positive := 1) is + begin + -- Stop data generation by asserting this reset. + aTestReset <= true; + Clk1xWait; + ac1Reset_n <= '0'; + ac3Reset_n <= '0'; + Clk1xWait; + ac1Reset_n <= '1'; + ac3Reset_n <= '1'; + + -- This wait is in Clk3x domain. This is used to change phase in which + -- data valid is asserted with respect to Clk3x and Clk1x rising edge. + -- Wait an additional 12 Clk3x cycles for the output data valid to be + -- de-asserted. + Clk3xWait(WaitCycles+12); + + -- De-asserting test reset will start data generation. + aTestReset <= false; + + -- Wait for a random time before we stop the test. + Clk3xWait(1000); + end procedure; + + begin + -- Change phase between Clk1x and Clk3x. See details in the DUT. + -- The wait in each phase test is used to move the de-assertion of data + -- generation logic reset. By doing this, we can change data valid + -- assertion phase between Clk3x and Clk1x. + -- p0. + PhaseTest(1); + + -- p1 + PhaseTest(2); + + -- p2. + PhaseTest(6); + + -- Stop simulation + StopSim <= true; + wait; + end process; + + -- Process to generate data to the DUT. + driver: process(Clk3x, aTestReset) + variable tempQdata : integer := 1; + variable tempIdata : integer := 128; + variable dataCount : integer := 0; + begin + if aTestReset then + tempQdata := 1; + tempIdata := 128; + dataCount := 0; + c3DataIn <= (others => '0'); + c3DataValidIn <= '0'; + elsif rising_edge(Clk3x) then + + if dataCount < 2 then + c3DataIn <= "0000000" & std_logic_vector(to_unsigned(tempQdata+1,17)) & + "0000000" & std_logic_vector(to_unsigned(tempIdata+1,17)) & + "0000000" & std_logic_vector(to_unsigned(tempQdata+0,17)) & + "0000000" & std_logic_vector(to_unsigned(tempIdata+0,17)); + dataCount := dataCount + 1; + c3DataValidIn <= '1'; + tempQdata := tempQdata +2; + tempIdata := tempIdata +2; + elsif dataCount = 2 then + c3DataIn <= (others => '0'); + dataCount := 0; + c3DataValidIn <= '0'; + end if; + + end if; + end process; + + -- Process to generate expected data that is used to verify the DUT output. + expected_data: process(Clk1x) + variable tempQdata : integer := 1; + variable tempIdata : integer := 128; + begin + if rising_edge(Clk1x) then + + if aTestReset and c1DataValidOut = '0' then + tempQdata := 1; + tempIdata := 128; + elsif c1DataValidOut = '1' then + tempQdata := tempQdata+4; + tempIdata := tempIdata+4; + end if; + c1ExpectedData <= "0000000" & std_logic_vector(to_unsigned(tempQdata+3,17)) & + "0000000" & std_logic_vector(to_unsigned(tempIdata+3,17)) & + "0000000" & std_logic_vector(to_unsigned(tempQdata+2,17)) & + "0000000" & std_logic_vector(to_unsigned(tempIdata+2,17)) & + "0000000" & std_logic_vector(to_unsigned(tempQdata+1,17)) & + "0000000" & std_logic_vector(to_unsigned(tempIdata+1,17)) & + "0000000" & std_logic_vector(to_unsigned(tempQdata+0,17)) & + "0000000" & std_logic_vector(to_unsigned(tempIdata+0,17)); + + end if; + end process; + + -- Process to continuously check output data from the DUT. + checker: process(Clk1x) + begin + if falling_edge(Clk1x) then + if c1DataValidOut = '1' then + assert c1DataOut = c1ExpectedData + report "ADC data out mismatch from expected" + severity error; + end if; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_8x4.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_8x4.vhd new file mode 100644 index 000000000..88e520a6b --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_adc_gearbox_8x4.vhd @@ -0,0 +1,206 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_adc_gearbox_8x4 +-- +-- Description: +-- +-- Self-checking testbench for adc_gearbox_8x4. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_adc_gearbox_8x4 is +end tb_adc_gearbox_8x4; + + +architecture RTL of tb_adc_gearbox_8x4 is + + component adc_gearbox_8x4 + port ( + clk1x : in std_logic; + reset_n_1x : in std_logic; + adc_q_in_1x : in std_logic_vector(127 downto 0); + adc_i_in_1x : in std_logic_vector(127 downto 0); + valid_in_1x : in std_logic; + enable_1x : in std_logic; + clk2x : in std_logic; + swap_iq_2x : in std_logic; + adc_out_2x : out std_logic_vector(127 downto 0); + valid_out_2x : out std_logic); + end component; + + signal cDataCheckNxtLo, cDataCheckLo: std_logic_vector(127 downto 0); + signal cDataCheckNxtHi : std_logic_vector(127 downto 0); + signal cDataCheckHi1, cDataCheckHi2: std_logic_vector(127 downto 0); + + signal adc_i_in_1x : std_logic_vector(127 downto 0); + signal adc_out_2x : std_logic_vector(127 downto 0); + signal adc_q_in_1x : std_logic_vector(127 downto 0); + signal enable_1x : std_logic; + signal reset_n_1x : std_logic; + signal swap_iq_2x : std_logic; + signal valid_in_1x : std_logic; + signal valid_out_2x : std_logic; + + signal StopSim : boolean; + constant kPer : time := 10 ns; + + signal Clk : std_logic := '1'; + signal Clk2x : std_logic := '1'; + + procedure ClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk); + end loop; + end procedure ClkWait; + +begin + + Clk <= not Clk after kPer/2 when not StopSim else '0'; + Clk2x <= not Clk2x after kPer/4 when not StopSim else '0'; + + dut: adc_gearbox_8x4 + port map ( + clk1x => Clk, + reset_n_1x => reset_n_1x, + adc_q_in_1x => adc_q_in_1x, + adc_i_in_1x => adc_i_in_1x, + valid_in_1x => valid_in_1x, + enable_1x => enable_1x, + clk2x => Clk2x, + swap_iq_2x => swap_iq_2x, + adc_out_2x => adc_out_2x, + valid_out_2x => valid_out_2x + ); + + main: process + begin + swap_iq_2x <= '0'; + valid_in_1x <= '0'; + enable_1x <= '0'; + reset_n_1x <= '0'; + ClkWait(5); + reset_n_1x <= '1'; + ClkWait(5); + + -- Ensure the outputs are quiet. + ClkWait(20); + assert valid_out_2x'stable(kPer*20) and valid_out_2x = '0' + report "valid not stable at de-asserted at startup" + severity error; + assert adc_out_2x'stable(kPer*20) and (adc_out_2x = std_logic_vector(to_unsigned(0,128))) + report "data not stable at zero at startup" + severity error; + + -- Valid asserted, Enable asserted, Enable de-asserted, Valid de-asserted. + + ClkWait(10); + valid_in_1x <= '1'; + ClkWait(10); + enable_1x <= '1'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '1' + report "valid not stable at asserted" + severity error; + + ClkWait(10); + enable_1x <= '0'; + ClkWait(10); + valid_in_1x <= '0'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '0' + report "valid not stable at de-asserted" + severity error; + + -- Enable asserted, Valid asserted, Enable de-asserted, Valid de-asserted. + + ClkWait(10); + enable_1x <= '1'; + ClkWait(10); + valid_in_1x <= '1'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '1' + report "valid not stable at asserted" + severity error; + + ClkWait(10); + enable_1x <= '0'; + ClkWait(10); + valid_in_1x <= '0'; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '0' + report "valid not stable at de-asserted" + severity error; + + StopSim <= true; + wait; + end process; + + -- Process to generate input data to DUT and expected output data. + driver: process(Clk) + variable tempQdata : integer := 1; + variable tempIdata : integer := 128; + variable qData8spc : std_logic_vector(127 downto 0); + variable iData8spc : std_logic_vector(127 downto 0); + begin + if rising_edge(Clk) then + qdata8Spc := std_logic_vector(to_unsigned(tempQdata+7,16)) & std_logic_vector(to_unsigned(tempQdata+6,16)) & + std_logic_vector(to_unsigned(tempQdata+5,16)) & std_logic_vector(to_unsigned(tempQdata+4,16)) & + std_logic_vector(to_unsigned(tempQdata+3,16)) & std_logic_vector(to_unsigned(tempQdata+2,16)) & + std_logic_vector(to_unsigned(tempQdata+1,16)) & std_logic_vector(to_unsigned(tempQdata ,16)); + adc_q_in_1x <= qData8Spc; + + iData8spc := std_logic_vector(to_unsigned(tempIdata+7,16)) & std_logic_vector(to_unsigned(tempIdata+6,16)) & + std_logic_vector(to_unsigned(tempIdata+5,16)) & std_logic_vector(to_unsigned(tempIdata+4,16)) & + std_logic_vector(to_unsigned(tempIdata+3,16)) & std_logic_vector(to_unsigned(tempIdata+2,16)) & + std_logic_vector(to_unsigned(tempIdata+1,16)) & std_logic_vector(to_unsigned(tempIdata ,16)); + adc_i_in_1x <= iData8Spc; + + + cDataCheckNxtLo <= qData8spc( 63 downto 48) & iData8spc( 63 downto 48) & + qData8spc( 47 downto 32) & iData8spc( 47 downto 32) & + qData8spc( 31 downto 16) & iData8spc( 31 downto 16) & + qData8spc( 15 downto 0) & iData8spc( 15 downto 0); + cDataCheckNxtHi <= qData8spc(127 downto 112) & iData8spc(127 downto 112) & + qData8spc(111 downto 96) & iData8spc(111 downto 96) & + qData8spc( 95 downto 80) & iData8spc( 95 downto 80) & + qData8spc( 79 downto 64) & iData8spc( 79 downto 64); + tempQdata := tempQdata+8; + tempIdata := tempIdata+8; + end if; + end process; + + -- Process to check DUT output with expected data. + checker: process(Clk2x) + variable tempout : integer := 1; + variable ExpectedData : std_logic_vector(127 downto 0) := (others => '0'); + begin + if falling_edge(Clk2x) then + if Clk = '1' then + ExpectedData := cDataCheckLo; + else + ExpectedData := cDataCheckHi2; + end if; + if valid_out_2x = '1' then + assert adc_out_2x = ExpectedData + report "ADC data out mismatch from expected" + severity error; + tempout := tempout +1; + end if; + cDataCheckLo <= cDataCheckNxtLo; + cDataCheckHi1 <= cDataCheckNxtHi; + cDataCheckHi2 <= cDataCheckHi1; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_capture_sysref.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_capture_sysref.vhd new file mode 100644 index 000000000..eb5cbcf08 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_capture_sysref.vhd @@ -0,0 +1,119 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_capture_sysref +-- +-- Description: +-- +-- Self-checking testbench for tb_capture_sysref. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_capture_sysref is +end tb_capture_sysref; + + +architecture RTL of tb_capture_sysref is + + component capture_sysref + port ( + pll_ref_clk : in std_logic; + rfdc_clk : in std_logic; + sysref_in : in std_logic; + enable_rclk : in std_logic; + sysref_out_pclk : out std_logic; + sysref_out_rclk : out std_logic); + end component; + + signal enable_rclk : std_logic := '0'; + signal sysref_out_pclk : std_logic := '0'; + signal sysref_out_rclk : std_logic := '0'; + signal sysref_in : std_logic := '0'; + + signal SysrefDly, SysrefDlyDly, rSysref : std_logic := '0'; + + signal StopSim : boolean; + constant kPerPRC : time := 30 ns; + constant kPerRF : time := 10 ns; + + signal PllRefClk : std_logic := '1'; + signal RfdcClk : std_logic := '1'; + + procedure ClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(PllRefClk); + end loop; + end procedure ClkWait; + +begin + + PllRefClk <= not PllRefClk after kPerPRC/2 when not StopSim else '0'; + RfdcClk <= not RfdcClk after kPerRF/2 when not StopSim else '0'; + + dut: capture_sysref + port map ( + pll_ref_clk => PllRefClk, + rfdc_clk => RfdcClk, + sysref_in => sysref_in, + enable_rclk => enable_rclk, + sysref_out_pclk => sysref_out_pclk, + sysref_out_rclk => sysref_out_rclk + ); + + main: process + begin + enable_rclk <= '1'; + ClkWait(100); + wait until falling_edge(sysref_out_rclk); + ClkWait; + wait until falling_edge(RfdcClk); + enable_rclk <= '0'; + ClkWait(100); + wait until falling_edge(RfdcClk); + enable_rclk <= '1'; + ClkWait(100); + + StopSim <= true; + wait; + end process; + + sysref: process(PllRefClk) + variable count : integer := 1; + begin + if rising_edge(PllRefClk) then + count := count +1; + if count = 10 then + sysref_in <= not sysref_in; + count := 1; + end if; + end if; + end process; + + checker_pll_ref_clk: process(PllRefClk) + begin + if falling_edge(PllRefClk) then + SysrefDly <= sysref_in; + SysrefDlyDly <= SysrefDly; + assert SysrefDlyDly = sysref_out_pclk + report "SYSREF incorrectly captured in the PllRefClk domain" + severity error; + end if; + end process; + + checker_rfdc_clk: process(RfdcClk) + begin + if falling_edge(RfdcClk) then + rSysref <= sysref_out_pclk; + assert (rSysref = sysref_out_rclk) or (enable_rclk = '0') + report "SYSREF incorrectly captured in the RfdcClk domain." + severity error; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_12x8.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_12x8.vhd new file mode 100644 index 000000000..cde766cbd --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_12x8.vhd @@ -0,0 +1,197 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_dac_gearbox_12x8 +-- +-- Description: +-- +-- Self-checking testbench for a gearbox that decreases the SPCs from 12 to +-- 8. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_dac_gearbox_12x8 is +end tb_dac_gearbox_12x8; + + +architecture RTL of tb_dac_gearbox_12x8 is + + signal TestStart : boolean; + + signal ac1Reset_n : std_logic := '0'; + signal arReset_n : std_logic := '0'; + signal c1DataIn : std_logic_vector(383 downto 0) := (others => '0'); + signal c1DataValidIn : std_logic := '0'; + signal rDataOut : std_logic_vector(255 downto 0); + signal rReadyForOutput : std_logic := '1'; + signal rDataValidOut : std_logic; + signal rDataToCheck, rDataToCheckDly0, rDataToCheckDly1, rDataToCheckDly2, + rDataToCheckDly3, rDataToCheckDly4 + : std_logic_vector(255 downto 0) := (others => '0'); + + signal StopSim : boolean; + constant kPer : time := 12 ns; + + signal Clk1x: std_logic := '1'; + signal RfClk: std_logic := '1'; + + procedure RfClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(RfClk); + end loop; + end procedure RfClkWait; + + procedure Clk1xWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk1x); + end loop; + end procedure Clk1xWait; + +begin + + Clk1x <= not Clk1x after kPer/4 when not StopSim else '0'; + RfClk <= not RfClk after kPer/6 when not StopSim else '0'; + + dut: entity WORK.dac_gearbox_12x8 (RTL) + port map ( + Clk1x => Clk1x, + RfClk => RfClk, + ac1Reset_n => ac1Reset_n, + arReset_n => arReset_n, + c1DataIn => c1DataIn, + c1DataValidIn => c1DataValidIn, + rDataOut => rDataOut, + rReadyForOutput => rReadyForOutput, + rDataValidOut => rDataValidOut + ); + + main: process + -- Procedure to start and stop data generation. + -- WaitCycles : This is a wait in Clk1x cycle. This is used to shift data + -- valid assertion. Depending on the Clk1x cycle, data valid + -- will be asserted either when both RfClk and Clk1x are phase + -- aligned or when both clocks are not phase aligned. + procedure PhaseTest(WaitCycles : positive := 1) is + begin + for i in 0 to 31 loop + -- Wait for certain RfClk cycles before starting the test. + Clk1xWait(WaitCycles); + TestStart <= true; + -- Random wait + Clk1xWait(1000+i); + TestStart <= false; + -- wait for few clock cycles for the output data valid to de-assert. + Clk1xWait(10); + end loop; + end procedure; + + begin + ac1Reset_n <= '0'; + arReset_n <= '0'; + TestStart <= false; + Clk1xWait(5); + ac1Reset_n <= '1'; + arReset_n <= '1'; + rReadyForOutput <= '1'; + + -- RfClk and Clk1x are phase aligned + PhaseTest(1); + + -- RfClk and Clk1x are phase aligned + PhaseTest(2); + + -- RfClk and Clk1x are not phase aligned + PhaseTest(3); + + -- Stop data input to the DUT and wait for few clock cycles for the output + -- data valid to be de-asserted. + TestStart <= false; + RfClkWait(10); + + StopSim <= true; + wait; + end process; + + -- Process to generate input data. + driver: process(Clk1x) + variable qDataIn : unsigned(15 downto 0) := x"0001"; + variable iDataIn : unsigned(15 downto 0) := x"0080"; + begin + if rising_edge(Clk1x) then + c1DataValidIn <= '0'; + if TestStart then + c1DataValidIn <= '1'; + c1DataIn <= std_logic_vector((qDataIn+11) & (iDataIn+11) & + (qDataIn+10) & (iDataIn+10) & + (qDataIn+9) & (iDataIn+9) & + (qDataIn+8) & (iDataIn+8) & + (qDataIn+7) & (iDataIn+7) & + (qDataIn+6) & (iDataIn+6) & + (qDataIn+5) & (iDataIn+5) & + (qDataIn+4) & (iDataIn+4) & + (qDataIn+3) & (iDataIn+3) & + (qDataIn+2) & (iDataIn+2) & + (qDataIn+1) & (iDataIn+1) & + (qDataIn+0) & (iDataIn+0)); + qDataIn := qDataIn+12; + iDataIn := iDataIn+12; + + else + c1DataValidIn <= '0'; + qDataIn := x"0001"; + iDataIn := x"0080"; + end if; + end if; + end process; + + -- Process to generate expected output data. + ExpectedData: process(RfClk) + variable qDataOut : unsigned(15 downto 0) := x"0001"; + variable iDataOut : unsigned(15 downto 0) := x"0080"; + begin + if rising_edge(RfClk) then + if TestStart then + rDataToCheck <= std_logic_vector((qDataOut+7) & (iDataOut+7) & + (qDataOut+6) & (iDataOut+6) & + (qDataOut+5) & (iDataOut+5) & + (qDataOut+4) & (iDataOut+4) & + (qDataOut+3) & (iDataOut+3) & + (qDataOut+2) & (iDataOut+2) & + (qDataOut+1) & (iDataOut+1) & + (qDataOut+0) & (iDataOut+0)); + + -- Data output that has to be verified. + qDataOut := qDataOut+8; + iDataOut := iDataOut+8; + else + qDataOut := x"0001"; + iDataOut := x"0080"; + end if; + rDataToCheckDly0 <= rDataToCheck; + rDataToCheckDly1 <= rDataToCheckDly0; + rDataToCheckDly2 <= rDataToCheckDly1; + rDataToCheckDly3 <= rDataToCheckDly2; + rDataToCheckDly4 <= rDataToCheckDly3; + end if; + end process; + + -- Process to check output data with expected data. + checker: process(RfClk) + begin + if falling_edge(RfClk) then + if rDataValidOut = '1' then + assert rDataOut = rDataToCheckDly4 + report "DAC data out mismatch from expected" + severity error; + end if; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_4x2.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_4x2.vhd new file mode 100644 index 000000000..45fe9e150 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_4x2.vhd @@ -0,0 +1,168 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_dac_gearbox_4x2 +-- +-- Description: +-- +-- Self-checking testbench used to test the gearbox that reduces a 4 SPC data +-- into a 2 SPC data. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_dac_gearbox_4x2 is +end tb_dac_gearbox_4x2; + + +architecture RTL of tb_dac_gearbox_4x2 is + + component dac_gearbox_4x2 + port ( + clk1x : in std_logic; + reset_n_1x : in std_logic; + data_in_1x : in std_logic_vector(127 downto 0); + valid_in_1x : in std_logic; + ready_out_1x : out std_logic; + clk2x : in std_logic; + data_out_2x : out std_logic_vector(63 downto 0); + valid_out_2x : out std_logic); + end component; + + signal TestStart : boolean; + + signal data_in_1x : std_logic_vector(127 downto 0); + signal data_out_2x : std_logic_vector(63 downto 0); + signal ready_out_1x : std_logic; + signal reset_n_1x : std_logic; + signal valid_in_1x : std_logic; + signal valid_out_2x : std_logic; + + signal StopSim : boolean; + constant kPer : time := 10 ns; + + signal Clk: std_logic := '1'; + signal Clk2x: std_logic := '1'; + + signal c2DataToCheck, c2DataToCheckDly0, c2DataToCheckDly1, c2DataToCheckDly2 + : std_logic_vector(63 downto 0) := (others => '0'); + + procedure ClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk); + end loop; + end procedure ClkWait; + +begin + + Clk <= not Clk after kPer/2 when not StopSim else '0'; + Clk2x <= not Clk2x after kPer/4 when not StopSim else '0'; + + dut: dac_gearbox_4x2 + port map ( + clk1x => Clk, + reset_n_1x => reset_n_1x, + data_in_1x => data_in_1x, + valid_in_1x => valid_in_1x, + ready_out_1x => ready_out_1x, + clk2x => Clk2x, + data_out_2x => data_out_2x, + valid_out_2x => valid_out_2x + ); + + main: process + begin + reset_n_1x <= '0'; + TestStart <= false; + ClkWait(5); + reset_n_1x <= '1'; + ClkWait(5); + + -- Ensure the outputs are quiet. + ClkWait(20); + assert valid_out_2x'stable(kPer*20) and valid_out_2x = '0' + report "valid not stable at de-asserted at startup" + severity error; + assert data_out_2x'stable(kPer*20) and (data_out_2x = x"0000000000000000") + report "data not stable at zero at startup" + severity error; + + -- Valid asserted, Enable asserted, Enable de-asserted, Valid de-asserted. + + ClkWait(10); + TestStart <= true; + + ClkWait(110); + assert valid_out_2x'stable(kPer*100) and valid_out_2x = '1' + report "valid not stable at asserted" + severity error; + + TestStart <= false; + ClkWait(10); + StopSim <= true; + wait; + end process; + + -- Process to generate input data to DUT. + driver: process(Clk) + variable tempQdata : integer := 1; + variable tempIdata : integer := 128; + begin + if rising_edge(Clk) then + valid_in_1x <= '0'; + if TestStart then + valid_in_1x <= '1'; + data_in_1x <= std_logic_vector(to_unsigned(tempQdata+3,16)) & std_logic_vector(to_unsigned(tempIdata+3,16)) & + std_logic_vector(to_unsigned(tempQdata+2,16)) & std_logic_vector(to_unsigned(tempIdata+2,16)) & + std_logic_vector(to_unsigned(tempQdata+1,16)) & std_logic_vector(to_unsigned(tempIdata+1,16)) & + std_logic_vector(to_unsigned(tempQdata+0,16)) & std_logic_vector(to_unsigned(tempIdata+0,16)); + tempQdata := tempQdata+4; + tempIdata := tempIdata+4; + end if; + end if; + end process; + + -- Process to generate expected data out of the DUT. + ExpectedData: process(Clk2x) + variable qDataOut : unsigned(15 downto 0) := x"0001"; + variable iDataOut : unsigned(15 downto 0) := x"0080"; + begin + if rising_edge(Clk2x) then + if TestStart then + c2DataToCheck <= std_logic_vector((qDataOut+1) & (iDataOut+1) & + (qDataOut+0) & (iDataOut+0)); + + qDataOut := qDataOut+2; + iDataOut := iDataOut+2; + else + qDataOut := x"0001"; + iDataOut := x"0080"; + end if; + c2DataToCheckDly0 <= c2DataToCheck; + c2DataToCheckDly1 <= c2DataToCheckDly0; + c2DataToCheckDly2 <= c2DataToCheckDly1; + end if; + end process; + + -- Process to check DUT output data with expected data. + checker: process(Clk2x) + begin + if falling_edge(Clk2x) then + if valid_out_2x = '1' then + assert data_out_2x = c2DataToCheckDly2 + report "DAC data out mismatch from expected" + severity error; + end if; + assert ready_out_1x = '1' + report "Ready for output is not asserted" + severity error; + + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_6x12.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_6x12.vhd new file mode 100644 index 000000000..950ed8db1 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_dac_gearbox_6x12.vhd @@ -0,0 +1,187 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_dac_gearbox_6x12 +-- +-- Description: +-- +-- Self-checking testbench used to test the gearbox that expands a 6 SPC data +-- into a 12 SPC data. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_dac_gearbox_6x12 is +end tb_dac_gearbox_6x12; + + +architecture RTL of tb_dac_gearbox_6x12 is + + signal TestStart : boolean; + + signal ac1Reset_n : std_logic; + signal ac2Reset_n : std_logic; + signal c1DataOut : std_logic_vector(383 downto 0); + signal c1DataValidOut : std_logic; + signal c2DataIn : std_logic_vector(191 downto 0) := (others => '0'); + signal c2DataValidIn : std_logic := '0'; + signal InPhase : boolean := false; + + signal c1DataToCheck, c1DataToCheckDly0, c1DataToCheckDly1, c1DataToCheckDly2 + : std_logic_vector(383 downto 0) := (others => '0'); + + signal StopSim : boolean; + constant kPer : time := 12 ns; + + signal Clk1x: std_logic := '1'; + signal Clk2x: std_logic := '1'; + + procedure Clk2xWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk2x); + end loop; + end procedure Clk2xWait; + +begin + + Clk1x <= not Clk1x after kPer/4 when not StopSim else '0'; + Clk2x <= not Clk2x after kPer/8 when not StopSim else '0'; + + dut: entity WORK.dac_gearbox_6x12 (RTL) + port map ( + Clk1x => Clk1x, + Clk2x => Clk2x, + ac1Reset_n => ac1Reset_n, + ac2Reset_n => ac2Reset_n, + c2DataIn => c2DataIn, + c2DataValidIn => c2DataValidIn, + c1DataOut => c1DataOut, + c1DataValidOut => c1DataValidOut + ); + + + main: process + + -- Procedure to start and stop data generation. + -- WaitCycles : This is a wait in Clk2x cycle. This is used to shift data + -- valid assertion. Depending on the Clk2x cycle, data valid + -- will be asserted either when both Clk1x and Clk2x are phase + -- aligned or when both clocks are not phase aligned. + -- Phase : This input is used in the logic that is used to check + -- output data with expected data. If data valid was asserted + -- when both clocks were phase aligned, then this input is + -- set to true and vice versa. + procedure PhaseTest(WaitCycles : positive := 1; + Phase : boolean := false) is + begin + -- Wait for certain Clk2x cycles before starting the test. + Clk2xWait(WaitCycles); + InPhase <= Phase; + TestStart <= true; + Clk2xWait(1000); -- Random wait. + TestStart <= false; + -- wait for few clock cycles for the output data valid to de-assert. + Clk2xWait(10); + end procedure; + + begin + + -- Assert and de-assert reset. + ac1Reset_n <= '0'; + ac2Reset_n <= '0'; + TestStart <= false; + Clk2xWait(5); + ac1Reset_n <= '1'; + ac2Reset_n <= '1'; + + PhaseTest(1, true); + PhaseTest(3, false); + PhaseTest(5, true); + + -- Stop data input to the DUT and wait for few clock cycles for the output + -- data valid to be de-asserted. + TestStart <= false; + Clk2xWait(10); + + StopSim <= true; + wait; + end process; + + driver: process(Clk2x) + variable tempQdata : unsigned(15 downto 0) := x"0001"; + variable tempIdata : unsigned(15 downto 0) := x"0080"; + begin + if rising_edge(Clk2x) then + c2DataValidIn <= '0'; + if TestStart then + c2DataValidIn <= '1'; + c2DataIn <= std_logic_vector((tempQdata+5) & (tempIdata+5) & + (tempQdata+4) & (tempIdata+4) & + (tempQdata+3) & (tempIdata+3) & + (tempQdata+2) & (tempIdata+2) & + (tempQdata+1) & (tempIdata+1) & + (tempQdata+0) & (tempIdata+0)); + tempQdata := tempQdata +6; + tempIdata := tempIdata +6; + else + c2DataValidIn <= '0'; + tempQdata := x"0001"; + tempIdata := x"0080"; + end if; + end if; + end process; + + -- Process to generate expected data out of the DUT. + ExpectedData: process(Clk1x) + variable qDataOut : unsigned(15 downto 0) := x"0001"; + variable iDataOut : unsigned(15 downto 0) := x"0080"; + begin + if rising_edge(Clk1x) then + if TestStart then + c1DataToCheck <= std_logic_vector((qDataOut+11) & (iDataOut+11) & + (qDataOut+10) & (iDataOut+10) & + (qDataOut+9) & (iDataOut+9) & + (qDataOut+8) & (iDataOut+8) & + (qDataOut+7) & (iDataOut+7) & + (qDataOut+6) & (iDataOut+6) & + (qDataOut+5) & (iDataOut+5) & + (qDataOut+4) & (iDataOut+4) & + (qDataOut+3) & (iDataOut+3) & + (qDataOut+2) & (iDataOut+2) & + (qDataOut+1) & (iDataOut+1) & + (qDataOut+0) & (iDataOut+0)); + + qDataOut := qDataOut+12; + iDataOut := iDataOut+12; + else + qDataOut := x"0001"; + iDataOut := x"0080"; + end if; + c1DataToCheckDly0 <= c1DataToCheck; + c1DataToCheckDly1 <= c1DataToCheckDly0; + c1DataToCheckDly2 <= c1DataToCheckDly1; + end if; + end process; + + -- Process to check output data with expected data. + checker: process(Clk1x) + begin + if falling_edge(Clk1x) then + if c1DataValidOut = '1' and InPhase then + assert c1DataOut = c1DataToCheckDly1 + report "ADC data out mismatch from expected" + severity warning; + elsif c1DataValidOut = '1' and (not InPhase) then + assert c1DataOut = c1DataToCheckDly2 + report "ADC data out mismatch from expected" + severity warning; + end if; + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_ddc_400m_saturate.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_ddc_400m_saturate.vhd new file mode 100644 index 000000000..37f570d87 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_ddc_400m_saturate.vhd @@ -0,0 +1,125 @@ +--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: tb_ddc_400m_saturate
+--
+-- Description:
+--
+-- Self-checking testbench used to check the saturation logic needed in DDC.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+library WORK;
+ use WORK.PkgRf.all;
+
+entity tb_ddc_400m_saturate is
+end tb_ddc_400m_saturate;
+
+
+architecture RTL of tb_ddc_400m_saturate is
+
+ component ddc_400m_saturate
+ port (
+ Clk : in std_logic;
+ cDataIn : in std_logic_vector(191 downto 0);
+ cDataValidIn : in std_logic;
+ cDataOut : out std_logic_vector(127 downto 0);
+ cDataValidOut : out std_logic);
+ end component;
+
+ signal TestStart : boolean := false;
+
+ signal cDataIn : std_logic_vector(191 downto 0);
+ signal cDataOut : std_logic_vector(127 downto 0);
+ signal cDataValidIn : std_logic;
+ signal cDataValidOut : std_logic;
+
+ signal StopSim : boolean;
+ constant kPer : time := 10 ns;
+ constant kSamplesPerClock : integer := 8;
+
+ signal Clk: std_logic := '1';
+
+ procedure ClkWait(X : positive := 1) is
+ begin
+ for i in 1 to X loop
+ wait until rising_edge(Clk);
+ end loop;
+ end procedure ClkWait;
+
+begin
+
+ Clk <= not Clk after kPer/2 when not StopSim else '0';
+
+ dut: ddc_400m_saturate
+ port map (
+ Clk => Clk,
+ cDataIn => cDataIn,
+ cDataValidIn => cDataValidIn,
+ cDataOut => cDataOut,
+ cDataValidOut => cDataValidOut);
+
+ main: process
+ begin
+
+ ClkWait;
+ TestStart <= false;
+ ClkWait;
+ TestStart <= true;
+
+ -- This wait is needed to sweep through the entire range of 17 bits signed
+ -- value. Since we operate the saturation logic with 8 samples per cycle,
+ -- we need to wait for 2^kDdcDataOutWidth/8. We are adding an extra 10
+ -- clock cycles wait just as a buffer for the DUT latency.
+ ClkWait(2**kDdcDataOutWidth/kSamplesPerClock + 10);
+ StopSim <= true;
+ wait;
+ end process;
+
+ -- Process to generate 17-bit signed data.
+ DataGen: process(Clk)
+ variable Sample : Sample17_t := kSmallest17;
+ begin
+ if falling_edge(Clk) then
+ if TestStart then
+ cDataValidIn <= '1';
+ cDataIn <= "0000000" & std_logic_vector(Sample+kSamplesPerClock-1) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-2) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-3) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-4) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-5) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-6) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-7) &
+ "0000000" & std_logic_vector(Sample+kSamplesPerClock-8);
+ Sample := Sample +8;
+ end if;
+ end if;
+ end process;
+
+ -- Check if saturation and data packing is done correctly.
+ DataCheck: process(Clk)
+ variable Sample : Sample17_t := kSmallest17;
+ variable ExpectedData : std_logic_vector(15 downto 0);
+
+ begin
+ if falling_edge(Clk) then
+ if cDataValidOut then
+ for i in 1 to 8 loop
+ ExpectedData := tb_saturate(std_logic_vector(Sample));
+ assert cDataOut(kSatDataWidth*i-1 downto kSatDataWidth*(i-1)) = ExpectedData
+ report "Saturation data out mismatch in index : " & to_string(i) & LF &
+ "Expected data is : " & to_hstring(ExpectedData) & LF &
+ "Received data is : " & to_hstring(cDataOut(kSatDataWidth*i-1 downto kSatDataWidth*(i-1)))
+ severity error;
+ Sample := Sample+1;
+ end loop;
+ end if;
+ end if;
+ end process;
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_duc_400m_saturate.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_duc_400m_saturate.vhd new file mode 100644 index 000000000..e3117bdd6 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_duc_400m_saturate.vhd @@ -0,0 +1,133 @@ +--
+-- Copyright 2021 Ettus Research, a National Instruments Brand
+--
+-- SPDX-License-Identifier: LGPL-3.0-or-later
+--
+-- Module: tb_duc_400m_saturate
+--
+-- Description:
+--
+-- Self-checking testbench used to check the saturation logic needed in DDC.
+--
+
+library IEEE;
+ use IEEE.std_logic_1164.all;
+ use IEEE.numeric_std.all;
+
+library WORK;
+ use WORK.PkgRf.all;
+
+entity tb_duc_400m_saturate is
+end tb_duc_400m_saturate;
+
+
+architecture RTL of tb_duc_400m_saturate is
+
+ component duc_400m_saturate
+ port (
+ Clk : in std_logic;
+ cDataIn : in std_logic_vector(287 downto 0);
+ cDataValidIn : in std_logic;
+ cReadyForInput : out std_logic;
+ cDataOut : out std_logic_vector(191 downto 0);
+ cDataValidOut : out std_logic := '0');
+ end component;
+
+ signal TestStart : boolean := false;
+
+ signal cDataIn : std_logic_vector(287 downto 0);
+ signal cDataOut : std_logic_vector(191 downto 0);
+ signal cDataValidIn : std_logic;
+ signal cDataValidOut : std_logic;
+
+ signal StopSim : boolean;
+ constant kPer : time := 10 ns;
+ constant kSamplesPerClock : integer := 12;
+
+ signal Clk: std_logic := '1';
+
+ procedure ClkWait(X : positive := 1) is
+ begin
+ for i in 1 to X loop
+ wait until rising_edge(Clk);
+ end loop;
+ end procedure ClkWait;
+
+begin
+
+ Clk <= not Clk after kPer/2 when not StopSim else '0';
+
+
+ -- cReadyForInput is a constant in the design and is not being tested.
+ dut: duc_400m_saturate
+ port map (
+ Clk => Clk,
+ cDataIn => cDataIn,
+ cDataValidIn => cDataValidIn,
+ cReadyForInput => open,
+ cDataOut => cDataOut,
+ cDataValidOut => cDataValidOut);
+
+ main: process
+ begin
+
+ ClkWait;
+ TestStart <= false;
+ ClkWait;
+ TestStart <= true;
+
+ -- This wait is needed to sweep through the entire range of 18 bits signed
+ -- value. Since we operate the saturation logic with 12 samples per cycle,
+ -- we need to wait for 2^kDucDataOutWidth/12. We are adding an extra 10
+ -- clock cycles wait just as a buffer for the DUT latency.
+ ClkWait(2**kDucDataOutWidth/kSamplesPerClock + 10);
+ StopSim <= true;
+ wait;
+ end process;
+
+ -- Process to generate 18-bit signed data.
+ DataGen: process(Clk)
+ variable Sample : Sample18_t := kSmallest18;
+ begin
+ if falling_edge(Clk) then
+ if TestStart then
+ cDataValidIn <= '1';
+ cDataIn <= "000000" & std_logic_vector(Sample+kSamplesPerClock-1) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-2) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-3) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-4) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-5) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-6) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-7) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-8) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-9) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-10) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-11) &
+ "000000" & std_logic_vector(Sample+kSamplesPerClock-12);
+ Sample := Sample +12;
+ end if;
+ end if;
+ end process;
+
+ -- Check if saturation and data packing is done correctly.
+ DataCheck: process(Clk)
+ variable Sample : Sample18_t := kSmallest18;
+ variable ExpectedData : std_logic_vector(15 downto 0);
+
+ begin
+ if falling_edge(Clk) then
+ if cDataValidOut then
+ for i in 1 to 12 loop
+ ExpectedData := tb_saturate(std_logic_vector(Sample));
+ assert cDataOut(kSatDataWidth*i-1 downto kSatDataWidth*(i-1)) = ExpectedData
+ report "Saturation data out mismatch in index : " & to_string(i) & LF &
+ "Expected data is : " & to_hstring(ExpectedData) & LF &
+ "Received data is : " & to_hstring(cDataOut(kSatDataWidth*i-1 downto kSatDataWidth*(i-1)))
+ severity error;
+ Sample := Sample+1;
+ end loop;
+ end if;
+ end if;
+ end process;
+
+end RTL;
diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_rf_nco_reset.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_rf_nco_reset.vhd new file mode 100644 index 000000000..066dd5f4b --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_rf_nco_reset.vhd @@ -0,0 +1,281 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_rf_nco_reset +-- +-- Description: +-- +-- Self-checking testbench for NCO reset sequencing. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +entity tb_rf_nco_reset is +end tb_rf_nco_reset; + + +architecture RTL of tb_rf_nco_reset is + + signal cAdc0xNcoUpdateReq : std_logic; + signal cAdc2xNcoUpdateReq : std_logic; + signal cDac0xNcoUpdateReq : std_logic; + signal cDac0xSysrefIntGating : std_logic; + signal cDac0xSysrefIntReenable : std_logic; + signal cDac1xNcoUpdateReq : std_logic; + signal cNcoPhaseRst : std_logic; + signal cNcoUpdateEn : std_logic_vector(5 downto 0); + signal dNcoResetDone : std_logic; + + signal cDac0xNcoUpdateBusy : std_logic_vector(1 downto 0) := "00"; + signal dStartNcoReset : std_logic := '0'; + signal cAdc0xNcoUpdateBusy : std_logic := '0'; + signal cAdc2xNcoUpdateBusy : std_logic := '0'; + signal cDac1xNcoUpdateBusy : std_logic := '0'; + + signal cSysref_ms, cSysref : std_logic := '0'; + signal cSysrefDlyd : std_logic_vector(1 downto 0) := "00"; + signal cDac0xSysrefIntGatingDlyd : std_logic := '0'; + signal cNcoPhaseRstDlyd : std_logic_vector(2 downto 0) := "000"; + + signal cWrCount : integer := 0; + type RfdcNcoState_t is (Idle, GateSysref, UpdateReq, CheckUpdate, + SysrefEn, WaitForSysref, ResetDone); + signal cRfdcNcoState : RfdcNcoState_t := Idle; + + signal StopSim : boolean; + constant kConfigClkPer : time := 25 ns; + -- SYSREF period is 2.5 MHz. + constant kSysrefPer : time := 400 ns; + -- DataClk period is 125 MHz and generated from the same clocking chip that + -- generated SYSREF and are related. + constant kDataClkPer : time := kSysrefPer/50; + + signal ConfigClk : std_logic := '0'; + signal DataClk : std_logic := '0'; + signal dSysref : std_logic := '0'; + + procedure DataClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(DataClk); + end loop; + end procedure DataClkWait; + + procedure ConfigClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(ConfigClk); + end loop; + end procedure ConfigClkWait; + + procedure SysrefWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(dSysref); + end loop; + end procedure SysrefWait; + +begin + + ConfigClk <= not ConfigClk after kConfigClkPer/2 when not StopSim else '0'; + DataClk <= not DataClk after kDataClkPer/2 when not StopSim else '0'; + dSysref <= not dSysref after kSysrefPer/2 when not StopSim else '0'; + + -- Both cNcoPhaseRst and cNcoUpdateEn are constants in the DUT. + dut: entity WORK.rf_nco_reset (RTL) + port map ( + ConfigClk => ConfigClk, + DataClk => DataClk, + dSysref => dSysref, + dStartNcoReset => dStartNcoReset, + cDac0xNcoUpdateBusy => cDac0xNcoUpdateBusy, + cDac0xNcoUpdateReq => cDac0xNcoUpdateReq, + cDac0xSysrefIntGating => cDac0xSysrefIntGating, + cDac0xSysrefIntReenable => cDac0xSysrefIntReenable, + cDac1xNcoUpdateBusy => cDac1xNcoUpdateBusy, + cDac1xNcoUpdateReq => cDac1xNcoUpdateReq, + cAdc0xNcoUpdateBusy => cAdc0xNcoUpdateBusy, + cAdc0xNcoUpdateReq => cAdc0xNcoUpdateReq, + cAdc2xNcoUpdateBusy => cAdc2xNcoUpdateBusy, + cAdc2xNcoUpdateReq => cAdc2xNcoUpdateReq, + cNcoPhaseRst => cNcoPhaseRst, + cNcoUpdateEn => cNcoUpdateEn, + dNcoResetDone => dNcoResetDone + ); + + main: process + + -- Procedure to sweep the entire SYSREF period. + -- When we strobe dStartNcoReset for one DataClk cycle. NCO reset sequence + -- is initiated. In this procedure, we sweep the dStartNcoReset strobe the + -- entire SYSREF cycle. + procedure SysrefSweep is + constant kSysrefInRfCycles : integer := kSysrefPer/kDataClkPer; + begin + for i in 1 to kSysrefInRfCycles loop + wait until cDac0xSysrefIntGating = '0' for 1 us; + assert cDac0xSysrefIntGating = '0' + report "NCO phase reset does not de-assert" + severity error; + SysrefWait; + DataClkWait(i); + dStartNcoReset <= '0'; + DataClkWait; + dStartNcoReset <= '1'; + DataClkWait; + dStartNcoReset <= '0'; + -- Wait for a minimum of 3 SYSREF period. 1 SYSREF edge is used to + -- initiate NCO reset, 1 SYSREF edge is used to re-enable SYSREF and 1 + -- SYSREF edge is used by RFDC to reset all NCOs. + SysrefWait(3); + end loop; + end procedure; + + begin + + -- Strobe dStartNcoReset across entire SYSREF period. + SysrefSweep; + -- Wait for a minimum of 3 SYSREF cycles to make sure NCO reset is complete. + SysrefWait(3); + + StopSim <= true; + wait; + end process; + + -- Process to mimic RFDC NCO reset + -- This state machine is based of "NCO frequency hopping" section in PG269 + -- (v2.2). Refer to multi-mode subsection for more details. + MimicRfdc: process(ConfigClk) + begin + if falling_edge(ConfigClk) then + cRfdcNcoState <= Idle; + case cRfdcNcoState is + + -- Wait until SYSREF internal gating is asserted. + when Idle => + cWrCount <= 0; + if cDac0xSysrefIntGating = '1' then + cRfdcNcoState <= GateSysref; + end if; + + -- Change cDac0xNcoUpdateBusy to "11" to indicate SYSREF is gated + -- internally when NCO update is requested on DAC tile 228. + -- cDac0xNcoUpdateBusy(0) is set to '1', the SYSREF is gated and + -- cDac0xNcoUpdateBusy(1) is set to '1', to indicate the NCO reset + -- process has started, but not complete. + when GateSysref => + cRfdcNcoState <= GateSysref; + if cDac0xNcoUpdateReq = '1' then + cRfdcNcoState <= UpdateReq; + cDac0xNcoUpdateBusy <= "11"; + end if; + + -- If NCO reset is requested on other tiles, assert NCO update busy on + -- other tiles as well. + when UpdateReq => + cRfdcNcoState <= CheckUpdate; + cDac1xNcoUpdateBusy <= cDac1xNcoUpdateReq; + cAdc0xNcoUpdateBusy <= cAdc0xNcoUpdateReq; + cAdc2xNcoUpdateBusy <= cAdc2xNcoUpdateReq; + + -- It takes 5 clock cycles to update each RFDC internal registers with + -- the used request change. In rf_nco_reset entity, we only want to + -- reset the NCO, which is a single bit. So, it should take only 5 + -- ConfigClk for the update. When the internal register is updated, set + -- cDac0xNcoUpdateBusy(0) to '0'. + when CheckUpdate => + cRfdcNcoState <= CheckUpdate; + if cWrCount > 4 then + cRfdcNcoState <= SysrefEn; + cDac0xNcoUpdateBusy <= "10"; --Indicates that SYSREF is gated. + cDac1xNcoUpdateBusy <= '0'; + cAdc0xNcoUpdateBusy <= '0'; + cAdc2xNcoUpdateBusy <= '0'; + end if; + cWrCount <= cWrCount + 1; + + -- Wait until internal SYSREF gating is disabled. + when SysrefEn => + cWrCount <= 0; + cRfdcNcoState <= SysrefEn; + if cDac0xSysrefIntReenable = '1' then + if cSysrefDlyd(0) = '0' and cSysref = '1' then + cDac0xNcoUpdateBusy <= "00"; --Indicates that NCO reset is complete. + cRfdcNcoState <= ResetDone; + else + cRfdcNcoState <= WaitForSysref; + end if; + end if; + + -- NCO reset is done on the rising edge of SYSREF. When NCO reset is + -- complete, set cDac0xNcoUpdateBusy(1) to '0'. + when WaitForSysref => + cRfdcNcoState <= WaitForSysref; + if cSysrefDlyd(0) = '0' and cSysref = '1' then + cDac0xNcoUpdateBusy <= "00"; --Indicates that NCO reset is complete. + cRfdcNcoState <= ResetDone; + end if; + + -- Wait in this state, until the next NCO reset is requested. + when ResetDone => + cRfdcNcoState <= ResetDone; + if cDac0xSysrefIntGating = '1' then + cRfdcNcoState <= GateSysref; + end if; + end case; + end if; + end process; + + -- SYSREF clock crossing from DataClk to ConfigClk and some pipelines. + ConfigClkSysref: process(ConfigClk) + begin + if rising_edge(ConfigClk) then + cSysref_ms <= dSysref; + cSysref <= cSysref_ms; + cSysrefDlyd <= cSysrefDlyd(cSysrefDlyd'high-1) & cSysref; + cDac0xSysrefIntGatingDlyd <= cDac0xSysrefIntGating; + cNcoPhaseRstDlyd <= cNcoPhaseRstDlyd(cNcoPhaseRstDlyd'high downto 1) + & cDac0xNcoUpdateBusy(1); + end if; + end process; + + -- Assertions + process(ConfigClk) + begin + if falling_edge(ConfigClk) then + + --Check if cNcoPhaseRst is a constant of '1'. + assert cNcoPhaseRst = '1' + report "NCO phase reset signal should be constant." + severity error; + -- Check if cNcoUpdateEn is a constant of "100000". + assert cNcoUpdateEn = "100000" + report "NCO phase reset signal should be constant." + severity error; + -- Check if NCO reset was requested on the rising edge of SYSREF. + if cDac0xSysrefIntGating = '1' and cDac0xSysrefIntGatingDlyd = '0' then + assert cSysrefDlyd = "01" + report "NCO reset did not start on SYSREF rising edge" + severity error; + end if; + + -- We wait for couple of clock cycles after NCO done signal is toggled in + -- from the RFDC. RFDC uses cDac0xNcoUpdateBusy(1) to indicate NCO reset + -- process is done. It is important to wait a minimum of three clock + -- cycles before this check is done. This wait is needed for clock + -- crossing. + if cNcoPhaseRstDlyd(2) = '1' and cNcoPhaseRstDlyd(1) = '0' then + assert dNcoResetDone = '1' + report "NCO Reset done should have been asserted after NCO " & + "reset request is de-asserted" + severity error; + end if; + + end if; + end process; + +end RTL; diff --git a/fpga/usrp3/top/x400/rf/testbench/tb_rf_reset_controller.vhd b/fpga/usrp3/top/x400/rf/testbench/tb_rf_reset_controller.vhd new file mode 100644 index 000000000..31a98fd67 --- /dev/null +++ b/fpga/usrp3/top/x400/rf/testbench/tb_rf_reset_controller.vhd @@ -0,0 +1,436 @@ +-- +-- Copyright 2021 Ettus Research, a National Instruments Brand +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- Module: tb_rf_reset_controller +-- +-- Description: +-- +-- Testbench for rf_reset_controller. +-- + +library IEEE; + use IEEE.std_logic_1164.all; + use IEEE.numeric_std.all; + +library WORK; + use WORK.PkgRFDC_REGS_REGMAP.all; + +entity tb_rf_reset_controller is +end tb_rf_reset_controller; + + +architecture RTL of tb_rf_reset_controller is + + component rf_reset_controller + port ( + ConfigClk : in std_logic; + DataClk : in std_logic; + PllRefClk : in std_logic; + RfClk : in std_logic; + RfClk2x : in std_logic; + DataClk2x : in std_logic; + dAdcResetPulse : in std_logic; + dDacResetPulse : in std_logic; + dAdcDataOutReset_n : out std_logic; + r2AdcFirReset_n : out std_logic; + rAdcRfdcAxiReset_n : out std_logic; + rAdcEnableData : out std_logic; + rAdcGearboxReset_n : out std_logic; + 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; + cSoftwareControl : in std_logic_vector(31 downto 0); + cSoftwareStatus : out std_logic_vector(31 downto 0)); + end component; + + signal cSoftwareStatus : std_logic_vector(31 downto 0); + signal r2AdcFirReset_n : std_logic; + signal r2DacFirReset_n : std_logic; + signal rAdcGearboxReset_n : std_logic; + signal rDacGearboxReset_n : std_logic; + + signal cSoftwareControl : std_logic_vector(31 downto 0) := (others => '0'); + signal dAdcResetPulse : std_logic := '0'; + signal dDacResetPulse : std_logic := '0'; + + constant kSwReset : std_logic := '0'; + constant kTimedReset : std_logic := '1'; + + -- All constants mentioned below are number of the particular clock cycles + -- PllRefClk period. For example, kDataClkCycles is the total number of + -- DataClk cycles in the PllRefClk period. + constant kDataClkCycles : integer := 2; + constant kDataClk2xCycles : integer := 4; + constant kRfClkCycles : integer := 3; + constant kRfClk2xCycles : integer := 6; + constant kConfigPer : time := 25 ns; + -- Make sure the PllRefClk period is a least common multiple of all the other + -- derived clock. + constant kPllRefClkPer : time := 12 ns; + constant kDataClkPer : time := kPllRefClkPer/2; + constant kDataClk2xPer : time := kPllRefClkPer/4; + constant kRfClkPer : time := kPllRefClkPer/3; + constant kRfClk2xPer : time := kPllRefClkPer/6; + + signal pReset : boolean := false; + signal dCount : integer := 0; + signal d2Count : integer := 0; + signal rCount : integer := 0; + signal r2Count : integer := 0; + + signal StopSim : boolean; + signal ConfigClk : std_logic := '1'; + signal RfClk : std_logic := '1'; + signal RfClk2x : std_logic := '1'; + signal DataClk : std_logic := '1'; + signal DataClk2x : std_logic := '1'; + signal PllRefClk : std_logic := '1'; + + signal dAdcDataOutReset_n : std_logic := '0'; + signal dAdcDataOutResetDlyd_n : std_logic := '0'; + signal dDacDataInReset_n : std_logic := '0'; + signal dDacDataInResetDlyd_n : std_logic := '0'; + signal d2DacFirReset_n : std_logic := '0'; + signal d2DacFirResetDlyd_n : std_logic := '0'; + signal rAdcRfdcAxiReset_n : std_logic := '0'; + signal rAdcRfdcAxiResetDlyd_n : std_logic := '0'; + signal rDacRfdcAxiReset_n : std_logic := '0'; + signal rDacRfdcAxiResetDlyd_n : std_logic := '0'; + signal r2AdcFirResetDlyd_n : std_logic := '0'; + signal r2DacFirResetDlyd_n : std_logic := '0'; + + signal ExpectedSwAdcResetDone : std_logic := '0'; + signal ExpectedAdcReset : std_logic := '0'; + signal ExpectedSwDacResetDone : std_logic := '0'; + signal ExpectedDacReset : std_logic := '0'; + signal ExpectedAxiAdcResetOut : std_logic := '0'; + signal ExpectedAxiDacResetOut : std_logic := '0'; + + -- Make sure the wait time for reset done check is at least 10 ConfigClk + -- cycles to account for all clock domain crossings. We also have some status + -- check in the testbench which requires the wait to be additional ConfigClk + -- cycles. This wait is in ConfigClk period. + constant kResetDoneWait : positive := 10; + + procedure ClkWait(signal clk : in std_logic; X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(clk); + end loop; + end procedure ClkWait; + + -- Check phase alignment of reset. We want to make sure the reset is asserted + -- on the 1st rising clock edge after the rising edge of PllRefClk. + procedure CheckAlignment( + signal Clk : in std_logic; -- Synchronous reset clock + signal Reset_n : in std_logic; -- Synchronous reset + signal ResetDlyd_n : inout std_logic; -- Delayed synchronous reset + signal PhaseCount : in integer; -- Phase count used to check alignment + Message : string) is -- Assertion message + begin + + -- Check if reset is asserted on the 1st Clk after the rising edge of + -- PllRefClk. + if falling_edge(Clk) then + ResetDlyd_n <= Reset_n; + if Reset_n = '0' and ResetDlyd_n = '1' then + assert PhaseCount = 1 + report Message & " reset is not asserted in the expected time" severity error; + end if; + end if; + end procedure CheckAlignment; + + -- Procedure to generate phase counter that is used to check the alignment of + -- phase of all clocks related to PllRefClk. + procedure PhaseCounter( + signal Clk : in std_logic; -- Clock related to PllRefClk + signal Reset : in boolean; -- Reset synchronous to PllRefClk + signal PhaseCount : inout integer; -- Phase count of Clk with respect to PllRefClk + ClockCycles : integer) is -- Number of Clk clock cycles in PllRefClk period + begin + if rising_edge(Clk) then + if Reset or PhaseCount = ClockCycles-1 then + PhaseCount <= 0; + else + PhaseCount <= PhaseCount+1; + end if; + end if; + end procedure PhaseCounter; + + procedure CheckExpectedValue( + signal Clk : in std_logic; + signal Actual : in std_logic; + signal Expected : in std_logic; + Message : string) is + begin + if falling_edge(Clk) then + -- Check if the actual value is as expected. + assert std_match(Actual, Expected) + report Message & " not as expected" & LF + & "Expected = " & std_logic'image(Expected) & LF + & "Actual = " & std_logic'image(Actual) severity error; + end if; + end procedure CheckExpectedValue; +begin + + ConfigClk <= not ConfigClk after kConfigPer/2 when not StopSim else '0'; + RfClk <= not RfClk after kRfClkPer/2 when not StopSim else '0'; + RfClk2x <= not RfClk2x after kRfClk2xPer/2 when not StopSim else '0'; + DataClk <= not DataClk after kDataClkPer/2 when not StopSim else '0'; + DataClk2x <= not DataClk2x after kDataClk2xPer/2 when not StopSim else '0'; + PllRefClk <= not PllRefClk after kPllRefClkPer/2 when not StopSim else '0'; + + -- rAdcEnableData is a constant and is not tested. + dut: rf_reset_controller + port map ( + ConfigClk => ConfigClk, + DataClk => DataClk, + PllRefClk => PllRefClk, + RfClk => RfClk, + RfClk2x => RfClk2x, + DataClk2x => DataClk2x, + dAdcResetPulse => dAdcResetPulse, + dDacResetPulse => dDacResetPulse, + dAdcDataOutReset_n => dAdcDataOutReset_n, + r2AdcFirReset_n => r2AdcFirReset_n, + rAdcRfdcAxiReset_n => rAdcRfdcAxiReset_n, + rAdcEnableData => open, + rAdcGearboxReset_n => rAdcGearboxReset_n, + dDacDataInReset_n => dDacDataInReset_n, + r2DacFirReset_n => r2DacFirReset_n, + d2DacFirReset_n => d2DacFirReset_n, + rDacRfdcAxiReset_n => rDacRfdcAxiReset_n, + rDacGearboxReset_n => rDacGearboxReset_n, + cSoftwareControl => cSoftwareControl, + cSoftwareStatus => cSoftwareStatus + ); + + main: process + + -- Procedure to generate software reset and expected DUR reset output. + procedure StrobeReset( + signal TimedReset : out std_logic; -- SW Reset control + signal ExpectedResetOut : out std_logic; -- Expected reset values + signal ExpectedAxiResetOut : out std_logic; -- Expected reset values + signal SwResetStatus : out std_logic; -- Expected SW reset status + SwReset : integer; -- SW Reset control + ResetType : std_logic; -- 0 = SW reset, 1 = UHD timed reset + ResetWait : positive := 1) is -- Wait time for test iteration + begin + if ResetType = kSwReset then + -- Assert software reset control on the rising edge of ConfigClk. Also + -- change the expected status to don't care as the status will change + -- only after few ConfigClk period. + ClkWait(ConfigClk); + TimedReset <= '0'; + cSoftwareControl(SwReset) <= '1'; + SwResetStatus <= '-'; + ExpectedResetOut <= '-'; + ExpectedAxiResetOut <= '-'; + ClkWait(ConfigClk, 1); + SwResetStatus <= '0'; + -- Wait for additional ConfigClk before changing the expected reset + -- value to '0'. This wait is needed to account for pipeline and clock + -- crossing delays. + ClkWait(ConfigClk, 1); + -- Changed expected reset output to '0' (active low). + ExpectedResetOut <= '0'; + ExpectedAxiResetOut <= '0'; + ClkWait(ConfigClk,1); + -- SW reset status should be asserted after 3 ConfigClk periods. This + -- wait is needed to account for pipeline and clock crossings. + SwResetStatus <= '1'; + -- De-assert software reset + ClkWait(ConfigClk,2); + cSoftwareControl(SwReset) <= '0'; + -- Change the expected reset outputs to don't care as it will take few + -- PllRefClk cycles and ConfigClk to DataClock crossing. + ExpectedAxiResetOut <= '-'; + ClkWait(ConfigClk,1); + ExpectedAxiResetOut <= '1'; + -- After few ConfigClk cycles, all reset outputs should be de-asserted. + ClkWait(ConfigClk,1); + ExpectedResetOut <= '-'; + ClkWait(ConfigClk,2); + ExpectedResetOut <= '1'; + -- Wait for ResetWait time before exiting the test iteration. + ClkWait(ConfigClk,ResetWait); + else -- Timed command. + ClkWait(DataClk,ResetWait); + TimedReset <= '1'; + -- RFDC should not be asserted with timed reset. + ExpectedAxiResetOut <= '1'; + -- Strobe the reset pulse only for one DataClk period. + ClkWait(DataClk,1); + TimedReset <= '0'; + ClkWait(PllRefClk,2); + ExpectedResetOut <= '-'; + -- Wait for 3 PllRefClk to account for pipeline delays. + ClkWait(PllRefClk,1); + ExpectedResetOut <= '0'; + ClkWait(PllRefClk,2); + ExpectedResetOut <= '-'; + -- Reset should be asserted only for two PllRefClk cycles. + ClkWait(PllRefClk,2); + ExpectedResetOut <= '1'; + ClkWait(DataClk,ResetWait); -- Wait between test. + end if; + end procedure StrobeReset; + + begin + -- Expected power on reset values. + ExpectedAdcReset <= '0'; + ExpectedAxiAdcResetOut <= '0'; + ExpectedDacReset <= '0'; + ExpectedAxiDacResetOut <= '0'; + + ClkWait(ConfigClk,1); + ClkWait(RfClk,1); + ExpectedAxiAdcResetOut <= '1'; + ExpectedAxiDacResetOut <= '1'; + ClkWait(ConfigClk,1); + ExpectedAdcReset <= '-'; + ExpectedDacReset <= '-'; + ClkWait(ConfigClk,1); + ExpectedAdcReset <= '1'; + ExpectedDacReset <= '1'; + ClkWait(ConfigClk,5); + -- This reset is for simulation to have a common reference to check for + -- clock alignment. + ClkWait(PllRefClk,1); + pReset <= true; + ClkWait(PllRefClk,1); + pReset <= false; + ClkWait(PllRefClk,1); + + --------------------------------------------------------------------------- + -- Test resets from software + --------------------------------------------------------------------------- + + ----------------------------------- + -- ADC + ----------------------------------- + + StrobeReset(dAdcResetPulse, ExpectedAdcReset, ExpectedAxiAdcResetOut, + ExpectedSwAdcResetDone, kADC_RESET, kSwReset, kResetDoneWait); + + -- Align reset to the rising edge of PllRefClk + ClkWait(PllRefClk,1); + StrobeReset(dAdcResetPulse, ExpectedAdcReset, ExpectedAxiAdcResetOut, + ExpectedSwAdcResetDone, kADC_RESET, kTimedReset, kResetDoneWait); + StrobeReset(dAdcResetPulse, ExpectedAdcReset, ExpectedAxiAdcResetOut, + ExpectedSwAdcResetDone, kADC_RESET, kSwReset, kResetDoneWait); + + -- Align reset to the falling edge of PllRefClk. + ClkWait(PllRefClk,1); + ClkWait(DataClk,1); + StrobeReset(dAdcResetPulse, ExpectedAdcReset, ExpectedAxiAdcResetOut, + ExpectedSwAdcResetDone, kADC_RESET, kTimedReset, kResetDoneWait); + + ----------------------------------- + -- DAC + ----------------------------------- + + StrobeReset(dDacResetPulse, ExpectedDacReset, ExpectedAxiDacResetOut, + ExpectedSwDacResetDone, kDAC_RESET, kSwReset, kResetDoneWait); + + -- Align reset to the rising edge of PllRefClk. + ClkWait(PllRefClk,1); + StrobeReset(dDacResetPulse, ExpectedDacReset, ExpectedAxiDacResetOut, + ExpectedSwDacResetDone, kDAC_RESET, kTimedReset, kResetDoneWait); + StrobeReset(dDacResetPulse, ExpectedDacReset, ExpectedAxiDacResetOut, + ExpectedSwDacResetDone, kDAC_RESET, kSwReset, kResetDoneWait); + + -- Align reset to the falling edge of PllRefClk. + ClkWait(PllRefClk,1); + ClkWait(DataClk,1); + StrobeReset(dDacResetPulse, ExpectedDacReset, ExpectedAxiDacResetOut, + ExpectedSwDacResetDone, kDAC_RESET, kTimedReset, kResetDoneWait); + + StopSim <= true; + wait; + end process main; + + ----------------------------------------------------------------------------- + -- Reset from software and UHD timed command + ----------------------------------------------------------------------------- + -- Check if the correct resets are getting asserted when UHD timed reset or + -- software reset is asserted. Except for RFDC AXI-S reset all other resets + -- should be strobed for UHD timed reset. + ----------------------------------------------------------------------------- + + -- Check if the reset done status is getting asserted as expected. + CheckExpectedValue(ConfigClk, cSoftwareStatus(kADC_SEQ_DONE), + ExpectedSwAdcResetDone, "ADC reset done status"); + CheckExpectedValue(ConfigClk, cSoftwareStatus(kDAC_SEQ_DONE), + ExpectedSwDacResetDone, "DAC reset done status"); + + -- Check if resets state in DataClk is as expected. + CheckExpectedValue(DataClk, dAdcDataOutReset_n, ExpectedAdcReset, + "ADC data out reset"); + CheckExpectedValue(DataClk, dDacDataInReset_n, ExpectedDacReset, + "DAC data out reset"); + + -- Check if resets state in DataClk2x is as expected. + CheckExpectedValue(DataClk2x, d2DacFirReset_n, ExpectedDacReset, + "400M interpolator reset"); + + ---- Check if resets state in RfClk2x is as expected. + CheckExpectedValue(RfClk2x, r2AdcFirReset_n, ExpectedAdcReset, + "ADC re-sampler reset"); + CheckExpectedValue(RfClk2x, r2DacFirReset_n, ExpectedDacReset, + "DAC re-sampler reset"); + + ---- Check if resets state in RfClk is as expected. + CheckExpectedValue(RfClk, rAdcRfdcAxiReset_n, ExpectedAxiAdcResetOut, + "ADC RFDC AXI-S interface reset"); + CheckExpectedValue(RfClk, rDacRfdcAxiReset_n, ExpectedAxiDacResetOut, + "DAC RFDC AXI-S interface reset"); + CheckExpectedValue(RfClk, rAdcGearboxReset_n, ExpectedAdcReset, + "ADC gearbox reset"); + CheckExpectedValue(RfClk, rDacGearboxReset_n, ExpectedDacReset, + "DAC gearbox reset"); + + + ----------------------------------------------------------------------------- + -- Reset alignment checks for resets + ----------------------------------------------------------------------------- + + ----------------------------------- + -- Clock counter + ----------------------------------- + -- We use counters to check the phase of all the derived clocks with respect + -- to PllRefClk. Each counter will rollover at the rising edge of PllRefClk. + ----------------------------------- + PhaseCounter(DataClk, pReset, dCount, kDataClkCycles); + PhaseCounter(DataClk2x, pReset, d2Count, kDataClk2xCycles); + PhaseCounter(RfClk, pReset, rCount, kRfClkCycles); + PhaseCounter(RfClk2x, pReset, r2Count, kRfClk2xCycles); + + -- Check for DataClk based synchronous reset alignment to PllRefClk. + CheckAlignment(DataClk, dAdcDataOutReset_n, dAdcDataOutResetDlyd_n, dCount, + "ADC data out"); + CheckAlignment(DataClk, dDacDataInReset_n, dDacDataInResetDlyd_n, dCount, + "DAC data in"); + + -- Check for DataClk2x based synchronous reset alignment to PllRefClk. + CheckAlignment(DataClk2x, d2DacFirReset_n, d2DacFirResetDlyd_n, d2Count, + "400M DAC FIR Filter"); + + -- Check for RfClk based synchronous reset alignment to PllRefClk. + CheckAlignment(RfClk, rAdcRfdcAxiReset_n, rAdcRfdcAxiResetDlyd_n, rCount, + "ADC RFDC reset "); + CheckAlignment(RfClk, rDacRfdcAxiReset_n, rDacRfdcAxiResetDlyd_n, rCount, + "DAC RFDC reset "); + + -- Check for RfClk2x based synchronous reset alignment to PllRefClk. + CheckAlignment(RfClk2x, r2AdcFirReset_n, r2AdcFirResetDlyd_n, r2Count, + "ADC decimation filter reset "); + CheckAlignment(RfClk2x, r2DacFirReset_n, r2DacFirResetDlyd_n, r2Count, + "DAC interpolation filter reset "); + +end RTL; |