【FPGA】FPGA sdram接口实现

一、sdram

同步动态随机存取内存(synchronous dynamic random-access memory,简称SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。
在这里插入图片描述

二、看sdram手册找关键

1.描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f2FfxqQT-1645064392712)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220107094530632.png)]

有4个bank,1个bank是8192行512列,1列是1个存储单元,1个存储单元是16bit,也就是1个sdram的数据总量是4 * 8192 * 512 * 16bit

需要在64ms内刷新8192行,这里我们设置接口的时候就行刷新,在7.8us内刷新1行

突发长度:

1,2,4,8或者全页突发(512)个数据

2.接口信号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eLHry3cX-1645064392712)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220107154036573.png)]

这是sdram需要的接口(就是我们sdram接口给sdram要输出的)

信号类型位宽说明
clkinput100MHZ时钟
ckeinput时钟使能
cs_ninput片选信号
bankinput2bank地址
addressinput13行地址
ras_ninputras
cas_ninputcas
we_ninputwe
dqminput2数据掩码
dqinout16数据

3.关键的时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wwnmzWcf-1645064392713)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220107154229742.png)]

这里有几个参数需要注意一下

名称时间说明
tRRC>60ns(6个周期) 一般取7个周期自动刷新需要等待的时间
tRP>18ns(2个周期) 一般取3个周期预充电需要等待的时间
tRCD>18ns(2个周期) 一般取3个周期行激活需要等待的时间
TMRD>2个周期 一般取3个周期模式寄存器设置需要等待的时间
tDPL>2个周期 就取2个周期数据写入到预充电等待延时的时间

这里的时钟是100MHZ,所以我们对参数的设置要根据100MHZ的

4.模式寄存器设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gAQC5jq7-1645064392713)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220107155334276.png)]

在模式寄存器设置状态的时候 是2个bank地址+13个行地址

突发长度BURST LENGTH看行地址A3A2A1A0

A2A1A0A3=0A3=1
3’b00011突发长度为1
3’b00122突发长度为2
3’b01044突发长度为4
3’b01188突发长度为8
3’b100XX
3’b101XX
3’b110XX
3’b111Full PageX突发长度为512(当A3=0)

写延时CAS LATENCY

A6A5A4CAS LATENCY
3’b0102延时2个周期
3’b0113延时3个周期

OP CODE

就常规的突发写突发读

A9
0突发读突发写
1突发读单字节写

5.各个状态不同的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1rNIewO-1645064392713)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220108095916860.png)]

各个指令CS,RAS,CAS,WEADDR[12:0] + A10 +BA[1:0]
Mode Register Set4’b0000BA(2’b00) + 3’b000,OP Code,2’b00,CAS Latency,BT,Burst Length
Bank Active4’b0011BA(2’b00) + Row Address(addr[21:9])
Read4’b0101BA(2’b00) + A10(1’b0) + Column Address(addr[8:0])(其余补齐0)
Write4’b0100BA(2’b00) + A10(1’b0) + Column Address(addr[8:0])(其余补齐0)
Precharge All Banks4’b0010A10(11’b100_0000_0000)
Auto Refresh4’b0001
No Operation4’b0111

6.手册里的状态机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RMl3HBXX-1645064392713)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220108105630436.png)]

这是手册里的状态图,我们只需要红色的状态,然后写自己的状态图

  • WAIT
  • PRECH
  • MOREST
  • IDLE
  • AUREF
  • ROWACT
  • READ
  • WRITE

7.写时序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59aL2eyu-1645248203202)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220219113412821.png)]

8.写到预充电时序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSELYQ3x-1645248203202)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220219113510393.png)]

写到预充电有一个tDPL的时间

9.读时序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5KsKRYAA-1645064392714)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220108110040455.png)]

这张图告诉我们在read的时候要延长2-3个周期才能读到数据,所以对于输出有效要延长2-3个周期

10.电源启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BERntuye-1645064392714)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220214165304791.png)]

上电之后要等待200us

