CPU的设计与实现(1)--方案设计

一、动机
我的动机很简单,因为自己从小对电子设备工作原理的兴趣,以及动手实现自己的计算机的愿望,促使我想做这个项目。另外,由于最近大半年在加拿大这边大学里给本科生指导Digital System Architecture相关的实验课,有了更多的自己探索和研究这方面的机会和资料,在业余时间里尝试现实这个目标并已在模拟软件里实现了该计算机原型。

二、目标
从这篇文章开始,我想分享我设计与实现一个CPU,以及进一步实现基于该CPU的计算机(取名Gater8)的整个过程,。这个CPU的每个部分实现将全都由最基本的各种门电路芯片(7400系列门电路芯片)搭建而成。它将是一个非常底层与粗糙的CPU,你将会见到一个体积由若干面包板,7400系列芯片,以及几百根杜绑线搭建而成的CPU。同时将为CPU添加RAM和ROM的访问能力,并添置输入输出设备,使之成为一个完整的计算机。该计算机将不会非常复杂,即会有4位或8位的数据总线和处理能力,非常少的寄存器,一至两个不超过8位的输入和输出设备连接能力,CPU的时钟周期估计为1MHz左右,初步设计约平均4个左右时钟周期完成一条机器指令。但是,该计算机的设计非常容易扩展成为32位甚至64位的CPU和处理能力,我将在整个设计过程中加以说明。

通过这个系列的文章,将逐步透彻和详细得向读者阐述如何制做一个真正的原创计算机,而非使用现成的CPU组装。并且最后完成的成品除了计算机本身的硬件之外,还将包含若干软件。项目完成后将包括:
设计文档部分:
(1)自制计算机原型设计原型文档,可在数字设计软件中运行。
(2)该计算机原型的7400系列芯片的设计版本。
(3)全部过程设计说明文档,即本系列博文。
硬件部分:
(1)一台自制计算机,包括控制器、运算器、存储器、输入设备、输出设备。
软件部分:
(1)配套的汇编器,将为自制计算机写的汇编语言程序翻译成对应的可执行机器码。
(2)一个或几个程序,这些程序能在自制计算机上运行演示,比如猜数字小游戏、播放音乐等。

项目包含的以上所有内容都将在项目完成后开源。目标开源地址:SourceForge 或 GitHub。

很重要的一点是,通过阅读本系列博文,你将会深入理解很多计算机特别是CPU的实现和工作原理,并有能力设计与实现符合自己需求的CPU以及基于这个CPU的功能完整的计算机。

三、市面上关于自制CPU的书籍
如果你想尝试找一本书讲如何教你设计与实现一个CPU,截止目前,市面上你能找到中文的或翻译成中文的书主要有以下3本(截止笔者写作时):

(1)《CPU自制入门》作者:(日)水头一寿,(日)米泽辽,(日)藤田裕士 著, 赵谦 译, 出版社: 人民邮电出版社, 出版时间:2013年12月。
(2)《自己设计制作CPU与单片机》作者: 姜咏江, 出版社: 人民邮电出版社, 出版时间: 2014-9-1。
(3)《自己动手写CPU》作者: 雷思磊, 出版社: 电子工业出版社, 出版时间: 2014-9-1。

以上几本书据我了解几乎都是利用流形的FPGA芯片入手,先讲解大概的计算机组成和原理,然后利用VHDL硬件描述语言实现每一个CPU或计算机内的部件,如运算器,控制器等,再将程序编译后烧写到FPGA芯片上,使之成为定制的CPU。由于FPGA芯片本身由非常多的互联的门电路构成,甚至现流形的这些芯片内还集成了RAM和ROM,因此这种方案定制CPU是比较可行的。

