北工大学实验学院(论文)
wire i_load = op[2]&~op[1]&~op[0]; wire i_store = op[2]&~op[1]& op[0]; wire i_beq = op[2]& op[1]&~op[0]; wire i_j = op[2]& op[1]& op[0];
Inst变量为CU的输入变量,是一个16位的总线型信号,为指令信号的输
入端。表示16位的指令。通过wire [5:0] op = Inst[15:11];操作,建立一个5位的总线变量op,解析出Inst的15至11位,即opcode部分。接下来根据opcode的不同,确定该指令是什么指令。例如在wire i_add = ~op[2]&~op[1]&~op[0];操作中,通过与运算,当op的高四位为0同时低位为1时,则认为是add指令,此时将i_add线型变量赋值为1,即输出高电平信号。该信号为后级处理做准备。确定指令类型后,接下来需要确定运算类型。
即确定ALU的操作码。在本设计中,ALU可以执行加法,减法,逻辑或,逻辑与四种运算,因此需要两位信号线来区分这四种运算。在CU中,用aluc表示,相关代码如下:
assign aluc[0] = i_sub | i_or;
assign aluc[1] = i_and | i_or;
可以看到aluc包含两位,根据aluc的含义,为每一位进行复制。aluc即对应的运算如下表:
Aluc 00 01 10 11 运算 + - & | 由于aluc第一位只有当减法运算和逻辑或运算的时候1,因此利用ssign aluc[0] = i_sub | i_or ;为aluc[0]进行赋值。同理aluc[1]和aluc[2]也用此方法进行赋值。完成这两句后,就完成了操作码的解析。最后需要完成的是访问寄存器堆,获得相应寄存器号对应的寄存器值。访问寄存器堆。若为R型指令,需要访问寄存器堆,获得两个源寄存器号对应的值。若为I型指令,需要访问一个寄存器堆,获得一个源寄存器号对应的值同时将立即数imm或者offset进行位扩充,扩充方式为符号扩展,从5位扩展至16位,以便下一周期参与运算。扩展代码如下:
assign offset = Inst[6]==1?{1'b1,Inst[6:0]}:{1'b0,Inst[6:0]}; 寄存器堆设计如下:
16
北工大学实验学院(论文)
上图中rs1和rs2信号引脚是3位的数据总线,表示寄存器的地址信号,当CU部件解码完成后,将rs1和rs2送入到这两个引脚,寄存器堆则根据寄存器号读取到相应的寄存器值,分别从DOrs1和DOrs2送出,可以看到DOrs1和DOrs2为两个8位的总线信号,用于表示8位的运算数。
若指令为beq或者Jump指令,CU需要做出特殊处理,即确定npc的值,即程序计数器的跳转目的PC值。实现代码如下:
always @(*) begin
if(i_j)
npc = Inst[7:0];
else if(i_beq && (rsvalue == rtvalue) && Inst[5] == 0)
npc = pc + Inst[5:0]; npc = pc - Inst[5:0]; npc = 0;
else if(i_beq && (rsvalue == rtvalue) && Inst[5] == 1) else
end
本段代码是用always描述的组合逻辑。在always块中,包含五个分支语句。针对五种情况对npc进行赋值。第一种情况表示当前指令是一个jump指令,则npc被赋值为Inst[7:0],Inst[7:0]为跳转目的地址。当指令为beq指令,同时满足rsvalue和rtvalues相等的条件,且Inst[5]为0的时候,则执行npc = pc + Inst[5:0]。其中rsvalue和rtvalue分别表示从寄存器堆中获得的相应寄存器的值。只有当两个值相等的时候,才会执行跳转指令,同时当Inst[5]等于0的时候,表示跳转偏移量为一个正数,因此执行npc 的值等于当前PC值加上偏移量,否则等于当前PC减去偏移量。
17
北工大学实验学院(论文)
4.3 执行阶段
该阶段进行CPU的核心运算操作,其基本结构如图3-19所示。EXE执行段的主要部件是ALU,还包括一个双路选择器Mux2。
图3-19 执行阶段的结构
在前一周期指令译码阶段准备好了指令要处理的操作数之后,就开始执行有效地址计算。从图中可以看到ALU包含三个输入信号,a,b和op,分表表示参与运算的两个操作数和一个运算码。A操作数直接来源于寄存器堆的读取结果,op来源为CU的解码结果。而b可以看到来源于双路选择器Mux2的输出端。这是因为b操作数有两种来源,当该指令是一条R类型的指令,即两个操作数都为寄存器值的时候,b运算数的来源同a一样,从寄存器堆的读取结果中获得;当该指令为I类型的指令时,b操作数为一个立即数,此时,该数由CU符号扩展后的offset充当。双路选择器就是用于选择这两个信号,channel为选择信号,连接到CU的alu_b输出端。根据对指令操作码部分的译码结果,ALU对两个16位的操作数完成算术、逻辑、移位或置位的运算,然后输出结果。Aluc代码如下:
always @(a or b or op)
begin
case(op)
3'b000:
res = a + b; res = a - b; res = a & b; res = a | b; res = a * b;
18
3'b001: 3'b010: 3'b011: 3'b100:
北工大学实验学院(论文)
end
3'b101:
res = a / b;
end case
以上代码为用always功能块实现的组合逻辑。敏感信号包括运算数a,b和运算符代码op,利用case语句完成多路的选择,例如当op == 000时,则执行res = a + b,res为运算结果,是alu模块的一个输出信号。由上图可以看到,输出结果连接至RegMem进行暂存,等到下一个周期的到来。信号输入输出的时序控制由ID段锁存器ID/EXE和EXE段锁存器EXE/MEM完成。
4.4 访存阶段和回写阶段设计
访存阶段和回写阶段的结构如图所示。主要由数据储存器dmem和寄存器堆组成。数据存储器负责储存程序运行时需要的数据。本设计中采用Quartus II提供的IP核来完实现。从图中可以看到,dmem包含四个输入信号,分别为地址信号,时钟信号,数据信号和写使能信号。Address连接到alu的res输出结果端。data连接到寄存器堆的DOrs2。Wren由CU控制,连接到CU的wmem输出端。在本文设计的指令集中,只有store和load指令需要访问内存。例如load r3 r0 1。Alu计算出r0 + 1的结果并将其作为地址送入到dmem的address信号接口处,在clock时钟的驱动下,dmeme的q引脚会输出对应地址的数据。当当前指令若为store指令,例如store r2 r0 1,r2寄存器的值会被送入到data信号出,同时CU将wren置为高电平,在clock时钟的驱动下,data被写入到address表示的地址处
图3-25 访存段的内部结构
完成访存操作后,就到了CPU运行的最后一个阶段,回写阶段。该阶段的目的是把运算或dmem读取结果写入到寄存器堆中。寄存器堆上包含we,data和rd管脚。这三个信号的作用于内存上类似,只需要通过rd向寄存器堆提供写入寄存器的地址,通过data提供需要写入的数据,同时we端高电平使能,就能
19
北工大学实验学院(论文)
完成回写操作。
4.5 本章小结
本章主要介绍了利用QUARTUS完成CPU实现及代码编写的完整步骤,在分析了CPU的工作流程和细节的基础上,详细描述了CPU各个阶段的实现方法,结合代码解析了CPU中各个部件的作用以及各个信号的意义。
20