SDRAM 控制器设计学习(一)——乒乓操作

(仅作为个人学习记录,以备后时之需)

一、乒乓操作

(一)乒乓操作原理

        乒乓操作示意图如上图所示。

        外部输入数据流通过 输入数据流选择单元 将数据流输入到 数据缓存模块,比较常用的存储单元有双口 RAM,FIFO,SDRAM 等,这里我们选择 SDRAM。

        在 第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块 1”。

        写完之后进入 第二个缓冲周期,在第二个缓冲周期数据流通过“输入数据流选择单元”将 数据写入到“数据缓冲模块 2”,同时“输出数据流选择单元”将“数据缓冲模块 1”的数据流读出。

        读出数据缓冲模块1的数据后进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入数据缓存模块 1”,同时读出数据缓冲模块 2”的数据。

        如此 反复循环 地操作,即为 乒乓操作

(二)乒乓操作特点

        通过“输入数据流选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流输出数据流都是连续不断的没有任何停顿的,因此非常适合对数据流进行流水线式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。

        乒乓操作的第二个特点是可以节约缓存空间,使用 双存储单元 比 单存储单元 更节省存储空间,这是很明显的。同时在某些数据处理时,必须要数据达到一定个数才能进行运算,故还可以达到数据缓存的目的。

        乒乓操作还可以实现 低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

(三)实验验证

1. 模块图

clk_gen:时钟模块,使用 PLL 核来生成不同的读写时钟。

data_gen:数据模块,产生输入数据(为了展示乒乓操作,数据就由我们自己产生)。

ram_ctrl:输入输出数据流选择模块模块,对应原理中的 输入选择模块输出选择模块,我们将其整合为一个模块。

ram:数据缓冲模块,使用两个双口 RAM 来缓存数据。

(1)时钟生成模块

        调用 PLL 核来生成 RAM 的读写时钟,生成一个 50MHz 时钟(写时 钟),一个 25MHz 时钟(读时钟)。使用 50MHz 时钟输入数据,25MHz 时钟输出数据。

(2)数据缓冲模块

        调用 RAM IP 核作为存储单元来完成乒乓操作。由于我们是使用不同时钟进行数据输入输出,所以我们需要使用不同的 RAM 读写时钟,我们是使用 50MHz 时钟输入数据,25MHz 时钟输出数据,所以这里我们 需要设置 RAM 的 写入时钟为 50MHz读出时钟为 25MHz

(3)数据生成模块
功能

        循环生成数据 8’d0~8’d199。8’d0~8’d99 作为第一包数据写入第一个缓冲模块,8’d100~8’d199 作为第二包数据写入第二个缓冲模块,依次循环写入。数据流需要满足 RAM 中设置的深度及位宽即可。

模块图

时序图

        复位之后我们就开始拉高使能信号(data_en),让其开始传输数据。所以我们只需在使能为高时让数据像计数器一样一直加即可,加到 199 时让其归 0 从头开始相加,这样就能产生循环的数据了。

(4)输入输出数据选择模块
状态机

IDLE:

        初始状态,在不工作或复位时就让状态机置为初始状态。

WRAM1:

        写RAM1状态。开始往 RAM1 中写入数据,此时由于 RAM2 中 并没有写入数据,所以我们不用对 RAM2 进行读取。也就是原理中的 第一个缓冲周期

        跳转条件:从前面的数据生成模块中我们可知,当输入数据使能为高(data_en)时,数据有效开始传输,输入数据使能为高时,在该状态下将第一个数据包(8’d0~8’d99)写入 RAM1 之中。

        

