简易CPU设计入门:项目总体的执行流程

在这一节,我来讲解我设计的这款简易CPU的总体的执行流程。由于这款CPU,很多东西,都是我根据自己的想法,去设计实现的。因此,会有一些东西,和流行的CPU原理教材上的运行机制有所不同。

我自己去设计CPU的时候,设置了一个分量很重,权限比较大的模块。这个模块叫做控制中心。在CPU运行的过程中,控制中心算是在控制着大部分的运行流程。控制中心模块的设置,算是本项目的一大特色。

下面,我来讲解本项目的总体执行流程。

步骤1:系统初始化

系统初始化,是本CPU运行时,要去做的第一件事。在我的这个项目里面,其实这个初始化没有做啥实际的事情。仅仅是用计数变量去查数,查到某一个数以后,就去发布一个初始化完成信号init_done。这个init_done信号仅保持一个时钟周期的高电平。这个初始化完成信号会传递给控制中心模块。

我目前还没有正式地去学习计算机组成原理,对于计算机的一些个执行流程,还缺乏了解。不过,我想,对于一个运行在仿真平台的CPU,在系统初始化阶段,总会需要初始化设置一些个东西。

比如说,可以去初始化SDRAM控制器,以便我们用这个CPU去执行内存读写功能。可以在初始化阶段,去初始化键盘,初始化显示器等等的东西。

在本项目里面,我没有使用到SDRAM控制器,因为,本项目,目前并没有下载到FPGA开发板上面去运行,仅仅是运行在ModelSIm仿真平台上。我所使用的ram模型,是利用Quartus软件的IP核功能,生成了两个ram IP核。

我所生成的两个ram IP核,一个是ram_disk,一个是ram。其中呢,ram_disk IP核,我用它来存储着程序指令。而ram,我将其用作存储数据的功能。ram_disk,可以将其视作硬盘。而ram,可以将其视为内存条。

对于一个实际的计算机系统,在硬件部分,需要去将硬盘的第一个扇区的代码,加载到内存中。硬盘的第一个扇区,叫做主引导扇区,英文缩写为MBR。将主引导扇区的代码,加载到内存的操作任务,这个是由计算机的BIOS来完成的。

在我这里,由于是仿真CPU,它不会接入硬盘,所以,我是直接设置了ram_disk IP核,并且将需要运行的各种指令,都写入了用于初始化ram_disk的内存初始化文件中,所以,本项目代码里,并不存在BIOS。

如果是写一个更加像真正的计算机系统的CPU项目,那么,这个BIOS,其实是需要去实现的。

而在我的这个项目里面,系统初始化阶段,它没有实现BIOS,没有去 初始化内存,硬盘等等的东西,仅仅是用计数变量差几个数,就当做是完成了初始化工作。所以,这的确是一个简易的CPU。同时呢,这个初始化模块,也作为本项目的一个象征性的,可扩展的模块。

如果你学完了本项目代码,你自己也在后续的学习与工作中,积累了丰富的项目经验,那么,你完全可以自己写一个内容更丰富,机制更加完善的系统初始化模块。

系统初始化模块,产生的是初始化完成信号init_done,它仅仅维持一个时钟周期的高电平。

我们来看下一个步骤。

步骤2:产生取指令使能信号

系统初始化完成信号init_done,会传递给控制中心模块。控制中心收到了这一信号以后,会第一次产生取指令使能信号。取指令使能信号,也是仅仅维持一个时钟周期。

关于时钟周期,在这里我作一下说明。在我的项目里,使用的系统时钟的频率,是50Mhz。·之所以使用这个 时钟频率,是因为,我在学习FPGA开发板的配套教程的时候,里面所用的板载时钟的频率,便是50Mhz。

由于我这里所涉及的CPU,仅仅是运行在仿真平台的,入门型的CPU。所以,在设置时钟频率的时候,我也是将其设置为入门型的50Mhz。至于那种高速的,达到几个Ghz的CPU,我估计,我一时半会儿,应该都是设计不出来的。高速CPU需要考虑的东西,会比低俗CPU需要考虑的东西多一些。在这里,作为入门学习者,我就使用50Mhz这个时钟频率了。

其实,我也简易大家,在入门阶段,不要有着太高的期待。如果凡事在一开始就有着太高的期待,这会影响后续的发展。因为一旦达不到,就容易产生受挫感。可以在初期发展阶段里,设置一个基础的目标。

中庸里面讲,【行远必自迩,登高必自卑】。

道德经里面讲,【合抱之木,生于毫末。九层之台,起于累土。千里之行,始于足下】。

