一般来讲,SPI通信,都是MCU作为主机,而FPGA作为从机。SPI数据从MCU输入,与FPGA不在同一时钟域。
一级D触发器可以实现上升沿、下降沿捕获电路。但是一级的D触发器,前一时刻的信号已经同步到同一时钟,而当前时刻直接从外部输入,与FPGA整体逻辑电路不在同一时钟域。可以使用两级D触发器。
为保证SPI总线数据的同步,其他信号/数据也需要经过2级D触发器输出同步。
//------------------------------------------------------------------------------------------------//分割线
`timescale 1ns/1ns
module spi_interface (
//clk&&rst_n
input rst_n ,
//spi_interface
input SCLK ,
input CS ,
input SDI ,
output SDO ,
//reg
output [ 7:0] WDATA ,
output [ 6:0] WADDR ,
output WDATA_VALID ,
input [ 7:0] RDATA ,
output [ 6:0] RADDR ,
output RDATA_VALID ,
output ADC_CLEAN
);
// =============================================================================== \
// *************** Interal signals and Interface *******************************
// =============================================================================== /
parameter idle = 7'b000_0001 ;
parameter write = 7'b000_0010 ;
parameter wdata_ready = 7'b000_0100 ;
parameter wrdata_wait = 7'b000_1000 ;
parameter read = 7'b001_0000 ;
parameter rdata_ready = 7'b010_0000 ;
parameter read_data = 7'b100_0000 ;
//state
reg [ 7:0] state_c ;
reg [ 7:0] state_n ;
reg [ 1:0] spi_cs ;
wire spi_cs_rise ;
reg [ 7:0] cnt ;
reg [15:0] data_in ;
//write
reg wr_flag ;
reg write_data_valid ;
reg [ 6:0] write_data_addr ;
reg [ 7:0] write_data ;
//read
reg read_data_valid ;
reg [ 6:0] read_data_addr ;
reg [ 7:0] spi_rdata ;
reg adc_reg_clean ;
// ============================================================================ \
// ************************ Main Code **********************************
// ============================================================================ /
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
spi_cs <= 2'b0 ;
end else begin
spi_cs[0] <= CS ;
spi_cs[1] <= spi_cs[0] ;
end
end
assign spi_cs_rise = ~spi_cs[1] & spi_cs[0] ;
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
cnt <= 8'b0 ;
end else begin
if(!CS) begin
cnt <= cnt + 1'b1 ;
end else begin
cnt <= 8'b0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
data_in <= 16'b0 ;
end else begin
if(CS == 1'b0) begin
if(cnt <= 8'd15) begin
data_in <= {data_in[14:0],SOI} ;
end else begin
data_in <= data_in ;
end
end else begin
data_in <= 16'd0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
wr_flag <= 1'b0 ;
end else begin
if(cnt == 8'b1) begin
wr_flag <= data_in[0] ;
end else begin
wr_flag <= 1'b0 ;
end
end
end
//因为write_data和address都是一个clk有效,所以需要有效信号write_data_valid
//而且在state_c==wdata_ready时cnt==18,因为data_in完全寄存了SDI
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
write_data_valid <= 1'b0 ;
end else begin
if(state_c == wdata_ready) begin
write_data_valid <= 1'b1 ;
end else begin
write_data_valid <= 1'b0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
write_data_addr <= 7'b0 ;
end else begin
if(state_c == wdata_ready) begin
write_data_addr <= data_in[14:8] ;
end else begin
write_data_addr <= 7'b0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
write_data <= 8'b0 ;
end else begin
if(state_c == wdata_ready) begin
write_data <= data_in[7:0] ;
end else begin
write_data <= 8'b0 ;
end
end
end
//read转rdata_ready的条件是cnt == 8'd7
//state_c == rdata_ready时cnt == 8'd8,此时刚好SDI在data_in的低八位进行存储。
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
read_data_valid <= 1'b0 ;
end else begin
if(state_c == rdata_ready && cnt == 8'd8) begin
read_data_valid <= 1'b1 ;
end else begin
read_data_valid <= 1'b0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
read_data_addr <= 7'b0 ;
end else begin
if(state_c == rdata_ready && cnt == 8'd8) begin
read_data_addr <= data_in[6:0] ;
end else begin
read_data_addr <= 7'b0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
spi_rdata <= 8'b0 ;
end else begin
if(state_c == rdata_ready && cnt == 8'd10 ) begin
spi_rdata <= RDATA ;
end else if(state_c == read_data) begin
spi_rdata <= spi_rdata << 1 ;
end else begin
spi_rdata <=8'b0 ;
end
end
end
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
adc_reg_clean <= 1'b0 ;
end else begin
if(state_c == write && cnt == 8'd16 && data_in[14:8] == 7'd3) begin
adc_reg_clean <= 1'b1 ;
end else begin
adc_reg_clean <= 1'b0 ;
end
end
end
// ============================================================================ \
// ************************ state **********************************
// ============================================================================ /
wire idle2writ ;
wire write2wdata_ready ;
wire wrdata_wait2idle ;
wire read2rdata_ready ;
wire rdata_ready2read_data ;
wire read_data2wrdata_wait ;
assign idle2writ = (cnt == 8'd2) && (wr_flag == 1'b0) && (state_c == idle) ;
assign write2wdata_ready = (cnt == 8'd16) && (state_c == write) ;
assign wrdata_wait2idle = spi_cs_rise && (state_c == wrdata_wait) ;
assign read2rdata_ready = (cnt == 8'd7) && (state_c == read) ;
assign rdata_ready2read_data = (cnt == 8'd10) && (state_c == rdata_ready) ;
assign read_data2wrdata_wait = (cnt == 8'd19) && (state_c == read_data) ;
always @ (posedge SCLK or negedge rst_n) begin
if(!rst_n) begin
state_c <= idle ;
end else begin
state_c <= state_n ;
end
end
always @ (*) begin
case(state_c)
idle : begin
if(idle2writ) begin
state_n = write ;
end else begin
state_n = read ;
end
end
write : begin
if(write2wdata_ready) begin
state_n = wdata_ready ;
end else begin
state_n = state_c ;
end
end
wdata_ready : begin
state_n = wrdata_wait ;
end
wrdata_wait : begin
if(wrdata_wait2idle) begin
state_n = idle ;
end else begin
state_n = state_c ;
end
end
read : begin
if(read2rdata_ready) begin
state_n = rdata_ready ;
end else begin
state_n = state_c ;
end
end
rdata_ready : begin
if(rdata_ready2read_data) begin
state_n = read_data ;
end else begin
state_n = state_c ;
end
end
read_data : begin
if(read_data2wrdata_wait) begin
state_n = wrdata_wait ;
end else begin
state_n = state_c ;
end
end
endcase
end
// ============================================================================ \
// ************************ output **********************************
// ============================================================================ /
assign WDATA = write_data_valid ;
assign WADDR = write_data_addr ;
assign WDATA = write_data ;
assign RDATA_VALID = read_data_valid ;
assign RADDR = read_data_addr ;
assign SDO = spi_rdata[7] ;
assign ADC_CLEAN = adc_reg_clean ;
endmodule