FPGA驱动SDRAM

该博客详细介绍了如何实现FPGA驱动SDRAM进行数据的读写操作。实验任务包括从PC向FPGA发送数据并保存到SDRAM,然后通过按键读取数据回传给PC。文章详细阐述了SDRAM的工作原理,包括其内部结构、突发长度、刷新机制以及读写命令的时序。博主使用SDRAM接口IP核简化设计,并设计了一个控制模块来管理读写操作,实现了读写仲裁以避免冲突。最后,博主通过PC串口与FPGA交互验证了设计的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.实验任务

实现PC向FPGA发送数据保存进SDRAM,再通过按键控制读出SDRAM中的数据发送给PC端,实现数据回环。

二.SDRAM介绍

同步动态随机存取内存(synchronous dynamic random-access memory,简称SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。
本次实验使用的SDRAM芯片(HY57V561620FTP)内部被分成了4个bank,每个bank有64Mbit大小,SDRAM与输入时钟同步,时钟信号上升沿采样,行列地址线复用,数据线输入输出复用。读写访问是突发的,突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称BL)。本次SDRAM芯片对应的突发长度有1、2、4、8、全页,自动预充电在突发访问结束自动进行预充电。访问从激活命令开始,紧接着是读写命令。激活命令选择激活的bank和行,而读写命令选择激活的bank和列。
SDRAM的数据存储是利用电容实现的,因为电容存在放电的特性,所以需要定时刷新芯片才可以保证数据不丢失。查看芯片手册,可以看见刷新周期为64ms,也就是说在64ms内要保证每一块内存都要刷新一次,内存共8192行,平均下来,只要每7.8us刷新一行,就能保证64ms内刷新所有内存。

SDRAM原理框图:
在这里插入图片描述
由上图可以看见行列地址线被复用了,SDRAM接收到地址,会将数据传递给相应的地址预译码器和地址计数器,行地址预译码器会激活bank的行,列地址预译码器会激活bank的列,同时模式寄存器设置的突发长度会联合列地址计数器共同决定当前操作的列地址。外部状态机给出时钟信号,时钟使能信号,片选信号,行地址,列地址,数据允许信号,数据屏蔽信号,输入数据;SDRAM输出数据。
每块bank大小为4M*16bit,如果想要扩展内存,有两种方法:

①扩展深度,将两块bank的地址线并联,片选信号并联,将数据线串联能够实现8M16bit。
②扩展宽度,将两块bank的地址线并联,数据线并联,通过一个2输入译码器选择片选信号就能实现4M
32bit。
如果既想扩展深度,又想扩展宽度,可以将上述两个方法结合起来。

注意:时钟和其他输入信号被异步重新启用,需要等待200us,同时FPGA和SDRAM的时钟存在时钟偏移,这是为了SDRAM能够采到稳定的数据,在芯片手册中有说到,具体偏移角度须自己调试。
SDRAM内部存在一个有限状态机,其状态跳转如下:
在这里插入图片描述
本次实验实现的是蓝色箭头表示的过程。
在读写命令之前,必须先激活相应的bank和行地址,激活命令和读写命令之间需要一个tRCD的间隔,READ突发读取,BA0和BA10用于选择bank,A10的值决定是否使用自动预充电,自动预充电用于突发结束时自动预充电,不选择自动预充电,则行保持打开供后续访问READ命令发出后,经过列激活延迟才可以得到数据。写命令用于启动对活动行的突发写访问。BA0和BA1的值用于选择数据开始传输的bank地址和列地址。A10的值决定了是否使用自动预充电。如果选择了自动预充电,则正在访问的行将在写突发结束时进行预充电;如果未选择自动预充电,则该行将保持打开状态以供后续访问。
SDRAM控制操作比较复杂,所以我们使用了SDRAM的接口IP,这大大减少了我们的工作量,在调用IP的基础上,我们只需要设计一个控制模块就能操作SDRAM进行读写操作了。

三.设计思路

程序框图:
在这里插入图片描述

四.代码实现

调用IP核(这里IP核的调用不同于之前的PLL,RAM等):
在这里插入图片描述
在这里插入图片描述
设置配置文件参数:
在这里插入图片描述
添加相关延时:
在这里插入图片描述

设置时钟为100MHZ:
在这里插入图片描述
完成相关连线和端口命名:
在这里插入图片描述
在这里插入图片描述
顶层模块设计:

/**********************************************************
// Copyright 2022.05-2025.05
// Contact with xxxxxxxxx@qq.com
================ xxx.v ======================
>> Author       : lj
>> Date         : 20XX/XX/XX
>> Description  : 
>> note         : 
>>              : 
>> V180121      : 
************************************************************/
module sdram_top(
    input                   clk       ,//时钟信号 50MHZ
    input                   rst_n     ,//复位信号
    input                   rx        ,//串口接收数据

    input                   key_in    ,//按键输入 作为读请求信号

    output    [12:0]        mem_addr  , 
    output    [1 :0]        mem_bank  , 
    output                  mem_cas_n , 
    output                  mem_cke   , 
    output                  mem_cs_n  , 
    inout     [15:0]        mem_dq    , 
    output    [1 :0]        mem_dqm   , 
    output                  mem_ras_n , 
    output                  mem_we_n  ,
    output                  sdram_clk ,     
  
    output                  tx         //串口发送数据
);

