草庐IT

基础SPI协议的FPGA实现(兼顾SPI4种模式)—Verilog

呆呆打代码 2023-04-21 原文

简要说明

        看见许多博主都有编写SPI协议,但是大都是对一个指定的时序进行FPGA的实现。于是就想编写一个比较常见的SPI通信协议,而且兼顾4种模式和不同数据长度。主要是用来对常见SPI协议的应用,和辅助对萌新对SPI时序,模式的理解。

SPI介绍

        SPI协议是常见的低速通信协议,具体的协议介绍就不太谈了。其它博主有十份优秀的帖子,此处只是说明一下特别注意事项。

        首先是对SPI的使用应该具体到一个芯片的datasheet的时序图,不同的芯片有不同的要求。

        必须理解        cpol  定义数据线 时钟线空闲时的状态 0->低电平 1->高电平。
                              cpha 定义数据在时钟的第几个边沿有效 0->第一个边沿 1->第二个边沿。

        SPI的4种模式就是[cpol,cpha] 组合而成,分别表示sclk空闲的状态和数据有效时刻。 

Verilog的实现    

        在此处的Verilog的实现是基于常见的SPI协议,可以兼顾4种模式和不同数据长度。该程序的核心思想就是利用好cpol,cpha信号,所以对4种模式不需要特别的生成多个对应电路。

主机:

`timescale 1ns / 1ps
//
// Company: 	/-----\
// Engineer:   [|^   ^|]					
//			    |  v  |
//				[-----]
// Create Date: 
// Design Name: 
// Module Name: spi_contrl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module spi_master#(
parameter 	cpol = 1,		// 定义数据线 时钟线空闲时的状怿 0->低电广 1->高电广
			cpha = 1,		// 定义数据在时钟的第几个上升沿有效 0->第一个上升沿 1->第二个上升沿
			sclk_cycle=10,  //定义sclk占几个clk_i
			data_width=8 	//定义收发位宽
)(	input clk_i,
	input rst_n_i,
	input spi_m_start_i,
	input  [ data_width-1 : 0 ] tx_data_i  ,
	output reg [ data_width-1 : 0 ] rx_data_o ,
	
	output sclk_o,
	output cs_n_o,
	output reg mosi_o,
	input miso_i

	);
	
	reg [5:0] sclk_cnt =0;		//sclk分频计数
	reg 	  sclk     =0;
	reg 	  sclk_dly =0;
	
	reg [5:0] prog_cnt=0;  		//spi程序运行计数	方便后面的赋值
	reg 	  cs_n      ;
	reg   	  spi_start_dly=0;
	reg [ data_width-1 : 0 ] tx_data=0;
	reg [ data_width-1 : 0 ] rx_data=0;
	
	对cs_n信号的处理//
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) 	begin
			cs_n <= 1;
			spi_start_dly <=0;
			end
		else begin 
			spi_start_dly <=spi_m_start_i;
			if( !spi_start_dly && spi_m_start_i)		/主机操作的上升沿就拉低cs_n
				cs_n <=0;
			else if (sclk_cnt==sclk_cycle-1 && prog_cnt == data_width)	//通过计数器实现对spi是否完成的判断,完成后cs_n拉高
				cs_n <=1;
			else cs_n <= cs_n;
		
		end 
	assign cs_n_o =cs_n;	
		
	/时钟分频和程序计数块///
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) begin
			prog_cnt <= 0;
			sclk_cnt <= 0;
			end  
		else if( !cs_n ) begin
			if ( sclk_cnt >= sclk_cycle-1)
				sclk_cnt <= 0;
			else 	sclk_cnt <=sclk_cnt+1;
				
			if (sclk_cnt==sclk_cycle-1 && prog_cnt == data_width)	//prog_cnt计数到data_width+1个 sclk 周期
				prog_cnt <= 0;
			else if (  sclk_cnt >= sclk_cycle-1)
				prog_cnt <= prog_cnt+1;
			else prog_cnt <= prog_cnt;
	
			end 
	//		sclk变化赋值   
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) begin
			sclk	 <= cpol;
			sclk_dly <= cpol;
			end 
		else  begin
		sclk_dly <=sclk;
		if (!cs_n) begin
			if( prog_cnt!=0 && sclk_cnt == 0  )  		//利用cpol信号对sclk进行变化
					sclk <= !cpol;
			else if( prog_cnt!=0 && sclk_cnt == (sclk_cycle/2)  )  	//半个周期就变化一次
					sclk <= cpol;
			end		
		else  sclk <=cpol;
		
		end 
		
	assign sclk_o =sclk;
		
	/输出数据偏移//	
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) 
			tx_data <= 0;  
		else if (!spi_start_dly && spi_m_start_i) 	
			tx_data <= tx_data_i;
		else if (!cs_n  && ((cpha==0 && cpol==0)||(cpha && cpol)) )begin	//0或3模式下的输出数据的高位偏移
				if ( sclk_dly==!cpol && sclk==cpol)
				tx_data <= {tx_data[data_width-2:0],1'b0};
				end 
			else if (!cs_n  &&  ((cpha && cpol==0)||(cpha==0 && cpol))) begin   //1或2模式下的输出数据的高位偏移
				if ( sclk_dly==cpol && sclk==!cpol)
				tx_data <= {tx_data[data_width-2:0],1'b0};
				end
				
				
				
				
			
//输出数据到mosi_o的赋值///
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) 
			mosi_o <= 0;  
		else  if (!cs_n  && !cpha) begin		//还是利用cpha信号下数据变化的特点 cpha==0 时需要提前赋值,
				if ( (prog_cnt==0 && sclk_cnt == (sclk_cycle/2))||(prog_cnt!=0 && sclk_cnt == (sclk_cycle/2+2)))
				mosi_o <= tx_data[data_width-1];
				end 
			else if (!cs_n  && cpha) begin
				if (prog_cnt!=0 && sclk_cnt == (sclk_cycle/2+2)) 
				mosi_o <= tx_data[data_width-1];
				end
				
	always @(posedge clk_i or negedge rst_n_i)			//输入的数据介绍
		if(!rst_n_i) 
			rx_data <= 0;  
		else if (prog_cnt ==data_width-1&&sclk_cnt==sclk_cycle-1)
				rx_data_o<=rx_data;
		else  if (!cs_n  && ((cpha==0 && cpol==0)||(cpha && cpol))) begin
				if ( sclk_dly==!cpol && sclk==cpol)
				rx_data <= {rx_data[data_width-2:0],miso_i};
				end 
			else if (!cs_n  &&  ((cpha && cpol==0)||(cpha==0 && cpol))) begin
				if ( sclk_dly==cpol && sclk==!cpol)
				rx_data <= {rx_data[data_width-2:0],miso_i};
				end			
				
			
	
endmodule

从机:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
//
// Design Name: 		/-----\
//					   [|^   ^|]
//						|  v  | 
//						|-----|
//
// Module Name: spi_salve
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module spi_salve#(
parameter 	cpol = 1,		// 定义数据线 时钟线空闲时的状态 0->低电平 1->高电平
			cpha = 1,		// 定义数据在时钟的第几个沿有效 0->第一个沿 1->第二个沿
			data_width=8 	//定义收发位宽
)(	input clk_i,
	input rst_n_i,
	input spi_s_start_i,
	input  [ data_width-1 : 0 ] tx_data_i  ,
	output reg [ data_width-1 : 0 ] rx_data_o ,
	input  sclk_i,
	input  cs_n_i,
	input  mosi_i,
	output miso_o

	);
	
	reg prog_flag	 =0;    //定义一个发数据操作寄存器 当salve有数据要发送而且没有完成时 为高
	reg spi_start_dly=0;
	reg spi_start_dly_1=0;
	reg cs_n_dly	 =0;
	reg sclk		 =cpol;
	
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) begin	
			sclk  <= cpol;
			end 
		else sclk <= sclk_i;
	
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) begin	
			prog_flag 		<= 0;
			spi_start_dly	<= 0;
			spi_start_dly_1 <= 0;
			cs_n_dly		<= 0;
			end 
		else begin
			spi_start_dly 	<= spi_s_start_i;
			spi_start_dly_1	<=spi_start_dly;
			cs_n_dly		<= cs_n_i;
			if( !spi_start_dly && spi_s_start_i  )///对程序进行的判断
				prog_flag <= 1;
			else if ( cs_n_dly && cs_n_i )
				prog_flag <= 0;
			end 
			
			
	reg [ data_width-1 : 0 ] tx_data = 0;		
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) 			
		 tx_data <= 0;		
		else if ( !spi_start_dly && spi_s_start_i )	
				tx_data <= tx_data_i;
			else if ( prog_flag ) begin
					if ( !cpha &&  sclk==cpol && sclk_i==!cpol)	//cpha==0 sclk的cpol变沿进行发数据变化
						tx_data <={ tx_data[ data_width-2:0 ],1'b0};
				else if ( cpha &&  sclk==!cpol && sclk_i==cpol)
						tx_data <={ tx_data[ data_width-2:0 ],1'b0};
			end 
				
			
			
	reg miso =0;
	
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) 			
		 miso <= 0;
		else  if ( !cs_n_i ) begin
				if ( cpha &&  sclk==cpol && sclk_i==!cpol)
						miso <=tx_data[ data_width-1];
				else if ( !cpha &&  ((sclk==!cpol && sclk_i==cpol) || (!spi_start_dly_1 && spi_start_dly))) 
						miso <=tx_data[ data_width-1];
			end 
			else miso <=0;
				
		assign miso_o = miso;

	reg [ data_width-1 : 0 ] rx_data=0;	
	always @(posedge clk_i or negedge rst_n_i)
		if(!rst_n_i) 	begin		
		 rx_data <= 0;
		 rx_data_o<=0;
		 end
		 else if ( !cs_n_dly && cs_n_i )
			rx_data_o<=rx_data;
		else  if ( !cs_n_i ) begin
				if ( !cpha &&  sclk==cpol && sclk_i==!cpol)		//cpha==0 sclk的cpol变沿进行收数据变化	
						rx_data  <={rx_data[ data_width-2 : 0 ],mosi_i};
				else if ( cpha &&  (sclk==!cpol && sclk_i==cpol)) 
						rx_data  <={rx_data[ data_width-2 : 0 ],mosi_i};
			end 
	
	 
	
	
	
endmodule

仿真:

`timescale 1ns / 1ps


