diff options
Diffstat (limited to 'fpga/usrp3/top/n3xx/dboards/common/sync/Pulser.vhd')
-rw-r--r-- | fpga/usrp3/top/n3xx/dboards/common/sync/Pulser.vhd | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/fpga/usrp3/top/n3xx/dboards/common/sync/Pulser.vhd b/fpga/usrp3/top/n3xx/dboards/common/sync/Pulser.vhd new file mode 100644 index 000000000..c6cb30f24 --- /dev/null +++ b/fpga/usrp3/top/n3xx/dboards/common/sync/Pulser.vhd @@ -0,0 +1,362 @@ +------------------------------------------------------------------------------- +-- +-- Copyright 2018 Ettus Research, a National Instruments Company +-- +-- SPDX-License-Identifier: LGPL-3.0-or-later +-- +-- +-- Purpose: +-- +-- The purpose of this module is to create a psuedo-clock "pulse" on the output +-- cPulse whenever cEnablePulse is asserted. +-- +-- The output period and high time are determined by the inputs cPeriod and +-- cHighTime, where cPeriod must be greater than cHighTime+2. When these values +-- are valid at the inputs, pulse cLoadLimits to load them into the pulser routine. +-- It is not recommended to leave cEnablePulse asserted when loading new limits. +-- +-- Dynamic period and duty cycle setup: +-- 1) Disable the pulser by de-asserting cEnablePulse. +-- 2) Load new period and duty cycle by modifying cPeriod and cHighTime. Pulse +-- cLoadLimits for at least one Clk cycle. +-- 3) Enable the pulser by asserting cEnablePulse. +-- 4) Repeat 1-3 as necessary. +-- +-- Static period and duty cycle setup: +-- 1) Tie cLoadLimits to asserted. +-- 2) Tie cPeriod and cHighTime to static values. +-- 3) Enable and disable the pulser by asserting and de-asserting cEnablePulser at will. +-- This input can also be tied asserted in this case. +-- +-- vreview_group Tdc +------------------------------------------------------------------------------- + +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + use ieee.math_real.all; + +entity Pulser is + generic ( + -- The pulse counter is kClksPerPulseMaxBits wide. + -- Why 16? Then both cPeriod and cHighTime fit nicely into one 32 bit register! + -- Minimum of 3 to make our default values for cHighTime work out. + kClksPerPulseMaxBits : integer range 3 to 32 := 16 + ); + port ( + aReset : in boolean; + Clk : in std_logic; + + -- Pulse cLoadLimits when cPeriod and cHighTime are valid. Is it not recommended to + -- load new limits when the output is enabled. + -- Alternatively, cLoadLimits can be tied high if cPeriod and cHighTime are also + -- tied to static values. + cLoadLimits : in boolean; + cPeriod : in unsigned(kClksPerPulseMaxBits - 1 downto 0); + cHighTime : in unsigned(kClksPerPulseMaxBits - 1 downto 0); + + -- When cEnablePulse is de-asserted, cPulse idles low on the following cycle. + -- When asserted, cPulse will then assert within a few cycles. + -- This input can be tied high, if desired, and the pulses will start several + -- clock cycles after aReset de-assertion. + cEnablePulse : in boolean; + + -- When cEnablePulse is asserted, cPulse will produce a rising edge every + -- cPeriod of the Clk input and a falling edge cHighTime cycles after + -- the rising edge. + cPulse : out boolean + ); +end Pulser; + + +architecture rtl of Pulser is + + signal cCounter, + cPeriodStored, + cHighTimeStored : unsigned(cPeriod'range); + + signal cSafeToStart_ms, cSafeToStart, cSafeToStartDly : boolean; + + attribute ASYNC_REG : string; + attribute ASYNC_REG of cSafeToStart_ms : signal is "true"; + attribute ASYNC_REG of cSafeToStart : signal is "true"; + +begin + + --synthesis translate_off + CheckInputRanges : process(Clk) + begin + if falling_edge(Clk) then + -- +2 since we have the output high offset from the zero of the counter + assert (cPeriodStored > cHighTimeStored + 2) + report "cPeriod is not greater than cHighTime + 2" severity error; + -- Ensure the high time is greater than 1... + assert (cHighTimeStored > 1) + report "cHighTime is not greater than 1" severity error; + end if; + end process; + --synthesis translate_on + + + -- ------------------------------------------------------------------------------------ + -- !!! SAFE COUNTER STARTUP !!! + -- This counter starts safely, meaning it cannot start counting immediately after + -- aReset de-assertion, because the counter cannot start until cSafeToStart asserts, + -- which cannot happen until 1-2 clock cycles after aReset de-assertion. + -- ------------------------------------------------------------------------------------ + CountFreqRefPeriod: process(aReset, Clk) + begin + if aReset then + cCounter <= (others => '0'); + cSafeToStart_ms <= false; + cSafeToStart <= false; + cSafeToStartDly <= false; + cPulse <= false; + cPeriodStored <= (others => '1'); + -- This is a rather arbitrary start value, but we are guaranteed that it is + -- less than the reset value of cPeriodStored as well as greater than 2, + -- so it works well enough in case the module isn't set up correctly. + cHighTimeStored <= to_unsigned(kClksPerPulseMaxBits+2,cHighTimeStored'length); + elsif rising_edge(Clk) then + -- Create a safe counter startup signal that asserts shortly after + -- aReset de-assertion. + cSafeToStart_ms <= true; + cSafeToStart <= cSafeToStart_ms; + -- In the case where cLoadLimits and cEnablePulse are tied high, we need to give + -- them one cycle to load before starting the counter, so we delay cSafeToStart + -- by one for the counter. + cSafeToStartDly <= cSafeToStart; + + if cEnablePulse and cSafeToStartDly then + -- Simple counter increment until ceiling reached, then roll over. + if cCounter >= cPeriodStored - 1 then + cCounter <= (others => '0'); + else + cCounter <= cCounter + 1; + end if; + + -- Pulse the output when counter is between 1 and cHighTimeStored. + if cCounter = 1 then + cPulse <= true; + elsif cCounter >= cHighTimeStored+1 then + cPulse <= false; + end if; + + else + cPulse <= false; + cCounter <= (others => '0'); + end if; + + if cLoadLimits and cSafeToStart then + cPeriodStored <= cPeriod; + cHighTimeStored <= cHighTime; + end if; + end if; + end process; + +end rtl; + + +-------------------------------------------------------------------------------- +-- Testbench for Pulser +-------------------------------------------------------------------------------- + +--synopsys translate_off +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + use ieee.math_real.all; + +entity tb_Pulser is end tb_Pulser; + +architecture test of tb_Pulser is + + constant kClksPerPulseMaxBits : integer := 16; + + --vhook_sigstart + signal aReset: boolean; + signal cEnablePulse: boolean; + signal cHighTime: unsigned(kClksPerPulseMaxBits-1 downto 0); + signal Clk: std_logic := '0'; + signal cLoadLimits: boolean; + signal cPeriod: unsigned(kClksPerPulseMaxBits-1 downto 0); + signal cPulse: boolean; + signal cPulseDut2: boolean; + --vhook_sigend + + signal StopSim : boolean; + constant kPer : time := 10 ns; + + signal CheckPulse : boolean := false; + signal cPulseSl : std_logic := '0'; + signal cPulseDut2Sl : std_logic := '0'; + + procedure ClkWait(X : positive := 1) is + begin + for i in 1 to X loop + wait until rising_edge(Clk); + end loop; + end procedure ClkWait; + +begin + + Clk <= not Clk after kPer/2 when not StopSim else '0'; + + main: process + begin + cEnablePulse <= false; + aReset <= true, false after 10 ns; + ClkWait(5); + + -- Ensure the pulse is quiet for a while. + ClkWait(100); + assert cPulse'stable(kPer*100) and not cPulse + report "pulse not stable at false at startup" + severity error; + + + -- Set up, then enable the pulse; expect it to go high after a few cycles. + cPeriod <= to_unsigned(250,cPeriod'length); + cHighTime <= to_unsigned(100,cPeriod'length); + cLoadLimits <= true; + ClkWait; + cLoadLimits <= false; + cEnablePulse <= true; + ClkWait(2); -- pulse rises here + wait until falling_edge(Clk); + assert cPulse report "cPulse not high two cycles after enabling" severity error; + -- After another clock cycle the checker below should be primed, so we can enable it. + ClkWait; + CheckPulse <= true; + ClkWait(to_integer(cHighTime)-1); + wait until falling_edge(Clk); + assert not cPulse report "Pulse not low after high requirement" severity error; + + -- Check the pulse high and low for a few cycles (duplicated below, but this also + -- checks that it actually is toggling). + for i in 0 to 100 loop + ClkWait(to_integer(cPeriod) - to_integer(cHighTime)); + wait until falling_edge(Clk); + assert cPulse report "Pulse not high when expected" severity error; + ClkWait(to_integer(cHighTime)); + wait until falling_edge(Clk); + assert not cPulse report "Pulse not low after high requirement" severity error; + end loop; + + -- Disable pulse, and check that it goes away for a long time + cEnablePulse <= false; + CheckPulse <= false; + -- 2 is about the max time for it to go away. + ClkWait(2); + ClkWait(2**kClksPerPulseMaxBits); + assert (not cPulse) and cPulse'stable(2**kClksPerPulseMaxBits*kPer) + report "disable didn't work" severity error; + + + -- Re-do all the initial tests with different periods and such. + + -- Enable the pulse, expect it to go high after a few cycles + cPeriod <= to_unsigned(10,cPeriod'length); + cHighTime <= to_unsigned(5,cPeriod'length); + cLoadLimits <= true; + ClkWait; + cLoadLimits <= false; + cEnablePulse <= true; + ClkWait(2); -- pulse rises here + wait until falling_edge(Clk); + assert cPulse report "cPulse not high two cycles after enabling" severity error; + -- After another clock cycle the checker below should be primed, so we can enable it. + ClkWait; + CheckPulse <= true; + ClkWait(to_integer(cHighTime)-1); + wait until falling_edge(Clk); + assert not cPulse report "Pulse not low after high requirement" severity error; + + -- Check the pulse high and low for a few cycles (duplicated below, but this also + -- checks that it actually is toggling). + for i in 0 to 100 loop + ClkWait(to_integer(cPeriod) - to_integer(cHighTime)); + wait until falling_edge(Clk); + assert cPulse report "Pulse not high when expected" severity error; + ClkWait(to_integer(cHighTime)); + wait until falling_edge(Clk); + assert not cPulse report "Pulse not low after high requirement" severity error; + end loop; + + ClkWait(100); + + + StopSim <= true; + wait; + end process; + + cPulseSl <= '1' when cPulse else '0'; + + -- Test the period and duty cycle of the pulse. + CheckPulseSpecs : process(cPulseSl) + variable LastRise : time := 0 ns; + begin + if falling_edge(cPulseSl) then + assert (not CheckPulse) or (now - LastRise = kPer*to_integer(cHighTime)) + report "High cycles requirement not met" severity error; + elsif rising_edge(cPulseSl) then + assert (not CheckPulse) or (now - LastRise = kPer*to_integer(cPeriod)) + report "Period requirement not met" & LF & + "Act: " & time'image(now-LastRise) & LF & + "Req: " & time'image(kPer*to_integer(cPeriod)) + severity error; + LastRise := now; + end if; + end process; + + --vhook_e Pulser dutx + dutx: entity work.Pulser (rtl) + generic map (kClksPerPulseMaxBits => kClksPerPulseMaxBits) --integer range 3:32 :=16 + port map ( + aReset => aReset, --in boolean + Clk => Clk, --in std_logic + cLoadLimits => cLoadLimits, --in boolean + cPeriod => cPeriod, --in unsigned(kClksPerPulseMaxBits-1:0) + cHighTime => cHighTime, --in unsigned(kClksPerPulseMaxBits-1:0) + cEnablePulse => cEnablePulse, --in boolean + cPulse => cPulse); --out boolean + + + --vhook_e Pulser dut2 + --vhook_a cLoadLimits true + --vhook_a cPeriod to_unsigned(5,kClksPerPulseMaxBits) + --vhook_a cHighTime to_unsigned(2,kClksPerPulseMaxBits) + --vhook_a cEnablePulse true + --vhook_a cPulse cPulseDut2 + dut2: entity work.Pulser (rtl) + generic map (kClksPerPulseMaxBits => kClksPerPulseMaxBits) --integer range 3:32 :=16 + port map ( + aReset => aReset, --in boolean + Clk => Clk, --in std_logic + cLoadLimits => true, --in boolean + cPeriod => to_unsigned(5,kClksPerPulseMaxBits), --in unsigned(kClksPerPulseMaxBits-1:0) + cHighTime => to_unsigned(2,kClksPerPulseMaxBits), --in unsigned(kClksPerPulseMaxBits-1:0) + cEnablePulse => true, --in boolean + cPulse => cPulseDut2); --out boolean + + cPulseDut2Sl <= '1' when cPulseDut2 else '0'; + + CheckDut2 : process (cPulseDut2Sl) + variable LastRise : time := 0 ns; + begin + if falling_edge(cPulseDut2Sl) then + assert (not CheckPulse) or (now - LastRise = kPer*2) + report "DUT 2 High cycles requirement not met" severity error; + elsif rising_edge(cPulseDut2Sl) then + assert (not CheckPulse) or (now - LastRise = kPer*5) + report "DUT 2 Period requirement not met" & LF & + "Act: " & time'image(now-LastRise) & LF & + "Req: " & time'image(kPer*5) + severity error; + LastRise := now; + end if; + end process; + + +end test; +--synopsys translate_on |