最近在学习zedboard,今天试着分析并理解一下Harald's Embedded Electronics 网站的的SPI发送ip核教学里的Verilog代码。链接如下:
ZYNQ: SPI Transmitter Using an AXI Stream Interface – Harald's Embedded Electronicshttp://www.harald-rosenfeldt.de/2017/12/28/zynq-spi-transmitter-using-an-axi-stream-interface/ 原文中的创建步骤就不再赘述了,这里只分析代码,需要的话可以点击上面的链接
原代码如下:
mySPI_Tx_AXIS_v1_0.v
`timescale 1 ns / 1 ps
module mySPI_Tx_AXIS_v1_0 #
(
// Users to add parameters here
parameter integer width = 8,
parameter integer clkdiv= 4,
// User parameters ends
// Do not modify the parameters beyond this line
// Parameters of Axi Slave Bus Interface S00_AXIS
parameter integer C_S00_AXIS_TDATA_WIDTH = 32
)
(
// Users to add ports here
output wire sclk,
output wire mosi,
output wire ss,
// User ports ends
// Do not modify the ports beyond this line
// Ports of Axi Slave Bus Interface S00_AXIS
input wire s00_axis_aclk,
input wire s00_axis_aresetn,
output wire s00_axis_tready,
input wire [C_S00_AXIS_TDATA_WIDTH-1 : 0] s00_axis_tdata,
input wire [(C_S00_AXIS_TDATA_WIDTH/8)-1 : 0] s00_axis_tstrb,
input wire s00_axis_tlast,
input wire s00_axis_tvalid
);
// Instantiation of Axi Bus Interface S00_AXIS
mySPI_Tx_AXIS_v1_0_S00_AXIS # (
.width(width),
.clkdiv(clkdiv),
.C_S_AXIS_TDATA_WIDTH(C_S00_AXIS_TDATA_WIDTH)
) mySPI_Tx_AXIS_v1_0_S00_AXIS_inst (
.sclk(sclk),
.mosi(mosi),
.ss(ss),
.S_AXIS_ACLK(s00_axis_aclk),
.S_AXIS_ARESETN(s00_axis_aresetn),
.S_AXIS_TREADY(s00_axis_tready),
.S_AXIS_TDATA(s00_axis_tdata),
.S_AXIS_TSTRB(s00_axis_tstrb),
.S_AXIS_TLAST(s00_axis_tlast),
.S_AXIS_TVALID(s00_axis_tvalid)
);
// Add user logic here
// User logic ends
endmodule
mySPI_Tx_AXIS_v1_0_S00_AXIS.v
`timescale 1 ns / 1 ps
module mySPI_Tx_AXIS_v1_0_S00_AXIS #
(
// Users to add parameters here
parameter integer width = 8,
parameter integer clkdiv= 4,
// User parameters ends
// Do not modify the parameters beyond this line
// AXI4Stream sink: Data Width
parameter integer C_S_AXIS_TDATA_WIDTH = 32
)
(
// Users to add ports here
output wire sclk,
output reg mosi = 0,
output wire ss,
// User ports ends
// Do not modify the ports beyond this line
// AXI4Stream sink: Clock
input wire S_AXIS_ACLK,
// AXI4Stream sink: Reset
input wire S_AXIS_ARESETN,
// Ready to accept data in
output wire S_AXIS_TREADY,
// Data in
input wire [C_S_AXIS_TDATA_WIDTH-1 : 0] S_AXIS_TDATA,
// Byte qualifier
input wire [(C_S_AXIS_TDATA_WIDTH/8)-1 : 0] S_AXIS_TSTRB,
// Indicates boundary of last packet
input wire S_AXIS_TLAST,
// Data is in valid
input wire S_AXIS_TVALID
);
// 八位的移位寄存器 This holds the shift register
reg [width-1 : 0] buffer = 0;
reg buffer_full = 0;
// 六位bit计数 Counts the bits
reg [5:0] bitcounter = 0;
// 四位的分频器 0-127 Makes things slower
reg [clkdiv-1:0] prescaler = 0;
// 状态机的四个状态 State machine states
localparam IDLE = 0;
localparam S1 = 1;
localparam S2 = 2;
localparam S3 = 3;
// 状态机的默认状态为 IDLE Default state is IDLE
reg [1:0] state = IDLE;
// 等待信号接收状态 Signals we are ready to receive
assign S_AXIS_TREADY = !buffer_full;
// SPI的时钟信号(数据在上升沿有效) 在state为状态S2或S3时,为1,其他状态为0 SPI Clock (data is valid during Low/High transition)
assign sclk = state==S2 || state==S3;
// SPI的从机选择 只有state等于状态IDLE时才会置0 SPI Slave Select
assign ss = state!=IDLE;
// 下面的程序为主要的状态机 This is the main state machine
always @(posedge S_AXIS_ACLK) begin
// There is only one important rule for an AXI Stream interface:
// If during the rising clock, S_AXIS_TVALID==1 and S_AXIS_TREADY==1, then we have to accept the data.
if (S_AXIS_TVALID==1 && S_AXIS_TREADY==1) begin
buffer <= S_AXIS_TDATA[width-1 : 0]; //把数据存入buffer
buffer_full = 1; //buffer存满标志置1
end else if (state==S3 && prescaler==1) begin
buffer_full = 0; //buffer存满标志清零
end
prescaler <= prescaler+1; //分频计数器prescaler加1 每128个S_AXIS_ACLK脉冲,prescaler溢出清零
if (prescaler==0) begin // The state transitions are synchronized to the SPI bit clock
case(state)
IDLE: begin // ss=0, sclk=0, mosi=0
mosi <= 0; //mosi清零
if (buffer_full==1) begin //如果buffer存满
mosi <= buffer[width-1]; //buffer的最高位发送到mosi
bitcounter <= 1; //bitcounter计数器置1
state <= S1; //状态变为S1
end
end
S1: begin // ss=1, sclk=0
if ( bitcounter==width ) begin //如果bitcounter计数器等于8,说明数据发送完
state <= S3; //进入状态S3
end else begin
state <= S2; //数据没有发送完,进入状态S2
buffer <= buffer<<1; //buffer左移一位,最高位改变
end
end
S2: begin // ss=1, sclk=1
state <= S1; //进入状态S1
mosi <= buffer[width-1]; //buffer的最高位发送到mosi
bitcounter <= bitcounter+1; //bitcounter计数器加1
end
S3: begin // ss=1, sclk=1 (last bit)
if (buffer_full==1) begin //如果buffer存满
mosi <= buffer[width-1]; //buffer的最高位发送到mosi
bitcounter <= 1; //bitcounter计数器置1
state <= S1; //进入状态S1
end else begin
state <= IDLE; //进入状态IDLE
end
end
default:begin
state <= IDLE; //进入状态IDLE
end
endcase
end
end
endmodule
这两个文件都是根据AXI Stream Interface的基础上修改实现的,根据原始文件的提示在其中加入用户的参数、接口、逻辑就可以实现用户想要功能的IP核。
mySPI_Tx_AXIS_v1_0.v代码分析
代码第4到15行为参数定义。第6、7行为添加的两个参数。一个width = 8,一个clkdiv = 4。
代码第18到20行定义了三个输出接口 sclk,mosi,ss,这三个接口都是SPI通讯的接口。
sclk、mosi、ss分别为SPI通讯的 时钟,数据发送接口,和从机选择。对于SPI通信协议不了解的朋友可以去看其他帖子。但在原贴中,ss这个接口的写法跟平时常见的不太一样,这里为高电平有效,其他地方常见的用法还是低电平有效。
mySPI_Tx_AXIS_v1_0_S00_AXIS.v代码分析
6、7行的两个参数,width为传输数据的位数这里为8。另一个clkdiv为分频器的位数,这里是4位,用来将S_AXIS_ACLK的频率降低128倍给SPI通讯的时钟sclk。
40-57行定义了一些寄存器和状态机的状态参数,我在代码里都给了注释。
buffer是一个八位寄存器,用来接收来自PS的数据,并在后续发送给SPI通讯的mosi接口
四个状态IDLE,S1,S2,S3,IDLE为初始状态,S3为结束状态,S1和S2为发送状态。
最关键的还是ss和sclk。ss为从机选择,高电平有效,这里在状态S1,S2,S3时都是高电平,在IDLE状态为低电平。sclk为时钟信号,在S2或S3时置1,IDLE和S1状态置0。
后面的代码里都给了注释,大家可以自己阅读。下面为工作的时序图。