Take the 2-minute tour ×
Electrical Engineering Stack Exchange is a question and answer site for electronics and electrical engineering professionals, students, and enthusiasts. It's 100% free, no registration required.

Context

I am currently reading the (very good) course material on "Introducing the Spartan 3E FPGA and VHDL" promoted here (community ads) and I don't really get the idea of splitting a critical path to increase performance:

The example is about displaying the 8 MSbits of a 30-bits counter on LEDs. In the codes below, the first example has a setup time of 4.053ns and the second 3.537ns, achieved by splitting the counter in two 15-bits counters and a carry flip-flop.

Question

Why does it work? Isn't the time to ripple the carry from the LS to the MS counter the same, and even higher from inserting the flip-flop? In the end, MS counter will still need its input from the LS counter to finish its cycle... Bonus points for those who illustrate their answer with diagrams of the critical path before/after and how the signals propagate.

Code

Before splitting:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Switches_LEDs is
    Port (
        switches : in STD_LOGIC_VECTOR(7 downto 0);
        LEDs : out STD_LOGIC_VECTOR(7 downto 0);
        clk : in STD_LOGIC
    );
end Switches_LEDs;

architecture Behavioral of Switches_LEDs is
    signal counter : STD_LOGIC_VECTOR(29 downto 0) := (others => ’0’);
begin
    LEDs <= counter(29 downto 22);

    clk_proc: process(clk, counter)
    begin
        if rising_edge(clk) then
            counter <= counter+1;
        end if;
    end process;
end Behavioral;

After splitting:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Switches_LEDs is
    Port ( 
        switches : in STD_LOGIC_VECTOR(7 downto 0);
        LEDs : out STD_LOGIC_VECTOR(7 downto 0);
        clk : in STD_LOGIC
    );
end Switches_LEDs;

architecture Behavioral of Switches_LEDs is
    signal counter : STD_LOGIC_VECTOR(29 downto 0) := (others => ’0’);
    signal incHighNext : STD_LOGIC := ’0’;
begin
    LEDs <= counter(29 downto 22);

    clk_proc: process(clk, counter)
    begin
        if rising_edge(clk) then
            counter(29 downto 15) <= counter(29 downto 15)+incHighNext;
            if counter(14 downto 0) = "111111111111110" then
                incHighNext <= ’1’;
            else
                incHighNext <= ’0’;
            end if;
            counter(14 downto 0) <= counter(14 downto 0)+1;
        end if;
    end process;
end Behavioral;
share|improve this question
add comment

1 Answer 1

up vote 5 down vote accepted

Good question! It does seem a little odd at first blush, but I assure you it makes sense.

In the first version, counter(14:0) must be all-ones before the carry can propagate all the way through to bit 15. With the path split, you test against "111111111111110", so you can tell one clock cycle earlier that the next cycle is going to generate a carry. If you examine the simulation output in a wave viewer, you'll see that incHighNext becomes 1 during the same clock cycle that counter(14 downto 0) becomes "111111111111111". At the end of that cycle, you can already tell that the high-bits-counter is going to have to increment, so you don't have to wait for the carry to propagate all the way from the lsb.

share|improve this answer
    
Thanks for the answer. I see it now: instead of cascading the values through all the bits, the LSC puts the next value for the MSC ready for collection on the next cycle; that way both counters are always settling parallel to each other. Somehow I thought the processes too were executing everything at the same time (even though it is said in the tutorial...), which confused me even more and I didn't see it. –  Mister Mystère Mar 8 at 15:49
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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