指令的数据通路及流水线概述
一种基本的 LEGv8 实现
学校教材使用的是ARM版本的,所以自然是ARM相关的。接下来主要是要实现下面的指令。
- 存储器访问指令(memory-reference instructions load register unscaled) (LDUR) and store register unscaled (STUR).
- The 算术逻辑指令(arithmetic-logical instructions) ADD,SUB,AND, and ORR.
- The instructions 比较(compare) and 分支(branch) on zero (CBZ) and branch (B).
LGEv8所有的指令格式
时钟策略
时钟策略 (clocking methodology)规定了信号可以读出和写人的时间规定读出和写入的时间非常重要,因为若一个信号在写入的同时被读出,那么读出的值可能是之前的旧值,也可能是新写入的值,甚至是两者的混合,计算机的设计中显然不允许这种不确定性。时钟策略即是为避免这种情况而提出的,以确保硬件行为是可预测的。
简单起见,我们假定采用边沿触发的时钟 (edge-triggered clocking)方法,即在时序逻辑单元中存储的所有值仅在时钟边沿更新。当一个状态单元在每个有效的时钟边沿都进行写入时,我们不必给出写控制信号 (controI signal) 。相反,若一个状态单元不是每个周期都进行更新,那么就需要一个显式的写控制信号。我们使用术语有效 (asserted)表示信号为逻辑高,无效 (deasserted) 表示逻辑低。
指令执行的主要步骤
LEGv8 指令通常包含以下五个处理步骤:
- 取指(IF - Instruction Fetch):从指令存储器中取指令,更新PC。
- 译码(ID - Instruction decode and register file read):读寄存器并对指令译码。
- 执行(EX - Execution or address calculation):执行操作(运算指令)或计算地址(访存指令)。
- 访存(MEM - Data memory access):从数据存储器中读取操作数(如有需要)。
- 回写(WB - Write back):将结果写回寄存器(如有需要)。
重要书本例题
单周期性能与流水线性能
假设单周期实现 (所有的指令都在一个时钟周期内完成执行)中指令执行的平均时间,与流水线下实现下指令执行的平均时间进行对比。
假设本例中主要功能单元的操作时间为:访问指令或数据存储器 $200ps$,ALU 操作 $200ps$,读写寄存器文件 $100ps$。单周期模型中,每条指令执行都只需一个时钟周期,因此需要延展时钟周期以满足最慢的指令。
假设多路选择器、控制单元、PC 访问以及符号扩展单元都没有延迟,忽略流水线寄存器自身延迟,对寄存器文件的写操作发生在时钟周期的前半段,而读操作发生在后半段。
数据通路
我们先来看看每条指令需要哪些数据通路部件 (datapath element),按抽象层展开。
- 存储程序指令的存储单元( a memory unit to store the instructions of a program),能够根据给定的地址提供指令。
- 程序计数器( Program Counter, PC ),PC 是一个用于保存当前指令地址的寄存器。
- 需要一个加法器来增加 PC 值,使其指向下条指令的地址。
任何指令的执行,都首先要从存储器中取出指令为准备执行下条指令,必须增加程序计数器以使其指向下一条指令,即向后移动4个字节。下图的数据通路能够取指令并能增加PC值以获得顺序下一条指令的地址。
R型指令
R型指令或者叫算术运算指令(用于寄存器)中各字段名称及含义如下:
- opcode:操作码,指明指令完成的基本操作。
- Rm:第二个源操作数寄存器。
- shamt:位移量
- Rn:第一个源操作数寄存器。
- Rd:目标操作数寄存器,存放操作结果
在LEGv8中,有以下R型指令:
由于R型指令有3个寄存器操作数,因此每条指令都要从寄存器文件(寄存器组/寄存器堆)中读出两个数据字,再写入一个数据字。
- 读出一个数据字,需要给寄存器文件一个输入,以指明所要读的寄存器号,并且寄存器文件将产生一个瑜出,包含从寄存器文件读出的值。
- 写入一个数据字时令,需要给寄存器文件提供两个输入, 一个指明要写的寄存器号,另一个提供要写的数据。
实现R型指令 ALU 操作所需的两个单元寄存器文件和 ALU。因为寄存器文件的写入是边沿触发的,故可以在同一时钟周期内读出和写入同一寄存器:读操作将读出以前写入的内容,而写入的内容在下一时钟周期才可读。
D型指令
该类型指令也叫数据传输指令主要是取数(Load register)指令和存数(Store register)指令,其一般形式为 LDUR X1, [X2, offset_value]
或STUR X1, [X2, offset_value]
。
- 如果是存数指令,待写数据要从寄存器
X1
中读出。 - 如果是取数指令,则要将从存储器中读出的数据存入寄存器文件中指定的寄存器中,如
X1
。 - op2字段是操作码字段的逻辑扩展;对于
load
指令,Rt
是目的寄存器;对于store
指令,Rt
是待存入数据所在的寄存器。
因此,寄存器文件和 ALU 都需要。此外,还需要一个单元将指令中的9位偏移字段符号扩展 (sign extend) 为64位的带符号值,以及一个保存读出或写入数据的数据存储单元。
- 数据存储器单元(Data memory unit)是一个状态单元,两个输入为地址和待写入的数据,一个输出为读出的结果。
- 读、写控制信号都是独立的,尽管任意时钟只能激活其中一个
- 存储器单元(Register file)需要一个读控制信号,因为读一个无效地址可能会出现问题。
CB型指令
即条件分支指令,CBZ 指令有两个操作数,一个寄存器用于测试结果是否为 0,另一个是 19 位偏移量,用于计算相对于分支指令所在地址的分支目标地址(branch target address)。指令格式为CBZ X1, offset
。Rt
是源寄存器,用于测试值是否为0。
分支指令的数据通路需要两个操作:计算分支目标地址的下一条指令的地址和测试寄存器内容。下图为数据通路中处理分支的部分,使用 ALU 计算分支条件是否成立,使用另一个加法器将自增后的 PC 值与符号扩展后左移 2 位的 19 位指令字段(分支偏移量)相加,得到分支目标地址。
LEGv8核心体系结构数据通路
该图还增加了一个多路选择器,用于选择是将顺序的指令地址 (PC+4) 还是分支目标地址写入 PC。
控制信号的作用
下图包含控制单元的简单数据通路。控制单元的输入为指令的 11 位操作码字段,输出包括三位的多路选择器控制信号( Reg2Loc、ALUSrc 和 MemtoReg ),三个寄存器文件和存储器读写的控制信号( RegWrite、MemRead和MemWrite ),一个决定是否可能分支的1位信号 ( Branch ),以及一个 ALU 的2位控制信号 (ALUOp) 。
数据通路的操作
R型指令数据通路操作
以ADD X1, X2, X3
为例, 按信息流向排序如下:
- 取指,递增 PC。
- 从寄存器文件中读出寄存器
X2
和X3
。 同时,主控制单元计算控制信号的状态。 - ALU 对从寄存器文件读出的数据进行操作,根据
opcode
字段确定 ALU 的功能。 - 将 ALU 的结果写入寄存器文件中的目的寄存器。
控制信号的设置:
数据通路:
类似的:
D型指令数据通路操作
以 LDUR X1, [X2, offset]
为例,按信息流向排序如下:
- 从指令存储器取指,递增 PC。
- 从寄存器文件读出寄存器 X2 的值。
- ALU 将从寄存器文件读出的值与符号扩展后的指令低9位值 (offset) 相加。
- 将 ALU 的结果作为数据存储器的地址。
- 将存储单元的数据写入寄存器文件 X1。
控制信号的设置:
数据通路:
类似的:
CB型指令数据通路操作
以 CBZ X1, offset
为例,按信息流向排序如下:
- 从指令存储器取指,递增 PC。
- 根据指令中的
4:0
位,从寄存器文件中读出寄存器X1
。 - ALU传递从寄存器文件中读出的数据值。PC值与符号扩展并左移2位后的指令低19位(offset)相加,结果是分值目标地址。
- 根据ALU的零输出决定将
PC+4
还是分支目标地址写入PC中。
控制信号的设置:
当Branch
信号为低电平(0)时,PC无条件更新为PC+4
;反之,如果ALU的零输出为高电平,则PC更新为分支目标地址。
数据通路:
类似的:
流水线冒险及解决方法
流水线会出现这样一种情况,即在下一个时钟周期中下一条指令不能执行。这种情况称为冒险 (hazard),有三种类型。
1. 结构冒险
第一种冒险称为结构冒险 (structural hazard),因硬件不支持待执行的指令组合而导致计划的指令不能在预定的时钟周期内执行。
常见解决方法
假设对于一个存储器在同一个时刻只能接受一个读操作,则有:
- 流水线阻塞(pipeline stall),产生气泡(bubble)。
- 指令和数据存放在不同的存储器中。靠在存储器当中设置独立的指令高度缓存(I-Cache)和数据高度缓存(D-Cache)来实现的。
- 使寄存器文件的写操作发生在时钟周期的前半段,而读操作发生在后半段。
2. 数据冒险
当一个步骤必须等待另一个步骤完成才能进行(指令需要某个数据而之前的指令正在操作这个数据),此时将造成流水线暂停,产生数据冒险(date hazard)。
常见解决方法
- 流水线阻塞(pipeline stall),产生气泡(bubble)。
- 数据前推(Forwarding)或者旁路(bypassing)。
- 流水线阻塞(pipeline stall)+数据前推(Forwarding)。主要用于解决取数——使用型数据冒险(load-use data hazard)。
3. 控制冒险
第三种冒险称为控制冒险 (control hazard),也称为分支冒险。如果现在要执行哪条指令,是由之前指令的运行结果来决定的,而现在之前指令的结果还没有产生,就导致了控制冒险。
常见解决方法
- 流水线阻塞(pipeline stall),产生气泡(bubble)。
- 分支预测(branch prediction),预测一些分支发生而预测另一些分支不发生。
- 延迟分支(delayed branch),顺序执行下一条指令,在这条指令延迟之后分支才发生。