You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决多SPI模块使用时“Net无法分配多个值”错误的方案

问题描述

我正在基于DE10-Lite FPGA开发一个SPI接口的三轴加速度计Verilog项目,为X/Y/Z轴分别设计了spi_ee_config模块(x_info、y_info、z_info)。每个模块都输出独立的片选信号(oSPI_CSN)和时钟信号(oSPI_CLK),但编译时出现Net Cannot Be Assigned More Than One Value错误。

经排查,错误根源是三个模块同时驱动GSENSOR_CS_N和GSENSOR_SCLK这两根信号线,导致总线冲突。现在需要解决的是如何正确共享SPI总线,确保同一时间只有一个模块驱动CS和CLK线路,同时完成三个轴的数据读取。

问题代码分析

冲突的顶层实例化代码

spi_ee_config_X x_info (
    .oSPI_CSN(GSENSOR_CS_N),
    .oSPI_CLK(GSENSOR_SCLK)
);

spi_ee_config_y y_info (
    .oSPI_CSN(GSENSOR_CS_N),
    .oSPI_CLK(GSENSOR_SCLK)
);

spi_ee_config_z z_info (
    .oSPI_CSN(GSENSOR_CS_N),
    .oSPI_CLK(GSENSOR_SCLK)
);

核心冲突点

每个spi_ee_config内部都实例化了spi_controller,而spi_controller中直接用assign推挽输出oSPI_CSN和oSPI_CLK。三个模块同时输出时,会出现多个驱动源争夺同一信号线的情况,触发编译错误。

解决方案:总线仲裁与SPI控制器复用

最优方案是复用单个SPI控制器,通过状态机调度依次触发X/Y/Z轴的读取操作,从根源避免总线冲突。具体逻辑如下:

  1. 只保留一个spi_controller实例作为共享硬件接口
  2. 设计调度模块,按顺序发送各轴的读取命令,等待当前操作完成后再触发下一个
  3. 统一管理SPI总线的使能与数据传输
代码示例

修改后的顶层模块

module top_gsensor (
    input         iRSTN,
    input         iSPI_CLK,
    input         iSPI_CLK_OUT,
    output        GSENSOR_CS_N,
    output        GSENSOR_SCLK,
    inout         GSENSOR_SDIO,
    output [15:0] oX_DATA,
    output [15:0] oY_DATA,
    output [15:0] oZ_DATA
);

// 共享SPI控制器实例
spi_controller u_spi_controller (
    .iRSTN(iRSTN),
    .iSPI_CLK(iSPI_CLK),
    .iSPI_CLK_OUT(iSPI_CLK_OUT),
    .iP2S_DATA(sched_p2s_data),
    .iSPI_GO(sched_spi_go),
    .oSPI_END(spi_end),
    .oS2P_DATA(spi_s2p_data),
    .SPI_SDIO(GSENSOR_SDIO),
    .oSPI_CSN(GSENSOR_CS_N),
    .oSPI_CLK(GSENSOR_SCLK)
);

// 调度状态机:轮询读取X/Y/Z轴数据
reg [1:0] state;
reg [7:0] sched_p2s_data; // 假设SI_DataL=7,共8位命令
reg sched_spi_go;
reg [15:0] x_data_reg, y_data_reg, z_data_reg;

localparam STATE_IDLE   = 2'd0;
localparam STATE_READ_X = 2'd1;
localparam STATE_READ_Y = 2'd2;
localparam STATE_READ_Z = 2'd3;

always @(posedge iSPI_CLK or negedge iRSTN) begin
    if (!iRSTN) begin
        state <= STATE_IDLE;
        sched_spi_go <= 1'b0;
        sched_p2s_data <= 'd0;
        x_data_reg <= 'd0;
        y_data_reg <= 'd0;
        z_data_reg <= 'd0;
    end else begin
        case (state)
            STATE_IDLE: begin
                sched_spi_go <= 1'b1;
                sched_p2s_data <= {1'b1, 7'h28}; // 读模式+X轴数据寄存器地址
                state <= STATE_READ_X;
            end
            STATE_READ_X: begin
                sched_spi_go <= 1'b0;
                if (spi_end) begin
                    x_data_reg <= spi_s2p_data;
                    sched_spi_go <= 1'b1;
                    sched_p2s_data <= {1'b1, 7'h2A}; // 读模式+Y轴数据寄存器地址
                    state <= STATE_READ_Y;
                end
            end
            STATE_READ_Y: begin
                sched_spi_go <= 1'b0;
                if (spi_end) begin
                    y_data_reg <= spi_s2p_data;
                    sched_spi_go <= 1'b1;
                    sched_p2s_data <= {1'b1, 7'h2C}; // 读模式+Z轴数据寄存器地址
                    state <= STATE_READ_Z;
                end
            end
            STATE_READ_Z: begin
                sched_spi_go <= 1'b0;
                if (spi_end) begin
                    z_data_reg <= spi_s2p_data;
                    state <= STATE_IDLE; // 完成一轮读取,回到空闲状态
                end
            end
        endcase
    end
end

// 输出各轴数据
assign oX_DATA = x_data_reg;
assign oY_DATA = y_data_reg;
assign oZ_DATA = z_data_reg;

endmodule

修正后的SPI控制器模块

module spi_controller (     
    input                             iRSTN,
    input                             iSPI_CLK,
    input                             iSPI_CLK_OUT,
    input         [7:0]               iP2S_DATA, 
    input                             iSPI_GO,
    output                            oSPI_END,
    output  reg [15:0]                oS2P_DATA,
    //  SPI Side              
    inout                             SPI_SDIO,
    output                            oSPI_CSN,
    output                            oSPI_CLK
);

reg [3:0] spi_count;
reg spi_count_en;

//  Structural coding
wire read_mode = iP2S_DATA[7];
wire write_address = spi_count[3];
assign oSPI_END = ~|spi_count;
assign oSPI_CSN = ~iSPI_GO; // 片选低有效,空闲时保持高电平
assign oSPI_CLK = spi_count_en ? iSPI_CLK_OUT : 1'b1; // 空闲时钟保持高电平
assign SPI_SDIO = spi_count_en && (!read_mode || write_address) ? iP2S_DATA[spi_count] : 1'bz;

always @ (posedge iSPI_CLK or negedge iRSTN) 
    if (!iRSTN)
    begin
        spi_count_en <= 1'b0;
        spi_count <= 4'hf;
        oS2P_DATA <= 'd0;
    end
    else 
    begin
        if (oSPI_END)
            spi_count_en <= 1'b0;
        else if (iSPI_GO)
            spi_count_en <= 1'b1;
            
        if (!spi_count_en)  
            spi_count <= 4'hf;      
        else
            spi_count <= spi_count - 4'b1;

        if (read_mode && !write_address)
            oS2P_DATA <= {oS2P_DATA[14:0], SPI_SDIO};
    end

endmodule
关键说明
  • 复用单个SPI控制器是资源效率最高的方案,既避免了重复硬件逻辑,又彻底解决总线冲突
  • 状态机调度确保同一时间只有一个轴的操作占用SPI总线,符合SPI协议的单主机规范
  • 对于三轴加速度计这类低速设备,轮询模式足够简单可靠;若需更高效率,可扩展为中断触发模式
  • 片选信号和时钟信号在空闲时保持高电平,符合多数SPI设备的时序要求

内容的提问来源于stack exchange,提问作者Mayank Neupane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:44:59