手把手带你实现SDRAM控制器(带Verilog代码) 您所在的位置:网站首页 cs2deom控制器 手把手带你实现SDRAM控制器(带Verilog代码)

手把手带你实现SDRAM控制器(带Verilog代码)

2024-07-06 01:31| 来源: 网络整理| 查看: 265

        上篇博客,我们了解了SDRAM的控制命令以及寻址方式,SDRAM芯片需要配合专门的控制电路使用才能发挥功能,这一节我们将一步步分析,使用Verilog搭建一个SDRAM驱动控制器。

目录

  学习目标

  问题分析

初始化模块

信息收集

模块接口确定 

状态机设计

仿真测试

Modelsim仿真:

  学习目标 搭建SDRAM控制器,能读,能写,并且可以自动初始化以及自动刷新。学习分析问题和使用Verilog解决问题的方法。   问题分析

          数字系统自顶向下的的设计原则,我们首先可以分析目标中的功能。不难看出SDRAM控制器应该包含以下模块:

初始化模块读控制模块写控制模块 自动刷新模块      

        各个模块将复用SDRAM顶层模块的输出接口,因此将会存在一个总线仲裁的问题,例如当自刷新模块和读模块同时发出请求时,而我们只能响应其中一个,所以我们解决把输出接口交给谁的问题,因此引出第五个模块:仲裁模块。

        为了验证设计的需要,我们设计的模块接口兼容野火电子的测试平台接口,各部分关系图如下:

初始化模块

        SDRAM在上电之后,在执行正常操作之前需要先执行一次初始化操作。

         初始化的流程都是固定的,查看参考手册给的信息可以确定初始化需要顺序执行以下操作:

给SDRAM上电,CKE设置为高电平,加载稳定的时钟。执行空指令,并且持续100us。100us过后执行预充电指令,并选中所有Bank(A10为高电平)。预充电指令写入后,等待tRP时间,写入自动刷新命令。之后保持空命令,持续tRC时间。tRC时间过后,在此写入自动刷新命令。自动刷新命令写入后,写入空操作命令持续tRC时间。等待时间结束后,即可写入模式寄存器配置指令,地址总线 A0-A11 参数不同 辅助模式寄存器不同模式的设置。模式寄存器配置指令写入后,还需要保持空操 作命令持续 tMRD 时间。tMRD时间过后,SDRAM初始化即完成。

         观察上述步骤,我们的脑海中肯定一直回荡着:计数器~、状态机~ ,对的,是这样的,完全没错!这个模块的设计主要就是围绕 状态机,佐以计数器。

        抛开状态机细节不谈,我们下一步应该要收集我们需要的资料信息:

信息收集 时钟资源:100MHz系统时钟,一个周期就是10usT=100us 为最小等待时间,我们在使用 SDRAM 时,等待时间 T 可适当延长,设定为200us。开发板的SDRAM信号为W9825G6KH,tRP为两个时钟周期,tMRD3个时钟周期,tRC7个时钟周期。初始化过程中,至少进行两次自动刷新,也可适当增加刷新次数,设定为8次。使用的命令集:NOP=4‘b0111,P_CHARGE=4’b0010,AUTO_REF = 4'b0001,M_REG_SET= 4'b0000。 模块接口确定 

         本状态机属于穆尔型状态机,时钟和复位接口作为仅有的两个输入,输出则根据SDRAM命令集接口有 init_cmd (4bit) 、init_ba(Bank辅助,2bit),init_addr(地址辅助,13bit),init_end(完成信号,1bit)

状态机设计

        状态机的设计有三大要素需要考虑:1、状态数目,2、状态转移条件,3、状态输出。

首先我们要设计出状态的数目,我们可以由前面的初始化步骤画出我们的状态转移图:

        平板太滑了,展现不出本人的书法水平,所以大家将就着看。 

可以看到,我们的状态机可以设计9个状态去实现它,可能有同学会问,为什么等待状态也要设置好几个不同的状态呀?能不能只设计一个?这是个好问题,其实这四个等待状态就相当于一个等待状态,我们的计数器也只设计一个,然后每个状态会传递不同的计数周期参数,并在计数完成时清零。当然也不是不可以每个状态设计一个计数器,这样代码会简单很多,其实我也建议大家这样做,我觉得Verilog代码设计思路越简单越好,不要像带着C语言那样一个main走天下的思路去写Verilog。

        接下来就是代码实现:

