项目代码下载
请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。
下载本项目代码
准备好了项目源代码以后,我们接着去讲解。
本节前言
关于通用寄存器,其实,我们已经是讲得差不多了。不过,接下来,在控制中心模块里面,还有一点与通用寄存器的读写操作有关的东西。我们本节就来讲解这个尾巴。
本节涉及的代码,为位于路径【......cpu_me01\code\Ctrl_Center\】里面的【ctrl_center.v】。
一. 系统总线与内部总线
在计算机里面,有三大系统总线,分别是数据总线,地址总线,还有控制总线。在控制中心模块里面,我们是可以找到这三大总线的。
图1
如图1所示,12行到14行,就是我们在控制中心里面所声明的三大总线了。【ctrl_bus】为控制中线,【addr_bus】为地址总线,【data_bus】为数据总线,它们都是16位的【inout】类型的变量。
而在16行到18行,它们是我们之前在通用寄存器的分节里面所讲的三大内部信号总线,分别是内部控制总线【ctrl_sig_inner】,内部地址总线【addr_sig_inner】和内部数据总线【data_sig_inner】。
在下面的截图里面,我展示了控制中心对三大内部总线的代理设置。
图2
在图2里面,125行到127行,显示了三大内部总线与代理变量的绑定情况。对应与三大内部总线的代理变量,分别为【ctrl_bus_represent】,【addr_bus_represent】与【data_bus_represent】。
我们来看一看三个代理变量的声明情况,如下图所示。
图3
在图2的第128行,还有new_task这个变量。在本节,我们会见到它,但是,本节,我暂时不打算细讲它。大家见到了关于new_task的逻辑,也请大家先不要细究new_task的逻辑。
这样一来,对三大内部总线及其代理变量,我们就粗略地讲了一下。
我们再来看几个变量。
图4
在图4中,第65行到第67行,是三大系统总线的缓存变量。为啥要缓存呢?因为,三大总线的有效时间很短。每一个总线,在传递了有效的数据以后,很快地,与总线连接着的线路就又会与总线断开连接了。为了能够在总线与短暂连接的线路断开连接以后,仍然能够使用三大系统总线的有效值,我们选择在总线保存着有效数据的时候,将三大系统总线的数据给保存下来。保存的位置,就是图4的65行到67行的变量。
第64行,我们以前讲过了,它们是四个内部寄存器。每一个内部寄存器都是16位的,inner_reg[0] 代表着第一个内部寄存器,inner_reg[1] 代表着第2个内部寄存器,其余的依此类推。
第68行这个东西,大家可以留有一个印象,它代表着一个索引。怎么来使用,以后见到了再说。
我们还是来看一看65行与67行的三大系统总线缓存变量的逻辑,如下图所示。
图5
从图5可以看到,当系统复位信号为低电平有效时,三大系统总线缓存变量与【ctrl_bus_index】变量被赋值为高阻态z值。默认状态下,三大系统总线缓存变量与【ctrl_bus_index】变量保持原值不变。只有当【new_task】变量为1时,三大系统总线缓存变量会被非阻塞赋值为三大系统总线变量的值,以缓存系统总线变量的数据。同时呢,【ctrl_bus_index】也会将控制总线的位1和位0给缓存下来。
在之前,我已经是说过了,new_task,在这里,我们不去深究,建议大家也先不要去深究。但是呢,既然是涉及了这个东西,我们还是要略微地来谈一谈这个变量。当【new_task】为1时,它是表示说,新任务到来了。注意,new_task为1仅保持一个时钟周期。在new_task为1期间,三大总线会保存着有效数据。同时,有效数据,也仅仅是维持着一个时钟周期,这个时间与new_task的时间是一样的。同时有效,同时无效,且有效的时间仅为一个时钟周期。
由于new_task为1标识了新任务的开始,且标识了,三大总线仅在此时保存着有效数据,而我们以后还需要用到三大总线的有效数据,所以,我们需要在检测到new_task为1的时候,及时地将三大总线的有效数据给保存下来。
从图5的else分支的代码可以了解到,当三大系统总线缓存变量保存了系统总线的值以后,接下来,它们会保持着这个值不变,直到下一次检测到new_task为1时,才会被修改为系统总线的新值。
注意,【ctrl_bus_index】与系统总线缓存变量的逻辑差不多,也是说,每当new_task为1时,就被赋值为新的值。此后,根据else分支的逻辑,【ctrl_bus_index】的值会保持不变,直到下一次检测到new_task为1时,才会被修改为新的值。
在这里,我们来略微地谈一谈,【ctrl_bus_index <= ctrl_bus[1:0]】这句代码的含义。这句代码是说,将控制总线有效值的低2位,位1与位0赋给ctrl_bus_index。
那么,位1位0的组合,代表着什么呢?
我们知道,对于2进制数,不同的数位,代表着不同位权。位0的位权为2的0次方,为1。为1的位权为2的1次方,为2。而位2的位权为2的2次方,为4。位3的位权为2的3次方,为8。位4的位权是2的4次方,为16。
关于位权,我们暂时不往下说了。
位1的位权为2,那么,ctrl_bus[0],代表的是ctrl_bus除以2以后的余数部分,或者是0,或者是1。
位2的位权为4,那么,ctrl_bus[1:0],代表的是ctrl_bus的值除以4以后的余数部分,可能是0,1,2,3中的一个,总之小于4。
位3的位权为8,那么,ctrl_bus[2:0],代表的是ctrl_bus的值除以8以后的余数部分,其可能的值为0,1,2,3,4,5,6,7,总之小于8。
位4的位权为16,那么,ctrl_bus[3:0],代表的是ctrl_bus的值除以16以后的余数部分,其可能的值为0,1,2,3等等,直到15,总之是小于16的数。。
我们知道,ctrl_bus 是16位的,那么,位10的位权,就是2的10次方,为1024。这个时候,ctrl_bus[10-1:0],也就是 ctrl_bus[9:0],它代表的是,ctrl_bus的值除以1024以后的余数部分,其可能的值为0,1,2,3等等,直到1023,总之是小于1024的数。
在这里呢,这个逻辑,你能明白,那是最好的。如果不明白的话,那么,这一块的知识,我建议你把它给背下来。掌握它的形式上的规律,将其背下来。以后呢,你无论是作硬件编程,还是去作软件编程的工作,这个逻辑,都可能会是有用的。
我们换一种形式,ctrl_bus[0],0加1等于1,2的1次方等于2,那么,ctrl_bus[0] 代表着 ctrl_bus 除以2以后的余数部分。其可能的值,为0,1,总之小于2。
ctrl_bus[1:0],方括号里面,冒号左边的值为1,1加1等于2,2的2次方等于4。那么,ctrl_bus[1:0] 代表着 ctrl_bus 除以4以后的余数部分,其可能的值,为0,1,2,3,总之小于4。
ctrl_bus[2:0],方括号里面,冒号左边的值为2,2加1等于3,2的3次方等于8。那么,ctrl_bus[2:0] 代表着 ctrl_bus 除以8以后的余数部分,其可能的值,为0,1,2,3,4,5,6,7,总之小于8。
ctrl_bus[3:0],方括号里面,冒号左边的值为3,3加1等于4,2的4次方等于16。那么,ctrl_bus[3:0] 代表着 ctrl_bus 除以16以后的余数部分,其可能的值,为0,1,2,3等等,直到15,总之小于16。
ctrl_bus[9:0],方括号里面,冒号左边的值为9,9加1等于10,2的10次方等于1024。那么,ctrl_bus[9:0] 代表着 ctrl_bus 除以1024以后的余数部分,其可能的值,为0,1,2,3等等,直到1023,总之小于1024。
那么,【ctrl_bus_index <= ctrl_bus[1:0]】,这句代码,它的代码的意思是,ctrl_bus的低2位的值,赋给ctrl_bus_index。ctrl_bus_index保存着ctrl_bus除以4以后的余数。其可能的值,为0,1,2,3,总之是小于4的值。
为啥要让ctrl_bus_index保存着ctrl_bus除以4的余数呢?为啥要让它的值为0,1,2,3这四个值中的一个呢?
因为,我们在控制中心里面,设置了四个内部寄存器。这四个内部寄存器的索引,恰好是0,1,2,3中的某一个值。这样一来,控制总线 ctrl_bus 的有效值的低2位,其实是指定了某一条微操作要去使用的内部寄存器的索引。也就是,我们要去使用 inner_reg[ctrl_bus_index] 这个内部寄存器。
本节讲到这里,似乎逻辑有点乱。那么,请大家多读几遍吧。我这里,也暂时没有想好说,怎么来梳理这一块的知识。在这里,我建议大家多读几遍,以熟悉我在上面所讲的东西。
二. reg_write_flag组节拍变量与寄存器写操作
我们来看一看这一组的几个变量。
图6
76到78行,我展示了 reg_write_flag 一组的节拍变量的声明代码。不过,还是提前剧透一下,虽说声明了这么多,而实际有用的,是【reg_write_flag】与【reg_write_flag_d1】两个变量。
我们来看【reg_write_flag】的逻辑。
图7
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
reg_write_flag <= 1'b0;
else if ((new_task == 1'b1) && (ctrl_bus >= 16'd0) && (ctrl_bus < 16'd4))
reg_write_flag <= 1'b1;
else
reg_write_flag <= 1'b0;
图7与下面的代码块中的代码是一样的。只不过,截图7的文本太小了,所以我才在代码块中又粘贴了一遍代码,方便大家看清楚代码。
图7中的逻辑是说,当new_task为1,且控制总线的值为【大于等于0,且小于4】时,则【reg_write_flag】被赋值为1。在默认情况下,【reg_write_flag】为0值。new_task为1,代表开始了新任务,开始了新的微操作。当new_task为1时,三大总线上会保存着有效数据。其中呢,控制总线的值,代表着本次任务的类型。
在new_task为1时,如果【0 <= ctrl_bus < 4】,则本任务为寄存器写操作。控制总线的低2位指定了使用的内部寄存器的索引。此时,地址总线上保存着的,是待写入的通用寄存器的 id 号。也就是,本次的操作任务,是将 inner_reg[ctrl_bus[1:0]] 的值,写入地址总线所指定的 id 号的通用寄存器。
而关于 ctrl_bus[1:0],我们说了,它将被赋给ctrl_bus_index这个变量。所以,我们也可以说,本次的操作任务,是将 inner_reg[ctrl_bus_index] 的值,写入写入地址总线所指定的 id 号的通用寄存器。
我们来看一下【reg_write_flag_d1】和【reg_write_flag_d2】的逻辑。如下图所示。
图8
从图8,我们可以知道,【reg_write_flag_d1】和【reg_write_flag_d2】属于是节拍变量。【reg_write_flag_d1】比【reg_write_flag】延后一个时钟周期。【reg_write_flag_d2】比【reg_write_flag_d1】延后一个时钟周期。
也就是说,当某一个时钟上升沿,系统检测到new_task为1,且控制总线的值为【大于等于0,且小于4】时,【reg_write_flag】被非阻塞赋值为1。而在下一个时钟上升沿之后的非阻塞赋值阶段,【reg_write_flag_d1】被非阻塞赋值为1。两者相差一个时钟周期。
我们往下看代码。
图9
图10
图9和图10展示了关于三大内部总线代理的部分逻辑。注意,在这里,【ctrl_bus_represent】,【addr_bus_represent】和【data_bus_represent】是内部总线代理。我们再将代理绑定代码贴一次。
图2副本,内部总线与对应的代理变量的绑定
从图9和图10来看,在默认状态下,三大总线代理的值都是处于高阻态的。因此,控制中心模块里面的三大内部总线变量的值,默认地,是处于高阻态的,是断开与总线的连接的。
在某一个时钟的上升沿,当检测到【reg_write_flag == 1'b1】条件满足时,三大内部总线代理会被非阻塞赋值为0值。这里的0,是一个无意义值。
在下一个时钟的上升沿,系统会检测到【reg_write_flag_d1 == 1'b1】条件满足,此时,三大内部总线代理变量会被非阻塞赋值为有效值。
【ctrl_bus_represent <= 16'h0001;】这一行代码的意思是,通过【ctrl_bus_represent】代理,给内部控制总线【ctrl_sig_inner】赋值为1。一个16位的变量,将其赋值为1,也就是说,它被赋值为0x0001,这个值,它只有位1为1,其余位都是0值。
大家还记得,【ctrl_sig_inner】的位1为1,代表什么含义吗?
图11
/*********************************************
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:保留
还有一种运算叫做读取立即数,将立即数放入内部寄存器。
此运算不需要通过内部信号的参与。
************************************************/
大家看到了吧?给【ctrl_sig_inner】赋值为1,令其位1为1,其余位都是0,这代表着,我们给通用寄存器发送了一个寄存器写使能信号。
那么,我们给发布寄存器写使能信号时,我们还得指定,要将数据写入哪个通用寄存器。这个呢,我们在内部地址代理总线上指定了要写入的通用寄存器的 id 号。
【addr_bus_represent <= addr_bus_buf;】,这一句代码,就是将寄存器 id 号,通过内部地址总线代理变量,写入内部地址总线上。注意,图9的427行的注释,有点问题。应该是说,【内部地址总线缓存着寄存器 id】,而不是说【地址总线缓存这寄存器 id】,这是我的疏忽。
想要进行着寄存器写操作,我们除了发布写使能信号与指定待写入的通用寄存器的 id 号之外,我们还得指定,要写入什么数据啊。
【data_bus_represent <= inner_reg[ctrl_bus_index];】这一行代码,就是用来指定待写入数据的。通过内部数据总线的代理变量,将有效数据,写入内部地址总线上。写入的值,是inner_reg[ctrl_bus_index] 。
等到过了【reg_write_flag_d1 == 1'b1】的时机以后,关于三大内部总线代理变量的条件,就会落入 else 分支里面,这个时候,如图10所示,三大内部总线代理会被赋值为高阻抗z值,也就是,会断开与总线的连接。此时,由于不存在与三大内部总线连接着的线路,所以呢,总线整个地处于高阻态,不存在有效值了。有效值,仅仅是维持了一个时钟周期,也就是,仅仅是在某一个时钟上升沿来到时,检测到【reg_write_flag_d1 == 1'b1】的时候,三大内部总线才被赋予了有效值,并且仅仅维持这一个时钟周期。一个时钟周期以后,三大内部总线就变为高阻态了。
我们来略微地梳理一下这个逻辑啊。
第1步,是new_task为1
并且呢,此时,控制总线 ctrl_bus 上保存着本次操作任务的类型。如果ctrl_bus 的取值范围是【大于等于0,且小于4】,这就代表着,本次,我们要去进行的,是寄存器写操作。
当new_task为1时,控制总线的低2位,指定了要使用的内部寄存器的索引。
通过【ctrl_bus_index <= ctrl_bus[1:0];】语句,将内部寄存器的索引给保留下来。在寄存器写操作里面,我们需要将 inner_reg[ctrl_bus_index] 的值写入某一个通用寄存器里面。
因此,在寄存器写操作里面,控制总线的低2位,实际上是指定了作为源操作数的内部寄存器的索引值。
在某一个时钟上升沿到来时,当检测到new_task为1,并且【0 <= ctrl_bus < 4】时,【reg_write_flag】会被非阻塞赋值为1。同时,三大总线的值,会分别被缓存到【ctrl_bus_buf】,【addr_bus_buf】和【data_bus_buf】里面。还有,执行【ctrl_bus_index <= ctrl_bus[1:0];】语句,保存内部寄存器的索引值。
第2步,reg_write_flag 为1
当时钟上升沿到来,且检测到 reg_write_flag 为1时, reg_write_flag_d1 会被非阻塞赋值为 1。同时呢,三大内部总线会通过各自的代理,被赋值为0值。0值,也就是一个无意义值。
第3步,reg_write_flag_d1 为 1
当时钟上升沿到来,且检测到 reg_write_flag_d1 为1时,三大内部总线,会通过各自的代理,分别被非阻塞赋值为有效值。
【ctrl_bus_represent <= 16'h0001;】,这一语句,将【ctrl_sig_inner】赋值为 0x0001,仅有位1为1,这时,相当于向通用寄存器发布了寄存器写使能信号。
【addr_bus_represent <= addr_bus_buf;】,这一语句,将【addr_sig_inner】赋值为系统总线中的地址总线中缓存的寄存器 id 号。本次,要写入的寄存器的 id 号,就在 addr_bus_buf 中保存着。
【data_bus_represent <= inner_reg[ctrl_bus_index];】,这一语句,将待写数据 inner_reg[ctrl_bus_index] 的值,通过内部数据总线代理,写入内部数据总线。
第4步,reg_write_flag 和 reg_write_flag_d1 均为0,三大内部总线代理的代码逻辑处于else 分支
时钟上升沿检测到这个条件,则控制中心模块的三大内部总线变量通过各自的代理变量,被非阻塞赋值为高阻态,因而,控制中心模块里面的三大内部总线变量,会与三大内部总线断开连接。三大内部总线处于高阻态。
三. reg_read_flag组节拍变量与寄存器读操作
图12
图12展示了这三个节拍变量。老样子,【reg_read_flag】和【reg_read_flag_d1】有用,而【reg_read_flag_d2】无用。
我们来看【reg_read_flag】的逻辑。
图13
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
reg_read_flag <= 1'b0;
else if ((new_task == 1'b1) && (ctrl_bus >= 16'd4) && (ctrl_bus < 16'd8))
reg_read_flag <= 1'b1;
else
reg_read_flag <= 1'b0;
在图13里面,我们可以看到【reg_read_flag】的代码逻辑。它的逻辑,平时为0值。而在某一个时钟上升沿到来时,当检测到new_task为1,且【4 <= ctrl_bus < 8】时,则【reg_read_flag】被非阻塞赋值为1。
new_task为1,代表着,新任务开始了。此时,系统控制总线【ctrl_bus】以及其他的两个总线都会保存着有效值。如果此时系统控制总线【ctrl_bus】中保存着的值的取值范围,是【4 <= ctrl_bus < 8】,则本次的新任务为寄存器读操作。
在这里,其实,【reg_read_flag】与【reg_write_flag】变为1时,【ctrl_bus】的取值范围是挨着的,且区间长度都是4。
如果【reg_write_flag】会被非阻塞赋值为1,则【ctrl_bus】的取值范围是,【0 <= ctrl_bus < 4】。此时的新任务,为寄存器写操作。
如果【reg_read_flag】会被非阻塞赋值为1,则【ctrl_bus】的取值范围是,【4 <= ctrl_bus < 8】。此时的新任务,为寄存器读操作。
好玩吗?
接着往下看。
图14
图14又是一个延时逻辑。【reg_read_flag_d1】比【reg_read_flag】延后一个时钟周期,而【reg_read_flag_d2】比【reg_read_flag_d1】延后一个时钟周期。
接着往下看。
图15
图16
图17
图15到图17展示了与 reg_read_flag 有关的逻辑,也就是与寄存器读操作有关的逻辑。
默认状态下,控制中心模块里面的三大内部总线变量及其代理变量,是处于高阻抗z值的状态,与三大内部总线处于断开连接的状态。如图15雨图17所示。
当在某一个时钟上升沿到来时,检测到【reg_read_flag】为1时,如图16所示,则控制中心模块里面的内部控制总线变量与内部地址总线变量会通过各自的代理,被赋值为0值。0值,也就是无意义值。而控制中心模块里的内部数据总线变量会通过它的代理变量被非阻塞赋值为高阻态z值,继续与内部数据总线处于断开连接的状态。
请问,为啥在这里,控制中心模块的内部数据总线变量会被赋值为高阻抗z值而不是0值呢?
因为,在这里,我们要去进行的,是寄存器读操作。在寄存器读操作里面,内部数据总线,应该是说,通用寄存器将它的数据传递到这个内部数据总线上。也就是,在寄存器读操作里面,这个内部数据总线是给通用寄存器使用的,仅用于将通用寄存器的数据放到这上面来。控制中心并不需要向通用寄存器传递数据。传也没用。寄存器读操作,它是要让数据从通用寄存器传递到控制中心,而不是从控制中心传递到寄存器。从控制中心传递到通用寄存器,那是寄存器写操作。而这里,我们要进行的,是寄存器读操作。所以呢,我们不需要在控制中心模块的内部数据总线变量上放入非高阻抗值,将其始终保持着雨内部数据总线的断开连接的状态就可以了。
然后呢,在下一个时钟上升沿到来时,系统会检测到【reg_read_flag_d1】为1。然后呢,我们一条一条去看三个非阻塞赋值语句。
【ctrl_bus_represent <= 16'h0002;】,【16'h0002】的意思就是,只有位1为1,而其余的位都是0。通过内部控制总线代理的非阻塞赋值,让内部控制总线被赋值为【16'h0002】,使其只有位1为1,其余位都是0,这个是什么意思呢?
ctrl_sig_inner[1]:register read enable:寄存器读使能
如上面的代码块所示,内部控制总线的位1为1,代表的是,控制中心向通用寄存器发布了寄存器读使能信号。
控制中心向通用寄存器发布寄存器读使能信号,那么,控制中心也需要指定,本次是想要读取哪个寄存器的值。
【addr_bus_represent <= addr_bus_buf;】,这一语句,使得控制中心模块的内部地址总线变量通过其代理变量,向内部地址总线写入了addr_bus_buf的值。在寄存器读操作里面,地址总线缓存变量中保存的,是待读取的通用寄存器的 id 号。将这个 id 号写入内部地址总线上,也就在发布寄存器读使能信号的时候,指定了待读取的通用寄存器的 id 号。
【data_bus_represent <= 16'hz;】,这一语句,使得控制中心模块的内部数据总线变量通过其代理,被非阻塞赋值非高阻态,因而断开了与内部数据总线的连接。原因,我在上面说过了。在寄存器读操作里面,不需要控制中心传递数据给通用寄存器,只需通用寄存器将数据传递给控制中心。因此,在控制中心向通用寄存器发布寄存器读使能信号时,控制中心模块的内部数据总线变量只需保持着高阻态就可以了。
到了这里,控制中心就算是发布了寄存器读使能信号了,还在内部地址总线上指定了待读取的通用寄存器的 id 号。
在下一个时钟的上升沿,【reg_read_flag】和【reg_read_flag_d1】均为0值,因而,控制中心模块的三大内部总线代理变量的逻辑落入了 else 分支,三大内部总线变量通过各自的代理,被赋值为高阻态值,与三大内部总线均断开了连接。
控制中心完成了它的任务,它也向通用寄存器发布了寄存器读使能信号,完成任务以后,它还同三大内部总线断开了连接。然而,操作并未结束。因为,当通用寄存器在接收到了寄存器读使能信号以后,与内部地址信号总线上的指定的 id 号相等的通用寄存器,会执行寄存器读操作,会将它保存的值,放到内部数据总线【data_sig_inner】上,并同时在【work_ok_inner】总线上写入1值。
通用寄存器会发布了通过【work_ok_inner】总线发布了完成信号,同时也在【data_sig_inner】总线上放入了它保存的数据。那么,在寄存器读操作里面,控制中心的目的,便是读取此值。那么,如何来读取通用寄存器放在【data_sig_inner】总线上的值呢?
我们继续看代码。
图18
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
inner_reg[0] <= 16'h0000;
inner_reg[1] <= 16'h0000;
inner_reg[2] <= 16'h0000;
inner_reg[3] <= 16'h0000;
end
else if (work_ok_inner == 1'b1 && ctrl_bus_buf >= 16'd4
&& ctrl_bus_buf < 16'd8)
inner_reg[ctrl_bus_index] <= data_sig_inner;
图18下面的代码块,包含了图18中,我们需要去注意的代码。
从图18的红色框线部分,我们看到,在系统复位时,四个内部寄存器被赋值为0值。
当在某一个时钟上升沿到来时,系统检测到了【work_ok_inner】总线为1,且 ctrl_bus_buf 的取值范围是,【大于等于4,且小于8】时,则证明本次操作为寄存器读操作。这个时候,内部数据总线上放置着指定的通用寄存器写进去的数据。这个时候,我们需要将内部数据总线【data_sig_inner】的值给取出来,放在一个地方。放在哪里呢?
在寄存器写操作里面,当new_task为1时,ctrl_bus的低2位指定了内部寄存器的索引值,我们需要将指定索引值的内部寄存器的值,写入通用寄存器。也就是,将 inner_reg[ctrl_bus_index] 的值传递给内部数据总线【data_sig_inner】,并最终写入指定id 号的通用寄存器。
而在寄存器读操作里面,当new_task为1时,ctrl_bus的低2位依然是指定了内部寄存器的索引值。我们需要在通用寄存器将其数据放在内部数据总线【data_sig_inner】上,并且同时发布了完成信号【work_ok_inner】以后,将【data_sig_inner】总线上的值放在指定索引值的控制中心的内部寄存器之中。也就是,执行【inner_reg[ctrl_bus_index] <= data_sig_inner;】这一语句。
在图18中,我们用的是【ctrl_bus_buf】而不是【ctrl_bus】,这是因为,此时,系统控制总线【ctrl_bus】早已经是变为高阻态了,此时控制总线上不含有有效值。然而,当初我们检测到new_task为1时,我们已经是将三大系统总线的值,分别缓存到【ctrl_bus_buf】,【addr_bus_buf】和【data_bus_buf】里面了。所以呢,此时,我们可以通过【ctrl_bus_buf】来引用当初的控制总线的值,以确定当初我们所要进行的操作,的确是寄存器读操作。
关于寄存器读操作,我就不去梳理各个信号了,大家自己来梳理时钟节拍与各变量的变化情况吧。
结束语
本节,依然是一个比较长的章节。我自己写东西的时候,也觉得很累。讲的时候,也会觉得讲起来费劲儿。好在,讲完了。
本节,有可能会需要你多看几遍。以后有机会,我会来修改本专栏的许多的章节吧,当然,也会包括修改本节。
大家再见。