SDRAM驱动篇之简易SDRAM控制器的verilog代码实现

article/2025/10/26 11:59:26

在Kevin写的上一篇博文《SDRAM理论篇之基础知识及操作时序》中,已经把SDRAM工作的基本原理和SDRAM初始化、读、写及自动刷新操作的时序讲清楚了,在这一片博文中,Kevin来根据在上一篇博文中分析的思路来把写一个简单的SDRAM控制器。

我们在上一篇博文中提到了这样一个问题,SDRAM是每隔15us进行刷新一次,但是如果当SDRAM需要进行刷新时,而SDRAM正在写数据,这两个操作之间怎么进行协调呢?因为我们是肯定需要保证写的数据不能丢失,所以,我们可以考虑这样来做:如果刷新的时间到了,先让写操作把正在写的4个数据(突发长度为4)写完,然后再去进行刷新操作。而如果在执行读操作也遇到需要刷新的情况,我们也可以这样来做,先让数据读完,再去执行刷新操作。

大家看完可能会想,说是这么说,那代码怎么来写呢?似乎还是没什么思路。大家可以想象一下,我们写的SDRAM控制器是肯定包括初始化、读操作、写操作及自动刷新这些操作的,既然这样,我们就可以给每一个操作写上一个模块独立开来,这样也便于我们每个模块的调试,显然这种思路是正确的。那怎么让我们的各个模块工作起来呢,虽然都是独立的模块,但很显然这几个模块之间又是相互关联的。就拿上面刚才说的那个情况来讲,如果SDRAM需要刷新了,而SDRAM却正在执行写操作,那我们刷新模块与写模块之间怎么进行控制呢?这个问题解决了,读模块与刷新模块之间的这个问题也可以很轻松的解决。大家不妨可以自己先想一下。

主状态机与各模块间的连线

为了解决各个模块之间不方便控制的情况,我们引入一个新的机制 ——“仲裁”机制。“仲裁”用来干什么呢?在这里边,“仲裁”相当于我们这个SDRAM控制器的老大,对SDRAM的各个操作统一协调:读、写及自动刷新都由“仲裁”来控制。说到这里,显然我们可以再写一个“仲裁”模块,既然在仲裁模块中要控制这么多操作,那自然而然的肯定想到了利用状态机。那我们的状态机怎么来设计呢?请看下图:

sdram_state

只给一个状态机的图,Kevin还是觉得不够说明问题,再上一个模块之间的示意图:

state_module

在讲之前,Kevin 要给大家打一下预防针:在接下来讲的过程中,大家一定要搞清楚Kevin说的是模块之间连线的关系还是状态机之间跳转的关系哦。

在仲裁模块中,初始化操作完成之后便进入到了“ARBIT”仲裁状态,只有处于仲裁状态的时候,“仲裁老大”才能进行下命令。我们先来模拟一下,当状态机处于“WRITE”写状态时,如果SDRAM刷新的时间到了,刷新模块同时向写模块和仲裁模块发送刷新请求ref_req信号,当写模块接受到ref_req之后,写模块在写完当前4个数据(突发长度为4)之后,写模块的写结束标志flag_wr_end拉高,然后状态机进入“ARBIT”仲裁状态,处于仲裁状态之后,此时有刷新请求ref_req,然后状态机跳转到“AREF”状态并且仲裁模块发送ref_en刷新使能,然后呢,刷新模块将刷新请求信号ref_req拉低并给sdram发送刷新的命令。等刷新完毕之后,刷新模块给仲裁模块发送flag_ref_end刷新结束标志,状态机跳转到“ARBIT”仲裁状态。

注意了,当刷新完跳转到“ARBIT”仲裁状态之后,如果之前我们的全部数据仍然没有写完(Kevin指的是全部数据,并不是一个突发长度的4个数据哦),那么此时我们仍然要给仲裁模块写请求“wr_req”,然后仲裁模块经过一系列判断之后,如果符合写操作的时机,那就给写模块一个写使能信号“wr_en”,然后跳转到“WRITE”写状态并且写模块开始工作。

对于读模块与刷新操作之间的协调,相信大家应该也能想象得到了,Kevin在这里就不再啰嗦了。

仲裁模块(顶层模块)代码介绍

