FPGA -- SPI 实现时序(超级灵活,超级好用)

前言

现在大多数的器件都是采用SPI进行通信,但是有的是标准的SPI,由的却不是标准的SPI通信(不遵循CPOL CPHA),为了兼容标准与不标准SPI,特意编写了灵活性较强的SPI。

一、SPI

在Stm32 手册中规定了 SPI的四种形式,我称之为标准的SPI,可以从图中发现在数据发生变化的时间有所不同,这主要是由CPHA决定的若CPHA=1则意味着数据数据是在时钟变化之后变化的,若CPHA=0则意味着数据数据是在时钟变化之前变化的;而CPOL代表的是CLK在闲时的电平。
在这里插入图片描述
标准和不标准之说,则是在数据接收时的异同,不标准的SPI在数据接收时,会指定在时钟的上升沿/下降沿返回数据,此类的SPI大多数是某些公司定制的。标准的SPI则依旧按照上图返回数据。

二、代码说明

端口介绍:

module spi_master_v3 #(
	parameter TX_DATA_WIDTH = 16,			
	parameter RX_DATA_WIDTH = 16
)(
	input 		i_sys_clk	,
	input 		i_reset_n	,
	input 		i_start		,					//发送触发信号,上升沿有效
	input		i_load_again,
	input 		i_CPOL	  	, 					//时钟极性  闲时为CPOL
	input		i_CPHA		,					//时钟相位	1:在数据来之前变化 0:在是数据来之后发生变化
	input		i_CS_nVAl 	,					//片选信号有效电平
	input 		i_MSB		,					//1:大端模式 	 0:小端模式
	input		i_RX_nDown	,					//1:上升沿收数据 0:下降沿收数据
	input		i_RX_ndirc_L,					//1:先接收高位	 0:先接收低位
	
	input 	[7:0]i_div_num		,
	input 	[7:0]i_tx_bit_num	,
	input 	[7:0]i_rx_bit_num	,
	input 	[3:0]i_delay_cycle	,				//CS延迟的节拍数
	
	input 	[TX_DATA_WIDTH-1:0]i_tx_data,
	output 	[TX_DATA_WIDTH-1:0]o_rx_data,
	
	output reg  o_axi_vali	,
	input	 	i_axi_rdy	,
	output     	o_clk		,
	output     	o_cs		,
	output     	o_mosi		,
	input 	   	i_miso		,
	output 	   	o_busy
);

load_again 的原理及使用方法:
在i_start 的上升沿时,采集第一次 i_load_again 的值,若i_load_again信号为高,那么
表示主机方,想发送突发数据(可以指定每次突发数据的长度),主机方想发送突发数据时,
只需要将 i_axi_rdy 信号拉高,通知模块准备接收突发数据流,并指定是否还有突发数据,若想
结束突发数据发送,只需将i_load_again拉低即可,模块在接收完成突发数据时,会将o_axi_vali
信号拉高;若在第一次发送数据时,把 i_load_again 拉低则表明只接收帧数据,此时拉高
i_axi_rdy 无效。

1.程序源码

