简易CPU设计入门:控制总线的剩余信号(二)

张开发
2026/4/3 4:19:42 15 分钟阅读
简易CPU设计入门:控制总线的剩余信号(二)
专栏导航上一篇简易CPU设计入门控制总线的剩余信号一专栏目录下一篇简易CPU设计入门控制总线的剩余信号三项目代码下载请大家首先准备好本项目所用的源代码。如果已经下载了那就不用重复下载了。如果还没有下载那么请大家点击下方链接来了解下载本项目的CPU源代码的方法。CSDN文章下载本项目代码上述链接为本项目所依据的版本。在讲解过程中我还时不时地发现自己在讲解与注释上的一些个错误。有时我还会添加一点新的资料。在这里我将动态更新的代码版本发在下面的链接中。Gitee项目简易CPU设计入门项目代码:讲课的时候我主要依据的是CSDN文章链接。然后呢如果你为了获得我的最近更新的版本那就请在Gitee项目链接里下载代码。准备好了项目源代码以后我们接着去讲解。本节前言在上一节我讲解了立即数读信号。本节我们继续来讲解控制总线中的剩余信号。本节的代码位于【......\cpu_me01\code\Ctrl_Center\】路径里面。主要讲解的代码是【ctrl_center.v】。我们来看一下控制总线的信号列表。如果【ctrl_bus】的取值范围是【0 ctrl_bus 4】表示本次操作为寄存器写操作。如果【ctrl_bus】的取值范围是【4 ctrl_bus 8】表示本次操作为寄存器读操作。如果【ctrl_bus】的取值范围是【8 ctrl_bus 12】表示本次操作为内存写操作。如果【ctrl_bus】的取值范围是【12 ctrl_bus 16】表示本次操作为内存读操作。如果【ctrl_bus】的取值范围是【16 ctrl_bus 20】表示本次操作为立即数读操作。如果【ctrl_bus】的取值范围是【20 ctrl_bus 24】表示本次操作为算术逻辑运算。如果【ctrl_bus】的取值范围是【24 ctrl_bus 28】表示本次操作为更新指令指针寄存器【ip】。如果【ctrl_bus】的取值范围是【28 ctrl_bus 32】表示本次操作为停机操作。以上的块引用部分的内容就是控制总线的全部信号了。我们之前讲了一部分它们是【0 ctrl_bus 20】的范围的信号。这样一来我们所剩下的算术逻辑运算控制信号更新指令指针寄存器ip的控制信号还有指示停机的控制信号。本节我们要去讲解的是算术逻辑运算控制信号。一. 系统总线与内部寄存器本节所要讲解的东西主要是跟算术逻辑运算有关。我们来看一看系统总线。图1图1中所示的代码位于控制中心模块的端口声明部分。它们分别是我们的仿真CPU项目中的控制总线地址总线数据总线它们都属于是系统总线。图2在图2中65行到67行分别是用来对控制总线、地址总线和数据总线进行缓存的变量。为啥要进行缓存呢因为三大系统总线中的信号的有效期仅有一个时钟周期稍纵即逝。而我们又需要在不同于总线数据有效期的时间里使用它们所以呢我们就声明了三个变量用来将三大总线的数据给缓存下来以便长久使用。在图2的 64 行我们声明了一个 reg 类型的数组如下面的代码块所示。reg [15:0] inner_reg[3:0];它的含义是声明四个 reg 类型的向量每一个向量都是16位的其中最高有效位是位15最低有效位是位0。四个向量用数组索引来引用。四个向量的引用方法为inner_reg[0]inner_reg[1] inner_reg[2]inner_reg[3]。这四个向量是我们的系统中的四个内部寄存器。注意它们是内部寄存器而非通用寄存器。图2的68行申请的变量它在代码中用来作为访问内部寄存器的索引变量。由于每当新指令任务到来之时要访问的内部寄存器的索引位于控制总线【ctrl_bus】中所以我将这个用来访问内部寄存器的索引变量命名为【ctrl_bus_index】。二. ALU_flag 组节拍变量图3图3所示的几个变量便是 ALU_flag 组节拍变量。从名字上可以大致猜到【ALU_flag】是主要的变量【ALU_flag_d1】比【ALU_flag】延后一个时钟周期【ALU_flag_d2】比【ALU_flag_d1】延后一个时钟周期。是否如此呢我们来看看下图所示的代码。图4从图4来看的确是说【ALU_flag】是主要的变量【ALU_flag_d1】比【ALU_flag】延后一个时钟周期【ALU_flag_d2】比【ALUflag_d1】延后一个时钟周期。三. new_task 变量与缓存系统总线的有效数据这个变量是我在控制中心模块里申请的一个 wire 型变量如下图所示。图5关于这个变量的含义本节我们依然是先不去深究。我们需要了解它的基本含义。如果它为1就代表了一个新的微指令的开始或者是代表了一个新的微操作的开始。当 new_task 为1的时候三大系统总线均含有有效数据。三大总线中的数据与 new_task 一样有效数据的存在时间只有一个时钟周期。对于 new_task 变量它的值我们不需要保存。而对于三大系统总线的有效数据我们是需要将其保存下来的因为它们正好处于有效期的时候我们可能暂时用不到但是 后面会有用所以我们需要将其缓存下来。图6在图6里面我们可以看到三大系统总线缓存变量与内部寄存器索引变量【ctrl_bus_index】的逻辑。在系统复位时三大系统总线缓存变量与内部寄存器索引变量【ctrl_bus_index】均被非阻塞赋值为高阻态值。在平时先来无事时也就是在【else】分支里面它们都保存着各自的现有值不变。每当 new_task 为1时也就是每当开启了一个新的微指令的时候三大系统总线缓存变量会缓存各自对应的系统总线的有效数据。同时呢内部寄存器索引变量【ctrl_bus_index】会将控制总线【ctrl_bus】的位1与位0给缓存下来。也就是在每一个新的微指令开启的时候控制总线的位选信号【ctrl_bus[1:0]】指定了本次的微指令需要访问的内部寄存器的索引号并且它会被缓存到变量【ctrl_bus_index】之中。在我们的系统中有四个内部寄存器。这样一来由控制总线【ctrl_bus】发布过来的每一个控制信号其实都是4个一组。原因在于每一个控制信号都需要指定要去访问的内部寄存器。通用寄存器读操作需要指定要去使用的内部寄存器索引。写操作也需要指定本次要访问的内部寄存器的索引其他的一些个控制信号也是如此的。对于每一种操作无论是通用寄存器的读写操作还是内存读写算术逻辑操作它们都含有索引字段。而索引值是控制总线的位1与位0所以索引字段的值的范围是0,1,2,3。四. ALU_flag 组节拍变量的逻辑首先呢我们来看 ALU_flag 的逻辑。图7always (posedge sys_clk or negedge sys_rst_n) if (sys_rst_n 1b0) ALU_flag 1b0; else if ((new_task 1b1) (ctrl_bus 16d20) (ctrl_bus 16d24)) ALU_flag 1b1; else ALU_flag 1b0;图7中所示是关于 ALU_flag 的逻辑。它的逻辑是系统复位与处于【else】分支时它都是0值。每当系统检测到【(new_task 1b1) (ctrl_bus 16d20) (ctrl_bus 16d24)】条件满足时则 ALU_flag 会被非阻塞赋值为 1。new_task 变量我们讲过了它为1表示开启了一个新的微指令操作标志着新任务的开始。而当 new_task 为1时控制总线【ctrl_bus】的值则是表示了本次微指令的功能。根据本节的前言部分的控制总线信号的列表信息如果【ctrl_bus】的取值范围是【20 ctrl_bus 24】且 new_task 为 1 时表示开启了一个新任务这个新任务的内容为算术逻辑操作。想要执行算术逻辑操作我们还需要指出要将运算结果保存在那里。那么这个数据在哪里呢这个数据目前是保存在四个内部寄存器中的某一个里面。具体保存位置的有效索引号保存在【ctrl_bus[1:0]】之中。我们将【ctrl_bus[1:0]】赋给【ctrl_bus_index】正是为了方便地引用这个索引号。五. 内部总线与三个操作数图8图8中16行到18行是三大内部总线分别为内部控制总线内部地址总线内部数据总线。21行是内部成功信号总线。在这四个内部总线里面内部控制总线、内部地址总线和内部数据总线分别有对应的代理变量以便可以让内部总线通过其代理变量参与时序逻辑运算。而在控制中心模块里面内部成功信号总线没有代理变量。因而在控制中心里面其实控制中心模块仅仅是读取这个总线的值而并不对其开展写入操作。图9图9所示为三大内部总线的代理变量。图10图10中的125到127行分别是三大内部总线与它们的代理变量的绑定代码。以上我算是讲解了内部总线变量及其代理变量的情况。在算术逻辑运算中我们还需要关注着三个操作数变量。图10-1控制中心模块中的操作数端口声明图10-1所示是控制中心模块的端口声明部分中的三个操作数变量。它们其实是对控制中心模块里面的四个内部寄存器中的前三个的绑定输出。我们看一下下面的代码。图10-2控制中心模块中的操作数变量的绑定输出从10-2可以看到操作数0对应着内部寄存器0操作数1对应着内部寄存器1操作数2对应着内部寄存器2。在讲解内存读写寄存器读写的时候我们并未讲解操作数变量与内部寄存器变量的绑定。而在讲解算术逻辑运算的时候我们需要讲解了。这是因为一个算术逻辑运算他可能会涉及不同数量的操作数。一般的算术逻辑运算它需要两个操作数加法需要加数和被加数乘法需要乘数和被乘数。而有的运算类型需要的仅仅是一个操作数。也有的需要三个操作数。为了满足这不同的类型的算术逻辑运算我统一地设置了三个操作数将内部寄存器中的三个予以绑定输出。六. 内部总线的逻辑算术逻辑操作是怎么回事当控制中心模块的 new_task 为1且根据控制总线的取值范围判断出本次的操作任务是算术逻辑运算接下来我们就需要通过往三大内部总线写入合适的值来向算术逻辑单元发布指令指示算术逻辑单元【ALU.v】来进行算术逻辑操作。我们通过关于内部总线的代码来了解控制中心是如何向【ALU】模块发出指令的。图11图12图13图14根据图11和图14在系统复位与【else】分支里面也就是说在系统复位与闲来无事时控制中心模块的三大内部总线代理变量均被非阻塞赋值为高阻态值。也就是说在系统复位与闲来无事时控制中心模块与三大内部总线是断开连接的。关于与总线断开连接这件事我们说过多次了。忘了的请大家复习下述链接所示的文章。简易CPU设计入门本系统中的通用寄存器四-CSDN博客在上面的链接的第七分解中我讲解了总线逻辑也讲解了什么叫做与总线断开连接。然后呢根据图12我们看到如果在某一个时钟的上升沿到来时系统检测到【ALU_flag 1】条件成立则内部控制总线代理变量与内部地址总线代理变量被赋予0值而内部数据总线代理变量被赋予高阻态值。也就是相当于说控制中心模块里面的内部控制总线变量与内部地址总线变量被赋予0值而内部数据总线变量被赋予高阻态值。此时控制中心里面的内部控制中心总线变量与内部地址总线变量均与同名的内部总线建立了连接而控制中心的内部数据总线变量则是与同名的内部数据总线处于断开连接的状态。由于控制中心模块通过三大内部总线变量【ctrl_sig_iner】【addr_sig_iner】和【data_sig_iner】与同名的三大内部总线相连且此时仅仅可能有控制中心模块与三大内部总线保持连接并不存在其他模块与三大内部总线保持连接的可能。所以呢此时控制中心模块里面的内部控制总线变量和内部地址总线变量被赋予0值就等同于内部控制总线和内部地址总线被赋予0值。而此时控制中心模块的内部数据总线变量被赋予高阻态z值等同于说控制中心模块的内部数据总线变量【data_sig_inner】与同名的内部数据总线是断开连接的。我们再往下看。根据图13如果在某一个时钟的上升沿到来时系统检测到【ALU_flag_d1 1】条件成立则则三大内部总线代理变量被分别赋予各自的有效值。我们先来看内部控制总线的情况【ctrl_bus_represent 16h0010;】。这一行的含义如果你是一路跟着我的专栏学习过来的那么它对你来讲应该是不难理解的。图15/********************************************* 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_bus_represent】是【ctrl_sig_inner】的代理变量。【ctrl_bus_represent】被赋值为【16h0010】相当于是将【ctrl_sig_inner】总线赋值为【16h0010】。【16h0010】这个数它一共是有16位其中只有位4为1其余都是0值。根据图15当【ctrl_sig_inner】总线的位4为1而其余都是0值这表示说控制中心模块发布了算术逻辑运算信号。所以呢图13里面的475行代码它的意思就是通过向内部控制总线写入【16h0010】向算术逻辑单元发布算术逻辑运算信号。在发布算术逻辑运算信号的同时我们需要告诉算术逻辑单元本次要去进行算术逻辑运算的类型是什么。我们要去进行加法运算呢还是逻辑移位运算呢还是按位与、按位或的运算呢图13的476行代码指示了这一点。addr_bus_represent addr_bus_buf;在当初new_task 为 1 时控制中心模块将三大系统总线的值都给缓存下来了。对于算术逻辑操作来讲地址总线的有效值代表了本次要去进行的算术逻辑运算的类型。如同一个值代表算术加法某一个值代表着逻辑左移等等。我们在 new_task 为 1 时将地址总线的有效值缓存到了 addr_bus_buf 里面而在图13中的 476 行我们又将这个地址值通过【addr_bus_represent】传给【addr_sig_inner】总线进而传递给算术逻辑单元【ALU】。在算术逻辑运算的输入参数中不需要内部地址总线的参与因此在图13的477行我们为内部地址总线代理变量赋予了高阻态值。data_bus_represent 16hz;七. 实例化算术逻辑单元与操作时序梳理关于算术逻辑单元【ALU】的实例化代码我们以前讲过参考下图所示的文章链接。简易CPU设计入门算术逻辑单元一-CSDN博客接下来我们来梳理一下内存写操作的操作时序。我们还是来设定一个0号时钟上升沿。一0号时钟上升沿在0号时钟上升沿系统检测到【new_task 1】并且【20 ctrl_bus 24】。于是在0号时钟上升沿之后的非阻塞赋值阶段根据图6三大系统总线缓存变量将三大总线的有效值给缓存了下来。注意当【new_task 1】条件满足之时三大总线上的确是含有着有效的数据。同时【ctrl_bus[1:0]】的值被赋给了【ctrl_bus_index】。对于算术逻辑操作来讲算术逻辑运算的运算结果需要被保存在某一个内部寄存器之中而【ctrl_bus[1:0]】则是在 new_task 为1时指定了这个内部寄存器的有效索引号。当本次的算术逻辑操作完成了以后【inner_reg[ctrl_bus_index]】里面会保存本次算术逻辑操作的运算结果。在0号时钟上升沿之后的非阻塞赋值阶段根据图7【ALU_flag】被赋值为1。二1号时钟上升沿在1号时钟上升沿系统检测到【ALU_flag 1】。在【ALU_flag 1】条件满足之时我们要准备向算术逻辑单元【ALU】发布关于算术逻辑操作的相关信号。在1号时钟上升沿的非阻塞赋值阶段根据图12我们通过对三大内部总线信号的代理变量的非阻塞赋值向三大内部总线传递0值或高阻态值。在1号时钟上升沿的非阻塞赋值阶段根据图4【ALU_flag_d1】会被非阻塞赋值为1。三2号时钟上升沿在2号时钟上升沿系统检测到【ALU_flag_d1 1】。在【ALU_flag_d1 1】条件满足之时我们要正式向算术逻辑单元【ALU】发布关于内存写操作的相关信号。在2号时钟上升沿的非阻塞赋值阶段根据图13我们通过对三大内部总线的代理变量的非阻塞赋值分别向三大内部总线传递各自的信号以开展算术逻辑操作方面的工作。通过【ctrl_bus_represent 16h0010;】我们向内部控制信号总线写入了一个只有位4为1而其余位均为0的值。写入这个值就表示说控制中心在向算术逻辑单元发布算术逻辑运算信号。通过【addr_bus_represent addr_bus_buf;】我们向内部地址信号总线写入有效的算术逻辑运算类型值它是我们本次要去进行的算术逻辑运算的类型。早在0号上升沿的非阻塞赋值阶段我们将地址总线【addr_bus】里面的地址值存入了【addr_bus_buf】之中。由此【addr_bus_buf】中就保存了本次的算术逻辑操作的运算类型值。而在此时在2号时钟上升沿的非阻塞赋值阶段我们要将【addr_bus_buf】里面保存的算术逻辑运算类型值写入内部地址总线【addr_sig_inner】之中。通过【data_bus_represent 16hz;】我们将控制中心模块的内部数据总线代理变量【data_bus_represent】所对应的控制中心模块的内部总线变量【data_sig_inner】设置为与同名的内部总线【data_sig_inner】保持在断开连接的状态。四3号时钟上升沿在这一时钟上升沿系统会检测到。【ALU_flag】和【ALU_flag_d1】均为0值。在这一上升沿之后的非阻塞赋值阶段三大内部信号总线的代理变量均被赋予了高阻态z值因此控制中心模块的三大内部信号总线变量也都会被赋值为高阻态z值因此控制中心模块会与同名的【ctrl_sig_inner】总线、【addr_sig_inner】总线和【data_sig_inner】总线断开连接。此时三大总线没有与之连接的线路因此也都处于高阻态状态。从本时钟上升沿开始系统将进行算术逻辑运算工作。这是【ALU】模块的工作。在进行完了算术逻辑运算工作以后在某一个时钟的上升沿控制中心模块会检测到内部总线变量【work_ok_inner】会变为有效的高电平。这时控制中心模块进行一番处理。处理如下图所示。图16如图16所示当检测到内部总线成功完成信号【work_ok_inner】为有效的高电平且控制总线缓存值为【20 ctrl_bus_buf 24】时此时算术逻辑单元完成了算术逻辑运算且运算结果保存在内部数据总线【data_sig_inner】里面。那么我们就将这个内部数据总线【data_sig_inner】中的运算结果给保存下来保存在某一个内部寄存器里面索引号由【ctrl_bus_index】来指定。这便是图16中的代码的功能。另外我们还可以看一看关于内部寄存器的其他的逻辑代码。图17图18图19根据图17在系统复位时四个内部寄存器被赋予0值。根据图19四个内部寄存器在处于【else】分支时也就是闲来无事时会保持现有值不变。而根据图18在操作类型为算术逻辑操作且成功完成之时则需要将内部数据总线【data_sig_inner】上面的值保存在以【ctrl_bus_index】为索引号的内部寄存器【inner_reg】之中。结束语本节内容可以说是很多。希望大家能够学好本节知识。专栏导航上一篇简易CPU设计入门控制总线的剩余信号一专栏目录下一篇简易CPU设计入门控制总线的剩余信号三

更多文章