PWM波を生成する / デューティを(0/n)から(n/n)まで(1/n)刻み指定できるようにする

参考: FPGA活用回路&サンプル記述集(2) ―― モータやLEDを駆動するパワー回路|Tech Village (テックビレッジ) / CQ出版株式会社

  • ちょうどnクロックで所望のpwm周期になるようなクロックを生成する。要するにソースクロックを(ソースクロック周波数/(pwm周波数*n))分周する。
  • 分周器の出力を0 ~ (n-1)のn進カウンターでカウントする。
  • デューティの指定値が0なら強制的にLを出力する。
  • デューティの指定値がnなら強制的にHを出力する。
  • n進カウンターの値が今0であったら次のカウントアップのタイミングでHを出力する。
  • n進カウンターの値が今デューティの指定値(1~n)であったら次のカウントアップのタイミングでLを出力する。
  • 反転出力もできるようにする。

VHDL_for_Quartus_Prime/pwm_gen at main · ti-nspire/VHDL_for_Quartus_Prime · GitHub

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

entity pwm_gen is
    generic(
        F_CLK   : positive := 48_000_000;
        DUTY_MAX: positive := 100;
        PWM_FREQ: positive := 1000
    );
    port(
        aclr_n : in std_logic;
        clk    : in std_logic;
        pol_inv: in std_logic; -- 0: そのまま出力; 1: 反転して出力
        duty   : in std_logic_vector(integer(ceil(log2(real(DUTY_MAX+1)))) - 1 downto 0);
    
        pwm_out: out std_logic
    );
end entity;

architecture rtl of pwm_gen is
    constant DIV_FACTOR: positive := F_CLK/(PWM_FREQ * DUTY_MAX);
    
    signal clk_divided   : std_logic                       := '1';
    signal counter_mod   : natural range 0 to DUTY_MAX - 1 := 0;
    signal pwm_out_inside: std_logic                       := '0';
begin

   -- クロック数DUTY_MAXで所望のpwm周期になるようソースクロック(F_CLK)を分周する。
    process(clk)
        constant TOP_VAL: natural                    := DIV_FACTOR/2 - 1;
        variable count  : natural range 0 to TOP_VAL := 0;
    begin
        if rising_edge(clk) then
            if count >= TOP_VAL then
                count := 0;
                clk_divided <= not clk_divided;
            else
                count := count + 1;
            end if;
        end if;
    end process;
    
   -- 分周したクロックをDUTY_MAX進カウンターでカウント(0 ~ (DUTY_MAX-1))する。
    process(clk_divided, aclr_n)
        constant TOP_VAL: natural := DUTY_MAX - 1;
    begin
        if aclr_n = '0' then
            counter_mod <= 0;
        elsif rising_edge(clk_divided) then
            if counter_mod >= TOP_VAL then
                counter_mod <= 0;
            else
                counter_mod <= counter_mod + 1;
            end if;
        end if;
    end process;

   -- pwm波を生成する。
    process(duty, clk_divided)
    begin

       -- 指定デューティが0ならLを出力し、
        if unsigned(duty) = 0 then
            pwm_out_inside <= '0';

       -- 指定デューティがDUTY_MAXならHを出力し、
        elsif unsigned(duty) = DUTY_MAX then
            pwm_out_inside <= '1';

        elsif rising_edge(clk_divided) then

           -- DUTY_MAX進カウンターがオーバーフローしていたら次のクロックでHを出力し、
            if counter_mod = 0 then
                pwm_out_inside <= '1';

           -- DUTY_MAX進カウンターが指定デューティに達していたら次のクロックでLを出力し、
            elsif counter_mod = unsigned(duty) then
                pwm_out_inside <= '0';

            end if;
        end if;
    end process;

   -- pwm波をそのまま出力または反転出力する。
    pwm_out <=
            pwm_out_inside when pol_inv = '0' else
        not pwm_out_inside when pol_inv = '1' else
        '0';

end architecture;

テストベンチ:

LIBRARY ieee;                                               
USE ieee.std_logic_1164.all;                                
use ieee.numeric_std.all;

ENTITY pwm_gen_vhd_tst IS
END pwm_gen_vhd_tst;
ARCHITECTURE pwm_gen_arch OF pwm_gen_vhd_tst IS
-- constants                                                 
-- signals                                                   
SIGNAL aclr_n : STD_LOGIC;
SIGNAL clk : STD_LOGIC;
SIGNAL duty : STD_LOGIC_VECTOR(6 DOWNTO 0);
SIGNAL pol_inv : STD_LOGIC;
SIGNAL pwm_out : STD_LOGIC;
COMPONENT pwm_gen
    PORT (
    aclr_n : IN STD_LOGIC;
    clk : IN STD_LOGIC;
    duty : IN STD_LOGIC_VECTOR(6 DOWNTO 0);
    pol_inv : IN STD_LOGIC;
    pwm_out : OUT STD_LOGIC
    );
END COMPONENT;
BEGIN
    i1 : pwm_gen
    PORT MAP (
-- list connections between master ports and signals
    aclr_n => aclr_n,
    clk => clk,
    duty => duty,
    pol_inv => pol_inv,
    pwm_out => pwm_out
    );

process
begin
    aclr_n  <= '1';
    pol_inv <= '0';
    
    duty <= std_logic_vector(to_unsigned(20, duty'length));

    wait;
end process;

process   
begin
    clk <= '1'; wait for 10417 ps; -- (1/F_CLK)/2
    clk <= '0'; wait for 10417 ps; -- (1/F_CLK)/2
end process;    
    
END pwm_gen_arch;

シミュレーション結果。pwm周波数は1kHz、デューティは20%にした。
f:id:ti-nspire:20211001175108p:plain