diff --git a/cores/primitive/lvds_comparator/lvds_comparator.core b/cores/primitive/lvds_comparator/lvds_comparator.core new file mode 100644 index 0000000..efb8098 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator.core @@ -0,0 +1,26 @@ +CAPI=2: + +name: joppeb:primitive:lvds_comparator:1.0 +description: LVDS comparator wrapper + +filesets: + wrapper: + files: + - lvds_comparator.v + file_type: verilogSource + generic: + files: + - lvds_comparator_generic_impl.v + file_type: verilogSource + spartan6: + files: + - lvds_comparator_spartan6.v + file_type: verilogSource + +targets: + default: + filesets: + - wrapper + - generic + - spartan6 + toplevel: lvds_comparator diff --git a/cores/primitive/lvds_comparator/lvds_comparator.v b/cores/primitive/lvds_comparator/lvds_comparator.v new file mode 100644 index 0000000..b3076e4 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator.v @@ -0,0 +1,19 @@ +module lvds_comparator( + input wire a, + input wire b, + output wire o +); +`ifdef FPGA_SPARTAN6 + lvds_comparator_spartan6_impl impl_i ( + .a(a), + .b(b), + .o(o) + ); +`else + lvds_comparator_generic_impl impl_i ( + .a(a), + .b(b), + .o(o) + ); +`endif +endmodule \ No newline at end of file diff --git a/cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v b/cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v new file mode 100644 index 0000000..2c495a2 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v @@ -0,0 +1,7 @@ +module lvds_comparator_generic_impl ( + input wire a, + input wire b, + output wire o +); + assign o = a; +endmodule \ No newline at end of file diff --git a/cores/primitive/lvds_comparator/lvds_comparator_spartan6.v b/cores/primitive/lvds_comparator/lvds_comparator_spartan6.v new file mode 100644 index 0000000..2eeaa04 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator_spartan6.v @@ -0,0 +1,14 @@ +module lvds_comparator_spartan6_impl ( + input wire a, + input wire b, + output wire o +); + IBUFDS #( + .DIFF_TERM("FALSE"), + .IOSTANDARD("LVDS33") + ) lvds_buf ( + .O(o), + .I(a), + .IB(b) + ); +endmodule \ No newline at end of file diff --git a/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core new file mode 100644 index 0000000..418144c --- /dev/null +++ b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core @@ -0,0 +1,29 @@ +CAPI=2: + +name: joppeb:signal:decimate_by_r_q15:1.0 +description: Q1.15 integer-rate decimator + +filesets: + rtl: + files: + - decimate_by_r_q15.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: decimate_by_r_q15 + parameters: + - R + - CNT_W + +parameters: + R: + datatype: int + description: Integer decimation ratio + paramtype: vlogparam + CNT_W: + datatype: int + description: Counter width for the decimation counter + paramtype: vlogparam diff --git a/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v new file mode 100644 index 0000000..49aedeb --- /dev/null +++ b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v @@ -0,0 +1,74 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Decimator by R +// Reduces the effective sample rate by an integer factor R by selecting every +// R-th input sample. Generates a one-cycle 'o_valid' pulse each time a new +// decimated sample is produced. +// +// Implements: +// For each valid input sample: +// if (count == R-1): +// count <= 0 +// o_q15 <= i_q15 +// o_valid <= 1 +// else: +// count <= count + 1 +// +// parameters: +// -- R : integer decimation factor (e.g., 400) +// output sample rate = input rate / R +// -- CNT_W : counter bit width, must satisfy 2^CNT_W > R +// +// inout: +// -- i_clk : input clock (same rate as 'i_valid') +// -- i_rst_n : active-low synchronous reset +// -- i_valid : input data strobe; assert 1'b1 if input is always valid +// -- i_q15 : signed 16-bit Q1.15 input sample (full-rate) +// -- o_valid : single-cycle pulse every R samples (decimated rate strobe) +// -- o_q15 : signed 16-bit Q1.15 output sample (decimated stream) +// +// Notes: +// - This module performs *pure downsampling* (sample selection only). +// It does not include any anti-alias filtering; high-frequency content +// above the new Nyquist limit (Fs_out / 2) will alias into the baseband. +// - For most applications, an anti-alias low-pass filter such as +// lpf_iir_q15 or a FIR stage should precede this decimator. +// - The output sample rate is given by: +// Fs_out = Fs_in / R +// - Typical usage: interface between high-rate sigma-delta or oversampled +// data streams and lower-rate processing stages. +// ============================================================================= +module decimate_by_r_q15 #( + parameter integer R = 400, // decimation factor + parameter integer CNT_W = 10 // width so that 2^CNT_W > R (e.g., 10 for 750) +)( + input wire i_clk, + input wire i_rst_n, + input wire i_valid, // assert 1'b1 if always valid + input wire signed [15:0] i_q15, // Q1.15 sample at full rate + output reg o_valid, // 1-cycle pulse every R samples + output reg signed [15:0] o_q15 // Q1.15 sample at decimated rate +); + reg [CNT_W-1:0] cnt; + + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin + cnt <= {CNT_W{1'b0}}; + o_valid <= 1'b0; + o_q15 <= 16'sd0; + end else begin + o_valid <= 1'b0; + + if (i_valid) begin + if (cnt == R-1) begin + cnt <= {CNT_W{1'b0}}; + o_q15 <= i_q15; + o_valid <= 1'b1; + end else begin + cnt <= cnt + 1'b1; + end + end + end + end +endmodule diff --git a/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core new file mode 100644 index 0000000..b134b5d --- /dev/null +++ b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core @@ -0,0 +1,24 @@ +CAPI=2: + +name: joppeb:signal:lpf_iir_q15_k:1.0 +description: First-order Q1.15 IIR low-pass filter + +filesets: + rtl: + files: + - lpf_iir_q15_k.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: lpf_iir_q15_k + parameters: + - K + +parameters: + K: + datatype: int + description: Filter shift factor + paramtype: vlogparam diff --git a/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v new file mode 100644 index 0000000..bfcaaa0 --- /dev/null +++ b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v @@ -0,0 +1,64 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Low-Pass IIR Filter (Q1.15) +// Simple first-order infinite impulse response filter, equivalent to an +// exponential moving average. Provides an adjustable smoothing factor based +// on parameter K. +// +// Implements: +// Y[n+1] = Y[n] + (X[n] - Y[n]) / 2^K +// +// This is a purely digital one-pole low-pass filter whose time constant +// approximates that of an analog RC filter, where alpha = 1 / 2^K. +// +// The larger K is, the slower the filter responds (stronger smoothing). +// The smaller K is, the faster it reacts to changes. +// +// parameters: +// -- K : filter shift factor (integer, 4..14 typical) +// cutoff frequency ≈ Fs / (2π * 2^K) +// larger K → lower cutoff +// +// inout: +// -- i_clk : input clock +// -- i_rst_n : active-low reset +// -- i_x_q15 : signed 16-bit Q1.15 input sample (e.g., 0..0x7FFF) +// -- o_y_q15 : signed 16-bit Q1.15 filtered output +// +// Notes: +// - The arithmetic right shift implements division by 2^K. +// - Internal arithmetic is Q1.15 fixed-point with saturation +// to [0, 0x7FFF] (for non-negative signals). +// - Useful for smoothing noisy ADC / sigma-delta data streams +// or modeling an RC envelope follower. +// ============================================================================= +module lpf_iir_q15_k #( + parameter integer K = 10 // try 8..12; bigger = more smoothing +)( + input wire i_clk, + input wire i_rst_n, + input wire signed [15:0] i_x_q15, // Q1.15 input (e.g., 0..0x7FFF) + output reg signed [15:0] o_y_q15 // Q1.15 output +); + wire signed [15:0] e_q15 = i_x_q15 - o_y_q15; + wire signed [15:0] delta_q15 = e_q15 >>> K; // arithmetic shift + wire signed [15:0] y_next = o_y_q15 + delta_q15; // clamp to [0, 0x7FFF] (handy if your signal is non-negative) + + function signed [15:0] clamp01; + input signed [15:0] v; + begin + if (v < 16'sd0) + clamp01 = 16'sd0; + else if (v > 16'sh7FFF) + clamp01 = 16'sh7FFF; + else + clamp01 = v; + end + endfunction + + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) o_y_q15 <= 16'sd0; + else o_y_q15 <= clamp01(y_next); + end +endmodule diff --git a/cores/signal/nco_q15/rtl/nco_q15.v b/cores/signal/nco_q15/rtl/nco_q15.v index fe20395..13c6bb4 100644 --- a/cores/signal/nco_q15/rtl/nco_q15.v +++ b/cores/signal/nco_q15/rtl/nco_q15.v @@ -8,23 +8,23 @@ // -- CLK_HZ : input clock frequency in Hz // -- FS_HZ : output sample frequency in Hz // inout: -// -- clk : input clock -// -- rst_n : reset -// -- freq_hz : decimal number of desired generated frequency in Hz, 0-FS/2 -// -- sin_q15/cos_q15 : I and Q outputs -// -- clk_en : output valid strobe +// -- i_clk : input clock +// -- i_rst_n : reset +// -- i_freq_hz : decimal number of desired generated frequency in Hz, 0-FS/2 +// -- o_sin_q15/o_cos_q15 : I and Q outputs +// -- o_clk_en : output valid strobe // ============================================================================= module nco_q15 #( parameter integer CLK_HZ = 120_000_000, // input clock parameter integer FS_HZ = 40_000 // sample rate )( - input wire clk, // CLK_HZ domain - input wire rst_n, // async active-low reset - input wire [31:0] freq_hz, // desired output frequency (Hz), 0..FS_HZ/2 + input wire i_clk, // CLK_HZ domain + input wire i_rst_n, // async active-low reset + input wire [31:0] i_freq_hz, // desired output frequency (Hz), 0..FS_HZ/2 - output reg signed [15:0] sin_q15, // Q1.15 sine - output reg signed [15:0] cos_q15, // Q1.15 cosine - output reg clk_en // 1-cycle strobe @ FS_HZ + output reg signed [15:0] o_sin_q15, // Q1.15 sine + output reg signed [15:0] o_cos_q15, // Q1.15 cosine + output reg o_clk_en // 1-cycle strobe @ FS_HZ ); localparam integer PHASE_FRAC_BITS = 6; localparam integer QTR_ADDR_BITS = 6; @@ -41,17 +41,17 @@ module nco_q15 #( endfunction reg [clog2(DIV)-1:0] tick_cnt; - always @(posedge clk or negedge rst_n) begin - if (!rst_n) begin tick_cnt <= 0; clk_en <= 1'b0; end + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin tick_cnt <= 0; o_clk_en <= 1'b0; end else begin - clk_en <= 1'b0; - if (tick_cnt == DIV-1) begin tick_cnt <= 0; clk_en <= 1'b1; end + o_clk_en <= 1'b0; + if (tick_cnt == DIV-1) begin tick_cnt <= 0; o_clk_en <= 1'b1; end else tick_cnt <= tick_cnt + 1'b1; end end - // 32-cycle shift-add multiply: prod = freq_hz * RECIP (no multiplications themself) - // Starts at clk_en, finishes in 32 cycles (<< available cycles per sample). + // 32-cycle shift-add multiply: prod = i_freq_hz * RECIP (no multiplications themself) + // Starts at o_clk_en, finishes in 32 cycles (<< available cycles per sample). reg mul_busy; reg [5:0] mul_i; // 0..31 reg [31:0] f_reg; @@ -59,15 +59,15 @@ module nco_q15 #( wire [95:0] recip_shift = {{32{1'b0}}, RECIP} << mul_i; // shift constant by i - always @(posedge clk or negedge rst_n) begin - if (!rst_n) begin + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin mul_busy <= 1'b0; mul_i <= 6'd0; f_reg <= 32'd0; acc <= 96'd0; end else begin - if (clk_en && !mul_busy) begin + if (o_clk_en && !mul_busy) begin // kick off a new multiply this sample mul_busy <= 1'b1; mul_i <= 6'd0; - f_reg <= (freq_hz > (FS_HZ>>1)) ? (FS_HZ>>1) : freq_hz; // clamp to Nyquist + f_reg <= (i_freq_hz > (FS_HZ>>1)) ? (FS_HZ>>1) : i_freq_hz; // clamp to Nyquist acc <= 96'd0; end else if (mul_busy) begin // add shifted RECIP if bit is set @@ -86,16 +86,16 @@ module nco_q15 #( wire [95:0] acc_round = acc + (96'd1 << (SHIFT-1)); wire [PHASE_BITS-1:0] ftw_next = acc_round[SHIFT +: PHASE_BITS]; // >> SHIFT - always @(posedge clk or negedge rst_n) begin - if (!rst_n) ftw_q <= {PHASE_BITS{1'b0}}; + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) ftw_q <= {PHASE_BITS{1'b0}}; else if (!mul_busy) ftw_q <= ftw_next; // update once product ready end // Phase accumulator (advance at FS_HZ) reg [PHASE_BITS-1:0] phase; - always @(posedge clk or negedge rst_n) begin - if (!rst_n) phase <= {PHASE_BITS{1'b0}}; - else if (clk_en) phase <= phase + ftw_q; + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) phase <= {PHASE_BITS{1'b0}}; + else if (o_clk_en) phase <= phase + ftw_q; end // Cosine phase = sine phase + 90° @@ -113,8 +113,8 @@ module nco_q15 #( // 64-entry quarter-wave LUT wire [7:0] mag_sin_u8, mag_cos_u8; - sine_qtr_lut64 u_lut_s (.addr(idx_sin), .dout(mag_sin_u8)); - sine_qtr_lut64 u_lut_c (.addr(idx_cos), .dout(mag_cos_u8)); + sine_qtr_lut64 u_lut_s (.i_addr(idx_sin), .o_dout(mag_sin_u8)); + sine_qtr_lut64 u_lut_c (.i_addr(idx_cos), .o_dout(mag_cos_u8)); // Scale to Q1.15 and apply sign wire signed [15:0] mag_sin_q15 = {1'b0, mag_sin_u8, 7'd0}; @@ -125,39 +125,39 @@ module nco_q15 #( wire signed [15:0] sin_next = sin_neg ? -mag_sin_q15 : mag_sin_q15; wire signed [15:0] cos_next = cos_neg ? -mag_cos_q15 : mag_cos_q15; - always @(posedge clk or negedge rst_n) begin - if (!rst_n) begin - sin_q15 <= 16'sd0; cos_q15 <= 16'sd0; - end else if (clk_en) begin - sin_q15 <= sin_next; cos_q15 <= cos_next; + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin + o_sin_q15 <= 16'sd0; o_cos_q15 <= 16'sd0; + end else if (o_clk_en) begin + o_sin_q15 <= sin_next; o_cos_q15 <= cos_next; end end endmodule module sine_qtr_lut64( - input wire [5:0] addr, - output reg [7:0] dout + input wire [5:0] i_addr, + output reg [7:0] o_dout ); always @* begin - case (addr) - 6'd0: dout = 8'd0; 6'd1: dout = 8'd6; 6'd2: dout = 8'd13; 6'd3: dout = 8'd19; - 6'd4: dout = 8'd25; 6'd5: dout = 8'd31; 6'd6: dout = 8'd37; 6'd7: dout = 8'd44; - 6'd8: dout = 8'd50; 6'd9: dout = 8'd56; 6'd10: dout = 8'd62; 6'd11: dout = 8'd68; - 6'd12: dout = 8'd74; 6'd13: dout = 8'd80; 6'd14: dout = 8'd86; 6'd15: dout = 8'd92; - 6'd16: dout = 8'd98; 6'd17: dout = 8'd103; 6'd18: dout = 8'd109; 6'd19: dout = 8'd115; - 6'd20: dout = 8'd120; 6'd21: dout = 8'd126; 6'd22: dout = 8'd131; 6'd23: dout = 8'd136; - 6'd24: dout = 8'd142; 6'd25: dout = 8'd147; 6'd26: dout = 8'd152; 6'd27: dout = 8'd157; - 6'd28: dout = 8'd162; 6'd29: dout = 8'd167; 6'd30: dout = 8'd171; 6'd31: dout = 8'd176; - 6'd32: dout = 8'd180; 6'd33: dout = 8'd185; 6'd34: dout = 8'd189; 6'd35: dout = 8'd193; - 6'd36: dout = 8'd197; 6'd37: dout = 8'd201; 6'd38: dout = 8'd205; 6'd39: dout = 8'd208; - 6'd40: dout = 8'd212; 6'd41: dout = 8'd215; 6'd42: dout = 8'd219; 6'd43: dout = 8'd222; - 6'd44: dout = 8'd225; 6'd45: dout = 8'd228; 6'd46: dout = 8'd231; 6'd47: dout = 8'd233; - 6'd48: dout = 8'd236; 6'd49: dout = 8'd238; 6'd50: dout = 8'd240; 6'd51: dout = 8'd242; - 6'd52: dout = 8'd244; 6'd53: dout = 8'd246; 6'd54: dout = 8'd247; 6'd55: dout = 8'd249; - 6'd56: dout = 8'd250; 6'd57: dout = 8'd251; 6'd58: dout = 8'd252; 6'd59: dout = 8'd253; - 6'd60: dout = 8'd254; 6'd61: dout = 8'd254; 6'd62: dout = 8'd255; 6'd63: dout = 8'd255; - default: dout=8'd0; + case (i_addr) + 6'd0: o_dout = 8'd0; 6'd1: o_dout = 8'd6; 6'd2: o_dout = 8'd13; 6'd3: o_dout = 8'd19; + 6'd4: o_dout = 8'd25; 6'd5: o_dout = 8'd31; 6'd6: o_dout = 8'd37; 6'd7: o_dout = 8'd44; + 6'd8: o_dout = 8'd50; 6'd9: o_dout = 8'd56; 6'd10: o_dout = 8'd62; 6'd11: o_dout = 8'd68; + 6'd12: o_dout = 8'd74; 6'd13: o_dout = 8'd80; 6'd14: o_dout = 8'd86; 6'd15: o_dout = 8'd92; + 6'd16: o_dout = 8'd98; 6'd17: o_dout = 8'd103; 6'd18: o_dout = 8'd109; 6'd19: o_dout = 8'd115; + 6'd20: o_dout = 8'd120; 6'd21: o_dout = 8'd126; 6'd22: o_dout = 8'd131; 6'd23: o_dout = 8'd136; + 6'd24: o_dout = 8'd142; 6'd25: o_dout = 8'd147; 6'd26: o_dout = 8'd152; 6'd27: o_dout = 8'd157; + 6'd28: o_dout = 8'd162; 6'd29: o_dout = 8'd167; 6'd30: o_dout = 8'd171; 6'd31: o_dout = 8'd176; + 6'd32: o_dout = 8'd180; 6'd33: o_dout = 8'd185; 6'd34: o_dout = 8'd189; 6'd35: o_dout = 8'd193; + 6'd36: o_dout = 8'd197; 6'd37: o_dout = 8'd201; 6'd38: o_dout = 8'd205; 6'd39: o_dout = 8'd208; + 6'd40: o_dout = 8'd212; 6'd41: o_dout = 8'd215; 6'd42: o_dout = 8'd219; 6'd43: o_dout = 8'd222; + 6'd44: o_dout = 8'd225; 6'd45: o_dout = 8'd228; 6'd46: o_dout = 8'd231; 6'd47: o_dout = 8'd233; + 6'd48: o_dout = 8'd236; 6'd49: o_dout = 8'd238; 6'd50: o_dout = 8'd240; 6'd51: o_dout = 8'd242; + 6'd52: o_dout = 8'd244; 6'd53: o_dout = 8'd246; 6'd54: o_dout = 8'd247; 6'd55: o_dout = 8'd249; + 6'd56: o_dout = 8'd250; 6'd57: o_dout = 8'd251; 6'd58: o_dout = 8'd252; 6'd59: o_dout = 8'd253; + 6'd60: o_dout = 8'd254; 6'd61: o_dout = 8'd254; 6'd62: o_dout = 8'd255; 6'd63: o_dout = 8'd255; + default: o_dout=8'd0; endcase end -endmodule \ No newline at end of file +endmodule diff --git a/cores/signal/nco_q15/tb/tb_nco_q15.v b/cores/signal/nco_q15/tb/tb_nco_q15.v index a85cb53..5f81fcc 100644 --- a/cores/signal/nco_q15/tb/tb_nco_q15.v +++ b/cores/signal/nco_q15/tb/tb_nco_q15.v @@ -15,12 +15,12 @@ module tb_nco_q15(); wire out_en; nco_q15 #(.CLK_HZ(120_000_000), .FS_HZ(40_000)) nco ( - .clk (clk), - .rst_n (resetn), - .freq_hz(freq), - .sin_q15(sin_q15), - .cos_q15(cos_q15), - .clk_en (out_en) + .i_clk (clk), + .i_rst_n (resetn), + .i_freq_hz(freq), + .o_sin_q15(sin_q15), + .o_cos_q15(cos_q15), + .o_clk_en (out_en) ); initial begin @@ -39,4 +39,4 @@ module tb_nco_q15(); $finish; end; -endmodule \ No newline at end of file +endmodule diff --git a/cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh b/cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh new file mode 100644 index 0000000..27e6804 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh @@ -0,0 +1,91 @@ +// rc_alpha_q15.vh +// Plain Verilog-2001 constant function: R(ohm), C(pF), Fs(Hz) -> alpha_q15 (Q1.15) +// Uses fixed-point approximation: 1 - exp(-x) ≈ x - x^2/2 + x^3/6, where x = 1/(Fs*R*C) +// All integer math; suitable for elaboration-time constant folding (e.g., XST). + +`ifndef RC_ALPHA_Q15_VH +`define RC_ALPHA_Q15_VH + +function integer alpha_q15_from_rc; + input integer R_OHM; // ohms + input integer C_PF; // picofarads + input integer FS_HZ; // Hz + + // Choose QN for x. N=24 is a good balance for accuracy/width. + integer N; + + // We'll keep everything as unsigned vectors; inputs copied into vectors first. + reg [63:0] R_u, C_u, FS_u; + + // x = 1 / (Fs * R * C) with C in pF -> x = 1e12 / (Fs*R*C_pf) + // x_qN = round( x * 2^N ) = round( (1e12 << N) / denom ) + reg [127:0] NUM_1E12_SLLN; // big enough for 1e12 << N + reg [127:0] DENOM; // Fs*R*C + reg [127:0] X_qN; // x in QN + + // Powers + reg [255:0] X2; // x^2 in Q(2N) + reg [383:0] X3; // x^3 in Q(3N) + + integer term1_q15; + integer term2_q15; + integer term3_q15; + integer acc; + +begin + N = 24; + + // Copy integer inputs into 64-bit vectors (no bit-slicing of integers) + R_u = R_OHM[31:0]; + C_u = C_PF[31:0]; + FS_u = FS_HZ[31:0]; + + // Denominator = Fs * R * C_pf (fits in < 2^64 for typical values) + DENOM = 128'd0; + DENOM = FS_u; + DENOM = DENOM * R_u; + DENOM = DENOM * C_u; + + // // Guard: avoid divide by zero + // if (DENOM == 0) begin + // alpha_q15_from_rc = 0; + // disable alpha_q15_from_rc; + // end + + // Numerator = (1e12 << N). 1e12 * 2^24 ≈ 1.6777e19 (fits in 2^64..2^65), + // so use 128 bits to be safe. + NUM_1E12_SLLN = 128'd1000000000000 << N; + + // x_qN = rounded division + X_qN = (NUM_1E12_SLLN + (DENOM >> 1)) / DENOM; + + // Powers + X2 = X_qN * X_qN; + X3 = X2 * X_qN; + + // Convert terms to Q1.15: + // term1 = x -> shift from QN to Q15 + term1_q15 = (X_qN >> (N - 15)) & 16'hFFFF; + + // term2 = x^2 / 2 -> Q(2N) to Q15 and /2 + term2_q15 = (X2 >> (2*N - 15 + 1)) & 16'hFFFF; + + // term3 = x^3 / 6 -> Q(3N) to Q15, then /6 with rounding + begin : gen_t3 + reg [383:0] tmp_q15_wide; + reg [383:0] tmp_div6; + tmp_q15_wide = (X3 >> (3*N - 15)); + tmp_div6 = (tmp_q15_wide + 6'd3) / 6; + term3_q15 = tmp_div6[15:0]; + end + + // Combine and clamp + acc = term1_q15 - term2_q15 + term3_q15; + if (acc < 0) acc = 0; + else if (acc > 16'h7FFF) acc = 16'h7FFF; + + alpha_q15_from_rc = acc; +end +endfunction + +`endif diff --git a/cores/signal/sd_adc_q15/rtl/rcmodel_q15.v b/cores/signal/sd_adc_q15/rtl/rcmodel_q15.v new file mode 100644 index 0000000..505d4f5 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/rcmodel_q15.v @@ -0,0 +1,55 @@ +`timescale 1ns/1ps + +// ============================================================================= +// RC model to convert sigma delta samples to Q1.15 +// Models the RC circuit on the outside of the FPGA +// Uses: Yn+1 = Yn + (sd - Yn)*(1-exp(-T/RC)) +// parameters: +// -- alpha_q15 : the 1-exp(-T/RC), defaults to R=3k3, C=220p and T=1/15MHz +// rounded to only use two bits (0b3b -> 0b00), the less +// bits the better +// inout: +// -- i_clk : input clock +// -- i_rst_n : reset signal +// -- i_sd_sample : 1 bit sample output from sd sampler +// -- o_sample_q15 : output samples in q.15 +// ============================================================================= +module rcmodel_q15 #( + parameter integer alpha_q15 = 16'sh0b00 +)( + input wire i_clk, + input wire i_rst_n, + input wire i_sd_sample, + output wire [15:0] o_sample_q15 +); + reg signed [15:0] y_q15; + wire signed [15:0] sd_q15 = i_sd_sample ? 16'sh7fff : 16'sh0000; + wire signed [15:0] e_q15 = sd_q15 - y_q15; + // wire signed [31:0] prod_q30 = $signed(e_q15) * $signed(alpha_q15); + wire signed [31:0] prod_q30; + // Use shift-add algorithm for multiplication + mul_const_shiftadd #( + .C($signed(alpha_q15)), + .IN_W(16), + .OUT_W(32) + ) alpha_times_e ( + .i_x(e_q15), + .o_y(prod_q30) + ); + wire signed [15:0] y_next_q15 = y_q15 + (prod_q30>>>15); + + // clamp to [0, 0x7FFF] (keeps signal view tidy) + function signed [15:0] clamp01_q15(input signed [15:0] v); + if (v < 16'sd0000) clamp01_q15 = 16'sd0000; + else if (v > 16'sh7FFF) clamp01_q15 = 16'sh7FFF; + else clamp01_q15 = v; + endfunction + + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) y_q15 <= 16'sd0000; + else y_q15 <= clamp01_q15(y_next_q15); + end + + assign o_sample_q15 = y_q15; + +endmodule diff --git a/cores/signal/sd_adc_q15/rtl/sd_adc_q15.v b/cores/signal/sd_adc_q15/rtl/sd_adc_q15.v new file mode 100644 index 0000000..7130a25 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/sd_adc_q15.v @@ -0,0 +1,57 @@ +module sd_adc_q15 #( + parameter integer R_OHM = 3300, + parameter integer C_PF = 220 +)( + input wire i_clk_15, + input wire i_rst_n, + + input wire i_adc_a, + input wire i_adc_b, + output wire o_adc, + + output wire signed [15:0] o_signal_q15, + output wire o_signal_valid +); + `include "rc_alpha_q15.vh" + + wire sd_signal; + wire signed [15:0] raw_sample_biased; + wire signed [15:0] raw_sample_q15; + wire signed [15:0] lpf_sample_q15; + + sd_sampler sd_sampler( + .i_clk(i_clk_15), + .i_a(i_adc_a), .i_b(i_adc_b), + .o_sample(sd_signal) + ); + assign o_adc = sd_signal; + + localparam integer alpha_q15_int = alpha_q15_from_rc(R_OHM, C_PF, 15000000); + localparam signed [15:0] alpha_q15 = alpha_q15_int[15:0]; + localparam signed [15:0] alpha_q15_top = alpha_q15 & 16'hff00; + rcmodel_q15 #( + .alpha_q15(alpha_q15_top) + ) rc_model ( + .i_clk(i_clk_15), .i_rst_n(i_rst_n), + .i_sd_sample(sd_signal), + .o_sample_q15(raw_sample_q15) + ); + + lpf_iir_q15_k #( + .K(10) + ) lpf ( + .i_clk(i_clk_15), .i_rst_n(i_rst_n), + .i_x_q15(raw_sample_q15), + .o_y_q15(lpf_sample_q15) + ); + + decimate_by_r_q15 #( + .R(375), // 15MHz/375 = 40KHz + .CNT_W(10) + ) decimate ( + .i_clk(i_clk_15), .i_rst_n(i_rst_n), + .i_valid(1'b1), .i_q15(lpf_sample_q15), + .o_valid(o_signal_valid), .o_q15(o_signal_q15) + ); + +endmodule diff --git a/cores/signal/sd_adc_q15/rtl/sd_sampler.v b/cores/signal/sd_adc_q15/rtl/sd_sampler.v new file mode 100644 index 0000000..e17fa19 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/sd_sampler.v @@ -0,0 +1,18 @@ +module sd_sampler( + input wire i_clk, + input wire i_a, + input wire i_b, + output wire o_sample +); + + wire comp_out; + lvds_comparator comp ( + .a(i_a), .b(i_b), .o(comp_out) + ); + + reg registered_comp_out; + always @(posedge i_clk) + registered_comp_out <= comp_out; + assign o_sample = registered_comp_out; + +endmodule diff --git a/cores/signal/sd_adc_q15/sd_adc_q15.core b/cores/signal/sd_adc_q15/sd_adc_q15.core new file mode 100644 index 0000000..361973d --- /dev/null +++ b/cores/signal/sd_adc_q15/sd_adc_q15.core @@ -0,0 +1,57 @@ +CAPI=2: + +name: joppeb:signal:sd_adc_q15:1.0 +description: Sigma-delta ADC front-end with Q1.15 output + +filesets: + rtl_common: + depend: + - joppeb:primitive:lvds_comparator + - joppeb:signal:lpf_iir_q15_k + - joppeb:signal:decimate_by_r_q15 + - joppeb:util:mul_const + files: + - rtl/rc_alpha_q15.vh: + is_include_file: true + - rtl/rcmodel_q15.v + - rtl/sd_adc_q15.v + file_type: verilogSource + rtl_sampler: + files: + - rtl/sd_sampler.v + file_type: verilogSource + sim_sampler: + files: + - sim/sd_sampler.v + file_type: verilogSource + tb: + files: + - tb/tb_sd_adc_q15.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl_common + - rtl_sampler + toplevel: sd_adc_q15 + parameters: + - R_OHM + - C_PF + sim: + default_tool: icarus + filesets: + - rtl_common + - sim_sampler + - tb + toplevel: tb_sd_adc_q15 + +parameters: + R_OHM: + datatype: int + description: RC filter resistor value in ohms + paramtype: vlogparam + C_PF: + datatype: int + description: RC filter capacitor value in pF + paramtype: vlogparam diff --git a/cores/signal/sd_adc_q15/sim/sd_sampler.v b/cores/signal/sd_adc_q15/sim/sd_sampler.v new file mode 100644 index 0000000..99f2d1a --- /dev/null +++ b/cores/signal/sd_adc_q15/sim/sd_sampler.v @@ -0,0 +1,111 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Sigma-Delta sampler +// Simulates an RC circuit between o_sample and i_b and i_a sine at i_a +// ============================================================================= +module sd_sampler( + input wire i_clk, + input wire i_a, + input wire i_b, + output wire o_sample +); + + // Sine source (i_a input / P) + parameter real F_HZ = 5000; // input sine frequency (1 kHz) + parameter real AMP = 1.5; // sine amplitude (V) + parameter real VCM = 1.65; // common-mode (V), centered in 0..3.3V + + // Comparator behavior + parameter real VTH = 0.0; // threshold on (vp - vn) + parameter real VHYST = 0.05; // symmetric hysteresis half-width (V) + parameter integer ADD_HYST = 0; // 1 to enable hysteresis + + // 1-bit DAC rails (feedback into RC) + parameter real VLOW = 0.0; // DAC 0 (V) + parameter real VHIGH = 3.3; // DAC 1 (V) + + // RC filter (i_b input / N) + parameter real R_OHMS = 3300.0; // 3.3k + parameter real C_FARADS = 220e-12; // 220 pF + + // Integration step (ties to `timescale`) + parameter integer TSTEP_NS = 10; // sim step in ns (choose << tau) + + // ===== Internal state (simulation only) ===== + real vp, vn; // comparator i_a/i_b inputs + real v_rc; // RC node voltage (== vn) + real v_dac; // DAC output voltage from o_sample + real t_s; // time in seconds + real dt_s; // step in seconds + real tau_s; // R*C time constant in seconds + real two_pi; + reg q; // comparator latched output (pre-delay) + reg out; + reg sampler; + + initial sampler <= 1'b0; + always @(posedge i_clk) begin + sampler <= out; + end + assign o_sample = sampler; + + + // Helper task: update comparator with optional hysteresis + task automatic comp_update; + real diff; + begin + diff = (vp - vn); + + if (ADD_HYST != 0) begin + // simple symmetric hysteresis around VTH + if (q && (diff < (VTH - VHYST))) q = 1'b0; + else if (!q && (diff > (VTH + VHYST))) q = 1'b1; + // else hold + end else begin + q = (diff > VTH) ? 1'b1 : 1'b0; + end + end + endtask + + initial begin + // Init constants + two_pi = 6.283185307179586; + tau_s = R_OHMS * C_FARADS; // ~7.26e-7 s + dt_s = TSTEP_NS * 1.0e-9; + + // Init states + t_s = 0.0; + q = 1'b0; // start low + out = 1'b0; + v_dac= VLOW; + v_rc = (VHIGH + VLOW)/2.0; // start mid-rail to reduce start-up transient + vn = v_rc; + vp = VCM; + + // Main sim loop + forever begin + #(TSTEP_NS); // advance discrete time step + t_s = t_s + dt_s; + + // 1) Update DAC from previous comparator state + v_dac = sampler ? VHIGH : VLOW; + + // 2) RC low-pass driven by DAC: Euler step + // dv = (v_dac - v_rc) * dt/tau + v_rc = v_rc + (v_dac - v_rc) * (dt_s / tau_s); + vn = v_rc; + + // 3) Input sine on i_a + vp = VCM + AMP * $sin(two_pi * F_HZ * t_s); + + // 4) Comparator decision (with optional hysteresis) + comp_update(); + + // 5) Output with propagation delay + out = q; + end + end + + +endmodule diff --git a/cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v b/cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v new file mode 100644 index 0000000..c5ce51f --- /dev/null +++ b/cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v @@ -0,0 +1,36 @@ +`timescale 1ns/1ps + +module tb_sd_adc_q15(); + // Clock and reset generation + reg clk; + reg resetn; + initial clk <= 1'b0; + initial resetn <= 1'b0; + always #6.667 clk <= !clk; + initial #40 resetn <= 1'b1; + + // Default run + initial begin + $dumpfile("out.vcd"); + $dumpvars; + #2_000_000 + $finish; + end; + + wire sd_a; + wire sd_b; + wire sd_o; + wire signed [15:0] decimated_q15; + wire decimated_valid; + + sd_adc_q15 #( + .R_OHM(3300), + .C_PF(220) + ) dut( + .i_clk_15(clk), .i_rst_n(resetn), + .i_adc_a(sd_a), .i_adc_b(sd_b), .o_adc(sd_o), + .o_signal_q15(decimated_q15), + .o_signal_valid(decimated_valid) + ); + +endmodule diff --git a/cores/system/test/rtl/toplevel.v b/cores/system/test/rtl/toplevel.v index e14c9f8..49830a5 100644 --- a/cores/system/test/rtl/toplevel.v +++ b/cores/system/test/rtl/toplevel.v @@ -81,12 +81,12 @@ module toplevel #( .CLK_HZ(15_000_000), .FS_HZ(80_000) ) nco ( - .clk (clk_15), - .rst_n (sys_resetn), - .freq_hz(GPIO_A), - .sin_q15(sin_q15), - .cos_q15(), - .clk_en (clk_en) + .i_clk (clk_15), + .i_rst_n (sys_resetn), + .i_freq_hz(GPIO_A), + .o_sin_q15(sin_q15), + .o_cos_q15(), + .o_clk_en (clk_en) ); reg [5:0] dac_code; diff --git a/cores/util/mul_const/mul_const.core b/cores/util/mul_const/mul_const.core new file mode 100644 index 0000000..4602beb --- /dev/null +++ b/cores/util/mul_const/mul_const.core @@ -0,0 +1,15 @@ +CAPI=2: + +name: joppeb:util:mul_const:1.0 +description: Constant multiplier helpers implemented with shift-add logic + +filesets: + rtl: + files: + - rtl/mul_const.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl diff --git a/cores/util/mul_const/rtl/mul_const.v b/cores/util/mul_const/rtl/mul_const.v new file mode 100644 index 0000000..481e9b0 --- /dev/null +++ b/cores/util/mul_const/rtl/mul_const.v @@ -0,0 +1,31 @@ +`timescale 1ns/1ps + +module mul_const_shiftadd #( + parameter integer C = 0, + parameter integer IN_W = 16, + parameter integer OUT_W = 32 +)( + input wire signed [IN_W-1:0] i_x, + output reg signed [OUT_W-1:0] o_y +); + integer k; + integer abs_c; + reg signed [OUT_W-1:0] acc; + reg signed [OUT_W-1:0] x_ext; + + always @* begin + abs_c = (C < 0) ? -C : C; + acc = {OUT_W{1'b0}}; + x_ext = {{(OUT_W-IN_W){i_x[IN_W-1]}}, i_x}; + + for (k = 0; k < 32; k = k + 1) begin + if (abs_c[k]) + acc = acc + (x_ext <<< k); + end + + if (C < 0) + o_y = -acc; + else + o_y = acc; + end +endmodule