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>


Original-commit: 6d3765605262016a80f71e36357f749ea35cbe5a
This commit is contained in:
Wade Fife
2021-06-10 11:56:58 -05:00
committed by Aaron Rossetto
co-authored by Andrew Moch Daniel Jepson Javier Valenzuela Joerg Hofrichter Kumaran Subramoniam Max Köhler Michael Auchter Paul Butler Hector Rubio
parent bfef20ea45
commit 61782b02d7
205 changed files with 299634 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
--
-- Copyright 2021 Ettus Research, a National Instruments Brand
--
-- SPDX-License-Identifier: LGPL-3.0-or-later
--
-- Module: adc_3_1_clk_converter
--
-- Description:
--
-- This module transfers data from s_axis_aclk to m_axis_aclk. s_axis_aclk
-- must be three times the frequency of m_axis_aclk, and the two clocks must
-- be related (this module requires timing closure across the clock domain
-- boundary).
--
library IEEE;
use IEEE.std_logic_1164.all;
entity adc_3_1_clk_converter is
port(
s_axis_clk : in std_logic;
s_axis_resetn : in std_logic;
s_axis_tdata : in std_logic_vector(47 downto 0);
s_axis_tvalid : in std_logic;
m_axis_clk : in std_logic;
m_axis_resetn : in std_logic;
m_axis_tvalid : out std_logic;
m_axis_tdata : out std_logic_vector(47 downto 0)
);
end entity;
architecture RTL of adc_3_1_clk_converter is
-- To keep the implementation simple, this module does not implement a
-- correct AXIS handshake - it ignores m_axis_tready. adc_100m_bd already had
-- an assumption that the AXIS handshake is unneeded: ddc_saturate does not
-- accept _tready from the following component.
subtype Word_t is std_logic_vector(s_axis_tdata'range);
signal s_axis_tvalid_pipe : std_logic_vector(2 downto 0);
signal s_axis_tdata_reg : Word_t;
-- These _CDC signals will be sampled in the destination clock domain, but
-- will not produce any metastability because the input clocks must be
-- synchronous.
--
-- These signals must be driven by registers not to prevent glitches (as in
-- an asynchronous CDC), but to improve timing closure.
signal s_axis_tvalid_CDC : std_logic;
signal s_axis_tdata_CDC : Word_t;
-- m_axis_clk and s_axis_clk are nominally aligned by their rising edges.
-- Because m_axis_clk is more heavily loaded than s_axis_clk, m_axis_clk has
-- a larger distribution delay, which causes a large hold violation using
-- post-place timing estimates. The Ultrafast method (UG 949) recommends
-- addressing such hold violations when WHS < -0.5 ns. By resampling on the
-- falling edge of the destination clock, we get nominally half a period of
-- setup and half a period of hold. The destination clock delay reduces the
-- hold margin, and increases the setup margin.
signal m_axis_tvalid_fall : std_logic;
signal m_axis_tdata_fall : Word_t;
begin
-- In the source clock domain, we capture incoming valid data and keep a
-- history of _tvalid over the last three clock cycles. If s_axis_tvalid has
-- been asserted once in the last three clock cycles, assert
-- s_axis_tvalid_CDC to be sampled in the output clock domain. The length of
-- s_axis_tvalid_pipe must match the ratio of the clock frequencies (3:1).
InputSampling:
process (s_axis_clk) is
begin
if rising_edge(s_axis_clk) then
if s_axis_tvalid='1' then
s_axis_tdata_reg <= s_axis_tdata;
end if;
s_axis_tdata_CDC <= s_axis_tdata_reg;
if s_axis_resetn='0' then
s_axis_tvalid_pipe <= (others => '0');
s_axis_tvalid_CDC <= '0';
else
s_axis_tvalid_pipe <= s_axis_tvalid_pipe(1 downto 0) & s_axis_tvalid;
if (s_axis_tvalid_pipe /= "000") then
s_axis_tvalid_CDC <= '1';
else
s_axis_tvalid_CDC <= '0';
end if;
end if;
end if;
end process InputSampling;
FallingEdgeSampling:
process (m_axis_clk) is
begin
if falling_edge(m_axis_clk) then
m_axis_tvalid_fall <= s_axis_tvalid_CDC;
m_axis_tdata_fall <= s_axis_tdata_CDC;
end if;
end process FallingEdgeSampling;
OutputRegisters:
process (m_axis_clk) is
begin
if rising_edge(m_axis_clk) then
m_axis_tdata <= m_axis_tdata_fall;
if m_axis_resetn='0' then
m_axis_tvalid <= '0';
else
m_axis_tvalid <= m_axis_tvalid_fall;
end if;
end if;
end process OutputRegisters;
end RTL;
+120
View File
@@ -0,0 +1,120 @@
//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: adc_gearbox_2x1
//
// Description:
//
// Gearbox ADC data from 2 SPC to 1 SPC and corresponding 2x clock to 1x
// clock. Also implement data swapping to format packets to fit the FIR
// filter input requirements.
//
// This modules incurs one clk1x cycle of delay on the data and valid signals
// from input on the 1x domain to output on the 2x domain.
//
`default_nettype none
module adc_gearbox_2x1 (
input wire clk1x,
input wire reset_n_1x,
// Data is _presumed_ to be packed [Sample1, Sample0] (Sample0 in LSBs).
input wire [31:0] adc_q_in_1x,
input wire [31:0] adc_i_in_1x,
input wire valid_in_1x,
// De-assert enable_1x to clear the data synchronously from this module.
input wire enable_1x,
input wire clk2x,
// Data is packed [Q,I] (I in LSBs) when swap_iq_1x is '0'.
input wire swap_iq_2x,
output wire [31:0] adc_out_2x,
output wire valid_out_2x
);
// Re-create the 1x clock in the 2x domain to produce a deterministic
// crossing.
reg toggle_1x, toggle_2x = 1'b0, toggle_2x_dly = 1'b0, valid_2x = 1'b0, valid_dly_2x = 1'b0;
reg [31:0] data_out_2x = 32'b0, adc_q_data_in_2x = 32'b0, adc_i_data_in_2x = 32'b0;
// Create a toggle in the 1x clock domain (clock divider /2).
always @(posedge clk1x or negedge reset_n_1x) begin
if ( ! reset_n_1x) begin
toggle_1x <= 1'b0;
end else begin
toggle_1x <= ! toggle_1x;
end
end
// clk1x and clk2x are nominally aligned on their rising edges, but clk2x is
// more heavily loaded, which results in a later arrival time. That late
// arrival causes large estimated hold violations after place. The Ultrafast
// method (UG 949) suggests fixing post-place hold violations that are worse
// than -0.5 ns.
// Resampling 1x signals on the falling edge of clk2x provides nominally half
// a period of setup and half a period of hold. The late arrival of clk2x
// shifts some of that margin away from hold slack and into setup slack.
reg toggle_2x_fall = 1'b0;
reg [31:0] adc_q_in_2x_fall = 32'b0;
reg [31:0] adc_i_in_2x_fall = 32'b0;
reg valid_in_2x_fall = 1'b0;
reg enable_2x_fall = 1'b0;
always @(negedge clk2x) begin
toggle_2x_fall <= toggle_1x;
adc_q_in_2x_fall <= adc_q_in_1x;
adc_i_in_2x_fall <= adc_i_in_1x;
valid_in_2x_fall <= valid_in_1x;
enable_2x_fall <= enable_1x;
end
// Transfer the toggle from the 1x to the 2x domain. Delay the toggle in the
// 2x domain by one cycle and compare it to the non-delayed version. When
// they differ, push data_in[15:0] onto the output. When the match, push
// [31:16] onto the output. The datasheet is unclear on the exact
// implementation.
//
// It is safe to not reset this domain because all of the input signals will
// be cleared by the 1x reset. Safe default values are assigned to all these
// registers.
always @(posedge clk2x) begin
toggle_2x <= toggle_2x_fall;
toggle_2x_dly <= toggle_2x;
adc_q_data_in_2x <= adc_q_in_2x_fall;
adc_i_data_in_2x <= adc_i_in_2x_fall;
// Place Q in the MSBs, I in the LSBs by default, unless swapped = 1.
if (valid_2x) begin
if (swap_iq_2x) begin
if (toggle_2x != toggle_2x_dly) begin
data_out_2x[31:16] <= adc_i_data_in_2x[15:0];
data_out_2x[15: 0] <= adc_q_data_in_2x[15:0];
end else begin
data_out_2x[31:16] <= adc_i_data_in_2x[31:16];
data_out_2x[15: 0] <= adc_q_data_in_2x[31:16];
end
end else begin
if (toggle_2x != toggle_2x_dly) begin
data_out_2x[31:16] <= adc_q_data_in_2x[15:0];
data_out_2x[15: 0] <= adc_i_data_in_2x[15:0];
end else begin
data_out_2x[31:16] <= adc_q_data_in_2x[31:16];
data_out_2x[15: 0] <= adc_i_data_in_2x[31:16];
end
end
end else begin
data_out_2x <= 32'b0;
end
// Valid is simply a transferred version of the 1x clock's valid. Delay it
// one more cycle to align outputs.
valid_2x <= valid_in_2x_fall && enable_2x_fall;
valid_dly_2x <= valid_2x;
end
assign adc_out_2x = data_out_2x;
assign valid_out_2x = valid_dly_2x;
endmodule
`default_nettype wire
+143
View File
@@ -0,0 +1,143 @@
--
-- Copyright 2021 Ettus Research, a National Instruments Brand
--
-- SPDX-License-Identifier: LGPL-3.0-or-later
--
-- Module: dac_1_3_clk_converter
--
-- Description:
--
-- This module transfers data from s_axis_aclk to m_axis_aclk. m_axis_aclk
-- must be three times the frequency of s_axis_aclk, and the two clocks must
-- be related (this module requires timing closure across the clock domain
-- boundary).
--
library IEEE;
use IEEE.std_logic_1164.all;
entity dac_1_3_clk_converter is
port(
s_axis_aclk : in std_logic;
s_axis_aresetn : in std_logic;
s_axis_tvalid : in std_logic;
s_axis_tdata : in std_logic_vector(31 downto 0);
s_axis_tready : out std_logic := '1';
m_axis_aclk : in std_logic;
m_axis_aresetn : in std_logic;
m_axis_tready : in std_logic;
m_axis_tdata : out std_logic_vector(31 downto 0);
m_axis_tvalid : out std_logic
);
end entity dac_1_3_clk_converter;
architecture RTL of dac_1_3_clk_converter is
-- I was unable to think of a simple implementation that implements a correct
-- AXIS handshake on both ports. All my ideas became equivalent to a two
-- clock FIFO (although the clocks are synchronous, so the write-to-read
-- latency would have been certain).
--
-- We don't expect the DAC to ever hold off incoming data, and dac_100m_bd
-- already has the AXIS handshake disconnected: the FIR is configured to
-- disallow back pressure - it has no m_axis_data_tready pin.
--
-- I'm going with the simple, but not strictly correct, implementation.
-- s_axis_tready will be constantly true, even when it shouldn't be. The
-- bottom line is this component is likely useless for any application but
-- dac_100m_bd.
type output_fsm is (
idle,
got_data,
-- The recovery state of delay ensures that we don't re-use an old input
-- valid signal (remember the output clock is 3x the frequency of the input
-- clock)
recovery
);
subtype word is std_logic_vector(s_axis_tdata'range);
signal output_state_mclk : output_fsm;
signal axis_tdata_sclk : word;
signal axis_tvalid_sclk : std_logic;
signal axis_tdata_mclk : word;
signal axis_tvalid_mclk : std_logic;
begin
s_axis_tready <= '1';
input_valid_register:
process(s_axis_aclk, s_axis_aresetn) is
begin
if s_axis_aresetn='0' then
axis_tvalid_sclk <= '0';
elsif rising_edge(s_axis_aclk) then
axis_tvalid_sclk <= s_axis_tvalid;
end if;
end process;
input_data_register:
process (s_axis_aclk) is
begin
if rising_edge(s_axis_aclk) then
axis_tdata_sclk <= s_axis_tdata;
end if;
end process input_data_register;
-- These CDC registers will not become metastable because the two clock
-- domains are related.
cdc_input_valid_register:
process (m_axis_aclk, m_axis_aresetn) is
begin
if m_axis_aresetn='0' then
axis_tvalid_mclk <= '0';
elsif rising_edge(m_axis_aclk) then
axis_tvalid_mclk <= axis_tvalid_sclk;
end if;
end process cdc_input_valid_register;
cdc_input_data_register:
process (m_axis_aclk) is
begin
if rising_edge(m_axis_aclk) then
axis_tdata_mclk <= axis_tdata_sclk;
end if;
end process cdc_input_data_register;
output_data_register:
process (m_axis_aclk) is
begin
if rising_edge(m_axis_aclk) then
if output_state_mclk=idle then
m_axis_tdata <= axis_tdata_mclk;
end if;
end if;
end process output_data_register;
fsm: process(m_axis_aresetn, m_axis_aclk) is
begin
if m_axis_aresetn='0' then
output_state_mclk <= idle;
m_axis_tvalid <= '0';
elsif rising_edge(m_axis_aclk) then
m_axis_tvalid <= '0';
case output_state_mclk is
when idle =>
if axis_tvalid_mclk='1' then
output_state_mclk <= got_data;
end if;
when got_data =>
m_axis_tvalid <= '1';
output_state_mclk <= recovery;
when recovery =>
output_state_mclk <= idle;
end case;
end if;
end process fsm;
end RTL;
+118
View File
@@ -0,0 +1,118 @@
--
-- Copyright 2021 Ettus Research, a National Instruments Brand
--
-- SPDX-License-Identifier: LGPL-3.0-or-later
--
-- Module: dac_2_1_clk_converter
--
-- Description:
--
-- This module transfers data from s_axis_aclk to m_axis_aclk. s_axis_aclk
-- must be two times the frequency of m_axis_aclk, and the two clocks must be
-- related (this module requires timing closure across the clock domain
-- boundary).
--
library IEEE;
use IEEE.std_logic_1164.all;
entity dac_2_1_clk_converter is
port (
s_axis_aclk : in std_logic;
s_axis_aresetn : in std_logic;
s_axis_tvalid : in std_logic;
s_axis_tdata : in std_logic_vector(63 downto 0);
m_axis_aclk : in std_logic;
m_axis_aresetn : in std_logic;
m_axis_tready : in std_logic;
m_axis_tvalid : out std_logic;
m_axis_tdata : out std_logic_vector(63 downto 0)
);
end entity dac_2_1_clk_converter;
architecture RTL of dac_2_1_clk_converter is
-- To keep the implementation simple, this module does not implement a
-- correct AXIS handshake - it ignores m_axis_tready. dac_100m_bd already had
-- an assumption that the AXIS handshake is unneeded: duc_saturate does not
-- accept _tready from the following component. Also, registered_dac_data has
-- never accepted _tready from dac_2_1_clk_converter, so dac_100m_bd has
-- never supported complete AXIS dataflow.
subtype Word_t is std_logic_vector(s_axis_tdata'range);
signal s_axis_tvalid_pipe : std_logic_vector(1 downto 0);
signal s_axis_tdata_reg : Word_t;
-- These _CDC signals will be sampled in the destination clock domain, but
-- will not produce any metastability because the input clocks must be
-- synchronous.
--
-- These signals must be driven by registers not to prevent glitches (as in
-- an asynchronous CDC), but to improve timing closure.
signal s_axis_tvalid_CDC : std_logic;
signal s_axis_tdata_CDC : Word_t;
-- m_axis_aclk and s_axis_aclk are nominally aligned by their rising edges.
-- Because m_axis_aclk is more heavily loaded than s_axis_aclk, m_axis_aclk
-- has a larger distribution delay, which causes a large hold violation using
-- post-place timing estimates. The Ultrafast method (UG 949) recommends
-- addressing such hold violations when WHS < -0.5 ns. By resampling on the
-- falling edge of the destination clock, we get nominally half a period of
-- setup and half a period of hold. The destination clock delay reduces the
-- hold margin, and increases the setup margin.
signal m_axis_tvalid_fall : std_logic;
signal m_axis_tdata_fall : Word_t;
begin
-- In the source clock domain, we capture incoming valid data and keep a
-- history of _tvalid over the last three clock cycles. If s_axis_tvalid has
-- been asserted once in the last three clock cycles, assert
-- s_axis_tvalid_CDC to be sampled in the output clock domain. The length of
-- s_axis_tvalid_pipe must match the ratio of the clock frequencies (2:1).
InputSampling:
process (s_axis_aclk) is
begin
if rising_edge(s_axis_aclk) then
if s_axis_tvalid='1' then
s_axis_tdata_reg <= s_axis_tdata;
end if;
s_axis_tdata_CDC <= s_axis_tdata_reg;
if s_axis_aresetn='0' then
s_axis_tvalid_pipe <= (others => '0');
s_axis_tvalid_CDC <= '0';
else
s_axis_tvalid_pipe <= s_axis_tvalid_pipe(0) & s_axis_tvalid;
if (s_axis_tvalid_pipe /= "00") then
s_axis_tvalid_CDC <= '1';
else
s_axis_tvalid_CDC <= '0';
end if;
end if;
end if;
end process InputSampling;
FallingEdgeSampling:
process (m_axis_aclk) is
begin
if falling_edge(m_axis_aclk) then
m_axis_tvalid_fall <= s_axis_tvalid_CDC;
m_axis_tdata_fall <= s_axis_tdata_CDC;
end if;
end process FallingEdgeSampling;
OutputRegisters:
process (m_axis_aclk) is
begin
if rising_edge(m_axis_aclk) then
m_axis_tdata <= m_axis_tdata_fall;
if m_axis_aresetn='0' then
m_axis_tvalid <= '0';
else
m_axis_tvalid <= m_axis_tvalid_fall;
end if;
end if;
end process OutputRegisters;
end RTL;
+83
View File
@@ -0,0 +1,83 @@
--
-- Copyright 2021 Ettus Research, a National Instruments Brand
--
-- SPDX-License-Identifier: LGPL-3.0-or-later
--
-- Module: ddc_saturate
--
-- Description:
--
-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See
-- comments below for full description.
--
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity ddc_saturate is
port(
Clk : in std_logic;
cDataIn : in std_logic_vector(47 downto 0); -- [Q,I] (I in LSBs)
cDataValidIn : in std_logic;
cDataOut : out std_logic_vector(31 downto 0); -- [Q,I] (I in LSBs)
cDataValidOut : out std_logic
);
end ddc_saturate;
architecture RTL of ddc_saturate is
signal cDataOutI : std_logic_vector(15 downto 0) := (others => '0');
signal cDataOutQ : std_logic_vector(15 downto 0) := (others => '0');
begin
-----------------------------------------------------------------------------
-- Saturation
--
-- The output of the Xilinx FIR Compiler has already been rounded on the LSB
-- side, but hasn't been saturated on the MSB side.
-- Coefficients = 18 bit, 1 integer bit (1.17)
-- Data In = 16 bits, 1 integer bit (1.15)
-- 1.17 * 1.15 = 2.32, and the Xilinx FIR core rounds to 2.15
-- Data Out = 17 bits, 2 integer bits (2.15), with 17 LSBs already rounded
-- off inside the FIR core.
-- We need to manually saturate the 2.15 number back to a 1.15 number
--
-- If 2 MSBs = 00, output <= input without MSB, e.g. positive number < 1
-- If 2 MSBs = 01, output <= 0.111111111111111, e.g. positive number >= 1
-- If 2 MSBs = 10, output <= 1.000000000000000, e.g. negative number < -1
-- If 2 MSBs = 11, output <= input without MSB, e.g. negative number >= -1
-----------------------------------------------------------------------------
Saturation:
process(Clk)
begin
if rising_edge(Clk) then
-- Pipeline data valid to match the data
cDataValidOut <= cDataValidIn;
-- I, from cDataIn(16 downto 0)
if cDataIn(16 downto 15) = "01" then
cDataOutI <= "0111111111111111";
elsif cDataIn(16 downto 15) = "10" then
cDataOutI <= "1000000000000000";
else
cDataOutI <= cDataIn(15 downto 0);
end if;
-- Q, from cDataIn(40 downto 24)
if cDataIn(40 downto 39) = "01" then
cDataOutQ <= "0111111111111111";
elsif cDataIn(40 downto 39) = "10" then
cDataOutQ <= "1000000000000000";
else
cDataOutQ <= cDataIn(39 downto 24);
end if;
end if;
end process Saturation;
cDataOut <= cDataOutQ & cDataOutI;
end RTL;
+87
View File
@@ -0,0 +1,87 @@
--
-- Copyright 2021 Ettus Research, a National Instruments Brand
--
-- SPDX-License-Identifier: LGPL-3.0-or-later
--
-- Module: duc_saturate
--
-- Description:
--
-- Saturation logic for reducing 2x24 bit words to 2x16 bit words. See
-- comments below for full description.
--
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity duc_saturate is
port(
Clk : in std_logic;
cDataIn : in std_logic_vector(47 downto 0);
cDataValidIn : in std_logic;
cReadyForInput : out std_logic;
cDataOut : out std_logic_vector(31 downto 0);
cDataValidOut : out std_logic := '0'
);
end duc_saturate;
architecture RTL of duc_saturate is
signal cDataOutI : std_logic_vector(15 downto 0) := (others => '0');
signal cDataOutQ : std_logic_vector(15 downto 0) := (others => '0');
begin
-----------------------------------------------------------------------------
-- Saturation
--
-- The output of the Xilinx FIR Compiler has already been rounded on the LSB
-- side, but hasn't been saturated on the MSB side.
-- Coefficients = 18 bit, 1 integer bit (1.17)
-- Data In = 16 bits, 1 integer bit (1.15)
-- Xilinx FIR core rounds to output to 3.31. The filter coefficients has a
-- gain of 3 to compensate for the amplitude loss in interpolation, the
-- Xilinx FIR core rounds the output to 3.15.
-- Data Out = 18 bits, 3 integer bits (3.15), with 16 LSBs already rounded
-- off inside the FIR core.
-- We need to manually saturate the 3.15 number back to a 1.15 number
--
-- If 3 MSBs = 000, output <= input without MSB, e.g. positive number < 1
-- If 3 MSBs = 0x1/01x, output <= 0.111111111111111, e.g. positive number >= 1
-- If 3 MSBs = 1x0/10x, output <= 1.000000000000000, e.g. negative number < -1
-- If 3 MSBs = 111, output <= input without MSB, e.g. negative number >= -1
-----------------------------------------------------------------------------
Saturation:
process(Clk)
begin
if rising_edge(Clk) then
-- Pipeline data valid to match the data
cDataValidOut <= cDataValidIn;
-- I, from cDataIn(17 downto 0)
if cDataIn(17) = '0' and cDataIn(16 downto 15) /= "00" then
cDataOutI <= "0111111111111111";
elsif cDataIn(17) = '1' and cDataIn(16) /= cDataIn(15) then
cDataOutI <= "1000000000000000";
else
cDataOutI <= cDataIn(15 downto 0);
end if;
-- Q, from cDataIn(41 downto 24)
if cDataIn(41) = '0' and cDataIn(40 downto 39) /= "00" then
cDataOutQ <= "0111111111111111";
elsif cDataIn(41) = '1' and
(not (cDataIn(40 downto 39) = "11")) then
cDataOutQ <= "1000000000000000";
else
cDataOutQ <= cDataIn(39 downto 24);
end if;
end if;
end process Saturation;
cDataOut <= cDataOutQ & cDataOutI;
cReadyForInput <= '1';
end RTL;
+362
View File
@@ -0,0 +1,362 @@
//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rf_core_100m
//
// Description:
//
// Top-level wrapper for the ADC/DAC processing logic. One of these wrappers
// exists for every supported Data Rate. An instance of this core should
// exist per dboard.
//
// Data/RF Specs:
// DBs: 1
// RX/DB: 2
// TX/DB: 2
// Data Rate: 122.88 or 125 MSps @ 1 SPC
//
// Input Clocks, all aligned to one another and coming from same MMCM
// rfdc_clk: 184.32 or 187.5 MHz (3x pll_ref_clk)
// rfdc_clk_2x: 368.64 or 375 MHz (6x pll_ref_clk)
// data_clk: 122.88 or 125 MHz (2x pll_ref_clk)
//
`default_nettype none
module rf_core_100m (
//---------------------------------------------------------------------------
// Clocking
//---------------------------------------------------------------------------
// Main Clock Inputs
input wire rfdc_clk,
input wire rfdc_clk_2x,
input wire data_clk,
input wire data_clk_2x, // Unused, kept for rf_core_* interface consistency.
// AXI4-Lite Configuration Clock
// This clock is used to synchronize status bits for the RFDC registers in
// the AXI-S clock domain.
input wire s_axi_config_clk,
//---------------------------------------------------------------------------
// RFDC Data Interfaces
//---------------------------------------------------------------------------
// All ports here are in the rfdc_clk domain.
// ADC
input wire [31:0] adc_data_in_i_tdata_0,
output wire adc_data_in_i_tready_0,
input wire adc_data_in_i_tvalid_0,
input wire [31:0] adc_data_in_q_tdata_0,
output wire adc_data_in_q_tready_0,
input wire adc_data_in_q_tvalid_0,
input wire [31:0] adc_data_in_i_tdata_1,
output wire adc_data_in_i_tready_1,
input wire adc_data_in_i_tvalid_1,
input wire [31:0] adc_data_in_q_tdata_1,
output wire adc_data_in_q_tready_1,
input wire adc_data_in_q_tvalid_1,
// DAC
output wire [63:0] dac_data_out_tdata_0,
input wire dac_data_out_tready_0,
output wire dac_data_out_tvalid_0,
output wire [63:0] dac_data_out_tdata_1,
input wire dac_data_out_tready_1,
output wire dac_data_out_tvalid_1,
//---------------------------------------------------------------------------
// User Data Interfaces
//---------------------------------------------------------------------------
// All ports here are in the data_clk domain.
// ADC
output wire [31:0] adc_data_out_tdata_0, // Packed [Q,I] with Q in MSBs
output wire adc_data_out_tvalid_0,
output wire [31:0] adc_data_out_tdata_1, // Packed [Q,I] with Q in MSBs
output wire adc_data_out_tvalid_1,
// DAC
input wire [31:0] dac_data_in_tdata_0, // Packed [Q,I] with Q in MSBs
output wire dac_data_in_tready_0,
input wire dac_data_in_tvalid_0,
input wire [31:0] dac_data_in_tdata_1, // Packed [Q,I] with Q in MSBs
output wire dac_data_in_tready_1,
input wire dac_data_in_tvalid_1,
//---------------------------------------------------------------------------
// Miscellaneous
//---------------------------------------------------------------------------
// Invert I/Q control signals from RFDC to DSP chain.
input wire [3:0] invert_adc_iq_rclk2,
input wire [3:0] invert_dac_iq_rclk2,
// Control/status vectors from/to RFDC.
// Notice these are all in the s_axi_config_clk domain.
output wire [15:0] dsp_info_sclk,
output wire [15:0] axi_status_sclk,
// Resets.
input wire adc_data_out_resetn_dclk,
input wire adc_enable_data_rclk,
input wire adc_rfdc_axi_resetn_rclk,
input wire dac_data_in_resetn_dclk,
input wire dac_data_in_resetn_rclk,
input wire dac_data_in_resetn_rclk2x,
input wire fir_resetn_rclk2x,
// Version (Constant)
output wire [95:0] version_info
);
`include "../../regmap/rfdc_regs_regmap_utils.vh"
`include "../../regmap/versioning_regs_regmap_utils.vh"
`include "../../regmap/versioning_utils.vh"
// Fixed for this implementation.
localparam NUM_ADC_CHANNELS = 2;
localparam NUM_DAC_CHANNELS = 2;
// ADC data interface from RFDC.
wire [31:0] adc_data_in_i_tdata [0:7]; // 2 SPC (I)
wire [31:0] adc_data_in_q_tdata [0:7]; // 2 SPC (Q)
wire [ 7:0] adc_data_in_i_tready;
wire [ 7:0] adc_data_in_q_tready;
wire [ 7:0] adc_data_in_i_tvalid;
wire [ 7:0] adc_data_in_q_tvalid;
// DAC data interface to RFDC.
wire [63:0] dac_data_out_tdata [0:7]; // 2 SPC (I + Q)
wire [ 7:0] dac_data_out_tready;
wire [ 7:0] dac_data_out_tvalid;
// ADC data interface to user.
wire [31:0] adc_data_out_tdata [0:7]; // 1 SPC (I + Q)
wire [ 7:0] adc_data_out_tready;
wire [ 7:0] adc_data_out_tvalid;
// DAC data interface from user.
wire [31:0] dac_data_in_tdata_preswap [0:7]; // 1 SPC (I + Q)
wire [31:0] dac_data_in_tdata [0:7]; // 1 SPC (I + Q)
wire [ 7:0] dac_data_in_tready;
wire [ 7:0] dac_data_in_tvalid;
wire [ 7:0] invert_dac_iq_dclk;
wire [15:0] axi_status;
//---------------------------------------------------------------------------
// Resets, Debug and Misc.
//---------------------------------------------------------------------------
// Group all these status bits together. They don't toggle frequently so data
// coherency is not an issue here.
// Using constants for DB0 since the bits are the 16 LSBs in a 32-bit vector.
// DB1 simply uses the 16 MSBs when wiring the status vector.
assign axi_status[USER_ADC_TREADY_MSB :USER_ADC_TREADY ] = adc_data_out_tready [1:0];
assign axi_status[USER_ADC_TVALID_MSB :USER_ADC_TVALID ] = adc_data_out_tvalid [1:0];
assign axi_status[RFDC_ADC_I_TVALID_MSB:RFDC_ADC_I_TVALID] = adc_data_in_i_tvalid[1:0];
assign axi_status[RFDC_ADC_Q_TVALID_MSB:RFDC_ADC_Q_TVALID] = adc_data_in_q_tvalid[1:0];
assign axi_status[RFDC_ADC_I_TREADY_MSB:RFDC_ADC_I_TREADY] = adc_data_in_i_tready[1:0];
assign axi_status[RFDC_ADC_Q_TREADY_MSB:RFDC_ADC_Q_TREADY] = adc_data_in_q_tready[1:0];
assign axi_status[RFDC_DAC_TVALID_MSB :RFDC_DAC_TVALID ] = dac_data_out_tvalid [1:0];
assign axi_status[RFDC_DAC_TREADY_MSB :RFDC_DAC_TREADY ] = dac_data_out_tready [1:0];
synchronizer #(
.WIDTH (16),
.STAGES (2),
.INITIAL_VAL (0),
.FALSE_PATH_TO_IN (1)
) synchronizer_axis_status (
.clk (s_axi_config_clk),
.rst (1'b0),
.in (axi_status),
.out (axi_status_sclk)
);
// Drive the DSP info vector with information on this specific DSP chain.
assign dsp_info_sclk[FABRIC_DSP_BW_MSB :FABRIC_DSP_BW] = FABRIC_DSP_BW_100M;
assign dsp_info_sclk[FABRIC_DSP_RX_CNT_MSB:FABRIC_DSP_RX_CNT] = NUM_ADC_CHANNELS;
assign dsp_info_sclk[FABRIC_DSP_TX_CNT_MSB:FABRIC_DSP_TX_CNT] = NUM_DAC_CHANNELS;
//---------------------------------------------------------------------------
// ADC Post-Processing
//---------------------------------------------------------------------------
// Data comes from the RFDC as 2 SPC, separate streams for each channel and
// I/Q.
assign adc_data_in_i_tdata[0] = adc_data_in_i_tdata_0;
assign adc_data_in_q_tdata[0] = adc_data_in_q_tdata_0;
assign adc_data_in_i_tdata[1] = adc_data_in_i_tdata_1;
assign adc_data_in_q_tdata[1] = adc_data_in_q_tdata_1;
assign adc_data_in_i_tready_0 = adc_data_in_i_tready[0];
assign adc_data_in_i_tvalid[0] = adc_data_in_i_tvalid_0;
assign adc_data_in_q_tready_0 = adc_data_in_q_tready[0];
assign adc_data_in_q_tvalid[0] = adc_data_in_q_tvalid_0;
assign adc_data_in_i_tready_1 = adc_data_in_i_tready[1];
assign adc_data_in_i_tvalid[1] = adc_data_in_i_tvalid_1;
assign adc_data_in_q_tready_1 = adc_data_in_q_tready[1];
assign adc_data_in_q_tvalid[1] = adc_data_in_q_tvalid_1;
// ADC Data from the RFDC arrives here as 2 SPC with separate I and Q
// streams. It leaves the adc_100m_bd as 1 SPC with I and Q packed into a
// single 32 bit word.
genvar adc_num;
generate
for (adc_num=0; adc_num < (NUM_ADC_CHANNELS); adc_num = adc_num + 1)
begin : adc_gen
adc_100m_bd adc_100m_bd_gen (
.adc_data_out_resetn_dclk (adc_data_out_resetn_dclk),
.data_clk (data_clk),
.enable_data_to_fir_rclk (adc_enable_data_rclk),
.fir_resetn_rclk2x (fir_resetn_rclk2x),
.rfdc_adc_axi_resetn_rclk (adc_rfdc_axi_resetn_rclk),
.rfdc_clk (rfdc_clk),
.rfdc_clk_2x (rfdc_clk_2x),
.swap_iq_2x (invert_adc_iq_rclk2 [adc_num]),
.adc_data_out_tvalid (adc_data_out_tvalid [adc_num]),
.adc_data_out_tdata (adc_data_out_tdata [adc_num]),
.adc_i_data_in_tvalid (adc_data_in_i_tvalid[adc_num]),
.adc_i_data_in_tready (adc_data_in_i_tready[adc_num]),
.adc_i_data_in_tdata (adc_data_in_i_tdata [adc_num]),
.adc_q_data_in_tvalid (adc_data_in_q_tvalid[adc_num]),
.adc_q_data_in_tready (adc_data_in_q_tready[adc_num]),
.adc_q_data_in_tdata (adc_data_in_q_tdata [adc_num])
);
end
endgenerate
// Data is released to the user as 1 SPC, separate streams for each channel.
assign adc_data_out_tdata_0 = adc_data_out_tdata[0];
assign adc_data_out_tdata_1 = adc_data_out_tdata[1];
// There is no tready going to the ADC (one has to be always ready for ADC
// data), but it is still a component of the axi_status vector as a generic
// AXI stream status. Report 1'b1 to the status vector consistent with being
// always ready
assign adc_data_out_tready[0] = 1'b1;
assign adc_data_out_tvalid_0 = adc_data_out_tvalid[0];
assign adc_data_out_tready[1] = 1'b1;
assign adc_data_out_tvalid_1 = adc_data_out_tvalid[1];
//---------------------------------------------------------------------------
// DAC Pre-Processing
//---------------------------------------------------------------------------
// Data comes from the user as 1 SPC, separate streams for each channel.
assign dac_data_in_tdata_preswap[0] = dac_data_in_tdata_0;
assign dac_data_in_tdata_preswap[1] = dac_data_in_tdata_1;
assign dac_data_in_tready_0 = dac_data_in_tready[0];
assign dac_data_in_tvalid[0] = dac_data_in_tvalid_0;
assign dac_data_in_tready_1 = dac_data_in_tready[1];
assign dac_data_in_tvalid[1] = dac_data_in_tvalid_1;
// Optionally swap IQ data positions in the vector. First cross the swap
// vector over to the data_clk domain.
synchronizer #(
.WIDTH (8),
.STAGES (2),
.INITIAL_VAL (0),
.FALSE_PATH_TO_IN (1)
) synchronizer_invert_dac_iq (
.clk (data_clk),
.rst (1'b0),
.in (invert_dac_iq_rclk2),
.out (invert_dac_iq_dclk)
);
genvar dac_num;
generate
for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1)
begin : dac_swap_gen
assign dac_data_in_tdata[dac_num][15:00] = invert_dac_iq_dclk[dac_num] ?
(dac_data_in_tdata_preswap[dac_num][31:16]) : (dac_data_in_tdata_preswap[dac_num][15:0]);
assign dac_data_in_tdata[dac_num][31:16] = invert_dac_iq_dclk[dac_num] ?
(dac_data_in_tdata_preswap[dac_num][15:00]) : (dac_data_in_tdata_preswap[dac_num][31:16]);
end
endgenerate
// These streams are then interpolated by dac_100m_bd, and form a single
// stream per channel, 2 SPC, packed: MSB [Sample1Q, Sample1I, Sample0Q,
// Sample0I] LSB.
generate
for (dac_num=0; dac_num < (NUM_DAC_CHANNELS); dac_num = dac_num + 1)
begin : dac_gen
dac_100m_bd dac_100m_bd_gen (
.dac_data_in_resetn_dclk (dac_data_in_resetn_dclk),
.dac_data_in_resetn_rclk (dac_data_in_resetn_rclk),
.dac_data_in_resetn_rclk2x (dac_data_in_resetn_rclk2x),
.data_clk (data_clk),
.rfdc_clk (rfdc_clk),
.rfdc_clk_2x (rfdc_clk_2x),
.dac_data_out_tdata (dac_data_out_tdata [dac_num]),
.dac_data_out_tvalid (dac_data_out_tvalid[dac_num]),
.dac_data_out_tready (dac_data_out_tready[dac_num]),
.dac_data_in_tdata (dac_data_in_tdata [dac_num]),
.dac_data_in_tvalid (dac_data_in_tvalid [dac_num]),
.dac_data_in_tready (dac_data_in_tready [dac_num])
);
end
endgenerate
// Data is released to the RFDC as 2 SPC, separate streams per channel (I/Q
// together).
assign dac_data_out_tdata_0 = dac_data_out_tdata[0];
assign dac_data_out_tdata_1 = dac_data_out_tdata[1];
assign dac_data_out_tready[0] = dac_data_out_tready_0;
assign dac_data_out_tvalid_0 = dac_data_out_tvalid[0];
assign dac_data_out_tready[1] = dac_data_out_tready_1;
assign dac_data_out_tvalid_1 = dac_data_out_tvalid[1];
//---------------------------------------------------------------------------
// Version
//---------------------------------------------------------------------------
// Version metadata, constants come from auto-generated
// versioning_regs_regmap_utils.vh
assign version_info = build_component_versions(
RF_CORE_100M_VERSION_LAST_MODIFIED_TIME,
build_version(
RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MAJOR,
RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MINOR,
RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_BUILD
),
build_version(
RF_CORE_100M_CURRENT_VERSION_MAJOR,
RF_CORE_100M_CURRENT_VERSION_MINOR,
RF_CORE_100M_CURRENT_VERSION_BUILD
)
);
endmodule
`default_nettype wire
//XmlParse xml_on
//<regmap name="VERSIONING_REGS_REGMAP">
// <group name="VERSIONING_CONSTANTS">
// <enumeratedtype name="RF_CORE_100M_VERSION" showhex="true">
// <info>
// 100 MHz RF core.{BR/}
// For guidance on when to update these revision numbers,
// please refer to the register map documentation accordingly:
// <li> Current version: @.VERSIONING_REGS_REGMAP..CURRENT_VERSION
// <li> Oldest compatible version: @.VERSIONING_REGS_REGMAP..OLDEST_COMPATIBLE_VERSION
// <li> Version last modified: @.VERSIONING_REGS_REGMAP..VERSION_LAST_MODIFIED
// </info>
// <value name="RF_CORE_100M_CURRENT_VERSION_MAJOR" integer="1"/>
// <value name="RF_CORE_100M_CURRENT_VERSION_MINOR" integer="0"/>
// <value name="RF_CORE_100M_CURRENT_VERSION_BUILD" integer="0"/>
// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MAJOR" integer="1"/>
// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_MINOR" integer="0"/>
// <value name="RF_CORE_100M_OLDEST_COMPATIBLE_VERSION_BUILD" integer="0"/>
// <value name="RF_CORE_100M_VERSION_LAST_MODIFIED_TIME" integer="0x20102617"/>
// </enumeratedtype>
// </group>
//</regmap>
//XmlParse xml_off