Skip to content

Conversation

@jgw0915
Copy link

@jgw0915 jgw0915 commented Dec 18, 2025

Fix false load-use hazards by gating rs1/rs2 usage in ID stage

Background

While analyzing control hazard waveforms, I discovered that the current load-use hazard detection logic can spuriously trigger stalls for certain instruction patterns.

Specifically, the control logic in Control.scala compares rd_ex against both rs1_id and rs2_id unconditionally. However, for several instruction types, the bit positions corresponding to rs1 or rs2 do not represent architectural source registers.

Root Cause

Snipaste_2025-12-15_23-58-59
  • For I-type load instructions (e.g., lw a0, 16(a5)), only rs1 is architecturally used.

  • rs2_id is still wired directly from instruction bits [24:20], which encode imm[4:0] for I-type instructions.

  • When the immediate value coincidentally equals a register index (e.g., imm = 16x16), the condition

    rd_ex == rs2_id
    

    may incorrectly evaluate to true.

  • This causes io_pc_stall, io_if_stall, and io_id_flush to assert, inserting an unnecessary bubble even though no real data dependency exists.

This behavior is technically consistent with the existing implementation but reflects a decode-level limitation: the hazard unit does not know whether the ID-stage instruction actually uses rs1 or rs2.

Fixes in this PR

Explicit rs1 / rs2 usage signals

  • Added decode-time signals to indicate whether an instruction uses rs1 and/or rs2
    • Identified that jal, auipc, and lui do not use rs1 (the same bit positions encode immediates)
    • rs2 is only considered for instruction classes that architecturally use a second source register, including:
      • R-type ALU instructions (e.g., add, sub, and, or)
      • S-type store instructions (e.g., sw)
      • B-type branch instructions (e.g., beq, bne)
  • These signals follow the added design hint mentioned in CA25 Exercise 19
  • Enables the control logic to reason about architectural source operands correctly

Result

Snipaste_2025-12-16_01-02-28
  • Eliminates false-positive load-use stalls
  • Preserves correct handling of genuine hazards
  • Improves correctness and precision of control hazard detection
  • Waveform behavior after the fix matches architectural expectations and avoids unnecessary pipeline stalls

You can check the whole implementation logic in the Control.scala on my Github repo

Comment on lines +164 to +170
val usesRs2 = (opcode === InstructionTypes.RM) ||
(opcode === InstructionTypes.S) ||
(opcode === InstructionTypes.B)

val usesRs1 = !(opcode === Instructions.jal) &&
!(opcode === Instructions.lui) &&
!(opcode === Instructions.auipc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed camelCase and snake_case:

  val usesRs2 = ...  // camelCase                                                                                                                      
  io.uses_rs2_id := usesRs2  // snake_case    

Should be consistent - use snake_case per project conventions:

  val uses_rs2 = ...                                                                                                                                   
  io.uses_rs2_id := uses_rs2

Copy link
Contributor

@jserv jserv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pull request adds uses_rs1_id and uses_rs2_id signals and wires them to Control, but Control.scala still has ? placeholders. The hazard detection logic is not modified to use these signals.
Looking at the author's full implementation in their fork, the intended fix is:

  val hazard_ex_rs1 = io.uses_rs1_id && (io.rd_ex === io.rs1_id)
  val hazard_ex_rs2 = io.uses_rs2_id && (io.rd_ex === io.rs2_id)

  when(
    ((io.memory_read_enable_ex || io.jump_instruction_id) &&
     (io.rd_ex =/= 0.U) &&
     (hazard_ex_rs1 || hazard_ex_rs2))
    ||
    (io.jump_instruction_id &&
     io.memory_read_enable_mem &&
     (io.rd_mem =/= 0.U) &&
     ((io.uses_rs1_id && (io.rd_mem === io.rs1_id)) ||
      (io.uses_rs2_id && (io.rd_mem === io.rs2_id))))
  ) { ... }

The PR needs to include this Control.scala change or the fix is non-functional.

Comment on lines +168 to +170
val usesRs1 = !(opcode === Instructions.jal) &&
!(opcode === Instructions.lui) &&
!(opcode === Instructions.auipc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing edge cases:

  • CSR immediate variants (csrrwi, csrrsi, csrrci) use zimm[4:0] in rs1 field, not actual rs1
  • fence instruction doesn't use rs1 for data dependency purposes

Suggested fix:

  val usesRs1 = !(opcode === Instructions.jal) &&
                !(opcode === Instructions.lui) &&
                !(opcode === Instructions.auipc) &&
                !(opcode === Instructions.fence) &&
                !(opcode === Instructions.csr &&
                  (funct3 === InstructionsTypeCSR.csrrwi ||
                   funct3 === InstructionsTypeCSR.csrrsi ||
                   funct3 === InstructionsTypeCSR.csrrci))

@jserv jserv changed the title Fix/false load-use stall by gating rs1 amd rs2 harzard check Fix/false load-use stall by gating rs1 amd rs2 hazard check Dec 19, 2025
@jserv
Copy link
Contributor

jserv commented Dec 19, 2025

Exercise 19 Intent: This is part of CA25 Exercise 19. The PR adds the usage signals which is good guidance, but should:

  1. Keep the exercise structure intact (placeholders remain for students)
  2. Add the signals as "hints" without solving the exercise completely

Since this PR is meant to be a complete fix, the Control.scala hazard logic needs updating.

@jserv
Copy link
Contributor

jserv commented Dec 19, 2025

Consider to contribute test case demonstrating false stall is eliminated:

lw  x16, 16(x0)   # imm[4:0] = 16 = x16
add x1, x2, x3    # rs2 = x3, should NOT stall despite x16 match

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants