1+ // A simple hazard interface, in the style of Jang et al. (2024).
2+
3+ // Suppose we have a RISC-V CPU that uses the 5 usual stages
4+ // (Fetch-Decode-Execute-Memory-Write), and we want to execute
5+ // instructions `I1, I2`:
6+ // ```
7+ // I1: ld r2, 0(r1) # Load mem[r1] into r2
8+ // I2: sub r3, r4, r2 # Set r3 := r4 - r2
9+ // ```
10+ // `r2` appears in both instructions, so there is a data dependency.
11+ // In particular, when I1 is at the execute stage and I2 is at the decode stage,
12+ // I2 must be stalled since `r2` is unavailable (it is still being computed).
13+ // I2 can only proceed when I1 is at the memory stage and communicates
14+ // the value of `r2` via a bypass.
15+
16+ // A *hazard interfaces* generalizes ready-valid interface, by allowing
17+ // the receiver to send back an extra `resolver` signal on top of the
18+ // usual `ready` bit. This example encodes it.
19+
20+ // This is a read-after-write data hazard check:
21+ // on top of sending back an availability ("ready") bit,
22+ // the sender indicates whih register it has locked (it is currently writing to),
23+ // and whether the locked register contains a valid value,
24+ // so that the sender can stall if there's a conflict (i.e. if the sender
25+ // needs to use the same register).
26+
27+ // The transfer only fires when ready(payload, resolver) holds:
28+ // `DUT.o_available = true && !(DUT.o_lock_valid && DUT.o_locked_reg == src_reg)`
29+
30+ // In standard valid-ready, the sender would just send back one single `ready`
31+ // bit, so this example is not expressible.
32+
33+ // This interface is written from the perspective of the receiver,
34+ // so the backward signals are output fields.
35+ struct HazardInterface {
36+ // Forward signals (sender -> receiver): the "payload"
37+ in i_valid: u1,
38+ in i_src_reg: u5, // The register being read by the sender's stage
39+ in i_payload: u32,
40+
41+ // Backward signals (receiver -> sender)
42+ out o_available: u1, // This is the same as the ready bit
43+ out o_locked_reg: u5, // Which register is currently locked by the receiver
44+ out o_lock_valid: u1, // Whether the register contains a valid value
45+ }
46+
47+ #[idle]
48+ prot idle<DUT: HazardInterface>() {
49+ DUT.i_valid := 1'b0;
50+ DUT.i_src_reg := X;
51+ DUT.i_payload := X;
52+ step();
53+ }
54+
55+ // Sends a `payload` from the `src_reg` from the sender to the reciever
56+ prot send_data<DUT: HazardInterface>(in src_reg: u5, in payload: u32) {
57+ DUT.i_valid := 1'b1;
58+ DUT.i_src_reg := src_reg;
59+ DUT.i_payload := payload;
60+
61+ // Keep waiting if the receiver is not ready (not availalbe),
62+ // and if the receiver has locked the same register as `src_reg`
63+ // (and the register contains a value value)
64+ // Note: this while-loop is the key difference from ready-valid,
65+ // in usual ready-valid we'd just have `while (DUT.o_ready == 0) { step(); }`
66+ while (DUT.o_available == 1'b0 || (DUT.o_lock_valid == 1'b1 && DUT.o_locked_reg == src_reg)) {
67+ step();
68+ }
69+
70+ // Data transfer occurs during this cycle
71+ step();
72+
73+ DUT.i_valid := X;
74+ DUT.i_src_reg := X;
75+ DUT.i_payload := X;
76+ step();
77+ }
0 commit comments