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/400m | |
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/400m')
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/adc_gearbox_2x4.vhd | 142 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/adc_gearbox_8x4.v | 105 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/dac_gearbox_12x8.vhd | 233 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/dac_gearbox_4x2.v | 80 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x12.vhd | 124 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/dac_gearbox_6x8.vhd | 99 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/ddc_400m_saturate.vhd | 81 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/duc_400m_saturate.vhd | 86 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/rf/400m/rf_core_400m.v | 387 |
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
|