今天讲的是FPGA SPI的配置主要是以TI的DAC3283为例
主要有写时序和读时序如下图1,图2所示:

图1:写时序

图2:读时序
今天主要介绍的是写时序:SCLK是DAC3283的串行接口时钟输入。串行数据使能SDENB是DAC3283的使能端。SDIO是串行数据输入端,将DAC3283的输入数据在SCLK的上升边缘上触发。

图3:写时序状态流程图
代码实现:
module spi_ctrl(input wire sclk,//系统时钟50Mhzinput wire rst_n,input wire work_en,ouput reg conf_end,output wire spi_clk,//小于50-60Mhz,这里使用1Mhzoutput wire spi_sdi,output wire spi_csninput wire spi_sdo//读输入管脚先不进行编程);parameter IDLE = 5'b0_0001;parameter WAIT = 5'b0_0010;parameter R_MEM= 5'b0_0100;parameter W_REG= 5'b0_1000;parameter STOP = 5'b1_0000;parameter H_DIV_CYC = 5'd25-1;//按位与a=&4'b001=1'b0,相邻两位相与reg [4:0] state;//状态机寄存器变量,编码方式采用独热码reg [4:0] div_cnt;reg clk_p=1'b0;wire clk_n;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;reg data_end;reg sdi;reg csn;reg tck;//分频计数器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 <= 'd0;elsediv_cnt=div_cnt+1'b1;//发送数据时钟,分频计数器时钟不允许做寄存器触发时钟,也就是不能卸载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;assign clk_n=~clk_p;//时钟沿alwsys @ (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)pose_flag<=1'b1;elsepose_flag<=1'b0;//等待时间alwsys @ (posedge sclk or negedge rst_n)if(rst_n==1'b0)wait_cnt<='d0;else if(state==WAIT &&pose_flag==1'b1)wait_cnt<=wait_cnt+1;else if(state !=WAIT)wait_cnt<=4'd0;//shift移出数据alwsys @ (posedge sclk or negedge rst_n)if(rst_n==1'b0)shift_cnt<='d0;else if(state==W_REG && pose_flag==1'b1)shift_cnt=shift_cnt+1'b1;else if(state != W_REG)shift_cnt<=4'd0;//读mem的地址产生always@(posedge sclk or negedge rst_n)if(rst_n==1'b0)r_addr<='d0;else if(stete=R_MEM)r_addr<=r_addr+1'b1;assign wren=1'b0;always@(posedge sclk or negedge rst_n)if(rst_n==1'b0)shift_buf<='d0;else if(state==R_MEM)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)//等效于r_addr==5'd31data_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];else if(state!=W_REG)sdi<=1'b0;//使能信号always @ (posedge sclk or negedge rst_n)if(rst_n==1'b0)csn<=1'b0;else if(state==W_REG)csn<=1'b0;elsecsn<=1'b1;//时钟always @ (posedge sclk or negedge rst_n)if(rst_n==1'b0)tck<=1'b0;else if(state<=W_REG)tck<=clk_n;elsetck<=1'b0;assign spi_clk=tck;assign spi_csn=csn;assign spi_sdi=sdi;//fmsalways@(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)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//输出标志,级联使用always @ (posedge sclk or negedge rst_n)if(rst_n==1'b0)conf_end<=1'b0;else if(state==STOP)conf_end<=1'b1;//例化语句endmodule//时钟skew时钟的相位偏差度,时钟偏斜
测试代码
1ns/1nsmodule tb_ex_spi;reg sclk;reg rat_n;reg work_en;initial beginrst_n=0;sclk=0;#100;rst_n=1;endinitial beginwork_en=0;#150work_en=1;endalways #10 sclk=~sclk;spi_ctrl spi_ctrl_inst((sclk);(rst_n);(work_en);();();();();(););endmodule