aboutsummaryrefslogtreecommitdiffstats
path: root/sound.v
blob: 6f8626ce3785ab76e1aa375c327b759b9f0e3856 (plain)
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