目录

  • 1. 功能介绍
    • 1.1. 全双工 串行 同步
      • 波特率
      • 只能全双工
    • 1.2. 工作模式
      • Master/Slave Select Register(MSTR)
      • Clock Polarity(CPOL)与 Clock Phase(CPHA)
      • CPHA的意义
    • 1.3. SPI与UART的区别
  • 2. 架构
    • 2.1. spi
    • 2.2. baud_clk_gen
  • 3. 逻辑设计
    • 3.1. spi_master
    • 3.2. spi_slave
    • 3.3. spi
    • 3.4. spi_tb
  • 4. 测试
    • 4.1. spi_master与spi_slave1、spi_slave2的pingpong测试

《SPI Block Guide V04.01, Motorola, Inc》
SPI原理超详细讲解—值得一看
arduino教程-9. 串行外设接口(spi)
SPI通信总线原理及工作过程
《FPGA Verilog开发实战指南——基于Altera EP4CE10》


1. 功能介绍

串行外设接口(Serial Peripheral Interface, SPI)协议是Motorola提出的一种全双工、串行、同步通信协议,广泛用于 电可擦编程只读存储器(Electrically Erasable Programmable Read-Only Memory,EEPROM)、Flash、实时时钟(Real Time Clock,RTC)、数模转换器ADC、数字信号处理器DSP以及数字信号解码器上。

相比于UART,SPI的速率较快,但是缺陷与UART相同——没有握手机制确认数据是否接受,故数据可靠性存在缺陷

下面细讲

1.1. 全双工 串行 同步

可以通过介绍想一下SPI的信号啊,全双工串行说明可以同时单bit读写,所以一个SPI模块一定有两根线用于发送、接受。

而同步则说明SPI模块间通信是在同一个时钟域下,所以还有一个时钟信号。

如下图

其中SCK是同步时钟,MOSI(Master Output Slave Input)和MISO(Master Input Slave Output)是全双工串行总线,CSn则用于对Slave使能、低电平有效。而Master和Slave则表示主机和从机。

波特率

SPI速度比UART快就体现在波特率上,UART是在异步时钟常用的115.2kbps嘛最高可达3Mbps,而SPI由于工作在同步时钟的波特率从12.21kHZ~12.5MHZ不等。

别忘了SPI是同步串口,所以波特率是通过通过同步时钟保证的。

常用UART速率就是115200HZ,SPI则是3MHZ,I2C就是1MHZ左右。
而且UART数据位只能是5~7 bit,而SPI则可以任意
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)

别忘了时钟频率/波特率 = 每bit变化的时钟周期数,对于SPI来说就是SCK每周期的时钟周期数

只能全双工

注意SPI是只能全双工工作,不存在半双工工作模式。

UART也是全双工,即发送的同时可以接收。但是呢UART也可以变成半双工,即发送的同时不接收,接收的时候可以不发送。
而SPI只能全双工的意思是,发送的同时必须接收,要么就既不发送也不接收,为什么呢?

这个与SPI的发送接收机制有关,实际上SPI的发送和接收是基于一个移位寄存器实现的。

如下图Master移位寄存器和Slave移位寄存器均在SCK时钟域下

工作过程是这样的:

● Master将/SS拉低开启与Slave通信

● Master和Slave各自将缓存Memory数据放入各自的移位寄存器中

● SCK每一拍将Master移位寄存器的一位通过MOSI传送至Slave移位寄存器的中,Slave移位寄存器的一位通过MISO传送至Master移位寄存器的中

● 经过8个SCK周期,实现了Master与Slave的数据交换。

本质上是Master通过/SS控制对Slave的读和写。
所以说Master只写不读,就不用采样交换之后的寄存器值。只读不写,就可以给Slave发空数据,然后采样交换之后的寄存器值。当然也可以又读又写。

1.2. 工作模式

之前的功能除了同步时钟,其他的与UART没什么区别,但是SPI可以配置成多种工作模式

Master/Slave Select Register(MSTR)

MSTR是SPI control register 1 的某一位,用于确定该SPI是Master还是Slave,也就是说SPI具有一主多从工作模式

一个Master SPI 可连接多个Slave SPI,Master通过片选信号线选择与哪几个Slave建立通信,如下图

而I2C、EMIF则是通过地址选择相应的Slave

