AM Modulation with DDS in Xilinix Vivado

Introduction

In this project, I will demonstrate how amplitude modulation (AM) works using a practical digital implementation. The AM signal is generated using a Direct Digital Synthesis (DDS) approach implemented in Vivado. By controlling the amplitude of the carrier with a low-frequency message signal, this example illustrates the fundamental principles of AM modulation in a way that is easy to visualize and verify in simulation and hardware.

AM Modulation Theory

Amplitude Modulation (AM) Implementation
Amplitude modulation is implemented by varying the amplitude of a high-frequency carrier signal in proportion to a lower-frequency message (baseband) signal. In its simplest analog or digital form, this is achieved by multiplying the carrier waveform by the message signal after applying a suitable DC offset to ensure that the envelope remains positive. Mathematically, the AM signal can be written as:
s(t) = Ac[1 + m(t)] cos(2πfct)
Ac: is the carrier amplitude
m(t): is the normalized message signal
fc: is the carrier frequency
In digital implementations such as FPGA- or DSP-based systems, AM is commonly realized using a multiplier block to scale the carrier by the message signal, followed by an adder to include the carrier component. This method produces the characteristic AM spectrum consisting of the carrier and two sidebands located
at fc ± fm.

Modulation Sensitivity in Amplitude Modulation (AM)

In amplitude modulation (AM), the modulation sensitivity describes how strongly the amplitude of the carrier signal responds to changes in the amplitude of the modulating (message) signal. It defines the proportional relationship between the message signal and the resulting variation in the
carrier envelope.

The AM signal can be expressed mathematically as:

s(t) = Ac [ 1 + ka m(t) ] cos(2πfct)

where Ac is the carrier amplitude, m(t) is the message signal, fc is the carrier frequency, and ka is the amplitude modulation sensitivity.

Physically, the modulation sensitivity ka determines how much the carrier amplitude changes for a given change in the message signal amplitude. A larger value of ka results in deeper modulation, while a smaller value produces shallower modulation.

The AM modulation index, denoted by μ, is directly related to the modulation sensitivity and is given by:

μ = ka Am

where Am is the peak amplitude of the message signal. For μ < 1, the signal is under-modulated; for μ = 1, the signal is fully modulated; and for μ > 1, over-modulation occurs, causing envelope distortion.

In digital and FPGA-based AM implementations, the modulation sensitivity is realized as a scaling coefficient in a multiplier block that controls how the message samples modulate the carrier amplitude.

Implementation

In this implementation, a 1 kHz message tone is used to amplitude-modulate a 2 MHz carrier frequency. The goal is to demonstrate AM behavior. The message signal and carrier are both generated using DDS cores, ensuring precise frequency control. The digital operation implemented in this design follows the expression:
Modulated Signal s(t) = cos(2πfmt) × cos(2πfct) + cos(2πfct) ,
where the message tone is multiplied with the carrier and the carrier component is added back to form a conventional AM signal with a carrier present. This approach directly maps to a hardware implementation using a multiplier and adder, making it well suited for FPGA-based AM modulation experiments.
In this implementation the Amplitude modulation sensitivity and modulation index are assumed to be equal to 1: μ = 1.

VHDL code

The code below uses 200MHz frequency for the DDS clock. Also note the four clock delay used in the process at the end of the code. This is used to synchronize the inputs to the adder IP core. It takes 4 clock cycles for the multiplier to complete one multiplication computation. Also notice that product was scaled down to 16 signed bits and then added to the coresponding 16 bits of the carrier frequency sample.

  library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.all;

 
entity AM_Modulator_TopLevel is
  port (
    sys_clk : in std_logic; 
    reset   : in STD_LOGIC
  );
end AM_Modulator_TopLevel;

architecture behavioral of AM_Modulator_TopLevel is

signal clk200MHz                : std_logic; 
signal clk400MHz                : std_logic;           
signal locked_Sig               : std_logic;
signal DDS0_tvalid_out          : std_logic;
signal CarrierFrequency         : std_logic_vector(15 downto 0); 
signal CarrierFrequency_dly     : std_logic_vector(15 downto 0);

