Using remotesyn and added NCO

This commit is contained in:
Jojojoppe
2025-10-05 23:20:25 +02:00
parent 639541728f
commit 83cc449c6f
35 changed files with 397 additions and 147990 deletions

9
.gitignore vendored
View File

@@ -1,6 +1,3 @@
impl out
src build
env
*.vcd
*.log
cvcsim*

View File

@@ -1,20 +0,0 @@
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Physical Constraints file
//Tool Version: V1.9.12
//Part Number: GW1NSR-LV4CQN48PC7/I6
//Device: GW1NSR-4C
//Created Time: Wed 10 01 18:02:30 2025
IO_LOC "adc1_A" 39,40;
IO_PORT "adc1_A" IO_TYPE=LVDS25 PULL_MODE=NONE BANK_VCCIO=3.3;
IO_LOC "adc1_O" 41;
IO_PORT "adc1_O" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led" 10;
IO_PORT "led" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "reset_n" 15;
IO_PORT "reset_n" IO_TYPE=LVCMOS18 PULL_MODE=UP BANK_VCCIO=1.8;
IO_LOC "clk" 45;
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3;
IO_LOC "button" 14;
IO_PORT "button" IO_TYPE=LVCMOS18 PULL_MODE=UP BANK_VCCIO=1.8;

View File

@@ -1,8 +0,0 @@
//Copyright (C)2014-2025 GOWIN Semiconductor Corporation.
//All rights reserved.
//File Title: Timing Constraints file
//Tool Version: V1.9.12
//Created Time: 2025-10-01 16:50:37
create_clock -name CLK_IN -period 37.037 -waveform {0 18.518} [get_ports {clk}]
create_clock -name CLK_120 -period 8.333 -waveform {0 4.167} [get_nets {clk_120}]
create_clock -name CLK_15 -period 66.666 -waveform {0 33.333} [get_nets {clk_15}]

View File

@@ -1,133 +0,0 @@
`timescale 1ns/1ps
module sampling(
input wire adc_A,
input wire adc_B,
output wire adc_O,
input wire clk_15,
input wire clk_120,
input wire reset_n
);
wire sigmadelta_sample;
sigmadelta_sampler m_sdsampler(
.clk(clk_15),
.A(adc_A),
.B(adc_B),
.out(sigmadelta_sample)
);
assign adc_O = sigmadelta_sample;
// RC model, output is y_next_q15
// ------------------------------
reg signed [15:0] y_q15;
wire signed [15:0] x_q15 = sigmadelta_sample ? 16'sh7fff : 16'sh0000;
wire signed [15:0] e_q15 = x_q15 - y_q15;
wire signed [31:0] prod_q30 = $signed(16'sh0b00) * $signed(e_q15); // factor should be 0b3b, used bit simplified here
wire signed [15:0] delta_q15 = prod_q30 >>> 15;
wire signed [15:0] y_next_q15 = y_q15 + delta_q15;
// Optional clamp to [0, 0x7FFF] (keeps GTKWave 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 clk_15 or negedge reset_n) begin
if (!reset_n) y_q15 <= 16'sd0000;
else y_q15 <= clamp01_q15(y_next_q15);
end
// ------------------------------
wire signed [15:0] lpfed_q15;
lpf_iir_q15 #(.K(7)) m_lpf (
.clk(clk_15),
.rst_n(reset_n),
.x_q15(y_next_q15),
.y_q15(lpfed_q15)
);
wire signed [15:0] decimated_q15;
decimate_by_r #(.R(375)) m_decimator (
.clk(clk_15),
.rst_n(reset_n),
.in_valid(1'b1),
.in_q15(lpfed_q15),
.out_valid(),
.out_q15(decimated_q15)
);
// decimated_q15 + out_valid @ 40KHz
endmodule
// --------------------------------------------------------------------
// Simple 1-pole IIR LPF, Q1.15 in/out, multiplier-less (alpha = 1/2^K)
// --------------------------------------------------------------------
module lpf_iir_q15 #(
parameter integer K = 10 // try 8..12; bigger = more smoothing
)(
input wire clk,
input wire rst_n,
input wire signed [15:0] x_q15, // Q1.15 input (e.g., 0..0x7FFF)
output reg signed [15:0] y_q15 // Q1.15 output
);
wire signed [15:0] e_q15 = x_q15 - y_q15;
wire signed [15:0] delta_q15 = e_q15 >>> K; // arithmetic shift
wire signed [15:0] y_next = 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 clk or negedge rst_n) begin
if (!rst_n) y_q15 <= 16'sd0;
else y_q15 <= clamp01(y_next);
end
endmodule
// -------------------------------------------------------------
// Decimate-by-R: passes through one sample every R input clocks
// No $clog2 used; set CNT_W wide enough for your R.
// -------------------------------------------------------------
module decimate_by_r #(
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 clk,
input wire rst_n,
input wire in_valid, // assert 1'b1 if always valid
input wire signed [15:0] in_q15, // Q1.15 sample at full rate
output reg out_valid, // 1-cycle pulse every R samples
output reg signed [15:0] out_q15 // Q1.15 sample at decimated rate
);
reg [CNT_W-1:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {CNT_W{1'b0}};
out_valid <= 1'b0;
out_q15 <= 16'sd0;
end else begin
out_valid <= 1'b0;
if (in_valid) begin
if (cnt == R-1) begin
cnt <= {CNT_W{1'b0}};
out_q15 <= in_q15;
out_valid <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
end
end
end
end
endmodule

View File

@@ -1,24 +0,0 @@
`timescale 1ns/1ps
module sigmadelta_sampler(
input wire clk,
input wire A,
input wire B,
output wire out
);
wire O;
reg out_r;
TLVDS_IBUF m_cmp(
.I(A),
.IB(B),
.O(O)
);
always @(posedge clk) begin
out_r = O;
end
assign out = out_r;
endmodule

