专栏导航上一篇简易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 24】的范围的信号。这样一来我们所剩下的是更新指令指针寄存器ip信号和指示停机的控制信号。本节我们要去讲解的是更新指令指针寄存器ip信号。一. 系统总线与内部寄存器本节所要讲解的东西主要是跟指令指针寄存器 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】。二. ip_update_flag 组节拍变量图3图3所示的几个变量便是 ip_update_flag 组节拍变量。从名字上可以大致猜到【ip_update_flag】是主要的变量【ip_update_flag_d1】比【ip_update_flag】延后一个时钟周期【ip_update_flag_d2】比【ip_update_flag_d1】延后一个时钟周期。是否如此呢我们来看看下图所示的代码。图4从图4来看的确是说【ip_update_flag】是主要的变量【ip_update_flag_d1】比【ip_update_flag】延后一个时钟周期【ip_update_flag_d2】比【ip_update_flag_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]】指定了每一次的微指令需要访问的内部寄存器的索引号。对于更新指令指针寄存器 ip 的微操作内部寄存器的索引号有着特殊的功能。作为不同的控制信号的通用操作我们还是选择将内部寄存器的索引号给保存下来并且依然保存在【ctrl_bus_index】里面。在这里当 new_task 为1时也就是每当开启了一个新的微指令的时候数据总线上所保存的是本次的更新指令指针寄存器 ip 的操作可能会用到的值所以我们需要将其缓存下来。经过了缓存操作以后则数据总线缓存变量【data_bus_buf】里面保存了当初的数据总线上的值。在我们的系统中有四个内部寄存器。这样一来由控制总线【ctrl_bus】发布过来的每一个控制信号其实都是4个一组。原因在于每一个控制信号都需要指定要去访问的内部寄存器。通用寄存器读操作需要指定要去使用的内部寄存器索引。写操作也需要指定本次要访问的内部寄存器的索引其他的一些个控制信号也是如此的。对于每一种操作无论是通用寄存器的读写操作还是内存读写算术逻辑操作它们都含有索引字段。而索引值是控制总线的位1与位0所以索引字段的值的范围是0,1,2,3。四. ip_update_flag 组节拍变量的逻辑首先呢我们来看 ip_update_flag 的逻辑。图7always (posedge sys_clk or negedge sys_rst_n) if (sys_rst_n 1b0) ip_update_flag 1b0; else if ((new_task 1b1) (ctrl_bus 16d24) (ctrl_bus 16d28)) ip_update_flag 1b1; else ip_update_flag 1b0;图7中所示是关于 ip_update_flag 的逻辑。它的逻辑是系统复位与处于【else】分支时它都是0值。每当系统检测到【(new_task 1b1) (ctrl_bus 16d24) (ctrl_bus 16d28)】条件满足时则 ip_update_flag 会被非阻塞赋值为 1。new_task 变量我们讲过了它为1表示开启了一个新的微指令操作标志着新任务的开始。而当 new_task 为1时控制总线【ctrl_bus】的值则是表示了本次微指令的功能。根据本节的前言部分的控制总线信号的列表信息如果【ctrl_bus】的取值范围是【24 ctrl_bus 28】且 new_task 为 1 时表示开启了一个新任务这个新任务的内容为更新指令指针寄存器 ip 的操作。想要执行更新指令指针寄存器 ip 的操作我们还需要指出更新指令指针寄存器 ip 的方式是什么。那么这个更新方式是如何来指定的呢这个更新方式目前是由控制总线缓存变量的低2位来指定。控制总线缓存变量的低2位同时也保存在了【ctrl_bus_index】之中。五. 更新指令指针寄存器 ip图8在图8里面根据391和392行代码在系统复位之时ip 被赋予0值。根据408和409行的代码在处于【else】分支也就是闲来无事之时ip 保持现有值不变。对于图8在本节我们不去关心406与407行我们只当做【exe_running】一直为 1 就可以了。我们需要关注的重点是393到405行的代码。当检测到【ip_update_flag】为1的时候继续检测【ctrl_bus_buf】的低2位。【ctrl_bus_buf[1:0]】其实和【ctrl_bus_index】是一样的保存的都是同样的值。如果【ctrl_bus_buf[1:0]】为 0则让 ip 加上 1 。如果【ctrl_bus_buf[1:0]】为 1则让 ip 加上 data_bus_buf。如果【ctrl_bus_buf[1:0]】为 2则将 ip 赋值为 data_bus_buf。如果【ctrl_bus_buf[1:0]】为 3则将 ip 赋值为 0 值。如果【ctrl_bus_buf[1:0]】为其他值则保持现有值不变。在这里如果【ctrl_bus_buf[1:0]】为 0这表示这是一个通常的指令指针寄存器的更新操作。也就是说执行完了某一条指令以后将指令指针加1指向下一条指令。如果【ctrl_bus_buf[1:0]】为 1这表示本次要将指令指针寄存器 ip 加上一个值形成新的 ip 值。这个可以用来模拟着条件转移指令。如果【ctrl_bus_buf[1:0]】为 2这表示将指令指针寄存器 ip 赋值为一个固定的值这可以用来模拟着英特尔8086处理器的【jmp CS:IP】指令格式。不过在我的这个指令里面它其实是将指令指针寄存器直接更新为指令中的 IP 寄存器而非【CS:IP】寄存器组。如果【ctrl_bus_buf[1:0]】为 3则将指令指针寄存器 ip 给清零。还有一个else情形这个纯粹是充数用的没啥实际用处。在这里控制总线缓存变量的低2位用来指定更新指令指针寄存器的更新方式以便可以模拟着不同的转移指令包括去模拟条件转移指令和无条件转移指令。这部分其实我的思考还并不成熟。大家凑合着看吧。六. 操作时序梳理我们来梳理一下更新指令指针寄存器操作的操作时序。我们还是来设定一个0号时钟上升沿。一0号时钟上升沿在0号时钟上升沿系统检测到【new_task 1】并且【24 ctrl_bus 28】。这是我们的本节的操作时序的基准。于是在0号时钟上升沿之后的非阻塞赋值阶段根据图6三大系统总线缓存变量将三大总线的有效值给缓存了下来。注意当【new_task 1】条件满足之时三大总线上的确是含有着有效的数据。同时【ctrl_bus[1:0]】的值被赋给了【ctrl_bus_index】。对于更新指令指针寄存器 ip 的操作来讲数据总线【data_bus】里面的值可能会被用到因此将其缓存到地址总线缓存变量【data_bus_buf】里面。更新指令指针需要指定更新方式而【ctrl_bus[1:0]】则是在 new_task 为1时指定了指令指针寄存器 ip 的更新方式。在0号时钟上升沿之后的非阻塞赋值阶段根据图7【ip_update_flag】被赋值为1。二1号时钟上升沿在1号时钟上升沿系统检测到【ip_update_flag 1】。在【ip_update_flag 1】条件满足之时我们需要根据控制总线缓存变量的低2位的值来采取不同的方式更新指令指针寄存器 ip 的值。图8中的532和533行代码展示了更新指令指针寄存器 ip 的逻辑。如果【ctrl_bus_buf[1:0]】为 0则让 ip 加上 1 。如果【ctrl_bus_buf[1:0]】为 1则让 ip 加上 data_bus_buf。如果【ctrl_bus_buf[1:0]】为 2则将 ip 赋值为 data_bus_buf。如果【ctrl_bus_buf[1:0]】为 3则将 ip 赋值为 0 值。结束语本节的内容我觉得还可以。希望大家可以学好本节的内容。专栏导航上一篇简易CPU设计入门控制总线的剩余信号二专栏目录下一篇简易CPU设计入门控制总线的剩余信号四