欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

FPGA与某个DAC芯片的SPI配置

程序员文章站 2024-02-23 22:56:36
...
//FPGA做主机,数模转换器(DAC)芯片做从机,实现SPI接口的配置(SPI串行外设接口)
//该DAC芯片的SPI配置的寄存器是8位的
//7位是读写选择信号,读信号高有效,写信号低有效
//6位和第5位是读写几个BYTE的定义,001BYTE);012BYTE);103BYTE);114BYTE//4~0位,是要操作的目标寄存器的地址
module 	spi_crl(
		input	wire sclk,//系统时钟50MHz,使用该时钟做全局时钟
		input 	wire rst_n,//复位信号
		input 	wire work_en,//模块工作的使能端
		output 	reg  conf_end,//配置结束的标志
		output 	wire spi_clk,//SPI的时钟,一般是50MHz到60MHz,
		output 	wire spi_sdi,//SPI的输入数据(由从机输出到主机)
		output 	wire spi_csn,//SPI的片选信号,低有效
		input 	wire spi_sdo//SPI的输出(由主机输出到从机),暂时不进行考虑,不使用
);
//将sclk与spi_sdo的中心对齐,是建立时间和保持时间达到最大,有充足的裕量,确保满足建立时间和保持时间。

//采用状态机来描述
parameter IDLE 	= 5'b00001;//空闲状态
parameter WAIT 	= 5'b00010;//等待状态,为了区分两次命令,
parameter R_MEM	= 5'b00100;//读memory变量的值
parameter W_REG	= 5'b01000;//写数据到寄存器中,命令8个byte,数据8个byte
parameter STOP 	= 5'b10000;//停止状态
parameter H_DIV_CYC = 5'd25-1;//定义一个分频的系数

//实现过程中涉及到的一些寄存器
reg [4:0] state;//状态机的寄存器变量,采用独热码
reg [4:0] div_cnt;//用来分频的计数器
reg clk_p;//正向时钟,与分频后的时钟有关,也有一个180°的相位
wire clk_n;//反向时钟,也就是clk_p的反向,即没有相位的改变,这个才是SPI的时钟。
reg pose_flag;//上升沿的标志位,用于分频同步
reg [3:0] wait_cnt;//等待时间的计数器,产生等待时间。
reg [3:0] shift_cnt;//数据结束的计时,也就是数据接收完后进入下一个状态
reg [4:0] r_addr;//读地址
wire [15:0] r_data;//读的数据
wire wren;//写使能信号
reg [15:0] shift_buf;
//定义一个串行数据的缓存器,即shift_buf,是在读完数据后(R_MEM状态后)进行缓存,但是在W_REG状态时才完成赋,因为有一个时钟的延时。
reg data_end;//32位数据写入结束的标志
reg sdi;//用来给spi_sdi赋值
reg csn;//用来给spi_csn赋值
reg tclk;//用来给spi_clk赋值

//以下是实现的过程

//分频器的实现,分频为1M的时钟;
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		div_cnt <= 5'd0;
	else if(div_cnt == H_DIV_CYC)
		div_cnt <= 5'd0;
	else 
		div_cnt <= div_cnt + 1'b1;
		
//clk_p的产生,即一个方波,
//分频时钟不允许做及寄存器的触发时钟,也就是不能出现在always块的触发列表中
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		clk_p <= 1'b0;
	else if(div_cnt == H_DIV_CYC)
		clk_p <= ~clk_p;
//时钟取反得到SPI的时钟
//这里解释取反的原因,因为在sclk的上升沿时钟有效,即可以认为sclk的0时刻是一个上升沿,但是clk_p的上升沿是在计数到24后也就是25个时钟后的地方,与sclk的上升沿没有对齐,根据波形可以知道,取反后,即clk_n=~clk_p,实现了sclk和spi_clk的同步。
assign clk_n = ~clk_p;		

