spi_phy.v 的上层模块是spi_cmd.v. spi_cmd.v模块主要是负责命令(指令)的调度。 上层的系统模块可以将相关的spi 命令(指令)写入到spi_fifo_wr (ip 核)中, spi_cmd.v 模块通过读取spi_fifo_wr 的内容,得到相关的命令(指令),然后产生spi_phy.v 所需要的相关控制信号, 数据将通过spi_phy.v 模块发送到其他外设(spi 设备); 同时 ,从spi_phy.v 模块中读取到的数据,也会被存储到spi_cmd.v 模块中的spi_fifo_rd (ip核)中, 供spi_cmd.v 模块之上的系统模块调用。
参考文章:
SPI 通讯协议 及 SPI 相关工程 详解
spi_cmd.v 模块介绍
spi_cmd.v 模块 下层连接spi_phy.v,上层模块为SPI_IF.V 。 spi_cmd.v 和 spi_phy.v 模块都是使用的phy_clk 时钟的, 他们的上层模块将使用系统时钟(sys_clk)。使用fifo 作为两个时钟域的接口是比较适合的。spi_cmd.v 模块 负责将上层模块的指令(fifo 中) 正确的发送给spi_phy.v 模块, 同时将spi_phy.v 模块中读到的数据写入(spi_fifo_rd) 中, 供上层模块读取。
代码介绍:
|
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 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: Fraser Innovation Inc // Engineer: WilliamG // // Create Date: 2020/06/22 10:58:53 // Design Name: // Module Name: spi_cmd.v // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// /* flash command 02h page program 20h subsector erase d8h sector erase c7h chip erase 9fh read ID 03h read 06h write enable 04h write disable 05h read status register */ module spi_cmd # ( parameter SPI_MODE = "FLASH" ) ( input phy_clk, // phy interface output spi_cs, //SPI片选 output spi_clk, //SPI时钟 output spi_mosi, //SPI主器件输出从器件输入 input spi_miso, //SPI主器件输入从器件输出 // 系统时钟域 input sys_clk, input [17:0] spi_din, input spi_wr, input spi_rd, output [7:0] spi_dout, output spi_empty, input spi_cmd_vld, output spi_cmd_ack, input rd_dev_state_en, // for spi flash output [7:0] rd_dev_state, // read spi flash 05 state register, this value cannot be written to fifo input rst_n ); //======================================================================================= reg [1:0] phy_cmd_vld = 0; always @ (posedge phy_clk or negedge rst_n) if(!rst_n) phy_cmd_vld <= 0; else phy_cmd_vld <= {phy_cmd_vld[0], spi_cmd_vld}; wire pos_cmd_vld = (phy_cmd_vld == 2'b01) ? 1'b1 : 1'b0; reg phy_cmd_ack = 0; reg [7:0] phy_cmd_ack_r = 0; always @ (posedge phy_clk ) if(phy_cmd_ack) phy_cmd_ack_r <= 8'hff; else phy_cmd_ack_r <= {phy_cmd_ack_r[6:0], 1'b0}; assign spi_cmd_ack = phy_cmd_ack_r[7]; //======================================================================================= /* 定义 写fifo ; 18bit: bit[17] == 0, bit[7:0] 是数据, bit[16:8] 无用; bit[17] == 1 作为flash 的读标识 和 结束标识。 剩余的bit[16:0] 读spi 的 byte 数量; 举例 : 18'h2_0000 表示当前命令没有需要从flash 读取的数据。 18'h2_0003 表示当前命令需要从flash 读取3个数据。 每次flash 操作 都必须使用 18'b1x_xxxx_xxxx_xxxx_xxxx 作为最后一条命令, 即使没有要从flash读取数据,也要写入fifo 18'h2_0000 作为 本次命令传输结束。 举例: 读 spi flash ID 18'h0_009f // 发送READ-ID 命令 18'h2_0003 // 结束符,从flash 中读取3 bytes 数据 写 spi flash 0x20_0000 0x01,0x02,0x03 18'h0_0006 // write enable command 18'h2_0000 // 结束符,从flash 中读取0 bytes 数据 18'h0_0002 // write enable command 18'h0_0020 // flash 地址 最高8bit 18'h0_0000 // flash 地址 中间8bit 18'h0_0000 // flash 地址 最低8bit 18'h0_0001 // flash 写数据 0x01 18'h0_0002 // flash 写数据 0x02 18'h0_0003 // flash 写数据 0x03 18'h2_0000 // 结束符,从flash 中读取0 bytes 数据 擦除扇区 0x80_0000 18'h0_0020 // erase command 18'h0_0080 // flash 地址 最高8bit 18'h0_0000 // flash 地址 中间8bit 18'h0_0000 // flash 地址 最低8bit 18'h2_0000 // 结束符,从flash 中读取0 bytes 数据 */ reg wfifo_rd = 0; wire [17:0] wfifo_dout; wire wfifo_empty; SPI_FIFO_WR SPI_FIFO_WR_inst ( .wr_clk (sys_clk), // input wire wr_clk .din (spi_din), // input wire [17 : 0] din .wr_en (spi_wr), // input wire wr_en .full (), // output wire full .rd_clk (phy_clk), // input wire rd_clk .rd_en (wfifo_rd), // input wire rd_en .dout (wfifo_dout), // output wire [17 : 0] dout .valid (), // output wire valid .empty (wfifo_empty), // output wire empty .rst (!rst_n) // input wire rst ); //======================================================================================= reg [31:0] rd_len = 0; reg spi_rev_en = 0; reg spi_cs_cmd = 1; reg [7:0] flash_din = 8'hff; reg flash_din_rdy = 0; wire [7:0] flash_dout; wire flash_dout_vld; wire flash_data_done; reg [31:0] spi_cnt = 0; reg [2:0] spi_st = 0; always @ (posedge phy_clk or negedge rst_n) if(!rst_n) begin spi_rev_en <= 0; phy_cmd_ack <= 0; wfifo_rd <= 0; flash_din_rdy <= 0; spi_cs_cmd <= 1; spi_st <= 0; end else case(spi_st) 0: begin phy_cmd_ack <= 0; spi_cs_cmd <= 1; spi_rev_en <= 0; spi_cnt <= 0; flash_din <= 8'hff; wfifo_rd <= 0; if(pos_cmd_vld) begin spi_st <= 1; end end 1: begin if(!wfifo_empty) begin wfifo_rd <= 1; spi_st <= 2; end end 2: begin spi_cs_cmd <= 0; wfifo_rd <= 0; spi_st <= 3; end 3: begin flash_din <= wfifo_dout[7:0]; flash_din_rdy <= 1; wfifo_rd <= 1; spi_st <= 4; end 4: begin wfifo_rd <= 0; flash_din_rdy <= 0; spi_st <= 5; end 5: begin flash_din_rdy <= 0; if(flash_data_done) begin if(wfifo_dout[17]) begin rd_len <= wfifo_dout[16:0]; spi_cnt <= 1; flash_din <= 8'hff; if(wfifo_dout[16:0] == 0) spi_st <= 7; else begin flash_din_rdy <= 1; spi_st <= 6; end end else begin flash_din <= wfifo_dout[7:0]; flash_din_rdy <= 1; wfifo_rd <= 1; spi_st <= 4; end end end 6: begin spi_rev_en <= 1; flash_din_rdy <= 0; if(flash_data_done) begin if(spi_cnt == rd_len) spi_st <= 7; else begin spi_cnt <= spi_cnt + 1; flash_din <= 8'hff; flash_din_rdy <= 1; end end end 7: begin spi_cs_cmd <= 1; spi_rev_en <= 0; flash_din_rdy <= 0; phy_cmd_ack <= 1; spi_st <= 0; end default : spi_st <= 0; endcase reg [7:0] rx_fifo_din = 0; reg rx_fifo_we = 0; //======================================================================================= // 系统上层程序 发送 rd_dev_state_en, 想要得到 rd_dev_state。 从phy_clk 时钟域 转换到 sys_clk 时钟域 reg [7:0] rd_dev_state_r = 0; always @ (posedge sys_clk) if(rd_dev_state_en) rd_dev_state_r <= rx_fifo_din; else rd_dev_state_r <= 0; assign rd_dev_state = rd_dev_state_r; // 从sys_clk 时钟域 转换到 phy_clk 时钟域 reg rd_dev_state_en_phy = 0; always @ (posedge phy_clk) rd_dev_state_en_phy <= rd_dev_state_en; //======================================================================================= // 05 寄存器读取的内容,没有被写进fifo, 当然也是可以写入的, 再由上层程序读取。 always @ (posedge phy_clk) if(flash_dout_vld & spi_rev_en) begin rx_fifo_din <= flash_dout; rx_fifo_we <= (SPI_MODE == "FLASH") ? ~rd_dev_state_en_phy : 1'b1; // rx_fifo_we <= 1'b1; end else rx_fifo_we <= 0; wire rx_fifo_full; SPI_FIFO_RD SPI_FIFO_RD_inst ( .wr_clk (phy_clk), // input wire wr_clk .din (rx_fifo_din), // input wire [7 : 0] din .wr_en (rx_fifo_we), // input wire wr_en .full (rx_fifo_full), // output wire full .rd_clk (sys_clk), // input wire rd_clk .rd_en (spi_rd), // input wire rd_en .dout (spi_dout), // output wire [7 : 0] dout .valid (), // output wire valid .empty (spi_empty), // output wire empty .rst (!rst_n) // input wire rst ); //======================================================================================= // spi_phy 驱动模块 spi_phy spi_phy_inst ( .phy_clk (phy_clk), .spi_cs_cmd (spi_cs_cmd), .flash_din (flash_din), .flash_din_rdy (flash_din_rdy), .flash_data_done (flash_data_done), .flash_dout (flash_dout), .flash_dout_vld (flash_dout_vld), .spi_cs (spi_cs), // SPI片选 .spi_clk (spi_clk), // SPI 时钟 .spi_mosi (spi_mosi), // SPI主器件输出从器件输入 .spi_miso (spi_miso), // SPI主器件输入从器件输出 .rst_n (rst_n) ); //======================================================================================= endmodule |
模块接口介绍
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
input <span style="color: #0000ff;">phy_clk</span>, // phy interface 和其他spi 设备连接的pin output <span style="color: #0000ff;">spi_cs</span>, //SPI片选 output <span style="color: #0000ff;">spi_clk</span>, //SPI时钟 output <span style="color: #0000ff;">spi_mosi</span>, //SPI主器件输出从器件输入 input <span style="color: #0000ff;">spi_miso</span>, //SPI主器件输入从器件输出 // 系统时钟域 input <span style="color: #0000ff;">sys_clk</span>, // 上层模块(系统时钟域) 时钟 input [17:0] <span style="color: #0000ff;">spi_din</span>, // 上层模块(系统时钟域) 写入spi_fifo_wr (IP) 的数据 input <span style="color: #0000ff;">spi_wr</span>, // 上层模块(系统时钟域) 写入spi_fifo_wr 写信号 input <span style="color: #0000ff;">spi_rd</span>, // 上层模块(系统时钟域) 读 spi_fifo_rd 读信号 output [7:0] <span style="color: #0000ff;">spi_dout</span>, // 上层模块(系统时钟域) 读 spi_fifo_rd 中的数据(由 spi_phy.v 写入) output <span style="color: #0000ff;">spi_empty</span>, // spi_fifo_rd (IP) 空 信号指示 input <span style="color: #0000ff;">spi_cmd_vld</span>, // 上层模块(系统时钟域)通知本模块 状态机, 上层命令发送完毕, 本模块可以读取spi_fifo_wr 中的数据,并执行当前spi 操作, 多个系统时钟宽度。 output <span style="color: #0000ff;">spi_cmd_ack</span>, // 本模块(phy时钟域)返回给上层模块, 通知当前的命令(指令)已经执行完毕, 等待上层模块发送新的命令,多个phy 时钟宽度。 input <span style="color: #0000ff;">rd_dev_state_en</span>, // 上层模块(系统时钟域)读 spi flash (spi 设备) 05 寄存器(stutas register) output [7:0] <span style="color: #0000ff;">rd_dev_state</span>, // 上层模块(系统时钟域)05 寄存器 的数据, input <span style="color: #0000ff;">rst_n</span> |
在本模块接口中,上层模块的spi 命令主要是通过fifo 进行交互的, 这样可以隔离不同的时钟域。
spi_cmd_vld, spi_cmd_ack 这对信号用于通知上层和本层模块:命令是否可以执行, 命令是否执行完毕, 多是多时钟周期的信号, 然后对这些信号采集上升沿 , 就可以实现时钟域转换了。
rd_dev_state_en, rd_dev_state 这对信号用于对spi flash 的05 寄存器进行读取, 当然也可以使用spi_fifo_rd 进行读取, 因为05寄存器需要反复读取,作为上层模块的某些固定操作,可以使用这对信号直接读取,不再使用spi_fifo_rd 通道了。
spi_cmd_vld, spi_cmd_ack 信号转换
reg [1:0] phy_cmd_vld = 0;
always @ (posedge phy_clk or negedge rst_n)
if(!rst_n) phy_cmd_vld <= 0;
else phy_cmd_vld <= {phy_cmd_vld[0], spi_cmd_vld};
wire pos_cmd_vld = (phy_cmd_vld == 2’b01) ? 1’b1 : 1’b0;
对上层模块spi_cmd_vld 信号(sys_clk 时钟域)在phy 时钟域下采样,获得上升沿 pos_cmd_vld 供本层模块使用。这也是异步时钟域信号转换的一种方法。
reg phy_cmd_ack = 0;
reg [7:0] phy_cmd_ack_r = 0;
always @ (posedge phy_clk )
if(phy_cmd_ack) phy_cmd_ack_r <= 8’hff;
else phy_cmd_ack_r <= {phy_cmd_ack_r[6:0], 1’b0};
assign spi_cmd_ack = phy_cmd_ack_r[7];
本层模块产生的单沿信号 phy_cmd_ack 进行多时钟周期延迟,产生长周期(phy_clk) 信号,供上层模块异步采样。
命令写fifo 的数据结构
这里我们设计了一个spi_fifo_wr 数据结构, 这个fifo 数据的宽度为18bit, 不同的spi 操作(transaction)可能需要很多条命令才能完成。举例: spi flash 写操作: 02 + addr[23:16] + addr[15:08] + addr[07:00] + data0 + data1 + data2 + data3 + ……
命令格式:
1)所有命令都是8bit的, bit[17] 都必须 为 0, bit[16:08] 是多少,不重要, 也不关心。
2)所有命令都必须有且只有一条读操作的长度(RD LENGTH) ,作为最后一条指令 ,同时也是本次操作的结束标志 bit[17] 必须为 1; bit[16:00] 代表 读取数据(bytes)次数,如果不需要读取bit[16:00] = 0, 最多可以读出 17’h 1_1111_1111 这么多个byes 的数据。
3) 所有写操作 (不需要从spi flash 设备中读取数据) ,最后一条指令必须是 18’h2_0000_0000 。 (结束标志, 读取长度 0 byte)。
4)如果有读spi 的操作 ,最后一条指令必须是 (18’h2_0000_0000 | RD LENGTH) 。 (结束标志, 读取长度 RD LENGTH bytes)。
5)fifo中只能有一个操作(transaction) ,每个操作可能有多个命令。例如:02 + addr[23:16] + addr[15:08] + addr[07:00] + data0 + data1 + data2 + data3 。 向spi flash 某个地址写入 4 个数据,这里就有8 个命令 + 结束命令 (18’h2_0000_0000) 共同组成一个操作(transaction)。
命令举例:
1) 在 0x30_0000 地址 连续写 0x01, 0x02,0x03, 0x04 4个bytes 数据
操作(transaction)1: // 打开spi flash 写有效
18’h0_0006
18’h2_0000
操作(transaction)2: // 在 0x30_0000 地址 连续写 0x01, 0x02,0x03, 0x04 4个bytes 数据
18’h0_0002
18’h0_0030
18’h0_0000
18’h0_0000
18’h0_0001
18’h0_0002
18’h0_0003
18’h0_0004
18’h2_0000
操作(transaction)3: // 连续读取05 寄存器,直到 状态寄存器返回的数据 最低位 为 0
18’h0_0005
18’h2_0001
2) 从spi flash 读取DEV ID 3 bytes
18’h0_009f
18’h2_0003
3) 从 0x40_0000 读取 11个数据
18’h0_0003
18’h0_0040
18’h0_0000
18’h0_0000
18’h2_000b
状态机解析:
状态机主要用于读取spi_fifo_wr 中的数据,同时将相关的命令发给spi_phy.v
状态0:空闲状态, 等待 fifo中填满一条操作(transaction),上层会发送一个操作准备好的信号,经过时钟域转换得到pos_cmd_vld, 这时,准备读取spi_fifo_wr 中的数据了,跳转状态1
状态1:如果fifo 不是空, 开始读取spi_fifo_wr , 跳转状态2
状态2:由于使用的是标准的fifo , 所以需要等待一个时钟周期,才能得到fifo里的数据。 跳转状态3
状态3:将fifo中读取到的数据发送给spi_phy.v 同时发送 flash_din_rdy 信号给spi_phy.v 模块 ; 读取spi_fifo_wr 中的下一个数据, 跳转状态4
状态4:跳转状态5
状态5:如果spi_phy.v 完成数据的发送, 那就看一下最新从fifo中读取到的数据格式。
如果从fifo中读到的数据有结束标志(bit 17 == 1),
if(RD LENGTH == 0), 跳转状态 7
else 连续发送 8 个时钟, 这时的 flash_din 是多少已经不重要了, spi 控制器 只要连续发送 时钟, 数据就会从miso 线上返回。 跳转状态 6
如果从fifo中读到的数据结束标志(bit 17 == 0) , 表示还有命令需要发送, 发送当前的数据到spi_phy.v , 从fifo中读下一个数据,跳转状态 4
状态6:准备开始接收 miso 线过来的数据 spi_rev_en = 1。 同时spi_cnt 计数器 加一。
if(RD LENGTH == spi_cnt), 跳转状态 7
else 继续发送 时钟。
状态7:所有操作(transcation 结束) ,通知上层模块 本次操作结束(phy_cmd_ack = 1) . 返回状态0
rd_dev_state_en, rd_dev_state 信号操作
|
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 |
// 系统上层程序 发送 rd_dev_state_en, 想要得到 rd_dev_state。 从phy_clk 时钟域 转换到 sys_clk 时钟域 reg [7:0] rd_dev_state_r = 0; always @ (posedge sys_clk) if(rd_dev_state_en) rd_dev_state_r <= <span style="color: #0000ff;">rx_fifo_din</span>; else rd_dev_state_r <= 0; assign <span style="color: #0000ff;">rd_dev_state</span> = rd_dev_state_r; // 从sys_clk 时钟域 转换到 phy_clk 时钟域 reg rd_dev_state_en_phy = 0; always @ (posedge phy_clk) rd_dev_state_en_phy <= <span style="color: #0000ff;">rd_dev_state_en</span>; //======================================================================================= // 05 寄存器读取的内容,没有被写进fifo, 当然也是可以写入的, 再由上层程序读取。 always @ (posedge phy_clk) if(flash_dout_vld & spi_rev_en) begin <span style="color: #0000ff;">rx_fifo_din</span> <= flash_dout; <span style="color: #0000ff;">rx_fifo_we</span> <= (SPI_MODE == "FLASH") ? ~rd_dev_state_en_phy : 1'b1; // <span style="color: #0000ff;">rx_fifo_we</span> <= 1'b1; end else <span style="color: #0000ff;">rx_fifo_we</span> <= 0; |
将spi_phy.v 读取到的数据赋值给 rd_dev_state。 同时 将 rd_dev_state_en 同步到phy_clk 时钟域上。
接下来就是 spi_fifo_rd (IP ) 的写入操作了, 如果不希望 05 状态寄存器写入 fifo 使用:
rx_fifo_we <= (SPI_MODE == “FLASH”) ? ~rd_dev_state_en_phy : 1’b1;
如果认为单独提取 05 状态 不是非常必要,上层可以读取fifo ,得到 05 寄存器的状态 可以设置:
rx_fifo_we <= 1’b1;
目前工程中是将上层模块某些固定的操作中05 寄存器的读取,单独使用, 而不是写入到fifo中。
SPI 数据读取fifo
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
wire rx_fifo_full; SPI_FIFO_RD SPI_FIFO_RD_inst ( .wr_clk (phy_clk), // input wire wr_clk .din (rx_fifo_din), // input wire [7 : 0] din .wr_en (rx_fifo_we), // input wire wr_en .full (rx_fifo_full), // output wire full .rd_clk (sys_clk), // input wire rd_clk .rd_en (spi_rd), // input wire rd_en .dout (spi_dout), // output wire [7 : 0] dout .valid (), // output wire valid .empty (spi_empty), // output wire empty .rst (!rst_n) // input wire rst ); |
spi 接收fifo 主要用于从spi_phy.v 的miso 线上接收数据,经过 串行到并行的decode ,得到 8bit 的接收数据。
always @ (posedge phy_clk)
if(flash_dout_vld & spi_rev_en)
begin
rx_fifo_din <= flash_dout;
rx_fifo_we <= (SPI_MODE == “FLASH”) ? ~rd_dev_state_en_phy : 1’b1;
// rx_fifo_we <= 1’b1;
end
else rx_fifo_we <= 0;
其中 flash_dout, flash_dout_vld 是从flahs_phy.v 模块传输过来的, 同时配合本层状态机逻辑 spi_rev_en (接收数据 允许)。 最终得到 rx_fifo_din, rx_fifo_we 这两个fifo 接口上的信号。
spi_fifo_rd 使用的的是标准fifo, 8bit 输入, 8bit输出, 没有相关的数据结构,即:所有数据都是从spi_phy.v 接收来的。 上层模块只需要读取这个fifo , 就可以得到相应的数据了。
仿真波形
spi_fifo_wr fifo中输入 18’h0_009f, 18’h2_0003 。 发送9f 命令给spi flash , 从flash 中得到相应的read-id 信息。
总结
到此, spi_cmd.v 模块介绍完毕, 大家可以注意到,这个模块本身(配合spi_phy.v) 不但可以和spi flash (外设)通讯, 也可以连接其他种类的spi (外设)。 这个模块本身是和spi flash 无关的, 当然,spi flash 只是其中的一种应用罢了。