`timescale 1ns / 100ps
//
// Company: 
// Engineer: 
// 
// Create Date:    22:41:44  2020年10月26日 
// Design Name: 
// Module Name:    spi_master_v3 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
/*
 此版本的SPi总的来说能都应对绝大部分 SPI时序,其中在版本中加入了load_again功能,以及
 i_RX_nDown,i_RX_ndirc_L,具体的使用见端口注释
 
 load_again 的原理及使用方法:
	   在i_start 的上升沿时,采集第一次 i_load_again 的值,若i_load_again信号为高,那么
	   表示主机方,想发送突发数据(可以指定每次突发数据的长度),主机方想发送突发数据时,
	   只需要将 i_axi_rdy 信号拉高,通知模块准备接收突发数据流,并指定是否还有突发数据,若想
	   结束突发数据发送,只需将i_load_again拉低即可,模块在接收完成突发数据时,会将o_axi_vali
	   信号拉高;若在第一次发送数据时,把 i_load_again 拉低则表明只接收帧数据,此时拉高
	   i_axi_rdy 无效。
 */
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//
module spi_master_v3 #(
	parameter TX_DATA_WIDTH = 16,			
	parameter RX_DATA_WIDTH = 16
)(
	input 		i_sys_clk	,
	input 		i_reset_n	,
	input 		i_start		,
	input		i_load_again,
	input 		i_CPOL	  	, 					//时钟极性  闲时为CPOL
	input		i_CPHA		,					//时钟相位	1:在数据来之前变化 0:在是数据来之后发生变化
	input		i_CS_nVAl 	,					//片选信号低电平有效
	input 		i_MSB		,					//1:大端模式 	 0:小端模式
	input		i_RX_nDown	,					//1:上升沿收数据 0:下降沿收数据
	input		i_RX_ndirc_L,					//1:先接收高位	 0:先接收低位
	
	input 	[7:0]i_div_num		,
	input 	[7:0]i_tx_bit_num	,
	input 	[7:0]i_rx_bit_num	,
	input 	[3:0]i_delay_cycle	,				//CS延迟的节拍数
	
	input 	[TX_DATA_WIDTH-1:0]i_tx_data,
	output 	[TX_DATA_WIDTH-1:0]o_rx_data,
	
	output reg  o_axi_vali	,
	input	 	i_axi_rdy	,
	output     	o_clk		,
	output     	o_cs		,
	output     	o_mosi		,
	input 	   	i_miso		,
	output 	   	o_busy
);
/*********************************************************/
reg [1:0]start_buff;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n)
		start_buff <= 0;
	else 
		start_buff <= {start_buff[0] , i_start};
end

wire start_flag = (start_buff == 2'b01);

/*********************************************************/
reg 	[7:0]div_num	;
reg 	[7:0]tx_bit_cnt_f;
reg 	[7:0]rx_bit_cnt_f;
reg 	[7:0]tx_bit_cnt_load;
reg 	[3:0]delay_cycle;
reg 	[TX_DATA_WIDTH-1:0]tx_load_again_data;
reg 	load_again;
reg 	flash_data;
reg 	load_again_data_done;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n) begin
		div_num  <= 0;
		delay_cycle <= 0;
		load_again <= 0;
		flash_data <= 0;
		tx_bit_cnt_load <= 0;
		tx_load_again_data <= 0;
	end
	else if(start_flag)begin
		div_num <= i_div_num;
		tx_bit_cnt_f <= i_tx_bit_num;
		rx_bit_cnt_f <= i_rx_bit_num;
		delay_cycle <= i_delay_cycle;
		load_again <= i_load_again;
		o_axi_vali <= 0;
		flash_data <= 0;
	end 
	else if(load_again_data_done) begin
		flash_data <= 0;
		o_axi_vali <= 0;
	end
	else if(load_again && i_axi_rdy )  begin	//采集下一次的load_again信号	
		tx_bit_cnt_load <= i_tx_bit_num;
		load_again 		<= i_load_again; 		
		tx_load_again_data <= i_tx_data;		//接收数据已经饱和
		o_axi_vali		<= 1;
		flash_data      <= 1;
	end 
	else begin
		o_axi_vali <= 0;
	end
	
end


/*********************************************************/
localparam idle = 1;
localparam s1 	= idle << 1;
localparam s2 	= idle << 2;
localparam s3 	= idle << 3;
localparam s4 	= idle << 4;


(* fsm_coding = "One-HOt" *)reg [4:0]curr_sta;
(* fsm_coding = "One-HOt" *)reg [4:0]next_sta;


//fsm_1
always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n) 
		curr_sta <= idle;
	else 
		curr_sta <= next_sta;
end

//fsm_2
reg tx_start;
reg delay_clr;
wire delay_cs_ok;
reg [7:0]tx_bit_cnt;

always @(*) begin
	next_sta = idle;
	if(!i_reset_n) begin
		next_sta = idle;
		tx_start = 0;
		delay_clr = 1;
	end
	else begin
		case (curr_sta)
			idle :begin
				if(start_flag)
					next_sta = s1;
				else
					next_sta = idle;
				tx_start = 0;
				delay_clr = 1;
			end
			s1:begin
				if(delay_cs_ok) begin
					next_sta = s2;
					delay_clr = 1;
				end
				else begin
					next_sta = s1;
					delay_clr = 0;
				end
				tx_start = 0;
			end
			s2:begin
				if(tx_bit_cnt == tx_bit_cnt_f) 		//传输完成一次
					next_sta = flash_data ? s4 : s3;
				else 
					next_sta = s2;
					
				tx_start = 1;
				delay_clr = 1;
			end
			s3:begin
				if(delay_cs_ok) begin
					next_sta = idle;
					delay_clr = 1;
				end
				else begin
					next_sta = s3;
					delay_clr = 0;
				end
				tx_start = 0;
			end
			s4:begin
				if(load_again_data_done)
					next_sta = s2;
				else 
					next_sta = s4; 
			end
			default:next_sta = idle;
		endcase
	end
end


//fsm_3
reg cs;
reg clk;

reg [TX_DATA_WIDTH-1:0]tx_data;


wire clk_half;
wire clk_full;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n)begin
		cs  <= ~i_CS_nVAl;
		clk <= i_CPOL;
		tx_bit_cnt <= 0;
		load_again_data_done <= 0;
	end
	else begin
		case(curr_sta)
			idle:begin
				cs  <= ~i_CS_nVAl;
				clk <= i_CPOL;
				load_again_data_done <= 0;
			end
			s1:begin
				cs  <= i_CS_nVAl;
				clk <= i_CPOL;
				tx_bit_cnt <= 0;
				tx_data <= i_tx_data;
				
				if(delay_cs_ok && i_CPHA)
					clk <= ~i_CPOL;
				else 
					clk <= i_CPOL;
			end
			s2:begin
				if(tx_bit_cnt == tx_bit_cnt_f - 1 && clk_full && i_CPHA && !flash_data )
					clk <= i_CPOL;
				else if(clk_half || clk_full)
					clk <= ~clk;
				
				if(clk_full) begin
					tx_bit_cnt <= tx_bit_cnt + 1'b1;
					tx_data <= i_MSB ? tx_data << 1 : tx_data >> 1;
				end
				load_again_data_done <= 0;
			end
			s3:begin
				//cs  <= ~i_CS_nVAl;
			end
			s4:begin				//装载loag_again_data
				tx_data <= tx_load_again_data;
				tx_bit_cnt_f <= tx_bit_cnt_load;
				tx_bit_cnt <= 0;
				load_again_data_done <= 1;
			end
			default:;
		endcase
	end
		
end

assign o_clk = clk;
assign o_mosi= i_MSB ? tx_data[TX_DATA_WIDTH - 1]:tx_data[0];
assign o_cs = cs;
assign o_busy = (curr_sta !=idle);


/******************************************************************************/
//rx data
wire [7:0]delay_bit = tx_bit_cnt_f - rx_bit_cnt_f;
reg [7:0]rx_bit_cnt;
reg [RX_DATA_WIDTH-1:0]rx_data;

reg clk_f;
always @(posedge i_sys_clk )
	clk_f <= clk;

wire clk_up = (!clk_f) & clk;
wire clk_dn = (!clk) & clk_f;

always @(posedge i_sys_clk or negedge i_reset_n) begin 
	if(!i_reset_n) begin 
		rx_bit_cnt <= 0;
		rx_data <= 0;
	end
	else if(start_flag) begin
		rx_bit_cnt <= 0;
		rx_data <= 0;
	end

	else if(((!i_RX_nDown && clk_dn) || (i_RX_nDown && clk_up))&& rx_bit_cnt >= delay_bit) begin 
		if(i_RX_ndirc_L)				//先收低位数据
			rx_data <= {rx_data[RX_DATA_WIDTH-2:0],i_miso};
		else
			rx_data <= {i_miso,rx_data[RX_DATA_WIDTH-1:1]};
		rx_bit_cnt <= rx_bit_cnt + 1;
	end
	else if((!i_RX_nDown && clk_dn) || (i_RX_nDown && clk_up))begin
		rx_bit_cnt <= rx_bit_cnt + 1;
	end
end

assign o_rx_data = i_RX_ndirc_L ? rx_data : rx_data >>(RX_DATA_WIDTH - delay_bit) ;

/******************************************************************************/
reg [7:0]div_num_cnt;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n)
		div_num_cnt <= 0;
	else if(tx_start)begin
		if(div_num_cnt == div_num)
			div_num_cnt <= 0;
		else 
			div_num_cnt <= div_num_cnt + 1;
	end
	else 
		div_num_cnt <= 0;
end

assign clk_half = (div_num_cnt == div_num >> 1);
assign clk_full = (div_num_cnt == div_num >> 0);

/******************************************************************************/
reg [3:0]delay_cnt;

always @(posedge i_sys_clk or negedge i_reset_n) begin
	if(!i_reset_n)
		delay_cnt <= 0;
	else if(delay_clr)
		delay_cnt <= 0;
	else 
		delay_cnt <= delay_cnt + 1;
end

assign delay_cs_ok = (delay_cnt ==  delay_cycle);

/******************************************************************************/
function integer calc_width(input integer num);
	begin
		calc_width = 0;
		while(num > 0) begin
			num = num >> 1 ;
			calc_width = calc_width + 1;
		end
	end
endfunction

/******************************************************************************/

endmodule


2.仿真源码

`timescale 1ns / 100ps
//
// Company: 
// Engineer: 	Deam
// 
// Create Date:    21:39:35  2020年10月30日 
// Design Name: 
// Module Name:    tb_spi_master_v3 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//
module   tb_spi_master_v3  ;

