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<