这是2011下半年我在这边第一年的一个期末课程项目。课程是嵌入式系统。我利用一个FPGA开发板做了一个VGA视频输出的弹球游戏。自认为做得不错,课程拿了A+,也因为这个作业通过了暑期实习面试。现在拿出来分享。
平台:
该课程使用的是Sparten 3e starter board开发板。开发板信息及购买地址:http://www.digilentinc.com/Products/Detail.cfm?NavTop=2&NavSub=423&Prod=S3EBOARD&CFID=11126446&CFTOKEN=5eb3071295cc867b-F8FA2FD5-5056-0201-02D18B76539FBE9D
实现效果:
在这个开发板上我做了个弹球游戏。该弹球游戏有上下两个挡板互相弹球,一个挡板由板上按键控制,另一个挡板由一个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键盘的控制部分,工作量跟我比小了很多)
frameborder="0" width="640" height="498" src="http://v.qq.com/iframe/player.html?vid=i0150qodz1d&tiny=0&auto=0" allowfullscreen="">源代码:
源代码可以在我的GitHub里找到:
https://github.com/shallen320/Ping_pong_Sparten3e
报告:
最后附上我的课程报告。全英文的不翻译了,大家凑合看吧
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->nm){
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.