Jump to content
  • 0

PWM


Garrett

Question

I'm working on some VHDL PWM code and need some guidance.

- Does the max_counter check need to be at the end so that 100% duty cycle is achieved?

- What can be done to speed up this routine (I want to run close to 300 Mhz)? Can I have the LSB drive the rest of the counter to improve performance? Is there a way to use more Boolean comparisons to speed it up?

I'm very new to FPGAs and VHDL so any help would be very much appreciated.

- Garrett

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity Generic_PWM_x1 is
port(
	clk: in std_logic;
	pwm_var: in std_logic_vector(19 downto 0);
	pwm_out: out std_logic
	
);
end Generic_PWM_x1;

architecture Behavioral of Generic_PWM_x1 is
signal counter: std_logic_vector(19 downto 0):= (others=>'0');
signal max_counter: std_logic_vector(19 downto 0):= (others=>'1');

begin

process(clk)
begin
	if rising_edge(clk) then
		counter <= std_logic_vector( unsigned(counter) + 1 );		
		if counter=max_counter then
			counter<=(others=>'0');
		else
			if counter<pwm_var then
				pwm_out<='1';
			else
				pwm_out<='0';
			end if;
		end if;
	end if;

end process;

end Behavioral;

 

Link to comment
Share on other sites

8 answers to this question

Recommended Posts

Hi,

This might give you some ideas, it should be about the fastest you can achieve..

begin
  if rising_edge(clk) then
    -------------------------------------------
    -- Drop the output when the conter = PWM level
    -- Enable the output when count is zero
    -- Note the special ordering to ensure always off when pwm_var = 0
    -- Use only absolute comparisons so no math is involved.
    -------------------------------------------
    if counter = pwm_var then
      pwm_out<='0';
    elsif counter = 0
      pwm_out<='1';
    end if;

    -------------------------------------------
    -- Update the counter
    -------------------------------------------
    if counter=max_counter then
      counter<=(others=>'0');
    else
      counter <= std_logic_vector( unsigned(counter) + 1 );		
    end if;
  end if;                                     
end process;

In general, if you want to generate an 'always on' output when pwm_var is all ones, then max_counter = has to be initialized to "(0=>'0', others => '1')" .e.g. "11111110" for an 8-bit counter.

Oh, and things can go wrong if pwm_var is changed at the wrong time - best of to latch it into a local signal at the same time that max_counter is reset back to zeros.

Link to comment
Share on other sites

Oh, the other option is to use a DDR output, and generate two output bits per cycle, allowing a 150 MHz design can generate 300 Mb/s.

This is a pretty generic technique - using the serializers you can generate about 1Gb/s running the design on a 125 MHz clock!

Link to comment
Share on other sites

I've tried 192 Mhz (5.208 ns - original design speed) and 288 MHz (3.47 ns). With either constraint, the best case results were about the same.

 

P.S. Just because I want to make sure I'm implementing this correctly, I have included the code below. Please also confirm the data latch.

architecture Behavioral of Generic_PWM_x1 is
signal counter: std_logic_vector(19 downto 0):= (others=>'0');
signal pwm_latch: std_logic_vector(19 downto 0):= (others=>'0');
constant max_counter: std_logic_vector(19 downto 0):= (others=>'1');

begin
process(clk)

begin
  if rising_edge(clk) then
    -------------------------------------------
    -- Drop the output when the counter = PWM level
    -- Enable the output when count is zero
    -- Note the special ordering to ensure always off when pwm_var = 0
    -- Use only absolute comparisons so no math is involved.
    -------------------------------------------
    if counter = pwm_latch then
      pwm_out<='0';
    elsif counter = "00000000000000000000" then
      pwm_out<='1';
    end if;

    -------------------------------------------
    -- Update the counter
    -------------------------------------------
    if counter=max_counter then
      counter<=(others=>'0');
	  pwm_latch<=pwm_var;
    else
      counter <= std_logic_vector( unsigned(counter) + 1 );		
    end if;
  end if;                                     
end process;

end Behavioral;

 

Link to comment
Share on other sites

Hi again,

I just tried this out on an Basys3 - (Artix7-35 grade 1C) and it meets timing just - 0.008 ns slack at 3.3ns clock. The limiting thing was the length of the carry chain in the 20-bit adder.

That can be broken into two shorter adders, that run faster. However, it is pretty hard to do it without getting bugs - hopefully I've got it right here:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity Generic_PWM_x1 is
port(
	clk: in std_logic;
	pwm_var: in std_logic_vector(19 downto 0);
	pwm_out: out std_logic
	
);
end Generic_PWM_x1;

architecture Behavioral of Generic_PWM_x1 is
    signal   counter    : unsigned(19 downto 0):= (others=>'0');
    signal   pwm_latch  : unsigned(19 downto 0):= (others=>'0');
    constant max_counter: unsigned(19 downto 0):= (others=>'1');
    signal   carry_next : std_logic := '0';
begin
process(clk)
    begin
      if rising_edge(clk) then
        -------------------------------------------
        -- Drop the output when the counter = PWM level
        -- Enable the output when count is zero
        -- Note the special ordering to ensure always off when pwm_var = 0
        -- Use only absolute comparisons so no math is involved.
        -------------------------------------------
        if counter = pwm_latch then
          pwm_out<='0';
        elsif counter = 0 then
          pwm_out<='1';
        end if;
    
        -------------------------------------------
        -- Update the counter, but splitting the carry chain
        -- into an 11-bit and 9-bit adder
        ----------------------------------------------------
        if counter=max_counter then
          counter <=(others=>'0');
          carry_next <= '0';
          pwm_latch <= unsigned(pwm_var);
        else
          counter(10 downto 0) <= counter(10 downto 0)+1;
          if carry_next = '1' then
            counter(19 downto 11)   <= counter(19 downto 11) + 1;
          end if;		

          if counter(10 downto 0) = "11111111110" then
            carry_next <= '1';
          else
            carry_next <= '0';
          end if;
        end if;
      end if;                                     
    end process;

end Behavioral;

That just meets timing at 333 MHz (3.0 ns). 

 

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...