当前位置:   article > 正文

FPGA常见接口及逻辑实现(三)—— SPI

FPGA常见接口及逻辑实现(三)—— SPI

一、SPI协议简介

        SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种同步串行接口,相对于之前介绍过的UART和I2C,SPI的速率就高出很多,最高能到100M左右,SPI协议比较简单,就不多做介绍,主要介绍下与UART和I2C的不同,SPI是一主多从协议,每个从机通过一根片选信号线和主机连接,所以从接口线数量上没有I2C的优势,但是SPI是全双工通信,两根数据线,同时读写,而且还有带宽更大的DUAL SPI和QUAD SPI,分别是两根数据线同时写和四根数据线同时写,不过DSPI和QSPI一般就是半双工的了,读写不能同时进行。相较于UART,最大的不同就是SPI是同步协议,数据随总线时钟一同发送,所以接收方不需要进行波特率匹配,使用更灵活。

二、SPI协议的verilog实现思路

        SPI的时序简单,开始发送后,拉低对应从机的片选信号,然后随着总线时钟一位一位地发送数据到总线上,不同于UART的是SPI一般先发送最高位,发送完一个字节就接着发送下一个字节,没有停止位或者起始位,知道主机重新拉高片选信号,一次操作结束。

        要实现SPI,首先要产生SPI时钟,时钟一般通过系统时钟分频产生,将其参数化,以便灵活设置SPI速率。

        其次就是SPI的四种模式,由时钟极性和时钟相位两个参数来控制,其中模式0和3是上升沿采样,模式1和2是下降沿采样,模式0和1空闲时为低电平,模式2和3空闲时为高电平,记住这两个最重要的区别即可。

        在上一篇I2C的内容中,最后实现了用寄存器去控制I2C接口,这次SPI也用同样的方法来实现,所以提前预留出各种寄存器的接口。

        对参数和接口进行规划之后,就是具体时序的实现了,首先时钟分频自然是通过计数器来实现,计数的过程中还能顺便在时钟的跳变沿产生脉冲信号方便使用,同时每个时钟周期对应一比特操作,对操作的比特也进行计数,每八个比特就是一个字节;至于数据线的操作就和串口一模一样,根据比特计数器的值发送或接收对应位的比特即可。

        根据上述的思路画出时序图:

        可以看到图中片选信号提前一个时钟周期就拉低了,但是实际操作并不需要这样,只要在读写的过程中片选信号保持为低即可。

        以上都是SPI主机的实现思路,对于SPI的从机,一般想到的首先就是SPI flash,或者SPI屏幕等等,可以看出SPI的从机不像I2C的从机那样有比较通用的实现方法,很难写出一个很通用的从机模块,不过万变不离其宗,从机无论如何都是要接收主机发来的数据的,所以对于从机,我就只实现一个兼容性较好的可以完成接收数据操作和完成写回数据操作的模块,具体要实现的功能就基于此模块的基础上去修改,应该也会减少很多工作量。

        对于从机的参数,主要需要兼容主机的四种模式,一般读写flash的时候,SPI flash都同时支持模式0和模式3,或是模式1和模式2,而前文我提到模式0和模式3的共同点是上升沿采样,所以我们就用采样沿参数化,当采样沿为上升沿时,兼容模式0和3,反之兼容模式1和2。但是对于不同的采样沿,显然需要两套不同的代码,这种情况下就需要generate关键字在不同的情况下生成不同的逻辑。

