Numerically Controlled Oscillator (NCO) Chisel Generator
Overview
This repository contains a generator of a parametrizable numerically controlled oscillator with various options, written in Chisel hardware design language. Numerically Controlled Oscillators (NCO), or Direct Digital Synthesizers (DDS), are important components in many digital communication systems. They are used for constructing digital down and up converters, demodulators, and implementing various types of modulation schemes. The description of this generator can be found among the proceedings of 2021 24th International Symposium on Design and Diagnostics of Electronic Circuits & Systems (DDECS), where it is thoroughly elaborated.
To make a Chisel project and include NCO generator, an open source starter template Chipyard Framework can be used.
Numerically controlled oscillators
A Numerically Controlled Oscillator sources sinusoidal waveforms for use in many applications. A common method for digitally generating a complex valued sinusoid uses a lookup table scheme. A lookup table stores samples of a sinusoid. A digital integrator is used to generate an appropriate phase argument mapped by the lookup table to give the desired output waveform. Output waveform depends on the hardware parameters, as well as on the system clock signal frequency.
The NCO generator has following features: - SIN/COS Lookup table can be generated individually or together with Phase Generator - Standard or Rasterized mode where Rasterized mode eliminates phase noise from phase truncation - Lookup table can be stored in distributed or block RAM - Lookup table size, as well as the size of data samples inside the table can vary - Optional phase dithering that spreads the spectral line energy for greater Spurious Free Dynamic Ranage (SFDR) - Phase dithering or Taylor series correction options provide high dynamic range signals - Output data precision can vary - Different input interfaces - Optinal multiplier used for the output - Optional output signal quadrature amplitude modulation (QAM) scheme implemented - Output buffering
The generated module consists of a Phase Generator and a SIN/COS Lookup Table (phase to sinusoid conversion). SIN/COS Lookup Table can also be used individually. The Phase Generator consists of a phase accumulator followed by an optional adder to provide addition of phase offset. The phase increment and phase offset can be independently configured to be either fixed, programmable or streaming (AXI4 interface). When configured as fixed, the NCO ouput frequency is set when the module is customized and cannot be adjusted after it is embedded in design. When configured as programmable, output frequency depends on the inputs. It is up to the user to regulate them. When configured as streaming, ouput frequency depends on the streaming inputs using AXI4 protocol. Output always has a streaming interface. When the phase generator is not used, phase increment input port becomes input port for the absolute value of the phase used for transformation into sine and cosine outputs using look-up table. Look-up table can be implemented either using block RAM or distributed RAM. The latency of the module depends on the type of the implemented memory and can be either one clock cycle if distributed RAM is used or two clock cycles if block RAM is used. Because of the symmetry of the sinusoid waveform, only a quarter cycle of the waveform is stored in the memory, leading to considerable memory reduction. In standard mode of operation, characteristics of the output waveform, such as SFDR are degraded because of the phase truncation performed in order to get the address for the look-up table. There are two options regarding the improvement of waveform characteristics: phase dithering and interpolation using Taylor series correction. In the rasterized mode of operation, there is no phase truncation. Rasterized operation is intended for configurations where the desired frequency is a rational fraction of the system clock frequency. In the rasterized mode, output frequency equals fout = (fclk x pinc) / M, where fclk is system clock frequency, pinc is phase increment and M is the value four times larger than the look-up table size. In standard mode, output frequency is equal fout = (fclk x pinc) / 2B, where B is the phase width after truncation. Block diagram of a NCO generator is shown below.
Previously explained generator is described with following Scala files available insidesrc/main/scala
directory:
TreeReduce.scala
- contains modifiedTreeReduce
functionSyncROM.scala
- contains description ofSyncROM
- block RAM memory moduleNCOTable.scala
- contains look-up table parameter description and look-up table itselfNCO.scala
- contains parameter description and top level moduleNCO
Inputs
Decoupled interface is used if streaming interface is chosen. In that case, freq.bits are phase increment data or absolute phase value data and poff.bits are phase offset data.
freq
, phase increment or absolute phase value data: -Flipped(Decoupled(params.protoFreq))
- wrapped with valid/ready signals if appropriate interface is selected as streaming -Input(params.protoFreq)
- if appropriate interface is selected as programmable -None
- if appropriate interface is selected as fixedpoff
, phase offset: -Flipped(Decoupled(params.protoFreq))
- wrapped with valid/ready signals if appropriate interface is selected as streaming -Input(params.protoFreq)
- if appropriate interface is selected as programmable -None
- if appropriate interface is selected as fixedinputEnable
, enable signal for fixed configuration: -Input(Bool())
- used only iffreq
interface is selected as fixed
Outputs
Decoupled interface is used where .bits are data that should be sent to the streaming output.
out: Decoupled(DspComplex(params.protoOut, params.protoOut))
- complex output data wrapped with valid/ready signals
Setting up the parameters
NCO parameters are defined inside case class NCOParams
. User can set up these parameters in order to generate a numerically controlled oscillator for his own purposes.
case class NCOParams[T <: Data]
(
phaseWidth: Int,
tableSize: Int,
phaseConv: UInt => T,
protoFreq: UInt,
protoOut: T,
protoTable: Option[T] = None,
nInterpolationTerms: Int = 0,
rasterizedMode: Boolean,
ditherEnable: Boolean,
syncROMEnable: Boolean,
phaseAccEnable: Boolean,
roundingMode: TrimType = RoundHalfUp,
pincType: InterfaceType = Streaming,
poffType: InterfaceType = Streaming,
useMultiplier: Boolean = false,
numMulPipes: Int = 1,
useQAM: Boolean = false
)
The explanation of each parameter is given below:
phaseWidth
- width of the phase before phase truncationtableSize
- size of the look-up tablephaseConv
- function that converts phase truncation surplus in order to use it in calculation of Taylor series termsprotoFreq
- data type of the frequencyprotoOut
- data type of the output. Can be either FixedPoint or DspRealprotoTable
- data type of the samples inside look-up table. Can be either FixedPoint or DspRealnInterpolationTerms
- number of terms to be calculated for Taylor series correctionrasterizedMode
- determines if the module will work in standard or rasterize mode of operationditherEnable
- determines if phase dithering will be usedsyncROMEnable
- determines if look-up table will be implemented using block RAM or distributed RAMphaseAccEnable
- determines if phase accumulator will be usedroundingMode
- rounding mode used for trimming values to fixed pointpincType
- type of the input interface for phase increment/absolute phase valuepoffType
- type of the input interface for phase offsetuseMultiplier
- determines if the output multiplier will be implementednumMulPipes
- determines the number of pipeline registers used after multiplication operationsuseQAM
- determines if the QAM scheme will be implemented
Tests
Besides main source code, various tests for NCO generator are provided in this repository. To run a test written in Scala simulation environment, user should execute the following command: testOnly nco.testNameSpec
where
nco.testNameSpec
stands for a group of tests named testNameSpec inside nco package. These tests, as well as the other structures needed for running them, are located in src/test/scala
directory in following files:
NCOTester.scala
- contains testers for testing both complete NCO module and values inside look-up tableNCOTableSpec.scala
- used for testing values inside look-up table. Can be run usingtestOnly nco.NCOTableSpec
commandNCOSpec.scala
- used for testing complete NCO module. Can be run usingtestOnly nco.NCOSpec
commandanalysesTesterUtil.scala
- contains functions used for plotting frequency and time domains of signalsanalysesTester
- contains tester used for parameter analysis of the NCO generatoranalysesSpec
- used for analysing performance of generated NCO modules depending on several parameters. Can be run usingtestOnly nco.NCOAnalysisSpec
command
NCOLazyModuleBlock
NCOLazyModuleBlock is a generator of numerically controlled oscillators with the same parameters and functionalities as the NCO generator described above, with one difference: it can use AXI4 Interface for accessing memory-mapped control registers. Whether this interface will be used, depends on the parameters regarding input interfaces, multiplication operation and QAM scheme. Following memory-mapped control registers can exist:
poff
- phase offset value whenpoffType
parameter is set as Configfreq
- phase increment/phase value whenpincType
parameter is set as ConfiginputEN
- input enable signal whenpincType
parameter is set as FixedenableMultiplying
- multiplying enable signal whenuseMultiplier
parameter is set as truemultiplyingFactor
- factor value for multiplying whenuseMultiplier
parameter is set as true. This value can be smaller or equal 1 and it is of theprotoOut
data type and format. It can be assigned by sending the binary representation in the appropriate format converted to integer value. For example, if FixedPoint data type with width equal to 16 and binary point equal to 14 is used, for setting the multiplying factor to 0.5, value of 0x2000 should be sent (binary representation of 0.5 in appropriate format is 00.10000000000000, converted to integer value it is 0010000000000000 binary or 0x2000 hex).enableQAM
- QAM enable signal whenuseQAM
parameter is set as trueiinQAM
- QAM in-phase input signal whenuseQAM
parameter is set as true andpincType
parameter is not set as StreamingqinQAM
- QAM quadrature input signal whenuseQAM
parameter is set as true andpincType
parameter is not set as Streaming
This generator adds optional AXI4 Streaming Slave Interfaces for QAM in-phase and quadrature inputs when phase/phase increment parameter is set as Streaming and QAM is used. Also, input phase/phase increment and/or phase offset can have AXI4 Streaming Slave Interface if appropriate parameters are set as Streaming. Output signals always have AXI4 Streaming Master Interface.