diff --git a/TSConf.sdc b/TSConf.sdc index f232903..48f08b5 100644 --- a/TSConf.sdc +++ b/TSConf.sdc @@ -29,9 +29,12 @@ set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|cl set_multicycle_path -to [get_ports {VGA_*}] -setup 5 set_multicycle_path -to [get_ports {VGA_*}] -hold 4 -set_false_path -to {dac:*} +# Some relaxed constrain for DAC, which is feed by 28 MHz derived clock +set_multicycle_path -to {dac|*} -setup 3 +set_multicycle_path -to {dac|*} -hold 2 set_false_path -to [get_ports {AUDIO_L}] set_false_path -to [get_ports {AUDIO_R}] + set_false_path -to [get_ports {LED}] set_false_path -from [get_ports {UART_RX}] diff --git a/TSConf.sv b/TSConf.sv index d1600dd..3516703 100644 --- a/TSConf.sv +++ b/TSConf.sv @@ -407,19 +407,25 @@ mist_video #(.COLOR_DEPTH(8), .SD_HCNT_WIDTH(11), .OUT_COLOR_DEPTH(VGA_BITS), .B ////////////////// SOUND /////////////////// -dac #(.C_bits(16)) dac_l ( - .clk_i(clk_sys), - .res_n_i(~init_reset), - .dac_i({~SOUND_L[15], SOUND_L[14:0]}), - .dac_o(AUDIO_L) +wire dac_l, dac_r; +hybrid_pwm_sd_2ndorder #(.signalwidth(16)) dac ( + .clk(clk_sys & ce_28m), + .reset_n(~init_reset), + .d_l({~SOUND_L[15], SOUND_L[14:0]}), + .q_l(dac_l), + .d_r({~SOUND_R[15], SOUND_R[14:0]}), + .q_r(dac_r) ); -dac #(.C_bits(16)) dac_r ( - .clk_i(clk_sys), - .res_n_i(~init_reset), - .dac_i({~SOUND_R[15], SOUND_R[14:0]}), - .dac_o(AUDIO_R) -); +reg [23:0] mute_cnt = 0; +always @(posedge clk_sys) begin + if (init_reset) + mute_cnt <= 1; + else if (mute_cnt && ce_28m) + mute_cnt <= mute_cnt + 1'b1; +end +assign AUDIO_L = mute_cnt? 1'bZ : dac_l; +assign AUDIO_R = mute_cnt? 1'bZ : dac_r; endmodule diff --git a/files.qip b/files.qip index dd1bab7..3cdbb03 100644 --- a/files.qip +++ b/files.qip @@ -16,7 +16,8 @@ set_global_assignment -name SYSTEMVERILOG_FILE rtl/sound/turbosound.sv set_global_assignment -name SYSTEMVERILOG_FILE rtl/sound/saa1099.sv set_global_assignment -name VERILOG_FILE rtl/sound/gs.v set_global_assignment -name VERILOG_FILE rtl/sound/gs_top.v -set_global_assignment -name SYSTEMVERILOG_FILE rtl/sound/compressor.sv +set_global_assignment -name VERILOG_FILE rtl/sound/compressor.v +set_global_assignment -name VERILOG_FILE rtl/sound/hybrid_pwm_sd_2ndorder.v set_global_assignment -name VERILOG_FILE rtl/video/video_ts_render.v set_global_assignment -name VERILOG_FILE rtl/video/video_ts.v set_global_assignment -name VERILOG_FILE rtl/video/video_sync.v diff --git a/rtl/sound/compressor.sv b/rtl/sound/compressor.v similarity index 94% rename from rtl/sound/compressor.sv rename to rtl/sound/compressor.v index d4b9567..613ac41 100644 --- a/rtl/sound/compressor.sv +++ b/rtl/sound/compressor.v @@ -20,9 +20,9 @@ module compressor ( - input clk, - input [11:0] in1, in2, - output [15:0] out1, out2 + input clk, + input [11:0] in1, in2, + output reg [15:0] out1, out2 ); reg [10:0] a1; diff --git a/rtl/sound/hybrid_pwm_sd_2ndorder.v b/rtl/sound/hybrid_pwm_sd_2ndorder.v new file mode 100644 index 0000000..f442a8a --- /dev/null +++ b/rtl/sound/hybrid_pwm_sd_2ndorder.v @@ -0,0 +1,255 @@ +// Hybrid PWM / Sigma Delta DAC +// +// 16-bit Sigma Delta with 5-bit output, feeding a PWM. + +// If rising and falling edges aren't perfectly symmetrical, a significant +// amount of noise can be introduced into a sigma-delta DAC, since the number +// of rising- and falling-edges within a given time period is either directly +// dependent upon the code, or pseudo-random. + +// The PWM output stage, on the other hand, results results in a constant +// number of rising- and falling-edges in a given time period, so any edge imbalance +// will result in a DC offset rather than audible noise. + +// 2nd order variant with low-pass input filter and high-pass feedback filter. +// Copyright 2021 by Alastair M. Robinson + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that they will +// be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see +// + + +module hybrid_pwm_sd_2ndorder #(parameter signalwidth=16, parameter filtersize=4) +( + input clk, + input reset_n, + input [signalwidth-1:0] d_l, + output q_l, + input [signalwidth-1:0] d_r, + output q_r +); + +reg q_l_reg; +reg q_r_reg; +assign q_l=q_l_reg; +assign q_r=q_r_reg; + +reg [12:0] initctr; +reg init = 1'b1; +reg initfilterena; + +always @(posedge clk) +begin + initfilterena<=1'b0; + + if(init) + begin + if(infilterena) + begin + initctr<=initctr+1'b1; + if(initctr==0) + initfilterena<=1'b1; + end + if(infiltered_l[signalwidth-1:3]==d_l[signalwidth-1:3]) + init<=1'b0; + end +end + +// Input filtering - a simple single-pole IIR low-pass filter. +// configurable number of bits. + +wire [signalwidth-1:0] infiltered_l; +wire [signalwidth-1:0] infiltered_r; +reg infilterena; + +iirfilter_stereo # (.signalwidth(signalwidth),.cbits(filtersize),.immediate(0)) inputfilter +( + .clk(clk), + .reset_n(reset_n), + .ena(init ? initfilterena : infilterena), + .d_l(d_l), + .d_r(d_r), + .q_l(infiltered_l), + .q_r(infiltered_r) +); + + +// Approximation of reconstruction filter, +// subtracted from the incoming signal to +// steer the first stage of the sigma delta. + +// 9 bits for the coefficient (1/512) + +wire [signalwidth-1:0] outfiltered_l; +wire [signalwidth-1:0] outfiltered_r; + +iirfilter_stereo # (.signalwidth(signalwidth),.cbits(9),.immediate(1)) outputfilter +( + .clk(clk), + .reset_n(reset_n), + .ena(1'b1), + .d_l(q_l_reg ? {signalwidth{1'b1}} : {signalwidth{1'b0}}), + .d_r(q_r_reg ? {signalwidth{1'b1}} : {signalwidth{1'b0}}), + .q_l(outfiltered_l), + .q_r(outfiltered_r) +); + +reg [6:0] pwmcounter; +wire [6:0] pwmthreshold_l; +wire [6:0] pwmthreshold_r; +reg [33:0] scaledin; +reg [signalwidth+1:0] sigma_l; +reg [signalwidth+1:0] sigma2_l; +reg [signalwidth+1:0] sigma_r; +reg [signalwidth+1:0] sigma2_r; + +wire [signalwidth+1:0] sigmanext_l; +wire [signalwidth+1:0] sigmanext_r; + +assign sigmanext_l = sigma_l+{2'b0,infiltered_l}-{2'b0,outfiltered_l}; +assign sigmanext_r = sigma_r+{2'b0,infiltered_r}-{2'b0,outfiltered_r}; + +assign pwmthreshold_l = sigma2_l[signalwidth+1:signalwidth-5]; +assign pwmthreshold_r = sigma2_r[signalwidth+1:signalwidth-5]; + + +always @(posedge clk,negedge reset_n) +begin + if(!reset_n) begin + sigma_l<={signalwidth+2{1'b0}}; + sigma_r<={signalwidth+2{1'b0}}; + sigma2_l={signalwidth+2{1'b0}}; + sigma2_r={signalwidth+2{1'b0}}; + pwmcounter<=7'b111110; + end else begin + infilterena<=1'b0; + + if(pwmcounter==pwmthreshold_l) + q_l_reg<=1'b0; + + if(pwmcounter==pwmthreshold_r) + q_r_reg<=1'b0; + + if(pwmcounter==7'b11111) // Update threshold just before pwmcounter wraps around + begin + + infilterena<=1'b1; + + // PWM + + sigma_l<=sigmanext_l; + sigma2_l=sigmanext_l+{7'b0010000,sigma2_l[signalwidth-6:0]}; + + sigma_r<=sigmanext_r; + sigma2_r=sigmanext_r+{7'b0010000,sigma2_r[signalwidth-6:0]}; + + if(sigma2_l[signalwidth+1]==1'b1) + q_l_reg<=1'b0; + else + q_l_reg<=1'b1; + + if(sigma2_r[signalwidth+1]==1'b1) + q_r_reg<=1'b0; + else + q_r_reg<=1'b1; + + end + + pwmcounter[6:5]<=2'b0; + pwmcounter[4:0]<=pwmcounter[4:0]+5'b1; + + end +end + +endmodule + + +module iirfilter_stereo # +( + parameter signalwidth = 16, + parameter cbits = 5, + parameter immediate +) +( + input clk, + input reset_n, + input ena, + input [signalwidth-1:0] d_l, + input [signalwidth-1:0] d_r, + output [signalwidth-1:0] q_l, + output [signalwidth-1:0] q_r +); + +iirfilter_mono # (.signalwidth(signalwidth),.cbits(cbits),.immediate(immediate)) left +( + .clk(clk), + .reset_n(reset_n), + .ena(ena), + .d(d_l), + .q(q_l) +); + +iirfilter_mono # (.signalwidth(signalwidth),.cbits(cbits),.immediate(immediate)) right +( + .clk(clk), + .reset_n(reset_n), + .ena(ena), + .d(d_r), + .q(q_r) +); + +endmodule + + + +// Simplistic IIR low-pass filter. +// function is simply y += b * (x - y) +// where b=1/(1<