如何在两个always块中操作通用寄存器?AXI与自定义模块同步方案咨询
解决FPGA多Always块写同一寄存器的冲突问题
这在FPGA设计里是个典型的「多驱动冲突」坑——Verilog/SV里绝对不允许同一个寄存器被多个always块直接赋值,否则综合工具要么报错,要么生成不可控的组合逻辑,硬件行为完全不可预测。我给你两个工业界常用的靠谱方案,结合你的需求来实现:
方案一:单驱动源+脉冲触发(最直接)
核心思路是把原来两个always块的写操作,拆分成置位/清零脉冲信号,然后让flag寄存器只由一个always块驱动,通过多路选择逻辑决定最终值。如果AXI接口和主时钟(mainclk)是不同时钟域,还要先做跨时钟域同步。
// 定义控制信号:把原有的直接写flag,改成生成单周期脉冲 reg axi_set_flag; // AXI侧产生的置位脉冲 reg axi_set_flag_sync; // 同步到mainclk域的置位脉冲(跨时钟域用) reg task_done; // 自定义任务完成后的清零脉冲 reg flag; // 最终的flag寄存器 // 1. AXI接口的always块(假设AXI时钟为s_axi_aclk) always @(posedge s_axi_aclk) begin // 当用户通过AXI写flag寄存器时,生成单周期置位脉冲 if (axi_write_flag_trigger) begin // 替换成你实际的AXI写触发条件 axi_set_flag <= 1'b1; end else begin axi_set_flag <= 1'b0; end // 其他AXI接口逻辑... end // 2. 跨时钟域同步(如果AXI时钟和mainclk不同,必须做!避免亚稳态) always @(posedge mainclk) begin axi_set_flag_sync <= axi_set_flag; end // 3. 自定义任务的always块(mainclk域) always @(posedge mainclk) begin if (flag == 1'b1) begin // 执行你的任务逻辑... // 任务完成时生成单周期清零脉冲 if (task_finish_condition) begin // 替换成你实际的任务完成判断 task_done <= 1'b1; end else begin task_done <= 1'b0; end end else begin task_done <= 1'b0; end end // 4. 唯一驱动flag的always块(关键!避免多驱动) always @(posedge mainclk) begin if (axi_set_flag_sync) begin flag <= 1'b1; // AXI触发置位 end else if (task_done) begin flag <= 1'b0; // 任务完成清零 end // 注:如果两个脉冲同时触发(概率极低,同步后基本不会发生),这里优先级是置位优先,可按需调整 end
方案二:状态机整合逻辑(更适合复杂任务)
如果你的自定义任务逻辑比较复杂,用状态机统一管理任务的「空闲-启动-运行-完成」生命周期,flag的赋值完全由状态机控制,从根源上避免多驱动问题。
// 定义状态机状态 reg [1:0] task_state; localparam IDLE = 2'b00; localparam RUNNING = 2'b01; localparam DONE = 2'b10; reg axi_start_task; // AXI侧触发的任务启动信号(需同步到mainclk域) reg flag; // 1. AXI接口逻辑(同步到mainclk,或先做跨时钟域同步) always @(posedge s_axi_aclk) begin if (axi_write_flag_trigger) begin axi_start_task <= 1'b1; end else begin axi_start_task <= 1'b0; end end // 2. 跨时钟域同步(如果需要) always @(posedge mainclk) begin axi_start_task <= axi_start_task_raw; // axi_start_task_raw是AXI侧的原始信号 end // 3. 主状态机always块(mainclk域,唯一控制flag) always @(posedge mainclk) begin case(task_state) IDLE: begin flag <= 1'b0; // AXI触发任务启动 if (axi_start_task) begin task_state <= RUNNING; flag <= 1'b1; end end RUNNING: begin // 执行你的任务逻辑... // 任务完成后跳转到DONE状态 if (task_finish_condition) begin task_state <= DONE; end end DONE: begin flag <= 1'b0; task_state <= IDLE; // 回到空闲状态,等待下一次触发 end default: task_state <= IDLE; endcase end
关键注意事项
- 跨时钟域必须同步:如果AXI接口的时钟和mainclk不是同一个,一定要用两级触发器对AXI侧的控制信号做同步,否则会出现亚稳态,导致逻辑异常。
- 脉冲信号要单周期:不管是置位还是清零信号,都要确保是单周期脉冲,避免重复触发任务或者重复清零。
- 绝对避免多驱动:永远不要让同一个寄存器被多个always块直接赋值,这是FPGA设计的基本规范。
内容的提问来源于stack exchange,提问作者Rahul Tungar
相关产品推荐
相关产品推荐

