Menu Close

SPI EEPROM 芯片Verilog模型仿真

在SPI接口的EEPROM的原理与使用中给大家介绍了怎样学习使用一颗SPI接口的EEPROM芯片,包括SPI接口EEPROM芯片的广泛特点,一般通用的操作命令,操作时序图等。在学习使用SPI协议控制EEPORM芯片,调试SPI controller控制程序时,下载程序到FPGA开发板上进行调试是一种常用的方法。在调试时,可以使用示波器, 或者Quartus, Vivado 的工具自带的调试工具(ILA,SignalTap)。

本文介绍一种方法,直接使用仿真工具验证SPI controller, 如果仿真正确,可以再进行编译,在线调试,这样会大大缩减开发周期。在对SPI EEPROM controller 进行仿真时, 在没有SPI EEPROM (slave device)的情况下,也就没有设备应答,因此难以验证controller 是否正确。 解决方法是绝大多数的SPI EEPROM厂家都会提供芯片Verilog模型的源码,开发者可以使用芯片Verilog模型来仿真验证controller。这样controller给仿真模块的芯片Verilog模型发送读写指令,芯片Verilog模型会进行应答。本文将介绍如何使用SPI EEPROM 芯片Verilog模型仿真,以及分析仿真结果的波形。

1. 选择芯片Verilog模型

之前学习了如何操作和使用SPI接口EEPROM芯片Microchip的AT25040B。本文为了验证SPI controller,首选的仿真模型应该是AT25040B。但是Microchip没有提供对应芯片的仿真Verilog模型。因此本文选择了相似的芯片M25AA040A/25LC040A模型进行仿真(SPI接口,容量为4 kbit的EEPROM芯片)。查找相应的仿真芯片模型如图1,图2所示。

图1

图2 芯片Verilog模型

下载解压后得到两个Verilog文件,如图3所示。

图3

文件下载:

[content_control]

25xx040A_Verilog_Model.zip

[/content_control]

 

2. 建立仿真工程

将两个Verilog都加进Vivado工程的simulation source,并建立一个仿真的testbench(spi_eeprom_tb.v),如图4所示。

图4

在spi_eeprom_tb.v中设计了如下基本操作:

  • 写使能锁存
  • 读状态寄存器
  • 写数据
  • 读状态机寄存器 + 读数据

这里需要注意的是,在controller进行读操作,即 EEPROM进行写操作时,controller仍然需要提供稳定的时钟,片选也需要稳定为低电平。举例读状态寄存器时序图,如图5所示:

图5 读状态寄存器

 

完整的spi_eeprom_tb.v代码如下所示,其中repeat的用法可以在文章Verilog repeat 循环语句中复习:

