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

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • How to Read the Diagrams
  • Part 0: What Is a Protocol?10
  • Part 1: Timing and Clocking11
  • Part 2: Encoding and Framing12
  • Part 3: Handshakes13
  • Part 4: Flow Control17
  • Part 5: Transactions19
    • Reads and Writes Are Not Symmetric
    • The Request/Response Model
    • Address Maps and Decoding
    • Register Semantics
    • Combined and Split Transactions
    • Independent Channels
    • Posted and Non-Posted Writes
    • Bursts
    • Alignment, Strobes and Narrow Transfers
    • Boundary Rules
    • Width and Endianness Conversion
    • Transaction Attributes
    • Atomics
    • Sideband or In-Band
    • Interrupts and Notification
    • Descriptors, Rings and Doorbells
    • Bridges and Protocol Conversion
    • Protocols Compared: Transactions
    • Drills: Transactions
  • Part 6: Concurrency and Performance17
  • Part 7: Ordering13
  • Part 8: Sharing and Arbitration12
  • Part 9: Deadlock and Liveness13
  • Part 10: Reliability and Recovery20
  • Part 11: Bring-Up and Link Management10
  • Part 12: Power8
  • Part 13: Shared State and Coherence9
  • Part 14: Security and Isolation7
  • Part 15: Specifying and Verifying11
  • Part 16: Protocols Dissected17
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 5: Transactions
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 5: Transactions
  4. Register Semantics

Part 5 · Transactions · Working (W)

Register Semantics

Read-only, read-write, write-1-to-clear, read-to-clear and registers with side effects: what each access type means, the traps in each, and how to read a 64-bit counter over a 32-bit bus.

