从零认识cpu之RISC-V架构(三)构建一个单周期CPU详细教程


本文章参考书籍:Digital Design and Computer Architecture RISC-V Edition
代码获取: https://github.com/handsomeo7y/single_cycle_riscv

  单周期(single-cycle)是指CPU从取出1条指令到执行完该指令只需1个时钟周期。即在一个周期内完成所有操作。CPU的架构是基于指令集的,为了使微架构易于理解,我将重点放在RISC-V指令集的一个子集上。具体来说包括一下指令集合:

1. 设计思路

  我们将微处理器结构的设计分为两个部分:1.数据路径。2.控制单元。数据路径描述了数据的流动情况;控制单元则控制数据路径,比如路径中的选择器等。
  要设计一个复杂的系统,我们可以先列出系统中的核心模块,再往核心模块之间加入逻辑模块。CPU运行的过程是取指令,译码,执行指令。所以我们需要一个存储器来存储指令;同时数据也要存在存储器中,所以需要将存储器分一块来存储数据;程序计数器指示了指令的地址,也是必不可少的;最后数据操作都是在寄存器之间执行的,寄存器文件也是必须的。因此四个核心模块如下图:
在这里插入图片描述
1.PC为当前指令的指针,而PCNext则是下一条指令的地址
2.指令是提前写好的,因此只需要一个读端口即可。A表示需要读取的指令的地址。
3.A1, A2, A3为地址输入端口,位数为5位,对应着32(2 5 ^5 5)个32-bit的寄存器。A1, A2为源操作数的地址,A3为目的操作数地址。RD1和RD2为数据输出端口,WD3为数据写入端口,当WE3拉高时,在时钟上升沿写入数据。
4.WD为数据写入端口,RD为读出端口。A为地址。WE为使能,1时写入,0时读出。

2.单周期处理器

  为了具体描述四个核心模块之间的逻辑关系,使用具体的指令来作为例子,如下图。这里假设寄存器x5中的值为6,x9中的值为0x2004(这里是32位,但是省略了前面的0,后面都是一样,就不重复说明了),存储器中地址为0x2000的值为10。整体的过程是:lw 在存储器地址0x2000(0x2004-4)中读取的值为10,并将10装载到x6中;sw 将 x6中的10写入到 0x200C(0x2004+8)中;or x5中 0110 2 _2 2(6)和x6中10(1010 2 _2 2)相或得到14(1110 2 _2 2);beq 跳转到label L7处,即程序循环运行。
在这里插入图片描述
  关于汇编语言到机器语言的译码,之后再出一篇文章来说明。这里就先直接应用了。首先第一步我们要取指令,所以将PC指向的地址赋值给指令存储器。这里取了第一个指令lw,地址为0x1000,指令为:0xFFC4A303,如下图:
在这里插入图片描述
  按照前面所说的思路,我们先分析数据路径。

2.1 lw 数据路径

  取完指令,我们按如下步骤来分析lw 的数据路径:
  1.读取源寄存器中的基地址。指令中rs1的部分为源寄存器,所以instr 19 : 15 _{19:15} 19:15为源寄存器的地址,我们将其输入到寄存器文件中,即连接到A1(这里只有一个源操作数,所以连接到A1,而不是A2。具体和指令的类型有关),然后在RD1端口读取值。如下图:
在这里插入图片描述
  2.立即数扩展。lw 需要偏移量,但是指令中立即数的位数只有12bit,我们需要将其扩展到32bit,以便后续的计算。因此我们需要设计一个位数扩展模块,需要注意的是,我们是符号扩展。-4=0xFFC 扩展成 0xFFFFFFFC,如下图:
在这里插入图片描述
  3.将偏移量加到基地址上。ALU接收两个源操作数,SrcA为基地址,SrcB为扩展后的偏移量。ALU有三位控制信号,来决定不同的操作,当执行加法时为:000。下图展示了这一步骤的情况:
在这里插入图片描述
  4.从存储器中读取数据并将其写入到寄存器文件中。在第三步得到地址之后就可以在数据存储器中读取数据了,读出的数据连接到寄存器文件模块的写入端口。指令中rd表示目的寄存器,即指令Instr 11 : 7 _{11:7} 11:7,需要连接到A3。到这一步,基本上将四个模块都联系起来了,如下图:
在这里插入图片描述
  5.PC指向下一个指令。最后一步是将PC指向下一个指令,由于RISC-V是字节地址类型,所以地址递增为4。如下图:
在这里插入图片描述

2.2 sw 数据路径

   sw 的数据路径和lw差不多,下图展示了在lw 的基础上新增的与sw 数据路径:
在这里插入图片描述
区别主要在三个方面:
1.sw 没有目的寄存器,有两个源寄存器,所以A1,A2都使用了。A3没有使用
2.立即数扩展模块有区别,lw 指令中 Instr 31 : 20 _{31:20} 31:20为立即数,但是sw中Instr 31 : 25 _{31:25} 31:25和Instr 11 : 7 _{11:7} 11:7为立即数,因此扩展模块变成直接接收 Instr 31 : 7 _{31:7} 31:7,并根据ImmSrc来选择 Instr 31 : 7 _{31:7} 31:7中哪些作为立即数。
3.数据写入到数据存储器。数据从RD2端口出来,连接到WD端口。并将MemWrite置1,表示写入。

2.3 R-type instruction 数据路径

   R-type instruction 包括 add, sub, or, and, slt。这些指令都是从源寄存器读取值,在ALU中操作,然后将结果写入到目的寄存器中。新增的数据路径如下图:

