aboutsummaryrefslogtreecommitdiffstats
path: root/sound.v
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2005-01-31 18:01:40 +0000
committerH. Peter Anvin <hpa@zytor.com>2005-01-31 18:01:40 +0000
commit45ce78ae8db253547475f0e344f224e3b8b0defc (patch)
treeac0291eb20b6dfcbe32f8993b56b7ed6d6e11a45 /sound.v
parent362d3b89e4f5f69e07ef56c39b63d0396172ef10 (diff)
downloadabc80-45ce78ae8db253547475f0e344f224e3b8b0defc.tar.gz
abc80-45ce78ae8db253547475f0e344f224e3b8b0defc.tar.xz
abc80-45ce78ae8db253547475f0e344f224e3b8b0defc.zip
Actual sound engine
Diffstat (limited to 'sound.v')
-rw-r--r--sound.v328
1 files changed, 328 insertions, 0 deletions
diff --git a/sound.v b/sound.v
new file mode 100644
index 0000000..598d7ed
--- /dev/null
+++ b/sound.v
@@ -0,0 +1,328 @@
+// $Id$
+//
+// Sound controller for ABC80
+//
+// This attempts to simulate the SN76477 sound generator
+// *as used in ABC80*.
+//
+// ABC80 has the following (approximate) parameters:
+//
+// SLF = 2.9 Hz
+// VCOmin = 640 Hz
+// Noise cutoff = 10 kHz
+// Attack = 22 ms
+// Decay = 470 ms
+
+
+// Model the 76477 SLF. This returns a "sawtooth" value
+// between (approximately) [1024,12288) which gives about
+// the 10:1 range needed by the VCO.
+`define vco_min 14'd1024
+`define vco_max 14'd12288
+
+module slf(
+ input clk, // 25 MHz
+ input clk_en, // One pulse every 16 us (62.5 kHz)
+ output slf, // SLF squarewave
+ output [13:0] saw // Sawtooth magnitude
+ );
+ reg up = 1;
+ reg [13:0] ctr = `vco_min;
+
+ assign slf = up;
+ assign saw = ctr;
+
+ always @(posedge clk)
+ if ( clk_en )
+ begin
+ if ( ctr[13:10] == 4'b1100 )
+ up <= 0;
+ else if ( ctr[13:10] == 4'b0000 )
+ up <= 1;
+
+ if ( up )
+ ctr <= ctr + 1;
+ else
+ ctr <= ctr - 1;
+ end // if ( clk_en )
+endmodule // slf
+
+//
+// The VCO. The output frequency = clk/pitch/2.
+//
+module vco(
+ input clk, // 25 MHz
+ input [13:0] pitch, // Pitch control
+ output vco, // VCO squarewave output
+ output vco2 // VCO output with every other pulse suppressed
+ );
+ reg [14:0] ctr = 0;
+ reg [1:0] cycle;
+
+ assign vco = cycle[0];
+ assign vco2 = cycle[0] & cycle[1];
+
+ always @(posedge clk)
+ begin
+ if ( ctr == 0 )
+ begin
+ ctr <= { pitch, 1'b0 };
+ cycle <= cycle + 1;
+ end
+ else
+ ctr <= ctr - 1;
+ end // always @ (posedge clk)
+endmodule // vco
+
+//
+// Noise (e.g. random number) generator./
+//
+module noise(
+ input clk, // 25 MHz
+ input clk_en, // One pulse every 16 us (62.5 kHz)
+ output noise
+ );
+ reg [15:0] lfsr = ~16'h0; // Must be nonzero
+
+ assign noise = lfsr[15];
+
+ wire lfsr_zero = (lfsr == 0);
+
+ always @(posedge clk)
+ if ( clk_en )
+ lfsr <= { lfsr[14:0], lfsr_zero } ^ (lfsr[15] ? 16'h54b9 : 16'h0);
+endmodule // noise
+
+//
+// Mixer
+//
+module mixer(
+ input slf,
+ input vco,
+ input noise,
+ input [2:0] mixer_ctl,
+ output mixer_out
+ );
+ reg out;
+
+ assign mixer_out = out;
+
+ always @(*)
+ case ( mixer_ctl )
+ 3'b000:
+ out <= vco;
+ 3'b001:
+ out <= slf;
+ 3'b010:
+ out <= noise;
+ 3'b011:
+ out <= vco & noise;
+ 3'b100:
+ out <= slf & noise;
+ 3'b101:
+ out <= slf & vco & noise;
+ 3'b110:
+ out <= slf & vco;
+ 3'b111:
+ out <= 1;
+ endcase // case( mixer_ctl )
+endmodule // mixer
+
+//
+// Envelope generator, consisting of one-shot generator,
+// envelope select, and envelope generation (attack/decay.)
+// Output is parallel digital.
+//
+module oneshot(
+ input clk, // 25 MHz
+ input clk_en, // One pulse every 16 us (62.5 kHz)
+ input inhibit,
+ output oneshot
+ );
+ reg out = 0;
+ reg inhibit1 = 0;
+ reg [10:0] ctr = 0;
+
+ reg oneshot;
+
+ wire ctr_or = |ctr;
+
+ always @(posedge clk)
+ if ( clk_en )
+ begin
+ inhibit1 <= inhibit;
+ oneshot <= ctr_or;
+
+ if ( ~inhibit & inhibit1 )
+ ctr <= 11'd1624; // ~26 ms
+ else if ( ctr_or )
+ ctr <= ctr - 1;
+ end
+endmodule // oneshot
+
+module envelope_select(
+ input [1:0] envsel,
+ input oneshot,
+ input vco,
+ input vco2,
+ output envelope
+ );
+ reg envelope;
+
+ always @(*)
+ begin
+ case ( envsel )
+ 2'b00:
+ envelope <= vco;
+ 2'b01:
+ envelope <= 1;
+ 2'b10:
+ envelope <= oneshot;
+ 2'b11:
+ envelope <= vco2;
+ endcase // case( envsel )
+ end // always @ (*)
+endmodule // envelope_select
+
+module envelope_shape(
+ input clk, // 25 MHz
+ input clk_en, // One pulse every 16 us (62.5 kHz)
+ input envelope,
+ output [13:0] env_mag
+ );
+ reg [13:0] env_mag = 0;
+
+ always @(posedge clk)
+ if ( clk_en )
+ begin
+ if ( envelope )
+ begin
+ if ( env_mag[13:11] != 3'b111 )
+ env_mag <= env_mag + 20;
+ end
+ else
+ begin
+ if ( |env_mag )
+ env_mag <= env_mag - 1;
+ end
+ end // if ( clk_en )
+endmodule // envelope_shape
+
+//
+// Delta-sigma DAC for the final output
+//
+module dac(
+ input [13:0] magnitude,
+ input clk, // The higher the better
+ output signal
+ );
+ reg [13:0] mag_latch = 0;
+ reg [15:0] sigma_latch = 0;
+
+ assign signal = sigma_latch[15];
+
+ wire [15:0] delta_add = { sigma_latch[15], sigma_latch[15], mag_latch };
+ wire [15:0] sigma_add = delta_add + sigma_latch;
+
+
+ always @(posedge clk)
+ begin
+ mag_latch <= magnitude;
+ sigma_latch <= sigma_add;
+ end
+endmodule // dac
+
+//
+// Putting it all together...
+//
+module sound_generator(
+ input ctr_16us,
+ input clk_fast,
+ input clk_25MHz,
+
+ input [2:0] mixer_ctl,
+ input vco_sel,
+ input vco_pitch,
+ input [1:0] envsel,
+ input inhibit,
+
+ output signal,
+ output [7:0] debug
+ );
+ wire w_slf;
+ wire [13:0] saw;
+ wire [13:0] vco_level;
+ wire w_vco;
+ wire w_vco2;
+ wire w_envelope;
+ wire w_noise;
+ wire w_oneshot;
+ wire w_mixer_out;
+
+ wire [13:0] env_mag;
+ wire signal_on;
+ wire [13:0] magnitude;
+
+ reg ctr_16us_1;
+ reg clk_en;
+
+ // ctr_16us should be synchronous with clk_25MHz.
+ // We set clk_en to 1 for one cycle every positive edge of ctr_16us.
+
+ always @(posedge clk_25MHz)
+ begin
+ ctr_16us_1 <= ctr_16us;
+ clk_en <= ctr_16us & ~ctr_16us_1;
+ end
+
+ slf slf ( .clk (clk_25MHz),
+ .clk_en (clk_en),
+ .saw (saw),
+ .slf (w_slf) );
+
+ assign vco_level = vco_sel ? saw : vco_pitch ? `vco_max : `vco_min;
+
+ vco vco ( .clk (clk_25MHz),
+ .pitch (vco_level),
+ .vco (w_vco),
+ .vco2 (w_vco2) );
+
+ noise noise ( .clk (clk_25MHz),
+ .clk_en (clk_en),
+ .noise (w_noise) );
+
+
+ mixer mixer ( .slf (w_slf),
+ .vco (w_vco),
+ .noise (w_noise),
+ .mixer_ctl (mixer_ctl),
+ .mixer_out (w_mixer_out) );
+
+
+ oneshot oneshot ( .clk (clk_25MHz),
+ .clk_en (clk_en),
+ .inhibit (inhibit),
+ .oneshot (w_oneshot) );
+
+ envelope_select envelope_select ( .envsel (envsel),
+ .oneshot (w_oneshot),
+ .vco (w_vco),
+ .vco2 (w_vco2),
+ .envelope (w_envelope) );
+
+ envelope_shape envelope_shape ( .clk (clk_25MHz),
+ .clk_en (clk_en),
+ .envelope (w_envelope),
+ .env_mag (env_mag) );
+
+
+ assign signal_on = ~inhibit & w_mixer_out;
+ assign magnitude = signal_on ? env_mag : 0;
+
+ dac dac ( .magnitude (magnitude),
+ .clk (clk_fast),
+ .signal (signal) );
+
+ assign debug = { w_noise, w_slf, w_vco, w_vco2, w_oneshot, w_envelope, w_mixer_out, signal_on };
+
+endmodule // sound_generator