//参数定义

//信号定义
    wire            key_done        ;
    wire            clk_100m        ;
    wire            clk_100m_s      ;

    wire    [7:0]   din             ;
    wire            din_vld         ;

    wire            ready           ;
    wire    [7:0]   dout            ;
    wire            dout_vld        ;

    wire            locked          ;


//模块例化
    //按键消抖模块
    key u_key(
        /*input                */.clk       (clk     ),//时钟信号
        /*input                */.rst_n     (rst_n   ),//复位信号
        /*input        [1:0]   */.key_in    (key_in  ),//按键输入信号
        /*output  reg  [1:0]   */.key_done  (key_done) //输出信号       
    );

    //锁相环
    pll	pll_inst (
	    .areset ( ~rst_n     ),
	    .inclk0 ( clk        ),
	    .c0     ( clk_100m   ),
	    .c1     ( clk_100m_s ),//100MHZ时钟,带有相位偏移
        .locked (locked      )
	);

    //串口接收模块
    uart_rx u_uart_rx(                  //将接收的串行数据转换成并行数据
        .clk        (clk        ),
        .rst_n      (rst_n      ),
        .rx         (rx         ),//串行数据
        .set_bps    (0          ),

        .dout       (din        ),//并行数据
        .dout_vld   (din_vld    )
    );

    //sdram控制
    sdram_controller u_sdram_controller(
        /*input                   */.clk        (clk_100m        ),//时钟信号 100MHZ
        /*input                   */.rst_n      (rst_n           ),//复位信号
        /*input                   */.clk_in     (clk             ),//50MHZ 时钟
        /*input                   */.clk_out    (clk             ),//50MHZ 时钟

        /*input                   */.ready      (ready           ),//串口发送模块准备好

        /*input       [7:0]       */.din        (din             ),//输入数据 来自串口接收模块
        /*input                   */.din_vld    (din_vld         ),//输入数据有效

        /*input                   */.rd_req     (key_done        ),//读请求信号 来自按键
        /*output      [7:0]       */.dout       (dout            ),//输出数据 发送给串口发送模块
        /*output                  */.dout_vld   (dout_vld        ),//输出数据有效 

        /*output      [12:0]      */.mem_addr   (mem_addr        ),
        /*output      [1 :0]      */.mem_bank   (mem_bank        ),
        /*output                  */.mem_cas_n  (mem_cas_n       ),
        /*output                  */.mem_cke    (mem_cke         ),
        /*output                  */.mem_cs_n   (mem_cs_n        ),
        /*inout       [15:0]      */.mem_dq     (mem_dq          ),
        /*output      [1 :0]      */.mem_dqm    (mem_dqm         ),
        /*output                  */.mem_ras_n  (mem_ras_n       ),
        /*output                  */.mem_we_n   (mem_we_n        ) 
    );

    //串口发送模块
    uart_tx u_uart_tx(                  //将接收的并行数据转换成串行数据传输
        .clk        (clk        ),
        .rst_n      (rst_n      ),
        .din        (dout       ),//并行数据
        .din_vld    (dout_vld   ),
        .set_bps    (0          ),

        .tx         (tx         ), //串行数据
        .ready      (ready      )
    );

    assign sdram_clk = clk_100m_s;
endmodule

控制模块调用SDRAM接口IP核:

/**********************************************************
// Copyright 2022.05-2025.05
// Contact with xxxxxxxxx@qq.com
================ xxx.v ======================
>> Author       : lj
>> Date         : 20XX/XX/XX
>> Description  : 
>> note         : 
>>              : 
>> V180121      : 
************************************************************/
module sdram_controller(
    input                   clk      ,//时钟信号 100MHZ
    input                   rst_n    ,//复位信号
    input                   clk_in   ,//50MHZ 时钟
    input                   clk_out  ,//50MHZ 时钟
    input                   ready    ,//串口发送模块准备好
    input       [7:0]       din      ,//输入数据 来自串口接收模块
    input                   din_vld  ,//输入数据有效
    input                   rd_req   ,//读请求信号 来自按键
    output      [7:0]       dout     ,//输出数据 发送给串口发送模块
    output                  dout_vld ,//输出数据有效
    output      [12:0]      mem_addr ,
    output      [1 :0]      mem_bank ,
    output                  mem_cas_n,
    output                  mem_cke  ,
    output                  mem_cs_n ,
    inout       [15:0]      mem_dq   ,
    output      [1 :0]      mem_dqm  ,
    output                  mem_ras_n,
    output                  mem_we_n  
);
//参数定义

