单周期CPU设计指导¶
不要抄袭 不要抄袭 不要抄袭
以下仅为辅助材料,仅供参考
指令集手册
单周期CPU设计最大的困难在于理论的匮乏与例子的缺少,因此以下会补充一些单周期CPU设计相关的理论并以R-Type为例给出单周期CPU设计的方式
单周期CPU相关理论¶
RISCV指令集架构¶
sys1-proj要实现的指令类型有R/I/S/B/U/J Type共六类,每条指令都由32位的inst确定,这32位存储了指令的类型,数据信息,需要读或写的寄存器编号,例如
pc表示当前执行的位置,每条指令占用四位存储空间,除遇到B Type(分支跳转)或J Type(跳转)指令,其余每条指令执行完后切换下一条指令pc需要+4inst的内容是通过取指(Instuction Fetch)得到的,Instruction memory中以pc值为索引存储了所有我们需要的指令,在取指阶段,通过向imem_ift输入pc,instruction memory会返回对应的inst,这一过程是通过valid-ready协议达成的(不过在sys1中,我们只需知道向imem输入pc,会得到返回inst),由于存储单元的问题,返回的inst是64位的,我们需要通过pc[2]选择高32位或低32位为我们需要的指令。
指令集手册中写有每类指令与每条指令的构成,在Controller.sv中我们通过inst判断指令类型并输出对应选择器的选择信号控制Core执行。 如下图,对于一条R-type类型的ADD指令,指令类型如下确定


