从零开始写riscv处理器(一)指令集

1. 引言

要控制计算机硬件,就必须用它的语言。计算机语言中的单词称为指令,其词汇表称为指令系统 (instruction set)

1.1 从软件到硬件

众所周知,计算机中的硬件只能执行极为简单的低级指令,从复杂的应用程序到原始的指令涉及若干软件层次来将高层次操作解释或翻译成简单的计算机指令。下图给出了这些软件的层次结构,外层是应用软件,中心是硬件,系统软件 (systems software) 位于两者之间。

系统软件就像应用软件与底层硬件之间的过渡连接层一样,系统软件有很多种,其中有两种对于现代计算机系统来说是必需的:操作系统编译器

  • 操作系统 (operating system) 是用户程序和硬件之间的接口,为用户提供各种服务和监控功能。
  • 编译器 (compiler) 把高级语言 (如 C、C++、Java 或 Visual Basic 等) 编写的程序翻译成硬件能执行的指令。

1.2 从高级语言到硬件语言

对于计算机来说,最简单的信号是通和断。通常认为计算机语言就是二进制数,每个二进制数由0和1组成。计算机服从于我们的命令,即计算机术语中的指令 (instruction)。指令是能被计算机识别并执行的位串,可以将其视为数字,例如:

1001010100101110。

第一代程序员是直接使用二进制数与计算机通信的,这是一项非常乏味的工作。所以他们很快发明了助记符,以符合人类的思维方式。最初助记符是手工翻译成二进制的,其过程显然过于烦琐。随后设计人员开发了一种称为汇编器(assembler) 的软件,可以将助记符形式的指令自动翻译成对应的二进制。例如, 程序员写下:

add A,B

其中,add就是助记符。汇编器会将该符号翻译成:

1001010100101110

该指令告诉计算机将 A 和 B 两个数相加。这种符号语言的名称今天还在用,即汇编语言 (assembly language)。而机器可以理解的二进制语言是机器语言 (machine language)
虽然这是一个巨大的进步,但是汇编语言需要程序员写出计算机执行的每条指令,要求程序员像计算机一样思考,这样仍不符合人的思维习惯。所以需要编写一个程序来将更强大的高级语言翻译成计算机指令,编译器(complier) 由此诞生,可将高级编程语言编译为汇编语言语句。此后,程序员可以直接用高级语言来写程序,高级编程语言及其编译器大大地提高了软件的生产率。下图显示了高级语言、汇编语言、机器语言、编译器、汇编器之间的关系:

而本文要讲的就是RISCV处理器中的汇编语言程序。

2. RISCV指令集

RISCV指令集分为基础指令集和扩展指令集;又根据处理器位数不同,分为32位、64位以及128位。,其中32位指令集(RV32I)为了进一步支持嵌入式又制定嵌入式指令集(RV32E),将32个32位的通用寄存器砍掉一半,即只有16个32位的通用寄存器,其他保持不变;

  • “I” 基本整数集,其中包含整数的基本计算、Load/Store和控制流,所有的硬件实现都必须包含这一部分。
  • “C”压缩指令扩展,将某些指令进行压缩,提高代码密度;
  • “M” 整数乘除法扩展,增加了整数寄存器中的乘除法指令。
  • “A” 原子操作扩展,增加对储存器的原子读、写、修改和处理器间的同步。
  • “F” 单精度浮点扩展,增加了浮点寄存器、计算指令、L/S指令。
  • “D” 双精度扩展,扩展双精度浮点寄存器,双精度计算指令、L/S指令。
    I+C+M+F+A+D 被缩写为 “G” ,共同组成通用的标量指令。

本项目实现的是RISCV基础指令集RV32I,下面将介绍RV32I指令集。

3. RV寄存器

RV32I型具有32个32-bit的通用寄存器,具体定义和功能如下:

汇编名是通用寄存器的别名,在RISC-V汇编当中,都使用这些名称来代表这些寄存器。
其中,最特殊的是x0寄存器,即零寄存器,其被硬编码为0,写入数据忽略,读取数据为0。

4. RV32I指令集

4.1 引例

为了快速理解指令的相关概念,还是从一个例子入手,就以每台计算机都必须实现的加法运算为例:
在高级语言中,实现加法为:a = b + c。而在RISCV汇编指令中为:

