NCO (TM) - Time-Multiplexed
Time-multiplexed version of the NCO for parallel waveform generation. Generates multiple independent waveforms using shared hardware resources.
Introduction
The NCO (TM) block is the time-multiplexed version of the Numerically Controlled Oscillator. It generates multiple independent waveforms in parallel, with each channel having its own frequency, phase, and duty cycle settings.
This block is designed for applications requiring multiple synchronized or independent oscillators, such as multi-channel signal generation, beamforming, or polyphonic synthesis.
Pin Description
Properties
Usage
Time-Multiplexed Architecture
The NCO_TM processes all channels in parallel using fully unrolled loops. Each channel has:
- Independent phase accumulator
- Independent frequency (PERIOD) setting
- Independent phase offset
- Independent duty cycle control
All channels share the same waveform type, output resolution, and LUT.
Channel Configuration
The TM factor determines the number of parallel channels:
| TM Factor | Channels | PERIOD Width | PHASE Width | OUT Width |
|---|---|---|---|---|
| 2 | 2 | 64/128 bits | 32 bits | 2×OUT_BITS |
| 4 | 4 | 128/256 bits | 64 bits | 4×OUT_BITS |
| 8 | 8 | 256/512 bits | 128 bits | 8×OUT_BITS |
| 16 | 16 | 512/1024 bits | 256 bits | 16×OUT_BITS |
| 32 | 32 | 1024/2048 bits | 512 bits | 32×OUT_BITS |
The total bus widths are multiplied by the TM factor.
Input Signal Packing
Input signals are packed with channel 0 in the least significant bits:
PERIOD = [Ch_N-1] [Ch_N-2] ... [Ch_1] [Ch_0]
MSB LSB
For example, with TM=4 and PERIOD_BITS=32:
- Bits [31:0] = Channel 0 period
- Bits [63:32] = Channel 1 period
- Bits [95:64] = Channel 2 period
- Bits [127:96] = Channel 3 period
Output Signal Packing
Output signals follow the same packing convention:
OUT = [Ch_N-1] [Ch_N-2] ... [Ch_1] [Ch_0]
MSB LSB
For example, with TM=4 and OUT_BITS=16:
- Bits [15:0] = Channel 0 output
- Bits [31:16] = Channel 1 output
- Bits [47:32] = Channel 2 output
- Bits [63:48] = Channel 3 output
Per-Channel Phase Accumulators
Each channel maintains its own phase accumulator that increments independently:
$$ \phi_i[n] = \phi_i[n-1] + \Delta\phi_i \mod 2^N $$
where $i$ is the channel index. This allows each channel to generate a completely independent frequency.
Frequency Control
Each channel’s PERIOD slice is the number of clock cycles in one output period of that channel, so its nominal frequency is
$$ f_{out,i} \simeq \frac{f_{clk}}{\text{PERIOD}_i} $$
All channels operate on the same clock, but each has independent frequency control.
Each lane converts its PERIOD into a phase increment on a fixed 16-bit grid, one turn being 65536 units:
$$ \Delta\phi_i = \left\lfloor \frac{65536}{\text{PERIOD}_i} \right\rfloor \cdot 2^{N-16} $$
so the frequency actually realised on lane $i$ is
$$ f_{out,i} = f_{clk} \cdot \frac{\left\lfloor 65536/\text{PERIOD}_i \right\rfloor}{65536} $$
and it equals $f_{clk}/\text{PERIOD}_i$ exactly only when PERIOD$_i$ 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 each accumulator are permanently zero: whatever “Accumulator bits” is set to, each accumulator behaves as a 16-bit one and every lane’s frequency grid is
$$ \Delta f = \frac{f_{clk}}{65536} $$
i.e. 1.526 kHz at a 100 MHz sample rate, 3.815 kHz at 250 MHz - for every accumulator width. This is verified by regression, not asserted: the same PERIOD stream produces a bit-identical output word with “Accumulator bits” = 32 and with 64 (ci-test cases nco_tm::tm2_freq_grid_acc32_sine / tm2_freq_grid_acc64_sine and the matching RAMP pair). “Accumulator bits” changes the width of the TM accumulator registers only, i.e. the area the block costs (TM × bits of register); it buys no frequency resolution, so 32 is the cheapest fully functional choice – and for TRIANGLE it is also the fastest, because that waveform’s per-lane slope divider is as wide as the accumulator plus 16 bits (see Timing).
PERIOD = 0 and PERIOD = 1 freeze a lane
PERIOD = 0 is illegal and the block substitutes 1 for that lane. 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. That lane’s phase stops advancing and its slice of the output word holds the last value it had, while the other lanes keep running.
Expect a frozen lane 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 same scaling choice applies to every lane:
| Format | Bits per lane | Written word | Integer period used |
|---|---|---|---|
| UINT32 | 32 | period | word |
| 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. Each lane computes
period = PERIOD_i >> frac_bitsand uses that integer alone, so the fixed-point formats carry no sub-sample period resolution: they behave exactly like UINT32 (UINT64 for FIXED40.24) fed with the rescaled word, and exist only so that a period already carried in a fixed-point word can be connected directly. A written period of 20.8 samples is used as 20.
Waveform Types
All channels generate the same waveform type (configured at design time):
| Waveform | Description |
|---|---|
| SINE | Sinusoidal wave using shared LUT |
| COSINE | Cosine wave (sine with 90° phase shift) |
| RAMP | Linear ramp from minimum to maximum |
| TRIANGLE | Triangular wave, rise/fall split set by that channel’s DUTY |
| SQUARE | Square wave with per-channel duty cycle |
| SAWTOOTH | Inverted ramp from maximum to minimum |
The SYMMETRY input is not read by any of them - see the SYMMETRY pin below.
Per-Channel Parameters
Each channel can have independent:
- PERIOD: Waveform frequency
- PHASE: Phase offset
- DUTY: Duty cycle (for TRIANGLE/SQUARE)
SYMMETRY is sliced per channel too, but no lane reads it.
Timing
The block is fully pipelined: one new output word - all TM channels at once - on every clock cycle (II = 1), so the aggregate throughput is TM × f_clk samples per second. 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, 16-bit lanes, 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 | 23 |
| RAMP, SQUARE, SAWTOOTH | 22 |
| TRIANGLE | 73 / 82 / 90 / 98 / 106 for “Accumulator bits” 32 / 40 / 48 / 56 / 64 |
Every figure is exactly one cycle more than the scalar NCO, because this core publishes its output word through an extra register (tNCO_OUT) that the scalar one does not have. The TM factor, the period format and the lane width change none of them. TRIANGLE is the one waveform whose latency grows with “Accumulator bits”: its per-lane 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 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.
All channels share the same latency, so the phase relationships between lanes are exactly what the PHASE slices ask for - the delay is common to all of them.
Resource Usage
The TM version uses more resources than the single-channel NCO:
- Phase accumulators: TM × accumulator_bits
- LUT: Shared (1 copy)
- DSP blocks: Proportional to TM factor
For minimal resource usage with many channels, consider using a lower TM factor with external time-multiplexing.
Synchronization
All channels are processed synchronously on the same clock edge. This ensures:
- Zero phase drift between channels
- Deterministic timing relationships
- Suitable for coherent multi-channel applications
Resources & Timing
-
Latency: 23 clock cycles for SINE/COSINE, 22 for RAMP/SQUARE/SAWTOOTH, 73 to 106 for TRIANGLE depending on the accumulator width (73 at 32 bits, 90 at the default 48, 106 at 64) – measured on the generated RTL, vitis_hls 2020.2, part xc7a35t, 10 ns clock; one cycle more than the scalar NCO because of the extra output register, and clock- and part-dependent
-
Throughput: TM samples per clock cycle (II=1)
HLS-based implementation with fully unrolled parallel processing. Resource usage scales with TM factor; the accumulator width changes area only, never the frequency resolution.