2
votes

Im coding a snake game in VHDL using the DE2-115 FPGA from Altera. I have connected the FPGA with a monitor using VGA protocol to show the game.

I have a problem to show the snake and to move it around the monitor with all push bottom (the FPGA has 4, up/down/left/right)

In the code it is used SQ_X1 and SQ_Y1 to do the movement every moment a push bottom is in low state.

Is any other way to do the movement? without press any bottom (with a loop or something like this) and to change of direction whe the bottom is pressed to simulate the snake movement?

ARCHITECTURE behavior OF hw_image_generator IS
    SIGNAL SQ_X1,SQ_Y1: INTEGER RANGE 0 TO 1688:=400; --initial position X      
    SIGNAL SQ_X2,SQ_Y2: INTEGER RANGE 0 TO 1688:=600; --initial position Y
    SIGNAL DRAW: STD_LOGIC;                                         

BEGIN
    SQ(column,row,SQ_X1,SQ_Y1,DRAW);    -- function that print a square (head of snake with SQ variables)   
    PROCESS(disp_ena, row, column, down, up, left, right)
    BEGIN
        IF(disp_ena = '1') THEN     --display time  
            IF (column=19 or column=1901 or row=19 or row=1181) THEN  -- print the border
                red <= (OTHERS => '1'); 
                green   <= (OTHERS => '0');
                blue <= (OTHERS => '0');
            ELSE
                red <= (OTHERS => '0');
                green   <= (OTHERS => '0');
                blue <= (OTHERS => '0');
            END IF;

            IF (DRAW = '1') THEN   -- it comes from a package that print a square (head of snake)
                red <= (OTHERS => '1');
                green   <= (OTHERS => '1');
                blue <= (OTHERS => '1');
            END IF;

            --up, down right and left are input of FPGA (push bottom)
            --SQs are the current postions of X and Y and the add is to make the movement
            IF(up='0' AND down='1' AND right='1' AND left='1' )THEN
                IF (SQ_X1-40 > 20) THEN 
                    SQ_X1<=SQ_X1-40;
                END IF;        
            END IF;

            IF(up='1' AND down='0' AND right='1' AND left='1')THEN
                IF (SQ_X1+40 < 1180) THEN 
                    SQ_X1<=SQ_X1+40;
                END IF; 
            END IF;
            IF(tup='1' AND down='1' AND right='0' AND left='1')THEN
                IF (SQ_Y1+40 < 1900) THEN 
                    SQ_Y1<=SQ_Y1+40;
                END IF; 
            END IF;
            IF(up='1' AND down='1' AND right='1' AND left='0')THEN
                IF (SQ_Y1-40 > 20) THEN 
                    SQ_Y1<=SQ_Y1-40;
                END IF;
            END IF;
        END IF;

    END PROCESS;
END BEHAVIOR;
1
This desgin seem to have no clock!? How should it work without a clock? Have you simulated your code? - Paebbels
Hi Paebbels! the clock is in other module that provide the current position of the row and column so in this module does not need a clock. - July
and sorry for the spelling Ben Voigt! I am still learning english languaje! XD - July
So, if this module has no clock, who should synthesis translate this line SQ_X1<=SQ_X1+40; into hardware? Solution 1: Synthesis complains a multiple driver issue. Solution 2: Synthesis infers latches -> this is no good style and in 99.99% of designs an error. - Paebbels
another clk is handle this state machine, as the issue has been resolved! thanks Paebbels! - July

1 Answers

1
votes

First of all, I recommend you split your display and your position update functionality into separate processes to make things clearer.

You seem to already have mostly everything you need for your display process, except that, as Paebbels mentions in his comment, this should be a synchronous (i.e. clocked) process. I assume your clock is named clk.

PROCESS(clk)
BEGIN
    IF rising_edge(clk) THEN
        --display time 
        IF(disp_ena = '1') THEN 
            -- print the border                
            IF (column=19 or column=1901 or row=19 or row=1181) THEN
                red <= (OTHERS => '1'); 
                green   <= (OTHERS => '0');
                blue <= (OTHERS => '0');
            ELSE
                red <= (OTHERS => '0');
                green   <= (OTHERS => '0');
                blue <= (OTHERS => '0');
            END IF;

            IF (DRAW = '1') THEN   
                red <= (OTHERS => '1');
                green   <= (OTHERS => '1');
                blue <= (OTHERS => '1');
            END IF;
        END IF;
    END IF;
END PROCESS;

For the position updates, you will need two things:

  1. A state, which represents the current direction of the snake, and which can be acted upon by the buttons.
  2. Some form of time base to update the position.

The first point is easy to implement. First, declare the direction state signal.

type direction_t is (DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT);
signal direction : direction_t;

Then, create the direction state process.

process(clk) begin
    if rising_edge(clk) then
        if rst = '1' then
            direction <= DIR_UP;
        else
            if up = '0' then
                direction <= DIR_UP;
            elsif down = '0' then
                direction <= DIR_DOWN;
            elsif left = '0' then
                direction <= DIR_LEFT;
            elsif right = '0' then
                direction <= DIR_RIGHT;
            end if;
        end if;
    end if;
end process;

I have made the assumption here that you have a reset signal available named rst and that this reset is synchronous active-high. Adapt to your taste.

Also, please note that, while not strictly necessary for this particular example, as general best practices:

  • up, down, left and right should be properly re-synchronized if they come from another clock domain, or are completely asynchronous (as buttons are).
  • up, down, left and right should be debounced if they come directly from buttons, or other "bouncy" mechanical devices.

Finally, you need to create a clock enable signal which pulses periodically and serves as a time base for position updates. An easy way to create such a signal is usually to use a counter to divide your clock, but if the clock is too fast, as is probably your case if it is a video clock, you end up with too big a counter, which negatively affects timings. There are various solutions to this, but in your case, you can probably use your row and column signals to generate a pulse each time you reach the end of a video frame, something like this:

signal update_ce : std_logic;

-- (...)

process(clk) begin
    if rising_edge(clk) then
        -- adapt the value to your taste
        if disp_ena = '1' and column = 1901 and row = 1181 then
            update_ce <= '1';
        else
            update_ce <= '0';
        end if;
    end if;
end process;

You might have to divide the frequency of this signal again with a counter if the update is too fast, but at least your counter won't have to count in the millions. Given this signal, you can now update your position like so:

process(clk)
begin
    if rising_edge(clk) then
        if rst = '1' then
            -- adapt starting position to your taste
            SQ_X1<= 100;
            SQ_Y1<= 100;
        elsif update_ce = '1' then
            case direction is
            when DIR_UP =>
                if SQ_X1 - 1 > 20 then 
                    SQ_X1 <= SQ_X1 - 1;
                end if;

            when DIR_DOWN =>
                if SQ_X1 + 1 < 1180 then 
                    SQ_X1 <= SQ_X1 + 1;
                end if; 

            when DIR_LEFT =>
                if SQ_Y1 - 1 > 20 then 
                    SQ_Y1 <= SQ_Y1 - 1;
                end if;

            when others =>
            -- DIR_RIGHT
                if SQ_Y1 - 1 > 20 then 
                    SQ_Y1 <= SQ_Y1 - 1;
                end if;                    
            end case;
        end if;
    end if;
end process;