add a , b , c

显而易见,这条RISC-V 汇编指令指示计算机将两个变量 b 和 c 相加并将其总和放入 a 中。这种符号表示是固定的,其中每个 RISC-V 算术指令只执行一个操作,并且必须总是只有三个变量。例如,假设要将四个变量 b、c、d、e的和放入变量a中,需要三条指令来完成者四个变量的相加。

add a , b, c //The sum of b and c is placed in a
add a , a, d //The sum of b, c, and d is now in a
add a , a, e //The sum of b, c, d, and e is now in a

类似于这总加法操作的指令为R型指令(还有其他类型指令,后面将会讲到),R型指令有三个操作数:两个被加到一起的数和一个放置总和的位置,例如add指令中的a,b,c。

与高级语言程序不同,算术指令的操作数会受到限制;它们必须取自寄存器,而寄存器数量有限并内建于硬件的特殊位置。寄存器是硬件设计中的基本元素,当计算机设计完成后,对程序员也可见,因此可以将寄存器视为计算机构建的“砖块”。在 RISC-V 体系结构中,寄存器的大小为32位;成组的32位频繁出现,因此它们在 RISC-V 体系结构中被命名为字。(另一个常见大小是成组的 64位,在 RISC-V 体系结构中称为双字。)
还是上面例子,如果已知变量a、b、c分别分配给寄存器x19、x20、x21,编译后的代码为:

add x19、x20、x21

表示,将x20、x21寄存器中的值相加放入x19寄存器中,将x20、x21称为源寄存器,x19称为目的寄存器
事实上,RISCV处理器执行的是机器代码,将上面汇编指令翻译为机器指令为:

00000001010110100000100110110011

这样不便于理解,不妨多翻译一步,先用十进制表示,然后用二进制表示。翻译为十进制机器指令为:

一条指令的每一段称为一个字段。第一、第四和第六个字段(0、0 和 51)组合起来告诉 RISC-V 计算机该指令执行加法操作。第二个字段给出了作为加法运算的第二个源操作数的寄存器编号(21 表示 X21), 第三个字段给出了加法运算的另一个源操作数(20 代表X20)。第五个字段存放要接收总和的寄存器编号(9 代表 x9)。因此,该指令将寄存器 X20和寄存器 x21 相加并将和存放在寄存器 x9 中。
该指令也可表示为二进制的形式:

这种指令的设计被称为指令格式。为了把它和汇编语言区分开来,我们把指令的数字表示称作机器语言,把这样的指令序列称作机器码

RISCV每个字段都有各自的功能,给它们分别命名使其更易于讨论:

以下是 RISC-V 指令中每个字段名称的含义:

  • opcode(操作码):指令的基本操作,这个缩写是它的惯用名称。
  • rd:目的操作数寄存器,用来存放操作结果。
  • funct3:一个另外的操作码字段。
  • rs1:第一个源操作数寄存器。
  • rs2:第二个源操作数寄存器。
  • funct7:一个另外的操作码字段。

现在,你已经大概了解了R型指令的指令格式,接着来看RV32I指令集中的其他类型指令,不同类型指令有不同的指令格式。

4.2 RV32I中的指令类型

下图是RV32I 六大类指令类型,从这里可以看出Bit域定义是非常规整的,相同的定义总是在同一位置,这样让代码更简洁、实现更加容易:

4.3 RV32I指令集

4.3.1 指令集总览

下面是RV32I各指令的指令格式:

将各指令按类型划分:

4.3.2 汇编指令对应的操作汇总

4.3.3 各指令运算

接着,按类型逐条来看各个指令实现什么样的功能:

add

  • addrd = rd1 + rs2,源寄存器1、2相加(忽略溢出),结果将被写入目标寄存器中;
  • 汇编写法:
    add rd, rs1, rs2

sub

  • subrd = rd1 – rs2,源寄存器1 – 源寄存器2(忽略溢出),结果将被写入目标寄存器中;
  • 汇编写法:
    sub rd, rs1, rs2

sll

  • sllrd = rd1 << rs2,源寄存器1值左移源寄存器2[4:0]位,空位补0,结果写入目标寄存器;
  • 汇编写法:
    sll rd, rs1, rs2