WRAM2_RRAM1:

        写 RAM2 读 RAM1 状态。第 2 包数据写入到 RAM2 中,同时读出 RAM1 中的写入的第一包数据。

        跳转条件:

        1. 当 第一包数据写入完毕(ram1_wr_addr == 8'd99) 之后,马上跳到该状态。

        2. 当 RAM1 写入完毕,RAM2读出完毕后(ram1_wr_addr == 8'd99),跳转到该状态。(进入循环后)

        综上知,WRAM2_RRAM1 进入条件为 ram1_wr_addr == 8'd99            

WRAM1_RRAM2:

        写 RAM1 读 RAM2 状态。向 RAM1 中写入 第三包数据,此时第三包数据会把第一包数据覆盖,而我们的第一包数据已经读取出来了, 并不会使数据丢失。同时读出 RAM2 中的第二包数据。

        跳转条件:

        1. 当第二包数据写完之后,我们的第一包数据应该也是刚好读完的,此时我们跳转到该状态。

        2. 当 RAM2 写入完毕,RAM1 读出完毕后,跳转到该状态。(进入循环后)

写 RAM 波形图

        当 data_en == 1‘b1 时,跳转到 WRAM1 状态,这里我们使用时钟的下降沿进行检测触发跳转。

        为什么使用下降沿触发跳转?

        使用时钟的上升沿产生使能、地址、数据时,写入 或 读取时 上升沿采到的就是数据变化的那一刻采到的信号可能就是 不稳定的状态,从而导致数据出错,所以这里我们使用时钟的下降沿去产生这些信号的话,上升沿就能采到数据的稳定状态了。

        向 ram 里写入数据,我们需要产生 写使能、写地址、写数据:

        定义 3 个 reg 表示 写使能、写地址、写数据。

ram1_wr_en

        ram1 写使能,初始值为 0。当状态机为写 RAM1 状态时,我们让 ram1 写使能为高,这里我们可以使用 组合逻辑赋值

ram1_wr_addr

        ram1 写地址,初使值为 0。当 ram1 写使能为高时让写地址开始相加, 一个时钟写一个数据,同样采用时钟的下降沿触发

        当 ram1 写使能为高时让写地址开始相加, 一个时钟写一个数据,同样采用时钟的下降沿触发。

        当地址加到 8’d99 时说明 100 个数据已经写完。写完之后地址归 0,状态机跳到下一状态。

ram1_wr_data

        ram1 写数据。ram1 和 ram2 中的 写数据 都是由 数据生成模块 传过来的,而上一模块数据是由时钟上升沿产生的,所以这里我们需先对传来的数据,使用下降沿先进行寄存,当写使能为 1 时,让写入的数据为寄存的数据即可。

        这里我们使用 组合逻辑赋值,这样使能、地址、数据在同一时钟沿下才能相互对应。

        当状态机跳转到 WRAM2_RRAM1 状态时,我们需要往 ram2 中写入数据的同时读取 ram1 中的数据,先确定 ram2 的写时序。        

        往 ram2 中写入数据时,使能、地址和数据的时序产生方法与 ram1 的使能、地址和数据的时序产生方法是一致的。

        ram2 写完之后状态机跳到 WRAM1_RRAM2 状态,在该状态我们需对 ram1 写,ram2 读,相关信号的时序与前面状态的产生方法一致。写完之后又跳回 WRAM2_RRAM1 状态,如此循环。

读 RAM 波形图      

        相关信号也使用时钟下降沿去进行产生,这样读数据时能采到稳定的读地址。读时钟是 25MHz 时钟,所以 读相关信号 我们也使用 该时钟(25M)去产生。

        状态机 是在 clk_50m 时钟的 下降沿 处变化的。

        在 WRAM2_RRAM1 状态 时我们需要读取 ram1 里的数据,我们就需要产生读使能和读地址。在该状态下我们让读使能 ram1_rd_en 为 1,此时我们不能用组合逻辑去进行产生读使能,而需要使用 clk_25m 时钟下降沿触发去产生,这样我们 读使能 才能与 读时钟 对应。

        ram1_rd_addr

        在 读 ram1 使能信号为高 时让其 一直加 即可。因为我们设置的 读取数据位宽是 16bit,是 输入数据位宽的两倍,即 读出的一个数据为写入的两个数据。当其地址加到 49 时,表明读出了 50 个 16bit 数据,这说明写入的 100 个 8bit 数据已读完。这个时候我们让地址归 0,等待下一次的读取。

        当状态为 WRAM1_RRAM2 时,需要读取 ram2 中的数据,使能和地址的产生方法与 ram1 一致。

        读使能信号和地址产生了之后,读时钟的上升沿采到使能和地址信号后就会读出数据,每个数据为 16bit,为两个写入数据。

        写入 ram1 的最后两个数据为十进制的 98、99,转换为 16 进制就是 62、63,所 以读取的最后一个数据为 16’h6362。同理 ram2 读取的第一个数据为 16’h6564,最后一个 数据为 16’hc7c6。

        data_out:乒乓操作输出的数据。当读 ram1 使能为高时,输出读 ram1 的值;读 ram2 使能为高时,输出读 ram2 的值。因为 读数据 是在 读时钟 上升出产生的,我们使用读时钟 下降沿 将读出的数据给 data_out 输出,这样就能无缝地把写入的数据全部输出。

        

二、总结与收获

1. 最终目的是使输出的数据连续,也就是 data_out 是不间断的数据流,检查仿真波形的时候首先看这个信号的波形,然后看各个模块的波形,我认为的顺序是:

        (1)检查时钟模块是否输出正确的时钟,比如 locked 信号是否能正常拉高以锁定

PLL 锁相环生成的时钟模块 locked 作用:
        高,表示生成的时钟稳定

        低,时钟不稳定 无法时钟

应该将 locked 信号与复位信号(rst_n)相与(&),只要有一个没有拉高就表示系统未稳定。

        (2)检查数据生成模块是否生成 连续的循环数据 。

        (3)检查 控制模块 的相关信号的波形是否按照预期显示。

我在编写的代码的时候就忘记了本次实验的 使能、地址、数据模块都要在下降沿触发,经检查,将所有模块的 posedge clk 换成 negedge clk 就可以;

还有,两个 ram_rd_data 应该在使能信号拉高以后再输出数据,但是我设置 ram 核的时候,portB 选项中的 Enable Port Type 选成了 Always ,这样ram核会一直输出当前地址的数据,未传输 ram1/2_rd_addr 时默认地址应该是0,所以我观察到的 ram_rd_data 在rd_en使能信号拉高前以及刚刚拉高的一个周期内会显示 16'h0100,这样不对,将 ram_rd_en 信号与enb 端连接,使能未拉高时 data 就无效,就不会输出数据,因此 ram_rd_data 不会提前显示 第0个数据。

 3. ram_rd_data 输出数据的时间是上升沿,因为 ram 核设置的是上升沿触发。由于 data_out 是下降沿触发,所以 data_out 的时序只落后 ram_rd_data 半个 clk_25m 的周期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值