FPGA实战操作(1) -- SDRAM(Verilog实现)
对SDRAM基本概念的介绍以及芯片手册说明,请参考上一篇文章SDRAM操作说明。
1. 说明
如图所示为状态机的简化图示,过程大概可以描述为:SDRAM(IS42S16320D)上电初始化完成后,进入“空闲”状态,此时一直监控外部控制模块给予的控制信号。初始化完成后,外部定时器开始定时,定时周期为SDRAM刷新周期(7.7us),一旦计数到刷新周期后,向状态机发送auto_ref_req(自动刷新请求),此时状态机进入“刷新”状态,这样就确保在无任何操作时,SDRAM能正常完成刷新。刷新完成后回到“空闲”状态。
当处于空闲状态时,接收到写命令(wr_en),进入“写”状态(有效接收读写命令的时刻有特殊要求,后面再详细说明),在full_page下连续写600个数据(100MHz,恰好耗时6us多一点,这样方便不用考虑定时刷新),写完之后,发送wr_done命令,进入“刷新”状态,相对于每次连续写完成后,提前刷新一次。此时,定时刷新的计数器清零,重新开始计数。
读多过程跟写过程类似,读完600个数据之后,手动完成刷新。

现在就来说一说,“空闲”状态接收读写命令的特殊要求。理论上充电周期为7.8125us,为保证600次读写在充电周期内完成,并且前后预留一些其他命令的时间,所以推荐在0~1us这个时间内接受读写命令,这样读写的时候专注读写就可以了。当然这是我的设计方式,如有更好的设计方式,那更好,欢迎分享。

