首页 > 代码库 > 自己动手写处理器之第三阶段——教学版OpenMIPS处理器蓝图

自己动手写处理器之第三阶段——教学版OpenMIPS处理器蓝图

将陆续上传本人写的新书《自己动手写处理器》(尚未出版),今天是第十篇,我尽量每周四篇


      从本章开始将一步一步地实现教学版OpenMIPS处理器。本章给出了教学版OpenMIPS的系统蓝图,首先介绍了系统的设计目标,其中详细说明了OpenMIPS处理器计划实现的5级流水线。3.2节给出了OpenMIPS处理器的接口示意图,及各个接口的作用。3.3节简单解释了各个源代码文件的作用。最后描述了OpenMIPS处理器的实现方法,读者将发现本书给出的实现方法与现有书籍的方法完全不同,更加易于理解、便于实践。

3.1 系统设计目标

3.1.1 设计目标

      本书第二部分设计实现的教学版OpenMIPS处理器,是一款具有哈佛结构的32位标量处理器,兼容MIPS32 Release 1指令集架构(后文不再注明Release 1),这样的好处是可以使用现有的MIPS编译环境,如:GCC编译器等。OpenMIPS的设计目标如下。

  •  五级整数流水线,分别是:取指、译码、执行、访存、回写
  •  哈佛结构,分开的指令、数据接口
  •  32个32位整数寄存器
  •  大端模式
  •  向量化异常处理,支持精确异常处理
  •  支持6个外部中断
  •  具有32bit数据、地址总线宽度
  •  能实现单周期乘法
  •  支持延迟转移
  •  兼容MIPS32指令集架构,支持MIPS32指令集中的所有整数指令
  •  大多数指令可以在一个时钟周期内完成

      上述设计目标都很容易理解,除了延迟转移和精确异常,前者将在第8章“转移指令的实现”中介绍,后者将在第11章“异常相关指令的实现”中介绍。

3.1.2 五级整数流水线

      本书讲的是计算机中的流水线,首先听一听维基百科中对计算机流水线的定义:流水线是指将计算机指令处理过程拆分为多个步骤,并通过多个硬件处理单元并行执行来加快指令执行速度。此处有两个关键词:(1)拆分;(2)并行。指令的处理从直观上分析至少可以拆分为三步:从存储器取出指令、解释指令、按照解释的结果执行,简单的说就是:取指、译码、执行。如果我们只有一个硬件处理单元,这个单元既要取指,又要译码,还要执行,假设上述三种操作都可以在时间T完成,那么一条指令的处理时间为3T,n条指令的处理时间就为3nT,但是如果我们设计有三个硬件单元,分别做这三项工作的一项,那么就可以在执行的同时对下一条指令译码,在对下一条指令译码的同时还可以再取一条指令,这就是经典的三级流水线,如图3-1所示。


      从图3-1可知,在三级流水线上执行3条指令所需时间为5T,而如果没有使用流水线则需要9T,流水线确实加快了指令执行。ARM7采用的就是三级流水线。但世间事没有这么简单完美的,上面假设取指、译码、执行需要的时间都是T,实际并非如此,比如取指的时间就可能很长,假设取指需要2T时间,那么如图3-2所示。


      可见在3T-4T的时间段、5T-6T的时间段,流水线在等待取指结束,此时译码阶段、执行阶段都停滞,这样一来自然就慢下来,最后,执行3条指令所需时间为8T。解决取指时间过长的措施是引入缓存(Cache),处理器从缓存读取指令只需要1个时钟周期。

      还有一种情况是执行阶段时间过长,比如指令为加载/存储指令(Load/Store)时,由于涉及到访问存储器,执行阶段所需的时间就可能大于T,此时也会导致流水线停滞。为了解决这种情况下的流水线停滞问题,引入了五级流水线,分别是:取指、译码、执行、访存、回写。如图3-3所示。


      其中访存阶段(Memory Access)的作用是从存储器装载数据到寄存器或者将寄存器数据保存到存储器,当然,如果不是Load/Store指令则不需要这一步,此时在访存阶段就只是将执行阶段的运算结果送到下一级回写阶段。回写阶段(Write Back)的作用是将数据写入目的寄存器。ARM9就采用了这种五级流水线,OpenMIPS的设计目标也是五级流水线。具体而言,OpenMIPS五级流水线各个阶段的主要工作如下。

  •  取指阶段:从指令存储器读出指令,同时确定下一条指令地址。
  •  译码阶段:对指令进行译码,从通用寄存器中读出要使用的寄存器的值,如果指令中含有立即数,那么还要将立即数进行符号扩展或无符号扩展。如果是转移指令,并且满足转移条件,那么给出转移目标,作为新的指令地址。
  •  执行阶段:按照译码阶段给出的操作数、运算类型,进行运算,给出运算结果。如果是Load/Store指令,那么还会计算Load/Store的目标地址。
  •  访存阶段:如果是Load/Store指令,那么在此阶段会访问数据存储器,反之,只是将执行阶段的结果向下传递到回写阶段。同时,在此阶段还要判断是否有异常需要处理,如果有,那么会清除流水线,然后转移到异常处理例程入口地址处继续执行。
  •  回写阶段:将运算结果保存到目标寄存器。

      读者可能对上述流水线各个阶段的主要工作还不完全理解,没有关系,本书也不是一次实现上述全部工作,而是逐步完善,一开始,只实现流水线各个阶段的基本工作,慢慢地丰富、完善。

