cpu设计和实现(数据访问)

news2024/11/25 14:54:18

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        在cpu设计当中,数据访问是比较重要的一个环节。一般认为,数据访问就是内存访问。其实不然。我们都知道,cpu访问一般有指令访问和数据访问两种。指令访问就是rom访问,这个比较纯粹。但是数据访问就比较复杂一点。因为,除了单纯的ram读取访问之外,数据访问还担负着io访问的功能。一般的外设访问都是通过ip来完成的,这些io都有自己的设备地址空间,那么cpu如何通过这些设备地址空间来控制这些外设呢?答案就是数据访问。

1、一般数据访问

        对于cpu自身来说,其实并不关心外面访问的数据是ram数据、还是io数据,对它来说都是一样的。所以,首先cpu一般需要先定义一个外部空间,不妨称之为data_ram.v,

`include "defines.v"

module data_ram(

	input	wire										clk,
	input wire										ce,
	input wire										we,
	input wire[`DataAddrBus]			addr,
	input wire[3:0]								sel,
	input wire[`DataBus]						data_i,
	output reg[`DataBus]					data_o
	
);

	reg[`ByteWidth]  data_mem0[0:`DataMemNum-1];
	reg[`ByteWidth]  data_mem1[0:`DataMemNum-1];
	reg[`ByteWidth]  data_mem2[0:`DataMemNum-1];
	reg[`ByteWidth]  data_mem3[0:`DataMemNum-1];

	always @ (posedge clk) begin
		if (ce == `ChipDisable) begin
			//data_o <= ZeroWord;
		end else if(we == `WriteEnable) begin
			  if (sel[3] == 1'b1) begin
		      data_mem3[addr[`DataMemNumLog2+1:2]] <= data_i[31:24];
		    end
			  if (sel[2] == 1'b1) begin
		      data_mem2[addr[`DataMemNumLog2+1:2]] <= data_i[23:16];
		    end
		    if (sel[1] == 1'b1) begin
		      data_mem1[addr[`DataMemNumLog2+1:2]] <= data_i[15:8];
		    end
			  if (sel[0] == 1'b1) begin
		      data_mem0[addr[`DataMemNumLog2+1:2]] <= data_i[7:0];
		    end			   	    
		end
	end
	
	always @ (*) begin
		if (ce == `ChipDisable) begin
			data_o <= `ZeroWord;
	  end else if(we == `WriteDisable) begin
		    data_o <= {data_mem3[addr[`DataMemNumLog2+1:2]],
		               data_mem2[addr[`DataMemNumLog2+1:2]],
		               data_mem1[addr[`DataMemNumLog2+1:2]],
		               data_mem0[addr[`DataMemNumLog2+1:2]]};
		end else begin
				data_o <= `ZeroWord;
		end
	end		

endmodule

        有了这个data_ram.v,那么mem.v就有了访问的空间了,可以输出地址和数据了,

				`EXE_LB_OP:		begin
					mem_addr_o <= mem_addr_i;
					mem_we <= `WriteDisable;
					mem_ce_o <= `ChipEnable;
					case (mem_addr_i[1:0])
						2'b00:	begin
							wdata_o <= {{24{mem_data_i[31]}},mem_data_i[31:24]};
							mem_sel_o <= 4'b1000;
						end
						2'b01:	begin
							wdata_o <= {{24{mem_data_i[23]}},mem_data_i[23:16]};
							mem_sel_o <= 4'b0100;
						end
						2'b10:	begin
							wdata_o <= {{24{mem_data_i[15]}},mem_data_i[15:8]};
							mem_sel_o <= 4'b0010;
						end
						2'b11:	begin
							wdata_o <= {{24{mem_data_i[7]}},mem_data_i[7:0]};
							mem_sel_o <= 4'b0001;
						end
						default:	begin
							wdata_o <= `ZeroWord;
						end
					endcase
				end

        涉及到的命令有load和store两种,我们可以挑选lb这一个命令进行解析,其他命令都是一个道理。首先,从代码上看,很明显mem.v传递给data_ram.v的地址是mem_addr_o。有了这个地址之后,mem.v就可以从data_ram.v那里获取mem_data_i的数据了。并且,还可以根据mem_addr_i末两位,决定最终写回的数据w_data_o取哪一部分。看到这里,大家可能会有一个疑问,那mem_addr_i又是哪里来的,通过查看ex_mem.v文件,发现了这么一句,

    mem_mem_addr <= ex_mem_addr;

        这说明,在exe阶段,地址其实就已经准备好了。这样,我们继续查看ex.v文件,发现了很有趣的这三条语句,

  //aluop_o传递到访存阶段,用于加载、存储指令
  assign aluop_o = aluop_i;
  
  //mem_addr传递到访存阶段,是加载、存储指令对应的存储器地址
  assign mem_addr_o = reg1_i + {{16{inst_i[15]}},inst_i[15:0]};

  //将两个操作数也传递到访存阶段,也是为记载、存储指令准备的
  assign reg2_o = reg2_i;

        这三条语句告诉我们,它们就是为mem访存做准备的。其中,mem_addr_o就是访存的最终地址。在exe阶段,地址就已经准备好了。而且,aluop_o需要继续递延到mem访存阶段,因为到时候还需要根据它继续判断执行的访存指令是哪一个。

        有了这些内容,我们才感觉到mem.v有了点实际的意义,不再像以前一样,只是负责数据的透传,没有什么具体的用途。当然,在整个测试的过程中,我们也发现2个问题,

        1)在defines.v中,既要把InstMemNum调整为64,还要把DataMemNum调整为64,不然iverilog编译不了;

        2)原来openmips.v Line375有编译错误,需要修改下,把ram_ce_o修改成1位wire即可,

C:\Users\feixiaoxing\Desktop\design_mips_cpu\Examples-in-book-write-your-own-cpu-master\Code\Chapter9_1>C:\iverilog\bin\iverilog.exe -o tb *.v
openmips.v:375: warning: Port 22 (mem_ce_o) of mem expects 1 bits, got 4.
openmips.v:375:        : Padding 3 high bits of the expression.
openmips_min_sopc.v:59: warning: Port 11 (ram_ce_o) of openmips expects 4 bits, got 1.
openmips_min_sopc.v:59:        : Padding 3 high bits of the port.

        有了上面这些内容做铺垫,就可以通过dumpfile、dumpvars的方法生成vcd文件调试波形了。

2、ll和sc命令

        在cpu里面完成原子操作有很多办法。最最常见的方法,就是关中断、开中断。这个方法非常容易想到,毕竟如果把中断都关掉了,那么基本上cpu就不会受到任何外来的干扰了。但是,mips cpu在这个基础上又想到了另外一个方法,那就是ll和sc。

        ll和sc通过影子寄存器llbit的方法,同样可以实现原子访问。首先ll访问的时候,cpu会从ram中读取数据,并且将llbit置为1。接着sc保存的时候,cpu会检测llbit是否为1。如果是1,则成功写入数据,llbit置为0,返回的寄存器置为1;如果是0,则取消写入过程,llbit不变,返回的寄存器置为0。

        整个过程最为精妙的地方在于两点,

        1)ll和sc一定要配对使用,单独使用是不成立的;

        2)影子寄存器对用户来说是不可见的,并且sc中写入数据的那个寄存器,同时担当了返回值的作用。