On this page

    Note

    Level: Working (W) · Answers: What exactly is being asked?

    Before this: Address Maps and Decoding

    An engineer attaches a debugger to a running chip and opens a window showing the interrupt-status registers. Interrupts start going missing. Close the window and they come back. The debugger is not writing anything. It is only reading, and in this design some reads change the register they read.

    What can a register access mean besides "store this value" and "give me that value", and what goes wrong with each meaning?

    The obvious answer, and why it breaks

    The obvious model of a register is a memory location: a write stores a value, a read returns it, and reading has no effect. Many registers do behave like that. But registers are the interface between software and hardware that is changing on its own, and pure storage cannot express "tell me what happened, and let me acknowledge it". So registers acquire access types in which reads or writes have side effects, and every side effect is a trap for someone who assumed a register was memory.

    The idea: access types

    Type

    A write does

    A read does

    Trap

    RO (read-only)

    Nothing (or an error)

    Returns hardware state

    Software writes it and assumes it changed

    RW (read-write)

    Stores the value

    Returns the stored value

    Read-modify-write races with hardware that also updates it

    W1C (write 1 to clear)

    Clears each bit written as 1; bits written as 0 are untouched

    Returns status

    Read-modify-write writes back 1s and clears events it never saw

    W1S (write 1 to set)

    Sets each bit written as 1

    Returns the value

    As W1C, mirrored

    RC (read to clear)

    Nothing

    Returns status and clears it

    Any extra read loses events: debuggers, speculative or repeated reads

    Read with side effect

    Varies

    Pops a FIFO, acknowledges an interrupt

    A read that is retried or duplicated consumes data twice

    WO (write-only)

    Triggers an action (start, reset)

    Returns 0 or garbage

    Software reads it back to check, and learns nothing

    W1C exists because of the read-modify-write trap. With a plain RW status register, software would read the status, clear the bit it handled and write the rest back; any bit hardware set in between would be overwritten with 0 and lost. With W1C, software writes 1 only to the bits it handled, and every other bit, including any that just appeared, is untouched.

    Build it: a W1C status register

    Four event sources set four status bits. Software clears a bit by writing 1 to it. The one subtle case is an event that fires in the same cycle software clears that bit: the set must win, or the event is lost.

    tb.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
    // A write-1-to-clear (W1C) status register. Hardware sets a bit when its event
    // fires; software clears a bit by writing 1 to it (writing 0 leaves it alone). If an
    // event fires in the same cycle software clears that bit, the set wins, so the new
    // event is never lost.
    module tb;
      logic clk = 0;
      always #5 clk = ~clk;
      initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end
    
      logic [3:0] event_in = 0;       // hardware events, one bit per source
      logic       wr_en = 0;          // software write strobe
      logic [3:0] wdata = 0;
      logic [3:0] status = 0;
      always @(posedge clk)
        status <= (status & ~(wr_en ? wdata : 4'b0)) | event_in;   // clear, then set: set wins
    
      int cyc = 0;
      always @(posedge clk) cyc <= cyc + 1;
      always @(posedge clk) begin
        event_in <= 0; wr_en <= 0; wdata <= 0;
        case (cyc)
          1: event_in <= 4'b0001;                           // event 0
          2: event_in <= 4'b0100;                           // event 2
          4: begin wr_en <= 1; wdata <= 4'b0001; end        // software clears bit 0 only
          6: begin wr_en <= 1; wdata <= 4'b0100; event_in <= 4'b0100; end   // clear bit 2 as event 2 fires again
          8: begin wr_en <= 1; wdata <= 4'b0100; end        // clear bit 2 again
          default: ;
        endcase
      end
    
      int errors = 0;
      always @(posedge clk) begin
        if (cyc == 6 && status != 4'b0100) errors++;   // bit 0 cleared, bit 2 untouched
        if (cyc == 8 && status != 4'b0100) errors++;   // the clear and the new event collided: still set
        if (cyc == 10 && status != 4'b0000) errors++;   // the second clear empties it
      end
      initial begin
        #130;
        if (errors == 0) $display("PASS: writing 1 clears only that bit, and an event in the clearing cycle is kept");
        else $display("FAIL: %0d checks failed", errors);
        $finish;
      end
    endmodule

    The single update line does it: clear the bits written as 1, then OR in this cycle's events, so a simultaneous event survives the clear.

    Watch it run

    How to read it, slot by slot:

    • Slots 1 and 2: events 0 and 2 fire (event_in 1, then 4). The register updates on the next edge, so status is 1 in slot 2 and 5 (bits 0 and 2) in slot 3.

    • Slot 4: software writes 1 (wr_en, wdata 1). In slot 5 status is 4: bit 0 cleared, bit 2 untouched, although software wrote 0 to it.

    • Slot 6: software writes 4 to clear bit 2, and in the same cycle event 2 fires again (event_in 4). In slot 7 status is still 4: the new event survived the clear.

    • Slot 8: software clears bit 2 again, with no new event; status is 0 from slot 9.

    Worked numbers: reading a 64-bit counter over a 32-bit bus

    A free-running 64-bit counter must be read as two 32-bit halves, and it can carry from the low half into the high half between the two reads.

    Diagram
    Counter = 0x0000_0001_FFFF_FFFF
    Read low half:   0xFFFF_FFFF
      (counter increments to 0x0000_0002_0000_0000)
    Read high half:  0x0000_0002
    Software sees    0x0000_0002_FFFF_FFFF   wrong by 2^32 - 1, about 4.3 billion ticks
    • Fix 1: read high, low, high. If the two high reads differ, a carry happened in between; read again.

    • Fix 2: a shadow register. Reading the low half copies the high half into a shadow register at that instant; the next read returns the shadow. Both halves come from the same moment. (The shadow is itself a read side effect, so a stray read of the low half alone is harmless but changes what the next high read returns.)

    Where these types appear

    • Interrupt status registers are almost always W1C, for the reason above.

    • FIFO data registers in UARTs and similar peripherals often pop on read: reading the receive register takes the oldest byte.

    • Register description languages (such as IP-XACT and SystemRDL) name the access types so that RTL, documentation, software headers and verification models are generated from one description.

    What breaks

    The opening story (illustrative): the interrupt-status register was RC. Every refresh of the debugger window read it, clearing whatever was pending, so the interrupt handler later found nothing. The same design also lost interrupts under a compiler change that made the driver read the register twice. The fix was to make status W1C and keep reads free of side effects. As a rule, a read should never destroy information.

    Why this matters

    Senior lens. Review register maps for side effects on reads first: every read-to-clear or pop-on-read register is an assumption that nothing else (a debugger, a retry, a speculative access, a second driver) ever reads it. Prefer W1C for status, keep reads side-effect free where you can, generate RTL and headers from one description, and give every wide counter an atomic read method.

    Interview

    Asked: "Why is read-to-clear dangerous?"

    Weak answer: It's slower.

    Strong answer: Because any extra read destroys information. A debugger refreshing a window, a retried bus transaction, a driver that reads twice, a speculative access: each clears events that the real handler never sees. W1C avoids it: reads have no side effect and software clears exactly the bits it handled by writing 1s, and in hardware a set in the same cycle must win over the clear so a new event isn't lost.

    Follow-ups: (1) Why not use a plain RW status register? (2) How do you read a 64-bit counter over a 32-bit bus safely?

    Check yourself A plain RW status register holds 0b0101. Software reads it, clears bit 0, and writes back 0b0100. Meanwhile hardware set bit 1. What happens?

    Bit 1 is overwritten with 0 by the write-back and the event is lost. With W1C, software would write 0b0001 and bit 1 would survive.

    Check yourself In the listing, why does the update clear first and then OR in the events?

    So that an event in the same cycle as a clear of that bit leaves the bit set; clearing after setting would lose it.

    Check yourself Reading a 64-bit counter as low then high gives 0x0000_0003_FFFF_FFFF, but the high half read again gives 0x0000_0004. What do you do?

    A carry happened between the reads; read the low half again (and the high half), and use the pair whose high halves agree.

    Key takeaways

    • Registers have access types (RO, RW, W1C, RC, side-effect reads), and each side effect is a trap for code that treats registers as memory.

    • W1C status avoids read-modify-write races; hardware must let a same-cycle event win over a clear.

    • Reads should not destroy information, and wide counters need an atomic read method.

    Keep going

    Next: Lesson 5.5, Combined and Split Transactions. Hold the bus until the answer comes, or let go and be answered later?

    Related topics

    • Part 5 overview: Transactions

    • RTL course: Memory and Buses

    Previous

    71 / 234

    Next: Combined and Split Transactions

    Up next

    Up nextCombined and Split Transactions

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Reads and Writes Are Not Symmetric
    • The Request/Response Model
    • Independent Channels
    clkevent_in014040wr_enwdata0104040status01540
    Simulation output: a W1C status register. Writing 1 clears only that bit; an event arriving in the clearing cycle is kept.