signal DDS1_tvalid_out          : std_logic; 
signal MultAdd_In_valid         : std_logic; 
signal MessageTone_Signal       : std_logic_vector(15 downto 0); 
signal MessageCarrierMultiplied : std_logic_vector(31 downto 0); 
--signal PCOUT_Signal             : std_logic_vector(47 downto 0); 
signal MessageCarrierMultipliedScaled  : std_logic_vector(15 downto 0);
signal AM_ModulatedSignal       : std_logic_vector(16 downto 0);


signal CarrierFrequency_dly1 : std_logic_vector(15 downto 0);
signal CarrierFrequency_dly2 : std_logic_vector(15 downto 0);
signal CarrierFrequency_dly3 : std_logic_vector(15 downto 0);
signal CarrierFrequency_dly4 : std_logic_vector(15 downto 0);

component clk_wiz_0
port
 (
  clk_out1          : out    std_logic;
  clk_out2          : out    std_logic;
  locked            : out    std_logic;
  clk_in1           : in     std_logic
 );
end component;


COMPONENT dds_compiler_0
  PORT (
    aclk : IN STD_LOGIC;
    m_axis_data_tvalid : OUT STD_LOGIC;
    m_axis_data_tdata : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
  );
END COMPONENT;

COMPONENT mult_gen_0
  PORT (
    CLK : IN STD_LOGIC;
    A : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
    B : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
    P : OUT STD_LOGIC_VECTOR(31 DOWNTO 0)
  );
END COMPONENT;

COMPONENT dds_compiler_1
  PORT (
    aclk : IN STD_LOGIC;
    m_axis_data_tvalid : OUT STD_LOGIC;
    m_axis_data_tdata : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
  );
END COMPONENT;

COMPONENT c_addsub_0
  PORT (
    A : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
    B : IN STD_LOGIC_VECTOR(15 DOWNTO 0);
    CLK : IN STD_LOGIC;
    S : OUT STD_LOGIC_VECTOR(16 DOWNTO 0)
  );
END COMPONENT;


begin

Clk_Wizard: clk_wiz_0
   port map (  
   clk_out1 => clk200MHz,
   clk_out2 => clk400MHz,            
   locked => locked_Sig,
   clk_in1 => sys_clk
 );
    

DDS_0 : dds_compiler_0
  PORT MAP (
    aclk => clk200MHz,
    m_axis_data_tvalid => DDS0_tvalid_out,
    m_axis_data_tdata => CarrierFrequency
  ); 
 
 DDS1 : dds_compiler_1
 PORT MAP (   
    aclk => clk200MHz,
    m_axis_data_tvalid => DDS1_tvalid_out,
    m_axis_data_tdata => MessageTone_Signal
  );
  
Multiplier : mult_gen_0
  PORT MAP (
    CLK => clk200MHz,  
    A => CarrierFrequency,
    B => MessageTone_Signal,
    P => MessageCarrierMultiplied
  );


MessageCarrierMultipliedScaled  CarrierFrequency_dly4,
    B => MessageCarrierMultipliedScaled,
    CLK => clk200MHz,
    S => AM_ModulatedSignal
  );
 ----------------------------------------------------------------------
 -- This process is used to synchronize Muliplication and addition 
 -- operation: it takes 4 clock cycles to complete one multiplication
 -----------------------------------------------------------------------  
DELAY_PROC: process(clk200MHz)
begin
  if rising_edge(clk200MHz) then
    if reset = '1' then
      CarrierFrequency_dly1  '0');
      CarrierFrequency_dly2  '0');
      CarrierFrequency_dly3  '0');
      CarrierFrequency_dly4  '0');
    else
      CarrierFrequency_dly1 <= CarrierFrequency;
      CarrierFrequency_dly2 <= CarrierFrequency_dly1; 
      CarrierFrequency_dly3 <= CarrierFrequency_dly2;  
      CarrierFrequency_dly4 <= CarrierFrequency_dly3;
    end if;
  end if;
end process;
end behavioral; 
  

Vivado Simulation

The simulation image below shows the modulated message signal. You can see the message signal envelop matches the message signal.

Figure1: AM Modulation Waveform
Figure2: 1KHz Modulated Signal on the Scope

Source Code

For a complete copy of the source code, please send me a message or send me a request in the comments section below. I hope you will find this tutorial helpful, and I would really appreciate any feedback or comments.

Author: Farid M