在这里插入图片描述
有两个区别:
1.之前ALU SrcB的来源为立即数,但是在R-type instruction中来源都是寄存器。
2.ALU的结果不作为数据,也不作为地址输入到数据存储器中,而是作为数据存储到寄存器文件中,因此需要加一个结果选择器。
note:关于 ALUControl , ALUControl is 000 for addition, 001 for subtraction, 010 for
AND, 011 for OR, and 101 for set less than. ALU内部的逻辑暂时先不管,先构建顶层。

2.4 beq 数据路径

   beq指令相较前面模块的区别在于指令地址的跳转,之前相邻指令之间地址递增为4。而beq 指令则是要跳转到label的位置,于是改动如下图:
在这里插入图片描述
   前面提到了立即数在不同指令类型中位置不一样,这里表格中ImmSrc控制了在Extend内部对Instr 31 : 7 _{31:7} 31:7的操作。通过于立即数相加,实现地址的跳转。这样我们就将数据路径大致构建完成。

2.5 控制单元

   数据路径中穿插了许多控制信号,我们将其整合起来,就形成了一个完整的基于四条指令集的处理器。中间省略了一些细节,不过没关系,可以先看整体的思路。在单周期处理器控制单元中控制信号的产生主要基于op,funct3和funct7。在RV32I 指令集中只使用了funct7的第五位。所以我们只需要考虑op( I n s t r 6 : 0 Instr_{6:0} Instr6:0), funct3( I n s t r 14 : 12 Instr_{14:12} Instr14:12)和funct7 5 _5 5( I n s t r 30 Instr_{30} Instr30)。完整的单周期处理器如下:
在这里插入图片描述
  控制单元我们可以看成一个译码器,输入op,funct3和funct7 5 _5 5,然后输出一些控制信号。我们又把这个译码器分为main decoder 和alu decoder,如下图:
在这里插入图片描述
  如果我们知道译码器的真值表,那么写代码会很容易,根据每条指令的路径,我们可以得到主译码的真值表:
在这里插入图片描述
   branch 和ALUOp作为中间信号又传递到其他的模块。其中x表示不关心,即这条指令没有用到此控制信号。单bit的控制信号很好理解,在图中一般为写使能信号和二选一选择器的控制信号。而ImmSrc控制着立即数扩展单元,ALUOp则输入到ALU decoder中。我们再来看一下ALU decoder的真值表:
在这里插入图片描述
  至于真值表详细是如何得到的,还得再了解一下汇编语言到机器语言的转换。而且真值表也不唯一,例如在四选一选择器中,00表示的选项和01表示的选项都是可以按自己的想法来设计。所以我暂时也没想去细致的了解,先按书中的来。
  以and指令为例,我们分析一下它的路径以及控制信号的值:
在这里插入图片描述

  add指令最后需要将值写入到目的寄存器中,所以RegWrite为1;add指令没有用到立即数,ImmSrc为xx;add指令的两个源操作数为寄存器操作数,SrcB选择RD2端口输出的数。add指令执行加法,ALUControl为010;and指令不需要写入存储器,MemWrite为0;结果选择从ALU中计算出的结果,ResultSrc为0;最后指令不跳转,选择地址加4,PCSrc为0。

2.6更多的指令

  前面的构建的处理器只是基于一小部分的指令集,我们继续添加两条指令:addi(add immediate)和jal(jump and link)。想构造一个完整的指令集也是可以的,不过工作量比较大,这些基本的指令足够了解原理了。

2.6.1 addi 指令

  对于addi指令,我们并不需要添加额外的逻辑模块,只需要修改控制单元的真值表即可,它和R-type比较类似,区别就在于addi的一个源操作数为立即数,真值表如下:
在这里插入图片描述

2.6.2 jal 指令

   jal指令表示跳转到指定位置,并且保存返回的位置(PC+4),例如jal sample ,即跳转到target为sample的地方,并且保存jal smaple 下一条指令的位置在ra寄存器。添加jal指令我们需要改动两个地方:1.跳转到指定位置需要将PC与立即数相加,而jal指令中立即数的位置和其他指令不同,因此需要修改立即数扩展模块。2.需要将返回地址写入到ra寄存器中,因此result的选择要多一个。先来第一点,我们对ImmSrc的译码新增一种情况即可:
在这里插入图片描述  第二点,在结果处也新增一个选项,这个选项是PCPlus4(jal指令的下一条指令的地址),如下图:
在这里插入图片描述
  最后修改控制单元的真值表,jal和主要和main decoder有关,因此main decoder修改成:
在这里插入图片描述
  加了一个jump信号来决定PCSrc的值。只有J类指令,jump=1,beq这种分支指令jump=0,branch=1。下图为main decoder添加两条指令后的真值表:
在这里插入图片描述
  RegWrite=1,ResultSrc = 10,因为需要将PC+4写到寄存器文件中;ImmSrc
= 11,来选择21bit的跳转偏移量;ALUSrc 和 ALUOp不重要;MemWrite = 0,因为不需要写入存储器。Branch=0,因为指令不是分支。最后jump=1,因为要跳转。分支和跳转是有区别的。

3 仿真结果

  通过以上思路构建了基于RISC-V部分指令集的架构,文章最上方的代码提供了这个single-cycle processor的仿真。仿真的文本说明为下图1,仿真结果如下图2和图3:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
note:仿真成功的标志是存储器地址为100的位置值为25。仿真的结果只出现在仿真开始的一小段时间,注意把观察轴拉到起始位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值