View File

@@ -1,39 +0,0 @@
`timescale 1ns/1ps
module toplevel(
input wire clk,
input wire reset_n,
input wire button,
output wire led,
input wire adc1_A,
input wire adc1_B,
output wire adc1_O
);
reg led_v;
wire led_i;
wire clk_120;
wire clk_15;
gw_pllvr m_pll(
.clkout(clk_120),
.reset(!reset_n),
.clkin(clk)
);
gw_clkdiv8 m_clkdiv8(
.clkout(clk_15),
.hclkin(clk_120),
.resetn(reset_n)
);
always @(posedge clk_120 or negedge reset_n) begin
if (!reset_n) begin
led_v <= 1'b0;
end else begin
led_v <= led_i;
end
end
assign led = led_v;
endmodule

View File

@@ -1,12 +0,0 @@
[General]
file=gw_clkdiv8
ipc_version=4
module=gw_clkdiv8
target_device=gw1nsr4c-009
type=clock_clkdiv
version=1.0
[Config]
Calibration=false
Division_Factor=8
Language=0

View File

@@ -1,14 +0,0 @@
-series GW1NSR
-device GW1NSR-4C
-device_version
-package QFN48P
-part_number GW1NSR-LV4CQN48PC7/I6
-mod_name gw_clkdiv8
-file_name gw_clkdiv8
-path /data/joppe/projects/modem/IP/gowin_clkdiv/
-type CLKDIV
-file_type vlg
-division_factor 8
-calib false

View File

@@ -1,29 +0,0 @@
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: IP file
//Tool Version: V1.9.12
//Part Number: GW1NSR-LV4CQN48PC7/I6
//Device: GW1NSR-4C
//Created Time: Wed Oct 1 18:23:11 2025
module gw_clkdiv8 (clkout, hclkin, resetn);
output clkout;
input hclkin;
input resetn;
wire gw_gnd;
assign gw_gnd = 1'b0;
CLKDIV clkdiv_inst (
.CLKOUT(clkout),
.HCLKIN(hclkin),
.RESETN(resetn),
.CALIB(gw_gnd)
);
defparam clkdiv_inst.DIV_MODE = "8";
defparam clkdiv_inst.GSREN = "false";
endmodule //gw_clkdiv8

View File

@@ -1,18 +0,0 @@
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Template file for instantiation
//Tool Version: V1.9.12
//Part Number: GW1NSR-LV4CQN48PC7/I6
//Device: GW1NSR-4C
//Created Time: Wed Oct 1 18:23:11 2025
//Change the instance name and port connections to the signal names
//--------Copy here to design--------
gw_clkdiv8 your_instance_name(
.clkout(clkout), //output clkout
.hclkin(hclkin), //input hclkin
.resetn(resetn) //input resetn
);
//--------Copy end-------------------

View File

@@ -1,25 +0,0 @@
[General]
file=gw_pllvr
ipc_version=4
module=gw_pllvr
target_device=gw1nsr4c-009
type=clock_pllvr
version=1.0
[Config]
CKLOUTD3=false
CLKFB_SOURCE=0
CLKIN_FREQ=27
CLKOUTD=false
CLKOUTP=false
CLKOUT_BYPASS=false
CLKOUT_DIVIDE_DYN=true
CLKOUT_FREQ=120
CLKOUT_TOLERANCE=0
DYNAMIC=true
LANG=0
LOCK_EN=false
MODE_GENERAL=true
PLL_PWD=false
PLL_REGULATOR=false
RESET_PLL=true

View File

@@ -1,34 +0,0 @@
-series GW1NSR
-device GW1NSR-4C
-device_version
-package QFN48P
-part_number GW1NSR-LV4CQN48PC7/I6
-mod_name gw_pllvr
-file_name gw_pllvr
-path /data/joppe/projects/modem/IP/ge_pllvr/
-type PLL
-pllvr true
-file_type vlg
-dev_type GW1NSR-4C
-dyn_idiv_sel false
-idiv_sel 9
-dyn_fbdiv_sel false
-fbdiv_sel 40
-dyn_odiv_sel false
-odiv_sel 8
-dyn_da_en true
-rst_sig true
-rst_sig_p false
-pll_reg false
-fclkin 27
-clkfb_sel 0
-en_lock false
-clkout_bypass false
-clkout_ft_dir 1
-en_clkoutp false
-clkoutp_bypass false
-en_clkoutd false
-clkoutd_bypass false
-en_clkoutd3 false

View File

@@ -1,67 +0,0 @@
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: IP file
//Tool Version: V1.9.12
//Part Number: GW1NSR-LV4CQN48PC7/I6
//Device: GW1NSR-4C
//Created Time: Wed Oct 1 13:08:32 2025
module gw_pllvr (clkout, reset, clkin);
output clkout;
input reset;
input clkin;
wire lock_o;
wire clkoutp_o;
wire clkoutd_o;
wire clkoutd3_o;
wire gw_vcc;
wire gw_gnd;
assign gw_vcc = 1'b1;
assign gw_gnd = 1'b0;
PLLVR pllvr_inst (
.CLKOUT(clkout),
.LOCK(lock_o),
.CLKOUTP(clkoutp_o),
.CLKOUTD(clkoutd_o),
.CLKOUTD3(clkoutd3_o),
.RESET(reset),
.RESET_P(gw_gnd),
.CLKIN(clkin),
.CLKFB(gw_gnd),
.FBDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.IDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.ODSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.PSDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.DUTYDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.FDLY({gw_gnd,gw_gnd,gw_gnd,gw_gnd}),
.VREN(gw_vcc)
);
defparam pllvr_inst.FCLKIN = "27";
defparam pllvr_inst.DYN_IDIV_SEL = "false";
defparam pllvr_inst.IDIV_SEL = 8;
defparam pllvr_inst.DYN_FBDIV_SEL = "false";
defparam pllvr_inst.FBDIV_SEL = 39;
defparam pllvr_inst.DYN_ODIV_SEL = "false";
defparam pllvr_inst.ODIV_SEL = 8;
defparam pllvr_inst.PSDA_SEL = "0000";
defparam pllvr_inst.DYN_DA_EN = "true";
defparam pllvr_inst.DUTYDA_SEL = "1000";
defparam pllvr_inst.CLKOUT_FT_DIR = 1'b1;
defparam pllvr_inst.CLKOUTP_FT_DIR = 1'b1;
defparam pllvr_inst.CLKOUT_DLY_STEP = 0;
defparam pllvr_inst.CLKOUTP_DLY_STEP = 0;
defparam pllvr_inst.CLKFB_SEL = "internal";
defparam pllvr_inst.CLKOUT_BYPASS = "false";
defparam pllvr_inst.CLKOUTP_BYPASS = "false";
defparam pllvr_inst.CLKOUTD_BYPASS = "false";
defparam pllvr_inst.DYN_SDIV_SEL = 2;
defparam pllvr_inst.CLKOUTD_SRC = "CLKOUT";
defparam pllvr_inst.CLKOUTD3_SRC = "CLKOUT";
defparam pllvr_inst.DEVICE = "GW1NSR-4C";
endmodule //gw_pllvr

View File

@@ -1,18 +0,0 @@
//Copyright (C)2014-2025 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Template file for instantiation
//Tool Version: V1.9.12
//Part Number: GW1NSR-LV4CQN48PC7/I6
//Device: GW1NSR-4C
//Created Time: Wed Oct 1 13:08:32 2025
//Change the instance name and port connections to the signal names
//--------Copy here to design--------
gw_pllvr your_instance_name(
.clkout(clkout), //output clkout
.reset(reset), //input reset
.clkin(clkin) //input clkin
);
//--------Copy end-------------------

View File

@@ -1,9 +0,0 @@
`timescale 1ns/1ps
module glbl;
reg gsri = 0;
initial begin
#10 gsri = 1; // release reset after 100ns
end
GSR GSR (.GSRI(gsri));
endmodule

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +0,0 @@
`timescale 1ns/1ps
module sampling_tb;
reg clk_15;
reg clk_120;
reg reset_n;
sampling m_sampling(
.clk_15(clk_15),
.clk_120(clk_120),
.reset_n(reset_n)
);
initial begin
$dumpfile("sampling_tb.vcd");
$dumpvars (0, sampling_tb);
clk_15 <= 1'b0;
clk_120 <= 1'b0;
reset_n <= 1'b0;
#50 reset_n <= 1'b1;
#2000000
$finish;
end
always #(500/15) clk_15 = ~clk_15;
always #(500/120) clk_120 = ~clk_120;
endmodule

View File

@@ -1,106 +0,0 @@
`timescale 1ns/1ps
module sigmadelta_sampler(
input wire clk,
input wire A,
input wire B,
output wire out
);
// ===== Tunable parameters =====
// Sine source (A input / P)
parameter real F_HZ = 1.5e3; // 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 (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 A/B inputs
real v_rc; // RC node voltage (== vn)
real v_dac; // DAC output voltage from O
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 O;
reg sampler;
initial sampler <= 1'b0;
always @(posedge clk) begin
sampler <= O;
end
assign out = 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
O = 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 A
vp = VCM + AMP * $sin(two_pi * F_HZ * t_s);
// 4) Comparator decision (with optional hysteresis)
comp_update();
// 5) Output with propagation delay
O = q;
end
end
endmodule

