FPGA Verilog他山之石系列之3 带FIFO的SPI发送驱动

Posted by

SPI可以说是FPGA最重要的接口了,它的可以发送任何自定义bit流。但是我们系统时钟是一个固定值,当我们要调整SPI的发送串行时钟时,采用FIFO既可以实现跨时钟域处理,有可以充当缓存。

但是我们需要注意的是FIFO输出模式,有正常模式,也有数据提前准备的模式。其中正常模式,数据要读使能的下个时钟出来,新手往往很难处理这种情况。

我们建议使用状态机来简化对FIFO的读出测操作。

上代码

module array_spi_tx(
	input								CLK_i				,//系统时钟
	input								RST_i				,
	input								spi_tx_clk			,  //  可以更改
	input				[351	:0]			spi_tx_data			,// 串口读到需下发数据
	input				[11	:0]			spi_tx_length		        ,// 串口读到数据字长
	input								spi_tx_en			,
	output	reg							spi_tx_o				
    );
	localparam				tx_idle		=	2'b00				,
						tx_delay	=	2'b01				,
					        tx_pack		=	2'b10				,
						tx_send		=	2'b11				;

	reg	    [1	    :0]		tx_state				;
	reg	    [11	    :0]		tx_count				;
	reg	    [11	    :0]		spi_tx_length_r				;
	reg	    [351    :0]		tx_data0				;
	reg				rd_en					;
	wire	[363	:0]		dout					;
	wire				full, empty				;

	spi_tx_fifo u_spi_tx_fifo (
		.wr_clk			(	CLK_i					),// input
		.wr_rst			(      ~RST_i                                   ),// input 1复位
		.wr_en			(	spi_tx_en				),// input
		.wr_data		(      {spi_tx_length, spi_tx_data}	        ),// input [289:0]
		.wr_full		(	full					),// output
		.almost_full	        (						),// output
		.rd_clk			(	spi_tx_clk				),// input
		.rd_rst			(      ~RST_i					),// input 1复位
		.rd_en			(	rd_en					),// input
		.rd_data		(	dout					),// output [289:0]
		.rd_empty		(	empty					),// output
		.almost_empty	        (						) // output
	);	

	always @(posedge spi_tx_clk or negedge RST_i)begin
		if(~RST_i)begin
			spi_tx_o		<= 1'b1								;
			tx_count		<= 12'd0							;
			tx_data0		<= 352'd0							;
			
			rd_en			<= 1'b0								;
			
			tx_state		<= tx_idle							;
		end
		else case (tx_state)
		tx_idle:begin
			if(empty == 1'b0)begin
				rd_en		<= 1'b1								;
				tx_state	<= tx_dely							;
			end
			else begin
				rd_en		<= 1'b0								;
				tx_state	<= tx_idle							;
			end
		end
		tx_delay:begin
			rd_en			<= 1'b0								;
			tx_state		<= tx_pack							;
		end
		tx_pack:begin
			tx_data0		<= dout[351	: 0]						;
			tx_count		<= dout[363-:12]						;
			spi_tx_length_r         <= dout[363-:12]					        ;
			tx_state		<= tx_send							;
		end
		tx_send:begin
			if( tx_count > 12'd0 ) begin
				tx_count	<= tx_count - 1 						;
				spi_tx_o        <= tx_data0[spi_tx_length_r - 1]	                        ;
				tx_data0        <= tx_data0<<1							;
			end
			else begin
				spi_tx_o	<= 1'b1								;
				tx_state	<= tx_idle							;
			end
		end
		default:
				tx_state	<= tx_idle							;
		endcase
	end
endmodule

处理的诀窍增加了一个状态delay来等一拍 因为有些FIFO读取时序是这样,如果不需要,修改去掉delay这个状态即可

我们来总结一个这个模块:

1 FIFO引入实现了跨时钟域和数据缓存

2 delay状态的有无照顾各种类型的FIFO

3 数据中带有需要发送SPI比特流的长度信息,实现任意长度的SPI数据发送(当然受制于FIFO的数据宽度)

4 SPI的时钟可以随时自己更改

结论:

这是一个优秀的设计,非常值得FPGA的初学者参考或直接用于项目中。

下期再见

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

我们将24小时内回复。
取消