sequential component
NCO Tick Generator
An NCO (numerically controlled oscillator) tick generator divides the clock by a fraction. A 16-bit accumulator adds step to phase_r on every rising edge. Each time the sum overflows 16 bits, tick goes high for one cycle. Over any long window the tick rate is exactly step / 65536 ticks per clock.
A counter-based timer can only divide by whole numbers: one tick every N cycles, nothing in between. The accumulator sidesteps that limit by keeping its remainder. When the step does not divide the range evenly, some gaps are one cycle longer than others, but nothing is ever lost. The leftover phase carries into the next wrap, so the average rate stays exact. This is the same trick behind baud-rate generators and direct digital synthesis.
The design registers tick from the adder's carry bit, so the output is glitch-free and arrives one cycle after the wrapping addition.
In the waveform, watch phase_r climb as a sawtooth and wrap. At step = 0x6000 (3/8 of the range), the tick gaps run 3, 3, 2 cycles and then repeat: uneven spacing, exact average.
In the netlist, the whole machine is one adder and one register row. tick is the adder's carry-out, delayed by a flip-flop.
Counter-based timers, and when whole-number division is the right tool, are built from first principles in Sequential Design Patterns.
-- NCO tick generator: a 16-bit phase accumulator whose carry-out is the
-- tick. Average rate is exactly step/65536 per clock; the kept remainder
-- is what makes fractional rates exact.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity nco_tick_generator is
port (
clk : in std_logic;
rst : in std_logic;
step : in std_logic_vector(15 downto 0);
tick : out std_logic
);
end entity nco_tick_generator;
architecture rtl of nco_tick_generator is
signal phase_r : unsigned(15 downto 0);
signal tick_r : std_logic;
signal sum : unsigned(16 downto 0);
begin
-- One 17-bit add per cycle: bit 16 is the wrap, the low 16 bits carry
-- the remainder into the next cycle.
sum <= ('0' & phase_r) + ('0' & unsigned(step));
nco_p : process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
phase_r <= (others => '0');
tick_r <= '0';
else
phase_r <= sum(15 downto 0);
tick_r <= sum(16);
end if;
end if;
end process nco_p;
tick <= tick_r;
end architecture rtl;