What is an NCO?
An NCO is the acronym of Numerically Controlled Oscillator.
Basically, it is implemented with an accumulator which adds a constant value FCW (Frequency Control Word). The accumulator wrap around every time it reaches its maximum value. For example, if the maximum value is 15 (4-bit accumulator) and the FWC = 4, the accumulator will count:
0, 4, 8, 12, 1, 5, 9… and so on.
The NCO is widely used in digital signal processing. In this post, we are going to understand how to implement an NCO in VHDL.
You can check also the Wikipedia page relative to NCO for further info relative to the math behind the NCO. Here we want to focus on then VHDL implementation of the NCO.
NCO use in digital processing
The NCO is widely used in the digital implementation of DDS, digital UP/Down converter, Variable clock generation, digital PLL.
You can find an NCO inside a DAC such as DAC5687 Interpolated DAC
The DAC5687 is a dual-channel 16-bit high-speed digital-to-analog converter (DAC) with integrated 2×, 4×, and 8× interpolation filters, a complex numerically controlled oscillator (NCO), onboard clock multiplier, and Phase IQ compensation, and on-chip voltage reference. The DAC architecture is reported below. In this architecture, the NCO provides a frequency up-conversion of the input signal.
Another example is the Digital I/Q demodulator integrated into ADC AD6655. In this case, the NCO is used to implement a frequency down-conversion
The NCO are also widely used inside the microcontrollers. An example is the PWM implementation in PIC microcontroller as in Figure 4
We can continue with other several examples of NCO usage in digital but is time to see how to implement an NCO in VHDL for our use.
NCO fixed-point implementation
To implement an NCO on FPGA or ASIC we need to quantize the NCO. Generally, the number of bits used in the NCO quantization is in the range of 24-48 bit.
For a 32 bit quantization, the frequency resolution of the NCO is Fc/232,
where Fc is the clock frequency of the system.
For example, if the clock frequency is 100 MHz the frequency resolution will be:
100.000.000/ 4.294.967.296 = 0,023 Hz
More general, the wrap-around frequency Fw is given in (Eq.1)
Fw = Fc x FCW/(2^N) (Eq.1)
Where N=number of bit of NCO
Example:
If N=2^16, Fc= 100 MHz, Fw= 10 MHz FCW = 10/100 x 65536 = 6553
In this case, using a
FCW = 6553 the Fw = 100x6553/65536 = 9,9990844726562500 MHz
Or in the case of ceiling the result:
FCW = 6554 the Fw = 100x6554/65536 = 10,000610351562500 MHz
As you can see, in this case, since Eq.1 doesn’t give an integer result the final NCO frequency contains an error respect to desired frequency due to the rounding or truncation of the Frequency Control Word (FCW).
NCO VHDL implementation
In this section, we are going to understand how to implement an NCO in VHDL.
Let’s start with a simple implementation of an 8-bit NCO.
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity nco8 is port ( i_clk : in std_logic; i_rstb : in std_logic; i_sync_reset : in std_logic; i_fcw : in std_logic_vector(7 downto 0); o_nco : out std_logic_vector(7 downto 0)); end nco8; architecture rtl of nco8 is signal r_sync_reset : std_logic; signal r_fcw : unsigned(7 downto 0); signal r_nco : unsigned(7 downto 0); begin p_nco8 : process(i_clk,i_rstb) begin if(i_rstb='0') then r_sync_reset <= '1'; r_fcw <= (others=>'0'); r_nco <= (others=>'0'); elsif(rising_edge(i_clk)) then r_sync_reset <= i_sync_reset ; r_fcw <= unsigned(i_fcw); if(r_sync_reset='1') then r_nco <= (others=>'0'); else r_nco <= r_nco + r_fcw; end if; end if; end process p_nco8; o_nco <= std_logic_vector(r_nco); end rtl;
VHDL code of an NCO
The NCO VHDL code implements an accumulator that will accumulate the input FCW register when the synchronous “i_sync_reset” control signal value is zero.
As clear from the VHDL code, the input signals are registered using an assignment under the clocked process to have a better result in terms of static timing analysis, i.e. timing performances.
If you don’t want to add the input registers you must pay attention to providing input signal already registered in the same clock domain of the NCO clock.
It is very important especially for the “i_sync_reset” signal if you want to guarantee the functionality of the NCO.
The use of an asynchronous signal, i.e. signal provided from a different clock domain, is the classical error when you design in VHDL even made from the expert designer.
NCO VHDL simulation
Figure 5 shows the simulation of the VHDL code of the NCO where the Frequency Control Word (FCW) is set to 19. In this case, the 8-bit NCO wrap at 2^8 = 256. The wrapping period will be:
256/19 = 13.47 i.e. a no integer number. As clear from the figure the wrapping cycle alternates between 13 and 14 in the long term the average will be around 13.47. In the simulation, the clock cycle is set to 10 ns, so 140 ns is relative to 14 clock cycles.
NCO VHDL implementation with start phase
When you use an NCO sometimes you need to provide an NCO start phase different from zero.
For example, when you use an NCO to drive a SIN/COS table or a CORDIC to implement a trigonometric function. An example of NCO with start phase architecture is given in Figure 6.
In this case, the start-phase value will be loaded into the NCO accumulator register when the NCO is under synchronous reset. The VHDL code for an NCO with start phase can be written as follow:
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity nco8_phase is port ( i_clk : in std_logic; i_rstb : in std_logic; i_sync_reset : in std_logic; i_start_phase : in std_logic_vector(7 downto 0); i_fcw : in std_logic_vector(7 downto 0); o_nco : out std_logic_vector(7 downto 0)); end nco8_phase; architecture rtl of nco8_phase is signal r_sync_reset : std_logic; signal r_start_phase : unsigned(7 downto 0); signal r_fcw : unsigned(7 downto 0); signal r_nco : unsigned(7 downto 0); begin p_nco8 : process(i_clk,i_rstb) begin if(i_rstb='0') then r_sync_reset <= '1'; r_start_phase <= (others=>'0'); r_fcw <= (others=>'0'); r_nco <= (others=>'0'); elsif(rising_edge(i_clk)) then r_sync_reset <= i_sync_reset ; r_start_phase <= unsigned(i_start_phase); r_fcw <= unsigned(i_fcw); if(r_sync_reset='1') then r_nco <= r_start_phase; else r_nco <= r_nco + r_fcw; end if; end if; end process p_nco8; o_nco <= std_logic_vector(r_nco); end rtl;
NCO VHDL simulation with different start phase
Figure 8 shows the simulation of the VHDL code of the NCO with start phase. The test bench instantiates two identical 8-bit NCO with the same Frequency Control Word (FCW) and different start phase as in Figure 7
FCW= is set to 19 as the previous example, start_phase=0; start_phase2=16. As in this case, the 8-bit NCO wrap at 2^8 = 256 with a wrapping period of 256/19 = 13.47 i.e. a no integer number. So the wrapping cycle alternates between 13 and 14. The two NCOs start at the same time and due to the different start phase, the wrapping instant is different between the two NCO.
If you appreciated this post, please help us to share it with your friend.
[social_sharing style=”style-7″ fb_like_url=”https://surf-vhdl.com/how-to-implement-nco-vhdl” fb_color=”light” fb_lang=”en_US” fb_text=”like” fb_button_text=”Share” tw_lang=”en” tw_url=”https://surf-vhdl.com/how-to-implement-nco-vhdl” tw_button_text=”Share” g_url=”https://surf-vhdl.com/how-to-implement-nco-vhdl” g_lang=”en-GB” g_button_text=”Share” linkedin_url=”https://surf-vhdl.com/how-to-implement-nco-vhdl” linkedin_lang=”en_US” alignment=”center”]
If you need to contact us, please write to: surf.vhdl@gmail.com
We appreciate any of your comment, please post below:
After all this, to program a FPGA you need to convert the above programs by the use of compiler. What is the source of that compiler?
here you can find free video courses
https://surf-vhdl.com/course
where you will find the flow starting from VHDL to the FPGA programming file
Simple way to create a digitally controlled variable oscillator! Interesting circuit!
Thank You! 🙂
An equivalent version you can play with in TL-Verilog: http://makerchip.com/sandbox/0YEfLfv/0Y6hLB
Essentially it’s this one-liner:
$Nco[7:0] <= $reset ? $start_phase[7:0] : $Nco + $fcw[7:0];
The NCO code does not show how the “wrap around” operation happens to generate the sequence
0,4,8,12,1,5,9,…
The way I see it, it will produce 0,4,8,12,9,4,… etc
Am I missing something here?
Sorry, I meant the way it is currently written the accumulator will periodically cycle like this:
0,4,8,12,0,4,8,…
NOT 0,4,8,12,1,5,… etc
Hi,
First off, this is very interesting post to explain the basic.
It’s appreciated!
Secondly, any chance you could post your test bench for this code, the one I write is giving me different results and I cannot see why.
Thanks and keep up the good work!
Hi, thank you for your feedback.
Join-us signing up and you will be updated with the VHLD of NCO and more
Cheers!
Hi, Thank you for the useful material. I have joined your telegram channel but I have not found the test bench for NCO there.
That great ,nice
Thank you!
Hello here,
I think everyone is going fine.
I have used this code , it can generate the sample of sine wave , but I can not view my simulation wave sine like this poster.
Maybe I have not used the same software for my simulation (because me I am using Isim from Xilinx ISE 14.7)
And I have learned a lof of thing to understanding this program, thank for all !!