Skip to content

Instantly share code, notes, and snippets.

@pavel-kirienko
Created August 6, 2025 12:33
Show Gist options
  • Save pavel-kirienko/7ccd1f7079f5c90dfdd03a29b36e10f5 to your computer and use it in GitHub Desktop.
Save pavel-kirienko/7ccd1f7079f5c90dfdd03a29b36e10f5 to your computer and use it in GitHub Desktop.
A simple numerically controlled oscillator (NCO) in Verilog
/// A numerically controlled oscillator (NCO) that outputs a sawtooth wave, whose frequency is a function of
/// clk rate and frequency_control_word, and the amplitude spans the range [0, 2**OUTPUT_WIDTH).
/// The output frequency is defined as:
///
/// f_out = (f_clk * frequency_control_word) / (2**PHASE_ACCUMULATOR_WIDTH)
///
/// Solve for frequency_control_word:
///
/// frequency_control_word = ((2**PHASE_ACCUMULATOR_WIDTH) * f_out) / f_clk
///
/// The phase of the output signal can be adjusted using phase_control_word, which is a value in the range
/// [0, 2**PHASE_ACCUMULATOR_WIDTH) that maps to [0, 2 pi).
///
/// Both of the control words can be changed arbitrarily; changes take effect in the next cycle.
module nco #(
parameter OUTPUT_WIDTH = 8, ///< Larger values reduce phase noise.
parameter PHASE_ACCUMULATOR_WIDTH = 64 ///< Larger values reduce frequency error.
)
(
input wire clk,
input wire reset,
input wire [PHASE_ACCUMULATOR_WIDTH-1:0] frequency_control_word,
input wire [PHASE_ACCUMULATOR_WIDTH-1:0] phase_control_word,
output wire [OUTPUT_WIDTH-1:0] out
);
reg [PHASE_ACCUMULATOR_WIDTH-1:0] acc = 0;
wire [PHASE_ACCUMULATOR_WIDTH-1:0] acc_phased = acc + phase_control_word;
assign out = acc_phased[PHASE_ACCUMULATOR_WIDTH-1:PHASE_ACCUMULATOR_WIDTH-OUTPUT_WIDTH];
always @(posedge clk or posedge reset) begin
if (reset) begin
acc <= 0;
end else begin
acc <= acc + frequency_control_word;
end
end
endmodule
/// iverilog -Wall -Wno-timescale -y. nco_tb.v && vvp a.out
/// Then open nco.vcd in GTK Wave or whatever.
`timescale 1ns/1ns
module nco_tb;
reg reset = 0;
initial begin
# 1 reset = 1;
# 2 reset = 0;
# 100 pcw = 32;
# 100 fcw = 4;
# 100 pcw = 16;
# 100 fcw = 8;
# 100 $finish;
end
reg clk = 0;
always begin
#1 clk = !clk;
end
reg [5:0] fcw = 1;
reg [5:0] pcw = 0;
wire [1:0] out;
nco #(2, 6) nco_it (clk, reset, fcw, pcw, out);
initial begin
$dumpfile("nco.vcd");
$dumpvars();
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment