How to fix clock hold in this code?

960 views Asked by At

I'm trying to create a VGA driver in VHDL.

I'm going for 640x480 @ 60 Hz, so I need 25 MHz and 31.5 KHz clocks. divider_h process is driven by 50 MHz clock and results in 25 MHz clock. On each tick of 25 MHz clock h_counter is incremented by process h_sync and when it reaches some value (H_FRONT + H_SYNC - 1), divider_v process is fired and sets clock_v to 1 for a brief period of time.

Quartus II's timing analysis fails and a warning appears: Can't achieve minimum setup and hold requirement clock along 1 path(s). Clock hold section in compilation report points out that MSB of v_counter is the culprit, with minimum slack time of -0.050 ns. Second lowest slack time is 0.218 ns, which is fine.

I tried to use long 1 state for clock_v with brief 0 state and minimum slack time has increased to -0.019 ns, which is still unacceptable.

As I understand, hold clock issue means that inputs are changed before they are processed correctly, so I tried to make both 1 and 0 appear for roughly same period of time. To my surprise, over 40 paths turned red with the same error.

Commenting out v_clock process fixes the problem.

Why is slack time for MSB so much higher than for other bits? What can I do to fix this issue?

Here is my code:

library ieee;
use ieee.std_logic_1164.all;

library Famikon;
use Famikon.Types.all;
use Famikon.VgaNames.all;


entity VgaDriver is
    port (
        -- inputs
        clock_50: in STD_LOGIC; -- 50 MHz
        reset: in bit; -- '1' resets
        r, g, b: in screen_pixel; -- bit_vector subtype

        -- outputs
        vga_x, vga_y: out hw_coord; -- integer subtype
        vga_drawing: out bit;
        hw_r, hw_g, hw_b: out hw_pixel; -- bit_vector subtype
        vga_hsync, vga_vsync: out bit;
        vga_blank, vga_clock: out bit
    );
end entity;


architecture Arch of VgaDriver is
    signal clock_h, clock_v: std_logic;
    signal h_counter, v_counter: vga_counter; -- integer subtype
    signal h_coord, v_coord: hw_coord; -- integer subtype
    signal h_active, v_active: bit;
begin

    -- some irrelevant code --

    h_active <= '1' when (h_counter >= H_BLANK) else '0';
    v_active <= '1' when (v_counter >= V_BLANK) else '0';


    divider_h: process (clock_50) begin
        if (rising_edge(clock_50)) then
            clock_h <= not clock_h;
        end if;
    end process;


    divider_v: process (h_counter) begin
        if (h_counter /= H_FRONT + H_SYNC - 1) then
            clock_v <= '0';
        else
            clock_v <= '1';
        end if;
    end process;


    h_clock: process (clock_h, reset) begin
        if (rising_edge(clock_h)) then
            if (reset = '1') then
                h_counter <= 0;
            elsif (h_counter = H_TOTAL - 1) then
                h_counter <= 0;
            else
                h_counter <= h_counter + 1;
            end if;
        end if;
    end process;


    v_clock: process (clock_v, reset) begin
        if (rising_edge(clock_v)) then
            if (reset = '1') then
                v_counter <= 0;
            elsif (v_counter = V_TOTAL - 1) then
                v_counter <= 0;
            else
                v_counter <= v_counter + 1;
            end if;
        end if;
    end process;


end architecture;

Details of the failing path:

  • From: v_counter[9]
  • To: v_counter[9]
  • From Clock: clock
  • To Clock: clock
  • Required Hold Relationship: 0.000 ns
  • Required Shortest P2P Time: 0.618 ns
  • Actual Shortes P2P Time: 0.568 ns
  • Minimum Slack: -0.050 ns

v_counter is a bit_vector(9 downto 0). When I use Locate in Design on this path, Quartus points to the first line of v_clock process (one with rising_edge(clock_v)).

There's also a warning about ripple clocks which mentions all h_counter bits, clock_h and three gated clocks: Equal0, Equal0~1 and Equal0~0.

2

There are 2 answers

5
Kevin Thibedeau On

