element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Benefits of Membership
    • Achievement Levels
    • Members Area
    • Personal Blogs
    • Feedback and Support
    • What's New on element14
  • Learn
    Learn
    • Learning Center
    • eBooks
    • STEM Academy
    • Webinars, Training and Events
    • More
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • More
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • More
  • Products
    Products
    • Arduino
    • Dev Tools
    • Manufacturers
    • Raspberry Pi
    • RoadTests & Reviews
    • Avnet Boards Community
    • More
  • Store
    Store
    • Visit Your Store
    • Choose Another Store
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
FPGA
  • Technologies
  • More
FPGA
Blog VHDL PWM generator with dead time: the design
  • Blog
  • Forum
  • Documents
  • Events
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
FPGA requires membership for participation - click to join
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
Author: Jan Cumps
Date Created: 12 Jun 2021 7:15 PM
Views: 2396
Likes: 12
Comments: 20
  • zynq
  • pinq
  • fpga
  • vivado
  • vhdl
  • spartan
Related
Recommended

VHDL PWM generator with dead time: the design

Jan Cumps
Jan Cumps
12 Jun 2021

I posted a series of FPGA blogs. They focus on the toolchains and steps to get a working design.

A common theme in those articles is the VHDL source. Each time, it's a PWM generator.

A specific kind of PWM block: it can generate complementary output signals, to drive a transistor half bridge.

 

In this post, I'm drilling into that VHDL part.

 

 

Why this PWM module with 2 outputs and dead time

I wanted to use a relevant exercise that solves a common task in electronics: drive a transistor half-bridge. Half-bridge circuits are all around. You'll find them in power designs such as motor control, buck converters, ... Virtually every digital output pin of a microcontroller has one.

 

It's a very common and well known design, and it's easy to translate the requirements into criteria for a digital driving circuit. And that's what we're making here: a digital circuit for an FPGA, with VHDL.

 

The PWM block is made to control this type of half-bridge circuits:

 

When you control such a design, there is always one watch-out: both transistors should never conduct at the same time. Because you would short the power supply directly to ground (shoot-trough).

Because there is a little delay between closing the gate and switching off the source-drain channel, you can't drive the two transistors with a simple inverted signal.

During that delay, both transistors would conduct and there would be a high current directly from power supply to ground. Ignoring to deal with this will destroy the transistors in seconds.

 

The remedy is to put a waiting time between switching off one transistor, and witching on the other one. This time is called dead time or dead band.

 

For efficient operation of the design, this dead time should be as short as possible. For reliability, it should be long enough to avoid the shoot-through.

 

This VHDL design can generate the control signals for the two transistors, and gives a very fine control over the dead time (resolution: one clock tick). You can precisely define it, and guarantee that it's always there.

 

 

VHDL interface

 

The PWM block has these inputs and outputs:

 

 

pin Function
clk_i input: clock signal
duty_i input: 8 bit duty cycle. Range: 0 - 255 == 0 - 100%
band_i input: 4 bit dead time, 0 - 15 clock ticks time between falling edge of one output and rising edge of the other one
pwmA_o output: PWM signal, opposite to pwmB_o, will wait dead time before switching on after pwmB_o becomes low
pwmB_o output: PWM signal, opposite to pwmA_o, will wait dead time before switching on after pwmA_o becomes low

 

You can control the block from any input that can set duty cycle and dead time.

In my Spartan blog, it's controlled by a rotary encoder (mouse scroll wheel) that's connected to the FPGA.

In the recent Vivado / Pynq blogs, these two inputs are controlled by the ARM processors embedded on the Zynq silicon.

It's the exact same VHDL though.

 

entity Pwm is
  port (
    clk_i  : in  std_logic;             -- Input clock.
    duty_i : in  std_logic_vector (7 downto 0);      -- Duty-cycle input.
    band_i : in  std_logic_vector (3 downto 0);      -- number of clock-ticks to keep both signals low before rising edge
    pwmA_o  : out std_logic;            -- PWM output.
    pwmB_o  : out std_logic             -- PWM output inverse.
    );
end entity;

 

VHDL implementation

 

The clock ticks are what brings this design to life.

A single full lifecycle of the block is 255 clock ticks.

This is because the duty_i input bus has 8 bit resolution.

We can do a single transition of output A and/or B per clock tick. So for 8 bits precision we'll consume 255 ticks.

 

Each tick, a counter is increased, and the values of output A or B are calculated.

If we ignore the dead time for a moment:

  • all clock ticks from 0 to duty_i, A should be high, B should be low
  • all clock ticks from duty_i to 255, A should be low and B should be high.

 

The dead time is inserted by adding its value to the desired point where either A or B is set to high.

 

    if rising_edge(clk_i) then
      pwmA_o   <= LO;
      timer_r <= timer_r + 1;

 