View File

@@ -1,64 +0,0 @@
`timescale 1ns/1ps
module toplevel_tb;
reg clk;
reg reset_n;
reg button;
wire led;
toplevel m_toplevel(
.clk(clk),
.reset_n(reset_n),
.button(button),
.led(led),
.adc1_A(1'b0),
.adc1_B(1'b0)
);
initial begin
$dumpfile("toplevel_tb.vcd");
$dumpvars (0, toplevel_tb);
clk <= 1'b0;
reset_n <= 1'b0;
button <= 1'b0;
#50 reset_n <= 1'b1;
// Wait for clk 120 starts
@(posedge toplevel_tb.m_toplevel.clk_120);
#78 button <= 1'b1;
#185 button <= 1'b0;
#2000000
$finish;
end
always #18.5 clk = ~clk;
// Simulation stuff
// PLL quickstart
`ifndef TIMING_SIM
reg tb_pll_clk = 1'b0;
always #4.15 tb_pll_clk = ~tb_pll_clk;
initial begin
@(posedge reset_n);
repeat (8) @(posedge clk); // give the model time to measure CLKIN
force toplevel_tb.m_toplevel.m_pll.pllvr_inst.LOCK = 1'b1;
force toplevel_tb.m_toplevel.m_pll.pllvr_inst.CLKOUT = tb_pll_clk;
force toplevel_tb.m_toplevel.m_pll.pllvr_inst.CLKOUTP = tb_pll_clk;
force toplevel_tb.m_toplevel.m_pll.pllvr_inst.RESET = 1'b1;
end
`endif
// SDF annotation
`ifdef TIMING_SIM
initial begin
$sdf_annotate("impl/pnr/modem.sdf", m_toplevel, , , "MAXIMUM");
end
`endif
endmodule

View File

@@ -0,0 +1,27 @@
# Main clock input
NET "aclk" LOC = P126;
NET "aclk" TNM_NET = "SYS_CLK_PIN";
TIMESPEC TS_SYS_CLK_PIN = PERIOD "SYS_CLK_PIN" 10 ns HIGH 50 %;
# Boards button row
NET "aresetn" LOC = P120;
NET "aresetn" IOSTANDARD = LVCMOS33;
NET "aresetn" PULLUP;
NET "led_green" LOC = P29;
NET "led_green" IOSTANDARD = LVCMOS33;
NET "led_red" LOC = P26;
NET "led_red" IOSTANDARD = LVCMOS33;
NET "r2r[0]" LOC = P131;
NET "r2r[1]" LOC = P133;
NET "r2r[2]" LOC = P137;
NET "r2r[3]" LOC = P139;
NET "r2r[4]" LOC = P141;
NET "r2r[5]" LOC = P1;
NET "r2r[0]" IOSTANDARD = LVCMOS33;
NET "r2r[1]" IOSTANDARD = LVCMOS33;
NET "r2r[2]" IOSTANDARD = LVCMOS33;
NET "r2r[3]" IOSTANDARD = LVCMOS33;
NET "r2r[4]" IOSTANDARD = LVCMOS33;
NET "r2r[5]" IOSTANDARD = LVCMOS33;

View File

@@ -1,15 +0,0 @@
<?xml version="1" encoding="UTF-8"?>
<!DOCTYPE gowin-fpga-project>
<Project>
<Template>FPGA</Template>
<Version>5</Version>
<Device name="GW1NSR-4C" pn="GW1NSR-LV4CQN48PC7/I6">gw1nsr4c-009</Device>
<FileList>
<File path="HW/sigmadelta_sampler.v" type="file.verilog" enable="1"/>
<File path="HW/toplevel.v" type="file.verilog" enable="1"/>
<File path="IP/gw_clkdiv8/gw_clkdiv8.v" type="file.verilog" enable="1"/>
<File path="IP/gw_pllvr/gw_pllvr.v" type="file.verilog" enable="1"/>
<File path="CON/io.cst" type="file.cst" enable="1"/>
<File path="CON/timing.sdc" type="file.sdc" enable="1"/>
</FileList>
</Project>

View File

@@ -1,27 +0,0 @@
<?xml version="1" encoding="UTF-8"?>
<!DOCTYPE ProjectUserData>
<UserConfig>
<Version>1.0</Version>
<FlowState>
<Process ID="Synthesis" State="2"/>
<Process ID="Pnr" State="2"/>
<Process ID="Gao" State="2"/>
<Process ID="Rtl_Gao" State="2"/>
<Process ID="Gvio" State="0"/>
<Process ID="Place" State="2"/>
</FlowState>
<ResultFileList>
<ResultFile ResultFileType="RES.netlist" ResultFilePath="impl/gwsynthesis/modem.vg"/>
<ResultFile ResultFileType="RES.pnr.bitstream" ResultFilePath="impl/pnr/modem.fs"/>
<ResultFile ResultFileType="RES.pnr.pin.rpt" ResultFilePath="impl/pnr/modem.pin.html"/>
<ResultFile ResultFileType="RES.pnr.posp.bin" ResultFilePath="impl/pnr/modem.db"/>
<ResultFile ResultFileType="RES.pnr.pwr.rpt" ResultFilePath="impl/pnr/modem.power.html"/>
<ResultFile ResultFileType="RES.pnr.report" ResultFilePath="impl/pnr/modem.rpt.html"/>
<ResultFile ResultFileType="RES.pnr.timing.paths" ResultFilePath="impl/pnr/modem.timing_paths"/>
<ResultFile ResultFileType="RES.pnr.timing.rpt" ResultFilePath="impl/pnr/modem.tr.html"/>
<ResultFile ResultFileType="RES.syn.report" ResultFilePath="impl/gwsynthesis/modem_syn.rpt.html"/>
<ResultFile ResultFileType="RES.syn.resource" ResultFilePath="impl/gwsynthesis/modem_syn_rsc.xml"/>
</ResultFileList>
<Ui>000000ff00000001fd0000000200000000000001000000029ffc0200000001fc000000360000029f0000000000fffffffaffffffff0200000003fb00000030004600700067006100500072006f006a006500630074002e00500061006e0065006c002e00440065007300690067006e0100000000ffffffff0000000000000000fb00000032004600700067006100500072006f006a006500630074002e00500061006e0065006c002e00500072006f00630065007300730100000000ffffffff0000000000000000fb00000036004600700067006100500072006f006a006500630074002e00500061006e0065006c002e0048006900650072006100720063006800790100000000ffffffff000000000000000000000003000004f60000010afc0100000001fc00000000000004f6000000a100fffffffa000000000100000002fb00000032004600700067006100500072006f006a006500630074002e00500061006e0065006c002e00470065006e006500720061006c0100000000ffffffff0000004c00fffffffb0000002e004600700067006100500072006f006a006500630074002e00500061006e0065006c002e004900730073007500650100000000ffffffff000000a100ffffff000003f00000029f00000004000000040000000800000008fc000000010000000200000004000000220043006f00720065002e0054006f006f006c006200610072002e00460069006c00650100000000ffffffff0000000000000000000000220043006f00720065002e0054006f006f006c006200610072002e004500640069007401000000a8ffffffff0000000000000000000000240043006f00720065002e0054006f006f006c006200610072002e0054006f006f006c00730100000174ffffffff0000000000000000000000280043006f00720065002e0054006f006f006c006200610072002e00500072006f0063006500730073010000024fffffffff0000000000000000</Ui>
<FpUi>312e30313131000000ff00000000fd00000002000000000000010000000193fc0200000001fc00000039000001930000008401000018fa000000010200000002fb0000001c0044006f0063006b00650072002e00530075006d006d0061007200790100000000ffffffff0000006b00fffffffb0000001c0044006f0063006b00650072002e004e00650074006c0069007300740100000000ffffffff0000005d00ffffff0000000300000500000000fefc0100000001fc00000000000005000000007b00fffffffa00000001010000000bfb0000001c0044006f0063006b00650072002e004d0065007300730061006700650100000000ffffffff0000005c00fffffffb0000002c0044006f0063006b00650072002e0049002f004f002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00fffffffb000000380044006f0063006b00650072002e005000720069006d00690074006900760065002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00fffffffb000000300044006f0063006b00650072002e00470072006f00750070002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00fffffffb000000360044006f0063006b00650072002e005200650073006f0075007200630065002e005200650073006500720076006100740069006f006e0100000000ffffffff0000004a00fffffffb000000380044006f0063006b00650072002e0043006c006f0063006b002e004e00650074002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00fffffffb000000420044006f0063006b00650072002e00470043004c004b002e005000720069006d00690074006900760065002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00fffffffb000000420044006f0063006b00650072002e00480043004c004b002e005000720069006d00690074006900760065002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00fffffffb000000440044006f0063006b00650072002e00470043004c004b0032002e005000720069006d00690074006900760065002e0043006f006e00730074007200610069006e007400730000000000ffffffff0000004a00fffffffb000000460044006f0063006b00650072002e00480043004c004b00350041002e005000720069006d00690074006900760065002e0043006f006e00730074007200610069006e007400730000000000ffffffff0000004a00fffffffb0000002e0044006f0063006b00650072002e0056007200650066002e0043006f006e00730074007200610069006e007400730100000000ffffffff0000004a00ffffff000003fa0000019300000004000000040000000800000008fc000000010000000200000001000000180054006f006f006c004200610072002e00460069006c00650100000000ffffffff0000000000000000</FpUi>
</UserConfig>

45
project.cfg Normal file
View File

@@ -0,0 +1,45 @@
[project]
name = modem
version = 0.1
out_dir = out
build_dir = build
[server]
hostname = localhost
port = 2020
privkey = /home/joppe/.ssh/id_rsa
pubkey = /home/joppe/.ssh/id_rsa.pub
[target.synth]
toolchain = ISE
# Toolchain settings
family = spartan6
device = xc6slx9
package = tqg144
speedgrade = -2
toplevel = top_generic
xst_opts = -vlgincdir rtl
#ngdbuild_opts =
#map_opts =
#par_opts =
#netgen_opts =
#bitgen_opts =
#trce_opts =
# Files
#files_vhdl =
files_verilog = rtl/toplevel/top_generic.v
rtl/core/nco_q15.v
files_con = boards/mimas_v1/constraints.ucf
files_other = rtl/core/nco_q15_funcs.vh
[target.sim]
toolchain = iverilog
runtime = all
toplevel = tb_nco_q15
ivl_opts = -Irtl
#vvp_opts =
# Files
#files_sysverilog =
files_verilog = sim/tb/tb_nco_q15.v
rtl/core/nco_q15.v
files_other = rtl/core/nco_q15_funcs.vh

176
rtl/core/nco_q15.v Normal file
View 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
View 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

View 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

21
scripts/gen_sin_lut.py Normal file
View File

@@ -0,0 +1,21 @@
import math
# for i in range(0, 64, 4):
# for j in range(4):
# val = math.sin((i+j) * math.pi/256)
# val = int(val*256*1.4)
# print(f"6'd{i+j}: dout=8'd{val}; ", end='')
# print('')
N = 64 # quarter-wave samples
AMP = 255 # 8-bit max
for i in range(0, N, 4):
line = []
for j in range(4):
k = i + j
theta = k * math.pi / (2*N) # 0 .. (π/2)*(N-1)/N ; never hits 1.0
val = int(math.floor(AMP * math.sin(theta) + 0.5)) # rounded, stays <=254
if val > 255: val = 255
line.append(f"6'd{k}: dout = 8'd{val};")
print(' '.join(line))

6
scripts/planahead.tcl Normal file
View File

@@ -0,0 +1,6 @@
create_project project_1 /tmp/project_1 -part xc6slx9tqg144-2
set_property design_mode GateLvl [current_fileset]
add_files -norecurse /data/joppe/projects/modem/out/synth/synth.ngc
import_files -force -norecurse
import_files -fileset constrs_1 -force -norecurse /data/joppe/projects/modem/boards/mimas_v1/constraints.ucf
import_as_run -run impl_1 -twx /data/joppe/projects/modem/out/synth/timing.twx /data/joppe/projects/modem/out/synth/synth.ncd

View File

@@ -1 +0,0 @@
openFPGALoader impl/pnr/modem.fs

View File

@@ -1 +0,0 @@
gowin_sh scripts/run_all.tcl

View File

@@ -1,2 +0,0 @@
open_project modem.gprj
run all

View File

@@ -1,13 +0,0 @@
cvc \
+define+TIMING_SIM \
+acc \
+sdf_noerrors +sdf_nowarns \
+show_canceled_e +suppress_warns+653+3102 \
-v SIM/prim_tsim.v \
impl/pnr/modem.vo \
SIM/toplevel_tb.v \
SIM/globals.v \
+sdf_annotate+impl/pnr/modem.sdf+toplevel_tb.m_toplevel \
+maxdelays \
+librescan
./cvcsim

View File

@@ -1,23 +0,0 @@
# iverilog -o toplevel_tb.vp \
# /opt/packages/gowin/IDE/simlib/gw1n/prim_sim.v \
# HW/toplevel.v \
# SIM/toplevel_tb.v
# vvp toplevel_tb.vp
cvc +acc \
+show_canceled_e +suppress_warns+653+3102 \
+define+FAST_PLL_SIM \
-v SIM/prim_tsim.v \
\
IP/gw_pllvr/gw_pllvr.v \
IP/gw_clkdiv8/gw_clkdiv8.v \
HW/toplevel.v \
HW/sampling.v \
\
SIM/sigmadelta_sampler.v \
\
SIM/sampling_tb.v \
SIM/globals.v \
+librescan
./cvcsim
# SIM/toplevel_tb.v \

50
sim/tb/tb_nco_q15.v Normal file
View File

@@ -0,0 +1,50 @@
`timescale 1ns/1ps
module tb_nco_q15();
`include "core/nco_q15_funcs.vh"
// Clock and reset generation
reg clk;
reg resetn;
initial clk <= 1'b0;
initial resetn <= 1'b0;
always #4.17 clk <= !clk;
initial #40 resetn <= 1'b1;
// Default run
initial begin
$dumpfile("out.vcd");
$dumpvars;
#5_000_000
$finish;
end;
reg [31:0] ftw_in;
reg ftw_we;
wire [15:0] sin_q15;
wire [15:0] cos_q15;
wire out_en;
nco_q15 #(.PHASE_BITS(16)) nco (
.clk (clk),
.rst_n (resetn),
.ftw_in (ftw_in),
.ftw_we (ftw_we),
.sin_q15(sin_q15),
.cos_q15(cos_q15),
.clk_en (out_en)
);
initial begin
ftw_in = 32'h0;
ftw_we = 1'b0;
#100
@(posedge clk);
ftw_in = ftw_from_hz(1000, 16, 40000);
ftw_we = 1'b1;
@(posedge clk);
ftw_we = 1'b0;
end;
endmodule