32BIT的SPI主机控制

发布于:2025-05-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

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,  //MHZ
    parameter 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);
    begin
        for(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)begin
    if(rst_n == 0)begin
        clkdiv_cnt <= 1'b0; 
    end else if(clkdiv_cnt == CLK_DIV_CNT - 1'b1)begin
        clkdiv_cnt <= 1'b0;
    end else begin
        clkdiv_cnt <= clkdiv_cnt + 1'b1;
    end
end

//sck 
always@(posedge clk or negedge rst_n)begin
    if(rst_n == 0)begin
        SPI_SCK <= sck_idle;
    end  else if((clkdiv_cnt == (CLK_DIV_CNT - 1'b1)) || (clkdiv_cnt == (CLK_DIV_CNT >> 1) -1'b1))begin
        SPI_SCK <= ~SPI_SCK;
    end else begin
        SPI_SCK <= SPI_SCK;
    end   
end   

//输入输出使能
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        datain_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 
 end
 
 assign 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)begin
    if(!rst_n)begin
        current_state <= S_RST;
    end else begin
        current_state <= next_state;
    end
end

always@(*)begin
    next_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)begin
    case(current_state)
        S_RST,S_IDLE: WrDataLatch <= 32'd0;
        S_ACK: WrDataLatch <= WrData;
        S_RUN:begin
            if(dataout_en)begin
                WrDataLatch <= {WrDataLatch[30:0],1'b0}; //先输出高位数据出来
//                MOSI_o <= WrDataLatch[31];
            end else begin
                WrDataLatch <= WrDataLatch;
            end
            end
            default:WrDataLatch <= 32'd0;
    endcase
end

//接收数据模块 
always@(posedge clk)begin
    case(current_state)
        S_RST,S_ACK:RdDataLatch <=32'd0;
        S_RUN:begin
            if(datain_en)begin
                RdDataLatch <= {RdDataLatch[30:0],MISO}; //先把高位数据输入进去
            end else begin
                RdDataLatch <= RdDataLatch;
            end
        end
        default:RdDataLatch <= RdDataLatch;
    endcase
end

//时钟边沿计数 已处理的位数计数器
always@(posedge clk)begin
    case(current_state)
        S_RST:WrRdBitsCnt <= 8'd0;
        S_RUN:begin
            if(datain_en || dataout_en)begin
                WrRdBitsCnt <= WrRdBitsCnt + 1'b1;
            end else begin
                WrRdBitsCnt <= WrRdBitsCnt;
            end
        end
        default:WrRdBitsCnt <= 8'd0;
    endcase
end

//片选信号
always@(posedge clk)begin
    case(current_state)  //其余状态保持默认值1
        S_RUN:CS_O <= 1'b0;
        default:CS_O <= 1'b1;
    endcase
end

//锁存要处理的数据位数
always@(posedge clk)begin
    case(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 / 1ps

module tb_SPI_Master;

    // 参数定义
    parameter CLK_FREQ = 50;  // 时钟频率 50 MHz
    parameter SPI_CLK = 1000; // SPI 时钟频率 1 MHz
    parameter 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) begin
        if (CS == 0) begin
            SPISlaveRdData <= {SPISlaveRdData[30:0], MOSI}; // 从机接收数据
        end
    end
    
    
//    reg [31:0] SPISlaveWrData_o;
    // 模拟从机行为:发送数据
    always @(posedge SCK) begin
        if (CS == 0) begin
            MISO <= SPISlaveWrData[31]; // 从机发送最高位
            SPISlaveWrData <= SPISlaveWrData << 1; // 数据左移
        end
    end

    // 测试过程
    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;
    end

endmodule

仿真图略

参考:Verilog实现的SPI通信协议(主机模式)_spi 控制器的状态机跳转-CSDN博客


网站公告

今日签到

点亮在社区的每一天
去签到