3.1.3 指令执行周期

      OpenMIPS设计目标中提到:实现MIPS32指令集中的所有整数指令,并且大多数指令可以在一个时钟周期内执行完成。具体而言,OpenMIPS实现的所有指令执行完成需要的时钟周期如表3-1所示。


      对表3-1有以下几点说明。

      (1)OpenMIPS计划采用试商法完成除法运算,对于32位的除法,执行阶段至少需要32个时钟周期,再加上一些准备工作需要的时钟周期,最后需要36个时钟周期才能执行完成。在第7章“算术操作指令的实现”中会具体介绍除法指令的实现过程。

      (2)乘累加指令madd、maddu,乘累减指令msub、msubu都需要2个时钟周期才能执行完成。主要是因为这4条指令都要做两次运算,一次乘法、一次加/减法,如果将这两次运算放在执行阶段的一个时钟周期中完成,那么会使执行阶段所需要的时间明显增加,从而降低OpenMIPS工作时钟的频率,因此,OpenMIPS设计在执行阶段使用两个时钟周期完成这4条指令,一个时钟周期进行乘法,下一个时钟周期进行加/减法。在第7章“算术操作指令的实现”中会具体介绍乘累加、乘累减指令的实现过程。

3.2 教学版OpenMIPS处理器接口

      教学版OpenMIPS处理器的外部接口如图3-4所示。采用左边是输入接口,右边是输出接口的方式绘制,这样比较直观,便于理解。各接口的描述如表3-2所示,可以分为三类:系统控制接口(包括复位、时钟、中断)、指令存储器接口、数据存储器接口。