//上升沿的标志位的产生,用于分频同步
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		pose_flag <= 1'b0;
	else if(clk_p == 1'b0 && div_cnt == H_DIV_CYC)//在计数器计到24,而且clk_p=1,时拉高
		pose_flag <= 1'b1;
	else 
		pose_flag <= 1'b0;

//等待时间的产生
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		wait_cnt <= 4'b0;
	else if(state == WAIT && pose_flag == 1'b1)//要按时钟来计数,
		wait_cnt <= wait_cnt + 1'b1;
	else if(state != WAIT)//只有在等待状态时才计数
		wait_cnt <= 4'b0;	
		
//状态机的描述		
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		state <= IDLE;
	else case(state)
			IDLE:if(work_en == 1'b1)
					state <= WAIT;
			WAIT:if(wait_cnt[3] == 1'b1)//等待了8个spi_clk的时间
					state <= R_MEM;
			R_MEM:state <= W_REG;//这里不需要等待,读出来后就可以写入数据
			W_REG:if(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end != 1'b1)
				  state <= WAIT;
				  else if(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end == 1'b1)
				  state <= STOP;
			STOP: state <= STOP;
			default: state <= IDLE;
		 endcase 

//shift_cnt的定义,也就是每一条命令的时间
always @(posedge sclk or negedge rst_n)
    if(rst_n == 1'b0)
			shift_cnt <= 4'b0;
	 else if(state == W_REG && pose_flag == 1'b1)
			shift_cnt <= shift_cnt + 1'b1;
	 else if(state != W_REG)//不在写寄存器状态时直接不使用该计数器
			shift_cnt <= 4'b0;

//读memory的地址的产生
always @(posedge sclk or negedge rst_n)
	  if(rst_n == 1'b0)
			r_addr <=5'b0;
	  else if(state == R_MEM)
			r_addr <= r_addr + 1'b1;
assign wren = 1'b0;	//这里使用的RAM只读不写,所以赋值为1,让写使能无效	

//shift_buf的值赋值,
always @(posedge sclk or negedge rst_n)
	 if(rst_n == 1'b0)
			shift_buf <= 5'b0;
	 else if(state == R_MEM)//将RAM中读取的数据赋值给shift_buf
			shift_buf <= r_data;
	 else if(state == W_REG && pose_flag == 1'b1)
			shift_buf <= {shift_buf[14:0],1'b1};

//data_end的产生,最后一个需要移位的数据
always @(posedge sclk or negedge rst_n)			
	 if(rst_n == 1'b0)
			data_end <= 1'b0;
	 else if(state == R_MEM && (&r_addr) == 1'b1)//地址从00000~11111,即取完了所有的数据,等效于r_addr==5'd31;
			data_end <= 1'b1;

//数据的输出
always @(posedge sclk or negedge rst_n)	
	 if(rst_n == 1'b0)
			sdi <= 1'b0;
	 else if(state == W_REG)
			sdi <= shift_buf[15];//将RAM中的数据的读取出来后赋值给寄存器再进行写入
    else if(state != W_REG)
			sdi <= 1'b0;
			
//片选信号的生成,低有效
always @(posedge sclk or negedge rst_n)
	 if(rst_n == 1'b0)
			csn <= 1'b1;//低有效,所以初始化位1,无效的状态
	 else if(state == W_REG)
			csn <= 1'b0;
	 else 
			csn <= 1'b1;

//spi_clk的产生,即tclk,在不工作的时候是低电平
always @(posedge sclk or negedge rst_n)
	 if(rst_n == 1'b0)
			tclk <= 1'b0;
	 else if(state == W_REG)
	       tclk <= clk_n;
	 else 
			 tclk <= 1'b0;

//配置结束标志的产生
always @(posedge sclk or negedge rst_n)
	 if(rst_n == 1'b0)	 
			conf_end <= 1'b0;
	 else if(state == STOP)
			conf_end <= 1'b1;
	 
//进行连线
assign spi_clk = tclk;
assign spi_sdi = sdi;
assign spi_csn = csn;

//例化一个RAM,用来读取数据
ram_16x32_sr	ram_16x32_sr_inst (
	.address ( r_addr ),//读地址
	.clock ( sclk ),
	.data ( 16'd0 ),//写的数据
	.wren ( wren ),//写使能高有效,读使能低有效
	.q ( r_data )//读数据
	);
endmodule
`timescale 1ns/1ns
module tb_ex_spi;
reg sclk;
reg rst_n;
reg work_en;
wire spi_clk;
wire spi_sdi;
wire spi_csn;
reg [15:0] send_mem [31:0];//定义一个memory变量
reg [15:0] shift_buf;

wire spi_sdo;
wire  conf_end;


initial begin 
		rst_n=0;
		sclk=0;
		#100;
		rst_n=1;
		end
		
initial begin 
		work_en=0;
	   #150;
	   work_en=1;
	   end

initial begin
		$readmemb("dac_ini_16x32.mif",send_mem);//memory变量的初始化
		end 
	
initial begin
		rec_spi();
		end 
		
always #10 sclk=~sclk;

spi_crl spi_crl_inst(
		.sclk		(sclk)		,
		.rst_n		(rst_n)		,
		.work_en	(work_en)	,
		.conf_end	()		,
		.spi_clk	(spi_clk)		,
		.spi_sdi	(spi_sdi)		,
		.spi_csn	(spi_csn)		,
		.spi_sdo	()	//读输入管脚不进行配置
);

//定义一个任务,用于接收数据,验证写入的数据是否为读取到的数据
task rec_spi();
		integer i,j;
		begin 
			for(i=0;i<32;i=i+1)
				begin
					for(j=0;j<16;j=j+1)
						begin
							@(posedge spi_clk);
								shift_buf={shift_buf[14:0],spi_sdi};
							if(j == 15 && shift_buf == send_mem[i])
								$display("ok data index is %d rec_d=%b send_d=%b",i,shift_buf,send_mem[i]);
							else if(j == 15)
								$display("error");		
						end
				end
		end 
		
endtask
endmodule 
quit -sim
.main clear

vlib work
vmap work work

vlog -work work tb_ex_spi.v
vlog ./../design/spi_ctrl.v
vlog ./../quartus_prj/ipcore_dir/ram_16x32_sr.v
vlog ./altera_lib/*.v

vsim -voptargs=+acc work.tb_ex_spi

add wave  tb_ex_spi/spi_crl_inst/*
add wave  tb_ex_spi/rec_spi/*

run 300us
memory的数据
0000000001110000
0000000100110001
0000001000000000
0000001100010000
0000010011111111
0000010100000000
0000011000000000
0000011100000000
0000100000000000
0000100101010101
0000101010101010
0000101101010101
0000110010101010
0000110101010101
0000000000000000
0000111101010101
0001000010101010
0001000100100100
0001000100000010
0001001111000010
0001010000000000
0001010100000000
0001011000000000
0001011100000100
0001100010000011
0001100100000000
0001101000000000
0001101100000000
0001110000000000
0001110100000000
0001111000100100
0001111101010010
RAM的初始化文件,用.mif文件。
-- Copyright (C) 1991-2013 Altera Corporation
-- Your use of Altera Corporation's design tools, logic functions 
-- and other software and tools, and its AMPP partner logic 
-- functions, and any output files from any of the foregoing 
-- (including device programming or simulation files), and any 
-- associated documentation or information are expressly subject 
-- to the terms and conditions of the Altera Program License 
-- Subscription Agreement, Altera MegaCore Function License 
-- Agreement, or other applicable license agreement, including, 
-- without limitation, that your use is for the sole purpose of 
-- programming logic devices manufactured by Altera and sold by 
-- Altera or its authorized distributors.  Please refer to the 
-- applicable agreement for further details.

-- Quartus II generated Memory Initialization File (.mif)

WIDTH=16;
DEPTH=32;

ADDRESS_RADIX=UNS;
DATA_RADIX=BIN;

CONTENT BEGIN
	0   :   0000000001110000;
	1   :   0000000100110001;
    2   :   0000001000000000;
	3   :   0000001100010000;
	4   :   0000010011111111;
	5   :   0000010100000000;
	6   :   0000011000000000;
    7   :   0000011100000000;
	8   :   0000100000000000;
	9   :   0000100101010101;
    10  :   0000101010101010;
	11  :   0000101101010101;
    12  :   0000110010101010;
	13  :   0000110101010101;
	14  :   0000000000000000;
	15  :   0000111101010101;
	16  :   0001000010101010;
    17  :   0001000100100100;
	18  :   0001000100000010;
	19  :   0001001111000010;
	20  :   0001010000000000;
	21  :   0001010100000000;
    22  :   0001011000000000;
	23  :   0001011100000100;
	24  :   0001100010000011;
	25  :   0001100100000000;
	26  :   0001101000000000;
    27  :   0001101100000000;
	28  :   0001110000000000;
	29  :   0001110100000000;
	30  :   0001111000100100;
	31  :   0001111101010010;
END;

寄存器的配置
FPGA与某个DAC芯片的SPI配置FPGA与某个DAC芯片的SPI配置状态转移图:
FPGA与某个DAC芯片的SPI配置modelsim仿真结果:
FPGA与某个DAC芯片的SPI配置