SDRAM那些事儿第一季教程 —— 小结

2020.2.18 留
肺炎当道,被堵在家回不去学校,莫得器材,莫得设备,干不了活,只有手边一块放假顺手捎回来的 FPGA 开发板。在家颓过了整个春节,手机电脑都快玩吐了 也不能一直浪费时间,那就搞搞这块板子呗,为假期结束后要做的东西提前准备好改造素材也好。然后就瞄上了 Kevin 大佬的 SDRAM 教程。12 号晚开工,中间翘了两天,直到 18 号下午正式调通。东西虽然简单,但是整体的梳理还是要做一下的,故有此一文。(顺便提高以后可能存在的复习效率)

一、项目整体介绍

  要做的事情很简单,PC 端利用串口助手通过 UART 向 FPGA 板子发送数据包,FPGA 接收后提取出对 SDRAM 操作命令及操作数据,即将数据写入 SDRAM 和将数据从 SDRAM 读出。数据从 SDRAM 被读出后,通过串口回发到 PC端。项目的系统设计框图如下所示(下图中 Cmd_encode 有误,应为 Cmd_decode,不是编码而是解码):
在这里插入图片描述

二、系统各模块设计结构梳理

2.1 串口收发模块

2.1.1 UART_RX模块设计

在这里插入图片描述
  串口接收时序图设计如上图。下面说明设计思路:

  • rx 为 FPGA 的串口接收端,传递 PC 端过来的数据。空闲时为高电平,PC 端要发送数据过来时会将该线拉低表示起始位,接着依次发送 8bit 数据(最低位优先)。无校验位(完整的 RS232 协议中有一位的奇偶校验位)。
  • rx_t ~ rx_ttt 都是对 rx 信号的延拍。打两拍作用是跨时钟域处理(PC 端与 FPGA 端使用的明显不是同一个时钟域),打三拍作用是捕获 rx 信号的下降沿(起始位)。这里附上其他人关于跨时钟域处理的学习总结: FPGA基础学习(3) – 跨时钟域处理方法
  • rx_flag 是串口接收端识别出起始位后,拉高表示接收模块开始工作的标志信号。在接收 8bit 数据完毕后重新拉低。
  • baud_cnt 为波特率计数器,FPGA 使用时钟为 50MHz,所以串口发送 1bit 的数据占用的时钟周期数计算方式为:
    (1 / 9600)* 10^9 / 20 = 5208 ,这里 9600 为波特率,20 为 20ns。也可记为 50 * 10^6 / 9600,50 * 10^6 即 50MHz。
    波特率计数器计满自动清零。
  • bit_flagbaud_cnt 的中间值(此时的数据最稳定),用于作为数据抓取标志。
  • bit_cnt 用于统计一次通信累计接收的数据位数,计数到 8 表示当前数据接收完毕后清零。
  • rx_data 通过移位拼接实现串转并。
  • po_flag 为串口数据接收完成,数据可用的标识。

  Modelsim 软件仿真结果如下图:
在这里插入图片描述

2.1.2 UART_TX模块设计

在这里插入图片描述
  串口发送时序图设计如上图。下面说明设计思路:

  • tx_trig 为串口发送触发信号,对应串口接收模块中的 po_flag
  • tx_data 是需要通过串口发送出去的数据。这里额外注意 tx_trig 为高时的数据。
  • tx_data_reg 是新定义的一个寄存器,用来锁存 tx_data 的值(防止发送期间因 tx_data 变化而导致数据混乱)。
  • tx_flag 为串口发送模块处于工作状态的标志信号,对应串口接收模块中的 rx_flag
  • baud_cntbit_flagbit_cnt 则与串口接收模块中的用途相同(这里的 bit_flag 之所以放在 baud_cnt 计满溢出的位置而非中间位置只是为了写代码时方便一些)。
  • rs232_tx 为串口发送信号,只在 baud_cnt 为 0 且 tx_flag 为高的时候才发送起始位。

  Modelsim 软件仿真结果如下图:
在这里插入图片描述

2.2 命令解析模块

在这里插入图片描述

  • uart_flag 为 UART_RX 模块给出的 po_flag 信号输入,uart_data 为 UART_RX 模块给出的 rx_data
  • rec_num 为接收数据计数器,计满 4 个清零(该项目默认一帧数据一次写入4个)。注意,该计数器不对 8‘haa 读命令进行计入。
  • cmd_reg 为命令寄存器,默认一帧数据中的第一个数据为 SDRAM 控制器的操作命令。—— 8‘h55 为写,8’haa 为读。
  • wr_trig 为给 SDRAM 的写触发信号。等4个数据全部写入 wfifo 后,再让 SDRAM 控制器到 wfifo 这里读出来。
  • rd_trig 为给 SDRAM 的读触发信号,当解析出 8’haa 读命令时给出触发脉冲。
  • wfifo_wr_en 为 wfifo 的写使能信号。命令解析模块解析出 8’h55 写命令后,将接下来的 4 个数据判断为要写入 wfifo 的数据。
  • wr_trigrd_trigwfifo_wr_en 这三者实际上都可看作是不同条件下对 uart_flag 的筛分。

  Modelsim 软件仿真结果如下图:
在这里插入图片描述

2.3 读写FIFO(wfifo、rfifo)

2.3.1 wfifo时序设计及要点