// 刷新模块 New_Designed By BUCT WWD 2022/9/7 // SDRAM初始化模块,初始化步骤:上电--延时200us--预充电--刷新x8--寄存器设定--完成 module sdram_init( input wire sys_clk, //系统时钟信号 input wire sys_rst_n,//系统复位信号 output wire[1:0] init_ba, //Bank辅助位 output wire[3:0] init_cmd,//命令位 output wire[12:0] init_addr,//地址辅助位 output wire init_end //初始化结束标志 ); parameter IDLE = 4'd0,Wait1 = 4'd1,P_CHAR = 4'd2,Wait2 =4'd3,Auto_REF=4'd4,Wait3=4'd5,MRS=4'd6,Wait4=4'd7,OK=4'd8;//九个状态 parameter Init_t = 15'd20000,//初始化200us,各项等待参数 TRP_t = 15'd2, TRC_t = 15'd7, TMRD_t = 15'd3; parameter P_CHARGE = 4'b0010, //指令集 Au_REF = 4'b0001, NOP = 4'b0111, M_REG_SET = 4'b0000; parameter REF_TIME = 4'd8; //自刷新次数参数 reg[3:0] state,next_state; reg[14:0] cnt_now; //延时长度寄存器 reg[14:0] cnt_value; //计时计数器值 reg[3:0] REF_cnt; //刷新计数 reg[1:0] init_ba_reg; reg[3:0] init_cmd_reg; reg[12:0] init_addr_reg; reg init_end_reg; always@(posedge sys_clk or negedge sys_rst_n)begin//状态转移 if(sys_rst_n ==1'b0) state = 8))) || (RW_interrupt_read[2] == 1'b1)) begin Pc_b2 = 1'b1; Act_b2 = 1'b0; RP_chk2 = $time; Auto_precharge[2] = 1'b0; Read_precharge[2] = 1'b0; RW_interrupt_read[2] = 1'b0; if (Debug) $display ("at time %t NOTE : Start Internal Auto Precharge for Bank 2", $time); end end if ((Auto_precharge[3] == 1'b1) && (Read_precharge[3] == 1'b1)) begin if ((($time - RAS_chk3 >= tRAS) && ((Burst_length_1 == 1'b1 && Count_precharge[3] >= 1) || (Burst_length_2 == 1'b1 && Count_precharge[3] >= 2) || (Burst_length_4 == 1'b1 && Count_precharge[3] >= 4) || (Burst_length_8 == 1'b1 && Count_precharge[3] >= 8))) || (RW_interrupt_read[3] == 1'b1)) begin Pc_b3 = 1'b1; Act_b3 = 1'b0; RP_chk3 = $time; Auto_precharge[3] = 1'b0; Read_precharge[3] = 1'b0; RW_interrupt_read[3] = 1'b0; if (Debug) $display ("at time %t NOTE : Start Internal Auto Precharge for Bank 3", $time); end end // Internal Precharge or Bst if (Command[0] == `PRECH) begin // Precharge terminate a read with same bank or all banks if (Bank_precharge[0] == Bank || A10_precharge[0] == 1'b1) begin if (Data_out_enable == 1'b1) begin Data_out_enable = 1'b0; end end end else if (Command[0] == `BST) begin // BST terminate a read to current bank if (Data_out_enable == 1'b1) begin Data_out_enable = 1'b0; end end if (Data_out_enable == 1'b0) begin Dq_reg = tRAS) && // Case 2 (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [0] >= 1) || // Case 1 (Burst_length_2 == 1'b1 && Count_precharge [0] >= 2) || (Burst_length_4 == 1'b1 && Count_precharge [0] >= 4) || (Burst_length_8 == 1'b1 && Count_precharge [0] >= 8))) || (RW_interrupt_write[0] == 1'b1 && WR_counter[0] >= 2)) begin // Case 3 (stop count when interrupt) Auto_precharge[0] = 1'b0; Write_precharge[0] = 1'b0; RW_interrupt_write[0] = 1'b0; #tWRa; // Wait for tWR Pc_b0 = 1'b1; Act_b0 = 1'b0; RP_chk0 = $time; if (Debug) $display ("at time %t NOTE : Start Internal Auto Precharge for Bank 0", $time); end end end always @ (WR_counter[1]) begin if ((Auto_precharge[1] == 1'b1) && (Write_precharge[1] == 1'b1)) begin if ((($time - RAS_chk1 >= tRAS) && (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [1] >= 1) || (Burst_length_2 == 1'b1 && Count_precharge [1] >= 2) || (Burst_length_4 == 1'b1 && Count_precharge [1] >= 4) || (Burst_length_8 == 1'b1 && Count_precharge [1] >= 8))) || (RW_interrupt_write[1] == 1'b1 && WR_counter[1] >= 2)) begin Auto_precharge[1] = 1'b0; Write_precharge[1] = 1'b0; RW_interrupt_write[1] = 1'b0; #tWRa; // Wait for tWR Pc_b1 = 1'b1; Act_b1 = 1'b0; RP_chk1 = $time; if (Debug) $display ("at time %t NOTE : Start Internal Auto Precharge for Bank 1", $time); end end end always @ (WR_counter[2]) begin if ((Auto_precharge[2] == 1'b1) && (Write_precharge[2] == 1'b1)) begin if ((($time - RAS_chk2 >= tRAS) && (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [2] >= 1) || (Burst_length_2 == 1'b1 && Count_precharge [2] >= 2) || (Burst_length_4 == 1'b1 && Count_precharge [2] >= 4) || (Burst_length_8 == 1'b1 && Count_precharge [2] >= 8))) || (RW_interrupt_write[2] == 1'b1 && WR_counter[2] >= 2)) begin Auto_precharge[2] = 1'b0; Write_precharge[2] = 1'b0; RW_interrupt_write[2] = 1'b0; #tWRa; // Wait for tWR Pc_b2 = 1'b1; Act_b2 = 1'b0; RP_chk2 = $time; if (Debug) $display ("at time %t NOTE : Start Internal Auto Precharge for Bank 2", $time); end end end always @ (WR_counter[3]) begin if ((Auto_precharge[3] == 1'b1) && (Write_precharge[3] == 1'b1)) begin if ((($time - RAS_chk3 >= tRAS) && (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [3] >= 1) || (Burst_length_2 == 1'b1 && Count_precharge [3] >= 2) || (Burst_length_4 == 1'b1 && Count_precharge [3] >= 4) || (Burst_length_8 == 1'b1 && Count_precharge [3] >= 8))) || (RW_interrupt_write[3] == 1'b1 && WR_counter[3] >= 2)) begin Auto_precharge[3] = 1'b0; Write_precharge[3] = 1'b0; RW_interrupt_write[3] = 1'b0; #tWRa; // Wait for tWR Pc_b3 = 1'b1; Act_b3 = 1'b0; RP_chk3 = $time; if (Debug) $display ("at time %t NOTE : Start Internal Auto Precharge for Bank 3", $time); end end end task Burst; begin // Advance Burst Counter Burst_counter = Burst_counter + 1; // Burst Type if (Mode_reg[3] == 1'b0) begin // Sequential Burst Col_temp = Col + 1; end else if (Mode_reg[3] == 1'b1) begin // Interleaved Burst Col_temp[2] = Burst_counter[2] ^ Col_brst[2]; Col_temp[1] = Burst_counter[1] ^ Col_brst[1]; Col_temp[0] = Burst_counter[0] ^ Col_brst[0]; end // Burst Length if (Burst_length_2) begin // Burst Length = 2 Col [0] = Col_temp [0]; end else if (Burst_length_4) begin // Burst Length = 4 Col [1 : 0] = Col_temp [1 : 0]; end else if (Burst_length_8) begin // Burst Length = 8 Col [2 : 0] = Col_temp [2 : 0]; end else begin // Burst Length = FULL Col = Col_temp; end // Burst Read Single Write if (Write_burst_mode == 1'b1) begin Data_in_enable = 1'b0; end // Data Counter if (Burst_length_1 == 1'b1) begin if (Burst_counter >= 1) begin Data_in_enable = 1'b0; Data_out_enable = 1'b0; end end else if (Burst_length_2 == 1'b1) begin if (Burst_counter >= 2) begin Data_in_enable = 1'b0; Data_out_enable = 1'b0; end end else if (Burst_length_4 == 1'b1) begin if (Burst_counter >= 4) begin Data_in_enable = 1'b0; Data_out_enable = 1'b0; end end else if (Burst_length_8 == 1'b1) begin if (Burst_counter >= 8) begin Data_in_enable = 1'b0; Data_out_enable = 1'b0; end end end endtask //**********************将SDRAM内的数据直接输出到外部文件*******************************// /* integer sdram_data,ind; always@(sdram_r) begin sdram_data=$fopen("sdram_data.txt"); $display("Sdram dampout begin ",sdram_data); // $fdisplay(sdram_data,"Bank0:"); for(ind=0;ind


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有