Skip to content

One voice's logic, in turn

The sound chip has three voices but only one voice’s worth of logic: one adder for the oscillators, one envelope, one multiplier, taking turns twelve times a microsecond. That page shows the slots; this one shows how it’s done in Verilog, starting with an engine small enough to read in one go, and measures what the sharing saves. It’s the same trade as on Making it faster, taken further.

The small engine: three voices, each an oscillator (a 24-bit accumulator with its frequency added every step, as on the sound chip page) whose top 8 bits are a sawtooth, multiplied by the voice’s 8-bit volume; the three added together into the sample.

The obvious way is three copies of everything, all working at once, every clock:

Three copies: three oscillators (a0, a1, a2), three adders, three multipliers, all acting at every clock. Set the frequencies (f0–f2) and volumes (v0–v2), say f0 to 30000 and v0 to 200, and clock: each oscillator moves on by its frequency every clock, and out is the three sawtooths (each oscillator’s top 8 bits) times their volumes, added. With those numbers, a0’s top 8 bits reach 1 after three clocks, and out becomes 200 at the next. out is worked out every clock, but it only changes when a0’s top 8 bits do: once every 65,536 / 30,000 ≈ 2.2 clocks, so every second or third clock (the waveform draws one box for as long as a value stays the same). Simulated gate by gate (three multipliers make this one slow to load).

A new sample every clock: at the board’s 12 MHz, twelve million samples a second.

Far fewer. A sound chip’s sample rate is fixed by what it’s for, not by its clock:

  • It sets the pitch. The oscillator moves on by its frequency once a sample, so the same frequency number gives a note twelve times higher at twelve times the rate. The SID makes a sample a microsecond, and its registers mean that: a frequency of n is n × 1,000,000 / 2²⁴ Hz. A chip with the SID’s registers has to step at the SID’s rate.
  • Nothing after it could use more. A million samples a second is already fifty times what anyone can hear, and the one-bit DAC’s filter removes everything above 20 kHz.

So the real chip makes a sample every 12 clocks, and the small engine here one every 4 (a round number for a small example). Slowing the three copies down takes only a counter, and an if around everything else:

reg [1:0] slot; // counts 0, 1, 2, 3, 0, …
always @(posedge clk) begin
slot <= slot + 1;
if (slot == 3) begin // a sample only every fourth clock
out <= a0[23:16] * v0 + a1[23:16] * v1 + a2[23:16] * v2;
a0 <= a0 + f0;
a1 <= a1 + f1;
a2 <= a2 + f2;
end
end

The rest is the same as before:

The same three copies, acting only at the clock where slot is 3: a sample every 4 clocks (scroll the editor down to the always block to see the difference). With f0 at 30000 and v0 at 200, out becomes 200 at the fourth sample now, the sixteenth clock.

For three clocks in four, all that logic waits. That idle time is what sharing turns into savings.

Instead: one adder and one multiplier, and three slots. In slot 0 they work on voice 0, in slot 1 on voice 1, in slot 2 on voice 2; each voice’s product is added into a running total, and in slot 3 the total comes out as the sample. The oscillators are kept in a small memory of three, acc, and the slot picks which to use:

One copy of the logic, picking each voice’s state by index: a is acc[slot], this slot’s voice’s oscillator; the frequency and volume are picked the same way. Clock and watch slot count 0 to 3, a show each voice’s oscillator in turn, and total build up the sample, which goes to out in slot 3. The samples are the same as the three copies’ (checked in Icarus, over 1,000 samples).

That’s the idea, but picking costs something. acc[slot] is a multiplexer: for each of the oscillator’s 24 bits, a little circuit that chooses one of three inputs by the slot. And writing back, acc[slot] <= …, needs each of the three registers to know whether it’s the one being written.

The sound chip avoids that with a trick: don’t fetch the voice being worked on; bring it round. Keep the three oscillators in a ring of registers, r0, r1 and r2, and turn the ring one place every slot: r1 moves to r0, r2 to r1, and r0, with its frequency added, goes round to the back, r2. The voice being worked on is always in r0, so the logic only ever looks at r0: no multiplexer. After three turns every voice is back where it started, ready for the next sample.

The ring engine, a clock at a time, with the numbers it computes (frequencies 5,000, 7,919 and 12,345; volumes 200, 90 and 255), from its thousandth sample on. In each of slots 0–2, the voice in r0 (lit) has its top 8 bits multiplied by its volume and added into the sample, and moves to the back of the ring with its frequency added: the one adder and the one multiplier, used once each. In slot 3 the ring rests and the sample comes out. This is the same arithmetic as the Verilog below (checked against it, sample for sample).