`timescale 1ns / 1ps



module spi_eeprom_tb(

    );
localparam  CLK_PERIOD = 140;    
    
reg  SI = 0;        // serial data input
wire SO;            // serial data output
reg  SCK = 0;       // serial data clock
reg  CS_N = 1;      // chip select - active low

reg  WP_N = 1;      // write protect pin - active low
reg  HOLD_N = 1;    // interface suspend - active low

reg  reset = 0;     // model reset/power-on reset

//  -------- Command -----------
reg [7:0] RDSR_CMD = 8'h05;
reg [7:0] WREN_CMD = 8'h06;

reg [7:0] WRITE_CMD = 8'h02;
reg [7:0] WRITE_ADDR = 8'h80;
reg [7:0] WRITE_DATA = 8'h00;

reg [7:0] READ_CMD = 8'h03;
reg [7:0] READ_ADDR = 8'h80;
// ------------------------------

reg [7:0] DATA = 0;
reg valid = 0;

integer i = 0;
initial
begin
    reset = 1;
    SI = 0;
    SCK = 0;
    CS_N = 1;
    #200;
    reset = 0;
    #800;
    
//----------------- WRITE WR_EN ----------------
    #300;
    
    i = 0;
    
    SI = 0;
    SCK = 0;
    CS_N = 0;
    #CLK_PERIOD;

    repeat(8)
    begin
        SI = WREN_CMD[7-i];
        SCK = 0;
        #(CLK_PERIOD/2);
        
        SCK = 1;
        i = i + 1;
        #(CLK_PERIOD/2);
    end
    SI = 0;
    SCK = 0;
    CS_N = 0;
    #CLK_PERIOD;
    CS_N = 1;

//---------- READ SR REGISTER   -------------- 
    #300;
    i = 0;
    
    SI = 0;
    SCK = 0;
    CS_N = 0;
    #CLK_PERIOD;

    repeat(8)
    begin
        SI = RDSR_CMD[7-i];
        SCK = 0;
        #(CLK_PERIOD/2);
        
        SCK = 1;
        i = i + 1;
        #(CLK_PERIOD/2);
    end
    
    SCK = 0;
    i = 0;
    #CLK_PERIOD;

    repeat(8)
    begin
        SCK = 0;
        #(CLK_PERIOD/2);

        DATA[7-i] = SO;
        SCK = 1;
        i = i + 1;
        #(CLK_PERIOD/2);
    end
    
    SI = 0;
    valid = 1;
    SCK = 0;
    #CLK_PERIOD;
    valid = 0;
    CS_N = 1;
    
    //--------------- WRITE data-----------------
    #300;
    i = 0;
    
    SI = 0;
    SCK = 0;

    CS_N = 0;
    #CLK_PERIOD;

    repeat(8)
    begin
        SI = WRITE_CMD[7-i];
        SCK = 0;
        #(CLK_PERIOD/2);
        
        i = i + 1;
        SCK = 1;
        #(CLK_PERIOD/2);
    end
    
    SI = 1;
    SCK = 0;
    CS_N = 0;
    i = 0;
    
    #300;
    
    repeat(8)
    begin
        SI = WRITE_ADDR[7-i];
        SCK = 0;
        #(CLK_PERIOD/2);
        i = i + 1;
        SCK = 1;
        #(CLK_PERIOD/2);
    end
    
    repeat (8)
    begin
        i = 0;
        SI = 0;
        SCK = 0;
        
        #300;
        
        repeat(8)
        begin
            SI = WRITE_DATA[7-i];
            SCK = 0;
            #(CLK_PERIOD/2);
            
            i = i + 1;
            SCK = 1;
            #(CLK_PERIOD/2);
        end
        WRITE_DATA = WRITE_DATA + 1;
    end
        
    SCK = 0;
    #CLK_PERIOD;
    CS_N = 1;

    
    i = 0;
    DATA = 8'h01;
   //----------- READ SR REGISTER  -------------------
   while(DATA[0] == 1)
   begin
        #2000;
        i = 0;
        
        SI = 0;
        SCK = 0;
        CS_N = 0;
        #70;

        repeat(8)
        begin
            SI = RDSR_CMD[7-i];
            SCK = 0;
            #(CLK_PERIOD/2);

            i = i + 1;
            SCK = 1;
            #(CLK_PERIOD/2);
        end
        
        SCK = 0;
        i = 0;
        #300;


        valid = 0;
        repeat(8)
        begin
            SCK = 0;
            #(CLK_PERIOD/2);

            DATA[7-i] = SO;
            SCK = 1;
            i = i + 1;
            #(CLK_PERIOD/2);
        end
        valid = 1;
        SCK = 0;
        #CLK_PERIOD;
        valid = 0;
        CS_N = 1;   
    end
    
    //-----------------  read data-------------
    #300;
    i = 0;
    
    SI = 0;
    SCK = 0;
    CS_N = 0;

    #CLK_PERIOD;

    repeat(8)
    begin
        SI = READ_CMD[7-i];
        SCK = 0;
        #(CLK_PERIOD/2);

        i = i + 1;
        SCK = 1;
        #(CLK_PERIOD/2);
    end
    
    SI = 0;
    SCK = 0;
    i = 0;
    
    #300;
    
    repeat(8)
    begin
        SI = READ_ADDR[7-i];
        SCK = 0;
        #(CLK_PERIOD/2);

        i = i + 1;
        SCK = 1;
        #(CLK_PERIOD/2);
    end
    
    repeat (8)
    begin
        i = 0;
        SI = 0;
        SCK = 0;
        
        #300;
       
        valid = 0;
        repeat(8)
        begin
            SCK = 0;
            #(CLK_PERIOD/2);
            
            SCK = 1;
            DATA[7-i] = SO;
            i = i + 1;
            #(CLK_PERIOD/2);
        end
        
        valid = 1;
        SCK = 0;
        #CLK_PERIOD;
        valid = 0;
    end
        
    SCK = 0;
    #100;
    CS_N = 1;

    
    #2000;    
    $stop;    
end

// 可以使用任一芯片Verilog模型进行仿真
M25AA040A  SPI_DEVICE_inst  
//M25LC040A  SPI_DEVICE_inst  
(
    .SI     (SI), 
    .SO     (SO), 
    .SCK    (SCK), 
    .CS_N   (CS_N), 
    .WP_N   (WP_N), 
    .HOLD_N (HOLD_N), 
    .RESET  (reset)
);


   
endmodule

 

3. 观察仿真结果

复习SPI接口的EEPROM的原理与使用中指令表如表1所示:

表1

命令/Instruction name 命令格式/Instruction formt 操作/operation 命令描述
WREN 0000X110 Write enable Set

write enable latch

写使能锁存
WRDI 0000X100 Write disable instruction

Reset write enable latch

复位写使能
RDSR 0000X101 Read status Register 读状态寄存器
WRSR 0000X001 Write status register 写状态寄存器
READ 0000A011 Read data

from memory array

读数据A表示AT25040的第9位地址
WRITE 0000A010 Write data to

memory array

写数据A表示AT25040的第9位地址为0或1

  • 写使能锁存

 

图6 写使能锁存仿真

注意图6中片选在SCK时钟波形前后都是稳定的低电平。因为写使能锁存是master发给slave的指令,所以SI根据SCK输出8’h06,而SO保持高阻。对比芯片的写使能时序图,如图7所示,证明仿真波形正确。

图7 写使能锁存时序图

 

  • 读状态寄存器

状态寄存器格式如表2所示,注意bit 1表示的是写使能:1’b1表示写使能;1’b0表示没有写使能。

表2:

图8所示为读状态寄存器的时序图。

图8读状态寄存器时序图

 

图9 读状态寄存器仿真

从图9的仿真中可以看出来CS_N信号在 “master读状态机寄存器 + slave输出状态寄存器” 的整个过程中都保持为低电平。SI首先输出8’h05命令,接着SCK停顿了一个时钟周期(这里是因为时钟频率比较高,给芯片内部一点缓冲时间),再进行翻转,SO输出8’02,标志前一个写使能已经打开。

读状态寄存器经常会在写使能锁存之后使用,主要是为了确保芯片的写使能已经打开。

  • 写数据

图10所示为写一个字节数据,在仿真中实际上写了8个字节的数据,如图11所示。

图10 写数据时序图

图11 写数据

图11所示为SI输出写指令(8’h02)+ 字节地址({1’b0,8’h80}) + 数据(8’h00) + 数据(8’h01) + 数据(8’h02) + 数据(8’h03) + 数据(8’h04) + 数据(8’h05) + 数据(8’h06) + 数据(8’h07)。

  • 读状态机寄存器 + 读数据

为了确保读出准确的数据,需要先读出状态寄存器,如果bit 0为0,即表示上一笔写操作已经成功完成,否则需要等待芯片内部完成写操作。在仿真中将读出的状态寄存器存在DATA中,可以看到 图12中将近5ms的时间DATA的值都是8’h03,表示芯片当前正在内部写数据。

图12 写数据时间约为5ms

在图13红色方框处显示读出状态寄存器的值为8’h00(bit 0的值为0),表示终于可以进行读操作。读操作的命令是8’h03,读地址是8’h80。SO返回读出的数据8’h00,8’h01,8’h02,8’h03,8’h04,8’h05,8’h06,8’h07。对比芯片的读数据时序图13,仿真结果是正确的。

 

图13 读数据仿真

图14 读数据时序图

 

4. 仿真总结

从以上的仿真结果可以看出,Microchip提供的芯片Verilog模型可以配合controller测试一些SPI的基本操作,且仿真图与对应的时序图一致,包括芯片编程等待的时间5 ms也很好的模拟了出来。总的来说,使用芯片Verilog模型仿真是一种很好的验证手段,可以在没有真实EEPROM器件的时候,验证controller 的正确性。

 

仿真工程下载:

[content_control]

spi_eeprom.zip

[/content_control]

 

Posted in FPGA, FPGA 教材教案, Verilog, Verilog, Vivado, Vivado, 元器件, 存储器, 开发工具, 开发工具, 开发语言, 教材与教案, 文章, 模拟器件, 编程语言, 资料区

发表回复

相关链接