L13 Implementing RISC-V Processor in Hardware
MIT 6.004 2019 L13 Implementing RISC-V Processor in Hardware,由教授Silvina Hanono Wachman讲述。
这次讲座的主题为:在硬件中实现RISC-V处理器
主要内容
- 搭建 RISC-V 处理器。
- 处理器的主要组成部分:寄存器文件、主存储器、ALU(算术逻辑单元)。
- 新增了一个叫做 PC(程序计数器)的寄存器,用于指向下一条要执行的指令。
- 处理指令的步骤。
- 从内存中取指(fetch)。
- 解码指令(decode)。
- 执行指令(execute)。
- 更新状态(update state)。
- 主要组件。
- ALU:用于执行算术和逻辑操作。
- 寄存器文件:有两个读端口和一个写端口。
- 主存储器:暂时使用一种“魔法内存”模型,允许在一个时钟周期内完成读写操作。
- 指令解码。
- 根据参考手册,RISC-V 指令的底部 7 位为操作码(opcode)。
- 操作码决定了指令类型和编码方式。
- ALU 指令。
- 处理两寄存器的 ALU 操作(op 类型)。
- 处理带立即数的 ALU 操作(op immediate 类型)。
- 分支指令。
- 使用分支 ALU 比较两个寄存器,决定是否进行分支。
- 提取立即数并扩展为 32 位。
- 加载和存储指令。
- 处理无条件跳转指令。
- 处理加载和存储指令,生成访问主存储器的地址。
- 指令解码和执行。
- 解码:提取指令类型、ALU 函数、分支函数、源寄存器和目的寄存器、立即数。
- 执行:根据指令类型执行相应操作,更新 PC 和寄存器文件。
- 更新状态。
- 检查目的寄存器是否有效,更新寄存器文件。
- 更新 PC。
- 执行主存储器请求(加载或存储)。
计算机硬件综述
提醒:实验五的截止日期即将到来。
RISC-V处理器的构建
今天我们将学习如何构建RISC-V处理器。首先回顾一下
,我们讨论了处理器的主要组成部分:
- 寄存器文件
- 主存
- 算术逻辑单元(ALU)
这些组件的基本工作流程如下:
- ALU操作:读取寄存器文件中的操作数,执行某种操作,然后将结果写回寄存器文件。
- 加载和存储操作:在主存和寄存器文件之间进行数据传输。
- 控制流指令:允许非顺序执行指令。
为了支持控制流操作,我们还需要一个名为程序计数器(PC)的寄存器。PC指向下一条要执行的指令在内存中的位置。如果控制流改变(即下一条指令不是顺序执行的下一条),就需要更新PC以指向正确的位置,从而获取正确的指令。
指令处理步骤
让我们思考处理一条指令的各个步骤:
- 取指:从内存中取出程序计数器(PC)所指向的指令。
- 解码:解析指令,提取指令中所有相关的字段。
- 执行:执行指令。通常,这意味着使用ALU执行某种操作;对于加载和存储操作,则是访问主存。
- 状态更新:更新寄存器文件和程序计数器,以便为下一个周期做好准备。
处理器主要组件
为了构建处理器,我们需要了解以下主要组件:
- 寄存器文件
- 算术逻辑单元(ALU)
- 内存
- 解码器
- 执行单元
我们将逐一详细讲解这些组件,并且在今天的课程结束时,希望能够实现一个单周期的RISC-V处理器。在下一节课中,我们还会探讨如何将这个处理器实现为一个多周期的非流水线实现,并讨论为什么你可能会考虑这种实现方式。
算术逻辑单元(ALU)
算术逻辑单元(ALU)概述
ALU(算术逻辑单元)执行所有的算术和逻辑功能。它的基本结构如图所示,有两个操作数a和b,还有一个功能选择器(func),根据这三个输入来生成结果。ALU的功能可以通过定义一个函数来实现,这个函数名为alu
。
typedef Bit#(32) Word;
typedef enum {Add, Sub, And, Or, Xor, Slt, Sltu, Sll, Sra, Srl} AluFunc deriving (Bits, Eq);
function Word alu(Word a, Word b, AluFunc func);
在这个函数中,Word
是一个32位的值类型,而AluFunc
是一个枚举类型,列出了RISC-V支持的所有ALU操作。在Lab 4中,我们已经实现了这个功能。
分支比较的ALU
除了基本的ALU操作(如加法和减法),ALU还包含了用于分支比较的部分,这部分被称为分支ALU(ALU for Branch Comparisons)。它主要用于在两个寄存器之间进行比较,并产生一个布尔值来决定是否进行分支。
typedef enum {Eq, Neq, Lt, Ltu, Ge, Geu} BrFunc deriving (Bits, Eq);
function Bool aluBr(Word a, Word b, BrFunc func);
和基本ALU类似,分支ALU的输入也是两个32位的值a和b,以及一个分支功能选择器(BrFunc),用于指定要进行哪种比较操作。该函数返回一个布尔值,表示是否执行分支。
分支ALU的实现
分支ALU的实现相对简单,它通过一个case语句来检查分支功能选择器(BrFunc)的值,并根据不同的比较操作返回相应的布尔值。
function Bool aluBr(Word a, Word b, BrFunc brFunc);
Bool brTaken = case(brFunc)
Eq : (a == b);
Neq: (a != b);
Lt : signedLT(a, b);
Ltu: (a < b);
Ge : signedGE(a, b);
Geu: (a >= b);
endcase;
return brTaken;
endfunction
在上述代码中,brFunc
可以是以下几种值之一:
Eq
:检查a和b是否相等Neq
:检查a和b是否不相等Lt
:检查a是否小于b(有符号)Ltu
:检查a是否小于b(无符号)Ge
:检查a是否大于等于b(有符号)Geu
:检查a是否大于等于b(无符号)
aluBr
函数通过这些比较操作返回布尔值brTaken
,用于判断是否进行分支操作。
寄存器文件
寄存器文件概述
寄存器文件是一个具有三个端口的模块,其中包括两个读端口和一个写端口。这意味着我们可以在同一个周期内同时执行两次读取和一次写入操作。这使得我们能够在一个周期内完成需要读取两个源操作数并写入一个目标操作数的ALU操作。
寄存器文件的接口定义
以下是寄存器文件接口的定义:
typedef Bit#(32) Word;
typedef Bit#(5) RIndx;
interface RFile2R1W;
method Word rd1(RIndx index);
method Word rd2(RIndx index);
method Action wr(RIndx index, Word data);
endinterface
Word
:一个32位的值类型,用于表示寄存器或内存中的数据。RIndx
:一个5位的值类型,用于指定要访问的32个寄存器中的哪一个。
接口包含三个方法:
rd1
:从寄存器文件的端口1读取数据,根据传入的索引返回对应寄存器中的值。rd2
:从寄存器文件的端口2读取数据,功能与rd1
相同。wr
:写入数据到指定的寄存器,根据传入的索引和数据更新寄存器的值。
寄存器文件的实现
寄存器文件的实现如下:
module mkRFile2R1W(RFile2R1W);
Vector#(32, Reg#(Word)) rfile <- replicateM(mkReg(0));
method Word rd1(RIndx indx) = rfile[indx];
method Word rd2(RIndx indx) = rfile[indx];
method Action wr(RIndx indx, Word data);
if (indx != 0) begin
rfile[indx] <= data;
end
endmethod
endmodule
rfile
:定义了一个包含32个寄存器的向量,每个寄存器的大小为32位。replicateM(mkReg(0))
:创建32个初始值为0的寄存器。
三个方法的实现:
rd1
和rd2
方法:根据传入的索引从寄存器文件中读取数据并返回。wr
方法:根据传入的索引和数据更新寄存器的值,除了对索引为0的寄存器的写入操作无效(寄存器0硬连线为0,不能被写入)。
同时读写操作
寄存器文件支持同时进行读和写操作。在同一个周期内进行两次读取和一次写入时,读取操作总是先于写入操作进行。这意味着,如果我们对同一个寄存器进行读和写操作,首先会读取旧值,然后在周期结束时写入新值。
| {rd1, rd2} < wr |
这保证了在读写冲突的情况下,读取操作总是读取到写入前的旧值。
魔法内存模型
魔法内存模型概述
今天我们将讨论一个称为“魔法内存”的模块。之所以称其为“魔法”,是因为它的行为方式并非现实中内存的工作方式,但在今天的课程中,我们将其作为模型使用。在后续课程中,我们将讨论更现实的内存模型。
魔法内存模型假设内存的行为就像一个寄存器文件,即可以进行组合逻辑读取。如果进行写入操作,将在时钟上升沿执行。然而,实际的SRAM(静态随机存取存储器)和DRAM(动态随机存取存储器)并不如此快速,因此在单个时钟周期内访问内存是不现实的,这些复杂性将在后续课程中处理。
与寄存器文件不同,魔法内存只有一个端口,该端口用于读取或写入操作,因此无法同时进行加载和存储操作。
魔法内存接口
魔法内存接口基本上只有一个方法,即请求方法。根据操作的不同,我们将发送一个操作类型,指定是进行加载还是存储。如果是加载操作,我们会发送要读取的内存地址,读取结果将返回在load data
线上。如果是存储操作,我们会发送要写入的内存地址以及要写入的数据,并将使能信号设为真,以便更新主内存中的该位置。
以下是魔法内存接口的定义:
interface MagicMemory;
method ActionValue#(Word) req(MemReq r);
endinterface
typedef struct {MemOp op; Word addr; Word data;} MemReq deriving (Bits, Eq);
typedef enum {Ld, St} MemOp deriving (Bits, Eq);
魔法内存接口包含一个req
方法,接受一个MemReq
类型的参数。MemReq
结构体包含以下信息:
op
:操作类型,表示是加载还是存储。addr
:要访问的内存地址。data
:在存储操作中,要写入内存的数据。
魔法内存的使用示例
假设我们实例化了一个名为m
的内存模块,可以通过调用m.req
方法进行内存操作。
let data <- m.req(MemReq{op:Ld, addr:a, data:dwv});
let dummy <- m.req(MemReq{op:St, addr:a, data:v});
在加载操作中,设置操作类型为Ld
,地址为a
,数据设为默认值dwv
(即0)。加载操作将返回从内存读取的值,并将其赋给data
变量。
在存储操作中,设置操作类型为St
,地址为a
,数据为要写入的值v
。存储操作不会返回任何有意义的值,因此结果被赋给一个虚拟变量dummy
。
指令解码
指令解码概述
指令解码是识别和理解程序计数器指向的32位指令的过程。为了执行指令,我们需要从中提取各种字段,包括ALU操作、源寄存器、目标寄存器以及常量等。并非所有的32位值都是有效指令,因此在解码过程中还需要处理无效指令。
RISC-V指令集相对简单,使解码过程比其他复杂指令集(如x86)要容易得多。
例子:解码RISC-V指令
以下是一个RISC-V指令的例子:
00000000000100010000000110110011
参考手册规定了各个字段的位置和含义:
opcode
(操作码):0110011(代表操作码Op,R型编码)funct3
:000(代表ADD操作)rd
(目标寄存器):00011(x3)rs1
(源寄存器1):00010(x2)rs2
(源寄存器2):00001(x1)
指令解码过程
我们根据操作码和其他字段来确定指令的含义。具体步骤如下:
- 操作码解析:从32位指令中提取底部7位作为操作码,确定指令类型(例如R型编码)。
- 字段解析:根据指令类型,提取相应字段。对于R型指令,提取目标寄存器(rd)、源寄存器1(rs1)、源寄存器2(rs2)和功能码(funct3)。
- 功能解析:根据功能码确定具体的操作(如ADD)。
指令执行示例
对于上述解码的指令,我们可以推导出其执行过程:
rf.wr(3, alu(rf.rd1(2), rf.rd2(1), Add)); pc <= pc + 4;
- 读取源寄存器的值:调用寄存器文件的读取方法,读取寄存器x2和x1的值。
- 执行ALU操作:将读取到的两个源操作数传递给ALU,并执行ADD操作。
- 写入目标寄存器:将ALU的结果写入寄存器x3。
- 更新程序计数器:将程序计数器增加4,以指向下一条指令。
通过以上过程,我们成功地解码并执行了一条RISC-V指令。这个过程涉及从指令中提取字段、确定操作类型、执行相应的ALU操作,并更新寄存器和程序计数器。这个例子展示了RISC-V指令集的简单性和易解码性。
ALU 指令
基本ALU指令
在RISC-V架构中,ALU(算术逻辑单元)指令可以分为两类:没有立即数操作数的指令和有立即数操作数的指令。所有这些指令的编码方式基本相同,只是具体的ALU操作有所不同。
没有立即数操作数的ALU指令
以下是一些没有立即数操作数的基本ALU指令:
指令 | 描述 | 执行 |
---|---|---|
ADD rd, rs1, rs2 | 加法 | reg[rd] <= reg[rs1] + reg[rs2] |
SUB rd, rs1, rs2 | 减法 | reg[rd] <= reg[rs1] - reg[rs2] |
SLL rd, rs1, rs2 | 逻辑左移 | reg[rd] <= reg[rs1] « reg[rs2] |
SLT rd, rs1, rs2 | 有符号小于 | reg[rd] <= (reg[rs1] < reg[rs2]) ? 1 : 0 |
SLTU rd, rs1, rs2 | 无符号小于 | reg[rd] <= (reg[rs1] <u reg[rs2]) ? 1 : 0 |
XOR rd, rs1, rs2 | 异或 | reg[rd] <= reg[rs1] ^ reg[rs2] |
SRL rd, rs1, rs2 | 逻辑右移 | reg[rd] <= reg[rs1] »u reg[rs2] |
SRA rd, rs1, rs2 | 算术右移 | reg[rd] <= reg[rs1] » reg[rs2] |
OR rd, rs1, rs2 | 或 | reg[rd] <= reg[rs1] |
AND rd, rs1, rs2 | 与 | reg[rd] <= reg[rs1] & reg[rs2] |
这些指令归类为OP
类型指令,其中包含字段func
, rd
, rs1
, rs2
,func
字段指定了ALU的具体操作。
带有立即数操作数的ALU指令
以下是一些带有立即数操作数的ALU指令:
指令 | 描述 | 执行 |
---|---|---|
ADDI rd, rs1, immI | 加立即数 | reg[rd] <= reg[rs1] + immI |
SLTI rd, rs1, immI | 小于立即数(有符号) | reg[rd] <= (reg[rs1] < immI) ? 1 : 0 |
SLTIU rd, rs1, immI | 小于立即数(无符号) | reg[rd] <= (reg[rs1] <u immI) ? 1 : 0 |
XORI rd, rs1, immI | 异或立即数 | reg[rd] <= reg[rs1] ^ immI |
ORI rd, rs1, immI | 或立即数 | reg[rd] <= reg[rs1] |
ANDI rd, rs1, immI | 与立即数 | reg[rd] <= reg[rs1] & immI |
SLLI rd, rs1, immI | 逻辑左移立即数 | reg[rd] <= reg[rs1] « immI |
SRLI rd, rs1, immI | 逻辑右移立即数 | reg[rd] <= reg[rs1] »u immI |
SRAI rd, rs1, immI | 算术右移立即数 | reg[rd] <= reg[rs1] » immI |
这些指令归类为OPIMM
类型指令,包含字段func
, rd
, rs1
, immI
,其中immI
是直接在指令中编码的常量值。
指令解码的复杂性
尽管ALU指令的解码看起来相对简单,但某些情况下解码过程可能相当复杂。例如,分支指令的解码涉及从指令中提取多个字段并执行条件判断。
分支指令解码
分支指令的操作码和其他字段如下:
opcode: 1100011
funct3: 000 (表示BEQ指令)
rd: 无
rs1: 源寄存器1
rs2: 源寄存器2
imm: 立即数(分散在指令的不同位)
对于分支指令,我们需要:
- 从寄存器文件中读取源寄存器的值。
- 执行比较操作,例如检查两个寄存器值是否相等。
- 如果条件满足,更新程序计数器(PC)以跳转到新的地址;否则,继续执行下一条指令。
分支指令(Branch Instructions)
分支指令仅在执行的aluBr操作上有所不同
指令 | 描述 | 执行 | 英文原文 |
---|---|---|---|
BEQ rs1, rs2, immB | 分支 = | pc <= (reg[rs1] == reg[rs2]) ? pc + immB : pc + 4 | Branch = |
BNE rs1, rs2, immB | 分支 ≠ | pc <= (reg[rs1] != reg[rs2]) ? pc + immB : pc + 4 | Branch ≠ |
BLT rs1, rs2, immB | 分支 < (有符号) | pc <= (reg[rs1] <s reg[rs2]) ? pc + immB : pc + 4 | Branch < (Signed) |
BGE rs1, rs2, immB | 分支 ≥ (有符号) | pc <= (reg[rs1] ≥s reg[rs2]) ? pc + immB : pc + 4 | Branch ≥ (Signed) |
BLTU rs1, rs2, immB | 分支 < (无符号) | pc <= (reg[rs1] <u reg[rs2]) ? pc + immB : pc + 4 | Branch < (Unsigned) |
BGEU rs1, rs2, immB | 分支 ≥ (无符号) | pc <= (reg[rs1] ≥u reg[rs2]) ? pc + immB : pc + 4 | Branch ≥ (Unsigned) |
这些指令被归为一个称为BRANCH的类别,包含字段(brFunc, rs1, rs2, immB)。
其他指令
除了上述分类的指令外,还有一些不匹配其他模式的指令,如无条件跳转指令、加载上部立即数指令、加载和存储指令等。这些指令每个都有自己独特的操作码,并需要单独处理。
指令解码器的功能
指令解码器需要能够从指令中提取以下字段:
- 指令类型(如OP, OPIMM, BRANCH等)。
- ALU功能、分支功能。
- 源寄存器和目标寄存器。
- 立即数值(根据指令类型可能有所不同)。
解码器还需要处理无效的指令编码,将其标记为不支持的指令并适当处理。
总之,指令解码器的主要任务是正确识别和处理各种类型的指令,确保处理器能够按照预期执行程序。
编码示例
立即数编码
在RISC-V指令集中,有多种类型的立即数编码方式。每种编码方式定义了立即数在指令中的具体位置和位数。这些编码方式包括R型、I型、S型、B型、U型和J型。
示例指令编码
下图展示了一部分指令的编码方式。这些指令分组显示了不同类型的指令编码。相同类型的指令共享相同的操作码,并有类似的字段分布。
指令解码器的实现
指令解码器需要根据操作码和功能码解析出指令的具体类型和操作。不同类型的指令具有不同的字段分布,因此解码器需要能够识别这些模式,并正确提取字段以执行相应的操作。
通过参考手册中的指令编码表,可以清晰地了解每种指令的位分布,并在硬件设计中准确地实现这些指令的解码和执行。
解码指令类型
解码函数需要提取的字段
解码函数需要提取的字段包括:
- 指令类型(Instruction Type)
- ALU功能(ALU Function)
- 分支功能(Branch Function)
- 目标寄存器(Destination Register)
- 源寄存器(Source Registers)
- 立即数值(Immediate Value)
这些字段组成了一个结构体,称为解码指令类型(Decoded Instruction Type)。
解码指令类型的字段定义
解码指令类型的字段及其类型定义如下:
typedef struct {
IType iType;
AluFunc aluFunc;
BrFunc brFunc;
RDst dst;
RIndx src1;
RIndx src2;
Word imm;
} DecodedInst deriving (Bits, Eq);
typedef enum {OP, OPIMM, BRANCH, LUI, JAL, JALR, LOAD, STORE, Unsupported} IType deriving (Bits, Eq);
typedef enum {Add, Sub, And, Or, Xor, Slt, Sltu, Sll, Sra, Srl} AluFunc deriving (Bits, Eq);
typedef enum {Eq, Neq, Lt, Ltu, Ge, Geu} BrFunc deriving (Bits, Eq);
typedef struct {Bool valid; RIndx index;} RDst deriving (Bits);
字段详细说明
- 指令类型(IType):
- 表示指令的类别,如
OP
、OPIMM
、BRANCH
等。
- 表示指令的类别,如
- ALU功能(AluFunc):
- 表示ALU的具体操作类型,如
Add
、Sub
、And
等。
- 表示ALU的具体操作类型,如
- 分支功能(BrFunc):
- 表示分支指令的比较操作,如
Eq
(等于)、Neq
(不等于)等。
- 表示分支指令的比较操作,如
- 目标寄存器(RDst):
- 表示目标寄存器,包括两个信息:
index
:目标寄存器的索引。valid
:目标寄存器是否有效的标志位。如果无效,则不更新寄存器文件。
- 表示目标寄存器,包括两个信息:
- 源寄存器(RIndx):
- 表示源寄存器的索引。
- 立即数值(Word imm):
- 表示立即数值。
解码函数实现
解码函数的作用是从32位的指令中提取上述字段,并将其封装到DecodedInst
结构体中。下面是解码函数的示例实现:
function DecodedInst decode(Bit#(32) inst) {
DecodedInst di;
// 提取指令类型
di.iType = ...;
// 提取ALU功能
di.aluFunc = ...;
// 提取分支功能
di.brFunc = ...;
// 提取目标寄存器及其有效性
di.dst.valid = ...;
di.dst.index = ...;
// 提取源寄存器
di.src1 = ...;
di.src2 = ...;
// 提取立即数值
di.imm = ...;
return di;
}
在解码过程中,需要根据指令的操作码(opcode)和功能码(funct3、funct7)来确定指令类型和操作。具体的提取逻辑可以参考RISC-V的参考手册。
目标寄存器的有效性
目标寄存器的有效性(valid位)用于决定是否更新寄存器文件。如果valid
位为0,表示该指令不更新目标寄存器文件。例如,当目标寄存器的索引为0时,即使valid
位为1,也会被视为无效目标寄存器,不进行写操作。
总结
解码指令类型是RISC-V处理器中的重要一环。通过解码函数从指令中提取各个字段,并将其封装到结构体中,可以为指令的执行提供必要的信息。在实现中,需要仔细处理各种指令类型和字段,以确保处理器能够正确地执行每条指令。
指令处理器
将所有部分整合在一起
在接下来的讲座中,我们将整合所有之前讨论的部分,看看我们给你的其他组件是如何组合在一起以创建整个处理器的。我们将定义一个mkProcessor
模块,该模块的接口是空的,意味着这个模块没有关联的方法。
处理器模块的定义
这个模块代表了实际的处理器。首先,我们需要实例化处理器中的状态,包括程序计数器(PC)、寄存器文件(Register File)和内存。我们将内存分为两个独立的部分,一个用于指令,一个用于数据。
处理器模块的代码
module mkProcessor(Empty);
Reg#(Word) pc <- mkReg(0);
RFile2R1W rf <- mkRFile2R1W;
MagicMemory iMem <- mkMagicMemory;
MagicMemory dMem <- mkMagicMemory;
rule doProcessor;
let inst <- iMem.req(MemReq{op:Ld, addr:pc, data:dwv});
let dInst = decode(inst);
// dInst字段: iType, aluFunc, brFunc, dst, src1, src2, imm
let rVal1 = rf.rd1(dInst.src1);
let rVal2 = rf.rd2(dInst.src2);
let eInst = execute(dInst, rVal1, rVal2, pc);
// eInst字段: iType, dst, data, addr, nextPC
// 更新pc, rf, dMem使用eInst (具体代码见第23页)
endrule
endmodule
指令处理过程
- 获取指令:
- 从指令内存(iMem)中获取当前PC地址处的指令。
- 执行内存请求,类型为加载(Ld),地址为PC的值,数据字段为虚拟值(dwv)。
- 解码指令:
- 调用解码函数(decode)解析指令(inst)。
- 解码后的指令包含字段:指令类型(iType)、ALU功能(aluFunc)、分支功能(brFunc)、目标寄存器(dst)、源寄存器1(src1)、源寄存器2(src2)和立即数(imm)。
- 读取源寄存器:
- 使用解码后的源寄存器索引,从寄存器文件(rf)中读取源寄存器1(src1)和源寄存器2(src2)的值。
- 执行指令:
- 调用执行函数(execute),传入解码后的指令(dInst)、源寄存器值(rVal1、rVal2)和PC。
- 执行函数返回包含更新后的状态信息的数据结构(eInst),包括:指令类型(iType)、目标寄存器(dst)、数据(data)、地址(addr)和下一个PC值(nextPC)。
- 更新状态:
- 根据执行函数返回的结果更新PC、寄存器文件和数据内存。
以上代码展示了一个简化的RISC-V处理器的实现过程。这个处理器模块中,包含了获取指令、解码指令、读取寄存器、执行指令和更新状态的过程。具体的解码和执行函数将在实验6中详细实现。通过这个模块,可以理解处理器的基本工作流程和各个组件之间的交互。
执行函数
执行函数的作用
执行函数负责根据解码后的指令(DecodedInst)和从寄存器文件中读取的两个源操作数(rVal1和rVal2),以及当前的程序计数器(PC),生成不同的结果。这些结果包括数据、下一个程序计数器(nextPC)和地址。执行函数根据指令类型设置这些字段的适当值。
执行函数的实现
function ExecInst execute(DecodedInst dInst, Word rVal1, Word rVal2, Word pc);
// 从解码后的指令中提取字段:iType, aluFunc, brFunc, imm
// 初始化执行指令(eInst)的字段:data, nextPC, addr为默认值
ExecInst eInst;
eInst.data = dwv;
eInst.nextPC = dwv;
eInst.addr = dwv;
case (dInst.iType) matches
// OP 类型指令
OP: begin
eInst.data = alu(rVal1, rVal2, dInst.aluFunc);
eInst.nextPC = pc + 4;
end
// OPIMM 类型指令
OPIMM: begin
eInst.data = alu(rVal1, dInst.imm, dInst.aluFunc);
eInst.nextPC = pc + 4;
end
// BRANCH 类型指令
BRANCH: begin
eInst.nextPC = aluBr(rVal1, rVal2, dInst.brFunc) ? pc + dInst.imm : pc + 4;
end
// LUI 类型指令
LUI: begin
eInst.data = dInst.imm;
eInst.nextPC = pc + 4;
end
// JAL 类型指令
JAL: begin
eInst.data = pc + 4;
eInst.nextPC = pc + dInst.imm;
end
// JALR 类型指令
JALR: begin
eInst.data = pc + 4;
eInst.nextPC = (rVal1 + dInst.imm) & ~1;
end
// LOAD 类型指令
LOAD: begin
eInst.addr = rVal1 + dInst.imm;
eInst.nextPC = pc + 4;
end
// STORE 类型指令
STORE: begin
eInst.data = rVal2;
eInst.addr = rVal1 + dInst.imm;
eInst.nextPC = pc + 4;
end
endcase
return eInst;
endfunction
各种指令类型的执行逻辑
- OP 类型指令:
- 执行ALU操作,使用两个源操作数和ALU功能码(aluFunc)。
- 结果存储在
eInst.data
中。 - 更新
nextPC
为pc + 4
。
- OPIMM 类型指令:
- 执行ALU操作,使用源操作数和立即数(imm)。
- 结果存储在
eInst.data
中。 - 更新
nextPC
为pc + 4
。
- BRANCH 类型指令:
- 执行分支操作,使用两个源操作数和分支功能码(brFunc)。
- 如果分支条件成立,更新
nextPC
为pc + imm
,否则更新为pc + 4
。
- LUI 类型指令:
- 将立即数(imm)存储在
eInst.data
中。 - 更新
nextPC
为pc + 4
。
- 将立即数(imm)存储在
- JAL 类型指令:
- 将
pc + 4
存储在eInst.data
中。 - 更新
nextPC
为pc + imm
。
- 将
- JALR 类型指令:
- 将
pc + 4
存储在eInst.data
中。 - 更新
nextPC
为(rVal1 + imm) & ~1
,确保地址对齐。
- 将
- LOAD 类型指令:
- 计算内存地址
addr
为rVal1 + imm
。 - 更新
nextPC
为pc + 4
。
- 计算内存地址
- STORE 类型指令:
- 将源操作数2(rVal2)存储在
eInst.data
中。 - 计算内存地址
addr
为rVal1 + imm
。 - 更新
nextPC
为pc + 4
。
- 将源操作数2(rVal2)存储在
执行函数根据不同的指令类型执行相应的操作,并生成包含数据、下一个程序计数器和地址的执行指令结构体。通过这种方式,处理器能够根据解码后的指令和源操作数执行正确的操作,并更新其状态。
更新状态
更新状态的步骤
在执行指令并生成结果后,最后一步是更新处理器的状态。需要完成以下操作:
- 提取执行指令(ExecInst)的字段:数据(data)、地址(addr)、目标寄存器(dst)。
- 根据指令类型执行内存访问。
- 写入寄存器文件(如果目标寄存器有效)。
- 更新程序计数器(PC)。
更新状态的实现
简化代码
// 提取 eInst 的字段: data, addr, dst
let data = eInst.data;
// 内存访问
if (eInst.iType == LOAD) begin
data <- dMem.req(MemReq{op:Ld, addr:addr, data:dwv});
end else if (eInst.iType == STORE) begin
let dummy <- dMem.req(MemReq{op:St, addr:addr, data:data});
end
// 写入寄存器文件
if (dst.valid) rf.wr(dst.index, data);
// 更新PC
pc <= eInst.nextPC;
完整的更新状态函数
将更新状态的操作打包到一个返回动作的函数中:
function Action updateState(ExecInst eInst, Reg#(Word) pc, RFile2R1W rf, MagicMemory dMem);
return (action
// 提取 eInst 的字段: data, addr, dst
let data = eInst.data;
// 内存访问
if (eInst.iType == LOAD) begin
data <- dMem.req(MemReq{op:Ld, addr:addr, data:dwv});
end else if (eInst.iType == STORE) begin
let dummy <- dMem.req(MemReq{op:St, addr:addr, data:data});
end
// 写入寄存器文件
if (dst.valid) rf.wr(dst.index, data);
// 更新PC
pc <= eInst.nextPC;
endaction);
endfunction
通过将更新状态的操作打包成一个函数,可以更清晰地管理和执行这些操作。这个函数会根据执行指令的类型进行相应的内存访问、寄存器文件写入和PC更新,从而完成处理器状态的更新。
单周期RISC-V处理器
下图展示了我们构建的完整单周期RISC-V处理器的结构:
处理器的主要组件
- 程序计数器(PC):跟踪当前执行的指令地址。
- 指令内存(Inst Memory):存储指令。
- 解码单元(Decode):将从指令内存中读取的指令解码为可执行的操作。
- 寄存器文件(Register File):存储寄存器值,提供读取和写入端口。
- 执行单元(Execute):执行ALU和分支操作,生成结果和下一个PC值。
- 数据内存(Data Memory):存储和访问数据。
处理流程
- 获取指令:从指令内存中获取当前PC地址处的指令。
- 解码指令:将指令解码为各个字段,包括指令类型、源寄存器、目标寄存器、立即数等。
- 读取寄存器:从寄存器文件中读取源寄存器的值。
- 执行指令:根据指令类型执行相应的操作,生成结果数据、地址和下一个PC值。
- 更新状态:根据执行结果更新寄存器文件和PC,并进行内存访问(加载或存储数据)。
通过以上步骤,我们构建了一个完整的单周期RISC-V处理器,实现了指令的获取、解码、执行和状态更新。
下面是20版内容
冯·诺依曼计算机的结构
- 运算路径(Datapath):负责数据处理的路径,包括寄存器和算术逻辑单元(ALU)。
- 内部存储:用于存储临时数据和中间结果。
- 数据地址和数据:与主存储器交换数据。
- 控制单元(Control Unit):负责解释指令并生成控制信号以协调运算路径的操作。
- 状态:跟踪操作的进展和条件。
- 指令地址:从主存储器中获取指令。
- 主存储器(Main Memory):存储程序和数据。
- 存储地址和数据。
- 程序计数器(PC):存储将要执行的指令的地址。
详细解释
- 寄存器(registers):用于快速存储和访问数据的高速存储单元。
- 算术逻辑单元(ALU):执行基本的算术和逻辑运算。
- 指令译码(Instruction decoding):将指令转化为控制信号。
- 控制信号:用于控制数据流和操作执行。
指令(Instructions)
- 指令是基本的工作单元。
- 每条指令指定:
- 一个操作(操作码,opcode):要执行的操作。
- 源操作数和目标操作数:用于操作的数据和存储结果的位置。
详细解释
- 二进制数据编码的指令:指令以二进制格式存储在内存中。
- 程序计数器(PC):存储当前要执行的指令的地址。
- 逻辑循环:CPU按照指令循环执行指令:
- 取指令(Fetch instruction):从内存中取出当前指令。
- 译指令(Decode instruction):将指令翻译成控制信号。
- 读操作数(Read src operands):读取指令中指定的源操作数。
- 执行(Execute):执行指令指定的操作。
- 写结果(Write dst operand):将结果写入目标位置。
- 计算下一个PC(Compute next PC):计算下一条指令的地址。
具体示例
- 二进制指令示例:
1101000111011
表示 R1 <- R2 + R3,即将R2和R3的值相加并存储到R1中。
通过对这些内容的理解,可以更好地掌握冯·诺依曼计算机的基本结构和指令执行的过程。
方法:增量特性实现(Incremental Featurism)
我们将为每类指令分别实现数据通路(datapath),然后将它们合并(使用多路复用器等)。
步骤
- ALU指令:实现算术逻辑单元(ALU)指令的数据通路。
- 加载和存储指令:实现加载和存储指令的数据通路。
- 分支和跳转指令:实现分支和跳转指令的数据通路。
组件库
- 寄存器(Registers):用于存储和快速访问数据。
- 多路复用器(Muxes):选择信号路径的开关。
- “黑盒”ALU:执行算术和逻辑运算的单元。
- 内存(Memories):
- 指令内存(Instruction Memory):存储指令。
- 数据内存(Data Memory):存储数据。
多端口寄存器文件(Multi-Ported Register File)
详细解释
- 写端口(Write Port):用于写入数据的端口。
- 时钟信号(clk):控制写入操作的时钟信号。
- 读端口1(Read Port 1)和读端口2(Read Port 2):用于读取数据的端口。
- 寄存器文件(Register File):包含多个寄存器,支持多端口操作。
- RA1 和 RA2:独立的读地址。
- WA:写地址。
- WE:写使能信号。
- WD:写数据。
- CLK:时钟信号。
- RD1 和 RD2:独立的读数据。
示例说明
- 组合逻辑的读端口:两个组合逻辑的读端口和一个时钟控制的写端口。
- 内部逻辑:确保寄存器0(Reg[0])始终读为0。
组件细节
- 负载使能寄存器(Load-enabled register):
- EN:使能信号。
- clk:时钟信号。
- D:数据输入。
- Q:数据输出。
通过逐步实现每类指令的数据通路,并结合多个组件库,我们可以构建一个功能齐全的计算机架构。这种增量特性实现的方法允许我们逐步测试和验证每个组件的功能,从而构建一个可靠的系统。
寄存器文件时序(Register File Timing)
2个组合逻辑读端口,1个时钟写端口
- 读地址(Read address, RA):用于指定读取数据的寄存器地址。
- 读数据(Read data, RD):从指定地址读取的数据。
- 时钟(CLK):控制写入操作的时钟信号。
- 写使能(Write enable, WE):控制写入操作的使能信号。
- 写地址(Write address, WA):用于指定写入数据的寄存器地址。
- 写数据(Write data, WD):要写入的数据。
详细解释
- 读时序:当RA设定后,RD将在传播延迟(t_PD)后返回对应的数据。
- 写时序:当时钟上升沿到来时,若写使能信号为高电平(有效),则在写地址(WA)指定的寄存器中写入写数据(WD)。
- 读-写冲突:如果WA与RA1相同,则在下一个时钟沿到来之前,RD1将读出旧的Reg[RA1]的值。
内存时序(Memory Timing)
详细解释
- 组合逻辑加载(Loads are combinational):数据在同一个时钟周期内返回,与加载请求同时进行。
- 时钟控制存储(Stores are clocked):存储操作由时钟控制。
- 魔术内存(magic memory):在实验6中,我们将假设内存模块行为类似于寄存器文件的时序,尽管这并不真实。
下周的学习内容
- 不同内存模型及其权衡:下周我们将学习各种不同的内存模型及其权衡点。
- 现实内存的使用:在设计项目中,我们将为处理器使用现实的内存模型。
通过理解这些内容,可以掌握寄存器文件和内存的时序特性,以及如何处理读写冲突和时序逻辑。
ALU指令(ALU Instructions)
-
指令格式:
0000000 00010 00001 000 00011 0110011
- funct7:0000000
- rs2:00001
- rs1:00010
- funct3:000
- rd:00011
- opcode:0110011
这32位指令表示的RISC-V指令是什么?
- 参考手册规定这些字段如下:
- opcode = 0110011:操作码op,R型编码
- funct3 = 000:对应ADD指令
- funct7 = 0000000
- rd = 00011:寄存器x3
- rs1 = 00010:寄存器x2
- rs2 = 00001:寄存器x1
详细解释
- 这条指令对应的RISC-V汇编指令为:
ADD x3, x2, x1
,即将寄存器x2和x1的值相加,并将结果存储在寄存器x3中。
取指和译码(Instruction Fetch/Decode)
- 使用计数器(PROGRAM COUNTER, PC)取下一条指令:
- 使用PC作为内存地址。
- 将PC加4,在周期结束时加载新值。
- 从内存中取指令。
- 译码指令:
- 直接使用一些指令字段(寄存器编号,立即数值)。
- 使用操作码(opcode),功能码3(funct3)和功能码7(funct7)位生成控制信号。
详细解释
- 取指:PC保存当前指令的地址,PC每次加4来获取下一条指令的地址(因为每条指令占用4个字节)。
- 译码:将取出的指令进行解释,根据操作码和功能码生成相应的控制信号,指示ALU和其他部件如何操作。
- 指令字段:
- opcode:指示指令类型和操作。
- funct3:细化操作类型。
- funct7:进一步细化操作类型。
- rd:目标寄存器。
- rs1和rs2:源寄存器。
通过这些内容的理解,可以掌握RISC-V指令的基本格式和取指、译码的过程,进一步理解计算机指令执行的机制。
ALU指令(ALU Instructions)
不同之处仅在于执行的ALU操作
指令 | 描述 | 执行 | 英文原文 |
---|---|---|---|
ADD rd, rs1, rs2 | 加法 | reg[rd] <= reg[rs1] + reg[rs2] | Add |
SUB rd, rs1, rs2 | 减法 | reg[rd] <= reg[rs1] - reg[rs2] | Subtract |
SLL rd, rs1, rs2 | 逻辑左移 | reg[rd] <= reg[rs1] « reg[rs2] | Shift Left Logical |
SLT rd, rs1, rs2 | 小于则置位 (有符号) | reg[rd] <= (reg[rs1] < reg[rs2]) ? 1 : 0 | Set Less Than (Signed) |
SLTU rd, rs1, rs2 | 小于则置位 (无符号) | reg[rd] <= (reg[rs1] < reg[rs2]) ? 1 : 0 | Set Less Than (Unsigned) |
XOR rd, rs1, rs2 | 异或 | reg[rd] <= reg[rs1] ^ reg[rs2] | Exclusive OR |
SRL rd, rs1, rs2 | 逻辑右移 | reg[rd] <= reg[rs1] »u reg[rs2] | Shift Right Logical |
SRA rd, rs1, rs2 | 算术右移 | reg[rd] <= reg[rs1] »s reg[rs2] | Shift Right Arithmetic |
OR rd, rs1, rs2 | 或 | reg[rd] <= reg[rs1] | reg[rs2] | OR |
AND rd, rs1, rs2 | 与 | reg[rd] <= reg[rs1] & reg[rs2] | AND |
这些指令被归为一个称为OP的类别,包含字段(AluFunc, rd, rs1, rs2)。
详细解释
- ADD:将rs1和rs2的值相加,结果存储在rd中。
- SUB:将rs1和rs2的值相减,结果存储在rd中。
- SLL:将rs1的值左移rs2位,结果存储在rd中。
- SLT:如果rs1的值小于rs2,则rd为1,否则为0(有符号比较)。
- SLTU:如果rs1的值小于rs2,则rd为1,否则为0(无符号比较)。
- XOR:将rs1和rs2的值进行异或操作,结果存储在rd中。
- SRL:将rs1的值右移rs2位,结果存储在rd中(逻辑右移)。
- SRA:将rs1的值右移rs2位,结果存储在rd中(算术右移)。
- OR:将rs1和rs2的值进行或操作,结果存储在rd中。
- AND:将rs1和rs2的值进行与操作,结果存储在rd中。
寄存器-寄存器ALU数据通路(Register-Register ALU Datapath)
-
指令格式:
funct7 rs2 rs1 funct3 rd opcode
- funct7:功能码7,用于指定具体操作。
- rs2:源寄存器2。
- rs1:源寄存器1。
- funct3:功能码3,用于进一步指定操作类型。
- rd:目标寄存器。
- opcode:操作码,指定指令类型。
操作码为0110011表示操作类型(Op type)
- 操作码(opcode)0110011:表示这是一个R型(寄存器到寄存器)的ALU操作指令。这类指令包括加法、减法、逻辑运算和移位操作等。
- 功能码(funct3 和 funct7)生成控制信号:
- funct3, Inst[30] => AluFunc:使用功能码3和指令的第30位生成AluFunc,用于区分具体的ALU操作。
- 例如,funct3为000时,第30位为0表示加法操作(ADD),第30位为1表示减法操作(SUB)。
- 例如,funct3为101时,第30位为0表示逻辑右移(SRL),第30位为1表示算术右移(SRA)。
- Inst[30]:用于区分加法/减法,或逻辑移位/算术移位。通过指令的第30位,可以确定具体的ALU操作类型。
- funct3, Inst[30] => AluFunc:使用功能码3和指令的第30位生成AluFunc,用于区分具体的ALU操作。
数据通路示例
寄存器-寄存器 ALU 数据路径
这张图展示了RISC-V处理器中寄存器-寄存器ALU指令的数据路径。下面是对图中每个部分的详细解释。
程序计数器(PC)
程序计数器(PC)存储当前要执行的指令地址。每次执行指令后,PC会自动加4,以指向下一条指令。
指令内存(Instruction Memory)
指令内存(Instruction Memory)存储了程序的所有指令。PC提供指令地址,指令内存根据该地址输出相应的指令。指令通过32位的总线Inst[31:0]
传送到解码器。
解码器(Decoder)
解码器负责解析从指令内存中获取的指令。指令的不同字段包括:
Opcode
(操作码):指示指令的类型。Funct3
和Funct7
:进一步指定操作类型。rs1
和rs2
:源寄存器索引。rd
:目标寄存器索引。
解码器根据操作码和功能码生成控制信号,例如AluFunc
(指定ALU操作)和WERF
(寄存器文件写使能信号)。
寄存器文件(Register File)
寄存器文件包含32个寄存器,每个寄存器为32位宽。寄存器文件具有两个读端口和一个写端口。
RA1
和RA2
:源寄存器1和2的地址(由指令的rs1
和rs2
字段指定)。RD1
和RD2
:从源寄存器1和2读取的数据。WA
:目标寄存器的地址(由指令的rd
字段指定)。WD
:要写入目标寄存器的数据。WE
:写使能信号(当该信号为高时,允许写入寄存器文件)。
算术逻辑单元(ALU)
ALU执行指定的算术或逻辑操作。操作类型由AluFunc
信号控制,源操作数由寄存器文件提供。
A
和B
:ALU的输入操作数,分别来自寄存器文件的RD1
和RD2
。ALU
:执行操作并生成结果。32
:表示数据路径的位宽。
数据路径流程
- 指令获取:
- PC提供当前指令地址。
- 指令内存根据PC提供的地址输出指令。
- 指令解码:
- 解码器解析指令字段,包括操作码、功能码、源寄存器和目标寄存器地址。
- 解码器生成控制信号,例如
AluFunc
和WERF
。
- 寄存器读取:
- 寄存器文件根据
rs1
和rs2
字段提供源寄存器地址。 - 通过
RD1
和RD2
总线从寄存器文件中读取数据。
- 寄存器文件根据
- 执行操作:
- ALU根据
AluFunc
信号执行指定的操作,输入操作数来自RD1
和RD2
。 - ALU输出结果。
- ALU根据
- 结果写回:
- 如果
WERF
信号有效,则ALU的结果通过WD
写回到寄存器文件中指定的目标寄存器(rd
)。
- 如果
这个数据路径展示了RISC-V处理器中寄存器-寄存器ALU指令的执行过程。通过程序计数器、指令内存、解码器、寄存器文件和ALU的协同工作,处理器能够有效地获取、解码、执行指令,并更新状态。这是RISC-V处理器的核心工作机制,为更复杂的指令集处理提供了基础。
ALU指令(ALU Instructions)
含一个立即数操作数的ALU指令
指令 | 描述 | 执行 | 英文原文 |
---|---|---|---|
ADDI rd, rs1, immI | 加法(立即数) | reg[rd] <= reg[rs1] + immI | Add Immediate |
SLTI rd, rs1, immI | 小于则置位(立即数,有符号) | reg[rd] <= (reg[rs1] <s immI) ? 1 : 0 | Set Less Than Immediate (Signed) |
SLTIU rd, rs1, immI | 小于则置位(立即数,无符号) | reg[rd] <= (reg[rs1] <u immI) ? 1 : 0 | Set Less Than Immediate (Unsigned) |
XORI rd, rs1, immI | 异或(立即数) | reg[rd] <= reg[rs1] ^ immI | Xor Immediate |
ORI rd, rs1, immI | 或(立即数) | reg[rd] <= reg[rs1] | immI | Or Immediate |
ANDI rd, rs1, immI | 与(立即数) | reg[rd] <= reg[rs1] & immI | And Immediate |
SLLI rd, rs1, immI | 逻辑左移(立即数) | reg[rd] <= reg[rs1] « immI | Shift Left Logical Immediate |
SRLI rd, rs1, immI | 逻辑右移(立即数) | reg[rd] <= reg[rs1] »u immI | Shift Right Logical Immediate |
SRAI rd, rs1, immI | 算术右移(立即数) | reg[rd] <= reg[rs1] »s immI | Shift Right Arithmetic Immediate |
这些指令被归为一个称为OPIMM的类别,包含字段(AluFunc, rd, rs1, immI)。
详细解释
- ADDI:将rs1和立即数immI相加,结果存储在rd中。
- SLTI:如果rs1的值小于立即数immI,则rd为1,否则为0(有符号比较)。
- SLTIU:如果rs1的值小于立即数immI,则rd为1,否则为0(无符号比较)。
- XORI:将rs1和立即数immI的值进行异或操作,结果存储在rd中。
- ORI:将rs1和立即数immI的值进行或操作,结果存储在rd中。
- ANDI:将rs1和立即数immI的值进行与操作,结果存储在rd中。
- SLLI:将rs1的值左移immI位,结果存储在rd中。
- SRLI:将rs1的值右移immI位,结果存储在rd中(逻辑右移)。
- SRAI:将rs1的值右移immI位,结果存储在rd中(算术右移)。
通过这些内容的理解,可以掌握含立即数操作数的ALU指令的基本格式和执行过程,进一步理解计算机指令执行的具体操作。
寄存器-立即数ALU数据通路(Register-Immediate ALU Datapath)
-
指令格式:
imm[11:0] rs1 funct3 rd opcode
- imm[11:0]:立即数,用于与寄存器值进行操作。
- rs1:源寄存器1。
- funct3:功能码3,用于进一步指定操作类型。
- rd:目标寄存器。
- opcode:操作码,指定指令类型。
操作码为0010011表示操作类型(OpImm type)
- 操作码(opcode)0010011:表示这是一个I型(立即数到寄存器)的ALU操作指令。这类指令包括加法、比较、逻辑运算和移位操作等。
- 功能码(funct3)生成控制信号:
- funct3, Inst[30] => AluFunc:使用功能码3和指令的第30位生成AluFunc,用于区分具体的ALU操作。
- 例如,funct3为000时,第30位为0表示加法操作(ADDI),第30位为1表示减法操作(没有立即数减法指令)。
- 例如,funct3为101时,第30位为0表示逻辑右移(SRLI),第30位为1表示算术右移(SRAI)。
- funct3, Inst[30] => AluFunc:使用功能码3和指令的第30位生成AluFunc,用于区分具体的ALU操作。
数据通路示例
寄存器-立即数 ALU 数据路径
这张图展示了RISC-V处理器中寄存器-立即数(Register-Immediate)ALU指令的数据路径。下面是对图中每个部分的详细解释。
程序计数器(PC)
程序计数器(PC)存储当前要执行的指令地址。每次执行指令后,PC会自动加4,以指向下一条指令。
指令内存(Instruction Memory)
指令内存(Instruction Memory)存储了程序的所有指令。PC提供指令地址,指令内存根据该地址输出相应的指令。指令通过32位的总线Inst[31:0]
传送到解码器。
解码器(Decoder)
解码器负责解析从指令内存中获取的指令。指令的不同字段包括:
Opcode
(操作码):指示指令的类型。Funct3
和Funct7
:进一步指定操作类型。rs1
:源寄存器索引。rd
:目标寄存器索引。imm[11:0]
:立即数。
解码器根据操作码和功能码生成控制信号,例如AluFunc
(指定ALU操作)和WERF
(寄存器文件写使能信号)。
寄存器文件(Register File)
寄存器文件包含32个寄存器,每个寄存器为32位宽。寄存器文件具有两个读端口和一个写端口。
RA1
:源寄存器1的地址(由指令的rs1
字段指定)。RD1
:从源寄存器1读取的数据。WA
:目标寄存器的地址(由指令的rd
字段指定)。WD
:要写入目标寄存器的数据。WE
:写使能信号(当该信号为高时,允许写入寄存器文件)。
ALU(算术逻辑单元)
ALU执行指定的算术或逻辑操作。操作类型由AluFunc
信号控制,源操作数由寄存器文件和立即数提供。
A
和B
:ALU的输入操作数,A
来自寄存器文件的RD1
,B
来自多路选择器(BSEL)。ALU
:执行操作并生成结果。32
:表示数据路径的位宽。
数据路径流程
- 指令获取:
- PC提供当前指令地址。
- 指令内存根据PC提供的地址输出指令。
- 指令解码:
- 解码器解析指令字段,包括操作码、功能码、源寄存器、目标寄存器地址和立即数。
- 解码器生成控制信号,例如
AluFunc
、BSEL
和WERF
。
- 寄存器读取:
- 寄存器文件根据
rs1
字段提供源寄存器地址。 - 通过
RD1
总线从寄存器文件中读取数据。
- 寄存器文件根据
- 多路选择器(BSEL):
- BSEL根据控制信号选择第二个操作数的来源:要么是寄存器文件中的数据(
RD2
),要么是立即数(imm[11:0]
经过符号扩展)。
- BSEL根据控制信号选择第二个操作数的来源:要么是寄存器文件中的数据(
- 执行操作:
- ALU根据
AluFunc
信号执行指定的操作,输入操作数来自RD1
和BSEL输出的数据。 - ALU输出结果。
- ALU根据
- 结果写回:
- 如果
WERF
信号有效,则ALU的结果通过WD
写回到寄存器文件中指定的目标寄存器(rd
)。
- 如果
总结
这个数据路径展示了RISC-V处理器中寄存器-立即数ALU指令的执行过程。通过程序计数器、指令内存、解码器、寄存器文件、ALU和多路选择器的协同工作,处理器能够有效地获取、解码、执行寄存器-立即数指令,并更新状态。这是RISC-V处理器的一部分,提供了对立即数操作的支持。
加载和存储指令(Load and Store Instructions)
指令 | 描述 | 执行 | 英文原文 |
---|---|---|---|
LW rd, immI(rs1) | 加载字 | reg[rd] <= mem[reg[rs1] + immI] | Load Word |
SW rs2, immS(rs1) | 存储字 | mem[reg[rs1] + immS] <= reg[rs2] | Store Word |
LW和SW需要访问内存以执行操作,因此需要计算有效的内存地址。
加载指令(Load Instruction)
这张图展示了RISC-V处理器中加载指令的数据路径。以下是对图中每个部分的详细解释。
指令格式
imm[11:0] rs1 010 rd 0000011
imm[11:0]
:立即数,用于地址计算。rs1
:源寄存器1的地址。rd
:目标寄存器的地址。funct3
:功能码3,用于进一步指定操作类型。func3 = 010
表示读取或存储字(word),即32位的数据。opcode
:操作码,0000011表示加载指令(例如lw)。
数据路径解释
Load: Reg[rd] <= Mem[Reg[rs1] + SXT(imm[11:0])]
- 操作码 0000011 对应于
lw
(Load Word)
程序计数器(PC)
程序计数器(PC)存储当前要执行的指令地址。每次执行指令后,PC会自动加4,以指向下一条指令。
指令内存(Instruction Memory)
指令内存(Instruction Memory)存储了程序的所有指令。PC提供指令地址,指令内存根据该地址输出相应的指令。指令通过32位的总线Inst[31:0]
传送到解码器。
解码器(Decoder)
解码器负责解析从指令内存中获取的指令。指令的不同字段包括:
Opcode
(操作码):指示指令的类型,这里0000011表示加载指令。Funct3
:进一步指定操作类型,这里010表示lw(加载字)。rs1
:源寄存器1的地址。rd
:目标寄存器的地址。imm[11:0]
:立即数。
解码器根据操作码和功能码生成控制信号,例如BSEL
(选择立即数作为ALU输入)、WDSEL
(选择写回数据)、AluFunc
(指定ALU操作,这里是Addi)、WERF
(寄存器文件写使能信号)和MWR
(内存写使能信号)。
寄存器文件(Register File)
寄存器文件包含32个寄存器,每个寄存器为32位宽。寄存器文件具有两个读端口和一个写端口。
RA1
:源寄存器1的地址(由指令的rs1
字段指定)。RD1
:从源寄存器1读取的数据。WA
:目标寄存器的地址(由指令的rd
字段指定)。WD
:要写入目标寄存器的数据。WE
:写使能信号(当该信号为高时,允许写入寄存器文件)。
ALU(算术逻辑单元)
ALU执行指定的算术或逻辑操作。操作类型由AluFunc
信号控制,源操作数由寄存器文件和立即数提供。
A
:ALU的输入操作数,来自寄存器文件的RD1
。B
:ALU的输入操作数,来自多路选择器(BSEL)。ALU
:执行操作并生成结果,这里是地址计算Reg[rs1] + SXT(imm[11:0])
。32
:表示数据路径的位宽。
数据内存(Data Memory)
数据内存用于存储和加载数据。地址由ALU计算得出。
Adr
:要访问的内存地址。WD
:要写入的数据。RD
:从内存读取的数据。MWR
:内存写使能信号(在加载指令中未用)。
数据路径流程
- 指令获取:
- PC提供当前指令地址。
- 指令内存根据PC提供的地址输出指令。
- 指令解码:
- 解码器解析指令字段,包括操作码、功能码、源寄存器、目标寄存器地址和立即数。
- 解码器生成控制信号,例如
AluFunc
、BSEL
、WDSEL
、WERF
和MWR
。
- 寄存器读取:
- 寄存器文件根据
rs1
字段提供源寄存器地址。 - 通过
RD1
总线从寄存器文件中读取数据。
- 寄存器文件根据
- 多路选择器(BSEL):
- BSEL选择立即数作为ALU的第二个操作数。
- 地址计算:
- ALU根据
AluFunc
信号(Addi)执行地址计算,输入操作数来自RD1
和立即数。 - ALU输出内存地址。
- ALU根据
- 内存访问:
- 数据内存根据计算出的地址进行读取操作。
- 读取的数据通过
RD
总线传送回寄存器文件。
- 结果写回:
WDSEL
选择内存读取的数据作为写回数据。- 如果
WERF
信号有效,则读取的数据通过WD
写回到寄存器文件中指定的目标寄存器(rd
)。
存储指令(Store Instruction)
这张图展示了RISC-V处理器中存储指令的数据路径。以下是对图中每个部分的详细解释。
指令格式
存储指令的格式如下:
imm[11:5] rs2 rs1 funct3 imm[4:0] opcode
imm[11:5]
和imm[4:0]
:立即数,用于地址计算。rs2
:源寄存器2的地址,包含要存储的数据。rs1
:源寄存器1的地址,包含基地址。funct3
:功能码3,用于进一步指定操作类型。func3 = 010
表示读取或存储字(word),即32位的数据。opcode
:操作码,0100011表示存储指令(例如sw)。
数据路径解释
Store: Mem[Reg[rs1] + SXT(imm[11:0])] <= Reg[rs2]
- 操作码 0100011 对应于
sw
(Store Word)
程序计数器(PC)
程序计数器(PC)存储当前要执行的指令地址。每次执行指令后,PC会自动加4,以指向下一条指令。
指令内存(Instruction Memory)
指令内存(Instruction Memory)存储了程序的所有指令。PC提供指令地址,指令内存根据该地址输出相应的指令。指令通过32位的总线Inst[31:0]
传送到解码器。
解码器(Decoder)
解码器负责解析从指令内存中获取的指令。指令的不同字段包括:
Opcode
(操作码):指示指令的类型,这里0100011表示存储指令。Funct3
:进一步指定操作类型,这里010表示sw(存储字)。rs1
:源寄存器1的地址。rs2
:源寄存器2的地址。imm[11:5]
和imm[4:0]
:立即数。
解码器根据操作码和功能码生成控制信号,例如BSEL
(选择立即数作为ALU输入)、WDSEL
(选择写回数据)、AluFunc
(指定ALU操作,这里是Addi)、WERF
(寄存器文件写使能信号)和MWR
(内存写使能信号)。
寄存器文件(Register File)
寄存器文件包含32个寄存器,每个寄存器为32位宽。寄存器文件具有两个读端口和一个写端口。
RA1
:源寄存器1的地址(由指令的rs1
字段指定)。RA2
:源寄存器2的地址(由指令的rs2
字段指定)。RD1
:从源寄存器1读取的数据。RD2
:从源寄存器2读取的数据。WA
:目标寄存器的地址(这里未用)。WD
:要写入目标寄存器的数据(这里未用)。WE
:写使能信号(这里未用)。
ALU(算术逻辑单元)
ALU执行指定的算术或逻辑操作。操作类型由AluFunc
信号控制,源操作数由寄存器文件和立即数提供。
A
:ALU的输入操作数,来自寄存器文件的RD1
。B
:ALU的输入操作数,来自多路选择器(BSEL)。ALU
:执行操作并生成结果,这里是地址
计算Reg[rs1] + SXT(imm[11:0])
。
32
:表示数据路径的位宽。
数据内存(Data Memory)
数据内存用于存储和加载数据。地址由ALU计算得出。
Adr
:要访问的内存地址。WD
:要写入的数据。RD
:从内存读取的数据(这里未用)。MWR
:内存写使能信号。
数据路径流程
- 指令获取:
- PC提供当前指令地址。
- 指令内存根据PC提供的地址输出指令。
- 指令解码:
- 解码器解析指令字段,包括操作码、功能码、源寄存器、目标寄存器地址和立即数。
- 解码器生成控制信号,例如
AluFunc
、BSEL
、WDSEL
、WERF
和MWR
。
- 寄存器读取:
- 寄存器文件根据
rs1
字段提供源寄存器地址,通过RD1
总线从寄存器文件中读取数据。 - 寄存器文件根据
rs2
字段提供源寄存器地址,通过RD2
总线从寄存器文件中读取数据。
- 寄存器文件根据
- 多路选择器(BSEL):
- BSEL选择立即数作为ALU的第二个操作数。
- 地址计算:
- ALU根据
AluFunc
信号(Addi)执行地址计算,输入操作数来自RD1
和立即数。 - ALU输出内存地址。
- ALU根据
- 内存访问:
- 数据内存根据计算出的地址进行写入操作。
- 写入的数据来自寄存器文件的
RD2
。 MWR
信号控制内存写入操作。
分支指令(Branch Instructions)
分支指令仅在执行的aluBr操作上有所不同
指令 | 描述 | 执行 | 英文原文 |
---|---|---|---|
BEQ rs1, rs2, immB | 分支 = | pc <= (reg[rs1] == reg[rs2]) ? pc + immB : pc + 4 | Branch = |
BNE rs1, rs2, immB | 分支 ≠ | pc <= (reg[rs1] != reg[rs2]) ? pc + immB : pc + 4 | Branch ≠ |
BLT rs1, rs2, immB | 分支 < (有符号) | pc <= (reg[rs1] <s reg[rs2]) ? pc + immB : pc + 4 | Branch < (Signed) |
BGE rs1, rs2, immB | 分支 ≥ (有符号) | pc <= (reg[rs1] ≥s reg[rs2]) ? pc + immB : pc + 4 | Branch ≥ (Signed) |
BLTU rs1, rs2, immB | 分支 < (无符号) | pc <= (reg[rs1] <u reg[rs2]) ? pc + immB : pc + 4 | Branch < (Unsigned) |
BGEU rs1, rs2, immB | 分支 ≥ (无符号) | pc <= (reg[rs1] ≥u reg[rs2]) ? pc + immB : pc + 4 | Branch ≥ (Unsigned) |
这些指令被归为一个称为BRANCH的类别,包含字段(brFunc, rs1, rs2, immB)。
详细解释
- BEQ:当
reg[rs1]
等于reg[rs2]
时,程序计数器pc
跳转到pc + immB
,否则跳转到pc + 4
。 - BNE:当
reg[rs1]
不等于reg[rs2]
时,程序计数器pc
跳转到pc + immB
,否则跳转到pc + 4
。 - BLT:当
reg[rs1]
小于reg[rs2]
(有符号比较)时,程序计数器pc
跳转到pc + immB
,否则跳转到pc + 4
。 - BGE:当
reg[rs1]
大于或等于reg[rs2]
(有符号比较)时,程序计数器pc
跳转到pc + immB
,否则跳转到pc + 4
。 - BLTU:当
reg[rs1]
小于reg[rs2]
(无符号比较)时,程序计数器pc
跳转到pc + immB
,否则跳转到pc + 4
。 - BGEU:当
reg[rs1]
大于或等于reg[rs2]
(无符号比较)时,程序计数器pc
跳转到pc + immB
,否则跳转到pc + 4
。
用于分支比较的ALU(ALU for Branch Comparisons)
ALU for Branch Comparisons
- 类似于ALU,但返回一个布尔值。
详细解释
- 功能(func):用于分支比较操作的功能码,可能包括大于(GT),小于(LT),等于(EQ)等。
- ALU Br:专门用于分支比较操作的ALU单元,返回一个布尔值,指示比较的结果。
数据通路示例
- a 和 b:参与比较的两个寄存器值。
- func:指定比较操作的功能码(如GT,LT,EQ)。
- branch:比较结果,布尔值(true或false)。
数据通路详细步骤
这个图片展示了RISC-V分支指令的格式和执行流程。下面详细解释其中的各个部分和流程。
分支指令格式
RISC-V分支指令的编码如下:
imm[12|10:5]
: 立即数的一部分,从指令的第31位和第30-25位。rs2
: 第二个源寄存器,从指令的第24-20位。rs1
: 第一个源寄存器,从指令的第19-15位。func3
: 功能字段,从指令的第14-12位。用于区分不同类型的分支指令。imm[4:1|11]
: 立即数的另一部分,从指令的第11-8位和第7位。opcode
: 操作码,从指令的第6-0位。对于分支指令,操作码为1100011
。
分支指令的执行流程
- 指令取值:
- 程序计数器 (PC) 从指令存储器 (Instruction Memory) 中取出当前指令。
- 立即数生成 (immB):
immB
是通过将指令中的立即数部分 (imm[12, 10:5, 4:1, 11]) 进行符号扩展 (SXT) 得到的完整立即数。- 符号扩展的过程将指令中的立即数部分合并,并保持符号位不变。
- 寄存器文件读取:
- 根据指令中的
rs1
和rs2
字段,从寄存器文件 (Register File) 中读取相应的寄存器值 (Reg[rs1] 和 Reg[rs2])。
- 根据指令中的
- 分支条件计算:
- 根据
func3
字段,ALU (算术逻辑单元) 使用相应的比较操作 (BrFunc) 比较寄存器Reg[rs1]
和Reg[rs2]
的值。 func3
字段的值决定了具体的比较操作,例如相等 (BEQ), 不等 (BNE), 小于 (BLT), 大于等于 (BGE) 等。
- 根据
- PC 更新:
- 根据比较结果,如果条件满足 (branch = 1),PC 将更新为
pc + immB
,否则更新为pc + 4
。 - 这个更新决定了程序的下一个执行地址,是分支指令的核心功能。
- 根据比较结果,如果条件满足 (branch = 1),PC 将更新为
- 执行控制信号:
- Decoder 产生各种控制信号 (PCSEL, BSEL, WDSEL, AluFunc, WERF, MWR) 来控制数据路径和操作。
具体的分支指令类型和 func3
字段的值
分支指令 | func3 | 描述 |
---|---|---|
BEQ | 000 | 等于分支 (Branch if Equal) |
BNE | 001 | 不等于分支 (Branch if Not Equal) |
BLT | 100 | 小于分支 (Branch if Less Than) |
BGE | 101 | 大于等于分支 (Branch if Greater or Equal) |
BLTU | 110 | 无符号小于分支 (Branch if Less Than Unsigned) |
BGEU | 111 | 无符号大于等于分支 (Branch if Greater or Equal Unsigned) |
通过上述步骤和表格,可以清晰理解RISC-V分支指令的编码格式和执行流程。
分支指令(Branch Instructions)
分支指令(Branch Instructions)
-
指令格式:
imm[12|10:5] rs2 rs1 funct3 imm[4:1|11] opcode
-
**imm[12 10:5] 和 imm[4:1 11]**:立即数,用于计算跳转地址。 - rs2:源寄存器2。
- rs1:源寄存器1。
- funct3:功能码3,用于进一步指定操作类型。
- opcode:操作码,指定指令类型。
-
BEQ指令格式
imm[12|10:5] rs2 rs1 000 imm[4:1|11] 1100011
数据通路示例
- 取指:PC从指令存储器中取出指令,PC每次加4。
- 译码:将指令译码为控制信号,控制ALU和寄存器文件的操作。
- 执行:ALU根据控制信号进行比较操作,如果条件满足,PC跳转到
pc + immB
,否则跳转到pc + 4
。
分支指令的数据通路图
- 指令格式:
imm[12|10:5] rs2 rs1 000 imm[4:1|11] 1100011
- 指令译码:分离出
rs1
,rs2
和imm[12:1|11]
。 - 比较操作:
ALU Br
执行比较操作,根据funct3
指定的操作对reg[rs1]
和reg[rs2]
进行比较。 - 跳转计算:根据比较结果确定PC的新值。如果比较结果为true,则PC跳转到
pc + immB
,如果比较结果为false,则PC跳转到pc + 4
。
其他指令(Remaining Instructions)
其他指令(Remaining Instructions)
指令 | 描述 | 执行 | 英文原文 |
---|---|---|---|
JAL rd, immJ | 跳转并链接 | reg[rd] <= pc + 4 pc <= pc + immJ | Jump and Link |
JALR rd, immI(rs1) | 跳转并链接寄存器 | reg[rd] <= pc + 4 pc <= {reg[rs1] + immI, 1’b0} | Jump and Link Register |
LUI rd, immU | 加载高位立即数 | reg[rd] <= immU | Load Upper Immediate |
这些指令各自属于一个类别,需要从指令中提取不同的字段。JAL和JALR指令同时更新PC和reg[rd]
。
详细解释
- JAL:跳转并链接指令,跳转到
pc + immJ
,同时将返回地址(pc + 4
)存储在目标寄存器rd
中。 - JALR:跳转并链接寄存器指令,跳转到
(reg[rs1] + immI)[31:1]
,将返回地址(pc + 4
)存储在目标寄存器rd
中。 - LUI:加载高位立即数,将高位立即数
immU
加载到目标寄存器rd
中。
数据通路详细步骤
- 取指:
- PC指向指令存储器中的当前指令地址。
- PC每次加4,指向下一条指令地址。
- 译码:
- 解码指令,提取寄存器地址和立即数。
- 跳转和链接(JAL和JALR):
- JAL:
- 将当前PC加4,存储在目标寄存器
rd
中。 - 计算跳转地址
pc + immJ
,并更新PC。
- 将当前PC加4,存储在目标寄存器
- JALR:
- 将当前PC加4,存储在目标寄存器
rd
中。 - 计算跳转地址
(reg[rs1] + immI)[31:1]
,并更新PC。
- 将当前PC加4,存储在目标寄存器
- JAL:
- 加载高位立即数(LUI):
- 将高位立即数
immU
加载到目标寄存器rd
中。
- 将高位立即数
通过这些内容的理解,可以掌握其他指令的基本格式和执行过程,进一步理解计算机指令执行的具体操作。
JALR指令(JALR Instruction)
JALR指令(Jump and Link Register)
-
指令格式:
imm[11:0] rs1 000 rd 1100111
- imm[11:0]:立即数,用于计算跳转地址。
- rs1:源寄存器1,基地址来自这个寄存器。
- rd:目标寄存器,存储返回地址。
- opcode:操作码,指定指令类型。
JALR指令格式
imm[11:0] rs1 000 rd 1100111
数据通路示例
- 取指:PC从指令存储器中取出指令,PC每次加4。
- 译码:将指令译码为控制信号,控制ALU和寄存器文件的操作。
- 执行:ALU根据控制信号计算地址,将PC加4存储在
rd
中,并将PC设置为(reg[rs1] + immI)[31:1]
。
JALR指令的数据通路图
JALR指令
JALR: immI = SXT(imm[11:0])
Reg[rd] <= pc + 4
pc <= {(Reg[rs1] + immI)[31:1], 1'b0}
- 操作码 1100111 对应于
Jalr
(Jump and Link Register)
数据通路详细步骤
这个图片展示了RISC-V中的 Jalr
(Jump and Link Register) 指令的格式和执行流程。下面我将详细解释其中的各个部分和流程。
Jalr
指令格式
RISC-V Jalr
指令的编码如下:
imm[11:0]
: 立即数,从指令的第31-20位。rs1
: 源寄存器,从指令的第19-15位。func3
: 功能字段,固定为000
,从指令的第14-12位。rd
: 目标寄存器,从指令的第11-7位。opcode
: 操作码,从指令的第6-0位。对于Jalr
指令,操作码为1100111
。
Jalr
指令的执行流程
- 指令取值:
- 程序计数器 (PC) 从指令存储器 (Instruction Memory) 中取出当前指令。
- 立即数生成 (immI):
immI
是通过将指令中的立即数部分 (imm[11:0]) 进行符号扩展 (SXT) 得到的完整立即数。
- 寄存器文件读取:
- 根据指令中的
rs1
字段,从寄存器文件 (Register File) 中读取相应的寄存器值 (Reg[rs1])。
- 根据指令中的
- 目标地址计算:
- 将寄存器
Reg[rs1]
的值和立即数immI
相加,得到跳转的目标地址。
- 将寄存器
- PC 更新:
- 根据计算出的目标地址,将PC更新为
{(Reg[rs1] + immI)[31:1], 1'b0}
。 - 这个更新确保了跳转地址是对齐到2字节边界的。
- 根据计算出的目标地址,将PC更新为
- 返回地址存储:
- 将当前PC加上4(即下一条指令的地址)存储到目标寄存器
rd
中,以便在跳转后可以返回。
- 将当前PC加上4(即下一条指令的地址)存储到目标寄存器
- 执行控制信号:
- Decoder 产生各种控制信号 (PCSEL, BSEL, WDSEL, AluFunc, WERF, MWR) 来控制数据路径和操作。
具体流程详解
- 指令取值:
- 当前PC从指令存储器中取出
Jalr
指令。
- 当前PC从指令存储器中取出
- 立即数生成:
- 符号扩展立即数
immI
。
- 符号扩展立即数
- 寄存器文件读取:
- 读取寄存器
rs1
的值。
- 读取寄存器
- 目标地址计算:
JT
是通过将Reg[rs1]
和immI
相加计算得到的。- 目标地址
JT
的最低一位被置为0,以确保地址对齐。
- PC 更新和返回地址存储:
- 将
JT
更新到PC中。 - 将
PC + 4
存储到寄存器rd
中。
- 将
- 执行控制信号:
- 根据指令格式和操作码,Decoder生成相应的控制信号。
通过上述步骤和解释,可以清晰理解RISC-V Jalr
指令的编码格式和执行流程。
单周期RISC-V处理器(Single-Cycle RISC-V Processor)
单周期RISC-V处理器结构图
- PC(程序计数器):保持当前指令地址。
- 指令存储器(Inst Memory):存储程序指令。
- 译码单元(Decode):将指令译码为控制信号。
- 寄存器文件(Register File):包含所有寄存器,具有2个读取端口和1个写入端口。
- 执行单元(Execute):执行指令中的运算。
- 数据存储器(Data Memory):存储和读取数据。
数据流
- 取指:
- 程序计数器(PC)从指令存储器中取出当前指令。
- 指令传递到译码单元。
- 译码:
- 译码单元将指令译码为控制信号。
- 提取寄存器地址和立即数。
- 寄存器读取:
- 从寄存器文件中读取源寄存器值(
rs1
和rs2
)。
- 从寄存器文件中读取源寄存器值(
- 执行:
- 执行单元根据控制信号执行运算。
- 计算结果或目标地址。
- 数据存储器访问(如果需要):
- 根据执行单元的计算结果访问数据存储器。
- 读取或写入数据。
- 写回:
- 将执行结果写回寄存器文件或数据存储器。
指令解码器(Instruction Decoder)
指令解码器功能
- 目的:从32位指令中提取指令类型和各种字段。
- 字段:
- 指令类型:OP、OPIMM、BRANCH、JAL、JALR、LUI、LOAD、STORE、Unsupported
- ALU功能:aluFunc
- 分支ALU功能:brFunc
- 寄存器字段:rd、rs1、rs2
- 立即数常量:immI(12)、immB(12)、immJ(20)、immU(20)、immS(12),每个常量都作为带有适当符号扩展的32位值使用。
详细解释
- 指令类型识别:
- 识别当前指令属于哪种类型(如OP、OPIMM、BRANCH等)。
- 字段提取:
- 从指令中提取寄存器地址(
rd
、rs1
、rs2
)。 - 提取立即数常量(
immI
、immB
、immJ
、immU
、immS
)。
- 从指令中提取寄存器地址(
- 功能码提取:
- 提取ALU和分支ALU的功能码(
aluFunc
、brFunc
)。
- 提取ALU和分支ALU的功能码(
- 符号扩展:
- 对提取的立即数进行符号扩展,将其扩展为32位值。
注意事项
- 字段完整性:注意没有任何指令包含所有字段。每种指令类型只使用其需要的特定字段。
通过这些内容的理解,可以掌握单周期RISC-V处理器的结构和指令解码的过程,进一步理解计算机指令执行的具体操作。
执行功能(Execute Function)
输入(Inputs)
- 从寄存器文件读取的值(Values read from register file)
- 解码后的指令字段(Decoded instruction fields)
- 程序计数器(PC)(PC)
逻辑(Logic)
- 算术逻辑单元(ALU):用于执行算术和逻辑运算。
- 分支ALU(BrALU):用于执行分支比较运算。
- 下一PC生成(NextPC generation):根据指令计算下一条指令的地址。
输出(Outputs)
- 目标寄存器(Destination register):存储运算结果的寄存器。
- 写入寄存器文件或内存的数据(Data to write to register file or memory):运算结果或加载的数据。
- 加载和存储操作的地址(Address for load and store operations):计算出的内存地址。
- 下一PC(NextPC):下一条指令的地址。
RISC-V Inside
漫画解释
- 左侧人物:拿着一本《瓦尔多与瓦尔多》(Waldo & Walstead)书,问道:“这就是构建处理器的全部吗?”
- 右侧人物:穿着MIT 6.004课程的T恤,回答:“不,你还得印那些小小的‘RISC-V Inside’贴纸。”
这幅漫画幽默地展示了构建处理器的复杂性和附加任务(如品牌化)的必要性。
详细解释
执行功能模块(Execute Function Module)
- 输入(Inputs):
- 从寄存器文件中读取的值:执行操作所需的源寄存器数据。
- 解码后的指令字段:包含操作码、功能码、立即数等,用于确定指令的具体操作。
- 程序计数器(PC):当前指令的地址,用于计算下一条指令的地址。
- 逻辑(Logic):
- 算术逻辑单元(ALU):执行加法、减法、与、或等运算。
- 分支ALU(BrALU):执行分支比较运算,根据条件决定是否跳转。
- 下一PC生成:根据当前指令和操作结果计算下一条指令的地址(如顺序执行或分支跳转)。
- 输出(Outputs):
- 目标寄存器:存储ALU或BrALU运算结果的寄存器。
- 写入寄存器文件或内存的数据:如算术运算结果或从内存加载的数据。
- 加载和存储操作的地址:计算出的用于内存访问的地址。
- 下一PC:计算出的下一条指令的地址。
通过这些内容的理解,可以掌握执行功能模块的输入、逻辑和输出,进一步理解计算机指令执行的具体操作。