11.初始化的刷新

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HV9XPJS2-1645064392714)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220216210907298.png)]

第一次初始化的时候刷新要刷新8次刷新等待时间 8*tREF

三、状态机设计

1.sdram接口状态机

这个状态机看上去很麻烦,确实,因为要初始化要刷新

  • 未初始化:PRECHARGE -->MODE REGISTER SET -->IDLE

  • 初始化完成刷新时间到或者刷新标志挂载 :IDLE–> AUTO REFRESH AUTO REFRESH --> IDLE

  • 初始化完成刷新时间到或者刷新标志挂载 : PRECHARGE --> AUTO REFRESH --> IDLE

  • 初始化完成未到刷新时间:PRECHARGE --> IDLE

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmCR9Oo0-1645064392715)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220214140752780.png)]

四、代码实现sdram接口

1.sdram_interface.v

module sdram_interface(
    input               clk,
    input               sclk,
    input               rst_n,
    //sdram接口和control
    input   [15:0]      din,// 从wrfifo来的数据
    output  [15:0]      dout,// 给rdfifo的数据
    output              dout_vld,
    input   [23:0]      brc_address,// bank row col 地址 2+13+9
    input               rd_req,// 读请求
    input               wr_req,// 写请求
    output              busy,// 接口busy
    output              ack,// 接口回应
    //sdram接口和sdram
    output              sdram_clk,
    output              sdram_cke,
    output              sdram_cs_n,
    output  [1:0]       sdram_bank,
    output  [12:0]      sdram_address,
    output              sdram_ras_n,
    output              sdram_cas_n,
    output              sdram_we_n,
    output  [1:0]       sdram_dqm,
    //单总线dq三态门
    input   [15:0]      sdram_dq_in,
    output  [15:0]      sdram_dq_out,
    output              sdram_dq_out_en
);


parameter   MODE_BURST  = 3'b011,// A2A1A0
            BURST_TYPE  = 1'b0,// A3
            CAS_LATENCY = 3'b010,// 模式寄存器延时
            OP_CODE     = 1'b0;// 突发读和突发写

parameter   BURST_LENGTH = 8;// 突发长度

// cs ras cas we
parameter   MRS_CMD     = 4'b0000,// 模式寄存器选择命令
            BA_CMD      = 4'b0011,// bank激活命令
            READ_CMD    = 4'b0101,// 读命令
            WRITE_CMD   = 4'b0100,// 写命令
            PAB_CMD     = 4'b0010,// 预充电命令
            AF_CMD      = 4'b0001,// 自刷新命令
            NOOP_CMD    = 4'b0111;// 没有操作的命令

// 时间都是按照周期来算的
// sdram是100MHZ 1个周期是10ns
parameter   POWAIT_TIME     = 20000 ,// 开启电源等待时间200us=200000ns
            TRRC            = 7     ,// 刷新等待时间>60ns 70ns
            TRP             = 3     ,// 预充电等待时间>18ns 30ns
            TRCD            = 3     ,// 行激活等待时间>18ns 30ns
            TMRD            = 3     ,// 寄存器模式设置等待时间>2clk 3clk 30ns
            TDPL            = 2     ,// 数据写入到预充电等待延时的时间>2clk 2clk
            AUFRESH_TIME    = 780   ,// 每7.8us自动刷新
            CAS_TIME        = 2     ;// 读延时时间2clk


// 状态机
localparam  POWAIT  = 8'b0000_0001,// 上电等待状态
            PRECH   = 8'b0000_0010,// 预充电状态
            MDREST  = 8'b0000_0100,// 模式寄存器设置状态
            IDLE    = 8'b0000_1000,// 初始状态
            AUREFR  = 8'b0001_0000,// 自刷新状态
            ROWACT  = 8'b0010_0000,// 行激活状态
            READ    = 8'b0100_0000,// 读状态
            WRITE   = 8'b1000_0000;// 写状态

// 状态机
reg     [7:0]       state_c;
reg     [7:0]       state_n;

