aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/n3xx/dboards/common/sync/Pulser.vhd
blob: c6cb30f24ede7af07ed653c4d500ec7368af3aa3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
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