Vivado版本:2019.2 Modelsim版本:Modelsim SE-64 10.7
实验内容:通过SPI接口模拟主机读写从机寄存器,从机SPI时序图在下方给出。
SPI:是Serial Peripheral Interface的缩写,既串行外设接口,是一种4线高速全双工同步的通信总线。通常由一个主模块和一个从模块或多个从模块组成,主模块选择一个从模块进行同步通信,从而完成数据传输。单向传输时,3根线也可以。
其工作方式为主从方式,双向通信至少需要4根线,分别是
CS——Chip Select,片选信号;
SCLK——Serial Clock,时钟信号;
MOSI——Master Output Slave Input,主设备输出,从设备输入信号、
MISO——Master Input Slave Output,主设备输入,从设备输出信号。
当有多个从机时,每个从机都有对应的CS,而SCLK、MOSI、MISO三根线是可以复用的,主机通过使能对应的CS信号来选择从机进行交互。SPI可以有4中工作模式,这四种模式以两个信号区分:
CPOL既Clock Polarity,时钟的极性,也就是SCLK在空闲状态是高电平还是低电平。CPOL为1时,SCLK在空闲时刻为高电平、CPOL为0时,SCLK在空闲时刻为低电平;
CPHA,既Clock Phase,时钟的相位决定了数据采样与输出的沿的位置。CPHA为1时,在时钟前沿输出数据,时钟后沿采样数据;CPHA为0时,时钟前沿采样,后沿输出。至于时钟的前沿和后沿是上升沿还是下降沿,需要看SCLK空闲时刻的状态,例如,SCLK空闲时刻为高时,其前沿为下降沿,后沿为上升沿。
四种模式对应如下:
当CPHA为0时,第一个bit需要在时钟采样沿前半个SCLK时钟周期输出到数据线上。
下面介绍一种典型的SPI时序,该SPI使用三线,其将MOSI与MISO合并为一根,变为单向通信,也就是主机在此时刻只能进行读或写,不能同时进行。
从机按照下面时序图工作,也就是每次通信24bit,前16位输入,后8位根据读写标志控制为输出或输入。24bit中第一位为读写标志,信号为低时执行写入操作,为高时执行读出操作,后跟两位低电平的空闲位,接着跟13位地址,最后是8位数据位。
按照上图从机时序,编写主机内容,访问从机寄存器。假定从机内部有8个寄存器,地址为0x100、0x101、0x102、0x103、0x104、0x105、0x106、0x107,其余地址操作无效;从机的sclk信号只支持10MHz且高低电平保持时间最小为10ns。
打开vivado软件,新建一个工程,配置芯片为xc7z020clg400-1:
进入工程,新建一个master.v文件。按照spi通信时序编写主机模块,该模块输入信号为系统时钟sysclk、系统复位sysrst_n、启动信号start、读写标志RW_flag、地址addr、写入数据wr_data,输出信号为返回数据rd_data,SPI接口信号为spi_CSn、spi_SCLK、spi_SDIO;其中spi_SDIO为双向接口。
模块内部首先检测start信号的上升沿,当出现上升沿,就拉低spi_CSn信号,启动发送读写标志与地址。然后判断,读写标志为高时发送完地址后将spi_SDIO置为输入,然后接收8位数据;读写标志为低时发送完地址后继续发送wr_data上的数据,至发送完成后拉高spi_CSn信号。
代码很简单。
这里提供一个从机的代码,该从机内部有8个寄存器,初值为0到7。
`timescale 1ns / 1ps
module slave(
input wire sysclk,
input wire sysrst_n,
input wire CSn,
input wire SCLK,
inout wire SDIO
);
//---------------------------------------------------------------
// Define Signal
//---------------------------------------------------------------
reg [ 7:0] slave_reg0 = 8’d0;
reg [ 7:0] slave_reg1 = 8’d1;
reg [ 7:0] slave_reg2 = 8’d2;
reg [ 7:0] slave_reg3 = 8’d3;
reg [ 7:0] slave_reg4 = 8’d4;
reg [ 7:0] slave_reg5 = 8’d5;
reg [ 7:0] slave_reg6 = 8’d6;
reg [ 7:0] slave_reg7 = 8’d7;
reg sclk_r = 0;
wire rx_pulse;
wire tx_pulse;
reg [ 4:0] cnt_rx_pulse = 0;
reg [ 4:0] cnt_tx_pulse = 0;
wire sdi;
reg RW_flag = 0;
reg [12:0] recv_addr = 0;
reg recv_addr_flag = 0;
reg [ 7:0] recv_data = 0;
reg recv_data_flag = 0;
reg [ 7:0] send_data = 0;
wire sdo;
reg tri_sdio = 1;
//---------------------------------------------------------------
// Main Program
//---------------------------------------------------------------
always @(posedge sysclk) begin
sclk_r <= SCLK;
end
assign rx_pulse = (CSn == 1’b0) ? (SCLK & (~sclk_r)) : 0;
assign tx_pulse = (CSn == 1’b0 && cnt_rx_pulse > 5’d16) ? ((~SCLK) & sclk_r) : 0;
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_rx_pulse <= 0;
end
else if (CSn == 1’b1) begin
cnt_rx_pulse <= 0;
end
else if (rx_pulse == 1’b1) begin
cnt_rx_pulse <= cnt_rx_pulse + 1;
end
else begin
cnt_rx_pulse <= cnt_rx_pulse;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_tx_pulse <= 0;
end
else if (CSn == 1’b1) begin
cnt_tx_pulse <= 0;
end
else if (tx_pulse == 1’b1) begin
cnt_tx_pulse <= cnt_tx_pulse + 1;
end
else begin
cnt_tx_pulse <= cnt_tx_pulse;
end
end
assign sdi = SDIO;
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
RW_flag <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse == 'd0) begin
RW_flag <= sdi;
end
else begin
RW_flag <= RW_flag;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_addr <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse >= 'd3 && cnt_rx_pulse <= 'd15) begin
recv_addr <= {recv_addr[11:0],sdi};
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_addr_flag <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse == 'd15) begin
recv_addr_flag <= 1;
end
else begin
recv_addr_flag <= 0;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_data <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse >= 'd16 && cnt_rx_pulse <= 'd23) begin
recv_data <= {recv_data[11:0],sdi};
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_data_flag <= 0;
end
else if (rx_pulse == 1’b1 && cnt_rx_pulse == 'd23) begin
recv_data_flag <= 1;
end
else begin
recv_data_flag <= 0;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
slave_reg0 = 8’d0;
slave_reg1 = 8’d1;
slave_reg2 = 8’d2;
slave_reg3 = 8’d3;
slave_reg4 = 8’d4;
slave_reg5 = 8’d5;
slave_reg6 = 8’d6;
slave_reg7 = 8’d7;
end
else if (RW_flag == 1’b0 && recv_data_flag == 1’b1) begin
case (recv_addr)
13’h100 :slave_reg0 <= recv_data;
13’h101 :slave_reg1 <= recv_data;
13’h102 :slave_reg2 <= recv_data;
13’h103 :slave_reg3 <= recv_data;
13’h104 :slave_reg4 <= recv_data;
13’h105 :slave_reg5 <= recv_data;
13’h106 :slave_reg6 <= recv_data;
13’h107 :slave_reg7 <= recv_data;
default :begin
slave_reg0 = slave_reg0;
slave_reg1 = slave_reg1;
slave_reg2 = slave_reg2;
slave_reg3 = slave_reg3;
slave_reg4 = slave_reg4;
slave_reg5 = slave_reg5;
slave_reg6 = slave_reg6;
slave_reg7 = slave_reg7;
end
endcase
end
else begin
slave_reg0 = slave_reg0;
slave_reg1 = slave_reg1;
slave_reg2 = slave_reg2;
slave_reg3 = slave_reg3;
slave_reg4 = slave_reg4;
slave_reg5 = slave_reg5;
slave_reg6 = slave_reg6;
slave_reg7 = slave_reg7;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
send_data <= 0;
end
else if (RW_flag == 1’b1 && recv_addr_flag == 1’b1) begin
case (recv_addr)
13’h100 :send_data <= slave_reg0;
13’h101 :send_data <= slave_reg1;
13’h102 :send_data <= slave_reg2;
13’h103 :send_data <= slave_reg3;
13’h104 :send_data <= slave_reg4;
13’h105 :send_data <= slave_reg5;
13’h106 :send_data <= slave_reg6;
13’h107 :send_data <= slave_reg7;
default :send_data <= 0;
endcase
end
else if (RW_flag == 1’b1 && tx_pulse == 1’b1) begin
send_data <= {send_data[6:0],1’b0};
end
end
assign sdo = send_data[7];
assign SDIO = (tri_sdio == 1’b1) ? 1’bz : sdo;
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
tri_sdio <= 1;
end
else if (CSn == 1’b1) begin
tri_sdio <= 1;
end
else if (RW_flag == 1’b1 && recv_addr_flag == 1’b1) begin
tri_sdio <= 0;
end
end
endmodule
编写完主机代码后,编写tb测试激励。
测试激励也很简单,将主机模块与从机模块例化进来,spi端口连接上,然后控制主机模块先读取地址0x106的寄存器,然后将0xEE写入地址为0x100寄存器中。
主机仿真图如下,当发送完读写标志与地址后,将SDIO置为输入,接收从机返回的数据,可以得到接收到的数据为0x06
从机仿真图如下,当接收完读写标志与地址后,判断读写标志,为读,则把地址对应的寄存器中的值通过移位输出出去。
当接收到读标志与读地址后,从机将地址对应的寄存器中的值通过SDIO返回:
主机写入时序如下:
从机接收写入的地址与数据,并放在对应的寄存器中,时序如下:
附:
主机代码:
`timescale 1ns / 1ps
module master(
input wire sysclk,
input wire sysrst_n,
input wire start,
input wire RW_flag,
input wire [12:0] addr,
input wire [ 7:0] wr_data,
output reg [ 7:0] rd_data = 0,
output wire spi_CSn,
output wire spi_SCLK,
inout wire spi_SDIO
);
//---------------------------------------------------------------
// Define Signal
//---------------------------------------------------------------
reg start_r = 0;
reg csn = 1;
reg sclk = 0;
reg [ 3:0] cnt_sclk = 0;
reg [23:0] send_data = 0;
reg sdo = 0;
reg tri_sdio = 0;
reg [ 4:0] cnt_recv = 0;
reg [ 7:0] recv_data = 0;
wire sdi;
//---------------------------------------------------------------
// Main Program
//---------------------------------------------------------------
always @(posedge sysclk) begin
start_r <= start;
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
csn <= 1;
end
else if (cnt_recv == 'd24 && cnt_sclk == 'd9) begin
csn <= 1;
end
else if (start == 1’b1 && start_r == 1’b0) begin
csn <= 0;
end
end
assign spi_CSn = csn;
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
sclk <= 0;
end
else if (cnt_sclk == 'd4) begin
sclk <= 1;
end
else if (cnt_sclk == 'd9) begin
sclk <= 0;
end
else begin
sclk <= sclk;
end
end
assign spi_SCLK = sclk;
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_sclk <= 0;
end
else if (csn == 1’b0 && cnt_sclk == 'd9) begin
cnt_sclk <= 0;
end
else if (csn == 1’b0) begin
cnt_sclk <= cnt_sclk + 1;
end
else begin
cnt_sclk <= 0;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
send_data <= 0;
end
else if (start == 1’b1 && start_r == 1’b0) begin
send_data <= {RW_flag,2’b0,addr,wr_data};
end
else if (cnt_sclk == 'd9) begin
send_data <= {send_data[22:0],1’b0};
end
else begin
send_data <= send_data;
end
end
assign spi_SDIO = (tri_sdio == 1’b1) ? 1’bz : send_data[23];
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
tri_sdio <= 0;
end
else if (csn == 1’b1) begin
tri_sdio <= 0;
end
else if (RW_flag == 1’b1 && csn == 1’b0 && cnt_recv == 'd16 && cnt_sclk == 'd9) begin
tri_sdio <= 1;
end
else begin
tri_sdio <= tri_sdio;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
cnt_recv <= 0;
end
else if (csn == 1’b1) begin
cnt_recv <= 0;
end
else if (csn == 1’b0 && cnt_sclk == 'd4) begin
cnt_recv <= cnt_recv + 1;
end
else begin
cnt_recv <= cnt_recv;
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
recv_data <= 0;
end
else if (RW_flag == 1’b1 && csn == 1’b0 && cnt_recv >= 'd16 && cnt_sclk == 'd4) begin
recv_data <= {recv_data[6:0],sdi};
end
end
always @(posedge sysclk) begin
if (sysrst_n == 1’b0) begin // reset
rd_data <= 0;
end
else if (RW_flag == 1’b1 && csn == 1’b0 && cnt_recv == 'd24 && cnt_sclk == 'd9) begin
rd_data <= recv_data;
end
end
assign sdi = spi_SDIO;
endmodule
测试激励:
`timescale 1ns / 1ps
module tb_top (); /* this is automatically generated */
// clock
reg clk;
initial begin
clk = 'b0;
forever #(5) clk = ~clk;
end
// synchronous reset
reg rst_n;
initial begin
rst_n <= 'b0;
repeat(100)@(posedge clk);
rst_n <= 'b1;
end
// (*NOTE*) replace reset, clock, others
reg start = 0;
reg RW_flag = 0;
reg [12:0] addr = 0;
reg [ 7:0] wr_data = 0;
wire [ 7:0] rd_data;
wire spi_CSn;
wire spi_SCLK;
wire spi_SDIO;
initial begin
repeat(110)@(posedge clk);
start <= 1;
RW_flag <= 1;
addr <= 13'h106;
@(posedge clk);
start <= 0;
@(posedge spi_CSn);
repeat(10)@(posedge clk);
start <= 1;
RW_flag <= 0;
addr <= 13'h100;
wr_data <= 8'hEE;
@(posedge clk);
start <= 0;
end
master inst_master (
.sysclk (clk ),
.sysrst_n (rst_n ),
.start (start ),
.RW_flag (RW_flag ),
.addr (addr ),
.wr_data (wr_data ),
.rd_data (rd_data ),
.spi_CSn (spi_CSn ),
.spi_SCLK (spi_SCLK ),
.spi_SDIO (spi_SDIO )
);
slave inst_slave (
.sysclk (clk ),
.sysrst_n (rst_n ),
.CSn (spi_CSn ),
.SCLK (spi_SCLK ),
.SDIO (spi_SDIO )
);