📜 ⬆️ ⬇️

River Raid on FPGA

Haven't done River Raid on FPGA yet? Ok, then I will.


Most recently, the FPGA was a black box for me, and Verilog seemed to be magic in general - how can you write a program that will build a circuit on logic elements? I planned to study this for a long time, but I didn’t even want to start without a real piece of iron.

And recently, with Aliexpress, an inexpensive and decent device based on Cyclone IV came to me, but with (at that time) a fatal flaw: documentation in Chinese. I admit, I became disheartened and even asked for advice here on Habré. Gathering my strength, I managed to launch a primitive program from the Chinese. The device blinked with a LED and I screamed “hurray” to myself. Having rummaged through the remaining examples, even when I was at the elementary level, I realized that the truth is being said: the Chinese code is terrible. I did not intend to study the crooked code, and since my hands itched, I wanted to write some simple program right off the bat. I decided that it would be ping-pong: the algorithm is primitive, and the result is spectacular. I saw the modules for working with VGA and keyboard here in the article on an FPGA-Tetris article (thanks to the authors of these modules), and the rest is a “technical matter”.

I spent a few hours on ping pong. Honestly, I didn’t want to beat my head against the wall because I didn’t understand why some things didn’t work out - the very concept of verilog was in the cut with any that I had met before.
')
By the way, the Quartus feature helped to draw logic circuits by source code. I understood how conditions, cycles, etc. are realized “in hardware”.

Having finished ping-pong, I came to the conclusion that the task turned out to be too simple and I had to choose something more interesting. It is clear that the second “project” will also be a game - it is interesting to do it and, as I said, the result is effective. From childhood came the memory of how I looked at other children playing in River Raid - I could not afford to play, it was expensive. After watching the video gameplay on YouTube, proceeded to the realization of a childhood dream. Looking ahead, here's what I got in the end:



I will say straight away: I have a couple of weeks of experience in this industry, so I don’t give advice — I could be wrong, mistaken, etc. In the comments I ask experienced comrades to correct what I am mistaken. This article is motivating. As they say "no gods burn pots" and you can.

What are my difficulties?

It is impossible to simply take and write something in a register in different places of the code, if the compiler cannot clearly state the conditions: with such - we write it, with such - this. Why this happens is clear - the input to the record at the register is one and you can not send 2 signals without any logic to one input.

We programmers love to divide code into classes, subroutines, etc. It is logical to take the block of formation of one entity into one module (in terms of verilog), and the formation of another entity into another module. But if both modules change the values ​​of the same variable?

Solving such problems is the right architecture. This is the 2nd difficulty. As far as I now see, the correct architecture in verilog is a little more important than in classical programming languages. I remember when I completed the work unit of enemies (airplanes, ships, etc.), after compiling, the number of fpga elements involved increased by several thousand and this threatened that I could not have enough elements at all and for minimal functionality! I had to redo the architecture.

Successful rule to define everything, even the width of variables. Unfortunately, I did not fully adhere to this rule and therefore, if you suddenly want to increase the screen resolution, you will have to change a couple of dozen variables.

Little about the project


The device does not have a video driver or video memory. The video signal is formed "manually." The module that does this provides 2 dynamic parameters: the current x and y are the corresponding positions on the screen at a given point in time. In order to “draw” something, you need to constantly monitor these 2 parameters and at the right time send a certain pixel color to the RGB monitor.

As you know, VGA uses an analog signal for color, but the Chinese are second-hand or connected by one digital output per RGB. As a result, I have only 8 colors in my arsenal. Of course, I tried to compensate for this with the screen resolution, but still an acceptable picture on 8 colors did not work. I tried to play: in one pass to draw a dot in one color, in another - in another, to get half tones, but nothing sensible came out, the blinking irritated.

I also want to say that I don’t really like the architecture - I managed to weakly break it into modules, since the logic of “drawing the screen” is very closely intertwined with calculations. Perhaps, if you take a breather, after a while I would (I learned it and) would revise it (the architecture).

First of all, I scrolled the river. For this there is an array in which the position of the scrolling and the rate of expansion (contraction) of the river starting from this position are indicated. Each frame I “pass” through this array from the initial position (a floating window in essence), modifying the current position X of the river bank according to the current Y coordinate and the current data in this array. The river and the islands (for them a separate array) are symmetrical in me (as in the original game) - so the right side of the river and the islands are mirrored.

Sprites for the enemies first stored in the arrays, but rather quickly I did not have enough elements fpga - I had to transfer to Cyclone IV ROM. There is a small problem with the latter. The fact is that ROM is synchronous, so in order to set the pixel address in the sprite, I need to measure (well, or half a cycle, if I use negedge) to know the coordinates of the current point relative to the upper left corner of the object. This, of course, is feasible, you just need to look for the intersection of the current point on the screen with the coordinates of the objects by shifting their coordinates to the left by 1 pixel when comparing. Since such things are being done in a loop, this additional logic would throw in a hundred elements. I decided to save money (since the elements remained in the butt) and not bother. As a result, the sprite is drawn one pixel to the right than it actually is.

There is also a bug of overlapping enemies on each other (with other objects everything is ok) - since the ROM for sprites on all enemies is the same, in order to calculate the address of which sprite to use in case of intersection of objects, you need to know which object pixel is currently transparent. There is a rather complicated logic, so I decided not to bother either.

In order to maximally break the code into entities, I implemented a kind of conveyor (state machine): at the first stage, it is checked whether it is worth changing the direction of the river, at the second whether it is necessary to place a new enemy in the FIFO, at the third stage to move the enemies, etc.

Pictures for sprites were kindly provided by Google, and mif-files were formed by a python script.

The world is traced quite a bit, for about a minute of the game, just tired - the project sawed during non-working and non-family time at night. If someone can run on their glands - I can finish

» Github Project

I express my gratitude to ishevchuk . thanks to him, I realized how powerful fpga thing is

Source: https://habr.com/ru/post/313092/


All Articles