在上一节,我粗略地讲了讲顶层模块代码。在前面的某一个文章里面,我讲了本CPU项目代码的整体运行流程,那一篇文章的链接如下所示。
在项目的总体执行流程里面,首先要去进行的,便是系统的初始化工作。
在系统的初始化阶段,要去做什么呢?在个人电脑上,系统加电以后,首先去运行的,是BIOS软件程序。BIOS,就是基本输入输出系统。这个系统,用来对系统的硬件作各种检测和初始化工作。比如说,系统里面是否有插入鼠标啊,是否有插入硬盘啊,还要对内存进行初始化设置啊,等等。
在我的这个简易的CPU项目代码里面,没有这种复杂的BIOS代码。因为我暂时也写不出来那么复杂的东西。但是呢,我还是觉得,要留有这么一个象征性的模块,表示说,在CPU正常运转之前,先要进行初始化设置。
初始化模块位于哪里呢?我们还是以【cpu_me01】文件夹作为起始,则系统初始化模块位于路径【cpu_me01\code\sys_init.v】里面。
我的这个CPU项目的名字,我将其取名为【cpu_me01】了。项目代码的下载方法,请参阅如下链接。
项目的下载方法,大家已经知道了,初始化模块的文件路径大家也知道了,接下来呢,请大家打开【sys_init.v】文件。
这个文件的篇幅并不大,我将这个代码文件的内容贴在下面的代码块里面。
module sys_init
(
input wire sys_clk,
input wire sys_rst_n,
output reg init_done
);
reg [9:0] cnt;
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt <= 10'd0;
else if (cnt < 10'd20)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
init_done <= 1'b0;
else if (cnt == 10'd9)
init_done <= 1'b1;
else
init_done <= 1'b0;
endmodule
代码块的内容,是供大家浏览用的。不要求你将里面的代码全都给记住。浏览一下即可。
一. 输入输出信号列表
接下来,我们来学习这个模块的代码。
在图1里面,第1行显示了本模块的名字,为【sys_init】。
第3行到第5行,显示了本模块的输入输出信号列表。其中,输入信号为系统时钟信号和系统复位信号。系统复位信号的名字为【sys_rst_n】,里面的【n】表示此信号为低电平有效。输出信号为初始化完成信号【init_done】。
init,这个东西,它是【initialization】的缩写,是【初始化】的意思。
在编程语言里面,申请了一个变量以后,第一次设置它的值,就是对这个变量的初始化。
对一个系统的某些变量,某些东西进行第一次的设置,这个是对系统的初始化。
在信号列表里面,两个输入信号,时钟信号与复位信号,都是从顶层模块【cpu_top】中传递过来的。我们来看一看顶层模块中的相关代码。
在顶层模块图1中,有两个输入信号,它们也是系统时钟信号和系统复位信号。然后呢,在顶层模块图2中,我们看到,顶层模块的系统时钟信号和系统复位信号分别与【sys_init】模块的系统时钟信号与系统复位信号连接起来了。然后呢,【sys_init】模块的初始化完成信号【init_done】还与顶层模块的同名信号连接起来了。在顶层模块图3中,我们看到,我是在顶层模块里面申请了一个wire型的【init_done】变量。
后面我们还会涉及许多的代码文件,它们也都有着系统时钟与系统复位信号。这些个代码文件中的这俩信号,都直接或间接地来自顶层模块中的系统时钟与系统复位信号,就像【sys_init.v】一样。
谈到时钟信号,就会有频率的问题。那么,在我们的系统中,时钟频率为多少呢?设计模块是没有讲的。但是呢,我在顶层模块的test bench文件里面,是有设置的。我是将整个的仿真CPU的时钟频率设置为50MHz。
以下几行关键代码,可以完成这一功能。
`timescale 1ns/1ns
reg sys_clk;
initial sys_clk = 1;
always #10 sys_clk = ~sys_clk ;
如代码块所示,我们将1个时间单位设置为1纳秒。时钟信号的初始值为1,每10纳秒,时钟信号反转一次。20纳秒,为一个时钟周期。
1 / 20ns = 1 / (20 * 10^(-9)) = 1000 / (20 * 10^(-6)) = 50 / 10^(-6) = 50 * 10^6 Hz = 50MHz
在这里,由于我列的不是数学的分式,而是用编程的数学符号来列式计算的,所以可能有点不好懂。以后,等我熟练了CSDN的公式编辑器的用法以后,我再去用标准的数学分式来写了。
在本项目代码里面,时钟频率,为50MHz。前面,我已经推导过了。大家稍微看一看,相信是可以看懂的。
二. 计数变量cnt
接下来,我们回到【sys_init】模块里面,接着看图1。
第8行里面,我是申请了一个10比特的向量变量,变量名为【cnt】。cnt,就是【count】的缩写。cnt,或者是【count】,一般都是用来计数的。
给什么计数呢?
我们接着往下看。
在图2里面,这一块代码是用来设置cnt的执行逻辑的。
cnt变量在系统复位信号为0的时候为0值。当系统复位信号为非0的时候,cnt在每一个时钟信号的上升沿到来时加1。直到变为20的时候,cnt保持此值不变。
三. 初始化完成信号init_done
我们接着往下看。
在图3里面,我们看到,当系统检测到cnt的值为9的时候,【init_done】变量被非阻塞赋值为1。其余时刻,【init_done】均为0值。也就是,【init_done】信号仅当系统检测到【cnt】为9的时候才会变为1,且仅维持一个时钟周期。其余时刻【init_done】信号均为0值。
这样一来,我们来看看系统初始化模块所做的事情。就是申请了一个变量【cnt】来查数。查到9的时候,让init_done被非阻塞赋值为1。当init_done变为1时,表示系统初始化完成。系统初始化完成的高电平有效信号,会通过顶层模块的同名信号,传递给其他模块,推动本仿真CPU继续往后执行着。
结束语
在这里,代码中开始涉及了非阻塞赋值的问题。
非阻塞赋值,我认为,它可以说是Verilog HDL语法中的一个难点。对此,我可能会花费一些个时间,用来探讨这一问题。
有可能,在学习本专栏的时候,你已经是明白了非阻塞赋值的概念了。也有可能,你尚未弄明白。那么,在本专栏里面,我将尝试着对非阻塞赋值的知识点,作一点讲解与梳理。
本节,就先到这里了。大家再见。