简易CPU设计入门:算术逻辑单元(四)
项目代码下载
请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。
CSDN文章:下载本项目代码
上述链接为本项目所依据的版本。
在讲解过程中,我还时不时地发现自己在讲解与注释上的一些个错误。有时,我还会添加一点新的资料。在这里,我将动态更新的代码版本发在下面的链接中。
Gitee项目:简易CPU设计入门项目代码:
讲课的时候,我主要依据的是CSDN文章链接。然后呢,如果你为了获得我的最近更新的版本,那就请在Gitee项目链接里下载代码。
准备好了项目源代码以后,我们接着去讲解。
本节前言
上一节的文章,我主要是采用【复制 + 修改】的方法来完成的。本节,依然会采用这种方法。因为,整个的算术逻辑操作的代码思路,其实是一样的。
在本节,我们要来讲解乘法运算。
本节的代码,位于【......cpu_me01\code\ALU\】路径里面。主要讲解的代码,是【multi_cell.v】代码文件。
一. 端口声明
我们来看一下端口声明部分的代码。
【multi_cell.v】里面的端口声明,和相同路径里面的【ALU.v】里面的端口声明是一样的。
第3行和第4行是系统时钟与系统复位信号。系统时钟信号,其频率为50MHz。系统复位信号为低电平有效。
第6行到第8行,它们对应着控制中心里面的3个内部寄存器。其中呢,【oprand0】对应着控制中心里面的0号内部寄存器,【oprand1】对应着控制中心里面的1号内部寄存器,【oprand2】对应着控制中心里面的2号内部寄存器。
我们可以看一下控制中心里面的代码。
图2里面的代码,是控制中心模块里面的代码。它显示了三个操作数变量与三个内部寄存器的绑定代码。在这里,三个操作数变量【oprand0-2】是如何声明的,以及它们是如何与本节的图1中的三个操作数变量【oprand0-2】相联系的,我就不去展示了。有兴趣的话,你可以自己去查看。
第10行到第12行,是本系统中的三大内部总线。第14行,是用于发送【成功完成】信号的总线。
二. 变量声明与总线绑定
第17行和第18行,是用来计时的两个变量。其中呢,【cal_time_d1】延后【cal_time】一个时钟周期。
第20行,它是用来保存计算结果的。本节的模块名称为【multi_cell】,所以呢,这里的 res,保存的是乘法法运算的运算结果。
第21行和第22行,分别是端口声明中的【work_ok_inner】和【data_sig_inner】总线变量的代理变量。
我们来看一下两个总线代理与对应的总线变量的绑定代码。
图4显示了这种总线变量与它的代理变量的绑定代码。
三. 计算
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
res <= 1'b0;
else if ((ctrl_sig_inner[4] == 1'b1) && (addr_sig_inner == 6'd2))
res <= oprand1 * oprand0;
else
res <= res;
图5中的代码,就是执行计算的代码,它也是关于 res 的逻辑的代码。
res的逻辑是,在系统复位时,它是被赋值为0值。在【else】分支里面,也就是闲来无事时,它是保持现有值不变。而每当满足【(ctrl_sig_inner[4] == 1'b1) && (addr_sig_inner == 6'd2)】条件之时,它就执行计算,将【oprand1 * oprand0】的运算结果赋给 res 变量。
在这里,【ctrl_sig_inner[4] == 1'b1】是什么意思?
我们还是来复习一下内部控制总线的知识。
/*********************************************
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:保留
还有一种运算叫做读取立即数,将立即数放入内部寄存器。
此运算不需要通过内部信号的参与。
************************************************/
根据图6,当内部控制总线的位4为1,而其余位都是0值时,表示控制中心向算术逻辑单元发布了算术逻辑运算使能信号。
算术逻辑运算,可以包含着多种运算类型。加法算一个,减法也算是一个。那么,如何来区分不同的运算类型呢?这个时候,我们就通过内部地址总线【addr_sig_inner】来表示不同的运算种类。
当本模块收到的【addr_sig_inner】总线信号的值是2时,就表示执行乘法运算。
所以呢,图5所示代码的意思是,当【multi_cell】模块收到了算术逻辑运算使能信号,并且根据内部地址信号值,确定本次的运算类型为乘法运算时,则在非阻塞赋值阶段,将端口中的操作数1乘以操作数0,结果放在 res 变量里面。
四. 计时变量
always @(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cal_time <= 1'b0;
else if ((ctrl_sig_inner[4] == 1'b1) && (addr_sig_inner == 6'd2))
cal_time <= 1'b1;
else
cal_time <= 1'b0;
在图7里面,显示了【cal_time】的逻辑。在系统复位与【else】分支里面,它都是被赋予0值。而在满足了【(ctrl_sig_inner[4] == 1'b1) && (addr_sig_inner == 6'd2)】条件之时,它就被赋予 1 值。
这个条件,它其实和第三分节中的 res 被重新计算的条件一样的。它是说,在收到了算术逻辑运算使能信号,且根据内部地址总线的值,判断出,本次执行的运算类型为乘法运算时,则将【cal_time】变量赋值为 1 值。
然后呢,我们再来看【cal_time_d1】的逻辑。
图8显示了【cal_time_d1】的逻辑。它是将【cal_time】延后一个时钟周期,所形成的信号。
五. 总线逻辑
图9显示了本模块的两个总线变量【data_sig_inner】和【work_ok_inner】的对应代理变量【data_sig_represent】和【work_ok_represent】的逻辑。
当检测到【cal_tijme == 1】条件满足之时,在此后的非阻塞赋值阶段,【data_sig_represent】和【work_ok_represent】被赋予 0 值。
当检测到【cal_tijme_d1 == 1】条件满足之时,在此后的非阻塞赋值阶段,【data_sig_represent】和【work_ok_represent】分别被赋予各自的有效值。其中,【data_sig_represent】被赋予 res 的值,也就是,将本模块的算术逻辑运算结果,保存下来。而【work_ok_represent】被赋予 1 值。
在这里,两大代理变量分别与本模块的两大总线变量相绑定。而本模块的两大总线变量,又与各自的同名的内部总线相连接。所以呢,向代理写入了什么值,就相当于,向对应的内部总线传递了什么值。
六. 总体逻辑
本模块的总体逻辑是,当检测到了算术逻辑使能信号,且运算类型为乘法运算时,则执行操作数1乘以操作数0的乘法运算,并将运算结果暂时保存在 res 变量里面。同时,将【cal_time】设置为 1 值。
然后呢,在检测到【cal_time == 1】条件满足之时,对两大内部总线【data_sig_inner】和【work_ok_inner】分别写入 0 值。同时,将【cal_time_d1】设置为 1 值。
在检测到【cal_time_d1 == 1】条件满足之时,对两大内部总线【data_sig_inner】和【work_ok_inner】分别传递有效值。其中呢,【data_sig_inner】被传入运算结果 res 的值,【work_ok_inner】被传递 1 值。
【work_ok_inner】被写入 1 值,表示本次操作成功完成。在【work_ok_inner】被写入 1 值的同时,运算结果也同时被传到【data_sig_inner】总线里面了。所以呢,成功完成信号【work_ok_inner】与运算结果【data_sig_inner】同时有效。控制中心,会从这两个内部总线接收到消息,并且会进行相应的处理的。
结束语
本节内容,应该不难。如果你理解了我之前所讲解的加法与减法操作的逻辑,本节,其实并不难。
在你熟悉了之前的加法与减法的讲解内容以后,有可能你会觉得,本节的内容,有点废话练习的意思。
其实,在学习中,有些时候,这种重复性的东西,对于学习是有好处的。重复几次,可以令你对有关知识更为熟练,印象也更为深刻。
在我的讲解中,基本上,我是喜欢采用比较详细的讲解方式。详细,也就意味着,好多的讲解,有可能,你已经是很熟悉了。
学习,是一种在旧知识的基础上,积累新知识的过程。如果,我们在学习的时候,旧知识有着足够的比例,因而在学习新知识的时候,并不觉得累,我觉得是好的。