而关于直接从门电路入手制做CPU乃至计算机的书籍主要是一些以讲计算机组成原理,体系结构为主的教科书。一些国产教科书过多教本宣科,并没有多大实际指导作用,而国外引进的一些里确有些不错的,比如:
(1)《Computer Architecture & Organisation Semester - V (Electronics & Telecommunication》作者:D.A.Godse A.P.Godse,出版社:Technical Publications Pune, 第一版: 2003, 重印: 2005.

上面这本书还有国内翻译版。

另外还有一本是以讲解从半导体原件的最底层工作原理到上层数字电路原理的书,是本难得的好书,不过可能没有汉化翻译版,但我仍然高度推荐:
(2)《Bebop to the Boolean Boogie: An Unconventional Guide to Electronics》第三版,作者:Clive Maxfield,出版社:Newnes,2008.

其它的很多书籍并没有引进,因此,我高度建议英语不错的读者多多参阅国外的相关书籍,定能获益菲浅。

两种CPU和计算机的方法的优缺点
下面我们来比较一下我的方案(通过7400系列芯片搭建)和FPGA的方案之间的区别,以及各自的优缺点:
通过7400构造计算机的优点:
(1)能更好理解计算机构造的原理,这是计算机科学的最根基的基础。
(2)能提升读者动手能力。
(3)能了解很多实际芯片的细节,为后续自制复杂数字电路做好铺垫。
(4)更有成就感,这是几乎最底层的完全由你自创的计算机。
通过7400构造计算机的缺点:
(1)计算机运算速度相对较慢。
(2)计算机对外设扩展功能相对较弱。

通过FPGA构造计算机的优点:
(1)CPU速度更快。
(2)联接外设能力较强。
通过FPGA构造计算机的缺点:
(1)相对不能更好理解计算机内部电路之间的互联和工作原理。
(2)直接从FPGA芯片定制到计算机需要很强的焊接和其它外围设备联接等知识,一般读者不可能清楚,除非使用开发板,但失去了CPU联接到外部设备的动手能力。

其实以上两种方案的优缺点互为相反。相对来说,用7400实现的方案更为底层,掌握了这种方法后很容易学会其它方法。
 
、7400系列芯片自制计算机赏析
下面我要给大家介绍几款国外作者制做的基于7400系列门电路芯片的计算机,国内貌似还没有见到类似作品,也许有但我不清楚。

5.1 Mik-Prof
项目链接地址(含youtube演示DEMO):https://sites.google.com/site/eedramlib/mik-pro

详细信息:
CPU位宽:4 bit
运算速度:580 Hz
指令数:8条
RAM:8KB SRAM(采用芯片为CY系列DIP 28直插芯片)
ROM:未知
输入设备:12按键电话键盘
输出设备:5*7点阵的LED
演示程序:赛车小游戏等。
开源状态:80%开源,开源了电路设计图,所使用的芯片,部分文档,但详细的基于芯片级的连接设计并未公开(有能力的读者可以自己完成这个工作)。
原型机如图1所示:
CPU的设计与实现(1)--方案设计图1. Mik-Prof 4位计算机原型。
 
项目链接地址(含youtube演示DEMO):http://www.bigmessowires.com/nibbler/
 
详细信息:
CPU位宽:4 bit
运算速度:2.46 MHz, 1.23 MIPS
指令数:16
RAM:4K*4bit SRAM(采用CY7C168A直插芯片)
ROM:4KB (芯片为28C64,芯片容为8KB,但由于使用12位地址总线,因此使用低4KB)
输入设备:4按键
输出设备:1602 LCD、压电扬声器
演示程序:猜数字、青蛙过河等小游戏。
开源状态:100%开源,配套软件和文档齐全,包括汇编器源码、模拟器源码、微指令生成器源码、详细设计图等。
原型机如图2所示:
CPU的设计与实现(1)--方案设计图2. Nibbler 4位计算机原型,图中正在运行猜数字游戏。
 
以上两个都是4位的计算机。虽然Nibbler的地址总线达到12位,但仍为4位计算机,因为它采用了4位的算术逻辑单元(即ALU运算器),芯片为74LS181。
 
Nibbler计算机已经是功能非常完整的计算机了,而且设计非常优化。它仅采用了13个7400系列芯片。驱动1602 LCD作为显示器,可以实现相对较为复杂的功能。而且作者开源的代码中包含了若干汇编代码示例,以及它的控制器实现采用的是微代码方式,周时作者还提供了微控制代码的生成程序,因此它可以作为一个4位的计算机设计模板,实现属于你自己指令集的计算机。
 
六、设计方案详解
下面我要给大家介绍一下我将要设计和实现的CPU和计算机的详细结构和细节,并命名该项目为Gater8,其含意即为用门做的8位计算机,类似于Nibbler,即4位计算机。
 
CPU设计后是为了用的,因此我们将从完整的计算机的使用为目的进行各个部件的设计和分析。Gater8的设计过程将经历以下几个主要步骤:
(1)先用类似LogicWorks等软件设计好数字电路图,并在该软件内测试每个部件和总体功能,要能正常运行。这个期间,将同时完成指令集的设计。但由于LogicWorks等软件对于1602 LCD等不支持,因此功能测试相对有限。
(2)在第一步的基础上,将原电路图用7400系列芯片代替后,得到新的可用于下一步实际搭建电路图。
(3)按7400电路图搭建出实际计算机。
(4)手工编写机器码程序,用于测试搭好的计算机。
(5)编写汇编器。
(6)编写一个小游戏,用作演示DEMO。
 
以下分析各硬件部件的详细设计。
 
6.1 CPU部分设计
CPU是计算机的核心,它主要包括运算器和控制器两个部分,这两部分是由总线连接在一起,从设计的角度细分的话,还包括寄存器和外部IO接口。在CPU内部,各部件之间的信号传输都是通过总线完成。一般来说,按信号的种类可将CPU内部的信号分为三类:1.数据信号,比如用于计算的实际数据。2.地址信号,地址是用于访问内存RAM或ROM使用的。3.控制信号,由控制器生成用于控制CPU内各部件协同处理。
 
按总线的使用方式,有时可将CPU分为单总线和多总线等。单总线将只使用一个总线传输数据信号和地址信号。多总线CPU则各使用一个总线传输相应类型的信号。而控制信号一般是由控制器直接传送到各个部件的控制端口上,如果不从逻辑上将这些信号传输的线总称为控制总线,一般会忽视这个总线。

以下分别分析CPU设计中4大部件的细节。
 
6.1.1 总线设计
关于数据总线和地址总线:如前所述,Gater8将能处理8位的数据,因此它至少有8位的数据总线。由于如果使用单总线结构,8位地址只能访问256字节数据和代码,这有点过小。因此,我将不使用单总线结构,将地址总线独立出来,并使用12位的地址总线,这样一共可访问4KB代码和数据,对于一个手工搭建的计算机,这差不多够了,而且由于地址总线的独立性,未来的位数扩展也不会太复杂。其实,很多早期玩过Atari 2600游戏机的可能知道Atari 2600的ROM最多也只有4KB,但却开发出了很多非常不错的游戏。

另外,任一时刻,对于连接在总线的部件来说,只能有一个部件向总线输出数据,不然就会发生冲突,CPU就无法继续运行。为了实现这一点,对于每一个连接在总线上的部件来说,都需要用一组三态缓冲器来控制其是否向总线输出数据,而每个部件所需的三态缓冲个数和这个部件的位数对应,每个三态缓冲控制1位数据的输出。所有这些部件的三态缓冲的协同工作,保证不发生冲突的任务是由控制器完成的。
 
关于IO接口部分:我计划采用类似Nibbler的简单做法,即对每一个IO通道直接采用一个带三态缓冲(Tri-State Buffer)控制的8位寄存器(由8个D flip-flop,即触发器构成)连接到数据总线上。并且不将IO设备映到内存空间(很多ARM CPU都是映射的),这样设计上会比较简单,而且功能上够用,也容易用未来实现的汇编程序控制IO设备。
 
下图为初步设计的数据总线和地址总线,以及与一个4KB ROM和一个4KB RAM之间的连接:
CPU的设计与实现(1)--方案设计
图3. 图中上方为8位数据总线,下方为12位地址总线,3个74163芯片用于级联成为一个12位的PC寄存器。
 
图3中并没有画出控制总线,但实际上图中大部分粉色显示的各个端口号都是控制器的一部分。比如RAM芯片的MEMenable、MEMin、MEMout,以及最下方74163芯片的PC端口等。
 
6.1.2 寄存器设计
由于Gater8是8位的CPU,因此所有数据处理相关的寄存器都将至少是8位的。寄存器个数的设计是比较自由的,比如Nibbler只有一个数据寄存器,那就是累加器,这样的设计会大大简化指令集,也会使硬件电路变得简单。而我打算多设计几个数据寄存器,初步计划是以下3个,均为8 bit:
(1)A寄存器,即累加器Accumulator。用作2个ALU数据来源中的其中一个,并同时也用来保存ALU的计算结果。
(2)T寄存器,即临时寄存器(Temporary Instruction)。用作2个ALU数据来源中的另一个,并同时也用来保存从RAM中取回的数据,即相当于MD寄存器(Memeory Data)。
(3)B寄存器,通用寄存器(General-Purpose Register)。用户可任意使用,也可用作栈顶指针。

另外,肯定还有以下寄存器用于完成取指和程序计数功能:
(4)IR寄存器,即指令寄存器(Instruction Register),8 bit。用于保存从内存中取回的待执行的指令。
(5)PC寄存器,即程序计数器(Program Counter),12 bit。用于保存下一个位于内存中的待取字节的地址。
(6)MA寄存器,即内存地址寄存器(Memory Address),12 bit。用于保存访问内存数据的地址。在实际实现时,这个寄存器可以简化去掉。

以上寄存器列表是初步设计,实际实现时可能略有删减。比如Nibbler的设计中没有T、B、MA这三个寄存器,即Nibbler只有一个数据寄存器:A寄存器。

在逻辑电路设计阶段,8 bit寄存器可用8个flip-flop(12 bit的则用12个flip-flop),即触发器联接而成。将每一个寄存器的输入端口都连接到相应的总线上,输出端口通过相应位宽的三态缓冲器再连接到对应总线上。由于flip-flop种类较多,我将主要使用D flip-flop。
 
图4演示了A寄存器连接在8位总线上的结构:
CPU的设计与实现(1)--方案设计

图4. 8位累加寄存器A通过8个三态缓冲连接到8位数据总线上。
 
上图中,ORST端口连接RST信号,即RESET信号,用于CPU重置,此时寄存器内8位数据均设置为0。Ain端口为8个D flip-flop的时钟信号端口,用于从总线上输入数据到寄存器内。Aout端口为8个三态缓冲器的使能端口,用于将寄存器的数据输出到总线上。
 
图5为上图中8位寄存器(名为8 bit reg芯片)的内部结构:
CPU的设计与实现(1)--方案设计
 
图5. 8位寄存器内部结构。
 
图6为图4中名为8 bit TS芯片的内部结构:
CPU的设计与实现(1)--方案设计
 
图6. 8 bit TS内部结构。
 
7400系列芯片中比如74825或74874就结合了图3中8 bit reg和8 bit TS两个部件于一体,非常方便使用。或者使用图中两个独立的部件,其中8位寄存器本身部分可用74277芯片实现,而8个三态缓冲可用74244实现。74HCT377的详细DataSheet可以参考74HC/HCT377-Q100 DataSheet

6.1.3 ALU设计
ALU,即算术逻辑单元(Arithmetic Logic Unit),用于完成CPU内的算术和逻辑运算,它是一个组合电路,无需时钟信号协助就能完成计算(不同于时序电路,需要时钟信号)。算术运算主要包括:加、减、乘、除,逻辑运算主要包括:与、或、非、异或等。

算术运算中,加法是最基本的运算,而减法、乘法运算都可以化为加法运算。而逻辑运算中,与和非两个运算可以组合出其它的逻辑运算。因此,在指令数有限的情况下无需实现所有的算术和逻辑运算,只需选择部分便可。另外,指令的选择还和具体应用有关,因此并不是一定要包含逻辑运算指令(Mik-Prof就没有)。

在数字电路的设计角度来看,逻辑运算最好实现,只要用各种门一搭马上就出来了,而算术运算的电路相对复杂一些,需要通过分析基本的位运算再到数与数之间的算术规则,从而再用K-map或布尔代数求出相应的布尔表达式,再转化为逻辑电路。不过也并不太复杂,在后面的博文中,我会详细而清楚得说明设计过程。

关于ALU在CPU中是如何连接的:根据具体运算指令,ALU需要1至2个Operand,即操作数,我将使用A和T寄存器来提供这两个操作数。运算结果保存回A寄存器。Nibbler的设计中,由于没有T寄存器,它是将A寄存器内的数和数据总线上的数直接计算,然后再将结果保存回A寄存器。这样的设计省掉了T寄存器的存和取,电路简化,计算反而速度更快,前提是这样的设计能满足实际需求。
 
7400系列芯片中提供了一款74181芯片,它是一个多功能的4位ALU,提供了加(ADD)、减(SUB)、移位(Shift)、比较大小(CMP)、与(AND)、或(OR)、异或(XOR)、与非(NAND)、或非(NOR)等共计16种4位数据运算。通过适当连接两片74181芯片是可以很方便实现8位的数据运算的。当然在下一步的逻辑电路设计时我会先用各种门来实现自己的ALU。详细的74181数据手册可参考:74181 DataSheet。Nibbler就是使用了一片74181作为ALU。

6.1.4 控制器(Control Unit)设计
控制器CU,即Control Unit,是CPU中第二大部件,没有它的话,CPU不能自动完成计算和取指等一系列操作。控制器的设计必须在完成所有以上步骤之后再开始,它肯定是CPU设计中的最后一个环节。控制器是时序电路,必须借助时钟信号来驱动(区别于ALU的组合电路)。

没有控制器的CPU,其实也可以用于计算,不过得人工有序得拨动各个部件的控制开关能才协调这些部件完成相应的指令。控制器的作用就是代替人的手动拨动开关,变为由时钟信号驱动的自己拨动开关。

控制器发出的每一组时序控制信号都和具体的指令运行阶段有关,因此必须先得完成指令集、以上各CPU部件的连接,设计好Data path(即数据通道)等工作后才能开始设计控制器。

控制器的实现方式有两种:
(1)微代码(Micro Code)方式。这是早期CISC型CPU广泛采用的方式,比如80X86系列CPU。
(2)硬布线(Hard Wired)方式。这是后期RISC型CPU所广泛使用,比如ARM系列CPU。

微代码的实现方式在70,80,90年代的CPU中使用较为常见。其优点是:比较灵活,定制能力强,当指令集发生某些变更无需改变硬件设计,而是通过重新设计微代码烧进ROM中即可。缺点是:速度相对较慢,因为它的每一次CPU状态都要访问ROM来输出该状态对应的控制信号,而访问ROM的速度肯定比直接运行门电路要慢。这个访ROM取控制信号的时间可能会是速个CPU运行环节中最慢的一环,因此可能直接导制整体CPU的速度的下降。因为CPU的时钟频率必须要按最慢的那个部件的时间来设置。

硬布线方式在90年代以后的RISC型CPU中较上常见。其优点是:速度快,因为无需访问ROM,而是直接通过逻辑门电路生成每一个CPU状态对应的控制信号。缺点是:它的门电路比较复杂,对于想用7400系列芯片搭建出来比较困难。比如,假设Gater8有16条指令,除了每条指令都会有固定的取指状态之外,不同的指令还会有:立即数寻址、直接寻址、执行等其它状态,假设平均每条指令有3个状态,那么一共将有16×3=48个状态。因此,我们的控制器需要一个6位的计数器,即Counter(为什么是6位:因为48介于2的5次方和2的6次方之间,5位不够,6位就够了)来生成足够48个的状态,然后还需要一个6->64的译码器,即Decoder,将每一个状态都对应到唯一的输出信号端口上,而每一个输出信号端口则连接着不同CPU部件的控制端口。光是6->64的这个Decoder的引脚数就至少要70根以上,是没有这样的7400芯片的,但也许能找位数少的Decoder拼接出来,不过还是比较复杂的。

Nibbler采用的是微代码方式,它使用两片28C16 EEPROM芯片,共同输出16位的控制信号(即16位宽的控制总线)。Mik-Prof应该是硬布线方式,这和它的指令集非常简单也有关系。

那么我要制做的Gater8现在先不限制在逻辑电路设计阶段要使用哪一种方式,可能两种都设计。而在实现阶段根据方便程度选择其中一种实现,即可能优先考虑硬布线,但如果过于复杂,可能改用微代码方式。
 
由于控制器的实现不同于ALU等部件,它完全是按照自定义的指令集和各各件之间连接的情况而定。如果使用硬布线方式,除了逻辑设计阶段我会用各种门来实现外,实际实现阶段也没有类似74181这样的芯片可用。当然,设计上是可以适当简化,使得方便用现有的7400芯片去搭建控制器。这是个探索和折中的过程。也是我认为整个CPU设计过程中最复杂的部分。
 
6.2 I/O部分设计
I/O,即输入输出(Input/Output)功能是完整的计算机必要的组成部分。这部分的设计宗旨是:尽可能简单可行。这部分的设计参考Nibbler的思想。
 
6.2.1 Input设计
打算只做对4至8个按键的支持,每个按键均通过一个三态缓冲器连接到数据总线上。如下图所示:
 
CPU的设计与实现(1)--方案设计图7. 按键S1至S4通过74HCT125(内置4个三态缓冲器)连接到左边的数据总线上。图片来自Nibbler的设计。
 
图7的设计非常简单,当CPU需要读取输入信号时,只需打一个低电平到/OEIN控制端口,4个按键的按压状态就会输入到数据总线上,等待进一步处理。
 
6.2.2 Output设计
将做对两个输出设备的支持:(1)1602 LCD,以及(2)压电扬声器。设计上也是直接将这些输出设备通过三态缓冲或D flip-flops直接连接到数据总线上。如下图所示:
CPU的设计与实现(1)--方案设计图8. 两个4位输出设备各通过74HCT173(内置4个D flip-flop)连接和控制两个设备,其中右上角为1602 LCD,右下角为Piezo,即压电扬声器。图片来自Nibbler。
 
按图8中所示的连接方式,当计算机需要向输出设备OUT0输出信号时,只需打图中下方的74HCT173芯片,数据将从数据总线输出到对应的Q1至Q4端口。合理的设计输出数据和输出频率就能使这个压电扬声器发出需要的音乐。
 
按照上述说明,Gater8的输入输出部分的逻辑设计将会是如图8所示结构:
CPU的设计与实现(1)--方案设计
 
图9. Gater8的I/O接口部分设计图。
 
图9上方为8位数据总线,DEV0in和DEV1in分别控制总线数据向两个输出设备(取名分别为DEV0和DEV1)的寄存器输入,DEV0out和DEV1out则分别控制两个输出设备的寄存器到实际设备的数据输出。实际上,我觉得这两个寄存器应该可以省略,除非上述从总线到设备的两步数据传送之间还有其它事情要做(不过目前我还没想到)。如果省略寄存器,这样就少了两个控制信号,即DEV0in和DEV1in,控制器的设计也会简单一些。图8中的DEV2为唯一的输入设备,没有设计寄存器,数据将通过控制信号EXTout直接传送到数据总线上。
 
6.3 存储器部分设计
这部分就直接使用RAM和ROM芯片将相应的地址针脚连接到地址总线上,如果实现中有MA寄存器,则RAM芯片的地址针脚先连接到MA寄存器的输出端口,MA的输入端口则直连接至地址总线上。

需要考虑的一个问题是,存储架构是采用冯诺依曼架构呢,还是采用哈佛架构。

冯诺依曼架构是将数据和代码都存在一起,而哈佛架构则将两者分开存储。早期计算机几乎都采用冯氏架构,目前已有非常多的CPU和计算机采用哈佛架构了,比如ARM中的Cortex-M系列CPU,被广泛应用到嵌入式计算中。

Nibbler采用的是哈佛架构。从分析Nibbler的逻辑电路图来看,似乎它把4KB的ROM和4K*4bit的RAM连接在了同一地址空间,总共提供了4KB或4K*4bit的存储空间。

这两个架构都有各自优点,Gater8在此不作限定,待下一步逻辑电路设计时再确定。本系列博文不打算自己设计RAM和ROM的细节,而是直接采用现成芯片。由于Gater8预计支持12位宽的地址总线,因此地址空间为4KB,芯片暂定为:
RAM:CY62256LL,SRAM,DIP-28直插封装。数据手册参考:CY62256LL Data Sheet
ROM:28C64,EEPROM,直插封装。数据手册参考:28c64 Data Sheet
以上芯片容量均超过4KB,实现时,多余的不使用的地址端口将被接地。

七、小结
以上为初步的方案设计,主要介绍了自制CPU的现状,以及展示了两个国外较为成功的基于自制CPU的自制计算机。功能相对完整,包含完整的计算机所需的控制器、运算器、存储器、输入设备、以及输出设备五大部分。

后续博文将从逻辑电路设计开始逐步完善成为一个实际的计算机,敬请期待。

 

http://blog.sina.com.cn/s/blog_6f38945b0102w5io.html

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`define NOP 5'b00000 `define HALT 5'b00001 `define LOAD 5'b00010 `define STORE 5'b00011 `define LDIH 5'b10000 `define ADD 5'b01000 `define ADDI 5'b01001 `define ADDC 5'b10001 `define SUB 5'b01011 `define SUBI 5'b10011 `define SUBC 5'b10111 `define CMP 5'b01100 // control `define JUMP 5'b11000 `define JMPR 5'b11001 `define BZ 5'b11010 `define BNZ 5'b11011 `define BN 5'b11100 `define BNN 5'b11101 `define BC 5'b11110 `define BNC 5'b11111 // logic / shift `define AND 5'b01101 `define OR 5'b01111 `define XOR 5'b01110 `define SLL 5'b00100 `define SRL 5'b00110 `define SLA 5'b00101 `define SRA 5'b00111 // general register `define gr0 3'b000 `define gr1 3'b001 `define gr2 3'b010 `define gr3 3'b011 `define gr4 3'b100 `define gr5 3'b101 `define gr6 3'b110 `define gr7 3'b111 // FSM `define idle 1'b0 `define exec 1'b1 /******* the whole module CPU is made of Instuction_Mem module, PCPU module and Data_Mem module ********/ module CPU( input wire clk, clock, enable, reset, start, input wire[3:0] select_y, output [7:0] select_segment, output [3:0] select_bit ); wire[15:0] d_datain; wire[15:0] i_datain; wire[7:0] d_addr; wire[7:0] i_addr; wire[15:0] d_dataout; wire d_we; wire[15:0] y; reg [20:0] count = 21'b0; Instruction_Mem instruction(clock,reset,i_addr,i_datain); PCPU pcpu(clock, enable, reset, start, d_datain, i_datain, select_y, i_addr, d_addr, d_dataout, d_we, y); Data_memory data(clock, reset, d_addr, d_dataout, d_we, d_datain); Board_eval eval(clk, y, select_segment, select_bit); endmodule /************************ Instruction memeory module *****************************/ module Instruction_Mem ( input wire clock, reset, input wire[7:0] i_addr, output [15:0] i_datain ); reg[15:0] i_data[255:0]; // 8 bits pc address to get instructions reg[15:0] temp; always@(negedge clock) begin if(!reset) begin i_data[0] <= {`LOAD, `gr1, 1'b0, `gr0, 4'b0000}; i_data[1] <= {`LOAD, `gr2, 1'b0, `gr0, 4'b0001}; i_data[2] <= {`ADD, `gr3, 1'b0, `gr1, 1'b0, `gr2}; i_data[3] <= {`SUB, `gr3, 1'b0, `gr1, 1'b0, `gr2}; i_data[4] <= {`CMP, `gr3, 1'b0, `gr2, 1'b0, `gr1}; i_data[5] <= {`ADDC, `gr3, 1'b0, `gr1, 1'b0, `gr2}; i_data[6] <= {`SUBC, `gr3, 1'b0, `gr1, 1'b0, `gr2}; i_data[7] <= {`SLL, `gr2, 1'b0, `gr3, 1'b0, 3'b001}; i_data[8] <= {`SRL, `gr3, 1'b0, `gr1, 1'b0, 3'b001}; i_data[9] <= {`SLA, `gr4, 1'b0, `gr1, 1'b0, 3'b001}; i_data[10] <= {`SRA, `gr5, 1'b0, `gr1, 1'b0, 3'b001}; i_data[11] <= {`STORE, `gr3, 1'b0, `gr0, 4'b0010}; i_data[12] <= {`HALT, 11'b000_0000_0000}; end else begin temp = i_data[i_addr[7:0]]; end end assign i_datain = temp; endmodule /**************************** PCPU module ***************************/ module PCPU( input wire clock, enable, reset, start, input wire [15:0] d_datain, // output from Data_Mem module input wire [15:0] i_datain, // output from Instruction_Mem module input wire [3:0] select_y, // for the board evaluation output [7:0] i_addr, output [7:0] d_addr, output [15:0] d_dataout, output d_we, output [15:0] y ); reg [15:0] gr [7:0]; reg nf, zf, cf; reg state, next_state; reg dw; reg [7:0] pc; reg[15:0] y_forboard; reg [15:0] id_ir; reg [15:0] wb_ir; reg [15:0] ex_ir; reg [15:0] mem_ir; reg [15:0] smdr = 0; reg [15:0] smdr1 = 0; reg signed [15:0] reg_C1; //有符号 reg signed [15:0] reg_A; reg signed [15:0] reg_B; reg signed [15:0] reg_C; reg signed [15:0] ALUo; //************* CPU control *************// always @(posedge clock) begin if (!reset) state <= `idle; else state <= next_state; end always @(*) begin case (state) `idle : if ((enable == 1'b1) && (start == 1'b1)) next_state <= `exec; else next_state <= `idle; `exec : if ((enable == 1'b0) || (wb_ir[15:11] == `HALT)) next_state <= `idle; else next_state <= `exec; endcase end assign i_addr = pc; // 准备下一条指令的地址 //************* IF *************// always @(posedge clock or negedge reset) begin if (!reset) begin id_ir <= 16'b0; pc <= 8'b0; end else if (state ==`exec) // Stall happens in IF stage, always compare id_ir with i_datain to decide pc and id_ir begin // 当即将被执行的指令要用到之前load写入的值时, stall two stages , id and ex. /* 指令中后第二、三个操作数均为寄存器时,需要判断LOAD的第一个操作数是否与这些指令的后两个寄存器有冲突 为一部分算数运算指令和逻辑运算指令 */ if((i_datain[15:11] == `ADD ||i_datain[15:11] == `ADDC ||i_datain[15:11] == `SUB ||i_datain[15:11] == `SUBC ||i_datain[15:11] == `CMP ||i_datain[15:11] == `AND ||i_datain[15:11] == `OR ||i_datain[15:11] == `XOR) &&( (id_ir[15:11] == `LOAD && (id_ir[10:8] == i_datain[6:4] || id_ir[10:8] == i_datain[2:0])) ||(ex_ir[15:11] == `LOAD && (ex_ir[10:8] == i_datain[6:4] || ex_ir[10:8] == i_datain[2:0])) ) ) // end if begin id_ir <= 16'bx; pc <= pc; // hold pc end /* 指令中第二个操作数为寄存器变量并参与运算时,需要判断LOAD的第一个操作数是否与这些指令的第二个操作数的寄存器有冲突 为移位指令和STORE指令 */ else if (( i_datain[15:11] == `SLL ||i_datain[15:11] == `SRL ||i_datain[15:11] == `SLA ||i_datain[15:11] == `SRA ||i_datain[15:11] == `STORE) &&((id_ir[15:11] == `LOAD &&(id_ir[10:8] == i_datain[6:4])) ||(ex_ir[15:11] == `LOAD &&(ex_ir[10:8] == i_datain[6:4])) ) ) begin id_ir <= 16'bx; pc <= pc; // hold pc end /* 跳转指令系列,id和ex阶段都需要stall,mem阶段跳转 */ else if(id_ir[15:14] == 2'b11 || ex_ir[15:14] == 2'b11) begin id_ir <= 16'bx; pc <= pc; // hold pc end /* mem阶段跳转 */ else begin // BZ & BNZ if(((mem_ir[15:11] == `BZ) && (zf == 1'b1)) || ((mem_ir[15:11] == `BNZ) && (zf == 1'b0))) begin id_ir <= 16'bx; pc <= reg_C[7:0]; end // BN & BNN else if(((mem_ir[15:11] == `BN) && (nf == 1'b1)) || ((mem_ir[15:11] == `BNN) && (nf == 1'b0))) begin id_ir <= 16'bx; pc <= reg_C[7:0]; end // BC & BNC else if(((mem_ir[15:11] == `BC) && (cf == 1'b1)) || ((mem_ir[15:11] == `BNC) && (cf == 1'b0))) begin id_ir <= 16'bx; pc <= reg_C[7:0]; end // JUMP else if((mem_ir[15:11] == `JUMP) || (mem_ir[15:11] == `JMPR)) begin id_ir <= 16'bx; pc <= reg_C[7:0]; end // 非跳转指令且没有检测到冲突 else begin id_ir <= i_datain; pc <= pc + 1; end end // end else end // else reset end // end always //************* ID *************// always @(posedge clock or negedge reset) begin if (!reset) begin ex_ir <= 16'b0; reg_A <= 16'b0; reg_B <= 16'b0; smdr <= 16'b0; end else if (state == `exec) //Data forwarding happens in ID stage, always check id_ir to decide reg_A/B begin ex_ir <= id_ir; // ********************reg_A 赋值******************* // /* 其他无冲突的情况 */ // reg_A <= r1: 要用到 r1 参与运算的指令,即除 "JUMP" 外的控制指令和一些运算指令,将寄存器r1中的值赋给reg_A if ((id_ir[15:14] == 2'b11 && id_ir[15:11] != `JUMP) || (id_ir[15:11] == `LDIH) || (id_ir[15:11] == `ADDI) || (id_ir[15:11] == `SUBI)) reg_A <= gr[id_ir[10:8]]; else if (id_ir[15:11] == `LOAD) reg_A <= gr[id_ir[6:4]]; // case for data forwarding, 当前指令第2个操作数用到之前指令第1个操作数的结果 else if(id_ir[6:4] == ex_ir[10:8]) reg_A <= ALUo; else if(id_ir[6:4] == wb_ir[10:8]) reg_A <= reg_C1; else if(id_ir[6:4] == mem_ir[10:8]) reg_A <= reg_C; //reg_A <= r2: 如果运算中不用到 r1,要用到 r2, 则将 gr[r2] else reg_A <= gr[id_ir[6:4]]; //************************* reg_B赋值************************// if (id_ir[15:11] == `STORE) begin reg_B <= {12'b0000_0000_0000, id_ir[3:0]}; //value3 smdr <= gr[id_ir[10:8]]; // r1 end // case for data forwarding, 当前指令第3个操作数用到之前指令第1个操作数的结果 else if(id_ir[2:0] == ex_ir[10:8]) reg_B <= ALUo; else if(id_ir[2:0] == wb_ir[10:8]) reg_B <= reg_C1; else if(id_ir[2:0] == mem_ir[10:8]) reg_B <= reg_C; /* 其他无冲突的情况 */ else if ((id_ir[15:11] == `ADD) || (id_ir[15:11] == `ADDC) || (id_ir[15:11] == `SUB) || (id_ir[15:11] == `SUBC) || (id_ir[15:11] == `CMP) || (id_ir[15:11] == `AND) || (id_ir[15:11] == `OR) || (id_ir[15:11] == `XOR)) reg_B <= gr[id_ir[2:0]]; end end //************* ALUo *************// always @ (*) begin // {val2, val3} if (ex_ir[15:11] == `JUMP) ALUo <= {8'b0, ex_ir[7:0]}; // 跳转指令 r1 + {val2, val3} else if (ex_ir[15:14] == 2'b11) ALUo <= reg_A + {8'b0, ex_ir[7:0]}; //算数运算,逻辑运算,计算结果到ALUo, 并计算cf标志位 else begin case(ex_ir[15:11]) `LOAD: ALUo <= reg_A + {12'b0000_0000_0000, ex_ir[3:0]}; `STORE: ALUo <= reg_A + reg_B; `LDIH: {cf, ALUo} <= reg_A + { ex_ir[7:0], 8'b0 }; `ADD: {cf, ALUo} <= reg_A + reg_B; `ADDI:{cf, ALUo} <= reg_A + { 8'b0, ex_ir[7:0] }; `ADDC: {cf, ALUo} <= reg_A + reg_B + cf; `SUB: {cf, ALUo} <= {{1'b0, reg_A} - reg_B}; `SUBI: {cf, ALUo} <= {1'b0, reg_A }- { 8'b0, ex_ir[7:0] }; `SUBC:{cf, ALUo} <= {{1'b0, reg_A} - reg_B - cf}; `CMP: {cf, ALUo} <= {{1'b0, reg_A} - reg_B}; `AND: {cf, ALUo} <= {1'b0, reg_A & reg_B}; `OR: {cf, ALUo} <= {1'b0, reg_A | reg_B}; `XOR: {cf, ALUo} <= {1'b0, reg_A ^ reg_B}; `SLL: {cf, ALUo} <= {reg_A[4'b1111 - ex_ir[3:0]], reg_A << ex_ir[3:0]}; `SRL: {cf, ALUo} <= {reg_A[ex_ir[3:0] - 4'b0001], reg_A >> ex_ir[3:0]}; `SLA: {cf, ALUo} <= {reg_A[ex_ir[3:0] - 4'b0001], reg_A <<< ex_ir[3:0]}; `SRA: {cf, ALUo} <= {reg_A[4'b1111 - ex_ir[3:0]], reg_A >>> ex_ir[3:0]}; default: begin end endcase end end //************* EX *************// always @(posedge clock or negedge reset) begin if (!reset) begin mem_ir <= 16'b0; reg_C <= 16'b0; dw <= 0; nf <= 0; zf <= 0; smdr1 <= 16'b0; end else if (state == `exec) begin mem_ir <= ex_ir; reg_C <= ALUo; if (ex_ir[15:11] == `STORE) begin dw <= 1'b1; smdr1 <= smdr; end // 设置标志位zf, nf, 算数和逻辑运算 else if(ex_ir[15:14] != 2'b11 && ex_ir[15:11] != `LOAD) begin zf <= (ALUo == 0)? 1:0; nf <= (ALUo[15] == 1'b1)? 1:0; dw <= 1'b0; end else dw <= 1'b0; end end // PCPU module 的输出 assign d_dataout = smdr1; assign d_we = dw; assign d_addr = reg_C[7:0]; //************* MEM *************// always @(posedge clock or negedge reset) begin if (!reset) begin wb_ir <= 16'b0; reg_C1 <= 16'b0; end else if (state == `exec) begin wb_ir <= mem_ir; if (mem_ir[15:11] == `LOAD) reg_C1 <= d_datain; else if(mem_ir[15:14] != 2'b11) reg_C1 <= reg_C; end end //************* WB *************// always @(posedge clock or negedge reset) begin if (!reset) begin gr[0] <= 16'b0; gr[1] <= 16'b0; gr[2] <= 16'b0; gr[3] <= 16'b0; gr[4] <= 16'b0; gr[5] <= 16'b0; gr[6] <= 16'b0; gr[7] <= 16'b0; end else if (state == `exec) begin // 回写到 r1 if ((wb_ir[15:14] != 2'b11) &&(wb_ir[15:11] != `STORE) &&(wb_ir[15:11] != `CMP) ) gr[wb_ir[10:8]] <= reg_C1; end end // 板极验证 assign y = y_forboard; // 板极验证需要的输出 always @(select_y) begin case(select_y) 4'b0000: y_forboard <= {8'B0,pc}; 4'b0001: y_forboard <= id_ir; 4'b0010: y_forboard <= reg_A; 4'b0011: y_forboard <= reg_B; 4'b0100: y_forboard <= smdr; 4'b0101: y_forboard <= ALUo; 4'b0110: y_forboard <= {15'b0, cf}; 4'b0111: y_forboard <= {15'b0, nf}; 4'b1000: y_forboard <= reg_C; 4'b1001: y_forboard <= reg_C1; 4'b1010: y_forboard <= gr[0]; 4'b1011: y_forboard <= gr[1]; 4'b1100: y_forboard <= gr[2]; 4'b1101: y_forboard<= gr[3]; 4'b1110: y_forboard <= gr[4]; 4'b1111: y_forboard <= gr[5]; endcase end endmodule /**************************** Data memory module ******************************/ module Data_memory ( input wire clock, reset, input wire [7:0] d_addr, input wire [15:0] d_dataout, input wire d_we, output [15:0] d_datain ); reg[15:0] temp; reg[15:0] d_data[255:0]; always@(negedge clock) begin if(!reset) begin d_data[0] <= 16'hFc00; d_data[1] <= 16'h00AB; end else if(d_we) begin d_data[d_addr] <= d_dataout; end else begin temp = d_data[d_addr]; end end assign d_datain = temp; endmodule /**************************** Board evaluation module ******************************/ module Board_eval ( input wire clock, input wire [15:0] y, output reg [7:0] select_segment, output reg [3:0] select_bit ); parameter SEG_NUM0 = 8'b00000011, SEG_NUM1 = 8'b10011111, SEG_NUM2 = 8'b00100101, SEG_NUM3 = 8'b00001101, SEG_NUM4 = 8'b10011001, SEG_NUM5 = 8'b01001001, SEG_NUM6 = 8'b01000001, SEG_NUM7 = 8'b00011111, SEG_NUM8 = 8'b00000001, SEG_NUM9 = 8'b00001001, SEG_A = 8'b00010001, SEG_B = 8'b11000001, SEG_C = 8'b01100011, SEG_D = 8'b10000101, SEG_E = 8'b01100001, SEG_F = 8'b01110001; // 位选 parameter BIT_3 = 4'b0111, BIT_2 = 4'b1011, BIT_1 = 4'b1101, BIT_0 = 4'b1110; reg [20:0] count = 0; always @ (posedge clock) begin count <= count + 1'b1; end always @ (posedge clock) begin case(count[19:18]) 2'b00: begin select_bit <= BIT_3; case(y[15:12]) 4'b0000: select_segment <= SEG_NUM0; 4'b0001: select_segment <= SEG_NUM1; 4'b0010: select_segment <= SEG_NUM2; 4'b0011: select_segment <= SEG_NUM3; 4'b0100: select_segment <= SEG_NUM4; 4'b0101: select_segment <= SEG_NUM5; 4'b0110: select_segment <= SEG_NUM6; 4'b0111: select_segment <= SEG_NUM7; 4'b1000: select_segment <= SEG_NUM8; 4'b1001: select_segment <= SEG_NUM9; 4'b1010: select_segment <= SEG_A; 4'b1011: select_segment <= SEG_B; 4'b1100: select_segment <= SEG_C; 4'b1101: select_segment <= SEG_D; 4'b1110: select_segment <= SEG_E; 4'b1111: select_segment <= SEG_F; endcase end 2'b01: begin select_bit <= BIT_2; case(y[11:8]) 4'b0000: select_segment <= SEG_NUM0; 4'b0001: select_segment <= SEG_NUM1; 4'b0010: select_segment <= SEG_NUM2; 4'b0011: select_segment <= SEG_NUM3; 4'b0100: select_segment <= SEG_NUM4; 4'b0101: select_segment <= SEG_NUM5; 4'b0110: select_segment <= SEG_NUM6; 4'b0111: select_segment <= SEG_NUM7; 4'b1000: select_segment <= SEG_NUM8; 4'b1001: select_segment <= SEG_NUM9; 4'b1010: select_segment <= SEG_A; 4'b1011: select_segment <= SEG_B; 4'b1100: select_segment <= SEG_C; 4'b1101: select_segment <= SEG_D; 4'b1110: select_segment <= SEG_E; 4'b1111: select_segment <= SEG_F; endcase end 2'b10: begin select_bit <= BIT_1; case(y[7:4]) 4'b0000: select_segment <= SEG_NUM0; 4'b0001: select_segment <= SEG_NUM1; 4'b0010: select_segment <= SEG_NUM2; 4'b0011: select_segment <= SEG_NUM3; 4'b0100: select_segment <= SEG_NUM4; 4'b0101: select_segment <= SEG_NUM5; 4'b0110: select_segment <= SEG_NUM6; 4'b0111: select_segment <= SEG_NUM7; 4'b1000: select_segment <= SEG_NUM8; 4'b1001: select_segment <= SEG_NUM9; 4'b1010: select_segment <= SEG_A; 4'b1011: select_segment <= SEG_B; 4'b1100: select_segment <= SEG_C; 4'b1101: select_segment <= SEG_D; 4'b1110: select_segment <= SEG_E; 4'b1111: select_segment <= SEG_F; endcase end 2'b11: begin select_bit <= BIT_0; case(y[3:0]) 4'b0000: select_segment <= SEG_NUM0; 4'b0001: select_segment <= SEG_NUM1; 4'b0010: select_segment <= SEG_NUM2; 4'b0011: select_segment <= SEG_NUM3; 4'b0100: select_segment <= SEG_NUM4; 4'b0101: select_segment <= SEG_NUM5; 4'b0110: select_segment <= SEG_NUM6; 4'b0111: select_segment <= SEG_NUM7; 4'b1000: select_segment <= SEG_NUM8; 4'b1001: select_segment <= SEG_NUM9; 4'b1010: select_segment <= SEG_A; 4'b1011: select_segment <= SEG_B; 4'b1100: select_segment <= SEG_C; 4'b1101: select_segment <= SEG_D; 4'b1110: select_segment <= SEG_E; 4'b1111: select_segment <= SEG_F; endcase end endcase end endmodule 2).test `timescale 1ns / 1ps //////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 21:22:32 12/29/2014 // Design Name: CPU // Module Name: C:/Users/liang/Desktop/embed/CPU/CPU/CPUTest.v // Project Name: CPU // Target Device: // Tool versions: // Description: // // Verilog Test Fixture created by ISE for module: CPU // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // //////////////////////////////////////////////////////////////////////////////// module CPU_test; // Inputs reg clock; reg enable; reg reset; reg [3:0] select_y; reg start; // Outputs wire [15:0] y; // Instantiate the Unit Under Test (UUT) CPU cpu ( .clock(clock), .enable(enable), .reset(reset), .start(start), .select_y(select_y) ); initial begin // Initialize Inputs clock = 0; enable = 0; reset = 0; select_y = 0; start = 0; // Wait 100 ns for global reset to finish #100; forever begin #5 clock <= ~clock; end // Add stimulus here end initial begin // Wait 100 ns for global reset to finish #100; $display("pc: id_ir : ex_ir :reg_A: reg_B: reg_C: cf: nf: zf: regC1: gr1: gr2: gr3: gr4: gr5:"); $monitor("%h: %b: %b: %h: %h: %h: %h: %h: %h: %h: %h: %h: %h: %h: %h", cpu.pcpu.pc, cpu.pcpu.id_ir, cpu.pcpu.ex_ir, cpu.pcpu.reg_A, cpu.pcpu.reg_B, cpu.pcpu.reg_C, cpu.pcpu.cf, cpu.pcpu.nf, cpu.pcpu.zf, cpu.pcpu.reg_C1, cpu.pcpu.gr[1], cpu.pcpu.gr[2], cpu.pcpu.gr[3], cpu.pcpu.gr[4], cpu.pcpu.gr[5]); enable <= 1; start <= 0; select_y <= 0; #10 reset <= 0; #10 reset <= 1; #10 enable <= 1; #10 start <=1; #10 start <= 0; end endmodule
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值