FPGA|Verilog-自己写的SPI驱动
1. 状态变量设置
localparam IDLE = 6'b00_0001;
localparam GEN_DCLK = 6'b00_0010;
localparam ACK = 6'b00_0100;
这里采用状态独热编码(One-Hot Encoding)
在 FPGA 开发中,独热编码能简化组合逻辑、提升时序性能
2. 两段式状态机,明晰跳转条件
3. 采用end_cnt_clk和end_cnt_num结合的方式方便时序控制,准确进行clk_div的分频
4. 仿真效果展示
仿真上板通过
5. 全部代码
module spi_driver1(
input clk ,
input rst ,
input MISO ,
output reg MOSI ,// 1
input CPOL ,
input CPHA ,
input [ 7:0] data_in ,
output reg [ 7:0] data_out ,//
input [ 15:0] clk_div ,
output nCS ,// 1
input nCTRL ,
output reg DCLK ,// 1
input wr_req ,
output wr_ack // 1
);
localparam IDLE = 6'b00_0001;
localparam GEN_DCLK = 6'b00_0010;
localparam ACK = 6'b00_0100;
reg [5:0]cstate;
reg [5:0]nstate;
reg [25:0]cnt_clk;
reg [7:0]cnt_num;
reg [7:0]num;
wire [15:0]MAX_CNT = clk_div;
wire add_cnt_clk = cstate != IDLE;
wire end_cnt_clk = add_cnt_clk && cnt_clk == MAX_CNT - 1;
wire add_cnt_num = end_cnt_clk;
wire end_cnt_num = add_cnt_num && cnt_num == num - 1;
assign nCS = nCTRL;
assign wr_ack = cstate == ACK && end_cnt_clk;
always @(posedge clk or posedge rst) begin
if(rst)begin
cnt_clk <= 26'd0;
end else if(add_cnt_clk)begin
if(end_cnt_clk)
cnt_clk <= 26'd0;
else
cnt_clk <= cnt_clk + 26'd1;
end
end
always @(posedge clk or posedge rst) begin
if(rst)begin
cnt_num <= 8'd0;
end else if(add_cnt_num)begin
if(end_cnt_num)
cnt_num <= 8'd0;
else
cnt_num <= cnt_num + 8'd1;
end
end
reg [8*20-1:0]state_name;
always @(*) begin
case (cstate)
IDLE :begin num = 1; state_name = "IDLE "; end
GEN_DCLK:begin num = 8; state_name = "GEN_DCLK"; end
ACK :begin num = 1; state_name = "ACK "; end
default :begin num = 1; state_name = "IDLE "; end
endcase
end
wire IDLE_GEN_DCLK = (cstate == IDLE) && wr_req ;
wire GEN_DCLK_ACK = (cstate == GEN_DCLK) && end_cnt_num ;
wire ACK_IDLE = (cstate == ACK) && end_cnt_num ;
always @(posedge clk or posedge rst) begin
if(rst)begin
DCLK <= (CPOL == 1'b0)?1'b0:1'b1;
end else if(cstate == GEN_DCLK && (cnt_clk == MAX_CNT/2 - 1 || cnt_clk == MAX_CNT - 1))begin
DCLK <= ~DCLK;
end
end
reg flag_mosi;
always @(posedge clk or posedge rst) begin
if(rst)begin
MOSI <= 1'b0;
flag_mosi <= 1'b0;
end else if(cstate == GEN_DCLK)begin
if(CPOL == 1'b0 && cnt_clk == 0)begin
MOSI <= data_in[7 - cnt_num];
flag_mosi <= 1'b1;
end else if(CPOL == 1'b1 && cnt_clk == MAX_CNT/2 - 1)begin
MOSI <= data_in[7 - cnt_num];
flag_mosi <= 1'b1;
end else begin
flag_mosi <= 1'b0;
end
end else if(cstate == ACK)begin
MOSI <= 1'b0;
flag_mosi <= 1'b0;
end
end
reg flag_data_out;
always @(posedge clk or posedge rst) begin
if(rst)begin
data_out <= 8'd0;
flag_data_out <= 1'b0;
end else if(cstate == GEN_DCLK)begin
if(CPOL == 1'b0 && cnt_clk == MAX_CNT/2 - 1)begin
data_out <= {data_out[6:0],MISO};
flag_data_out <= 1'b1;
end else if(CPOL == 1'b1 && cnt_clk == MAX_CNT - 1)begin
data_out <= {data_out[6:0],MISO};
flag_data_out <= 1'b1;
end else begin
flag_data_out <= 1'b0;
end
end else begin
flag_data_out <= 1'b0;
end
end
always @(posedge clk or posedge rst) begin
if(rst)begin
cstate <= IDLE;
end else begin
cstate <= nstate;
end
end
always @(*) begin
case (cstate)
IDLE :
if(IDLE_GEN_DCLK)
nstate = GEN_DCLK;
else
nstate = cstate;
GEN_DCLK:
if(GEN_DCLK_ACK)
nstate = ACK;
else
nstate = cstate;
ACK :
if(ACK_IDLE)
nstate = IDLE;
else
nstate = cstate;
default :
nstate = IDLE;
endcase
end
endmodule