PWM Signal Generation with VHDL and Verilog
PWM Signal Generation with VHDL and Verilog
- 10 min read

PWM Signal Generation with VHDL and Verilog
PWM (Pulse Width Modulation) is an output format widely used in electronic applications, in which the value of the output is controlled by varying the amount of time the digital output stays active. With this output format, the effect of an analogue output can be produced using only a digital signal. It is also possible to transfer data between digital systems by making use of the timing parameters of the PWM signal.
PWM is based on the principle of switching digital systems rapidly. It works by changing the power delivered to the output through the on time (Ton) and off time (Toff) within a period. The structure of the PWM signal is given in Figure 1.
T: Period Ton: The time the output is held active at ‘1’ Duty: Expressed as the Ton/T ratio.

Figure 1 - Structure of the PWM signal
In the PWM signal structure shown in Figure 1, the power delivered to the output over one period is equal to Pmax*(Ton/T). Mathematically this can be written as follows.

Since the term on the right that is multiplied by 0 vanishes, the equation simplifies to the following form.

The PWM signal format is based on changing the power delivered to the output by varying the Ton/T ratio within a period. For example, a system that is to be driven at 100% power requires a Ton/T ratio of 1, whereas a system that is to run at 10% power requires a Ton/T ratio of 0.1.
By changing the duty ratio it becomes possible to change output quantities such as the power, the voltage or the position of the system being controlled. Varying the duty value changes the average time the output stays active and therefore lets the effective output value be adjusted. This method is particularly suitable for systems that can be switched on and off rapidly (heaters, lighting products, motors, SMPS units and so on), whose output power or speed can be controlled in this way. Because the switching is done very fast, at a frequency higher than the perception frequency of a human being or of the measuring system, the output behaves as though it were being controlled by a true analogue signal.
A PWM implementation in the VHDL and Verilog hardware design languages is given below. In these designs the clock frequency, the frequency of the PWM signal and the resolution of the PWM input value that controls the module are all defined as generics. The input and output parameters can therefore be adjusted as required.
The number of clock cycles required for one unit of resolution is calculated from the generic parameters at synthesis time and stored in the constant named pwm_tick_counter_limit_c. The input named PWM_i is sampled at the start of every PWM period and written into a signal named pwm_value. When pwm_tick_counter reaches pwm_tick_counter_limit_c-1, that is, when the time required for one unit of resolution has elapsed, the signal named pwm_tick is driven to ‘1’ for one clock cycle. When pwm_tick is ‘1’, the value of the signal named pwm_duty_counter, which holds the position within the PWM period in units of resolution, is incremented by one. Comparing the value taken from the PWM_i input with pwm_duty_counter determines whether the PWM signal should be in its Ton or Toff state. While the counter value lies between 0 and pwm_value-1 the output is driven to ‘1’, producing the Ton state; when it exceeds that value the output is driven to ‘0’.
The PWM generator code written in VHDL is given below.
--VHDLVerilog.com (VerilogVHDL.com) - 2025
--PWM controller
library ieee;
use ieee.std_logic_1164.all;
use IEEE.numeric_std.all;
entity pwm_generator is
generic
(
CLK_FREQ_c : integer := 100_000_000;--CLK frequency (Hz)
PWM_FREQ_c : integer := 100;--PWM frequency (Hz)
PWM_RESOLUTION_c: integer := 10--PWM resolution in bits
);
port
(
CLK_i : in std_logic;--CLK input
RESET_n_i : in std_logic;--active low reset
EN_i : in std_logic;
PWM_VALUE_i : in std_logic_vector(PWM_RESOLUTION_c-1 downto 0);
PWM_o : out std_logic
);
end entity;
architecture pwm_generator_beh of pwm_generator is
--Logarithm function used to calculate the bit width of the pwm_tick_counter signal
--Taken from the Xilinx Language Templates.
function clogb2( depth : natural) return integer is
variable temp : integer := depth;
variable ret_val : integer := 0;
begin
while temp > 0 loop
ret_val := ret_val + 1;
temp := temp / 2;
end loop;
return ret_val;
end function;
--Constant holding the number of clock ticks required for one duty unit
constant pwm_tick_counter_limit_c : integer := (CLK_FREQ_c + ((PWM_FREQ_c * (2**PWM_RESOLUTION_c))/2) ) / (PWM_FREQ_c * (2**PWM_RESOLUTION_c));
signal pwm_tick_counter : unsigned(clogb2(pwm_tick_counter_limit_c)-1 downto 0) := (others=>'0');--signal declaration for the time counter
signal pwm_tick : std_logic := '0';--declaration of the signal triggered when one duty unit has elapsed
signal pwm_duty_counter : unsigned(PWM_RESOLUTION_c-1 downto 0) := (others=>'0');--signal declaration for the duty counter
signal pwm_value : unsigned(PWM_RESOLUTION_c-1 downto 0) := (others=>'0');--signal in which the PWM input is sampled and stored at the start of the period
begin
process(CLK_i)
begin
if rising_edge(CLK_i) then
if RESET_n_i = '0' then--sync reset
--Output clearing
PWM_o <= '0';
--signal clearing in the reset state
pwm_value <= (others=>'0');--clear the register holding the PWM value
pwm_tick_counter <= (others=>'0');--clear the time counter
pwm_tick <= '0';--clear the signal that triggers the period counter
pwm_duty_counter <= (others=>'0');--clear the period counter.
else
if EN_i = '1' then--if the ENABLE input is active
if pwm_duty_counter = 0 then--if the period is complete, accept a new input value
pwm_value <= unsigned(PWM_VALUE_i);--sample the PWM input
end if;
--counter used to measure the unit duty period
if pwm_tick_counter = to_unsigned(pwm_tick_counter_limit_c-1,clogb2(pwm_tick_counter_limit_c)) then--if the end of the unit duty period has been reached
pwm_tick_counter <= (others=>'0');--clear the unit duty counter
pwm_tick <= '1';
else
pwm_tick_counter <= pwm_tick_counter + 1;--increment the unit duty counter.
pwm_tick <= '0';
end if;
--counter used to measure the PWM period
if pwm_tick = '1' then--if the end of the PWM period has been reached
if pwm_duty_counter = (pwm_duty_counter'range => '1') then--if the end of the PWM period has been reached (all bits are 1)
pwm_duty_counter <= (others=>'0');--clear the duty counter
else
pwm_duty_counter <= pwm_duty_counter + 1;--increment the duty counter.
end if;
end if;
if pwm_duty_counter < pwm_value then--if the PWM is in its Ton phase
PWM_o <= '1';
else--if the PWM is in its Toff phase
PWM_o <= '0';
end if;
else--if the ENABLE input is inactive
--Output clearing
PWM_o <= '0';
pwm_value <= (others=>'0');--clear the register holding the PWM value
pwm_tick_counter <= (others=>'0');--clear the time counter
pwm_tick <= '0';--clear the signal that triggers the period counter
pwm_duty_counter <= (others=>'0');--clear the period counter.
end if;
end if;--RESET_n_i
end if;--rising_edge
end process;
end architecture;
The PWM generator code written in Verilog, which is exactly equivalent to the code above, is given below.
//VHDLVerilog.com (VerilogVHDL.com) - 2025
//PWM controller
module pwm_generator
#(
parameter CLK_FREQ_c = 100_000_000,//CLK frequency (Hz)
parameter PWM_FREQ_c = 100,//PWM frequency (Hz)
parameter PWM_RESOLUTION_c = 10//PWM resolution in bits
)
(
input CLK_i,//CLK input
input RESET_n_i,//active low reset
input EN_i,
input [PWM_RESOLUTION_c-1:0] PWM_VALUE_i,
output reg PWM_o
);
//Logarithm function used to calculate the bit width of the pwm_tick_counter signal
//Taken from the Xilinx Language Templates (***).
function integer clogb2;
input integer depth;
for (clogb2=0; depth>0; clogb2=clogb2+1)
depth = depth >> 1;
endfunction
//Constant holding the number of clock ticks required for one duty unit
localparam pwm_tick_counter_limit_c = (CLK_FREQ_c + ((PWM_FREQ_c * (2**PWM_RESOLUTION_c))/2) ) / (PWM_FREQ_c * (2**PWM_RESOLUTION_c));
reg [clogb2(pwm_tick_counter_limit_c)-1:0] pwm_tick_counter = {clogb2(pwm_tick_counter_limit_c){1'b0}};//signal declaration for the time counter
reg pwm_tick = 1'b0;//declaration of the signal triggered when one duty unit has elapsed
reg [PWM_RESOLUTION_c-1:0] pwm_duty_counter = 0;//signal declaration for the duty counter
reg [PWM_RESOLUTION_c-1:0] pwm_value = 0;//signal in which the PWM input is sampled and stored at the start of the period
always@(posedge CLK_i)
begin
if(~RESET_n_i) begin //sync reset
//Output clearing
PWM_o <= 1'b0;
//signal clearing in the reset state
pwm_value <= 0;//clear the register holding the PWM value
pwm_tick_counter <= 0;//clear the time counter
pwm_tick <= 1'b0;//clear the signal that triggers the period counter
pwm_duty_counter <= 0;//clear the period counter.
end else begin
if(EN_i) begin//if the ENABLE input is active
if(pwm_duty_counter == 0)//if the period is complete, accept a new input value
pwm_value <= PWM_VALUE_i;//sample the PWM input
//counter used to measure the unit duty period
if(pwm_tick_counter == (pwm_tick_counter_limit_c-1)) begin//if the end of the unit duty period has been reached
pwm_tick_counter <= 0;//clear the unit duty counter
pwm_tick <= 1'b1;
end else begin
pwm_tick_counter <= pwm_tick_counter + 1;//increment the unit duty counter.
pwm_tick <= 1'b0;
end
//counter used to measure the PWM period
if(pwm_tick == 1)//if the end of the PWM period has been reached
if(pwm_duty_counter == {PWM_RESOLUTION_c{1'b1}}) //if the end of the PWM period has been reached (all bits being 1)
pwm_duty_counter <= 0;//clear the duty counter
else
pwm_duty_counter <= pwm_duty_counter + 1;//increment the duty counter.
if(pwm_duty_counter < pwm_value)//if the PWM is in its Ton phase
PWM_o <= 1'b1;
else//if the PWM is in its Toff phase
PWM_o <= 1'b0;
end else begin//if the ENABLE input is inactive
//Output clearing
PWM_o <= 1'b0;
//signal clearing in the reset state
pwm_value <= 0;//clear the register holding the PWM value
pwm_tick_counter <= 0;//clear the time counter
pwm_tick <= 1'b0;//clear the signal that triggers the period counter
pwm_duty_counter <= 0;//clear the period counter.
end
end
end
endmodule
The testbench code written in VHDL is given below.
--VHDLVerilog.com (VerilogVHDL.com) - 2025
--PWM controller testbench
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity pwm_generator_tb is
generic
(
CLK_FREQ_c : integer := 100_000_000;--CLK frequency (Hz)
PWM_FREQ_c : integer := 100;--PWM frequency (Hz)
PWM_RESOLUTION_c: integer := 10--PWM resolution in bits
);
end entity;
architecture DUT of pwm_generator_tb is
---------------------------------------------------------------------------------------------------
--components
---------------------------------------------------------------------------------------------------
component pwm_generator is
generic
(
CLK_FREQ_c : integer := 100_000_000;--CLK frequency (Hz)
PWM_FREQ_c : integer := 100;--PWM frequency (Hz)
PWM_RESOLUTION_c: integer := 10--PWM resolution in bits
);
port
(
CLK_i : in std_logic;--CLK input
RESET_n_i : in std_logic;--active low reset
EN_i : in std_logic;
PWM_VALUE_i : in std_logic_vector(PWM_RESOLUTION_c-1 downto 0);
PWM_o : out std_logic
);
end component;
---------------------------------------------------------------------------------------------------
--signals
---------------------------------------------------------------------------------------------------
constant clock_period_c : time := 10ns;--100MHz
signal clk : std_logic := '0';--clk
signal reset_n : std_logic := '1';--active low reset
signal en : std_logic := '0';--enable
signal pwm_value : std_logic_vector(PWM_RESOLUTION_c-1 downto 0) := (others=>'0');
begin
--clock generator
clock_process :
process
begin
clk <= '1';
wait for clock_period_c/2;
clk <= '0';
wait for clock_period_c/2;
end process;
PWM_GENERATOR_TEST :
process
begin
reset_n <= '0';--reset asserted
en <= '0';
wait for clock_period_c;
reset_n <= '1';--reset released
pwm_value <= "0000000000";--0
wait for 1ms;
en <= '1';
wait for 15ms;
pwm_value <= "0010000000";
wait for 20ms;
pwm_value <= "0001000000";
wait for 20ms;
pwm_value <= "1000000000";
wait for 3.5ms;
pwm_value <= "1111111111";
en <= '0';
wait for 15ms;
pwm_value <= "1111111111";
en <= '1';
wait for 15ms;
en <= '0';
pwm_value <= "0000000000";
wait;
end process;
---------------------------------------------------------------------------------------------------
--instantiations
---------------------------------------------------------------------------------------------------
DUT : pwm_generator
generic map
(
CLK_FREQ_c => CLK_FREQ_c,
PWM_FREQ_c => PWM_FREQ_c,
PWM_RESOLUTION_c=> PWM_RESOLUTION_c
)
port map
(
CLK_i => clk,
RESET_n_i => reset_n,
EN_i => en,
PWM_VALUE_i => pwm_value,
PWM_o => open
);
end architecture;
The testbench code written in Verilog, which is exactly equivalent to the testbench above, is given below.
//VHDLVerilog.com (VerilogVHDL.com) - 2025
//PWM controller testbench
`timescale 1ns / 1ns
module pwm_generator_tb
#
(
parameter CLK_FREQ_c = 100_000_000,//CLK frequency (Hz)
parameter PWM_FREQ_c = 100,//PWM frequency (Hz)
parameter PWM_RESOLUTION_c = 10//PWM resolution in bits
);
localparam clock_period_c = 10;
reg clk = 1'b0;//clk
reg reset_n = 1'b0;//active low reset
reg en = 1'b0;//enable
reg [PWM_RESOLUTION_c-1:0] pwm_value = 0;
always #(clock_period_c/2) clk=~clk; //100MHz
initial begin
reset_n <= 1'b0;//reset asserted
en <= 1'b0;
#clock_period_c
reset_n <= 1'b1;//reset released
pwm_value <= 10'b0000000000;//0
#1000000//1ms
en <= 1;
#15000000//15ms
pwm_value <= 10'b0010000000;
#20000000//20ms
pwm_value <= 10'b0001000000;
#20000000//20ms
pwm_value <= 10'b1000000000;
#20000000//20ms
pwm_value <= 10'b1111111111;
en <= 0;
#3500000//3.5ms
pwm_value <= 10'b1111111111;
en <= 1;
#15000000//15ms
en <= 0;
pwm_value <= 10'b0000000000;
$stop;
end
pwm_generator
#(
.CLK_FREQ_c (CLK_FREQ_c),
.PWM_FREQ_c (PWM_FREQ_c),
.PWM_RESOLUTION_c (PWM_RESOLUTION_c)
)
DUT
(
.CLK_i (clk),
.RESET_n_i (reset_n),
.EN_i (en),
.PWM_VALUE_i (pwm_value),
.PWM_o ()
);
endmodule
The waveform output of the testbench codes above is given in Figure 2.
Figure 2 - Waveform output
Figure 1 used in this article is taken from Learn Electronics India.