本文参考书目:《计算机组成,软硬件接口,Risc-V版》
CPU结构和指令集是分不开的,因此,我们在使用HDL语言实现CPU之前,首先了解其指令集。
简单数据通路,即RV32I的一个子集,实现RV32I指令集中的部分简单指令。(主要是R型,B型,lw和sw指令),下面先介绍这些指令。
R型指令可以罗列为下图
或者是下图的样子
主要实现的功能是实现逻辑运算。R-type指令的opcode都是0110011
,如前所示,R型指令的特殊之处在于其使用了rs1,rs2和rd,运算完毕rs1和rs2之后,把rs1,rs2运算完毕的结果写回(Write Back)RF
中的rd. 值得一提的是,Risc-V有32个通用寄存器x0-x31
,不妨将他们叫做寄存器堆(RF
,Register File)。寄存器堆的首个寄存器,即x0是始终为0的,无法通过写入改变其值。
lw
指令 lw指令是I型指令中的一种,lw指令详细描述如下
我们可以学会使用这种公式来表示指令的功能
x [ r d ] = M [ x [ r s 1 ] + s e x t ( o f f s e t ) ] x[rd] = M[x[rs1]+sext(offset)] x[rd]=M[x[rs1]+sext(offset)]
lw
指令用到了rs1,rd和offset三类数据,需要读Data Memory
并写回(Write Back)给RF
。其中和R型指令的共同点是都用了rs和rd。
值得一提的是,同样属于I型指令的addi
这些,就不需要访存MEM这一步,而是直接将结果写回。而且这两类I型指令的opcode也不同,足见其特殊之处。因此I-Type指令可以细分为访存类和立即数运算类。
sw
指令 sw指令的详细叙述如下
sw使用了rs1,rs2和offset三类数据,需要将rs2的数据写入到Data Memory
.
B型指令是 branch,分支跳转指令的简称,其格式如下
或者如下
B型指令的opcode是1100011
,其用到了rs1,rs2和offset,这一点和sw一样。只不过B指令做的不是计算rs1和offset的结果作为访存地址,而是将rs1,rs2作为ALU输入。而offset是和pc进行运算的(如果条件转移满足的话),可以参考beq
指令。
通过下图我们可以看到该结构是如何将上述四种指令结合起来
我们以R型指令为例(结合上图)。
PC
是时序电路,假设当下PC输入时pc+4,pc输出是pc,那当下一个posedge到来时,PC输出变成pc+4,完成了一次输入到输出的刷新。pc变成pc+4后,输入到Instruction Memory
的read address就发生了变化。Instruction Memory
是组合逻辑,当Read Address变化后,其输出的Instruction也发生变化。
输出的Instruction经过简单的译码之后,给出register1和register2的地址。给到RF
,RF的读取也是组合逻辑,因此随之Read data1和Read data2也会立即变化.
rs1,rs2送到ALU中,计算完毕的结果在写回处,即RF的write data处等待下次时钟周期的到来.
下一个posedge到来后写回的数据就会被写入RF。
以上描述了指令运行的五个阶段,IF,ID,EX,MEM和WB,在R型指令中没有MEM阶段,EX的结果直接WB到RF。
我们再看lw
指令。
lw指令选择计算rs1和offset,计算的结果作为Data Memory的读地址,Mem的读取也是组合逻辑,读取的结果WB到RF。因此该指令是完全包含五个阶段的。
我们再看sw
指令。
同样计算rs1和offset的结果作为Data Memory的写地址,rs2作为Write data,由于写是时序,因此在下一个posedge到来时将值写入到Data memory中。这个指令是没有WB阶段的,因为是store word,把数据写入到Data Memory就标志着结束。
最后我们再来看B
指令。
B型指令例如beq
,将rs1和rs2进行对比,如果输出结果Zero指示为0的话,就将PC
的输入值改为pc+offset(左移1位,按照16位对指令空间进行地址跳转),否则仍然是pc+4.
控制模块将ALU的控制单独分离出来一个模块。这样做的好处是多级控制,简化逻辑。如下图所示。
*需要注意的是我们写的是RV32I,所以图上的64位有关的叙述我们需要进行调整。
我们可以观察到,除了ALU控制之外,Control的控制是基于7位的opcode的,这一定程度上也能解释I型指令中,为何访存指令和立即数算数指令,它们的结构一样(因此都是I型指令),却要用不同的opcode.见下图
因为立即数运算和访存的执行很像,都是rs1和offset相加,只不过立即数运算是直接将相加结果写回到RF,而访存指令是将结果作为访存的地址,得到的输出再写回RF,因此多了一步MEM。因此这些需要通过控制模块来实现,所以采用不同的opcode来保证不同的控制输出(例如多选器选择写回RF的数据源)