Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
    • SRAM and ROM
    • Register Files
      • Multi-Port Register File Fundamentals
      • 2R1W Register File with Bypass
      • Read-During-Write Hazards and Assertions
    • Valid/Ready Handshake
    • AXI-Lite
    • AXI-Stream
    • Bus Integration
    • Synchronous FIFO
    • Register-Map and CSR RTL
    • Memory Reliability
    • Peripheral RTL Integration
    • Full AXI4: RTL Roadmap
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Register Files
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. Register Files
  5. Read-During-Write Hazards and Assertions

Part 7 · Memory & Buses · Intermediate

Read-During-Write Hazards and Assertions

Decode-versus-writeback collisions, the bypass assertion, and a check that x0 always reads zero.

On this page

    Why this matters

    A decode read that overlaps a writeback to the same register uses the old value, and nothing fails loudly: the arithmetic is silently wrong. One assertion on the bypass contract catches it, provided the x0 case is excluded.

    After this lesson you can: write the assertion that a same-cycle read sees the write data, include the nonzero guard, and explain why x0 needs it.

    Decode reads while writeback writes

    Without bypass, a read in decode can observe old data while writeback updates the same register. This creates silent arithmetic bugs.

    Diagram
    Cycle N: writeback writes x5 <= result; decode reads rs1 = x5 for the next instruction.
    No bypass:  decode sees the old x5 and computes with a stale operand.
    With bypass: decode sees the new result in the same cycle.

    The bypass from the previous lesson is the fix for the same-cycle case. A writeback that lands a cycle after the decode read is a different hazard, handled by forwarding in the pipeline, not by this mux.

    The assertion

    The bypass contract is one property: a same-cycle read of the written register must see the write data. The SystemVerilog form below needs a clocked context, so it is shown partial; the testbench checks the same property procedurally.

    design.sv
    1
    2
    3
    4
    // snippet: the hazard property in SVA shape (needs a clocked module around it)
    // If the same register is read and written in one cycle, the read data must match the write data.
    // assert property (@(posedge clk)
    //   we_i && (waddr_i == raddr0_i) && (waddr_i != '0) |-> (rdata0_o == wdata_i));

    The nonzero guard is load-bearing. Without it, any write while reading x0 would demand that zero equal the write data, which never holds.

    rf_hazard_check.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // Same-cycle hazard checker: raises a flag while the contract is broken.
    // err_byp0/1 mirror the SVA property above for both read ports; err_x0 is the
    // second check, demanding that x0 always read zero on both ports.
    module rf_hazard_check #(
      parameter int DW = 32,
      parameter int AW = 5
    ) (
      input  logic          we_i,
      input  logic [AW-1:0] waddr_i,
      input  logic [AW-1:0] raddr0_i,
      input  logic [AW-1:0] raddr1_i,
      input  logic [DW-1:0] wdata_i,
      input  logic [DW-1:0] rdata0_o,
      input  logic [DW-1:0] rdata1_o,
      output logic          err_byp0,
      output logic          err_byp1,
      output logic          err_x0
    );
      assign err_byp0 = we_i && (waddr_i != '0) && (waddr_i == raddr0_i) && (rdata0_o != wdata_i);
      assign err_byp1 = we_i && (waddr_i != '0) && (waddr_i == raddr1_i) && (rdata1_o != wdata_i);
      assign err_x0 = ((raddr0_i == '0) && (rdata0_o != '0)) ||
                      ((raddr1_i == '0) && (rdata1_o != '0));
    endmodule

    Common mistakes

    Common pitfall

    Checking the read data after the clock edge instead of during the write cycle proves nothing about bypass. The assertion must hold while we_i is asserted and the addresses match, before the edge commits the write.

    Interview

    Likely question: write the assertion for a register-file bypass, including x0.

    • Antecedent: write enabled, write address equals read address, write address nonzero.

    • Consequent: read data equals write data.

    • Plus: a second check that reads of address 0 always return zero.

    Check yourself Decode reads x5 in the same cycle writeback writes x5 with the bypass present. What does decode see?

    The new result. The bypass forwards the write data to the read port in the same cycle, so no stale operand reaches the next instruction.

    Check yourself Why does the hazard assertion exclude write address zero?

    Reads of x0 always return zero, so demanding rdata == wdata there would fail on every write. The exclusion keeps the property to registers the bypass actually serves.

    Testbench

    The register file under a decode-versus-writeback collision, watched by the hazard checker on every cycle.

    regfile_2r1w.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    module regfile_2r1w #(
      parameter int DW = 32,
      parameter int AW = 5
    ) (
      input  logic          clk,
      input  logic          rst_n,
      input  logic          we_i,
      input  logic [AW-1:0] waddr_i,
      input  logic [DW-1:0] wdata_i,
      input  logic [AW-1:0] raddr0_i,
      input  logic [AW-1:0] raddr1_i,
      output logic [DW-1:0] rdata0_o,
      output logic [DW-1:0] rdata1_o
    );
      logic [DW-1:0] rf [2**AW];
      logic bypass0, bypass1;
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          integer i;
          for (i = 0; i < 2**AW; i++) rf[i] <= '0;
        end else if (we_i && (waddr_i != '0)) begin
          rf[waddr_i] <= wdata_i;
        end
      end
      assign bypass0 = we_i && (waddr_i != '0) && (waddr_i == raddr0_i);
      assign bypass1 = we_i && (waddr_i != '0) && (waddr_i == raddr1_i);
      always_comb begin
        rdata0_o = (raddr0_i == '0) ? '0 : rf[raddr0_i];
        rdata1_o = (raddr1_i == '0) ? '0 : rf[raddr1_i];
        if (bypass0) rdata0_o = wdata_i;
        if (bypass1) rdata1_o = wdata_i;
      end
    endmodule
    
    module rf_hazard_check #(
      parameter int DW = 32,
      parameter int AW = 5
    ) (
      input  logic          we_i,
      input  logic [AW-1:0] waddr_i,
      input  logic [AW-1:0] raddr0_i,
      input  logic [AW-1:0] raddr1_i,
      input  logic [DW-1:0] wdata_i,
      input  logic [DW-1:0] rdata0_o,
      input  logic [DW-1:0] rdata1_o,
      output logic          err_byp0,
      output logic          err_byp1,
      output logic          err_x0
    );
      assign err_byp0 = we_i && (waddr_i != '0) && (waddr_i == raddr0_i) && (rdata0_o != wdata_i);
      assign err_byp1 = we_i && (waddr_i != '0) && (waddr_i == raddr1_i) && (rdata1_o != wdata_i);
      assign err_x0 = ((raddr0_i == '0) && (rdata0_o != '0)) ||
                      ((raddr1_i == '0) && (rdata1_o != '0));
    endmodule
    

    Remember

    • The property: write enabled, write address equals read address and is nonzero, then read data equals write data.

    • Without the nonzero guard the property fails on every write while x0 is read, because x0 reads zero.

    • Check during the write cycle, before the edge commits the write. A check after the edge proves nothing about bypass.

    Keep going

    • Practice: 2R1W register file: read-first vs write-first semantics

    • Interview: run a mock interview on this topic

    Related topics

    • Read-first versus write-first

    Previous

    197 / 350

    Next: Valid/Ready Handshake

    Up next

    Up nextValid/Ready Handshake

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Multi-Port Register File Fundamentals