解决多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轴的读取操作,从根源避免总线冲突。具体逻辑如下:
- 只保留一个
spi_controller实例作为共享硬件接口 - 设计调度模块,按顺序发送各轴的读取命令,等待当前操作完成后再触发下一个
- 统一管理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
相关产品推荐
相关产品推荐