`include "defines.v"

module LLbit_reg(

	input	wire										clk,
	input wire										rst,
	
	input wire                    flush,
	//写端口
	input wire										LLbit_i,
	input wire                    we,
	
	//读端口1
	output reg                    LLbit_o
	
);


	always @ (posedge clk) begin
		if (rst == `RstEnable) begin
					LLbit_o <= 1'b0;
		end else if((flush == 1'b1)) begin
					LLbit_o <= 1'b0;
		end else if((we == `WriteEnable)) begin
					LLbit_o <= LLbit_i;
		end
	end

endmodule

        这就是llbit寄存器的读写代码。注意,llbit的读取是在mem访问的阶段进行的,它与reg访问在id阶段完成、mfhi&mflo在exe阶段完成都不一样。写入则和所有寄存器一样,都是wb阶段完成的。

        最后,我们还得回到一个老生常谈的话题。那就是llbit有可能读取不正确的问题。因为llbit是mem阶段被读取的,那么完全有可能llbit处于wb写回阶段,但是还没有赋值给reg,那么这个时候数据预取的工作就又要做一遍了,这从mem.v代码也可以完全看得出来,

	always @ (*) begin
		if(rst == `RstEnable) begin
			LLbit <= 1'b0;
		end else begin
			if(wb_LLbit_we_i == 1'b1) begin
				LLbit <= wb_LLbit_value_i;
			end else begin
				LLbit <= LLbit_i;
			end
		end
	end

3、load指令导致的流水线暂停

        前面我们讨论过,exe中出现madd这样指令的时候会出现流水线暂停。其实大家思考下,如果出现这样两条指令的时候,也会出现流水线暂停,


...... 
lw   $1, 0x0($0)    
beq  $1, $2, Label  
...... 

        前者刚从memory取出一个数据,保存到寄存器1。紧接着寄存器1和寄存器2马上就要进行比较判断了,判断的结果决定了pc后续的跳转地址是哪里。但是这个时候,lw才刚刚到exe阶段,还没有到达mem阶段。应该怎么处理呢?其实,也没啥好办法,就是让流水线暂停一会,

	always @ (*) begin
			stallreq_for_reg1_loadrelate <= `NoStop;	
		if(rst == `RstEnable) begin
			reg1_o <= `ZeroWord;	
		end else if(pre_inst_is_load == 1'b1 && ex_wd_i == reg1_addr_o 
								&& reg1_read_o == 1'b1 ) begin
			stallreq_for_reg1_loadrelate <= `Stop;							
		end else if((reg1_read_o == 1'b1) && (ex_wreg_i == 1'b1) 
								&& (ex_wd_i == reg1_addr_o)) begin
			reg1_o <= ex_wdata_i; 
		end else if((reg1_read_o == 1'b1) && (mem_wreg_i == 1'b1) 
								&& (mem_wd_i == reg1_addr_o)) begin
			reg1_o <= mem_wdata_i; 			
		end else if(reg1_read_o == 1'b1) begin
			reg1_o <= reg1_data_i;
		end else if(reg1_read_o == 1'b0) begin
			reg1_o <= imm;
		end else begin
			reg1_o <= `ZeroWord;
	  end
	end
	

        直接查看第二个判断条件。如果前面一条指令是load指令,并且需要写入某一个寄存器,除此之外,写入的寄存器还是当前要读入的寄存器,那么stallreq_for_reg1_loadrelate直接设置为1。当然,不仅仅是stallreq_for_reg1_loadrelate,还有可能stallreq_for_reg2_loadrelate也可能设置为1。两者共同决定了stallreq的最终结果。

  assign stallreq = stallreq_for_reg1_loadrelate | stallreq_for_reg2_loadrelate;
  assign pre_inst_is_load = ((ex_aluop_i == `EXE_LB_OP) || 
  													(ex_aluop_i == `EXE_LBU_OP)||
  													(ex_aluop_i == `EXE_LH_OP) ||
  													(ex_aluop_i == `EXE_LHU_OP)||
  													(ex_aluop_i == `EXE_LW_OP) ||
  													(ex_aluop_i == `EXE_LWR_OP)||
  													(ex_aluop_i == `EXE_LWL_OP)||
  													(ex_aluop_i == `EXE_LL_OP) ||
  													(ex_aluop_i == `EXE_SC_OP)) ? 1'b1 : 1'b0;

其他:

        今天的知识点有点多,大家可以多多思考,多多体会,多多练习。最后还是感谢《自己动手写cpu》这本书,今天谈到的这些代码都可以在Chapter9_1、Chapter9_2、Chapter9_3目录里面找到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/46994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【微服务】SpringCloud中Ribbon的轮询(RoundRobinRule)与重试(RetryRule)策略

💖 Spring家族及微服务系列文章 ✨【微服务】SpringCloud中Ribbon集成Eureka实现负载均衡 ✨【微服务】SpringCloud轮询拉取注册表及服务发现源码解析 ✨【微服务】SpringCloud微服务续约源码解析 ✨【微服务】SpringCloud微服务注册源码解析 ✨

Nginx的操作

一、什么是nginx。 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器 , 其特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上nginx的并发能力在同类型的网页服务器中表现较好。 Nginx代码完全用C语言从头写成 . 能够支持高达 50,000 个并发连接数的响应. 现在…

【pen200-lab】10.11.1.72

pen200-lab 学习笔记 【pen200-lab】10.11.1.72 &#x1f525;系列专栏&#xff1a;pen200-lab &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月27日&#x1f334; &#x1f36d;作…

aws beanstalk 使用eb cli配置和启动环境

Elastic Beanstalk 不额外收费&#xff0c;只需为存储和运行应用程序所需的 AWS 资源付费 EB CLI 是 Amazon Elastic Beanstalk 的命令行界面&#xff0c;它提供了可简化从本地存储库创建、更新和监控环境的交互式命令 安装eb cli $ pip install virtualenv $ virtualenv ebve…

2023年考研数学测试卷(预测)

2023年考研数学测试卷 原题再现&#xff1a; 多的我也不说了&#xff0c;直接把预测的2023年考研数学卷子分享给大家好吧&#xff0c;准确详细全面是我的宗旨&#xff0c;我的博客创立初衷和发展方向肯定不应该只是"考试有用"&#xff0c;而是面对社会生产生活的有用…

Day815.数据库参数设置优化 -Java 性能调优实战

数据库参数设置优化 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于数据库参数设置优化。 MySQL 是一个灵活性比较强的数据库系统&#xff0c;提供了很多可配置参数&#xff0c;便于根据应用和服务器硬件来做定制化数据库服务。 数据库主要是用来存取数据的&#…

视频编解码 — DCT变换和量化

目录 视频编码流程 DCT变换 Hadamard变换 量化 H264中的DCT变换和量化 H264各模式的DCT变换和量化过程 1、亮度16x16帧内预测块 2&#xff0c;其它模式亮度块 3&#xff0c;色度块 小结 视频编码流程 DCT变换 离散余弦变换 它能将空域信号转换到频率上表示&#xff0…

建造者模式

文章目录定义优点使用场景代码实现定义 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 4个角色&#xff1a; Product产品类&#xff1a;通常是实现了模板方法模式&#xff0c;也就是有模板方法和基本方法Builder抽象建造者&#xf…

PyQt5可视化编程-事件、信号和对话框

一、概述: 所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的&#xff0c;当然也有其他的事件产生方式&#xff0c;比如网络的连接&#xff0c;窗口管理器或者定时器等。调用应用的exec_()方法时&#xff0c;应用会进入主循环&#xff0c;主循环会监听和分发事件。…

算法题:整数除法

一.题目描述以及来源 给定两个整数 a 和 b &#xff0c;求它们的除法的商 a/b &#xff0c;要求不得使用乘号 *、除号 / 以及求余符号 % 。 注意&#xff1a; 整数除法的结果应当截去&#xff08;truncate&#xff09;其小数部分&#xff0c;例如&#xff1a;truncate(8.345…

MP157-2-TF-A移植:

MP157-2-TF-A移植&#xff1a;1. TF-A移植&#xff1a;1.1 新建开发板的设备树1.2 修改设备树电源管理1.3修改TF卡和EMMC设备树1.4 修改USBOTG设备树2 编译测试2.1 Makefile.sdk 修改内容&#xff1a;2.2 编译命令&#xff1a;正点原子第九章内容&#xff1a;自己记的笔记&…

SpringBoot(One·上)

SpringBoot一、简介概述Spring Boot特性SpringBoot四大核心二、SpringBoot项目分析1、创建第一个案例结构目录和pom文件2、Springboot集成mvcSpringboot核心配置文件application.propertiesSpringboot核心配置文件application.yml或者application.yamlapplication.ymlapplicati…

Allegro削铜皮详细操作指导

Allegro削铜皮详细操作指导 Allegro可以编辑任意形状的铜皮,下面介绍几种削铜皮的方式 任意形状,shape-manual Void/cavity-Polygon 鼠标左键点击铜皮,铜皮会被亮起来 画出需要的形状 完成后如下图 方形shape-manual Void/cavity-Rectangular 同样的选择铜皮,画出需要…

通过 js 给元素添加动画样式animation属性 ,以及 perspective 属性探究

学习关键语句: js添加动画效果 js控制元素animation属性 写在前面 在制作组件的过程中呢 , 突然觉得这个动画啊应该由用户来决定到底是个啥样 , 但是怎么让用户操作这一步呢 ? 总不能让用户自己去写 css keyframe 吧 , 所以便有了这篇文章 , 同时 , 这篇文章的下半部分我们会…

Python+Selenium:Google patent数据爬取

准备工作,已搭建Python环境,安装Selenium pip install selenium -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com 步骤1: 根据Chrome版本下载ChromeDriver 下载链接地址: ChromeDriver - WebDriver for Chrome - Downloads 如在帮助——>关于Ch…

车道线检测-lanedet

源码&#xff1a;https://github.com/Turoad/lanedet 这是一个常见的检测网络整合版本&#xff0c;目前包括的检测网络有&#xff1a; 模型论文介绍 SCNN&#xff0c;RESA论文介绍&#xff0c;UFLD介绍&#xff0c;laneNet|其它相关模型&#xff0c;LaneATT介绍 数据集介绍…

机器学习之归一化

机器学习之归一化1.目的1.1损失函数求解问题1.2 归一化目的2. 归一化2.1 最大值最小值归一化2.2 标准化1.目的 1.1损失函数求解问题 线性回归Loss函数梯度公式 参数含义θ\thetaθ函数参数α\alphaα学习率xjix^i_{j}xji​x:数据集&#xff0c;i:样本&#xff0c;j:特征 【数…

OPengl学习(二)——opengl环境搭建

文章目录0、 概念/准备1、VSOpengl快速添加手动编译2、QT中使用opengl1.pro配置文件2.引入头文件 继承QGLWidget3.实现三个主要函数3、引用0、 概念/准备 opengl官网地址 1、OpenGL 函数库相关的 API 有核心库&#xff08;gl&#xff09;&#xff0c;实用库&#xff08;glu&a…

自动化状态监测和工业4.0解决方案-Softing uaGate SI

某公司为其注塑和反应/挤出系统采用了uaGate SI网关技术并实行了开放且独立于平台的OPC UA标准&#xff0c;以用于设备状态自动化监控&#xff0c;这有助于提高产量并避免机器停机。 自动化状态监测提高了产量并且可避免机器停机。为了将其设备控制系统与IT系统相连接起来&…

最小生成树(Prim算法与Kruskal算法)

一、什么是最小生成树 一个连通图的生成树是一个极小的连通子图&#xff0c;它含有图中全部的n个顶点&#xff0c;但只有足以构成一棵树的n&#xff0d;1条边。我们把构造连通网的最小代价生成树称为最小生成树。 例如下图中①、②、③都是左侧图的生成树&#xff0c;但③是构…