Skip to content

Commit 7c007e9

Browse files
committed
Hazard interface example
1 parent e3afb5b commit 7c007e9

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

protocols/tests/hazard_interfaces/hazard_interface.out

Whitespace-only changes.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
}

protocols/tests/hazard_interfaces/hazard_interface.tx

Whitespace-only changes.

0 commit comments

Comments
 (0)