`timescale 1ns/1ns
//为适应菊花链dummy需求,状态机中增加dummy状态来做sck输出
module spi_master
#(
parameter CLK_FREQUENCE = 50_000_000 , //系统时钟50M
SPI_FREQUENCE = 12_500_000 , //SPI 速率不超过15M,分区数少建议频率低一点
WDATA = 16 ,
CPOL = 1'b0,
CPHA = 1'b0
)
(
input clk , //system_clock
input rst_n , //system_rst rst=0;
input spi_wr , //SPI传输开始使能,1脉冲
input [WDATA -1 :0] data_in , //输入数据
// input data_vild ,
input [WDATA -1 :0] dummy_num , //控制输出延时空白
input d_start ,
output reg finish ,
output reg [WDATA -1 :0] data_out ,
output reg sck ,
output mosi ,
input miso ,
output csb
);
parameter CNT = (CLK_FREQUENCE/SPI_FREQUENCE)*2;
parameter CNT_WDITH = clogb2(CLK_FREQUENCE/SPI_FREQUENCE);
parameter SHIFT_WDITH = clogb2(WDATA);
//SPI_TX状态机
reg [2:0] tx_state_c,tx_state_n;
reg [SHIFT_WDITH - 1 :0] dummy_cnt ; //
reg [SHIFT_WDITH - 1 :0] tx_shift_cnt;
reg [WDATA + CPHA -1 :0] tx_shift_r;
reg mosi_out;
reg cs;
localparam IDLE = 3'b000;
localparam START = 3'b001;
localparam SHIFT = 3'b010;
localparam DUMMY = 3'b011;
localparam FINISH = 3'b100;
//==============================================================================
// sck时钟产生
//==============================================================================
reg [CNT_WDITH - 1: 0] clk_cnt;
reg clk_cnt_en;
always@ (posedge clk or negedge rst_n) //sck时钟计数
begin
if(!rst_n)
clk_cnt <= 'd0;
else if(clk_cnt_en == 1'b1)
if(clk_cnt == CNT - 1)
clk_cnt <= 'd0;
else
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 'd0;
end
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_cnt_en <= 1'b0;
else
if(clk_cnt == CNT - 1)
if(tx_state_n == FINISH)
clk_cnt_en <= 1'b0;
else
clk_cnt_en <= clk_cnt_en;
else
if(tx_state_c == SHIFT || tx_state_c == DUMMY)
clk_cnt_en <= 1'b1;
else
clk_cnt_en <= clk_cnt_en;
end
always@ (posedge clk or negedge rst_n)
begin
if(!rst_n)
sck <= CPOL[0];
else if(clk_cnt_en == 1'b1)
if(clk_cnt == CNT/2 - 1)
sck <= ~CPOL[0];
else if(clk_cnt == CNT - 1)
sck <= CPOL[0];
else
sck <= sck;
else
sck <= CPOL[0];
end
wire sck_posedge;
wire sck_negedge;
reg sck0;
reg sck1;
always@ (posedge clk or negedge rst_n)
begin
if(!rst_n)begin
sck0 <= CPOL[0];
sck1 <= CPOL[0];
end
else if(clk_cnt_en == 1'b1)begin
sck0 <= sck;
sck1 <= sck0;
end
else begin
sck0 <= CPOL[0];
sck1 <= CPOL[0];
end
end
assign sck_posedge = sck0 && ~sck1;
assign sck_negedge = sck1 && ~sck0;
wire shift_en;
wire sampl_en;
generate
case({CPOL[0],CPHA[0]})
2'b00:begin
assign shift_en = sck_negedge;
assign sampl_en = sck_negedge;
end
2'b01:begin
assign shift_en = sck_posedge;
assign sampl_en = sck_posedge;
end
2'b10:begin
assign shift_en = sck_posedge;
assign sampl_en = sck_posedge;
end
2'b11:begin
assign shift_en = sck_negedge;
assign sampl_en = sck_negedge;
end
default:begin
assign shift_en = sck_negedge;
assign sampl_en = sck_negedge;
end
endcase
endgenerate
// 1
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)begin
tx_state_c <= IDLE;
end
else begin
tx_state_c <= tx_state_n;
end
end
// 2
always@(*)
begin
case(tx_state_c)
IDLE:begin
if(spi_wr == 1'b1)
tx_state_n = START;
else if(d_start == 1'b1)
tx_state_n = DUMMY;
else
tx_state_n = IDLE;
end
START:begin
tx_state_n = SHIFT;
end
SHIFT:begin
if((tx_shift_cnt == WDATA -1 + CPHA)&&(clk_cnt == CNT - 1))begin
tx_state_n = FINISH;
end
else begin
tx_state_n = SHIFT;
end
end
DUMMY:begin
if((dummy_cnt == dummy_num - 1 + CPHA)&&(clk_cnt == CNT - 1))
tx_state_n = FINISH;
else
tx_state_n = DUMMY;
end
FINISH:begin
tx_state_n = IDLE;
end
default:begin
tx_state_n = IDLE;
end
endcase
end
// 3
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)begin
// clk_cnt_en <= 1'b0;
tx_shift_cnt <= 4'd0;
tx_shift_r <= 16'd0;
dummy_cnt <= 4'd0;
mosi_out <= 1'b0; //输出寄存器
cs <= 1'b1;
finish <= 1'b0;
end
else case(tx_state_c)
IDLE:begin
// clk_cnt_en <= 1'b0;
tx_shift_cnt <= 4'd0;
tx_shift_r <= 16'd0;
dummy_cnt <= 4'd0;
mosi_out <= 1'b0; //输出寄存器
cs <= 1'b1;
finish <= 1'b0;
end
START:begin
// clk_cnt_en <= 1'b1;
tx_shift_r[WDATA-1 :0] <= data_in;
cs <= 1'b0;
end
SHIFT:begin
if(shift_en)begin
// mosi_out <= tx_shift_r[WDATA + CPHA -1];
tx_shift_r <= {tx_shift_r[WDATA-1 :0],1'b0};
tx_shift_cnt <= tx_shift_cnt + 1'b1;
end
// clk_cnt_en <= 1'b1;
cs <= 1'b0;
end
DUMMY:begin
if(shift_en)begin
mosi_out <= 1'b0;
dummy_cnt <= dummy_cnt + 1'b1;
end
// clk_cnt_en <= 1'b1;
cs <= 1'b0;
end
FINISH:begin
// clk_cnt_en <= 1'b0;
cs <= 1'b1;
finish <= 1'b1;
tx_shift_r <= 'd0;
end
default:begin
end
endcase
end
assign mosi = tx_shift_r[WDATA + CPHA -1];
assign csb = cs;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
data_out <= 'd0;
else if (sampl_en)
data_out <= {data_out[WDATA-1:0],miso};
else
data_out <= data_out;
end
//位宽计算函数
function integer clogb2 (input integer depth);
begin
for (clogb2=0; depth>0; clogb2=clogb2+1)
depth = depth >>1;
end
endfunction
endmodule
tb
`timescale 1ns/1ns
module tb_spi();
parameter CLK_FREQUENCE = 50_000_000 ,
SPI_FREQUENCE = 12_500_000 ,
DATA_WIDTH = 16 ,
CPOL = 1'b1 ,
CPHA = 1'b0 ;
reg xtal_clk;
reg clk;
reg rst_n;
reg [15:0] data_in;
reg spi_wr;
reg [15:0] dummy_num;
reg d_start;
reg miso = 1'b0;
wire finish;
wire sck;
initial begin
xtal_clk = 0;
forever #10 xtal_clk= ~xtal_clk;
end
initial begin
clk = 0;
forever #10 clk = ~clk;
end
initial begin
rst_n = 0;
#200;
rst_n = 1;
end
initial fork
datain_generate;
spiwr_generate;
debug_information;
dstart_generate;
dummynum_generate;
join
task datain_generate;
begin
data_in = 0;
@(posedge rst_n)
data_in = 16'b1001_1101_1110_1011;
@(posedge finish)
data_in = 16'b1010_0011_1100_0110;
@(posedge finish)
;
@(posedge finish)
#200 $stop;
end
endtask
task spiwr_generate;
begin
spi_wr = 0;
@(posedge rst_n)
#20 spi_wr = 1;
#20 spi_wr = 0;
@(posedge finish)
#200 spi_wr = 1;
#20 spi_wr = 0;
end
endtask
task dstart_generate;
begin
d_start = 0;
@(posedge rst_n)
;
@(posedge finish)
;
@(posedge finish)
#200 d_start = 1;
#20 d_start = 0;
end
endtask
task dummynum_generate;
begin
dummy_num = 0;
@(posedge rst_n)
dummy_num = 12;
end
endtask
task debug_information;
begin
$display("----------------------------------------------");
$display("------------------ -----------------------");
$display("----------- SIMULATION RESULT ----------------");
$display("-------------- -------------------");
$display("---------------- ---------------------");
$display("----------------------------------------------");
$monitor("TIME = %d, mosi = %b, miso = %b, data_in = %b",$time, mosi, miso, data_in);
end
endtask
//the generate block to generate the miso
generate
if(CPOL == 0)
always @(posedge sck) begin
miso = $random;
end
else
always @(negedge sck) begin
miso = $random;
end
endgenerate
spi_master
#(
.CLK_FREQUENCE(CLK_FREQUENCE) , //系统时钟50M
.SPI_FREQUENCE(SPI_FREQUENCE) , //SPI 速率不超过15M,分区数少建议频率低一点
.WDATA (DATA_WIDTH) ,
.CPOL (CPOL),
.CPHA (CPHA)
)
spi_master(
.clk (xtal_clk),
.rst_n (rst_n),
.spi_wr (spi_wr) ,
.data_in (data_in) ,
.dummy_num (dummy_num),
.d_start (d_start),
.finish (finish),
.sck (sck) ,
.mosi (mosi) ,
.miso (miso) ,
.csb (csb)
);
endmodule
CPOL=0,CPHA=0
CPOL=0,CPHA=1
CPOL=1,CPHA=0
CPOL=1,CPHA=1