1
\$\begingroup\$

I am trying to understand and work with a design using multiple processes. It is a simple program, split in three processes:

  1. Counter (Count_proc)
  2. A PWM duty cycle counter process (PWM_target_counter)
  3. Output process (PWM_output)

The program should use the first process to count to a limit "FREQ_PWM_COUNTER_MAX". When this limit is reached, another process should increment a variable "pwm_target". The last process will set the output according to "pwm_target".

The program should make a moving duty cycle with a frequency of 50Hz.

The code should simply increment the duty cycle continuously from 0% to 100% and start over again. And it is this duty cycle I want to output.

The program is tested in real hardware and I get at the output(s) just a single pulse of 10ns each 5ms.

I could properly get it to work in a single process, but I want to learn how to use several processes. Any help or hints are appreciated?

entity PWM_Counter is
    Generic (
        INPUT_CLK       : integer :=100000000;   --100 MHz
        PWM_FREQ        : integer := 50);          --PWM frequency 50 Hz

    Port ( 
        Pwm_Out     : out std_logic;
        Debug       : out std_logic;
        Clk         : in std_logic);

end PWM_Counter;

architecture Behavioral of PWM_Counter is

--Constants
constant FREQ_PWM_COUNTER_MAX   : integer := INPUT_CLK/PWM_FREQ;    --Initialize frequency counts based on input clock frequency and wanted PWM frequency

--Signal
signal pwm_value             : std_logic := '0';                            --Signal to hold pwm_value
signal debug_value           : std_logic := '0';
signal counter               : integer range 0 to FREQ_PWM_COUNTER_MAX := 0; --Frequency counter
signal pwm_target            : integer range 0 to FREQ_PWM_COUNTER_MAX :=0;    --Contain the duty cycle target value for the counter
signal counter_output        : std_logic := '0';

begin

Pwm_Out <= pwm_value;
Debug <= debug_value;

        Count_proc : process(Clk)
        begin
            if rising_edge (Clk) then
                    if(counter = FREQ_PWM_COUNTER_MAX) then
                        counter <= 0;
                        counter_output <= '1';
                        debug_value <= '1';
                    else
                        counter <= counter + 1;
                        debug_value <= '0';
                    end if;
            end if;
        end process;
        
        PWM_target_counter : process(counter_output)
        begin
            if rising_edge(counter_output) then
                if (pwm_target < FREQ_PWM_COUNTER_MAX) then
                    pwm_target <= pwm_target + 1;
                else
                    pwm_target <= 0;    
                end if;
            end if;
        end process;
        
        PWM_output : process(Clk)
        begin 
            if rising_edge(Clk) then
                    if (counter < pwm_target) then
                        pwm_value <= '1';
                    else
                        pwm_value <= '0';
                    end if;
            end if;
        end process;
        
end Behavioral;

Just to try, I managed to make it into a single process.

entity PWM_Counter is
    Generic (
        INPUT_CLK       : integer :=100000000;   --100 MHz
        PWM_FREQ        : integer := 50);          --PWM frequency 50 Hz

    Port ( 
        Pwm_Out     : out std_logic;
        Debug       : out std_logic;
        Clk         : in std_logic;
        Reset       : in std_logic);

end PWM_Counter;

architecture Behavioral of PWM_Counter is

--Constants
constant FREQ_PWM_COUNTER_MAX   : integer := INPUT_CLK/PWM_FREQ;    --Initialize frequency counts based on input clock frequency and wanted PWM frequency
constant PWM_DUTY_CYCLE_STEP    : integer := 1000;
constant PWM_STEP               : integer := FREQ_PWM_COUNTER_MAX/PWM_DUTY_CYCLE_STEP;

--Signal
signal pwm_value             : std_logic := '0';                            --Signal to hold pwm_value
signal debug_value           : std_logic := '0';
signal counter               : integer range 0 to FREQ_PWM_COUNTER_MAX := 0; --Frequency counter
signal pwm_counter           : integer range 0 to FREQ_PWM_COUNTER_MAX :=0;    --Contain the duty cycle target value for the counter
signal pwm_target            : integer range 0 to FREQ_PWM_COUNTER_MAX*PWM_DUTY_CYCLE_STEP :=0;    --Contain the duty cycle target value for the counter

begin