first part of the 255 step cycle: counter between 0 and duty_i - 1:

 

      if timer_r < unsigned(duty_i) then
        pwmB_o <= LO;
        if timer_r >= unsigned(band_i)  then
          pwmA_o <= HI;
        end if;

 

second part: counter from duty_i to 255

 

Here, I convert band_i and duty_i to integers, because their sum may not fit into 8 bits.

If I did not convert them, VHDL would try to fit the sum in a construct that has the same size as the left operand.

 

      if timer_r >= to_integer(unsigned(band_i)) + to_integer(unsigned(duty_i)) then
        pwmB_o <= HI;
      end if;

 

The counter is defined in such a way that it automatically adapts to the bus width of duty_i, and wraps after 255 ticks:

 

  signal timer_r       : natural range 0 to 2**duty_i'length-1;

 

 

Full VHDL

 

this is the first version, at the end of this post you find a reworked approach after comments from community members)

Latest source on github: https://gist.github.com/jancumps/36f21e89bfb8e44f3dba7bf014ffd198

 

--*********************************************************************
-- Module for generating repetitive pulses.
--*********************************************************************
library IEEE;
use IEEE.std_logic_1164.all;

package PulsePckg is
  constant HI   : std_logic := '1';
  constant LO   : std_logic := '0';
  constant ONE  : std_logic := '1';
end package;

--*********************************************************************
-- PWM module.
--*********************************************************************
library IEEE;
use IEEE.MATH_REAL.all;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use WORK.PulsePckg.all;

entity Pwm is
  port (
    clk_i  : in  std_logic;             -- Input clock.
    duty_i : in  std_logic_vector (7 downto 0);      -- Duty-cycle input.
    band_i : in  std_logic_vector (3 downto 0);      -- number of clock-ticks to keep both signals low before rising edge
    pwmA_o  : out std_logic;            -- PWM output.
    pwmB_o  : out std_logic             -- PWM output inverse.
    );
end entity;

architecture arch of Pwm is
  signal timer_r       : natural range 0 to 2**duty_i'length-1;
begin
  process(clk_i)
  begin
    if rising_edge(clk_i) then
      pwmA_o   <= LO;
      timer_r <= timer_r + 1;

      if timer_r >= to_integer(unsigned(band_i)) + to_integer(unsigned(duty_i)) then
        pwmB_o <= HI;
      end if;

      if timer_r < unsigned(duty_i) then
        pwmB_o <= LO;
        if timer_r >= unsigned(band_i)  then
          pwmA_o <= HI;
        end if;
      end if;
    end if;
  end process;
end architecture;

 

The design is not complex. And can be simplified further. I leave it as an exercise for the reader (edit: see at the end of the post).

 

Result

 

I've loaded the design on a Pynq-Z2, and set duty cycle and dead time:

 

pwm_register[8:12].write(12)
pwm_register[0:8].write(200)

 

 

The oscilloscope shows signal A  (yellow) and B (blue).

I've used the math function A + B to make the dead band visible (purple).

The two cursors show the start and end of a 255 clock tick cycle (white).

 

  1. start of the cycle. counter = 0
  2. counter = 12. pwmA_o waits 12 clock ticks before being set to 1
  3. counter = 200. pwmA_o is set to 0.
    counter = 200 + 12: pwmB_o waits 12 clocks before being set to 1.
  4. counter = 255. End of cycle. pwmB_o is set to 0.

 

Edit: VHDL Restructure

 

After reading other people's VHDL papers and checking how jc2048 and wolfgangfriedrich structure a design, I rewrote the architecture in a more organised way:

Latest source on github: https://gist.github.com/jancumps/36f21e89bfb8e44f3dba7bf014ffd198

 

library IEEE;
use IEEE.std_logic_1164.all;

package PulsePckg is
  constant HI   : std_logic := '1';
  constant LO   : std_logic := '0';
  constant ONE  : std_logic := '1';
end package;

--*********************************************************************
-- PWM module.
--*********************************************************************

library IEEE;
use IEEE.MATH_REAL.all;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use WORK.PulsePckg.all;

entity Pwm is
  port (
    n_reset_i : in  std_logic;          -- async reset
    clk_i  : in  std_logic;             -- Input clock.
    duty_i : in  std_logic_vector (7 downto 0);      -- Duty-cycle input.
    band_i : in  std_logic_vector (3 downto 0);      -- number of clock-ticks to keep both signals low before rising edge
    pwmA_o  : out std_logic;            -- PWM output.
    pwmB_o  : out std_logic             -- PWM output inverse.
    );
end entity;

architecture arch of Pwm is
  signal timer_r       : natural range 0 to 2**duty_i'length-1;
begin

  clocked: process(clk_i, n_reset_i)
  begin
    pwmA_o   <= LO;
    pwmB_o   <= LO;
    
    -- async reset
    if n_reset_i = '0' then
        timer_r <= 0;
        
    elsif rising_edge(clk_i) then
      -- timer
      timer_r <= timer_r + 1;
      -- output a
      if timer_r < unsigned(duty_i) and timer_r >= unsigned(band_i)  then
        pwmA_o <= HI;
      end if;
      -- output b
      if timer_r >= to_integer(unsigned(band_i)) + to_integer(unsigned(duty_i)) then
        pwmB_o <= HI;
      end if;
    end if; -- rising_edge
  end process clocked;
    
