DAC Virtual Register 800x100 1
WP_Term Object
    [term_id] => 106
    [name] => FPGA
    [slug] => fpga
    [term_group] => 0
    [term_taxonomy_id] => 106
    [taxonomy] => category
    [description] => 
    [parent] => 0
    [count] => 315
    [filter] => raw
    [cat_ID] => 106
    [category_count] => 315
    [category_description] => 
    [cat_name] => FPGA
    [category_nicename] => fpga
    [category_parent] => 0

VHDL parameterized PWM controller

VHDL parameterized PWM controller
by Claudio Avi Chami on 09-18-2016 at 7:00 am

Digital outputs can either go ON or OFF. Analog signals, on the other side, can smoothly assume multiple values in a range. There is a technique that emulates analog behavior with a digital output. That technique is PWM, namely, Pulse Width Modulation. It can be implemented as pulses with varying ‘high’ and ‘low’ duration. However, one rather simple implementation is to take a fixed output frequency and vary only the duty cycle. If the pulses are fast enough compared to the response time of the system, a PWM is equivalent to a varying analog signal, whose amplitude is proportional to the duty cycle.

The maximum output frequency of the PWM output depends on the clock signal used to generate the PWM, and on the resolution desired.

For example, if our clock frequency fc=100MHz, and we want our signal to have 64 different ‘analog’ values, the max. PWM frequency output will be 100MHz/64 ~1.5MHz.

In many applications we don’t need a PWM which is that fast. In that case, the clock frequency is first passed through a divider, and the divider output is used to generate the PWM.

The code for the PWM controller presented below is based in this idea, first it instantiates a clock divider and then PWM generation:

[CODE]library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.pwm_reg_pack.ALL;

entity pwm is
port (
clk : in std_logic;
rst : in std_logic;

— signals from top module to registers sub-module
en : in std_logic;
duty : in std_logic_vector(DUTY_CYCLE_W-1 downto 0);
pwm_out : out std_logic
end entity pwm;

architecture rtl of pwm is
signal clk_en : std_logic;
signal cnt : unsigned(PERIOD_W-1 downto 0);
signal cnt_duty : unsigned(DUTY_CYCLE_W-1 downto 0);

cnt_pr : process(clk, rst)
if (rst = ‘1’) then
cnt ‘0’);
clk_en <= '0';
elsif (rising_edge(clk)) then
— default
clk_en <= '0';

if (en = ‘1’) then
if (cnt = 0) then
cnt <= to_unsigned(PERIOD-1, cnt'length);
clk_en <= '1';
cnt <= cnt – 1;
end if;
end if;
end if;
end process cnt_pr;

cnt_duty_pr : process(clk, rst)
if (rst = ‘1’) then
cnt_duty ‘0’);
pwm_out <= '0';
elsif (rising_edge(clk)) then
if (clk_en = ‘1’) then
cnt_duty <= cnt_duty + 1;
end if;
if (cnt_duty < unsigned(duty)) then
pwm_out <= '1';
pwm_out <= '0';
end if;
end if;
end process cnt_duty_pr;

end rtl;

The are two control signals among the ports of the entity:

  • en: enables the PWM output
  • duty: is the duty cycle of the PWM output

The first counter cntis the frequency divider, which originates the clk_ensignal. The clk_en signal is used to increment the duty cycle counter cnt_duty. The value of cnt_duty cycles from 0 to max on each cycle. During the cycle, while the counter is less than the programmed duty value, the output value pwm_out is high, otherwise, it is low.

The values for the constants used are included in a package file:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

package pwm_reg_pack is

— Data size definitions
constant SYS_CLK : natural := 100_000; — System clock in kHz
constant PWM_CLK : natural := 500; — PWM clock in kHz
constant DUTY_CYCLE_W : natural := 5; — PWM resolution in bits
constant PERIOD : natural := SYS_CLK / (PWM_CLK * 2**DUTY_CYCLE_W);
constant PERIOD_W : natural := integer(ceil(log2(real(PERIOD+1))));

end pwm_reg_pack;

On these simulation waveforms we can see how the PWM works. Notice that the implementation is such that the output can be zero all the time, but it cannot be ‘high’ all the time.

The waveform below is a ‘zoom-in’:

A further zoom showing when the cnt_duty value becomes greater than duty and the output goes low (in this graph, cnt_duty is shown with values and not as ‘analog graph’, for clarity):

The module sources, including simulation testbench, are attached below. Replace the .txt extension with .vhd extension

My blog:FPGA Site

Proposed exercises:


  • Identify on the simulation waveforms the maximum output value
  • Change the testbench so it will output exactly five cycles for each possible duty value
  • Change the source so the output can have a value of ‘allways high’ when duty = max. value

  • Comments

    There are no comments yet.

    You must register or log in to view/post comments.