基於FPGA的VGA視訊彈球遊戲
這是2011下半年我在這邊第一年的一個期末課程專案。課程是嵌入式系統。我利用一個FPGA開發板做了一個VGA視訊輸出的彈球遊戲。自認為做得不錯,課程拿了A+,也因為這個作業通過了暑期實習面試。現在拿出來分享。
平臺:
實現效果:
在這個開發板上我做了個彈球遊戲。該彈球遊戲有上下兩個擋板互相彈球,一個擋板由板上按鍵控制,另一個擋板由一個PS/2鍵盤控制,視訊通過開發板的VGA口輸出到普通顯示器上,同時板上液晶顯示器顯示比分。
實現思路:
PS/2介面,按鍵,液晶顯示介面都是使用的現成的IP。
VGA顯示介面是自己通過VHDL寫的。
主程式是Xilinx Sparten 3e FPGA的Microblaze核心作為處理器上C語言程式設計實現的。軟體程式設計使用了qp-nano狀態機。(qp-nano:http://www.state-machine.com/qp/qpn/)
主要難點是VGA介面和軟體實現。
VGA介面主要要弄清楚訊號的時序。這個在開發板手冊上有說明。我使用的是640x480 @60hz的解析度,相應的訊號模式間下圖:
其次要顯示圖片就得有視訊記憶體。視訊記憶體我用的是FPGA內部的BRAM,速度快但容量小。由於Microblaze微處理器需要32KB的記憶體,我只剩下8KB的視訊記憶體可用。由於BRAM一個位元組還預留了個校驗位,所以實際上可用空間是8K×9bit。一個畫素只要3個bit,顯示8種顏色足以。最後有效的顯示區域為128×192。這個解析度對於這個小遊戲來說足夠了,要知道gameboy只有160x144的解析度,但照樣有很多偉大的遊戲。
還有個難點就是和軟體的介面問題。Microblaze使用的是PLB介面。大部分介面部分的VHDL Xilinx已經幫你寫好了。我只弄了兩個暫存器,一個賦予要寫的地址畫素資訊,一個控制寫入。
軟體相比VHDL來說就簡單了許多,只要對上面設定好的暫存器寫畫素資訊即可。我當時使用了個狀態機控制遊戲,具體怎麼做忘了,實現起來應該不難。
實際效果:
實際執行效果我弄了個視訊,如下:
(視訊中那個人是我的partner,他主要負責了PS2鍵盤的控制部分,工作量跟我比小了很多)
原始碼:
原始碼可以在我的GitHub裡找到:
報告:
最後附上我的課程報告。全英文的不翻譯了,大家湊合看吧
VGA PingPong Game
Objective:
The goal of the project is to build a video game device on the Xilinx Spartan 3E Starter Board. In order to achieve this,we implement a VGA and PS/2 keyboard interface as the output and input of the game. Xilinx doesn’t provide any VGA interface in EDK, we make our own using VHDL. The PS/2 interface is ready made by Xilinx in EDK so we can directly use it.
The game is a 2 players game that one play versus another. Each player controls a paddle to bounce the ball to its rival.If the ball is missed by either of the player, he or she loses one point. The player who loses 3 points loses the game.
We have a VGA monitor as the output to display the game graphic with a resolution of 128*192. The on board LCD screen displays the scores and shows who wins.
We use the on board buttons and keyboard separately to control the two paddles because we find that the PS/2 interface cannot recognize two key at the same time. On board buttons West and East control the blue paddle. The keyboard A and S key control the green paddle. If the space key on the key board is pressed, the blue paddle emits the ball and starts the game.
VGAmodule
The block diagram of the VGA design is as follows:
Vga_sync:
This module is to generate the synchronizing signals VGA_HSYNC & VGA_VSYNC and outputs RGB color signalsVGA_RED, VGA_GREEN, VGA_BLUE.
The output resolution of the VGA signal is 640*480 @ 60Hz. The timing of VGA_HSYNC and VGA_VSYNC is specified in the following form:We use a 25Mhz clock to drive the module,each period corresponds to each pixel. During the 480 lines and 640 cycles each line, the RGB signals have valid output, thus giving colors to pixels on the monitor.
The essence of the module is two counters,one counts the number of pixels per line, and one counts lines per frame. During the RGB valid time, the address of display memory is generated according to the count number thus RGB signals can be read from the memory.
The variable ‘h_count’ is the number of pixels in a line, and ‘v_count’ is number of lines in a frame. ‘Addr’ is the current reading address of frame buffer. Since one address stores the RGB signal of three pixels (explained below), we have another variable ‘remain’ indicates the number of current pixel, can only be 0, 1 or 2. Variable ‘pix’ is the current number of pixel in a frame.
Following is a screen shot of our simulation result.
Clock:
The clock module is a clock divider todived the input 50Mhz clock signal to 25Mhz to drive the vga_sync.
Vga_ram:
This is the display memory. We use the remaining on chip Bram as the memory. Sparten3E has 20 blocks of Bram while Microblaze uses the 16 of them as a 32KB internal memory, so we can only use the 4 blocks left. Each block has a 2K * 9bit storage, so in total that serves 8K * 9bit storage. Each pixel need 3 bit space to store RGB value, so we can store 24K pixels, so we make the display area to 128*192.
The vga_ram is a dual port ram, with one input, one clock, and two outputs. One output is connected to vga_sync module to provide the in storage RGB signal, the other output is sent to DMA module and is used to calculated new data to write with. For example if the content of 0x24 is 001110101, and we need to change the second pixel from color yellow to color red, so DMA will need to read 0x24’s content 001110101 and change the second pixel to 100 and write 001100101 back to 0x24 in the vga_ram.
DMA:
This module connects the microblaze PLB bus to VGA for microblaze to write pixel signal to vga_ram. We implement tworegisters to achieve the functionality, their relative address is 0x0 and 0x4 and both 32 bits wide but only few bits are writable.
Pixel Register: VGA_base_address+0x0:
Bit[29 30 31]: RGB signal. Bit 29 is valueof red, bit 30 is green, bit 31 is blue. Each value is either 1 or 0, togetherone pixel can have 8 different colors.
Bit[27 28]: This two bits specifies whichpixel the RGB value give to. Each address corresponds to a 9 bit value, whichstores 3 pixel, if bit[27 28] is “00”, it points to the first pixel; “01” thesecond; “10”the third. If it is “11” that will clear the 9bit to all zero,which can be utilized when clear the screen.
Bit[14:26]: This area specify the addressof vga_ram. It can reach 8K address, together with NUM area, the register can reach every 24K pixel. The ADDR bits are always connected to the one of address port of vga_ram.
Other bits in the register are reserved.
Write Control Register:VGA_base_address+0x4:
Bit[31]: If this bit is written with 1, thecontent of Pixel Register will be written into vga_ram. For example if the content of Pixel Register is 1010001001001 01 101, when bit[31] of Write Control Register is written 1, the DMA will make the 2nd pixel in addr1010001001001 to be magenta. If the writing operation is complete, the bit[31]will be cleared to 0 automatically.
Bit[30] is read only, which reflex whetherthe writing operation is complete. If it is busy, the bit will be 0, otherwisethe bit is 1 indicating ready for another writing operation.
Other bits in the register are reserved.
We use a finite state machine to implemen tthe design. The writing process consists of 4 state, st1_read, st2_gen,st3_write, st0_null. The default sate is st0_null, with a low ‘wea’ and highbit[30]. When the bit[31] of write control register becomes 1, the statemachine starts and goes into the first state st1_read. At st1_read, the content of vga_ram at the writing address is read, bit[30] is asserted low and clearthe bit[31], then it goes to st2_gen, in which state the data to written withis generated. After st2_gen, st3_write is the state to write vga_ram with ahigh ‘wea’ signal, which is the write enable, to vga_ram. After st3_write, thestate machine change to st0_null state and bit[30] is asserted high again.
In the previous example, the data 001110101 at addr 0x24 is read at st1_read, and in st2_gen, we generate the new data 001100101 as we want to change the second pixel to red. In st3_wirte, we assert the write enable signal ‘wea’ to high to write new data to 0x24 and de-assertit in st0_null with a bit[30] to be 1 again.
The following screen shot is from our simulation result. In this simulation, the content of pixel register is 0x00aa0aa0 which is to change the first pixel from white to black at addr 0x1055.PS/2 interface
In our project we just use PS/2 interface in the XPS, and build the code based on that. The PS2 protocol consists of host-to-device and device-to-host communication. Since we just need to read the keyboard button, we just need device-to-host communication. In the project we use polling to implement this. When you press one button, keyboard writes the data into receive data register .RXx_Full_Status flag in the status register is set when the data packet is received by the Portx SIE and is waiting for the host to read this data packet. XPs2_Recv function can only receive one byte and store it in the buffer to wait for the host to read it.Graphic:
By writing the two registers at addrXPAR_VGA_0_BASEADDR+0x0 and XPAR_VGA_0_BASEADDR+0x4 we can draw our gamegraphic interface pixel by pixel. Below is an example of the function to draw pixels
void VGA_write_coord(int x, int y, int color){ //VGA write coordinates
int pixel, remain, addr, data;
pixel = x + y*128;
remain = pixel%3;
addr = pixel/3;
data = (addr<<5) + (remain<<3) + color; //calculate the data to write
VGA_mWriteReg(XPAR_VGA_0_BASEADDR, 0x0, data); //
VGA_mWriteReg(XPAR_VGA_0_BASEADDR, 0x4, 0x1); //write
}
Ball coordinates calculation
Since we still cannot get the floatpoint calculation to work on microblaze, the coordinates of the ball are all calculated in integers. The basic idea is to determine the path of ball using two point, original point and destination point
Below is the structure of the ball:typedef struct ball_tag{
int x; //current x coordinate
int y; //current y coordinate
int x_d; // x coordinate of destine point
int y_d; // y coordinate of destin point
int x_o; // x coordinate of original point
int y_o; // y coordinate of original point
int q; // q=m>n?m/n:n/m
int r; // q=m>n?m%n:m%n
int a; // a=(q-r)/r
int b; // b=(q-r)%r
int m; //m=abs(x_d-x_o)
int n; //n=abs(y_d-y_o)
}ball;
The q,r,a,b,m,n are parameters to helpcalculate the path
Below is the code sample for ball to move at the direction with increasing y and increasing z;
if(bal->n>bal->m){
bal->y++;
dif=bal->y-bal->y_o;
if(bal->r==0)
bal->x=bal->x_o+dif/bal->q;
else {
del=(dif-((dif+1)/(bal->a+1)))/bal->q;
bal->x=bal->x_o+del;
}
}
else if(bal->n<bal->m){
bal->x++;
dif=bal->x-bal->x_o;
if(bal->r==0)
bal->y=bal->y_o+dif/bal->q;
else {
del=(dif-((dif+1)/(bal->a+1)))/bal->q;
bal->y=bal->y_o+del;
}
}
else { // the angle here is 45 degree
bal->x++;
bal->y++;
}
FSM in QP-nano
We only usetwo state in the QP-nano(code size limitation), and one is game_preset stateand another is game_ongoing state. In game_preset state we need to draw theball and draw paddle and enable to player to move the paddle with ball without firingthe ball. Once the space is pressed the game begins and transit to the game_on going state,which involves many events just as shown below in the FSM
Then we use LCD to present the mark andshow who is the winner.
Difficulties
When I imported the HDL design into the Xilinx Plat form Studio Project, I found that it did notwork as I expected, the pixels I write in software went all black, and the three pixels under the same address became black together. I realized that itis a problem of writing vga_ram, that data could not be written properly,instead, they are all written zero. Since it is really difficult to simulateperipheral design with the PLB bus, I had a really hard time tried to find out what went wrong. Then I realized that I can convert the peripheral into a FPGA project with the similar PLB peripheral functionality and to simulate it as aFPGA project. It still took me three days to find the problem becauseeverything behaved correctly in the waveform simulation. Finally I found outthe problem is that the data signal becomes invalid simultaneously with ‘wea’signal became 0 from 1. Then I prolonged the data signal hold time and the problem finally resolved.
For the PS/2 interface, at first we want to use interrupt mode to read the scan code from the keyboard, because in interrupt mode XPs2_Recv can receive data and interrupt handler will continue receive data and store it in buffer. You can define the buffer size as your will. But we can only write into buffer withnumber of bytes equals to the buffer size and cannot clear the buffer after thehost reads that. In this case, the host cannot response even we press morebuttons.
Anotherdifficulty is to calculate the ball’s path without using float point, but afterfew hours thinking, I found a good way to approximate the path to a smooth skewline with integers.
To be improved
Limited by theBram, the VGA can only display a small area at the left up corner at thescreen. Given more time, actually we can move the display area to the center ofthe screen and zoom in to a double size.
It takes us three weeks to finish the VGA peripheral and thus we do not have enough time to polish the software game. There are still many bugs and the features are limited,for example the ball’s speed is too slow and it is too easy not to miss theball. Besides my original intention is to add a title screen to the game and add a single player mode, thus players can select 1 player or 2 players at the title screen. Another reason we cannot add this feature is that we are runningout of the 32KB ram memory. I think this can be solved if we make our codeshorter or to store the code in SDRam
Another problem is that the PS/2 keyboard performs not so well in the polling mode. It is really slow to correspond to a key pressed down. If we have time, we will change it to interrupt mode which may achieve better performance.