3.3 文件说明

      OpenMIPS是五级流水线处理器,流水线各个阶段的模块、对应的文件如图3-5所示。图中每个模块的上方标注的是模块名,下方标注的是对应的文件名。模块之间的关系没有绘出,因为关系比较复杂,在书中不便绘制,读者可以参考本书光盘中的“openmips模块连接关系图.vsd”文件,其中绘制了模块之间详细的连接关系。


      具体说明如下。

      (1)取指阶段

  •  PC模块:给出指令地址,其中实现指令指针寄存器PC,该寄存器的值就是指令地址。对应pc_reg.v文件
  •  IF/ID模块:实现取指与译码阶段之间的寄存器,将取指阶段的结果(取得的指令、指令地址等信息)在下一个时钟传递到译码阶段。对应if_id.v文件

      (2)译码阶段

  •  ID模块:对指令进行译码,译码结果包括运算类型、运算所需的源操作数、要写入的目的寄存器地址等。对应id.v文件。
  •  Regfile模块:实现了32个32位通用整数寄存器,可以同时进行两个寄存器的读操作和一个寄存器的写操作。对应regfile.v文件。
  •  ID/EX模块:实现译码与执行阶段之间的寄存器,将译码阶段的结果在下一个时钟周期传递到执行阶段。对应id_ex.v文件。

      (3)执行阶段

  •  EX模块:依据译码阶段的结果,进行指定的运算,给出运算结果。对应ex.v文件。
  •  DIV模块:进行除法运算的模块。对应div.v文件。
  •  EX/MEM模块:实现执行与访存阶段之间的寄存器,将执行阶段的结果在下一个时钟周期传递到访存阶段。对应ex_mem.v文件。

      (4)访存阶段

  •  MEM模块:如果是加载、存储指令,那么会对数据存储器进行访问。此外,还会在该模块进行异常判断。对应mem.v文件。
  •  MEM/WB模块:实现访存与回写阶段之间的寄存器,将访存阶段的结果在下一个时钟周期传递到回写阶段。对应mem_wb.v文件。

      (5)回写阶段

  •  CP0模块:对应MIPS架构中的协处理器CP0。
  •  LLbit模块:实现寄存器LLbit,在链接加载指令ll、条件存储指令sc的处理过程中会使用到该寄存器,第9章会详述。
  •  HILO模块:实现寄存器HI、LO,在乘法、除法指令的处理过程中会使用到这两个寄存器,第7章会详述。

      另外,还有一个CTRL模块,这个模块是用来控制整个流水线的暂停、清除等动作,所以不便于将其归入流水线中的一个阶段,在图3-5的上部单独画出。对应ctrl.v文件。

      本书的附录A给出了各个模块的接口示意图,及接口的作用描述。读者可能会感觉模块太多、每个模块的接口也太多,似乎很难理解,这种担心是不必要的,之前也提到本书不是一次实现上述全部模块,而是首先实现其中一部分模块,这一部分模块也只实现少量接口,只要能满足我们的要求即可,然后随着OpenMIPS实现的指令种类越来越多,慢慢添加模块、增加接口。

3.4 实现方法

      在写作本书之前,已经出现了一些介绍软核处理器实现的书籍,这些书在介绍实现方法时有一个共同点:一次考虑所有的指令、所有的情况,然后给出代码。笔者认为这不是一种读者易于接受的方法,而且这也可能不是作者实现处理器时采用的方法。在本书中,笔者借鉴了软件开发中的“增量模型”,使用了一种完全不同的实现方法:先考虑最简单的情况,给出代码,然后考虑稍微多一点的情况,修改、补充代码,随着考虑情况的增多,不停地修改、补充代码,最终,实现需求。

      在第4章中,我们就考虑了一种最简单的情况——只实现一条指令,这条指令是逻辑“或”指令ori,借助这条指令,可以搭建OpenMIPS流水线的结构,此时的数据流图如图3-6所示。读者暂时不用理解其具体含义,只需与图3-7对比,体会各自的复杂度,具体含义会在第4章介绍。


      在后续章节中我们依次实现逻辑操作指令、移位操作指令、空指令、移动操作指令、算术操作指令、转移指令、加载存储指令、协处理器访问指令、异常相关指令,最终实现了MIPS32指令集架构中定义的所有整数指令,此时的数据流图如图3-7所示。同样,读者此时暂不用理解其具体含义,只需与图3-6对比,体会各自的复杂度。


      对比图3-6、3-7,会发现复杂度大大增加,如果笔者一开始就考虑实现图3-7所示的数据流图,那么读者需要知道MIPS32指令集中定义的所有的指令,还要理解其作用。显然会增加理解难度。更好的方法是,一开始只考虑图3-6所示的数据流图,读者只需要理解指令ori的作用,然后一步步添加实现更多指令,同时丰富完善数据流图,最终实现图3-7所示的数据流图。比如:在添加实现转移指令的时候,就会为图3-6数据流图中的译码阶段增加“转移判断”模块;在添加实现异常相关指令的时候,就会为图3-6数据流图中的访存阶段增加“异常判断”模块。

      以上就是本书在实现OpenMIPS处理器时采用的实现方法,下一章将实现最小结构,只考虑执行一条指令ori。


未完待续!

OpenMIPS详细的模块连接关系图可以在http://download.csdn.net/detail/leishangwen/7667697下载


自己动手写处理器之第三阶段——教学版OpenMIPS处理器蓝图