slt

  • sltrd = (rd1 < rs2)? 1:0,有符号比较源寄存器1、2值,源寄存器1小,目标寄存器写1,否则写0;
  • 汇编写法:
    slt rd, rs1, rs2

sltu

  • slturd = (rd1 < rs2)? 1:0,无符号比较源寄存器1、2值,源寄存器1小,目标寄存器写1,否则写0;
  • 汇编写法:
    sltu rd, rs1, rs2

xor

  • xorrd = rd1 ^ rs2,源寄存器1、2按位异或,结果写入目标寄存器;
  • 汇编写法:
    xor rd, rs1, rs2

srl

  • srlrd = rd1 >> rs2,源寄存器1值右移源寄存器2[4:0]位,空位补0,结果写入目标寄存器;
  • 汇编写法:
    srl rd, rs1, rs2

sra

  • srard = rd1 >> rs2,源寄存器1值循环右移源寄存器2[4:0]位,结果写入目标寄存器;
  • 汇编写法:
    sra rd, rs1, rs2

or

  • orrd = rd1 | rs2,源寄存器1、2按位或,结果写入目标寄存器;
  • 汇编写法:
    or rd, rs1, rs2

and

  • andrd = rd1 & rs2,源寄存器1、2按位与,结果写入目标寄存器;
  • 汇编写法:
    and rd, rs1, rs2

lui

  • luird = imm << 12,将其携带的20位立即数作为32位的高位,低12位置0,然后将结果载入到目标寄存器中;
  • 汇编写法:
    lui  rd,  imm

aupic

  • auipcrd = PC+(imm << 12),将其携带的20位立即数作为32位的高位,低12位置0,然后与当前的PC值相加,将结果写入目标寄存器中;
  • 汇编写法:
    auipc  rd, imm

jal

  • jalrd = PC+4;PC+=imm,将其携带的20位立即数做符号扩展,并左移一位,产生32bit的有符号数,然后与PC值相加产生指令存储器的目标地址,跳转至PC±1MB的地址范围;同时将紧随其后的指令的地址存入目标寄存器;
  • 汇编写法:
    jal  rd, offset

beq

  • beqif(rs1 $==$ rs2) PC+= imm,源寄存器1、2值相等,跳转至目标地址;而目标地址由立即数符号扩展,并左移1位与PC值相加产生;
  • 汇编写法:
    beq  rs1, rs2, imm

bne

  • bneif(rs1 != rs2) PC+= imm,源寄存器1、2值不相等,跳转至目标地址;而目标地址由立即数符号扩展,并左移1位与PC值相加产生;
  • 汇编写法:
    bne  rs1, rs2, imm

blt

  • bltif(rs1 < rs2) PC+= imm,源寄存器1小于源寄存器2值,跳转至目标地址;而目标地址由立即数符号扩展,并左移1位与PC值相加产生;
  • 汇编写法:
    blt  rs1, rs2, imm

bge

  • bgeif(rs1 >= rs2) PC+= imm,源寄存器1不小于源寄存器2值,跳转至目标地址;而目标地址由立即数符号扩展,并左移1位与PC值相加产生;
  • 汇编写法:
    bge  rs1, rs2, imm

bltu

  • bltuif(rs1 < rs2) PC+= imm,源寄存器1小于源寄存器2值,跳转至目标地址;而目标地址由立即数符号扩展,并左移1位与PC值相加产生;
  • 汇编写法:
    bltu  rs1, rs2, imm

bgeu

  • bgeuif(rs1 >= rs2) PC+= imm,源寄存器1不小于源寄存器2值,跳转至目标地址;而目标地址由立即数符号扩展,并左移1位与PC值相加产生;
  • 汇编写法:
    bgeu  rs1, rs2, imm

sb

  • sbM$[$rs1+imm$]$ $[0:7]$ = rs2$[0:7]$ ,立即数符号扩展后,与源寄存器1相加作为数据存储器的地址,将源寄存器2的最低字节存入;
  • 汇编写法:
    sb  rs2, offset(rs1)

