作业内容:
使用Verilog HDL设计简单的RISC处理器,简称为处理器Z,具体要求如下。
作业要求:
- 作业共分为 5 个基础任务和一个进阶任务,请务必按照顺序完成。
- 推荐使用** Vivado**完成本次作业,如果电脑遇到vivado安装问题,也可以使用其他仿真软件,例如Modelsim等。
- 最终作业的提交仅需要一份**PDF的实验报告,命名要求:学号-姓名-处理器报告.pdf**。
- 报告中应包括对于基础任务5(已经实现的基础完整处理器)的设计思路,数据和控制通路(在ppt绘图工具中以框图形式画出提交截图),testbench代码及对于关键代码的注释,Vivado仿真结果截图及结果说明。
- 如实现了进阶任务,报告中应体现对附加要求的设计思路以及关键代码,testbench代码及代码说明,Vivado仿真结果截图及结果说明。
- 报告的附录中应包括一份最终实现的源代码(保存为xxx.v)及对于代码文件说明(保存为readme.txt)。
- 评分:本次作业共计 20 分
- 必须完成5个基础任务和1个进阶任务(可以选择1.1与1.2 或选择 2.1与2.2)
- 基础任务的设计思路,数据和控制通路框图,testbench代码以及代码说明,Vivado仿真结果及结果说明各占2分,共计 10分。
- 进阶任务,第一问占5分,第二问占5分。
- 如存在抄袭,则抄袭者与被抄袭者均为 0 分。
处理器Z说明:
-
处理器Z只包括取指、译码、执行、写回、更新PC五个操作。(处理器Z包括如下六个操作:取指、译码、执行、访存、写回、更新PC。)
-
处理器Z将指令字长和数据字长均固定为 4 个字节 (32bit)。 因此对于取指操作,每个时钟周期只需要从ram中读取 4 个字节的指令即可。
-
指令集说明:处理器Z只包括 5 条指令,分别为IRMOV, ADD, SUB, AND, XOR。
-
寄存器说明:处理器 Z 的片上寄存器只包括 6 个 32 位寄存器,分别命名为 %r0, %r1, …, %r5。
-
指令编码说明:处理器Z的指令长度固定为 4 个字节(32bit)
-
第一个字节(31:24)表示指令类型,高 4 位表示指令代码icode,低 4 位表示指令功能ifun;其中IRMOV的指令代码为 1 ,功能代码为 0 ;ADD的指令代码为 2 ,功能代码为 0 ;SUB的指令代码为 2 ,功能代码为 1 ;AND的指令代码为 2 ,功能代码为 2 ;XOR的指令代码为 2 ,功能代码为 3 。
-
第二个字节(23:16)表示操作数所在寄存器的ID,其中高 4 位表示rA,低4 位表示rB;
-
最后两个字节(15:0)表示立即数valC。
-
例:指令AND %r4, %r5 (%r4 = %r4 and %r5 ),将被表示为22_45_00_00;指 令 IRMOV $16, %r0 (%r0 = 16),将被表示为10_f0_00_10。
-
关于课程设计的说明:
src文件夹中已经提供了一些基础模块的设计源代码,你要做的是按照每个基础任务的要求对设计代码进行修改并完成相应的测试仿真!
在以下任务中,所有的指令都是通过在tb先逐个写入存储器,再令working = 1使处理器工作。大家也可以使用hex文件将指令初始化入存储器,这样就可以省去working接口并简化tb,具体选择哪一种方式可以根据自己喜好自由选择。
基础任务 1 :实现取指以及更新PC操作。
具体要求:
-
任务 1 只需要实现取指及更新PC即可。取指操作:处理器Z在每个时钟周期从存储器中读取 4 个字节的指令;更新PC:由于指令字长的固定,因此PC指针每个时钟周期只需要加 1 即可。
-
首先先创建两个文件,一个是处理器Z的顶层文件processor.v,另一个是存储器文件ram.v。
-
对于ram.v,用来存放和读取指令。
-
端口描述:
module ram( input clock, // Clock input [8:0] addr, // Read/write address input wEn, // Write enable input [31:0] wDat, // Write data input rEn, // Read enable output reg[31:0] rDat // Read data = line:arch:synchram:rDatB );
-
功能描述:对于写入数据,当写使能信号wr有效时,将数据wdata写入地址为addr的存储单元中;对于读出数据,读使能信号rd有效时,将地址为addr的存储单元中的数据输出到数据总线rdata上。
-
-
对于processor.v
-
端口描述:
module processor( input clock, input [8:0] addr, input wEn, input [31:0] wDat, input working, output [3:0] icode, output [3:0] ifun, output [3:0] rA, output [3:0] rB, output [15:0] valC );
-
功能描述:当working有效时,处理器Z在每个时钟周期从存储器中取出一条 32 位的指令,并解析指令,最终输出;当working无效时,并且在写使能信号wEn有效时, 通过addr和wDat向指令存储器中写入数据。
-
仿真要求:
编写testbench 以完成仿真,时钟周期为50MHz,在前 3 个时钟周期令working信号为 0 ,使处理器不工作,并依次向指令存储器地址为0-2的存储单元中写入(IRMOV $16, %r5), (SUB %r4, %r5) ,(ADD %r1, %r2)这 3 条指令。然后令working信号为 1 ,使处理器工作,完成这 3 条指令的取指操作,最终输出每个时钟周期取指的结果。
基础任务 2 :实现写回操作及指令IRMOV的操作。
具体要求:
-
在任务 1 完成的基础上,增加一个寄存器读写regfile.v 文件,并修改processor.v 文件,因此你的设计中应该至少包括以下文件,processor.v,ram.v,regfile.v。
-
对于regfile.v
-
端口描述:
module regfile( input [ 3:0] dstE, input [31:0] valE, input [ 3:0] dstM, input [31:0] valM, input reset, input clock, output [31:0] r0, output [31:0] r1, output [31:0] r2, output [31:0] r3, output [31:0] r4, output [31:0] r5 );
-
功能描述:采用同步复位,当reset有效,将寄存器r0-r5的数据清零;每个时钟周期,将数据valE写入寄存器ID为dstE的寄存器;每个时钟周期,将数据valM写入寄存器ID为dstM的寄存器;每个时钟周期输出寄存器r0-r5的数据。
-
-
对于processor.v,
-
端口描述:
module processor( input clock, input [8:0] addr, input wr, input [31:0] wdata, input working, output [31:0] r0, output [31:0] r1, output [31:0] r2, output [31:0] r3, output [31:0] r4, output [31:0] r5 );
-
补充功能描述: 当取指结果为IRMOV(即指令代码为 1 ,功能代码为 0)时,通过寄存器文件提供的dstM和valM接口,将立即数ValC写入到寄存器ID为rB的寄存器中。(寄存器文件提供的dstE和valE接口将在任务 4 中使用,本任务中可以悬空)
-
仿真要求:
假定你的学号后两位为ab(a=5,b=5);
编写testbench以完成仿真,时钟周期为50MHz,在前 4 个时钟周期令working信号为 0 ,使处理器不工作,并依次向指令存储器地址为0-3的存储单元中写入 4 条IRMOV指令,分别修改寄存器 %r0 - %r3 的值,使其等于ab, ab+1, ab+2 ,ab+3。然后令working信号为 1 ,使处理器工作,完成这 4 条指令的执行,以修改每个寄存器的数据,最终输出每个时钟周期这 4 个寄存器的数据。
基础任务 3 :实现译码操作。
具体要求:
-
在任务 2 完成的基础上,修改regfile.v文件以及processor.v文件。
-
对于regfile.v,
-
端口描述:
module regfile( input [ 3:0] dstE, input [31:0] valE, input [ 3:0] dstM, input [31:0] valM, input [ 3:0] rA, output [31:0] valA, input [ 3:0] rB, output [31:0] valB, input reset, input clock, output [31:0] r0, output [31:0] r1, output [31:0] r2, output [31:0] r3, output [31:0] r4, output [31:0] r5 );
-
功能描述:采用同步复位,当reset有效,将寄存器r0-r5的数据清零;每个时钟周期,将数据valE写入寄存器ID为dstE的寄存器;每个时钟周期,将数据valM写入寄存器ID为dstM的寄存器;每个时钟周期,将寄存器ID为srcA的寄存器数据输出到valA;每个时钟周期,将寄存器ID为srcB的寄存器数据输出到valB;
-
每个时钟周期输出寄存器r0-r5的数据。
-
-
对于processor.v,
-
端口描述:
module processor( input clock, input [8:0] addr, input wr, input [31:0] wdata, input working, output [31:0] valA, output [31:0] valB );
-
补充功能描述: 无论取指结果如何,通过将寄存器ID为rA的寄存器数据输出到总线valA上,将寄存器ID为rB的寄存器数据输出到总线valB上。(寄存器文件提供的dstE和valE接口将在任务 4 中使用,本任务中可以悬空)
-
仿真要求:
假定你的学号后两位为ab(a=5,b=5);
编写 testbench 以完成仿真,时钟周期为 50MHz,在前 6 个时钟周期令working信号为 0 ,使处理器不工作,并依次向指令存储器地址为0-5的存储单元中写入 6 条IRMOV指令, 分别修改寄存器%r0-%r5的值,使其等于 ab , ab+1 ,ab+2 , ab+3 , ab+4 , ab+5 再依次向指令存储器地址为 6-8 的存储单元中写入(ADD %r0, %r1), (SUB %r2, %r3), (AND %r4, %r5)这 3 条指令。然后令working信号为 1 ,使处理器工作,完成这 几条指令的执行,先修改 6 个寄存器的数据,再执行ALU操作,最终输出每个时钟周期寄存器rA和 rB 所对应的数据(valA和valB)。
基础任务 4 :实现执行操作以及其他指令的操作。
具体要求:
-
在任务 3 完成的基础上,增加一个算数逻辑运算 alu.v 文件,并修改processor.v 文件,因此你的设计中应该至少包括以下文件:processor.v,ram.v,regfile.v,alu.v。
-
对于alu.v,
-
端口描述:
module alu( input [31:0] aluA, input [31:0] aluB, input [ 3:0] alufun, output [31:0] valE );
-
功能描述:根据功能代码alufun的值,对操作数aluA和aluB做相应运算,并将结果通过valE输出。alufun为 0 ,做加法,为 1 做减法(A-B),为 2 做逻辑与运算,为 3 做逻辑异或运算。
-
-
对于processor.v,
-
端口描述:
module processor( input clock, input [8:0] addr, input wr, input [31:0] wdata, input working, output [31:0] valE );
-
补充功能描述:当取指结果为ADD, SUB, AND, XOR时, 通过寄存器文件提供的dstE和valE接口,将算数逻辑运算的结果valE写入到寄存器ID为rA的寄存器中。
-
仿真要求:
假定你的学号后两位为ab(a=5,b=5);
编写 testbench 以完成仿真,时钟周期为 50MHz,在前 6 个时钟周期令working信号为 0 ,使处理器不工作,并依次向指令存储器地址为0-5的存储单元中写入 6 条IRMOV指令, 分别修改寄存器%r0-%r5的值,使其等于 ab , ab+1 ,ab+2 , ab+3 , ab+4 , ab+5 再依次向指令存储器地址为 6-8 的存储单元中写入(ADD %r0, %r1), (SUB %r2, %r3), (AND %r4, %r5)这 3 条指令。然后令working信号为 1 ,使处理器工作,完成这几条指令的执行,最终输出每个时钟周期ALU得到的结果valE的值。
基础任务 5 :处理器Z的最终实现。
具体要求:
-
在任务 1 , 2 , 3 , 4 全部完成的基础上,修改processor.v文件。
-
对于processor.v,
-
端口描述:
module processor( input clock, input [8:0] addr, input wr, input [31:0] wdata, input working, input [3:0] rID, output [3:0] rdata, );
-
流水化:使用四级流水线,例化各个模块,第一级在每个时钟周期取指并解析,第二级在每个时钟周期译码得到操作数rA和rB,第三级在每个时钟周期执行算数逻辑运算得到ALU的运算结果valE,第四级在每个时钟周期将ALU的运算结果valE写回寄存器文件;将立即数valC写回寄存器文件。
-
仿真要求:
编写 testbench 以完成仿真,时钟周期为 50MHz,在前 10 个时钟周期令working信号为 0 ,使处理器不工作,并依次向指令存储器地址为0-5的存储单元中写入 6 条IRMOV指令,分别修改寄存器%r0-%r5的值,使其等于 128 , 129 ,130 , 131 , 132 , 133 ,再依次向指令存储器地址为7 - 10 的存储单元中写入(ADD %r0, %r1), (SUB %r2, %r3), (AND %r4, %r5), (ADD %r1, %r0)这 4 条指令。然后令working信号为 1 ,使处理器工作,完成这 10 条指令的执行。当处理器完成所有指令的执行之后,令working信号为 0 , 最后 6 个时钟周期通过rID依次读出 6 个寄存器的值。
进阶任务:
1.1 握手机制
背景:握手
带有握手信号的接口可以使用明确的时序信号进行数据传送,这种用于握手的信号通常称为valid(有效)信号和ready(就绪)信号。如下图所示,
·对于上游逻辑来说,上游逻辑发出一个数据,如果数据有效,上游逻辑就会把传送给下游的valid信号拉高,即告诉它数据有效可以接收!
·如果下游准备好了,就把传送给上游的ready信号拉高,告诉它我准备好了,如果你数据有效,我就可以接收!
·所以当上游传输的数据有效,且下游也准备好时,数据就会在下一个周期被下游取走,上游可以接着传下一个数据。
·握手成功(fire)为:valid和ready同时为高的情况
·当ready信号为0,valid为1时,出现反压情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JodFzF3k-1687327535645)(F:/fpga/project/RISCV/design/handshake.png)]
在现代处理器中,这种机制是随处可见的,若流水线深度很大,由于每一级都需要握手,流水线最后一级反压信号可能会一直串扰到最前一级,这时的ready信号就会成为关键路径,严重影响处理器的性能。我们该如何解决这个问题?
具体要求:
任务1:实现上述总线握手场景;
任务2:假定上游的valid信号不满足时序要求,要对valid信号用寄存器打一拍,实现握手场景;
任务3:假定下游的ready信号不满足时序要求,要对ready信号用寄存器打一拍,实现握手场景;
上游逻辑端口描述:
名称 方向
clk i
rst_n i
valid o
ready i
data[7:0] o
下游逻辑端口描述:
名称 方向
clk i
rst_n i
valid i
ready o
data[7:0] o
仿真要求:
对每个任务单独编写 testbench 以完成仿真,时钟周期为 50MHz。
注意:1.1是单独对握手协议的仿真与处理器无关,可以将该模块单独仿真,tb的设计不做具体要求但要求模拟出握手正常与反压的情况。
1.2握手机制时序问题与数据冒险处理
具体要求:
1.对于1.1的握手协议,在译码级和执行级之间对valid和ready数据分别打一拍
2.在发生数据冒险时,需要能够正确处理(处理方式可以是转发与暂停或者仅简单暂停)。
仿真要求:
假设你的学号尾号为ab(a=5,b=5);
编写 testbench 以完成仿真,时钟周期为 50MHz,在前 20 个时钟周期令working信号为 0 ,使处理器不工作,并依次向指令存储器地址为0-3的存储单元中写入 4 条IRMOV指令,分别修改寄存器%r0-%r3的值,使其等于 ab , ab+1 ,ab+2 , ab+3 ,再依次向指令存储器地址为4-9的存储单元中写入 (SUB %r1, %r0), (AND %r2, %r3),(ADD %r0, %r3), (XOR %r0, %r2),(SUB %r3, %r2), (AND %r3, %r1)这 6条指令。然后令working信号为 1 ,使处理器工作,完成这10条指令的执行。当处理器完成所有指令的执行之后,令working信号为 0 , 最后 8 个时钟周期通过rID依次读出 4 个寄存器的值。
2.1 增加条件码
具体要求:
-
在以上完成的基础上,增加条件码cc
-
对于alu.v
-
端口描述:
module alu( input [31:0] aluA, input [31:0] aluB, input [3:0] alufun, output [31:0] valE, output cc );
-
功能描述:根据功能代码alufun的值,对操作数aluA和aluB做相应运算,并将结果通过valE输出。alufun为0,做加法,为1做减法(A-B),为2做按位与运算,为3做按位异或运算;要求cc=1当且仅当valE>=0。
-
-
对于processor.v
-
端口描述:
module processor( input clock, input [8:0] addr, input wr, input [31:0] wdata, input working, output [31:0] valE, output [2:0] cc );
-
仿真要求:
假设你的学号为ab(a=5,b=5);
编写testbench以完成仿真,时钟周期为50MHz,在前12个时钟周期令working信号为0,使处理器不工作,并依次向指令存储器地址为0-3的存储单元中写入4条IRMOV指令,分别修改寄存器%r0-%r3的值,使其等于ab , ab+1 ,ab+2 , ab+3 再依次向指令存储器地址为4-5的存储单元中写入(ADD %r0, %r1), (SUB %r2, %r3)这2条指令。然后令working信号为1,使处理器工作,输出每个时钟周期ALU得到的结果valE的值和cc的值。
2.2:增加JGE指令,实现动态分支预测
具体要求:
- 在任务1,2,3,4,5和进阶任务2.1完成的基础上,为ISA增加JGE指令。
- JGE指令需要关联进阶任务2中的条件码,具体策略自定义不做要求
- 实现最简单的动态分支预测,实现最简单的1位分支预测缓冲区,即用上一次分支指令选中的结果来预测下一次分支是否发生(注:初始状态为分支发生)
仿真要求:
编写testbench完成以下32条指令的仿真,最终输出每个时钟周期PC指针的值。
A: IRMOV $1, %r0
IRMOV $2, %r1
IRMOV $3, %r2
IRMOV $4, %r3
IRMOV $5, %r4
ADD %r5, %r4
D: ADD %r1, %r0
E: SUB %r3, %r4
F: JGE D
G: ADD %r2, %r4
H: ADD %r3, %r5
J: JGE H
K: ADD %r1, %r0
L: ADD %r2, %r3
M: SUB %r4, %r5
N: JGE L
附录:RTL推荐代码风格 - 加分项
1.使用标准的DFF模块例化,生成寄存器
2.推荐使用assign语法代替if-else和case语法