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

Verilog单周期CPU开发:Always块等待输入时如何设置延迟?

Verilog多周期CPU指令读取问题分析与修复

你的代码核心问题

  1. 阻塞赋值误用:时序逻辑always块里全用了=阻塞赋值,这会导致信号在同一时钟周期内立即更新,引发竞争冒险,硬件时序逻辑必须用<=非阻塞赋值。
  2. 非法阻塞等待:repeat(25) @(posedge i_clk);是纯仿真的行为级写法,完全无法综合成实际硬件。硬件里的延迟等待必须用状态机+计数器实现,不能用这种阻塞式循环。
  3. 复位逻辑缺失:整个always块没处理negedge i_rst_n复位信号,复位时寄存器(比如ins_next_addr、o_finish_wire)没有初始化,导致上电或复位后状态混乱。
  4. 终止条件错误:你判断的是0xFFFFFFFF,但需求是遇到0xEEEEEEEE时终止,条件不匹配。
  5. 指令内存交互逻辑错误:o_i_valid_addr_wire一直置1,多数指令内存需要的是脉冲式有效信号(仅在发送地址的周期置1),而非持续高电平;且发送地址后直接阻塞等待,导致后续地址无法正常发送,第一条指令的处理直接卡死。

修复方案:用状态机实现多周期流程

因为指令内存需要5个周期返回指令,这已经不属于严格的单周期CPU架构,必须用多周期状态机控制流程。核心是划分状态流转:

  • IDLE:复位后初始状态,准备发送第一条指令地址
  • SEND_ADDR:发送指令地址并拉有效信号
  • WAIT_INST:计数5个时钟周期,等待指令返回
  • DECODE_EXECUTE:接收指令、解码执行,判断是否终止,准备下一条地址

修正后的代码示例:

// 定义状态枚举
localparam IDLE          = 2'b00;
localparam SEND_ADDR     = 2'b01;
localparam WAIT_INST     = 2'b10;
localparam DECODE_EXECUTE= 2'b11;

reg [1:0] current_state;
reg [2:0] wait_cnt; // 计数5个周期(0-4对应5个时钟沿)

always @(posedge i_clk or negedge i_rst_n) begin
    if(!i_rst_n) begin
        // 复位初始化所有寄存器
        current_state <= IDLE;
        ins_next_addr <= 32'd0;
        o_i_addr_wire <= 32'd0;
        o_i_valid_addr_wire <= 1'b0;
        o_finish_wire <= 1'b0;
        wait_cnt <= 3'd0;
        // 其他解码寄存器初始化
        rs1 <= 5'd0;
        rs2 <= 5'd0;
        write_reg <= 5'd0;
        // ... 其余信号同理
    end else begin
        case(current_state)
            IDLE: begin
                o_finish_wire <= 1'b0;
                current_state <= SEND_ADDR;
            end

            SEND_ADDR: begin
                // 发送地址+有效脉冲
                o_i_addr_wire <= ins_next_addr;
                o_i_valid_addr_wire <= 1'b1;
                current_state <= WAIT_INST;
                wait_cnt <= 3'd0;
            end

            WAIT_INST: begin
                o_i_valid_addr_wire <= 1'b0; // 拉低有效信号(若内存需要持续高电平则保留1'b1)
                if(wait_cnt == 3'd4) begin // 完成5个周期等待
                    current_state <= DECODE_EXECUTE;
                end else begin
                    wait_cnt <= wait_cnt + 1'b1;
                    current_state <= WAIT_INST;
                end
            end

            DECODE_EXECUTE: begin
                // 终止判断:匹配需求的0xEEEEEEEE
                if(i_i_inst_reg == 32'hEEEEEEEE) begin
                    o_finish_wire <= 1'b1;
                    current_state <= IDLE; // 终止后回到空闲状态
                end else begin
                    // 解码操作
                    rs1        <= i_i_inst_reg[19:15];
                    rs2        <= i_i_inst_reg[24:20];
                    write_reg  <= i_i_inst_reg[11:7];
                    read_data1 <= register[rs1];
                    read_data2 <= register[rs2];
                    imm_val    <= i_i_inst_reg[31:20];
                    funct3     <= i_i_inst_reg[14:12];
                    opcode     <= i_i_inst_reg[6:0];

                    // 更新PC,准备下一条指令
                    ins_next_addr <= ins_next_addr + 32'd4;
                    current_state <= SEND_ADDR;
                end
            end
        endcase
    end
end

额外注意事项

  • 确保i_i_inst_reg是同步寄存器:需要单独写一个always块在posedge i_clk下把i_i_inst赋值给i_i_inst_reg,避免亚稳态。
  • 指令内存的o_i_valid_addr_wire接口要匹配:如果指令内存要求有效信号持续高电平直到指令返回,那就在WAIT_INST阶段保持1'b1即可。
  • 单周期CPU的定义是一条指令在一个时钟周期内完成,但你的需求中指令内存延迟5个周期,必须改成多周期架构,这是需求决定的。

内容的提问来源于stack exchange,提问作者William Chen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:50:25