From c3cd979a2b9bc6dc1ef05117e568e0a64c4a74f7 Mon Sep 17 00:00:00 2001 From: Seb Holzapfel Date: Wed, 12 Jun 2024 17:54:51 +0200 Subject: [PATCH] gateware: switch `clk_fs` for `strobe` asserted once per sample (#61) * Instead of dividing `clk_256fs` by 256 to create `clk_fs`, switch to a strobe signal that is high once per sample. This makes it more obvious that the whole repository works in 1 clock domain. * Tested all example DSP cores on ecpix5 and icebreaker. --- README.md | 2 +- gateware/boards/colorlight_i5/sysmgr.v | 9 --- gateware/boards/ecpix5/sysmgr.v | 9 --- gateware/boards/gatemate_evb/sysmgr.v | 9 --- gateware/boards/icebreaker/sysmgr.v | 10 --- gateware/boards/pico_ice/sysmgr.v | 9 --- gateware/cal/cal.sv | 9 +-- gateware/cores/bitcrush.sv | 16 +++-- gateware/cores/clkdiv.sv | 18 ++--- .../cores/{stereo_echo.sv => digital_echo.sv} | 32 ++------- gateware/cores/filter.sv | 4 +- gateware/cores/mirror.sv | 2 +- gateware/cores/pitch_shift.sv | 5 +- gateware/cores/sampler.sv | 34 ++++----- gateware/cores/seqswitch.sv | 69 ++++++++++--------- gateware/cores/touch_cv.sv | 2 +- gateware/cores/util/delayline.sv | 19 ++--- gateware/cores/util/echo.sv | 14 ++-- .../util/filter/karlsen_lpf_pipelined.sv | 7 +- gateware/cores/util/transpose.sv | 51 +++++++------- gateware/cores/util/wavetable_osc.sv | 19 ++--- gateware/cores/vca.sv | 2 +- gateware/cores/vco.sv | 23 ++++--- gateware/drivers/ak4619.sv | 41 +++++------ gateware/eurorack_pmod.sv | 6 +- gateware/mk/common.mk | 2 +- gateware/scripts/verilator_lint.sh | 2 +- gateware/sim/ak4619/tb_ak4619.py | 46 ++++++------- gateware/sim/cal/tb_cal.py | 21 ++++-- gateware/sim/integration/tb_integration.py | 4 +- gateware/sim/transpose/tb_transpose.py | 22 +++--- gateware/sim/vca/tb_vca.py | 20 ++++-- gateware/top.sv | 17 +++-- 33 files changed, 268 insertions(+), 287 deletions(-) rename gateware/cores/{stereo_echo.sv => digital_echo.sv} (50%) diff --git a/README.md b/README.md index 3803feb..2eb3795 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ This repository contains a bunch of example DSP cores which are continuously bei - `gateware/cores/clkdiv.sv` - Clock divider - `gateware/cores/sampler.sv` - .wav sampler - `gateware/cores/seqswitch.sv` - Sequential routing switch -- `gateware/cores/stereo_echo.sv` - Echo / decimating delay effect +- `gateware/cores/digital_echo.sv` - Echo / delay effect - `gateware/cores/vca.sv` - VCA (voltage controlled amplifier) - `gateware/cores/vco.sv` - VCO (voltage controlled oscillator) - `gateware/cores/bitcrush.sv` - Bitcrusher diff --git a/gateware/boards/colorlight_i5/sysmgr.v b/gateware/boards/colorlight_i5/sysmgr.v index fd194ba..40418ba 100644 --- a/gateware/boards/colorlight_i5/sysmgr.v +++ b/gateware/boards/colorlight_i5/sysmgr.v @@ -5,7 +5,6 @@ module sysmgr ( input wire clk_in, input wire rst_in, output wire clk_256fs, - output wire clk_fs, output wire rst_out ); @@ -15,12 +14,10 @@ wire pll_reset; wire rst_i; reg [7:0] rst_cnt; -reg [7:0] clkdiv; assign pll_reset = rst_in; assign rst_i = ~rst_cnt[7]; assign rst_out = rst_i; -assign clk_fs = clkdiv[7]; `ifndef VERILATOR_LINT_ONLY @@ -75,10 +72,4 @@ always @(posedge clk_in) else if (~rst_cnt[7]) rst_cnt <= rst_cnt + 1; -always @(posedge clk_256fs) - if (rst_i) - clkdiv <= 8'h00; - else - clkdiv <= clkdiv + 1; - endmodule // sysmgr diff --git a/gateware/boards/ecpix5/sysmgr.v b/gateware/boards/ecpix5/sysmgr.v index c9c3340..02954ef 100644 --- a/gateware/boards/ecpix5/sysmgr.v +++ b/gateware/boards/ecpix5/sysmgr.v @@ -5,7 +5,6 @@ module sysmgr ( input wire clk_in, input wire rst_in, output wire clk_256fs, - output wire clk_fs, output wire rst_out ); @@ -15,12 +14,10 @@ wire pll_reset; wire rst_i; reg [7:0] rst_cnt; -reg [7:0] clkdiv; assign rst_i = ~rst_cnt[7]; assign rst_out = rst_i; assign pll_reset = rst_in; -assign clk_fs = clkdiv[7]; `ifndef VERILATOR_LINT_ONLY @@ -71,10 +68,4 @@ always @(posedge clk_in) else if (~rst_cnt[7]) rst_cnt <= rst_cnt + 1; -always @(posedge clk_256fs) - if (rst_i) - clkdiv <= 8'h00; - else - clkdiv <= clkdiv + 1; - endmodule // sysmgr diff --git a/gateware/boards/gatemate_evb/sysmgr.v b/gateware/boards/gatemate_evb/sysmgr.v index 1117e04..7615e0e 100644 --- a/gateware/boards/gatemate_evb/sysmgr.v +++ b/gateware/boards/gatemate_evb/sysmgr.v @@ -4,7 +4,6 @@ module sysmgr ( input wire clk_in, input wire rst_in, output wire clk_256fs, - output wire clk_fs, output wire rst_out ); @@ -14,12 +13,10 @@ wire pll_reset; wire rst_i; reg [7:0] rst_cnt; -reg [7:0] clkdiv; assign pll_reset = rst_in; assign rst_i = ~rst_cnt[7]; assign rst_out = rst_i; -assign clk_fs = clkdiv[7]; `ifndef VERILATOR_LINT_ONLY @@ -46,10 +43,4 @@ always @(posedge clk_in) else if (~rst_cnt[7]) rst_cnt <= rst_cnt + 1; -always @(posedge clk_256fs) - if (rst_i) - clkdiv <= 8'h00; - else - clkdiv <= clkdiv + 1; - endmodule // sysmgr diff --git a/gateware/boards/icebreaker/sysmgr.v b/gateware/boards/icebreaker/sysmgr.v index 70dd906..41580c0 100644 --- a/gateware/boards/icebreaker/sysmgr.v +++ b/gateware/boards/icebreaker/sysmgr.v @@ -8,7 +8,6 @@ module sysmgr ( // but we leave all this PLL logic here as you might need to scale // the clock down to 12m on different boards. output wire clk_256fs, - output wire clk_fs, output wire rst_out ); @@ -22,12 +21,10 @@ module sysmgr ( wire rst_i; reg [7:0] rst_cnt; - reg [7:0] clkdiv; assign clk_256fs = clk_1x_i; assign pll_reset_n = ~rst_in; assign rst_i = rst_cnt[7]; - assign clk_fs = clkdiv[7]; // PLL instance `ifndef COCOTB_SIM @@ -69,13 +66,6 @@ module sysmgr ( else if (rst_cnt[7]) rst_cnt <= rst_cnt + 1; - always @(posedge clk_256fs) - if (rst_i) - clkdiv <= 8'h00; - else - clkdiv <= clkdiv + 1; - - `ifndef COCOTB_SIM `ifndef VERILATOR_LINT_ONLY SB_GB rst_gbuf_I ( diff --git a/gateware/boards/pico_ice/sysmgr.v b/gateware/boards/pico_ice/sysmgr.v index 83cbeed..09a6b57 100644 --- a/gateware/boards/pico_ice/sysmgr.v +++ b/gateware/boards/pico_ice/sysmgr.v @@ -3,7 +3,6 @@ module sysmgr ( input wire rst_in, output wire clk_256fs, - output wire clk_fs, output wire rst_out ); @@ -11,10 +10,8 @@ module sysmgr ( wire rst_i; reg [7:0] rst_cnt = 8'h80; - reg [7:0] clkdiv; assign clk_256fs = clk_12m; - assign clk_fs = clkdiv[7]; assign rst_i = rst_cnt[7]; `ifndef VERILATOR_LINT_ONLY @@ -35,12 +32,6 @@ module sysmgr ( else if (rst_cnt[7]) rst_cnt <= rst_cnt + 1; - always @(posedge clk_256fs) - if (rst_i) - clkdiv <= 8'h00; - else - clkdiv <= clkdiv + 1; - `ifndef VERILATOR_LINT_ONLY SB_GB rst_gbuf_I ( .USER_SIGNAL_TO_GLOBAL_BUFFER(rst_i), diff --git a/gateware/cal/cal.sv b/gateware/cal/cal.sv index 44ea2b8..14bbec2 100644 --- a/gateware/cal/cal.sv +++ b/gateware/cal/cal.sv @@ -26,7 +26,7 @@ module cal #( )( input rst, input clk_256fs, - input clk_fs, + input strobe, input [7:0] jack, input signed [W-1:0] in0, input signed [W-1:0] in1, @@ -64,7 +64,6 @@ logic signed [W-1:0] cal_mem [0:(2*N_CHANNELS)-1]; logic signed [(2*W)-1:0] out [N_CHANNELS]; logic [2:0] ch; logic [2:0] state; -logic l_clk_fs; // Calibration memory for 8 channels stored as // 2 bytes shift, 2 bytes multiply * 8 channels. @@ -73,7 +72,6 @@ initial $readmemh(CAL_MEM_FILE, cal_mem); always_ff @(posedge clk_256fs) begin if (rst) begin - l_clk_fs <= 0; ch <= 0; state <= CAL_ST_LATCH; out[0] <= 0; @@ -86,10 +84,7 @@ always_ff @(posedge clk_256fs) begin out[7] <= 0; end else begin - l_clk_fs <= clk_fs; - - // On rising clk_fs. - if (clk_fs && (l_clk_fs != clk_fs)) begin + if (strobe) begin state <= CAL_ST_LATCH; ch <= 0; end else begin diff --git a/gateware/cores/bitcrush.sv b/gateware/cores/bitcrush.sv index b692808..acc9366 100644 --- a/gateware/cores/bitcrush.sv +++ b/gateware/cores/bitcrush.sv @@ -14,7 +14,7 @@ module bitcrush #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -27,6 +27,7 @@ module bitcrush #( ); logic signed [W-1:0] mask; +logic signed [W-1:0] out0; logic signed [W-1:0] out1; logic signed [W-1:0] out2; logic signed [W-1:0] out3; @@ -43,13 +44,16 @@ assign mask = (sample_in0 > 4*5000) ? 16'b1111111111111111 : (sample_in0 > 4* 500) ? 16'b1110000000000000 : 16'b1100000000000000; -always_ff @(posedge sample_clk) begin - out1 <= sample_in1 & mask; - out2 <= sample_in2 & mask; - out3 <= sample_in3 & mask; +always_ff @(posedge clk) begin + if (strobe) begin + out0 <= sample_in0; + out1 <= sample_in1 & mask; + out2 <= sample_in2 & mask; + out3 <= sample_in3 & mask; + end end -assign sample_out0 = sample_in0; +assign sample_out0 = out0; assign sample_out1 = out1; assign sample_out2 = out2; assign sample_out3 = out3; diff --git a/gateware/cores/clkdiv.sv b/gateware/cores/clkdiv.sv index 757e973..ebc1e92 100644 --- a/gateware/cores/clkdiv.sv +++ b/gateware/cores/clkdiv.sv @@ -19,7 +19,7 @@ module clkdiv #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -46,13 +46,15 @@ localparam OUT_LO = `FROM_MV(0); logic last_state_hi = 1'b0; logic [3:0] div = 0; -always_ff @(posedge sample_clk) begin - if (sample_in0 > SCHMITT_HI && !last_state_hi) begin - last_state_hi <= 1'b1; - // Increment count on every rising edge. - div <= div + 1; - end else if (sample_in0 < SCHMITT_LO && last_state_hi) begin - last_state_hi <= 1'b0; +always_ff @(posedge clk) begin + if (strobe) begin + if (sample_in0 > SCHMITT_HI && !last_state_hi) begin + last_state_hi <= 1'b1; + // Increment count on every rising edge. + div <= div + 1; + end else if (sample_in0 < SCHMITT_LO && last_state_hi) begin + last_state_hi <= 1'b0; + end end end diff --git a/gateware/cores/stereo_echo.sv b/gateware/cores/digital_echo.sv similarity index 50% rename from gateware/cores/stereo_echo.sv rename to gateware/cores/digital_echo.sv index 64e0e42..80d650d 100644 --- a/gateware/cores/stereo_echo.sv +++ b/gateware/cores/digital_echo.sv @@ -1,27 +1,20 @@ -// Dual digital echo effect. +// Digital echo effect. // // Given input audio on input 0 / 1, apply a digital echo effect. // // Mapping: // - Input 0: Audio input 0 -// - Input 1: Audio input 1 // - Output 0: Audio input 0 (mirrored) // - Output 1: Audio input 0 (echo) -// - Output 2: Audio input 1 (mirrored) -// - Output 3: Audio input 1 (echo) -module stereo_echo #( +module digital_echo #( parameter W = 16, // Length of the echo buffers in samples. - parameter ECHO_LEN = 2048, - // Decimate samples - this allows you to get long echo times - // without using all the BRAM. Effectively you end up with: - // ECHO_LEN (effective) = ECHO_LEN * (2 << DECIMATE) - parameter DECIMATE = 2 + parameter ECHO_LEN = 4096 )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -33,26 +26,13 @@ module stereo_echo #( input [7:0] jack ); -logic [15:0] decimate = 0; -logic decimate_clk = decimate[DECIMATE]; - -always_ff @(posedge sample_clk) begin - decimate <= decimate + 1; -end - echo #(W, ECHO_LEN) echo0( - .sample_clk(decimate_clk), + .clk(clk), + .strobe(strobe), .sample_in(sample_in0), .sample_out(sample_out1) ); -echo #(W, ECHO_LEN) echo1( - .sample_clk(decimate_clk), - .sample_in(sample_in1), - .sample_out(sample_out3) -); - assign sample_out0 = sample_in0; -assign sample_out2 = sample_in1; endmodule diff --git a/gateware/cores/filter.sv b/gateware/cores/filter.sv index 68a8d91..c4c63c5 100644 --- a/gateware/cores/filter.sv +++ b/gateware/cores/filter.sv @@ -13,7 +13,7 @@ module filter #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -28,7 +28,7 @@ module filter #( karlsen_lpf_pipelined #(.W(W)) lpf_inst( .rst(rst), .clk(clk), - .sample_clk(sample_clk), + .strobe(strobe), .sample_in(sample_in0), .sample_out(sample_out0), .g(sample_in1), diff --git a/gateware/cores/mirror.sv b/gateware/cores/mirror.sv index bbcf5cb..76f80cb 100644 --- a/gateware/cores/mirror.sv +++ b/gateware/cores/mirror.sv @@ -9,7 +9,7 @@ module mirror #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, diff --git a/gateware/cores/pitch_shift.sv b/gateware/cores/pitch_shift.sv index 6d22b99..b576308 100644 --- a/gateware/cores/pitch_shift.sv +++ b/gateware/cores/pitch_shift.sv @@ -14,7 +14,7 @@ module pitch_shift #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -29,7 +29,8 @@ module pitch_shift #( transpose #( .W(W) ) transpose_instance ( - .sample_clk(sample_clk), + .clk, + .strobe, .pitch(sample_in1), .sample_in(sample_in0), .sample_out(sample_out1) diff --git a/gateware/cores/sampler.sv b/gateware/cores/sampler.sv index d430a13..7712e5a 100644 --- a/gateware/cores/sampler.sv +++ b/gateware/cores/sampler.sv @@ -18,7 +18,7 @@ module sampler #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -46,21 +46,23 @@ logic [$clog2(N_SAMPLES):0] sample_pos = 0; // Value of the last sample at sample_pos, synchronized to sample_clk. logic [W-1:0] cur_sample = 16'h0; -always_ff @(posedge sample_clk) begin - sclkdiv <= sclkdiv + 1; - if (sclkdiv % 2 == 0 && sample_pos <= N_SAMPLES) begin - sample_pos <= sample_pos + 1; - end - if (sample_in0 < TRIGGER_HI) begin - // Hold first sample as long as we have no trigger. As - // soon as it goes high, we 'allow' playback. - sample_pos <= 0; - end - if (sample_pos < N_SAMPLES) begin - cur_sample <= wav_samples[sample_pos[$clog2(N_SAMPLES)-1:0]]; - end else begin - // If we go past the end of the sample, hold 0V at the output. - cur_sample <= 16'h0; +always_ff @(posedge clk) begin + if (strobe) begin + sclkdiv <= sclkdiv + 1; + if (sclkdiv % 2 == 0 && sample_pos <= N_SAMPLES) begin + sample_pos <= sample_pos + 1; + end + if (sample_in0 < TRIGGER_HI) begin + // Hold first sample as long as we have no trigger. As + // soon as it goes high, we 'allow' playback. + sample_pos <= 0; + end + if (sample_pos < N_SAMPLES) begin + cur_sample <= wav_samples[sample_pos[$clog2(N_SAMPLES)-1:0]]; + end else begin + // If we go past the end of the sample, hold 0V at the output. + cur_sample <= 16'h0; + end end end diff --git a/gateware/cores/seqswitch.sv b/gateware/cores/seqswitch.sv index ee86c64..aa78e44 100644 --- a/gateware/cores/seqswitch.sv +++ b/gateware/cores/seqswitch.sv @@ -18,7 +18,7 @@ module seqswitch #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -43,43 +43,44 @@ logic last_state_hi = 1'b0; // Current routing state of the sequential switch. logic [1:0] switch_state = 2'b00; -always_ff @(posedge sample_clk) begin +always_ff @(posedge clk) begin + if (strobe) begin + // Rising edge of clock. + if (sample_in0 > SCHMITT_HI && !last_state_hi) begin + last_state_hi <= 1'b1; - // Rising edge of clock. - if (sample_in0 > SCHMITT_HI && !last_state_hi) begin - last_state_hi <= 1'b1; + // Update switch routing on a rising edge. + if (switch_state == 2'b10) switch_state <= 2'b00; + else switch_state <= switch_state + 1; + end - // Update switch routing on a rising edge. - if (switch_state == 2'b10) switch_state <= 2'b00; - else switch_state <= switch_state + 1; - end + // Falling edge of clock. + if (sample_in0 < SCHMITT_LO && last_state_hi) begin + last_state_hi <= 1'b0; + end - // Falling edge of clock. - if (sample_in0 < SCHMITT_LO && last_state_hi) begin - last_state_hi <= 1'b0; + // Samples mirrored at audio rate based on current routing. + case (switch_state) + 2'b00: begin + sample_out1 <= sample_in1; + sample_out2 <= sample_in2; + sample_out3 <= sample_in3; + end + 2'b01: begin + sample_out1 <= sample_in2; + sample_out2 <= sample_in3; + sample_out3 <= sample_in1; + end + 2'b10: begin + sample_out1 <= sample_in3; + sample_out2 <= sample_in1; + sample_out3 <= sample_in2; + end + default: begin + // State is never entered + end + endcase end - - // Samples mirrored at audio rate based on current routing. - case (switch_state) - 2'b00: begin - sample_out1 <= sample_in1; - sample_out2 <= sample_in2; - sample_out3 <= sample_in3; - end - 2'b01: begin - sample_out1 <= sample_in2; - sample_out2 <= sample_in3; - sample_out3 <= sample_in1; - end - 2'b10: begin - sample_out1 <= sample_in3; - sample_out2 <= sample_in1; - sample_out3 <= sample_in2; - end - default: begin - // State is never entered - end - endcase end assign sample_out0 = sample_in0; diff --git a/gateware/cores/touch_cv.sv b/gateware/cores/touch_cv.sv index 5662176..e8044df 100644 --- a/gateware/cores/touch_cv.sv +++ b/gateware/cores/touch_cv.sv @@ -15,7 +15,7 @@ module touch_cv #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, diff --git a/gateware/cores/util/delayline.sv b/gateware/cores/util/delayline.sv index c557ca8..ff7e6ba 100644 --- a/gateware/cores/util/delayline.sv +++ b/gateware/cores/util/delayline.sv @@ -6,7 +6,8 @@ module delayline #( parameter W = 16, parameter MAX_DELAY = 1024 )( - input sample_clk, + input clk, + input strobe, input [$clog2(MAX_DELAY)-1:0] delay, input signed [W-1:0] in, output logic signed [W-1:0] out @@ -17,13 +18,15 @@ logic [$clog2(MAX_DELAY)-1:0] waddr = 0; logic signed [W-1:0] bram[MAX_DELAY]; -always_ff @(posedge sample_clk) begin - waddr <= waddr + 1; - // This subtraction wraps correctly as long as MAX_DELAY is - // a power of 2. - raddr <= waddr - delay; - bram[waddr] <= in; - out <= bram[raddr]; +always_ff @(posedge clk) begin + if (strobe) begin + waddr <= waddr + 1; + // This subtraction wraps correctly as long as MAX_DELAY is + // a power of 2. + raddr <= waddr - delay; + bram[waddr] <= in; + out <= bram[raddr]; + end end endmodule diff --git a/gateware/cores/util/echo.sv b/gateware/cores/util/echo.sv index ac9fc23..6b022fb 100644 --- a/gateware/cores/util/echo.sv +++ b/gateware/cores/util/echo.sv @@ -4,7 +4,8 @@ module echo #( parameter W = 16, parameter ECHO_MAX_SAMPLES = 1024 )( - input sample_clk, + input clk, + input strobe, input signed [W-1:0] sample_in, output logic signed [W-1:0] sample_out ); @@ -18,15 +19,18 @@ logic signed [W-1:0] delay_in; logic signed [W-1:0] delay_out; delayline #(W, ECHO_MAX_SAMPLES) delay_0 ( - .sample_clk(sample_clk), + .clk(clk), + .strobe(strobe), .delay(DELAY), .in(delay_in), .out(delay_out) ); -always_ff @(posedge sample_clk) begin - delay_in <= (sample_in >>> 1) + (delay_out >>> FEEDBACK_SHIFT); - sample_out <= delay_out; +always_ff @(posedge clk) begin + if (strobe) begin + delay_in <= (sample_in >>> 1) + (delay_out >>> FEEDBACK_SHIFT); + sample_out <= delay_out; + end end endmodule diff --git a/gateware/cores/util/filter/karlsen_lpf_pipelined.sv b/gateware/cores/util/filter/karlsen_lpf_pipelined.sv index 33912f8..395371d 100644 --- a/gateware/cores/util/filter/karlsen_lpf_pipelined.sv +++ b/gateware/cores/util/filter/karlsen_lpf_pipelined.sv @@ -33,7 +33,7 @@ module karlsen_lpf_pipelined #( )( input rst, input clk, - input sample_clk, + input strobe, // See header comment for what these parameters mean. input signed [W-1:0] g, input signed [W-1:0] resonance, @@ -49,7 +49,6 @@ localparam WMULT = 18; // Width of multiplier input localparam signed [WMULT-1:0] MAX = (2**(W-1))-1; localparam signed [WMULT-1:0] MIN = -(2**(W-1)); -logic prev_sample_clk; logic [3:0] state; logic signed [WMULT-1:0] in_ex; @@ -72,7 +71,6 @@ assign resonance_ex = `CLAMP_POSITIVE(resonance) <<< 2; smul_shift_18x18 multiplier(.a(smul_a), .b(smul_b), .scale(smul_scale), .o(smul_out)); always_ff @(posedge clk) begin - prev_sample_clk <= sample_clk; if (rst) begin state <= 0; a1 <= 0; @@ -80,9 +78,8 @@ always_ff @(posedge clk) begin a3 <= 0; a4 <= 0; sample_out <= 0; - prev_sample_clk <= 0; end else begin - if (sample_clk != prev_sample_clk) begin + if (strobe) begin state <= 0; end else begin if (state < 8) diff --git a/gateware/cores/util/transpose.sv b/gateware/cores/util/transpose.sv index e1feed5..84b2efd 100644 --- a/gateware/cores/util/transpose.sv +++ b/gateware/cores/util/transpose.sv @@ -13,7 +13,8 @@ module transpose #( parameter WINDOW = 512, parameter XFADE = 128 )( - input sample_clk, + input clk, + input strobe, input signed [W-1:0] pitch, input signed [W-1:0] sample_in, output logic signed [W-1:0] sample_out @@ -43,42 +44,46 @@ logic signed [XFADE_BITS:0] env1_reg; // have no interpolation between integer delay line steps. delayline #(W, DELAYLINE_LEN) delay_0 ( - .sample_clk(sample_clk), + .clk, + .strobe, .delay({1'b0, delay_int}), .in(sample_in), .out(delay_out0) ); delayline #(W, DELAYLINE_LEN) delay_1( - .sample_clk(sample_clk), + .clk, + .strobe, .delay({1'b0, delay_int}+WINDOW), .in(sample_in), .out(delay_out1) ); -always_ff @(posedge sample_clk) begin - // The value we increment `d` by here is actually the 'pitch shift' amount. - // walking up the delay lines some amount faster or slower than usual. - // - // TODO: Make this track 1V/oct. - delay_frac <= delay_frac + (pitch >>> 8); +always_ff @(posedge clk) begin + if (strobe) begin + // The value we increment `d` by here is actually the 'pitch shift' amount. + // walking up the delay lines some amount faster or slower than usual. + // + // TODO: Make this track 1V/oct. + delay_frac <= delay_frac + (pitch >>> 8); - if (delay_int < XFADE) begin - env0 <= delay_int[XFADE_BITS:0]; - env1 <= (XFADE-1) - delay_int[XFADE_BITS:0]; - end else begin - env0 <= XFADE-1; - env1 <= 0; - end + if (delay_int < XFADE) begin + env0 <= delay_int[XFADE_BITS:0]; + env1 <= (XFADE-1) - delay_int[XFADE_BITS:0]; + end else begin + env0 <= XFADE-1; + env1 <= 0; + end - // Envelopes need to be delayed by 1 sample to avoid discontinuity as we - // swap between the 2 delay line feeds. - env0_reg <= env0; - env1_reg <= env1; + // Envelopes need to be delayed by 1 sample to avoid discontinuity as we + // swap between the 2 delay line feeds. + env0_reg <= env0; + env1_reg <= env1; - // TODO: pipeline these multiplies. - sample_out <= W'(((W+XFADE_BITS)'(delay_out0) * (W+XFADE_BITS)'(env0_reg)) >>> XFADE_BITS) + - W'(((W+XFADE_BITS)'(delay_out1) * (W+XFADE_BITS)'(env1_reg)) >>> XFADE_BITS); + // TODO: pipeline these multiplies. + sample_out <= W'(((W+XFADE_BITS)'(delay_out0) * (W+XFADE_BITS)'(env0_reg)) >>> XFADE_BITS) + + W'(((W+XFADE_BITS)'(delay_out1) * (W+XFADE_BITS)'(env1_reg)) >>> XFADE_BITS); + end end endmodule diff --git a/gateware/cores/util/wavetable_osc.sv b/gateware/cores/util/wavetable_osc.sv index e570b5f..134b130 100644 --- a/gateware/cores/util/wavetable_osc.sv +++ b/gateware/cores/util/wavetable_osc.sv @@ -5,7 +5,8 @@ module wavetable_osc #( parameter WAVETABLE_SIZE = 256 )( input rst, - input sample_clk, + input clk, + input strobe, input [31:0] wavetable_inc, output logic signed [W-1:0] out ); @@ -16,13 +17,15 @@ initial $readmemh(WAVETABLE_PATH, wavetable); // Position in wavetable - N.F fixed-point where BIT_START is size of F. logic [31:0] wavetable_pos = 0; -always_ff @(posedge sample_clk) begin - if (rst) begin - wavetable_pos <= 0; - end else begin - wavetable_pos <= wavetable_pos + wavetable_inc; - // Take top N bits of wavetable_pos as output. - out <= wavetable[wavetable_pos[FRAC_BITS+$clog2(WAVETABLE_SIZE)-1:FRAC_BITS]]; +always_ff @(posedge clk) begin + if (strobe) begin + if (rst) begin + wavetable_pos <= 0; + end else begin + wavetable_pos <= wavetable_pos + wavetable_inc; + // Take top N bits of wavetable_pos as output. + out <= wavetable[wavetable_pos[FRAC_BITS+$clog2(WAVETABLE_SIZE)-1:FRAC_BITS]]; + end end end diff --git a/gateware/cores/vca.sv b/gateware/cores/vca.sv index 647cdce..1435139 100644 --- a/gateware/cores/vca.sv +++ b/gateware/cores/vca.sv @@ -19,7 +19,7 @@ module vca #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, diff --git a/gateware/cores/vco.sv b/gateware/cores/vco.sv index 0fab58b..7ac914b 100644 --- a/gateware/cores/vco.sv +++ b/gateware/cores/vco.sv @@ -15,7 +15,7 @@ module vco #( )( input rst, input clk, - input sample_clk, + input strobe, input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -39,13 +39,15 @@ initial $readmemh(V_OCT_LUT_PATH, v_oct_lut); logic signed [W-1:0] lut_index = 0; logic signed [W-1:0] lut_index_clamp_lo = 0; -always_ff @(posedge sample_clk) begin - if (rst) begin - lut_index <= 0; - lut_index_clamp_lo <= 0; - end else begin - lut_index <= sample_in0 >>> 6; - lut_index_clamp_lo <= lut_index < 0 ? 0 : lut_index; +always_ff @(posedge clk) begin + if (strobe) begin + if (rst) begin + lut_index <= 0; + lut_index_clamp_lo <= 0; + end else begin + lut_index <= sample_in0 >>> 6; + lut_index_clamp_lo <= lut_index < 0 ? 0 : lut_index; + end end end @@ -55,8 +57,9 @@ wavetable_osc #( .WAVETABLE_PATH(WAVETABLE_PATH), .WAVETABLE_SIZE(WAVETABLE_SIZE) ) osc_0 ( - .rst(rst), - .sample_clk(sample_clk), + .rst, + .clk, + .strobe, .wavetable_inc(32'(v_oct_lut[$clog2(V_OCT_LUT_SIZE)'(lut_index_clamp_lo)])), .out(sample_out0) ); diff --git a/gateware/drivers/ak4619.sv b/gateware/drivers/ak4619.sv index 5596a55..56fcfef 100644 --- a/gateware/drivers/ak4619.sv +++ b/gateware/drivers/ak4619.sv @@ -1,12 +1,10 @@ // Driver for AK4619 CODEC. // // Usage: -// - `clk_256fs` and `clk_fs` determine the hardware sample rate. +// - `clk_256fs` determines the hardware sample rate. // - Once CODEC is initialized over I2C, samples can be streamed. -// - `sample_in` is latched on falling `clk_fs`. -// - `sample_out` transitions on falling `clk_fs`. -// - As a result, users of this core should latch sample_out (and transition -// sample_in) on rising `clk_fs`. +// - `sample_in` is latched on `strobe`. +// - `sample_out` transitions on `strobe`. // // This core assumes the device is configured in the audio // interface mode specified in ak4619-cfg.hex. This happens @@ -28,7 +26,7 @@ module ak4619 #( )( input rst, input clk_256fs, - input clk_fs, + input strobe, // Route these straight out to the CODEC pins. output pdn, @@ -38,13 +36,13 @@ module ak4619 #( output reg sdin1, input sdout1, - // Transitions on falling `clk_fs`. + // Transitions with `strobe`. output reg signed [W-1:0] sample_out0, output reg signed [W-1:0] sample_out1, output reg signed [W-1:0] sample_out2, output reg signed [W-1:0] sample_out3, - // Latches on falling `clk_fs`. + // Latches with `strobe`. input signed [W-1:0] sample_in0, input signed [W-1:0] sample_in1, input signed [W-1:0] sample_in2, @@ -56,7 +54,6 @@ localparam int N_CHANNELS = 4; logic signed [(W*N_CHANNELS)-1:0] dac_words; logic signed [W-1:0] adc_words [N_CHANNELS]; -logic last_fs; logic [7:0] clkdiv; logic [1:0] channel; @@ -74,24 +71,24 @@ assign bit_counter = clkdiv[5:1]; always_ff @(posedge clk_256fs) begin if (rst) begin - last_fs <= 0; + sdin1 <= 0; clkdiv <= 0; - dac_words = 0; - sample_out0 <= 0; - sample_out1 <= 0; - sample_out2 <= 0; - sample_out3 <= 0; - end else if (last_fs && ~clk_fs) begin - // Synchronize clkdiv to the incoming sample clock, latching - // our inputs and outputs at the falling edge of clk_fs. + adc_words[0] <= 0; + adc_words[1] <= 0; + adc_words[2] <= 0; + adc_words[3] <= 0; + dac_words <= 0; + end else if (strobe) begin + // Synchronize clkdiv to the incoming sample strobe, latching + // our inputs and outputs in the clock cycle that `strobe` is high. clkdiv <= 8'h0; - dac_words = {sample_in3, sample_in2, - sample_in1, sample_in0}; + dac_words <= {sample_in3, sample_in2, + sample_in1, sample_in0}; sample_out0 <= adc_words[0]; sample_out1 <= adc_words[1]; sample_out2 <= adc_words[2]; sample_out3 <= adc_words[3]; - last_fs <= clk_fs; + sdin1 <= dac_words[(1*W)-1]; end else if (bick) begin // BICK transition HI -> LO: Clock in W bits // On HI -> LO both SDIN and SDOUT do not transition. @@ -100,7 +97,6 @@ always_ff @(posedge clk_256fs) begin adc_words[channel][W - bit_counter - 1] <= sdout1; end clkdiv <= clkdiv + 1; - last_fs <= clk_fs; end else begin // BICK: LO -> HI // BICK transition LO -> HI: Clock out W bits // On LO -> HI both SDIN and SDOUT transition. @@ -115,7 +111,6 @@ always_ff @(posedge clk_256fs) begin sdin1 <= 0; end clkdiv <= clkdiv + 1; - last_fs <= clk_fs; end end diff --git a/gateware/eurorack_pmod.sv b/gateware/eurorack_pmod.sv index 3a240f6..33164b8 100644 --- a/gateware/eurorack_pmod.sv +++ b/gateware/eurorack_pmod.sv @@ -13,7 +13,7 @@ module eurorack_pmod #( parameter LED_CFG_FILE = "drivers/pca9635-cfg.hex" )( input clk_256fs, - input clk_fs, + input strobe, input rst, // Signals to/from eurorack-pmod hardware. @@ -98,7 +98,7 @@ cal #( ) cal_instance ( .rst(rst), .clk_256fs (clk_256fs), - .clk_fs (clk_fs), + .strobe (strobe), // Calibrated inputs are zeroed if jack is unplugged. .jack (jack), // Note: inputs samples are inverted by analog frontend @@ -127,7 +127,7 @@ ak4619 #( ) ak4619_instance ( .rst (rst), .clk_256fs (clk_256fs), - .clk_fs (clk_fs), + .strobe (strobe), .pdn (pdn), .mclk (mclk), diff --git a/gateware/mk/common.mk b/gateware/mk/common.mk index 0686490..c4fda6c 100644 --- a/gateware/mk/common.mk +++ b/gateware/mk/common.mk @@ -13,7 +13,7 @@ SRC_COMMON = eurorack_pmod.sv \ cores/vca.sv \ cores/vco.sv \ cores/pitch_shift.sv \ - cores/stereo_echo.sv \ + cores/digital_echo.sv \ cores/filter.sv \ cores/touch_cv.sv \ cores/util/filter/karlsen_lpf_pipelined.sv \ diff --git a/gateware/scripts/verilator_lint.sh b/gateware/scripts/verilator_lint.sh index 2c63734..5416ee1 100755 --- a/gateware/scripts/verilator_lint.sh +++ b/gateware/scripts/verilator_lint.sh @@ -56,6 +56,6 @@ verilator --lint-only cores/util/filter/karlsen_lpf.sv verilator --lint-only cores/util/filter/karlsen_lpf_pipelined.sv verilator --lint-only -Icores -Icores/util/filter filter.sv verilator --lint-only -Icores -Icores/util pitch_shift.sv -verilator --lint-only -Icores -Icores/util stereo_echo.sv +verilator --lint-only -Icores -Icores/util digital_echo.sv verilator --lint-only -Icores -Icores/util dc_block.sv verilator --lint-only -Icores -Icores/util wavetable_osc.sv diff --git a/gateware/sim/ak4619/tb_ak4619.py b/gateware/sim/ak4619/tb_ak4619.py index ac01ebe..7e18e34 100644 --- a/gateware/sim/ak4619/tb_ak4619.py +++ b/gateware/sim/ak4619/tb_ak4619.py @@ -11,33 +11,41 @@ @cocotb.test() async def test_ak4619_00(dut): - clk_256fs = Clock(dut.clk_256fs, 83, units='ns') - clk_fs = Clock(dut.clk_fs, 83*256, units='ns') - cocotb.start_soon(clk_256fs.start()) - cocotb.start_soon(clk_fs.start(start_high=False)) - TEST_L0 = 0xFC140000 TEST_R0 = 0xAD0F0000 TEST_L1 = 0xDEAD0000 TEST_R1 = 0xBEEF0000 - dut.sdout1.value = 0 + clk_256fs = Clock(dut.clk_256fs, 83, units='ns') + cocotb.start_soon(clk_256fs.start()) + dut.sample_in0.value = Force(0) + dut.sample_in1.value = Force(0) + dut.sample_in2.value = Force(0) + dut.sample_in3.value = Force(0) + dut.strobe.value = 0 + dut.sdout1.value = 0 dut.rst.value = 1 + + async def strobe(): + while True: + dut.strobe.value = 1 + await ClockCycles(dut.clk_256fs, 1) + dut.strobe.value = 0 + await ClockCycles(dut.clk_256fs, 255) + await RisingEdge(dut.clk_256fs) await RisingEdge(dut.clk_256fs) dut.rst.value = 0 - await FallingEdge(dut.lrck) + cocotb.start_soon(strobe()) await i2s_clock_out_u32(dut.bick, dut.sdout1, TEST_L0) await i2s_clock_out_u32(dut.bick, dut.sdout1, TEST_R0) await i2s_clock_out_u32(dut.bick, dut.sdout1, TEST_L1) await i2s_clock_out_u32(dut.bick, dut.sdout1, TEST_R1) - # Note: this edge is also where dac_words <= sample_in (sample.sv) + await FallingEdge(dut.strobe) - await RisingEdge(dut.clk_fs) - await FallingEdge(dut.clk_fs) print("Data clocked from sdout1 present at sample_outX:") print(hex(dut.sample_out0.value.integer)) print(hex(dut.sample_out1.value.integer)) @@ -54,8 +62,7 @@ async def test_ak4619_00(dut): dut.sample_in2.value = Force(TEST_L1 >> 16) dut.sample_in3.value = Force(TEST_R1 >> 16) - await FallingEdge(dut.lrck) - await FallingEdge(dut.lrck) + await RisingEdge(dut.strobe) result_l0 = await i2s_clock_in_u32(dut.bick, dut.sdin1) result_r0 = await i2s_clock_in_u32(dut.bick, dut.sdin1) @@ -68,14 +75,7 @@ async def test_ak4619_00(dut): print(hex(result_l1)) print(hex(result_r1)) - assert result_l0 == TEST_L0 - assert result_r0 == TEST_R0 - assert result_l1 == TEST_L1 - assert result_r1 == TEST_R1 - - dut.sample_in0.value = Release() - dut.sample_in1.value = Release() - dut.sample_in2.value = Release() - dut.sample_in3.value = Release() - - await FallingEdge(dut.clk_fs) + assert result_l0 & 0xFFFFFF00 == TEST_L0 + assert result_r0 & 0xFFFFFF00 == TEST_R0 + assert result_l1 & 0xFFFFFF00 == TEST_L1 + assert result_r1 & 0xFFFFFF00 == TEST_R1 diff --git a/gateware/sim/cal/tb_cal.py b/gateware/sim/cal/tb_cal.py index 0efccfb..c055c0a 100644 --- a/gateware/sim/cal/tb_cal.py +++ b/gateware/sim/cal/tb_cal.py @@ -14,13 +14,24 @@ async def test_cal_00(dut): sample_width = 16 clk_256fs = Clock(dut.clk_256fs, 83, units='ns') - clk_fs = Clock(dut.clk_fs, 83*256, units='ns') cocotb.start_soon(clk_256fs.start()) - cocotb.start_soon(clk_fs.start(start_high=False)) # Simulate all jacks connected so the cal core doesn't zero them dut.jack.value = Force(0xFF) + # Add 1/256 sample strobe + dut.strobe.value = 0 + async def strobe(): + while True: + dut.strobe.value = 1 + await ClockCycles(dut.clk_256fs, 1) + dut.strobe.value = 0 + await ClockCycles(dut.clk_256fs, 255) + await RisingEdge(dut.clk_256fs) + await RisingEdge(dut.clk_256fs) + dut.rst.value = 0 + cocotb.start_soon(strobe()) + clampl = -2**(sample_width-1) + 1 clamph = 2**(sample_width-1) - 1; @@ -65,10 +76,8 @@ async def test_cal_00(dut): if expect > clamph: expect = clamph if expect < clampl: expect = clampl print(f"ch={channel}\t{int(value):6d}\t", end="") - await FallingEdge(dut.clk_fs) - await RisingEdge(dut.clk_fs) - await RisingEdge(dut.clk_fs) - await RisingEdge(dut.clk_fs) + await RisingEdge(dut.strobe) + await RisingEdge(dut.strobe) output = signed_from_bits(cal_outx.value, sample_width) print(f"=>\t{int(output):6d}\t(expect={expect})") cal_inx.value = Release() diff --git a/gateware/sim/integration/tb_integration.py b/gateware/sim/integration/tb_integration.py index df36e95..c9d39a7 100644 --- a/gateware/sim/integration/tb_integration.py +++ b/gateware/sim/integration/tb_integration.py @@ -31,14 +31,14 @@ async def test_integration_00(dut): ak4619 = dut.eurorack_pmod1.ak4619_instance + await FallingEdge(dut.strobe) + N = 20 for i in range(N): v = bits_from_signed(int(16000*math.sin((2*math.pi*i)/N)), sample_width) - await FallingEdge(ak4619.lrck) - await i2s_clock_out_u32(ak4619.bick, ak4619.sdout1, v << 16) await i2s_clock_out_u32(ak4619.bick, ak4619.sdout1, v << 16) await i2s_clock_out_u32(ak4619.bick, ak4619.sdout1, v << 16) diff --git a/gateware/sim/transpose/tb_transpose.py b/gateware/sim/transpose/tb_transpose.py index 253caf6..80fa743 100644 --- a/gateware/sim/transpose/tb_transpose.py +++ b/gateware/sim/transpose/tb_transpose.py @@ -16,12 +16,18 @@ async def test_transpose_00(dut): sample_width = 16 - clock = Clock(dut.sample_clk, 5, units='us') - cocotb.start_soon(clock.start()) - - # Not needed at the moment as we aren't pipelining things - #clock = Clock(dut.clk, 83, units='ns') - #cocotb.start_soon(clock.start()) + clk_256fs = Clock(dut.clk, 83, units='ns') + cocotb.start_soon(clk_256fs.start()) + + # Add 1/256 sample strobe + dut.strobe.value = 0 + async def strobe(): + while True: + dut.strobe.value = 1 + await ClockCycles(dut.clk, 1) + dut.strobe.value = 0 + await ClockCycles(dut.clk, 255) + cocotb.start_soon(strobe()) dut.sample_in.value = 0 dut.pitch.value = 5000*4 @@ -29,7 +35,7 @@ async def test_transpose_00(dut): # Clock in some zeroes so the delay lines are full of zeroes. for i in range(1024): - await RisingEdge(dut.sample_clk) + await RisingEdge(dut.strobe) # Stimulate the pitch shifter with a sine wave and make sure # the output does not have any discontinuities @@ -38,7 +44,7 @@ async def test_transpose_00(dut): breaknext = False for i in range(2048): - await RisingEdge(dut.sample_clk) + await RisingEdge(dut.strobe) data_in = int(1000*math.sin(i / 100)) diff --git a/gateware/sim/vca/tb_vca.py b/gateware/sim/vca/tb_vca.py index 6afcc48..8937da4 100644 --- a/gateware/sim/vca/tb_vca.py +++ b/gateware/sim/vca/tb_vca.py @@ -14,17 +14,25 @@ async def test_vca_00(dut): sample_width=16 - clock = Clock(dut.sample_clk, 5, units='us') - cocotb.start_soon(clock.start()) - clock = Clock(dut.clk, 83, units='ns') - cocotb.start_soon(clock.start()) + clk_256fs = Clock(dut.clk, 83, units='ns') + cocotb.start_soon(clk_256fs.start()) + + # Add 1/256 sample strobe + dut.strobe.value = 0 + async def strobe(): + while True: + dut.strobe.value = 1 + await ClockCycles(dut.clk, 1) + dut.strobe.value = 0 + await ClockCycles(dut.clk, 255) + cocotb.start_soon(strobe()) ins = [dut.sample_in0, dut.sample_in1, dut.sample_in2, dut.sample_in3] outs = [dut.sample_out0, dut.sample_out1, dut.sample_out2, dut.sample_out3] for i in range(10): - await RisingEdge(dut.sample_clk) + await RisingEdge(dut.strobe) data_in = [] for inx in ins: @@ -32,7 +40,7 @@ async def test_vca_00(dut): data_in.append(random_sample) inx.value = bits_from_signed(random_sample, sample_width) - await RisingEdge(dut.sample_clk) + await RisingEdge(dut.strobe) data_out = [signed_from_bits(out.value, sample_width) for out in outs] diff --git a/gateware/top.sv b/gateware/top.sv index 8a81131..2ff10f4 100644 --- a/gateware/top.sv +++ b/gateware/top.sv @@ -29,7 +29,17 @@ module top #( logic rst; logic clk_256fs; -logic clk_fs; // FIXME: assign from PLL divider? +logic strobe; +logic [7:0] strobe_clkdiv; + +always_ff @(posedge clk_256fs) begin + if (rst) begin + strobe_clkdiv <= 8'h0; + end else begin + strobe_clkdiv <= strobe_clkdiv + 1; + end + strobe <= (strobe_clkdiv == 8'h0); +end // Button signal is used for resets, unless we are input calibration // mode in which case it is used for setting the output cal values. @@ -90,7 +100,6 @@ sysmgr sysmgr_instance ( .rst_in(1'b0), `endif .clk_256fs(clk_256fs), - .clk_fs(clk_fs), .rst_out(rst) ); @@ -101,7 +110,7 @@ sysmgr sysmgr_instance ( ) dsp_core_instance ( .rst (rst) , .clk (clk_256fs) - , .sample_clk (clk_fs) + , .strobe (strobe) , .sample_in0 (in0) , .sample_in1 (in1) , .sample_in2 (in2) @@ -151,7 +160,7 @@ eurorack_pmod #( .W(W) ) eurorack_pmod1 ( .clk_256fs(clk_256fs), - .clk_fs (clk_fs), + .strobe (strobe), .rst(rst), .i2c_scl_oe(i2c_scl_oe),