FPGA: - Split up MB registers that control daughterboard specific settings so that daughterboards 0 and 1 could have different setings, in preparation for future devices that require different settings. This requires a compat number bump to 8.0. - Add registers for additional RFDC information, including the block/tile mapping of the individual channels, and information about resampling capabilities - Identify sections of code that would be specific to X410/ZBX and move them to their own headers, so it's trivial to add device-specific sections of code instead for other devices in the future. - This includes constraints for clocks and I/O pins. - Remove ability to do timed ctrlport transactions to the MB CPLD, this was unused and possibly broken. - Move daughterboard-specific code into its own code location (dboards/zbx) - Move X410-specific register documentation to its own location (doc/X410) - Refactor Makefiles to split out X410/ZBX specific components and allow switching between device types - Add 512-bit AXI interconnects - Make number of timekeepers configurable (X410 keeps the single timekeeper) MPM: - Required compat is bumped to 8.0 - Now supports new registers for detecting DSP capabilities and multi-rate settings for the daughterboards - Adds MMCM controls (currently unused) Co-authored-by: Wade Fife <wade.fife@ni.com> Co-authored-by: Ryan Marlow <ryan@lmarlow.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Original-commit: c1d268917ea65dd9c5a42366014cb96d3c025223
294 lines
10 KiB
VHDL
294 lines
10 KiB
VHDL
--
|
|
-- Copyright 2021 Ettus Research, a National Instruments Brand
|
|
--
|
|
-- SPDX-License-Identifier: LGPL-3.0-or-later
|
|
--
|
|
-- Module: tb_rf_nco_reset
|
|
--
|
|
-- Description:
|
|
--
|
|
-- Self-checking testbench for NCO reset sequencing.
|
|
--
|
|
|
|
library IEEE;
|
|
use IEEE.std_logic_1164.all;
|
|
use IEEE.numeric_std.all;
|
|
|
|
entity tb_rf_nco_reset is
|
|
end tb_rf_nco_reset;
|
|
|
|
|
|
architecture RTL of tb_rf_nco_reset is
|
|
|
|
signal cAdc0xNcoUpdateReq : std_logic;
|
|
signal cAdc1xNcoUpdateReq : std_logic;
|
|
signal cAdc2xNcoUpdateReq : std_logic;
|
|
signal cAdc3xNcoUpdateReq : std_logic;
|
|
signal cDac0xNcoUpdateReq : std_logic;
|
|
signal cDac0xSysrefIntGating : std_logic;
|
|
signal cDac0xSysrefIntReenable : std_logic;
|
|
signal cDac1xNcoUpdateReq : std_logic;
|
|
signal cNcoPhaseRst : std_logic;
|
|
signal cNcoUpdateEn : std_logic_vector(5 downto 0);
|
|
signal dNcoResetDone : std_logic;
|
|
|
|
signal cDac0xNcoUpdateBusy : std_logic_vector(1 downto 0) := "00";
|
|
signal dStartNcoReset : std_logic := '0';
|
|
signal cAdc0xNcoUpdateBusy : std_logic := '0';
|
|
signal cAdc1xNcoUpdateBusy : std_logic := '0';
|
|
signal cAdc2xNcoUpdateBusy : std_logic := '0';
|
|
signal cAdc3xNcoUpdateBusy : std_logic := '0';
|
|
signal cDac1xNcoUpdateBusy : std_logic := '0';
|
|
|
|
signal cSysref_ms, cSysref : std_logic := '0';
|
|
signal cSysrefDlyd : std_logic_vector(1 downto 0) := "00";
|
|
signal cDac0xSysrefIntGatingDlyd : std_logic := '0';
|
|
signal cNcoPhaseRstDlyd : std_logic_vector(2 downto 0) := "000";
|
|
|
|
signal cWrCount : integer := 0;
|
|
type RfdcNcoState_t is (Idle, GateSysref, UpdateReq, CheckUpdate,
|
|
SysrefEn, WaitForSysref, ResetDone);
|
|
signal cRfdcNcoState : RfdcNcoState_t := Idle;
|
|
|
|
signal StopSim : boolean;
|
|
constant kConfigClkPer : time := 25 ns;
|
|
-- SYSREF period is 2.5 MHz.
|
|
constant kSysrefPer : time := 400 ns;
|
|
-- DataClk period is 125 MHz and generated from the same clocking chip that
|
|
-- generated SYSREF and are related.
|
|
constant kDataClkPer : time := kSysrefPer/50;
|
|
|
|
signal ConfigClk : std_logic := '0';
|
|
signal DataClk : std_logic := '0';
|
|
signal dSysref : std_logic := '0';
|
|
|
|
procedure DataClkWait(X : positive := 1) is
|
|
begin
|
|
for i in 1 to X loop
|
|
wait until rising_edge(DataClk);
|
|
end loop;
|
|
end procedure DataClkWait;
|
|
|
|
procedure ConfigClkWait(X : positive := 1) is
|
|
begin
|
|
for i in 1 to X loop
|
|
wait until rising_edge(ConfigClk);
|
|
end loop;
|
|
end procedure ConfigClkWait;
|
|
|
|
procedure SysrefWait(X : positive := 1) is
|
|
begin
|
|
for i in 1 to X loop
|
|
wait until rising_edge(dSysref);
|
|
end loop;
|
|
end procedure SysrefWait;
|
|
|
|
begin
|
|
|
|
ConfigClk <= not ConfigClk after kConfigClkPer/2 when not StopSim else '0';
|
|
DataClk <= not DataClk after kDataClkPer/2 when not StopSim else '0';
|
|
dSysref <= not dSysref after kSysrefPer/2 when not StopSim else '0';
|
|
|
|
-- Both cNcoPhaseRst and cNcoUpdateEn are constants in the DUT.
|
|
dut: entity WORK.rf_nco_reset (RTL)
|
|
port map (
|
|
ConfigClk => ConfigClk,
|
|
DataClk => DataClk,
|
|
dSysref => dSysref,
|
|
dStartNcoReset => dStartNcoReset,
|
|
cDac0xNcoUpdateBusy => cDac0xNcoUpdateBusy,
|
|
cDac0xNcoUpdateReq => cDac0xNcoUpdateReq,
|
|
cDac0xSysrefIntGating => cDac0xSysrefIntGating,
|
|
cDac0xSysrefIntReenable => cDac0xSysrefIntReenable,
|
|
cDac1xNcoUpdateBusy => cDac1xNcoUpdateBusy,
|
|
cDac1xNcoUpdateReq => cDac1xNcoUpdateReq,
|
|
cAdc0xNcoUpdateBusy => cAdc0xNcoUpdateBusy,
|
|
cAdc0xNcoUpdateReq => cAdc0xNcoUpdateReq,
|
|
cAdc1xNcoUpdateBusy => cAdc1xNcoUpdateBusy,
|
|
cAdc1xNcoUpdateReq => cAdc1xNcoUpdateReq,
|
|
cAdc2xNcoUpdateBusy => cAdc2xNcoUpdateBusy,
|
|
cAdc2xNcoUpdateReq => cAdc2xNcoUpdateReq,
|
|
cAdc3xNcoUpdateBusy => cAdc3xNcoUpdateBusy,
|
|
cAdc3xNcoUpdateReq => cAdc3xNcoUpdateReq,
|
|
cNcoPhaseRst => cNcoPhaseRst,
|
|
cNcoUpdateEn => cNcoUpdateEn,
|
|
dNcoResetDone => dNcoResetDone
|
|
);
|
|
|
|
main: process
|
|
|
|
-- Procedure to sweep the entire SYSREF period.
|
|
-- When we strobe dStartNcoReset for one DataClk cycle. NCO reset sequence
|
|
-- is initiated. In this procedure, we sweep the dStartNcoReset strobe the
|
|
-- entire SYSREF cycle.
|
|
procedure SysrefSweep is
|
|
constant kSysrefInRfCycles : integer := kSysrefPer/kDataClkPer;
|
|
begin
|
|
for i in 1 to kSysrefInRfCycles loop
|
|
wait until cDac0xSysrefIntGating = '0' for 1 us;
|
|
assert cDac0xSysrefIntGating = '0'
|
|
report "NCO phase reset does not de-assert"
|
|
severity error;
|
|
SysrefWait;
|
|
DataClkWait(i);
|
|
dStartNcoReset <= '0';
|
|
DataClkWait;
|
|
dStartNcoReset <= '1';
|
|
DataClkWait;
|
|
dStartNcoReset <= '0';
|
|
-- Wait for a minimum of 3 SYSREF period. 1 SYSREF edge is used to
|
|
-- initiate NCO reset, 1 SYSREF edge is used to re-enable SYSREF and 1
|
|
-- SYSREF edge is used by RFDC to reset all NCOs.
|
|
SysrefWait(3);
|
|
end loop;
|
|
end procedure;
|
|
|
|
begin
|
|
|
|
-- Strobe dStartNcoReset across entire SYSREF period.
|
|
SysrefSweep;
|
|
-- Wait for a minimum of 3 SYSREF cycles to make sure NCO reset is complete.
|
|
SysrefWait(3);
|
|
|
|
StopSim <= true;
|
|
wait;
|
|
end process;
|
|
|
|
-- Process to mimic RFDC NCO reset
|
|
-- This state machine is based of "NCO frequency hopping" section in PG269
|
|
-- (v2.2). Refer to multi-mode subsection for more details.
|
|
MimicRfdc: process(ConfigClk)
|
|
begin
|
|
if falling_edge(ConfigClk) then
|
|
cRfdcNcoState <= Idle;
|
|
case cRfdcNcoState is
|
|
|
|
-- Wait until SYSREF internal gating is asserted.
|
|
when Idle =>
|
|
cWrCount <= 0;
|
|
if cDac0xSysrefIntGating = '1' then
|
|
cRfdcNcoState <= GateSysref;
|
|
end if;
|
|
|
|
-- Change cDac0xNcoUpdateBusy to "11" to indicate SYSREF is gated
|
|
-- internally when NCO update is requested on DAC tile 228.
|
|
-- cDac0xNcoUpdateBusy(0) is set to '1', the SYSREF is gated and
|
|
-- cDac0xNcoUpdateBusy(1) is set to '1', to indicate the NCO reset
|
|
-- process has started, but not complete.
|
|
when GateSysref =>
|
|
cRfdcNcoState <= GateSysref;
|
|
if cDac0xNcoUpdateReq = '1' then
|
|
cRfdcNcoState <= UpdateReq;
|
|
cDac0xNcoUpdateBusy <= "11";
|
|
end if;
|
|
|
|
-- If NCO reset is requested on other tiles, assert NCO update busy on
|
|
-- other tiles as well.
|
|
when UpdateReq =>
|
|
cRfdcNcoState <= CheckUpdate;
|
|
cDac1xNcoUpdateBusy <= cDac1xNcoUpdateReq;
|
|
cAdc0xNcoUpdateBusy <= cAdc0xNcoUpdateReq;
|
|
cAdc1xNcoUpdateBusy <= cAdc1xNcoUpdateReq;
|
|
cAdc2xNcoUpdateBusy <= cAdc2xNcoUpdateReq;
|
|
cAdc3xNcoUpdateBusy <= cAdc3xNcoUpdateReq;
|
|
|
|
-- It takes 5 clock cycles to update each RFDC internal registers with
|
|
-- the used request change. In rf_nco_reset entity, we only want to
|
|
-- reset the NCO, which is a single bit. So, it should take only 5
|
|
-- ConfigClk for the update. When the internal register is updated, set
|
|
-- cDac0xNcoUpdateBusy(0) to '0'.
|
|
when CheckUpdate =>
|
|
cRfdcNcoState <= CheckUpdate;
|
|
if cWrCount > 4 then
|
|
cRfdcNcoState <= SysrefEn;
|
|
cDac0xNcoUpdateBusy <= "10"; --Indicates that SYSREF is gated.
|
|
cDac1xNcoUpdateBusy <= '0';
|
|
cAdc0xNcoUpdateBusy <= '0';
|
|
cAdc1xNcoUpdateBusy <= '0';
|
|
cAdc2xNcoUpdateBusy <= '0';
|
|
cAdc3xNcoUpdateBusy <= '0';
|
|
end if;
|
|
cWrCount <= cWrCount + 1;
|
|
|
|
-- Wait until internal SYSREF gating is disabled.
|
|
when SysrefEn =>
|
|
cWrCount <= 0;
|
|
cRfdcNcoState <= SysrefEn;
|
|
if cDac0xSysrefIntReenable = '1' then
|
|
if cSysrefDlyd(0) = '0' and cSysref = '1' then
|
|
cDac0xNcoUpdateBusy <= "00"; --Indicates that NCO reset is complete.
|
|
cRfdcNcoState <= ResetDone;
|
|
else
|
|
cRfdcNcoState <= WaitForSysref;
|
|
end if;
|
|
end if;
|
|
|
|
-- NCO reset is done on the rising edge of SYSREF. When NCO reset is
|
|
-- complete, set cDac0xNcoUpdateBusy(1) to '0'.
|
|
when WaitForSysref =>
|
|
cRfdcNcoState <= WaitForSysref;
|
|
if cSysrefDlyd(0) = '0' and cSysref = '1' then
|
|
cDac0xNcoUpdateBusy <= "00"; --Indicates that NCO reset is complete.
|
|
cRfdcNcoState <= ResetDone;
|
|
end if;
|
|
|
|
-- Wait in this state, until the next NCO reset is requested.
|
|
when ResetDone =>
|
|
cRfdcNcoState <= ResetDone;
|
|
if cDac0xSysrefIntGating = '1' then
|
|
cRfdcNcoState <= GateSysref;
|
|
end if;
|
|
end case;
|
|
end if;
|
|
end process;
|
|
|
|
-- SYSREF clock crossing from DataClk to ConfigClk and some pipelines.
|
|
ConfigClkSysref: process(ConfigClk)
|
|
begin
|
|
if rising_edge(ConfigClk) then
|
|
cSysref_ms <= dSysref;
|
|
cSysref <= cSysref_ms;
|
|
cSysrefDlyd <= cSysrefDlyd(cSysrefDlyd'high-1) & cSysref;
|
|
cDac0xSysrefIntGatingDlyd <= cDac0xSysrefIntGating;
|
|
cNcoPhaseRstDlyd <= cNcoPhaseRstDlyd(cNcoPhaseRstDlyd'high downto 1)
|
|
& cDac0xNcoUpdateBusy(1);
|
|
end if;
|
|
end process;
|
|
|
|
-- Assertions
|
|
process(ConfigClk)
|
|
begin
|
|
if falling_edge(ConfigClk) then
|
|
|
|
--Check if cNcoPhaseRst is a constant of '1'.
|
|
assert cNcoPhaseRst = '1'
|
|
report "NCO phase reset signal should be constant."
|
|
severity error;
|
|
-- Check if cNcoUpdateEn is a constant of "100000".
|
|
assert cNcoUpdateEn = "100000"
|
|
report "NCO phase reset signal should be constant."
|
|
severity error;
|
|
-- Check if NCO reset was requested on the rising edge of SYSREF.
|
|
if cDac0xSysrefIntGating = '1' and cDac0xSysrefIntGatingDlyd = '0' then
|
|
assert cSysrefDlyd = "01"
|
|
report "NCO reset did not start on SYSREF rising edge"
|
|
severity error;
|
|
end if;
|
|
|
|
-- We wait for couple of clock cycles after NCO done signal is toggled in
|
|
-- from the RFDC. RFDC uses cDac0xNcoUpdateBusy(1) to indicate NCO reset
|
|
-- process is done. It is important to wait a minimum of three clock
|
|
-- cycles before this check is done. This wait is needed for clock
|
|
-- crossing.
|
|
if cNcoPhaseRstDlyd(2) = '1' and cNcoPhaseRstDlyd(1) = '0' then
|
|
assert dNcoResetDone = '1'
|
|
report "NCO Reset done should have been asserted after NCO " &
|
|
"reset request is de-asserted"
|
|
severity error;
|
|
end if;
|
|
|
|
end if;
|
|
end process;
|
|
|
|
end RTL;
|