三、SPI主机的Verilog实现

        主机实现的难点主要在于兼容四种模式,一般对于四种模式的描述都是第一个变化沿怎么样,第二个变化沿怎么样,我只能说这种描述太抽象了,又难记又不方便转化为逻辑语言,对于模式0和3,无论是第几个变化沿,都是在上升沿采样,下降沿发送,这些是确定的,时钟空闲状态也很好确定,是协议规定的时钟极性,至于第一个变化沿的问题直接简化为发送时的时钟初始状态,无论时钟空闲状态是高还是低,都从一个低电平开始,假如空闲状态为高,自然会产生一个下降沿,假如空闲状态为低,则继续保持低,这样真正的第一个变化沿自然是上升沿了。

        综上所述,编写的SPI主机代码如下:

  1. `timescale 1ns / 1ps
  2. module spi_master#(
  3. parameter CLK_PHA = 0, // SPI时钟相位
  4. parameter CLK_POL = 0, // SPI时钟极性
  5. parameter SCK_DIV_CNT = 4 // SPI时钟分频系数
  6. )(
  7. input clk, // 输入时钟
  8. input rst_n, // 同步复位
  9. input op_start, // 操作开始信号
  10. output op_busy, // 操作忙碌信号
  11. input [7:0] op_len, // 操作长度
  12. input [7:0] cs_ctrl, // 片选信号
  13. output txc, // 数据请求
  14. input [7:0] txd, // 数据输入
  15. output rxv, // 数据有效
  16. output [7:0] rxd, // 数据输出
  17. output sck, // SPI时钟
  18. output mosi, // SPI主机输出从机输入
  19. input miso, // SPI主机输入从机输出
  20. output [7:0] cs_n // SPI片选
  21. );
  22. // 参数变量声明
  23. // SPI时钟空闲状态
  24. localparam [0:0] SCK_IDLE = CLK_POL;
  25. // SPI时钟初始状态
  26. localparam [0:0] SCK_INIT = CLK_PHA ? ~CLK_POL : CLK_POL;
  27. // 寄存器
  28. reg spi_clk;
  29. reg master_out;
  30. reg [3:0] clk_cnt;
  31. reg [3:0] bit_cnt;
  32. reg [7:0] byte_cnt;
  33. reg spi_busy;
  34. reg data_req;
  35. reg data_valid;
  36. reg [7:0] data_out;
  37. reg start_ff1;
  38. reg start_ff2;
  39. reg start_flag;
  40. reg sck_r;
  41. reg mosi_r;
  42. reg [7:0] cs_n_r;
  43. // 组合逻辑
  44. wire half_bit = clk_cnt == SCK_DIV_CNT/2 - 1;
  45. wire one_bit = clk_cnt == SCK_DIV_CNT - 1;
  46. wire one_byte = bit_cnt == 7;
  47. wire one_op = byte_cnt == (op_len - 1) & one_byte & one_bit;
  48. // 模块输出连线
  49. assign op_busy = spi_busy;
  50. assign txc = data_req;
  51. assign rxv = data_valid;
  52. assign rxd = data_out;
  53. assign sck = sck_r;
  54. assign mosi = mosi_r;
  55. assign cs_n = cs_n_r;
  56. // 时序逻辑
  57. // SPI主机接口输出
  58. always @(posedge clk) begin
  59. if(spi_busy) begin
  60. sck_r <= spi_clk;
  61. mosi_r <= master_out;
  62. cs_n_r <= cs_ctrl;
  63. end else begin
  64. sck_r <= SCK_IDLE;
  65. mosi_r <= 1'b0;
  66. cs_n_r <= 8'hff;
  67. end
  68. end
  69. // 启动信号二级同步
  70. always @(posedge clk) begin
  71. start_ff1 <= op_start;
  72. start_ff2 <= start_ff1;
  73. end
  74. always @(posedge clk) begin
  75. if(start_ff1 & ~start_ff2)
  76. start_flag <= 1'b1;
  77. else if(spi_busy)
  78. start_flag <= 1'b0;
  79. end
  80. // 产生SPI时钟,忙碌状态下,每半个比特周期翻转时钟信号
  81. always @(posedge clk) begin
  82. if(!rst_n)
  83. spi_clk <= SCK_INIT;
  84. else if(spi_busy & (half_bit | one_bit))
  85. spi_clk <= ~spi_clk;
  86. end
  87. // 忙碌标志信号,接收到启动信号拉高,发送完操作长度个字节后拉低
  88. always @(posedge clk) begin
  89. if(!rst_n)
  90. spi_busy <= 0;
  91. else if(start_flag)
  92. spi_busy <= 1;
  93. else if(one_op)
  94. spi_busy <= 0;
  95. end
  96. // SPI时钟周期计数器,忙碌状态下计数,计满一比特清零
  97. always @(posedge clk) begin
  98. if(!rst_n)
  99. clk_cnt <= 0;
  100. else if(spi_busy) begin
  101. if(one_bit)
  102. clk_cnt <= 0;
  103. else
  104. clk_cnt <= clk_cnt + 1;
  105. end
  106. end
  107. // 发送比特计数,发送完一字节计数器清零
  108. always @(posedge clk) begin
  109. if(!rst_n)
  110. bit_cnt <= 0;
  111. else if(spi_busy & one_bit) begin
  112. if(one_byte)
  113. bit_cnt <= 0;
  114. else
  115. bit_cnt <= bit_cnt + 1;
  116. end
  117. end
  118. // 在发送每比特的中间时刻对输入线进行采样
  119. always @(posedge clk) begin
  120. if(!rst_n)
  121. data_out <= 0;
  122. else if(spi_busy & half_bit) begin
  123. case (bit_cnt)
  124. 0:data_out[7] <= miso;
  125. 1:data_out[6] <= miso;
  126. 2:data_out[5] <= miso;
  127. 3:data_out[4] <= miso;
  128. 4:data_out[3] <= miso;
  129. 5:data_out[2] <= miso;
  130. 6:data_out[1] <= miso;
  131. 7:data_out[0] <= miso;
  132. default: data_out <= data_out;
  133. endcase
  134. end
  135. end
  136. // 依次发送每个比特到输出线
  137. always @(posedge clk) begin
  138. if(!rst_n)
  139. master_out <= 0;
  140. else if(start_flag & !spi_busy)
  141. master_out <= txd[7];
  142. else if(spi_busy & one_bit) begin
  143. case (bit_cnt)
  144. 0:master_out <= txd[6];
  145. 1:master_out <= txd[5];
  146. 2:master_out <= txd[4];
  147. 3:master_out <= txd[3];
  148. 4:master_out <= txd[2];
  149. 5:master_out <= txd[1];
  150. 6:master_out <= txd[0];
  151. 7:master_out <= txd[7];
  152. default: master_out <= master_out;
  153. endcase
  154. end
  155. end
  156. // 每字节发送结束前一个比特拉高数据请求信号
  157. always @(posedge clk) begin
  158. if(!rst_n)
  159. data_req <= 0;
  160. else if((bit_cnt == 6) & one_bit)
  161. data_req <= 1;
  162. else
  163. data_req <= 0;
  164. end
  165. // 每字节发送结束的比特拉高数据有效信号
  166. always @(posedge clk) begin
  167. if(!rst_n)
  168. data_valid <= 0;
  169. else if(one_byte & one_bit)
  170. data_valid <= 1;
  171. else
  172. data_valid <= 0;
  173. end
  174. // 每字节操作完成后字节计数加一,计数达到操作长度后清零
  175. always @(posedge clk) begin
  176. if(!rst_n)
  177. byte_cnt <= 0;
  178. else if(one_byte & one_bit) begin
  179. if(one_op)
  180. byte_cnt <= 0;
  181. else
  182. byte_cnt <= byte_cnt + 1;
  183. end
  184. end
  185. endmodule

        本次的代码和串口很像,只不过加入了部分寄存器配置端口。以下是spi寄存器代码:

  1. `timescale 1ns / 1ps
  2. module spi_reg(
  3. input clk,
  4. input en,
  5. input we,
  6. input [7:0] din,
  7. output [7:0] dout,
  8. input [7:0] addr,
  9. output op_start,
  10. input op_busy,
  11. output [7:0] op_len,
  12. output [7:0] cs_ctrl,
  13. input txc,
  14. output [7:0] txd,
  15. input rxv,
  16. input [7:0] rxd
  17. );
  18. reg [7:0] r_data_out;
  19. reg [7:0] r_tx_buffer [0:31]; // 0x00 - 0x1f write only
  20. reg [7:0] r_rx_buffer [0:31]; // 0x20 - 0x3f read only
  21. // bit 4-0: tx_buffer ptr
  22. reg [7:0] r_tx_ctrl = 0;
  23. // bit 4-0: rx_buffer ptr
  24. reg [7:0] r_rx_ctrl = 0;
  25. // bit 7: tx_buffer reset,self clear
  26. reg [7:0] r_tx_rst = 0;
  27. // bit 7: rx_buffer reset,self clear
  28. reg [7:0] r_rx_rst = 0;
  29. // bit 7: operate start,self clear
  30. reg [7:0] r_op_startup = 0;
  31. // bit 0: operate busy flag,read only
  32. reg [7:0] r_op_status = 0;
  33. // bit 7-0: operate length
  34. reg [7:0] r_op_length = 0;
  35. // bit 7-0: chip select control
  36. reg [7:0] r_cs_control = 0;
  37. reg [7:0] r_reserve_0 = 0;
  38. reg [7:0] r_reserve_1 = 0;
  39. reg [7:0] r_reserve_2 = 0;
  40. reg [7:0] r_reserve_3 = 0;
  41. reg [7:0] r_reserve_4 = 0;
  42. reg [7:0] r_reserve_5 = 0;
  43. reg [7:0] r_reserve_6 = 0;
  44. reg [7:0] r_reserve_7 = 0; // 0x40 - 0x4f
  45. reg [7:0] start_cnt;
  46. reg [7:0] txrst_cnt;
  47. reg [7:0] rxrst_cnt;
  48. assign dout = r_data_out;
  49. assign op_start = r_op_startup[7];
  50. assign op_len = r_op_length;
  51. assign cs_ctrl = r_cs_control;
  52. assign txd = r_tx_buffer[r_tx_ctrl[4:0]];
  53. always @(posedge clk) begin:READ_REGISTER
  54. if(en) begin
  55. case (addr)
  56. 8'h20: r_data_out <= r_rx_buffer[0];
  57. 8'h21: r_data_out <= r_rx_buffer[1];
  58. 8'h22: r_data_out <= r_rx_buffer[2];
  59. 8'h23: r_data_out <= r_rx_buffer[3];
  60. 8'h24: r_data_out <= r_rx_buffer[4];
  61. 8'h25: r_data_out <= r_rx_buffer[5];
  62. 8'h26: r_data_out <= r_rx_buffer[6];
  63. 8'h27: r_data_out <= r_rx_buffer[7];
  64. 8'h28: r_data_out <= r_rx_buffer[8];
  65. 8'h29: r_data_out <= r_rx_buffer[9];
  66. 8'h2a: r_data_out <= r_rx_buffer[10];
  67. 8'h2b: r_data_out <= r_rx_buffer[11];
  68. 8'h2c: r_data_out <= r_rx_buffer[12];
  69. 8'h2d: r_data_out <= r_rx_buffer[13];
  70. 8'h2e: r_data_out <= r_rx_buffer[14];
  71. 8'h2f: r_data_out <= r_rx_buffer[15];
  72. 8'h30: r_data_out <= r_rx_buffer[16];
  73. 8'h31: r_data_out <= r_rx_buffer[17];
  74. 8'h32: r_data_out <= r_rx_buffer[18];
  75. 8'h33: r_data_out <= r_rx_buffer[19];
  76. 8'h34: r_data_out <= r_rx_buffer[20];
  77. 8'h35: r_data_out <= r_rx_buffer[21];
  78. 8'h36: r_data_out <= r_rx_buffer[22];
  79. 8'h37: r_data_out <= r_rx_buffer[23];
  80. 8'h38: r_data_out <= r_rx_buffer[24];
  81. 8'h39: r_data_out <= r_rx_buffer[25];
  82. 8'h3a: r_data_out <= r_rx_buffer[26];
  83. 8'h3b: r_data_out <= r_rx_buffer[27];
  84. 8'h3c: r_data_out <= r_rx_buffer[28];
  85. 8'h3d: r_data_out <= r_rx_buffer[29];
  86. 8'h3e: r_data_out <= r_rx_buffer[30];
  87. 8'h3f: r_data_out <= r_rx_buffer[31];
  88. 8'h40: r_data_out <= r_tx_ctrl;
  89. 8'h41: r_data_out <= r_rx_ctrl;
  90. 8'h42: r_data_out <= r_tx_rst;
  91. 8'h43: r_data_out <= r_rx_rst;
  92. 8'h44: r_data_out <= r_op_startup;
  93. 8'h45: r_data_out <= r_op_status;
  94. 8'h46: r_data_out <= r_op_length;
  95. 8'h47: r_data_out <= r_cs_control;
  96. 8'h48: r_data_out <= r_reserve_0;
  97. 8'h49: r_data_out <= r_reserve_1;
  98. 8'h4a: r_data_out <= r_reserve_2;
  99. 8'h4b: r_data_out <= r_reserve_3;
  100. 8'h4c: r_data_out <= r_reserve_4;
  101. 8'h4d: r_data_out <= r_reserve_5;
  102. 8'h4e: r_data_out <= r_reserve_6;
  103. 8'h4f: r_data_out <= r_reserve_7;
  104. default: r_data_out <= r_data_out;
  105. endcase
  106. end
  107. end
  108. always @(posedge clk) begin:TX_BUFFER
  109. integer i;
  110. if(en & we) begin
  111. r_tx_buffer[addr] <= din;
  112. end else if(r_tx_rst[7]) begin
  113. for (i = 0;i < 32;i = i + 1) begin
  114. r_tx_buffer[i] <= 0;
  115. end
  116. end
  117. end
  118. always @(posedge clk) begin:RX_BUFFER
  119. integer j;
  120. if(rxv)
  121. r_rx_buffer[r_rx_ctrl[4:0]] <= rxd;
  122. else if(r_rx_rst[7]) begin
  123. for (j = 0;j < 32;j = j + 1) begin
  124. r_rx_buffer[j] <= 0;
  125. end
  126. end
  127. end
  128. always @(posedge clk) begin
  129. if(en & we & addr == 8'h40)
  130. r_tx_ctrl <= din;
  131. else if(r_tx_rst[7])
  132. r_tx_ctrl <= 0;
  133. else if(txc)
  134. r_tx_ctrl <= (r_tx_ctrl != 8'h1f) ? r_tx_ctrl + 1 : 0;
  135. end
  136. always @(posedge clk) begin
  137. if(en & we & addr == 8'h41)
  138. r_rx_ctrl <= din;
  139. else if(r_rx_rst[7])
  140. r_rx_ctrl <= 0;
  141. else if(rxv)
  142. r_rx_ctrl <= (r_rx_ctrl != 8'h1f) ? r_rx_ctrl + 1 : 0;
  143. end
  144. always @(posedge clk) begin
  145. if(en & we & addr == 8'h42)
  146. r_tx_rst <= din;
  147. else if(&txrst_cnt)
  148. r_tx_rst <= r_tx_rst & 8'b0111_1111;
  149. end
  150. always @(posedge clk) begin
  151. if(en & we & addr == 8'h43)
  152. r_rx_rst <= din;
  153. else if(&rxrst_cnt)
  154. r_rx_rst <= r_rx_rst & 8'b0111_1111;
  155. end
  156. always @(posedge clk) begin
  157. if(en & we & addr == 8'h44)
  158. r_op_startup <= din;
  159. else if(&start_cnt)
  160. r_op_startup <= r_op_startup & 8'b0111_1111;
  161. end
  162. always @(posedge clk) begin
  163. r_op_status <= {7'b0000000,op_busy};
  164. end
  165. always @(posedge clk) begin
  166. if(en & we) begin
  167. case(addr)
  168. 8'h46:r_op_length <= din;
  169. 8'h47:r_cs_control <= din;
  170. 8'h48:r_reserve_0 <= din;
  171. 8'h49:r_reserve_1 <= din;
  172. 8'h4a:r_reserve_2 <= din;
  173. 8'h4b:r_reserve_3 <= din;
  174. 8'h4c:r_reserve_4 <= din;
  175. 8'h4d:r_reserve_5 <= din;
  176. 8'h4e:r_reserve_6 <= din;
  177. 8'h4f:r_reserve_7 <= din;
  178. endcase
  179. end
  180. end
  181. initial begin:TX_BUF_INIT
  182. integer n;
  183. for(n = 0;n < 32;n = n + 1) begin
  184. r_tx_buffer[n] = 0;
  185. end
  186. end
  187. initial begin:RX_BUF_INIT
  188. integer m;
  189. for(m = 0;m < 32;m = m + 1) begin
  190. r_rx_buffer[m] = 0;
  191. end
  192. end
  193. always @(posedge clk) begin
  194. if(r_op_startup[7])
  195. start_cnt <= (&start_cnt) ? start_cnt : start_cnt + 1;
  196. else
  197. start_cnt <= 0;
  198. end
  199. always @(posedge clk) begin
  200. if(r_tx_rst[7])
  201. txrst_cnt <= (&txrst_cnt) ? txrst_cnt : txrst_cnt + 1;
  202. else
  203. txrst_cnt <= 0;
  204. end
  205. always @(posedge clk) begin
  206. if(r_rx_rst[7])
  207. rxrst_cnt <= (&rxrst_cnt) ? rxrst_cnt : rxrst_cnt + 1;
  208. else
  209. rxrst_cnt <= 0;
  210. end
  211. endmodule

        以上SPI主机是最常见的单线输出单线输入SPI接口,在实际项目中还经常会用到四线输出输入的qspi接口,正好最近的项目中也要用到qspi,就基于以上的spi修改了一个qspi主机模块,此qspi模块主要用于数据流写入,所以没有做读写双向处理,如果要支持四线读写,还需要做一些小修改。代码如下:

  1. `timescale 1ns / 1ps
  2. module qspi_master#(
  3. parameter CLK_PHA = 0, // SPI时钟相位
  4. parameter CLK_POL = 0, // SPI时钟极性
  5. parameter SCK_DIV_CNT = 4 // SPI时钟分频系数
  6. )(
  7. input clk, // 输入时钟
  8. input rst_n, // 同步复位
  9. input empty_n, // 发送缓存空
  10. input [1:0] wire_mode, // SPI线模式 0:单线 1:双线 2:四线
  11. output txc, // 数据请求
  12. input [7:0] txd, // 数据输入
  13. output rxv, // 数据有效
  14. output [7:0] rxd, // 数据输出
  15. output sck, // SPI时钟
  16. output cs_n, // SPI片选
  17. output sd_0, // 单线模式MOSI,双线四线输出线
  18. inout sd_1, // 单线模式MISO,双线四线输出线
  19. output sd_2, // 四线模式输出线
  20. output sd_3 // 四线模式输出线
  21. );
  22. // 参数变量声明
  23. // SPI时钟空闲状态
  24. localparam [0:0] SCK_IDLE = CLK_POL;
  25. // SPI时钟初始状态
  26. localparam [0:0] SCK_INIT = CLK_PHA ? ~CLK_POL : CLK_POL;
  27. // 寄存器
  28. reg spi_clk;
  29. reg [3:0] clk_cnt;
  30. reg [3:0] bit_cnt;
  31. reg spi_busy;
  32. reg data_req;
  33. reg data_valid;
  34. reg [7:0] data_out;
  35. reg start_ff1;
  36. reg start_ff2;
  37. reg start_flag;
  38. reg sck_r;
  39. reg cs_n_r;
  40. reg sd_0_r;
  41. reg sd_1_r;
  42. reg sd_2_r;
  43. reg sd_3_r;
  44. reg out_0;
  45. reg out_1;
  46. reg out_2;
  47. reg out_3;
  48. // 组合逻辑
  49. wire miso = sd_1;
  50. wire half_bit = clk_cnt == SCK_DIV_CNT/2 - 1;
  51. wire one_bit = clk_cnt == SCK_DIV_CNT - 1;
  52. wire one_byte =
  53. wire_mode == 0 ? bit_cnt == 7 :
  54. wire_mode == 1 ? bit_cnt == 3 :
  55. wire_mode == 2 ? bit_cnt == 1 : 1'b0;
  56. wire nxt_byte =
  57. wire_mode == 0 ? bit_cnt == 6 :
  58. wire_mode == 1 ? bit_cnt == 2 :
  59. wire_mode == 2 ? bit_cnt == 0 : 1'b0;
  60. // 模块输出连线
  61. assign txc = data_req;
  62. assign rxv = data_valid;
  63. assign rxd = data_out;
  64. assign sck = sck_r;
  65. assign cs_n = cs_n_r;
  66. assign sd_0 = sd_0_r;
  67. assign sd_1 = ((wire_mode == 1)|(wire_mode == 2)) ? sd_1_r : 1'bz;
  68. assign sd_2 = sd_2_r;
  69. assign sd_3 = sd_3_r;
  70. // 时序逻辑
  71. // SPI主机接口输出
  72. always @(posedge clk) begin
  73. if(spi_busy) begin
  74. sck_r <= spi_clk;
  75. cs_n_r <= 1'b0;
  76. sd_0_r <= out_0;
  77. sd_1_r <= out_1;
  78. sd_2_r <= out_2;
  79. sd_3_r <= out_3;
  80. end else begin
  81. sck_r <= SCK_IDLE;
  82. cs_n_r <= 1'b1;
  83. sd_0_r <= 1'b0;
  84. sd_1_r <= 1'b0;
  85. sd_2_r <= 1'b0;
  86. sd_3_r <= 1'b0;
  87. end
  88. end
  89. // 启动信号二级同步
  90. always @(posedge clk) begin
  91. start_ff1 <= empty_n;
  92. start_ff2 <= start_ff1;
  93. end
  94. always @(posedge clk) begin
  95. if(start_ff1 & ~start_ff2)
  96. start_flag <= 1'b1;
  97. else if(spi_busy)
  98. start_flag <= 1'b0;
  99. end
  100. // 产生SPI时钟,忙碌状态下,每半个比特周期翻转时钟信号
  101. always @(posedge clk) begin
  102. if(!rst_n)
  103. spi_clk <= SCK_INIT;
  104. else if(spi_busy & (half_bit | one_bit))
  105. spi_clk <= ~spi_clk;
  106. end
  107. // 忙碌标志信号,接收到启动信号拉高,发送完操作长度个字节后拉低
  108. always @(posedge clk) begin
  109. if(!rst_n)
  110. spi_busy <= 0;
  111. else if(start_flag)
  112. spi_busy <= 1;
  113. else if(one_bit & one_byte & !empty_n)
  114. spi_busy <= 0;
  115. end
  116. // SPI时钟周期计数器,忙碌状态下计数,计满一比特清零
  117. always @(posedge clk) begin
  118. if(!rst_n)
  119. clk_cnt <= 0;
  120. else if(spi_busy) begin
  121. if(one_bit)
  122. clk_cnt <= 0;
  123. else
  124. clk_cnt <= clk_cnt + 1;
  125. end
  126. end
  127. // 发送比特计数,发送完一字节计数器清零
  128. always @(posedge clk) begin
  129. if(!rst_n)
  130. bit_cnt <= 0;
  131. else if(spi_busy & one_bit) begin
  132. if(one_byte)
  133. bit_cnt <= 0;
  134. else
  135. bit_cnt <= bit_cnt + 1;
  136. end
  137. end
  138. // 在发送每比特的中间时刻对输入线进行采样
  139. always @(posedge clk) begin
  140. if(!rst_n)
  141. data_out <= 0;
  142. else if(wire_mode == 0 & spi_busy & half_bit) begin
  143. case (bit_cnt)
  144. 0:data_out[7] <= miso;
  145. 1:data_out[6] <= miso;
  146. 2:data_out[5] <= miso;
  147. 3:data_out[4] <= miso;
  148. 4:data_out[3] <= miso;
  149. 5:data_out[2] <= miso;
  150. 6:data_out[1] <= miso;
  151. 7:data_out[0] <= miso;
  152. default: data_out <= data_out;
  153. endcase
  154. end
  155. end
  156. // 依次发送每个比特到输出线
  157. always @(posedge clk) begin
  158. if(!rst_n)
  159. out_0 <= 0;
  160. else if(start_flag & !spi_busy)
  161. out_0 <= txd[7];
  162. else if(wire_mode == 0 & spi_busy & one_bit) begin
  163. case (bit_cnt)
  164. 0:out_0 <= txd[6];
  165. 1:out_0 <= txd[5];
  166. 2:out_0 <= txd[4];
  167. 3:out_0 <= txd[3];
  168. 4:out_0 <= txd[2];
  169. 5:out_0 <= txd[1];
  170. 6:out_0 <= txd[0];
  171. 7:out_0 <= txd[7];
  172. default: out_0 <= out_0;
  173. endcase
  174. end else if(wire_mode == 1 & spi_busy & one_bit) begin
  175. case (bit_cnt)
  176. 0:out_0 <= txd[5];
  177. 1:out_0 <= txd[3];
  178. 2:out_0 <= txd[1];
  179. 3:out_0 <= txd[7];
  180. default: out_0 <= out_0;
  181. endcase
  182. end else if(wire_mode == 2 & spi_busy & one_bit) begin
  183. case (bit_cnt)
  184. 0:out_0 <= txd[3];
  185. 1:out_0 <= txd[7];
  186. default: out_0 <= out_0;
  187. endcase
  188. end
  189. end
  190. always @(posedge clk) begin
  191. if(!rst_n)
  192. out_1 <= 0;
  193. else if(wire_mode != 0 & start_flag & !spi_busy)
  194. out_1 <= txd[6];
  195. else if(wire_mode == 1 & spi_busy & one_bit) begin
  196. case (bit_cnt)
  197. 0:out_1 <= txd[4];
  198. 1:out_1 <= txd[2];
  199. 2:out_1 <= txd[0];
  200. 3:out_1 <= txd[6];
  201. default: out_1 <= out_1;
  202. endcase
  203. end else if(wire_mode == 2 & spi_busy & one_bit) begin
  204. case (bit_cnt)
  205. 0:out_1 <= txd[2];
  206. 1:out_1 <= txd[6];
  207. default: out_1 <= out_1;
  208. endcase
  209. end
  210. end
  211. always @(posedge clk) begin
  212. if(!rst_n)
  213. out_2 <= 0;
  214. else if(wire_mode == 2 & start_flag & !spi_busy)
  215. out_2 <= txd[5];
  216. else if(wire_mode == 2 & spi_busy & one_bit) begin
  217. case (bit_cnt)
  218. 0:out_2 <= txd[1];
  219. 1:out_2 <= txd[5];
  220. default: out_2 <= out_2;
  221. endcase
  222. end
  223. end
  224. always @(posedge clk) begin
  225. if(!rst_n)
  226. out_3 <= 0;
  227. else if(wire_mode == 2 & start_flag & !spi_busy)
  228. out_3 <= txd[4];
  229. else if(wire_mode == 2 & spi_busy & one_bit) begin
  230. case (bit_cnt)
  231. 0:out_3 <= txd[0];
  232. 1:out_3 <= txd[4];
  233. default: out_3 <= out_3;
  234. endcase
  235. end
  236. end
  237. // 每字节发送结束前一个比特拉高数据请求信号
  238. always @(posedge clk) begin
  239. if(!rst_n)
  240. data_req <= 0;
  241. else if(nxt_byte & one_bit)
  242. data_req <= 1;
  243. else
  244. data_req <= 0;
  245. end
  246. // 每字节发送结束的比特拉高数据有效信号
  247. always @(posedge clk) begin
  248. if(!rst_n)
  249. data_valid <= 0;
  250. else if(one_byte & one_bit)
  251. data_valid <= 1;
  252. else
  253. data_valid <= 0;
  254. end
  255. endmodule

        主机部分到此结束,接下来介绍从机的实现。

四、SPI从机的Verilog实现

        从机的实现方式有两种,一是直接使用SCK作为时钟,二是使用高速时钟对SCK进行过采样来获取SCK的时钟沿,第一种方式实现的设计较为紧凑,第二种方式实现的设计可靠性较高,对于I2C来说,SCL最快也不过几M的频率,很容易实现第二种方式,但是SPI就不一样了,可能SCK基本都在几十M甚至100M左右,这样的话采样时钟起码需要两百多M,对于FPGA来说已经算是很高的系统频率了,舍本逐末了属于是,所以对于SPI从机我们选择用第一种方式实现。

        至于具体的功能,此从机主要模拟flash,接收几个字节的数据后返回数据,我还在内部ram的前几个字节提前初始化了几个数据来观察返回数据的正确性。代码如下:

  1. module spi_slave#(
  2. parameter SAMPLE_EDGE = "rise" // "rise" or "fall",update edge is the opposite one
  3. )(
  4. input wire sck, // SPI串行时钟
  5. input wire cs_n, // SPI片选信号
  6. input wire mosi, // SPI从机输入
  7. output wire miso // SPI从机输出
  8. );
  9. localparam RX_BYTE_CNT = 4; // 接收字节数,指一般情况下指令 + 地址的字节数
  10. reg [7:0] ram [0:255]; // 内部RAM
  11. reg [7:0] addr; // 操作RAM地址
  12. reg rx_valid; // 接收数据有效信号
  13. reg [7:0] rx_buffer; // 接收数据缓存区
  14. reg [7:0] des_reg; // 接收数据存放目标寄存器
  15. reg [2:0] bit_cnt; // 操作比特计数
  16. reg [3:0] byte_cnt; // 操作字节计数
  17. reg slave_out; // 从机输出
  18. reg state; // 状态信号 0:接收 1:发送
  19. // 操作计数满一字节
  20. wire one_byte = &bit_cnt;
  21. // 接收状态结束
  22. wire rx_done = byte_cnt == RX_BYTE_CNT - 1;
  23. // 接收进行中
  24. wire rx_busy = state == 1'b0;
  25. // 发送进行中
  26. wire tx_busy = state == 1'b1;
  27. // 发送缓存区,指向内部RAM
  28. wire [7:0] tx_buffer = ram[addr];
  29. assign miso = slave_out;
  30. // 初始状态为接收,接收完成后开始发送
  31. always @(posedge sck or posedge cs_n) begin
  32. if(cs_n)
  33. state <= 1'b0;
  34. else if(rx_done & one_byte)
  35. state <= 1'b1;
  36. end
  37. generate
  38. // 上升沿采样,即可满足模式0和模式3,其余逻辑简单,懒得注释了
  39. if(SAMPLE_EDGE == "rise") begin:MODE_0_3
  40. always @(posedge sck or posedge cs_n) begin
  41. if(cs_n)
  42. addr <= 1'b0;
  43. else if(tx_busy & one_byte)
  44. addr <= addr + 1;
  45. end
  46. always @(posedge sck or posedge cs_n) begin
  47. if(cs_n)
  48. des_reg <= 1'b0;
  49. else if(rx_valid)
  50. des_reg <= rx_buffer;
  51. end
  52. always @(posedge sck or posedge cs_n) begin
  53. if(cs_n)
  54. bit_cnt <= 0;
  55. else
  56. bit_cnt <= bit_cnt + 1;
  57. end
  58. always @(posedge sck or posedge cs_n) begin
  59. if(cs_n)
  60. byte_cnt <= 0;
  61. else if(one_byte)
  62. byte_cnt <= (rx_done) ? byte_cnt : byte_cnt + 1;
  63. end
  64. always @(posedge sck or posedge cs_n) begin
  65. if(cs_n) begin
  66. rx_valid <= 0;
  67. rx_buffer <= 0;
  68. end else if(rx_busy) begin
  69. rx_valid <= one_byte;
  70. case (bit_cnt)
  71. 0:rx_buffer[7] <= mosi;
  72. 1:rx_buffer[6] <= mosi;
  73. 2:rx_buffer[5] <= mosi;
  74. 3:rx_buffer[4] <= mosi;
  75. 4:rx_buffer[3] <= mosi;
  76. 5:rx_buffer[2] <= mosi;
  77. 6:rx_buffer[1] <= mosi;
  78. 7:rx_buffer[0] <= mosi;
  79. default:rx_buffer <= rx_buffer;
  80. endcase
  81. end
  82. end
  83. always @(negedge sck or posedge cs_n) begin
  84. if(cs_n)
  85. slave_out <= 0;
  86. else if(tx_busy) begin
  87. case (bit_cnt)
  88. 0:slave_out <= tx_buffer[7];
  89. 1:slave_out <= tx_buffer[6];
  90. 2:slave_out <= tx_buffer[5];
  91. 3:slave_out <= tx_buffer[4];
  92. 4:slave_out <= tx_buffer[3];
  93. 5:slave_out <= tx_buffer[2];
  94. 6:slave_out <= tx_buffer[1];
  95. 7:slave_out <= tx_buffer[0];
  96. default:slave_out <= slave_out;
  97. endcase
  98. end
  99. end
  100. // 下降沿采样,即可满足模式1和模式2
  101. end else if(SAMPLE_EDGE == "fall") begin:MODE_1_2
  102. always @(negedge sck or posedge cs_n) begin
  103. if(cs_n)
  104. addr <= 1'b0;
  105. else if(tx_busy & one_byte)
  106. addr <= addr + 1;
  107. end
  108. always @(negedge sck or posedge cs_n) begin
  109. if(cs_n)
  110. des_reg <= 1'b0;
  111. else if(rx_busy & one_byte)
  112. des_reg <= rx_buffer;
  113. end
  114. always @(negedge sck or posedge cs_n) begin
  115. if(cs_n)
  116. bit_cnt <= 0;
  117. else
  118. bit_cnt <= bit_cnt + 1;
  119. end
  120. always @(negedge sck or posedge cs_n) begin
  121. if(cs_n)
  122. byte_cnt <= 0;
  123. else if(one_byte)
  124. byte_cnt <= (rx_done) ? byte_cnt : byte_cnt + 1;
  125. end
  126. always @(negedge sck or posedge cs_n) begin
  127. if(cs_n) begin
  128. rx_valid <= 0;
  129. rx_buffer <= 0;
  130. end else if(rx_busy) begin
  131. rx_valid <= one_byte;
  132. case (bit_cnt)
  133. 0:rx_buffer[7] <= mosi;
  134. 1:rx_buffer[6] <= mosi;
  135. 2:rx_buffer[5] <= mosi;
  136. 3:rx_buffer[4] <= mosi;
  137. 4:rx_buffer[3] <= mosi;
  138. 5:rx_buffer[2] <= mosi;
  139. 6:rx_buffer[1] <= mosi;
  140. 7:rx_buffer[0] <= mosi;
  141. default:rx_buffer <= rx_buffer;
  142. endcase
  143. end
  144. end
  145. always @(posedge sck or posedge cs_n) begin
  146. if(cs_n)
  147. slave_out <= 0;
  148. else if(tx_busy) begin
  149. case (bit_cnt)
  150. 0:slave_out <= tx_buffer[7];
  151. 1:slave_out <= tx_buffer[6];
  152. 2:slave_out <= tx_buffer[5];
  153. 3:slave_out <= tx_buffer[4];
  154. 4:slave_out <= tx_buffer[3];
  155. 5:slave_out <= tx_buffer[2];
  156. 6:slave_out <= tx_buffer[1];
  157. 7:slave_out <= tx_buffer[0];
  158. default:slave_out <= slave_out;
  159. endcase
  160. end
  161. end
  162. end
  163. endgenerate
  164. // 初始化RAM数据
  165. initial begin:ram_initialize
  166. integer i;
  167. ram[0] <= 8'h53;
  168. ram[1] <= 8'h8b;
  169. ram[2] <= 8'h9c;
  170. ram[3] <= 8'hea;
  171. for (i = 4;i < 256;i = i + 1) begin
  172. ram[i] <= 0;
  173. end
  174. end
  175. endmodule

        接收的数据都写入了des_reg中,实际要使用时,只需要根据条件把des_reg改为别的寄存器或内存即可正确存储数据。

        我一般习惯使用同步复位,但是由于此从机是使用SCK作为时钟,无法同步复位,所以将片选信号作为异步复位来对整个模块进行复位。

        主机从机都实现了,接下来对两个模块进行仿真。

五、SPI主从仿真

        和上一篇的I2C主机类似,SPI主机模块也是通过寄存器控制的,像上次那样通过vio对各寄存器进行读写就可以完成对模块的操作,但是这样操作太麻烦了,一般对寄存器的操作是交给PS端来做的,我们可以写一个简单的类似MCU的模块,通过指令来控制读写寄存器,这样每次复位MCU就会从头到尾执行一遍我们提前写好的指令,就不用一个一个寄存器去操作了。

        MCU代码如下:

  1. `timescale 1ns / 1ps
  2. module mcu(
  3. input clk,
  4. input rst_n,
  5. input [23:0] ir,
  6. output [7:0] pc,
  7. output wr_en,
  8. output [7:0] wr_data,
  9. output rd_en,
  10. input [7:0] rd_data,
  11. output en,
  12. output we,
  13. output [7:0] din,
  14. input [7:0] dout,
  15. output [7:0] addr
  16. );
  17. localparam INITIAL = 8'b1111_0000;
  18. localparam FIFO_WR = 8'b0000_0001;
  19. localparam FIFO_RD = 8'b0000_0010;
  20. localparam RAM_WR = 8'b0000_1001;
  21. localparam RAM_RD = 8'b0000_1010;
  22. localparam JUMP = 8'b1000_0000;
  23. reg init;
  24. reg run;
  25. // [23:16]:opcode [15:8]:data [7:0]:addr
  26. reg [23:0] r_instr;
  27. reg [7:0] r_pcntr;
  28. reg [7:0] op_code;
  29. reg [7:0] op_data;
  30. reg [7:0] op_addr;
  31. reg fifo_we;
  32. reg [7:0] fifo_wd;
  33. reg fifo_re;
  34. reg [7:0] fifo_rd;
  35. reg ram_en;
  36. reg ram_we;
  37. reg [7:0] ram_di;
  38. reg [7:0] ram_ad;
  39. always @(posedge clk) begin
  40. if(!rst_n) begin
  41. init <= 1'b1;
  42. run <= 1'b0;
  43. end else if(&r_pcntr) begin
  44. init <= 1'b0;
  45. run <= 1'b0;
  46. end else if(init) begin
  47. init <= 1'b0;
  48. run <= 1'b1;
  49. end
  50. end
  51. // fetch
  52. always @(posedge clk) begin
  53. if(init) begin
  54. r_instr <= 0;
  55. r_pcntr <= 0;
  56. end else if(run) begin
  57. r_instr <= ir;
  58. r_pcntr <= ir[23:16] == JUMP ? ir[7:0] : r_pcntr + 1;
  59. end
  60. end
  61. // decode
  62. always @(posedge clk) begin
  63. if(init) begin
  64. op_code <= 0;
  65. op_data <= 0;
  66. op_addr <= 0;
  67. end else if(run) begin
  68. op_code <= r_instr[23:16];
  69. op_data <= r_instr[15:8];
  70. op_addr <= r_instr[7:0];
  71. end
  72. end
  73. // execute
  74. always @(posedge clk) begin
  75. if(init) begin
  76. fifo_we <= 0;
  77. fifo_wd <= 0;
  78. fifo_re <= 0;
  79. fifo_rd <= 0;
  80. ram_en <= 0;
  81. ram_we <= 0;
  82. ram_di <= 0;
  83. ram_ad <= 0;
  84. end else if(run) begin
  85. case (op_code)
  86. INITIAL:begin
  87. fifo_we <= 0;
  88. fifo_wd <= 0;
  89. fifo_re <= 0;
  90. fifo_rd <= 0;
  91. ram_en <= 0;
  92. ram_we <= 0;
  93. ram_di <= 0;
  94. ram_ad <= 0;
  95. end
  96. FIFO_WR:begin
  97. fifo_we <= 1'b1;
  98. fifo_wd <= op_data;
  99. end
  100. FIFO_RD:begin
  101. fifo_re <= 1'b1;
  102. end
  103. RAM_WR:begin
  104. ram_en <= 1'b1;
  105. ram_we <= 1'b1;
  106. ram_di <= op_data;
  107. ram_ad <= op_addr;
  108. end
  109. RAM_RD:begin
  110. ram_en <= 1'b1;
  111. ram_ad <= op_addr;
  112. end
  113. default:begin
  114. fifo_we <= 1'b0;
  115. fifo_re <= 1'b0;
  116. ram_en <= 1'b0;
  117. ram_we <= 1'b0;
  118. end
  119. endcase
  120. end
  121. end
  122. assign pc = r_pcntr;
  123. assign wr_en = fifo_we;
  124. assign wr_data = fifo_wd;
  125. assign rd_en = fifo_re;
  126. assign en = ram_en;
  127. assign we = ram_we;
  128. assign din = ram_di;
  129. assign addr = ram_ad;
  130. endmodule

        一个极其简单的三级流水线架构,只能实现ram读写和fifo读写,用来控制寄存器足够了。

        Vivado有一个很好用的功能,可以综合initial块以实现对寄存器的初始化,我们可以用这种方法来预设mcu的指令,编写一个简单的rom,然后对每个地址的内容进行初始化:

  1. `timescale 1ns / 1ps
  2. module irom(
  3. input clk,
  4. input en,
  5. input [7:0] addr,
  6. output [23:0] dout
  7. );
  8. reg [23:0] r_dout;
  9. (* ROM_STYLE = "distributed" *)
  10. reg [23:0] rom [0:255];
  11. assign dout = r_dout;
  12. always @(posedge clk) begin
  13. if(en)
  14. r_dout <= rom[addr];
  15. end
  16. localparam INITIAL = 8'b1111_0000;
  17. localparam FIFO_WR = 8'b0000_0001;
  18. localparam FIFO_RD = 8'b0000_0010;
  19. localparam RAM_WR = 8'b0000_1001;
  20. localparam RAM_RD = 8'b0000_1010;
  21. localparam JUMP = 8'b1000_0000;
  22. localparam OPCODE = 8'b0000_0000;
  23. localparam OPDATA = 8'b0000_0000;
  24. localparam OPADDR = 8'b0000_0000;
  25. initial begin
  26. rom[0] <= {INITIAL,OPDATA,OPADDR};
  27. rom[1] <= {OPCODE,OPDATA,OPADDR};
  28. rom[2] <= {RAM_WR,8'h85,8'h00};
  29. rom[3] <= {RAM_WR,8'h90,8'h01};
  30. rom[4] <= {RAM_WR,8'h4a,8'h02};
  31. rom[5] <= {RAM_WR,8'h5c,8'h03};
  32. rom[6] <= {RAM_WR,8'hff,8'h04};
  33. rom[7] <= {RAM_WR,8'hff,8'h05};
  34. rom[8] <= {RAM_WR,8'hff,8'h06};
  35. rom[9] <= {RAM_WR,8'hff,8'h07};
  36. rom[10] <= {OPCODE,OPDATA,OPADDR};
  37. rom[11] <= {RAM_WR,8'h08,8'h46};
  38. rom[12] <= {OPCODE,OPDATA,OPADDR};
  39. rom[13] <= {RAM_WR,8'b11111110,8'h47};
  40. rom[14] <= {OPCODE,OPDATA,OPADDR};
  41. rom[15] <= {RAM_WR,8'h00,8'h40};
  42. rom[16] <= {OPCODE,OPDATA,OPADDR};
  43. rom[17] <= {RAM_WR,8'h00,8'h41};
  44. rom[18] <= {OPCODE,OPDATA,OPADDR};
  45. rom[19] <= {RAM_WR,8'h80,8'h44};
  46. rom[20] <= {OPCODE,OPDATA,OPADDR};
  47. rom[21] <= {OPCODE,OPDATA,OPADDR};
  48. rom[22] <= {OPCODE,OPDATA,OPADDR};
  49. rom[23] <= {OPCODE,OPDATA,OPADDR};
  50. rom[24] <= {OPCODE,OPDATA,OPADDR};
  51. rom[25] <= {OPCODE,OPDATA,OPADDR};
  52. rom[26] <= {OPCODE,OPDATA,OPADDR};
  53. rom[27] <= {OPCODE,OPDATA,OPADDR};
  54. rom[28] <= {OPCODE,OPDATA,OPADDR};
  55. rom[29] <= {OPCODE,OPDATA,OPADDR};
  56. rom[30] <= {OPCODE,OPDATA,OPADDR};
  57. rom[31] <= {OPCODE,OPDATA,OPADDR};
  58. rom[32] <= {OPCODE,OPDATA,OPADDR};
  59. rom[33] <= {OPCODE,OPDATA,OPADDR};
  60. rom[34] <= {OPCODE,OPDATA,OPADDR};
  61. rom[35] <= {OPCODE,OPDATA,OPADDR};
  62. rom[36] <= {OPCODE,OPDATA,OPADDR};
  63. rom[37] <= {OPCODE,OPDATA,OPADDR};
  64. rom[38] <= {OPCODE,OPDATA,OPADDR};
  65. rom[39] <= {OPCODE,OPDATA,OPADDR};
  66. rom[40] <= {OPCODE,OPDATA,OPADDR};
  67. rom[41] <= {OPCODE,OPDATA,OPADDR};
  68. rom[42] <= {OPCODE,OPDATA,OPADDR};
  69. rom[43] <= {OPCODE,OPDATA,OPADDR};
  70. rom[44] <= {OPCODE,OPDATA,OPADDR};
  71. rom[45] <= {OPCODE,OPDATA,OPADDR};
  72. rom[46] <= {OPCODE,OPDATA,OPADDR};
  73. rom[47] <= {OPCODE,OPDATA,OPADDR};
  74. rom[48] <= {OPCODE,OPDATA,OPADDR};
  75. rom[49] <= {OPCODE,OPDATA,OPADDR};
  76. rom[50] <= {OPCODE,OPDATA,OPADDR};
  77. rom[51] <= {OPCODE,OPDATA,OPADDR};
  78. rom[52] <= {OPCODE,OPDATA,OPADDR};
  79. rom[53] <= {OPCODE,OPDATA,OPADDR};
  80. rom[54] <= {OPCODE,OPDATA,OPADDR};
  81. rom[55] <= {OPCODE,OPDATA,OPADDR};
  82. rom[56] <= {OPCODE,OPDATA,OPADDR};
  83. rom[57] <= {OPCODE,OPDATA,OPADDR};
  84. rom[58] <= {OPCODE,OPDATA,OPADDR};
  85. rom[59] <= {OPCODE,OPDATA,OPADDR};
  86. rom[60] <= {OPCODE,OPDATA,OPADDR};
  87. rom[61] <= {OPCODE,OPDATA,OPADDR};
  88. rom[62] <= {OPCODE,OPDATA,OPADDR};
  89. rom[63] <= {OPCODE,OPDATA,OPADDR};
  90. rom[64] <= {OPCODE,OPDATA,OPADDR};
  91. rom[65] <= {OPCODE,OPDATA,OPADDR};
  92. rom[66] <= {OPCODE,OPDATA,OPADDR};
  93. rom[67] <= {OPCODE,OPDATA,OPADDR};
  94. rom[68] <= {OPCODE,OPDATA,OPADDR};
  95. rom[69] <= {OPCODE,OPDATA,OPADDR};
  96. rom[70] <= {OPCODE,OPDATA,OPADDR};
  97. rom[71] <= {OPCODE,OPDATA,OPADDR};
  98. rom[72] <= {OPCODE,OPDATA,OPADDR};
  99. rom[73] <= {OPCODE,OPDATA,OPADDR};
  100. rom[74] <= {OPCODE,OPDATA,OPADDR};
  101. rom[75] <= {OPCODE,OPDATA,OPADDR};
  102. rom[76] <= {OPCODE,OPDATA,OPADDR};
  103. rom[77] <= {OPCODE,OPDATA,OPADDR};
  104. rom[78] <= {OPCODE,OPDATA,OPADDR};
  105. rom[79] <= {OPCODE,OPDATA,OPADDR};
  106. rom[80] <= {OPCODE,OPDATA,OPADDR};
  107. rom[81] <= {OPCODE,OPDATA,OPADDR};
  108. rom[82] <= {OPCODE,OPDATA,OPADDR};
  109. rom[83] <= {OPCODE,OPDATA,OPADDR};
  110. rom[84] <= {OPCODE,OPDATA,OPADDR};
  111. rom[85] <= {OPCODE,OPDATA,OPADDR};
  112. rom[86] <= {OPCODE,OPDATA,OPADDR};
  113. rom[87] <= {OPCODE,OPDATA,OPADDR};
  114. rom[88] <= {OPCODE,OPDATA,OPADDR};
  115. rom[89] <= {OPCODE,OPDATA,OPADDR};
  116. rom[90] <= {OPCODE,OPDATA,OPADDR};
  117. rom[91] <= {OPCODE,OPDATA,OPADDR};
  118. rom[92] <= {OPCODE,OPDATA,OPADDR};
  119. rom[93] <= {OPCODE,OPDATA,OPADDR};
  120. rom[94] <= {OPCODE,OPDATA,OPADDR};
  121. rom[95] <= {OPCODE,OPDATA,OPADDR};
  122. rom[96] <= {OPCODE,OPDATA,OPADDR};
  123. rom[97] <= {OPCODE,OPDATA,OPADDR};
  124. rom[98] <= {OPCODE,OPDATA,OPADDR};
  125. rom[99] <= {OPCODE,OPDATA,OPADDR};
  126. rom[100] <= {OPCODE,OPDATA,OPADDR};
  127. rom[101] <= {OPCODE,OPDATA,OPADDR};
  128. rom[102] <= {OPCODE,OPDATA,OPADDR};
  129. rom[103] <= {OPCODE,OPDATA,OPADDR};
  130. rom[104] <= {OPCODE,OPDATA,OPADDR};
  131. rom[105] <= {OPCODE,OPDATA,OPADDR};
  132. rom[106] <= {OPCODE,OPDATA,OPADDR};
  133. rom[107] <= {OPCODE,OPDATA,OPADDR};
  134. rom[108] <= {OPCODE,OPDATA,OPADDR};
  135. rom[109] <= {OPCODE,OPDATA,OPADDR};
  136. rom[110] <= {OPCODE,OPDATA,OPADDR};
  137. rom[111] <= {OPCODE,OPDATA,OPADDR};
  138. rom[112] <= {OPCODE,OPDATA,OPADDR};
  139. rom[113] <= {OPCODE,OPDATA,OPADDR};
  140. rom[114] <= {OPCODE,OPDATA,OPADDR};
  141. rom[115] <= {OPCODE,OPDATA,OPADDR};
  142. rom[116] <= {OPCODE,OPDATA,OPADDR};
  143. rom[117] <= {OPCODE,OPDATA,OPADDR};
  144. rom[118] <= {OPCODE,OPDATA,OPADDR};
  145. rom[119] <= {OPCODE,OPDATA,OPADDR};
  146. rom[120] <= {OPCODE,OPDATA,OPADDR};
  147. rom[121] <= {OPCODE,OPDATA,OPADDR};
  148. rom[122] <= {OPCODE,OPDATA,OPADDR};
  149. rom[123] <= {OPCODE,OPDATA,OPADDR};
  150. rom[124] <= {OPCODE,OPDATA,OPADDR};
  151. rom[125] <= {OPCODE,OPDATA,OPADDR};
  152. rom[126] <= {OPCODE,OPDATA,OPADDR};
  153. rom[127] <= {OPCODE,OPDATA,OPADDR};
  154. rom[128] <= {OPCODE,OPDATA,OPADDR};
  155. rom[129] <= {OPCODE,OPDATA,OPADDR};
  156. rom[130] <= {OPCODE,OPDATA,OPADDR};
  157. rom[131] <= {OPCODE,OPDATA,OPADDR};
  158. rom[132] <= {OPCODE,OPDATA,OPADDR};
  159. rom[133] <= {OPCODE,OPDATA,OPADDR};
  160. rom[134] <= {OPCODE,OPDATA,OPADDR};
  161. rom[135] <= {OPCODE,OPDATA,OPADDR};
  162. rom[136] <= {OPCODE,OPDATA,OPADDR};
  163. rom[137] <= {OPCODE,OPDATA,OPADDR};
  164. rom[138] <= {OPCODE,OPDATA,OPADDR};
  165. rom[139] <= {OPCODE,OPDATA,OPADDR};
  166. rom[140] <= {OPCODE,OPDATA,OPADDR};
  167. rom[141] <= {OPCODE,OPDATA,OPADDR};
  168. rom[142] <= {OPCODE,OPDATA,OPADDR};
  169. rom[143] <= {OPCODE,OPDATA,OPADDR};
  170. rom[144] <= {OPCODE,OPDATA,OPADDR};
  171. rom[145] <= {OPCODE,OPDATA,OPADDR};
  172. rom[146] <= {OPCODE,OPDATA,OPADDR};
  173. rom[147] <= {OPCODE,OPDATA,OPADDR};
  174. rom[148] <= {OPCODE,OPDATA,OPADDR};
  175. rom[149] <= {OPCODE,OPDATA,OPADDR};
  176. rom[150] <= {OPCODE,OPDATA,OPADDR};
  177. rom[151] <= {OPCODE,OPDATA,OPADDR};
  178. rom[152] <= {OPCODE,OPDATA,OPADDR};
  179. rom[153] <= {OPCODE,OPDATA,OPADDR};
  180. rom[154] <= {OPCODE,OPDATA,OPADDR};
  181. rom[155] <= {OPCODE,OPDATA,OPADDR};
  182. rom[156] <= {OPCODE,OPDATA,OPADDR};
  183. rom[157] <= {OPCODE,OPDATA,OPADDR};
  184. rom[158] <= {OPCODE,OPDATA,OPADDR};
  185. rom[159] <= {OPCODE,OPDATA,OPADDR};
  186. rom[160] <= {OPCODE,OPDATA,OPADDR};
  187. rom[161] <= {OPCODE,OPDATA,OPADDR};
  188. rom[162] <= {OPCODE,OPDATA,OPADDR};
  189. rom[163] <= {OPCODE,OPDATA,OPADDR};
  190. rom[164] <= {OPCODE,OPDATA,OPADDR};
  191. rom[165] <= {OPCODE,OPDATA,OPADDR};
  192. rom[166] <= {OPCODE,OPDATA,OPADDR};
  193. rom[167] <= {OPCODE,OPDATA,OPADDR};
  194. rom[168] <= {OPCODE,OPDATA,OPADDR};
  195. rom[169] <= {OPCODE,OPDATA,OPADDR};
  196. rom[170] <= {OPCODE,OPDATA,OPADDR};
  197. rom[171] <= {OPCODE,OPDATA,OPADDR};
  198. rom[172] <= {OPCODE,OPDATA,OPADDR};
  199. rom[173] <= {OPCODE,OPDATA,OPADDR};
  200. rom[174] <= {OPCODE,OPDATA,OPADDR};
  201. rom[175] <= {OPCODE,OPDATA,OPADDR};
  202. rom[176] <= {OPCODE,OPDATA,OPADDR};
  203. rom[177] <= {OPCODE,OPDATA,OPADDR};
  204. rom[178] <= {OPCODE,OPDATA,OPADDR};
  205. rom[179] <= {OPCODE,OPDATA,OPADDR};
  206. rom[180] <= {OPCODE,OPDATA,OPADDR};
  207. rom[181] <= {OPCODE,OPDATA,OPADDR};
  208. rom[182] <= {OPCODE,OPDATA,OPADDR};
  209. rom[183] <= {OPCODE,OPDATA,OPADDR};
  210. rom[184] <= {OPCODE,OPDATA,OPADDR};
  211. rom[185] <= {OPCODE,OPDATA,OPADDR};
  212. rom[186] <= {OPCODE,OPDATA,OPADDR};
  213. rom[187] <= {OPCODE,OPDATA,OPADDR};
  214. rom[188] <= {OPCODE,OPDATA,OPADDR};
  215. rom[189] <= {OPCODE,OPDATA,OPADDR};
  216. rom[190] <= {OPCODE,OPDATA,OPADDR};
  217. rom[191] <= {OPCODE,OPDATA,OPADDR};
  218. rom[192] <= {OPCODE,OPDATA,OPADDR};
  219. rom[193] <= {OPCODE,OPDATA,OPADDR};
  220. rom[194] <= {OPCODE,OPDATA,OPADDR};
  221. rom[195] <= {OPCODE,OPDATA,OPADDR};
  222. rom[196] <= {OPCODE,OPDATA,OPADDR};
  223. rom[197] <= {OPCODE,OPDATA,OPADDR};
  224. rom[198] <= {OPCODE,OPDATA,OPADDR};
  225. rom[199] <= {OPCODE,OPDATA,OPADDR};
  226. rom[200] <= {OPCODE,OPDATA,OPADDR};
  227. rom[201] <= {OPCODE,OPDATA,OPADDR};
  228. rom[202] <= {OPCODE,OPDATA,OPADDR};
  229. rom[203] <= {OPCODE,OPDATA,OPADDR};
  230. rom[204] <= {OPCODE,OPDATA,OPADDR};
  231. rom[205] <= {OPCODE,OPDATA,OPADDR};
  232. rom[206] <= {OPCODE,OPDATA,OPADDR};
  233. rom[207] <= {OPCODE,OPDATA,OPADDR};
  234. rom[208] <= {OPCODE,OPDATA,OPADDR};
  235. rom[209] <= {OPCODE,OPDATA,OPADDR};
  236. rom[210] <= {OPCODE,OPDATA,OPADDR};
  237. rom[211] <= {OPCODE,OPDATA,OPADDR};
  238. rom[212] <= {OPCODE,OPDATA,OPADDR};
  239. rom[213] <= {OPCODE,OPDATA,OPADDR};
  240. rom[214] <= {OPCODE,OPDATA,OPADDR};
  241. rom[215] <= {OPCODE,OPDATA,OPADDR};
  242. rom[216] <= {OPCODE,OPDATA,OPADDR};
  243. rom[217] <= {OPCODE,OPDATA,OPADDR};
  244. rom[218] <= {OPCODE,OPDATA,OPADDR};
  245. rom[219] <= {OPCODE,OPDATA,OPADDR};
  246. rom[220] <= {OPCODE,OPDATA,OPADDR};
  247. rom[221] <= {OPCODE,OPDATA,OPADDR};
  248. rom[222] <= {OPCODE,OPDATA,OPADDR};
  249. rom[223] <= {OPCODE,OPDATA,OPADDR};
  250. rom[224] <= {OPCODE,OPDATA,OPADDR};
  251. rom[225] <= {OPCODE,OPDATA,OPADDR};
  252. rom[226] <= {OPCODE,OPDATA,OPADDR};
  253. rom[227] <= {OPCODE,OPDATA,OPADDR};
  254. rom[228] <= {OPCODE,OPDATA,OPADDR};
  255. rom[229] <= {OPCODE,OPDATA,OPADDR};
  256. rom[230] <= {OPCODE,OPDATA,OPADDR};
  257. rom[231] <= {OPCODE,OPDATA,OPADDR};
  258. rom[232] <= {OPCODE,OPDATA,OPADDR};
  259. rom[233] <= {OPCODE,OPDATA,OPADDR};
  260. rom[234] <= {OPCODE,OPDATA,OPADDR};
  261. rom[235] <= {OPCODE,OPDATA,OPADDR};
  262. rom[236] <= {OPCODE,OPDATA,OPADDR};
  263. rom[237] <= {OPCODE,OPDATA,OPADDR};
  264. rom[238] <= {OPCODE,OPDATA,OPADDR};
  265. rom[239] <= {OPCODE,OPDATA,OPADDR};
  266. rom[240] <= {OPCODE,OPDATA,OPADDR};
  267. rom[241] <= {OPCODE,OPDATA,OPADDR};
  268. rom[242] <= {OPCODE,OPDATA,OPADDR};
  269. rom[243] <= {OPCODE,OPDATA,OPADDR};
  270. rom[244] <= {OPCODE,OPDATA,OPADDR};
  271. rom[245] <= {OPCODE,OPDATA,OPADDR};
  272. rom[246] <= {OPCODE,OPDATA,OPADDR};
  273. rom[247] <= {OPCODE,OPDATA,OPADDR};
  274. rom[248] <= {OPCODE,OPDATA,OPADDR};
  275. rom[249] <= {OPCODE,OPDATA,OPADDR};
  276. rom[250] <= {OPCODE,OPDATA,OPADDR};
  277. rom[251] <= {OPCODE,OPDATA,OPADDR};
  278. rom[252] <= {OPCODE,OPDATA,OPADDR};
  279. rom[253] <= {OPCODE,OPDATA,OPADDR};
  280. rom[254] <= {OPCODE,OPDATA,OPADDR};
  281. rom[255] <= {OPCODE,OPDATA,OPADDR};
  282. end
  283. endmodule

        这个过程就有点像写软件代码了,使用的还是自己的指令集。

        最终实现的效果应该是主机向从机写入85 90 4a 5c四个字节的数据,然后写入四个ff的同时读出53 8b 9c ea四个字节的数据。以下是仿真波形:

        首先看上电复位后mcu的操作,从0地址开始读出指令然后顺序执行,读写对应地址的寄存器。寄存器控制SPI开始操作之后,SPI的波形如下:

        结果符合预期。

六、SPI主机板级验证

        最后就是上板验证,本次验证将使用SPI主机去读nor flash的ID,flash的型号是W25Q64,阅读芯片手册,得知其读ID的指令为90,还需要写入24位0xXXXX00的地址,然后就可以返回生产ID和器件ID:

        在手册中得知生产ID和器件ID分别是0xEF和0x16。

        在irom中修改指令为写入90 00 00 00,其他保持不变,在顶层中例化irom,mcu和spi_master,综合实现生成比特流。

  1. `timescale 1ns / 1ps
  2. module top(
  3. input sys_clk,
  4. input sys_rst_n,
  5. output led,
  6. output sck,
  7. output mosi,
  8. input miso,
  9. output ss
  10. );
  11. wire clk_50M;
  12. wire [23:0] ir;
  13. wire [7:0] pc;
  14. wire en;
  15. wire we;
  16. wire [7:0] din;
  17. wire [7:0] dout;
  18. wire [7:0] addr;
  19. wire [7:0] cs_n;
  20. assign ss = cs_n[0];
  21. assign led = 1'b1;
  22. BUFG BUFG_inst(
  23. .I(sys_clk),
  24. .O(clk_50M)
  25. );
  26. irom irom_inst(
  27. .clk(clk_50M),
  28. .en(1'b1),
  29. .addr(pc),
  30. .dout(ir));
  31. mcu mcu_inst(
  32. .clk(clk_50M),
  33. .rst_n(sys_rst_n),
  34. .ir(ir),
  35. .pc(pc),
  36. .en(en),
  37. .we(we),
  38. .din(din),
  39. .dout(dout),
  40. .addr(addr));
  41. m_spi_top m_spi_top_inst(
  42. .clk(clk_50M),
  43. .rst_n(sys_rst_n),
  44. .en(en),
  45. .we(we),
  46. .din(din),
  47. .dout(dout),
  48. .addr(addr),
  49. .sck(sck),
  50. .mosi(mosi),
  51. .miso(miso),
  52. .cs_n(cs_n));
  53. ila_spi ila_inst(
  54. .clk(clk_50M),
  55. .probe0({sck,ss,mosi,miso})
  56. );
  57. endmodule

        将比特流下载到FPGA后,抓取波形如下:

        如图所示,正确地读出了ID,因为操作长度为8,所以又重复读出了两个字节的ID。

        三个常见低速接口终于更完了,接下来打算更新图像接口,常见的包括VGA时序,BT1120时序,DVI接口,HDMI接口,SDI接口,MIPI CSI和DSI等,欢迎持续关注!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/497153
推荐阅读
相关标签
  

闽ICP备14008679号