2
\$\begingroup\$

Very new to VHDL, but I have a fundamental issue understanding processes in VHDL. I have the two below processes:

entity BLINKY_LED is
    Generic (
        NUM_LEDS    : integer := 4;         
        CLK_RATE    : integer := 100000000; 
        BLINK_RATE  : integer := 2);        
    Port ( 
        Led_Out : out std_logic_vector(NUM_LEDS-1 downto 0);
        Clk     : in std_logic;
        Reset   : in std_logic);    
end BLINKY_LED;

architecture Behavioral of BLINKY_LED is

    constant MAX_VAL : integer := CLK_RATE/BLINK_RATE;
    constant BIT_DEPTH : integer := integer(ceil(log2(real(MAX_VAL))));     
    signal counter : unsigned(BIT_DEPTH - 1 downto 0) := (others => '0');     
    signal led_reg : std_logic_vector(NUM_LEDS-1 downto 0) := "1010";
    
begin

    Led_out <= led_reg;
    
    count_process : process(Clk)
    begin
        if rising_edge(Clk) then
            if((Reset = '0') OR (counter = MAX_VAL)) then
                counter <= (others => '0');
            else
                counter <= counter + 1;
            end if;
        end if;
    end process count_process;
    
    output_process : process(clk)
    begin
        if rising_edge(Clk) then
            if MAX_VAL = counter then 
                led_reg <= NOT led_reg;
            end if;
        end if;      
    end process output_process;        
end Behavioral;

What happens when 'counter' reaches 'MAX_VAL'? One process sets 'counter' to 0, and the other toggles some LEDs. I understand it such that both processes happen at the same time, so 'counter' could be sat to 0 so the LEDs will never be toggled or what?

\$\endgroup\$
7
  • 4
    \$\begingroup\$ Don't think of counter as a variable, think of it as actual counter component, whose output is connected to a comparator generating it's own reset signal and is connected to an input of a T-flipflop or similar that is connected to a LED. Think hardware, not software. \$\endgroup\$
    – Eugene Sh.
    Commented Dec 4 at 15:34
  • 1
    \$\begingroup\$ Signal updates are scheduled in a projected output waveform queue and the simulation kernel variables holding signal values are updated in a different part of a simulation cycle than processes sensitive to a signal event are resumed and subsequently suspend. This simulates concurrency. Here for processes sensitive to the same clock event and evaluated rising edge. \$\endgroup\$ Commented Dec 4 at 19:50
  • 1
    \$\begingroup\$ Could you please share the rest of the code? Specially Led_out and led_reg are quite relevant. \$\endgroup\$
    – devnull
    Commented Dec 4 at 23:25
  • 3
    \$\begingroup\$ This is the most important statement, in Eugene's comment: "Think hardware, not software." If you have a software background, don't listen to your gut feeling. VHDL is a hardware description language, not so much a programming language. \$\endgroup\$ Commented Dec 5 at 6:30
  • 2
    \$\begingroup\$ @devnull Yes have posted the complete code. I think I get the point with "Think hardware" statement. \$\endgroup\$
    – Tyassin
    Commented Dec 6 at 9:24

2 Answers 2

1
\$\begingroup\$

Real logic devices have propagation delay. It takes a few nanoseconds (in discrete logic) or at least 10's or 100's of picoseconds (on chip) for the output of a register to change after the clock edge is detected.

They also have hold time requirements. After the clock edge arrives, it's required to keep the data input constant for at least some specified amount of time to make sure the captured input is the one that was there at the moment the clock edge arrived.

As long as the hold time of the downstream logic (your LED toggling register) is less than the propagation delay of the upstream logic (your counter) plus any intermediate combinatorial logic (the comparator checking whether the counter value equals the max count), then the downstream logic's next output will reflect the old value of its input and not the new value (or some mixed up intermediate value).

If you're working on an FPGA design, they typically have a hold time of 0, meaning any propagation delay at all is enough to guarantee the logic will work as expected.

HDL simulators know all this and, if you don't specify delay times, will simulate behavior as if the hold times are satisfied.

\$\endgroup\$
2
\$\begingroup\$

With the complete code we can provide more details about how the hardware synthesis will be performed. See the comments added to your code:

entity BLINKY_LED is
    Generic (
        NUM_LEDS    : integer := 4;         
        CLK_RATE    : integer := 100000000; 
        BLINK_RATE  : integer := 2);        
    Port ( 
        Led_Out : out std_logic_vector(NUM_LEDS-1 downto 0);
        Clk     : in std_logic;
        Reset   : in std_logic); -- The interface includes a proper reset pin, 
                                 -- which is the portable way to provide initial
                                 -- values to the synchronous logic.
end BLINKY_LED;

architecture Behavioral of BLINKY_LED is
    constant MAX_VAL : integer := CLK_RATE/BLINK_RATE;
    constant BIT_DEPTH : integer := integer(ceil(log2(real(MAX_VAL))));     
    -- These signals will be interpreted as synchronous storage logic. The reason
    -- for that is: they change their values inside processes which only specify
    -- new values inside the scope of clock edges.
    -- Since all other input possibilities are not mentioned, memory is inferred.
    -- Using initial values like this, like if they were software variables,
    -- is not guaranteed to work (not portable). Use the Reset input for both,
    -- even asynchronously (outside the "if" edges).
    signal counter : unsigned(BIT_DEPTH - 1 downto 0) := (others => '0');     
    signal led_reg : std_logic_vector(NUM_LEDS-1 downto 0) := "1010";

begin

    -- The first code you posted had this inside the process (with the NOT).
    -- Outside the scope, this now connects the register output to the output pins.
    -- This is clean, readable and implements what you wanted.
    Led_out <= led_reg;

    count_process : process(Clk)
    begin
        -- 'counter' is synchronous storage with sync. reset, but the input of this
        -- register is provided by combinational logic. The comparison with MAX_VAL
        -- and the increment will be implemented with the best option available
        -- for the target hardware
        if rising_edge(Clk) then
            if((Reset = '0') OR (counter = MAX_VAL)) then
                counter <= (others => '0');
            else
                counter <= counter + 1;
            end if;
        end if;
    end process count_process;

    output_process : process(clk)
    begin
        -- There is no explicit reset for this register and this is not portable.
        -- Depending on the hardware, the inversion may be obtained for free
        -- (inverted stored bits at the flip-flops) or an external inversion
        -- circuit may be needed. The synthesis tool will also have the option
        -- to borrow the same combinational circuit which compares counter with MAX_VAL
        if rising_edge(Clk) then
            if MAX_VAL = counter then 
                led_reg <= NOT led_reg;
            end if;
        end if;      
    end process output_process;        
end Behavioral;

I think I get the point with "Think hardware" statement.

That's the way to go. The less you think like software (e. g. processes "running" in parallel) and check the hardware inferred by the synthesis tool, the better. Using the software model will work fine for simulation, but will keep you far from the resulting hardware.

\$\endgroup\$

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.