0 了解RISC-V
0.1 什么是RISC-V处理器
RISC-V(发音为"risk-five")是基于精简指令集计算(RISC)原则设计的一种开放标准指令集架构(ISA)。区别于x86或ARM等专有指令集架构,RISC-V允许任何组织或个人在遵守其开源许可协议的前提下,自由地使用、开发、制造和销售基于RISC-V的芯片和软件。这种开放性不仅推动了技术创新,还降低了进入门槛,使得更多的企业和研究机构能够参与到定制硬件解决方案的开发中。因此,RISC-V已经成为了教育、研究和商业领域中极为受欢迎的平台。
0.2 RISC-V的主要特点
开放和模块化:RISC-V提供了标准的基础指令集和一系列可选的扩展指令集,使得开发者可以根据需要定制适合自己应用场景的处理器。
简洁高效:基于RISC原则,RISC-V致力于通过较少的、更简单的指令实现高效率的数据处理,以达到提高处理速度和降低能耗的目标。
易于扩展:RISC-V的设计哲学支持易于扩展的指令集,以适应新兴计算需求和技术的发展。
广泛应用:从微控制器到超级计算机,RISC-V的应用范围极为广泛,覆盖了各类计算设备。
生态系统支持:尽管RISC-V比较新,但已经建立起了一个快速发展的生态系统,包括丰富的软件工具链、操作系统、硬件设计工具以及一个活跃的开发社区。
0.3 设计RISC-V处理器的好处
提升就业竞争力:在简历中展示数字IC设计经验可以吸引雇主的注意,尤其是那些寻求具有现代硬件设计能力的候选人的雇主。
易于学习:RISC-V的精简指令集使得它成为初学者友好的平台,便于快速上手和理解。
提升编程能力:通过设计RISC-V处理器,可以提高Verilog编程技能,这是数字设计领域的关键能力。
深入理解CPU架构:自己动手设计处理器可以帮助深入理解CPU的工作原理和计算机体系结构,为高级学习和研究打下坚实的基础。
1 RISC-V基本模块讲解
1.1 基础知识
1.1.1 ROM是什么?
在RISC-V架构中,ROM(只读存储器)扮演着特定而重要的角色,正如它在许多其他计算系统中的作用一样。ROM是一种非易失性存储介质,这意味着即使在断电的情况下,它也能保持存储的数据不丢失。在RISC-V系统中,ROM通常用于存储启动时必须的代码和数据,如引导加载程序(Bootloader)或固件,这些是计算机加电启动和初始化硬件所需的最初指令集。
在RISC-V系统中,ROM是一种存储器,用来保存电脑启动和运行必需的信息。想象它就像电脑的启动手册,当你打开电脑时,它告诉电脑怎么开始工作。这里的信息是固定的,一般不会改变,就像一本印刷好的书。
1.1.1.1 ROM的作用和特点
1.1.1.1.1 不可修改
ROM中的数据在制造过程中被编程,通常无法或难以修改。这使得ROM成为存储固件或其他不需要频繁更新的代码的理想选择。ROM里的内容通常在制造时就确定了,之后就很难改变。这就像是一本书,一旦印刷出来,你就不能在里面添加新的章节或改变文字了。
1.1.1.1.2 启动指令
在RISC-V系统中,ROM包含了引导加载程序,该程序负责在设备加电启动时初始化硬件,并加载操作系统或其他主程序到RAM中以供执行。ROM里保存着帮助电脑启动的程序,就好比一本指南,告诉电脑如何一步步启动直到准备好让你使用。
1.1.1.1.3 可靠性
由于ROM是非易失性的,它提供了一种安全可靠的方式来存储系统最基本和最关键的启动程序,确保系统即使在断电后也能正确启动。因为ROM不会轻易改变,所以它保存的启动程序是安全可靠的,确保电脑每次都能按照正确的步骤启动。
1.1.1.2 RISC-V中ROM的应用
在RISC-V架构的计算设备中,ROM可能包含以下内容:
引导加载程序(Bootloader):是系统启动时运行的第一段程序,负责初始化系统硬件,设置必要的运行参数,并加载更高级别的操作系统或应用程序。
基本输入输出系统(BIOS)或固件:在某些系统中,ROM还可能包含BIOS或固件,这些软件直接与硬件交互,提供系统级别的控制和配置功能。
配置数据:一些基础的配置数据,如硬件参数、系统设置等,也可能存储在ROM中,用于系统启动或运行时参考。
由于RISC-V是一个开放标准的ISA,具体使用ROM的方式可能会根据实现的具体硬件和软件需求有所不同。开发者和制造商可以根据自己的需要在设计时决定如何最有效地利用ROM来存储引导程序、固件或其他必要的系统代码。
1.1.1.3 ROM在RISC-V系统中的角色
想象你买了一款玩具组装套装,套装里有一本说明书(ROM),告诉你怎么一步步组装玩具(启动电脑)。说明书是不会变的,提供了启动和组装的所有必要信息。在RISC-V电脑里,ROM就扮演了这样的角色,确保电脑能够正确地启动和运行。
1.1.2 时序电路与组合逻辑
在数字电路设计中,时序电路和组合逻辑是构成复杂电路系统的两个基本元素。它们在功能和构造上有明显的区别。理解这些差异对于设计和分析数字电路系统非常重要。
1.1.2.1 组合逻辑 (Combinational Logic)
组合逻辑电路的输出仅仅依赖于当前的输入值,而不依赖于电路之前的状态或历史输入。这意味着,给定相同的输入,无论何时查询组合逻辑电路,其输出都会是相同的。
1.1.2.2 组合逻辑的特点
无记忆性:组合逻辑电路不能存储任何信息,它们不具备历史状态的概念。
确定性输出:给定一组特定的输入,输出是完全可预测的。
示例:加法器、减法器、逻辑门(如AND、OR、NOT)等。
1.1.2.3 时序电路 (Sequential Logic)
时序电路的输出不仅依赖于当前的输入值,还依赖于电路的先前状态(或历史)。这种依赖关系通过反馈路径实现,使电路能够“记住”信息。时序电路通常包含一个或多个存储元件,如触发器(Flip-Flops)或寄存器,用于保存状态信息。
1.1.2.4 时序电路的特点
有记忆性:时序电路可以存储信息,允许电路的当前状态受过去状态的影响。
依赖于时钟:许多时序电路依赖于时钟信号来同步状态的改变。
示例:计数器、寄存器、时钟驱动的电路等。
1.1.2.5 主要区别
记忆性:组合逻辑没有记忆功能,而时序电路可以存储历史信息。
时钟信号:时序电路通常需要时钟信号来同步状态改变,而组合逻辑电路则不需要。
设计复杂性:由于时序电路包含状态保存能力,其设计和分析通常比组合逻辑更复杂。
应用场景:组合逻辑适用于执行简单的逻辑决策和计算,而时序电路用于构建需要记忆和状态转换的复杂系统,如计算机内存、控制单元等。
理解这两种电路的区别和应用,是设计有效和高效数字系统的关键。
1.1.3 指令的存储
1.1.3.1 ROM(只读存储器)
主要用于存储那些在设备的整个生命周期内几乎不需要更改的信息,如引导程序(Bootloader)或固件。这些指令用于初始化硬件和加载操作系统等基础任务。
1.1.3.2 RAM(随机存取存储器)
程序运行时的指令和数据通常存储在RAM中,因为RAM可以快速读写,适合存储临时数据和正在运行的程序需要频繁访问的指令。
1.1.3.3 硬盘或固态硬盘
长期存储的程序和数据保存在硬盘或固态硬盘中。当需要运行某个程序时,它的指令会从硬盘加载到RAM中,因为直接从硬盘运行程序速度较慢。
1.1.3.4 闪存(Flash Memory)
类似于ROM,但可以被重新编程和擦除。一些设备可能将操作系统或其他软件存储在闪存中,便于更新。
1.1.3.5 Offchip SRAM(Off-chip Static Random-Access Memory)
指的是位于处理器或微控制器芯片外部的静态随机存取存储器。这种类型的SRAM并不集成在中央处理器(CPU)或系统芯片(SoC)上,而是作为独立的组件安装在电路板上。由于它位于芯片外部,因此通常通过数据总线与CPU或其他处理器通信。
1.1.3.5.1 Offchip SRAM的特点
速度快:SRAM比动态随机存取存储器(DRAM)速度快,因为它不需要像DRAM那样不断刷新。
易于访问:SRAM提供了随机访问,这意味着每个存储单元都可以直接访问,访问时间与存储单元的位置无关。
非易失性:SRAM是易失性存储器,这意味着一旦断电,其中存储的信息就会丢失。
成本高:与DRAM相比,SRAM的成本更高,且每位存储的密度较低。
功耗低:尽管SRAM速度快,但它通常比DRAM功耗低。
1.1.3.5.2 在计算机体系结构中的使用
高速缓存:SRAM常用于CPU内部的L1、L2和L3缓存,但在某些设计中,特别是较老的或特定的嵌入式系统中,缓存可能使用offchip SRAM。
存储中间数据:在某些处理任务中,offchip SRAM可以用于存储中间计算结果或频繁访问的数据。
外围设备和IO扩展:外围设备可能使用offchip SRAM来缓冲数据,以降低对主处理器的访问需求。
实时应用:实时系统中,SRAM用于存储对延迟敏感的数据,以确保快速、一致的响应时间。
由于其快速访问特性,offchip SRAM通常用于对性能要求极高的应用场景。随着技术的发展,许多现代应用中的SRAM已被集成到CPU芯片中,但offchip SRAM仍然在某些特殊应用场合中使用。
总的来说,ROM主要用于存储系统启动和初始化所需的基本指令。而日常使用中的应用程序和操作系统等指令通常不存储在ROM中,而是存储在RAM、硬盘或固态硬盘等其他存储设备中,以便于访问和更新。
1.1.4 大小端模式
大小端模式(Endianness)是指在计算机内存中存储多字节数据时,字节序列的排列顺序。这个概念对于理解数据如何在不同类型的计算机系统中被读取和写入是非常重要的。
主要有两种类型:大端(Big-Endian)和小端(Little-Endian)。
1.1.4.1 大端模式(Big-Endian)
在大端模式中,最高位字节(最重要的一部分)被存储在内存的最低地址处,其余字节按照重要性递减的顺序存储。这意味着,数字的第一个字节(例如,32位整数的最高8位)放在地址最小的地方。
举例:如果我们有一个值为0x12345678的32位整数,并且我们要将这个值存储在以地址0x00开始的内存中,那么在大端模式下,内存的排列会是这样的:
0x00: 12
0x01: 34
0x02: 56
0x03: 78
1.1.4.2 小端模式(Little-Endian)
在小端模式中,最低位字节(最不重要的一部分)被存储在内存的最低地址处,其余字节按照重要性递增的顺序存储。这意味着,数字的最后一个字节(例如,32位整数的最低8位)放在地址最小的地方。
举例:采用同样的32位整数0x12345678,如果我们要将这个值存储在以地址0x00开始的内存中,那么在小端模式下,内存的排列会是这样的:
0x00: 78
0x01: 56
0x02: 34
0x03: 12
1.1.4.3 为什么大小端模式很重要?
大小端模式的差异在于跨平台软件开发和网络通信中尤为重要。不同的计算机架构选择了不同的字节序,如果在这些平台之间交换数据,必须正确地转换字节序,以确保数据的正确性。例如,在网络协议中,通常采用大端模式(也称为网络字节顺序)来保证数据在不同系统间传输时的一致性。
理解和处理大小端模式的区别是编程中处理字节操作时必须考虑的关键因素,尤其是在涉及到底层数据处理、硬件接口编程和网络通信时。
1.1.5 RISC-V三级流水线
在讨论RISC-V三级流水线的时候,我们把整个处理过程分为几个阶段,这有助于提高处理器的执行效率,使得每个时钟周期都有指令在执行,从而提升整体性能。流水线的设计既包括时序逻辑也包括组合逻辑,其中:
时序逻辑:主要是指存储和传递不同阶段间数据的部分,比如寄存器,它们可以在不同的时钟周期内保持数据,以供流水线的下一阶段使用。
组合逻辑:则指那些在一个时钟周期内就能完成其逻辑操作的电路部分,如算术逻辑单元(ALU)或译码逻辑。
现在,让我们按照您提供的流程详细解释RISC-V的三级流水线各个阶段,包括时序和组合逻辑的作用:
1. PC指令地址 -> IF取指
PC(程序计数器):时序逻辑。PC保存着当前要执行指令的地址。在每个时钟周期结束时,PC更新为下一条指令的地址。
取指(IF):组合逻辑。根据PC指出的地址从指令存储器中取出指令。
2. IF_ID
IF_ID寄存器:时序逻辑。这是一个流水线寄存器,用于在取指阶段(IF)和译码阶段(ID)之间传递指令。它在每个时钟周期的末尾保存从IF阶段得到的指令,以便在下一个周期中进行译码。
3. ID译码
译码(ID):组合逻辑。这个阶段分析并译码IF阶段取出的指令,确定需要执行的操作以及操作所需的操作数。此阶段也负责读取寄存器文件以获取操作数。
4. ID_EX
ID_EX寄存器:时序逻辑。这是另一个流水线寄存器,用于在译码阶段(ID)和执行阶段(EX)之间传递译码后的指令信息、操作数和其他控制信号。它在每个时钟周期的末尾保存从ID阶段得到的所有必要数据,供EX阶段使用。
5. EX执行
执行(EX):组合逻辑。在这个阶段,执行译码阶段确定的操作,可能是算术计算、逻辑操作或其他类型的操作。对于跳转指令,这个阶段还会计算新的PC值。
这个简化的过程展示了RISC-V三级流水线中时序逻辑和组合逻辑的作用。实际上,现代的RISC-V实现可能包括更多级别的流水线和更复杂的逻辑,以进一步提高性能和效率。
1.1.6 RISC-V的寄存器组
在RISC-V架构中,寄存器组指的是一组可供程序员使用的寄存器,它们用于在执行指令时临时存储数据。这些寄存器可以被直接访问和操作,并且它们的使用对于指令的执行是至关重要的。在RISC-V的标准整数指令集中,通常有32个通用寄存器,每个寄存器都是32位(对于RV32I基础整数指令集)或64位(对于RV64I基础整数指令集)宽。
寄存器组中的寄存器可以被用于多种任务,包括但不限于:
作为算术和逻辑操作的操作数。
保存函数参数和返回值。
作为程序计数器(PC)的临时存储,在进行函数调用和返回时保存PC的值。
存储局部变量。
在RISC-V中,这32个寄存器分别标识为x0到x31。其中,x0寄存器是一个固定的零寄存器,即它总是返回0,并且不能被编程改变。其他寄存器(x1到x31)可用于通用目的。例如,x1寄存器通常用作返回地址(在调用函数时保存返回点),x2用作堆栈指针,而x10和x11则用于传递函数的前两个参数。
这些寄存器的使用遵循RISC-V的调用约定,这是一组规则,用来指导函数如何接受参数,如何返回结果,以及在函数调用过程中寄存器应该如何被保存和恢复。这些约定对于编译器、链接器以及汇编程序员来说都是非常重要的,以确保不同的程序和程序片段能够正确地相互工作。
1.2 32位RISC-V处理器指令格式
1.2.1 ADD指令
1.2.1.1 汇编指令
ADD rd, rs1, rs2
1.2.1.2 含义
将 `rs1` 寄存器的值与 `rs2` 寄存器的值相加,并将结果存储到 `rd` 寄存器中。
1.2.1.3 指令格式
| funct7 (7 bits) | rs2 (5 bits) | rs1 (5 bits) | funct3 (3 bits) | rd (5 bits) | opcode (7 bits) |
| 0000000 | rs2 | rs1 | 000 | rd | 0110011 |
1.2.1.4 指令字段解释
`funct7` (31-25 bits): 固定为 `0000000` 表示加法操作。
`rs2` (24-20 bits): 源寄存器 2。
`rs1` (19-15 bits): 源寄存器 1。
`funct3` (14-12 bits): 固定为 `000` 表示加法操作。
`rd` (11-7 bits): 目标寄存器。
`opcode` (6-0 bits): 固定为 `0110011` 表示 R 型指令。
1.2.2 SUB指令
1.2.2.1 汇编指令
SUB rd, rs1, rs2
1.2.2.2 含义
将 `rs1` 寄存器的值减去 `rs2` 寄存器的值,并将结果存储到 `rd` 寄存器中。
1.2.2.3 指令格式
| funct7 (7 bits) | rs2 (5 bits) | rs1 (5 bits) | funct3 (3 bits) | rd (5 bits) | opcode (7 bits) |
| 0100000 | rs2 | rs1 | 000 | rd | 0110011 |
1.2.2.4 指令字段解释
`funct7` (31-25 bits): 固定为 `0100000` 表示减法操作。
`rs2` (24-20 bits): 源寄存器 2。
`rs1` (19-15 bits): 源寄存器 1。
`funct3` (14-12 bits): 固定为 `000` 表示减法操作。
`rd` (11-7 bits): 目标寄存器。
`opcode` (6-0 bits): 固定为 `0110011` 表示 R 型指令。
1.2.3 LOAD指令
1.2.3.1 汇编指令
LW rd, offset(rs1)
1.2.3.2 含义
从内存中加载数据到寄存器 `rd`,地址由 `rs1` 的值加上偏移量 `offset` 确定。
1.2.3.3 指令格式
| imm[11:0] (12 bits) | rs1 (5 bits) | funct3 (3 bits) | rd (5 bits) | opcode (7 bits) |
| offset | rs1 | 010 | rd | 0000011 |
1.2.3.4 指令字段解释
`imm[11:0]` (31-20 bits): 偏移量。
`rs1` (19-15 bits): 基址寄存器。
`funct3` (14-12 bits): 固定为 `010` 表示字加载(word load)。
`rd` (11-7 bits): 目标寄存器。
`opcode` (6-0 bits): 固定为 `0000011` 表示 I 型指令。
1.2.4 STORE指令
1.2.4.1 汇编指令
SW rs2, offset(rs1)
1.2.4.2 含义
将 `rs2` 寄存器中的数据存储到内存中,地址由 `rs1` 的值加上偏移量 `offset` 确定。
1.2.4.3 指令格式
| imm[11:5] (7 bits) | rs2 (5 bits) | rs1 (5 bits) | funct3 (3 bits) | imm[4:0] (5 bits) | opcode (7 bits) |
| offset | rs2 | rs1 | 010 | offset | 0100011 |
1.2.4.4 指令字段解释
`imm[11:5]` (31-25 bits): 偏移量的高 7 位。
`rs2` (24-20 bits): 源寄存器。
`rs1` (19-15 bits): 基址寄存器。
`funct3` (14-12 bits): 固定为 `010` 表示字存储(word store)。
`imm[4:0]` (11-7 bits): 偏移量的低 5 位。
`opcode` (6-0 bits): 固定为 `0100011` 表示 S 型指令。
1.3 汇编语言与机器语言的转换
RISC-V 的汇编指令与机器码的转换过程涉及将人类可读的汇编语言转变为计算机可执行的二进制代码。汇编器(Assembler)完成这个任务。
1.3.1 示例
假设有一条汇编指令 `ADD x5, x6, x7`。汇编器会将其转换为对应的机器码。
1.3.2 汇编指令
ADD x5, x6, x7
1.3.3 机器码
0000000 00111 00110 000 00101 0110011
1.3.4 转换过程
1.3.4.1 将寄存器编号转换为二进制
`x5` -> `00101`
`x6` -> `00110`
`x7` -> `00111`
1.3.4.2 插入相应的字段
`funct7` -> `0000000`
`rs2` -> `00111`
`rs1` -> `00110`
`funct3` -> `000`
`rd` -> `00101`
`opcode` -> `0110011`
1.4 小结
通过对 RISC-V 的指令格式和基本模块的详细理解,可以更好地掌握该架构的特性和使用方法。这种知识不仅对于硬件设计人员有帮助,对于希望深入理解计算机体系结构和编译过程的软件开发人员也同样重要。RISC-V 的开放性和模块化设计使其成为一个理想的学习和研究平台。