end architecture;

 

This does not change the FPGA timings or use. The synthesis and implementation resulted into the exact same design and fabric consumption.

Critique is welcome ....

 

 

Pynq - Zync - Vivado series
Add Pynq-Z2 board to Vivado
Learning Xilinx Zynq: port a Spartan 6 PWM example to Pynq
Learning Xilinx Zynq: use AXI with a VHDL example in Pynq
VHDL PWM generator with dead time: the design
Learning Xilinx Zynq: use AXI and MMIO with a VHDL example in Pynq
Learning Xilinx Zynq: port Rotary Decoder from Spartan 6 to Vivado and PYNQ
Learning Xilinx Zynq: FPGA based PWM generator with scroll wheel control
Learning Xilinx Zynq: use RAM design for Altera Cyclone on Vivado and PYNQ
Learning Xilinx Zynq: a Quadrature Oscillator - 2 implementations
Learning Xilinx Zynq: a Quadrature Oscillator - variable frequency
Learning Xilinx Zynq: Hardware Accelerated Software
Automate Repeatable Steps in Vivado
Learning Xilinx Zynq: Try to make my own Accelerated OpenCV Function - 1: Vitis HLS
Learning Xilinx Zynq: Try to make my own Accelerated OpenCV Function - 2: Vivado Block Design
Learning Xilinx Zynq: Logic Gates in Vivado
Learning Xilinx Zynq: Interrupt ARM from FPGA fabric
Learning Xilinx Zynq: reuse and combine components to build a multiplexer
PYNQ version 2.7 (Austin) is released
PYNQ and Zynq: the Vitis HLS Accelerator with DMA training - Part 1: Turn C++ code into an FPGA IP
PYNQ and Zynq: the Vitis HLS Accelerator with DMA training - Part 2: Add the Accelerated IP to a Vivado design
PYNQ and Zynq: the Vitis HLS Accelerator with DMA training - Part 3: Use the Hardware Accelerated Code in Software
PYNQ and Zynq: the Vitis HLS Accelerator with DMA training - Deep Dive: the data streams between Accelerator IP and ARM processors
Use the ZYNQ XADC with DMA part 1: bare metal
Use the ZYNQ XADC with DMA part 2: get and show samples in PYNQ
VHDL: Convert a Fixed Module into a Generic Module for Reuse
Attachments:
johnson_counter.zip
johnson_counter_jupyter_notebook.zip
Anonymous

Top Comments

  • Jan Cumps
    Jan Cumps 11 months ago in reply to cstanton +3

    cstanton  wrote:

     

    For some reason I thought the code would look more obscure than it does...

    Yes, this is an example that is easy to follow. Everything can be  decided by the current value of one counter.

    A…

  • jc2048
    jc2048 11 months ago +3

    VHDL is fairly portable at the level of the basic programmable logic. Here is Jan's code moved to an Intel (Altera) device, a Max II CPLD (EPM240T100C5N). It's very much less capable than the Xilinx part…

  • jc2048
    jc2048 10 months ago +3

    I should probably stop doing this, but here's one more for luck. This is on a VIDOR 4000 (a Cyclone 10 FPGA).

     

     

     

Parents
  • cstanton
    cstanton 11 months ago

    For some reason I thought the code would look more obscure than it does...

    • Cancel
    • Up +1 Down
    • Reply
    • More
    • Cancel
Comment
  • cstanton
    cstanton 11 months ago

    For some reason I thought the code would look more obscure than it does...

    • Cancel
    • Up +1 Down
    • Reply
    • More
    • Cancel
Children
  • Jan Cumps
    Jan Cumps 11 months ago in reply to cstanton

    cstanton  wrote:

     

    For some reason I thought the code would look more obscure than it does...

    Yes, this is an example that is easy to follow. Everything can be  decided by the current value of one counter.

    A little step up would be a standard SPI Master implementation.

     

    Once you start with SPI Client and I2C in verilog or vhdl, it gets difficult for me.

    They are good exercises, because the protocols are well documented and familiar.

    • Cancel
    • Up +3 Down
    • Reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 10 months ago in reply to Jan Cumps

    Jan Cumps  wrote:

    ...

    A little step up would be a standard SPI Master implementation.

    ...

    The internet is wonderful. nandmaster wrote a SPI IP: https://github.com/nandland/spi-master/blob/master/VHDL/source/SPI_Master_With_Single_CS.vhd

    • Cancel
    • Up +1 Down
    • Reply
    • More
    • Cancel
Element14

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2022 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • Facebook
  • Twitter
  • linkedin
  • YouTube