// Company: 
// Engineer:
//
// Create Date:   
// Design Name:   spi_contrl
// Module Name:   E:/FPGA/vivado/spi_test/spi_ise/spi_master/spi_contrl_tb.v
// Project Name:  spi_master
// Target Device:  
// Tool versions:  
// Description: 
//
// Verilog Test Fixture created by ISE for module: spi_contrl
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 


module spi_contrl_tb;

parameter 	cpol = 0;		// 定义数据线 时钟线空闲时的状怿 0->低 1->高
parameter	cpha = 0;		// 定义数据在时钟的第几个上升沿有效 0->第一个沿 1->第二个沿
parameter	sclk_cycle=10;  //定义sclk占几个clk_i
parameter	data_width=12; 	//定义收发位宽


	reg clk_i;
	reg rst_n_i;
	reg spi_start_i;
	reg [ data_width-1 : 0 ]tx_data_i  ,tx_data_s;
	wire[ data_width-1 : 0 ] rx_data_o ;
	
	wire sclk_o;
	wire cs_n_o;
	wire mosi_o;
	reg   miso_i;


 spi_master#(
 	.cpol(cpol),			// 定义数据线 时钟线空闲时的状怿 0->低 1->高
	.cpha(cpha),			// 定义数据在时钟的第几个上升沿有效 0->第一个沿 1->第二个沿
	.sclk_cycle(sclk_cycle),//定义sclk占几个clk_i
	.data_width(data_width) //定义收发位宽
			

)spi_contrl_u(	
	.clk_i		(clk_i		),
	.rst_n_i		(rst_n_i		),
	.spi_m_start_i(spi_start_i),
	.tx_data_i	(tx_data_i	),
	.rx_data_o	(rx_data_o	),
	             
	.sclk_o		(sclk_o		),
	.cs_n_o		(cs_n_o		),
	.mosi_o		(mosi_o		),
	.miso_i		(miso_i		)

	);
	
reg spi_start_s	;
	
 spi_salve#(
 	.cpol(cpol),				// 定义数据线 时钟线空闲时的状怿 0->低  1->高
	.cpha(cpha),				// 定义数据在时钟的第几个上升沿有效 0->第一个沿 1->第二个沿
                                
	.data_width(data_width) 	//定义收发位宽
			

)spi_salve_u(	
	.clk_i		(clk_i		),
	.rst_n_i		(rst_n_i		),
	.spi_s_start_i(spi_start_s),
	.tx_data_i	(	tx_data_s),
	.rx_data_o	(	),
	             
	.sclk_i		(	sclk_o	),
	.cs_n_i		(	cs_n_o	),
	.mosi_i	(	mosi_o	),
	.miso_o		(		)

	);
	
	
	
always #5 clk_i=~clk_i;

initial begin

	clk_i=0;
	rst_n_i=0;
	spi_start_i=0;
	tx_data_i =  12'b1010101110 ;
	miso_i=0;
	spi_start_s=0;
#100;
	rst_n_i=1;
#100;	
	spi_start_i=1;
	tx_data_i =  12'b1010101110 ;
	spi_start_s=1;
	tx_data_s<=12'b10101010101111;
#20;
	spi_start_i=0;
spi_start_s=0;
#10;


	


end 
      
endmodule

仿真图:

2模式12bit仿真图

        在仿真图中粉色是常量,粉色上面信号是主机信号,下面是从机信号。分析2模式可以看见,主机输出mosi_o在sclk的下降沿有效。所以tx_data在另外的沿变化。

 0模式12bit仿真图

        该仿真直接把主机从机连接就行,需要注意的是在从机的start信号必须在sclk没有对数据操作前完成,否则影响tx_data的值。4种模式都仿真过,数据没有错误。读者可以自己多试试,有错误希望各位提醒一下。该程序主要利用好cpol,cpha信号。完成对4种模式的兼容,有大佬说可以用状态机,如果大家有需要也可以实验室用状态机编写。

 

有关基础SPI协议的FPGA实现(兼顾SPI4种模式)—Verilog的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  9. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  10. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

随机推荐