# Why does splitting a FPGA critical path work to improve performance?

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;

-

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.