| `timescale 1ns / 1ps |
| ////////////////////////////////////////////////////////////////////////////////// |
| // Company: |
| // Engineer: Wenting Zhang |
| // |
| // Create Date: 12:29:37 04/07/2018 |
| // Module Name: sound |
| // Project Name: VerilogBoy |
| // Description: |
| // GameBoy sound unit main file |
| // Dependencies: |
| // |
| // Additional Comments: |
| // On a real gameboy, audio mixing is done with an OpAmp (I am not sure, but |
| // this makes most sense according to the documents we have). I am using adder |
| // here to make that happen. Also, audio volume control is probably done with a |
| // PGA on a real gameboy, and I am using multiplication to implement that here. |
| // So this would synthesis some additional adders and multipliers which should |
| // not be part of a Game Boy. |
| ////////////////////////////////////////////////////////////////////////////////// |
| module sound( |
| input wire clk, |
| input wire rst, |
| input wire [15:0] a, |
| output reg [7:0] dout, |
| input wire [7:0] din, |
| input wire rd, |
| input wire wr, |
| output wire [15:0] left, |
| output wire [15:0] right, |
| // debug |
| output wire [3:0] ch1_level, |
| output wire [3:0] ch2_level, |
| output wire [3:0] ch3_level, |
| output wire [3:0] ch4_level |
| ); |
| |
| // Sound registers |
| reg [7:0] regs [0:31]; |
| |
| /* verilator lint_off UNUSED */ |
| wire [7:0] reg_nr10 = regs[00]; // $FF10 Channel 1 Sweep register (RW) |
| wire [7:0] reg_nr11 = regs[01]; // $FF11 Channel 1 Sound length/wave patternduty (RW) |
| wire [7:0] reg_nr12 = regs[02]; // $FF12 Channel 1 Volume envelope (RW) |
| wire [7:0] reg_nr13 = regs[03]; // $FF13 Channel 1 Freqency lo (W) |
| wire [7:0] reg_nr14 = regs[04]; // $FF14 Channel 1 Freqency hi (RW) |
| wire [7:0] reg_nr21 = regs[06]; // $FF16 Channel 2 Sound length/wave patternduty (RW) |
| wire [7:0] reg_nr22 = regs[07]; // $FF17 Channel 2 Volume envelope (RW) |
| wire [7:0] reg_nr23 = regs[08]; // $FF18 Channel 2 Freqency lo (W) |
| wire [7:0] reg_nr24 = regs[09]; // $FF19 Channel 2 Freqency hi (RW) |
| wire [7:0] reg_nr30 = regs[10]; // $FF1A Channel 3 Sound on/off (RW) |
| wire [7:0] reg_nr31 = regs[11]; // $FF1B Channel 3 Sound length (?) |
| wire [7:0] reg_nr32 = regs[12]; // $FF1C Channel 3 Select output level (RW) |
| wire [7:0] reg_nr33 = regs[13]; // $FF1D Channel 3 Frequency lo (W) |
| wire [7:0] reg_nr34 = regs[14]; // $FF1E Channel 3 Frequency hi (RW) |
| wire [7:0] reg_nr41 = regs[16]; // $FF20 Channel 4 Sound length (RW) |
| wire [7:0] reg_nr42 = regs[17]; // $FF21 Channel 4 Volume envelope (RW) |
| wire [7:0] reg_nr43 = regs[18]; // $FF22 Channel 4 Polynomial counter (RW) |
| wire [7:0] reg_nr44 = regs[19]; // $FF23 Channel 4 Counter/consecutive; Initial(RW) |
| wire [7:0] reg_nr50 = regs[20]; // $FF24 Channel contorl / ON-OFF / Volume (RW) |
| wire [7:0] reg_nr51 = regs[21]; // $FF25 Selection of Sound output terminal (RW) |
| wire [7:0] reg_nr52 = regs[22]; // $FF26 Sound on/off |
| /* verilator lint_on UNUSED */ |
| wire [4:0] reg_addr = {~a[4], a[3:0]}; // Convert 10-20 to 00-10 |
| |
| wire [2:0] ch1_sweep_time = reg_nr10[6:4]; |
| wire ch1_sweep_decreasing = reg_nr10[3]; |
| wire [2:0] ch1_num_sweep_shifts = reg_nr10[2:0]; |
| wire [1:0] ch1_wave_duty = reg_nr11[7:6]; |
| wire [5:0] ch1_length = reg_nr11[5:0]; |
| wire [3:0] ch1_initial_volume = reg_nr12[7:4]; |
| wire ch1_envelope_increasing = reg_nr12[3]; |
| wire [2:0] ch1_num_envelope_sweeps = reg_nr12[2:0]; |
| reg ch1_start; |
| wire ch1_single = reg_nr14[6]; |
| wire [10:0] ch1_frequency = {reg_nr14[2:0], reg_nr13[7:0]}; |
| wire [1:0] ch2_wave_duty = reg_nr21[7:6]; |
| wire [5:0] ch2_length = reg_nr21[5:0]; |
| wire [3:0] ch2_initial_volume = reg_nr22[7:4]; |
| wire ch2_envelope_increasing = reg_nr22[3]; |
| wire [2:0] ch2_num_envelope_sweeps = reg_nr22[2:0]; |
| reg ch2_start; |
| wire ch2_single = reg_nr24[6]; |
| wire [10:0] ch2_frequency = {reg_nr24[2:0], reg_nr23[7:0]}; |
| wire [7:0] ch3_length = reg_nr31[7:0]; |
| wire ch3_on = reg_nr30[7]; |
| wire [1:0] ch3_volume = reg_nr32[6:5]; |
| reg ch3_start; |
| wire ch3_single = reg_nr34[6]; |
| wire [10:0] ch3_frequency = {reg_nr34[2:0], reg_nr33[7:0]}; |
| wire [5:0] ch4_length = reg_nr41[5:0]; |
| wire [3:0] ch4_initial_volume = reg_nr42[7:4]; |
| wire ch4_envelope_increasing = reg_nr42[3]; |
| wire [2:0] ch4_num_envelope_sweeps = reg_nr42[2:0]; |
| wire [3:0] ch4_shift_clock_freq = reg_nr43[7:4]; |
| wire ch4_counter_width = reg_nr43[3]; // 0 = 15 bits, 1 = 7 bits |
| wire [2:0] ch4_freq_dividing_ratio = reg_nr43[2:0]; |
| reg ch4_start; |
| wire ch4_single = reg_nr44[6]; |
| wire s02_vin = reg_nr50[7]; |
| wire [2:0] s02_output_level = reg_nr50[6:4]; |
| wire s01_vin = reg_nr50[3]; |
| wire [2:0] s01_output_level = reg_nr50[2:0]; |
| wire s02_ch4_enable = reg_nr51[7]; |
| wire s02_ch3_enable = reg_nr51[6]; |
| wire s02_ch2_enable = reg_nr51[5]; |
| wire s02_ch1_enable = reg_nr51[4]; |
| wire s01_ch4_enable = reg_nr51[3]; |
| wire s01_ch3_enable = reg_nr51[2]; |
| wire s01_ch2_enable = reg_nr51[1]; |
| wire s01_ch1_enable = reg_nr51[0]; |
| wire sound_enable = reg_nr52[7]; |
| wire ch4_on_flag; |
| wire ch3_on_flag; |
| wire ch2_on_flag; |
| wire ch1_on_flag; |
| |
| reg [7:0] wave [0:15]; |
| wire [3:0] wave_addr_ext = a[3:0]; |
| wire [3:0] wave_addr_int; |
| wire [3:0] wave_addr = (ch3_on) ? (wave_addr_int) : (wave_addr_ext); |
| wire [7:0] wave_data = wave[wave_addr]; |
| |
| wire addr_in_regs = (a >= 16'hFF10 && a <= 16'hFF2F); |
| wire addr_in_wave = (a >= 16'hFF30 && a <= 16'hFF3F); |
| |
| // Bus RW |
| // Bus RW - Combinational Read |
| // This is a drawback of ISE XST, one can not use always@(*) and reg array together, |
| // so one have to write something (does not need to make sense, just as a place holder) |
| // and let the synthesizer to determine the correct sensitvity list. (Or one would have |
| // to enumerate EACH item in an array, otherwise it will give an error. |
| always @(a) |
| begin |
| dout = 8'hFF; |
| if (addr_in_regs) begin |
| if (a == 16'hFF26) |
| dout = {sound_enable, 3'b0, ch4_on_flag, ch3_on_flag, ch2_on_flag, ch1_on_flag}; |
| else |
| dout = regs[reg_addr]; |
| end |
| else |
| if (addr_in_wave) begin |
| dout = wave[wave_addr]; |
| end |
| end |
| |
| // Bus RW - Sequential Write |
| integer i; |
| |
| always @(posedge clk) |
| begin |
| if (rst) begin |
| for (i = 0; i < 32; i = i+1) begin |
| regs[i] <= 8'b0; |
| end |
| // wave pattern should not be initialized |
| end |
| else begin |
| if (wr) begin |
| if (addr_in_regs) begin |
| if (a == 16'hFF26) begin |
| if (din[7] == 0) begin |
| for (i = 0; i < 32; i = i+1) begin |
| regs[i] <= 8'b0; |
| end |
| end |
| else |
| regs[reg_addr] <= din; |
| end |
| else if (sound_enable) begin |
| regs[reg_addr] <= din; |
| end |
| end |
| else if (addr_in_wave) |
| //wave[wave_addr_ext] <= din; //what if we allow Write any way? |
| wave[wave_addr] <= din; // This is what happens trying to write to wave sample while it is on |
| end |
| // Initialize signal, should be triggered whenever a 1 is written |
| if ((wr)&&(a == 16'hFF14)) ch1_start <= din[7]; |
| else ch1_start <= 0; |
| if ((wr)&&(a == 16'hFF19)) ch2_start <= din[7]; |
| else ch2_start <= 0; |
| if ((wr)&&(a == 16'hFF1E)) ch3_start <= din[7]; |
| else ch3_start <= 0; |
| if ((wr)&&(a == 16'hFF23)) ch4_start <= din[7]; |
| else ch4_start <= 0; |
| end |
| end |
| |
| // Clock Enables (not clock) |
| wire clk_length_ctr; // 256Hz Length Control Clock |
| wire clk_vol_env; // 64Hz Volume Enevelope Clock |
| wire clk_sweep; // 128Hz Sweep Clock |
| wire clk_freq_div; // 2097152Hz Frequency Division Clock |
| |
| reg [15:0] clk_div; |
| always @(posedge clk) begin |
| if (rst) begin |
| clk_div <= 0; |
| end |
| else begin |
| clk_div <= clk_div + 1; |
| end |
| end |
| assign clk_length_ctr = clk_div[13:0] == {14{1'b1}}; |
| assign clk_vol_env = clk_div[15:0] == {16{1'b1}}; |
| assign clk_sweep = clk_div[14:0] == {15{1'b1}}; |
| assign clk_freq_div = clk_div[0] == 1'b1; |
| |
| // Channels |
| wire [3:0] ch1; |
| wire [3:0] ch2; |
| wire [3:0] ch3; |
| wire [3:0] ch4; |
| |
| sound_square sound_ch1( |
| .rst(~sound_enable), |
| .clk(clk), |
| .clk_length_ctr(clk_length_ctr), |
| .clk_vol_env(clk_vol_env), |
| .clk_sweep(clk_sweep), |
| .clk_freq_div(clk_freq_div), |
| .sweep_time(ch1_sweep_time), |
| .sweep_decreasing(ch1_sweep_decreasing), |
| .num_sweep_shifts(ch1_num_sweep_shifts), |
| .wave_duty(ch1_wave_duty), |
| .length(ch1_length), |
| .initial_volume(ch1_initial_volume), |
| .envelope_increasing(ch1_envelope_increasing), |
| .num_envelope_sweeps(ch1_num_envelope_sweeps), |
| .start(ch1_start), |
| .single(ch1_single), |
| .frequency(ch1_frequency), |
| .level(ch1), |
| .enable(ch1_on_flag) |
| ); |
| |
| sound_square sound_ch2( |
| .rst(~sound_enable), |
| .clk(clk), |
| .clk_length_ctr(clk_length_ctr), |
| .clk_vol_env(clk_vol_env), |
| .clk_sweep(clk_sweep), |
| .clk_freq_div(clk_freq_div), |
| .sweep_time(3'b0), |
| .sweep_decreasing(1'b0), |
| .num_sweep_shifts(3'b0), |
| .wave_duty(ch2_wave_duty), |
| .length(ch2_length), |
| .initial_volume(ch2_initial_volume), |
| .envelope_increasing(ch2_envelope_increasing), |
| .num_envelope_sweeps(ch2_num_envelope_sweeps), |
| .start(ch2_start), |
| .single(ch2_single), |
| .frequency(ch2_frequency), |
| .level(ch2), |
| .enable(ch2_on_flag) |
| ); |
| |
| sound_wave sound_ch3( |
| .rst(~sound_enable), |
| .clk(clk), |
| .clk_length_ctr(clk_length_ctr), |
| .length(ch3_length), |
| .volume(ch3_volume), |
| .on(ch3_on), |
| .single(ch3_single), |
| .start(ch3_start), |
| .frequency(ch3_frequency), |
| .wave_a(wave_addr_int), |
| .wave_d(wave_data), |
| .level(ch3), |
| .enable(ch3_on_flag) |
| ); |
| |
| sound_noise sound_ch4( |
| .rst(~sound_enable), |
| .clk(clk), |
| .clk_length_ctr(clk_length_ctr), |
| .clk_vol_env(clk_vol_env), |
| .length(ch4_length), |
| .initial_volume(ch4_initial_volume), |
| .envelope_increasing(ch4_envelope_increasing), |
| .num_envelope_sweeps(ch4_num_envelope_sweeps), |
| .shift_clock_freq(ch4_shift_clock_freq), |
| .counter_width(ch4_counter_width), |
| .freq_dividing_ratio(ch4_freq_dividing_ratio), |
| .start(ch4_start), |
| .single(ch4_single), |
| .level(ch4), |
| .enable(ch4_on_flag) |
| ); |
| |
| // Mixer |
| |
| /* |
| // Signed mixer |
| wire [5:0] sign_extend_ch1 = {{3{ch1[3]}}, ch1[2:0]}; |
| wire [5:0] sign_extend_ch2 = {{3{ch2[3]}}, ch2[2:0]}; |
| wire [5:0] sign_extend_ch3 = {{3{ch3[3]}}, ch3[2:0]}; |
| wire [5:0] sign_extend_ch4 = {{3{ch4[3]}}, ch4[2:0]}; |
| reg [5:0] mixed_s01; |
| reg [5:0] mixed_s02; |
| |
| always @(*) |
| begin |
| mixed_s01 = 6'd0; |
| mixed_s02 = 6'd0; |
| if (s01_ch1_enable) mixed_s01 = mixed_s01 + sign_extend_ch1; |
| if (s01_ch2_enable) mixed_s01 = mixed_s01 + sign_extend_ch2; |
| if (s01_ch3_enable) mixed_s01 = mixed_s01 + sign_extend_ch3; |
| if (s01_ch4_enable) mixed_s01 = mixed_s01 + sign_extend_ch4; |
| if (s02_ch1_enable) mixed_s02 = mixed_s02 + sign_extend_ch1; |
| if (s02_ch2_enable) mixed_s02 = mixed_s02 + sign_extend_ch2; |
| if (s02_ch3_enable) mixed_s02 = mixed_s02 + sign_extend_ch3; |
| if (s02_ch4_enable) mixed_s02 = mixed_s02 + sign_extend_ch4; |
| end |
| |
| assign left = (sound_enable) ? {mixed_s01[5:0], 14'b0} : 20'b0; |
| assign right = (sound_enable) ? {mixed_s02[5:0], 14'b0} : 20'b0; |
| */ |
| |
| // Unsigned mixer |
| reg [5:0] added_s01; |
| reg [5:0] added_s02; |
| always @(*) |
| begin |
| added_s01 = 6'd0; |
| added_s02 = 6'd0; |
| if (s01_ch1_enable) added_s01 = added_s01 + {2'b0, ch1}; |
| if (s01_ch2_enable) added_s01 = added_s01 + {2'b0, ch2}; |
| if (s01_ch3_enable) added_s01 = added_s01 + {2'b0, ch3}; |
| if (s01_ch4_enable) added_s01 = added_s01 + {2'b0, ch4}; |
| if (s02_ch1_enable) added_s02 = added_s02 + {2'b0, ch1}; |
| if (s02_ch2_enable) added_s02 = added_s02 + {2'b0, ch2}; |
| if (s02_ch3_enable) added_s02 = added_s02 + {2'b0, ch3}; |
| if (s02_ch4_enable) added_s02 = added_s02 + {2'b0, ch4}; |
| end |
| |
| wire [8:0] mixed_s01 = added_s01 * s01_output_level; |
| wire [8:0] mixed_s02 = added_s02 * s02_output_level; |
| |
| assign left = (sound_enable) ? {1'b0, mixed_s01[8:0], 6'b0} : 16'b0; |
| assign right = (sound_enable) ? {1'b0, mixed_s02[8:0], 6'b0} : 16'b0; |
| |
| // Debug Output |
| assign ch1_level = ch1; |
| assign ch2_level = ch2; |
| assign ch3_level = ch3; |
| assign ch4_level = ch4; |
| |
| endmodule |