//信号定义               
    wire     [23:0]     avs_port_address      ;
    wire                avs_port_write_n      ;
    wire     [15:0]     avs_port_writedata    ;
    wire                avs_port_read_n       ;
    wire     [15:0]     avs_port_readdata     ;
    wire                avs_port_readdatavalid;
    wire                avs_port_waitrequest  ;

//模块例化
    sdram_ctrl u_sdram_ctrl(
        /*input                   */.clk             (clk                   ),//时钟信号 100MHZ
        /*input                   */.rst_n           (rst_n                 ),//复位信号
        /*input                   */.clk_in          (clk_in                ),//50MHZ
        /*input                   */.clk_out         (clk_out               ),//50MHZ
        
        /*input      [7:0]        */.din             (din                   ),//输入数据 来自串口接收模块
        /*input                   */.din_vld         (din_vld               ),//输入数据有效

        /*output     [7:0]        */.dout            (dout                  ),//输出数据 发给串口发送模块
        /*output                  */.dout_vld        (dout_vld              ),//输出数据有效

        /*input                   */.ready           (ready                 ),//串口发送模块准备好
        /*input                   */.rd_req          (rd_req                ),//读请求信号 来自按键

        /*output     [23:0]       */.avm_addr        (avs_port_address      ),//地址
        /*output                  */.avm_wr_n        (avs_port_write_n      ),//写请求 低有效
        /*output     [15:0]       */.avm_wr_data     (avs_port_writedata    ),//写数据
        /*output                  */.avm_rd_n        (avs_port_read_n       ),//读请求 低有效
        /*input      [15:0]       */.avm_rd_data     (avs_port_readdata     ),//读出的数据
        /*input                   */.avm_rd_data_vld (avs_port_readdatavalid),//读出数据有效
        /*input                   */.avm_waitrequest (avs_port_waitrequest  ) //从机等待请求信号          
    );

	sdram_interface u0 (
		.clk_clk                (clk                   ),//      clk.clk
		.reset_reset_n          (rst_n                 ),//    reset.reset_n
		.mem_port_addr          (mem_addr              ),// mem_port.addr
		.mem_port_ba            (mem_bank              ),//         .ba
		.mem_port_cas_n         (mem_cas_n             ),//         .cas_n
		.mem_port_cke           (mem_cke               ),//         .cke
		.mem_port_cs_n          (mem_cs_n              ),//         .cs_n
		.mem_port_dq            (mem_dq                ),//         .dq
		.mem_port_dqm           (mem_dqm               ),//         .dqm
		.mem_port_ras_n         (mem_ras_n             ),//         .ras_n
		.mem_port_we_n          (mem_we_n              ),//         .we_n

		.avs_port_address       (avs_port_address      ),// avs_port.address
		.avs_port_byteenable_n  (2'b00                 ),//         .byteenable_n
		.avs_port_chipselect    (1'b1                  ),//         .chipselect
		.avs_port_writedata     (avs_port_writedata    ),//         .writedata
		.avs_port_read_n        (avs_port_read_n       ),//         .read_n
		.avs_port_write_n       (avs_port_write_n      ),//         .write_n
		.avs_port_readdata      (avs_port_readdata     ),//         .readdata
		.avs_port_readdatavalid (avs_port_readdatavalid),//         .readdatavalid
		.avs_port_waitrequest   (avs_port_waitrequest  ) //         .waitrequest
	);
endmodule

控制模块具体状态设计:
在这里插入图片描述
由于读写操作不能同时进行,为了防止一直读或一直写的情况,实现读写仲裁让读写操作交替进行。
具体实现如下:

//读写优先级仲裁
    //wr_flag
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            wr_flag <= 1'b0;
        end
        else if(wrfifo_usedw > BURST_LEN)begin
            wr_flag <= 1'b1;
        end
        else begin
            wr_flag <= 1'b0;
        end
    end

    //rd_flag
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rd_flag <= 1'b0;
        end
        else if(~rd_flag && rd_req)begin
            rd_flag <= 1'b1;
        end
        else begin
            rd_flag <= 1'b0;
        end
    end

    //flag_sel 0:上一次写 1:上一次读
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            flag_sel <= 1'b0;
        end
        else if(write2idle)begin
            flag_sel <= 1'b0;
        end
        else if(read2idle) begin
            flag_sel <= 1'b1;
        end
    end

    //prior_flag 0:写 1:读
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            prior_flag <= 1'b0;
        end
        else if(wr_flag && (flag_sel || ~rd_flag))begin
            prior_flag <= 1'b0;
        end
        else if(rd_flag && (~flag_sel || ~wr_flag)) begin
            prior_flag <= 1'b1;
        end
    end

使用SDRAM接口IP注意地址拼接:

{bank[1],addr[21:9],bank[0],addr[8:0]}

其余代码:略

五.测试

在这里插入图片描述
PC端利用串口调试助手向FPGA发送数据保存到SDRAM芯片中,FPFA开发板按下按键从SDRAM芯片中读出数据发送给PC端。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值