sequential component
Sigma-Delta Modulator
A sigma-delta modulator turns an 8-bit level into a one-bit stream whose pulse density carries the value. Over any 256-cycle window, the number of high cycles equals the level exactly. Filtering the stream with anything slow, an RC network or an LED's persistence, recovers the analog value.
The machine is one 8-bit accumulator and a 9-bit add. Every cycle the level is added to the accumulator, and the ninth bit, the carry out, is the pulse. A large level overflows often and packs the stream with ones; a small level overflows rarely. The leftover in the accumulator is never thrown away, so each cycle's error carries into the next and cancels. That is the whole trick of first-order sigma-delta, and it is why the density is exact rather than approximate.
A PWM at the same duty produces one long high interval per period. The modulator spreads the same on-time evenly across the window. Spreading pushes the switching noise up in frequency, where a small filter removes it cheaply. The PWM generator it contrasts with is built from first principles in Sequential Design Patterns.
In the waveform, compare the level 0x40 and 0xFF segments: one pulse every fourth cycle against an almost solid high line, while acc_r saws upward and wraps.
In the netlist, the whole modulator is one adder chain and nine flip-flops: eight for the accumulator and one for the pulse.
-- First-order sigma-delta modulator: one 8-bit accumulator, one 9-bit add.
-- The carry out IS the output: pulse density over 256 cycles equals level.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity sigma_delta_modulator is
port (
clk : in std_logic;
rst : in std_logic;
level : in std_logic_vector(7 downto 0);
pulse : out std_logic
);
end entity sigma_delta_modulator;
architecture rtl of sigma_delta_modulator is
signal acc_r : unsigned(7 downto 0);
signal pulse_r : std_logic;
-- 9 bits: bit 8 is the overflow that becomes the pulse
signal sum : unsigned(8 downto 0);
begin
sum <= ('0' & acc_r) + ('0' & unsigned(level));
acc_p : process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
acc_r <= (others => '0');
pulse_r <= '0';
else
-- The residue stays in acc_r, so per-cycle error carries forward
-- and cancels: the long-run density is exact, not approximate.
acc_r <= sum(7 downto 0);
pulse_r <= sum(8);
end if;
end if;
end process acc_p;
pulse <= pulse_r;
end architecture rtl;