注意MSTR决定了是MASTER SPI还是SLAVE SPI,如果是MASTER那么SCK、MOSI、CSn是输出,MISO是输入,如果是SLAVE那么SCK、MOSI、CSn是输入,MISO则是输出
片选不仅仅是选择了哪个Slave进行通信,片选信号拉低也是与该Slave通信开始的标志。

Clock Polarity(CPOL)与 Clock Phase(CPHA)

CPOL与CPHA都属于SPI control register 1,均为1bit,分别用于确定该SPI 作为Master时空闲(与任何Slave都没有通信)的SCK电平通信时对输入信号采样的SCK沿、对输出信号驱动的SCK沿

此处的采样是指将输入采样至移位寄存器,驱动是指将移位寄存器的值驱动给输出。
换句话说,CPHA决定的是 对输入采样并驱动给移位寄存器的SCK边沿 和 对移位寄存器采样并驱动给输出的SCK边沿

如下表

工作方式 CPOL CPHA MSTR 说明
SP0 1'b0 1'b0 1'b0 作为Master,空闲时SCK电平为低,上升沿MISO采样,下降沿MOSI驱动
1'b1 作为Slave,空闲时SCK电平为低,上升沿MOSI采样,下降沿MISO驱动
SP1 1'b0 1'b1 1'b0 作为Master,空闲时SCK电平为低,下降沿MISO采样,上升沿MOSI驱动
1'b1 作为Slave,空闲时SCK电平为低,下降沿MOSI采样,上升沿MISO驱动
SP2 1'b1 1'b0 1'b0 作为Master,空闲时SCK电平为高,下降沿MISO采样,上升沿MOSI驱动
1'b1 作为Slave,空闲时SCK电平为高,下降沿MOSI采样,上升沿MISO驱动
SP3 1'b1 1'b1 1'b0 作为Master,空闲时SCK电平为高,上升沿MOSI采样,下降沿MISO驱动
1'b1 作为Slave,空闲时SCK电平为高,上升沿MISO采样,下降沿MOSI驱动

假设SPI输入输出均为1bit,如下图所示,红色虚线表示SCK采样的边沿、绿色虚线表示SCK驱动的边沿

CPOL和CPHA需要MASTER SPI和SLAVE SPI统一
注意虽然输入输出都是1bit,但由于采样输入和驱动输出不在一个沿,所以移位寄存器得是2bit

实际上常用的工作模式是SP0和SP3,发送的数据则默认只是有效数据位,可配置为LSB发送或MSB发送

CPHA的意义

这里讲一下CPHA搞个采样驱动不同边沿有啥用。

如果使用的单沿采样驱动,此时采样输入和驱动输出在同一拍故移位寄存器就是1bit,那么从采样输入到移位寄存器、从移位寄存器到输出也是2拍,从速度上来说这个与CPHA模式相同。

实际上,CPHA模式的意义就是为了保证在信号中间时刻采样,以得到稳定的信号,利用SCK周期长的特点,让SCK的驱动沿和采样沿不是同一个。

我也一直在想同步时钟的话,SCK、MOSI和MISO的远距离传输必然会有信号偏斜的弊端。
UART是在baud_cnt为中间值时采样,与SPI在SCK中间沿采样异曲同工

如下图若在绿线处采样,那么就会采样到1,但由于延迟Slave那边的的MOSI.CK和MOSI.D会比Master这边的MOSI.CK和MOSI.Q晚一些。

但是Slave.MOSI.CK和Slave.MOSI.D慢的速度可能不同,会导致延迟之后红线处采样到0。但如果使用下降沿采样就会采样到1。

再说细一点,直接上那个熟悉的公式:

TsetupSI<TSCK+TSCK2SI−(TSCK2MO+TCK2QMO+TMO2SI)(a)T^{SI}_{setup}<T_{SCK}+T_{SCK2SI}-(T_{SCK2MO}+T^{MO}_{CK2Q}+T_{MO2SI}) \tag{a}TsetupSI​<TSCK​+TSCK2SI​−(TSCK2MO​+TCK2QMO​+TMO2SI​)(a)

TholdSI<TSCK2MO+TCK2QMO+TMO2SI−TSCK2SI(b)T^{SI}_{hold}<T_{SCK2MO}+T^{MO}_{CK2Q}+T_{MO2SI}-T_{SCK2SI} \tag{b}TholdSI​<TSCK2MO​+TCK2QMO​+TMO2SI​−TSCK2SI​(b)

首先TsetupSIT^{SI}_{setup}TsetupSI​一般可以满足,因为TSCKT_{SCK}TSCK​比较大,而且其他量相比于TSCKT_{SCK}TSCK​小很多。