sh

  • shM$[$rs1+imm$]$ $[0:15]$ = rs2$[0:15]$ ,立即数符号扩展后,与源寄存器1相加作为数据存储器的地址,将源寄存器2的最低2个字节存入;
  • 汇编写法:
    sh  rs2, offset(rs1)

sw

  • swM$[$rs1+imm$]$ $[0:31]$ = rs2$[0:31]$ ,立即数符号扩展后,与源寄存器1相加作为数据存储器的地址,将源寄存器2的整字存入;
  • 汇编写法:
    sw  rs2, offset(rs1)

lb

  • lbrd = M$[$rs1+imm$]$ $[0:7]$, 立即数符号扩展后,与源寄存器1相加,作为读取数据的地址,读出该地址的1字节数据,经符号扩展写入到目标寄存器中;
  • 汇编写法:
    lb  rd, offset(rs1)

lh

  • lhrd = M$[$rs1+imm$]$ $[0:15]$, 立即数符号扩展后,与源寄存器1相加,作为读取数据的地址,读出该地址的2字节数据,经符号扩展写入到目标寄存器中;
  • 汇编写法:
    lh  rd, offset(rs1)

lw

  • lwrd = M$[$rs1+imm$]$ $[0:31]$, 立即数符号扩展后,与源寄存器1相加,作为读取数据的地址,读出该地址的4字节数据,经符号扩展写入到目标寄存器中;
  • 汇编写法:
    lw  rd, offset(rs1)

lbu

  • lburd = M$[$rs1+imm$]$ $[0:7]$, 立即数符号扩展后,与源寄存器1相加,作为读取数据的地址,读出该地址的1字节数据,经0扩展写入到目标寄存器中;
  • 汇编写法:
    lbu  rd, offset(rs1)

lhu

  • lhurd = M$[$rs1+imm$]$ $[0:15]$, 立即数符号扩展后,与源寄存器1相加,作为读取数据的地址,读出该地址的2字节数据,经0扩展写入到目标寄存器中;
  • 汇编写法:
    lhu  rd, offset(rs1)

addi

  • addird = rs1+ imm,立即数做符号扩展,与源寄存器1相加(忽略溢出),将结果存入目标寄存器;
  • 汇编写法:
    addi  rd, rs1, imm

slti

  • sltird = (rs1 < imm)?1:0,立即数做符号扩展,与源寄存器1做比较:条件成立目标寄存器置1,否则置0;
  • 汇编写法:
    slti  rd, rs1, imm

sltiu

  • sltiurd = (rs1 < imm)?1:0,立即数做符号扩展,与源寄存器1做比较:条件成立目标寄存器置1,否则置0;
  • 汇编写法:
    sltiu  rd, rs1, imm

xori

  • xorird = rs1 ^ imm,立即数做符号扩展,与源寄存器1相异或,将结果存入目标寄存器;
  • 汇编写法:
    xori  rd, rs1, imm

ori

  • orird = rs1 | imm,立即数做符号扩展,与源寄存器1相或,将结果存入目标寄存器;
  • 汇编写法:
    ori  rd, rs1, imm

andi

  • andird = rs1 & imm,立即数做符号扩展,与源寄存器相与,将结果存入目标寄存器;
  • 汇编写法:
    ani  rd, rs1, imm

jalr

  • jalrrd = PC+4; PC = rs1 + imm,将其携带的12位立即数与源寄存器1值相加,并将结果的末尾清零,作为跳转的地址;与jal指令一样,将其后的指令地址存入目标寄存器中;
  • 汇编写法:
    jalr rd, offset(rs1)

slli

  • sllird = rs1 << imm[4:0],将源寄存器1值左移shamt位,空位填0,结果写入目标寄存器;shamt[5]为0有效;
  • 汇编写法:
    slli  rd, rs1, shamt

srli

  • srlird = rs1 >> imm[4:0],将源寄存器1值右移shamt位,空位填0,结果写入目标寄存器;shamt[5]为0有效;
  • 汇编写法:
    srli  rd, rs1, shamt

srai

  • sraird = rs1 >> imm[4:0],将源寄存器1值循环右移shamt位,结果写入目标寄存器;shamt[5]为0有效;
  • 汇编写法:
    srai  rd, rs1, shamt
来自广东

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