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
预期验证
修正后,时序会完全符合你的规划:
- Cycle1:设置
addr=0,we=1(读状态) - Cycle2:SRAM输出
mem(0)=000f,CPU将其存入IR - Cycle3:设置
addr=1,we=0(写状态) - Cycle4:SRAM将
IR=000f写入mem(1) - Cycle5:SRAM输出更新后的
mem(1)=000f,CPU将其存入DR
最终IR和DR都会是000f,这完全符合你把mem(0)写入mem(1)的预期逻辑。
内容的提问来源于stack exchange,提问作者Pare Kanes
相关产品推荐
相关产品推荐

