// ***************************************************************************************
//
// Copyright(c) 2000, micas Telecommunications Ltd., All right reserved
//
// Filename : spi_master.v
// Projectname : spi interface
// Author : huangbiao
// Email : huangbiao336@163.com
// Date : August 15st, 2025
// Version : 1.0
// Company : xxx Ltd.
// synthesize tool : vivado 2018.3
// sim tool : questa
//
// Description :
//
// Modification History
// Date By Review Revision Change Description
// ---------------------------------------------------------------------------------------
// 2025/08/15 huangbiao 1.0 initial
//
//
// ***************************************************************************************
// DEFINE
//
//
// ***************************************************************************************
//
// Module : spi_master.v
// Called by :
//
// ***************************************************************************************
//
// ***************************************************************************************
//
// ***************************************************************************************
//
// soft control lsb or msb first transmit
// lsb mode:address[0] --> address[14] -->w/r --> data[0] --> data[7]
// msb mode:w/r -->address[14] --> address[0] --> data[7] --> data[0]
// ***************************************************************************************
`resetall
`timescale 1ps/1ps
module spi_master #(
parameter D_WIDTH = 64,
parameter CPOL = 1'b0, // cpol = 0 : idle clock is low,cpol = 1 : idle clock is high
parameter CPHA = 1'b1, // cpha = 0 : first edge sample data,the second edge transmit data,cpha = 1 : first edge transmit data,the second edge sample data
parameter CSN_SETUP = 5'd2, // 2 clocks, the setup time is configed must be less 32 internal high frequence clocks
parameter CSN_HOLD = 5'd2, // 2 clocks, the hold time is configed must be less 32 internal high frequence clocks
parameter SLAVE_ACC = 5'd5 // 5 clocks, the slave turn round time is configed must be less 32 internal high frequence clocks
)(
input clk,
input clk_rst,
//cfg signal
input spi_type, // 1: 3 wire ; 0: 4 wire
input [ 3 : 0] cfg_clk_divid, // cfg spi clock dicide index,4 divide value is 3
input lmsb_mod, // lsb or msb first transmit,0:lsb mode,1:msb mode
input [ D_WIDTH-1: 0] cfg_addr_pha, // cfg w/r + address
input [ D_WIDTH-1: 0] cfg_data_pha, // cfg data
input [$clog2(D_WIDTH+1)-1 : 0] cfg_phase1_len, // cfg w/r + address length indicate
input [$clog2(D_WIDTH+1)-1 : 0] cfg_phase2_len, // cfg data length indicate
input cmd_start, // cfg 0-->1,rising start
input clear, // 1:clear 0:no
output reg spi_rd_data_vld,
output reg [ D_WIDTH-1: 0] spi_rd_data, // spi read data
output reg spi_busy, // spi interface status indicate,1:busy,0:idle
output reg spi_abort, // 1:abort 0:normal
output reg spi_cs,
output reg spi_clk,
inout spi_dio,
input spi_di
);
//******************************************************
localparam CNTWIDTH = $clog2(D_WIDTH+1) +1;
reg [ D_WIDTH-1: 0] addr_pha;
reg [ D_WIDTH-1: 0] data_pha;
reg [ CNTWIDTH-1: 0] cfg_trans_len; //trandmit bit length
reg [ CNTWIDTH-1: 0] trans_cnt;
reg cmd_start_d1;
reg pos_start;
wire first_edge;
wire second_edge;
reg first_edge_1d;
reg second_edge_1d;
reg first_edge_2d;
reg second_edge_2d;
reg [ 31: 0] start_shreg;
reg [ 31: 0] wait_shreg;
reg [ 31: 0] end_shreg;
reg [ 3 : 0] divide_cnt; // divide count
wire mask; // when mask signal enabled, the spi clk generated must be stopped
reg spi_cs_tmp;
reg spi_cs_tmp1;
reg spi_clk_tmp;
reg [ D_WIDTH-1: 0] msb_out_data;
reg [ D_WIDTH-1: 0] lsb_out_data;
reg [ D_WIDTH-1: 0] msb_in_data;
reg [ D_WIDTH-1: 0] lsb_in_data;
reg op_rd_wr;
reg spi_bi_dir_switch_tmp;
reg spi_sdo;
wire spi_sdi;
reg spi_bi_dir_switch;
//*************************************************************************************************
assign spi_dio = (spi_type == 1'b1) ? ((spi_bi_dir_switch == 1'b1) ? spi_sdo : 1'bz) : spi_sdo;
assign spi_sdi = (spi_type == 1'b1) ? ((spi_bi_dir_switch == 1'b0) ? spi_dio : 1'bz) : spi_di ;
//*************************************************************************************************
always@(posedge clk)
begin
if (lmsb_mod)
addr_pha <= cfg_addr_pha <<(D_WIDTH-cfg_phase1_len);
else
addr_pha <= cfg_addr_pha;
end
always@(posedge clk)
begin
if (lmsb_mod)
data_pha <= cfg_data_pha <<(D_WIDTH-cfg_phase2_len);
else
data_pha <= cfg_data_pha;
end
//transmit bit = address phase length + data phase length
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
cfg_trans_len <= 'd0;
else
cfg_trans_len <= cfg_phase1_len + cfg_phase2_len;
end
//generate spi interface start signal
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
begin
cmd_start_d1 <= 1'b0;
pos_start <= 1'b0;
end
else
begin
cmd_start_d1 <= cmd_start;
pos_start <= cmd_start&(!cmd_start_d1);
end
end
//generate mask signal for meeting csn setup time , hold time and readback turnround time
always @ (posedge clk)
begin
start_shreg <= {start_shreg[30:0], pos_start};
end_shreg <= {end_shreg[30:0], pos_end};
wait_shreg <= {wait_shreg[30:0], pos_wait};
end
assign pos_wait = ((~spi_cs_tmp) & (divide_cnt == cfg_clk_divid -1) & (trans_cnt == cfg_phase1_len-1))&op_rd_wr;
assign pos_end = (~spi_cs_tmp) & (divide_cnt == cfg_clk_divid -1) & (trans_cnt == cfg_trans_len-1);
assign mask = (|start_shreg[CSN_SETUP-1:0]) | (|wait_shreg[SLAVE_ACC-1:0]) | (|end_shreg[CSN_HOLD-1:0]) ;
//clk divide to generate spi clk
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
divide_cnt <= 4'd0;
else if(~spi_cs_tmp) //cs valid to generate spi clk
if (~mask)
divide_cnt <= (divide_cnt == cfg_clk_divid -1) ? 'd0 : divide_cnt + 4'd1;
else
;
else
divide_cnt <= 4'd0;
end
//actual transmit bit count
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
trans_cnt <= 'd0;
else if (pos_start)
trans_cnt <= 'd0;
else if (~spi_cs_tmp)
if (trans_cnt >=cfg_trans_len-1 )
trans_cnt <= trans_cnt;
else
trans_cnt <= (divide_cnt == cfg_clk_divid -1) ? trans_cnt +1'b1 : trans_cnt;
else
trans_cnt <= 'd0;
end
//generate spi cs
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_cs_tmp <= 1'b1;
else if(pos_start == 1'b1)
spi_cs_tmp <= 1'b0;
else if (end_shreg[CSN_HOLD-1])
spi_cs_tmp <= 1'b1;
else;
end
//generate spi clk
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_clk_tmp <= CPOL;
else if(spi_cs_tmp == 1'b0)
spi_clk_tmp <=(~mask)&((divide_cnt == 'd0 )|(divide_cnt == (cfg_clk_divid>>1))) ? ~spi_clk_tmp : spi_clk_tmp;
else
spi_clk_tmp <= CPOL;
end
assign first_edge = (~spi_cs_tmp) &(~mask)& (divide_cnt == 'd0);
assign second_edge = (~spi_cs_tmp) &(~mask)& (divide_cnt == cfg_clk_divid>>1);
always@(posedge clk)
begin
if(CPHA)
begin
if (first_edge&(trans_cnt =='d0))
msb_out_data <= addr_pha;
else if (first_edge&(trans_cnt ==cfg_phase1_len))
msb_out_data <= data_pha;
else
msb_out_data <= first_edge ? {msb_out_data[D_WIDTH-2:0],1'b0} : msb_out_data;
end
else
begin
if (pos_start)
msb_out_data <= addr_pha;
else if (second_edge&(trans_cnt ==cfg_phase1_len-1))
msb_out_data <= data_pha;
else
msb_out_data <= second_edge ? {msb_out_data[D_WIDTH-2:0],1'b0} : msb_out_data;
end
end
always@(posedge clk)
begin
if(CPHA)
begin
if (first_edge&(trans_cnt =='d0))
lsb_out_data <= addr_pha;
else if (first_edge&(trans_cnt ==cfg_phase1_len))
lsb_out_data <= data_pha;
else
lsb_out_data <= first_edge ? {1'b0, lsb_out_data[D_WIDTH-1:1]} : lsb_out_data;
end
else
begin
if (pos_start)
lsb_out_data <= addr_pha;
else if (second_edge&(trans_cnt ==cfg_phase1_len-1))
lsb_out_data <= data_pha;
else
lsb_out_data <= second_edge ? {1'b0, lsb_out_data[D_WIDTH-1:1]} : lsb_out_data;
end
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_sdo <= 1'b0;
else
spi_sdo <= lmsb_mod ? msb_out_data[D_WIDTH-1]:lsb_out_data[0];
end
always@(posedge clk or posedge clk_rst)
begin
if (clk_rst)
begin
first_edge_1d <= 1'b0;
first_edge_2d <= 1'b0;
second_edge_1d <= 1'b0;
second_edge_2d <= 1'b0;
end
else
begin
first_edge_1d <= first_edge;
first_edge_2d <= first_edge_1d;
second_edge_1d <= second_edge;
second_edge_2d <= second_edge_1d;
end
end
always@(posedge clk)
begin
if (pos_start)
msb_in_data <= 'd0;
else if(~CPHA)
msb_in_data <= ((~spi_bi_dir_switch)&first_edge_2d) ? {msb_in_data[D_WIDTH-2:0],spi_sdi} : msb_in_data;
else
msb_in_data <= ((~spi_bi_dir_switch)&second_edge_2d) ? {msb_in_data[D_WIDTH-2:0],spi_sdi} : msb_in_data;
end
always@(posedge clk)
begin
if (pos_start)
lsb_in_data <= 'd0;
else if(~CPHA)
lsb_in_data <= ((~spi_bi_dir_switch)&first_edge_2d) ? {spi_sdi, lsb_in_data[D_WIDTH-1:1]} : lsb_in_data;
else
lsb_in_data <= ((~spi_bi_dir_switch)&second_edge_2d) ? {spi_sdi, lsb_in_data[D_WIDTH-1:1]} : lsb_in_data;
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst)
spi_rd_data <= 'd0;
else if (end_shreg[CSN_HOLD])
spi_rd_data <= lmsb_mod ? msb_in_data : lsb_in_data>>(D_WIDTH-cfg_phase2_len);
else
;
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst)
spi_rd_data_vld <= 1'b0;
else
spi_rd_data_vld <= op_rd_wr & end_shreg[CSN_HOLD];
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
begin
spi_clk <= CPOL;
spi_cs_tmp1 <= 1'b1;
spi_cs <= 1'b1;
end
else
begin
spi_clk <= spi_clk_tmp;
spi_cs_tmp1 <= spi_cs_tmp;
spi_cs <= spi_cs_tmp1;
end
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_busy <= 1'b0;
else if(pos_start)
spi_busy <= 1'b1;
else if(end_shreg[CSN_HOLD])
spi_busy <= 1'b0;
else;
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_abort <= 1'b0;
else if(clear == 1'b1)
spi_abort <= 1'b0;
else if((trans_cnt != cfg_trans_len-1'b1) && (spi_cs_tmp == 1'b1) && (spi_cs_tmp1 == 1'b0))
spi_abort <= 1'b1;
else;
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst)
op_rd_wr <= 1'b0;
else
op_rd_wr <= cfg_addr_pha[cfg_phase1_len-1];
end
//generate spi bi dir switch
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_bi_dir_switch_tmp <= 1'b1;
else if(pos_start | end_shreg[CSN_HOLD])
spi_bi_dir_switch_tmp <= 1'b1;
else if((((second_edge&(trans_cnt ==cfg_phase1_len-1) &(~CPHA)) | (first_edge&(trans_cnt ==cfg_phase1_len)&CPHA)))& op_rd_wr) //transmit bit done to desert
spi_bi_dir_switch_tmp <= 1'b0;
else
;
end
always@(posedge clk or posedge clk_rst)
begin
if(clk_rst == 1'b1)
spi_bi_dir_switch <= 1'b1;
else
spi_bi_dir_switch <= spi_bi_dir_switch_tmp;
end
endmodule