当前位置: 首页 > news >正文

简易CPU设计入门:内存读写(五)

专栏导航

上一篇:简易CPU设计入门:内存读写(四)

专栏目录

下一篇:无

项目代码下载

请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。

CSDN文章:下载本项目代码

上述链接为本项目所依据的版本。

在讲解过程中,我还时不时地发现自己在讲解与注释上的一些个错误。有时,我还会添加一点新的资料。在这里,我将动态更新的代码版本发在下面的链接中。

Gitee项目:简易CPU设计入门项目代码:

讲课的时候,我主要依据的是CSDN文章链接。然后呢,如果你为了获得我的最近更新的版本,那就请在Gitee项目链接里下载代码。

准备好了项目源代码以后,我们接着去讲解。

本节前言

在上一节,我讲了控制中心里面,关于内存写操作的一些个逻辑。在本节,我来讲解关于内存读操作的逻辑。

本节的许多的内容,会需要以上一节的知识作为基础。

本节的代码,主要位于【...cpu_me01\code\Ctrl_Center\】路径里面,所所涉及的代码文件,主要是【rw_ram.v】和【ctrl_center.v】。

其中,【ctrl_center.v】是本节最为主要的代码文件。

一. 系统总线与内部寄存器

这一块的知识,请大家参考上一节文章的第一分节的讲解。上一节文章的链接如下。

简易CPU设计入门:内存读写(四)-CSDN博客

二. ram_read_flag 组节拍变量

图1

图1所示的几个变量,便是 ram_read_flag 组节拍变量。从名字上可以大致猜到,【ram_read_flag】是主要的变量,【ram_read_flag_d1】比【ram_read_flag】延后一个时钟周期,【ram_read_flag_d2】比【ram_read_flag_d1】延后一个时钟周期。

我们还是来看一看代码,来看看,我们的猜想是否正确。

图2

从图2来看,我们的猜想是对的。

三. new_task 变量与缓存系统总线的有效数据

对于这一块的知识,上一节文章的第三分节,同样是有讲述的。请大家参考上一节的文章,来学习这一块的知识。上一节文章的链接如下。

简易CPU设计入门:内存读写(四)-CSDN博客

四. ram_read_flag 组节拍变量的逻辑

首先呢,我们来看 ram_read_flag 的逻辑。

图3
always @(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) ram_read_flag <= 1'b0; else if ((new_task == 1'b1) && (ctrl_bus >= 16'd12) && (ctrl_bus < 16'd16)) ram_read_flag <= 1'b1; else ram_read_flag <= 1'b0;