funct7 = inst[31:25] == 7'b0000000;
funct3 = inst[14:12] == 3'b000;
opcode = inst[6:0] == 7'b0110011;
CPU组成¶
CPU的数据通路图如上,本质上Core.sv中需要完成的就是线路的连接(需要把上图中所有的线都定义并连接起来),而Controller.sv即Control Logic,完成的是根据指令(inst)输出对应选择器的控制信号
ALU.sv,Cmp.sv,Regfile.sv为封装的模块,对应图中的ALU,BranchComp,Reg[],这些模块的设计非常简单,只需注意x0是恒为0的,因此regfile要禁止对x0的写操作
IMEM,DMEM是repo给出的内存模块,只需将
imem_ift.r_request_bits.raddr
imem_ift.r_reply_bits.rdata
dmem_ift.r/w_request_bits.raddr/waddr
dmem_ift.r/w_reply_bits.rdata/wdata
dmem_ift.w_request_bits.wmask
ImmGen,DataPkg,MaskGen模块要自行设计与实现,具体在Data Package,Mask Generation,Data Trunc设计会详细解释
cosim是仿真测试,将pc,inst,wdata等接入cosim用于测试代码的正确性(虽然给出的cosim连线很多,但事实上我们通常只关心pc,inst,wdata的正确性,因为只要这些都正确了,指令执行就基本正确了)
指令执行阶段¶
指令执行分为五个阶段,具体为:
- IF(Instruction Fetch)
- ID(Instruction Decode)
- EX(Execute)
- MEM(Memory)
- WB(Write Back)
如果区分阶段,则数据通路图如下(以下是sys2-lab1的数据通路图)
四个Reg将通路图切分为五个阶段(该图仅供区分阶段参考用,实际连接使用CPU组成部分给出的通路图)
IF¶
Instruction Fetch阶段要完成取指操作,具体而言,就是将下一条指令从IMEM(Instruction Memory)中取出来,方法为,向imem.raddr传入pc,imem.rdata传出inst
完成取指后要对pc进行更新,对于一般指令,pc需要+4,对于条件符合(Cmp.sv返回cmp_res为true)的B型指令和J型指令,需要将pc更新为ALU的计算结果(计算出的跳转地址)
ID¶
Instruction Decode阶段要完成对取出的指令解码的操作,具体而言,就是判断取出的指令是什么类型的什么指令,rs1,rs2,rd是什么,需要哪些控制信号(在Controller.sv中完成)
随后通过rs1,rs2(寄存器的编号)从Regfile中取出对应的值,如果Regfile编写正确,那么只需输入rs1,rs2,Regfile输出的read_data_1和read_data_2就是读出的值
ImmGen设计在之后详细介绍
EX¶
Execute阶段要完成执行操作,主要是ALU的运算操作,ALU要根据指令类型选择性地读入0/read_data_1/pc和0/read_data_2/imm,并根据控制信号选择计算的方式(加/减/与或非等),B型指令需要CMP执行比较操作,计算得到alu_res根据不同的指令有不同的用途(例如add指令要将alu_res写入rd寄存器,sd指令要将alu_res存入dmem,j指令要将alu_res赋值给pc)
MEM¶
Memory阶段要执行访存操作,也就是访问内存,从内存中取值或存入值,事实上,只有ld(lw,lb,...)和sd(sw,sb,...)指令参与访存阶段,因为其余指令涉及的都是寄存器的操作,这是计算机的存储结构决定的(寄存器->缓存->内存->磁盘)
访存阶段要做的是向DMEM提供需要读或写的内存地址,如果是读(ld)DMEM会返回读取的值,如果是写(sd)则还需要向DMEM提供写入值
WB¶
Write Back阶段执行写回操作,具体而言,就是根据指令类型执行写入操作,如add指令要将计算结果alu_res通过regfile写入rd寄存器,j指令要将计算结果alu_res赋值给pc,ld指令要将读出的dmem_ift.r_reply_bits.rdata(通过datatrunc处理后)写回regfile,sd指令没有写回
R-Type数据通路与控制器设计¶
以下主要以ADD指令为例
与R-Type指令相关的控制信号有:
inst_reg(由opcode确定)
we_reg(由inst_reg确定,regfile的使能信号)
alu_op(由funct3与funct7确定,ALU的选择信号)
写法举例如下
always_comb begin
if (inst_reg) begin
case(funct3)
3'b000:alu_op = ...
...
endcase
end
else if ...
end
表示alu进行的是两个寄存器的加法
wb_sel(由inst_reg确定为WB_SEL_ALU)表示写回值是alu_res
其余指令的信号请尝试自行设计,如果不太清楚每个指令是做什么的,请阅读手册或善用LLM
相关多路选择器具体选择如下 控制信号的选项可通过查看repo/sys-project/include/core_struct.sv得到
各类信号的opcode,funct3也可以在里面找到(但请不要省略查询手册这一步,务必完整理解32位inst每一部分的作用)
Imm Generation,Data Package,Mask Generation,Data Trunc设计¶
ImmGen¶
这个模块的作用的通过指令类型,不同指令对应不同imm_op信号,从inst产生不同长度的立即数
不同选项表示不同的指令类型,例如I_IMM表示I-Type指令,S_IMM表示S-Type
其余选项对应的立即数产生方式请自行查阅手册或善于LLM
DataPkg¶
这个模块的作用是根据指令内存操作类型(Double Word,Word,Half,Byte以及是否是unsigned选择有效的位数)
一个word是32位,half-word是16位,对Half-word类型,通过将低16位重复四次组成64位输出
事实上,在DataPkg模块中,无符号与有符号的处理方式是相同的(why?)
MaskGen¶
这个模块的作用是生成掩码,具体而言,就是指选取输入的哪几位是有效的
同样,在MaskGen模块中,无符号与有符号的处理方式是相同的(why?)
DataTrunc¶
类似MaskGen,通过alu的低三位截取相应的有效位数
根据内存操作类型决定截取值的长度,根据alu的低三位(或两位或一位,与MaskGen对应)决定截取的具体位置
特别的,对无符号数而言,高位应全部置0而对于有符号数,高位应全部置为有效位数的最高位的值
以上不给出具体示例的部分都是由于如果给出示例,则模块编写过于简单,请多尝试自行思考
实验步骤¶
推荐的编写顺序如下:
1.先完成ALU.sv,Cmp.sv,Regfile.sv
2.按照通路图分5个阶段完成接线并接入ALU,Cmp,Regfile模块,明确每个阶段需要做什么
3.完成R-Type通路,编写相应的译码,控制信号,与多路选择器的选项(ImmGen,DataPkg,MaskGen,DataTrunc全部置0即可)
仿真通过make verilate TESTCASE=rtype
4.完成ImmGen,DataPkg,MaskGen,DataTrunc的编写
5.根据I,S,B,U,J的顺序编写,依次完成立即数生成,访存,分支处理,完成每项后进行对应type的仿真测试,如果出错按以下方式调试
调试与运行¶
1.语法错误
常见语法错误
illegal assignment to wire,perhaps intended var
意思是在时序逻辑中对wire类型进行了赋值
这是初学者很容易犯的错误
wire类型是线路,只能通过assign
而reg类型是寄存器,只能在时序逻辑中赋值
虽然使用logic类型编译时会自动判断是wire还是reg(也推荐这样编写),但变量一定是wire或reg类型,而不能同时是二者,因此对任意变量既使用assign又在时序逻辑中赋值一定是错误的
2.make wave
仿真测试
关注重点:pc,inst,wb_val(WDATA)的DUT(Device Under Test)与SIM(Simulation)是否一致
在GTKWave中File>Write/Read Save File可以实现保存和读取功能,避免每次都要拖动信号才能观看波形
例如出现以下报错
表示在执行li指令时,pc与inst错误,即没有正确的读入li指令,实际发生错误的是上一条j指令,观察波形如下
仿真给出的00000354的下一条应该为0000035c但波形显示的是00000358,观察alu_res是正确的0000035c,next_pc也是正确的0000035c,那么说明问题在于正确的next_pc没有在is_j信号为1的情况下传递给pc
事实上错误是由于
写在最后¶
未来的工作科研中大多是没有指导甚至连目标都不明确的任务,这篇文档并不是一篇心得或是攻略,你可以将其认为是一套补充材料,本质的目的在于帮助大家独立自主的完成CPU的设计,为系统2与系统3打好基础,未来的指导也会越来越少,但此处,请先学会利用手头的资源。
从无到有的过程往往是最困难的,这也正因如此,如果能够独立完成,一定会加深对数字逻辑与CPU组成扎实深厚的认识,任何问题勤问助教与LLM,加油!