但是TholdSIT^{SI}_{hold}TholdSI​则不好说,如果SCK走线时间和MOSI走线时间差距比较大的话,可能会违背。但如果对MO/CK和SI/CK在SCK不同沿的话,(b)(b)(b)式就会在左侧多一个12TSCK\frac{1}{2}T_{SCK}21​TSCK​,就可以保证。

可以算一下验证,触发器建立时间约为5ns,保持时间约为25ns,12.5MHZ最快的SPI周期是160ns
最后一个问题,那么为什么平时的设计没怎么见到这种双沿驱动呢?因为成本,你看双沿模式是不是比单沿模式多一个触发器?

1.3. SPI与UART的区别

上述是SPI本身的特性,但如果SPI要与外部模块交互呢?Master肯定有一个外部的用户时钟clk输入,用于传入要发送的数据并根据波特率产生SCK对吧。

但是Slave也需要与外部模块进行数据交互,所以Slave也需要有一个外部的clk输入。这样的话一定会涉及到SCK时钟域到clk时钟域的相互同步,这样的话是不是有一点点像UART?

UART不就是串行全双工么?然后tx和rx是通过波特率保证的。
而上述的SLAVE SPI 加上单bit同步,其本质也是通过波特率保证MOSI和MISO的交互

那么这样的SPI与UART的区别在哪里?

全双工:SPI发送接收必须同时完成,而UART可以发送接收分离

跨时钟域: SPI涉及到clk域与SCK域的相互同步问题,UART涉及到txd到rxd的跨时钟域问题

波特率实现: SPI是通过clk分频成SCK保证波特率的,而UART是通过clk计数器保证SCK波特率

工作模式:SPI具备一主多从工作,UART则是点对点
SPI基于SCK不同沿采样和驱动,UART则是基于波特率计数器采样和驱动。

2. 架构

由于SPI根据不同的配置产生不同的工作模式,所以SPI实现就需要根据parameter值用generate生成不同的代码。

根据谁呢?根据MSTR判断是生成spi_master还是spi_slave,如下图

注意顶层的spi使用了APB接口

2.1. spi

Signal Direction Width(bits) Description
prstn input 1 复位信号
pclk input 1 SPI的用户时钟
paddr input PADDR_WIDTH 用于访问spi内部FIFO
pwrite input 1 1表示写,0表示读
psel input 1 是否对spi选通
penable input 1 APB使能
pwdata input PDATA_WIDTH 写数据
prdata input PDATA_WIDTH 读出的数据
pready output 1 usart准备标志
sck inout 1 波特率时钟,Master SPI为output、Slave SPI为input
mosi inout 1 SPI单bit通信端口,Master SPI为output、Slave SPI为input
miso inout 1 SPI单bit通信端口,Master SPI为input、Slave SPI为output
csn_i input CHIP_SEL_NUM 对Master/Slave SPI片选的控制信号
csn_o output CHIP_SEL_NUM Master SPI对Slave SPI的片选控制

之后是参数描述

Parameter Units Description
BAUD_RATE bit per second 设定的波特率
PCLK_FREQ HZ clk的时钟频率
PADDR_WIDTH bit 访问SPI内部FIFO的地址位宽
PDATA_WIDTH bit 写入or读出的数据位宽
CHIP_SEL_NUM bit Master SPI可片选Slave SPI的个数
MSTR bit 0表示Master SPI、1表示Slave SPI
CPOL bit 表示sck空闲时的电平
CPHA bit 用于确定sck对单bit输入采样沿和单bit输出的驱动沿
ASYNC_FIFO_WIDTH bit 可选,异步FIFO深度

2.2. baud_clk_gen

用于产生波特率时钟sck

Signal Direction Width(bits) Description
rstn input 1 复位信号
clk input 1 SPI的用户时钟
sck output 1 波特率时钟

之后是参数描述

Parameter Units Description
BAUD_RATE bit per second 设定的波特率
CLK_FREQ HZ clk的时钟频率
CPOL bit 表示sck空闲时的电平

3. 逻辑设计

3.1. spi_master

串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_master

3.2. spi_slave

串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_slave

3.3. spi

整个顶层模块如下

