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

CPU与SRAM读写周期仿真异常:DR与IR同步变化问题排查

问题分析与解决方案

首先,咱们直接定位核心问题:你遇到的DR和IR同步变化,本质是模块间的时序匹配错误加上Testbench的时序设计不合理,导致数据没有按照预期的周期在CPU和SRAM之间传递。

具体问题拆解

1. 非阻塞赋值的时序延迟问题

你的CPU和SRAM模块都使用了时钟上升沿触发的非阻塞赋值(<=),这会导致信号更新延迟到当前时钟边沿之后:

  • 当CPU在时钟上升沿设置we=1(Cycle2)时,SRAM在同一个时钟上升沿看到的we还是旧值(比如初始的0),因此不会更新dout。
  • CPU此时给ir赋值时,拿到的是dout的旧值(而非期望的mem(0)=000f)。等到SRAM在下一个时钟上升沿更新dout时,Testbench已经切换到下一个select周期,CPU不再执行ir <= dout的赋值,导致ir从未正确获取mem(0)的值。

2. 多分支if的逻辑漏洞

CPU模块里用了多个独立的if(select==...)而非else if,这会导致:

  • 控制信号(比如we)不会在周期切换时被显式重置,可能出现SRAM的控制状态和当前周期不匹配的情况。
  • 若select切换不及时,可能意外触发多个分支的赋值,打乱预期逻辑。

3. Testbench的时序不匹配

你给每个select值设置了20ns的持续时间(对应2个时钟周期),但没有对齐时钟上升沿,也没给数据传递留足够的稳定时间,导致信号采样时机错误。

修复方案

方案1:优化SRAM的读逻辑(可选,简化时序)

把SRAM的读操作改成组合逻辑,让dout实时响应addr和we的变化,不需要等待时钟边沿:

// 修改sram.v,替换原有的第二个always块
assign dout = we ? mem[addr] : {word_width{1'bz}}; // 写操作时输出高阻(可根据需求调整)
// 写操作保持原有时序逻辑
always @ (posedge clk) begin 
  if(!we) mem[addr] <= din[word_width-1:0]; 
end 

方案2:修正CPU模块的逻辑分支与时序

改用case语句替代多个独立if,并提前设置SRAM的控制信号,确保数据传递时机正确:

// 修改cpu.v的always块
always @ (posedge clk or negedge reset) begin
  if(!reset) begin 
    ar <= 12'b0; 
    ir <= 16'b0; 
    pc <= 12'b0; 
    dr <= 16'b0; 
    ac <= 16'b0;
    addr <= 12'b0;
    din <= 16'b0;
    we <= 1'b1; // 初始化为读状态
  end 
  else begin
    case(select)
      3'b001: begin // Cycle1:设置地址+提前准备读状态
        addr <= 12'b0;
        we <= 1'b1;
      end
      3'b010: begin // Cycle2:读取mem(0)到IR
        ir <= dout; // 此时dout已经是mem(0)的有效值
      end
      3'b011: begin // Cycle3:设置地址+提前准备写状态
        addr <= 12'b1;
        we <= 1'b0;
      end
      3'b100: begin // Cycle4:将IR写入mem(1)
        din <= ir;
        // 写操作由SRAM在时钟上升沿自动执行
      end
      3'b101: begin // Cycle5:读取mem(1)到DR
        we <= 1'b1;
        dr <= dout; // 此时dout是更新后的mem(1)值
      end
      default: begin
        // 空闲状态,保持现有信号值
      end
    endcase
  end
end

方案3:修正Testbench的时序对齐

让select的切换对齐时钟上升沿,每个周期持续1个完整时钟周期(10ns),确保信号被正确采样:

// 修改tb_cpu.v的initial块
initial begin 
  clk = 0; 
  reset = 1; 
  $readmemb("sram.dat", tb_cpu.cpu.sram.mem); 
  sram_pointer = $fopen("sram_aftercycle.dat"); 
  reg_pointer = $fopen("reg.dat"); 
  #1 reset = 0; 
  #1 reset = 1; 
  #8 select = 3'b001; // 等待到下一个时钟上升沿前设置select
  #10 select = 3'b010; // 每个周期持续1个时钟周期
  #10 select = 3'b011; 
  #10 select = 3'b100; 
  #10 select = 3'b101; 
  #10; // 等待最后一个操作完成
  // 输出结果
  $fdisplay(reg_pointer, "AR = %b", tb_cpu.cpu.ar); 
  $fdisplay(reg_pointer, "IR = %b", tb_cpu.cpu.ir); 
  $fdisplay(reg_pointer, "PC = %b", tb_cpu.cpu.pc); 
  $fdisplay(reg_pointer, "DR = %b", tb_cpu.cpu.dr); 
  $fdisplay(reg_pointer, "AC = %b", tb_cpu.cpu.ac); 
  $fdisplay(sram_pointer, "mem[0000 0000 0000] = %b",tb_cpu.cpu.sram.mem[0]); 
  $fdisplay(sram_pointer, "mem[0000 0000 0001] = %b",tb_cpu.cpu.sram.mem[1]); 
  $fdisplay(sram_pointer, "mem[0000 0000 0010] = %b",tb_cpu.cpu.sram.mem[2]); 
  $fdisplay(sram_pointer, "mem[0000 0000 0011] = %b",tb_cpu.cpu.sram.mem[3]); 
  $fclose(sram_pointer); 
  $fclose(reg_pointer); 
  #5 $finish; 
end 

预期验证

修正后,时序会完全符合你的规划:

  1. Cycle1:设置addr=0,we=1(读状态)
  2. Cycle2:SRAM输出mem(0)=000f,CPU将其存入IR
  3. Cycle3:设置addr=1,we=0(写状态)
  4. Cycle4:SRAM将IR=000f写入mem(1)
  5. Cycle5:SRAM输出更新后的mem(1)=000f,CPU将其存入DR

最终IR和DR都会是000f,这完全符合你把mem(0)写入mem(1)的预期逻辑。

内容的提问来源于stack exchange,提问作者Pare Kanes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 18:52:29