【组原课设团队任务】FlyBird+FPGA+RISCV

小兔子乖乖队:使用FPGA设计与实现Flybird

B站地址https://www.bilibili.com/video/BV1Km4y1c78B/?vd_source=d3e5165825082cd17457aab2378b8f54

项目整体设计

经过个人任务的实现,本队拥有4个logisim实现的CPU以及1个FPGA实现的CPU,logisim工具中可展示的部分有限,而FPGA可以利用开发板上的各个接口实现更丰富的功能,可以更好地展示。本队经过讨论,选择做FlyBird,因为它不需要编写复杂的指令,对于课程设计中的支持24条指令的CPU友好,并且需要外部按键中断,能够充分展示CPU的功能。

nexys4ddr开发板支持VGA显示,板支持的RGB格式为RGB444,对于不同的显示分辨率以及屏幕刷新频率,需要采用不同的时钟频率。由于开发板上的存储空间有限,显示的图片需要占用比较大的空间,所以选择了显示分辨率为640x480,刷新频率为60hz,需要给VGA提供的时钟频率为25.17mhz。
由于flybird不需要很大的显示空间,按照图1,将屏幕分为5部分,其中中间的300x300区域为游戏区域,其余部分为静态展示。

图 1 显示设计图

制作背景图片以及bird的图片,并利用matlab将图片转换coe格式,利可以用vivado提供的IP核的功能,将coe文件存入到ROM中。
为了能够实现VGA的显示,需要将鸟的纵坐标,第一个柱子的坐标,第二个柱子的坐标存储下来,鸟的横向坐标固定,所以需要5个寄存器维护以上信息。由于当前的CPU不能直接维护以上5个寄存器,要在VGA显示和CPU之间加一个寄存器组,用来维护信息。VGA显示直接从寄存器组中并行的读取信息进行显示。而CPU则需要传送信息给寄存器组。如图2所示。

图 2 顶层结构设计图

由于CPU无法直接和寄存器组交互,将CPU中的ECALL指令功能进行改写,根据 a 7 的值,将 a7的值,将 a7的值,将a0的值送到寄存器组的某个位置,串行地将数据维护。规定$a7寄存器的值为1,2,3,4,5时,分别将数据送往寄存器组中的对应的五个寄存器。

为了实现交互,还需要实现中断,需要两个按键中断,以及一个时钟中断,包括开始游戏按键,上升按键,以及时钟中断(控制鸟自动下降和柱子自动左移)。将个人任务中FPGA实现的CPU进行更改,添加中断uret指令,添加EPC、IE寄存器、中断向量表以及中断采样电路,为了简化电路,采用单级中断实现。

编写汇编代码,主要分为三个中断函数,并且在汇编中采用伪随机数的方式实现柱子的高度变化,为了简化汇编的编写,尽量直接使用寄存器进行运算,为了减少读写数据存储器的次数等进行保护现场、恢复现场的操作,将x1-x5寄存器和VGA与CPU交换数据的寄存器组中的5个寄存器对应,其余寄存器可以自由使用。
使用RARS将汇编代码转换成机器码,在vivado中读取,将以上部分连接起来,完成整个项目,上板检验成果。

队员分工

① 刘景宇
实现VGA显示模块,添加中断处理逻辑,实现CPU与VGA数据交换,协助刘从政编写汇编。
②刘从政
编写flybird包含中断的汇编代码。
③张传飞
寻找图片,制作要显示的图片,协助张锦程制作展示ppt。
④张锦程
将图片转换成coe格式,负责制作团队任务展示ppt。
3.任务实现与调试
①设计制作显示的背景图片,得到的图片如下:

图 3 背景图
②将转换成coe文件,利用matlab转换代码,将png图片转换成coe格式
背景图和小鸟图制作好后,先将背景图和小鸟图分别像素设置为640x480和25x25;再将.png文件修改文件名转化为.bmp文件,最后通过Matlab代码将.bmp文件转为rgb444彩色图像并制作coe文件。
307200由640x480计算得出,此为转化背景图;若要转化小鸟图,则修改为625即可。
③编写维护小鸟和柱子坐标的汇编代码
主函数初始化各个坐标,然后为一个死循环,直到遇到中断时跳转到相应的中断处理函数,在其他函数中使用uret指令完成中断返回。对于柱子的纵坐标设计随机数函数使得坐标在正确范围内伪随机,主程序的汇编代码如下,其余代码放在附件中,其中对ecall指令按照以上的规定使用。

	addi x1,zero,0	    # 初始化,不是中断:开始游戏
	addi x2,zero,150   # bird的纵坐标
	addi x3,zero,200   # 第一个柱子的横坐标
	addi x4,zero,110   # 第一个柱子显示的位置	
	addi x5,zero,380   # 第二个柱子不显示出来(两个柱子始终横向始终差180)
	addi x6,zero,140  
	addi a0,x2,0       # 系统调用,方便VGA显示,ecall根据a7的内容,将对应的数值送到对应的位置(系统调用)
	addi a7,zero,1	    # bird的y
	ecall
	addi a0,x3,0       # 第一个柱子的x
	addi a7,zero,2
	ecall 
	addi a0,x4,0       # 第一个柱子的y
	addi a7,zero,3
	ecall
	addi a0,x5,0	    # 第二个柱子的x
	addi a7,zero,4
	ecall  
	addi a0,x6,0       # 第二个柱子的y
	addi a7,zero,5
	ecall
