在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]
[/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 循环语句中复习:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
`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 | 读数据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]
[/content_control]