简易CPU设计入门:取指令(三),ip_buf与rd_en的非阻塞赋值
在开篇,还是请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。
下载本项目代码
准备好了项目源代码以后,我们接着去讲解。
在之前的章节里,我是讲解了,使用Quartus II 13.1的IP核生成的功能,来生成RAM IP核。讲完了这个以后,我们在这一节,接着来讲解取指令模块。
我还是将取指令模块的代码给整体地贴在下面。
module get_instruct
(
input wire sys_clk,
input wire sys_rst_n,
input wire get_inst_en,
input wire [15:0] ip,
output reg decode_en,
output reg [15:0] instruct_code
);
reg [15:0] ip_buf;
wire [15:0] instruct_code_wire;
reg rd_en;
reg rd_en_d1;
reg rd_en_d2;
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
ip_buf <= 16'h0;
else if (get_inst_en == 1'b1)
ip_buf <= ip;
else
ip_buf <= ip_buf;
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if (get_inst_en == 1'b1)
rd_en <= 1'b1;
else
rd_en <= 1'b0;
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
rd_en_d1 <= 1'b0;
rd_en_d2 <= 1'b0;
end
else
begin
rd_en_d1 <= rd_en;
rd_en_d2 <= rd_en_d1;
end
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
decode_en <= 1'b0;
instruct_code <= 16'h0;
end
else if (rd_en_d1 == 1'b1)
begin
decode_en <= 1'b1;
instruct_code <= instruct_code_wire;
end
else
begin
decode_en <= 1'b0;
instruct_code <= instruct_code;
end
ram_disk_256x16 ram_disk_inst (
.aclr ( ~sys_rst_n ),
.address ( ip_buf ),
.clock ( sys_clk ),
.data ( 16'hz ),
.rden ( rd_en ),
.wren ( 1'b0 ),
.q ( instruct_code_wire )
);
endmodule
上述代码块,位于【\cpu_me01\code\get_instruct.v】里面。
一. ip_buf的非阻塞赋值
我们来看看关于ip_buf的非阻塞赋值代码块。
由图1可以看到,ip_buf在复位时,它为0值。检测到输入信号中的【get_inst_en】为高电平以后,ip_buf非阻塞赋值为输入信号【ip】的值。此后,ip_buf保持此值不变。
在这里,取指令使能信号【get_inst_en】,它是来自于控制中心模块【ctrl_center】的。每次准备取指令时,【get_inst_en】才会变为1,且仅仅维持一个时钟周期的高电平。其余的时间里,它为0值。
这样一来,图1中的【ip_buf】,它是说,每检测到一次【get_inst_en】为高电平,就缓存一次【ip】的值,然后在【get_inst_en】为低电平期间,【ip_buf】保持不变。下一次检测到一次【get_inst_en】为高电平的时候,就缓存新的【ip】的值。逻辑不难。
二. rd_en的非阻塞赋值
从图2来看,读使能变量【rd_en】,它在系统复位时,为0值。当检测到控制中心传来的取指令使能信号【get_inst_en】变为高电平的时候,读使能信号【rd_en】变为1。其余时间,【rd_en】为0。
取指令使能我们讲了,它是由控制中心模块传来的,且仅仅维持一个时钟周期的高电平,其余时间为0值。而【rd_en】其实也是一样的。【rd_en】的逻辑,仅仅在检测到【get_inst_en】为1时,【rd_en】才为1,其余时候,【rd_en】为0。而【get_inst_en】仅仅维持一个时钟周期,所以,【rd_en】的高电平也是仅仅维持一个时钟周期。
注意,【ip_buf】与【rd_en】都是本地变量,如下图所示。
图3中的第12行和第15行,可以表明,【ip_buf】与【rd_en】均为本地变量。
三. 延时信号【rd_en_d1】与【rd_en_d2】
在图3中,我们可以看到两个延时信号【rd_en_d1】与【rd_en_d2】。所谓的延时,是对【rd_en】信号进行延时。在这里,虽说我声明了两个延时信号,但是,实际用到的,只有【rd_en_d1】而已。之所以要多申请一个,是因为,我担心一个延时信号不够用。
生活中,好多事情我也是这样的。因为总担心东西会不够用,所以我倾向于多准备一些。对于擅长精打细算的人来讲,我这种做法,可能会显得有些不好理解。
我们还是来看一看代码。
我先来说一说代码的执行效果。效果就是,【rd_en_d1】相比【rd_en】延后了1个时钟周期,【rd_en_d2】又比【rd_en_d1】延后了1个时钟周期。
接下来,我又会用文字说明的方法,来演示这种代码执行过程了。请大家先将图4给保存好,以随时对照着我的文字讲解。
我们知道,【rd_en】变为1,是由于非阻塞赋值。那么,它肯定是在某一个时钟上升沿到来后,在非阻塞赋值的第2阶段变为1的。
那么,我们先将初始条件设置为,时钟周期处于【rd_en】变为1的之前的一个时钟周期。
【初始条件】假定某一个时钟上升沿到来时,rd_en == 0,且本周期里面 rd_en 不会变为1。我们将这个时钟上升沿,定义为0号上升沿。此时,rd_en_d1与rd_en_d2的值均为0。
在0号时钟上升沿到来后的非阻塞赋值第1阶段,计算非阻塞赋值的右侧表达式的值。这个时候,表达式【rd_en_d1 <= rd_en】与表达式【rd_en_d2 <= rd_en_d1】的右侧表达式分别为【rd_en】与【rd_en_d1】,它们都为0。
在0号时钟上升沿到来后的非阻塞赋值第2阶段,将右侧表达式的值赋予左侧的变量。这样一来,在这个阶段里,rd_en_d1与rd_en_d2的值均被赋值为0,与0号时钟上升沿到来时的值是一样的。
【1号时钟上升沿到来】此时rd_en为0,且假定本上升沿到来后的非阻塞赋值第2阶段里,rd_en被非阻塞赋值为1。此时,rd_en_d1与rd_en_d2的值依然为0。
在1号上升沿到来后的非阻塞赋值第1阶段,计算非阻塞赋值的右侧表达式的值。此时,右侧表达式【rd_en】与【rd_en_d1】的值均为0。
在1号时钟上升沿到来后的非阻塞赋值第2阶段,将右侧表达式的值赋予左侧的变量。这样一来,在这个阶段里,rd_en_d1与rd_en_d2的值均被赋值为0,与1号时钟上升沿到来时的值是一样的。
【2号时钟上升沿到来】此时rd_en为1,且本上升沿到来后的非阻塞赋值第2阶段里,rd_en被非阻塞赋值为0。此时,rd_en_d1 == 0,rd_en_d2 == 0。
在2号上升沿到来后的非阻塞赋值第1阶段,计算非阻塞赋值的右侧表达式的值。此时,右侧表达式【rd_en】的值为1,右测表达式【rd_en_d1】的值为0。
在2号上升沿到来后的非阻塞赋值第2阶段,将右侧表达式的值赋予左侧的变量。这样一来,rd_en_d1被赋值为1,rd_en_d2被赋值为0。也就是,rd_en_d1变为1,而rd_en_d2不变。
【3号时钟上升沿到来】此时,rd_en == 0,rd_en_d1 == 1, rd_en_d2 == 0。
在3号上升沿到来后的非阻塞赋值第1阶段,计算非阻塞赋值的右侧表达式的值。此时,右侧表达式【rd_en】的值为0,【rd_en_d1】的值为1。
在3号上升沿到来后的非阻塞赋值第2阶段,将右侧表达式的值赋予左侧的变量。这样一来,rd_en_d1被赋值为0,rd_en_d2被赋值为1。
【4号时钟上升沿到来】此时,rd_en == 0,rd_en_d1 == 0, rd_en_d2 == 1。
在4号上升沿到来后的非阻塞赋值第1阶段,计算非阻塞赋值的右侧表达式的值。此时,右侧表达式
【rd_en】的值为0,【rd_en_d1】的值为0。
在4号上升沿到来后的非阻塞赋值第2阶段,将右侧表达式的值赋予左侧的变量。这样一来,rd_en_d1被赋值为0,rd_en_d2被赋值为0。
我们将上面的几个文字说明的信息摘录一下,我们将【rd_en】,【rd_en_d1】与【rd_en_d2】变为1的时机给摘录下来。
在1号时钟上升沿到来后,在非阻塞赋值的第2阶段,rd_en被非阻塞赋值为1。
在2号时钟上升沿到来后,在非阻塞赋值的第2阶段,rd_en_d1被非阻塞赋值为1。
在3号时钟上升沿到来后,在非阻塞赋值的第2阶段,rd_en_d2被非阻塞赋值为1。
所以呢,rd_en,rd_en_d1,rd_en_d2,它们分别是右边的信号比左边的信号延后一个时钟周期变为1。
四. 延时例题
本文第三节主要是为了向大家讲述延时逻辑。那个具体的文字表述不是重点,重点时,再次出现延时逻辑,你得是能够明白。
下面,我给大家来一个例题。
如果第3节的知识,大家看懂了,那么,图5中的代码,其实是不难的。
图5中的代码,依然是一个延时逻辑。
signal,signal_d1,signal_d2,signal_d3,signal_d4,signal_d5,signal_d6,这几个信号,分别是右边的信号比左边的信号延后一个时钟周期。
延时逻辑,大家明白了没?
结束语
在本节,我又水了一节。
延时逻辑,也算是一个有趣的硬件代码逻辑。希望大家能学会。
本节里面,我没有采用表格的方法来解读代码逻辑,而是采用了文字说明的方法。其实两种方法只是不同的方法而已。你可以选择一种方法,来研究你所关注的问题。
表格的话,其实我还是比较推荐大家去使用它。图表,应该算是现代社会里,我们经常需要去观察和分析的材料吧。
会计里面有资产负债表,炒股的人也需要去看股票价格的走势图。信息时代,图表思维,大概也是我们需要去训练的一种思维了。
本节结束。