项目代码下载
请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。
CSDN文章:下载本项目代码
上述链接为本项目所依据的版本。
在讲解过程中,我还时不时地发现自己在讲解与注释上的一些个错误。有时,我还会添加一点新的资料。在这里,我将动态更新的代码版本发在下面的链接中。
Gitee项目:简易CPU设计入门项目代码:
讲课的时候,我主要依据的是CSDN文章链接。然后呢,如果你为了获得我的最近更新的版本,那就请在Gitee项目链接里下载代码。
准备好了项目源代码以后,我们接着去讲解。
本节前言
在上一节,我是讲解了【rw_ram】模块的端口声明与变量声明的部分。本节,我来将内存写操作。
本节的代码,主要位于【...cpu_me01\code\Ctrl_Center\】路径里面,所所涉及的代码文件,主要是【rw_ram.v】和【ctrl_center.v】。
一. 内部控制信号
/*********************************************
ctrl_sig_inner[0]:register write enable:寄存器写使能
ctrl_sig_inner[1]:register read enable:寄存器读使能
ctrl_sig_inner[2]:random memory write ebable:内存写使能
ctrl_sig_inner[3]:random memory read enable:内存读使能
ctrl_sig_inner[4]:Arithmetic and Logic calculate:算术逻辑运算
ctrl_sig_inner[5]:reserve:保留
ctrl_sig_inner[6]:reserve:保留
ctrl_sig_inner[7]:reserve:保留
ctrl_sig_inner[8]:reserve:保留
ctrl_sig_inner[9]:reserve:保留
ctrl_sig_inner[10]:reserve:保留
ctrl_sig_inner[11]:reserve:保留
ctrl_sig_inner[12]:reserve:保留
ctrl_sig_inner[13]:reserve:保留
ctrl_sig_inner[14]:reserve:保留
ctrl_sig_inner[15]:reserve:保留
还有一种运算叫做读取立即数,将立即数放入内部寄存器。
此运算不需要通过内部信号的参与。
************************************************/
在图1中,我们着重看40行的代码。它的意思是说,当内部控制信号总线的位2为1,其它位都是0的时候,它代表的是,控制中心发布了内存写使能信号。
接下来呢,我们来看一看。当检测到有效的内存写使能信号以后,都有哪些变化。
二. write_time 与 wr_en
从图2可以看到,write_time属于是这样的一种类型的变量。平时的时候,它是0值。只有在满足特定的条件的时候,它才变为高电平。
这个特定的条件,就是内部控制信号总线【ctrl_sig_inner】的位2为1。
当内部控制信号总线【ctrl_sig_inner】的位2为1时,代表着,控制中心发布了内存写使能信号。这个内部控制总线上的内存写使能信号仅仅是维持一个时钟周期,随后,内部控制总线将会处于高阻态状态。
所以呢,在这里,【write_time】维持高电平状态的时间,也是只有一个时钟周期。其余时间,它都是0值。
我们再来看一看【wr_en】的逻辑。
从图3可以看出,【wr_en】的逻辑是,当检测到【write_time】为1的时候,【wr_en】被非阻塞赋值为1,其余时间,为0值。
由于write_time仅维持一个时钟周期的高电平,所以呢,【wr_en】的高电平时间,也仅仅维持一个时钟周期。
三. 内部地址信号与内部数据信号的缓存
在图4里面,我们本节仅仅关注红色框线所示的部分的代码。
从图4的红色框线的代码部分来看,在系统复位信号为低电平有效时,也就是在系统复位的时候,地址缓存变量【addr_buf】与数据缓存变量【data_buf】被非阻塞赋值为0值。而在平时,没有特殊条件的时候,【addr_buf】与【data_buf】会保持它的现有值不变。
而在内部控制信号总线变量【ctrl_sig_inner】的位2为1时,也就是控制中心发布了内存写使能信号的时候,【addr_buf】会缓存内部地址信号总线变量【addr_sig_inner】的值,【data_buf】会缓存内部数据信号总线变量【data_sig_inner】的值。
也就是说,【addr_buf】与【data_buf】的逻辑是,系统复位时,它俩都被赋予0值。接下来呢,在不考虑其它条件的情况下,每次内部控制信号总线变量【ctrl_sig_inner】的位2为1时,这两个缓存变量,都来缓存一次对应的内部地址总线变量与内部数据信号总线变量的值。然后呢,平常的时候,保持原值不变,直到再次检测到内部控制信号总线变量【ctrl_sig_inner】的位2为1时,再将【addr_buf】与【data_buf】非阻塞赋值为新的对应的内部地址信号总线变量与内部数据信号总线的值。
四. 内存读写模块里面,对 RAM IP 核的实例化代码
图5,展示的是 RAM IP 核的实例化代码。对于 RAM IP 核的生成方法,我们本节不去细讲。想要了解本项目所使用的 RAM IP 核的生成方法与参数配置,请参考下述链接所示的文章。
调用IP核生成指令内存
点击了链接以后,请找到第二分节,在二号分节里面查看生成方法与参数配置。。
在确保你已经了解了本项目的 RAM IP 核的生成方法以后,你可以接着看下面的讲解。
从图5的133行可以看到,我们的【rw_ram】模块所实例化的模块和模块名分别为【ram_256x16】和【ram_256x16_inst】。模块名中的【256x16】表示说,这个 RAM IP 核,包含了256个内存单元,每个内存单元是16位,也就是2字节。
接下来,是信号连接部分。
对于内存写操作来说,我们需要使用的信号连接,位于134行,135行,136行,137行和139行。
在134行里面,我们可以看到,在 RAM IP 内部,使用的复位信号的名称为【aclr】。它的英文全名是【asynchronous clear】。不过呢,这个 RAM IP 核里面的复位信号,它并不是低电平有效的,而是高电平有效。而我们的【rw_ram】模块的系统复位信号【sys_rst_n】是低电平有效的。所以呢,为了使连接到 RAM IP 核的异步复位信号能够正确地发挥作用,我们需要将【rw_ram】模块的系统复位信号,【sys_rst_n】,取反以后,再连接到 RAM IP 核内部的【aclr】信号上。
第136行,我们将【rw_ram】模块的系统时钟信号【sys_clk】连接到 RAM IP 核内部的【clock】信号上。
以上两个信号的连接,是系统时钟与系统复位信号与 RAM IP 核的内部信号的连接方法。
对于内存写操作来说,真正起作用的,是写使能信号【wr_en】,待写数据【data_buf】与地址信号【addr_buf】。我们需要将待写数据【data_buf】的值,写入地址为【addr_buf】的内存单元中。开启写入操作的时机是,写使能信号【wr_en】为有效的高电平。
在这里,操作时序,就起到作用了。我们回顾一下,这些个变量,是怎样的时序变化。
当某一个时钟上升沿来临时,若是检测到内部控制信号总线变量的位2为1时,表明控制中心发布了内存写使能信号。这个时候,在非阻塞赋值阶段,将内部地址信号总线变量【addr_sig_inner】的值缓存到【addr_buf】里面,将内部数据信号总线变量【data_sig_inner】的值缓存到【data_buf】里面。
缓存了待写数据与待写地址信号以后,【addr_buf】与【data_buf】又分别在图5的135行与137行,与 RAM IP 核的内部信号连接了起来。而系统时钟与系统复位信号早就连接到 RAM IP 核上了。此时,有了待写数据与待写地址,所差的,就只剩下一个写使能信号了。
我们假定,当系统检测到【ctrl_sig_inner[2] == 1】条件满足的时候,所在的时钟跳变沿,为0号时钟上升沿。根据图2的代码,在0号上升沿之后的非阻塞赋值阶段,【write_time】被非阻塞赋值为1。
然后呢,在1号时钟上升沿的时候,系统会检测到【write_time == 1】条件满足。在1号上升沿之后的非阻塞赋值阶段,根据图3的代码,我们知道,【wr_en】被非阻塞赋值为1。
在2号时钟上升沿的时候,系统会检测到【wr_en == 1】条件满足。2号时钟上升沿使用的时钟信号,为系统时钟信号【sys_clk】。而 RAM IP 核使用的时钟信号,是由系统时钟信号【sys_clk】连接过去的。也就是,RAM IP 核使用的时钟信号,也是系统时钟信号【sys_clk】。
在图5的139行,我们看到,【rw_ram】模块的【wr_en】变量被连接到了 RAM IP 核的【wren】信号上。
【rw_ram】模块与 RAM IP 核使用相同的时钟信号,都使用了系统时钟【sys_clk】的节奏变化。而【rw_ram】模块的【wr_en】信号与 RAM IP 核的【wren】信号相连。这样,当在【rw_ram】模块中,在2号时钟上升沿里面,系统检测到【wr_en】信号为1的时候,此时,在 RAM IP 核里面,也会同时检测到内部的写使能信号【wren】变量为1。
当 RAM IP 核检测到内部的写使能信号为高电平的时候,就根据待写数据【data】与待写地址【address】,进行 ram 写操作了,将【data】写入【address】地址单元中。
五. 发布完成信号给控制中心
在2号时钟上升沿时,系统会检测到【wr_en】为1,这个时候,RAM IP 核就会开始进行内存写操作了。正常的话,应该是说,在3号时钟上升沿时,内存写操作完成。不过,由于内存写操作,并不需要从 RAM 中接收数据。我们可以在2号时钟上升沿时,向控制中心发布完成信号。
具体地,要怎么来发布呢?
我们先来看一看【write_time_d1】信号。其实,从名字上,大家应该可以猜到,它是一个节拍变量,比【write_time】延后一个时钟周期。我们来看看下面的代码。
从图6里面看,【write_time_d1】确实是比【write_time】延后一个时钟周期的节拍变量。
这样的话呢,由于在1号时钟上升沿到来时,系统可以检测到【write_time】为1,所以呢,在2号时钟上升沿到来时,系统就可以检测到【write_time_d1】为1。
反过来,当检测到【write_time_d1】为1的时候,证明此时的时钟跳变沿,为2号时钟上升沿。我们想要在2号时钟上升沿到来以后,给控制中心发布完成信号。我们来看代码。
从图7的131行可以看到,【work_ok_inner】的代理变量,为【work_ok_represent】变量。
对于图8,我们只看红色框线所示的代码。
从图8中的代码可以看到,【rw_ram】模块内部的代理变量【work_ok_represent】在平时,也就是在系统复位与else分支里面,它是被非阻塞赋值为高阻态。而【work_ok_represent】是【rw_ram】模块内部的【work_ok_inner】总线变量的代理。所以呢,在系统复位与闲来无事时,【rw_ram】模块内部的【work_ok_inner】总线变量也是处于高阻态z值,也是与【work_ok_inner】总线处于断开连接的状态。
然后呢,在1号时钟上升沿到来时,也就是检测到【write_time == 1】条件满足之时,通过代理变量【work_ok_represent】向【rw_ram】模块内部的【work_ok_inner】总线变量非阻塞赋值0值。
在2号时钟上升沿到来时,也就是检测到【write_time_d1 == 1】条件满足之时,通过代理变量【work_ok_represent】向【rw_ram】模块内部的【work_ok_inner】总线变量非阻塞赋值1值。
【rw_ram】模块内部的【work_ok_inner】总线变量与【work_ok_inner】总线是相连的,当【rw_ram】模块内部的【work_ok_inner】总线变量被赋值为1时,【work_ok_inner】总线的值也会同时变为1。
【work_ok_inner】总线变为1,控制中心也有这个同名的总线变量,所以呢,控制中心模块是可以接收到这个变为1的【work_ok_inner】总线的值的。所以呢,控制中心就收到了内存写操作完成的信号了。接下来,控制中心就可以进行其他的操作了。
需要注意的是,在2号时钟上升沿的非阻塞赋值阶段里,【rw_ram】模块内部的【work_ok_inner】总线变量通过代理变量【work_ok_represent】被非阻塞赋值为1。但是,在2号时钟上升沿到来时,在这个时刻,【work_ok_inner】总线上面的值并不是1。
由于是在2号上升沿的非阻塞赋值阶段里,【work_ok_inner】总线变为1值,所以,2号时钟上升沿到来的时刻,【work_ok_inner】总线不是1值,而3号时钟上升沿到来的时刻,【work_ok_inner】总线却是1值。
然后呢,由于图8的 else 分支的作用,在3号时钟上升沿的非阻塞赋值阶段,【rw_ram】模块内部的【work_ok_inner】总线变量通过代理变量【work_ok_represent】的非阻塞赋值代码,变为高阻态值。此时,【rw_ram】模块的【work_ok_inner】总线变量就与【work_ok_inner】总线断开连接了。
此时,由于【work_ok_inner】总线已经没有任何线路与之连接了,所以,【work_ok_inner】总线也变为了高阻态值。但是呢,这个高阻态z值,会在4号时钟上升沿到来时才被检测到。
结束语
好了,到了这里,本节的内存写操作就讲解完成了。
下一节,我们来讲解内存读操作。