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.
1 · Three voices, three copies
Section titled “1 · Three voices, three copies”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.
2 · How many samples does a sound need?
Section titled “2 · How many samples does a sound need?”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; endendThe 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.
3 · One copy, taking turns
Section titled “3 · One copy, taking turns”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.
4 · The ring
Section titled “4 · The ring”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.
5 · What it saves
Section titled “5 · What it saves”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 engine | Multipliers made of logic | With DSP blocks |
|---|---|---|
| Three copies, every clock | 576 logic cells | 128 logic cells, 3 DSP blocks |
| Three copies, a sample every 4 clocks | 578 | 132, 3 DSP blocks |
| One copy, picking by index | 406 | 235, 1 DSP block |
| One copy, a ring | 315 | 160, 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.
6 · The catches in the real thing
Section titled “6 · The catches in the real thing”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;endif (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);endcaseIt’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};7 · The whole of sound.v
Section titled “7 · The whole of sound.v”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
// 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; endcaseendmoduleSummary
Section titled “Summary”- 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.