module spi#(parameter    BAUD_RATE           =  12500000,parameter  PCLK_FREQ           =  50000000,parameter  PADDR_WIDTH         =  32,parameter    PDATA_WIDTH         =  32,parameter    CHIP_SEL_NUM        =  3,parameter MSTR                =  0,parameter CPOL                =  0,parameter CPHA                =  0,parameter ASYNC_FIFO_WIDTH    =  4096)(input                     prstn,input                     pclk,input  [PADDR_WIDTH-1:0]   paddr,input                     pwrite,input                        psel,input                      penable,input   [PDATA_WIDTH-1:0]   pwdata,output   [PDATA_WIDTH-1:0]   prdata,output                       pready,inout                        sck,inout                       mosi,inout                      miso,input  [CHIP_SEL_NUM-1:0]  csn_i,output    [CHIP_SEL_NUM-1:0]  csn_o);localparam TX_DATA_ADDR = 32'h0000_0000_0000_1000;
localparam RX_DATA_ADDR = TX_DATA_ADDR + 32'h4;
localparam CSN_I_ADDR = TX_DATA_ADDR + 32'h8;reg     [PDATA_WIDTH-1:0]       tx_data;
reg                             tx_data_val;
wire                            tx_fifo_full;
reg                             rx_req;
wire    [PDATA_WIDTH-1:0]       rx_data;
wire                            rx_data_val;
wire                            rx_fifo_empty;
reg     [PDATA_WIDTH-1:0]       prdata_r;
reg                             pready_r;always@(*) beginif(psel && penable && pwrite && paddr == TX_DATA_ADDR) begintx_data = pwdata;tx_data_val = 1'b1;endelse begintx_data = 'd0;tx_data_val = 1'b0;end
endalways@(*) beginif(psel && penable && !pwrite && paddr == RX_DATA_ADDR) prdata_r = rx_data;else if(psel && penable && !pwrite && paddr == TX_DATA_ADDR)prdata_r = tx_data;else if(psel && penable && !pwrite && paddr == CSN_I_ADDR)prdata_r = csn_i;elseprdata_r = 'd0;
endassign prdata = prdata_r;always@(posedge pclk or negedge prstn) beginif(!prstn)rx_req <= 1'b0;else if(psel && !pwrite && paddr == RX_DATA_ADDR) beginif(!penable)rx_req <= 1'b1;else if(rx_fifo_empty)rx_req <= 1'b1;elserx_req <= 1'b0;endelserx_req <= 1'b0;
endalways@(*) beginif(psel && penable && pwrite && paddr == TX_DATA_ADDR) pready_r = !tx_fifo_full;else if(psel && penable && !pwrite && paddr == RX_DATA_ADDR) pready_r = rx_data_val;else if(psel && penable && pwrite && paddr == CSN_I_ADDR) pready_r = 1'b1;elsepready_r = 1'b0;
endassign pready = pready_r;generate if(!MSTR) beginreg        [CHIP_SEL_NUM-1:0]      csn_i_r;always@(posedge pclk or negedge prstn) beginif(!prstn)csn_i_r <= {CHIP_SEL_NUM{1'b1}};else if(psel && (!penable) && pwrite && paddr == CSN_I_ADDR) csn_i_r <= pwdata;
endbaud_clk_gen#(.BAUD_RATE             (BAUD_RATE      ),.CPOL                 (CPOL           ),.CLK_FREQ             (PCLK_FREQ      ))u_baud_clk_gen(.rstn              (prstn          ),.clk              (pclk           ),.sck              (sck            ));spi_master#(.CHIP_SEL_NUM            (CHIP_SEL_NUM       ),.DATA_WIDTH               (PDATA_WIDTH        ),.ASYNC_FIFO_WIDTH     (ASYNC_FIFO_WIDTH   ),.CPOL                 (CPOL               ),.CPHA                 (CPHA               ))u_spi_master(.rstn                (prstn              ),.clk              (pclk               ),.tx_data          (tx_data            ),.tx_data_val      (tx_data_val        ),.tx_fifo_full     (tx_fifo_full       ),.rx_req               (rx_req             ),.rx_data          (rx_data            ),.rx_data_val      (rx_data_val        ),.rx_fifo_empty        (rx_fifo_empty      ),.sck              (sck                ),.mosi             (mosi               ),.miso             (miso               ),.csn_i                (csn_i_r            ),.csn_o                (csn_o              ));end
else beginspi_slave#(.CHIP_SEL_NUM          (CHIP_SEL_NUM       ),.DATA_WIDTH               (PDATA_WIDTH        ),.ASYNC_FIFO_WIDTH     (ASYNC_FIFO_WIDTH   ),.CPOL                 (CPOL               ),.CPHA                 (CPHA               ))u_spi_slave(.rstn             (prstn              ),.clk              (pclk               ),.tx_data          (tx_data            ),.tx_data_val      (tx_data_val        ),.tx_fifo_full     (tx_fifo_full       ),.rx_req               (rx_req             ),.rx_data          (rx_data            ),.rx_data_val      (rx_data_val        ),.rx_fifo_empty        (rx_fifo_empty      ),.sck              (sck                ),.mosi             (mosi               ),.miso             (miso               ),.csn              (csn_i[0]           ));endendgenerateendmodule

3.4. spi_tb

`timescale 1ns/1psmodule spi_tb();parameter BAUD_RATE           =  3000000;
parameter PCLK_FREQ         =  50000000;
parameter CHIP_SEL_NUM      =  2;
parameter PADDR_WIDTH       =  32;
parameter PDATA_WIDTH       =  32;
parameter CPOL              =  0;
parameter CPHA              =  0;
parameter ASYNC_FIFO_WIDTH  =  4096;parameter SPI_TX_DATA_ADDR = 32'h0000_0000_0000_1000;
parameter SPI_RX_DATA_ADDR = SPI_TX_DATA_ADDR + 32'h4;
parameter SPI_MASTER_CSN_I_ADDR = SPI_TX_DATA_ADDR + 32'h8;logic                                 prstn;
logic                               pclk;logic      [PADDR_WIDTH-1:0]       paddr_mst;
logic                               pwrite_mst;
logic                               psel_mst;
logic                               penable_mst;
logic       [PDATA_WIDTH-1:0]       pwdata_mst;
logic       [PDATA_WIDTH-1:0]       prdata_mst;
logic                               pready_mst;logic        [PADDR_WIDTH-1:0]       paddr_slv1;
logic                               pwrite_slv1;
logic                               psel_slv1;
logic                               penable_slv1;
logic       [PDATA_WIDTH-1:0]       pwdata_slv1;
logic       [PDATA_WIDTH-1:0]       prdata_slv1;
logic                               pready_slv1;logic       [PADDR_WIDTH-1:0]       paddr_slv2;
logic                               pwrite_slv2;
logic                               psel_slv2;
logic                               penable_slv2;
logic       [PDATA_WIDTH-1:0]       pwdata_slv2;
logic       [PDATA_WIDTH-1:0]       prdata_slv2;
logic                               pready_slv2;wire                                sck;
wire                                mosi;
wire                                miso;
logic                               miso_r;
wire                                miso_slv1;
wire                                miso_slv2;
logic       [CHIP_SEL_NUM-1:0]      csn_o;          initial beginpclk = 0;forever #10 pclk = !pclk;                   //50MHZ
endinitial beginprstn = 1;#50 prstn    = 0;#50 prstn  = 1;
endinitial beginpaddr_mst = 'd0;pwrite_mst = 1'b0;psel_mst = 1'b0;penable_mst = 1'b0;pwdata_mst = 'd0;paddr_slv1 = 'd0;pwrite_slv1 = 1'b0;psel_slv1 = 1'b0;penable_slv1 = 1'b0;pwdata_slv1 = 'd0;paddr_slv2 = 'd0;pwrite_slv2 = 1'b0;psel_slv2 = 1'b0;penable_slv2 = 1'b0;pwdata_slv2 = 'd0;#300;spi_pingpong_test();
endtask spi_pingpong_test();forkbeginspi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd1);spi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd2);endbeginspi_slave1_sequence(SPI_TX_DATA_ADDR,1'b1,32'd5);spi_slave1_sequence(SPI_TX_DATA_ADDR,1'b1,32'd6);endjoin@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b10);forkbeginspi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);spi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);endbeginspi_slave1_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);spi_slave1_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);endjoin@(posedge sck);@(posedge sck);@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b11);forkbeginspi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd3);spi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd4);endbeginspi_slave2_sequence(SPI_TX_DATA_ADDR,1'b1,32'd7);spi_slave2_sequence(SPI_TX_DATA_ADDR,1'b1,32'd8);endjoin@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b01);forkbeginspi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);spi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);endbeginspi_slave2_sequence(SPI_RX_DATA_ADDR,1'b0,32'd7);spi_slave2_sequence(SPI_RX_DATA_ADDR,1'b0,32'd8);endjoin@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b11);endtasktask spi_master_sequence(input bit [PADDR_WIDTH-1:0] addr,input bit write,input bit [PDATA_WIDTH-1:0] wdata);@(posedge pclk);#1;paddr_mst = addr;pwrite_mst = write;psel_mst = 1'b1;penable_mst = 1'b0;if(pwrite_mst)pwdata_mst = wdata;@(posedge pclk);#1;penable_mst = 1'b1;forever begin@(posedge pclk);if(psel_mst && penable_mst && pready_mst) begincase(paddr_mst)SPI_TX_DATA_ADDR:if(pwrite_mst)$display("spi master has successfully written data %d into transfer FIFO",pwdata_mst);else$display("spi master has written %d last time",pwdata_mst);SPI_RX_DATA_ADDR:if(pwrite_mst)$display("spi master: this read-only register can not be written.");else$display("spi master has successfully read data %d from receive FIFO",prdata_mst);SPI_MASTER_CSN_I_ADDR:if(pwrite_mst)$display("spi master has successfully selected %b",pwdata_mst[CHIP_SEL_NUM-1:0]);else$display("spi master has select %b last time",pwdata_mst[CHIP_SEL_NUM-1:0]);default:$display("spi master: paddr is set wrong");endcase#1;psel_mst = 1'b0;break;endend
endtasktask spi_slave1_sequence(input bit [PADDR_WIDTH-1:0] addr,input bit write,input bit [PDATA_WIDTH-1:0] wdata);@(posedge pclk);#1;paddr_slv1 = addr;pwrite_slv1 = write;psel_slv1 = 1'b1;penable_slv1 = 1'b0;if(pwrite_slv1)pwdata_slv1 = wdata;@(posedge pclk);#1;penable_slv1 = 1'b1;forever begin@(posedge pclk);if(psel_slv1 && penable_slv1 && pready_slv1) begincase(paddr_slv1)SPI_TX_DATA_ADDR:if(pwrite_slv1)$display("spi slave1 has successfully written data %d into transfer FIFO",pwdata_slv1);else$display("spi slave1 has written %d last time",pwdata_slv1);SPI_RX_DATA_ADDR:if(pwrite_slv1)$display("spi slave1: this read-only register can not be written.");else$display("spi slave1 has successfully read data %d from receive FIFO",prdata_slv1);default:$display("spi slave1: paddr is set wrong");endcase#1;psel_slv1 = 1'b0;break;endend
endtasktask spi_slave2_sequence(input bit [PADDR_WIDTH-1:0] addr,input bit write,input bit [PDATA_WIDTH-1:0] wdata);@(posedge pclk);#1;paddr_slv2 = addr;pwrite_slv2 = write;psel_slv2 = 1'b1;penable_slv2 = 1'b0;if(pwrite_slv2)pwdata_slv2 = wdata;@(posedge pclk);#1;penable_slv2 = 1'b1;forever begin@(posedge pclk);if(psel_slv2 && penable_slv2 && pready_slv2) begincase(paddr_slv2)SPI_TX_DATA_ADDR:if(pwrite_slv2)$display("spi slave1 has successfully written data %d into transfer FIFO",pwdata_slv2);else$display("spi slave1 has written %d last time",pwdata_slv2);SPI_RX_DATA_ADDR:if(pwrite_slv2)$display("spi slave1: this read-only register can not be written.");else$display("spi slave1 has successfully read data %d from receive FIFO",prdata_slv2);default:$display("spi slave1: paddr is set wrong");endcase#1;psel_slv2 = 1'b0;break;endend
endtaskspi#(.BAUD_RATE          (BAUD_RATE          ),.PCLK_FREQ            (PCLK_FREQ          ),.CHIP_SEL_NUM     (CHIP_SEL_NUM       ),.PADDR_WIDTH      (PADDR_WIDTH        ),.PDATA_WIDTH      (PDATA_WIDTH        ),.MSTR             (0                  ),.CPOL             (CPOL               ),.CPHA             (CPHA               ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH   ))u_spi_master(.prstn               (prstn                  ),.pclk             (pclk                   ),.paddr                (paddr_mst              ),.pwrite               (pwrite_mst             ),.psel             (psel_mst               ),.penable          (penable_mst            ),.pwdata               (pwdata_mst             ),.prdata               (prdata_mst             ),.pready               (pready_mst             ),.sck              (sck                    ),.mosi             (mosi                   ),.miso             (miso                   ),.csn_o                (csn_o                  ),.csn_i                (                       ));always@(*) begincase(csn_o)2'b10:miso_r = miso_slv1;2'b01:miso_r = miso_slv2;default: miso_r = 1'b1;endcase
endassign miso = miso_r;spi#(.BAUD_RATE            (BAUD_RATE          ),.PCLK_FREQ            (PCLK_FREQ          ),.CHIP_SEL_NUM     (CHIP_SEL_NUM       ),.PADDR_WIDTH      (PADDR_WIDTH        ),.PDATA_WIDTH      (PDATA_WIDTH        ),.MSTR             (1                  ),.CPOL             (CPOL               ),.CPHA             (CPHA               ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH   ))u_spi_slave1(.prstn               (prstn                  ),.pclk             (pclk                   ),.paddr                (paddr_slv1             ),.pwrite               (pwrite_slv1            ),.psel             (psel_slv1              ),.penable          (penable_slv1           ),.pwdata               (pwdata_slv1            ),.prdata               (prdata_slv1            ),.pready               (pready_slv1            ),.sck              (sck                    ),.mosi             (mosi                   ),.miso             (miso_slv1              ),.csn_o                (                       ),.csn_i                (csn_o[0]               ));spi#(.BAUD_RATE          (BAUD_RATE          ),.PCLK_FREQ            (PCLK_FREQ          ),.CHIP_SEL_NUM     (CHIP_SEL_NUM       ),.PADDR_WIDTH      (PADDR_WIDTH        ),.PDATA_WIDTH      (PDATA_WIDTH        ),.MSTR             (1                  ),.CPOL             (CPOL               ),.CPHA             (CPHA               ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH   ))u_spi_slave2(.prstn               (prstn                  ),.pclk             (pclk                   ),.paddr                (paddr_slv2             ),.pwrite               (pwrite_slv2            ),.psel             (psel_slv2              ),.penable          (penable_slv2           ),.pwdata               (pwdata_slv2            ),.prdata               (prdata_slv2            ),.pready               (pready_slv2            ),.sck              (sck                    ),.mosi             (mosi                   ),.miso             (miso_slv2              ),.csn_o                (                       ),.csn_i                (csn_o[1]               ));endmodule

4. 测试

4.1. spi_master与spi_slave1、spi_slave2的pingpong测试

测试的内容如spi_tb代码所写,先是将spi_master与spi_slave1进行1v1通信,然后是spi_master与spi_slave2进行1v1通信

参数设定如下:

parameter BAUD_RATE          =  3000000;            //SCK频率
parameter PCLK_FREQ         =  50000000;           //用户时钟
parameter CHIP_SEL_NUM      =  2;                  //spi master可片选的spi slave数量
parameter PADDR_WIDTH       =  32;                 //地址位宽
parameter PDATA_WIDTH       =  32;                 //数据位宽
parameter CPOL              =  0;                  //CPOL
parameter CPHA              =  0;                  //CPHA
parameter ASYNC_FIFO_WIDTH  =  4096;               //异步FIFO深度parameter SPI_TX_DATA_ADDR = 32'h0000_0000_0000_1000;           //spi内部发送FIFO的访问地址
parameter SPI_RX_DATA_ADDR = SPI_TX_DATA_ADDR + 32'h4;           //spi内部接收FIFO的访问地址
parameter SPI_MASTER_CSN_I_ADDR = SPI_TX_DATA_ADDR + 32'h8;      //spi_master内部片选寄存器csn_i的访问地址

首先给出transcript信息,数据交换正确

再看部分信号波形,在复位刚刚结束时,先将要传输的数据分别写入各自的FIFO内,spi master和spi slave不断去读,直到读出要发送的数据并将其写入各自的移位寄存器,在此期间片选信号为高。

之后片选信号拉低,表示开始传输,spi master直接从IDLE进入TRANS状态。由于CPOL和CPHA均为0,上升沿采样、下降沿驱动,故bit_cnt下降沿减1、移位寄存器在sck上升沿和下降沿均会变化。

当bit_cnt为0时传输结束,虽然此时csn_i依然为2’b10,但csn_o自动置2’b11。spi master先进入FIFO_WRITE状态将获得的数据写入rx_async_fifo,再在IDLE状态从tx_async_fifo读出新数据。

spi slave也是先写再读,整个过程与spi master类似不再赘述,可自行仿真。

CPOL和CPHA其他取值可用相同的方法进行测试。

串行外设接口(Serial Peripheral Interface, SPI)相关推荐

  1. 嵌入式硬件协议: SPI串行外设接口 Serial Peripheral Interface

    简介 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是 一种高速全双工的通信总线.它被广泛地使用在ADC.LCD 等设备与M ...

  2. 谈谈SPI (Serial Peripheral Interface,串行外设接口)

    今天我们来一起聊聊谈谈SPI (Serial Peripheral Interface,串行外设接口): 什么是SPI SPI (Serial Peripheral Interface,串行外设接口) ...

  3. arduino教程-9. 串行外设接口(spi)

    文章目录 相关资料 1. spi针脚 Arduino 串行外设接口 串行外设接口简介 板的SPI引脚 SPI.h 库 SPI.h官方示例 SPI为主机 例子 SPI为从机 例子 相关资料 SPI li ...

  4. 同步和串行的区别_谈谈SPI (Serial Peripheral Interface,串行外设接口)

    什么是SPI SPI (Serial Peripheral Interface,串行外设接口)是Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线: 它以主从方式 ...

  5. 微雪树莓派PICO笔记——7. SPI(串行外设接口)

    文章目录 SPI简介 硬件连接 通讯协议详解 RP2040 SPI 主要参数 RP2040 SPI 逻辑框图 machine.SPI类函数详解 例程地址 代码示例 代码实现 SPI简介 SPI全称为串 ...

  6. DSP SPI串行外设接口

    1.SPI介绍 1.1 SPI简介 SPI的全称是"Serial Peripheral Interface",意为串行外围接口,是Motorola首先在其MC68HCXX系列处理器 ...

  7. F28335第十一篇——串行外设接口(SPI)

    摘要 本文大致介绍了F28335中SPI工作原理和大致寄存器.还有很多细节知识没有列出,需要详细了解的同学,可以参考TI官方文档(TI官网免费下载),或者可以看书籍.重点推荐符晓编写的<TMS3 ...

  8. 串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_master

    目录 1. baud_clk_gen 1.1. 代码 2. spi_master 2.1. IDLE:等待写入 2.2. WAIT_CS:等待选通 2.3. TRANS:传输 (CPOL ^ CPHA ...

  9. 串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_slave

    目录 1. (csn == 1'b1) 时 1.1. spi_slave 完成 Master读写握手 的最短时间 1.2. spi_slave 完成 Slave读写握手 的最短时间 2. (csn = ...

  10. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

最新文章

  1. 去重之后统计条数_BOPET:12的普通包装膜到底去哪了?
  2. MATLAB实战系列(二十九)-头脑风暴优化(BSO)算法求解旅行商问题(TSP)-交叉算子
  3. 一文看尽9篇语义分割最新论文(GPSNet/Graph-FCN/HMANet等)
  4. Jenkins 权限配置与集群配置
  5. 音视频技术开发周刊 | 204
  6. SQL语句、PL/SQL语句、SQL*PLUS语句结束符号
  7. HBase: Thrift写数据报错——socket.error: [Errno 32] Broken pip
  8. 添加第三方库到Maven资源库
  9. 【经典回放】多种语言系列数据结构算法:二叉树(C#版)
  10. Webpack构建性能优化指南
  11. linux-01-概述
  12. inner join 与 left join 、right join之间的区别
  13. 将视图状态存入数据库(3)
  14. 28.类型提示的实现
  15. 软件项目管理案例教程第四版答案
  16. webrtc中视频采集实现分析(一) 采集及图像处理接口封装
  17. PicGo搭建图床避坑
  18. Activity中的数据传送—案例: 购买装备
  19. python 小游戏——外星人入侵源码倾情奉献
  20. 专利申请流程及费用及时间?

热门文章

  1. 暴雪守望先锋显示连接暴雪服务器超时,守望先锋 连接暴雪游戏服务器超时
  2. 重要不紧急紧急不重要
  3. Flink状态的缩放(rescale)与键组(Key Group)设计
  4. cvc 降噪_此降噪非彼降噪,你要的是哪种降噪?
  5. Excel技巧 一秒取消合并单元格,让你的取消合并操作简单快捷
  6. Java 简单计算器
  7. Trickbot 年度版本变化情况
  8. OPNsense用户手册-缓存代理
  9. 用好这 43 款 Chrome 插件,让你开发学习一下子好轻松!
  10. 【CVPR 2021】Revisiting Knowledge Distillation: An Inheritance and Exploration Framework