SPI傳輸位數可參數化配置。
SPI_MASTER:
`timescale 1ns / 1ps
module SPI_Master #(parameter CLK_FREQ = 50,parameter SPI_CLK = 1000,parameter CPOL = 0,parameter CPHA = 0
)(input clk,input rst_n,input WrRdReq, //讀/寫數據請求output WrRdReqAck,output WrRdFinish,input [6:0] WrRdDataBits, //輸入數據位寬input [31:0] WrData, //要寫入的數據 output [31:0] RdData, //讀取到的數據//SPI接口output SCK,output MOSI,input MISO,output CS
);//采樣信號
wire datain_en;
wire dataout_en;SPI_Master_Clock#
(.CLK_FREQ(CLK_FREQ),.CPOL(CPOL),.CPHA(CPHA),.SPI_CLK_FREQ(SPI_CLK)
)
u_SPI_Master_Clock
(
.clk(clk),
.rst_n(rst_n),.datain_en(datain_en),
.dataout_en(dataout_en),
.SCK(SCK)
);SPI_Master_ctrl u_SPI_Master_ctrl
(.clk(clk), .rst_n(rst_n), .WrRdReq(WrRdReq), .WrRdReqAck(WrRdReqAck), .WrRdFinish(WrRdFinish), .WrRdDataBits(WrRdDataBits),.WrData(WrData), .RdData(RdData), .datain_en(datain_en), .dataout_en(dataout_en), .MOSI(MOSI), .MISO(MISO), .CS(CS)
);
endmodule
SPI_MATSER_CLOCK
module SPI_Master_Clock#(parameter CLK_FREQ = 50, //MHZparameter CPOL = 1'b0,parameter CPHA = 1'b0,parameter SPI_CLK_FREQ = 1000 //Khz
)(input clk,input rst_n,output datain_en, //數據采樣控制信號output dataout_en, //數據輸出控制信號output SCK
);localparam sck_idle = CPOL;
localparam CLK_DIV_CNT = (CLK_FREQ *1000)/SPI_CLK_FREQ; //分頻計數器,此處分頻時鐘一個周期,分頻時鐘計數器要計數50,50個主時鐘周期//位寬計算
function integer clogb2(input integer depth);beginfor(clogb2 = 0;depth > 0;clogb2= clogb2 + 1)depth = depth >> 1; end
endfunction
reg[clogb2(CLK_DIV_CNT)-1:0] clkdiv_cnt; //[(6-1):0]reg SPI_SCK;
reg datain_en_o = 1'b0,dataout_en_o = 1'b0;//分頻時鐘計數器,計數到最大值表明經歷了一個SCK周期
always@(posedge clk or negedge rst_n)beginif(rst_n == 0)beginclkdiv_cnt <= 1'b0; end else if(clkdiv_cnt == CLK_DIV_CNT - 1'b1)beginclkdiv_cnt <= 1'b0;end else beginclkdiv_cnt <= clkdiv_cnt + 1'b1;end
end//sck
always@(posedge clk or negedge rst_n)beginif(rst_n == 0)beginSPI_SCK <= sck_idle;end else if((clkdiv_cnt == (CLK_DIV_CNT - 1'b1)) || (clkdiv_cnt == (CLK_DIV_CNT >> 1) -1'b1))beginSPI_SCK <= ~SPI_SCK;end else beginSPI_SCK <= SPI_SCK;end
end //輸入輸出使能
always@(posedge clk or negedge rst_n)beginif(!rst_n)begindatain_en_o <= 1'b0;dataout_en_o <= 1'b0;end else begin dataout_en_o <= (clkdiv_cnt == (CLK_DIV_CNT - 'd3))?1'b1:1'b0;datain_en_o <= (clkdiv_cnt == (CLK_DIV_CNT >> 1) - 'd1)?1'b1:1'b0;end endassign datain_en = datain_en_o;assign dataout_en = dataout_en_o;assign SCK = SPI_SCK;
endmodule
SPI_MATSER_CTRL:
module SPI_Master_ctrl
(input clk,input rst_n,input WrRdReq, //讀/寫數據請求output WrRdReqAck,output WrRdFinish,input [6:0] WrRdDataBits, //輸入數據位寬input [31:0] WrData, //要寫入的數據 output [31:0] RdData, //讀取到的數據//SPI寫讀控制信號input datain_en,input dataout_en,//SPI接口output MOSI,input MISO,output CS
);localparam [7:0] S_IDLE =8'd0;
localparam [7:0] S_ACK =8'd1;
localparam [7:0] S_RUN =8'd2;
localparam [7:0] S_END =8'd3;
localparam [7:0] S_RST =8'd4;reg [7:0] current_state = S_IDLE;
reg [7:0] next_state = S_IDLE;
reg CS_O = 1'b1;
reg [7:0] WrRdBitsCnt = 8'd0,WrRdBitsLatch = 8'd0; //用于計數已經發送或接收的位數 //用于存儲發送或接收的數據位數
reg [31:0] WrDataLatch = 32'd0,RdDataLatch = 32'd0; //用于存儲要發送的數據 //用于存儲從 SPI 設備接收到的數據//reg MOSI_o;always@(posedge clk or negedge rst_n)beginif(!rst_n)begincurrent_state <= S_RST;end else begincurrent_state <= next_state;end
endalways@(*)beginnext_state = S_RST;case(current_state)S_RST:next_state = S_IDLE;S_IDLE:next_state = (WrRdReq)?S_ACK:S_IDLE;S_ACK:next_state = (WrRdReq)?S_ACK:S_RUN;S_RUN:next_state = (WrRdBitsCnt == WrRdBitsLatch)? S_END : S_RUN;S_END:next_state = S_IDLE;default:next_state = S_RST;endcase
end//發送數據模塊
always@(posedge clk)begincase(current_state)S_RST,S_IDLE: WrDataLatch <= 32'd0;S_ACK: WrDataLatch <= WrData;S_RUN:beginif(dataout_en)beginWrDataLatch <= {WrDataLatch[30:0],1'b0}; //先輸出高位數據出來
// MOSI_o <= WrDataLatch[31];end else beginWrDataLatch <= WrDataLatch;endenddefault:WrDataLatch <= 32'd0;endcase
end//接收數據模塊
always@(posedge clk)begincase(current_state)S_RST,S_ACK:RdDataLatch <=32'd0;S_RUN:beginif(datain_en)beginRdDataLatch <= {RdDataLatch[30:0],MISO}; //先把高位數據輸入進去end else beginRdDataLatch <= RdDataLatch;endenddefault:RdDataLatch <= RdDataLatch;endcase
end//時鐘邊沿計數 已處理的位數計數器
always@(posedge clk)begincase(current_state)S_RST:WrRdBitsCnt <= 8'd0;S_RUN:beginif(datain_en || dataout_en)beginWrRdBitsCnt <= WrRdBitsCnt + 1'b1;end else beginWrRdBitsCnt <= WrRdBitsCnt;endenddefault:WrRdBitsCnt <= 8'd0;endcase
end//片選信號
always@(posedge clk)begincase(current_state) //其余狀態保持默認值1S_RUN:CS_O <= 1'b0;default:CS_O <= 1'b1;endcase
end//鎖存要處理的數據位數
always@(posedge clk)begincase(current_state)S_RST:WrRdBitsLatch <= 8'd0;S_ACK:WrRdBitsLatch <= {WrRdDataBits,1'b0};default:WrRdBitsLatch <= WrRdBitsLatch;endcase
end assign WrRdFinish = (current_state == S_END);
assign RdData = RdDataLatch;
//請求ack信號
assign WrRdReqAck = (current_state == S_ACK);
assign MOSI = WrDataLatch[31];
assign CS =CS_O;
endmodule
TB:
`timescale 1ns / 1psmodule tb_SPI_Master;// 參數定義parameter CLK_FREQ = 50; // 時鐘頻率 50 MHzparameter SPI_CLK = 1000; // SPI 時鐘頻率 1 MHzparameter CPOL = 0; // 時鐘極性parameter CPHA = 0; // 時鐘相位// 信號定義reg clk;reg rst_n;reg WrRdReq;wire WrRdReqAck;wire WrRdFinish;reg [6:0] WrRdDataBits;reg [31:0] SPIMasterWrData; // 主機發送的數據wire [31:0] SPIMasterRdData; // 主機接收的數據wire SCK;wire MOSI;reg MISO;wire CS;// 從機接收數據寄存器reg [31:0] SPISlaveRdData = 0; // 從機接收的數據// 從機發送數據寄存器reg [31:0] SPISlaveWrData = 0; // 從機發送的數據// 實例化 SPI_Master 模塊SPI_Master #(.CLK_FREQ(CLK_FREQ),.SPI_CLK(SPI_CLK),.CPOL(CPOL),.CPHA(CPHA)) uut (.clk(clk),.rst_n(rst_n),.WrRdReq(WrRdReq),.WrRdReqAck(WrRdReqAck),.WrRdFinish(WrRdFinish),.WrRdDataBits(WrRdDataBits),.WrData(SPIMasterWrData),.RdData(SPIMasterRdData),.SCK(SCK),.MOSI(MOSI),.MISO(MISO),.CS(CS));// 時鐘生成always #10 clk = ~clk;// 模擬從機行為:接收數據always @(posedge SCK) beginif (CS == 0) beginSPISlaveRdData <= {SPISlaveRdData[30:0], MOSI}; // 從機接收數據endend// reg [31:0] SPISlaveWrData_o;// 模擬從機行為:發送數據always @(posedge SCK) beginif (CS == 0) beginMISO <= SPISlaveWrData[31]; // 從機發送最高位SPISlaveWrData <= SPISlaveWrData << 1; // 數據左移endend// 測試過程initial begin// 初始化信號clk = 0;rst_n = 0;WrRdReq = 0;WrRdDataBits = 32; // 發送 32 位數據SPIMasterWrData = 32'h82345678; // 主機發送的數據SPISlaveWrData = 32'h12312345;MISO = 0;// 復位系統#20 rst_n = 0;#20 rst_n = 1;// 發送數據請求#100 WrRdReq = 1; // 模擬發送請求#100 WrRdReq = 0; // 結束發送請求// 等待發送完成wait(WrRdFinish == 1);// 檢查從機接收到的數據if (SPISlaveRdData == 32'h82345678) begin$display("Test Passed: SPISlaveRdData = %h", SPISlaveRdData);end else begin$display("Test Failed: SPISlaveRdData = %h, Expected = %h", SPISlaveRdData, 32'h82345678);end// 等待主機接收完成#100;// 檢查主機接收到的數據if (SPIMasterRdData == 32'h12312345) begin$display("Test Passed: SPIMasterRdData = %h", SPIMasterRdData);end else begin$display("Test Failed: SPIMasterRdData = %h, Expected = %h", SPIMasterRdData, 32'h12312345);end// 結束仿真#100 $stop;endendmodule
仿真圖略
參考:Verilog實現的SPI通信協議(主機模式)_spi 控制器的狀態機跳轉-CSDN博客