LOADING

加载过慢请开启缓存 浏览器默认开启

Verilog-SPI设计

2023/11/28 Verilog

Abstract: Verilog SPI 模块设计

设计思路

使用状态机实现

在不同的 SPI 时钟边沿操作移位寄存器输出或者读入数据线上的数据

模块文件

主机

// SPI 主机 片选信号需要单独生成
module SPI_master #(
    parameter CLK_FREQ_MHZ = 27,  // 系统时钟频率
    parameter BAUD_RATE_KHZ = 40,  // SPI 时钟频率
    parameter DATA_WIDTH = 8,  // 数据位宽
    parameter CPOL = 0,  // 时钟极性
    parameter CPHA = 0,  // 时钟相位
    parameter FIRST_BIT = 0  // 高位或低位优先 1为高位优先
) (
    input sys_clk,  // 系统时钟
    input rst_n,  // 低电平异步复位
    input miso,  // MISO
    input frame_start,  // 一个数据帧开始
    input [DATA_WIDTH-1:0] send_data,  // 待发送数据

    output spi_clk,  // SPI 时钟
    output mosi,  // MOSI
    output frame_end,  // 一个数据帧结束
    output [DATA_WIDTH-1:0] rev_data  // 接收到的数据
);

    localparam IDLE_S=0, LOAD_S=1, SHIFT_S=2, DONE_S=3;
    localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000 / BAUD_RATE_KHZ;  // 波特率计数最大值

    reg start_r0, start_r1;
    reg spi_clk_r0, spi_clk_r1;
    reg [1:0] cur_state, next_state;  // 状态
    reg [$clog2(DATA_WIDTH):0] data_cnt;  // 数据计数
    reg finish_pulse_r;  // 结束信号
    reg spi_clk_en;  // SPI 时钟使能信号
    reg [$clog2(BAUD_CNT_THRESHOLD):0] baud_rate_cnt;  // 波特率计数器
    reg spi_clk_r;  // SPI 时钟
    reg [DATA_WIDTH-1:0] shift_in_register;  // 输入移位寄存器
    reg [DATA_WIDTH-1:0] shift_out_register; // 输出移位寄存器

    wire start_posedge, spi_clk_posegde, spi_clk_negedge;  // 边沿检测
    wire sample_en, shift_en;  // 采样使能信号 移位使能信号

//****边沿检测****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            start_r0 <= 1'b0;
            start_r1 <= 1'b0;
        end
        else begin
            start_r0 <= frame_start;
            start_r1 <= start_r0;
        end
    end

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            spi_clk_r0 <= CPOL;
            spi_clk_r1 <= CPOL;
        end
        else begin
            spi_clk_r0 <= spi_clk_r;
            spi_clk_r1 <= spi_clk_r0;
        end
    end

    assign start_posedge = start_r0 && ~start_r1;  // 开始信号上升沿
    assign spi_clk_posegde = spi_clk_r0 && ~spi_clk_r1;  // SPI 时钟上升沿
    assign spi_clk_negedge = ~spi_clk_r0 && spi_clk_r1;  // SPI 时钟下降沿 
//********//

//****使能信号逻辑****//
    generate
        case (CPOL)
            0 : assign sample_en = (CPHA==1)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&(cur_state!=IDLE_S);
            1 : assign sample_en = (CPHA==0)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&&(cur_state!=IDLE_S);
            default : assign sample_en = spi_clk_posegde;
        endcase
    endgenerate

    generate
        case (CPOL)
            0 : assign shift_en = (CPHA==1)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
            1 : assign shift_en = (CPHA==0)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
            default : assign shift_en = spi_clk_negedge;
        endcase
    endgenerate
//********//

//****状态机 状态转换****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            cur_state <= IDLE_S;
        end
        else begin
            cur_state <= next_state;
        end
    end
//********//

//****状态机 次态确定****//
    always @(*) begin
        case (cur_state)
            IDLE_S : begin  // 空闲
                next_state = (start_posedge)? LOAD_S : IDLE_S;
            end
            LOAD_S : begin  // 数据装载
                next_state = SHIFT_S;
            end
            SHIFT_S : begin  // 交换数据
                next_state = (data_cnt==DATA_WIDTH)? DONE_S : SHIFT_S;
            end
            DONE_S : begin  // 结束
                next_state  =IDLE_S;
            end
            default : begin
                next_state = IDLE_S;
            end
        endcase
    end