下面先看一下在我们的仲裁模块(顶层模块)中状态机的定义:

 
  1. //state
  2. always @(posedge sclk or negedge s_rst_n)
  3. if(s_rst_n == 1'b0)
  4. state <= IDLE;
  5. else case(state)
  6. IDLE:
  7. if(key[0] == 1'b1)
  8. state <= INIT;
  9. else
  10. state <= IDLE;
  11. INIT:
  12. if(flag_init_end == 1'b1) //初始化结束标志
  13. state <= ARBIT;
  14. else
  15. state <= INIT;
  16. ARBIT:
  17. if(ref_req == 1'b1) //刷新请求到来且已经写完
  18. state <= AREF;
  19. else if(ref_req == 1'b0 && rd_en == 1'b1) //默认读操作优先于写操作
  20. state <= READ;
  21. else if(ref_req == 1'b0 && wr_en == 1'b1) //无刷新请求且写请求到来
  22. state <= WRITE;
  23. else
  24. state <= ARBIT;
  25. AREF:
  26. if(flag_ref_end == 1'b1)
  27. state <= ARBIT;
  28. else
  29. state <= AREF;
  30. WRITE:
  31. if(flag_wr_end == 1'b1)
  32. state <= ARBIT;
  33. else
  34. state <= WRITE;
  35. READ:
  36. if(flag_rd_end == 1'b1)
  37. state <= ARBIT;
  38. else
  39. state <= READ;
  40. default:
  41. state <= IDLE;
  42. endcase

下面简单的介绍一下状态机代码:

key[0]作为我们初始化的一个使能信号,如果是实际下板子的时候,我们还需要给按键加一个按键消抖模块。当按键0按下之后,代表我们的SDRAM的初始化使能信号来了,所以状态机从“IDLE”跳转到了“INIT”状态。在初始化状态,如果我们的初始化模块传来了初始化结束标志“flag_init_end”,那状态机跳转到“ARBIT”仲裁状态,在仲裁状态中,第一个“if”是判断刷新请求的,这也就说明了我们刷新的优先级最高。之后,如果处于仲裁状态,来了读使能信号或者写使能信号并且没有刷新请求,那状态机就跳转到对应的状态。如果处于读或写的状态,当读结束标志或者写结束标志来临的时候(这里的写结束标志和读结束标志都是指突发读或突发写的结束标志),那么就会跳转到仲裁状态。

初始化模块代码简单介绍

下面再简单的看下初始化模块中的代码:

 
  1. /***********************************************
  2. * Module Name : sdram_init
  3. * Engineer : Kevin
  4. * Function : sdram初始化模块
  5. * Date : 2016.01.10
  6. * Blog Website : dengkanwen.com
  7. * Version : v1.0
  8. ***********************************************/
  9. module sdram_init(
  10. input wire sclk, //系统时钟为50M,即T=20ns
  11. input wire s_rst_n,
  12. output reg [3:0] cmd_reg, //sdram命令寄存器
  13. output reg [11:0] sdram_addr, //地址线
  14. output reg [1:0] sdram_bank, //bank地址
  15. output reg flag_init_end //sdram初始化结束标志
  16. );
  17. parameter CMD_END = 4'd11, //初始化结束时的命令计数器的值
  18. CNT_200US = 14'd1_0000,
  19. NOP = 4'b0111, //空操作命令
  20. PRECHARGE = 4'b0010, //预充电命令
  21. AUTO_REF = 4'b0001, //自刷新命令
  22. MRSET = 4'b0000; //模式寄存器设置命令
  23.  
  24. reg [13:0] cnt_200us; //200us计数器
  25. reg flag_200us; //200us结束标志(200us结束后,一直拉高)
  26. reg [3:0] cnt_cmd; //命令计数器,便于控制在某个时候发送特定指令
  27. reg flag_init; //初始化标志:初始化结束后,该标志拉低
  28. //flag_init
  29. always @(posedge sclk or negedge s_rst_n)
  30. if(s_rst_n == 1'b0)
  31. flag_init <= 1'b1;
  32. else if(cnt_cmd == CMD_END)
  33. flag_init <= 1'b0;
  34. //cnt_200us
  35. always @(posedge sclk or negedge s_rst_n)
  36. if(s_rst_n == 1'b0)
  37. cnt_200us <= 14'd0;
  38. else if(cnt_200us == CNT_200US)
  39. cnt_200us <= 14'd0;
  40. else if(flag_200us == 1'b0)
  41. cnt_200us <= cnt_200us + 1'b1;
  42. //flag_200us
  43. always @(posedge sclk or negedge s_rst_n)
  44. if(s_rst_n == 1'b0)
  45. flag_200us <= 1'b0;
  46. else if(cnt_200us == CNT_200US)
  47. flag_200us <= 1'b1;
  48. //cnt_cmd
  49. always @(posedge sclk or negedge s_rst_n)
  50. if(s_rst_n == 1'b0)
  51. cnt_cmd <= 4'd0;
  52. else if(flag_200us == 1'b1 && flag_init == 1'b1)
  53. cnt_cmd <= cnt_cmd + 1'b1;
  54. //flag_init_end
  55. always @(posedge sclk or negedge s_rst_n)
  56. if(s_rst_n == 1'b0)
  57. flag_init_end <= 1'b0;
  58. else if(cnt_cmd == CMD_END)
  59. flag_init_end <= 1'b1;
  60. else
  61. flag_init_end <= 1'b0;
  62. //cmd_reg
  63. always @(posedge sclk or negedge s_rst_n)
  64. if(s_rst_n == 1'b0)
  65. cmd_reg <= NOP;
  66. else if(cnt_200us == CNT_200US)
  67. cmd_reg <= PRECHARGE;
  68. else if(flag_200us)
  69. case(cnt_cmd)
  70. 4'd0:
  71. cmd_reg <= AUTO_REF; //预充电命令
  72. 4'd6:
  73. cmd_reg <= AUTO_REF;
  74. 4'd10:
  75. cmd_reg <= MRSET; //模式寄存器设置
  76. default:
  77. cmd_reg <= NOP;
  78. endcase
  79. //sdram_addr
  80. always @(posedge sclk or negedge s_rst_n)
  81. if(s_rst_n == 1'b0)
  82. sdram_addr <= 12'd0;
  83. else case(cnt_cmd)
  84. 4'd0:
  85. sdram_addr <= 12'b0100_0000_0000; //预充电时,A10拉高,对所有Bank操作
  86. 4'd10:
  87. sdram_addr <= 12'b0000_0011_0010; //模式寄存器设置时的指令:CAS=2,Burst Length=4;
  88. default:
  89. sdram_addr <= 12'd0;
  90. endcase
  91. //sdram_bank
  92. always @(posedge sclk or negedge s_rst_n)
  93. if(s_rst_n == 1'b0)
  94. sdram_bank <= 2'd0; //这里仅仅只是初始化,在模式寄存器设置时才会用到且其值为全零,故不赋值
  95.  
  96. //sdram_clk
  97. assign sdram_clk = ~sclk;
  98. endmodule

下面我们来结合代码回顾下初始化过程:

首先,我们需要有200us的稳定期,所以我们便有了一个200us的计数器cnt_200us,而这个计数器是根据flag_200us的低电平来工作的。大家可以看到,falg_200us在200us计时之后一直拉高。在200us计满,即flag_200us拉高之后,我们就需要先给一个“NOP”命令,然后给两次“Precharge”命令,同时选中ALL Banks。

SDRAM写模块介绍

下面咱们先不对SDRAM的初始化进行仿真,等把全部的模块讲完再来仿真。接下来再继续说写操作:首先在我们的仲裁模块,初始化完成之后,就已经跳转到“ARBIT”仲裁状态了,然后,我们在testbench中模拟一个外部的写请求信号。咱们先看下写模块的代码:

 

 
  1. module sdram_write(
  2. input wire sclk,
  3. input wire s_rst_n,
  4. input wire key_wr,
  5. input wire wr_en, //来自仲裁模块的写使能
  6. input wire ref_req, //来自刷新模块的刷新请求
  7. input wire [5:0] state, //顶层模块的状态
  8. output reg [15:0] sdram_dq, //sdram输入/输出端口
  9. //output reg [3:0] sdram_dqm, //输入/输出掩码
  10. output reg [11:0] sdram_addr, //sdram地址线
  11. output reg [1:0] sdram_bank, //sdram的bank地址线
  12. output reg [3:0] sdram_cmd, //sdram的命令寄存器
  13. output reg wr_req, //写请求(不在写状态时向仲裁进行写请求)
  14. output reg flag_wr_end //写结束标志(有刷新请求来时,向仲裁输出写结束)
  15. );
  16. parameter NOP = 4'b0111, //NOP命令
  17. ACT = 4'b0011, //ACT命令
  18. WR = 4'b0100, //写命令(需要将A10拉高)
  19. PRE = 4'b0010, //precharge命令
  20. CMD_END = 4'd8,
  21. COL_END = 9'd508, //最后四个列地址的第一个地址
  22. ROW_END = 12'd4095, //行地址结束
  23. AREF = 6'b10_0000, //自动刷新状态
  24. WRITE = 6'b00_1000; //状态机的写状态
  25. reg flag_act; //需要发送ACT的标志
  26. reg [3:0] cmd_cnt; //命令计数器
  27. reg [11:0] row_addr; //行地址
  28. reg [11:0] row_addr_reg; //行地址寄存器
  29. reg [8:0] col_addr; //列地址
  30. reg flag_pre; //在sdram内部为写状态时需要给precharge命令的标志
  31.  
  32. //flag_pre
  33. always @(posedge sclk or negedge s_rst_n)
  34. if(s_rst_n == 1'b0)
  35. flag_pre <= 1'b0;
  36. else if(col_addr == 9'd0 && flag_wr_end == 1'b1)
  37. flag_pre <= 1'b1;
  38. else if(flag_wr_end == 1'b1)
  39. flag_pre <= 1'b0;
  40. //flag_act
  41. always @(posedge sclk or negedge s_rst_n)
  42. if(s_rst_n == 1'b0)
  43. flag_act <= 1'b0;
  44. else if(flag_wr_end)
  45. flag_act <= 1'b0;
  46. else if(ref_req == 1'b1 && state == AREF)
  47. flag_act <= 1'b1;
  48. //wr_req
  49. always @(posedge sclk or negedge s_rst_n)
  50. if(s_rst_n == 1'b0)
  51. wr_req <= 1'b0;
  52. else if(wr_en == 1'b1)
  53. wr_req <= 1'b0;
  54. else if(state != WRITE && key_wr == 1'b1)
  55. wr_req <= 1'b1;
  56. //flag_wr_end
  57. always @(posedge sclk or negedge s_rst_n)
  58. if(s_rst_n == 1'b0)
  59. flag_wr_end <= 1'b0;
  60. else if(cmd_cnt == CMD_END)
  61. flag_wr_end <= 1'b1;
  62. else
  63. flag_wr_end <= 1'b0;
  64. //cmd_cnt
  65. always @(posedge sclk or negedge s_rst_n)
  66. if(s_rst_n == 1'b0)
  67. cmd_cnt <= 4'd0;
  68. else if(state == WRITE)
  69. cmd_cnt <= cmd_cnt + 1'b1;
  70. else
  71. cmd_cnt <= 4'd0;
  72. //sdram_cmd
  73. always @(posedge sclk or negedge s_rst_n)
  74. if(s_rst_n == 1'b0)
  75. sdram_cmd <= 4'd0;
  76. else case(cmd_cnt)
  77. 3'd1:
  78. if(flag_pre == 1'b1)
  79. sdram_cmd <= PRE;
  80. else
  81. sdram_cmd <= NOP;
  82. 3'd2:
  83. if(flag_act == 1'b1 || col_addr == 9'd0)
  84. sdram_cmd <= ACT;
  85. else
  86. sdram_cmd <= NOP;
  87. 3'd3:
  88. sdram_cmd <= WR;
  89.  
  90. default:
  91. sdram_cmd <= NOP;
  92. endcase
  93. //sdram_dq
  94. always @(posedge sclk or negedge s_rst_n)
  95. if(s_rst_n == 1'b0)
  96. sdram_dq <= 16'd0;
  97. else case(cmd_cnt)
  98. 3'd3:
  99. sdram_dq <= 16'h0012;
  100. 3'd4:
  101. sdram_dq <= 16'h1203;
  102. 3'd5:
  103. sdram_dq <= 16'h562f;
  104. 3'd6:
  105. sdram_dq <= 16'hfe12;
  106. default:
  107. sdram_dq <= 16'd0;
  108. endcase
  109. /* //sdram_dq_m
  110. always @(posedge sclk or negedge s_rst_n)
  111. if(s_rst_n == 1'b0)
  112. sdram_dqm <= 4'd0; */
  113. //row_addr_reg
  114. always @(posedge sclk or negedge s_rst_n)
  115. if(s_rst_n == 1'b0)
  116. row_addr_reg <= 12'd0;
  117. else if(row_addr_reg == ROW_END && col_addr == COL_END && cmd_cnt == CMD_END)
  118. row_addr_reg <= 12'd0;
  119. else if(col_addr == COL_END && flag_wr_end == 1'b1)
  120. row_addr_reg <= row_addr_reg + 1'b1;
  121. //row_addr
  122. always @(posedge sclk or negedge s_rst_n)
  123. if(s_rst_n == 1'b0)
  124. row_addr <= 12'd0;
  125. else case(cmd_cnt)
  126. //因为下边的命令是通过行、列地址分开再给addr赋值,所以需要提前一个周期赋值,以保证在命令到来时能读到正确的地址
  127. 3'd2:
  128. row_addr <= 12'b0000_0000_0000; //在写命令时,不允许auto-precharge
  129. default:
  130. row_addr <= row_addr_reg;
  131. endcase
  132. //col_addr
  133. always @(posedge sclk or negedge s_rst_n)
  134. if(s_rst_n == 1'b0)
  135. col_addr <= 9'd0;
  136. else if(col_addr == COL_END && cmd_cnt == CMD_END)
  137. col_addr <= 9'd0;
  138. else if(cmd_cnt == CMD_END)
  139. col_addr <= col_addr + 3'd4;
  140. //sdram_addr
  141. always @(posedge sclk or negedge s_rst_n)
  142. if(s_rst_n == 1'b0)
  143. sdram_addr <= 12'd0;
  144. else case(cmd_cnt)
  145. 3'd2:
  146. sdram_addr <= row_addr;
  147. 3'd3:
  148. sdram_addr <= col_addr;
  149. default:
  150. sdram_addr <= row_addr;
  151. endcase
  152. //sdram_bank
  153. always @(posedge sclk or negedge s_rst_n)
  154. if(s_rst_n == 1'b0)
  155. sdram_bank <= 2'b00;
  156. endmodule

在我们的模块端口列表中,用key_wr来接收写请求信号,这个写请求信号,是在没有写完之前一直拉高的,在写完了全部数据之后才拉低的。当然这个代码的话,还是按照Kevin在上一篇博文中的理论来的,大家只要好好理解理论就可以很轻松的明白为什么代码要这样写了。

在写模块中,Kevin是让SDRAM循环着写16’h0012,16’h1203,16’h562f,16’hfe12这四个数据。

另外一点,在我们的这个写模块中,Kevin是在每写完4个数据,也就是突发结束后,有一个写完标志,从而使状态机跳转到仲裁状态,然后如果数据没写完,由于写请求是拉高的,所以如果此时没有刷新请求,那状态机还是会跳转到写状态继续写的。

SDRAM读操作模块

首先,咱们依然先上代码:

 
  1. module sdram_read(
  2. input wire sclk,
  3. input wire s_rst_n,
  4. input wire rd_en,
  5. input wire [5:0] state,
  6. input wire ref_req, //自动刷新请求
  7. input wire key_rd, //来自外部的读请求信号
  8. input wire [15:0] rd_dq, //sdram的数据端口
  9. output reg [3:0] sdram_cmd,
  10. output reg [11:0] sdram_addr,
  11. output reg [1:0] sdram_bank,
  12. output reg rd_req, //读请求
  13. output reg flag_rd_end //突发读结束标志
  14. );
  15.  
  16. parameter NOP = 4'b0111,
  17. PRE = 4'b0010,
  18. ACT = 4'b0011,
  19. RD = 4'b0101, //SDRAM的读命令(给读命令时需要给A10拉低)
  20. CMD_END = 4'd12, //
  21. COL_END = 9'd508, //最后四个列地址的第一个地址
  22. ROW_END = 12'd4095, //行地址结束
  23. AREF = 6'b10_0000, //自动刷新状态
  24. READ = 6'b01_0000; //状态机的读状态
  25. reg [11:0] row_addr;
  26. reg [8:0] col_addr;
  27. reg [3:0] cmd_cnt;
  28. reg flag_act; //发送ACT命令标志(单独设立标志,便于跑高速)
  29.  
  30. //flag_act
  31. always @(posedge sclk or negedge s_rst_n)
  32. if(s_rst_n == 1'b0)
  33. flag_act <= 1'b0;
  34. else if(flag_rd_end == 1'b1 && ref_req == 1'b1)
  35. flag_act <= 1'b1;
  36. else if(flag_rd_end == 1'b1)
  37. flag_act <= 1'b0;
  38. //rd_req
  39. always @(posedge sclk or negedge s_rst_n)
  40. if(s_rst_n == 1'b0)
  41. rd_req <= 1'b0;
  42. else if(rd_en == 1'b1)
  43. rd_req <= 1'b0;
  44. else if(key_rd == 1'b1 && state != READ)
  45. rd_req <= 1'b1;
  46. //cmd_cnt
  47. always @(posedge sclk or negedge s_rst_n)
  48. if(s_rst_n == 1'b0)
  49. cmd_cnt <= 4'd0;
  50. else if(state == READ)
  51. cmd_cnt <= cmd_cnt + 1'b1;
  52. else
  53. cmd_cnt <= 4'd0;
  54. //flag_rd_end
  55. always @(posedge sclk or negedge s_rst_n)
  56. if(s_rst_n == 1'b0)
  57. flag_rd_end <= 1'b0;
  58. else if(cmd_cnt == CMD_END)
  59. flag_rd_end <= 1'b1;
  60. else
  61. flag_rd_end <= 1'b0;
  62. //row_addr
  63. always @(posedge sclk or negedge s_rst_n)
  64. if(s_rst_n == 1'b0)
  65. row_addr <= 12'd0;
  66. else if(row_addr == ROW_END && col_addr == COL_END && flag_rd_end == 1'b1)
  67. row_addr <= 12'd0;
  68. else if(col_addr == COL_END && flag_rd_end == 1'b1)
  69. row_addr <= row_addr + 1'b1;
  70. //col_addr
  71. always @(posedge sclk or negedge s_rst_n)
  72. if(s_rst_n == 1'b0)
  73. col_addr <= 9'd0;
  74. else if(col_addr == COL_END && flag_rd_end == 1'b1)
  75. col_addr <= 9'd0;
  76. else if(flag_rd_end == 1'b1)
  77. col_addr <= col_addr + 3'd4;
  78. //cmd_cnt
  79. always @(posedge sclk or negedge s_rst_n)
  80. if(s_rst_n == 1'b0)
  81. cmd_cnt <= 4'd0;
  82. else if(state == READ)
  83. cmd_cnt <= cmd_cnt + 1'b1;
  84. else
  85. cmd_cnt <= 4'd0;
  86. //sdram_cmd
  87. always @(posedge sclk or negedge s_rst_n)
  88. if(s_rst_n == 1'b0)
  89. sdram_cmd <= NOP;
  90. else case(cmd_cnt)
  91. 4'd2:
  92. if(col_addr == 9'd0)
  93. sdram_cmd <= PRE;
  94. else
  95. sdram_cmd <= NOP;
  96. 4'd3:
  97. if(flag_act == 1'b1 || col_addr == 9'd0)
  98. sdram_cmd <= ACT;
  99. else
  100. sdram_cmd <= NOP;
  101. 4'd4:
  102. sdram_cmd <= RD;
  103. default:
  104. sdram_cmd <= NOP;
  105. endcase
  106. //sdram_addr
  107. always @(posedge sclk or negedge s_rst_n)
  108. if(s_rst_n == 1'b0)
  109. sdram_addr <= 12'd0;
  110. else case(cmd_cnt)
  111. 4'd4:
  112. sdram_addr <= {3'd0, col_addr};
  113. default:
  114. sdram_addr <= row_addr;
  115. endcase
  116. //sdram_bank
  117. always @(posedge sclk or negedge s_rst_n)
  118. if(s_rst_n == 1'b0)
  119. sdram_bank <= 2'd0;
  120.  
  121. endmodule

其实读模块和写模块是极其相似的,所以在这里,Kevin就不做赘述,大家慢慢消化吧。

SDRAM自动刷新模块

自动刷新模块算是比较简单的,等15us的时间过了,就向仲裁模块发刷新请求,然后在刷新完成之后,产生刷新结束标志:

 
  1. /*****************************************************************
  2. * Module Name : auto_refresh
  3. * Enegineer : Kevin
  4. * Function : sdram自动刷新
  5. * Blog Website : http://dengkanwen.com
  6. * Comment : 在这个模块中并没有bank地址的输出线,需要在顶层模块中设置
  7. ******************************************************************/
  8. module auto_refresh(
  9. input wire sclk,
  10. input wire s_rst_n,
  11. input wire ref_en,
  12. input wire flag_init_end, //初始化结束标志(初始化结束后,启动自刷新标志)
  13. output reg [11:0] sdram_addr,
  14. output reg [1:0] sdram_bank,
  15. output reg ref_req,
  16. output reg [3:0] cmd_reg,
  17. output reg flag_ref_end
  18. );
  19.  
  20. parameter BANK = 12'd0100_0000_0000, //自动刷新是对所有bank刷新
  21. CMD_END = 4'd10,
  22. CNT_END = 10'd749, //15us计时结束
  23. NOP = 4'b0111, //
  24. PRE = 4'b0010, //precharge命令
  25. AREF = 4'b0001; //auto-refresh命令
  26. reg [9:0] cnt_15ms; //15ms计数器
  27. reg flag_ref; //处于自刷新阶段标志
  28. reg flag_start; //自动刷新启动标志
  29. reg [3:0] cnt_cmd; //指令计数器
  30. //flag_start
  31. always @(posedge sclk or negedge s_rst_n)
  32. if(s_rst_n == 1'b0)
  33. flag_start <= 1'b0;
  34. else if(flag_init_end == 1'b1)
  35. flag_start <= 1'b1;
  36. //cnt_15ms
  37. always @(posedge sclk or negedge s_rst_n)
  38. if(s_rst_n == 1'b0)
  39. cnt_15ms <= 10'd0;
  40. else if(cnt_15ms == CNT_END)
  41. cnt_15ms <= 10'd0;
  42. else if(flag_start == 1'b1)
  43. cnt_15ms <= cnt_15ms + 1'b1;
  44. //flag_ref
  45. always @(posedge sclk or negedge s_rst_n)
  46. if(s_rst_n == 1'b0)
  47. flag_ref <= 1'b0;
  48. else if(cnt_cmd == CMD_END)
  49. flag_ref <= 1'b0;
  50. else if(ref_en == 1'b1)
  51. flag_ref <= 1'b1;
  52. //cnt_cmd
  53. always @(posedge sclk or negedge s_rst_n)
  54. if(s_rst_n == 1'b0)
  55. cnt_cmd <= 4'd0;
  56. else if(flag_ref == 1'b1)
  57. cnt_cmd <= cnt_cmd + 1'b1;
  58. else
  59. cnt_cmd <= 4'd0;
  60. //flag_ref_end
  61. always @(posedge sclk or negedge s_rst_n)
  62. if(s_rst_n == 1'b0)
  63. flag_ref_end <= 1'b0;
  64. else if(cnt_cmd == CMD_END)
  65. flag_ref_end <= 1'b1;
  66. else
  67. flag_ref_end <= 1'b0;
  68. //cmd_reg
  69. always @(posedge sclk or negedge s_rst_n)
  70. if(s_rst_n == 1'b0)
  71. cmd_reg <= NOP;
  72. else case(cnt_cmd)
  73. 3'd0:
  74. if(flag_ref == 1'b1)
  75. cmd_reg <= PRE;
  76. else
  77. cmd_reg <= NOP;
  78. 3'd1:
  79. cmd_reg <= AREF;
  80. 3'd5:
  81. cmd_reg <= AREF;
  82. default:
  83. cmd_reg <= NOP;
  84. endcase
  85. //sdram_addr
  86. always @(posedge sclk or negedge s_rst_n)
  87. if(s_rst_n == 1'b0)
  88. sdram_addr <= 12'd0;
  89. else case(cnt_cmd)
  90. 4'd0:
  91. sdram_addr <= BANK; //bank进行刷新时指定allbank or signle bank
  92. default:
  93. sdram_addr <= 12'd0;
  94. endcase
  95. //sdram_bank
  96. always @(posedge sclk or negedge s_rst_n)
  97. if(s_rst_n == 1'b0)
  98. sdram_bank <= 2'd0; //刷新指定的bank
  99. //ref_req
  100. always @(posedge sclk or negedge s_rst_n)
  101. if(s_rst_n == 1'b0)
  102. ref_req <= 1'b0;
  103. else if(ref_en == 1'b1)
  104. ref_req <= 1'b0;
  105. else if(cnt_15ms == CNT_END)
  106. ref_req <= 1'b1;
  107. //flag_ref_end
  108. always @(posedge sclk or negedge s_rst_n)
  109. if(s_rst_n == 1'b0)
  110. flag_ref_end <= 1'b0;
  111. else if(cnt_cmd == CMD_END)
  112. flag_ref_end <= 1'b1;
  113. else
  114. flag_ref_end <= 1'b0;
  115.  
  116. endmodule

至此,咱们的整个设计就已经讲完了,至于模块之间怎么连线,Kevin就不再硬性灌输了,留给大家自己完成吧。当然Kevin还是想提醒大家,因为SDRAM的数据总线是双向的,所以需要弄个三态门,在向SDRAM写数据的时候,模块定义的数据总线应为输出型,在接收数据时,需要定义成高阻态。

SDRAM仿真

设计讲完了,咱们来说下仿真,在我们仿真的时候,我们需要用到SDRAM的仿真模型(关于仿真模型,Kevin已经上传到“福利/文档手册”这个栏目下了)。

因为我们需要有一个200us的稳定期,所以我们可以先让Modelsim跑个200us,可以直接在命令窗口输入”run 200us”;200us的稳定器过了之后,接下来应该就是咱们的初始话了,所以我们在让modelsim跑600ns,下面是仿真的结果:

sdram_c

sdram_tb_wave

在上边的仿真中,我们已经知道SDRAM已经初始化成功了,设置的潜伏期为3,突发长度为4

下面我们再运行一段时间,就运行1us吧,往SDRAM中写数据:

sdram_wave

这里的写的数据,就是咱们在写模块中设置的那4个数,只是之前的是用16进制定义的,这里显示的是10进制。

然后我们再看一下读数据:sdram_read

大家可以看下,我们读出来的数据和写进来的数据是不是一样的呢?


http://chatgpt.dhexx.cn/article/EM9sATZB.shtml

相关文章

FPGA之SDRAM控制器设计(一)

MT48LC128M4A2 – 32 Meg x 4 x 4 banks是512M SRAM&#xff0c;总体概述如下图 分别从上电初始化&#xff0c;刷新&#xff0c;写&#xff0c;读四个部分进行设计&#xff0c;此外还包含主控状态机&#xff0c;一个顶层。 1&#xff1a;上电初始化 整体架构&#xff1a;从控…

内存控制器与SDRAM【赞】

原文链接&#xff1a;https://blog.csdn.net/qq_31216691/article/details/87115697 内存接口概念&#xff1a; 通常ARM芯片内置的内存很少&#xff0c;要运行Linux&#xff0c;需要扩展内存。ARM9扩展内存使用SDRAM内存&#xff0c;ARM11使用 DDR SDRAM。S3C2440通常外接32位6…

SDRAM 介绍

目录 1、名词解释 2、SDRAM 内部结构 3、SDRAM 外部信号描述 4、SDRAM 命令 4.1、COMMAND INHIBIT 4.2、NO OERATION 4.3、ACTIVE 4.4、LOAD MODE REGISTER (LMR) 4.5、READ 4.6、WRITE 4.7、PRECHARGE 4.8、BURST TERMINATE 4.9、REFRESH 4.9.1、AUTO REFRESH …

SDRAM控制器操作时序

此为学习http://dengkanwen.com/137.html整理的笔记&#xff0c;侵删&#xff01; SDRAM工作原理 内部的状态跳转图 我们所需关注的几个地方&#xff1a; 1&#xff09;粗黑线表示在该状态下会自动跳转到另一个状态&#xff0c;细黑线表示需要给命令才会跳转。 2&#xff09…

SDR SDRAM控制器设计

目录 前言 1、关于刷新 2、关于数据中心对齐 3、SDRAM芯片手册介绍 3.1SDRAM芯片的管脚 3.2 SDRAM指令集 3.3 模式寄存器 3.4 关于SDRAM上电初始化和装载模式寄存器 3.5 SDRAM刷新时序 3.6 关于写访问 3.7 关于突发访问 4、FPGA工程设计 4.1状态机设计 5、仿真测试…

【GD32】从零开始学GD32单片机高级篇——外部存储器控制器EXMC详解+SDRAM读写例程

目录 简介外部设备地址映射NOR和PSRAM的地址映射NAND/PC Card地址映射SDRAM地址映射 NOR/PSRAM控制器接口描述控制时序模式1模式2 NAND Flash或PC Card控制器接口描述控制时序 SDRAM控制器接口描述控制时序突发读操作突发写操作读写FIFO跨边界读写操作低功耗模式自刷新模式掉电…

初识内存控制器和SDRAM【一文了解】

原文链接&#xff1a;https://blog.csdn.net/qq_36243942/article/details/85596249 目录 1.引入内存控制器 2.不同位宽内存设备之间的连接 3.如何确定芯片的访问地址 4.分析读写NOR FLASH的读写时序 5.SDRAM初识 6.编程读/写 SDRAM 附录&#xff1a;源代码 1.引入内存控制器 我…

存储控制器(SDRAM操作)

什么是存储控制器 2440是32位单片机&#xff0c;进行数据访问时通过32位地址访问。 CPU发出32位地址信号给存储控制器&#xff0c;存储控制器根据地址信号设置片选信号及地址总线&#xff0c;将相应数据通过数据总线传回存储控制器&#xff0c;存储控制器将收到的数据以字节为…

数字IC实践项目(2)——高速SDRAM控制器的设计与综合(入门级工程项目)

数字IC实践项目&#xff08;2&#xff09;—高速SDRAM控制器的设计与综合&#xff08;入门级工程项目&#xff09; 写在前面的话项目简介和学习目的SDRAM简介SDRAM控制器简介完整项目框图SDRAM控制器项目框图SDRAM初始化模块SDRAM自动刷新模块SDRAM写模块SDRAM读模块SDRAM仲裁机…

SDRAM控制器——仲裁模块的实现

前面一文中&#xff0c;我们已经对SDRAM的上电初始化、自动刷新以及突发读写进行了学习。 本文跟着大佬学习SDRAM中的仲裁模块。 仲裁机制 仲裁&#xff08;arbit&#xff09;&#xff1a;在FPGA中&#xff0c;当多个source源同时发出请求&#xff0c;容易导致操作冲突&#x…

SDRAM读写控制器

第1节 –作者&#xff1a;小黑同学 本文为明德扬原创及录用文章&#xff0c;转载请注明出处&#xff01; 1.1 总体设计 1.1.1 概述 同步动态随机存取内存&#xff08;synchronous dynamic randon-access menory&#xff0c;简称SDRAM&#xff09;是有一个同步接口的动态随…

FPGA综合项目——SDRAM控制器

FPGA综合项目——SDRAM控制器 目录 1.整体框架2.串口接收模块3.接收模块测试仿真4.串口发送模块5.发送模块测试仿真6.SDRAM基础学习7.SDRAM顶层模块8.SDRAM初始化模块设计与仿真测试9.自动刷新模块设计与测试10.写模块设计与测试11.读模块设计与仿真测试12.通信处理模块13.顶层…

细说SDRAM控制器

SDRAM的基本概念 SDRAM凭借其极高的性价比&#xff0c;广泛应用于高速数据存储、实时图像处理等设计当中&#xff0c;但是相对于SRAM、FIFO等其他存储器件&#xff0c;SDRAM的控制相对复杂。虽说是复杂&#xff0c;但也不代表没办法实现&#xff0c;仔细梳理一下&#xff0c;发…

SDRAM控制器

1.SDRAM简介 简介&#xff1a;SDRAM为同步动态随机存储内存&#xff0c;同步指的是时钟与外部输入的时钟保持一致&#xff0c;也就是与外部共用一个时钟&#xff1b;动态指的是每个时间段内&#xff0c;都要进行一次刷新操作&#xff0c;否则里面的数据会丢失&#xff0c;这也…

FPGA学习历程(四):SDRAM 控制器(初始化与刷新)

目录 一、数据手册相关信息1.1 命令真值表1.2 时间参数1.3 模式寄存器配置 二、初始化模块2.1 模块时序图2.2 模块源码2.2.1 sdram_init.v2.2.2 sdram_top.v2.2.3 tb_sdram_top.v 2.3 Modelsim仿真 三、刷新模块3.1 模块时序图3.2 模块源码3.2.1 sdram_aref.v3.2.2 sdram_top.v…

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

上篇博客&#xff0c;我们了解了SDRAM的控制命令以及寻址方式&#xff0c;SDRAM芯片需要配合专门的控制电路使用才能发挥功能&#xff0c;这一节我们将一步步分析&#xff0c;使用Verilog搭建一个SDRAM驱动控制器。 目录 学习目标 问题分析 初始化模块 信息收集 模块接口确…

SDRAM 控制器(一)

1、基础知识 SDRAM&#xff08;synchronous Dynamic Random &#xff09;&#xff0c;同步动态随机存储器&#xff0c;同步指内存工作需要同步时钟&#xff0c;内存的命令的发送和数据的接收都以它为标准。动态是指需要不断地刷新来保证数据不丢失&#xff08;电容存储&#xf…

操作系统面试题(二十一):什么是DMA

DMA DMA&#xff08;Direct Memory Access 直接内存访问&#xff09; DMA意味着CPU授予I/O模块权限不涉及在不涉及CPU的情况下依然可以读取/写入内存&#xff0c;即DMA不需要CPUde支持 DMAC&#xff08;DMA 控制器&#xff09; 控制直接内存访问的过程 DMA的优点&#xff1a…

操作系统面试题:虚拟内存是什么,解决了什么问题,如何映射?

虚拟内存是什么&#xff1f; 虚拟内存别称虚拟存储器&#xff08;Virtual Memory&#xff09;。电脑中所运行的程序均需经由内存执行&#xff0c;若执行的程序占用内存很大或很多&#xff0c;则会导致内存消耗殆尽。为解决该问题&#xff0c;Windows中运用了虚拟内存技术&…

Linux面试题(34道)

1、Linux中各个目录的作用 1&#xff09;/ 根目录 2&#xff09;/boot 引导程序&#xff0c;内核等存放的目录 3&#xff09;/sbin 超级用户可以使用的命令的存放目录 4&#xff09;/bin 普通用户可以使用的命令的存放目录 5&#xff09;/lib 根目录下的所程序的共享库目录 6&…