wire                powait2prech ;
wire                prech2idle   ;
wire                prech2aurefr ;
wire                mdrest2idle  ;
wire                idle2aurefr  ;
wire                idle2rowact  ;
wire                aurefr2idle  ;
wire                aurefr2mdrest;
wire                rowact2write ;
wire                rowact2read  ;
wire                read2prech   ;
wire                write2prech  ;

// 不同状态不同的计数器
reg     [15:0]      cnt;
wire                add_cnt;
wire                end_cnt;

reg     [15:0]      X;


// 自动刷新计数器
reg     [9:0]       cnt_ref;
wire                add_cnt_ref;
wire                end_cnt_ref;

// 初始化标志 0代表初始化未完成 1代表初始化完成
reg                 init_flag;

// 自动刷新标志 0代表刷新时间未到 1代表要刷新
reg                 refresh_flag;

// 读写标志
reg                 rd_flag;
reg                 wr_flag;

// command = cs+ras+cas+we
reg     [3:0]       command;

// address = bank + 13位a
reg     [14:0]      address;

// dq_mask数据掩码
reg     [1:0]       dq_mask;

// 三态门
reg     [15:0]      dq_out;
reg                 dq_out_en;
reg     [15:0]      dq_in;
// 打两拍的dout_vld
reg                 dout_vld_r0;
reg                 dout_vld_r1;
reg                 dout_vld_r2;

// 接口响应
reg                 interface_ack;

// 状态机
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        state_c <= POWAIT; 
    end 
    else begin 
        state_c <= state_n;
    end 
end
    
always @(*)begin 
    case (state_c)
        POWAIT      :begin 
                    if(powait2prech)begin
                        state_n = PRECH;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        PRECH      :begin 
                    if(prech2idle)begin
                        state_n = IDLE;
                    end
                    else if(prech2aurefr)begin
                        state_n = AUREFR;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        MDREST      :begin 
                    if(mdrest2idle)begin
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        IDLE      :begin 
                    if(idle2rowact)begin
                        state_n = ROWACT;
                    end
                    else if(idle2aurefr)begin
                        state_n = AUREFR;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        AUREFR      :begin 
                    if(aurefr2idle)begin
                        state_n = IDLE;
                    end
                    else if(aurefr2mdrest)begin
                        state_n = MDREST;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        ROWACT      :begin 
                    if(rowact2write)begin
                        state_n = WRITE;
                    end
                    else if(rowact2read)begin
                        state_n = READ;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        READ      :begin 
                    if(read2prech)begin
                        state_n = PRECH;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        WRITE      :begin 
                    if(write2prech)begin
                        state_n = PRECH;
                    end
                    else begin
                        state_n = state_c;
                    end 
                   end
        default: state_n = IDLE;
    endcase
end
    
assign powait2prech     = state_c == POWAIT && (end_cnt);// 上电等待200us结束
assign prech2idle       = state_c == PRECH  && (init_flag) && (end_cnt);// 初始化完成并且预充电等待30ns结束
assign prech2aurefr     = state_c == PRECH  && (~init_flag) && (end_cnt);// 初始化未完成并且预充电等待30ns结束
assign mdrest2idle      = state_c == MDREST && (end_cnt);// 模式寄存器设置等待30ns结束
assign idle2aurefr      = state_c == IDLE   && ((refresh_flag) || (end_cnt_ref));// 刷新标志挂起或者刷新时间到
assign idle2rowact      = state_c == IDLE   && (rd_flag || wr_flag);// 读写标志
assign aurefr2idle      = state_c == AUREFR && (init_flag) && (end_cnt);// 初始化完成并且自动刷新等待70ns
assign aurefr2mdrest    = state_c == AUREFR && (~init_flag) && (end_cnt);// 初始化未完成并且自动刷新等待70ns
assign rowact2write     = state_c == ROWACT && (end_cnt) && (wr_flag);// 写标志并且行激活等待30ns
assign rowact2read      = state_c == ROWACT && (end_cnt) && (rd_flag);// 读标志并且行激活等待30ns
assign read2prech       = state_c == READ   && (end_cnt);// 读突发长度 + 读延迟结束 
assign write2prech      = state_c == WRITE  && (end_cnt);// 写突发长度 + 写延迟结束

