aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/rf/400m
diff options
context:
space:
mode:
authorWade Fife <wade.fife@ettus.com>2021-06-08 19:40:46 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 11:56:58 -0500
commit6d3765605262016a80f71e36357f749ea35cbe5a (patch)
tree7d62d6622befd4132ac1ee085effa1426f7f53e5 /fpga/usrp3/top/x400/rf/400m
parentf706b89e6974e28ce76aadeeb06169becc86acba (diff)
downloaduhd-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/400m')
-rw-r--r--fpga/usrp3/top/x400/rf/400m/adc_gearbox_2x4.vhd142
-rw-r--r--fpga/usrp3/top/x400/rf/400m/adc_gearbox_8x4.v105
-rw-r--r--fpga/usrp3/top/x400/rf/400m/dac_gearbox_12x8.vhd233
-rw-r--r--fpga/usrp3/top/x400/rf/400m/dac_gearbox_4x2.v80
-rw-r--r--fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x12.vhd124
-rw-r--r--fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x8.vhd99
-rw-r--r--fpga/usrp3/top/x400/rf/400m/ddc_400m_saturate.vhd81
-rw-r--r--fpga/usrp3/top/x400/rf/400m/duc_400m_saturate.vhd86
-rw-r--r--fpga/usrp3/top/x400/rf/400m/rf_core_400m.v387
9 files changed, 1337 insertions, 0 deletions
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