//********//

//****状态机 行为逻辑****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            spi_clk_en <= 1'b0;
            shift_in_register <= 'd0;
            shift_out_register <= 'd0;
            data_cnt <= 'd0;
            finish_pulse_r <= 1'b0;
        end
        else begin
            case (cur_state)
                IDLE_S : begin
                    spi_clk_en <= 1'b0;
                    shift_in_register <= 'd0;
                    shift_out_register <= 'd0;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b0;
                end
                LOAD_S : begin
                    spi_clk_en <= 1'b0;
                    shift_in_register <= 'd0;
                    shift_out_register <= send_data;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b0;
                end
                SHIFT_S : begin
                    spi_clk_en <= 1'b1;
                    if (shift_en) begin
                        shift_out_register <= (FIRST_BIT==1)? {shift_out_register[DATA_WIDTH-2:0], 1'd0} : {1'd0, shift_out_register[DATA_WIDTH-1:1]};
                    end
                    else begin
                        shift_out_register <= shift_out_register;
                    end

                    if (sample_en) begin
                        shift_in_register <= (FIRST_BIT==1)? {shift_in_register[DATA_WIDTH-2:0], miso} : {miso, shift_in_register[DATA_WIDTH-1:1]};
                        data_cnt <= data_cnt+'d1;

                    end
                    else begin
                        shift_in_register <= shift_in_register;
                        data_cnt <= data_cnt;
                    end

                    finish_pulse_r <= 1'b0;
                end
                DONE_S : begin
                    spi_clk_en <= 1'b0;
                    shift_in_register <= shift_in_register;
                    shift_out_register <= shift_out_register;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b1;
                end
                default : begin
                    spi_clk_en <= 1'b0;
                    shift_in_register <= 'd0;
                    shift_out_register <= 'd0;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b0;
                end
            endcase
        end
    end

    assign mosi = (FIRST_BIT==1)? shift_out_register[DATA_WIDTH-1] : shift_out_register[0];
    assign frame_end = finish_pulse_r;
    assign rev_data =  (finish_pulse_r)? shift_in_register : 'd0;
//********//