// 不同状态不同的计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin 
            if(end_cnt)begin 
                cnt <= 0;
            end
            else begin 
                cnt <= cnt + 1;
            end 
    end
   else  begin
       cnt <= cnt;
    end
end 

assign add_cnt = (state_c != IDLE);
assign end_cnt = add_cnt && cnt == X - 1;

always @(*)begin 
   if(state_c == POWAIT)begin
        X = POWAIT_TIME;
   end
   else if(state_c == PRECH)begin
        X = TRP;
   end
   else if(state_c == MDREST)begin
        X = TMRD;
   end
   else if((state_c == AUREFR) && (~init_flag))begin
        X = 8*TRRC;
   end
   else if((state_c == AUREFR) && (init_flag))begin
        X = TRRC;
   end
   else if(state_c == ROWACT)begin
        X = TRCD;
   end
   else if(state_c == WRITE)begin
        X = BURST_LENGTH + TDPL; // 突发长度 + 写到预充电延迟
   end
   else if(state_c == READ)begin
        X = BURST_LENGTH + CAS_TIME;// 突发长度 + 读延迟
   end
   else begin
        X = 0;
   end
end



// 自动刷新计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_ref <= 0;
    end 
    else if(add_cnt_ref)begin 
            if(end_cnt_ref)begin 
                cnt_ref <= 0;
            end
            else begin 
                cnt_ref <= cnt_ref + 1;
            end 
    end
   else  begin
       cnt_ref <= cnt_ref;
    end
end 

assign add_cnt_ref = init_flag;
assign end_cnt_ref = add_cnt_ref && cnt_ref == AUFRESH_TIME - 1;

// 初始化标志init_flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        init_flag <= 0;
    end 
    else if(mdrest2idle)begin 
        init_flag <= 1'b1;
    end 
end

// 自动刷新标志refresh_flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        refresh_flag <= 0;
    end 
    else if((~refresh_flag) && (end_cnt_ref))begin // 计时结束自动刷新标志拉高
        refresh_flag <= 1'b1;
    end 
    else if((refresh_flag) && (idle2aurefr))begin 
        refresh_flag <= 1'b0;
    end 
end

// 读写标志
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        wr_flag <= 0;
    end 
    else if(wr_req)begin 
        wr_flag <= 1'b1;
    end 
    else if(write2prech)begin 
        wr_flag <= 1'b0;
    end 
end


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

// command = cs+ras+cas+we
// 各个状态的cs ras cas we
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        command <= 0;
    end 
    else if(aurefr2mdrest)begin 
        command <= MRS_CMD;
    end 
    else if(idle2rowact)begin 
        command <= BA_CMD;
    end 
    else if(rowact2read)begin
        command <= READ_CMD;
    end
    else if(rowact2write)begin
        command <= WRITE_CMD;
    end
    else if(powait2prech || read2prech || write2prech)begin
        command <= PAB_CMD;
    end
    else if(prech2aurefr || idle2aurefr)begin
        command <= AF_CMD;
    end
    else begin
        command <= NOOP_CMD;
    end
end