reg 	i_sys_clk;
reg 	i_reset_n;

always #0.5 i_sys_clk =~i_sys_clk;

initial begin
	i_sys_clk = 0;
	i_reset_n = 0;
#2
	i_reset_n = 1;
end
/******************************************************************************/
parameter TX_DATA_WIDTH = 16;			
parameter RX_DATA_WIDTH = 16;

reg 		i_start;
reg 		i_miso;
reg 		i_load_again;
reg 		i_CS_nVAl;
reg 		i_CPOL;
reg 		i_CPHA;
reg 		i_MSB;
reg			i_RX_ndirc_L;
reg 		i_axi_rdy;
reg 		i_RX_nDown;

reg 	[7:0]i_div_num;
reg 	[7:0]i_tx_bit_num;
reg 	[7:0]i_rx_bit_num;
reg	 	[3:0]i_delay_cycle;
reg 	[TX_DATA_WIDTH-1:0]i_tx_data;
wire 	[RX_DATA_WIDTH-1:0]o_rx_data;
wire 	o_clk;
wire 	o_cs;
wire 	o_mosi;
wire 	o_busy;

spi_master_v3 #(
	.TX_DATA_WIDTH 		(TX_DATA_WIDTH	),			
	.RX_DATA_WIDTH 		(RX_DATA_WIDTH	)
)uut(

	.i_sys_clk		(i_sys_clk	),
	.i_reset_n		(i_reset_n	),
	
	.i_start		(i_start	),
	.i_tx_data		(i_tx_data	),
	.i_tx_bit_num	(i_tx_bit_num),
	.i_rx_bit_num	(i_rx_bit_num),
	.i_div_num		(i_div_num	),
	.i_CPOL			(i_CPOL		),
	.i_CS_nVAl		(i_CS_nVAl	),
	.i_CPHA			(i_CPHA		),
	.i_MSB			(i_MSB		),
	.i_RX_nDown		(i_RX_nDown	),
	.i_RX_ndirc_L	(i_RX_ndirc_L),
	.i_load_again	(i_load_again),
	.o_rx_data		(o_rx_data	),
	.i_delay_cycle	(i_delay_cycle),
	
	.i_axi_rdy		(i_axi_rdy	),
	.o_axi_vali		(o_axi_vali	),
	.o_clk			(o_clk		),
	.o_cs			(o_cs		),
	.o_mosi			(o_mosi		),
	.i_miso			(i_miso		),
	.o_busy			(o_busy		)

);
/******************************************************************************/
always @(negedge o_clk)
	i_miso <= $random;
