vhdlshift-register

1-cycle enable signal in a clocked process


I am taking a vhdl online course. One of the laboratory work is: "Based on frequency divider and 8-bit cyclic shift register implement a ring counter with a shift period of 1 s."

The task says that the most significant bit of the counter cannot be used as the clock signal of the shift register (i.e. in the if rising_edge (shifter (MSB)) construction. It is necessary to form the enable signal as a strobe.

I did the job. The result is accepted.

I have a question related to shift register by enable.

    shift_reg_proc : process(clk)
    begin
        if (rising_edge(clk)) then
            if    (srst = '1') then
                shift_reg <= "10000000";
            elsif (en = '1') then
                shift_reg <= shift_reg(0) & shift_reg(7 downto 1);
            end if;
        end if;
    end process shift_reg_proc

If the duration of the enable signal is 1 period clk, then there is a probability that at the moment of rising_edge (clk) the en signal level will not have time to become = 1. If this is the case, then it is not guaranteed that the register shift will occur in the next second. Is there any "correct" way to do this task? Is it so? Is my decision correct? Is the lab clue misleading?

I am attaching the implementation code, test bench and wave image.

ring_counter.vhd

--------------------------------------------------------------------------------
-- Based on frequency divider and 8-bit cyclic shift register implement a ring 
-- counter with a shift period of 1 s.
--------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;

entity ring_counter is
    
    port(clk  : in  std_logic;
         srst : in  std_logic;
         dout : out std_logic_vector(7 downto 0);
         en_o : out std_logic
    );

end entity ring_counter;

architecture behave of ring_counter is

    signal   cntr             : std_logic_vector(26 downto 0) := (others => '0');
    signal   cntr_msb_delayed : std_logic;
    signal   shift_reg        : std_logic_vector(7 downto 0);
    signal   en               : std_logic;
    constant cntr_msb_num     : integer := 4; -- 26 for DE board, 4 for test bench 

begin
    
    -- signal for test bench
    en_o <= en;

    --------------------------------------------------------------------------------
    -- Counter implementation
    --------------------------------------------------------------------------------

    cntr_proc : process(clk)
    begin
        if (rising_edge(clk)) then
            if (srst = '1') then
                cntr <= (others => '0');
            else
                cntr <= unsigned(cntr) + 1;
            end if;
        end if;
    end process cntr_proc;

    ----------------------------------------------------------------------------
    -- Shift register implementation
    ----------------------------------------------------------------------------

    shift_reg_proc : process(clk)
    begin
        if (rising_edge(clk)) then
            if    (srst = '1') then
                shift_reg <= "10000000";
            elsif (en = '1') then
                shift_reg <= shift_reg(0) & shift_reg(7 downto 1);
            end if;
        end if;
    end process shift_reg_proc;
    
    dout <= shift_reg;

    ----------------------------------------------------------------------------
    -- Enable signal generation
    ----------------------------------------------------------------------------

    -- Counter MSB delay for 1 period of clk
    delay_proc : process(clk)
    begin
        if (rising_edge(clk)) then
            cntr_msb_delayed <= cntr(cntr_msb_num);
        end if;
    end process delay_proc;

    en <= cntr(cntr_msb_num) and not cntr_msb_delayed;


end architecture behave;

ring_counter_tb.vhd

library ieee;
use ieee.std_logic_1164.all;

entity ring_counter_tb is
    
end entity ring_counter_tb;

architecture behave of ring_counter_tb is

    component ring_counter is
        port(clk  : in  std_logic;
             srst : in  std_logic;
             dout : out std_logic_vector(7 downto 0);
             en_o : out std_logic
        );
    end component ring_counter;

    signal clk  : std_logic;
    signal srst : std_logic;
    signal dout : std_logic_vector(7 downto 0);
    signal en_o : std_logic;

    constant clk_period : time := 4 ns;

begin

    dut : ring_counter
        port map (
            clk  => clk,
            srst => srst,
            dout => dout,
            en_o => en_o
        );

    clk_gen : process
    begin
        clk <= '0';
        wait for clk_period;
        loop
            clk <= '0';
            wait for clk_period/2;
            clk <= '1';
            wait for clk_period/2;
        end loop;
    end process clk_gen;

    srst <= '0',
            '1' after 100 ns,
            '0' after 150 ns;

    
end architecture behave;

wave for test bench


Solution

  • TL;DR

    The rising edge of clk after which en is raised is not the same as the rising edge of clk at which your shift register shifts. en is asserted high after rising edge N and de-asserted after rising edge N+1. Your shift register is thus shifted at rising edge N+1.

    So you have about one clock period delay between assertions of en and the register shifts. You don't care because your specification says that you want a shift period of 1 second. As long as en is periodic with a period of one second, even if there is a small constant delay between en and your shift register, you fulfill the specifications.

    But what is of uttermost importance is that, as it is seen by your shift register, en is asserted high sufficiently after rising edge N to avoid a too early shift and de-asserted sufficiently after rising edge N+1 to allow a good nice shift. If you are interested in this too, please continue reading.

    Detailed explanation

    Your en signal is computed from the outputs of registers synchronized on the same clock clk as your shift register. You cannot have any hold time problem there: the propagation delay from the rising edge of the clock to the outputs of your cntr and cntr_msb_delayed registers guarantee that en will arrive at your shift register sufficiently after the rising edge of the clock that caused it (assuming you don't have large clock skews). It cannot arrive too early.

    Can it arrive too late (setup time problem)? Yes, if your clock frequency is too high. The clock period would then be too short, en would not have enough time to be computed, stabilize and propagate to your shift register before the next rising edge of the clock and anything could happen (no shift at all, partial shift, metastabilities...)

    This is a very common concern in digital design: you cannot operate at an arbitrarily high clock frequency. If you could you would clock your own computer at yotta-Hz or even more, instead of giga-Hz, and everything would become instantaneous. It would be nice but it is not how the real world works.

    In a digital design you always have what is called a critical path. It is a particular chain of logic gates between a set of source registers and a destination register, along which the propagation delay of electrical signals is the largest of the whole design.

    Which path it is among all possible and the total delay along this path depend on your design's complexity (e.g. the number of bits of your counter), your target hardware technology (e.g. the FPGA of your prototyping board) and the operating conditions (temperature, voltage of power supply, speed-grade of your FPGA).

    (Yes, it depends also on the temperature, reason why hard-core gamers cool down their computers with high performance cooling systems. This avoids the destruction of the silicon and allows to operate the computer at a higher clock frequency with more frames per second and a better user experience.)

    The largest time it takes for the signals to travel from the source clock-edge to the arrival at destination, augmented by a small security margin called the setup time of the destination register, is the smallest clock period (highest clock frequency) at which you can run your design. As long as you don't exceed this limit your system works as expected.

    Hardware design tool chains usually comprise a Static Timing Analyzer (STA) that tells you what this maximum clock frequency is for your design, your target, and your operating conditions. If it tells you 500 MHz and you need only 350 MHz, everything is fine (you could however investigate and see if you could modify your design, save some hardware, and still run at 350 MHz).

    But if you need 650 MHz it is time to roll up your sleeves, look at the critical path (the STA will also show the path), understand it and rework your design to speed it up (e.g. pipeline long computations, use carry look ahead adders instead of carry ripple...) Note that, usually, when you encounter timing closure problems you do not consider only one critical path but the set of all paths that exceed your time budget because you want to eliminate them all, not just the worst. This is why the STA gives you not only the worst critical path but a list of critical paths, in decreasing order of severity.