图3中所示,是关于 ram_read_flag 的逻辑。它的逻辑是,系统复位与处于【else】分支时,它都是0值。每当系统检测到【(new_task == 1'b1) && (ctrl_bus >= 16'd12) && (ctrl_bus < 16'd16)】条件满足时,则 ram_write_flag 会被非阻塞赋值为 1。

new_task 变量我们讲过了,它为1,表示开启了一个新的微指令操作,标志着新任务的开始。而当 new_task 为1时,控制总线【ctrl_bus】的值,则是表示了本次微指令的功能。

如果【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】,表示本次操作为停机操作。

根据控制总线的控制信号列表,我们可以知道,当【12 <= ctrl_bus < 16】条件满足且 new_task 为 1 时,表示开启了一个新任务,这个新任务的内容就是执行内存写操作。

执行内存读操作,就是要将某一个数据,从内存单元里面读取出来。那么,在内存读操作之中,这个要去操作的内存地址值,便是由地址总线【addr_bus】指出。

想要执行内存读操作,我们还需要指出,将读出的数据放在哪里。那么,这个数据要放在哪里呢?这个数据,要保存在四个内部寄存器中的某一个里面。具体保存在哪里,这个内部寄存器的有效索引号,保存在【ctrl_bus[1:0]】之中。我们将【ctrl_bus[1:0]】赋给【ctrl_bus_index】,正是为了方便地引用这个索引号。

五. 内部总线

这一块的知识,上一节依然是有讲解的。大家可以通过学习上一节文章的第五分节的内容,来学习关于内部总线的知识。上一节文章的连接如下。

简易CPU设计入门:内存读写(四)-CSDN博客

六. 内部总线的逻辑

关于内部总线的逻辑,本节的关注点与上一节有所不同。上一节,我们所讲的,是在进行内存写操作的时候,内部总线的逻辑。本节,我们所讲的,是在进行内存读操作的时候,内部总线应该采取的逻辑。

当控制中心模块的 new_task 为1,且根据控制总线的取值范围,判断出本次的操作任务是内存读操作时,接下来,我们就需要通过往三大内部总线写入合适的值,来向内存读写单元发布指令,指示内存读写单元【rw_ram.v】来进行内存写操作。

我们通过关于内部总线的代码来了解,控制中心是如何向【rw_ram】模块发出指令的。

图4
图5
图6
图7

根据图4和图7,在系统复位与【else】分支里面,也就是说,在系统复位与闲来无事时,控制中心模块的三大内部总线代理变量均被非阻塞赋值为高阻态值。也就是说,在系统复位与闲来无事时,控制中心模块与三大内部总线是断开连接的。

关于与总线断开连接这件事,我们说过多次了。忘了的,请大家复习下述链接所示的文章。

简易CPU设计入门:本系统中的通用寄存器(四)-CSDN博客

在上面的连接的第七分解中,我讲解了总线逻辑,也讲解了,什么叫做与总线断开连接。

然后呢,根据图5,我们看到,如果在某一个时钟的上升沿到来时,系统检测到【ram_read_flag == 1】条件成立,则内部控制总线代理变量和内部地址总线代理变量被赋予0值,而内部数据总线代理变量被赋予高阻态值。也就是,相当于说,控制中心模块里面的内部控制总变量和内部地址总线变量被赋予0值,而内部数据总线变量依旧与同名的内部数据总线处于断开连接的状态。

由于控制中心模块通过三大内部总线变量【ctrl_sig_iner】,【addr_sig_iner】和【data_sig_iner】与同名的三大内部总线相连,且此时仅有控制中心模块与三大内部总线保持了连接,所以呢,对控制中心模块三大内部总线变量的代理变量的赋值,相当于说,给内部控制总线和内部地址总线赋予0值,而令内部数据总线与控制中心依旧处于断开连接的状态。

我们再往下看。

根据图6,如果在某一个时钟的上升沿到来时,系统检测到【ram_read_flag_d1 == 1】条件成立,则则三大内部总线代理变量被分别赋予各自的有效值。

我们先来看内部控制总线的情况,【ctrl_bus_represent <= 16'h0008;】。这一行的含义,如果你是一路跟着我的专栏学习过来的,那么,它对你来讲,应该是不难理解的。

图8
/********************************************* 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】被赋值为【16'h0008】,相当于是将【ctrl_sig_inner】总线赋值为【16'h0008】。【16'h0008】这个数,它一共是有16位,其中只有位3为1,其余都是0值。根据图8,当【ctrl_sig_inner】总线的位3为1,而其余都是0值时,这表示说,控制中心模块发布了内存读使能信号。

所以呢,图6里面的462行代码,它的意思就是,通过向内部控制总线写入【16'h0008】,向内存读写单元发布内存读使能信号。

在发布内存读使能信号的同时,我们需要告诉内存读写单元,本次要去操作的内存单元的地址值是什么。 图6的463行指示了这一点。

addr_bus_represent <= addr_bus_buf;

在当初,new_task 为 1 时,控制中心模块将三大系统总线的值都给缓存下来了。对于内存写操作来讲,地址总线的有效值,代表了本次要去操作的内存单元的地址值。我们在 new_task 为 1 时,将地址总线的有效值缓存到了 addr_bus_buf 里面,而在图6中的 463 行,我们又将这个地址值通过【addr_bus_represent】传给【addr_sig_inner】总线,进而传递给内存读写单元【rw_ram】。

开展内存写操作,我们并不需要指定待写入的数据。所以,在图5和图6中,我们都是将内部数据总线变量的代理变量【data_bus_represent】非阻塞赋值为高阻态值。也就是说,通过向代理变量赋值为高阻态值,进而将控制中心模块的内部数据总线变量【data_sig_inner】赋值为高阻态值,从而,将控制中心与内部总线【data_sig_inner】断开连接。

注意,在图5和图6中,我们都是让控制中心与内部总线【data_sig_inner】断开连接的。因为,在内存读操作中,我们并不需要给内存读写模块传入待写数据。

七. 实例化内存读写单元

这一块,还是请大家查看上一节文章的第七分节。上一节文章的链接如下。

简易CPU设计入门:内存读写(四)-CSDN博客

八. 内存读操作的操作时序梳理

我们来梳理一下内存读操作的操作时序。

我们还是来设定一个0号时钟上升沿。

(一)0号时钟上升沿

在0号时钟上升沿,系统检测到,【new_task == 1】,并且【12 <= ctrl_bus < 16】。

图9

于是,在0号时钟上升沿之后的非阻塞赋值阶段,根据图9,三大系统总线缓存变量将三大总线的有效值给缓存了下来。注意,当【new_task == 1】条件满足之时,三大总线上,的确是含有着有效的数据。同时,【ctrl_bus[1:0]】的值被赋给了【ctrl_bus_index】。对于内存读操作来讲,从内存单元读出的数据需要被保存在某一个内部寄存器之中,而【ctrl_bus[1:0]】则是在 new_task 为1时指定了这个内部寄存器的有效索引号。

在0号时钟上升沿之后的非阻塞赋值阶段,根据图3,【ram_read_flag】被赋值为1。

(二)1号时钟上升沿

在1号时钟上升沿,系统检测到【ram_read_flag == 1】。

在【ram_read_flag == 1】条件满足之时,我们要准备向内存读写单元【rw_ram】发布关于内存读操作的相关信号。

在1号时钟上升沿的非阻塞赋值阶段,根据图5,我们通过对三大内部总线信号的代理变量的非阻塞赋值,向内部控制总线和内部地址总线传递0值,向控制中心模块的内部数据总线传递高阻态值,使得控制中心模块与内部数据总线【data_sig_inner】断开连接。

在1号时钟上升沿的非阻塞赋值阶段,根据图2,【ram_read_flag_d1】会被非阻塞赋值为1。

(三)2号时钟上升沿

在2号时钟上升沿,系统检测到【ram_read_flag_d1 == 1】。

在【ram_read_flag_d1 == 1】条件满足之时,我们要正式向内存读写单元【rw_ram】发布关于内存读操作的相关信号。

在2号时钟上升沿的非阻塞赋值阶段,根据图6,我们通过对三大内部总线的代理变量的非阻塞赋值,分别向三大内部总线传递有效的信号,以开展内存写操作方面的工作。

通过【ctrl_bus_represent <= 16'h0008;】,我们向内部控制信号总线写入了一个只有位3为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 <= 16'hz;】,我们继续将控制中心模块与内部数据总线【data_sig_inner】断开连接。

(四)3号时钟上升沿

在这一时钟跳变沿,控制总线的内存读操作使能,已经是可以被内存读写单元接收了。接下来的内存读操作,由内存读写单元【rw_ram】来完成。对于【rw_ram】模块中的内存读操作,我们可以参考下面的链接所示的文章。

简易CPU设计入门:内存读写(三)-CSDN博客

这是我们已经讲过了的部分。如果你忘记了,或者是还没有学习过,那么,你可以前往链接所示的文章,去学习。

在确保你已经理解了上述链接所述的文章内容以后,我们接着来进行后续内容的讲解。

九. 接收从内存单元读出的数据

当内存读写单元【rw_ram】处理好了内存读操作以后,该模块会将读出的数据传递给内部数据总线【data_sig_inner】,并且会给成功完成信号总线【work_ok_inner】传递1值。

当我们检测到了【work_ok_inner】成功完成信号总线的值为1的时候,我们就需要将内部数据总线【data_sig_inner】给保存下来。保存的位置,是四个内部寄存器中的一个,其有效索引号,是从【ctrl_bus】的位1和位0里面,缓存下来的 ctrl_bus_index 。这样一来,我们需要将内部数据总线上的有效数据值,传递给 inner_reg[ctrl_bus_index] 。

我们来看一看下图所示的代码。

图10
图11
always @(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) begin inner_reg[0] <= 16'h0000; inner_reg[1] <= 16'h0000; inner_reg[2] <= 16'h0000; inner_reg[3] <= 16'h0000; end else if (work_ok_inner == 1'b1 && ctrl_bus_buf >= 16'd4 && ctrl_bus_buf < 16'd8) inner_reg[ctrl_bus_index] <= data_sig_inner; else if (work_ok_inner == 1'b1 && ctrl_bus_buf >= 16'd12 && ctrl_bus_buf < 16'd16) inner_reg[ctrl_bus_index] <= data_sig_inner; else if (imd_read_flag == 1'b1) inner_reg[ctrl_bus_index] <= data_bus_buf; else if (work_ok_inner == 1'b1 && ctrl_bus_buf >= 16'd20 && ctrl_bus_buf < 16'd24) inner_reg[ctrl_bus_index] <= data_sig_inner; else begin inner_reg[0] <= inner_reg[0]; inner_reg[1] <= inner_reg[1]; inner_reg[2] <= inner_reg[2]; inner_reg[3] <= inner_reg[3]; end

图10,图11与图11下面的代码块,展示了内部寄存器的逻辑。关于内部寄存器的逻辑,它们是说,在系统复位时,四个内部寄存器被赋予0值。在处于【else】分支的时候,也就是闲来无事的时候,它们是保持现有值不变。而在满足了图10所示的几个【else if】中的其中的一个条件的时候,则四个内部寄存器中的一个会被赋值,被赋值的内部寄存器的索引号,由 ctrl_bus_index 来指定。

我们来看下图所示的【else if】条件。

图12

在图12所示的条件里面,我们可以分两段来看。

第一段,【work_ok_inner == 1】,这个条件是表示,控制中心之外的某一个模块,向成功完成信号总线【work_ok_inner】传递了1值,表示该操作成功完成了。然而,究竟是成功完成了哪项操作,还需要通过第二段条件来指定。

我们来看第二段条件。

第二段,【12 <= ctrl_bus_buf < 16】。ctrl_bus_bus,是在当初检测到 new_task 为1的时候,从系统控制总线【ctrl_bus】里面缓存下来的信号值。如果【12 <= ctrl_bus_buf < 16】条件满足,则表示说,本次的操作,是内存读操作。

两段条件同时满足,就表示说,内存读操作完成了。既然是完成了,那么,内部数据总线【data_sig_inner】里面就会含有有效的数据,我们需要将这一数据保存到某一个内部寄存器里面。这个内部寄存器的索引号,是 ctrl_bus_index 。

在这里呢,要注意,内部数据总线上的有效数据,它仅仅维持一个时钟周期,我们必须要在检测到【work_ok_inner == 1】条件满足的时候,及时将这个值给保存起来。过期的话,就找不到这个有效值了。

图12中的一段代码,正是用于将从内存单元读出来,并且放入了内部数据总线中的有效数据,给保存起来。这段代码如下所示。

inner_reg[ctrl_bus_index] <= data_sig_inner;

到了这里,内存读操作的代码,我们就全部讲完了。

结束语

内存读写操作,我认为,它算是一个大的任务。我这里,讲起来有点费劲。我估计,你在学习的时候,也会觉得很费劲。

有些地方,我在表述的时候,我是觉得,有点困难。

有好的建议的,欢迎提出来。

下一节,我们要来讲解指令单元。

其实,到了指令单元,我们的专栏,已经算是快要结束了。当然了,我们还是会花费很多的章节,来讲解剩余的内容。

祝大家学习愉快。

专栏导航

上一篇:简易CPU设计入门:内存读写(四)

专栏目录

下一篇:无

http://www.jsqmd.com/news/494735/

相关文章:

  • 2026年热门的球面轴承品牌推荐:滑动轴承/自润滑轴承/石墨镶嵌轴承公司口碑推荐 - 行业平台推荐
  • Beagle 开源项目教程
  • Stork Oracle自动验证机器人技术解析
  • ARM64架构手动编译libtorch,安装MKL/oneDNN加速模型推理,详细流程!
  • Flow3自动化任务机器人技术解析与实现方案
  • 【亲测免费】 IBAN.js - 国际银行账号验证与格式化工具
  • 2026年质量好的防爆喷漆柜品牌推荐:环保喷漆柜实力工厂推荐 - 行业平台推荐
  • 0318晨间日记
  • Sowing Taker Auto Bot:自动化耕作协议的智能解决方案
  • Lunar 开源项目使用教程
  • go-wkhtmltopdf在AWS Lambda中的应用:无服务器PDF生成方案
  • 2026年质量好的定制喷粉房品牌推荐:工业喷粉房/大旋风喷粉房直销厂家推荐 - 行业平台推荐
  • OpenSCI自动化机器人技术解析:Base Sepolia测试网上的智能合约交互工具
  • Django-Dynamic-Scraper入门教程:从零开始构建你的第一个动态爬虫
  • 致我的17岁——未成年的终章
  • 2026年知名的点烟器DC线工厂推荐:点烟器插座生产厂家推荐 - 行业平台推荐
  • 自动驾驶大模型---Diffusion Planner
  • Tea Auto Bot:Tea Sepolia测试网自动化交互工具解析
  • 2026年质量好的保温水箱公司推荐:新疆地埋水箱口碑好的厂家推荐 - 行业平台推荐
  • react-shimmer源码解析:探索高性能图片加载组件的实现原理
  • Swot域名数据库详解:如何贡献并维护全球高校域名信息?
  • 避免90%状态错误:ADK-Python变量引用与上下文管理完全指南
  • multierr与标准库兼容性:errors.Is和errors.As完美结合
  • MegaETH Auto Bot:自动化参与Meganet带宽共享的技术解析
  • 汉字拼音转换神器pinyin:一站式解决注音、排序与检索难题
  • VapeLabs自动机器人技术解析与实现方案
  • Awesome Programming for Kids深度解析:从玩具机器人到代码世界的桥梁
  • Runtime实战教程:3个实例带你掌握动态创建实例的秘诀
  • Zygisk API完全指南:用NeoZygisk开发模块的5个关键步骤
  • 如何用No-as-a-Service快速获取创意拒绝理由?5分钟上手教程