initial begin 
	i_start	= 0;
	i_miso = 0;
	i_load_again = 0;
	i_CS_nVAl = 0;
	i_CPOL = 0;
	i_CPHA = 0;
	i_MSB = 1;
	
	i_RX_nDown = 0;
	i_RX_ndirc_L = 0;
	
	i_delay_cycle = 1;
	i_axi_rdy = 0;
	i_div_num = 20;
	i_tx_bit_num = 8;
	i_rx_bit_num = 4;
#10;
/* 	repeat (3) begin
		wait(!o_busy);
	#10;
		send_data();
	end */
	send_data();
#50 i_axi_rdy = 1;
	//send_data();
#100 i_load_again = 0;
	
end
		

/******************************************************************************/
task send_data;
	begin
	#1;
		i_start = 1;
		i_tx_data = $random;
		i_div_num = 10;
	#1; 
		i_start = 0;
	end
endtask
/******************************************************************************/
endmodule


3. do 文件源码

# ./当前路径
# ../上一级目录

#退出当前仿真
quit -sim

#清空命令行
.main clear

#创建库
#vlib  lib
#vib   ./lib/work

#将创建库映射至物理逻辑库
#vmap work  .lib/work

#编译文件
vlog -work work ./../spi_master_v3.v
vlog -work work ./../tb_spi_master_v3.v

