Skip to content

Instantly share code, notes, and snippets.

@jjcarrier
Last active March 8, 2022 22:34
Show Gist options
  • Save jjcarrier/7631350 to your computer and use it in GitHub Desktop.
Save jjcarrier/7631350 to your computer and use it in GitHub Desktop.
A parameterized PWM module written in VHDL
-- Written By Jon Carrier
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use ieee.numeric_std.all;
entity PWM is
generic (
prescale: integer := 0; --frequency(PWM_clk)=frequency(i_CLK/(prescale+1))
quantas: integer := 8 --PWM stages, determines granularity
);
port (
i_CLK: in std_logic; --Main input clock signal
i_PWM: in std_logic_vector(31 downto 0); --Controls the Duty Cycle of o_PWM
o_PWM: out std_logic := '1' --The Pulse Width Modulated output signal
);
end PWM;
architecture PWM_arch of PWM is
signal PWM_clk : std_logic :='0';
signal PWM_acc : std_logic_vector(31 downto 0) := x"00000000";
begin
--PRESCALING PROCESS
--Generates the clock divided signal, frequency(PWM_clk)=frequency(i_CLK/(prescale+1))
--PWM_clk drives the rest of the PWM logic
prescale_proc: process(i_CLK)
variable prescale_cnt : std_logic_vector(31 downto 0) := x"00000000";
begin
if rising_edge(i_CLK) then
if prescale_cnt >= prescale then
prescale_cnt := x"00000000";
PWM_clk <= not PWM_clk;
else
prescale_cnt := prescale_cnt + 1;
end if;
end if;
end process;
--ACCUMULATOR PROCESS
--Generates the accumlation variable PWM_acc
--This variable is used in the PWM as a means to determine
--when the PWM signal should switch.
accumulate: process(PWM_clk)
begin
if falling_edge(PWM_clk) then
if PWM_acc >= std_logic_vector(to_unsigned(quantas - 1, PWM_acc'length)) then
PWM_acc <= x"00000000";
else
PWM_acc <= PWM_acc + 1;
end if;
end if;
end process;
--PWM PROCESS
--Generates the PWM output signal, o_PWM
--i_PWM controls the output and specifies how many Time Quantas
--out of a Total Quanta the output must be set HIGH
modulate: process(PWM_clk, i_PWM)
begin
if rising_edge(PWM_clk) then
if PWM_acc >= i_PWM then
o_PWM <= '0';
elsif PWM_acc = x"00000000" then
o_PWM <= '1';
end if;
end if;
end process;
end PWM_arch;
@Eguy358
Copy link

Eguy358 commented Mar 8, 2022

What controls the width of the pulse? And what controls the frequency?

@jjcarrier
Copy link
Author

jjcarrier commented Mar 8, 2022

It has been a long time since I have looked at this code but here is my understanding.

What controls the width of the pulse?

The width depends on three factors:

  • PWM_clk, the PWM clock frequency (is is based on i_CLK divided down by prescale + 1)
  • quantas, the total number of time quantas to accumulate (determines granularity of the PWM input over the 0-100% duty cycle range, and the total period of the PWM output signal)
  • i_PWM, the input PWM value

A faster PWM_clk means PWM_acc will increment more rapidly, resulting in the o_PWM changing state more frequently and in turn results in shorter pulse widths.
quantas specifies the max value the PWM_acc is able to accumulate to, and is representative of the total period of one PWM cycle. So a larger value for quantas means a longer PWM period but also more granularity.
i_PWM is your input value that you are converting to PWM and is what directly controls the duty cycle and thus the width.

And what controls the frequency?

As hinted above, the output PWM signal’s frequency will be based on the input clock’s frequency (divided by the prescale + 1) and the number of quantas.

EDIT:
Dug up an old blog post I made on this which might provide additional insight:
http://carrierfrequency.blogspot.com/2013/11/here-is-yet-another-pwm-module-what.html

@Eguy358
Copy link

Eguy358 commented Mar 8, 2022

Thank you a lot, i will give it a try :)
If you do have the time, no pressure, are you able to make an example where you generate a o_PWm of 50 Hz and width 2ms, from a 12MHz native clk?

@jjcarrier
Copy link
Author

@Eguy358, I will skip on writing the VHDL/Verilog code as I do not have a dev environment setup for this right now, but I can help with the math.

A 50Hz PWM will have a period of 20ms. You can break this period into fractions of time using time quantas. To get a nice even 2ms pulse width you could use a value that is a multiple of 10. A higher value will give you better granularity if you need to provide a PWM output for a different pulse width or duty cycle. So we can go with quantas = 200.

With this, we can now calculate the prescaler to make this work which follows the form of:

PWM frequency = Input Clock Frequency / (prescaler + 1) / quantas
50Hz = 12MHz / (prescaler + 1) / 200

This results in:
prescaler = 1199

Then with this configuration, each unit of i_PWM is equal to:
i_PWM unit of time = (1/50)/200

or

i_PWM unit of time = 100us

So a 2ms pulse would be created using:
i_PWM = 2ms / 100us = 20

To summarize, to get a 2ms pulse width on a 50Hz PWM using a 12MHz clock, the following parameters could be used (there many possible solutions to this problem):

  • prescaler = 1199 (creates a PWM_clk of 10KHz)
  • quantas = 200 (200 quantas for a 10KHz PWM_clk = 20ms or 50Hz)
  • i_PWM = 20

Anyways hope that helps and I did not mix any math up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment