CPU自制入门1.8 AZ Processor 的设计与实现_CPU自制入门1.8 AZ Processor 的设计与实现试读-查字典图书网
查字典图书网
当前位置: 查字典 > 图书网 > 编程 > CPU自制入门 > 1.8 AZ Processor 的设计与实现

CPU自制入门——1.8 AZ Processor 的设计与实现

本节将对CPU 的设计与实现进行说明。首先讲述流水线处理技术的概要和实现方法,然后设计并实现AZ Processor。 1.8.1  关于CPU 流水线处理 流水线处理是一种提高CPU 的处理性能的技术。所谓流水线处理,是将处理操作分为多个阶段,然后像流水线作业一样执行。图1-84 展示了流水线处理的示意图。 图1-84 流水线处理示意图 CPU 中的各种硬件资源,只在处理的相应阶段使用,其他时间大多处于空闲状态。比如,运算器在指令执行时使用,在指令读取、解码时空闲。因此,为了高效使用这些硬件资源,引入了流水线处理技术。 在流水线处理的情况下,读取某条指令之后,在该指令解码的同时读取下一条指令。通过使各个阶段的动作重叠,可以让硬件资源有效使用,同时提高处理速度。流水线处理就像是将之前一个人完成的操作,分成N 个相连的步骤进行处理,以此将处理效率提高N 倍。 流水线处理中,处理的各个阶段被称为流水线级。各个流水线级的处理时间应该尽量相等。因为如果各个流水线级的处理时间不均等的话,最慢的流水线级的处理时间将成为系统的时钟周期。因此,多数CPU 会进一步细化读取、解码和执行这3 个步骤, 以实现高效的流水线。 最为典型的流水线分为5 个阶段,请参见图1-85。使用了流水线技术的CPU,通常在各个流水线级之间设置流水线寄存器,用来保存状态并传递给下一个操作阶段。 图1-85 CPU的流水线化 IF(Instruction Fetch)阶段 将PC 的值发送到内存,读取指令。 ID(Instruction Decode)阶段 将读取的指令解码并决定将要进行的操作,从寄存器堆读取数据。 EX(Execution)阶段 使用运算器执行操作。可以执行算术运算和逻辑运算的运算器称为ALU (Arithmetic and Logic Unit)。 MEM(Memory Access)阶段 进行内存访问。 WB(Write Back)阶段 将结果写回寄存器堆。 实现了流水线化的CPU,将这5 个流水线级的操作重叠使用,按照图1-86 所示的方式执行。 图1-86 流水线的流程 流水线冒险 流水线处理中,由于各个阶段的依赖关系、硬件资源的竞争等原因,会出现操作无法执行的情况。造成流水线故障的原因称为冒险,冒险分为构造、数据冒险和控制冒险3 种类型。 构造冒险 构造冒险是指由于硬件资源的竞争,操作无法同时执行的情况。图1-85 所示的流水线结构中,内存访问会造成构造冒险。IF 阶段和MEM 阶段都要涉及内存访问。由于访问内存使用的总线是共享资源,无法同时进行操作。因此,如果发生IF 阶段和MEM 阶段同时访问内存的情况,一方需要等待另一方访问完成。这种指令和数据使用同一通道的构造称为诺依曼架构。 如果导致冒险产生的硬件资源数量足够多,也可以避免冒险问题的发生。因此,指令用的内存和数据用的内存分别设置,即可解决构造冒险的问题。这种物理上将指令用和数据用的内存与访问通道分开的构造称为哈佛架构。图1-87 分别展示了诺依曼架构和哈佛架构。 图1-87 诺依曼架构与哈佛架构 哈佛架构的优点是,就算指令访问和数据访问同时发生,也不会发生冒险的情况。但是,也有指令和数据地址空间不同的缺点。在哈佛架构中,指令的0 号地址和数据的0 号地址指向不同的内容。这会引起软件设计上的问题。 近年来,大部分CPU 的指令和数据都放在同一内存中。但是,CPU 直接访问的缓存基本上都分为指令用和数据用两种,称为指令缓存和数据缓存。图1-88 展示了带有缓存的CPU 构造。通过两种缓存的使用,解决了指令访问和数据访问之间发生的构造冒险问题。 图1-88 带有缓存的CPU构造 数据冒险 数据冒险是指,由于指令执行所需要的数据还未准备好所引起的冒险情况。当即将执行的指令依赖于还未处理完成的数据时,会导致指令无法立刻开始执行,引发数据冒险。 为了回避数据冒险,我们使用一种称为直通(Forwarding)的方法。原本回写运算结果是在WB 阶段,但实际上决定运算结果是在EX 阶段。因此直通是指在运算结果确定的EX 阶段,将数据直接传递给下一个指令。 直通的示例如图1-89 所示。示例中使用流水线执行3 条有数据依赖关系的指令, 以此说明直通的动作原理。第二条指令要使用第一条指令的结果。第一条指令在EX 阶段可以确定运算结果后,直接将结果发送到处于ID 阶段的第二条指令。第三条指令同时依赖于第一和第二条指令。因此,可以直接从处于MEM 阶段的第一条指令和处于EX 阶段的第二条指令获取数据。以这种将运算结果直通的方式,可以消除原本需要等待WB 阶段完成的依赖关系。 图1-89 直通示例 使用直通解决依赖关系的方法仅有一个例外,就是数据需要使用Load 指令从内存调取的情况。由于内存的访问在MEM 阶段执行,因此处理结果要在MEM 阶段才能确定。而当前指令执行到MEM 结束时,下一条指令已经到达EX 阶段执行了。这与直通的机制不吻合。 这种依赖Load 指令而发生的冒险称为Load 冒险。Load 冒险不能从根本上避免, 因此要将有依赖关系的指令进行阻塞以解决该问题。阻塞是指让流水线的特定阶段停止一段时间。Load 冒险发生时的流水线动作如图1-90 所示。 图1-90 Load冒险发生时的流水线的动作 如果有Load 冒险发生,则将有依赖关系的指令延迟一个周期执行。如果将指令阻塞一个周期,前一条指令在MEM 阶段得到的数据就可以直通正在ID 阶段的下一条指令。这时候,流水线会浪费一个周期,这一周期让其传递无效的数据即可。这个操作称为流水线冒泡。如果Load 指令与和其有依赖关系的指令相差一条以上指令的距离,则不会发生Load 冒险。作为有效的处理操作的方法,在编译器中使用适当的调度算法也可以有效避免Load 冒险。 控制冒险 控制冒险是指无法确定下一条指令而引发的冒险情况。在执行可能会改变下一条指令的分支指令时,在这一条指令执行结果确定之前下一条指令无法开始执行,从而引起控制冒险。 控制冒险也无法从根本上避免,但是可以尽量将分支指令安排到流水线前段,从而减少因为控制冒险而引起的无效指令数量。比如在ID 阶段判定分支后,延迟一个周期就可以开始执行分支指向的下一条指令。控制冒险发生时的流水线动作如图1-91 所示。 图1-91 控制冒险发生时的流水线动作 因为在读取下一条指令前需要确定PC 寄存器的值,即使在ID 阶段判定分支也会产生一个周期的延迟。延迟期间会让流水线传送无效数据。流水线冒泡会浪费硬件资源, 因此可以采用延迟分支的方法,许可分支指令的下一条指令执行。 延迟分支是指分支指令执行后并不立刻跳转到分支结果指向的指令,在分支指令的下一条指令执行完毕后再进行跳转。分支指令的下一条指令称为延迟间隙,不论分支是否成立都会被执行。使用延迟分支可以避免流水线冒泡,使操作的处理更有效率。一般的分支与延迟分支如图1-92 所示,采用了延迟分支的流水线执行过程如图1-93 所示。 图1-92 一般的分支与延迟分支 图1-93 采用了延迟分支的流水线执行过程 CPU 模式 大部分CPU 至少都有两种CPU 模式。CPU 模式也称为特权级,它会根据CPU 的工作模式限制可以执行的操作。CPU 模式中,全部指令可以无限制执行的模式称为内核模式(Kernel Mode)或管理者模式(Supervisor Mode),操作系统等系统软件需要在内核模式下工作。反之,可执行的指令被限制的模式称为用户模式(User Mode),应用软件通常在用户模式下工作。用户模式中被限制的操作包括CPU 控制寄存器的访问、改变CPU 状态的指令等。如果应用程序擅自更改CPU 的状态,最坏会导致操作系统崩溃。因此,需要根据CPU 模式管理各种软件的权限。 大多情况下,CPU 的控制寄存器内都有可以设置CPU 模式的区域。在从高权限的内核模式转换到低权限的用户模式时,可以通过操作控制寄存器来实现。反之,如果要从低权限的用户模式转换到高权限的内核模式,需要使用专用的指令。 中断和异常 中断是指让CPU 暂停正在执行的操作,执行其他操作的功能。中断经常用在通知来自I/O 的事件、处理程序执行中的异步事件等情况。发生中断时,CPU 暂停当前操作, 并跳转到中断处理程序。这时,CPU 模式会变更到内核模式。中断处理完成后返回到中断处继续执行。中断处理的流程如图1-94 所示。 异常是指CPU 的执行产生了预期之外的结果。例如,遇到无法解码的指令、运算结果溢出以及操作违反权限等情况。遇到异常发生的情况时,CPU 将暂时中断当前程序,跳转到异常处理程序。这时,CPU 模式会变更到内核模式。异常处理完成后,原则上将返回异常中断处,但如果发生致命错误会强制中止执行的程序。异常处理的流程如图1-95 所示。 图1-94 中断处理的流程             图1-95 异常处理的流程 中断和异常最大的区别在于发生的原因。中断是由外部因素引起的与正在执行的操作的异步情况,而异常是在正在执行的操作的内部发生的。由于都是暂停正在进行的操作并跳转到处理程序,有着相同的动作特征,中断和异常的处理本质上是一致的。因此, 中断和异常使用相同机制不加区分的CPU 也很多。 异常发生时的流水线动作 流水线化的CPU 在异常发生时的处理稍微有些复杂。异常发生后,导致异常发生的指令以及其后的指令暂停执行,并跳转到异常处理程序。由于流水线化的CPU 中的后续指令也在执行中,需要先将流水线内所有数据缓存后,再将PC 寄存器设置为异常处理程序地址,最后重新启动流水线。有异常发生时的流水线动作如图1-96 所示。 图1-96 异常发生时的流水线动作 根据异常种类的不同,发生异常的流水线级也不同。因此异常发生时的动作较为复杂。最简便的方式是在流水线寄存器设置专用寄存器以标示异常发生的位置,最后在WB 阶段检查是否有异常发生。因为操作结果的写回是在WB 阶段,如果在WB 级执行前将其内容缓存,指令就可以和从未执行过一样。 但是,也有一个例外。只有写入内存的存储指令,在MEM 阶段就会将结果写入内存。因此,为了使存储指令无效,需要判断内存写入前的指令是否发生异常。 专栏 CPI 和MIPS 值 为了表示CPU 运行一条指令所需的时钟周期, 有一个称为CPI(Clock cycle Per Instruction)的指标。CPI 表示平均一条指令所需的时钟周期,知道了程序行数和CPI,即可计算出程序执行所需要的时钟周期数。 1.8.1 节介绍的CPU,一条指令的执行需要5 个时钟周期。由于使用了流水线技术,看起来可以同时执行5 条指令。但是,由于延迟或缓存会引发流水线冒泡,实际程序的不同, CPI 会有所变化。 MIPS(Million Instructions Per Second)是衡量CPU 性能的指标。MIPS 是表示每秒可以执行几百万条指令的数值,是用CPU 的动作频率除以平均CPI 计算得到的。 1.8.2  AZ Processor 的设计 AZ Processor的流水线结构 本章基于RISC 架构的32 位CPU,使用1.8.1 节讲解的典型的5 级流水线技术制作AZ Processor。AZ Processor 的框图如图1-97 所示。 图1-97 AZ Processor 框图 AZ Processor 由以下部分组成:流水线中的IF 阶段、ID 阶段、EX 阶段、MEM 阶段、CPU 中的存储器通用寄存器、控制CPU 的CPU 控制单元,以及CPU 可以直接访问的专用存储器SPM(Scratch Pad Memory)。虚线中的WB 阶段,实际上在结果写回的通用寄存器或CPU 控制单元中实现,这个模块本身并不存在。 IF 阶段和MEM 阶段通过总线与内存和I/O 相连。为了使流水线高效工作,需要每个周期都向流水线提供指令或数据。因此,我们为AZ Processor 设置可以高速访问的CPU 专用SPM。虽然SPM 和其他内存、I/O 同样分配有地址空间,但CPU 可以直接访问而不用通过总线。SPM 也有点像缓存,但却是本身分配有地址空间的存储器。 分支的判定在ID 阶段进行。我们采用了延迟分支机制,也就是说,分支指令的下一条指令被作为延迟间隙执行,以此规避控制冒险。EX 阶段和MEM 阶段的处理结果可以直通到ID 阶段,以此规避数据冒险。 流水线寄存器的停滞与刷新和流水线的控制等操作由控制单元负责。控制单元还可以接受来自外部的中断请求,并根据CPU 的设置输出中断检测信号。中断的检测是在EX 阶段进行的。 AZ Processor 流水线的细节如图1-98 所示。 AZ Processor的指令集架构 指令格式一览 AZ Processor 的指令,根据指令二进制代码内信息格式的不同分为5 类。指令的格式如图1-99 所示,指令代码中各字段的说明请参见表1-27。指令的最高6 位用来定义操作码(operation code),指示指令进行的操作。剩余的位称为操作数(operand),用来表示指令使用的寄存器的地址和立即数等。 图1-99 指令的格式 表1-27 指令字段 AZ Processor 指令格式最大可以有3 个操作数。指令根据操作数的不同,可以分为5 类:R3(3 Registers) 格式、R2(2 Registers) 格式、R1(1 Register) 格式、R0(0 Register) 格式和R2I(2 Registers & Immediate) 格式。Reserved 区域为保留(未使用)字段。 立即数是指嵌入到指令字段中的常数。程序中经常出现使用常数的运算。例如循环的递增、变量的初始化等众多场合。如果CPU 的指令只能使用寄存器,则需要将常数存储在内存并在每次使用时加载。这种做法复杂且效率低下,因此指令中嵌入立即数这种做法非常有效。 AZ Processor 指令一览如表1-28 所示。AZ Processor 有7 种类型的指令:逻辑运算指令、算术运算指令、移位指令、分支指令、内存访问指令、特殊指令,以及特权指令。 表1-28 指令一览 逻辑运算指令 逻辑运算指令对作为操作数的寄存器之间,或者寄存器与立即数之间进行逻辑运算, 并将结果存入寄存器。逻辑运算指令有针对寄存器间逻辑运算的R3 型,也有针对寄存器与立即数间逻辑运算的R2I 型。表1-29 列出了逻辑运算指令。 表1-29 逻辑运算指令 含有立即数的指令需要将16 位的立即数扩充到32 位后参与运算。扩充的方法有两种, 一种是高16 位全部用0 填充的0 扩充,一种是高16 位用MSB(符号位)填充的符号扩充。0 扩充和符号扩充的示意图请参见图1-100。逻辑运算指令中对立即数使用0 扩充。 图1-100 0扩充和符号扩充 算术运算指令 算术运算指令对作为操作数的寄存器之间,或者寄存器与立即数之间进行算术运算, 并将结果存入寄存器。算术运算指令有针对寄存器间算术运算的R3 型,也有针对寄存器与立即数间算术运算的R2I 型。表1-30 列出了算术运算指令。 表1-30 算术运算指令 加法指令和减法指令分为有符号与无符号两类。这两种指令的区别在于是否检测溢出。溢出是指运算结果超出寄存器或内存可以表示的范围。 下面以8 位数据间的加法运算为例进行说明。Verilog HDL 中以8'b01100100 的形式描述常数。例如,100(8'b01100100)加64(8'b01000000)结果为164(8'b10100100)。观察结果的二进制序列8'b10100100 可以发现,发生了向MSB(符号位)的进位。补码的8'b10100100 十进制值为-92,不是正确答案。因为有符号8 位整数的表现范围为-128~127,正确答案164 不在此范围内。 加法运算发生溢出有两种情况,“正数加正数得到负数”或“负数加负数得到正数”。减法运算发生溢出的情况有“负数减正数得到正数”或“正数减负数得到负数”。也就是说,如果运算结果的符号发生错误就会产生溢出。有符号指令需要检测溢出。如果运算结果有溢出,则产生溢出异常。 寄存器与立即数间的算术运算指令,立即数采用符号扩充。因此寄存器与立即数的算术运算指令中没有减法指令。立即数与负数相加和减法运算是等效的。 移位指令 移位指令对作为操作数的寄存器之间,或者寄存器与立即数之间进行移位,并将结果存入寄存器。移位是将二进制序列整体向左或向右移动的操作。序列向左移动称为左移,向右移动称为右移。图1-101 为移位的示例。移出的比特被废弃,移动产生的空位重新插入0 或1。空位插入0 的移位称为逻辑移位。 图1-101 移位操作 AZ Processor 的移位指令有针对寄存器间移位的R3 型,也有针对寄存器与立即数间移位的R2I 型。表1-31 列出了移位指令。32 位的二进制序列最大可以移动32 位。因此位移量用寄存器或立即数的最低5 位(2 的5 次方为32)表示。 表1-31 移位指令 分支指令 分支指令是改变程序流程的指令。如果分支成立,那么下一条将要执行的指令就会被改变。因为AZ Processor 采用了延迟分支处理,如果分支成立,要等到分支指令的下一条指令执行后再跳转到分支指向的指令。分支指令有R2I 型条件分支指令和R1 型无条件分支指令两种。分支指令如表1-32 所示。 表1-32 分支指令 条件指令对寄存器进行比较,如果条件成立则跳转到目标地址。目标地址由PC 寄存器与符号扩充后的立即数相加得到。立即数字段中指定的地址基于字(32 位)编址方式进行计算,每个字分配一个地址。 目标地址要利用流水线寄存器中PC 的值进行计算。因为PC 中存放的是下一条指令的地址,所以目标地址为“下一条指令的地址+ 立即数”。使用PC 值分支跳转到相对位置的方法称为PC 相对分支。 BE 指令在寄存器间的值相等和BNE 指令在寄存器间的值不等时分支成立。BSGT 指令与BUGT 指令对通用寄存器(GPR)间的值进行比较,当条件GPR[Ra]<GPR[Rb] 成立时分支成立。只是BSGT 指令将寄存器的值作为有符号数值进行比较,而BUGT 指令将寄存器的值作为无符号数值进行比较。 无条件分支指令会强制跳转程序。分支目标地址在寄存器中指定,这种分支称为寄存器间接分支。JMP 指令用来强制跳转到寄存器指定的地址。CALL 指令用来调用寄存器指定地址处的子程序。子程序的调用是指先执行子程序,处理完成后返回到调用处的操作。 JMP 指令与CALL 指令都是无条件跳转语句,在这一点上两者效果是相同的。不同之处在于CALL 指令在GPR31 寄存器中存放两条之后指令的地址。由于CALL 的下一条指令会被当作延迟间隙执行,所以GPR31 中存放的地址为“CALL 指令地址+8”。因为存放了子程序调用处的地址,可以在子程序执行完成后返回。在返回时,使用通用寄存器31 作为操作数并执行JMP 指令。图1-102 为子程序调用流程。 图1-102 子程序调用流程 内存访问指令 内存访问指令用来从内存读取数据或向内存写入数据。内存访问指令格式为R2I 型。表1-33 列出了内存访问指令。 表1-33 内存访问指令 LDW 指令用来从内存中读取1 个字的数据并存入寄存器中,读取地址由寄存器与符号扩充后的立即数相加得到。STW 指令用来将寄存器中1 个字的数值写入内存中,写入地址由寄存器与符号扩充后的立即数相加得到。这种地址指定的方式称为有偏移量的寄存器间接寻址。 执行内存访问指令时要对地址进行对齐检测。如果访问未对齐的地址,则会产生未对齐异常。对齐是指要访问数据的位置在单位数据的边界上。如果访问的地址跨过单位数据的边界线则称为未对齐。 图1-103 为对齐的示例。在按照字节对齐的地址空间中访问1 个字(4 字节)的数据时,如果起始地址为0x00,所访问的数据位于0x00 到0x03。这时数据起始于字的边界,是对齐的。字边界是从0 开始1 个字长的区间。假如从0x01 开始访问1 个字的数据,所访问的数据位于0x01 到0x04。这时,由于0x04 属于下一个字的空间,数据跨越了字的边界。这种访问的地址就是未对齐的。同样,从0x02 开始的1 个字,从0x03 开始的1 个字都是未对齐的。 未对齐会引起多次内存访问的问题。比如要访问从0x01 开始访问1 个字的数据, 而0x01 至0x03 与0x04 存放在不同的内存地址中。因此需要访问内存两次然后将数据进行组合。如果允许这种操作,硬件设计会变得复杂。因此在内存访问指令中进行对齐的检查,如果发生访问未对齐地址的情况,则产生未对齐异常。 图1-103 对齐 特殊指令 特殊指令是用来故意引发异常的指令,它的主要用途是变更CPU 模式。故意引发异常会转移到内核模式。AZ Processor 支持的特殊指令是称为TRAP 的指令,执行TRAP 指令的话会引发陷阱异常。由于TRAP 指令只用来引发异常,属于没有操作数的R0 型指令。系统调用指令如表1-34 所示。 表1-34 特殊命令 特权指令 特权指令是只能在内核模式执行的特殊指令。通过特权指令可以实现CPU 控制寄存器访问、从异常恢复等控制CPU 状态的操作。特权指令如表1-35 所示。 RDCR 指令用来读取控制寄存器的值并写入通用寄存器;WRCR 指令用来将通用寄存器的值写入控制寄存器;EXRT 指令用来从异常恢复。由于特权指令只能在内核模式执行,如果在用户模式执行会引发特权违反异常。 表1-35 特权指令 异常 AZ Processor 的异常一览如表1-36 所示。AZ Processor 的中断也与异常一样处理。CPU 中发生异常时,先将异常发生处指令的地址写入PC 寄存器,再将CPU 模式变更到内核模式,最后跳转到异常向量的地址。异常向量是指异常处理程序的起始地址。 表1-36 异常一览 专栏 指令集架构与微架构 CPU 架构(Architecture)大概分为指令集架构(Instruction Set Architecture)与微架构(Micro Architecture)两种。指令集架构是从CPU 所支持的指令集合、寄存器、异常以及中断等程序员的角度着眼的架构。反之,微架构是较指令集架构更底层,从实际硬件角度着眼的架构。 1.8.3  AZ Processor 的实现 CPU 全局使用的宏 CPU 代码全局使用的宏记述在isa.h 和cpu.h 两个文件中。isa.h 中记载的是与指令集架构有关的宏,cpu.h 中记载的是与微架构有关的宏。表1-37 与表1-38 分别列出了isa.h 与cpu.h 的内容。 表1-37 宏一览(cpu.h) 表1-38 宏一览(isa.h) 通用寄存器 我们首先制作作为CPU 存储区域的通用寄存器。AZ Processor 的指令最大可以指定三个寄存器作为操作数,从其中两个寄存器读取值,然后向另一个寄存器写入值。因此寄存器堆需要有两个读取端口和一个写入端口。通用寄存器的信号线一览如表1-39 所示,源程序如代码1-11 所示。 表1-39 信号一览(gpr.v) 代码1-11 通用寄存器(gpr.v) [Ⅰ]读取访问 (1)处对读取端口0 号、(2)处对读取端口1 号进行读取访问。如果在读取的同时对相同地址进行写入操作,则直接将写入的数据输出。当写入有效信号(we_)有效,并且写入地址(wr_addr)和读取地址(rd_addr_0 或rd_addr_1)一致时,写入的数据(wr_data)输出到输出数据(rd_data_0 或rd_data_1)。 [Ⅱ]异步复位 全部寄存器的值初始化为0。使用for 语句遍历所有寄存器进行初始化操作。 [Ⅲ]写入访问 当写入有效信号(we_)有效时,向指定的写入地址(wr_addr)写入数据(wr_data)。 SPM SPM(Scratch Pad Memory)是CPU 可以不经过总线直接访问的专用内存。SPM 使用一个名为spm 的模块构成。存储器使用FPGA 的Dual Port RAM 实现。表1-40 为spm 使用的宏一览,表1-41 为信号一览,代码1-12 为源程序。 表1-40 宏一览(spm.h) 表1-41 信号线一览(spm.v) 代码1-12 Scratch Pad Memory(spm.v) [Ⅰ]A 端口写入有效信号的生成 当来自IF 阶段的地址有效信号(if_spm_as_)有效、读/ 写信号(if_spm_rw) 为写入(WRITE)时,写入有效信号(wea)为有效。基本上IF 阶段只进行指令的读取,因此只有存储器读取操作。 [Ⅱ]B 端口写入有效信号的生成 当来自MEM 阶段的地址有效信号(mem_spm_as_) 有效、读/ 写信号(mem_spm_rw)为写入(WRITE)时,写入有效信号(web)为有效。 [Ⅲ]存储器的实例化 实例化赛灵思FPGA 的块RAM 模块。 总线接口 总线接口用来对总线的访问进行控制。CPU 在IF 阶段和MEM 阶段访问内存。总线接口接受来自CPU 的内存访问请求,并控制其对总线的访问。 因为AZ Processor 内置了SPM,总线接口要根据访问的地址选择总线和SPM 的访问。因为CPU 与SPM 直接连接,CPU 对SPM 进行读写只需要一个周期。访问总线时需要遵循总线协议进行访问控制。 在总线空闲状态的前提下,当未在执行刷新流水线操作、地址选通有效以及对1 号之外的总线从属进行访问时,可以进行总线访问。当正在执行刷新操作时流水线寄存器无效,无法进行访问。CPU 要访问总线时总线接口转移到总线请求状态,对总线控制权进行请求。如果总线许可信号有效,则表明总线控制权申请成功,总线接口转移到总线访问状态进行总线访问。最后,总线访问完成后使能就绪信号。这时,如果流水线在延迟状态,则总线接口转移到延迟状态等待延迟的解除。如果未发生延迟,则返回空闲状态。 总线接口的状态迁移图如图1-104 所示,信号一览如表1-42 所示。 图1-104 总线接口的状态迁移图 表1-42 信号线一览(bus_if.v) 总线接口由两部分组成,一部分是控制内存访问的组合电路,另一部分是控制总线接口状态的时序电路。内存访问控制部分的程序如代码1-13 所示。 代码1-13 内存访问控制(bus_if.v) [Ⅰ]生成总线从属索引 使用PC 寄存器最高3 位生成总线从属索引。 [Ⅱ]输出的赋值 将输入的地址(addr)、读/ 写(rw)和写入的数据(wr_data)信号输出到SPM。 [Ⅲ]代入默认值 读取的数据(rd_data)初始化为0,SPM 的地址选通信号(spm_as_)和总线忙信号(busy)设置为无效。 [Ⅳ]空闲状态 空闲状态下,如果刷新信号(flush)无效且地址选通信号(as_)有效时, 发生内存访问操作。 (1)处对即将访问的总线从属进行选择。当选中1 号总线从属时为访问SPM。SPM 需要在流水线非延迟的状态下访问。由于延迟状态中的流水线寄存器无法更新,如果这时允许总线的访问,CPU 会不断访问同一地址。因此CPU 需要等待延迟状态解除,在流水线寄存器可以更新时访问总线。 (2)处对是否有延迟的发生进行检测。如果是读取访问,则将从SPM 读取的数据(spm_rd_data)输出到数据输出端口(rd_data)。由于SPM 访问在一个周期即可完成,不需要使能总线忙信号(busy)。如果不是访问1 号总线从属, 则需要访问总线,并使能总线忙信号(busy)。 [Ⅴ]请求总线 总线访问正在进行时,总线忙信号(busy)有效。 [Ⅵ]访问总线 就绪信号(bus_rdy_) 使能时, 总线访问结束。读/ 写信号(rw) 为读取(READ)时,总线上的读取数据(bus_rd_data)的值输出到读取端口(rd_data)。就绪信号(bus_rdy_)无效时,说明总线访问正在进行,使能总线忙信号(busy)。 [Ⅶ]延迟 在等待延迟解除时,如果读/ 写信号(rw)为读取(READ),因为总线访问已经结束,直接将缓冲(rd_buf)中的数据输出到读取端口(rd_data),并使总线忙信号无效。 总线接口控制部分的程序如代码1-14 所示。 代码1-14 总线接口控制(bus_if.v) [Ⅰ]异步复位 复位信号(reset)有效时,寄存器将被初始化。该初始化操作会将总线接口状态(state)设置为空闲状态(BUS_IF_STATE_IDLE),将总线请求信号(bus_ req_)与地址选通信号(bus_as_)设置为无效,读/ 写信号(bus_rw)设置为读取(READ),将地址(bus_addr)、写入的数据(bus_wr_data)、读取缓冲(rd_buf) 清空为0。 [Ⅱ]空闲状态 在空闲状态下,如果刷新信号(flush)无效、地址选通信号有效,则会发生内存访问操作。(1)处选择要访问的总线从属。当访问目标是1 号之外的总线从属时,则会访问总线。访问总线时使能总线请求信号(bus_req_),状态转移到总线请求(BUS_IF_STATE_REQ)状态。同时,将CPU 的输出代入地址信号(bus_ addr)、读写信号(bus_rw)和写入数据信号(bus_wr_data)。 [Ⅲ]请求总线 (2) 处如果总线许可(bus_grnt_) 有效, 状态则会转移到总线访问状态(BUS_IF_STATE_ACCESS),且总线地址选通信号转为(bus_as_)有效。 [Ⅳ]访问总线 接下来将总线地址选通信号(bus_as_)设为无效,在(3)处等待就绪信号(bus_rdy_)。一旦就绪信号(bus_rdy_)有效,总线请求信号(bus_req_)则会无效,并释放总线。然后对地址(bus_addr)、读写信号(bus_rw)和写入数据信号(bus_wr_data)初始化。如果是读取访问的话,在(4)处将读取的数据(bus_rd_ data)保存到读取缓存(rd_buf)中。 总线访问完成时,如果流水线处于延迟状态,则等待延迟的解除。这样是为了避免延迟中对同一地址反复访问。(5)处对是否发生延迟进行检测。延迟信号(stall)有效时,状态迁移到延迟状态(BUS_IF_STATE_STALL);如果延迟信号(stall)转为无效,则状态转移到空闲状态(BUS_IF_STATE_IDLE)。 [Ⅴ]延迟 等待延迟状态的解除。如果延迟信号(stall)转为无效,则状态转移到空闲状态(BUS_IF_STATE_IDLE)。 Instruction Fetch(IF)阶段 IF 阶段的操作有取指令,并决定下一条PC 寄存器的内容。IF 阶段由流水线寄存器与总线接口组成。表1-43 列出了IF 阶段的模块一览。 表1-43 IF 阶段模块一览 IF 阶段是根据PC 寄存器的值进行指令读取的。因为要先确定PC 的值才可以进行指令读取,因此,指令存储到指令寄存器中的操作发生在PC 值确定后的下一个时钟周期。这样,指令和PC 寄存器对应的内容错开一个周期。图1-105 说明了PC 和指令寄存器的时序关系。 图1-105 PC与指令寄存器 由于SPM 也按照时钟上升沿同步读取动作,因此从SPM 读取指令时还要延迟一个周期。这样,指令与PC 寄存器的对应内容会错开两个周期。图1-106 展示了SPM 读取操作时的时序。 使用多个时钟的数字电路设计称为多相时钟电路。由于多相时钟设计会导致电路动作复杂、难以验证,所以不应过多使用。AZ Processor 只在SPM 读取时使用180 度相位的时钟。使用180 度相位时钟的话,SPM 访问的时序会变得紧张。由于在180 度相位时钟上升沿读取的数据,要在相位0 度时钟上升沿进行锁存,实质上要求SPM 数据读取速度为之前的两倍。 图1-106 SPM的读取 图1-107 2相时钟的SPM 读取 IF 阶段的流水线寄存器 IF 阶段的流水线寄存器(if_reg)的信号线一览如表1-44 所示,程序如代码1-15 所示。 表1-44 信号线一览(if_reg.v) 代码1-15 IF 阶段的流水线寄存器(if_reg.v) [Ⅰ]异步复位 复位信号(reset)有效时寄存器将被初始化。PC(if_pc)设置为复位向量(地址0),指令寄存器(if_insn)设置为NOP,流水线数据有效标志位(if_en)设置为无效。 [Ⅱ]流水线寄存器的更新 流水线寄存器在延迟信号(stall)无效时才能更新。 (1)处对流水线寄存器进行刷新操作。刷新信号(flush)有效时,PC(if_ PC)设置为新地址(new_pc),指令寄存器(if_insn)设置为NOP,流水线数据有效标志位(if_en)设置为无效。 (2)处对分支进行处理。分支信号(br_taken)有效时,PC(if_pc)被设置为分支目的地址(br_addr)。指令寄存器(if_insn)设置为读取的指令(insn)、流水线数据有效标志位(if_en)设置为有效。 (3)处对PC 的步进进行处理。在既没发生延迟也没发生分支的情况下,PC (if_pc)更新为下一条指令的地址(if_pc + 1'd1)。指令寄存器(if_insn)设置为读取的指令(insn)、流水线数据有效标志位(if_en)设置为有效。 IF 阶段的顶层模块 IF 阶段的顶层模块用于连接总线接口与IF 阶段的流水线寄存器。IF 阶段的顶层模块的连接图如图1-108 所示。由于IF 阶段只进行指令的读取,总线接口的读/ 写信号(rw)设置为读取(READ),写入的数据(wr_data)设置为0。由于每个时钟周期都会进行指令的读取,持续将地址有效信号(as_)设置为有效(ENABLE_)。 图1-108 端口连接图(if_stage.v) Instruction Decode(ID)阶段 ID 阶段对指令进行解码并生成必要的信号。数据的直通、Load 冒险的检测、分支的判定都在这一阶段进行。ID 阶段由指令解码器和流水线寄存器构成。表1-45 列出了ID 阶段的模块一览。 表1-45 ID 阶段模块一览 指令解码器 指令解码器从输入的指令码中分解出各个指令字段,生成地址、数据和控制等信号。数据的直通、Load 冒险的检测、分支的判定也在这个指令解码器中进行。表1-46 为指令解码器的信号线一览。 表1-46 信号线一览(decoder.v) 首先,指令字段的分解和必要信号的生成部分程序如代码1-16 所示。 代码1-16 内部信号生成与输出赋值(decoder.v) [Ⅰ]指令字段的分解 此处从输入的指令码中分解出各个指令字段。 [Ⅱ]立即数字段的扩充 此处将16 位立即数扩充到32 位。符号扩充的立即数赋给imm_s,0 扩充的立即数赋给imm_u。符号扩充的立即数用该立即数字段的MSB 填充高16 位。0 扩充则用0 填充高16 位。 [Ⅲ]寄存器读取地址 此处对寄存器读取地址进行赋值。通用寄存器读取地址使用指令的Ra 字段(ra_addr)和Rb 字段(rb_addr)。控制寄存器读取地址使用Ra 字段(ra_addr)。 [Ⅳ]通用寄存器的读取数据 此处定义通用寄存器的读取数据的信号。信号定义分为无符号(ra_data、rb_ data)与有符号(s_ra_data、s_rb_data)两种。有符号信号是通过用$signed() 处理无符号信号得到的。 [Ⅴ]地址的生成 此处生成指令解码器中使用的地址。由于延迟间隙的存在,CALL 指令的返回地址为两条指令之后的地址。因为PC(if_pc)中已经存放了下一条指令的地址,返回地址(ret_addr)为PC(if_pc)中的地址加1。 分支目标地址(br_target)代入PC 值加符号扩充后的立即数(imm_s)。因为地址为30 位,32 位立即数只取低位的30 位参与加法运算。 跳转目标地址(jr_target)代入Ra 寄存器(ra_data)的值。由于跳转目的地址(jr_ target)为字编址,而Ra 寄存器(ra_data)保存的地址为字节编址,因此只使用Ra 寄存器(ra_data)高位的30 位。 下面,与数据直通相关的程序如代码1-17 所示。 代码1-17 数据直通(decoder.v) [Ⅰ]Ra 寄存器的数据直通 因为流水线前的结果会成为最新值,直通的比较按EX 阶段、MEM 阶段的顺序进行。 来自EX 阶段的数据直通的产生条件为:ID/EX 流水线寄存器有效、Ra 寄存器的读取地址(ra_addr)与寄存器写入地址(id_dst_addr)相等,且寄存器的写入有效信号(id_gpr_we_)为有效。 来自MEM 阶段的数据直通的产生条件为:EX/MEM 流水线寄存器有效、Ra 寄存器的读取地址(ra_addr)与寄存器写入地址(ex_dst_addr)相等,且寄存器的写入有效信号(ex_gpr_we_)为有效。无法进行直通时,直接使用寄存器堆读取值。 [Ⅱ]Rb 寄存器的数据直通 来自EX 阶段的数据直通的产生条件为:ID/EX 流水线寄存器有效、Rb 寄存器的读取地址(rb_addr)与寄存器写入地址(id_dst_addr)相等,且寄存器的写入有效信号(id_gpr_we_)为有效。来自MEM 阶段的数据直通的产生条件为:EX/ MEM 流水线寄存器有效、Rb 寄存器的读取地址(rb_addr)与寄存器写入地址(ex_dst_addr)相等,且寄存器的写入有效信号(ex_gpr_we_)为有效。无法进行数据直通时,直接使用寄存器堆读取值。 Load 冒险检测程序如代码1-18 所示。 代码 1-18 Load 冒险检测(decoder.v) [Ⅰ]Load 冒险检测 Load 冒险产生的条件为:ID/EX 流水线寄存器中存放的之前的指令为Load 指令,通用寄存器的写入地址与当前指令的读取地址相等。ID/EX 流水线寄存器有效、内存操作(id_mem_op)为Load 指令(MEM_OP_LDW),且之前指令的写入地址(id_dst_addr)与Ra 寄存器的地址(ra_addr)或Rb 寄存器的地址(rb_addr) 相等时使能Load 冒险信号(ld_hazard)。 下面对指令解码器的主要部分——指令解码程序进行说明。各指令与相应的信号线解码结果如表1-47 所示。表1-47 中最上方的灰色行表示的是各信号的默认值。各指令相应信号线的值如果等于默认值,则标记为灰色。指令解码器的程序中,首先将各个信号初始化为默认值,然后根据解码结果,只将与默认值不同的信号赋予新值。代码1-19 列出的是信号初始化部分程序。 代码1-19 内部信号初始化(decoder.v) [Ⅰ]默认信号的默认值 此处依据表1-47 所示的默认值进行初始化。 下面,代码1-20 展示了逻辑运算指令解码部分程序。 代码1-20 逻辑运算指令解码(decoder.v) 表1-47 解码结果 [Ⅰ]ANDR 指令解码 此处将ALU 操作(alu_op)设置为AND(ALU_OP_AND),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_ we_)设置为有效。 [Ⅱ]ANDI 指令解码 此处将ALU 操作(alu_op)设置为AND(ALU_OP_AND),ALU 的1 号输入(alu_in_1)代入0 扩充后的立即数(imm_u),通用寄存器写入有效信号(gpr_ we_)设置为有效。 [Ⅲ]ORR 指令解码 此处将ALU 操作(alu_op)设置为OR(ALU_OP_OR),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_we_) 设置为有效。 [Ⅳ]ORI 指令解码 此处将ALU 操作(alu_op) 设置为OR(ALU_OP_OR),ALU 的1 号输入(alu_in_1)代入0 扩充后的立即数(imm_u),通用寄存器写入有效信号(gpr_ we_)设置为有效。 [Ⅴ]XORR 指令解码 此处将ALU 操作(alu_op)设置为XOR(ALU_OP_XOR),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_ we_)设置为有效。 [Ⅵ]XORI 指令解码 此处将ALU 操作(alu_op) 设置为XOR(ALU_OP_XOR),ALU 的1 号输入(alu_in_1)代入0 扩充后的立即数(imm_u),通用寄存器写入有效信号(gpr_ we_)设置为有效。 接下来,我们对算术运算指令的解码程序进行说明,如代码1-21 所示。 代码1-21 算术运算指令解码(decoder.v) [Ⅰ]ADDSR 指令解码 此处将ALU 操作(alu_op)设置为有符号加法(ALU_OP_ADDS),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅱ]ADDSI 指令解码 此处将ALU 操作(alu_op)设置为有符号加法(ALU_OP_ADDS),ALU 的1 号输入(alu_in_1)代入符号扩充后的立即数(imm_s),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅲ]ADDUR 指令解码 此处将ALU 操作(alu_op)设置为无符号加法(ALU_OP_ADDU),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅳ]ADDUI 指令解码 此处将ALU 操作(alu_op)设置为无符号加法(ALU_OP_ADDU),ALU 的1 号输入(alu_in_1)代入符号扩充后的立即数(imm_s),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅴ]SUBSR 指令解码 此处将ALU 操作(alu_op)设置为有符号减法(ALU_OP_SUBS),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅵ]SUBUR 指令解码 此处将ALU 操作(alu_op)设置为无符号减法(ALU_OP_SUBU),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_we_)设置为有效。 接下来,我们对移位指令的解码程序进行说明,如代码1-22 所示。 代码1-22 移位指令解码(decoder.v) [Ⅰ]SHRLR 指令解码 此处将ALU 操作(alu_op)设置为逻辑右移(ALU_OP_SHRL)、通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr)、通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅱ]SHRLI 指令解码 此处将ALU 操作(alu_op)设置为逻辑右移(ALU_OP_SHRL),ALU 的1 号输入(alu_in_1)代入0 扩充后的立即数(imm_u),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅲ]SHLLR 指令解码 此处将ALU 操作(alu_op)设置为逻辑左移(ALU_OP_SHLL),通用寄存器写入地址(dst_addr)中记入Rc 寄存器(rc_addr),通用寄存器写入有效信号(gpr_we_)设置为有效。 [Ⅳ]SHLLI 指令解码 此处将ALU 操作(alu_op)设置为逻辑左移(ALU_OP_SHLL),ALU 的1 号输入(alu_in_1)代入0 扩充后的立即数(imm_u),通用寄存器写入有效信号(gpr_we_)设置为有效。 接下来,我们对分支指令的解码程序进行说明,如代码1-23 所示。 代码1-23 分支指令解码(decoder.v) [Ⅰ]BE 指令解码 此处将分支目标地址(br_target)输出给分支地址(br_addr),并设置分支符号位(br_flag)为有效。Ra 寄存器(ra_data)与Rb 寄存器(rb_data)相等时,分支成立信号(br_taken)有效。 [Ⅱ]BNE 指令解码 此处将分支目标地址(br_target)输出给分支地址(br_addr),并设置分支符号位(br_flag)为有效。Ra 寄存器(ra_data)与Rb 寄存器(rb_data)不等时,分支成立信号(br_taken)有效。 [Ⅲ]BSGT 指令解码 此处将分支目标地址(br_target)输出给分支地址(br_addr),并设置分支符号位(br_flag)为有效。Rb 寄存器(s_rb_data)比Ra 寄存器(s_ra_data)大时, 分支成立信号(br_taken)有效。因为BSGT 指令为有符号比较,对寄存器进行比较时,使用有符号信号。 [Ⅳ]BUGT 指令解码 此处将分支目标地址(br_target)输出给分支地址(br_addr),并设置分支符号位(br_flag)为有效。Rb 寄存器(rb_data)比Ra 寄存器(ra_data)大时,分支成立信号(br_taken)有效。 [Ⅴ]JMP 指令解码 此处将分支目标地址(jr_target)输出给分支地址(br_addr),并设置分支符号位(br_flag)为有效。由于JMP 指令为无条件跳转,分支成立信号(br_taken)总是有效。 [Ⅵ]CALL 指令解码 此处将分支目标地址(jr_target)输出给分支地址(br_addr),并设置分支符号位(br_flag)为有效。由于CALL 指令为无条件跳转,分支成立信号(br_taken) 总是有效。因为要将CALL 指令的返回地址(ret_addr)写入31 号通用寄存器,返回地址(ret_addr)要代入ALU 的0 号输入(alu_in_0)。然后将通用寄存器写入地址(dst_addr)指定为31 号通用寄存器的地址,并使能通用寄存器写入有效信号(gpr_we_)。由于返回地址(ret_addr)为30 位的字编址格式,最低两位用0 扩充, 然后代入ALU 的0 号输入(alu_in_0)。 接下来,我们对内存访问指令的解码程序进行说明,如代码1-24 所示。 代码1-24 内存访问指令解码(decoder.v) [Ⅰ]LDW 指令解码 为了进行地址计算,需要将ALU 操作(alu_op)设置为无符号加法(ALU_ OP_ADDU),并将符号扩充后的立即数(imm_s)代入ALU 的1 号输入(alu_ in_1)。内存操作(mem_op)设置为字读取(MEM_OP_LDW),并使能通用寄存器写入有效信号(gpr_we_)。 [Ⅱ]STW 指令解码 为了进行地址计算,需要将ALU 操作(alu_op)设置为无符号加法(ALU_ OP_ADDU),并将符号扩充后的立即数(imm_s)代入ALU 的1 号输入(alu_ in_1)。内存操作(mem_op)设置为字写入(MEM_OP_STW)。 接下来,我们对特殊指令的解码程序进行说明,如代码1-25 所示。 代码1-25 特殊指令解码(decoder.v) [Ⅰ]TRAP 指令解码 TRAP 指令是引发陷阱异常的指令,因此将陷阱异常的异常代码(ISA_EXP_ TRAP)代入异常代码信号(exp_code)中。 接下来,我们对特权指令的解码程序进行说明,如代码1-26 所示。 代码1-26 特权指令解码(decoder.v) [Ⅰ]RDCR 指令解码 此处将从控制寄存器读取的值(creg_rd_data)代入ALU 的1 号输入,并使能通用寄存器写入有效信号(gpr_we_)。特权指令在内核模式之外模式执行时会引发特权异常。异常代码信号(exp_code)代入特权违反异常的异常代码(ISA_EXP_ PRV_VIO)。 [Ⅱ]WRCR 指令解码 此处将控制操作(ctrl_op)设置为写入(CTRL_OP_WRCR)。 [Ⅲ]EXRT 指令解码 此处将控制操作(ctrl_op)设置为异常恢复操作(CTRL_OP_EXRT)。 最后,当读入未定义指令时的处理程序如代码1-27 所示。 代码1-27 未定义指令的处理(decoder.v) [Ⅰ]未定义指令的处理 当读入未定义的指令时,在此处引发未定义指令异常。异常代码信号(exp_ code)代入未定义指令的异常代码(ISA_EXP_UNDEF_INSN)。 ID 阶段流水线寄存器 ID 阶段流水线寄存器(id_reg)的信号线一览如表1-48 所示,程序如代码1-28 所示。 表1-48 信号线一览(id_reg.v) 代码1-28 ID 阶段流水线寄存器(id_reg.v) [Ⅰ]异步复位 复位信号(reset)有效时寄存器会被初始化。因为复位时流水线内的数据无效,初始化时,此处将全部控制信号设为无效,数据信号设为0。 [Ⅱ]流水线寄存器的更新 流水线寄存器在延迟信号(stall)无效时才可更新。(1)处执行流水线寄存器的刷新操作。刷新信号(flush)有效时,所有流水线寄存器都会被初始化。(2)处执行流水线寄存器的更新操作。将指令解码的结果存入流水线寄存器。 ID 阶段顶层模块 ID 阶段顶层模块用来连接指令解码器与ID 阶段流水线寄存器。图1-109 展示了ID 阶段顶层模块的连接图。 图1-109 端口连接图(id_stage.v) Execution(EX)阶段 EX 阶段主要进行运算和中断检测操作。EX 阶段由算术逻辑运算单元和流水线寄存器构成。表1-49 为EX 阶段模块一览。 表1-49 EX 阶段模块一览 ALU ALU 根据输入指定的操作对数据进行处理,并输出处理结果。ALU 的输入为一个操作码和两个数据,输出为运算结果和溢出信号。ALU 的框图如图1-110 所示,信号线一览如表1-50 所示,源程序如代码1-29 所示。 图1-110 ALU模块图 表1-50 信号线一览(alu.v) 代码1-29 ALU(alu.v) [Ⅰ]有符号信号的生成 此处将输入信号(in_0, in_1)与输出信号(out)生成为有符号信号。有符号信号将被用在有符号加法和减法的溢出检测中。 [Ⅱ]算术逻辑运算 此处进行以下9 种运算操作:(1)逻辑与(AND)、(2)逻辑或(OR)、(3) 逻辑异或(XOR)、(4)有符号加法、(5)无符号加法、(6)有符号减法、(6)无符号减法、(8)逻辑右移、(9)逻辑左移。 32 位的移位运算,最大位移量为32 位。因此(8)和(9)的移位运算中,右边第二项输入使用5 位(in_1[`ShAmountLoc])。5 位可以表达的最大值为2 的5 次方, 即32。不进行任何运算(No Operation)时,在(10)处直接输出输入0 的值。 ALU 的NOP 在CALL、WRCR、RDCR 等指令执行时,为了将ID 阶段读取的寄存器的值按原样写回时使用。 [Ⅲ]溢出检测 在进行有符号加法和减法运算时,需要检测溢出。因此,(11)、(12)处分别对加法和减法的溢出进行检测。 加法溢出发生的条件为:正数加正数结果为负数,或负数加负数结果为正数。在处理有符号加法后,在(11)处检测该条件,如果条件满足则使能溢出信号(of)。 减法溢出发生的条件为:负数减去正数结果为正数,或正数减去负数结果为负数。在处理有符号减法后,在(12)处检测该条件,如果条件满足则使能溢出信号(of)。在处理有符号加法、减法以外的运算时,在(13)处设置溢出信号(of)为无效。 EX 阶段流水线寄存器 EX 阶段流水线寄存器的信号线一览如表1-51 所示,源程序如代码1-30 所示。 表1-51  信号线一览(ex_reg.v) 代码1-30 EX 阶段流水线寄存器(ex_reg.v) [Ⅰ]异步复位 复位信号(reset)有效时寄存器会被初始化。因为复位时流水线内的数据无效,初始化时,此处将全部控制信号设为无效,数据信号设为0。 [Ⅱ]流水线寄存器的更新 流水线寄存器在延迟信号(stall)无效时才可更新。 (1)处对流水线寄存器进行刷新操作。当刷新信号(flush)有效时,所有流水线寄存器将被初始化。 (2)处对中断进行检测。如果中断检测信号(int_detect)有效,则中止正在执行的指令,并将异常代码(ex_exp_code)设置为外部中断异常(ISA_EXP_EXT_ INT)。中止指令操作时,将内存操作信号(ex_mem_op)、控制寄存器操作信号(ex_ctrl_op)和通用寄存器写入有效信号(ex_gpr_we_)设置为无效。同时,将内存写入数据(ex_mem_wr_data)、通用寄存器写入地址(mem_dst_addr)和处理结果(ex_out)设置为0。 (3)处对溢出异常进行检测。如果溢出信号(alu_of)有效,则中止正在执行的指令操作,并将异常代码(ex_exp_code)设置为溢出异常(ISA_EXP_ OVERFLOW)。 (4)处对流水线寄存器进行更新。运算处理的结果在此处被存储到流水线寄存器。 EX 阶段顶层模块 EX 阶段顶层模块用来连接ALU 与EX 阶段流水线寄存器。图1-111 展示了EX 阶段顶层模块的连接图。 Memory(MEM)阶段 MEM 阶段主要负责内存的访问。在执行LDW 和STW 等指令时,内存访问操作是在MEM 阶段进行的。MEM 阶段由内存访问控制模块、流水线寄存器、以及总线接口构成。表1-52 为MEM 阶段的模块一览。 表1-52 MEM 阶段模块一览 图1-111 端口连接图(ex_stage.v) 内存访问控制模块 内存访问控制模块基于从EX 阶段流水线寄存器输入的内存操作(ex_mem_op),实施内存访问操作。内存访问控制模块的信号线一览如表1-53 所示,源程序如代码1-31 所示。 表1-53 信号线一览(mem_ctrl.v) 代码1-31 内存访问控制模块(mem_ctrl.v) [Ⅰ]输出的赋值 此处进行一系列输出的赋值:EX 阶段的写入数据(ex_mem_wr_data)代入写入数据(wr_data),EX 阶段输出(ex_out)的高30 位代入地址(addr),EX 阶段输出(ex_out)的低2 位代入字节偏移(offset)。 [Ⅱ]默认值 地址选通信号(as_)默认设置为无效,读/ 写信号默认设置为读取(READ), 输出信号(out)默认设置为0。 [Ⅲ]LDW 指令 LDW 指令执行时,需要对地址是否按字对齐进行检测。字节偏移(offset)为0 (BYTE_OFFSET_WORD)时,地址是对齐的,因此直接使能地址选通信号。LDW 为读取访问指令,要将读取数据(rd_data)赋值到输出(out)。字节偏移(offset) 不为0(BYTE_OFFSET_WORD)时,地址未对齐,使能未对齐信号(miss_align)。 [Ⅳ]STW 指令 STW 指令执行时,需要对地址是否按字对齐进行检测。字节偏移(offset)为0(BYTE_OFFSET_WORD)时,地址是对齐的,因此直接使能地址选通信号。字节偏移(offset)不为0(BYTE_OFFSET_WORD)时,地址未对齐,使能未对齐信号(miss_align)。 [Ⅴ]无内存访问 在没有内存访问操作发生时,直接将MEM 阶段的输出(ex_out)赋值给输出(out)。 MEM 阶段流水线寄存器 MEM 阶段流水线寄存器的信号线一览如表1-54 所示,源程序如代码1-32 所示。 表1-54 信号线一览(mem_reg.v) 代码1-32 MEM 阶段流水线寄存器(mem_reg.v) [Ⅰ]异步复位 复位信号(reset)有效时寄存器会被初始化。因为复位时流水线内的数据无效,初始化时,此处将全部控制信号设为无效,数据信号设为0。 [Ⅱ]流水线寄存器的更新 流水线寄存器在延迟信号(stall)无效时才可更新。 (1)处对流水线寄存器进行刷新操作。当刷新信号(flush)有效时,所有流水线寄存器将被初始化。 (2)处对未对齐异常进行检测。未对齐信号(miss_align)有效时,中止正在进行的操作,将异常代码(mem_exp_code)设置为未对齐异常(ISA_EXP_MISS_ ALIGN)。中止指令操作时,控制寄存器操作信号(ex_ctrl_op)、通用寄存器写入有效信号(ex_gpr_we_)设置为无效。同时,将通用寄存器写入地址(mem_dst_addr)、处理结果(mem_out)设置为0。 (3)处对流水线寄存器进行更新。内存操作的结果在此处被存储到流水线寄存器。 MEM 阶段顶层模块 MEM 阶段顶层模块用来连内存访问控制模块、MEM 阶段流水线寄存器、与总线接口。图1-112 展示了MEM 阶段顶层模块的端口连接图。 图1-112 端口连接图(mem_stage.v) CPU 控制模块 CPU 控制模块进行对保存CPU 状态的控制寄存器进行管理,并对流水线进行控制。CPU 控制模块由一个被称为ctrl 的模块构成。CPU 控制模块中设有设置和保存CPU 状态的控制寄存器。表1-55 为CPU 控制寄存器的一览。 表1-55 CPU 控制寄存器 控制寄存器0 :状态 [0] :执行模式寄存器(EM:Execution Mode) 用于设定CPU 的执行模式。该位为0 时表示CPU 处于内核模式,为1 时表示CPU 处于用户模式。 [1] :中断有效(IE:Interrupt Enable) 设置该位时中断有效。 控制寄存器1 :前一个状态 [0] :执行模式寄存器(EM:Execution Mode) 用于保存异常发生前的CPU 执行模式。 [1] :中断有效(IE:Interrupt Enable) 用于保存异常发生前的中断有效位。 控制寄存器2 :程序计数器 [31:2] :程序计数器(PC:Program Counter) 用于读取当前程序计数器。 控制寄存器3 :异常程序计数器 [31:2] :异常程序计数器(EPC:Exception Program Counter) 用于保存异常发生时的程序计数器。 控制寄存器4 :异常向量 [31:2] :异常向量(EXP_VECTOR:Exception Vector) 用于设定异常处理程序地址。异常发生时跳转到异常向量所存储的地址。 控制寄存器5 :异常原因寄存器 [2:0] :异常代码(CODE:Exception Code) 用于存储所发生异常的异常代码。 [3] :延迟间隙标志位(D:Delay Slot Flag) 发生延迟间隙异常时,该标志位有效。 控制寄存器6 :中断屏蔽 [7:0] :中断屏蔽(MASK:Interrupt Mask) 用于设定中断屏蔽(也称为中断掩字)。通过设置该寄存器,可以屏蔽指定中断。 控制寄存器7 :中断请求 [7:0] :中断请求(IRQ:Interrupt Request) 用于读取中断请求。 控制寄存器29 :ROM 容量 [31:0] :ROM 容量(ROM_SIZE:ROM Size) 用于读取所用ROM 的容量。 控制寄存器30 :SPM 容量 [31:0] :SPM 容量(SPM_SIZE:SPM Size) 用于读取所用SPM 的容量。 控制寄存器31 :CPU 信息 [31:24] :制作年份(YEAR:Year) 用于读取制作年份。制作年份为1970 加该寄存器中的值。 [23:16] :制作月份(MONTH:Month) 用于读取制作月份。 [15:8] :版本号(VER:Version) 用于读取CPU 的版本号。 [7:0] :修订号(REV:Revision) 用于读取CPU 的修订号。 控制寄存器0~7 用来控制CPU 操作或读取CPU 状态。控制寄存器29~31 用来读取内存容量、CPU 版本等CPU 相关信息。 表1-56 为CPU 控制模块的信号线一览。 表1-56 信号线一览(ctrl.v) CPU 控制单元针对各个流水线阶段的延迟和刷新操作进行控制。代码1-33 展示了生成CPU 控制信号的部分代码。 代码1-33 CPU 控制信号的生成(ctrl.v) [Ⅰ]延迟信号的赋值 延迟信号(stall) 在IF 阶段的忙信号(if_busy) 或MEM 阶段的忙信号(mem_busy)任何一个有效时有效。由于IF 阶段发生Load 冒险时也需要延迟,最终延迟信号(stall)与Load 冒险信号(ld_hazard)进行OR 运算。 [Ⅱ]刷新信号的赋值 由于ID 阶段发生Load 冒险时也需要刷新流水线,最终刷新信号(flush)与Load 冒险信号(ld_hazard)进行OR 运算。 [Ⅲ]流水线刷新的控制 此处生成刷新信号(flush)与刷新时的新的PC 寄存器(new_pc)值。(1)处指定默认值。初始化刷新信号(flush)为无效、新PC 寄存器(new_pc)值为0。在有异常发生时,刷新流水线并将CPU 的执行引入异常处理程序。(2)处使能刷新信号(flush)并将异常向量(exp_vector)写入新PC 寄存器(new_pc)。 通过执行EXRT 指令从异常恢复时,刷新流水线,并从异常程序计数器重启程序。(3)处使能刷新信号(flush)并将异常程序计数器(epc)写入新PC 寄存器(new_pc)。 执行WRCR 指令对控制寄存器进行写入操作后,之后的指令需要反映出CPU 状态变化。因此要将流水线刷新一次再执行下面的指令。由于MEM 阶段的PC (mem_pc)指向WRCR 指令的下一个地址,因此从MEM 阶段的PC(mem_pc) 的地址开始执行。(4)处使能刷新信号(flush)并将MEM 阶段的PC(mem_pc) 写入新PC 寄存器(new_pc)。 接下来,我们通过代码1-34 对中断检测部分程序进行说明。 代码1-34 中断检测(ctrl.v) [Ⅰ]中断检测 中断有效信号(int_en)有效,并且有任何中断请求发生的情况下,中断检测信号(int_detect)有效。中断请求信号(irq)会在中断屏蔽(mask)对应位为1 时被屏蔽。 应用屏蔽时,将中断屏蔽(mask)所有位翻转,并与中断请求信号(irq)进行AND 运算。然后取所有位的OR 运算结果,如果存在运算结果为1 的中断请求信号,则检测出有中断产生。 图1-113 展示了中断检测的逻辑。中断屏蔽(mask)可以针对各个中断请求(irq) 设置有效或无效。而中断有效信号则可以设置全体中断是否有效。 图1-113 中断检测的逻辑 接下来,通过代码1-35 对读取控制寄存器部分的源程序进行说明。 代码1-35 控制寄存器的读取(ctrl.v) [Ⅰ]读取控制寄存器 控制寄存器的读取基本上只是根据输入的读取地址(creg_rd_addr)将对应控制寄存器的值输出到数据读取信号(creg_rd_data)。各控制寄存器位宽不同,未使用的位用0 填充,然后输出到数据读取信号(creg_rd_data)。(1)处~(11)处分别对控制寄存器0~7、29~31 进行读取。(12)处将0 作为默认值代入。 对CPU 进行控制部分的源程序如代码1-36 所示。这部分进行控制寄存器的写入、异常的发生和恢复等控制操作。 代码1-36 CPU 的控制(ctrl.v) [Ⅰ]异步复位 复位信号(reset)有效时,全部寄存器将被初始化。复位时,执行模式(exe_ mode)被设为内核模式(CPU_KERNEL_MODE),中断有效信号(int_en)被设为无效,中断屏蔽(mask)被设置为全屏蔽状态。其他控制寄存器被设为无效、数据初始化为0。 [Ⅱ]PC 和分支标志位的保存 在MEM 阶段的流水线寄存器的数据有效,且没有延迟发生的情况下,在此处更新CPU 的状态。之前PC(pre_pc)保存MEM 阶段的PC(mem_pc)、分支标志位(br_flag)保存MEM 阶段的分支标志位(mem_br_flag)。这些信息在异常发生时使用。 [Ⅲ]发生异常 MEM 阶段异常代码(mem_exp_code)被设置时说明有异常发生。异常发生时,将当前执行模式(exe_mode)保存到之前执行模式(pre_exe_mode)中,并将当前中断有效信号(int_en)保存到之前中断有效信号(pre_int_en)中。然后,将执行模式(exe_mode)设置为内核模式(CPU_KERNEL_MODE)、中断有效信号(int_en)设置为无效。将MEM 阶段异常代码(mem_exp_code)保存到异常代码(exp_code)中。分支标志位(br_flag)有效时,因为前一条指令为分支指令,需要再此插入延迟间隙标志位(dly_flag)。最后,由于流水线寄存器中保存的PC 指向下一条指令的地址,需要将之前的PC(pre_pc)值代入EPC(epc)。 [Ⅳ]EXRT 指令 从异常恢复时执行EXRT 指令。从异常恢复时,将异常发生时备份的之前执行模式(pre_exe_mode)恢复到当前执行模式(exe_mode),之前中断有效信号(pre_ int_en)恢复到当前中断有效信号(int_en),将CPU 恢复到异常发生前的状态。 [Ⅴ]WRCR 指令 执行WRCR 指令可将MEM 阶段输出信号(mem_out)存入写入地址(mem_ dst_addr)指定的控制寄存器中。(1)~(6)处对相应的控制寄存器执行写入操作。 CPU 顶层模块 最后将流水线的各个阶段模块及其通用寄存器、CPU 控制模块以及SPM 相连接, 就完成了整个CPU 的设计部分。CPU 的顶层模块由名为cpu 的顶层模块构成。CPU 的顶层模块端口连接图如图1-114 所示。 1.8.4  小结 本节讲解了CPU 的设计与实现。虽然AZ Processor 指令不多,流水线结构也相对简单,但我们实现了中断和异常的支持,并搭载了总线接口,是一个单纯但完整的CPU。通过制作AZ Processor,读者们可以深入理解CPU 的构造和动作原理。 专栏 计算机架构相关书籍 作りながら学ぶコンピュータアーキテクチャ(天野英晴、西村克信著、培風館) (中文译名:《边做边学计算机架构》) 本书使用Verilog HDL 制作16 位的CPU,对计算机架构进行了系统、通俗易懂的讲解。本书难度属于大学和大专教材水平,适合初学者学习。阅读本书虽然需要一定的编程和电路基础,但作者对基础知识的讲解简单易懂,可以作为读者们的第一本计算机架构入门书。 コンピュータの構成と設計 第4版(デイビッド・A・パターソン、ジュン・L・ヘネシー著、成田光彰翻訳、日経BP 社) (中文版:《计算机组成与设计:硬件、软件接口(原书第4 版)》,康继昌 、樊晓桠、安建峰等译,机械工业出版社,2012.1) 本书是系统讲解计算机架构的世界名著。作者David A. Patterson 与John L. Hennessy 是计算机架构的世界级权威,他们写的教科书被作为计算机基础教育的标准用书。本书是计算机架构专业人员必备的一本著作。 图1-114 CPU顶层模块连接图

展开全文

推荐文章

猜你喜欢

附近的人在看

推荐阅读

拓展阅读

《CPU自制入门》其他试读目录

• 1.1 序
• 1.2 计算机系统
• 1.3 数字电路基础
• 1.4 Verilog HDL 语言
• 1.5 系统蓝图
• 1.6 总线的设计与实现
• 1.7 存储器的设计与实现
• 1.8 AZ Processor 的设计与实现 [当前]
• 1.9 I/O 的设计与实现
• 1.10 AZPR SoC 整体连接
• 1.11 AZPR SoC 的仿真
• 1.12 本章总结