#启动仿真
vsim -voptargs=+acc  work.tb_spi_master_v3

#添加波形
do tb_master_v3_wave.do


#运行仿真
run 500ns

################################################################################3
#10进制显示波形
#add wave -radix unsigned /tb_usart_rx/o_data

#模拟显示
#add wave -format Analog-Step -height 74 -max 255.0 /tb_usart_rx/o_data

三、仿真结果

在这里插入图片描述
在这里插入图片描述
在这里列出了使用loag_again 和不适用load_again的两种情况,更多情况请自行仿真,若出现错误,欢迎指出。内容为原创,原创不易。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
FPGA(现场可编程门阵列)和STM32(一种基于ARM架构的微控制器)可以通过SPI(串行外设接口)协议进行通信。在这种通信方式中,FPGA作为SPI通信的从机,而STM32作为主机。 为了实现这种通信,我们需要使用Verilog语言编写从机FPGA的代码。首先,我们需要确定FPGASPI接口的基本参数,如数据位宽、时钟频率和时钟极性等。然后,我们可以使用Verilog语言编写从机的SPI控制器,将其连接到FPGA的其他逻辑电路中。 在Verilog代码中,我们需要实现SPI的Slave模式。在SPI通信中,从机始终被动地响应主机的指令,并将数据传送给主机。从机的Verilog代码需要包括两个关键部分:状态机和数据传输。 状态机是从机的控制核心,它根据主机的指令进行状态切换,并管理数据传输过程的流程控制。例如,当主机发起读取指令时,从机会进入接收状态,并将要传输的数据存储到缓冲区中。当主机发起写入指令时,从机会进入发送状态,并将数据从缓冲区传输给主机。 数据传输部分负责实际的数据传输。从机需要实现接收和发送两个功能。接收部分负责接收主机发送的数据,并将其存储到缓冲区中。发送部分负责从缓冲区中读取数据,并将其传输给主机。 在编写Verilog代码时,需要注意时序问题和信号同步。SPI通信需要精确的时钟同步,在从机和主机之间共享和交换数据需要遵循一定的时序要求。因此,在设计代码时要特别注意时钟同步和数据的正确传输顺序。 最后,我们需要将Verilog代码综合到FPGA芯片中,并进行功能验证和调试。在验证过程中,我们可以通过观察FPGA输出波形和和STM32的通信结果来判断通信是否成功。如果通信出现问题,我们可以通过调试代码和时序分析来进行故障排查和修复。 通过以上步骤,我们可以实现FPGA作为从机与STM32进行SPI协议通信,并使用Verilog语言完成代码的设计与实现
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值