精华内容
下载资源
问答
  • cpu设计
    万次阅读 多人点赞
    2018-01-03 13:30:50

    设计CPU的主体思路如下:

    设计一个简单的CPU,我们需要先设计一个简易版的指令系统,然后根据该指令系统搭建对应的数据通路,在数据通路的基础上实现控制逻辑,下一步是加上时钟信号,划分流水线,紧接着解决流水线中的冲突问题,提高流水线的效率,最后是解决CPU例外的问题。

    是不是觉得设计CPU特别高大上?现在看不懂这一个过程没有关系,跟着我一步步的理解下来,到最后你就会豁然开朗了!

    简易的指令系统SMIPS

    在设计CPU前,我们需要先设计好CPU的指令系统。为什么需要一个指令系统?指令系统是一个CPU所能处理的全部指令的集合,是一个CPU的根本属性。比如INTEL采用的指令集是X86,现在常用的手机上的指令集是ARM。我们都知道用C/PYTHON等高级语言开发了一个程序以后都需要翻译成机器语言才能运行,而这些机器语言就是一条条的指令。
    我们从MIPS指令集从挑选了一些简单的但具有代表性的指令作为本CPU的指令集(取名叫SMIPS, simple MIPS)。该指令集分为三部分:ALU,访存以及转移指令。ALU指令是进行常见的加减乘除,移位,与或非等运算所需要的指令。访存指令是从内存中取数或是存数的指令。转移指令是进行指令跳转,比如实现循环,分支等功能的指令。
    指令的编码格式:这个小型的指令系统有两种编码格式:寄存器型,立即数型。
    寄存器型指令形如 ADDU r1,r2,r3 (把r2,r3相加,结果存放到r1):OP代表指令的操作码(指令标识),代表了你是哪一类指令,比如ALU指令(除了ADDIU外)的操作码都是000000。那么怎么区分具体是哪一条ALU指令呢?这就需要FUNC(辅助操作码)了,比如ADDU的FUNC是100001。RS(例子中的r2),RT(例子中的r3)存放两个源操作数。RD(例子中的r1)存放目的操作数。SA字段比较复杂,在设计这个小型CPU的时候不需要涉及到,所以不再描述。
    立即数型指令形如 ADDU r1,r2,#3 (把r2与立即数3相加,结果存放到r1): IMM(例子中的#3)是立即数immediate。
             或者LW r1,52(r2) (从r2+52这个地址中取出数据到r1): IMM(例子中的52)是偏移offset。

    现在,我们已经设计好了小型指令系统SMIPS,这个指令系统虽然很简单,但是功能上已经能媲美X86了!有了这个指令系统,我们也可以像INTEL一样做出一个迷你版的CPU了!

    最初版本的数据通路

    前面我们已经设计好了伟大的指令系统SMIPS,基于SMIPS,我们现在可以开始设计CPU的数据通路了。什么叫数据通路?比较官方的说法是数字系统中,各个子系统通过数据总线连接形成的数据传送路径成为数据通路。简单的说,数字系统就是我们要设计的CPU,CPU由很多子系统组成,把这些子系统连接在一起画出来的图就是数据通路。
    那么CPU由哪些子系统构成呢?在最初版本中,CPU的主要模块包括一个指令存储器,一个数据存储器,一个通用寄存器堆,一个指令寄存器(IR)以及一个程序计数器(PC)。
    指令存储器和数据存储器分别用来存储所有的指令和数据。IR用来存放当前要执行的指令,PC则用来存放当前要执行的指令的地址,通用寄存器堆里存放了若干个寄存器。

    我们先把这些子系统连接起来,画出最初版本的数据通路。CPU在工作时,先用PC作为地址去指令存储器里取指令,将取到的指令存放到IR中,接着CPU对IR中的指令进行解析,比如IR中存放了一条指令 ADDU r1,r2,r3,CPU解析得到要去寄存器堆取出r2,r3寄存器的值,并且知道了这是一条加法指令。然后寄存器堆将r2,r3的值送到ALU(运算部件)进行加法运算,最后将算完的结果保存到寄存器堆中的r1寄存器。如果是一条访存指令,则会从数据存储器中取数或是存数,然后把相应的结果放回寄存器堆。
    CPU的大体思路就是这样,但是这里面,存在了很多没有解决的小问题需要在后面一个一个的解决。

    问题1、寄存器堆内部长什么样子?

    如果我们把寄存器堆当成是一个黑盒子的话,那么我们需要从黑盒子中读取寄存器的值或者是向黑盒子中写入寄存器的值。情况1、读取寄存器的值:简单的说如果要读某一个寄存器的值,很自然地,我们需要知道寄存器的地址(在这个CPU中,我们认为总共有32个寄存器,那么寄存器的地址就是寄存器的标号)。有了地址以后,我们希望从黑盒子里输出这个寄存器的值,所以我们需要一个输入线RA(read address)和输出线RD(read data)。情况2、写寄存器的值:同样的,如果我们要写寄存器的值,我们也需要知道要写哪一个寄存器,即寄存器的地址WA(write address)。同时,我们还需要告诉黑盒子打算写什么值到寄存器WD(write data),以及一个写使能信号WE(write enable)。
    考虑黑盒子的内部:对于读寄存器而言,本质上是一个32选1的选择器从32个寄存器中根据输入的RA信号来选择一个寄存器的值输出给RD。而对于写寄存器来说则和读寄存器相反,利用一个5-32译码器,根据输入信号WA(比如00001)选中某一个寄存器(比如第1号寄存器),再与上全局写使能信号WE,来打开对应寄存器的写使能位,最后再将数据WD值写到该寄存器中。由于,我们的CPU的源寄存器可能有两个,所以有两组读寄存器的输入和输出。下面,我们把黑盒子画出来,如图所示。
    其实数据存储器和指令存储器的内部和寄存器堆长得十分类似,本质上都是译码器和选择器,只不过他们的线更多更复杂一些,所以在这里就不一一赘述了。

    问题2、PC的值是从哪里来的?

    我们都知道CPU中的指令是按顺序执行的,一条执行完紧接着执行下一条指令,那么下一条指令的PC值就应该是当前PC值+4(假设一条指令长度为4个字节)。另外一种情况就是执行转移指令,从当前执行指令跳转到某一条远方的指令,那么PC值为当前PC值+offset。所以需要有一个二选一选择器来选择PC应该+4还是+offset,在选择完之后需要有一个加法器来把选择后的结果和PC相加,并送到PC中。

    问题3、CPU是怎么解析IR中的指令的?

    在前面的指令系统中,我们知道了IR中的指令由op(操作码),func(功能码),rd(目标寄存器),rs、rt(两个源寄存器),还有imm(立即数/偏移量)这几部分构成。而IR中的域统一控制了寄存器堆(决定取哪些寄存器的值,存哪些寄存器的值),运算部件(利用ALU的哪个运算功能),数据存储器(访问哪个数据单元)等等。为了让数据根据指令的要求在数据通路中正确地流动,需要根据IR中得到的信息通路进行控制(简单的说就是加一堆的选择器和控制信号)。我们按照指令从准备运算的数据,进行对应的运算,再到存放运算的结果,最后到取下一条指令这一过程来分析需要设置哪些选择器
    (1)准备运算数据:我们知道有些指令是对两个源操作数rs,rt进行运算(比如addu r1,r2,r3) ,但有些指令是对一个源操作数rs和一个立即数imm进行运算(比如addiu,lw,sw指令等),所以我们需要二选一选择器来选择第二个操作数是rt还是imm,然后把选择后的结果送入ALU中进行运算。
    (2)进行什么运算:我们需要知道当前指令到底要进行哪种运算(加减乘除?),所以需要有一个控制信号来控制ALU进行对应运算
    (3)存放什么结果:我们需要考虑当前这条指令到底是进行了ALU运算,还是进行了访存操作得到了最后的结果。所以我们需要有一个二选一选择器来选择到底是哪一种情况。
    (4)结果存到哪个寄存器:运算完后,我们需要把结果写回到寄存器堆,这时候就出现一个问题,有的指令目的寄存器是rd,有的指令目的寄存器是rt(lw指令)。所以,这时候我们又需要一个二选一选择器来选择目的寄存器是rd还是rt了。
    (5)取下一条指令:在问题2中,我们知道了PC值要么+4要么+offset。
    (6)两个写使能控制:我们知道,有些指令是不用写入寄存器堆的,比如SW指令(把数写到内存中,所以不需要写到寄存器堆中)以及转移指令(修改PC的值,所以不需要写到寄存器中),所以在寄存器堆处有一个写使能信号WE1。另外在数据存储器处,同样也有一个写使能信号WE来控制是否写入内存,因为LW指令是从内存中取数而不用写入内存。
    在了解了CPU是如何解析IR中的指令以后,我们的数据通路又变了一个模样啦!
    这个图比之前的图多了好多信息量...不过仔细看下来也不是很复杂,无非就是多了一些控制信号C1,C2,C3,C4,C5,C6,ALUOP,还有多了一些选择器MUX2等等。下面我们再来分析一遍增加了哪些东西。

    C1:就是情况(5)中我们说到的PC需要+4还是+offset的控制信号

    C2:就是情况(1)中我们说到的进行ALU运算的第二个源操作数到底是rt还是imm的控制信号

    C3:就是情况(4)中我们说到的要把最后的结果到底要保存到rd还是rt寄存器中的控制信号

    C4:就是情况(3)中我们说到的要到底要存放ALU的结果还是从数据存储器中得到的结果的控制信号

    C5: 就是情况(6)中的寄存器写使能端WE1

    C6:就是情况(6)中数据存储器的写使能端WE

    ALUOP:就是情况(2)中我们要进行哪种ALU运算

    TIPS: 另外在图中,我们还看到有个小部件是符号扩展从imm连到了offset,这是因为imm数的长度不够32位,无法和PC进行直接运算,需要把数扩展成32位才能进行运算。还有一个br_taken部件是为了支持有条件跳转,比如指令BEQ R2,R3,offset,功能是如果R2==R3,PC=PC+offset,所以会有一个判断两寄存器值是否满足跳转条件。

    到目前为止,我们几乎已经完成了主要的数据通路和控制通路啦!离实现一个CPU又进了一步!但是,这个CPU还是不能工作。。为什么呢?因为这个CPU还缺少了时钟部分,时钟就是CPU的驱动,相当于人的心脏,有了心脏的跳动,人才能正常活动,CPU也一样。

    小知识:我们知道影响一台计算机快慢有很多因素,但归根到底,我们可以把运行速度分成三部分,程序运行所需要的指令数,每条指令所需要的时钟周期数(CPI),每个时钟周期的长度,三部分乘在一起就是一个程序的运行时间。所需要的指令数可以通过算法优化,编译器优化来减少;每条指令所需要的时钟周期和指令系统的设计,以及体系结构的设计有关;时钟周期的长度简单来说就是我们平常买电脑时所说的CPU主频(2GHZ,3GHZ这种)。所以在这里,我们需要给我们的CPU加上心跳!

    给CPU加上时钟

    简单的来说,我们可以把一条指令的执行分成三步,1、计算PC的值 2、根据PC的值从指令存储器中取出指令放到指令寄存器IR中 3、根据指令的内容,控制指令的执行,把结果写到寄存器堆或数据存储器中。指令执行的三个阶段,如图所示。
    这三个步骤,我们可以用三个时钟信号来控制,第一个时钟计算PC的值,完成取指后(把指令从指令存储器中取出),第二个时钟把取出的指令送到IR中去,等指令执行完以后,第三个时钟周期把指令的运算结果送到通用寄存器或数据存储器中。这三个时钟信号,我们可以用一个统一的时钟CLK进行分频,分出三个时钟信号给这三个步骤使用。

    改进一、三步化两步

    现在这个CPU能够一拍一拍、一条一条的执行指令了,但是这个CPU不够高效,因为其实前一条指令的执行和当前指令计算PC的值并不会冲突,所以我们可以把第N条指令的执行和第N+1条指令的计算PC重叠起来。效果上看,我们可以把计算PC的值当做指令执行阶段的一部分,那么一条指令执行就可以当做只有取指和执行两个阶段了。效果如图所示。


    那么此时,我们可以把统一的时钟CLK分频,分出两个时钟信号来控制这两个步骤。

    改进二、两步化一步

    这时候,我们就会有一个新的想法,既然计算PC和执行已经重叠了,那有没有办法把取指和执行也重叠了呢?答案是肯定的,当第N条指令执行完,已经把结果写回寄存器或内存,第N+1条指令可以使用第N条指令的结果。这种方式在大多数情况下是成立的,但有一种特殊情况我们需要考虑,那就是转移指令,如果第N条指令是转移指令的话,在第N+1条指令在取指的时候,需要知道第N条指令转移是否成功?往哪里跳转?而这个结果是需要等待第N条指令执行完才能知道的,此时第N条指令的执行和第N+1条指令的取指不能重叠。

    那有没有办法把这种情况解决呢?答案是肯定的,有人提出了延迟槽技术。延迟槽技术简单的说就是不管跳转指令是否转移成功,紧跟着的那条指令都必须执行。这样的好处就是转移指令的执行和下一条指令的取指可以重叠了!

    经过这样的改进以后,我们可以把第N条指令的执行和第N+1条指令的取指重叠,效果如图所示。


    那么此时,我们只需要一个时钟信号就能控制整个CPU的所有部分啦!加上时钟信号以后,CPU如图所示。

    稍微解释一下CLK的连线,当时钟信号一来,第N+2条指令收到信号(信号传送到寄存器堆和内存)就开始执行阶段的任务(把结果写回寄存器堆或内存),第N+1条指令收到信号(信号传送到IR)就开始取指阶段的任务(CPU解析IR中的指令),第N条指令收到信号(信号传送到PC)开始计算PC阶段的任务。

    其实,上述的重叠思想就是流水线设计思想,经过我们一步步的合并,现在这个CPU就能够正常的工作啦!但是,这种流水线的设计有一个问题,就是执行在执行阶段需要干的事情太多了!指令进入IR以后,要进行译码(生成各种控制信号C1,C2...),然后读寄存器的值,把值送到ALU,有时候还需要访存,最后还要把结果写回寄存器,上一步时钟信号把指令送到IR,要等很久的时间,才能发出下一个时钟信号把结果写回寄存器。。。

    标准五级流水

    既然,执行阶段要干的事情太多了,我们不如把执行阶段划分成若干个部分,这样就不用等太长时间了。按照上一节所说的任务,我们把执行阶段分成译码ID,执行EX,访存MEM,写回WB4个阶段。译码阶段就是分析这条指令要干什么事情,用到哪些寄存器,生成各种控制信号。执行阶段就是进行ALU运算。如果是一个访存操作,就根据算出来的地址访问数据存储器。最后,把结果写回到寄存器堆。这样的设计就是传统的RISC( Reduced Instruction Set Computer) CPU设计中的标准五级流水线。
    在标准五级流水线中,同时用五条指令在执行,每条执行所处的流水线阶段不一样,当然控制信号也不一样。所以需要在每个阶段设置中间寄存器来保存不同流水线阶段的数据。这个数据包括源寄存器的值,目标寄存器标号,以及各种控制信号。
    在译码阶段,读出源寄存器的值以后,当时钟信号来以后需要将v1,v2(源寄存器值), ALUop(运算类型), c4(选择写回结果),c5(寄存器写使能),c6(数据存储器写使能),dest(目标寄存器地址)等数据保存到中间寄存器。其他阶段不在一一赘述。

    现在,我们可以设计出标准的五级流水的CPU啦!效果如图所示。


    指令相关和流水线冲突

    指令相关

    前面设计的五级流水使得CPU的主频大幅度提高,但是这样的CPU执行可能会导致错误的运行结果。为什么呢?举个例子,比如第N条指令把结果写到R1寄存器,第N+1条指令要用到R1的值进行运算。在五级流水中,第N条指令要在第五级才把结果写回到寄存器,而第N+1条指令要在第二级译码阶段就要把寄存器的值读出来。此时,第N条指令还没把R1写回,第N+1条指令就去读R1的值(旧值),这样就造成了运算结果的错误,这就是指令相关导致了执行结果错误。
    指令相关分为三种:数据相关,控制相关以及结构相关。控制相关是指一条指令是否执行取决于转移指令的执行结果,在目前的设计中,我们利用延迟槽技术使控制相关得以解决。结构相关是指两条指令用了同一个功能部件,在目前的设计中,我们不用去考虑结构相关。最头疼的是数据相关了,数据相关是指两条指令访问了同一寄存器或内存单元,数据相关使得我们的执行结果出错,我们需要重新设计通路来解决这个问题。
    先来看数据相关有哪些类型:1、RAW写后读相关,就是前面指令写一个寄存器后面指令读该寄存器值 2、WAR读后写相关,后面指令写一个寄存器前面指令读该寄存器的值3、WAW写后写相关,两条指令写同一个寄存器。在本CPU的五级流水中,不会引起后面两种相关:WAR前面指令读旧值,后面指令写旧值;WAW前面指令写旧值,后面指令写旧值。只会引起RAW相关,我们来举个数据相关的例子。
    图中红线表示RAW相关,第一条指令要把结果写回寄存器,第2、3、4条指令要在ID开始阶段读该寄存器的值,按照目前的五级流水,第2、3、4指令会先于第1条指令读出结果,导致了结果错误。

    流水线阻塞

    对于上面说的情况,我们可以让第2条指令在译码阶段等待3拍,然后再去读寄存器的值,同样的,后面的3、4、5条指令也相应等待(由于流水线是有序的,所以第五条指令虽然没有数据相关,但是仍要等待)。我们称这种等待要流水线阻塞(STALL)。
    那么,怎么找到RAW数据相关呢?其实很简单,在ID译码阶段,我们可以将处于该阶段的指令的rs,rt分别和处于EX,MEM,WB阶段的目标寄存器号dest进行相等比较,如果有一个相等,且该寄存器不是0号寄存器(默认0号寄存器的值恒等于0),这条指令不能前进。
    那么,怎么才流水线阻塞呢?我们需要对PC和IR的输入使能进行控制,如果判断结果为1(相等且非0号寄存器),那么就让PC和IR保持当前的值不变(使能端输入0)。同时,让EX阶段的流水线输入指令无效信号,用流水线空泡填充(把0保存到中间寄存器),效果如图所示。

    利用前递技术提高效率

    前面,我们用阻塞技术来保证了流水线按规定的次序执行,但阻塞必然会引起流水线效率的降低,但其实我们可以通过硬件的方式来提高效率。
    我们来回顾上一节的阻塞技术:由于存在数据相关,后面指令需要等待前面指令把值写回以后才能继续执行。那么,有没有可能前面指令结果算完以后不要写回,直接传给后面指令呢?比如,A从图书馆借了本书,B这时候也要借这本书,阻塞技术是说你等A把书还回图书馆后,B在去图书馆借。但我们现在希望A不要把书还给图书馆,直接把书给B不就行了吗?这种技术在流水线中就叫前递技术。

    那么前递技术在硬件上要怎么实现呢?

    设置ALU输入的3选1选择器

    我们可以再原来的ALU每一个输入端增加了一个三选一选择器,三个输入分别是原来的ALU输入和下一级流水线的输出(EX的ALU运算结果)以及再下一级流水线的输出(MEM的结果),这样如果后面指令要用到前面指令的运算或者访存结果,就可以直接通过选择器直接选择前面指令的结果(中间寄存器保存着前面指令的数据),就不用等到结果写回寄存器再从寄存器中读值了。

    设置选择器控制信号

    那这个选择器的控制信号应该怎么设置呢?为了进行前递,我们需要比对处于EX级指令的源寄存器号和处于MEM或WB级指令的目标寄存器号是否相等,如果相等且不是0号寄存器,则说明处于EX流水线指令和前面的指令存在数据相关,那么直接读取前面指令的结果用于ALU的输入。值得注意的是,在原来的设计中,指令的源寄存器号是不用存在中间寄存器中的,使用前递技术后,需要把源寄存器号SRC1和SRC2传递到EX流水级中。
    举个例子(以ALU左输入为例),当SRC1和DESTM(MEM级)、DESTW(WB级)都不相等时,选择中间通路,即正常ALU输入。当SRC1等于DESTW时,选择右边通路。当SRC1等于DESTM且在MEM级上是LOAD操作时,由于目标寄存器值还没有形成(上上条指令还没有执行MEM),所以流水线需要等一拍,后面的流水线暂停,往前面的流水线送空操作。
    具体设计如图所示:

    通过前递技术,COU流水线的效率又提高了很多。到目前为止,我们把CPU越做越复杂,但是效率越做越高。离完成我们的CPU越来越近了!

    流水线的例外处理

    到目前为止,我们几乎已经要完成了CPU的设计,回顾一下,我们已经完成了数据通路,控制通路,流水线等逻辑,看起来所以指令都在规规矩矩的执行。但我们忽略了一个大问题,例外!!(Exception,也叫异常)。举个简单的小例子,CPU在工作时,我们敲了一下键盘,CPU此时需要去响应敲键盘这个事件,而敲键盘就是我们说的例外。例外的发生是随机的,不可预测的,CPU不知道什么时候发生例外。比如这时候CPU在计算C=A+B时,突然来了例外,PC需要跳转到另外一个值去处理这个例外,当例外处理完以后,回来还需要继续算C=A+B,还得算对,就好像没发生过例外一样,所以需要保存现场。
    发生例外时,硬件要保证发生例外的指令前面的所有指令都执行完了,后面的指令一条都没动。在流水线中多条指令同时发生例外的情况下,要保证有序的处理。
    在五级流水线中,为了实现精确例外,我们可以在指令的执行过程中,把发生的例外先记录下来,到流水线的写回阶段再进行处理,这样就保证了前面的指令都执行完,后面的指令都没有修改机器的状态。
    因为不知道例外什么时候到来,理论上什么时候处理都可以。所以我们可以再译码阶段对外部中断进行统一采样,然后随译码阶段的指令前进到写回时统一处理。
    那么硬件上我们如何设计例外处理呢?1、首先我们需要保存例外信号(EX)以及发生例外时的指令PC,既然要到写回阶段才处理例外的话,我们需要在每一阶段的中间寄存器都保存这两个信号(增加EX项和PC项),用来记录发生的例外以及例外发生时指令的PC。2、在写回阶段处理例外时,我们需要保存发生例外的PC值。所以我们可以设置一个专门的寄存器EPC来保存该值,然后把PC置为处理该例外的程序的入口地址。3、在PC输入端,我们需要增加一个2选1选择器,一个是正常的PC值,另一个是例外处理程序的入口地址,由例外信号EX控制。
    具体设计如图:
    这个例外处理通路并不是一个完整的通路,它没有保存例外发生的原因,也没有例外返回时把EPC寄存器的值返还给PC的通路等。但操作系统只要知道发生例外的PC,就可以模拟指令执行知道发生例外的原因,还可以通过专用的指令修改PC的值等等做法来解决上述问题。

    总结

    一步一步,从无到右,从简单到复杂,现在,我们的CPU总算是设计好啦!回顾一下这个简单CPU设计的心路历程:首先我们从MIPS里选取了10几条指令构成了一个简单的指令系统,根据这个指令系统我们搭建了一条数据通路,在数据通路的基础上实现了控制逻辑,然后给CPU加上了时钟信号,在这个过程中,我们不断的将指令执行步骤重叠。之后为了提高效率,在此基础上,我们又将执行阶段细分,得到标准的五级流水,提高了主频。在五级流水中,由于数据相关的问题,使得程序执行可能出错,为了保证正确性,我们采用阻塞技术,阻塞引起相关的指令。之后为了提高流水线效率,我们又采用了前递技术,使得前面执行的结果直接传送给后面的指令。最后,我们实现了精确例外,让例外到流水线写回阶段统一处理。这个过程我们简化了很多步骤,真正的CPU远比这个复杂的多。但是我们的小型CPU,麻雀虽小五脏俱全。其实,每一步好好琢磨起来,整个过程并不是特别难!



    更多相关内容
  • MIPS CPU设计(HUST)

    2021-07-01 00:15:52
    cpu.rar,cpu.circ
  • 单总线CPU设计(现代时序)(HUST).zip,1-7关都用这个.txt
  • 8指令单周期MIPS CPU设计 1、单周期硬布线控制器 2、单周期 MIPS(硬布线)
  • 头歌单总线CPU设计(变长指令周期3级时序)(HUST).zip,变长指令周期---硬布线控制器设计.txt,硬布线控制器组合逻辑单元.txt,变长指令周期---时序发生器输出函数设计.txt,MIPS指令译码器设计.txt,变长指令周期---时序...
  • 由KingDuan设计的一个简单的CPU模型,阐述CPU设计过程中的一些原理和经验。 参考文档:https://www.cnblogs.com/kingduan/p/4054484.html
  • 头歌educoder教学实践平台计算机组成原理MIPS CPU设计(HUST),第1关到第5关,源代码txt格式打开。 第1关 单周期MIPS CPU设计 第2关 微程序地址转移逻辑设计 第3关 MIPS微程序CPU设计 第4关 硬布线控制器状态机设计 ...
  • MIPS CPU设计(HUST).zip

    2022-01-04 11:22:52
    MIPS CPU设计(HUST).zip,4-1 MIPS CPU设计(HUST),第6关:单周期CPU单级中断机制设计(构建中).txt,第3关:MIPS微程序CPU设计.txt,第4关:硬布线控制器状态机设计.txt,第7关:单周期CPU多级中断机制设计---硬件堆栈...
  • 内含CPU所有Verilog源码、论文详细解析,作业成绩为优秀 所有代码和论文皆为原创,严禁二次转载!
  • 华科计算机组成原理实验 单总线CPU设计(定长指令周期3级时序)(HUST)解题报告对应资源: https://blog.csdn.net/Spidy_harker/article/details/106296219
  • CPU设计实验报告

    2016-01-20 19:08:54
    计算机组成原理的课程设计,使用vhdl,含有Visio图
  • 设计一个CPU的具体过程,包括实验目的,逻辑图
  • 单周期CPU设计全过程: 组成原理实验课,包含十六条指令实现,完整的代码以及详细的实验报告,是本人实验课的作业。用verilog HDL语言或者VHDL语言来编写,实现单周期CPU的设计。 用Verilog HDL语言编写MIPS32单...
  • 用VHDL写的模拟cpu程序,可以下载到硬件完成仿真,东南大学课程设计
  • 电子科技大学CPU设计:《单周期CPU的设计与实现》-实验指导书.docx电子科技大学CPU设计:《单周期CPU的设计与实现》-实验指导书.docx电子科技大学CPU设计:《单周期CPU的设计与实现》-实验指导书.docx电子科技大学...
  • 16位单时钟周期CPU设计

    热门讨论 2013-05-01 22:42:17
    重庆大学 大三下计算机组成原理第二个项目,满分通过的 十六位单周期CPU设计,用的软件是logisim-win-2.7.1,包含project2终极.circ RAM.hex ROM.hex ROM.s 设计报告.doc 可直接提交了
  • 单周期MIPS CPU设计

    2020-12-09 15:45:32
    单周期MIPS CPU设计,利用运算器实验,存储系统实验中构建的运算器、寄存器文件、存储系统等部件以及Logisim中其它功能部件构建一个32位MIPS CPU单周期处理器。
  • 通过设计并实现支持 10 条指令的 CPU,进一步理解和掌握 CPU设计的基本原理和过程。 实验内容 设计和实现一个支持如下十条指令的单周期CPU。 非访存指令  清除累加器指令CLA  累加器取反指令COM  算术右移一...

    实验二三(CPU部件实现之ALU、寄存器堆、PC、RAM)

    系统硬件综合设计-多周期CPU的设计与实现

    一、实验目的

    通过设计并实现支持 10 条指令的 CPU,进一步理解和掌握 CPU设计的基本原理和过程。

    二、实验内容

    设计和实现一个支持如下十条指令的单周期CPU。

    非访存指令
     清除累加器指令CLA
     累加器取反指令COM
     算术右移一位指令SHR:将累加器ACC中的数右移一位,结果放回ACC
     循环左移一位指令CSL:对累加器中的数据进行操作
     停机指令STP

    访存指令
     加法指令ADD X:[X] + [ACC] –〉ACC,X为存储器地址,直接寻址
     存数指令STA X,采用直接寻址方式
     取数指令LDA X,采用直接寻址

    转移类指令
     无条件转移指令JMP imm:signExt(imm) -> PC
     有条件转移(负则转)指令BAN X: ACC最高位为1则(PC)+ X -> PC,否则不变

    三、实验原理

    单周期 CPU 是指所有指令均在一个时钟周期内完成的 CPU。CPU 由数据通路及其控制部件两部分构成,因而要完成一个支持若干条指令 CPU 的设计, 需要依次完成以下两件事:

    1. 根据指令功能和格式设计 CPU 的数据通路;

    2. 根据指令功能和数据通路设计控制部件。
      在这里插入图片描述

    四、实验步骤

    1. CPU各模块Verilog实现

    PC 模块

    PC 模块功能描述

    输入时钟信号 clk、重置信号 rst
    输出指令地址 pc(8 位)
    功能每个时钟上升沿 PC 的值自动加 1,并输出

    源码:

    module PC(
    input stop,
    input clk,
    input rst,
    input pcJMP,
    input banEBL,
    input ban,
    input wire [7:0] data_in,
    output reg [7:0] PC
    );
    always @(posedge clk )begin
    if(rst==1) begin PC = 0;end
    if(stop!=1)begin
    PC = PC +1;
    if(banEBL == 1) begin
    if(ban == 1) begin
    PC = PC + data_in;
    end
    end
    if(pcJMP == 1 ) begin PC = data_in;end
    end
    end
    endmodule
    

    指令存储器模块

    指令存储器模块功能描述

    输入8位指令地址Addr
    输出16位指令Ins
    功能存放待执行的指令(初始化),并根据地址输出指令
     源码:
     module ins(
      output reg[15:0] outins,
      input wire[7:0] adder,
      output reg[5:0] r1,
      output reg[7:0] r2,
      input wire clk
     );
     reg [15:0] storage [0:255];
     reg [15:0] temp;
     initial begin
      storage [8'h00] = 16'h0000; //start
      storage [8'h01] = 16'h0100; //cla
      storage [8'h02] = 16'h0200; //com
      storage [8'h03] = 16'h0300; //shr
      storage [8'h04] = 16'h0400; //csl
      storage [8'h05] = 16'h0500; //add x
      storage [8'h06] = 16'h0600; //sta x
      storage [8'h07] = 16'h0701; //lda x
      storage [8'h08] = 16'h0803; //jmp
      storage [8'h09] = 16'h0902; //ban
     storage [8'h0a] = 16'hffff; //stop
     end
      always @(adder)begin 
      temp = storage[adder];
      r1 = {storage[adder][5:0]};
      r2 = {storage[adder][7:0]};
      outins = temp;
      end
     endmodule
    

    寄存器堆

    寄存器堆模块功能描述

    输入时钟信号clk、读写控制线wr_en、读寄存器编号read_reg1和 read_reg2、写寄存器编号write_reg、写入数据write_data
    输出对应两个读寄存器编号的寄存器值reg1和reg2
    功能根据读寄存器编号给出对应寄存器的值;在写允许情况下,把写入端的数据在clk下降沿写到写寄存器编号对应的寄存器

    源码:

     module register(RA, RB, RW, busW, clk, WE, busA, busB, RD);
    input wire [2:0] RA, RB, RW;
     input wire [15:0] busW;
     input clk, WE, RD;
     output reg [15:0] busA, busB;
     reg [15:0] Reg[7:0];
     always @(negedge clk)
     begin 
      if(WE == 1)
      begin
      Reg[RW] = busW;
      busA = Reg[RA];
      busB = Reg[RB];
      end else if(RD == 1)begin 
     busA = Reg[RA];
      busB = Reg[RB];
     end
     end
     endmodule
    

    ALU

    ALU 模块功能描述

    输入操作数in1和in2、操作选择信号alu_op
    输出ALU运算结果Z
    功能根据操作选择信号计算in1和in2的运算结果Z

    源码:

    module alu(outalu, a, b, select);
    output [15:0] outalu;
    input [15:0]a, b;
    input [2:0]select;
    reg [15:0] outalu;
    always @(\*)
    case(select)
    3'b000: outalu = a + b;
    3'b001: outalu = a - b;
    3'b010: outalu = a&b;
    3'b011: outalu = a\|b;
    3'b100: outalu = a\<\<b;
    3'b101: outalu = a\>\>b;
    3'b110: outalu = a\*b;
    3'b111: outalu = a%b;
    default: outalu = 16'b1111111111111111;
    endcase
    endmodule
    

    CU

    控制单元模块功能描述

    输入指令(操作码)
    输出寄存器堆的读写控制线wr_en、ALU的操作选择信号alu_op
    功能根据当前指令功能对wr_en和alu_op赋值

    源码:

    module cu(
    input wire[7:0] accout,
    input wire [15:0] outins,
    output reg stop,
    output reg [1:0]accop,
    output reg ena,
    output reg [2:0] aluop,
    output reg enable,
    output reg pcJMP,banEBL,ban
    );
    always @\* begin
    case(outins[15:8])
    8'h00:
    begin
    stop = 0;
    end
    8'hff:
    begin
    stop = 1;
    end
    8'h01:
    begin
    ena = 1;
    accop = 2'b00;
    banEBL = 0;
    pcJMP=0;
    end
    8'h02:
    begin
    ena = 1;
    accop = 2'b01;
    banEBL = 0;
    pcJMP=0;
    end
    8'h03:
    begin
    ena = 1;
    accop = 2'b10;
    banEBL = 0;
    pcJMP=0;
    end
    8'h04:
    begin
    ena = 1;
    accop = 2'b11;
    banEBL = 0;
    pcJMP=0;
    end
    8'h05:
    begin
    ena = 1;
    enable = 0;
    aluop = 3'b000;
    banEBL = 0;
    pcJMP=0;
    end
    8'h06:
    begin
    ena = 1;
    enable = 1;
    banEBL = 0;
    pcJMP=0;
    end
    8'h07:
    begin
    ena = 1;
    enable = 0;
    banEBL = 0;
    pcJMP = 0;
    end
    8'h08:
    begin
    pcJMP = 1 ;
    end
    8'h09:
    begin
    banEBL = 1 ;
    if(accout[7] == 1)
    begin
    ban = 1 ;
    end
    end
    endcase
    end
    endmodule
    

    2. CPU顶层文件封装实现

    通过将以上定义的模块进行连接、封装就得到了目标
    CPU,该CPU的输入为系统时钟信号clk和重置信号 reset。

    源码:

    module cpu(
    input clk,
    input rst,
    output wire [7:0] pc
    );
    
    wire [2:0] RA, RB, RW;
    wire [15:0] busW;
    wire WE, RD;
    wire [15:0] busA, busB;
    wire [15:0] accout;
    wire [2:0] aluop;
    wire stop, pcJMP, banEBL, ban, ena, enable;
    wire [15:0] outins;
    wire [1:0] accop;
    wire [5:0] r1;
    wire [7:0] r2;
    wire [15:0] dataout, out1, out2;
    alu malu(.outalu(out1),.a(accout),.b(out2),.select(aluop));
    
    pc
    mpc(.stop(stop),.clk(clk),.rst(rst),.pcJMP(pcJMP),.banEBL(banEBL),.ban(ban),.data_in(r2),.pc(pc));
    
    ins mins(.outins(outins),.adder(pc),.r1(r1),.r2(r2),.clk(clk));
    
    cu
    mcu(.accout(accout),.outins(outins),.stop(stop),.accop(accop),.ena(ena),.aluop(aluop),.enable(enable),.pcJMP(pcJMP),.banEBL(banEBL),.ban(ban));
    
    acc myacc(.ena(ena),.accop(accop),.accin(out1),.acc(accout));
    
    register mregister(RA, RB, RW, busW, clk, WE, busA, busB, RD);
    
    Datastorage
    mDatastorage(.address(r1),.enable(enable),.clk(clk),.dataout(dataout),.datain(accout));
    
    endmodule
    

    3. CPU模拟仿真

    为了仿真验证所实现的
    CPU,需要定义测试文件并在测试文件中对指令存储器和寄存器堆中的相应寄存器的值进行初始化,并通过仿真波形图查看是否指令得到了正确执行。

    源码:

     module MyCPU_tb;
     parameter CYCLE = 32;
     reg clk;
     wire [7:0] PC;
     reg rst;
    cpu MyCPU(clk, rst, PC);
     initial clk = 0;
     always \#(CYCLE/2) clk = \~ clk;
     initial begin
     clk = 1;
      rst = 0;
      (CYCLE\*1) 
     clk = 1;
      rst = 1;
    (CYCLE\*2) 
      rst = 0;
    (CYCLE\*3) 
      rst = 0;
    (CYCLE\*4) 
     rst = 0;
    (CYCLE\*5) 
     rst = 1;
    (CYCLE\*6) 
     rst = 0;
    (CYCLE\*7) 
     rst = 0;
    (CYCLE\*8) 
      rst = 0;
      (CYCLE\*9) 
      rst = 1;
     (CYCLE\*9)
     rst = 0;
      force MyCPU.r1 = 7'h5;
      force MyCPU.r2 = 8'h9;
      (CYCLE\*10)
      force MyCPU.outins = 16'h0600;
     force MyCPU.banEBL = 1;
      (CYCLE\*12)
      force MyCPU.accout[7] = 1;
      force MyCPU.r2 = 16'h2;
     (CYCLE\*13)
     force MyCPU.outins = 16'h0902;
      (CYCLE\*14)
      force MyCPU.outins = 16'hffff;
     (CYCLE\*20)
     stop;
     end
     endmodule
    

    五、modelSim仿真结果

    清除累加器指令CLA

    执行指令前,如图5.1所示:

    在这里插入图片描述
    图5.1

    执行指令后,如图5.2所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9lJ6dVt-1610787453384)(media/92d7ac9edf7775c651fc3e37eef5e4a5.png)]
    图5.2

    累加器取反指令COM

    执行指令前,如图5.3所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rB6Sh2LG-1610787453391)(media/e7af1e7dce6c71101e3f3d732c96f6e0.png)]图5.3

    执行指令后,如图5.4所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXynIFUm-1610787453392)(media/a98398c1e25747b02fa75a1d847e9b5a.png)]
    图5.4

    算术右移一位指令SHR:将累加器ACC中的数右移一位,结果放回ACC

    执行指令前,如图5.5所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eMmLaVKX-1610787453395)(media/80dbd2d5db4070c5aaec9ef846fea9ac.png)]

    图5.5

    执行指令后,如图5.6所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xezWK3i0-1610787453397)(media/48bb809aa1ac8ce18a494fab0ce26dd6.png)]

    图5.6

    循环左移一位指令CSL:对累加器中的数据进行操作

    执行指令前,如图5.7所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwA1n8fG-1610787453398)(media/24fa769f95bdfaa04487a1ca1b793d53.png)]
    图5.7

    执行指令后,如图5.8所示
    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPfxkHRG-1610787453399)(media/03dfc2f09759be50326ef504a7c1d2d8.png)]
    图5.8

    停机指令STP

    执行指令前后如图5.9所示

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOViaw3g-1610787453401)(media/fc0b30d1f555eb85c23689619e2c47df.png)]

    图5.9

    加法指令ADD X:[X] + [ACC] –〉ACC,X为存储器地址,直接寻址

    执行指令前后,如图5.10所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r9fjWOMT-1610787453403)(media/d52943c1a02bdebe3666ce493e6cfe92.png)]
    图5.10

    存数指令STA X,采用直接寻址方式

    执行指令前,如图5.11所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q3btY2Tu-1610787453404)(media/73739c832c1bb67492779f69958795a9.png)]

    图5.11

    执行指令后,如图5.12所示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2nW8kme6-1610787453405)(media/2c5accabe8dd4a73409e876005f3160f.png)]

    图5.12

    取数指令LDA X,采用直接寻址

    指令执行前后如图5.13所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTy7UvYi-1610787453407)(media/101a280351ee6fa45cc8396a5813b1e0.png)]

    图5.13

    无条件转移指令JMP imm:signExt(imm) -> PC

    指令执行前后,如图5.14所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7vpaJEMY-1610787453408)(media/f3ce653a6f09b4c16af7a0ff8e914b91.png)]

    图5.14

    有条件转移(负则转)指令BAN X: ACC最高位为1则(PC)+ X -> PC,否则PC不变

    指令执行前后如图5.15所示:

    ### [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8ZZOr53-1610787453411)(media/4f354793d0725ed393c53efcd8f4a7e8.png)]

    图5.15

    六、实验结果分析

    清除累加器指令 CLA:执行清除累加器指令 CLA 前,程序计数寄存器ACC为不确定状态,ACC 处于待机状态;执行 CLA 后,0000 被送入ACC 中,CPU可以开始执行程序。如图 5.1 与图 5.2 所示。

    累加器取反指令 COM:执行累加器取反指令 COM 前,ACC 的输出accout 为 0000H;执行后COM 后,accout 跳变为 FFFFH,ACC 的内容被成功取反输出。如图 5.3 与图 5.4 所示。

    算术右移一位指令 SHR:执行算术右移一位指令 SHR 前,ACC 的输出accout 为FFFFH;执行 SHR 后,accout 跳变为 7FFFH,ACC 的内容被成功右移一位。如图 5.5 与图5.6所示。

    循环左移一位指令 CSL:执行循环左移一位指令 CSL 前,ACC 的输出accout 为7FFFH;执行 CLS 后,accout 跳变为 FFFEH,ACC 的内容被成功循环左移一位。如图 5.7与图 5.8 所示。

    停机指令STP:执行停机指令STP后,PC停止工作,outins赋值为FFFFH。如图 5.9所示。

    加法指令 ADD X:执行加法指令 ADD X 前,程序计数器寄存器 ACC 的值为 9H,r1 为5H,r2 为 9H;执行 ADD X 后,ACC 的值为 13H,即(r2)+(ACC)->ACC,ACC+1->ACC。如图 5.10所示。

    存数指令 STA X:执行存数指令 STA X 前,存储器 dataout 为不确定状态;执行 STA X后,因 ACC=06H,传送給 ALU 的指令为 0B,因此ACC 中的数据 FFFEH在下一个时钟下降沿传送給 dataout。如图 5.11与图 5.12 所示。

    取数指令 LDA X:执行取数指令 LDA X 前,程序计数器寄存器 ACC的值为 06H;执行 LDAX 后,因 accop=3H,所以依然执行(ACC)+1->ACC,因此 ACC的值在下一个时钟下降沿变为 07H。如图5.13 所示。

    无条件转移指令 JMP imm:执行无条件转移指令 JMP imm 前,程序计数器寄存器 ACC的值为 07H,pcJMP=1H,允许无条件跳转;执行JMP imm后outins=0803H,ins=03H,所以在下一个时钟上升沿,ACC 跳变到 03H。如图 5.14所示。

    有条件转移(负则转)指令 BAN X:执行有条件转移(负则转)指令BAN X前,程序计数器寄存器 ACC 的值为04H,r2=02H,ban=1,允许有条件跳转,;执行有条件转移(负则转)指令 BAN X 后,(ACC)+(r2)->ACC,(ACC)+1->ACC,所以在下一个时钟上升沿,ACC 跳变为 07H。如图5.15所示。

    七、后记

    实验二三(CPU部件实现之ALU、寄存器堆、PC、RAM)

    既来之 则赞之
    若有疑问,欢迎评论
    本文仅供参考,务必独立思考

    完整项目baidu网盘链接
    链接:https://pan.baidu.com/s/1B9eXsySMq_MvHXM_PQtemw
    提取码:ehu4

    展开全文
  • 单周期CPU设计实验精选报告.pdf单周期CPU设计实验精选报告.pdf单周期CPU设计实验精选报告.pdf单周期CPU设计实验精选报告.pdf单周期CPU设计实验精选报告.pdf单周期CPU设计实验精选报告.pdf单周期CPU设计实验精选报告....
  • 32位MIPS CPU 设计 实验报告.pdf32位MIPS CPU 设计 实验报告.pdf32位MIPS CPU 设计 实验报告.pdf32位MIPS CPU 设计 实验报告.pdf32位MIPS CPU 设计 实验报告.pdf32位MIPS CPU 设计 实验报告.pdf32位MIPS CPU 设计 ...
  • 通过设计并实现支持一条指令的CPU,理解和掌握CPU设计的基本原理和过程。 二、 实验内容 设计和实现一个支持加法指令的单周期CPU。要求该加法指令(表示为add r1,r2,r3)格式约定如下: 采用寄存器寻址,r1,...

    一、 实验目的

    通过设计并实现支持一条指令的CPU,理解和掌握CPU设计的基本原理和过程。

    二、 实验内容

    设计和实现一个支持加法指令的单周期CPU。要求该加法指令(表示为add r1,r2,r3)格式约定如下:
    采用寄存器寻址,r1,r2,r3为寄存器编号,r1和r2存放两个源操作数,r3为目标寄存器,其功能为[r1] + [r2] -> r3;
    指令字长16位,操作码和地址码字段分配如下所示:

    在这里插入图片描述

    三、 实验原理

    单周期CPU是指所有指令均在一个时钟周期内完成的CPU。CPU由数据通路及其控制部件两部分构成,因而要完成一个支持若干条指令CPU的设计,需要依次完成以下两件事:
    1) 根据指令功能和格式设计CPU的数据通路;
    2) 根据指令功能和数据通路设计控制部件。

    3.1 根据功能和格式完成CPU的数据通路设计
    本实验需要设计的CPU只需要支持一条加法指令,而该指令的功能是在一个时钟周期内从寄存器组中r1和r2中取出两个操作数,然后送到ALU进行加法运算,最后把计算结果保存到r1寄存器中。下图给出了改加法指令的数据通路图。
    在这里插入图片描述

    此外,还需要确定各个部件的位数,为了简单起见,我们假设目标CPU的机器字长、存储字长和指令字长相等均为16位,存储单元个数假设为256,按字寻址,并取PC位数为8。
    3.2 根据指令功能、数据通路完成控制单元的设计
    控制单元的功能是为当前要执行的指令产生微操作命令从而完成该指令的执行。为了能够完成加法指令的执行,结合图1,控制单元需要在取出指令后根据指令操作码(本例中是加法指令),控制ALU(参考实验二)做加法(通过给alu_op信号线相应赋值),并把结果写回寄存器组(参考实验三)中(通过给wr_en赋值为true)。图2给出了整合控制单元后目标CPU的原理图,系统时钟信号也已标注。
    在这里插入图片描述

    自己生成的原理图:
    在这里插入图片描述

    图片清晰度可能不够
    可见附件CPU-1.pdf

    四、 实验步骤

    在第三部分通过对该CPU实现细节的分析、设计,并得到该CPU的原理图后,就可以依次实现各个模块,并进行仿真验证了。
    4.1 CPU各模块Verilog实现
    在前面实验中,已经分别设计和实现了PC、指令存储器、寄存器组和ALU,这里只给出各个模块的功能描述及其接口定义,具体实现可以直接使用或者调整前面试验的实现代码。

    1) PC模块
    在这里插入图片描述

    module pc4(
      input clk, rst,
      output reg [7:0] pc
    );
      
      always@(posedge clk) begin // 上升沿判断
        if(rst == 1)
          pc = 0;
        else
          pc = pc + 1;
      end
     
    endmodule
    

    2) 指令存储器模块
    在这里插入图片描述

    module IPunit4(
      output reg [15:0] Ins,
      input [7:0] Addr
    );
    
      integer i,j;
      reg [15:0] unit[8'b11111111:0]; // 1k按字编址
      
      initial begin
        for(i = 0; i < 256; i = i + 1) 
        begin
          j = i % 6;
          unit[i][2:0] = j;
          unit[i][5:3] = j + 1;
          unit[i][8:6] = j + 2;
          unit[i][15:9] = 0;
        end
      end
      
      always@* begin // 任何时钟改变均会进入
        Ins = unit[Addr];
      end
      
    endmodule
    

    3) 寄存器堆
    在这里插入图片描述

    module reg_stack4(
      input  clk, wr_en,
      input  [15:0] write_data,
      input  [2:0] read_reg1, read_reg2, write_reg,
      output reg [15:0] reg1, reg2
    );
      integer i;
      reg [15:0] regunit[7:0];
      
      initial begin
        for(i = 0; i < 8; i = i + 1) begin
          regunit[i] = i;
        end
      end
      
      always@* begin // 任何一个时钟的变化都会引起
        reg1 = regunit[read_reg1];
        reg2 = regunit[read_reg2];
      end
      
      always@(negedge clk) begin // 在每一个下降沿就行判断
          if(wr_en == 1)
            regunit[write_reg] = write_data;
      end
      
    endmodule
    

    4) ALU
    在这里插入图片描述

    module alu4(
      input  [15:0] in1, in2,st_r1,st_addr,
      input  [2:0] alu_op,
      output reg [15:0] Z
    );
      always@* begin
        case(alu_op)
          3'b000: Z = in1+in2;
          3'b001: Z = in1-in2;
          3'b010: Z = in1&&in2;
          3'b011: Z = in1||in2;
          3'b100: Z = in1<<in2;
          3'b101: Z = in1>>in2;
          
          3'b110: Z = st_r1;// 进一步实验中的
        endcase
      end
      
    endmodule
    

    5) 控制单元
    在这里插入图片描述

    Verilog关键代码:

    module cu4(
      input [6:0] Ins_op,
      output reg wr_en,
      output reg [2:0] alu_op
    );
      always@* begin // 任何时钟信号都引起
        if(Ins_op == 0)
        begin
          wr_en = 1;
        end
          alu_op = 3'b000;// 设置alu的操作码 对应为加
      end
    endmodule
    

    4.2 CPU顶层文件封装实现
    通过根据图2将以上定义的模块进行连接、封装就得到了目标CPU,该CPU的输入为系统时钟信号clk和重置信号reset。
    Verilog关键代码:

    module cpu(
      input  clk, rst
    );
     
      wire wr;
      wire [2:0] alu_op;
      wire [7:0] addr;
      wire [15:0] z, ins, r1, r2,st_r1,st_addr;
      
      pc4 pc4(
        .clk(clk), .rst(rst), .pc(addr)
      );
      
      IPunit4 IPunit4(
        .Addr(addr), .Ins(ins)
      );
      
      cu4 cu4(
        .Ins_op(ins[15:9]),
        .wr_en(wr), .alu_op(alu_op)
      );
      
      reg_stack4 reg_stack4(
        .clk(clk), .wr_en(wr), 
        .read_reg1(ins[8:6]), .read_reg2(ins[5:3]), .write_reg(ins[2:0]),
        .write_data(z), .reg1(r1), .reg2(r2)
      );
      
      alu4 alu4(
         .st_r1(ins[8:6]),.st_addr(ins[5:0]),
         .in1(r1), .in2(r2),
        .alu_op(alu_op), .Z(z)
      );
      
    Endmodule
    

    4.3 CPU模拟仿真
    为了仿真验证所实现的CPU,需要定义测试文件并在测试文件中对指令存储器和寄存器堆中的相应寄存器的值进行初始化,并通过仿真波形图查看是否指令得到了正确执行。
    TestBench关键代码

    module cpu_sim;
      reg clk, rst;
      always #1 clk = ~clk;
      
      initial begin
        clk = 1;
        rst = 1;
        #10 rst = 0;
      end
     
      cpu uut(
        .clk(clk), .rst(rst)
      );
    endmodule
    

    2)ModelSim仿真及分析
    在这里插入图片描述

    添加其他变量到监控中

    在这里插入图片描述

    根据ModelSim仿真,我们可以看出
    Alu_op一直为0,则一直执行加法操作
    Z中存放的是结果 即 r1+ r2 = Z
    根据ModelSim仿真图可以知道实验成功

    五、总结

    通过实验,请思考你认为完成一个CPU的设计与实现主要由哪几个步骤完成?主要注意事项有哪些?
    (1) 设计指令
    设计指令包括设计指令的长度,指令要被分成几段的内容,每段内容需要去赋予什么样的功能,例如存数指令

    在这里插入图片描述

    (2) 设计组件
    例如ALU,CU,IR等组件,每一个组件有自己的功能和输入信号和输出信号,这个环节要保证每一个的组件能够正常运行

    (3) cpu顶层文件封装
    cpu顶层文件封装,即对每一个组件进行连接,实现各个组件的相互通信,以达到cpu能够正常运行

    注意事项 (即PPT第二章到第五章的内容)
    在这里插入图片描述

    第一次运行后,需要手动将一些变量添加进监视器
    在这里插入图片描述

    选择create files
    Add files改变文件会同时改源文件
    所以不要不同实验使用同一份文件(血的教训

    六、进一步实验

    1)在本节实现的单指令CPU基础上,添加存数指令st r1,addr,实现一个可以支持加法和存数指令的CPU,并使用ModelSim进行仿真验证。
    指令st r1, addr: [r1] -> mem[addr]的格式如下:
    在这里插入图片描述

    实验分析提示
    首先根据新增加的访存指令功能设计其数据通路,如图6.1。为使CPU能够支持前面的加法指令和该访存指令,只需要将两个数据通路进行合并(即图3.1和图6.1),并最终得到该CPU的原理图,如图6.2所示。
    在这里插入图片描述

    如果要设计支持更多条指令的CPU,只需根据每条指令设计其数据通路,并采用合并的方式构建支持所有指令的CPU数据通路,然后在进行控制单元的设计即可。在合并过程中需要注意的是:当某个部件的输入端口有多个输入来源的时候需在此端口前添加一个多路选择器从而允许控制部件根据执行的需要选择所需的数据来源。
    在这里插入图片描述

    为了实现我们的存数指令,我们可以在原来的基础上进行修改:
    Verilog文件中需要修改的部分:
    IR
    在这里插入图片描述

    CU
    在这里插入图片描述

    ALU
    在这里插入图片描述

    ModelSim仿真及分析
    在这里插入图片描述

    根据ModelSim仿真,我们可以看出
    Alu_op在0-40ns,80-100ns仍然执行加数指令
    40ns到80ns时为6,执行的是我们的存数指令
    根据ModelSim仿真图可以知道实验成功

    望能对诸君有所帮助,切忌直接抄袭(觉得有用记得点赞呀~)

    展开全文
  • 设计一台微程序控制的模型计算机 1) 拟定指令系统(含机器指令的字长、格式、寻址方式及指令的种类等) 2) 设计数据通路,给出模型机中所含的部件及其间的连接,以及信息在数据通路中传送时所需的微命令。 3) ...
  • 单周期CPU设计

    千次阅读 2022-03-22 10:42:28
    设计一个基于mips指令集子集的单周期cpu。 子集指令如下: addu,subu,add,and,or,slt,addi,addiu,andi,ori,lui ,lw,sw,beq,j,jal,jr。 所有指令都不考虑溢出。 最终实现的单周期处理器能够通过...

    项目源码已在Github开源:

    https://github.com/WatermelonKnife/CPU

    • 设计要求

    1. 设计一个基于mips指令集子集的单周期cpu。
    2. 子集指令如下:

    addu,subu,add,and,or,slt,addi,addiu,andi,ori,lui ,lw,sw,beq,j,jal,jr。

    1. 所有指令都不考虑溢出。
    2. 最终实现的单周期处理器能够通过Modelsim功能仿真。
    • 设计说明
    1. 单周期处理器由 datapath(数据通路)和 controller(控制器)组成。
      1. 数据通路由如下 module 组成:pc(程序计数器)、gpr (通用寄存器组)、alu(算术逻辑单元)、im(指令存储器)、dm(数据存储器)等。
      1. im:容量为4KB(32bit×1024 字)。
      1. dm:容量为 4KB(32bit×1024 字)。
    2. 如下是供你参考的数据通路示意图。


    Figure1 数据通路参考图

    1. clock上升沿有效;reset低电平有效,异步复位;控制信号高电平有效。
    2. reset信号有效时,复位pc寄存器为0x0000_3000
    3. 整个 project 必须采用模块化设计。

    1. 每个 module 由一个独立的 Verilog HDL 文件组成。
    2. 所有mux模块(包括不同位数、不同端口数等)都建模在一个mux.v中。
    3. 为使得代码更加清晰可读,建议多使用宏定义,并将宏定义组织在header.v中。
    4. ctrl.v文件中定义控制模块。
    5. s_cycle_cpu.v定义顶层模块。
    6. code.txt文本文件是需要加载到指令存储器中的机器代码。

    • 模块定义要求
    1. 模块定义必须满足下表中的要求。
    2. 特别注意:im模块的输入pc为32位,但指令存储器只有4kB大小,所以取指令时只取pc的低12位作为地址。

    不直接用32位pc值作为指令存储器地址的原因是mars工具设置的代码段的起始地址是0x0000_3000,但是仿真的时候存储器不能定义太大,否则仿真速度会很慢。所以取pc的低12位作为指令地址,这样CPU就是从指令存储器的地址0开始运行。(当然是在你的程序大小不超过4kB的前提下)

    文件

    模块定义要求

    s_cycle_cpu.v

    //顶层模块

    module s_cycle_cpu(clock,reset);

    input clock;  //时钟信号

    input reset;  //复位信号

    pc.v

    //pc模块

    module pc(pc,clock,reset,npc);

    output [31:0] pc;  //当前指令地址

    input clock;

    input reset;

    input [31:0] npc;  //下条指令地址

    im.v

    //指令存储器

    module im(instruction,pc);

    output [31:0] instruction;

    input [31:0] pc;

    //指令存储器只有4k大小,所以pc12位地址有效(屏蔽掉高位)

    reg [31:0] ins_memory[1023:0]; //4kB指令存储器

    gpr.v

    //通用寄存器

    module gpr(a,b,clock,reg_write,num_write,rs,rt,data_write);

    output [31:0] a;  //寄存器1的值

    output [31:0] b;  //寄存器2的值

    input clock;

    input reg_write;  //写使能信号

    input [4:0] rs; //读寄存器1编号

    input [4:0] rt; //读寄存器2编号

    input [4:0] num_write; //写寄存器编号

    input [31:0] data_write; //写数据

    //32个寄存器

    reg [31:0] gp_registers[31:0];

    dm.v

    //数据存储器

    module dm(data_out,clock,mem_write,address,data_in);

    output [31:0] data_out;

    input clock;

    input mem_write;

    input [31:0] address;

    input [31:0] data_in;

    //4KB数据存储器

    reg [31:0] data_memory[1023:0];

    alu.v

    //alu模块

    module alu(c, a, b);

    output [31:0] c;

    input [31:0] a, b;

    1. 实验过程
      1. 基本模块
        1. PC模块

    1实验思路

           Pc记录当前正在执行的指令的地址,并且能正确在时钟周期上升沿指到下一条指令的地址,所以设计时只需要定义reg,并且在时钟周期上升沿正确赋值即可.

    2具体实现

          

    对模块进行仿真,在时钟周期上升沿,pc正确得到npc结果.

    3遇到的问题及解决

    注意本模块要求初值位置为00003000h,正确赋初值即可.    

    2. IM模块

    1实验思路

    本模块需要定义一个指令存储器,并且能根据输入的pc地址正确查找到指令.设计时,只需要根据pc的地址,正确输出指令内容即可.

    2具体实现

    3遇到的问题及解决

    注意pc的位宽为32位,故地址的后两位永远是零,即四个字节为一个单位.

    3. GPR模块

    1实验思路

           Gpr模块需要定义一个寄存器堆,该寄存器堆需要两个读出数据的端口,故需要两个数据地址的输入,还要能够实现数据写入,即需要数据 , 地址 , 写使能的输入信号,并且要求该模块能根据这些信号正确的读写. 设计时,需要根据读写信号正确读写即可.

    2具体实现

          

    对GPR进行仿真,观察波形图,在正确的写入下,陈宫根据rs,rt 读出了相应的值.

    3遇到的问题及解决

           在设计之初,使用了initial语句实现对零号寄存器的写操作,并且在后面通过判断决定不能再向0号寄存器内部写入数据 .

    然而 后来学习得知,这种写法是无法综合的,故应该更换写法,在输出时判断,如果是零号,就直接输出零.

    4实验反思及思考

           在写实例模块时,不能使用initial语句,该语句只能在testbench中使用.

    ​​​​​​​4. ALU模块

    1实验思路

           该模块需要根据不同的alu_op操作码的值,确定不同的操作,并且正确计算,得出结果.

    2具体实现

          

    经过仿真,可以得出正确的加法结果 c = a + i ;

    3遇到的问题及解决

           在该模块的编写过程中,

    1.曾把无符号指令和有符号指令分开书写,后来学习得知,这样会综合出两个加法器,会增加电路复杂度,所以应该统一有符号和无符号的器件,只是在特殊标识位有区别.

    2.曾不理解slt的作用,而不知道从何下手,后来上网查询了指令功能后,的值需要使用减法器来判断,相减后判断正负即可判断大小.

    4实验反思及思考

           要注意具体仿真出的电路的简洁性,写出优的代码.2.要注意对不同的功能进行聚类,统一写出一个模块,减少电路仿真量.

    ​​​​​​​5. dm模块

    1实验思路

           该模块需要定义内存(即数据存储器),并且能正确进行读写操作. 编写时,只需要根据输入的地址和写使能信号正确读写,并输出结果即可.

    2具体实现

          

    3遇到的问题及解决

           注意pc的大小是4字节,需要忽视掉地址后两位即可.

    4实验反思及思考

           该模块和寄存器类似,正确赋值即可.

      1. 能够执行addu指令的单周期CPU

    1实验思路

           初步只需要实现一个指令,故不需要太多的控制信号,只要建立正确的完整的数据通路即可.书写时,根据老师给出的数据通路,对每一条数据线进行定义,并且正确定义每个模块,并且将线接正确.

    2具体实现

          

    3遇到的问题及解决

    1.实验时,reg_write这种定值的线不知道该如何接,最初先定义了一个寄存器,然后将寄存器接入.最后得知可以直接接入定值线,后来接入后可以正确运行. (调试过程仅在J指令时说明)

    2.实验时,遇到过接口类型不对应的报错,发生错误的原因是线宽度定义错误或者缺失该线的定义,应该在一开始就注意避免这样的错误,先定义后使用.

    4实验反思及思考

           在具体的实例化过程中,如果使用位置对应进行接线,就需要按照一定的规律,以防止自己接线接乱.比如,严格按照前面输出信号,后面定义输入信号,并且对具体的线的定义要有意义,这样在接线时才能知晓自己在接哪里.​​​​​​​

    6. 能够执行R型指令的单周期CPU

    1实验思路

           要想增加R型指令,只需要增加一条aluop控制信号即可.目前为止,控制信号还很少,控制模块还很好写. 书写控制模块,需要根据instruction的op字段和funct字段确定执行的具体操作.

    2具体实现

    Ctrl单元:

          

    顶层模块接线:(仅增加一个ctrl单元)

          

    3遇到的问题及解决

    在该模块的编写过程中,1.曾把无符号指令和有符号指令分开书写,后来学习得知,这样会综合出两个加法器,会增加电路复杂度,所以应该统一有符号和无符号的器件,只是在特殊标识位有区别. (调试过程仅在J指令时说明)

    4实验反思及思考

           1.接线时要先定义后使用.

    2.使用位置关联进行接线,要明确每根线的意义,防止接错.

    3.对ctrl单元的分类要精细.

    1. 添加I型指令

    1实验思路

    实现I型指令时,就需要用到立即数字段.由于立即数在指令中位宽不够,则需要进行扩展,所以要写一个扩展单元,该单元要根据输入的扩展控制信号确定是符号扩展还是零扩展. 2.由于指令变成了I型指令,则输入地址字段变成了rt字段,故需要一个选择器判断regfile的写寄存器地址是rs还是rt.同样,此时出现了立即数直接相加,因此ALU的另一个端口b也需要加一个选择器判断是经过扩展的立即数还是直接从寄存器里读出的值.

    增加了这三个元器件,控制信号又需要三个,因此还要根据指令的op字段确定指令类型,从而细分这些控制信号的值.

    2具体实现

    1.扩展器

    2.顶层模块新加的定义:(图片贴出了最终版本)

    3.控制模块新加的判断语句: (图片贴出了最终版本)

    3遇到的问题及解决

           1.实验过程中,发现线接错,后来重接. (调试过程仅在J指令时说明)

           2.随着控制信号的增多,如何分类成了一个难题,通过回忆数字逻辑相关知识,经过分析,发现不同指令之间的区别,比如R与I的区分只需要判断op==5’h00即可.仅使用了几个选择器就将指令大体分类完成.

    4实验反思及思考

    ​​​​​​​2. 添加MEM型指令

    1实验思路

           增加MEM指令需要增加一个数据存储器,并且在读出数据存储器时加一个选择器判断是alu的输出还是数据存储器的输出.

    因此还需要增加一个控制信号来控制数据读出端口.

    2具体实现

           1.顶层模块:

           2.ctrl单元:

    3遇到的问题及解决

           1.在该模块,遇到读出和写入不正确,后来调试发现控制信号不对,最后改正(调试过程仅在J指令时说明)

    4实验反思及思考

           1.随着控制信号的增多,对不同的分类要明确细致.

           2.在分出新的类别时,要把旧的已存在的控制信号定义清楚.否则延续旧的控制信号值可能导致出错.

           3.随着数据线的增多,接线要明确意义,防止混乱.

    ​​​​​​​3. 添加J型指令

    1实验思路

           按照新的指令要求,需要更改alu能输出zero信号.还要增加几个选择器来判断.数据的组合可以直接用线来完成.具体实现如下:

    2具体实现

           1.alu的增加:

          

           2.过程中使用的{}的定义:

          

    3.顶层模块的增加:

    4.控制模块的增加:

    5.控制模块的总体分类方式:

    3遇到的问题及解决

           实验中,总是遇到仿真到一半直接跳转到最后的情况,反复排查后发现还是自己的控制信号有问题.

           排查过程:

                  1.使用mars单步执行指令,判断寄存器情况;

    2.使用Modelsim仿真cpu,一个一个周期看哪个信号线出错,

    3.最后发现指令总是在3028h的位置直接跳转到304c(应该是302c),观察mars发现是beq指令,故判断是s_npc选择了错误的端口,是控制信号写错导致.最后更改,正确.

    4.经过调试,最后得到正确的仿真结果.

    最终版数据流图:

     

    1. 实验总结

    1.实验过程中,经常犯一些低级错误,原因是对verilog 的语法不熟悉,写出错误代码(比如把组合逻辑块写入时序逻辑内,此时还需要额外定义reg , 在非tb的模块中使用initial语句等)通过实验强化了自己的编码能力.

    2. 随着控制信号的增多,对不同的分类要明确细致.在分出新的类别时,要把旧的已存在的控制信号定义清楚.否则延续旧的控制信号值可能导致出错.

    3. 随着数据线的增多,接线要明确意义,防止混乱.

    4.实验伊始,还不知mars的具体功能和modelsim 可以看具体的寄存器值,可以看定义的每根线的值,后来经过老师的讲解和与同学的讨论,学会了这两个辅助工具的绝妙用处.mars单步执行具体观察寄存器值和执行过程,modelsim将时间设置为100ns,单步执行,具体观察线的值和寄存器的值,判断出错位置. 通过学习这些软件的使用,大大加快了我差错的速度和编写的能力.

    展开全文
  •  参考所给的16位实验CPU设计与实现,体会其整体设计思路,并理解该CPU的工作原理。在此基础上,对该16位的实验CPU(称为参考CPU)进行改造,以设计得到一个8位的CPU。总的要求是将原来16位的数据通路,改成8位的...
  • CPU设计CPU设计CPU设计CPU设计CPU设计
  • 单周期CPU设计【Verilog】

    千次阅读 2021-02-23 21:20:13
    第一章 单周期CPU设计原理 为实现单周期CPU,本文首先研究了单周期CPU的相关理论和工作原理。本章首先简要介绍单周期CPU的基本概念,然后对CPU的工作原理进行简要分析,以便为单周期CPU设计和开发提供理论基础...
  • Verilog单周期CPU设计(超详细)

    万次阅读 多人点赞 2019-01-19 12:27:24
    实验 单周期CPU一、设计目的与目标实验内容实验要求二、课程设计器材硬件平台软件平台三、 CPU逻辑设计总体方案指令模块MIPS指令格式指令处理流程数据通路总体结构图设计流程逻辑图四、模块详细设计PCAdd4...
  • 多周期CPU设计(Verilog) 多周期CPU设计与实现

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 665,878
精华内容 266,351
关键字:

cpu设计