NCO (Numerically Controlled Oscillator)
High-precision waveform generator using phase accumulator technique. Supports sine, cosine, ramp, triangle, square, and sawtooth waveforms.
Introduction
The NCO (Numerically Controlled Oscillator) block generates high-precision periodic waveforms using the phase accumulator technique. This is a fundamental building block for Direct Digital Synthesis (DDS) applications.
The NCO supports multiple waveform types (sine, cosine, ramp, triangle, square, sawtooth) with configurable output resolution, frequency precision, and phase offset.
Pin Description
Phase offset (unsigned 16-bit). Adds a constant phase shift to the output waveform.
- 0x0000 = 0°
- 0x4000 = 90°
- 0x8000 = 180°
- 0xC000 = 270°
Duty cycle control (unsigned 16-bit). Used for TRIANGLE and SQUARE waveforms.
- 0x0000 = defaults to 50%
- 0x8000 = 50%
- 0xFFFF = ~100%
Properties
Number of bits of the output signal
Number of bits of the output signal. Range: 8 to 32, default 16.Default: 16
Range: 8 – 32
Width of the internal phase accumulator register. Does NOT change the frequency resolution: the increment is always a multiple of 2^(bits-16), so the frequency grid is CLK/65536 for every setting and a wider accumulator only costs area. 32 is the cheapest fully functional choice.
Width of the internal phase accumulator register. It does NOT change the frequency resolution. The phase increment is always a multiple of 2^(bits-16), so the accumulator is effectively 16-bit and the frequency grid is f_clk/65536 for every setting; a wider accumulator only costs more area. Verified by regression: the same PERIOD stream gives a bit-identical output at 32 and at 64 bits. It does affect the LATENCY of the TRIANGLE waveform, and only that one: the slope divider is as wide as the accumulator plus 16 bits, so TRIANGLE costs 72 clock cycles at 32 bits and 105 at 64. Available values: 32, 40, 48, 56, 64, default 48 (32 is the cheapest fully functional choice).Default: 48
Options: 32 40 48 56 64
Select the type of waveform to generate
Type of waveform to generate. Available values: SINE, COSINE, RAMP, TRIANGLE, SQUARE, SAWTOOTH, default SINE.Default: SINE
Options: SINE COSINE RAMP TRIANGLE SQUARE SAWTOOTH
Select signed or unsigned output
Output signal format. Available values: UNSIGNED, SIGNED, default SIGNED.Default: SIGNED
Options: UNSIGNED SIGNED
Scaling of the PERIOD input word (FIXED40.24 also widens the pin to 64 bit). The fractional bits are DISCARDED, not rounded: these formats give no sub-sample period resolution, they only let you connect a period already carried in a fixed-point word.
Scaling of the PERIOD input word (and, for FIXED40.24, its width: 64 bits instead of 32). The fractional bits are discarded, not rounded, so none of these formats gives sub-sample period resolution: they only let the block be fed a period that is already carried in a rescaled fixed-point word. Available values: UINT32, FIXED16.16, FIXED24.8, FIXED40.24, default UINT32.Default: UINT32
Options: UINT32 FIXED16.16 FIXED24.8 FIXED40.24
Sine/Cosine lookup table size (larger = better resolution but more BRAM)
Sine/Cosine lookup table size. Larger values provide better waveform quality but use more BRAM. Available values: 256 (1.41° res), 512 (0.70° res), 1024 (0.35° res), 2048 (0.18° res), 4096 (0.09° res), 8192 (0.04° res), default 1024 (0.35° res).Default: 1024 (0.35° res)
Options: 256 (1.41° res) 512 (0.70° res) 1024 (0.35° res) 2048 (0.18° res) 4096 (0.09° res) 8192 (0.04° res)
Usage
Phase Accumulator Principle
The NCO uses a phase accumulator to generate periodic waveforms. The phase accumulator is an N-bit register that increments by a fixed amount (phase increment) on each clock cycle:
$$ \phi[n] = \phi[n-1] + \Delta\phi \mod 2^N $$
where:
- $\phi[n]$ is the current phase
- $\Delta\phi$ is the phase increment
- $N$ is the accumulator bit width
The phase accumulator naturally wraps around, producing a sawtooth-like phase ramp that drives the waveform generation.
Frequency Control
PERIOD is the number of clock cycles in one output period, so the nominal output frequency is
$$ f_{out} \simeq \frac{f_{clk}}{\text{PERIOD}} $$
The block does not use PERIOD directly. It converts it into a phase increment on a fixed 16-bit grid, one turn being 65536 units:
$$ \Delta\phi = \left\lfloor \frac{65536}{\text{PERIOD}} \right\rfloor \cdot 2^{N-16} $$
where $N$ is the accumulator bit width (32-64 bits). The frequency actually realised is therefore
$$ f_{out} = f_{clk} \cdot \frac{\left\lfloor 65536/\text{PERIOD} \right\rfloor}{65536} $$
which equals $f_{clk}/\text{PERIOD}$ exactly only when PERIOD divides 65536 (2, 4, 8, 16, 32, …, 1024, …, 65536 - PERIOD = 1 is the one exception, see below, because an increment of a whole turn wraps to zero). Example measured on the block: PERIOD = 20 gives an increment of $\lfloor 65536/20 \rfloor = 3276$ instead of 3276.8, i.e. a realised period of $65536/3276 = 20.005$ samples - right to 0.025 % but drifting by one sample every 4000.
Frequency Resolution
The frequency grid does NOT depend on “Accumulator bits”.
Every increment is a multiple of $2^{N-16}$, so the low $N-16$ bits of the accumulator are permanently zero: whatever “Accumulator bits” is set to, the accumulator behaves as a 16-bit one and the frequency grid is
$$ \Delta f = \frac{f_{clk}}{65536} $$
| Sample rate | Frequency step (any accumulator width) |
|---|---|
| 100 MHz | 1.526 kHz |
| 250 MHz | 3.815 kHz |
| 1 GHz | 15.26 kHz |
This is verified by regression, not asserted: the same PERIOD stream produces a bit-identical output with “Accumulator bits” = 32 and with 64 (ci-test cases nco::freq_grid_acc32_sine / freq_grid_acc64_sine and the matching RAMP pair). “Accumulator bits” changes the width of the accumulator register only, i.e. the area the block costs; it buys no frequency resolution. 32 is therefore the cheapest fully functional choice – and for TRIANGLE it is also the fastest, because that waveform’s slope divider is as wide as the accumulator plus 16 bits (see Timing).
This block has no frequency word and no way to place an output frequency off the $f_{clk}/65536$ grid: if a finer grid is required, change the sample rate that feeds the block.
PERIOD = 0 and PERIOD = 1 stop the oscillator
PERIOD = 0 is illegal and the block substitutes 1. PERIOD = 1 means “one sample per output period”, i.e. a whole turn per sample: the increment is $\lfloor 65536/1 \rfloor = 65536$ units, which is exactly one turn and therefore wraps to zero. The phase stops advancing and the output holds the last value it had.
Expect a flat DC line for PERIOD = 0 and PERIOD = 1, not the fastest possible oscillation. The fastest usable setting is PERIOD = 2 (two samples per period, i.e. Nyquist).
Period Format
The PERIOD input can be written in several rescaled formats:
| Format | Total Bits | Written word | Integer period used |
|---|---|---|---|
| UINT32 | 32 | period | period |
| FIXED16.16 | 32 | period × 65536 | word » 16 |
| FIXED24.8 | 32 | period × 256 | word » 8 |
| FIXED40.24 | 64 | period × 16777216 | word » 24 |
The fractional bits are DISCARDED, not rounded. The core computes
period = PERIOD >> frac_bitsand uses that integer alone, so the fixed-point formats carry no sub-sample period resolution. FIXED16.16 / FIXED24.8 / FIXED40.24 behave exactly like UINT32 (UINT64 for FIXED40.24) fed with the rescaled word: they are a convenience for connecting a period that another part of the design already keeps in a fixed-point word, and nothing else. A written period of 20.8 samples is used as 20.
Sub-sample frequency control is not available on this block in any period format: see Frequency Resolution above for the grid that actually applies.
Waveform Types
The NCO can generate the following waveforms:
| Waveform | Description |
|---|---|
| SINE | Sinusoidal wave using lookup table |
| COSINE | Cosine wave (sine with 90° phase shift) |
| RAMP | Linear ramp from minimum to maximum |
| TRIANGLE | Triangular wave, rise/fall split set by DUTY |
| SQUARE | Square wave with adjustable duty cycle |
| SAWTOOTH | Inverted ramp from maximum to minimum |
The SYMMETRY input is not read by any of them - see the SYMMETRY pin below.
Sine/Cosine Generation
For SINE and COSINE waveforms, the NCO uses a lookup table (LUT) containing pre-computed sine values. The phase accumulator’s upper bits index into the LUT.
| LUT Size | Angular Resolution |
|---|---|
| 256 | 1.41° |
| 512 | 0.70° |
| 1024 | 0.35° |
| 2048 | 0.18° |
| 4096 | 0.09° |
| 8192 | 0.04° |
Larger LUTs provide better waveform quality (lower spurious content) but consume more BRAM.
Duty Cycle Control
For TRIANGLE and SQUARE waveforms, the DUTY input controls the symmetry:
- SQUARE: DUTY determines the HIGH time percentage
- TRIANGLE: DUTY determines the rising edge percentage
The DUTY input is a 16-bit unsigned value where:
- 0x0000 = 0% (falls back to 50%)
- 0x8000 = 50%
- 0xFFFF = 100%
There is no second symmetry control: the SYMMETRY pin is accepted and ignored.
Phase Offset
The PHASE input adds a constant phase offset to the output:
$$ \phi_{total} = \phi_{acc} + \phi_{offset} $$
The PHASE input is a 16-bit unsigned value representing the phase in the range [0, 2π):
- 0x0000 = 0°
- 0x4000 = 90°
- 0x8000 = 180°
- 0xC000 = 270°
Output Format
The output can be configured as:
- Signed: Output ranges from $-(2^{N-1})$ to $(2^{N-1}-1)$
- Unsigned: Output ranges from 0 to $(2^N-1)$
where N is the output bit width (8-32 bits).
For SINE and COSINE the table is 16 bit whatever N is, so the amplitude is rescaled, never re-tabulated: N above 16 shifts the table word up (the low N-16 bits are always zero, there is no interpolation) and N below 16 shifts it down, keeping the sign. The shift down is arithmetic, i.e. it rounds towards minus infinity, so with N = 12 the peak is 32767 » 4 = +2047 while the trough is -2048: the negative half of the wave sits exactly one count below the mirror of the positive half. The phase-mapped waveforms (RAMP, TRIANGLE, SQUARE, SAWTOOTH) take the top N bits of their phase word instead, so for them a narrow output is only a coarser quantisation.
Timing
The block is fully pipelined: one new output sample on every clock cycle (II = 1). The input-to-output latency is not a small constant: it is set by the HLS schedule of the pipelined 65536/PERIOD divider, and for TRIANGLE by a second divider on top of it.
Measured on the generated RTL (vitis_hls 2020.2, part xc7a35t, 10 ns target clock, OUT 16 bit, 1024-entry LUT) by stepping PHASE by half a turn and counting the clocks to the first different output word:
| Waveform | Latency (clock cycles) |
|---|---|
| SINE, COSINE | 22 |
| RAMP, SQUARE, SAWTOOTH | 21 |
| TRIANGLE | 72 / 81 / 89 / 97 / 105 for “Accumulator bits” 32 / 40 / 48 / 56 / 64 |
The period format and the output width change none of them. TRIANGLE is the one waveform whose latency grows with “Accumulator bits”: its slope divider is as wide as the accumulator plus 16 bits, so it costs about 8 more pipeline stages for every 8 accumulator bits. Picking 32 instead of the default 48 therefore saves 17 clock cycles of latency and the area of a 16-bit-wider divider, with no effect whatsoever on the waveform (the extra accumulator bits are always zero). For every other waveform the accumulator width is free.
Treat these as the numbers for the stated conditions: the exact depth is chosen by HLS from the target clock period and the FPGA part, so a faster clock or a different device can shift them by a cycle or two. The symbol badge declares the SINE/COSINE value for a scalar NCO; the TM twin is one cycle longer because it publishes its output through an extra register.
Latency only delays the waveform in time; because the oscillator is free-running it does not affect the frequency, and a constant delay on a periodic signal is indistinguishable from a phase offset. It matters only when the NCO output has to stay aligned with another path in the design.
Resources & Timing
-
Latency: 22 clock cycles for SINE/COSINE, 21 for RAMP/SQUARE/SAWTOOTH, 72 to 105 for TRIANGLE depending on the accumulator width (72 at 32 bits, 89 at the default 48, 105 at 64) – measured on the generated RTL, vitis_hls 2020.2, part xc7a35t, 10 ns clock; set by the HLS schedule of the 65536/PERIOD divider plus, for TRIANGLE, the accumulator-wide slope divider, so clock- and part-dependent
-
Throughput: 1 sample per clock cycle (II=1)
HLS-based implementation. Resource usage varies with LUT size and accumulator width; the accumulator width changes area only, never the frequency resolution.