How to Implement NCO in VHDL

 What is an NCO?

An NCO is the acronym of Numerically Controlled Oscillator.

Figure 1 – NCO basic architecture

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.

Figure 2- DAC 5687 architecture

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

Figure 3 – ADC AD 6655

 

The NCO are also widely used inside the microcontrollers. An example is the PWM implementation in PIC microcontroller  as in Figure 4

Figure 4 – PWM implementation using NCO in PIC microcontroller

 

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.

Figure 5 – 8-bit NCO simulation with FCW=19

 

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.

 

Figure 6 – NCO with Start-Phase control

 

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

Figure 7 – NCO test bench with two NCO, different start phase same FCW

 

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.

 

Figure 8 – simulation of two 8-bit NCO with different start phase 0, 16 and FCW=19

 


 


 

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:

13 thoughts to “How to Implement NCO in VHDL”

  1. 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?

  2. 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?

  3. 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

  4. 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!

    1. Hi, thank you for your feedback.
      Join-us signing up and you will be updated with the VHLD of NCO and more
      Cheers!

      1. Hi, Thank you for the useful material. I have joined your telegram channel but I have not found the test bench for NCO there.

  5. 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 !!

Leave a Reply

Your email address will not be published. Required fields are marked *