Using remotesyn and added NCO
This commit is contained in:
176
rtl/core/nco_q15.v
Normal file
176
rtl/core/nco_q15.v
Normal file
@@ -0,0 +1,176 @@
|
||||
`timescale 1ns/1ps
|
||||
// ------------------------------------------------------------
|
||||
// nco_q15.v
|
||||
// Tiny DDS/NCO @ FS_HZ sample rate with Q1.15 sine/cos outputs
|
||||
// - Phase accumulator width: PHASE_BITS (default 32)
|
||||
// - Quarter-wave LUT: 2^QTR_ADDR_BITS entries (default 64)
|
||||
// - Clock domain: CLK_HZ (default 120 MHz), creates 1-cycle strobe at FS_HZ
|
||||
// - Frequency control: write 'ftw' (32-bit tuning word)
|
||||
// FTW = round(f_out * 2^PHASE_BITS / FS_HZ)
|
||||
// ------------------------------------------------------------
|
||||
|
||||
module nco_q15 #
|
||||
(
|
||||
// -------- Synth parameters --------
|
||||
parameter integer PHASE_BITS = 32, // accumulator width
|
||||
parameter integer QTR_ADDR_BITS = 6, // 64-entry quarter-wave LUT
|
||||
parameter integer CLK_HZ = 120_000_000, // input clock (Hz)
|
||||
parameter integer FS_HZ = 40_000 // output sample rate (Hz)
|
||||
)
|
||||
(
|
||||
input wire clk, // CLK_HZ domain
|
||||
input wire rst_n, // async active-low reset
|
||||
|
||||
// Frequency control
|
||||
input wire [31:0] ftw_in, // Frequency Tuning Word (FTW)
|
||||
input wire ftw_we, // write-enable strobe (1 clk pulse)
|
||||
|
||||
// Outputs (valid on clk_en rising pulse, i.e., at FS_HZ)
|
||||
output reg signed [15:0] sin_q15, // signed Q1.15 sine
|
||||
output reg signed [15:0] cos_q15, // signed Q1.15 cosine
|
||||
output reg clk_en // 1-cycle strobe @ FS_HZ
|
||||
);
|
||||
|
||||
`include "core/nco_q15_funcs.vh"
|
||||
|
||||
// ==========================================================
|
||||
// Sample-rate enable: divide CLK_HZ down to FS_HZ
|
||||
// - DIV must be an integer (CLK_HZ / FS_HZ).
|
||||
// - clk_en goes high for exactly 1 clk cycle every DIV cycles.
|
||||
// ==========================================================
|
||||
localparam integer DIV = CLK_HZ / FS_HZ;
|
||||
|
||||
// Optional safety for misconfiguration (ignored by synthesis tools):
|
||||
initial if (CLK_HZ % FS_HZ != 0)
|
||||
$display("WARNING nco_q15: CLK_HZ (%0d) not divisible by FS_HZ (%0d).", CLK_HZ, FS_HZ);
|
||||
|
||||
// Counter width: enough bits to count to DIV-1 (use a generous fixed width to keep 2001-compatible)
|
||||
// If you prefer, replace 16 with $clog2(DIV) on a tool that supports it well.
|
||||
reg [15:0] tick_cnt;
|
||||
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
tick_cnt <= 16'd0;
|
||||
clk_en <= 1'b0;
|
||||
end else begin
|
||||
if (tick_cnt == DIV-1) begin
|
||||
tick_cnt <= 16'd0;
|
||||
clk_en <= 1'b1; // 1-cycle pulse
|
||||
end else begin
|
||||
tick_cnt <= tick_cnt + 16'd1;
|
||||
clk_en <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ==========================================================
|
||||
// Frequency control register
|
||||
// - You present ftw_in and pulse ftw_we (1 clk) to update.
|
||||
// ==========================================================
|
||||
reg [31:0] ftw;
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) ftw <= ftw_from_hz(1000, PHASE_BITS, FS_HZ); // Start at 1khz
|
||||
else if (ftw_we) ftw <= ftw_in;
|
||||
end
|
||||
|
||||
// ==========================================================
|
||||
// Phase accumulators
|
||||
// - phase_sin advances by FTW once per sample (on clk_en).
|
||||
// - cosine is generated by a +90° phase lead (π/2), i.e., add 2^(PHASE_BITS-2).
|
||||
// Here we realize it by deriving phase_cos from phase_sin each sample.
|
||||
// ==========================================================
|
||||
reg [PHASE_BITS-1:0] phase_sin, phase_cos;
|
||||
wire [PHASE_BITS-1:0] phase_cos_plus90 = phase_sin + ({{(PHASE_BITS-2){1'b0}}, 2'b01} << (PHASE_BITS-2)); // +90°
|
||||
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
phase_sin <= {PHASE_BITS{1'b0}};
|
||||
phase_cos <= {PHASE_BITS{1'b0}};
|
||||
end else if (clk_en) begin
|
||||
phase_sin <= phase_sin + ftw;
|
||||
// Keep cosine aligned to the same sample using a +90° offset from updated phase_sin
|
||||
phase_cos <= phase_cos_plus90 + ftw;
|
||||
end
|
||||
end
|
||||
|
||||
// ==========================================================
|
||||
// Phase -> quadrant/index
|
||||
// - q_*: top 2 bits select quadrant (0..3).
|
||||
// - idx_*_raw: next QTR_ADDR_BITS select a point in 0..π/2.
|
||||
// - For odd quadrants, mirror the LUT index.
|
||||
// ==========================================================
|
||||
wire [1:0] q_sin = phase_sin[PHASE_BITS-1 -: 2];
|
||||
wire [1:0] q_cos = phase_cos[PHASE_BITS-1 -: 2];
|
||||
|
||||
wire [QTR_ADDR_BITS-1:0] idx_sin_raw = phase_sin[PHASE_BITS-3 -: QTR_ADDR_BITS];
|
||||
wire [QTR_ADDR_BITS-1:0] idx_cos_raw = phase_cos[PHASE_BITS-3 -: QTR_ADDR_BITS];
|
||||
|
||||
wire [QTR_ADDR_BITS-1:0] idx_sin = (q_sin[0]) ? ({QTR_ADDR_BITS{1'b1}} - idx_sin_raw) : idx_sin_raw;
|
||||
wire [QTR_ADDR_BITS-1:0] idx_cos = (q_cos[0]) ? ({QTR_ADDR_BITS{1'b1}} - idx_cos_raw) : idx_cos_raw;
|
||||
|
||||
// ==========================================================
|
||||
// Quarter-wave 8-bit LUT (0..255). 64 entries map 0..π/2.
|
||||
// ==========================================================
|
||||
wire [7:0] lut_sin_mag, lut_cos_mag;
|
||||
sine_qtr_lut64 u_lut_s (.addr(idx_sin), .dout(lut_sin_mag));
|
||||
sine_qtr_lut64 u_lut_c (.addr(idx_cos), .dout(lut_cos_mag));
|
||||
|
||||
// ==========================================================
|
||||
// Sign & scale to Q1.15
|
||||
// - Scale: <<7 so 255 becomes 32640 (slightly below 32767).
|
||||
// - Apply sign by quadrant: quadrants 2 & 3 are negative.
|
||||
// ==========================================================
|
||||
wire signed [15:0] sin_mag_q15 = {1'b0, lut_sin_mag, 7'd0};
|
||||
wire signed [15:0] cos_mag_q15 = {1'b0, lut_cos_mag, 7'd0};
|
||||
|
||||
wire sin_neg = (q_sin >= 2); // quadrants 2,3
|
||||
wire cos_neg = (q_cos >= 2);
|
||||
|
||||
wire signed [15:0] sin_q15_next = sin_neg ? -sin_mag_q15 : sin_mag_q15;
|
||||
wire signed [15:0] cos_q15_next = cos_neg ? -cos_mag_q15 : cos_mag_q15;
|
||||
|
||||
// ==========================================================
|
||||
// Output registers (update on clk_en)
|
||||
// ==========================================================
|
||||
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_q15_next;
|
||||
cos_q15 <= cos_q15_next;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 64-entry quarter-wave sine ROM (8-bit), indices 0..63 map 0..π/2.
|
||||
// Plain Verilog case ROM. You can regenerate these with a script
|
||||
// or replace with a vendor-specific ROM for BRAM inference.
|
||||
// ------------------------------------------------------------
|
||||
module sine_qtr_lut64(
|
||||
input wire [5:0] addr,
|
||||
output reg [7:0] 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;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
24
rtl/core/nco_q15_funcs.vh
Normal file
24
rtl/core/nco_q15_funcs.vh
Normal file
@@ -0,0 +1,24 @@
|
||||
// ==========================================================
|
||||
// Helper: FTW function (compile-time or runtime call)
|
||||
// FTW = round( f_hz * 2^PHASE_BITS / FS_HZ )
|
||||
// Notes:
|
||||
// - Accepts integer Hz.
|
||||
// - Uses 64-bit math to avoid overflow for typical params.
|
||||
// - Can be called from a testbench or combinational logic that
|
||||
// prepares 'ftw_in' before asserting 'ftw_we'.
|
||||
// Example:
|
||||
// initial begin
|
||||
// #1;
|
||||
// $display("FTW 1kHz = 0x%08x", ftw_from_hz(1000));
|
||||
// end
|
||||
// ==========================================================
|
||||
function [31:0] ftw_from_hz;
|
||||
input integer f_hz;
|
||||
input integer phase_bits;
|
||||
input integer fs_hz;
|
||||
reg [63:0] numer;
|
||||
begin
|
||||
numer = ((64'd1 << phase_bits) * f_hz) + (fs_hz/2);
|
||||
ftw_from_hz = numer / fs_hz;
|
||||
end
|
||||
endfunction
|
||||
45
rtl/toplevel/top_generic.v
Normal file
45
rtl/toplevel/top_generic.v
Normal file
@@ -0,0 +1,45 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module top_generic(
|
||||
input wire aclk,
|
||||
input wire aresetn,
|
||||
|
||||
output wire led_green,
|
||||
output wire led_red,
|
||||
|
||||
output wire[5:0] r2r
|
||||
);
|
||||
|
||||
`include "core/nco_q15_funcs.vh"
|
||||
|
||||
assign led_green = 1'b0;
|
||||
assign led_red = 1'b0;
|
||||
|
||||
wire [15:0] sin_q15;
|
||||
wire clk_en;
|
||||
nco_q15 #(
|
||||
.CLK_HZ(100_000_000),
|
||||
.PHASE_BITS(16)
|
||||
) nco (
|
||||
.clk (aclk),
|
||||
.rst_n (aresetn),
|
||||
.ftw_in (32'h0),
|
||||
.ftw_we (1'b0),
|
||||
.sin_q15(sin_q15),
|
||||
.cos_q15(),
|
||||
.clk_en (clk_en)
|
||||
);
|
||||
|
||||
// sin_q15: signed Q15 in [-32768, +32767]
|
||||
wire signed [15:0] s = sin_q15;
|
||||
// Bias to 0..65535 and round before downscaling by 1024 (>>10)
|
||||
wire [16:0] biased = s + 17'sd32768; // 0..65535
|
||||
wire [5:0] dac_code_next = biased[15:10]; // 0..63 (MSB=bit5)
|
||||
// Register it at the sample rate (clk_en)
|
||||
reg [5:0] dac_code;
|
||||
always @(posedge aclk or negedge aresetn) begin
|
||||
if (!aresetn) dac_code <= 6'd0;
|
||||
else if (clk_en) dac_code <= dac_code_next;
|
||||
end
|
||||
assign r2r = dac_code;
|
||||
endmodule
|
||||
Reference in New Issue
Block a user