2. 代码实现
状态机的代码如下所示,清晰的描述了各状态之间的跳变及其跳变条件。其中信号ctrl_valid即为上图中命令有效期的时间区间。在各状态描述的时序逻辑模块中,只是产生了读、写或刷新执行模块的使能信号,即在“写”状态的时候,使能写模块,完成相信的写操作。
    always @ (posedge clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				begin
					current_status <= IDLE;
				end
			else if(init_ing == 1'b0)
				begin
					current_status <= next_status;
				end
			else
				begin
					current_status <= IDLE;
				end
		end
	always @ (rst_n or current_status or sdram_wrreq or sdram_rdreq or ref_req_auto or wr_done or rd_done or ref_done or ctrl_valid)
		begin
			next_status = 5'dx;
			case(current_status)
				IDLE:
					begin
						if(ref_req_auto == 1'b1)						//收到自动刷新请求
							begin
								next_status = AUTO_REF;
							end
						else if(ctrl_valid == 1'b1 && sdram_wrreq == 1'b1)//在读写控制有效区内收到写请求
							begin
								next_status = WRITE;
							end
						else if(ctrl_valid == 1'b1 && sdram_rdreq == 1'b1)	     //在读写控制有效区内收到读请求
							begin
								next_status = READ;
							end
						else
							begin
								next_status = IDLE;
							end
					end
				WRITE:
					begin
						if(wr_done == 1'b1)
							begin
								next_status = AUTO_REF;
							end
						else
							begin
								next_status = WRITE;
							end
					end
				READ:
					begin
						if(rd_done == 1'b1)
							begin
								next_status = AUTO_REF;
							end
						else
							begin
								next_status = READ;
							end
					end
				AUTO_REF:
					begin
						if(ref_done == 1'b1)
							begin
								next_status = IDLE;
							end
						else
							begin
								next_status = AUTO_REF;
							end
					end
				default:
					begin
						next_status = IDLE;
					end
			endcase
		end
	//各个状态下的使能信号,以控制相应的模块执行相应的操作
	always @ (posedge clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				begin
					wr_start <= 1'b0;
					rd_start <= 1'b0;
					ref_start <= 1'b0;
				end
			else
				begin
					case(next_status)
						IDLE:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b0;
								ref_start <= 1'b0;
							end
						WRITE:
							begin
								wr_start <= 1'b1;
								rd_start <= 1'b0;
								ref_start <= 1'b0;
							end
						READ:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b1;
								ref_start <= 1'b0;
							end
						AUTO_REF:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b0;
								ref_start <= 1'b1;
							end
						default:
							begin
								wr_start <= 1'b0;
								rd_start <= 1'b0;
								ref_start <= 1'b0;
							end
					endcase
				end
		end
以下给出写操作模块的部分代码,读操作和刷新同理。中间有些信号是我工程需要,参考一下思路即可。
        always @(posedge clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				begin
					cke_wr <= 1'b0;
					cmd_wr <= NOP;
					dqm_wr <= DQM_DIS;
					bank_addr_wr <= BANK0;
					addr_wr <= DONT_CARE_ADDR;
					wr_done <= 1'b0;
					wr_first_flag_r <= 1'b0;
					status_wr <= 4'd0;
				end
			else if(wr_start == 1'b1)
				begin
					case(status_wr)
						4'd0:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd1:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= ACT;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= row_addr;	//行地址
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd2:									//4'd2和4'd3是为了延时T_RCD,即两个时钟
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd3:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd4:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b1;                       //用于写入第一个数据的时序标记
								status_wr <= status_wr + 4'd1;
							end
						4'd5:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= WR;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= column_addr;		//{A12A11,A10,column_address}
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= status_wr + 4'd1;
							end
						4'd6:
							begin
								if(sdram_wr_done == 1'b1)		//用于增加NOP持续周期
									begin
										cke_wr <= 1'b1;
										cmd_wr <= NOP;
										dqm_wr <= DQM_DIS;
										bank_addr_wr <= BANK0;
										addr_wr <= DONT_CARE_ADDR;
										wr_done <= 1'b1;
										wr_first_flag_r <= 1'b0;
										status_wr <= status_wr + 4'd1;
									end
								else
									begin
										cke_wr <= 1'b1;
										cmd_wr <= NOP;
										dqm_wr <= DQM_EN;
										bank_addr_wr <= BANK0;
										addr_wr <= DONT_CARE_ADDR;
										wr_done <= 1'b0;
										wr_first_flag_r <= 1'b0;
										status_wr <= status_wr;
									end
							end
						4'd7:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_DIS;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= 4'd0;
							end
						default:
							begin
								cke_wr <= 1'b1;
								cmd_wr <= NOP;
								dqm_wr <= DQM_EN;
								bank_addr_wr <= BANK0;
								addr_wr <= DONT_CARE_ADDR;
								wr_done <= 1'b0;
								wr_first_flag_r <= 1'b0;
								status_wr <= 4'd0;
							end
					endcase
				end
			else
				begin
					cke_wr <= 1'b1;
					cmd_wr <= NOP;
					dqm_wr <= DQM_EN;
					bank_addr_wr <= BANK0;
					addr_wr <= DONT_CARE_ADDR;
					wr_done <= 1'b0;
					wr_first_flag_r <= 1'b0;
					status_wr <= 4'd0;
				end
		end
参考文献
SDRAM驱动篇之简易SDRAM控制器的verilog代码实现
FPGA实战操作(1) -- SDRAM(Verilog实现)的更多相关文章
- FPGA实战操作(1) -- SDRAM(操作说明)
		
SDRAM是做嵌入式系统中,常用是的缓存数据的器件.基本概念如下(注意区分几个主要常见存储器之间的差异): SDRAM(Synchronous Dynamic Random Access Memory ...
 - FPGA实战操作(2) -- PCIe总线(例程设计分析)
		
1.框架总览 平台:vivado 2016.4 FPGA:A7 在实际应用中,我们几乎不可能自己去编写接口协议,所以在IP核的例程上进行修改来适用于项目是个不错的选择. 通过vivado 中有关PCI ...
 - FPGA按键去抖verilog代码
		
按键去抖的原因及其分类就不罗嗦了. 在这里解释一段代码,代码是网上找的,看了半天没懂,无奈查了半天想了半天,终于明白了... module sw_debounce(clk,rst_n,sw1,sw2, ...
 - FPGA实战操作(2) -- PCIe总线(协议简述)
		
目录 1. PCIe基础知识 2. 事务层协议 2.1 数据包结构 2.2 帧头含义详述 3. 报文举例 3.1 寄存器读报文 3.2 完成报文 4. 机制简述 4.1 Non-Posted和Post ...
 - FPGA之驱动sdram控制兼容性移植实验
		
cb早在2012年就推出了VIP 视频开发板 V1.4 这套开发板是ep2的,摄像头是ov7670,虽然不如当前的vip20强大,但也算是其雏形. 在vip20后期,cb对sdram以及其他模块进行 ...
 - FPGA 状态机-序列检测器verilog
		
实现功能:检测出串行输入数据4位Data二进制序列0101,当检测到该序列的时候,out=1,否则out=0 (1)给出状态编码,画出状态图 (2)门电路实现 (3)verilog实现 首先规定Q3Q ...
 - 【FPGA篇章三】FPGA常用语句:Verilog基本语法要素
		
欢迎大家关注我的微信公众账号,支持程序媛写出更多优秀的文章 Verilog中总共有十九种数据类型,我们先介绍四个最基本的数据类型,他们是: reg型.wire型.integer型.parameter型 ...
 - 基于fpga的256m的SDRAM控制器
		
2018/7/26 受教于邓堪文老师,开始真真学习控制sdram 由于自己买的sdram模块是256的,原来老师的是128,所以边学边改,不知道最后好不好使,但是我有信心 一.sdram的初始化 sd ...
 - 【推荐图书】+ 基于Nios II的嵌入式SoPC系统设计与Verilog开发实例+C#入门经典等
		
[推荐图书]+ 基于Nios II的嵌入式SoPC系统设计与Verilog开发实例+C#入门经典等 3赞 发表于 2016/7/4 21:14:12 阅读(1921) 评论(3) 初次接触FPGA,到 ...
 
随机推荐
- java 多线程系列---JUC原子类(一)之框架
			
根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类. 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;2. 数组类型: AtomicIn ...
 - javascript——对象的基础知识
			
一.javascript作为脚本语言可以完成以下任务: 操纵浏览器对象,如窗口的打开与关闭: 操纵Dom树: 通过XMLHttpRequest对象与服务器端进行异步通信: XML编程,借助于Activ ...
 - eclipse安卓模拟器Failed to install on device 'emulator-5554': timeout处理方案
			
我们在用模拟器调试的时候,经常会出现Failed to install on device 'emulator-5554': timeout这个错误.其实就是有些虚拟器在部署的时候时间过于长.系统就认 ...
 - hadoop再次集群搭建(5)-CDH Install
			
登录 http://node1.com:7180/.用户名和密码都是admin.启动服务命令是 service cloudera-scm-server start 最开始两个页面直接conti ...
 - volatile关键字在多线程中的作用
 - Ajax笔记(二)
			
JSON基本概念: JSON:javaScript对象表示法(JavaScript Object Notation) JSON是存储和交换文本信息的语法,类似XML.它采用键值对的方式来组织,易于人们 ...
 - java虚拟机垃圾回收机制详解
			
首先,看一下java虚拟机运行的时候内存分配图: jvm虚拟机栈:一个是线程独有的,每次启动一个线程,就创建一个jvm虚拟机栈,线程退出的时候就销毁.这里面主要保存线程本地变量名和局部变量值. 本地方 ...
 - [tensorflow]异或门的实现
			
一段小程序:待理解 import tensorflow as tf import numpy as np #输入训练数据,这里是python的list, 也可以定义为numpy的ndarray x_d ...
 - 【转】pecl,pear的不同
			
PEAR是PHP扩展与应用库(the PHP Extension and Application Repository)的缩写.它是一个PHP扩展及应用的一个代码仓库,基于php代码的,安装目录在/u ...
 - Mat的迭代器使用
			
如果你熟悉 C++的 STL 库,那一定了解迭代器(iterator)的使用.迭代器可以方便地遍历所有元素.Mat 也增加了迭代器的支持,以便于矩阵元素的遍历.下面的例程功能跟上一节的例程类似,但是由 ...