系列文章目录

(一)从零开始设计RISC-V处理器——指令系统
(二)从零开始设计RISC-V处理器——单周期处理器的设计
(三)从零开始设计RISC-V处理器——单周期处理器的仿真
(四)从零开始设计RISC-V处理器——ALU的优化
(五)从零开始设计RISC-V处理器——五级流水线之数据通路的设计
(六)从零开始设计RISC-V处理器——五级流水线之控制器的设计
(七)从零开始设计RISC-V处理器——五级流水线之数据冒险
(八)从零开始设计RISC-V处理器——五级流水线之控制冒险
(九)从零开始设计RISC-V处理器——五级流水线之分支计算前移
(十)从零开始设计RISC-V处理器——五级流水线之静态预测


本人本科生一枚,之前学习了计算机体系结构(RISC-V版)这本书,对CPU的设计产生了浓厚的兴趣,于是决定在寒假期间,自己动手设计一个基于RISC-V指令集的CPU,顺便写一些文章,记录自己的学习成果,如果文章中有不合理之处,希望大家能批评指正。

(目前单周期处理器代码已更新完成,点击链接直达:基于RISC-V指令集的单周期处理器的设计

(视频版正在更新中~:
1.基于RISC-V指令集的处理器设计——CPU的基本原理
2.基于RISC-V指令集的处理器设计——指令集的介绍

今天是第一天,首先进行指令的选取。

如下表所示,计划实现以下37条指令。
在这里插入图片描述
对指令的理解:
1.LUI:将20位立即数的值左移12位(低12位补零)成为一个32位数,将其写回rd。这一指令主要是为了在寄存器中存入比较大的立即数,比如,要想在寄存器X1中存入一个数,可以用addi指令实现(addi x1,x0,100),但这个数的范围有限(-2048~2047),因为addi指令的立即数部分只有12位,能表示最大的无符号数位0xfff(十进制4095),对应的有符号数的范围则为-2048到2047。当立即数超出这个范围,则需要用lui指令。

如lui x1,0xffff ,二进制指令:00001111111111111111000010110111

汇编器执行结果如下:
在这里插入图片描述
2.AUIPC:将20位立即数的值左移12位(低12位补零)成为一个32位数,再加上该指令的pc值,再将结果写回rd。这条指令与lui类似,可能是为了实现PC在较大范围内跳转(仅仅是猜测,因为这条指令我还没有接触过)。

如auipc x2,0xfff,二进制指令:00000000111111111111000100010111

当前PC为4,汇编器执行结果如下:
在这里插入图片描述
3.JAL:PC+4 的结果送 rd 但不送入PC,然后计算下条指令地
址。转移地址采用相对寻址,基准地址为当前指令地址
(即 PC),偏移量为立即数 imm20 经符扩展后的值的 2 倍。
在实际的汇编程序编写中,跳转的目标往往使用汇编程序中的 label,汇编器会自动根据 label 所在的地址计算出相对的偏移量赋予指令编码。

如:
jal x3,label1 二进制:00000000100000000000000111101111
addi x4,x0,4
label1:
addi x5,x0,5
当前PC为 8,跳过给X4赋值这条指令,并且将12赋给X3寄存器,汇编器执行结果如下:
在这里插入图片描述
(补充:仔细分析上面jal指令的二进制会发现,立即数部分是4,4*2=8,PC+8=12,跳过中间的一条指令,这样看起来似乎没什么问题。

但是再看jal的立即数部分,总共是20位,拼接起来是[20:1],这里为什么不是[19:0]?

观察其他的指令格式,如果立即数是12位,那么会表示为[11:0],但JAL,BEQ,BNE,BLT,BGE,BLTU,BGEU却例外。

于是引发了以下思考(个人观点,未经查证,以后深入学习之后再回来补充):假设立即数其实是21位的,即[20:0],只不过始终保持最低位为0,所以在指令中仅仅体现[20:1],这样一来,21位的立即数一定是2的倍数,将立即数乘以2之后,一定是4的倍数,这样就可以保证跳转后的PC一定是4的倍数。所以,对于一个label,假设当前指令为第0行,label为第几行,立即数[20:1]则为多少,这样,立即数最低位补0再乘以2,与当前PC相加,则为正确的跳转地址。

以上的假设便能够合理地解释立即数为[20:1]的原因。

基于以上假设再来看上面jal指令的二进制,发现并不是这样,在以上的假设中,立即数应该是2,但汇编器转化出来的立即数是4,所以要么是我的假设出了问题,要么是汇编器出了问题。

站在汇编器的角度,立即数就是20位的,不存在最低位补个0的说法,所以汇编器在计算立即数的时候,自然而然地就已经将立即数设置成2的倍数了。对于BEQ,BNE,BLT,BGE,BLTU,BGEU指令也是一样。

在这里我更愿意相信我的假设,也许并不一定正确,但这看起来似乎更合理。以上仅为个人拙见,日后深入学习后可能才会发现其中的奥妙。)

4.JALR:jalr 指令使用 12 位立即数(有符号数)作为偏移量,与操作数寄存器 rs1中的值相加,然后将结果的最低有效位置0。jalr指令将其下一条指令的 PC(即当前指令PC+4)的值写入其结果寄存器 rd。

如:
addi x4,x0,4
jalr x8,x4,8 二进制:00000000100000100000111111100111
addi x5,x0,5
addi x6,x0,6
addi x7,x0,7
jalr指令所在的PC为4,4+8=12,则跳向PC=12的指令,同时将8存入X8寄存器。汇编器执行结果如下:
在这里插入图片描述
5.BEQ:beq指令只有在操作数寄存器rs1中的数值与操作数寄存器rs2的数值相等时,才会跳转,跳转地址为offset的有符号扩展和最低位补0(即乘以2)的偏移量加上BEQ指令的地址。

如:
addi x1,x0,1
label1:
add x1,x1,x1
addi x2,x0,2
beq x1,x2,label1 二进制:11111110001000001000110011100011

汇编器执行结果如下:
在这里插入图片描述
6.BNE:bne指令只有在操作数寄存器rs1中的数值与操作数寄存器rs2的数值不相等时,才会跳转,跳转地址为offset的有符号扩展和最低位补0(即乘以2)的偏移量加上BNE指令的地址。

如:
addi x1,x0,1
label1:
add x1,x1,x1
addi x2,x0,4
bne x1,x2,label1 二进制:11111110001000001001110011100011

汇编器执行结果如下:
在这里插入图片描述
7.BTL:blt指令只有在操作数寄存器rs1中的数值小于操作数寄存器rs2的数值时(有符号数),才会跳转,跳转地址为offset的有符号扩展和最低位补0(即乘以2)的偏移量加上BLT指令的地址。

如:
addi x1,x0,-1
label1:
add x1,x1,x1
addi x2,x0,-2
blt x1,x2,label1 二进制:11111110001000001100110011100011

汇编器执行结果如下:
在这里插入图片描述
8.BGE:bge指令只有在操作数寄存器rs1中的数值大于或等于操作数寄存器rs2的数值时(有符号数),才会跳转,跳转地址为offset的有符号扩展和最低位补0(即乘以2)的偏移量加上BGE指令的地址。
(注意bge指令的条件是大于等于,而blt的条件是小于)

如:
addi x1,x0,-1
label1:
add x1,x1,x1
addi x2,x0,-2
bge x1,x2,label1 二进制:11111110001000001101110011100011

汇编器执行结果如下:
在这里插入图片描述
9.BLTU:bltu指令只有在操作数寄存器rs1中的数值小于操作数寄存器rs2的数值时(无符号数),才会跳转,跳转地址为offset的有符号扩展和最低位补0的偏移量加上BLTU指令的地址。

如:
addi x1,x0,-1
label1:
add x1,x1,x1
addi x2,x0,2
bltu x1,x2,label1 二进制:11111110001000001110110011100011

这里一定是不跳转的,X1的值为-2,但是将其看作是一个无符号的数,就是一个非常大的正数。汇编器执行结果如下:

有符号表示:
在这里插入图片描述
无符号表示:
在这里插入图片描述
10.BGEU:bgeu指令只有在操作数寄存器rs1中的数值大于或等于操作数寄存器rs2的数值时(无符号数),才会跳转,跳转地址为offset的有符号扩展和最低位补0的偏移量加上BGEU指令的地址。

如:
addi x1,x0,-1
label1:
add x1,x1,x1
addi x2,x0,-2
bgeu x1,x2,label1 二进制:11111110001000001111110011100011

这里一定是跳转一次的,因为刚开始时X1=X2,跳转一次后不会再跳转。
汇编器执行如果如下:

有符号表示:
在这里插入图片描述
无符号表示:
在这里插入图片描述
11.SB:sb指令 将操作数寄存器rs2中的低8位数据,写回存储器

如:
addi x1,x0,0xf1
addi x2,x0,0xf2
addi x3,x0,0xf3
addi x4,x0,0xf4
sb x1,0,x0
sb x2,1,x0
sb x3,2,x0
sb x4,3,x0

汇编器执行结果如下:
在这里插入图片描述

12.SH:sh指令 将操作数寄存器rs2中的低16位数据,写回存储器。

如:
lui x1,0xf0f11
addi x1,x1,0x7ff
sh x1,12,x0

汇编器执行结果如下:

寄存器X1:
在这里插入图片描述
数据存储器:
在这里插入图片描述
13.SW:sw指令 将操作数寄存器rs2中的32位数据,写回存储器。

如:
lui x1,0xf0f11
addi x1,x1,0x7ff
sw x1,12,x0

汇编器执行结果如下:
在这里插入图片描述
14.LB:lb指令从存储器中读回一个8位的数据,进行符号位扩展后写回寄存器rd。

如:
lui x1,0x3ef12
addi x1,x1,0x7ff
sw x1,12,x0
lb x2,12,x0
lb x3,13,x0
lb x4,14,x0
lb x5,15,x0

现在X1寄存器存入一个0x3ef127ff,然后将这32位数存入数据存储器,再从数据存储器中依次读一个字节并进行符号扩展后存入寄存器。汇编器执行结果如下:

X1寄存器:
在这里插入图片描述

数据存储器:
在这里插入图片描述
符号扩展写入寄存器:
在这里插入图片描述
15.LH:lh指令从存储器中读回一个16位的数据,进行符号位扩展后写回寄存器rd。

如:
lui x1,0x3ef12
addi x1,x1,0x7ff
sw x1,12,x0
lh x2,12,x0
lh x3,13,x0
lh x4,14,x0
lh x5,15,x0

汇编器执行结果如下:

数据存储器:
在这里插入图片描述
符号扩展写入寄存器:
在这里插入图片描述
(思考:这里的X5中的83是什么,调出来数据存储器的数据可以看到,由于这里的地址是非对其访问,读两个字节就需要访问两个存储块。因此,为了提升指令的运行效率,使用load和store指令时,尽量采用边界对齐的地址访问)

16.LW:lw指令从存储器中读回一个32位的数据,写回寄存器rd。

如:
lui x1,0xf0f11
addi x1,x1,0x7ff
sw x1,0,x0
lw x2,0,x0

汇编器的执行结果如下:

数据存储器:
在这里插入图片描述

寄存器:
在这里插入图片描述
17.LBU:lbu指令从存储器中读回一个8位的数据,进行高位补零扩展后写回
寄存器rd。

如:
lui x1,0x3ef12
addi x1,x1,0x7ff
sw x1,12,x0
lbu x2,12,x0
lbu x3,13,x0
lbu x4,14,x0
lbu x5,15,x0

汇编器的执行结果如下:

数据存储器:
在这里插入图片描述
零扩展写入寄存器:
在这里插入图片描述
18.LHU:lhu指令从存储器中读回一个16位的数据,进行高位补零扩展后写回寄存器rd。

如:
lui x1,0x3ef12
addi x1,x1,0x7ff
sw x1,12,x0
lhu x2,12,x0
lhu x3,13,x0
lhu x4,14,x0
lhu x5,15,x0

汇编器执行结果如下:

数据存储器:
在这里插入图片描述
零扩展写入寄存器:
在这里插入图片描述
19.SLT:slt指令将操作数寄存器rs1中的数值与寄存器rs2当作有符号数进行比较。小于则置一,即如果rs1中的值小于rs2中的值,则输出1,否则输出0,结果写回rd。

如:
addi x1,x0,-1
addi x2,x0,1
slt x3,x1,x2

汇编器执行结果如下:
在这里插入图片描述
20.SLTU:sltu指令将操作数寄存器rs1中的数值与寄存器rs2当作无符号数进行比。如果rs1中的值小于rs2中的值,则输出1,否则输出0,结果写回rd。

如:
addi x1,x0,-1
addi x2,x0,1
sltu x3,x1,x2

汇编器执行结果如下:
在这里插入图片描述
21.SLTI:slti指令将操作数寄存器rs1中的数值与12位立即数当作有符号数进行比较,如果rs1中的值小于立即数的值,则输出1,否则输出0,结果写回rd。

如:
addi x1,x0,-1
slti x2,x1,1

汇编器执行结果如下:
在这里插入图片描述
22.SLTIU:sltiu指令将操作数寄存器rs1中的数值与12位立即数当作无符号数进行比较,如果rs1中的值小于立即数的值,则输出1,否则输出0,结果写回rd。

如:
addi x1,x0,-1
sltiu x2,x1,1

汇编器执行结果如下:
在这里插入图片描述
23.SLL:逻辑左移(SLL)根据寄存器(rs2)中的移位量对寄存器(rs1)中的值执行逻辑左移,并存储在(rd)寄存器中。

如:
addi x1,x0,4
addi x2,x0,0xf0
sll x3,x2,x1

汇编器执行结果如下:
在这里插入图片描述
24.SRL:逻辑右移(SRL)根据寄存器(rs2)中的移位量对寄存器(rs1)中的值执行逻辑右移,左边空出来的位补0,并存储在(rd)寄存器中。

如:
addi x1,x0,4
addi x2,x0,-0xf0
srl x3,x2,x1

汇编器执行结果如下:
在这里插入图片描述
25.SRA:算数右移(SRA)根据寄存器(rs2)中的移位量对寄存器(rs1)中的值执行算数右移,左边空出来的位补符号位,并存储在(rd)寄存器中。

如:
addi x1,x0,4
addi x2,x0,-0xf0
sra x3,x2,x1

汇编器执行结果如下:
在这里插入图片描述
26.SLLI:slli指令根据5位立即数的移位量对寄存器(rs1)中的值执行逻辑左移(低位补零),并存储在(rd)寄存器中。
27.SRLI:srli指令根据5位立即数的移位量对寄存器(rs1)中的值执行逻辑右移(高位补零),并存储在(rd)寄存器中。
28.SRAI:srai指令根据5位立即数的移位量对寄存器(rs1)中的值执行算数右移(高位补入符号位),并存储在(rd)寄存器中。

如:
addi x2,x0,-0xf0
slli x3,x2,4
srli x4,x2,4
srai x5,x2,4

汇编器执行结果如下:
在这里插入图片描述
29.ADD:add指令将寄存器(rs1)与寄存器(rs2)中的值相加,并写回(rd)寄存器中。
30.SUB:sub指令将寄存器(rs1)与寄存器(rs2)中的值相减,并写回(rd)寄存器中。
31.ADDI:addi指令将操作数寄存器rs1的整数值与12位立即数进行加法操作,
结果写回寄存器rd。

如:
addi x1,x0,-1
add x2,x1,x1
sub x3,x2,x1

汇编器执行结果如下:
在这里插入图片描述
32.XOR:xor指令在寄存器(rs1)的内容和寄存器(rs2)的内容之间执行逐位逻辑“异或”运算,并存储在(rd)寄存器中。
33.OR:or指令在寄存器(rs1)的内容和寄存器(rs2)的内容之间执行逐位逻辑“或”运算,并存储在(rd)寄存器中。
34.AND:and指令在寄存器(rs1)的内容和寄存器(rs2)的内容之间执行逐位逻辑“与”运算,并存储在(rd)寄存器中。
35.XORI:xori指令将操作数寄存器rs1中的数值与12位立即数进行异或操作,
结果写回rd。
36.ORI:ori指令将操作数寄存器rs1中的数值与12位立即数进行或操作,
结果写回rd。
37.ANDI:andi指令将操作数寄存器rs1中的数值与12位立即数进行与操作,
结果写回rd。

如:
addi x1,x0,0b11001100
addi x2,x0,0b00111111
xor x3,x1,x2
or x4,x1,x2
and x5,x1,x2
xori x6,x1,0b00111111
ori x7,x1,0b00111111
andi x8,x1,0b00111111

汇编器执行结果如下:
在这里插入图片描述
以上就是计划实现的37条指令的用法。下一篇文章开始设计处理器。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