• 【组原课设团队任务】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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ④完成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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

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

    图 4 顶部视图

    图 5 顶层文件

    ⑤效果图展示。

    图 6 效果图展示

  • 相关阅读:
    java基于微信小程序的校园服务平台 uniapp 小程序
    医疗与大模型:重塑未来医疗生态的营销之道
    【数据结构】链表中二级指针的应用
    async/await 原理及执行顺序分析
    vuex的辅助函数
    海外反欺诈解决方案专家ADVANCE.AI与印度尼西亚第一家替代信用评分提供商SDB达成战略合作
    计算机毕业设计ssm+vue基本微信小程序的南通农商银行微银行系统
    【电源专题】为什么旁路/去耦电容这么重要?在PCB Layout时应该注意什么?
    java反射高级用列(脱敏+aop)
    开学了,如何用python开发个上课点名小程序~
  • 原文地址:https://blog.csdn.net/jingyu_1/article/details/127605910