简易CPU设计入门:本系统中的通用寄存器(一)
项目代码下载
请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。
下载本项目代码
准备好了项目源代码以后,我们接着去讲解。
本节前言
从本节开始,我们要逐渐地来接触代码执行中的逻辑了。代码执行逻辑,属于是控制中心的代码,或者是属于控制中心中的一个子系统。
控制中心系统,算是本系统的一个核心系统。它的代码量,在本系统中,是最大的。很长的一段时间里,我都在犯愁着,如何来讲解控制中心的逻辑。其实到现在,我也还在犯愁这件事。不过,也算是经历了很长时间的磨蹭,到了这会儿,也不得不去讲解着了。
从本节开始,我们要去学习的,是本系统中的通用寄存器的逻辑。
通用寄存器的读写,DRAM的读写,我认为,它应该算是一个CPU必须要实现的指令。我们在使用CPu进行运算的时候,必须要有存放数据的地方。而寄存器与内存,是存放数据的基本的场所。
在这里,我们先来学习寄存器读写的知识。
一. 寄存器元素模块:register_element
在我这里,实现寄存器的一个基本的模块,位于【......cpu_me01\code\Ctrl_Center\】下面的【register_element.v】代码文件中。
我们来看一看模块的声明部分。
图1
大家看到了吧?模块的名字,叫做【register_element】,它和代码文件的名字是一致的。从第3行到第9行,一共7行代码,是寄存器元素模块的信号声明的部分。
第三行和第四行,是系统时钟和复位信号。这俩信号,和我们之前所讲解的系统时钟与系统复位信号,是一样的。时钟信号,我们都是将其定为,频率为50MHz。而系统复位信号,则是说,低电平有效复位。高电平的时候,不进行复位操作。
第9行呢,是输出信号,data_reg。这个信号,它同时也是保存了某一个寄存器里面所存储的值。当我们向某一个通用寄存器写入一个值的时候,其实就是将值,写入某一个寄存器元素的【data_reg】变量里面。而读寄存器的操作,其实就是将某一个通用寄存器的【data_reg】变量里面的值,给读取出来。
二. 总线建模:使用 inout 数据类型
在这里呢,其实,比较有趣的,还是四个带有【inout】字样的信号。带有【inout】字样的信号,是用来干嘛的呢?
我本人的话,没咋阅读过别人的处理器设计代码。在使用【inout】类型的变量的时候,我也是凭借着自己的感觉,来使用的。
按照我的理解呢,【inout】类型的变量,它适合用作总线结构。
我们来设想一下,假定说,甲模块,它有一个输入变量,假定它的声明为【input wire _in_sig】。很显然的,这是一个1位的输入信号。然后呢,有三个模块,它们都需要输出一个信号,并且呢,这三个信号,都需要连接到甲模块的【_in_sig】信号上。
在这个时候,如果将三个模块的输出变量的类型,直接声明为【output】类型,并且呢,让这三个模块的输出变量直接驱动甲模块的【_in_sig】信号,那么,这就会出现,三个模块同时驱动甲模块的【_in_sig】的现象,会产生竞争。
在这个时候呢,我们就需要设定一个机制,让三个信号,并不同时驱动甲模块的【_in_sig】输入信号,但是呢,又都跟甲模块的【_in_sig】信号有联系。在这种情况下,我们就可以声明一个总线。
在这个总线上,三个输出信号的模块,假定,它们分别为1号,2号和3号模块。这三个模块,在任一时刻,最多可以有一个模块将值写入这个总线上面。然后呢,出现在总线上的值,可以驱动甲模块的【_in_sig】信号。
三个模块,1号,2号和3号模块,它们可以选择断开或连接到总线上。但是呢,任一时刻,最多仅可以有一个信号连接到总线上。如果1号模块连接到总线,2号与3号均断开与总线的连接,在这种情况下,1号模块的输出值,会出现在总线上,并且呢,1号模块的这个输出值,会驱动甲模块的【_in_sig】信号。
同理地,也可以是说,1号和3模块断开与总线的连接,2号连接到总线上。这个时候,2号模块的输出值出现在总线上,并且它会驱动甲模块的【_in_sig】信号。
同理地,也可以是说,1号和2号模块断开与总线的连接,3号模块连接到总线上。在这个时候,3号模块的输出值出现在总线上,并且驱动甲模块的【_in_sig】信号。
最后呢,也可以是说,1号,2号和3号模块均断开与总线的连接,此时总线处于高阻态。在这个时候,甲模块的【_in_sig】信号线上,没有接收到有效的驱动信号。
像这样地,如果我们需要多个驱动源,来驱动同一个模块信号,此时,为了避免多个驱动源的竞争,我们就可以使用总线结构。对总线的建模,在Verilog中,我们使用【inout】类型的变量。
当然了,仅有【inout】类型,是不足以避免冲突的。我们还需要有总线的协议。总线协议有很多种。I2C是一种,PCI-Express也是一种。
在我们的系统中,其实我这里没有建立什么总线协议,没有总线仲裁机制。我只是自己在代码中,控制着不同的驱动源对总线的使用。并且,在使用中,确保仅有一个驱动源的驱动信号出现在总线上。
目前,对我来讲,I2C总线,它还是一个庞然大物。PCI-Express,那就更是大得厉害了。
如果,你还没有专门地学习过总线知识,没有尝试过,自己去实现一个总线协议的话,那么,我这里的代码,算是能为你提供一个很简单的,使用总线的方法。当然了,非常地简单,简陋。
三. 三个内部信号总线
在图1中,第5行到第7行,它是声明了三个内部信号总线,都是16位的。它们是内部控制信号总线,内部数据信号总线,内部地址信号总线。
这里所说的内部信号总线,虽然它们也带有控制,地址,数据的字样。但是,它应该不属于我们在汇编语言与微机原理教程中所谈到的控制总线、地址总线和数据总线。
我这里用了应该,是因为,我自己也不了解通行的写CPU代码的写法。不知道我自己这里建立的几种内部信号总线,究竟是什么。反正呢,我自己就先这么用着了。
整个的这个项目,有一小部分东西,我是借鉴了流行的教材的写法。但是呢,大多数的东西,都是我自己想出来的。因为,流行的教材上面的代码,我看了一会儿功夫,就觉得看不懂了。所以呢,干脆就自己想,自己来设计一个。
关于第5行到第7行的三种内部信号总线的用法,在这本分节里面,我们先不讲,以后,我们再慢慢地来讲解它们。
四. 完成信号:work_ok_inner
接下来,我们来说第8行代码。它也是一个【inout】类型的信号,因而,它是使用了总线结构。
这个【work_ok_inner】信号,它是做什么用的呢?
在寄存器元素模块里面,当我们进行完了寄存器读写的工作以后,我们需要向控制中心发送一下完成信号,表示说,读写工作已经是完成了。这个完成信号信号,就用【work_ok_inner】信号来表示。
比如说,我们想要往寄存器里面写入一个值,当控制中心从寄存器读写模块里,或者,从我们的这个寄存器元素模块里面,收到了完成信号,那就表示,已经是将值写入目标寄存器里面了。然后呢,控制中心就可以去做其他的工作了。
如果呢,我们想要从寄存器里面读取它的数值,那么,当控制中心收到了完成信号的时候,控制中心就可以从某一个信号总线上接收寄存器元素传过来的值。那么,这个传递寄存器元素保存的数值的信号总线,其实正是内部数据信号总线,也就是我们在第7行声明的【data_sig_inner】信号。
而具体地,寄存器元素模块是如何将寄存器中的数据传递给内部数据信号总线【data_sig_inner】,进而传递给控制中心的,又是如何将完成信号【work_ok_inner】传递给控制中心的,这个,我们以后会慢慢地来讲解。此处,我们仅需要有一个大致的了解即可。
结束语
在本节,我们仅仅是讲了寄存器元素模块【register_element】中的信号声明部分。希望大家能够理解我在本节所讲的东西。
如何讲好控制中心的内容,对我来讲,这算是一个挑战。
我们都加油,争取搞好本项目代码的教学工作。
本节结束。