It would help if you provided the start and end point of the failing path. It looks like the main problem is that you're generating different clocks and have cross domain timing issues or incomplete constraints establishing the relationship between the different domains. Presumably reset comes from the clock_50 domain and it shouldn't be directly used in the other two domains. A more robust solution would use a single clock with synchronous enables to get the same effect as clock_v and clock_h.

When generated clocks are necessary you should not be attempting to build them in the FPGA fabric but rather use an onboard PLL/DLL to manage them. The skew between domains is better controlled that way and the timing analyzer will handle the situation better. Every domain needs to have its own reset signal that is synchronized to its own clock.

It may also be a good idea to register v_active and h_active to separate the comparison logic from whatever they feed into. Plus you are using synchronous resets so there is no need to have them in the sensitivity lists.

0
AudioBubble On

Because you don't include your Famikon library packages someone would have to look up VGA timing. (Your code sample isn't a Minimal, Complete, and Verifiable example).

Jenning up something to get your design to analyze took adding these:

package Types is
    subtype screen_pixel is bit_vector (3 downto 0);
    subtype hw_coord     is natural range 0 to 1023;
    subtype hw_pixel     is bit_vector (3 downto 0);
    subtype vga_counter  is natural range 0 to 1023;
end package;
package VgaNames is
    constant H_FRONT:   natural range 0 to 1023 := 16;
    constant H_SYNC:    natural range 0 to 1023 := 96;
    constant H_BACK:    natural range 0 to 1023 := 48;
    constant H_BLANK:   natural range 0 to 1023 := H_FRONT + H_SYNC + H_BACK;
    constant H_VISIBLE: natural range 0 to 1023 := 640;
    constant H_TOTAL:   natural range 0 to 1023 := H_BLANK + H_VISIBLE;

    constant V_FRONT:   natural range 0 to 1023 := 10;
    constant V_SYNC:    natural range 0 to 1023 := 2;
    constant V_BACK:    natural range 0 to 1023 := 33;
    constant V_BLANK:   natural range 0 to 1023 := V_FRONT + V_SYNC + V_BACK;
    constant V_VISIBLE: natural range 0 to 1023 := 480;
    constant V_TOTAL:   natural range 0 to 1023 := V_BLANK + V_VISIBLE;
end package;

You also notice that by listing the blanking intervals first:

h_active <= '1' when (h_counter >= H_BLANK) else '0';
v_active <= '1' when (v_counter >= V_BLANK) else '0';

You're either going to do offset arithmetic for vga_x and vga_y or use separate counters. This can be cured by starting the non blanked portion of the display at 0 for both h_counter and v_counter.

This isn't responsible for the vcount[9] setup time issue however.

There are other issues as well.

divider_v: process (h_counter) begin
    if (h_counter /= H_FRONT + H_SYNC - 1) then
        clock_v <= '0';
    else
        clock_v <= '1';
    end if;
end process;

This looks like it's responsible for the ripple clocks. You're trying to produce clock_v by combinatorial logic. You should not do so for some valid reasons - there can be non-symmetrical delays between h_counter bits and the gates use to recognize the comparison, there are multiple levels of combinatorial logic even using LUT, and the routing delays can mismatch. Whether or not your clock_v works is up to the vagaries of layout and it wants to be registered (likely with clock_h).

It'd be extremely helpful to answerer to know who the vendor is for the target device and the actual warning and error messages. You might find fixing clock_v is sufficient, but it may not be, in which case I'd imagine as Kevin indicates it's a lack of properly constraining your design.

There's an older style VHDL VGA timing generator that came up recently on stack Exchange question (See VHDL VGA sync circuit). It's from Chapter 12 of Pong P. Chu's book 'FPGA PROTOTYPING BY VHDL EXAMPLES, XILINX SPARTAN-3 Version', file name list_ch12_01_vga_sync.vhd, found in the code listing zip download available on the author's companion website. As Brian Drummond points out it's harder to read for those unwilling to use editor search or a printout, spreading the horizontal and vertical counters across three process statements (the view of the original design specification is too flat for some tastes and has seemingly too many processes).

Brian has an additional answer which provides an example combining processes and shows nesting of the vertical counter counter in the horizontal counter. It also shows the non-blanked horizontal and vertical intervals beginning at "0000000000" (0). You could notice Brian also comments on the equivalent signals to h_active and v_active remaining un-registered.