凡事,不建议说,在一开始的时候,就要求的过高,过严。

在去做一个很艰难,很复杂的事情的时候,最好不要一下子就要求得过高。急于求成,会有欲速则不达的问题。稳扎稳打,稳健进取,虽然可能进度会缓慢一些。然而,在前进的路上,面对那种看不到成果的时代,面对一时没有质变的慢速积累与量变时代,可能是我们经常要去做的事情。

如果,只想生活在质变里,只想要生活在里程碑,革命性进展里面,那么,漫长的千里万里的路途,很难说,你能够走得很远。

本步骤,产生的是仅仅维持一个时钟周期的取指令使能信号。这个使能信号会传递给取指令模块。与取指令使能信号同时传递给取指令模块的,还有指令指针寄存器的值。

步骤3:取指令

控制中心产生的取指令使能信号和指令指针寄存器的值,由取指令模块来接收。收到了取指令使能信号以后,取指令模块开始取指令。

前面我们有讲过ram_disk模块,它是由 ram IP核生成的一个东西。在项目代码里面,ram_disk模块的正式名称为【ram_disk_256x16】。名称中的256,表示这个模块包含着256个单元。16表示,每一个ram单元有16bit。也就是说,这个模拟ram disk是一个有着256个2字节的东西,一共是512字节。

为啥只设定了这么点的容量呢?

因为这只是一个入门型的项目。

在取指令模块里面,我是实例化了一个ram_disk实例。代码如下。

图1

里面的具体的参数啥的,以后我还会去讲。在这里,大家只要有一个基本的印象,知道说,对ram_disk模块的实例化,是在取指令模块里面进行的,就可以了。

指令存储在ram_disk的实例里面。想要去指令,我们需要指定从ram_disk的那个地址里面取指令。这个地址,就是控制中心传过来的指令指针寄存器的值。

指令指针寄存器,在英特尔处理器里面,我们可以把它叫做 instructtion pointer,简称 IP。

英特尔X86处理器采用分段模型,用代码段寄存器CS和指令指针寄存器联合指定要操作的程序指令地址。然而,并不是所有的处理器都用这种分段模型来指定程序指令的地址,其实也可以用单一的一个寄存器来指定程序指令地址啊。

所以呢,在流行的操作系统原理教材里面,经常将用来指示程序指令地址的东西,叫做程序计数器,英文是【Program Counter】,简称PC。

在我这里,我没有用pc来表示程序计数器,而是用ip或者是instruct_pointer来表示。也就是,我采用了英特尔的术语。

取指令工作完成以后,会产生一个译码使能信号和本哦快刚刚取出的指令的指令码。译码使能信号与指令码,同时输出。

步骤4:译码

取指令模块产生的译码使能信号和指令码均由译码模块来接收。收到了译码使能信号以后,译码模块负责对其进行译码。

译码模块,将收到的指令码分为3个部分。第1部分是操作码,第2部分是保留位,第3部分是操作数。

在我的这个CPU项目里面,每一条指令,固定地占用16bit,也就是2字节的长度。在这16bit里面,位15到位11,用于表示操作码。·位10到位8为保留位,本项目并不使用这3位。而位7到位0,则是用作操作数。如下图所示。

图2

在计算机英语里面,opcode表示操作码,而oprand表示操作数。在图2里面,我是将其分别用op_code和op_rand来表示了。个人爱好。你要是不喜欢这种表示法,你也可以修改成别的 样子。

另外,译码的英文,我在写代码的时候,偷了点懒,没有去差英文。我就直接将其设置为decode_unit了。decode,应该是解码的意思。编码和解码,算是 视频播放,音频播放等等的多媒体操作里面,经常去做的事情。我估计【decoe】应该不是译码的英文写法。但是,我还是这么写了。

在本项目里面,提到【decode_unit】的时候,你需要清除,它是译码模块,也就是传说中的译码器。

译码模块完成以后,会产生译码完成信号【decode_done】信号。图2中有显示这个信号啊。除了译码完成信号之外,操作码,保留位,操作数也会与译码完成信号,同时输出,传递给控制中心。

还得强调一下,这个译码完成信号,也是仅仅保持一个时钟周期的高电平。

在这里,又一次出现了控制中心这个模块。在我的这个项目里面,控制中心是一个很重要的模块,可以说是分量很大的一个模块。

步骤5:公布指令信息

控制中心接收到了译码完成信号以后,会将从译码模块接收而来的操作码,保留位,操作数等,用新的变量,来对其输出。输出的操作码,操作数,保留位,这个是所有指令都可以见到的,相当于是一个指令公示牌。

