1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
|
// $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
`define vco_min 14'd1024
`define vco_max 14'd12499
// Model the 76477 SLF. This returns a "sawtooth" value
// between (approximately) [1024,12499] which gives about
// the 10:1 range needed by the VCO.
module slf(
input clk, // 16 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] == `vco_max )
up <= 0;
else if ( ctr[13:10] == `vco_min )
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, // 16 MHz
input [13:0] pitch, // Pitch control
output vco, // VCO squarewave output
output vco2 // VCO output with every other pulse suppressed
);
reg [13: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;
cycle <= cycle + 1;
end
else
ctr <= ctr - 1;
end // always @ (posedge clk)
endmodule // vco
//
// Noise (e.g. random number) generator. The periodicity is ~2 Hz,
// which should be inaudible.
//
module noise(
input clk, // 16 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 envelope,
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:
// This doesn't match the documentation, but if the documentation
// is followed and this is out <= 1, then "out 6,255" is silent,
// which it definitely wasn't on real hardware.
out <= envelope;
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, // 16 MHz
input clk_en, // One pulse every 16 us (62.5 kHz)
input inhibit,
output reg oneshot
);
reg out = 0;
reg inhibit1 = 0;
reg [10:0] ctr = 0;
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 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, // 16 MHz
input clk_en, // One pulse every 16 us (62.5 kHz)
input envelope,
output reg [13:0] env_mag
);
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
//
// Putting it all together...
//
module sound_generator(
input clk,
input stb_16us,
input [2:0] mixer_ctl,
input vco_sel,
input vco_pitch,
input [1:0] envsel,
input inhibit,
output [13:0] magnitude
);
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 clk_en = stb_16us;
slf slf ( .clk (clk),
.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),
.pitch (vco_level),
.vco (w_vco),
.vco2 (w_vco2) );
noise noise ( .clk (clk),
.clk_en (clk_en),
.noise (w_noise) );
mixer mixer ( .slf (w_slf),
.vco (w_vco),
.noise (w_noise),
.envelope (w_envelope),
.mixer_ctl (mixer_ctl),
.mixer_out (w_mixer_out) );
oneshot oneshot ( .clk (clk),
.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),
.clk_en (clk_en),
.envelope (w_envelope),
.env_mag (env_mag) );
assign signal_on = ~inhibit & w_mixer_out;
assign magnitude = env_mag & {14{signal_on}};
endmodule // sound_generator
//
// I2S output module - outputs 256 clocks/frame
//
module sound_i2s(
input i2s_clk, // 16 MHz
input [2:0] mixer_ctl,
input vco_sel,
input vco_pitch,
input [1:0] envsel,
input inhibit,
output i2s_dat,
output i2s_lrck
);
reg [7:0] ctr;
wire [13:0] magnitude;
reg [13:0] sample;
reg [13:0] serial_out;
wire stb_16us = &ctr;
assign i2s_dat = serial_out[13];
assign i2s_lrck = ctr[7];
always @(posedge i2s_clk)
ctr <= ctr + 1;
always @(posedge i2s_clk)
if (stb_16us)
sample <= magnitude;
// We load serial_out after clock 1, which means each frame has two
// zero padding bits at the front. Bit 0 is required by I2S standard
// format, and bit 1 means our unsigned output is always in the positive
// half of the signed number space.
always @(posedge i2s_clk)
if (ctr[6:0] == 7'h01)
serial_out <= sample;
else
serial_out <= { serial_out[12:0], 1'b0 };
sound_generator sound_generator (
.clk (i2s_clk),
.stb_16us (stb_16us),
.mixer_ctl (mixer_ctl),
.vco_sel (vco_sel),
.vco_pitch (vco_pitch),
.envsel (envsel),
.inhibit (inhibit),
.magnitude (magnitude)
);
endmodule // sound_i2s
|