Pwm_Out <= pwm_value;
Debug <= debug_value;

        PWM_Output : process(Clk)
        begin
            if rising_edge (Clk) then
                if (Reset = '0') then
                    if (counter = FREQ_PWM_COUNTER_MAX) then
                        counter <= 0;
                        pwm_value <= '1';
                        debug_value <= '1';
                        if ( pwm_target = FREQ_PWM_COUNTER_MAX) then
                            pwm_target <= 0;
                            pwm_counter <= 0;
                        else
                            pwm_counter <= pwm_counter + 1;
                            pwm_target <= pwm_counter*PWM_STEP;
                        end if;
                    elsif (counter < FREQ_PWM_COUNTER_MAX) then
                        counter <= counter + 1;
                        if (counter < pwm_target) then
                            pwm_value <= '1';
                            debug_value <= '1';
                        else
                            pwm_value <= '0';
                            debug_value <= '0';
                        end if;
                    end if;
                else
                    counter <= 0;
                    pwm_counter <= 0;
                    pwm_target <= 0;
                    pwm_value <= '0';
                    debug_value <= '0';       
                end if;
            end if;
        end process;
        
        
end Behavioral;

But at least for me it seems more complicated (even though it actually do work now.)

\$\endgroup\$
2
  • 1
    \$\begingroup\$ The text describes the code but what would be interesting to know, for a more complete answer is what is this circuit (not program) supposed to do? Is the interface supposed to receive an intended duty cycle and provide a pulsed output according to the frequency, defined at instantiation, and the on/off times defined dynamically? \$\endgroup\$
    – devnull
    Commented 10 hours ago
  • 1
    \$\begingroup\$ The code should simply increment the duty cycle continuously from 0% to 100% and start over again. And it is this duty cycle I want to output. \$\endgroup\$
    – Tyassin
    Commented 10 hours ago

1 Answer 1

7
\$\begingroup\$

I could properly get it to work in a single process, but I want to learn how to use several processes.

The first step is stop thinking about "programs". There are 3 synchronous components and no explicit reset. Relying on initializations is thinking like you do in software and is not portable. Even on hardware targets which will accept this, they will only work as a power on reset.

The second process uses the counter_output as another clock. This is also not recommended as it makes the processes of checking the timing (setup and hold times) more complicated.

The third process is sensitive to the first clock, but it depends on a signal which is changed by the second process.

Thinking in hardware (at least Flip-Flops and pure combinational circuits) is a must. That way each block (e. g. process) will look like one of the fundamental blocks connected by signals.

The code should simply increment the duty cycle continuously from 0% to 100% and start over again.

This is just one suggestion. You could break it down even more, splitting the counters in registers and more combinational processes:

library IEEE;
use IEEE.std_logic_1164.all;

entity PWM_Counter is
    Generic (
        INPUT_CLK       : integer := 100000000;
        PWM_FREQ        : integer := 50);
    Port ( 
        Pwm_Out     : out std_logic;
       Reset       : in std_logic;
        Clk         : in std_logic);
end PWM_Counter;

architecture Behavioral of PWM_Counter is

constant FREQ_PWM_COUNTER_MAX : integer := INPUT_CLK/PWM_FREQ;

signal last_period : std_logic;

signal counter : integer range 0 to FREQ_PWM_COUNTER_MAX;
signal pwm_target : integer range 0 to FREQ_PWM_COUNTER_MAX;

begin
    PWM_period : process(counter)
    begin
        if (counter = FREQ_PWM_COUNTER_MAX) then
            last_period <= '1';
        else
            last_period <= '0';
        end if;
    end process;

clock_count : process(Reset, Clk)
begin
    if (Reset = '1') then
        counter <= 0;
    elsif rising_edge (Clk) then
        if (last_period = '1') then
            counter <= 0;
        else
            counter <= counter + 1;
        end if;
    end if;
end process;

PWM_duty : process(Reset, Clk)
begin
    if (Reset = '1') then
        pwm_target <= 0;
    elsif rising_edge(Clk) then
        if (last_period = '1') then
            if (pwm_target = FREQ_PWM_COUNTER_MAX) then
                pwm_target <= 0;    
            else
                pwm_target <= pwm_target + 1;
            end if;
        end if;
    end if;
end process;

PWM_output : process(pwm_target, counter)
begin
    if (counter < pwm_target) then
        Pwm_Out <= '1';
    else
        Pwm_Out <= '0';
    end if;
end process;

end Behavioral;
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Great I will look into your example and get a better understanding on how to split it up in several processes. \$\endgroup\$
    – Tyassin
    Commented 8 hours ago
  • 2
    \$\begingroup\$ @Tyassin One possible course of action is to learn about FSMs implemented down to the logic level. It will give you a good understanding on how combinational and synchronous components interact. Later, it is just a matter of how to describe the same hardware using VHDL (or Verilog). \$\endgroup\$
    – devnull
    Commented 8 hours ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.