The ring in Verilog. Clock and watch the three values move along: r1 to r0, r2 to r1, and r0 plus its frequency to r2. The frequency and volume still have to be picked by the slot (they come in on separate pins here); in the real chip they’re in the ring too, with each voice’s other settings and state.

Here are all three, synthesised by Yosys and packed into the UP5K’s logic cells by nextpnr: first with their multipliers made of logic, then with the UP5K’s DSP blocks (its ready-made multipliers) doing the multiplying.

The small engineMultipliers made of logicWith DSP blocks
Three copies, every clock576 logic cells128 logic cells, 3 DSP blocks
Three copies, a sample every 4 clocks578132, 3 DSP blocks
One copy, picking by index406235, 1 DSP block
One copy, a ring315160, 1 DSP block

Slowing the copies down costs almost nothing (a counter of two bits, and a few cells to use it), and saves nothing either: the logic is all still there, waiting. With the multipliers made of logic, sharing them saves almost half, and the ring saves another 91 cells over picking by index: those are the multiplexers.

With DSP blocks the answer flips: three copies take the fewest logic cells. The multiplying costs no cells there, and what’s left to share is only the adders, which are cheap; the slot counter, the running total and the choosing cost more than they save. Sharing still saves two DSP blocks, and those are scarce: the UP5K has 8.

So sharing pays when the part being shared is big, or scarce. The real sound chip has seven multiplications every microsecond (three envelopes, three in the filter, the volume): seven DSP blocks as copies, one shared. And each voice has 136 bits of registers and state, all of which would need a three-way multiplexer to be picked by index: the ring saves that on every bit.

sound.v is the same idea with more in each slot. A few things make it trickier than the small engine.

A product comes a slot later. The multiplier has a register on its output (it makes the DSP block faster, and lets the multiplying and the adding happen in different slots):

always @(posedge clk) p <= ma * mb;

So whatever uses a product does it one slot after the numbers went in: the voices are multiplied in slots 6–8 and added into the mix in slots 7–9.

Some work spills into the next microsecond. The filter needs three multiplications in a row, each using the last’s result, and there aren’t enough slots left after the voices. So its last steps happen in the first slots of the next microsecond, while the oscillators are already moving on: the sound comes out a microsecond later than it would from three copies, which nobody can hear.

The voice before is always in the same place too. Hard sync and ring modulation make each voice depend on the voice before it (voice 3 before voice 1). That voice has just been worked on, so it has just gone to the back of the ring:

wire [23:0] prev_acc = r2[75:52];
wire prev_rose = r2[135];

The CPU must write at the right moment. The ring turns during slots 0–8, nine turns, three times round. So between microseconds, when the CPU writes, voice n is in place n, and a write to voice n’s registers goes straight there:

if (slot <= 4'd8) begin // turn the ring
r0 <= r1;
r1 <= r2;
r2 <= done;
end
if (wr && a < 5'd21) case (n)
2'd0: r0 <= written(r0, reg7, d);
2'd1: r1 <= written(r1, reg7, d);
default: r2 <= written(r2, reg7, d);
endcase

It’s also why the chip needs exactly twelve clocks a microsecond: machine.v’s CLK_DIV of 12.

One adder does subtracting too. The filter and the mix need both, and subtracting is adding the number’s bits turned over, plus one, so a single adder with a “minus” input does both:

wire signed [23:0] sum = x + (y ^ {24{minus}}) + {23'd0, minus};

Here’s the chip’s source, as it goes on the board, with its comments. It’s checked against the site’s SID model cycle by cycle (hdl/scripts/sound-check.ts), and the sound chip page explains what each part does as sound.

sound.v
sites/cpu/hdl/periph/sound.v
// A sound chip in the style of the SID (the Commodore 64's): three voices, each an oscillator with four
// waveforms and an envelope, and a filter they can go through. Its registers are laid out as the SID's
// (base + $00 to $18 for writing, $1B and $1C for reading), so a program written for the SID's registers
// drives it too. It follows the site's own SID model (packages/core/lib/synth/sid.ts, the SID chip page on
// the DSP site): the same oscillators, waveforms, noise, envelope (including its rate counter, and the
// quirk that comes from it) and a state-variable filter, with the 8580's centred waveforms and straight
// cutoff curve.
//
// The chip steps once a microsecond, on the CPU's clock enable, as the SID steps once per C64 clock cycle
// (so a frequency register value n gives n × 1,000,000 / 2²⁴ Hz, about 0.06 Hz a step).
//
// One voice's worth of logic, used in turn
// ----------------------------------------
// Between two clock enables there are twelve clocks. Rather than three copies of each voice's logic, there's
// one, and the twelve clocks are slots, each doing one job. Each voice's registers and state are kept in a
// ring of three: the voice being worked on is always in the first place (r0), and after each of slots 0–8
// the ring turns one place, so the next voice comes round. Nine turns a microsecond is three times round,
// so between microsecond steps, when the CPU writes, voice n is always in place n. (Picking a voice out of
// three wherever it is would take a multiplexer on every bit; turning the ring takes none.)
//
// slots 0–2 oscillator and envelope, for voices 1, 2, 3
// slots 3–5 hard sync, for voices 1, 2, 3 (after all three oscillators have stepped, as on the chip)
// slots 6–8 the waveform, times the envelope, for voices 1, 2, 3
// slots 7–9 each product added into the filter's input or the output's
// slots 10–11, and 1–6 of the next microsecond: the filter, and the mix
// slots 10–11 the volume
//
// There's one multiplier (a DSP block), used in seven slots: the three envelopes, the filter's three
// products, and the volume; and one adder for all the filter's and the mix's sums, used in ten. A product
// comes out of the multiplier a slot after its numbers go in. The sound comes out a microsecond later than
// it would from three copies, which nobody can hear. (This is the "one part, used in turn" trade from the
// Making it faster page. It needs CLK_DIV = 12 in machine.v: twelve clocks a microsecond.)
//
// Registers (from the device's base address), as the SID's:
// +$00/$07/$0E FREQ LO the voice's frequency, 16 bits
// +$01/$08/$0F FREQ HI
// +$02/$09/$10 PW LO pulse width, 12 bits: the pulse is high while the oscillator's top 12 bits
// +$03/$0A/$11 PW HI are below it
// +$04/$0B/$12 CONTROL bit 0 gate (1 starts the envelope's attack, 0 its release), 1 sync, 2 ring
// modulation, 3 test (holds the oscillator at zero), 4 triangle, 5 sawtooth,
// 6 pulse, 7 noise
// +$05/$0C/$13 ATTACK/DECAY attack in bits 4–7, decay in bits 0–3
// +$06/$0D/$14 SUSTAIN/RELEASE sustain level in bits 4–7, release in bits 0–3
// +$15 FC LO cutoff, bits 0–2 of 11
// +$16 FC HI cutoff, bits 3–10
// +$17 RES/FILT resonance in bits 4–7; bits 0–2: voices 1–3 through the filter
// +$18 MODE/VOL volume in bits 0–3; bit 4 low-pass, 5 band-pass, 6 high-pass; bit 7: voice 3 off
// +$1B OSC3 read: voice 3's waveform, top 8 bits (with noise: a random number)
// +$1C ENV3 read: voice 3's envelope
module sound (
input clk,
input rst,
input ce,
input sel,
input we,
input [4:0] a,
input [7:0] d,
output reg [7:0] q,
output reg signed [15:0] out
);
// ------------------------------------------------------------------------- a voice, as 136 bits
// Its registers (the CPU writes these) and its state, packed into one vector, at these places:
// [15:0] freq [27:16] pw [35:28] ctrl [43:36] attack/decay [51:44] sustain/release
// [75:52] the oscillator (a phase accumulator) [98:76] the noise shift register [106:99] the envelope
// [108:107] its stage: 0 attack, 1 decay and sustain, 2 release [123:109] the rate counter
// [128:124] the exponential counter [133:129] its period [134] held at zero
// [135] the oscillator's top bit just went from 0 to 1 (for sync)
localparam W = 136;
reg [W-1:0] r0, r1, r2;
wire [15:0] freq = r0[15:0];
wire [11:0] pw = r0[27:16];
wire [ 7:0] ctrl = r0[35:28];
wire [ 7:0] ad = r0[43:36];
wire [ 7:0] sr = r0[51:44];
wire [23:0] acc = r0[75:52];
wire [22:0] lfsr = r0[98:76];
wire [ 7:0] env = r0[106:99];
wire [ 1:0] stage = r0[108:107];
wire [14:0] rcnt = r0[123:109];
wire [ 4:0] xcnt = r0[128:124];
wire [ 4:0] xper = r0[133:129];
wire hzero = r0[134];
// The voice before this one (voice 3 before voice 1) has just been round, so it's in the last place.
wire [23:0] prev_acc = r2[75:52];
wire prev_rose = r2[135];
// The global registers.
reg [10:0] fc;
reg [ 3:0] res, vol;
reg [ 2:0] route, mode;
reg off3;
reg [ 7:0] osc3, env3;
// The slot: 0 in the clock after the clock enable, counting up to 11 (15: waiting for the first).
reg [3:0] slot;
always @(posedge clk) slot <= rst ? 4'd15 : ce ? 4'd0 : (slot == 4'd15 ? slot : slot + 4'd1);
wire osc_slot = slot <= 4'd2;
wire sync_slot = slot >= 4'd3 && slot <= 4'd5;
wire wave_slot = slot >= 4'd6 && slot <= 4'd8;
wire [1:0] vno = slot == 4'd6 ? 2'd0 : slot == 4'd7 ? 2'd1 : 2'd2; // whose wave it is
// ---------------------------------------------------------------- the voice's logic, for r0
// The oscillator, and the noise, which steps when the accumulator's bit 19 goes from 0 to 1.
wire test = ctrl[3];
wire [23:0] acc_up = acc + {8'd0, freq};
wire rose_up = !acc[23] && acc_up[23];
wire [22:0] lfsr_up = (!acc[19] && acc_up[19]) ? {lfsr[21:0], lfsr[22] ^ lfsr[17]} : lfsr;
// The envelope's rate counter counts to its stage's period; then the envelope may step (decay and
// release only every xper periods, which makes their curves roughly exponential).
function [14:0] rate(input [3:0] n);
case (n)
4'd0: rate = 15'd9; 4'd1: rate = 15'd32; 4'd2: rate = 15'd63; 4'd3: rate = 15'd95;
4'd4: rate = 15'd149; 4'd5: rate = 15'd220; 4'd6: rate = 15'd267; 4'd7: rate = 15'd313;
4'd8: rate = 15'd392; 4'd9: rate = 15'd977; 4'd10: rate = 15'd1954; 4'd11: rate = 15'd3126;
4'd12: rate = 15'd3907; 4'd13: rate = 15'd11720; 4'd14: rate = 15'd19532; default: rate = 15'd31251;
endcase
endfunction
wire [ 3:0] nibble = stage == 2'd0 ? ad[7:4] : stage == 2'd1 ? ad[3:0] : sr[3:0];
wire [14:0] rcnt_up = rcnt + 15'd1;
wire period = rcnt_up == rate(nibble);
wire [ 4:0] xcnt_up = xcnt + 5'd1;
wire exp_wait = stage != 2'd0 && xcnt_up != xper;
wire steps = period && !exp_wait && !hzero;
wire [ 7:0] sustain = {sr[7:4], sr[7:4]};
wire [ 7:0] env_up = stage == 2'd0 ? env + 8'd1 : (stage == 2'd1 && env == sustain) ? env : env - 8'd1;
reg [ 4:0] xper_up;
reg hzero_up;
always @* begin
xper_up = xper;
hzero_up = hzero;
if (steps) case (env_up)
8'hFF: xper_up = 5'd1;
8'h5D: xper_up = 5'd2;
8'h36: xper_up = 5'd4;
8'h1A: xper_up = 5'd8;
8'h0E: xper_up = 5'd16;
8'h06: xper_up = 5'd30;
8'h00: begin xper_up = 5'd1; hzero_up = 1'b1; end
default: ;
endcase
end
// The voice after this slot's work.
reg [W-1:0] done;
always @* begin
done = r0;
if (osc_slot) begin
if (!test) begin
done[75:52] = acc_up;
done[98:76] = lfsr_up;
end
done[135] = !test && rose_up;
done[123:109] = period ? 15'd0 : rcnt_up;
if (period) done[128:124] = exp_wait ? xcnt_up : 5'd0;
if (steps) begin
done[106:99] = env_up;
if (stage == 2'd0 && env_up == 8'hFF) done[108:107] = 2'd1;
end
done[133:129] = xper_up;
done[134] = hzero_up;
end
if (sync_slot && ctrl[1] && prev_rose) done[75:52] = 24'd0;
end
// The waveform: those chosen are ANDed together, as on the chip.
wire tri_msb = acc[23] ^ (ctrl[2] && prev_acc[23]);
wire [11:0] w_tri = ctrl[4] ? {(tri_msb ? ~acc[22:12] : acc[22:12]), 1'b0} : 12'hFFF;
wire [11:0] w_saw = ctrl[5] ? acc[23:12] : 12'hFFF;
wire [11:0] w_pul = ctrl[6] ? ((test || acc[23:12] >= pw) ? 12'hFFF : 12'h000) : 12'hFFF;
wire [11:0] w_noi = ctrl[7] ? {lfsr[20], lfsr[18], lfsr[14], lfsr[11], lfsr[9], lfsr[5], lfsr[2], lfsr[0], 4'b0000} : 12'hFFF;
wire [11:0] wave = ctrl[7:4] == 4'd0 ? 12'd0 : (w_tri & w_saw & w_pul & w_noi);
// ------------------------------------------------------------------------------ the filter's numbers
// Cutoff: from 30 Hz to 12 kHz in a straight line, as ω₀ = 2π f / 1 MHz, in 65536ths.
wire [19:0] w0_wide = {9'd0, fc} * 20'd616;
wire [15:0] w0 = 16'd12 + w0_wide[19:8];
// 1/Q, in 16384ths: from 1.41 (no resonance) down to 0.59.
reg [15:0] q1;
always @* case (res)
4'd0: q1 = 23174; 4'd1: q1 = 21177; 4'd2: q1 = 19497; 4'd3: q1 = 18064;
4'd4: q1 = 16827; 4'd5: q1 = 15749; 4'd6: q1 = 14800; 4'd7: q1 = 13960;
4'd8: q1 = 13209; 4'd9: q1 = 12536; 4'd10: q1 = 11927; 4'd11: q1 = 11375;
4'd12: q1 = 10872; 4'd13: q1 = 10411; 4'd14: q1 = 9988; default: q1 = 9598;
endcase
// The filter's state (hp high-pass, bp band-pass, lp low-pass), the sums of the voices into it and
// straight out, and the mix.
reg signed [23:0] lp, bp, hp, filt_in, direct, mixed;
// -------------------------------------------------------------------------------- the multiplier
// The wave, centred on zero: wave − 2048, which is the wave with its top bit turned over, as a signed number.
wire signed [15:0] centred = {{5{~wave[11]}}, wave[10:0]};
reg signed [15:0] ma, mb;
always @* case (slot)
4'd0: begin ma = hp[23:8]; mb = w0; end
4'd2: begin ma = bp[23:8]; mb = w0; end
4'd6, 4'd7, 4'd8: begin ma = centred; mb = {8'd0, env}; end
4'd9: begin ma = bp[23:8]; mb = q1; end
default: begin ma = mixed[23:8]; mb = {12'd0, vol}; end
endcase
reg signed [31:0] p; // the product, a slot later
always @(posedge clk) p <= ma * mb;
// Where each voice's product goes, travelling alongside it. With no filter mode chosen, a voice routed to
// the filter passes straight through instead; voice 3 off silences it only when it isn't routed to the
// filter (both as on the real chips).
reg into_filter, silenced;
always @(posedge clk) if (wave_slot) begin
into_filter <= route[vno] && mode != 3'd0;
silenced <= vno == 2'd2 && off3 && !route[2];
end
// ------------------------------------------------------------------------------------ the adder
// Its two numbers, whether it subtracts, for each slot:
// 1 bp + ω₀·hp (the band-pass, from the last microsecond's high-pass)
// 3 lp + ω₀·bp (the low-pass, from the new band-pass)
// 4–6 the mix: the voices that skip the filter, minus each of the filter's outputs chosen (the
// filter's output is inverted on its way to the mixer, as on the chip)
// 7–9 each voice's product, into the filter's input or the mix's (the first starts both afresh)
// 10 filt_in − bp/Q, then 11: − lp (the high-pass)
reg signed [23:0] x, y;
reg minus;
wire signed [23:0] voice = silenced ? 24'sd0 : p[23:0];
always @* begin
x = 24'sd0; y = 24'sd0; minus = 1'b0;
case (slot)
4'd1: begin x = bp; y = p >>> 8; end
4'd3: begin x = lp; y = p >>> 8; end
4'd4: begin x = direct; y = mode[0] ? lp : 24'sd0; minus = 1'b1; end
4'd5: begin x = mixed; y = mode[1] ? bp : 24'sd0; minus = 1'b1; end
4'd6: begin x = mixed; y = mode[2] ? hp : 24'sd0; minus = 1'b1; end
4'd7: begin x = 24'sd0; y = voice; end
4'd8, 4'd9: begin x = into_filter ? filt_in : direct; y = voice; end
4'd10: begin x = filt_in; y = p >>> 6; minus = 1'b1; end
4'd11: begin x = hp; y = lp; minus = 1'b1; end
default: ;
endcase
end
// Subtracting is adding the bits turned over, plus one: so one adder does both.
wire signed [23:0] sum = x + (y ^ {24{minus}}) + {23'd0, minus};
// ---------------------------------------------------------------- the slots, and the CPU's writes
// A CPU write into a voice's registers (voice n is in place n then).
function [W-1:0] written(input [W-1:0] v, input [2:0] r, input [7:0] dv);
begin
written = v;
case (r)
3'd0: written[7:0] = dv;
3'd1: written[15:8] = dv;
3'd2: written[23:16] = dv;
3'd3: written[27:24] = dv[3:0];
3'd4: begin
written[35:28] = dv;
if (dv[3]) begin written[75:52] = 24'd0; written[98:76] = 23'h7FFFF8; end // test
if (!v[28] && dv[0]) begin written[108:107] = 2'd0; written[134] = 1'b0; end // gate on
else if (v[28] && !dv[0]) written[108:107] = 2'd2; // gate off
end
3'd5: written[43:36] = dv;
default: written[51:44] = dv;
endcase
end
endfunction
// A voice after reset: silent, released, its envelope held at zero, the noise register's start.
localparam [W-1:0] FRESH = {1'b0, 1'b1, 5'd1, 5'd0, 15'd0, 2'd2, 8'd0, 23'h7FFFF8, 24'd0, 52'd0};
wire wr = ce && sel && we;
wire [1:0] n = a < 5'd7 ? 2'd0 : a < 5'd14 ? 2'd1 : 2'd2;
wire [2:0] reg7 = a - {n, 3'b000} + {3'b000, n}; // a − 7n
always @(posedge clk) begin
if (rst) begin
r0 <= FRESH; r1 <= FRESH; r2 <= FRESH;
fc <= 0; res <= 0; vol <= 0; route <= 0; mode <= 0; off3 <= 0;
lp <= 0; bp <= 0; hp <= 0; filt_in <= 0; direct <= 0; mixed <= 0; out <= 0;
end else begin
if (slot <= 4'd8) begin // turn the ring
r0 <= r1;
r1 <= r2;
r2 <= done;
end
if (wr && a < 5'd21) case (n)
2'd0: r0 <= written(r0, reg7, d);
2'd1: r1 <= written(r1, reg7, d);
default: r2 <= written(r2, reg7, d);
endcase
if (wr) case (a)
5'd21: fc[2:0] <= d[2:0];
5'd22: fc[10:3] <= d;
5'd23: begin res <= d[7:4]; route <= d[2:0]; end
5'd24: begin vol <= d[3:0]; mode <= d[6:4]; off3 <= d[7]; end
default: ;
endcase
if (slot == 4'd8) begin osc3 <= wave[11:4]; env3 <= env; end
case (slot)
4'd1: bp <= sum;
4'd3: lp <= sum;
4'd4, 4'd5, 4'd6: mixed <= sum;
4'd7: begin
if (into_filter) begin filt_in <= sum; direct <= 24'sd0; end
else begin direct <= sum; filt_in <= 24'sd0; end
end
4'd8, 4'd9: if (into_filter) filt_in <= sum; else direct <= sum;
4'd10, 4'd11: hp <= sum;
default: ;
endcase
// The volume: mixed is up to about ±2^22; out = mixed × vol / 1024.
if (slot == 4'd11) out <= p >>> 2;
end
end
always @* case (a)
5'd27: q = osc3;
5'd28: q = env3;
default: q = 8'hFF;
endcase
endmodule
  • One copy of a voice’s logic can do every voice’s work, a slot each, when the clock is faster than the work needs.
  • Picking each voice’s state by index costs a multiplexer on every bit; keeping it in a ring that turns each slot brings the right voice to the logic instead.
  • Sharing pays when the part shared is big or scarce: for the small engine, half the logic cells with multipliers made of logic, but more cells (and fewer DSP blocks) with DSP blocks.
  • In sound.v: products a slot later, the filter spilling into the next microsecond, the voice before in the last place, and writes only between microseconds.