在这里插入图片描述
  上半部分对接命令解析模块,对接 SDRAM 的主要是下半部分。这也是尤为需要注意的地方:在命令解析模块向 SDRAM 给出写触发脉冲( wr_trig )后,SDRAM 给出 wfifo 的读使能信号,从 wfifo 里读出要写入 SDRAM 的数据。所以,wfifo 的读使能信号在 wfifo 数据未被全部读出时必须保持高电平,全部读出后才能拉低。

2.3.2 rfifo时序设计及要点

在这里插入图片描述
在这里插入图片描述
  图中的 rfifo_empty 信号为 rfifo 的空信号,实际应该取反,即为空时为高电平,内部有数据时该信号拉低。当命令解析模块解析出 8’haa 读命令时,给 SDRAM 控制器一个读触发脉冲。SDRAM 收到后,将 rfifo 的写使能信号拉高,并把读出来的数据写入 rfifo(上图中的红框部分)。由此,rfifo 内部不再为空, rfifo_empty 信号拉低,UART_TX 模块给出 rfifo 的读使能,将从 rfifo 读出的数据做并转串处理后发送。


以下的部分为 SDRAM 控制器设计,这就需要根据自身的所用芯片的不同,参考芯片的数据手册进行修改了。Kevin 大佬在视频教程中所用的是地址位为 12 位的 SDRAM,而我板子上的 SDRAM 是 13 位的地址,就不能直接用,得自己看着改。
在这里插入图片描述
在这里插入图片描述

2.4 SDRAM控制器 —— 初始化

  时间已经从手册上查出并标注在了时序图的对应位置。
在这里插入图片描述
  初始化操作完成后,对外输出一个高电平表示初始化操作已完成。

2.5 SDRAM控制器 —— 刷新

在这里插入图片描述
  自动刷新的频率根据手册要求来决定。如博主使用的 SDRAM 芯片要求在 64ms 内刷新完 8192 行,这就要求 64 * 1000 / 8192 = 7.8us 要有一次刷新。
  同时,在这里引入仲裁机制。顾名思义,在判断出 SDRAM 已完成初始化的情况下,为确保刷新、读、写的操作循环有序进行,引入仲裁机制。这里对刷新模块引出刷新请求信号,即在刷新模块内部进行 7.8us 的周期计时,计满拉高刷新请求信号,传递至仲裁状态机给出刷新使能后,再由刷新模块对 SDRAM 执行刷新操作。(这里先不考虑读、写模块)刷新操作执行完成后,拉高刷新完成信号,反馈给仲裁状态机,确保仲裁状态机退出刷新标记状态,以便进行下一次仲裁。

2.6 SDRAM控制器 —— 写

在这里插入图片描述
  接收到写触发信号后,写模块对外拉高写请求信号,经仲裁状态机判断后给出写使能(默认处理优先级顺序为刷新、写、读),写模块执行写操作。写操作完成后,同样拉高写完成信号,反馈到仲裁状态机。由于写模块整个操作流程涉及到的命令较多,故使用状态机进行设计。
  在这里还有两点细节需要注意:

  • 可能存在写满了一行,但仍未写完的情况,此时需要Precharge关闭现有行,然后回跳到行激活状态,激活新行后继续执行写操作。
  • 可能存在正在执行写操作时刷新模块发出刷新请求的情况,此时继续保持写入直到此次突发写完再关闭现有行,退出写状态,并重新发送写请求等待仲裁状态机响应。

  Modelsim 软件仿真如下,几张图依次对应开始写入、写入中途刷新、写完一行换行、写完数据的情况:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  这是代码里设定的状态及其对应值:
在这里插入图片描述

2.7 SDRAM控制器 —— 读

在这里插入图片描述
  读与写设计思路大体相同(代码可以看作是类似写的镜像),最需要注意的部分就在时序图上:读模块的 read 命令、Precharge 命令的给出与数据读出的时序关系。

SDRAM(Synchronous Dynamic Random Access Memory)是一种现代的计算机内存技术,与传统的DRAM(Dynamic Random Access Memory)相比具有更高的带宽和更低的延迟。下面我将简要介绍SDRAM的一些重要特点和相关代码。 1. 同步性:SDRAM与系统总线同步操作,其读写操作需要与系统时钟同步。为了实现同步,我们可以使用如下代码来初始化SDRAM控制器: ```C void sdram_init() { // 设置SDRAM控制寄存器,使其与系统总线同步 SDRAM_CTRL->CLKDIV = get_sdram_clkdiv(); SDRAM_CTRL->CMD = get_sdram_cmd(); } ``` 2. 列地址选通:SDRAM的存储单元被组织为多行多列的矩阵,读写操作需要选择相应的行和列。下面是一个简单的示例代码: ```C void sdram_read(uint32_t row, uint32_t column, uint8_t *data) { // 设置行地址 SDRAM_CTRL->ADDR = get_sdram_row_addr(row); // 设置列地址 SDRAM_CTRL->ADDR = get_sdram_column_addr(column); // 读取数据 *data = SDRAM_CTRL->DATA; } ``` 3. 端口复用:为了节省系统资源,SDRAM的地址和数据线可以与其他外设共用。下面是一个示例代码,演示了如何配置SDRAM端口和其他外设的复用: ```C void sdram_port_mux() { // 配置SDRAM地址线与其他外设复用 GPIOA->AFR[0] |= GPIO_AF_SDIO; // 配置SDRAM数据线与其他外设复用 GPIOB->AFR[1] |= GPIO_AF_SDIO; } ``` 总之,SDRAM是一种高速、大容量的内存技术,其使用需要进行初始化和配置,并且可以与其他外设进行端口复用。以上代码示例涵盖了SDRAM初始化、读写操作和端口复用的基本过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值