Search code examples
vhdlfpgaintel-fpgaquartus

Can't resolve multiple constant drivers - two triggers must change the same vector


I know what the error means and why it's bad, but can't figure out how to do it in other way.

Can't resolve multiple constant drivers for net "snake[17]" at snake_driver.

(and others the same)

The assignment is that we have a "moving snake" in a std_logic_vector, which goes end-to-end, and when you press a button (toggle signal), the snake changes it's length (2, 3, 4, 5, 6, 2, ...)

Obviously, the snake vector must therefore be changed by both a process listening for toggle, and the one listening for clock. When I put both in the same process, I got an error that two edge detects can't be in the same process.

/\____    +--------------------+
toggle ---->  change length    |
          |          v         |
          |  [snake register] =====> snake 17 downto 0
\/\/\/    |          ^         |
clock  ---->  move one step    |
          +--------------------+

Any ideas welcome.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity snake_driver is
    generic
    (
        LEN : integer := 18;
        MAX_SNAKE_LEN : integer := 6
    );

    port
    (
        clk : in std_logic;
        change : in std_logic;
        output : out std_logic_vector(LEN-1 downto 0)
    );
end snake_driver;

architecture Anaconda of snake_driver is
signal snake : std_logic_vector(LEN-1 downto 0) := (0 => '1', 1 => '1', others => '0'); -- two snake pieces
signal dir_right : boolean := FALSE; -- start left
begin


    process(change)

        variable data : std_logic_vector(LEN-1 downto 0); -- two snake pieces

        variable snake_len : integer := 2; -- snake 2 long
    begin

        if rising_edge(change) then -- change snake length
            -- add snake piece based on direction

            data := snake; -- <-- here I tried to avoid the problem
                           -- by caching snake data. To no avail.

            if snake_len = MAX_SNAKE_LEN then

                snake_len := 2;

                -- shorten to 2 len

                if dir_right then
                    -- moving right, remove from left
                    data := std_logic_vector(unsigned(data) and shift_right(unsigned(data), MAX_SNAKE_LEN-2));
                else
                    -- moving left, remove from right
                    data := std_logic_vector(unsigned(data) and shift_left(unsigned(data), MAX_SNAKE_LEN-2));
                end if;
            else

                -- add one at the end
                if dir_right then
                    -- moving right, add on left
                    data := std_logic_vector(unsigned(data) or shift_left(unsigned(data), 1));
                else
                    -- moving left, add on right
                    data := std_logic_vector(unsigned(data) or shift_right(unsigned(data), 1));
                end if;

            end if;


            snake <= data;
        end if;

    end process;




    -- move snake on clock
    process(clk)
        -- variables in the process
        variable data : std_logic_vector(LEN-1 downto 0);

    begin
        -- shift the snake
        if rising_edge(clk) then

            data := snake;

            if dir_right then
                -- right move
                data(LEN-2 downto 0) := data(LEN-1 downto 1);

                if data(0) = '1' then
                    dir_right <= FALSE; -- change direction
                end if;             
            else
                -- left move
                data(LEN-1 downto 1) := data(LEN-2 downto 0);

                if data(LEN-1) = '1' then
                    dir_right <= TRUE; -- change direction
                end if;

            end if;

            snake <= data;

        end if;

    end process;


    -- send changed data to output
    output <= snake;    

end Anaconda;

Solution

  • How fast is your clock? Typically the clock in an FPGA is pretty fast (50MHz or higher). If it is fast, you will often sample your "toggle" signal on each clock edge with a delay element to detect edges. You can use this edge detect to increase the length of the snake. If you need to move the snake slowly enough for a human to detect it, you will typically only move a step every x number of clock edges (using a counter to generate a clock enable). This can all be done in a single process that is only reliant on your high speed clock edge.

    If your toggle signal is sourced from a physical switch, however, you may want to be careful about about the physical phenomenon of switch bouncing. You can avoid most bouncing issues by ignoring positive edges for a certain number of counts after detecting a negative edge. This way only the first positive edge counts when you press the switch and none of the (bouncing) positive edges are used on releasing the switch.

    If any of what I said above doesn't make sense I can go into more detail.

    Here is an example of what I said above with a snake that always rotates right, begins 2 bits wide, and has no limit on the maximum size (warning: not tested):

    architecture behav of snake is
    
    signal clk       : std_logic; --actually an input. 50MHz
    signal toggle    : std_logic := '0'; --actually an input, not an internal signal
    signal toggle_d  : std_logic := '0';
    signal snake_vec : std_logic_vector(17 downto 0) := "000000000000000011";
    signal disable_count : unsigned(20 downto 0):=(others => '0'); --about 50ms to cycle through full range
    signal step_count    : unsigned(24 downto 0):=(others => '0'); --about 0.7s to cycle through full range
    
    begin
      process(clk)
        if (clk = '1' and clk'event) then
          toggle_d <= toggle; --store previous value of toggle
          if (toggle_d = '1' and toggle = '0') then --trigger blocking counter on negative edges of toggle
            disable_count <= (others => '1');
          elsif (disable_count /= 0) then --count down if blocking counter is enabled
            disable_count <= disable_count-1;
          end if;
    
          if (toggle_d = '0' and toggle = '1' and disable_count = 0) then --extend on allowed rising edges of toggle
            snake_vec <= snake_vec or (snake_vec(0) & snake_vec(17 downto 1); --extend the snake by 1
          else
            step_count <= step_count-1; --this could be + or -
            if (step_count = 0) then --functions as a clock enable every 1 in 33.5 million cycles
              snake_vec <= snake_vec(0) & snake_vec(17 downto 0); --rotate snake
            end if;
          end if;      
        end if;
      end process;
    end behav;
    

    EDIT: with the situation you described in the comments, the best way would probably be to latch the event on the high speed clock and read it with the low speed clock. Below is an example how to do that. Note that your clock divider needs to give an output on the high speed clock for each rising edge of the divided clock (assuming a counter-based clock divider). For your project, you may want to do the high speed latching outside of the block that uses the slow speed clock - it can pass the event as an input.

    architecture behav of snake is
    
    signal clk       : std_logic; --50MHz
    signal divide_event : std_logic; --on the 50MHz domain, single clock wide pulse every rising edge of 4Hz clock, sourced from clock divider
    signal clk_slow  : std_logic; --4Hz
    signal toggle    : std_logic := '0'; --actually an input, not an internal signal
    signal toggle_d  : std_logic := '0';
    signal snake_vec : std_logic_vector(17 downto 0) := "000000000000000011";
    
    begin
      process(clk)
        if (clk = '1' and clk'event) then
          toggle_d <= toggle; --store previous value of toggle
          if (toggle_d = '0' and toggle = '1') then
            extend_event <= '1';
          elsif divide_event = '1' then
            extend_event <= '0';
          end if;
      end process;
    
      process(clk_slow)
        if (clk_slow = '1' and clk_slow'event) then
          if (extend_event = '1') then
            snake_vec <= snake_vec or (snake_vec(0) & snake_vec(17 downto 1); --extend the snake by 1
          else
            snake_vec <= snake_vec(0) & snake_vec(17 downto 0); --rotate snake
          end if;
        end if;  
      end process
    end behav;