控制中心公示了指令的操作码与操作数等信息以后,相关的指令单元会作出回应。

步骤6:指令执行

一个处理器,一般地,会支持很多很多的指令。

一条指令的执行,可以划分为很多的执行状态。默认的状态为IDLE。

一个指令模块,它在IDLE状态里,工作就是不停地查询控制中心的指令信息公示牌,看看指令公示牌中的操作码与它自己是否想通。如果相同,那么,本指令模块被激活,本指令就开始了它的执行周期。

本款仿真CPU,它用来表示操作码的位有5位。5个二进制位,它的值的范围是从0到31,共有32种不同的值。因此,本款仿真CPU共可以支持32条指令。

是不是太少了?对于本项目来讲,32条指令,已经是足够用了。因为,整个的项目里面,我仅仅是

实现了4条指令。一个加法指令,一个减法指令,一个将立即数传送给通用寄存器的指令,最后一个是将通用寄存器的值传递给内存单元。

我在设计寄存器的时候,总共是实例化了8个通用寄存器。然而,在指令单元中,实际使用的,仅仅是8个通用寄存器里面的第一个。

所以,对于一个仅实现了4条指令的仿真CPU来讲,最多32条指令的数量,已经是足够用了。

而学完了本专栏教程之后,我相信,你应该能够做到,自己去扩展本项目的指令,很方便地写出乘法,除法,左移,右移等等的指令的模块代码。

本仿真CPU总共是支持4条指令,每一条指令,有很多的运行状态。默认地,是运行在空闲状态,所有的指令都是默认处于空闲状态。所有的指令模块在空闲状态时,都是去不断地查询者,指令公示牌里面的操作码是否与本模块的指令码一致。

当有一天,控制中心的指令公示牌发布了指令信息,并且当指令公示牌里面的操作码与本仿真CPU所实现的4条指令中的某一条指令的操作码一致,这个时候,这个一致的指令模块就会开始执行了。

指令在执行过程中,会向控制中心发信号。这种信号制度,我暂时不展开。以后我们再去细讲。

你说一条指令在执行过程中,它都会做啥呢?

其中的一件事,肯定是要去更新程序计数器,也就是要去更新着本项目中的指令指令寄存器的值。

当一个指令执行完毕以后,会向控制中心发送指令完成信号【job_ok】。

步骤7:控制中心发布新的取指令使能信号

某一个指令模块发布的指令完成信号【job_ok】,由控制中心来接收。控制中心收到了信号以后,如果没啥问题,就发布新的取指令使能信号,以开启下一条指令的取指令、译码、执行的周期循环。也就是,本步骤以后,系统会跳转到步骤3去执行。

这样一来,系统刚开始加电时,会执行步骤1到步骤7。接下来,步骤3到步骤7的循环执行。

当然了,也存在着例外。这个例外情况,指的是,本仿真CPU设置了停机微操作。如果停机微操作被触发的话,那么,在执行这条指令的时候,当步骤7完成以后,整个系统就会悬停在这里,不会往下执行了。

关于微操作的概念,我们以后会具体讲解。在本节,就先不去细讲了。

在这里,我还得是来说明一下。控制中心的指令公示牌,它是说,在执行某一条指令的时候,始终都会显示这一条指令的信息。执行完了某一条指令以后,在执行下一条指令时,当完成了译码以后,控制中心会显示新的指令的信息。

大体上是这样的。在初始化阶段,直到第一条指令的译码工作完成前,指令公示牌里面的操作码,操作数和保留位,显示的一直是0。

从第1条指令完成译码以后,到第2条指令完成译码之前,指令公示牌公布的都是第1条指令的操作码,保留位和操作数。

从第2条指令完成译码以后,到第3条指令完成译码之前,指令公示牌公布的都是第2条指令的操作码,保留位和操作数。

从第N条指令完成译码以后,到第(N + 1)条指令完成译码之前,指令公示牌公布的都是第N条指令的操作码,保留位和操作数。

结束语

本仿真CPU的总体执行流程,到这里就算是讲完了。

整个的项目,它是一个取指令,译码,执行等环节,串行地来执行的。只有执行完了某一条指令以后,下一条指令的取指令、译码、执行的循环才会开始。

所以,本项目是一个简单的,入门型的CPU。它仅仅适合于初学者的入门学习场合。你如果想要用这套代码来运行Windows系统,那肯定是不行的。如果你想靠着这个CPU项目来做服务器,那更是不行的。

对于CPU知识,我也算是处在学习阶段。

有兴趣的同学,可以一起来学习CPU知识。祝愿大家学有所成。

本节结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值