第4章 数据处理类指令剖析
本章将对数据处理类指令的处理过程进行分析,通过分析了解流水线、寄存器文件RF模块、算术逻辑单元ALU模块等CPU内部模块的基本作用与实现等。数据处理类指令包含算术、逻辑、移位、比较、数据传送等指令,都可以在一个时钟周期内执行完成,通过分析我们发现其执行过程也很清晰明了。
本章首先对数据处理类指令进行罗列,说明各个指令的作用,然后会给出一个示例程序,其中使用到部分数据处理类指令,分析其执行效果,利用我们在第2 章中建立的实验环境,可以编译示例程序,查看OR1KSim模拟器的执行效果,与之前分析的执行效果进行对比,同时得到可以在ModelSim中使用的存储器初始化文件,利用后者可以进行ModelSim仿真。在4.3节介绍了流水线的一般模型,从4.4节开始,将对数据处理类指令中有代表性的两条指令l.add、l.sfeqi进行分析,并且是按照流水线驱动的方式进行,分析结束后会指出其余数据处理类指令的处理过程与上面两条指令的不同之处。指令之间最大的不同的就是送往ALU的操作码不同,ALU要依据不同的操作码进行不同的运算,所以在4.6节对ALU模块进行了深入分析。在4.7节对流水线中存在的数据相关进行介绍,同时给出OR1200中的解决方法。有了这些基础,我们可以定制自己的指令,在4.8节介绍OR1200中的自定义指令,并定制一个计算32位数据奇偶校验位的指令。本章最后给出了OR1200中流水线数据通路图,由于我们只分析了数据处理类指令,所以本章给出的流水线数据通路图并不完整,在后续章节的分析中将逐步完善。
4.1 数据处理类指令说明
笔者对数据处理类指令的分类有48条,表4.1给出了OR1200中实现的所有算术处理类指令及其说明。
表4.1 算术处理类指令
续表
续表
续表
笔者将OR1200的数据处理类指令再细分为如下类别。
● 比较指令
l.sfeq、l.sfeqi、l.sfges、l.sfgesi、l.sfgeu、l.sfgeui、l.sfgts、l.sfgtsi、l.sfgtu、l.sfgtui、l.sfleu、l.sfleui、l.sfles、l.sflesi、l.sflts、l.sfltsi、l.sfltu、l.sfltui、l.sfne、l.sfnei
● 移位指令
l.rori、l.sll、l.slli、l.sra、l.srai、l.srl、l.srli
● 逻辑运算指令
l.and、l.andi、l.or、l.ori、l.xor、l.xori
● 算术运算指令
l.add、l.addc、l.addi、l.addic、l.sub
● 数据传送
l.movhi、l.cmov、l.extbs、l.extbz、l.exths、l.exthz、l.extws、l.extwz
● 其余指令
l.ff1、l.fl1
上述指令助记符也是很好理解的,比如有很多的比较指令,这些指令的助记符都是以“l.sf”开始,其中“l.”表示指令是基本指令集ORBIS32中的指令,“sf”代表“set flag”,因为比较指令都要通过设置SR寄存器的F位以表示比较结果,比较指令助记符的最后一个或两个字母有如下几种情况。
● 如果指令助记符的最后是“i”(immediate),则表示是与立即数比较
● 如果指令助记符的最后一位是“s”(signed),则表示作为符号数比较
● 如果指令助记符的最后是“si”;则表示指令中含有立即数,该立即数符号扩展后作为符号数比较。
● 如果指令助记符的最后一位是“u”(unsigned),则表示作为无符号数比较
● 如果指令助记符的最后是“ui”,则表示指令中含有立即数,该立即数符号扩展后作为无符号数比较。
这样理解就很好记忆了。表4.1中出现的16位立即数扩展包含两种情况:16位立即数符号扩展、16 位立即数无符号扩展。两者都是将16 位立即数扩展为32 位,符号扩展是将16位立即数的最高位复制到扩展后的32位数据的高16位,无符号扩展则是将扩展后的32位数据的高16位都置为0,如表4.2所示。
表4.2 16位立即数扩展举例
4.2 分析用例
本节给出一个简单的汇编程序,其中用到了部分数据处理类指令,程序本身没有什么特殊目的,读者可以借助这个程序对OR1200的数据处理类指令有一个更直接的了解,同时在对某一个具体指令分析的时候,可以将用例程序加载到最小系统中,使用ModelSim仿真,从而观察该指令在流水线各个阶段引起的信号变化。程序如下。
.org 0x100 .global _start _start: l.andi r0,r0,0 #r0寄存器应该始终为0,所以在OR1200运行的一开始就将其清零 l.movhi r4,0x8000 #r4初始化为0x80000000 l.movhi r2,0x0 #r2初始化为0x0 l.ori r2,r2,0x1234 #r2变为0x1234 l.ori r3,r2,0x1234 #r2与0x1234相“或”,执行后的结果赋值给r3,使得r3为0x1234 l.add r4,r2,r3 #r4为0x1234+0x1234=0x2468 l.sfeqi r4,0x1234 #r4不等于0x1234,所以本指令执行结束后SR[F]为0 l.sfeqi r2,0x1234 #r2等于0x1234,所以本指令执行结束后SR[F]为1 l.nop 0x0001 #模拟器执行到该指令时会退出执行
参考第2章中给出的步骤,在Ubuntu虚拟机中新建一个文件Example.S,内容就是上述代码,然后复制Makefile、Bin2Mem.exe、ram.ld到Example.S所在目录,打开终端,调整路径至代码所在目录,输入“make all”,就得到了OR1KSim模拟器的执行结果Example.trace及可以在ModelSim中使用的存储器初始化文件mem.data。Example.trace文件的内容如下。
Seeding random generator with value 0x90cf4593 Or1ksim 2011-08-15 Building automata... done, num uncovered: 0/213. Parsing operands data... done. Resetting PIC. loadcode: filename Example.or32 startaddr=00000000 virtphy_transl=00000000 Not COFF file format ELF type: 0x0002 ELF machine: 0x005c ELF version: 0x00000001 ELF sec = 5 Section: .text, vaddr: 0x00000000, paddr: 0x0 offset: 0x00002000, size: 0x00000124 S 00000100: a4000000 l.andi r0,r0,0 r0 = 00000000flag:0 //r0初始化为0x0 S 00000104: 18808000 l.movhi r4,0x8000 r4 = 80000000flag:0//r4初始化为0x80000000 S 00000108: 18400000 l.movhi r2,0 r2 = 00000000flag:0 //r2初始化为0x0 S 0000010c: a8421234 l.ori r2,r2,0x1234 r2 = 00001234flag:0 //r2等于0x1234 S 00000110: a8621234 l.ori r3,r2,0x1234 r3 = 00001234flag:0 //r3等于0x1234 S 00000114: e0821800 l.add r4,r2,r3 r4 = 00002468flag:0 //r4等于r2与r3之和 S 00000118: bc041234 l.sfeqi r4,0x1234 flag: 0 //r4不等于0x1234,SR[F]为0 S 0000011c: bc021234 l.sfeqi r2,0x1234 flag: 1 //r2等于0x1234,SR[F]为1 exit(4660) @reset : cycles 0, insn #0 @exit : cycles 8, insn #9 diff : cycles 8, insn #9
可见程序的执行效果确如设想的那样。本书光盘的Chapter4目录下包括ModelSim仿真工程,Chapter4/Code目录下包括示例程序源代码。
在上面的程序中有两条指令会在本章做重点分析:l.add、l.sfeqi,这两条指令分别有各自的特点。
l.add的特点是:源操作数都是寄存器,并且有要写入的目的寄存器。
l.sfeqi的特点是:指令中含有立即数,没有要写入的目的寄存器,进行的运算与l.add进行的运算不同。
其余数据处理类指令主要有三点不同:指令中有无立即数、有无要写入的目的寄存器、运算种类。
4.3 流水线的简单模型
本书在分析具体指令的时候采用的是流水线驱动的分析方法,所以在分析具体指令之前,需要介绍流水线的简单模型。
数字电路中有组合逻辑、时序逻辑之分,其中时序逻辑最基本的器件是寄存器。这里的寄存器不是处理器中所说的寄存器r0~r31,处理器中的寄存器是一个更高层面的概念,时序逻辑中使用的寄存器是类似于D触发器这种数字电路的基本器件。寄存器按照给定时间脉冲来进行同步时序操作,使得时序逻辑电路具有记忆功能。而组合逻辑电路则由逻辑门组成,提供电路的所有逻辑功能。实际的数字电路是组合逻辑与时序逻辑的结合。如果寄存器的输出端和输入端存在环路,这样的电路被称为“状态机”,如图4.1所示。如果寄存器之间有连接,而没有上述的环路,这样的电路结构被称为“流水线结构”,如图4.2所示。
图4.2 流水线的简单模型
在流水线结构中信号在寄存器之间传递,每传递到一级都会引起相应的组合逻辑电路变化,对这种模型进行抽象描述就是寄存器传输级(RTL:Register Transfer Level)。我们可以使用如下伪代码描述流水线结构。
module PipeLine( Signal1 ) input Signal1; //外部输入的信号 reg Signal2; reg Signal3; reg Signal4; always @( Signal1) begin …… //组合逻辑1,外部输入的信号会引起一些电路状态变化 end always @(posedge clk) begin //第一个D触发器,Signal1的值传递给Signal2 …… Signal2 <= Signal1; end always @( Signal2) begin …… //组合逻辑2,信号Signal2的值会引起一些电路状态变化 end always @(posedge clk) begin //第二个D触发器,Signal2的值传递给Signal3 …… Signal3 <= Signal2; end always @( Signal3) begin …… //组合逻辑3,信号Signal3的值会引起一些电路状态变化 end always @(posedge clk) begin//第三个D触发器,Signal3的值传递给Signal4 …… Signal4 <= Signal3; end endmodule
从图1.8可以发现CTRL模块在OR1200的CPU中处于一个核心位置,实际上在CTRL模块中实现了流水线,其代码基本结构就如同上面的伪代码,只是将Signal1、Signal2、Signal3和Signal4分别替换为if_insn、id_insn、ex_insn和wb_insn,表示取指阶段的指令、译码阶段的指令、执行阶段的指令和上一条执行完毕的指令,如下所示。
module CTRL( if_insn) input if_insn; //IF模块将取得的指令送到CTRL模块,指令进入流水线 reg id_insn; //处于流水线译码阶段的指令 reg ex_insn; //处于流水线执行阶段的指令 reg wb_insn; //上一条执行完毕的指令 always @( if_insn) begin …… //取指阶段的组合逻辑输出 end always @(posedge clk) begin //取指阶段的时序逻辑,在其中指令会进入译码阶段 …… id_insn <= if_insn; end always @( id_insn) begin …… //译码阶段的组合逻辑输出 end always @(posedge clk) begin //译码阶段的时序逻辑,在其中指令会进入执行阶段 …… ex_insn <= id_insn; end always @( ex_insn) begin …… //执行阶段的组合逻辑输出 end always @(posedge clk) begin //执行阶段的时序逻辑,在其中指令会被保存到wb_insn …… wb_insn <= ex_insn; end endmodule
上述代码中的wb_insn虽然从字面理解是Write Back Instruction,可翻译为回写阶段的指令,但是笔者并没有因此认为OR1200是四级流水线,还是坚持认为OR1200是三级流水线,因为wb_insn的作用实在有限,没有必要单独拿出来作为流水线的一个阶段,从后面的指令分析中也可以看出这一点。
有了上面的模型,读者就可以满怀信心,进入流水线驱动的指令分析过程了。
4.4 l.add指令分析
l.add指令的用法是:l.add rD,rA,rB,作用是将寄存器rA、rB的值相加,结果保存在寄存器rD中,同时该指令影响特殊寄存器SR的CY、OV位,其指令格式如表4.3所示。
表4.3 l.add指令格式
在分析用例中有指令l.add r4,r2,r3,对应的指令二进制数代码为0xe0821800,其解释如表4.4所示。可见指令的二进制数代码中实际存放的是寄存器rD、rA和rB的序号。
表4.4 l.add r4,r2,r3指令二进制数解释
为了便于说明,将指令l.add处理过程涉及的主要模块及模块之间的连接关系用图4.3表示,图中给出了模块的主要输入/输出接口,这些输入/输出接口在我们分析l.add及其余数据处理类指令的处理过程中已经够用,各个模块详细的输入/输出接口连接关系请参考本书光盘的or1200_cpu.vsd文件。在图4.3中每个模块的左边是输入接口,右边是输出接口。
CTRL中实现了流水线,完成指令的解码,输出控制信号,调度其他模块工作;RF模块描述了通用寄存器堆(或称为寄存器文件)的实现;OPERAND_MUX是一个操作数选择器,从多个输入中选择两个作为ALU的操作数a、b;ALU模块接收CTRL的各种运算操作码,同时接收OPERAND_MUX输出的操作数,从而进行各种算术、逻辑、移位、数据传送、比较等运算;WB_MUX是回写复用器,从多个输入中选择一个输出,作为要写入目的寄存器的数据(图4.3中只画了一个输入,就是ALU的运算结果result);SPRS中有对特殊寄存器操作的代码,同时也有一些特殊寄存器在其中实现,如SR。
图4.3 数据处理类指令涉及的主要模块的局部连接关系
4.4.1 l.add取指阶段的组合逻辑输出
OR1200 处理器的流水线是在CTRL模块中实现的,所以要分析的代码主要在文件or1200_ctrl.v中。在分析具体指令处理过程时,不再考虑取指过程,认为IF模块已经取得指令,并且送入CTRL模块的输入接口if_insn(参考3.5、3.6 节对取指过程的分析)。CTRL模块的if_insn为l.add指令,会引起如下信号变化。
(1)CTRL模块依据if_insn控制输出if_maci_op。if_maci_op表示取指阶段的指令是否为l.maci。此处if_insn[31:26]等于OR1200_OR32_ALU(即0x38),所以if_maci_op为0。在这里需要明确:所有的宏定义都在or1200_defines.v中定义。
or1200_ctrl.v
`ifdef OR1200_MAC_IMPLEMENTED //在最小系统中可以配置使用MAC
assign if_maci_op = (if_insn[31:26] == `OR1200_OR32_MACI);
`else
assign if_maci_op = 1'b0;
`endif
(2)CTRL模块改变rf_addra、rf_addrb、rf_rda和rf_rdb的值。
or1200_ctrl.v assign rf_addra = if_insn[20:16]; //rf_addra为源寄存器rA的序号 assign rf_addrb = if_insn[15:11]; //rf_addrb为源寄存器rB的序号 assign rf_rda = if_insn[31] || if_maci_op;//此处rf_rda存储的是指令的最高位, //表示是否读取rA assign rf_rdb = if_insn[30]; //此处rf_rdb存储的是指令的次高位, //表示是否读取rB
从图4.3可以知道rf_addra、rf_addrb、rf_rda和rf_rdb 4个信号都输出到RF模块,分别对应RF模块的addra、addrb、rda和rdb。RF是寄存器文件(Register File)模块,其内部主要还是RAM,寄存器就对应RAM中的存储单元,比如:r1对应mem[1]、r2对应mem[2],所以对寄存器的读写实际都是对RAM的读写。寄存器r0~r31可以映射到特殊寄存器的组0,所以使用特殊寄存器访问指令l.mtspr、l.mfspr也可以访问寄存器。本章不考虑RF中与l.mtspr、l.mfspr有关的部分,此时RF内部结构可以使用图4.4简单描述,注意图中只是列出了RF的主要接口,并且内部的rf_a、rf_b模块还是采用“左边接口是输入、右边接口是输出”这样的方式绘制的。
图4.4 RF内部结构简单描述
可见RF中有两个模块rf_a、rf_b,这两个模块都是简单双口RAM(读/写端口分开),在OR1200中简单双口RAM的定义在文件or1200_dpram.v中,主要代码如下。
or1200_dpram.v module or1200_dpram ( clk_a, ce_a, addr_a, do_a, clk_b, ce_b, we_b, addr_b, di_b ); // Default address and data buses width parameter aw = 5; // 默认地址线是5位宽度,因为只有32个寄存器 parameter dw = 32; // 数据宽度是32,寄存器是32位的 // // Generic synchronous double-port RAM interface // input clk_a; // 时钟 input ce_a; // 读操作片选信号 input [aw-1:0] addr_a; // 读地址 output [dw-1:0] do_a; // 输出数据 input clk_b; // 时钟 input ce_b; // 写操作片选信号 input we_b; // 写使能信号 input [aw-1:0] addr_b; // 写地址 input [dw-1:0] di_b; // 要写入的数据 reg [dw-1:0] mem [(1<<aw)-1:0] //与QMEM中一样,也是使用数组描述RAM reg [aw-1:0] addr_a_reg; // 读地址寄存器 assign do_a = mem[addr_a_reg]; //读RAM always @(posedge clk_a) if (ce_a) addr_a_reg <= addr_a; //寄存读地址 // RAM write // always @(posedge clk_b) if (ce_b & we_b) mem[addr_b] <= di_b; //写RAM endmodule // or1200_dpram
与QMEM中RAM定义是相似的,只是RF中RAM的地址线是5位,因为只有32个寄存器。观察图4.4,RF中使用了两个简单双口RAM:rf_a、rf_b,并且这两个简单双口RAM的“写”相关接口是一样的,但是“读”端口相互分开,这样设计的优点是:如果是写寄存器,那么会同时写rf_a、rf_b,所以rf_a、rf_b中保存的内容始终是一样的;如果是读寄存器,那么可以同时读出两个寄存器的值,分别是dataa、datab。
通过对RF模块的分析可以看出CTRL模块的输出rf_rda、rf_addra、rf_addrb和rf_rdb分别表示:读rf_a使能、读rf_a的地址、读rf_b使能和读rf_b的地址。回顾l.add模块在取指阶段的组合逻辑输出,内容如下。
or1200_ctrl.v assign rf_addra = if_insn[20:16]; //读rf_a的地址 assign rf_addrb = if_insn[15:11]; //读rf_b的地址 assign rf_rda = if_insn[31] || if_maci_op; //读rf_a使能 assign rf_rdb = if_insn[30]; //读rf_b使能
对于指令l.add而言,需要读出两个寄存器的值,所以其指令的二进制数代码的最高位、次高位都为1。从这里也可以总结出如果指令的最高两位都是1,那么该指令需要读取两个寄存器的值作为源操作数;如果指令的最高两位只有一位是1,那么该指令需要读取一个寄存器的值作为源操作数;如果指令的最高两位都是0,那么该指令不需要读取寄存器。
以上就是l.add指令在取指阶段的组合逻辑输出,主要工作就是为读取源寄存器做准备。完整的信号变化参考表4.5。
表4.5 l.add指令在取指阶段的组合逻辑输出
4.4.2 l.add取指阶段的时序逻辑输出
(1)RF中rf_a、rf_b单元会分别寄存读地址,参考上面关于简单双口RAM的代码分析。
(2)CTRL模块会设置变量sel_imm的值,该值表示是否选择立即数作为ALU模块的操作数b,为1表示选择立即数作为操作数b,在后面译码阶段sel_imm的作用会更加清楚明白。代码如下,此处l.add指令的26~31位是0x38,即OR1200_OR32_ALU,所以设置sel_imm为0。需要说明一下,id_freeze表示流水线译码阶段停滞,在指令的处理过程中暂时不考虑流水线停滞的情况,认为id_freeze始终为0,同样也认为if_freeze、ex_freeze始终为0。因为流水线停滞是由于一些指令的处理过程引起的,所以在分析某些指令的时候会介绍其如何改变if_freeze、id_freeze和ex_freeze的值,从而引起流水线停滞。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
sel_imm <= 1'b0;
else if (!id_freeze) begin //不考虑id_freeze为1的情况
case (if_insn[31:26]) // synopsys parallel_case
`OR1200_OR32_JALR: // j.jalr,转移类指令中的一种
sel_imm <= 1'b0;
`OR1200_OR32_JR: // l.jr,转移类指令中的一种
sel_imm <= 1'b0;
`OR1200_OR32_RFE: // l.rfe,异常返回指令
sel_imm <= 1'b0;
`OR1200_OR32_MFSPR: // l.mfspr,读取特殊寄存器指令
sel_imm <= 1'b0;
`OR1200_OR32_MTSPR: // l.mtspr,写特殊寄存器指令
sel_imm <= 1'b0;
`OR1200_OR32_XSYNC: //l.sys,系统调用指令
sel_imm <= 1'b0;
`ifdef OR1200_MAC_IMPLEMENTED
`OR1200_OR32_MACMSB: // l.mac/l.msb,乘累加/乘累减指令
sel_imm <= 1'b0;
`endif
`OR1200_OR32_SW: // l.sw,存储字指令
sel_imm <= 1'b0;
`OR1200_OR32_SB: // l.sb,存储字节指令
sel_imm <= 1'b0;
`OR1200_OR32_SH: // l.sh,存储半字指令
sel_imm <= 1'b0;
`OR1200_OR32_ALU: //不带立即数的算术逻辑指令
sel_imm <= 1'b0;
`OR1200_OR32_SFXX: //比较指令
sel_imm <= 1'b0;
`ifdef OR1200_IMPL_ALU_CUST5
`OR1200_OR32_CUST5: // l.cust5,用户自定义指令
sel_imm <= 1'b0;
`endif
`ifdef OR1200_FPU_IMPLEMENTED
`OR1200_OR32_FLOAT: //浮点处理单元的指令
sel_imm <= 1'b0;
`endif
`OR1200_OR32_NOP: // l.nop,空指令
sel_imm <= 1'b0;
default: begin //其余指令默认选择立即数作为ALU模块的操作数b
sel_imm <= 1'b1;
end
endcase
end
end
(3)CTRL模块设置其输出id_branch_op的值,表示转移指令的具体类型,输出到GENPC模块,转移指令将改变GENPC中寄存器pc的值,在分析转移类指令时会对其进行详细分析。对于指令l.add而言,不用考虑该值,id_branch_op为OR1200_BRANCHOP_NOP。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
id_branch_op <= `OR1200_BRANCHOP_NOP;
else if (id_flushpipe)
id_branch_op <= `OR1200_BRANCHOP_NOP;
else if (!id_freeze) begin //id_freeze表示是否暂停译码阶段,此处不考虑
case (if_insn[31:26])
`OR1200_OR32_J: // 转移指令l.j
id_branch_op <= `OR1200_BRANCHOP_J;
`OR1200_OR32_JAL: // 转移指令l.jal
id_branch_op <= `OR1200_BRANCHOP_J;
`OR1200_OR32_JALR: // 转移指令l.jalr
id_branch_op <= `OR1200_BRANCHOP_JR;
`OR1200_OR32_JR: // 转移指令l.jr
id_branch_op <= `OR1200_BRANCHOP_JR;
`OR1200_OR32_BNF: // 转移指令l.bnf
id_branch_op <= `OR1200_BRANCHOP_BNF;
`OR1200_OR32_BF: // 转移指令l.bf
id_branch_op <= `OR1200_BRANCHOP_BF;
`OR1200_OR32_RFE: // 异常返回指令l.rfe
id_branch_op <= `OR1200_BRANCHOP_RFE;
default: // Non branch instructions
id_branch_op <= `OR1200_BRANCHOP_NOP;
endcase
end
end
(4)将if_insn锁存到id_insn中,l.add指令进入译码阶段,内容如下。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
id_insn <= {`OR1200_OR32_NOP, 26'h041_0000};
else if (id_flushpipe) //不考虑特殊情况,认为id_flushpipe为0
id_insn <= {`OR1200_OR32_NOP, 26'h041_0000};
else if (!id_freeze) begin
id_insn <= if_insn; //指令进入译码阶段
end
end
以上就是l.add指令在取指阶段的时序逻辑输出,主要工作如下。
● 判断指令是否选择立即数作为ALU模块的操作数b。
● 指令进入译码阶段。
完整的信号变化参考表4.6。
表4.6 l.add指令在取指阶段的时序逻辑输出
4.4.3 l.add译码阶段的组合逻辑输出
(1)RF中rf_a、rf_b单元会读出所需源寄存器的值,通过dataa、datab送入OPERAND_MUX模块,参考图4.3。
(2)CTRL模块给id_void赋值,id_void表示处于译码阶段的指令是否为空指令,此处为0,表示不是空指令。
or1200_ctrl.v assign id_void = (id_insn[31:26] == `OR1200_OR32_NOP) & id_insn[16];
(3)CTRL模块给id_branch_addtarget赋值,将id_insn的低26位符号扩展,然后与id_pc相加,结果存入id_branch_addtarget,该信号在转移类指令时会用到,此处不用考虑。
or1200_ctrl.v assign id_branch_addrtarget = {{4{id_insn[25]}}, id_insn[25:0]} + id_pc[31:2];
(4)CTRL模块给id_macrc_op赋值,如果指令是l.macrc,则id_macrc_op为1,反之为0,该信号在分析乘法除法类指令时会用到,此处不用考虑该值。
or1200_ctrl.v `ifdef OR1200_MAC_IMPLEMENTED assign id_macrc_op = (id_insn[31:26] == `OR1200_OR32_MACRC) & id_insn[16]; `else assign id_macrc_op = 1'b0; `endif
(5)CTRL模块依据id_insn控制输出sel_a,在这里涉及流水线中数据相关的解决方法,在4.7节中将专门介绍,此处可以简单认为sel_a就是OR1200_SEL_RF。从图4.3中可知sel_a输出到OPERAND_MUX模块。
or1200_ctrl.v always @(rf_addrw or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw) if ((id_insn[20:16] == rf_addrw) && rfwb_op[0]) //涉及流水线数据相关的问题 sel_a = `OR1200_SEL_EX_FORW; else if ((id_insn[20:16] == wb_rfaddrw) && wbforw_valid) sel_a = `OR1200_SEL_WB_FORW; else sel_a = `OR1200_SEL_RF;
(6)CTRL模块依据id_insn控制输出sel_b,在这里涉及流水线中数据相关的解决方法,也将在4.7节中专门介绍,此外,在之前的输出中sel_imm为0,表示l.add不选择立即数作为ALU模块的操作数b,所以此处可以简单认为sel_b就是OR1200_SEL_RF。从图4.3中可知sel_b也输出到OPERAND_MUX模块。
or1200_ctrl.v always @(rf_addrw or sel_imm or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw) if (sel_imm) //如果指令需要立即数,则sel_b为OR1200_SEL_IMM sel_b = `OR1200_SEL_IMM; else if ((id_insn[15:11] == rf_addrw) && rfwb_op[0]) //涉及流水线数据相 //关的问题 sel_b = `OR1200_SEL_EX_FORW; else if ((id_insn[15:11] == wb_rfaddrw) && wbforw_valid) sel_b = `OR1200_SEL_WB_FORW; else sel_b = `OR1200_SEL_RF;
(7)CTRL模块依据id_insn控制输出id_simm,id_simm表示指令执行需要的立即数,对l.add而言id_simm为{{16'b0}, id_insn[15:0]},也就是将指令中的立即数进行零扩展赋值给id_simm,实际上l.add不使用立即数,所以id_simm的值为多少对指令执行没有影响。参考图4.3可知id_simm也输出到OPERAND_MUX模块。
or1200_ctrl.v always @(id_insn) begin case (id_insn[31:26]) // synopsys parallel_case `OR1200_OR32_ADDI: // l.addi id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; `OR1200_OR32_ADDIC: // l.addic id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; `OR1200_OR32_LWZ, `OR1200_OR32_LBZ, `OR1200_OR32_LBS,//加载指令l.lxx `OR1200_OR32_LHZ, `OR1200_OR32_LHS: id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; `ifdef OR1200_MULT_IMPLEMENTED `OR1200_OR32_MULI: // 乘法指令l.muli id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; `endif `ifdef OR1200_MAC_IMPLEMENTED `OR1200_OR32_MACI: // 与立即数乘累加指令l.maci id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; `endif `OR1200_OR32_MTSPR: // 特殊寄存器写指令l.mtspr id_simm = {16'b0, id_insn[25:21], id_insn[10:0]}; `OR1200_OR32_SW, `OR1200_OR32_SH, `OR1200_OR32_SB: // 存储指令l.sw、l.sh、l.sb id_simm = {{16{id_insn[25]}}, id_insn[25:21], id_insn[10:0]}; `OR1200_OR32_XORI: // l.xori id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; `OR1200_OR32_SFXXI: // l.sfxxi,与立即数比较指令 id_simm = {{16{id_insn[15]}}, id_insn[15:0]}; default: id_simm = {{16'b0}, id_insn[15:0]}; endcase end
(8)参考图4.3可知上面的(1)、(5)、(6)、(7)分别输出dataa、datab,sel_a,sel_b,id_simm到OPERAND_MUX模块的rf_dataa、rf_datab,sel_a,sel_b,simm接口,在OPERAND_MUX模块中将选择最终的操作数作为ALU的输入a、b,但在此阶段只会依据sel_a、sel_b暂存操作数,暂时不考虑流水线相关的问题,所以对指令l.add而言,muxed_a将暂存rf_dataa,也就是从RF的rf_a单元读出的寄存器rA的值,muxed_b将暂存rf_datab,也就是从RF的rf_b单元读出的寄存器rB的值。
or1200_operandmuxes.v always @(ex_forw or wb_forw or rf_dataa or sel_a) begin casez (sel_a) //synopsys parallel_case //此时sel_a为OR1200_SEL_RF `OR1200_SEL_EX_FORW: muxed_a = ex_forw; `OR1200_SEL_WB_FORW: muxed_a = wb_forw; default: muxed_a = rf_dataa; //rf_dataa是从RF的rf_a中读出的寄存器rA的值 endcase end // // Forwarding logic for operand B register // always @(simm or ex_forw or wb_forw or rf_datab or sel_b) begin casez (sel_b) // synopsys parallel_case //此时sel_b也为OR1200_SEL_RF `OR1200_SEL_IMM: muxed_b = simm; `OR1200_SEL_EX_FORW: muxed_b = ex_forw; `OR1200_SEL_WB_FORW: muxed_b = wb_forw; default: muxed_b = rf_datab; //rf_datab是从RF的rf_b中读出的寄存器rB的值 endcase end
(9)CTRL模块给出multicycle的值,参考本书光盘中的or1200_cpu.vsd可知,multicycle输出到FREEZE模块,会影响流水线,当指令需要的周期数大于1时,流水线会暂停,以等待指令执行完毕。OR1200中数据处理类指令都只需要一个时钟周期,所以multicycle始终为OR1200_ONE_CYCLE。
or1200_ctrl.v always @(id_insn) begin case (id_insn[31:26]) //synopsys parallel_case `OR1200_OR32_RFE, //异常返回指令l.rfe `OR1200_OR32_MFSPR: //特殊寄存器读指令l.mfspr multicycle = `OR1200_TWO_CYCLES; //指令l.rfe、l.mfspr需要的时钟 //周期大于1 default: begin multicycle = `OR1200_ONE_CYCLE; //宏定义OR1200_ONE_CYCLE的值为0 end endcase end
(10)CTRL模块给出wait_on的值,参考本书光盘中的or1200_cpu.vsd可知,wait_on也输出到FREEZE模块,会影响流水线。对指令l.add而言,虽然id_insn[31:26]是OR1200_OR32_ALU,但是其最后4位都是0,所以wait_on等于OR1200_WAIT_ON_NOTHING,表示不会使流水线暂停。
or1200_ctrl.v
always @(id_insn) begin
case (id_insn[31:26]) // synopsys parallel_case
`OR1200_OR32_ALU:
wait_on = ( 1'b0
`ifdef OR1200_DIV_IMPLEMENTED
| (id_insn[4:0] == `OR1200_ALUOP_DIV) | (id_insn[4:0] == `OR1200_ALUOP_DIVU)
`endif
`ifdef OR1200_MULT_IMPLEMENTED
|(id_insn[4:0]==`OR1200_ALUOP_MUL) |(id_insn[4:0]==`OR1200_ALUOP_MULU)
`endif
) ? `OR1200_WAIT_ON_MULTMAC : `OR1200_WAIT_ON_NOTHING;
`ifdef OR1200_MULT_IMPLEMENTED
`ifdef OR1200_MAC_IMPLEMENTED
`OR1200_OR32_MACMSB,
`OR1200_OR32_MACI,
`endif
`OR1200_OR32_MULI:
wait_on = `OR1200_WAIT_ON_MULTMAC; //乘法指令需要多个时钟周期
`endif
`ifdef OR1200_MAC_IMPLEMENTED
`OR1200_OR32_MACRC:
wait_on=id_insn[16]?`OR1200_WAIT_ON_MULTMAC: `OR1200_WAIT_ON_NOTHING;
`endif
`ifdef OR1200_FPU_IMPLEMENTED
`OR1200_OR32_FLOAT: begin
wait_on = id_insn[`OR1200_FPUOP_DOUBLE_BIT] ? 0 : `OR1200_WAIT_ON_FPU;
end
`endif
`ifndef OR1200_DC_WRITETHROUGH //注意这里的OR1200_DC_WRITETHROUGH源代码中
//拼写成了OR1200_DC_WRITEHROUGH,应该是拼写
//错误,此处表示数据缓存采用通写法
`OR1200_OR32_MTSPR: begin
wait_on = `OR1200_WAIT_ON_MTSPR;
end
`endif
default: begin
wait_on = `OR1200_WAIT_ON_NOTHING;
end
endcase // case (id_insn[31:26])
end // always @ (id_insn)
(11)CTRL模块给出id_mac_op的值,该信号与乘法指令有关,此处不用考虑,其值就是OR1200_MACOP_NOP。
or1200_ctrl.v always @(id_insn) begin case (id_insn[31:26]) // synopsys parallel_case `OR1200_OR32_MACI: // 与立即数乘累加指令l.maci id_mac_op = `OR1200_MACOP_MAC; `OR1200_OR32_MACMSB: // 乘累加指令l.mac, 乘累减指令 // l.msb id_mac_op = id_insn[2:0]; default: id_mac_op = `OR1200_MACOP_NOP; endcase end
(12)CTRL模块给出id_lsu_op的值,该信号输出到LSU模块,与加载存储类指令有关,此处不用考虑,其值就是OR1200_LSU_NOP。
or1200_ctrl.v
always @(id_insn) begin
case (id_insn[31:26]) // synopsys parallel_case
`OR1200_OR32_LWZ: //加载字指令l.lwz
id_lsu_op = `OR1200_LSUOP_LWZ;
`OR1200_OR32_LBZ: // 加载字节指令l.lbz
id_lsu_op = `OR1200_LSUOP_LBZ;
`OR1200_OR32_LBS: // 加载字节指令l.lbs
id_lsu_op = `OR1200_LSUOP_LBS;
`OR1200_OR32_LHZ: // 加载半字指令l.lhz
id_lsu_op = `OR1200_LSUOP_LHZ;
`OR1200_OR32_LHS: // 加载半字指令l.lhs
id_lsu_op = `OR1200_LSUOP_LHS;
`OR1200_OR32_SW: // 存储字指令l.sw
id_lsu_op = `OR1200_LSUOP_SW;
`OR1200_OR32_SB: // 存储字节指令l.sb
id_lsu_op = `OR1200_LSUOP_SB;
`OR1200_OR32_SH: // 存储半字指令l.sh
id_lsu_op = `OR1200_LSUOP_SH;
default:
id_lsu_op = `OR1200_LSUOP_NOP; // Non load/store instructions
endcase
end
(13)CTRL模块改变rfe的值,参考本书光盘中的or1200_cpu.vsd可知该信号输出到IF模块,当指令是异常返回指令l.rfe时,会使得rfe为1,反之为0。在分析l.rfe指令时会分析该信号的作用,此处不用考虑,其值就是0。
or1200_ctrl.v assign rfe = (id_branch_op == `OR1200_BRANCHOP_RFE) | (ex_branch_op == `OR1200_BRANCHOP_RFE);
以上就是指令l.add在译码阶段的组合逻辑输出,虽然有很多输出,但主要工作如下。
● 读出指令需要的源寄存器的值,输出到OPERAND_MUX
● 给出立即数的值到OPERAND_MUX
● 给出OPERAND_MUX的输入sel_a、sel_b的值
● OPERAND_MUX利用输入的sel_a、sel_b,从输入的数据中选择两个操作数暂存在muxed_a、muxed_b
● 依据指令类型输出其余模块的控制信号
完整的信号变化参考表4.7。
表4.7 l.add指令在译码阶段的组合逻辑输出
4.4.4 l.add译码阶段的时序逻辑输出
(1)在译码阶段的组合逻辑输出中muxed_a、muxed_b分别是rf_dataa、rf_datab,即读出的寄存器值,在本阶段OPERAND_MUX模块会寄存muxed_a、muxed_b到operand_a、operand_b,参考图4.3,可知operand_a、operand_b直接连接到ALU模块输入的a、b,作为ALU模块下一步运算的操作数,代码如下。
or1200_operandmuxes.v // // Operand A register // always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) begin operand_a <= 32'd0; saved_a <= 1'b0; end else if (!ex_freeze && id_freeze && !saved_a) begin operand_a <= muxed_a; saved_a <= 1'b1; end else if (!ex_freeze && !saved_a) begin //不考虑复杂情况,此处的 //operand_a就是muxed_a operand_a <= muxed_a; end else if (!ex_freeze && !id_freeze) saved_a <= 1'b0; end // // Operand B register // always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) begin operand_b <= 32'd0; saved_b <= 1'b0; end else if (!ex_freeze && id_freeze && !saved_b) begin operand_b <= muxed_b; saved_b <= 1'b1; end else if (!ex_freeze && !saved_b) begin //不考虑复杂情况,此处的 //operand_b就是muxed_b operand_b <= muxed_b; end else if (!ex_freeze && !id_freeze) saved_b <= 1'b0; end
(2)CTRL模块将id_simm的值赋给ex_simm,ex_simm输出到LSU、SPRS模块,此处为{{16'b0}, id_insn[15:0]},但实际上l.add指令并不使用该值,在加载存储类指令中会用到该变量。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) ex_simm <= 32'h0000_0000; else if (!ex_freeze) begin //ex_freeze表示是否暂停执行阶段,不考虑特 //殊情况,认为其值为0 ex_simm <= id_simm; end end
(3)CTRL模块将id_branch_addtarget的值赋给ex_branch_addtarget,该信号同id_branch_addtarget一样,也输出到GENPC模块,对指令l.add而言,无须考虑该信号。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) ex_branch_addrtarget <= 0; else if (!ex_freeze) ex_branch_addrtarget <= id_branch_addrtarget; end
(4)CTRL模块给ex_macrc_op赋值,ex_macrc_op输出到MULTI_MAC模块,表示是否为l.macrc指令。一般情况下就是把id_macrc_op的值赋给ex_macrc_op,对指令l.add而言此处为0。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) ex_macrc_op <= 1'b0; else if (!ex_freeze & id_freeze | ex_flushpipe) ex_macrc_op <= 1'b0; else if (!ex_freeze) //不考虑特殊情况,就是把 //id_macrc_op的值赋给ex_macrc_op ex_macrc_op <= id_macrc_op; end
(5)CTRL模块给出rf_addrw的值,参考图4.3,可知该信号输出到RF模块,表示指令要写的目的寄存器序号,对l.add而言该值就是rD的序号。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
rf_addrw <= 5'd0;
else if (!ex_freeze & id_freeze)
rf_addrw <= 5'd00;
else if (!ex_freeze)
case (id_insn[31:26]) // synopsys parallel_case
`OR1200_OR32_JAL, `OR1200_OR32_JALR:
rf_addrw <= 5'd09; // link register r9
default:
rf_addrw <= id_insn[25:21]; //从表4.4中可以看出id_insn
//[25:21]正是rD的序号
endcase
end
(6)CTRL模块给出except_illegal的值,参考本书光盘中的or1200_cpu.vsd可知该信号输出到EXCEPION模块,如果为1,表示指令出错(比如:使用了处理器不支持的指令),会使得OR1200进入指令异常的处理例程,反之表示指令正常。此处为0。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) except_illegal <= 1'b0; else if (!ex_freeze & id_freeze | ex_flushpipe) except_illegal <= 1'b0; else if (!ex_freeze) begin case (id_insn[31:26]) // synopsys parallel_case `OR1200_OR32_J, `OR1200_OR32_JAL, `OR1200_OR32_JALR, `OR1200_OR32_JR, `OR1200_OR32_BNF, `OR1200_OR32_BF, `OR1200_OR32_RFE, `OR1200_OR32_MOVHI, `OR1200_OR32_MFSPR, `OR1200_OR32_XSYNC, `ifdef OR1200_MAC_IMPLEMENTED `OR1200_OR32_MACI, `endif `OR1200_OR32_LWZ, `OR1200_OR32_LBZ, `OR1200_OR32_LBS, `OR1200_OR32_LHZ, `OR1200_OR32_LHS, `OR1200_OR32_ADDI, `OR1200_OR32_ADDIC, `OR1200_OR32_ANDI, `OR1200_OR32_ORI, `OR1200_OR32_XORI, `ifdef OR1200_MULT_IMPLEMENTED `OR1200_OR32_MULI, `endif `OR1200_OR32_SH_ROTI, `OR1200_OR32_SFXXI, `OR1200_OR32_MTSPR, `ifdef OR1200_MAC_IMPLEMENTED `OR1200_OR32_MACMSB, `endif `OR1200_OR32_SW, `OR1200_OR32_SB, `OR1200_OR32_SH, `OR1200_OR32_SFXX, `ifdef OR1200_IMPL_ALU_CUST5 `OR1200_OR32_CUST5, `endif `OR1200_OR32_NOP: except_illegal <= 1'b0; //以上都是合法指令 `ifdef OR1200_FPU_IMPLEMENTED `OR1200_OR32_FLOAT: except_illegal <= id_insn[`OR1200_FPUOP_DOUBLE_BIT]; `endif `OR1200_OR32_ALU: except_illegal <= 1'b0 `ifdef OR1200_MULT_IMPLEMENTED `ifdef OR1200_DIV_IMPLEMENTED `else | (id_insn[4:0] == `OR1200_ALUOP_DIV) //没有配置DIV,但是使用除法指 //令时会引起异常 | (id_insn[4:0] == `OR1200_ALUOP_DIVU) `endif `else | (id_insn[4:0] == `OR1200_ALUOP_DIV) //没有配置MULT,但是使用除法或 //乘法指令时会引起异常 | (id_insn[4:0] == `OR1200_ALUOP_DIVU) | (id_insn[4:0] == `OR1200_ALUOP_MUL) `endif `ifdef OR1200_IMPL_ADDC `else | (id_insn[4:0] == `OR1200_ALUOP_ADDC) //没有配置实现l.addc指令,但 //是使用l.addc指令时会引起异常 `endif `ifdef OR1200_IMPL_ALU_FFL1 `else | (id_insn[4:0] == `OR1200_ALUOP_FFL1) //没有配置实现l.ffl1指令,但 //是使用l.ffl1指令时会引起异常 `endif `ifdef OR1200_IMPL_ALU_ROTATE `else | ((id_insn[4:0] == `OR1200_ALUOP_SHROT) &(id_insn[9:6] == `OR1200_SHROTOP_ROR)) //没有配置实现算术移位指令,但是使用算术移位指令时会引起异常 `endif `ifdef OR1200_IMPL_SUB `else | (id_insn[4:0] == `OR1200_ALUOP_SUB) //没有配置实现l.sub指令,但是 //使用l.sub指令时会引起异常 `endif `ifdef OR1200_IMPL_ALU_EXT `else | (id_insn[4:0] == `OR1200_ALUOP_EXTHB) | //没有配置实现扩展指令, //但是使用扩展指令时会引起异常 (id_insn[4:0] == `OR1200 ALUOP EXTW) `endif ; default: except_illegal <= 1'b1; //其他不存在的指令都会引起异常 endcase end // if (!ex_freeze) end
(7)CTRL模块给出alu_op的值,参考图4.3,可知该信号输出到ALU模块的同名接口,alu_op是ALU模块操作码,此处为{1'b0,id_insn[3:0]},即5'b00000。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) alu_op <= `OR1200_ALUOP_NOP; else if (!ex_freeze & id_freeze | ex_flushpipe) alu_op <= `OR1200_ALUOP_NOP; else if (!ex_freeze) begin case (id_insn[31:26]) // synopsys parallel_case `OR1200_OR32_MOVHI: // l.movhi指令 alu_op <= `OR1200_ALUOP_MOVHI; `OR1200_OR32_ADDI: // l.addi指令 alu_op <= `OR1200_ALUOP_ADD; `OR1200_OR32_ADDIC: // l.addic指令 alu_op <= `OR1200_ALUOP_ADDC; `OR1200_OR32_ANDI: // l.andi指令 alu_op <= `OR1200_ALUOP_AND; `OR1200_OR32_ORI: // l.ori指令 alu_op <= `OR1200_ALUOP_OR; `OR1200_OR32_XORI: // l.xori指令 alu_op <= `OR1200_ALUOP_XOR; `ifdef OR1200_MULT_IMPLEMENTED `OR1200_OR32_MULI: // l.muli指令 alu_op <= `OR1200_ALUOP_MUL; `endif `OR1200_OR32_SH_ROTI: //带立即数的移位指令 alu_op <= `OR1200_ALUOP_SHROT; `OR1200_OR32_SFXXI: //与立即数比较指令 alu_op <= `OR1200_ALUOP_COMP; `OR1200_OR32_ALU: alu_op <= {1'b0,id_insn[3:0]}; `OR1200_OR32_SFXX: //寄存器比较指令 alu_op <= `OR1200_ALUOP_COMP; `ifdef OR1200_IMPL_ALU_CUST5 `OR1200_OR32_CUST5: //自定义指令 alu_op <= `OR1200_ALUOP_CUST5; `endif default: begin alu_op <= `OR1200_ALUOP_NOP; end endcase end end
(8)CTRL模块给出alu_op2的值,参考图4.3,可知该信号也输出到ALU模块,在上述(7)中,alu_op代表一类指令,比如:移位指令;alu_op2指代具体的移位指令,比如:逻辑左移指令等。对指令l.add而言alu_op2的值为0。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
alu_op2 <= 0;
else if (!ex_freeze & id_freeze | ex_flushpipe)
alu_op2 <= 0;
else if (!ex_freeze) begin
alu_op2 <= id_insn[`OR1200_ALUOP2_POS]; // `define OR1200_ALUOP2_POS 9:6
end
end
(9)CTRL模块给出comp_op的值,参考图4.3,可知该信号也输出到ALU模块,在上述(7)中,alu_op代表一类指令,比如:比较指令;comp_op指代具体的比较指令,比如:大于等于指令等。对指令l.add而言comp_op的值没有意义。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE) begin
comp_op <= 4'd0;
end else if (!ex_freeze & id_freeze | ex_flushpipe)
comp_op <= 4'd0;
else if (!ex_freeze)
comp_op <= id_insn[24:21]; //如果是比较指令,那么在指令的
//21~24位存放的是比较类型
end
(10)CTRL模块给出spr_write、spr_read的值,spr_write为1表示写特殊寄存器,spr_read为1表示读特殊寄存器。对指令l.add而言,两者都为0。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE) begin
spr_read <= 1'b0;
spr_write <= 1'b0;
end
else if (!ex_freeze & id_freeze | ex_flushpipe) begin
spr_read <= 1'b0;
spr_write <= 1'b0;
end
else if (!ex_freeze) begin
case (id_insn[31:26]) // synopsys parallel_case
`OR1200_OR32_MFSPR: begin // 特殊寄存器读指令l.mfspr
spr_read <= 1'b1;
spr_write <= 1'b0;
end
`OR1200_OR32_MTSPR: begin // 特殊寄存器写指令l.mtspr
spr_read <= 1'b0;
spr_write <= 1'b1;
end
default: begin //默认情况下spr_write、spr_read都为0
spr_read <= 1'b0;
spr_write <= 1'b0;
end
endcase
end
end
(11)CTRL模块将id_mac_op的值寄存到ex_mac_op,id_mac_op在译码阶段的组合逻辑输出中已经确定其值为0,所以此处ex_mac_op也为0,表示处于流水线执行阶段的指令不是乘法指令。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) ex_mac_op <= `OR1200_MACOP_NOP; else if (!ex_freeze & id_freeze | ex_flushpipe) ex_mac_op <= `OR1200_MACOP_NOP; else if (!ex_freeze) ex_mac_op <= id_mac_op; end
(12)CTRL模块将id_branch_op的值寄存到ex_branch_op,同id_branch_op一样,ex_branch_op也输出到GENPC模块。id_branch_op在取指阶段的时序逻辑输出中已经确定其值为0,所以此处ex_branch_op也为0,表示处于流水线执行阶段的指令不是转移指令。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) if (rst == `OR1200_RST_VALUE) ex_branch_op <= `OR1200_BRANCHOP_NOP; else if (!ex_freeze & id_freeze | ex_flushpipe) ex_branch_op <= `OR1200_BRANCHOP_NOP; else if (!ex_freeze) ex_branch_op <= id_branch_op;
(13)给出rfwb_op的值,参考图4.3,可知该值输出到WB_MUX模块,同时其最低位还输出到RF模块的we接口,从下面的代码中可以知道如果一条指令需要写目的寄存器,那么rfwb_op的最低位就会为1。rfwb_op在WB_MUX模块中的作用是从多个输入中选择要写入目的寄存器的数据,对此在l.add指令执行阶段的组合逻辑输出中会有分析。此处rfwb_op的值为{`OR1200_RFWBOP_ALU, 1’b1},即4’b0001,所以会写目的寄存器。
or1200_ctrl.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
rfwb_op <= `OR1200_RFWBOP_NOP;
else if (!ex_freeze & id_freeze | ex_flushpipe)
rfwb_op <= `OR1200_RFWBOP_NOP;
else if (!ex_freeze) begin
case (id_insn[31:26]) // synopsys parallel_case
`OR1200_OR32_JAL: // j.jal指令
rfwb_op <= {`OR1200_RFWBOP_LR, 1'b1};
`OR1200_OR32_JALR: // j.jalr指令
rfwb_op <= {`OR1200_RFWBOP_LR, 1'b1};
`OR1200_OR32_MOVHI: // l.movhi指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`OR1200_OR32_MFSPR: // l.mfspr指令
rfwb_op <= {`OR1200_RFWBOP_SPRS, 1'b1};
`OR1200_OR32_LWZ: // l.lwz指令
rfwb_op <= {`OR1200_RFWBOP_LSU, 1'b1};
`OR1200_OR32_LBZ: // l.lbz指令
rfwb_op <= {`OR1200_RFWBOP_LSU, 1'b1};
`OR1200_OR32_LBS: // l.lbs指令
rfwb_op <= {`OR1200_RFWBOP_LSU, 1'b1};
`OR1200_OR32_LHZ: // l.lhz指令
rfwb_op <= {`OR1200_RFWBOP_LSU, 1'b1};
`OR1200_OR32_LHS: // l.lhs指令
rfwb_op <= {`OR1200_RFWBOP_LSU, 1'b1};
`OR1200_OR32_ADDI: // l.addi指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`OR1200_OR32_ADDIC: // l.addic指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`OR1200_OR32_ANDI: // l.andi指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`OR1200_OR32_ORI: // l.ori指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`OR1200_OR32_XORI: // l.xori指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`ifdef OR1200_MULT_IMPLEMENTED
`OR1200_OR32_MULI: // l.muli指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`endif
`OR1200_OR32_SH_ROTI: // 带立即数的移位、循环指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`OR1200_OR32_ALU: //所有不带立即数的算术指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`ifdef OR1200_ALU_IMPL_CUST5
`OR1200_OR32_CUST5: // l.cust5,定制指令
rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1};
`endif
`ifdef OR1200_FPU_IMPLEMENTED
`OR1200_OR32_FLOAT: // 与浮点运算单元有关的指令
rfwb_op <= {`OR1200_RFWBOP_FPU,!id_insn[3]};
`endif
default:
rfwb_op <= `OR1200_RFWBOP_NOP;
//`define OR1200_RFWBOP_NOP 4'b0000
endcase
end
end
(14)CTRL模块给出sig_syscall的值,参考本书光盘中的or1200_cpu.vsd可知该信号输出到EXCEPTION模块,该值为1表示当前指令是系统调用指令l.sys,反之为0,在分析异常处理类指令时将分析该信号的作用。对指令l.add而言,sig_syscall的值为0。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) sig_syscall <= 1'b0; else if (!ex_freeze & id_freeze | ex_flushpipe) sig_syscall <= 1'b0; else if (!ex_freeze) begin //如果是l.sys指令,那么 //sig_syscall为1 sig_syscall <= (id_insn[31:23] == {`OR1200_OR32_XSYNC, 3'b000}); end end
(15)CTRL模块给出sig_trap的值,参考本书光盘中的or1200_cpu.vsd可知该信号输出到EXCEPTION模块,该值为1表示当前指令是自陷指令l.trap,反之为0,在分析异常处理类指令时将分析该信号的作用。对指令l.add而言,sig_trap的值为0。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) sig_trap <= 1'b0; else if (!ex_freeze & id_freeze | ex_flushpipe) sig_trap <= 1'b0; else if (!ex_freeze) begin //如果是l.trap指令,那么 //sig_trap为1 sig_trap <= (id_insn[31:23] == {`OR1200_OR32_XSYNC, 3'b010})| du_hwbkpt; end end
(16)CTRL模块给出dc_no_writethrough的值,该信号输出到DCache模块,对OR1200而言,该值始终为0。
or1200_ctrl.v
`ifdef OR1200_DC_NOSTACKWRITETHROUGH //OR1200没有定义OR1200_DC_
//NOSTACKWRITETHROUGH
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE)
dc_no_writethrough <= 0;
else if (!ex_freeze)
dc_no_writethrough <= (id_insn[20:16] == 5'd1) | (id_insn[20:16] == 5'd2);
end
`else
assign dc_no_writethrough = 0; //所以dc_no_writethrough始终为0
`endif
(17)ex_insn等于id_insn,指令进入执行阶段。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) ex_insn <= {`OR1200_OR32_NOP, 26'h041_0000}; else if (!ex_freeze & id_freeze | ex_flushpipe) ex_insn <= {`OR1200_OR32_NOP, 26'h041_0000}; else if (!ex_freeze) begin //不考虑特殊情况 ex_insn <= id_insn; end end
以上就是l.add指令在译码阶段的时序逻辑输出,虽然输出较多,但主要工作如下。
● OPERAND_MUX将muxed_a、muxed_b分别送入ALU的输入接口a、b,从而ALU得到操作数a、b
● CTRL模块将操作码alu_op、alu_op2和comp_op送入ALU模块
● CTRL模块将要写的目的寄存器地址送入RF;RF的输入we为1,准备写寄存器
● WB_MUX的rfwb_op为OR1200_RFWBOP_ALU,表示选择ALU的输出result作为回写复用器的输出
● 指令进入执行阶段
完整的信号变化参考表4.8。
表4.8 l.add指令在译码阶段的时序逻辑输出
续表
表4.8显示有多个信号输出至ALU模块,包括:alu_op、alu_op2、comp_op、operand_a和operand_b,参考图4.3可知ALU的操作数、操作码都已经准备就绪,所以在下一个阶段就可以按照操作码进行运算了。
4.4.5 l.add执行阶段的组合逻辑输出
(1)CTRL模块给出ex_void的值,参考本书光盘中的or1200_cpu.vsd可知ex_void输出到EXCEPTION模块,表示处于执行阶段的指令是否为空指令或者填充指令(如:0x1461000、0x1441000),对l.add指令而言,此时ex_insn为l.add,所以ex_void为0。
or1200_ctrl.v assign ex_void = (ex_insn[31:26] == `OR1200_OR32_NOP) & ex_insn[16];
(2)CTRL模块给出ex_spr_write、ex_spr_read的值,参考本书光盘中的or1200_cpu.vsd可知这两个信号输出到SPRS模块,用于对特殊寄存器的读写操作,其中spr_write、spr_read在译码阶段的时序逻辑中都已赋值为0,所以此时的ex_spr_write、ex_spr_read也都为0。
or1200_ctrl.v assign ex_spr_write = spr_write && !abort_mvspr; //不考虑特殊情况,认为 //abort_mvspr为0 assign ex_spr_read = spr_read && !abort_mvspr;
(3)CTRL模块给出mac_op的值,参考本书光盘中的or1200_cpu.vsd可知mac_op输出到MULTI_MAC模块,用于确定乘法操作的种类。此处不考虑特殊情况,认为abort_mvspr就是0,所以mac_op等于ex_mac_op,在译码阶段ex_mac_op为0,所以mac_op为0。
or1200_ctrl.v //不考虑特殊情况,认为abort_mvspr为0 assign mac_op = abort_mvspr ? `OR1200_MACOP_NOP : ex_mac_op;
(4)CTRL模块给出rfe的值,参考本书光盘中的or1200_cpu.vsd可知该信号输出到IF模块,当指令是异常返回指令l.rfe时,会使得rfe为1,反之为0,在指令的译码阶段也会改变该值,在分析l.rfe指令时会分析该信号的作用,此处就是0。
or1200_ctrl.v assign rfe = (id_branch_op == `OR1200_BRANCHOP_RFE) | (ex_branch_op == `OR1200_BRANCHOP_RFE);
(5)ALU模块根据输入的操作数、操作码进行运算,通过result接口输出,参考图4.3,可知result输出到WB_MUX模块的muxin_a。对于指令l.add而言,result是两个源寄存器之和。
or1200_alu.v
always @(alu_op or alu_op2 or a or b or result_sum or result_and or macrc_op
or shifted_rotated or mult_mac_result or flag or carry
`ifdef OR1200_IMPL_ALU_EXT
or extended
`endif
`ifdef OR1200_IMPL_ALU_CUST5
or result_cust5
`endif
) begin
casez (alu_op)
`OR1200_ALUOP_ADD:begin//此时的alu_op是5'b00000,正是OR1200_ALUOP_ADD
result = result_sum;
……
其中result_sum计算方式如下。
or1200_alu.v
//此时ALU模块的输入a、b,就是读出的寄存器值
assign {cy_sum, result_sum} = (a + b_mux) + carry_in;
其中b_mux定义如下:
`ifdef OR1200_IMPL_ALU_COMP3 //默认配置中定义了OR1200_IMPL_ALU_COMP3
`ifdef OR1200_IMPL_SUB //无论有没有配置减法操作,b_mux此时都等于b
assign b_mux = ((alu_op==`OR1200_ALUOP_SUB) |
(alu_op==`OR1200_ALUOP_ COMP)) ? (~b)+1 : b;
`else
assign b_mux = (alu_op==`OR1200_ALUOP_COMP) ? (~b)+1 : b;
`endif
`else // ! `ifdef OR1200_IMPL_ALU_COMP3
`ifdef OR1200_IMPL_SUB
assign b_mux = (alu_op==`OR1200_ALUOP_SUB) ? (~b)+1 : b;
`else
assign b_mux = b;
`endif
`endif
其中carry_in定义如下:
`ifdef OR1200_IMPL_ADDC //无论有没有配置OR1200_IMPL_ADDC,carry_in此时都为0
assign carry_in = (alu_op==`OR1200_ALUOP_ADDC) ?
{{width-1{1'b0}}, carry} : {width{1'b0}};
`else
assign carry_in = {width-1{1'b0}};
`endif
(6)参考图4.3,可知WB_MUX模块的两个输入muxin_a、rfwb_op都已确定,其中muxin_a就是寄存器相加的和,rfwb_op是译码阶段的时序逻辑输出,其值为{`OR1200_RFWBOP_ALU, 1'b1},所以会改变WB_MUX的输出muxout,该信号输出到RF模块的dataw。
or1200_wbmux.v always @(muxin_a or muxin_b or muxin_c or muxin_d or muxin_e or rfwb_op) begin casez(rfwb_op[`OR1200_RFWBOP_WIDTH-1:1]) // synopsys parallel_case //muxin_a就是ALU的输出信号result的值 `OR1200_RFWBOP_ALU: muxout = muxin_a; `OR1200_RFWBOP_LSU: begin muxout = muxin_b; end `OR1200_RFWBOP_SPRS: begin muxout = muxin_c; end `OR1200_RFWBOP_LR: begin muxout = muxin_d + 32'h8; end `ifdef OR1200_FPU_IMPLEMENTED `OR1200_RFWBOP_FPU : begin muxout = muxin_e; end `endif default : begin muxout = 0; end endcase end
在RF中,因为不是特殊寄存器操作指令l.mfspr、l.mtspr,所以spr_valid、spr_write都为0,从而rf_dataw就是dataw。
or1200_rf.v
assign rf_dataw = (spr_valid & spr_write) ? spr_dat_i : dataw;
(7)ALU模块除了计算结果,还会设置变量flagforw、flag_we的值,参考图4.3,可知flagforw与ALU的输出flag_we相与后送入SPRS模块,flag_we则直接送入SPRS模块。此处flag_we为1,flagforw取决于寄存器相加的结果。
or1200_alu.v
always @(alu_op or result_sum or result_and or flagcomp
) begin
casez (alu_op) // synopsys parallel_case
`OR1200_ALUOP_ADDC,
`OR1200_ALUOP_ADD : begin
//当和为0时,会设置flagforw为1,反之flagforw为0
flagforw = (result_sum == 32'h0000_0000);
flag_we = 1'b1; //无论flagforw的值为多少,flag_we都为1
end
`OR1200_ALUOP_AND: begin
flagforw = (result_and == 32'h0000_0000);
flag_we = 1'b1;
end
`OR1200_ALUOP_COMP: begin
flagforw = flagcomp;
flag_we = 1'b1;
end
default: begin
flagforw = flagcomp;
flag_we = 1'b0;
end
endcase
end
(8)ALU计算cyforw、cy_we的值,参考图4.3,可知cyforw输出到SPRS模块,cy_we输出到RF模块的cy_we_i。此处cy_we始终为1,cyforw取决于寄存器相加时是否有进位。
or1200_alu.v
always @(alu_op or cy_sum or cy_sub
) begin
casez (alu_op) // synopsys parallel_case
`OR1200_ALUOP_ADDC,
`OR1200_ALUOP_ADD : begin
cyforw = cy_sum; //这里的cy_sum就是在(5)中计算和的时候的进位
cy_we = 1'b1; //cy_we输出到RF模块的cy_we_i
end
`OR1200_ALUOP_SUB: begin
cyforw = cy_sub;
cy_we = 1'b1;
end
default: begin
cyforw = 1'b0;
cy_we = 1'b0;
end
endcase
end
在RF模块中,cy_we_i会改变cy_we_o的值,cy_we_o输出到SPRS模块,代码如下,此处不讨论特殊情况,认为wb_freeze为0、flushpipe为0,所以cy_we_o为1。
or1200_rf.v always @(`OR1200_RST_EVENT rst or posedge clk) if (rst == `OR1200_RST_VALUE) rf_we_allow <= 1'b1; else if (~wb_freeze) //此处讨论一般情况,wb_freeze为0,flushpipe为 //0,所以rf_we_allow为1 rf_we_allow <= ~flushpipe; assign cy_we_o = cy_we_i && ~wb_freeze && rf_we_allow; //所以cy_we_o等于 //cy_we_i,为1
(9)ALU计算ovforw、ov_we的值,ovforw表示运算过程是否有溢出,参考图4.3,可知ovforw、ov_we都输出到SPRS模块,此处ov_we为1,ovforw的值取决于加法操作的结果。
or1200_alu.v always @(alu_op or ov_sum) begin casez (alu_op) // synopsys parallel_case `OR1200_ALUOP_ADDC, `OR1200_ALUOP_SUB, `OR1200_ALUOP_ADD : begin ovforw = ov_sum; ov_we = 1'b1; end default: begin ovforw = 1'b0; ov_we = 1'b0; end endcase end 其中ov_sum等于如下: assign ov_sum = ((!a[width-1] & !b_mux[width-1]) & result_sum[width-1]) | ((!a[width-1] & b_mux[width-1]) & result_sum[width-1] & alu_op== `OR1200_ALUOP_SUB) | ((a[width-1] & b_mux[width-1]) & !result_sum[width-1]); ov_sum为1的情况有:(1)加法时,a、b为正,但是结果为负; (2)减法时,a为正、b为正,结果为负; (3)加法时,a为负、b为负,结果为正
(10)参考图4.3,可知在(7)、(8)、(9)中得到的输出flagforw、flag_we、cyforw、cy_we_o、ovforw、ov_we都输入到SPRS模块,SPRS模块会依据这些变量的值设置寄存器to_sr的相关位。to_sr的值最终会保存到特殊寄存器SR,所以此处也就是设置特殊寄存器SR的相关标志位。
or1200_sprs.v assign to_sr[`OR1200_SR_OV] = (except_started) ? sr[`OR1200_SR_OV] : (branch_op == `OR1200 _ BRANCHOP_RFE) ? esr[`OR1200_SR_OV] : ov_we ? ovforw : (spr_we && sr_sel) ? spr_dat_o[`OR1200_SR_OV] : sr[`OR1200_SR_OV]; assign to_sr[`OR1200_SR_CY] = (except_started) ? sr[`OR1200_SR_CY] : (branch_op == `OR1200_BRANCHOP_ RFE) ? esr[`OR1200_SR_CY] : cy_we ? cyforw : (spr_we && sr_sel) ? spr_dat_o[`OR1200_SR_CY] : sr[`OR1200_SR_CY]; assign to_sr[`OR1200_SR_F] = (except_started) ? sr[`OR1200_SR_F] : (branch_op == `OR1200_BRANCHOP_ RFE) ? esr[`OR1200_SR_F] : flag_we ? flagforw :(spr_we && sr_sel) ? spr_dat_o[`OR1200_SR_F] : sr[`OR1200_SR_F];
上述代码中exception_started是EXCEPTION模块的输出,不考虑异常发生的情况,即except_started为0,同时也不是指令l.rfe,即branch_op不等于OR1200_BRANCHOP_RFE。从而to_sr的OV、CY、F位分别等于ALU的对应输出。
(11)改变SPRS模块的sr_we的值,该值控制SR寄存器的写使能。这里sr_we为1。
assign sr_we = (spr_we && sr_sel) | (branch_op == `OR1200_BRANCHOP_RFE) | flag_we | cy_we | ov_we;
以上就是l.add指令在执行阶段的组合逻辑输出,主要工作如下。
● ALU依据操作数、操作码计算结果
● WB_MUX依据rfwb_op、输入的数据,从中选择一个数据输出到muxout,muxout输出到RF的dataw,作为要写入RF的数据,此处选择的就是ALU的输出result,也就是ALU的运算结果
● 依据运算结果,准备修改特殊寄存器SR的CY、OV、F位
完整的信号变化参考表4.9。
表4.9 l.add指令在执行阶段的组合逻辑输出
参考图4.3及表4.9可知,指令l.add在执行阶段的组合逻辑输出会确定RF模块的输入dataw,而在之前的译码阶段会确定RF的输入addrw、we的值,其中addrw为要写入的目的寄存器序号、we为1表示写寄存器、dataw为ALU的计算结果,所以在下一步会写RF中的目的寄存器。
4.4.6 l.add执行阶段的时序逻辑输出
(1)SPRS模块更新变量sr_reg的值。
or1200_sprs.v
always @(posedge clk or `OR1200_RST_EVENT rst)
if (rst == `OR1200_RST_VALUE)
sr_reg <= {2'b01, `OR1200_SR_EPH_DEF, {`OR1200_SR_WIDTH-4{1'b0}},
1'b1};
else if (except_started)
sr_reg <= to_sr[`OR1200_SR_WIDTH-1:0];
else if (sr_we) //在执行阶段的组合逻辑输出中sr_we为1
sr_reg <= to_sr[`OR1200_SR_WIDTH-1:0];
sr_reg将被赋值给特殊寄存器SR。
or1200_sprs.v always @(sr_reg or sr_reg_bit_eph_muxed) sr={sr_reg[`OR1200_SR_WIDTH-1:`OR1200_SR_WIDTH-2], sr_reg_bit_eph_muxed, sr_reg[`OR1200_SR_WIDTH-4:0]};
(2)RF模块写目的寄存器,从图4.4的RF模块内部结构可知,会同时写rf_a、rf_b,所以rf_a、rf_b的内容始终都是一样的,写入的值是ALU的运算结果。
(3)CTRL模块会改变wb_rfaddrw的值,wb_rfaddrw寄存刚刚写入的目的寄存器序号,此处就是l.add指令中rD的序号。寄存的目的是为了判断是否发生流水线数据相关,在本章会有专门的部分介绍OR1200中流水线数据相关的解决方法。
or1200_ctrl.v always @(posedge clk or `OR1200_RST_EVENT rst) begin if (rst == `OR1200_RST_VALUE) wb_rfaddrw <= 5'd0; else if (!wb_freeze) wb_rfaddrw <= rf_addrw; end
(4)WB_MUX模块会保存指令执行结果到变量muxreg中,目的也是为了在发生流水线数据相关时使用,muxreg输出到OPERAND_MUX。同时依据指令是否写目的寄存器,判断muxreg是否有效,muxreg_valid存储muxreg是否有效的信息,muxreg输出到CTRL模块。对指令l.add而言,muxreg为加法的结果,muxreg_valid为1。
or1200_wbmux.v
always @(posedge clk or `OR1200_RST_EVENT rst) begin
if (rst == `OR1200_RST_VALUE) begin
muxreg <= 32'd0;
muxreg_valid <= 1'b0;
end
else if (!wb_freeze) begin
muxreg <= muxout; //保存将要写到目的寄存器的值
muxreg_valid <= rfwb_op[0]; //rfwb_op[0]为1,表示要写目的寄存器,
//此时muxreg有效,反之无效
end
end
以上就是l.add指令在执行阶段的时序逻辑输出,主要工作如下。
● 将运算结果写入目的寄存器
● 修改特殊寄存器SR
完整的信号变化参考表4.10。
表4.10 l.add指令在执行阶段的时序逻辑输出
至此,l.add指令的处理过程分析完毕,回顾一下l.add指令的作用:l.add rD,rA,rB,将寄存器rA、rB的值相加,结果保存在寄存器rD中,同时该指令影响特殊寄存器SR的CY、OV位。从执行阶段的时序逻辑输出可知确实实现了上述功能。
4.4.7 第一条指令分析小结
l.add是我们分析的第一条指令,因此分析得比较细致,所有的代码都列出来了,本书在分析其余指令的时候就不再重复列出所有的代码,读者需要时常查阅l.add指令的处理过程,参考其中的代码。
l.add也是一个很简单的指令,而且在分析过程中没有考虑特殊情况(比如:id_freeze、ex_freeze为1时导致的流水线暂停等特殊情况都没有考虑)。通过这个简单的指令我们初步接触了流水线,并且对流水线每个阶段完成的工作有了认识,指令l.add的处理过程可以总结如下。
● 流水线取指阶段的组合逻辑:为读取指令需要的源寄存器做准备
● 流水线取指阶段的时序逻辑:判断是否是带立即数的指令,本节l.add是不带立即数的指令
● 流水线译码阶段的组合逻辑:读出指令需要的源寄存器的值,得到ALU所需要的操作数a、b
● 流水线译码阶段的时序逻辑:给出ALU的操作码
● 流水线执行阶段的组合逻辑:ALU按照操作码进行运算,给出要写的目的寄存器及要写的值
● 流水线执行阶段的时序逻辑:写目的寄存器,修改SR寄存器
通过本节的分析可以发现,在译码阶段,CTRL模块有很多控制信号输出到其余模块,如:LSU、MULTI_MAC等模块,这些控制信号在分析其余类指令的时候会用到,但在本章数据处理类指令中都用不到。
4.5 l.sfeqi指令分析
本节分析数据处理类指令中的另一条指令l.sfeqi,其用法是:l.sfeqi rA,I将指令中的16位立即数进行符号扩展,然后与寄存器rA的值比较,如果相等,则设置特殊寄存器SR的F位为1,反之设置SR的F位为0,指令格式如表4.11所示。
表4.11 l.sfeqi指令格式
在分析用例中有指令l.sfeqi r2,0x1234,对应的指令二进制数代码为0xbc021234,其解释如表4.12所示。
表4.12 l.sfeqi r2,0x1234指令二进制数解释
l.sfeqi指令与l.add指令的不同之处有三点:(1)l.sfeqi指令执行需要立即数;(2)l.sfeqi指令没有要写入的目的寄存器,只是需要改变SR的F位;(3)l.sfeqi进行的是比较运算。这三点不同决定了指令l.sfeqi的处理过程与l.add有些许不同,本小节在分析过程中对在流水线各个阶段l.sfeqi与l.add的不同之处做了重点标识。
4.5.1 l.sfeqi取指阶段的组合逻辑输出
同l.add指令的分析一样,还是从IF模块已经取得指令,并且送入CTRL模块的输入if_insn开始分析指令l.sfeqi。表4.13给出了l.sfeqi在取指阶段的组合逻辑输出,从中可知l.sfeqi只需要从RF中读出一个寄存器的值,此处就是rA的值。分析中使用的代码参考l.add指令的分析过程。
表4.13 l.sfeqi指令在取指阶段的组合逻辑输出
4.5.2 l.sfeqi取指阶段的时序逻辑输出
表4.14给出了l.sfeqi在取指阶段的时序逻辑输出,此处设置sel_imm为1,表示指令选择立即数作为ALU模块运算的操作数b。分析中使用的代码参考l.add指令的分析过程。
表4.14 l.sfeqi指令在取指阶段的时序逻辑输出
4.5.3 l.sfeqi译码阶段的组合逻辑输出
表4.15给出了l.sfeqi在译码阶段的组合逻辑输出,在此阶段会对指令中的16位立即数进行符号扩展,扩展为{{16{id_insn[15]}}, id_insn[15:0]},赋值给id_simm,同时由于sel_imm为1,所以会选择id_simm的值作为ALU模块运算的操作数b暂存到muxed_b。分析中使用的代码参考l.add指令的分析过程。
表4.15 l.sfeqi指令在译码阶段的组合逻辑输出
4.5.4 l.sfeqi译码阶段的时序逻辑输出
表4.16给出了l.sfeqi在译码阶段的时序逻辑输出,与指令l.add译码阶段的时序逻辑输出相比,有以下区别:(1)ALU的操作数b是符号扩展后的立即数的值;(2)rf_addrw不再有意义,因为没有要写的目的寄存器;(3)ALU模块的输入alu_op变为OR1200_ALUOP_COMP表示是比较指令;(4)ALU的输入alu_op2没有意义;(5)ALU的输入comp_op变为4’b 0000,该信号表示具体的比较类型;(6)rfwb_op变为OR1200_RFWBOP_NOP,表示没有要写入的目的寄存器。分析中使用的代码参考l.add指令的分析过程。
表4.16 l.sfeqi指令在译码阶段的时序逻辑输出
续表
4.5.5 l.sfeqi执行阶段的组合逻辑输出
在此阶段,ALU模块会依据输入的操作数a、b,操作码alu_op、alu_op2、comp_op进行计算,对于指令l.sfeqi而言,需要进行两个操作数的比较,代码如下。
or1200_alu.v always @(alu_op or alu_op2 or a or b or result_sum or result_and or macrc_op or shifted_rotated or mult_mac_result or flag or carry `ifdef OR1200_IMPL_ALU_EXT or extended `endif `ifdef OR1200_IMPL_ALU_CUST5 or result_cust5 `endif ) begin casez (alu_op) // synopsys parallel_case …… `ifdef OR1200_CASE_DEFAULT default: begin `else `OR1200_ALUOP_COMP, `OR1200_ALUOP_AND: begin `endif result=result_and; //l.sfeqi并不写目的寄存器,所以此处的 //result无意义 end ……
其中result_and计算如下。
or1200_alu.v assign result_and = a & b;
reuslt输出到WB_MUX,用于确定要写入目的寄存器的数据,由于本指令并不写目的寄存器,所以result实际不需要考虑。指令l.sfeqi的目的是设置SR中F位的值,代码如下。
or1200_alu.v
always @(alu_op or result_sum or result_and or flagcomp
) begin
casez (alu_op) // synopsys parallel_case
`ifdef OR1200_ADDITIONAL_FLAG_MODIFIERS
`ifdef OR1200_IMPL_ADDC
`OR1200_ALUOP_ADDC,
`endif
`OR1200_ALUOP_ADD : begin
flagforw = (result_sum == 32'h0000_0000);
flag_we = 1'b1;
end
`OR1200_ALUOP_AND: begin
flagforw = (result_and == 32'h0000_0000);
flag_we = 1'b1;
end
`endif
`OR1200_ALUOP_COMP: begin
flagforw = flagcomp;
flag_we = 1'b1;
end
default: begin
flagforw = flagcomp;
flag_we = 1'b0;
end
endcase
end
其中flagcomp的计算如下。
or1200_alu.v always @(comp_op or a_eq_b or a_lt_b) begin case(comp_op[2:0]) // synopsys parallel_case `OR1200_COP_SFEQ: //OR1200_COP_SFEQ即3’b000 flagcomp = a_eq_b; //要写入SR[F]位的就是a_eq_b的值 `OR1200_COP_SFNE: flagcomp = ~a_eq_b; `OR1200_COP_SFGT: flagcomp = ~(a_eq_b | a_lt_b); `OR1200_COP_SFGE: flagcomp = ~a_lt_b; `OR1200_COP_SFLT: flagcomp = a_lt_b; `OR1200_COP_SFLE: flagcomp = a_eq_b | a_lt_b; default: flagcomp = 1'b0; endcase end
在OR1200的实现中给出了三种a_eq_b的计算方法,默认是第三种OR1200_IMPL_ALU_COMP3,在这种方法中比较两个32位操作数是否相等采用的步骤是:对操作数b取反加1,然后与操作数a相加,如果和的低32位为0,则表示两个操作数相等。比如:操作数a、b都为0x87654321,对b取反加1,为0x789abcdf,然后加上a,和的低32位为0,进位为1。
(~0x87654321)+1+(0x87654321)=0x00000000,CY为1
其代码如下。
`ifdef OR1200_IMPL_ALU_COMP3 //默认比较运算采用的是第三种实现方法 `ifdef OR1200_IMPL_SUB //对于比较指令,需要操作数b取反加1赋值给b_mux assign b_mux=((alu_op==`OR1200_ALUOP_SUB)|(alu_op==`OR1200_ALUOP_COMP))?(~b)+1: b; `else assign b_mux = (alu_op==`OR1200_ALUOP_COMP) ? (~b)+1 : b; `endif …… `endif assign {cy_sum, result_sum} = (a + b_mux) + carry_in; //操作数a与b_mux相加,result_sum是加法的结果 `ifdef OR1200_IMPL_ALU_COMP3 assign a_eq_b = !(|result_sum); //如果加法的结果为0,则a等于b,a_eq_b为1, //反之a不等于b,a_eq_b为0
判断结果会被存储到SPRS模块中特殊寄存器SR的F位,保存步骤与l.add指令是一样的,并且该指令不影响SR的CY、OV位。表4.17给出了l.sfeqi指令在执行阶段的组合逻辑输出,分析中使用的代码参考l.add指令的分析过程。
表4.17 l.sfeqi指令在执行阶段的组合逻辑输出
4.5.6 l.sfeqi执行阶段的时序逻辑输出
表4.18给出了指令l.sfeqi在执行阶段的时序逻辑输出。l.sfeqi指令没有要写入的目的寄存器,所以muxreg_valid为0,表示muxreg、wb_rfaddrw都是无效的,本指令不会与后面的指令发生流水线数据相关的问题。分析中使用的代码参考l.add指令的分析过程。
表4.18 l.sfeqi指令在执行阶段的时序逻辑输出
4.5.7 第二条指令分析小结
本节一开始便指出了l.sfeqi指令与l.add指令的不同之处有三点:(1)l.sfeqi指令执行需要立即数;(2)l.sfeqi指令没有要写入的目的寄存器,只是需要改变SR的F位;(3)l.sfeqi进行的是比较运算。正是由于这三点不同带来了指令l.sfeqi与l.add执行过程中的一些变化之处。
(1)指令执行需要立即数
指令执行如果需要立即数,那么在取指阶段的时序逻辑输出中sel_imm为1,从而在译码阶段的组合逻辑输出中sel_b为OR1200_SEL_IMM,最终会使得ALU的操作数b为指令中的立即数的符号扩展或立即数的零扩展。
(2)指令有无要写入的目的寄存器
如果指令没有要写入的目的寄存器,那么rfwb_op[0]为0,反之rfwb_op[1]为1,rfwb_op[0]输出到RF模块的we接口,表示写使能,所以rfwb_op[0]为1 会写RF中的目的寄存器,rfwb_op[1]为0则不会写RF中的寄存器。
(3)指令的运算类型
在译码阶段的时序逻辑输出中,CTRL模块会输出alu_op、alu_op2、comp_op到ALU模块,指示运算类型,ALU依据不同的运算类型进行运算。
其余数据处理类指令主要也是在这三个方面有所区别,前两个区别的处理过程是一目了然的,对于第三个区别的处理过程则需要ALU依据不同的操作码分情况进行,此内容涉及ALU的设计,下一节将专题分析ALU模块。
4.6 ALU分析
ALU是算术逻辑单元,所有的数据处理类指令都要通过ALU运算得到最终结果。对不同的数据处理类(如:移位、算术、比较等)指令,ALU有不同的运算方法。同一类别的指令在ALU中的处理方法是相似的。CTRL模块会在译码阶段通过ALU的alu_op、alu_op2、comp_op三个接口送入操作码,可以认为alu_op是主操作码,alu_op2是移位操作、数据传送操作的次操作码,comp_op是比较操作的次操作码。ALU实现运算有很多种方法,这在分析l.sfeqi指令的时候也提到过,比如操作数比较的方法就有三种,三种方法综合出的电路有不同的运算速度和FPGA资源占用。用户可以依据实际情况灵活选择不同的实现方法。在or1200_defines.v中与ALU配置有关的宏定义如下。
`define OR1200_IMPL_ADDC //是否实现l.addc、l.addci指令,默认实现 `define OR1200_IMPL_SUB //是否实现l.sub指令,默认实现 `define OR1200_IMPL_CY //是否实现SR的CY位,默认实现 `define OR1200_IMPL_OV //是否实现SR的OV位,默认实现 //`define OR1200_IMPL_ALU_ROTATE //是否实现循环移位,默认不实现 //`define OR1200_IMPL_ALU_COMP1 //`define OR1200_IMPL_ALU_COMP2 `define OR1200_IMPL_ALU_COMP3 //三种比较运算的实现方法,默认是第三种 `define OR1200_IMPL_ALU_FFL1 //是否实现l.ff1、l.fl1指令,默认实现 //`define OR1200_IMPL_ALU_CUST5 //是否实现用户定制指令l.cust5,默认不实现 `define OR1200_IMPL_ALU_EXT //是否实现l.extXs与l.extXz指令,默认实现
用户可以禁用某些指令,以减少OR1200占用的芯片资源。ALU的代码可以分为多段,下面是第一段(此处比较运算的实现方法采用默认的OR1200_IMPL_ALU_COMP3方式),在该段中给出了输入输出接口,同时利用输入的操作数进行了一些计算,得到a_eq_b、a_lt_b、cy_sub、carry_in、b_mux、cy_sum、result_sum、ov_sum和result_and的值,其中多个值都可以在后面直接使用。
or1200_alu.v module or1200_alu( a, b, mult_mac_result, macrc_op,alu_op, alu_op2, comp_op, cust5_op, cust5_limm, result, flagforw, flag_we, ovforw, ov_we, cyforw, cy_we, carry, flag ); parameter width = `OR1200_OPERAND_WIDTH; input [width-1:0] a; //输入的操作数a input [width-1:0] b; //输入的操作数b input [width-1:0] mult_mac_result; //输入的MULTI_MAC模块的计算结果 input macrc_op; //表示是否为指令l.macrc input [`OR1200_ALUOP_WIDTH-1:0] alu_op; //主操作码 input [`OR1200_ALUOP2_WIDTH-1:0] alu_op2; //移位操作、数据传送操作的次操作码 input [`OR1200_COMPOP_WIDTH-1:0] comp_op; //比较操作的次操作码 input [4:0] cust5_op; //用户定制指令l.cust5的次操作码 input [5:0] cust5_limm; //用户定制指令l.cust5中的立即数 output [width-1:0] result; //运算结果 output flagforw; //运算后的SR[F]位 output flag_we; //是否修改SR[F]位 output cyforw; //运算后的SR[CY]位 output cy_we; //是否修改SR[CY]位 output ovforw; //运算后的SR[OV]位 output ov_we; //是否修改SR[OV]位 input carry; //输入的SR[CY]位 input flag; //输入的SR[F]位 …… //a_eq_b表示a是否等于b,在分析l.sfeqi的时候解释过,此处不再赘述 assign a_eq_b = !(|result_sum); //a_lt_b表示a是否小于b,comp_op[3]为1表示是符号数比较,此时有三种情况:(1)a //负b正,那么a_lt_b为1;(2)a正b正,并且a-b为负,那么a_lt_b为1;(3)a负b //负,并且a-b为负,那么a_lt_b为1。其余情况均为0 //comp_op[3]为0表示无符号数比较,此时直接使用a < b得到结果 assign a_lt_b = comp_op[3] ? ((a[width-1] & !b[width-1]) | (!a[width-1] & !b[width-1] & result_sum[width-1])| (a[width-1] & b[width-1] & result_sum[width-1])): (a < b); // cy_sub表示减法是否借位,在a小于b的时候,如果此时是减法指令,那么cy_sub必然为 //1,反之为0 assign cy_sub = a_lt_b; //如果是l.addc、l.addci指令,则使用到SR寄存器的CY位,此处的carry就是SPRS模块的输 //出SR[CY] assign carry_in = (alu_op==`OR1200_ALUOP_ADDC) ? {{width-1{1'b0}},carry} : {width{1'b0}}; //在减法和比较运算的时候,b_mux等于b的补码,其余情况下b_mux就是b assign b_mux = ((alu_op==`OR1200_ALUOP_SUB) | (alu_op==`OR1200_ALUOP_COMP)) ?(~b)+1 : b; //a、b_mux、carry_in相加 assign {cy_sum, result_sum} = (a + b_mux) + carry_in; //ov_sum表示和是否溢出,ov_sum为1的情况有:(1)加法时,a、b为正,但是结果为负;(2) //加法时,a为负、b为负,结果为正;(3)减法时,a为正、b为正,结果为负 assign ov_sum = ((!a[width-1] & !b_mux[width-1]) & result_sum[width-1]) | ((a[width-1] & b_mux[width-1]) & !result_sum[width-1]); //result_and是两个操作数相与的结果 assign result_and = a & b;
ALU代码的第二段依据alu_op2的不同进行移位、数据传送操作的运算,运算结果分别存储在shifted_rotated、extended中。代码如下。
or1200_alu.v always @(alu_op2 or a or b) begin case (alu_op2) // synopsys parallel_case `OR1200_SHROTOP_SLL : //逻辑左移 shifted_rotated = (a << b[4:0]); `OR1200_SHROTOP_SRL : shifted_rotated = (a >> b[4:0]); //逻辑右移 `ifdef OR1200_IMPL_ALU_ROTATE `OR1200_SHROTOP_ROR : //循环移位 shifted_rotated = (a << (6'd32-{1'b0,b[4:0]})) | (a >> b[4:0]); `endif default: //算术移位 shifted_rotated= ({32{a[31]}} <<(6'd32-{1'b0,b[4:0]}))| a>>b[4:0]; endcase end always @(alu_op or alu_op2 or a) begin casez (alu_op2) `OR1200_EXTHBOP_HS : extended = {{16{a[15]}},a[15:0]}; //指令l.exthz `OR1200_EXTHBOP_BS : extended = {{24{a[7]}},a[7:0]}; //指令l.extbs `OR1200_EXTHBOP_HZ : extended = {16'd0,a[15:0]}; //指令l.exthz `OR1200_EXTHBOP_BZ : extended = {24'd0,a[7:0]}; //指令l.extbz default: extended = a; //指令l.extws、l.extwz endcase // casez (alu_op2) end
ALU代码的第三段依据comp_op的不同进行比较操作的运算,运算结果存储在变量flagcomp中。此处就使用了在ALU代码第一段中计算得到的a_eq_b、a_lt_b,内容如下。
or1200_alu.v always @(comp_op or a_eq_b or a_lt_b) begin case(comp_op[2:0]) // synopsys parallel_case `OR1200_COP_SFEQ: //判断是否相等指令 flagcomp = a_eq_b; `OR1200_COP_SFNE: //判断是否不相等指令,直接对a_eq_b取非即可 flagcomp = ~a_eq_b; `OR1200_COP_SFGT: //判断a是否大于b flagcomp = ~(a_eq_b | a_lt_b); `OR1200_COP_SFGE: //判断a是否大于等于b flagcomp = ~a_lt_b; `OR1200_COP_SFLT: //判断a是否小于b flagcomp = a_lt_b; `OR1200_COP_SFLE: //判断a是否小于等于b flagcomp = a_eq_b | a_lt_b; default: flagcomp = 1'b0; endcase end
ALU代码的第四段依据不同的alu_op操作码,给出flagforw、flag_we、cyforw、cy_we、ovforw和ov_we的值,代码如下。
or1200_alu.v //给出flagforw、flag_we的值,在l.addc、l.addci、l.add、l.addi、l.and比较指令的 //执行中均会改变SR[F]的值 always @(alu_op or result_sum or result_and or flagcomp ) begin casez (alu_op) `OR1200_ALUOP_ADDC, //对于l.addc、l.addci、l.add、l.addi指令如果 `OR1200_ALUOP_ADD : begin //和等于0x0,那么需要设置SR[F]为1 flagforw = (result_sum == 32'h0000_0000); flag_we = 1'b1; end `OR1200_ALUOP_AND: begin //对于l.and指令如果相与的结果等于0x0,那么 //需要设置SR[F]为1 flagforw = (result_and == 32'h0000_0000); flag_we = 1'b1; end `OR1200_ALUOP_COMP: begin flagforw = flagcomp; //比较指令需要设置SR[F]为上面计算出的比较结果 flag_we = 1'b1; end default: begin flagforw = flagcomp; //其余指令执行不会设置SR[F] flag_we = 1'b0; end endcase end //给出cyforw、cy_we的值,在l.addc、l.add、l.addci、l.addi、l.sub指令的执行过程中 //都会改变SR[CY]的值 always @(alu_op or cy_sum or cy_sub ) begin casez (alu_op) // synopsys parallel_case `OR1200_ALUOP_ADDC, `OR1200_ALUOP_ADD : begin cyforw = cy_sum; //此处的cy_sum是在ALU代码的第一段计算出来的 cy_we = 1'b1; end `OR1200_ALUOP_SUB: begin cyforw = cy_sub; //此处的cy_sub也是在ALU代码的第一段计算出来的 cy_we = 1'b1; end default: begin cyforw = 1'b0; cy_we = 1'b0; end endcase end //给出ovforw、ov_we的值,在l.addc、l.add、l.addci、l.addi、l.sub指令的执行过程中 //都会改变SR[OV]的值 always @(alu_op or ov_sum) begin casez (alu_op) `OR1200_ALUOP_ADDC, `OR1200_ALUOP_SUB, `OR1200_ALUOP_ADD : begin ovforw = ov_sum; //此处的ov_sum是在ALU代码的第一段计算出来的 ov_we = 1'b1; end default: begin ovforw = 1'b0; ov_we = 1'b0; end endcase end
ALU代码的第五段依据不同的alu_op得到最终的运算结果,存储在result中,result输出到WB_MUX模块,代码如下。
or1200_alu.v always @(alu_op or alu_op2 or a or b or result_sum or result_and or macrc_op or shifted_rotated or mult_mac_result or flag or carry or extended or result_cust5 ) begin casez (alu_op) `OR1200_ALUOP_FFL1: begin casez (alu_op2) 0: begin // FF1 l.ff1指令的计算方法 result = a[0] ? 1 : a[1] ? 2 : a[2] ? 3 : a[3] ? 4 : a[4] ? 5 : a[5] ? 6 : a[6] ? 7 : a[7] ? 8 : a[8] ? 9 : a[9] ? 10 : a[10] ? 11 : a[11] ? 12 : a[12] ? 13 : a[13] ? 14 : a[14] ? 15 : a[15] ? 16 : a[16] ? 17 : a[17] ? 18 : a[18] ? 19 : a[19] ? 20 : a[20] ? 21 : a[21] ? 22 : a[22] ? 23 : a[23] ? 24 : a[24] ? 25 : a[25] ? 26 : a[26] ? 27 : a[27] ? 28 : a[28] ? 29 : a[29] ? 30 : a[30] ? 31 : a[31] ? 32 : 0; end default: begin // FL1 l.fl1指令的计算方法 result = a[31] ? 32 : a[30] ? 31 : a[29] ? 30 : a[28] ? 29 : a[27] ? 28 : a[26] ? 27 : a[25] ? 26 : a[24] ? 25 : a[23] ? 24 : a[22] ? 23 : a[21] ? 22 : a[20] ? 21 : a[19] ? 20 : a[18] ? 19 : a[17] ? 18 : a[16] ? 17 : a[15] ? 16 : a[14] ? 15 : a[13] ? 14 : a[12] ? 13 : a[11] ? 12 : a[10] ? 11 : a[9] ? 10 : a[8] ? 9 : a[7] ? 8 : a[6] ? 7 : a[5] ? 6 : a[4] ? 5 : a[3] ? 4 : a[2] ? 3 : a[1] ? 2 : a[0] ? 1 : 0 ; end endcase // casez (alu_op2) end // case: `OR1200_ALUOP_FFL1 `OR1200_ALUOP_CUST5 : begin //用户自定义的指令 result = result_cust5; end `OR1200_ALUOP_SHROT : begin //如果是移位指令,那么result就是上 //面计算的shifted_rotated result = shifted_rotated; end `OR1200_ALUOP_ADDC, `OR1200_ALUOP_SUB, `OR1200_ALUOP_ADD : begin result = result_sum; //l.add、l.addi、l.addc、l.addci、 //l.sub的运算结果 end `OR1200_ALUOP_XOR : begin //l.xor指令 result = a ^ b; end `OR1200_ALUOP_OR : begin //l.or指令 result = a | b; end `OR1200_ALUOP_EXTHB : begin //l.extbs、l.extbz、l.exths、 //l.exthz的计算结果 result = extended; //此处的extended在上面已经计算出来了 end `OR1200_ALUOP_EXTW : begin //l.extws、l.extwz的计算结果 result = extended; end `OR1200_ALUOP_MOVHI : begin //l.movhi与l.macrc指令二进制数的 //最高6位是一样的,所以此处需要进一步判断 if (macrc_op) begin result = mult_mac_result; //是l.macrc指令,mullti_mac_ //result来自MULTI_MAC模块 end else begin result = b << 16; //是l.movhi指令,将操作数b左移16位 end end `OR1200_ALUOP_DIV, `OR1200_ALUOP_DIVU, `OR1200_ALUOP_MUL, `OR1200_ALUOP_MULU : begin result = mult_mac_result; //乘法、除法指令的运算结果都来自 //MULTI_MAC模块 end `OR1200_ALUOP_CMOV: begin //l.cmov指令 result = flag ? a : b; end default: begin `OR1200_ALUOP_COMP, `OR1200_ALUOP_AND: begin //对于l.and指令,result等于result_and,对于比较指 //令,没有要写的目的寄存器,所以当指令是比较指令时, //此处的result没有意义 result=result_and; end endcase end
4.7 流水线数据相关的解决方法
上文中提到过流水线相关的问题,本节将对流水线相关的问题进行说明。流水线中经常有一些被称为“相关”的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些“相关”会降低流水线的性能。流水线中的相关分为三种类型。
(1)结构相关:指令在执行的过程中,由于硬件资源满足不了指令执行的要求,发生硬件资源冲突而产生的相关。比如:指令和数据都共享一个存储器,在某个时钟周期内,流水线既要完成某条指令对存储器中数据的访问操作,又要完成后续指令的取指令操作,这样就会发生存储器访问冲突,产生结构相关。
(2)数据相关:指在流水线中执行的几条指令中,一条指令依赖于前面指令的执行结果。
(3)控制相关:指流水线中的分支指令或者其他需要改写PC的指令造成的相关。
结构相关、控制相关将在后续指令分析中讨论。本小节重点讨论流水线数据相关的有关情况,同时介绍OR1200对数据相关的处理方法。流水线数据相关又分为三种情况:RAW、WAR、WAW。
● RAW:Read After Write,假设指令j是指令i后面执行的指令,RAW表示指令i将数据写入寄存器后,指令j才能从这个寄存器读取数据。如果指令j在指令i写入寄存器前尝试读出该寄存器的内容,将得到不正确的数据。
● WAR:Write After Read,假设指令j是指令i后面执行的指令,WAR表示指令i读出数据后,指令j才能写这个寄存器。
● WAW:Write After Write,假设指令j是指令i后面执行的指令,WAW表示指令i将数据写入寄存器后,指令j才能将数据写入这个寄存器。
对于OR1200处理器而言,这是一个三级流水线的处理器,从前几节对数据处理类指令的分析可知只有在流水线执行阶段才会写寄存器(实际上其余类指令也是一样的),因此不会存在WAW相关,又因为只能在流水线译码阶段读寄存器,所以不存在WAR相关,因此OR1200的流水线只存在RAW相关。在示例代码中就存在RAW相关的问题,内容如下。
…… l.add r4,r2,r3 l.sfeqi r4,0x1234 ……
在指令l.add中会写r4,随后的指令l.sfeqi会读出r4的数据进行比较运算,通过前面的分析知道l.add在执行阶段的时序逻辑中写r4,此时l.sfeqi处于译码阶段的时序逻辑,而在l.sfeqi的译码阶段的组合逻辑中已经得到了r4的值,如果得到的是修改前的值,那么l.sfeqi使用这个r4的值进行比较运算就会出现错误,如图4.5所示。使用ModelSim仿真可以更加清晰地认识到这个问题,如图4.6所示。
图4.5 流水线数据相关的示例
图4.6 ModelSim仿真波形呈现的数据相关问题
解决这种问题的方法有三种。
(1)插入暂停周期:当检测到相关时,在流水线中插入一些暂停周期,如图4.7所示。
图4.7 在流水线中插入暂停周期消除数据相关
(2)编译器调度:编译器检测到相关后,可以改变部分指令的执行顺序,如图4.8所示。此时的流水线情况如图4.9所示。
4.8 编译器通过改变指令执行顺序消除相关
4.9 编译器调度改变指令的执行顺序
(3)数据前推:将计算结果从其产生处直接送到其他指令需要处或所有有需要的功能单元处,避免流水线暂停。在OR1200中l.add在执行阶段的组合逻辑过程中会得到要写入r4的新值,可以直接将该值送入下一条指令l.sfeqi的译码阶段,从而使得l.sfeqi在译码阶段得到r4的新值,如图4.10所示。
图4.10 数据前推解决流水线相关
OR1200就是采用数据前推的方法来解决流水线数据相关的问题。通过补充完善图4.3,添加部分信号使得可以完成数据前推的工作,如图4.11所示,主要是将WB_MUX的输出mux_out直接送入OPERAND_MUX模块的输入ex_forw,在OPERAND_MUX模块中参与ALU操作数的选择过程。
图4.11 mux_out输出到OPERAND_MUX的ex_forw实现数据前推
假设指令j是指令i后面执行的指令,在指令i执行阶段的组合逻辑中,WB_MUX将要写入目的寄存器的值通过mux_out输出到RF的data_w,同时也输出到OPERAND_MUX的ex_forw。
指令i处于执行阶段的组合逻辑输出的时候,指令j正处于译码阶段的组合逻辑输出,此时在CTRL模块中会判断指令j与其上一条指令i是否存在数据相关,代码如下。
or1200_ctrl.v always @(rf_addrw or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw) if ((id_insn[20:16] == rf_addrw) && rfwb_op[0]) //此时的rf_addrw是指令i要写的目的寄存器序号, //rfwb_op[0]为1,表示要写目的寄存器,反之不写目 //的寄存器,id_insn[20:16]是指令j需要的源寄存器 //rA的序号,所以此处就是判断rA是否是指令i要写 //入的寄存器 sel_a = `OR1200_SEL_EX_FORW; else if ((id_insn[20:16] == wb_rfaddrw) && wbforw_valid) sel_a = `OR1200_SEL_WB_FORW; else sel_a = `OR1200_SEL_RF; always @(rf_addrw or sel_imm or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw) if (sel_imm) sel_b = `OR1200_SEL_IMM; else if ((id_insn[15:11] == rf_addrw) && rfwb_op[0]) //此时的rf_addrw是指令i要写的目的寄存器序号, //rfwb_op[0]为1,表示要写目的寄存器,反之不写目 //的寄存器,id_insn[15:11]是指令j需要的源寄存器 //rB的序号,所以此处就是判断rB是否是指令i要写 //入的寄存器 sel_b = `OR1200_SEL_EX_FORW; else if ((id_insn[15:11] == wb_rfaddrw) && wbforw_valid) sel_b = `OR1200_SEL_WB_FORW; else sel_b = `OR1200_SEL_RF;
如果指令i要写目的寄存器,且指令i的目的寄存器就是指令j的源寄存器rA或者rB,则会使得sel_a或者sel_b为OR1200_SEL_EX_FORW,参考图4.11可知sel_a、sel_b都输出到OPERAND_MUX模块,后者会依据sel_a、sel_b的值选择ALU的操作数,代码如下。
or1200_operandmuxes.v always @(ex_forw or wb_forw or rf_dataa or sel_a) begin casez (sel_a) // synopsys parallel_case `OR1200_SEL_EX_FORW: //如果指令j需要读取的寄存器rA正是指令i要写的目的寄存器 muxed_a = ex_forw; //那么就直接把要写入的值赋给muxed_a `OR1200_SEL_WB_FORW: muxed_a = wb_forw; default: muxed_a = rf_dataa; //rf_dataa是从RF的rf_a中读出的寄存器值 endcase end // // Forwarding logic for operand B register // always @(simm or ex_forw or wb_forw or rf_datab or sel_b) begin casez (sel_b) // synopsys parallel_case `OR1200_SEL_IMM: muxed_b = simm; `OR1200_SEL_EX_FORW: //如果指令j需要读取的寄存器rB正是指令i要写的目的寄存器 muxed_b = ex_forw; //那么就直接把要写入的值赋给muxed_b `OR1200_SEL_WB_FORW: muxed_b = wb_forw; default: muxed_b = rf_datab; //rf_datab是从RF的rf_b中读出的寄存器值 endcase end
而此时OPERAND_MUX模块的输入ex_forw正是WB_MUX的输出muxout,也就是指令i要写的目的寄存器的新值,从而实现了数据前推,解决了流水线数据相关问题。图4.12显示的是ModelSim仿真呈现的OR1200处理器数据相关的解决步骤。
细心的读者可能在上面的代码中注意到sel_a、sel_b的值还可能是OR1200_SEL_WB_FORW,是的,在OR1200的设计中考虑了两种数据相关:(1)相邻指令的数据相关;(2)相隔一条指令的两条指令之间的数据相关。前一种相关正是本节上面所讨论的。对于后一种情况,假设指令m是指令i后面执行的第二条指令,也就是指令m与指令i相隔一条指令,那么会将指令i的rf_addrw保存在wb_rfaddrw中,将指令i的rfwb_op[0]保存在wbforw_valid中,从而在指令m的译码阶段判断是否发生数据相关,参考l.add指令执行阶段的时序逻辑输出。但在笔者看来“相隔一条指令的两条指令之间的数据相关”是一个伪命题,因为这种情况下不会发生数据相关,从图4.5中也可以知道指令i会在执行阶段的时序逻辑中写目的寄存器,此时其后面的第二条指令m还没有读取到源寄存器,所以等指令m读到所需的源寄存器时,得到的值已经是修改后的值了,因此不会出现数据相关的问题,也无须保存指令i要写的目的寄存器序号rf_addrw、rfwb_op[0]等变量的值。相应代码可以修改为如下内容。
图4.12 ModelSim呈现的OR1200中数据相关的解决步骤
or1200_ctrl.v always @(rf_addrw or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw) if ((id_insn[20:16] == rf_addrw) && rfwb_op[0]) sel_a = `OR1200_SEL_EX_FORW; else if ((id_insn[20:16] == wb_rfaddrw) && wbforw_valid) sel_a = `OR1200_SEL_WB_FORW; else sel_a = `OR1200_SEL_RF; always @(rf_addrw or sel_imm or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw) if (sel_imm) sel_b = `OR1200_SEL_IMM; else if ((id_insn[15:11] == rf_addrw) && rfwb_op[0]) sel_b = `OR1200_SEL_EX_FORW; else if ((id_insn[15:11] == wb_rfaddrw) && wbforw_valid) sel_b = `OR1200_SEL_WB_FORW; else sel_b = `OR1200_SEL_RF;
为简化起见,本书在分析其余指令的时候都不再考虑数据相关的情况。
4.8 定制属于自己的指令
OpenRISC 1000的指令集中定义了8个自定义指令,分别是:l.cust1~l.cust8。这些自定义指令只是占用了一个操作码,可用与否取决于具体的实现,在OR1200中实现了l.cust5,并且编译器支持l.cust5。其指令用法是:l.cust5 rD,rA,rB,L,K,有两个源操作数,并且有要写入的目的寄存器,指令格式如表4.19所示。
表4.19 l.cust5指令的格式
有了本章之前的学习,对l.cust5的分析应该是轻车熟路了,表4.20给出取指、译码阶段的部分关键信号。分析中使用的代码参考l.add指令的分析过程。
表4.20 l.cust5取指、译码阶段的关键信号
表4.20给出了取指、译码阶段的主要信号,所以进入执行阶段的时候,ALU的操作数a、b是从RF中读出的源寄存器rA、rB的值,ALU的操作码alu_op是OR1200_ALUOP_CUST5,同时rfwb_op[0]为1,表示l.cust5会写目的寄存器,ex_insn中保存的就是l.cust5指令。在l.cust5执行阶段的组合逻辑过程中,CTRL模块会得到cust5_op、cust5_limm的值,内容如下。
or1200_ctrl.v assign cust5_op = ex_insn[4:0]; //l.cust5指令中的K assign cust5_limm = ex_insn[10:5]; //l.cust5指令中的L
从图4.11可知这两个信号输出到ALU的同名接口,在ALU中会依据输入的cust5_op、cust5_limm进行计算,内容如下。
or1200_alu.v
always @(cust5_op or cust5_limm or a or b) begin
casez (cust5_op) // synopsys parallel_case
5'h1 : begin
casez (cust5_limm[1:0])
2'h0: result_cust5 = {a[31:8], b[7:0]};
2'h1: result_cust5 = {a[31:16], b[7:0], a[7:0]};
2'h2: result_cust5 = {a[31:24], b[7:0], a[15:0]};
2'h3: result_cust5 = {b[7:0], a[23:0]};
endcase
end
5'h2 :
result_cust5 = a | (1 << cust5_limm);
5'h3 :
result_cust5 = a & (32'hffffffff ^ (1 << cust5_limm));
default: begin
result_cust5 = a;
end
endcase
end
OR1200预先定义了几种计算。
(1)cust5_op为1:使用rB[7:0]替换rA中的第cust5_limm[1:0]个字节,替换后的结果保存在result_cust5中。
(2)cust5_op为2:将rA的第cust5_limm位变为1,修改后的结果保存在result_cust5中。
(3)cust5_op为3:将rA的第cust5_limm位变为0,修改后的结果保存在result_cust5中。
由于此时ALU的输入alu_op为OR1200_ALUOP_CUST5,所以最终会将上面计算的result_cust5作为ALU的运算结果。
or1200_alu.v always @(alu_op or alu_op2 or a or b or result_sum or result_and or macrc_op or shifted_rotated or mult_mac_result or flag or carry or extended or result_cust5 ) begin casez (alu_op) …… `OR1200_ALUOP_CUST5 : begin //用户自定义的指令 result = result_cust5; //result将被写入目的寄存器 end ……
接下来的步骤就与l.add一样了,主要就是将运算结果写入目的寄存器,此处不再赘述,读者可以参考l.add指令的分析过程。所以,自定义指令的关键就是在ALU模块中的计算过程,依据cust5_op、cust5_limm,可以设置多种运算。
现在假设我们需要一条指令,作用是为一个32位数计算奇偶校验位。奇偶校验位表示给定位数的二进制数中1 的个数是奇数还是偶数,是最简单的错误检测码。奇偶校验位有两种类型:偶校验位与奇校验位。如果数据中1的个数是奇数,那么偶校验位就置为1,从而使得总的1的个数是偶数。如果给定数据中1的个数是偶数,那么奇校验位就置为1,使得总的1的个数是奇数。本书设计的自定义指令用法是:
l.cust5 rD,rA,r0,0,4 //计算寄存器rA中的数据的偶校验位,结果存储在rD中
l.cust5 rD,rA,r0,1,4 //计算寄存器rA中的数据的奇校验位,结果存储在rD中
修改ALU中的计算过程如下。
or1200_alu.v always @(cust5_op or cust5_limm or a or b) begin casez (cust5_op) // synopsys parallel_case 5'h1 : begin …… end 5'h2 : …… 5'h3 : …… 5'h4 : begin casez (cust5_limm[1:0]) 2'h0: result_cust5 = parity; //计算偶校验位 2'h1: result_cust5 = parity ? 0 : 1; //计算奇校验位 default: begin result_cust5=parity; end endcase default: begin result_cust5 = a; end endcase end 其中parity定义如下: wire parity; assign parity=^a; //利用缩位运算符异或“^”得到a中1的个数是奇数还是偶数
OR1200默认配置是没有l.cust5指令的,所以还需要在or1200_defines.v中作如下修改。
`define OR1200_IMPL_ALU_CUST5 //去掉前面的注释
另外修改CTRL模块的1012行的代码,内容如下。
`ifdef OR1200_ALU_IMPL_CUST5 `OR1200_OR32_CUST5: rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1}; `endif 改为 `ifdef OR1200_IMPL_ALU_CUST5 //此处应该是OR1200作者的笔误 `OR1200_OR32_CUST5: rfwb_op <= {`OR1200_RFWBOP_ALU, 1'b1}; `endif
这样OR1200就可以执行我们定义的计算奇偶校验位的指令了。下面编写程序验证我们的自定义指令是否可以得到正确执行,在Example.S中添加如下代码。
…… //在本条指令之前已设置r2为0x1234,本指令计算0x1234的偶校验位 l.cust5 r4,r2,r0,0,4 l.cust5 r4,r2,r0,1,4 //本指令计算0x1234的奇校验位 ……
还是在Ubuntu中使用“make all”编译链接程序,同时得到OR1KSim模拟器执行的结果,以及ModelSim仿真使用的存储器初始化文件,因为是自定义的指令,所以OR1KSim模拟器执行的结果是不正确的,需要使用ModelSim仿真查看指令执行结果,如图4.13所示,从波形图中可知指令的执行是按照我们的设计进行的,分别计算了偶校验位、奇校验位。
图4.13 ModelSim仿真显示用户自定义指令的执行效果
4.9 不完整流水线数据通路图
本章对OR1200中的数据处理类指令进行了深入分析,基于本章的分析,图4.14给出了OR1200中流水线数据通路图,但该图只是针对数据处理类指令绘制的,并不完整,在后续章节中将逐步完善。图中显示了数据处理类指令处理过程中流水线各个阶段的数据流转。
(1)取指阶段:GENPC模块计算指令地址,然后通过指令Wishbone总线从指令存储器中取得指令,取得的指令送入IF模块,该指令进入流水线。
(2)译码阶段:取出指令需要的通用寄存器的值dataa、datab,同时对指令中可能有的立即数进行符号扩展或零扩展得到id_simm,OPERAND_MUX模块从中选择两个数据作为下一步的运算数据operand_a、operand_b,为了解决流水线数据相关的问题,将执行阶段的运算结果muxout也作为OPERAND_MUX模块的备选数据。
(3)执行阶段:ALU模块依据操作码alu_op、alu_op2、comp_op,对操作数a、b进行运算,a、b就是译码阶段的输出operand_a、operand_b,计算过程会改变SPRS中特殊寄存器SR的CY、OV、F位,计算的结果输出到WB_MUX模块,后者是一个多路选择器,依据rfwb_op从多个输入中选择一个作为输出,对数据处理类指令而言WB_MUX将ALU的计算结果作为输出muxout的值,如果存在要写入的目的寄存器,那么muxout将被写入目的寄存器。
图4.14 不完整流水线数据通路图