// address
// MSB高字节
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        address <= 0;
    end 
    else if(aurefr2mdrest)begin 
        address <= {2'b00,3'b000,OP_CODE,2'b00,CAS_LATENCY,BURST_TYPE,MODE_BURST};
    end 
    else if(idle2rowact)begin 
        address <= {brc_address[23:22],brc_address[21:9]};
    end 
    else if(rowact2read)begin
        address <= {brc_address[23:22],4'b0000,brc_address[8:0]};
    end
    else if(rowact2write)begin
        address <= {brc_address[23:22],4'b0000,brc_address[8:0]};
    end
    else if(powait2prech || read2prech || write2prech)begin
        address <= 1'b1 << 10;
    end
end

// 数据掩码dqm
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dq_mask <= 2'b00;
    end 
    else if((state_c == WRITE) && (cnt == BURST_LENGTH - 1))begin// dq_mask在写数据的时候最后需要延长2个周期,所以最后两个周期不写数据
        dq_mask <= 2'b11;
    end 
    else if(write2prech)begin 
        dq_mask <= 2'b00;
    end 
end

// 三态门
// dq_out_en
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dq_out_en <= 0;
    end 
    else if(rowact2write)begin// 开始写就输出使能拉高
        dq_out_en <= 1'b1;
    end 
    else if((state_c == WRITE) && (cnt >= BURST_LENGTH - 1))begin// 写完数据后输出使能拉低
        dq_out_en <= 1'b0;
    end 
end

// dq_in
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dq_in <= 0;
    end 
    else begin 
        dq_in <= sdram_dq_in;
    end 
end

// dout_vld输出数据有效
// 因为read延长2个周期 打两拍
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dout_vld_r0 <= 0;
        dout_vld_r1 <= 0;
        dout_vld_r2 <= 0;
    end 
    else begin 
        dout_vld_r0 <= (state_c == READ);
        dout_vld_r1 <= dout_vld_r0;
        dout_vld_r2 <= dout_vld_r1;
    end 
end

//interface_ack
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            interface_ack <= 0;
        end 
        else if(rowact2read | rowact2write)begin 
            interface_ack <= 1'b1;
        end 
        else if((state_c == WRITE && cnt == BURST_LENGTH-1) || read2prech)begin 
            interface_ack <= 1'b0;
        end 
    end

// sdram串行时钟
assign sdram_clk = sclk;

// 时钟使能一直拉高
assign sdram_cke = 1'b1;

// cs ras cas we = command
assign {sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n} = command;
// assign sdram_cs_n   = command[3];
// assign sdram_ras_n  = command[2];
// assign sdram_cas_n  = command[1];
// assign sdram_we_n   = command[0];

// bank地址
assign sdram_bank   = address[14:13];

// 给sdram的地址数据
assign sdram_address = address[12:0];

// 给sdram的数据掩码
assign sdram_dqm    = dq_mask;

// 三态门
assign sdram_dq_out = din;
assign sdram_dq_out_en = dq_out_en;

// 从sdram读到的数据
assign dout = dq_in;

assign dout_vld = dout_vld_r2;

// sdram接口busy
assign busy = (state_c != IDLE) ? 1:0;

// sdram接口相应ack
assign ack = interface_ack;

endmodule

五、仿真验证

这里使用了一个sdram的仿真模型

  • 初始化
    在这里插入图片描述
  • 数据输入输出
    在这里插入图片描述

六、quartus调用ip核实现sdram接口

打开platform designer
搜索sdram
在这里插入图片描述
根据上面的手册的参数去配置一下
在这里插入图片描述
在这里插入图片描述
创建完毕后连线,命名
user_port 用户接口
mem_port sdram接口
在这里插入图片描述

最后将生成的.qip文件导入到工程中
在这里插入图片描述
看看ip组件有无sdram_interface,有说明导入成功了
在这里插入图片描述
最后ip生成的sdram接口
使用的时候例化即可
在这里插入图片描述

七、总结

以上两种sdram接口的实现方式

  1. 自己手写一个sdram的接口,也就500行左右,虽然麻烦了点,但是自己懂得sdram的原理,自己日后使用接口的时候懂得如何去修改
  2. quartus调用一个sdram接口的ip核,直接一步到位,省去了很多的麻烦,但是日后调用的使用,例如以后要做的项目便是ov5640摄像头的时候就会出现很多问题,因为里面的原码也看不懂,所以难以理解里面的原理

我的sdram接口连续写了好几天,我真的很菜,只能跟着老师的代码修修改改,最后实现了,大致的原理也明白了。
下一步是sdram的读写控制,把sdram项目写完

  • 22
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值