//****SPI 时钟生成****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            baud_rate_cnt <= 'd0;
        end
        else begin
            if (spi_clk_en) begin
                if (baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
                    baud_rate_cnt <= 'd0;
                end
                else begin
                    baud_rate_cnt <= baud_rate_cnt + 'd1;
                end
            end
            else begin
                baud_rate_cnt <= 'd0;
            end
        end
    end

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            spi_clk_r <= CPOL;
        end
        else begin
            if (baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
                spi_clk_r <= ~spi_clk_r;
            end
            else begin
                spi_clk_r <= spi_clk_r;
            end
        end
    end

    assign spi_clk = spi_clk_r;
//**********//

endmodule

从机

// SPI 从机
module SPI_slave # (
    parameter CLK_FREQ_MHZ = 27,  // 系统时钟频率
    parameter BAUD_RATE_KHZ = 40,  // SPI 时钟频率
    parameter DATA_WIDTH = 8,  // 数据位宽
    parameter CPOL = 0,  // 时钟极性
    parameter CPHA = 0,  // 时钟相位
    parameter FIRST_BIT = 0  // 高位或低位优先 1为高位优先
) (
    input sys_clk,  // 系统时钟
    input rst_n,  // 低电平异步复位
    input spi_clk,  // SPI 时钟
    input mosi,  // MOSI
    input cs_n,  // 片选 低电平有效
    input [DATA_WIDTH-1:0] send_data,  // 待发送数据

    output miso,  // MISO
    output frame_end,  // 一个数据帧结束
    output [DATA_WIDTH-1:0] rev_data  // 接收到的数据
);

    localparam IDLE_S=0, LOAD_S=1, SHIFT_S=2, DONE_S=3;

    reg spi_clk_r0, spi_clk_r1;
    reg cs_n_r0, cs_n_r1;
    reg [1:0] cur_state, next_state;  // 状态
    reg [$clog2(DATA_WIDTH):0] data_cnt;  // 数据计数
    reg [DATA_WIDTH-1:0] shift_in_register;  // 输入移位寄存器
    reg [DATA_WIDTH-1:0] shift_out_register; // 输出移位寄存器
    reg finish_pulse_r;  // 结束脉冲信号

    wire spi_clk_posegde, spi_clk_negedge;  // SPI 时钟边沿检测
    wire cs_n_negedge;  // 片选信号边沿检测
    wire sample_en, shift_en;  // 采样使能信号 移位使能信号

//****边沿检测****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            spi_clk_r0 <= CPOL;
            spi_clk_r1 <= CPOL;
        end
        else begin
            spi_clk_r0 <= spi_clk;
            spi_clk_r1 <= spi_clk_r0;
        end
    end

    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            cs_n_r0 <= 1'b1;
            cs_n_r1 <= 1'b1;
        end
        else begin
            cs_n_r0 <= cs_n;
            cs_n_r1 <= cs_n_r0;
        end
    end

    assign spi_clk_posegde = spi_clk_r0 && ~spi_clk_r1;  // SPI 时钟上升沿
    assign spi_clk_negedge = ~spi_clk_r0 && spi_clk_r1;  // SPI 时钟下降沿
    assign cs_n_negedge = ~cs_n_r0 && cs_n_r1;  // 片选信号下降沿
//********//

//****使能信号逻辑****//
    generate
        case (CPOL)
            0 : assign sample_en = (CPHA==1)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&&(cur_state!=IDLE_S);
            1 : assign sample_en = (CPHA==0)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&&(cur_state!=IDLE_S);
            default : assign sample_en = spi_clk_posegde;
        endcase
    endgenerate

    generate
        case (CPOL)
            0 : assign shift_en = (CPHA==1)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
            1 : assign shift_en = (CPHA==0)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
            default : assign shift_en = spi_clk_negedge;
        endcase
    endgenerate
//********//

//****状态机 状态转换****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            cur_state <= IDLE_S;
        end
        else begin
            cur_state <= next_state;
        end
    end
//********//

//****状态机 次态确定****//
    always @(*) begin
        case (cur_state)
            IDLE_S : begin  // 空闲
                next_state = (cs_n_negedge)? LOAD_S : IDLE_S;
            end
            LOAD_S : begin  // 数据装载
                next_state = SHIFT_S;
            end
            SHIFT_S : begin  // 交换数据
                next_state = (data_cnt==DATA_WIDTH)? DONE_S : SHIFT_S;
            end
            DONE_S : begin  // 结束
                next_state  =IDLE_S;
            end
            default : begin
                next_state = IDLE_S;
            end
        endcase
    end
//********//

//****状态机 行为逻辑****//
    always @(posedge sys_clk or negedge rst_n) begin
        if (~rst_n) begin
            shift_in_register <= 'd0;
            shift_out_register <= 'd0;
            data_cnt <= 'd0;
            finish_pulse_r <= 1'b0;
        end
        else begin
            case (cur_state)
                IDLE_S : begin
                    shift_in_register <= 'd0;
                    shift_out_register <= 'd0;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b0;
                end
                LOAD_S : begin
                    shift_in_register <= 'd0;
                    shift_out_register <= send_data;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b0;
                end
                SHIFT_S : begin
                    if (shift_en) begin
                        shift_out_register <= (FIRST_BIT==1)? {shift_out_register[DATA_WIDTH-2:0], 1'd0} : {1'd0, shift_out_register[DATA_WIDTH-1:1]};
                    end
                    else begin
                        shift_out_register <= shift_out_register;
                    end

                    if (sample_en) begin
                        shift_in_register <= (FIRST_BIT==1)? {shift_in_register[DATA_WIDTH-2:0], mosi} : {mosi, shift_in_register[DATA_WIDTH-1:1]};
                        data_cnt <= data_cnt+'d1;
                    end
                    else begin
                        shift_in_register <= shift_in_register;
                        data_cnt <= data_cnt;
                    end

                    finish_pulse_r <= 1'b0;
                end
                DONE_S : begin
                    shift_in_register <= shift_in_register;
                    shift_out_register <= shift_out_register;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b1;
                end
                default : begin
                    shift_in_register <= 'd0;
                    shift_out_register <= 'd0;
                    data_cnt <= 'd0;
                    finish_pulse_r <= 1'b0;
                end
            endcase
        end
    end

    assign miso = (FIRST_BIT==1)? shift_out_register[DATA_WIDTH-1] : shift_out_register[0];
    assign frame_end = finish_pulse_r;
    assign rev_data =  (finish_pulse_r)? shift_in_register : 'd0;
//********//

endmodule

Module-sim 测试文件

// SPI 模块测试
`timescale 1ns/1ns

module SPI_module_tb;
    
    localparam CLK_FREQ_MHZ = 27;  // 系统时钟频率
    localparam BAUD_RATE_KHZ = 40;  // SPI 时钟频率
    localparam DATA_WIDTH = 8;  // 数据位宽
    localparam CPOL = 0;  // 时钟极性
    localparam CPHA = 0;  // 时钟相位
    localparam FIRST_BIT = 0;  // 高位或低位优先 1为高位优先

    reg sys_clk;
    reg rst_n;
    reg cs_n;
    reg frame_start;  // 帧开始信号 控制主机
    reg [DATA_WIDTH-1:0] master_send_data;  // 主机待发送数据
    reg [DATA_WIDTH-1:0] slave_send_data;  // 从机待发送数据

    wire [DATA_WIDTH-1:0] master_rev_data;  // 主机接收到数据
    wire [DATA_WIDTH-1:0] slave_rev_data;  // 从机接收到数据
    wire master_frame_end;  // 主机帧结束信号
    wire slave_frame_end;  // 从机帧结束信号
    wire miso, mosi;  // 串行数据线
    wire spi_clk;  // SPI 时钟

    initial begin
        sys_clk = 1'b0;
        rst_n = 1'b0;
        # 5;
        rst_n = 1'b1;

        forever begin  // 50 MHz
            sys_clk = 1'b0;
            # 1;
            sys_clk = 1'b1;
            # 1;
        end
    end

    initial begin
        master_send_data = 'h12;
        slave_send_data = 'h21;
    end

    initial begin
        cs_n = 1'b1;
        frame_start = 1'b0;

        # 20;
        cs_n = 1'b0;
        # 5;
        frame_start = 1'b1;
    end

    SPI_master #(
        .CLK_FREQ_MHZ(CLK_FREQ_MHZ),
        .BAUD_RATE_KHZ(BAUD_RATE_KHZ),
        .DATA_WIDTH(DATA_WIDTH),
        .CPOL(CPOL),
        .CPHA(CPHA),
        .FIRST_BIT(FIRST_BIT)
    ) spi_master_tb (
        .sys_clk(sys_clk),  // 系统时钟
        .rst_n(rst_n),  // 低电平异步复位
        .miso(miso),  // MISO
        .frame_start(frame_start),  // 一个数据帧开始
        .send_data(master_send_data),  // 待发送数据

        .spi_clk(spi_clk),  // SPI 时钟
        .mosi(mosi),  // MOSI
        .frame_end(master_frame_end),  // 一个数据帧结束
        .rev_data(master_rev_data)  // 接收到的数据
    );

    SPI_slave #(
        .CLK_FREQ_MHZ(CLK_FREQ_MHZ),
        .BAUD_RATE_KHZ(BAUD_RATE_KHZ),
        .DATA_WIDTH(DATA_WIDTH),
        .CPOL(CPOL),
        .CPHA(CPHA),
        .FIRST_BIT(FIRST_BIT)
    ) spi_slave_tb (
        .sys_clk(sys_clk),  // 系统时钟
        .rst_n(rst_n),  // 低电平异步复位
        .spi_clk(spi_clk),  // SPI 时钟
        .mosi(mosi),  // MOSI
        .cs_n(cs_n),  // 片选 低电平有效
        .send_data(slave_send_data),  // 待发送数据

        .miso(miso),  // MISO
        .frame_end(slave_frame_end),  // 一个数据帧结束
        .rev_data(slave_rev_data)  // 接收到的数据
    );


endmodule