main: 
	nop 		    # 缓冲一下
	j main

④完成VGA显示和CPU更改的硬件设计。
新添加的单级中断处理部分的代码如下,其余部分代码放在附件中,其中要特别注意中断采样电路的实现,在调试中经常出现一个错误,在两个always中对同一个wire进行赋值会报错,尽管他们的触发条件不同(仍可能冲突),就必须进行更改。实现逻辑和个人任务中的logisim实现逻辑类似。

reg [31:0] pc_int1;
reg [31:0] pc_int2;
reg [31:0] pc_int3;
reg int1,int2,int3,intA,intB,intC;      //保留当前的中断中断信号
reg [1:0]intnum;                   //当前中断处于处理的部分
wire [1:0]intsel;                   //产生的中断中最大的中断号
reg [31:0]epc ;
    reg int_en ;                      //中断使能信号
    wire int_sig;                            //表明有中断
	assign   int_sig = int1 | int2 | int3;        //表明当前是否有中断信号
	initial begin
	    pc_int1 <= 32'h0000005c;
        pc_int2 <= 32'h00000078;
        pc_int3 <= 32'h00000084;
        epc <= 32'h0;
	    int_en <= 1'b1;int1 <= 0;int2<=0;int3<=0; intA<=0;intB<=0;intC<=0;
	end
	always@(posedge btn_start or posedge int1) begin if(int1)intA=0;else intA=1;end           
	always@(posedge btn_up or posedge int2) begin if(int2)intB=0;else intB=1;end
	always@(posedge clk_int or posedge int3) begin if(int3)intC=0;else intC=1;end	
	assign intsel=(int1==1) ? 2'b11:((int2==1) ? 2'b10 :(int3==1 ? 2'b01:2'b00 ));
     always@(posedge clk)begin        //中断模块,同时替代了PC寄存器
        int1=int1|intA;                 //主要起一个同步的作用
	    int2=int2|intB; 
	    int3=int3|intC; 
        if(uret)    begin                   //uret指令,中断返回指令
            pc_new = epc;
            int_en = 1;               //开中断
            case(intnum)               //根据产生中断时的内容进行处理
                2'b11: int1<=0;        //同步清零
                2'b10: int2<=0;
                2'b01: int3<=0;
             endcase
            end
        else if(int_sig & int_en)begin      //有中断信号并且处于开中断 
             int_en = 0;                  //关中断
             epc = pc_normal;
             case(intsel)               //根据当前的中断选择
                2'b11: begin intnum=2'b11; end     //记录下是谁产生的中断
                2'b10: begin intnum=2'b10; end
                2'b01: begin intnum=2'b01; end
            endcase
            case(intnum) 						//根据当前的中断选择,intnum中才是存放的当前是哪个中断
                2'b11: begin pc_new=pc_int1; end
                2'b10: begin pc_new=pc_int2; end
                2'b01: begin pc_new=pc_int3; end
            endcase
            end
        else pc_new=pc_normal;
    end   
    always@(posedge clk)
    begin
        if(ecall)
        begin
            case(Rd_data1)
                32'd1:bird_y <= Rd_data2;
                32'd2:pillar1_x <= Rd_data2;
                32'd3:pillar1_y <= Rd_data2;
                32'd4:pillar2_x <= Rd_data2;
                32'd5:pillar2_y <= Rd_data2;
            endcase
        end
    End

对于VGA显示模块,参考VGA实现原理,将显示模块和自己的功能适配,为了简化实现,将整个图片coe使用IP核ROM存储下来,同时为了避免显示时冲突,设置控制信号,确认当前显示像素点的坐标,从而确定当前显示鸟的rgb,柱子的rgb还是背景的rgb,按照以上逻辑编写代码,代码放于附件中。
将整个项目整合,文件结构如下图,其中top为整个项目,riscv_top为cpu部分,vga_display为vga显示部分,顶部视图如图4所示。

图 4 顶部视图

图 5 顶层文件

⑤效果图展示。

图 6 效果图展示

你可能感兴趣的:(fpga开发)