在前两节,我是讲解了系统初始化代码与系统初始化的验证代码。
本节,我在前面两节的基础上,来讲解系统初始化代码中的【init_done】信号变为高电平的逻辑。
想要学习本节内容,请自行回顾如下所示的两节内容。
同时呢,我也要求,你得是已经下载了本项目的配套源代码。若是还没有下载,请浏览如下章节,了解下载方法。
下载好了代码以后,请大家自行打开【cpu_me01\code\sys_init.v】。
至于上一节的验证代码,我再将其贴出来一次。
`timescale 1ns/1ns
module tb_sys_init();
reg sys_clk;
reg sys_rst_n;
wire init_done;
wire [9:0] cnt;
assign cnt = sys_init_inst.cnt;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#60
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
sys_init sys_init_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.init_done(init_done)
);
endmodule
接下来,我们来讲解系统初始化模块中的【init_done】变量。
一. 非阻塞赋值的复习
在这里,我之所以要去讲解一下init_done变量的变化情况,其实就是为了讲解一下非阻塞赋值的知识。
非阻塞赋值,这算是一个难点知识。
在行为级语句里面,有一种赋值语句,叫做过程赋值语句。过程赋值语句分为两种类型,一种叫做阻塞型赋值,一种叫做非阻塞赋值。
非阻塞赋值的执行分为两个阶段。
第一阶段,计算右侧表达式的值,并且在计算的时候,使用的是各个变量的旧值。
第二阶段,在相应的仿真时刻,将右侧表达式的计算结果,赋给左侧的变量。
也就是,第一阶段是计算右侧表达式,第二阶段是将结果赋给左侧变量。第一阶段为计算表达式,第二阶段为正式赋值。
非阻塞赋值语句,常常用在时序逻辑代码中,与时钟信号相关联。
二. init_done信号
接下来,我来讲解系统初始化模块中的【init_done】信号。
我们先来看一看cnt变量的情况。
![](https://i-blog.csdnimg.cn/direct/715bb38dab37446992da91a106274157.png)
cnt变量是一个10位的reg型变量。由图1可知,在系统复位信号变为高电平以后,cnt变量开始正常计数,从1计数到20.到了20以后,保持不变。
然后呢,我们来看init_done信号。
![](https://i-blog.csdnimg.cn/direct/03aa088144c5460a901af8dbc6063424.png)
由图2可知,在检测到cnt等于9的时候,init_done被非阻塞赋值为1,并且,仅仅在cnt等于9的时候,init_done才被非阻塞赋值为1。等到检测到cnt为10的时候,init_done又会被非阻塞赋值为0。也就是,init_done的高电平仅保持一个时钟周期。
那么,请问,当init_done变为高电平的瞬间,它是处在【cnt == 9】的左沿,还是处于【cnt == 10】的左沿呢?
我先揭晓答案,是处在【cnt == 10】的左沿。为什么是这种结果,这是我在这一节要去分析的。关于非阻塞赋值的问题,我觉得挺难的。到现在,我也不能说完全掌握了这一知识点。但是呢,我所掌握的部分,我自己的一点理解,我会把它分享给大家。
我说的也不一定对。如果你有更好的理解,也欢迎批评指正。
接下来,我来分析这个过程。
(1)初始条件,假定【cnt == 8 && sys_rst_n == 1】
过了一会儿功夫,时钟的上升沿到来了,对cnt信号进行检测,发现它是小于20的,所以,cnt会被非阻塞赋值为9。我们要对这一过程进行细化。
![](https://i-blog.csdnimg.cn/direct/8b73704f79834a8eaaff19f21b71c555.png)
图3所示的表格,相信,应该是不难理解的。在这里,我们需要明确的是,为什么说,在第一阶段的时候,cnt依然保持原值,而不是赋值为新值9呢?因为按照非阻塞赋值的原理,它是在第二阶段,才被赋予新值9的。在第一阶段,仅仅是计算得到了新值9,但是这个新值并未被赋给cnt变量,所以,在第一阶段,cnt仍然为旧值8,而非新值9。但是呢,新值已经是被计算出来了。
然后呢,到了第二阶段,cnt才被赋予新值9。
经过这样的讲解,我相信,图3中所说明的问题,大家应该是已经理解了。这段讲解,应该是不难的。那么,接下来呢,我稍微地来扩充一下子这个表格,让它包含更多的时钟信号。
![](https://i-blog.csdnimg.cn/direct/61e40d9a35324f8f8181acc9a4e941c7.png)
对于上面的表格,请大家自己去看一看。在这里呢,它都是说,某一个时钟上升沿到来后,在第一阶段,cnt保持原值。在第二阶段,cnt变为新值。这个呢,我们在图3里面,应该就已经是理解了。
在这里呢,有一个新的理解点,是在上升沿到来时,cnt的值。如果在时钟上升沿的瞬间,去检测cnt的值的话,那么,它的值,其实就是cnt变量的旧值,而非非阻塞赋值后的新值。也就是,某一个时钟上升沿到来的时候,如果在这个时候,去检测cnt的值的话,那么,这个时候的cnt变量的值它和第一阶段中cnt的值是一样的。这是另外一个重要的知识点。请大家好好地去理解和回味一下子这个知识点。
对于图3和图4,这两张图所涉及的非阻塞赋值的知识点,请大家,好好地来回味一下子。尤其是说,当你还没有很好地理解非阻塞赋值的时候,这两张图呢,你尤其是需要好好地来回味一下子了。
我个人的学习体会是,学习的时候,阅读新内容,学习新的知识点是一方面。而对于一些个重点,难点,多读几遍,多沉思,这种品味,这种停留,沉淀,其实是学习的一个重要的过程。有的东西呢,如果不好转过来这个弯,那么,你就可以多去看两遍,可以掩卷沉思,可以在这个地方,停一停,让自己适应和回味一会儿。这种沉淀的过程,我认为,它应该也算是学习过程中的重要一环。
我不知道你的智力水平是怎样的。也许呢,你学习新知识,理解重难点知识的时候,你比我掌握得快。但是呢,如果你学习新知识,理解重点难点的时候,你的情况跟我差不多,我还是建议,你显停留一会儿,把这个重难点的部分,先去沉淀那么一会儿。这种沉淀的过程,应该是有助于适应和理解新知识的。
理解好了以后,我们再来扩充一下图4所示的表格,将init_done信号也给加进来。如下图所示。
![](https://i-blog.csdnimg.cn/direct/e3004981f909475eac2ff6b0d247b044.png)
对于 上面的这个图呢,还请大家自己去细看一下啊。
在这里呢,cnt的变化与前面相同,第一阶段保持原值,第二阶段变化为新值10。而我们要重点关注的,是init_dopne信号的变化。
关于init_done信号,在它的检测代码里面,它检测的是cnt的值。在检测的时候,它检测到的,是cnt变量的旧值,而非cnt被非阻塞赋值后的新值。
在第1个上升沿到来时,cnt的值,为8。所以,在init_done信号所在的代码块里面,检测到的cnt的值为8。由于检测到的值是8而不是9,所以,init_done信号在第二阶段依旧为0。
在第2个上升沿到来时,cnt的值为9。此时,在init_done信号所在的代码块里面,检测到的cnt的值为9,符合了init_done变为高电平的条件。所以,在第一阶段,init_done保持原值0,在第二阶段,init_done变为高电平,变为1值。
在第3个上升沿到来时,cnt的值为10。此时,在init_done信号所在的代码块里面,检测到的cnt的值为10,符合init_done信号所在的代码块中的【else】部分的条件,所以 呢,在第一阶段,init_done保持原值中的高电平,而到了第二阶段,就变回了低电平。
以上的分析,是我对与cnt与init_done信号的波形变化的分析。这里也包含着我对于非阻塞赋值的理解。
这一部分的理解,算是我个人的一种理解。对于非阻塞赋值的细节变化,大家在ModelSim里面是看不到的,仅能够通过猜想,通过逻辑分析的方式,来理解其中的变化。
我的理解就是这样的。
本节的知识,说多不多,说少也不少。对于我在上面所展示的几张表格,还请大家多看一看,多想一想。我认为,这一块,是很重要的。
而且呢,这种理解,它与大家之前在别的教材上对于非阻塞赋值的讲解,可能是不一样的。所以呢,对此,我觉得,大家最好是能够把这一节,好好地来想一想。
究竟我说的对不对,有没有哪些个地方,说的不严密,你呢,你都可以好好地去想一想。
最好呢,你自己可以写一些个其他的代码,来验证一下子。验证的话,那就是通过ModelSim的波形图,来进行验证了。当然了,验证的一个基础条件就是,时钟的频率,应该是频率不太高的场景。比如100Mhz,50Mhz,都可以。如果是几个Ghz的高频场景,那你就不要用我的这种分析了。
我目前对于Verilog HDL的学习,也仅仅限于50Mhz,100Mhz等等的,频率不太高的场合。对于几个Ghz的那种高频场合,我暂时还没有能力去研究。
三. init_done信号在cnt的哪个边沿变为高电平呢
我们还是得回到图5或者是图4。
虽说,在第1个上升沿到来时,检测到的cnt值为8,但是,通过非阻塞赋值,在第1个时钟上升沿到来后的第二阶段,cnt会变为9。由于两个阶段的时间间隔非常小,且我们一般都会在test bench文件中设置【`timescale 1ns/1ns】,所以,在ModelSim里面查看波形的时候,cnt在第1个时钟上升沿到来的时候,当时就变为9了。
在ModelSim里面,看不到非阻塞赋值的两大阶段的细微变化。这一变化,只能是由我们自己去分析,去假设和验证。
这样的话,从第1个上升沿开始,到第2个上升沿到来之前,ModelSim里面展示的cnt的值为9。
从第2个上升沿开始,到第3个上升沿到来之前,ModelSim里面展示的cnt的值为10。
ModelSim的波形图变化如下所示。
![](https://i-blog.csdnimg.cn/direct/15ab3022b6a248bbbbe0e1ce48572fee.png)
我们在ModelSim的波形图中也看到了,以【cnt == 8】作为初始条件,它之后的第1个时钟上升沿,init_done为低电平。它之后的第2个时钟上升沿,init_done变为高电平,且仅保持一个时钟周期。初始条件之后的第2个时钟上升沿,正好是位于【cnt == 10】的左沿的位置,而不是位于【cnt == 9】的左沿位置。
结束语
对于本节的讲解,也就是我本人对非阻塞赋值的理解,还是请大家务必 要多看,多思考和品位一下。这算是Verilog中的一个重点知识。这块呢,我觉得是需要认真对待的一点。
在学习知识的时候,有的地方,确实是需要学的精准一些。如果理解得模模糊糊,不透彻,这在平时的小项目里面,或许没啥。但是呢,对于重大的项目来讲,这种模糊理解,可能就会造成一定的障碍了。如果想要锤炼自己的技术,我认为,某些个地方,也是需要有着精准的理解和掌握的。
如果缺乏对一些个知识点的精准理解,那么,这应该是会对个人的能力提升,带来一定的不良影响。
在这里呢,我打算培养的,是这种操作系统与处理器方面的精英人才,我要求能跟下来课程的人,尽量地,把能力,提升到精英的级别。咱们这里,尽量地,不要浅尝辄止。
我自己能力有限,有的地方,可能我的理解,也不到位,还可能存在着理解错误的情况。但是呢,我自己能理解的地方,我会力求将其讲好。
本系列里面,我讲解的是简易CPU代码。以后呢,我有可能会写出来一个逻辑复杂一些的CPU。如果我有能力写出来那种,逻辑复杂一些的,性能优越一些的CPU,那么,我也会尽量地,把它们给讲出来。
我是希望能够这种讲课,带出来一批精英程序员。必须得是带出来一批精英程序员,而且数量必须得是足够。如果是说,人才很多,但是呢,达不到高端的要求的话,那么,很难说,能够开发出英特尔与ARM级别的优秀的处理器来。如果是达到了技术的高端的要求,然而,人数不足,我估计也开发不出来英特尔与ARM级别的高端处理器。
如果有心提升自己的编程水平,有心获得高收益的话,我还是希望,看我的当前的专栏,以及以后的专栏的同学,能够把技术,锤炼到精英的层次,而不要浅尝辄止。