P4_L0_document
P4课下
目标
将P3中使用Logisim搭建的MIPS单周期CPU电路转用Verilog HDL描述,并在其基础上支持更多指令。
完成的只是从Logisim电路到Verilog代码的映射。
设计草稿
整体设计
具体模块化与层次化设计
单周期处理器
顶层模块、控制器和数据通路,放置在顶层目录下。code.txt存储相应的机器码。处理器为32位单周期处理器,不考虑延迟槽,支持指令为add, sub, ori, lw, sw, beq, lui, jal, jr, nop
,nop
为空指令,不进行任何操作,add, sub
按无符号加减法处理(不考虑溢出)。
控制器模块
用一个独立的Verilog HDL文件,实现控制器这个单一的职责,降低模块间耦合度。
顶层目录
其中每个module都由一个独立的Verilog HDL文件组成。
顶层文件为mips.v,有效驱动信号要求包括且仅包括同步复位信号 reset 和时钟信号 clk,接口定义如下:
1 |
|
数据通路设计
P3遗产(我的丑陋的数据通路图)
注意
根据设计的数据通路架构图及各个部件的输入输出端口,在顶层Verilog文件中定义一些内部的wire型变量,利用模块间的逻辑关系,对各个部件模块进行实例化,串接在一起,使之成为一个整体,最后预留出控制器信号的输入和输出端口即可。(省流:类似于Logisim中使用标签的方法)
IFU(取指令单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号,将PC寄存器的值复位为起始地址0x00003000 |
NPC[31:0] | I | 32位,代表下一条指令的地址,读入后用PC寄存器存储起来 |
PC[31:0] | O | 32位,代表当前指令的地址,输出给NPC模块来生成下一条指令的地址 |
Data[31:0] | O | 32位,当前指令的机器码 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 同步复位 | 当时钟上升沿到来且reset有效时,将PC寄存器的值复位为起始地址0x00003000,PC寄存器通过一个内部的32位reg 型变量存储。 |
2 | 取指令 | 通过当前PC值读取ROM中相应位置的指令并输出当前指令,而ROM则通过对reg 型变量建立数组的方式来表示,例如reg [31:0] ROM [0:4095] ,根据实验要求一开始使用initial 块通过$readmemh 将code.txt 中的十六进制指令读入存储器ROM中。 |
GRF(寄存器单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号,将32个寄存器中的值全都清零。 |
RegWrite | I | 写使能信号,1:可向GRF中写入数据;0:不可写入 |
rs[4:0] | I | 5为地址输入信号,指定32个寄存器中一个,将其中存储的数据读出到RD1 |
rt[4:0] | I | 5为地址输入信号,指定32个寄存器中一个,将其中存储的数据读出到RD2 |
RegAddr[4:0] | I | 5为地址输入信号,指定32个寄存器中一个作为写入的目标寄存器 |
RegData[31:0] | I | 32位输入数据 |
PC[31:0] | I | 32位,表示相应指令的存储地址 |
Rs[31:0] | O | 输出rs寄存器中的32位数据 |
Rt[31:0] | O | 输出rt寄存器中的32位数据 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 同步复位 | 时钟上升沿到来且reset信号有效时,所有寄存器存储的数据清零。 |
2 | 读数据 | 读出rs,rt地址对应寄存器中所存储的数据到Rs,Rt中。寄存器堆的实现方式同IFU模块中的ROM器件类似。 |
3 | 写数据 | 当RegWrite有效且时钟上升沿来临时,将RegData写入RegAddr寄存器中。 |
4 | 输出数据,用于评测 | 每个时钟上升沿到来时若要写入数据(即写使能信号为 1 且非 reset 时)则输出写入的位置及写入的值 |
NPC(指令转移单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
PC[31:0] | I | 32位,当前指令的地址 |
ifBeq | I | 1位,表示当前指令是否为beq |
zero | I | 1位,表示是否满足相等关系 |
imm[15:0] | I | 16位,16位立即数 |
Rs[31:0] | I | 32位,rs寄存器的值,作为jr 指令成立时的跳转地址 |
ifJr | I | 1位,表示当前指令是否为jr |
NPC[31:0] | O | 32位,下一条指令的地址 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 非跳转指令的PC自增 | 对于非跳转指令,需要通过PC自加4完成向下一条指令的转移 |
2 | 跳转指令的PC跳转 | 针对类如beq 等跳转指令的在PC自增功能的基础下的PC地址偏移即将再加上立即数左移两位后符号扩展成32位的结果,进而实现PC的跳转;针对jr 指令需要将输入的寄存器存储的值赋值给PC即可实现相应的指令地址的转移。 |
DM(数据寄存器)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
MemData[31:0] | I | 32位,写入数据 |
MemAddr[31:0] | I | 32位,地址选择信号 |
MemToRead | I | 1位,是否读出信号 |
MemWrite | I | 1位,是否写入信号 |
clk | I | 时钟信号 |
reset | I | 复位信号 |
pc[31:0] | I | 32位,当前指令的地址 |
RD[31:0] | O | 32位,输出数据 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 读数据 | MemToRead有效时,地址选择信号MemAddr选择的存储字放在RD输出总线上输出 |
2 | 写数据 | MemWrite有效且时钟信号上升沿到来时,MemData上数据被写入地址MemAddr选择的存储单元中 |
3 | 同步复位 | 时钟上升沿到来且reset有效时,将RAM地址复位为起始地址0x00000000 |
4 | 输出数据,用于评测 | 每个时钟上升沿到来时若要写入数据(即写使能信号为 1 且非 reset 时)则输出写入的位置及写入的值 |
ALU(运算单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
ALUctr[2:0] | I | 3位,进行I1和I2的相关运算,课下目前只需要2位就可以了,但是多留一位给P4课上做准备() |
I1[31:0] | I | 32位,操作数一 |
I2[31:0] | I | 32位,操作数二 |
Out[31:0] | O | 32位,ALU计算结果 |
zero | O | 1位,判断减操作的结果是否为0 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 加操作 | ALUctr为00 时进行I1和I2的加操作 |
2 | 减操作 | ALUctr为01 时进行I1-I2的操作 |
3 | 或操作 | ALUctr为10 时进行I1|I2的操作 |
4 | 加载到高位的操作 | ALUctr为11 时进行将imm加载到高位 |
Ext(扩展单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
imm[15:0] | I | 16位立即数输入 |
ifSignExt | I | 1位,表示是否进行符号位的扩展 |
immediate[31:0] | O | 32位,立即数扩展后的结果 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 0扩展 | 将16位立即数进行0扩展 |
2 | 符号扩展 | 将16位立即数进行符号位扩展 |
RegAddrSel(寄存器堆写入地址选择单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
rt[4:0] | I | 5位rt地址数 |
rd[4:0] | I | 5位rd地址数 |
RegDst | I | 1位,表示是否选择rd作为写入地址 |
ifJal | I | 1位,表示当前指令是否为jal |
RegAddr | O | 5位,最终的写入地址 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | RegDst选择 | 通过RegDst 选择是否将rd 地址数作为写入地址 |
2 | jal选择 | 通过ifJal 选择是否将$ra 作为写入寄存器 |
RegDataSel(寄存器堆写入数据选择单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
ALUout[31:0] | I | 32位,ALU模块计算结果 |
RD[31:0] | I | 32位,lw 指令下DM模块提供的读取入寄存器的数据 |
pc[31:0] | I | 32位,当前指令地址 |
MemToReg | I | 1位,表示选择RD作为写入数据 |
ifJal | I | 1位,表示当前指令是不是jal |
RegData[31:0] | O | 32位,最终写入数据 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 选择RD | 通过MemToReg选择是否将RD作为写入数据 |
2 | 选择下一条指令的地址 | 通过ifJal选择是否将PC+4作为写入数据 |
ALUInSel(ALU模块的操作数选择单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
Rs[31:0] | I | 32位,GPR[rs](对于课下,其实本可以不用传入的,但是传入方便课上加有可能的相关指令) |
Rt[31:0] | I | 32位,GPR[rt] |
immediate[31:0] | I | 32位,经过扩展Ext模块的立即数 |
ALUsec | I | 1位,选择ALU的第二操作数为立即数 |
I1[31:0] | O | 32位,ALU模块的第一个操作数 |
I2[31:0] | O | 32位,ALU模块的第二个操作数 |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 选择ALU模块的第二个操作数 | 通过ALUsec选择ALU模块的第二个操作数 |
jump(处理J跳转指令的NPC处理单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
pc[31:0] | I | 32位,当前指令地址 |
norNPC[31:0] | I | 32位,不进行J跳转指令的正常NPC |
instr[31:0] | I | 32位,当前指令 |
jump | I | 1位,表示当前指令是否是jal 跳转指令 |
NPC | O | 32位,最终的NPC |
模块功能定义:
序号 | 功能名称 | 描述 |
---|---|---|
1 | 处理跳转地址 | 处理jal 跳转指令的跳转地址 |
2 | 选择NPC是否为跳转地址 | 通过jump 选择是否进行地址的跳转 |
Controller(控制器单元)
模块端口定义:
信号名 | 方向 | 描述 |
---|---|---|
op[5:0] | I | 6位,区分非R型指令的标识 |
func[5:0] | I | 6位,区分R型指令的标识 |
RegWrite | O | 1位,表示是否要将结果写回寄存器中 |
MemWrite | O | 1位,表示是否要将结果写入DM中 |
ifSignExt | O | 1位,表示立即数是否进行符号扩展 |
ALUctr[2:0] | O | 3位,作为ALU模块的运算控制器 |
ifBeq | O | 1位,表示当前指令是否为beq |
MemToReg | O | 1位,表示在写使能端有效前提下判断是否从DM读取数据写入寄存器中,其余情况均是从ALU运算得到结果写入寄存器中 |
RegDst | O | 1位,表示回写的寄存器是否为rd |
ALUsec | O | 1位,表示ALU进行操作的第二个操作数是否是立即数 |
jump | O | 1位,表示是否执行jal 的跳转指令 |
ifJr | O | 1位,表示当前指令是否为jr |
ifJal | O | 1位,表示当前指令是否为jal |
输入与输出之间的关系表:
func | 100000 | 100010 | n/a | n/a | n/a | n/a | n/a | 000000 | n/a | 001000 |
---|---|---|---|---|---|---|---|---|---|---|
op | 000000 | 000000 | 001101 | 001111 | 100011 | 101011 | 000100 | 000000 | 000011 | 000000 |
variate | add | sub | ori | lui | lw | sw | beq | nop | jal | jr |
RegDst | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
ALUsec | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
MemToReg | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
RegWrite | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
MemWrite | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
ifBeq | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
ifSignExt | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
ALUctr[0] | (add(00))0 | (sub(01))1 | (or(10))0 | (lui(11))1 | (add(00))0 | (add(00))0 | (sub(01))1 | 0 | 0 | 0 |
ALUctr[1] | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
ALUctr[2] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
jump | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
ifJr | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
ifJal | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
测试方案
在上一次的随机生成代码的基础上加上了人工调试,得到了下列覆盖较为全面的代码:
1 |
|
得到相应机器码导入ISE后,检查出了许多的bugs:
1.在DM模块中,将传入的地址右移2位赋给变量actAddr
,而actAddr
的定义为:wire actAddr;
显然位宽不对应,而恰好给的样例只涉及了0、1位置的存入(一开始认为输出的是实际的RAM的地址(这也是第三个bug)),且并没有认真看样例输出的是8位16进制数,被硬控一小时。
2.传入评测前一定要确保IM模块导入的文本名称为:code.txt
,否则将什么都没有传入。
3.输出的是右移前的地址,即按字节的地址,不是按字的地址。
同时我利用P3随机生成的代码测试了对于add
、sub
、lui
、ori
、lw
、sw
、beq
指令的测试情况:
1 |
|
源码如上,再通过学长的魔改版mars的输出,同ISE波形图输出进行IDEA的输出对拍,发现除了对$0的写入不同(前者会忽略,即输出并不造成影响),其他完全一致。
因此只需要单独测试jal
和jr
指令即可。
思考题
1.阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?
1.这个addr信号是ALU模块的结果输出,代表了lw
和sw
指令下对DM中的对应地址。
2.因为地址是以字节表示的,又因为处理器是32位的,因此两个地址间相差一个字即4个字节,在数值上表现为相差4。而DM中的RAM是按字表示,即RAM中两个相邻存储数值在DM存储位置的数值上差1,因此外界传入的对于RAM的地址索引需要经过除四即右移两位再传给RAM,而DM的容量为4KB(2^12^B),共需要12位,加上右移两位的影响,因此对于ALU模块提供的地址数应取其211位而不是 09位。
2.思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。
两种译码方式:可以记录下指令对应的控制信号如何取值,也可以记录下控制信号每种取值所对应的指令。
前者可能代码示例:
1 |
|
后者k代码示例:
1 |
|
1.前者的代码是针对每一个指令去输出对应的控制信号的,一个很显然的优点就是当增加指令时,只需要增加对应指令成立时对应的控制信号输出为1即可。但是虽然个人感觉代码编写上过于繁琐,因为得判断多条指令是否成立,在指令少的时候可能很清晰,但一旦指令数过多,会导致代码的冗长。
2.后者的代码是针对每一条控制信号采用或逻辑将每个控制信号对应的所有指令或起来达到相应效果,在逻辑上会更加清晰,代码行数也较少。但是缺点就是增加指令时,需要在多条控制信号处增加,较为繁琐。
总的看来,前者是面向指令译码而后者是面向控制信号译码,二者各有优劣。
3.在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位与异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。
在同步复位中,clk
信号是优先于reset
信号的,只有当clk
信号到达触发沿且reset
有效时,reset
才会起作用,否则reset
不起作用。
在异步复位中,reset
信号是优先于clk
信号的,当reset
信号一有效,即发挥作用,不用考虑此时的clk
信号。
4.C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分(详见文档 page 34、page 35)。
在忽略溢出的前提下,add
和addu
在不溢出的情况下得到的结果一致,且在溢出的情况下,两者溢出后剩下的数也一致,且add
并不会报溢出错误,所以此情况下add
和addu
等价,同理,对于addi
和addiu
在忽略溢出的情况下,两者等价。
P4课上
注意:过度的模块化会导致课上添加指令时的不便,建议在顶层模块中留一些多路选择器,不然加端口会及其麻烦。
第一题(eam)
将GRF[rs]低16位被模数看作有符号数,对17求模(得到的是最小非负整数,例如:16’h23得到结果为16’h1、而16’hffff得到的结果为16’h10),再根据GRF[rt]的最高位对求模结果进行扩展得到的结果返回GRF[rd],关键在于求模一定要是正数,可以采取((a%b + b)%b)的方式来确保得到的数是0-b-1的正数。不然大有被硬控一小时的风险。
第二题(cptl)
判断GRF[rs]的低18位中是否有连续的6位数是1,若是将PC+4传给GRF[rt],否则传给GRF[31],同时进行beq
指令的无条件跳转。没啥好说的,认真加端口就行了。
注意:判断6位连续1的方法可以通过循环的方式实现:
1 |
|
第三题(olw)
如指令名,有条件lw
,olw $rt, offset($rs)
:若获取的内存值是非递减序列,就写入,否则不写入。非递减序列:只要该位为1,那么低于该位的位上都为1.
重要的是如何判断非递减序列,大致思路如下:
1 |
|
由于第一题被硬控好久,导致第三题没时间做,一定要充分思考,牢记教训!