add branch speculation using shadows
[soc.git] / src / scoreboard / shadow.py
1 from nmigen.compat.sim import run_simulation
2 from nmigen.cli import verilog, rtlil
3 from nmigen import Module, Signal, Cat, Array, Const, Elaboratable, Repl
4 from nmigen.lib.coding import Decoder
5
6 from scoreboard.shadow_fn import ShadowFn
7
8
9 class Shadow(Elaboratable):
10 """ implements shadowing 11.5.1, p55
11
12 shadowing can be used for branches as well as exceptions (interrupts),
13 load/store hold (exceptions again), and vector-element predication
14 (once the predicate is known, which it may not be at instruction issue)
15
16 Inputs
17 * :shadow_wid: number of shadow/fail/good/go_die sets
18
19 notes:
20 * when shadow_wid = 0, recover and shadown are Consts (i.e. do nothing)
21 """
22 def __init__(self, shadow_wid=0):
23 self.shadow_wid = shadow_wid
24
25 if shadow_wid:
26 # inputs
27 self.issue_i = Signal(reset_less=True)
28 self.shadow_i = Signal(shadow_wid, reset_less=True)
29 self.s_fail_i = Signal(shadow_wid, reset_less=True)
30 self.s_good_i = Signal(shadow_wid, reset_less=True)
31 # outputs
32 self.go_die_o = Signal(reset_less=True)
33 self.shadown_o = Signal(reset_less=True)
34 else:
35 # outputs when no shadowing needed
36 self.shadown_o = Const(1)
37 self.go_die_o = Const(0)
38
39 def elaborate(self, platform):
40 m = Module()
41 s_latches = []
42 for i in range(self.shadow_wid):
43 sh = ShadowFn()
44 setattr(m.submodules, "shadow%d" % i, sh)
45 s_latches.append(sh)
46
47 # shadow / recover (optional: shadow_wid > 0)
48 if self.shadow_wid:
49 i_l = []
50 fail_l = []
51 good_l = []
52 shi_l = []
53 sho_l = []
54 rec_l = []
55 # get list of latch signals. really must be a better way to do this
56 for l in s_latches:
57 i_l.append(l.issue_i)
58 shi_l.append(l.shadow_i)
59 fail_l.append(l.s_fail_i)
60 good_l.append(l.s_good_i)
61 sho_l.append(l.shadow_o)
62 rec_l.append(l.recover_o)
63 m.d.comb += Cat(*i_l).eq(Repl(self.issue_i, self.shadow_wid))
64 m.d.comb += Cat(*fail_l).eq(self.s_fail_i)
65 m.d.comb += Cat(*good_l).eq(self.s_good_i)
66 m.d.comb += Cat(*shi_l).eq(self.shadow_i)
67 m.d.comb += self.shadown_o.eq(~(Cat(*sho_l).bool()))
68 m.d.comb += self.go_die_o.eq(Cat(*rec_l).bool())
69
70 return m
71
72 def __iter__(self):
73 if self.shadow_wid:
74 yield self.issue_i
75 yield self.shadow_i
76 yield self.s_fail_i
77 yield self.s_good_i
78 yield self.go_die_o
79 yield self.shadown_o
80
81 def ports(self):
82 return list(self)
83
84
85 class ShadowMatrix(Elaboratable):
86 """ Matrix of Shadow Functions. One per FU.
87
88 Inputs
89 * :n_fus: register file width
90 * :shadow_wid: number of shadow/fail/good/go_die sets
91
92 Notes:
93
94 * Shadow enable/fail/good are all connected to all Shadow Functions
95 (incoming at the top)
96
97 * Output is an array of "shadow active" (schroedinger wires: neither
98 alive nor dead) and an array of "go die" signals, one per FU.
99
100 * the shadown must be connected to the Computation Unit's
101 write release request, preventing it (ANDing) from firing
102 (and thus preventing Writable. this by the way being the
103 whole point of having the Shadow Matrix...)
104
105 * go_die_o must be connected to *both* the Computation Unit's
106 src-operand and result-operand latch resets, causing both
107 of them to reset.
108
109 * go_die_o also needs to be wired into the Dependency and Function
110 Unit Matrices by way of over-enabling (ORing) into Go_Read and
111 Go_Write, resetting every cell that is required to "die"
112 """
113 def __init__(self, n_fus, shadow_wid=0):
114 self.n_fus = n_fus
115 self.shadow_wid = shadow_wid
116
117 # inputs
118 self.issue_i = Signal(n_fus, reset_less=True)
119 self.shadow_i = Array(Signal(shadow_wid, name="sh_i", reset_less=True) \
120 for f in range(n_fus))
121 self.s_fail_i = Signal(shadow_wid, reset_less=True)
122 self.s_good_i = Signal(shadow_wid, reset_less=True)
123
124 # outputs
125 self.go_die_o = Signal(n_fus, reset_less=True)
126 self.shadown_o = Signal(n_fus, reset_less=True)
127
128 def elaborate(self, platform):
129 m = Module()
130 shadows = []
131 for i in range(self.n_fus):
132 sh = Shadow(self.shadow_wid)
133 setattr(m.submodules, "sh%d" % i, sh)
134 shadows.append(sh)
135 # connect shadow/fail/good to all shadows
136 m.d.comb += sh.s_fail_i.eq(self.s_fail_i)
137 m.d.comb += sh.s_good_i.eq(self.s_good_i)
138 # this one is the matrix (shadow enables)
139 m.d.comb += sh.shadow_i.eq(self.shadow_i[i])
140
141 # connect all shadow outputs and issue input
142 issue_l = []
143 sho_l = []
144 rec_l = []
145 for l in shadows:
146 issue_l.append(l.issue_i)
147 sho_l.append(l.shadown_o)
148 rec_l.append(l.go_die_o)
149 m.d.comb += Cat(*issue_l).eq(self.issue_i)
150 m.d.comb += self.shadown_o.eq(Cat(*sho_l))
151 m.d.comb += self.go_die_o.eq(Cat(*rec_l))
152
153 return m
154
155 def __iter__(self):
156 yield self.issue_i
157 yield from self.shadow_i
158 yield self.s_fail_i
159 yield self.s_good_i
160 yield self.go_die_o
161 yield self.shadown_o
162
163 def ports(self):
164 return list(self)
165
166
167 class BranchSpeculationRecord(Elaboratable):
168 """ A record of which function units will be cancelled and which
169 allowed to proceed, on a branch.
170
171 Whilst the input is a pair that says whether the instruction is
172 under the "success" branch shadow (good_i) or the "fail" shadow
173 (fail_i path), when the branch result is known, the "good" path
174 must be cancelled if "fail" occurred, and the "fail" path cancelled
175 if "good" occurred.
176
177 therefore, use "good|~fail" and "fail|~good" respectively as
178 output.
179 """
180
181 def __init__(self, n_fus):
182 self.n_fus = n_fus
183
184 # inputs: record *expected* status
185 self.issue_i = Signal(reset_less=True)
186 self.good_i = Signal(reset_less=True)
187 self.fail_i = Signal(reset_less=True)
188
189 # inputs: status of branch (when result was known)
190 self.br_i = Signal(reset_less=True)
191 self.br_good_i = Signal(reset_less=True)
192 self.br_fail_i = Signal(reset_less=True)
193
194 # outputs: true if the *expected* outcome matched the *actual* outcome
195 self.matched_o = Signal(reset_less=True)
196
197 def elaborate(self, platform):
198 m = Module()
199
200 # registers to record *expected* status
201 good_r = Signal()
202 fail_r = Signal()
203
204 with m.If(self.br_i):
205 # we expected fail, return OK that fail was EXPECTED... OR...
206 # we expected good, return OK that good was EXPECTED
207 success = Signal(reset_less=True)
208 m.d.comb += success.eq((good_r & self.br_good_i) | \
209 (fail_r & self.br_fail_i) )
210 # ... but only set these where a good or fail *is* expected...
211 with m.If(good_r | fail_r):
212 m.d.comb += self.matched_o.eq(success)
213 m.d.sync += good_r.eq(0) # might be set if issue set as well
214 m.d.sync += fail_r.eq(0) # might be set if issue set as well
215 with m.If(self.issue_i):
216 m.d.sync += good_r.eq(good_r | self.good_i)
217 m.d.sync += fail_r.eq(fail_r | self.fail_i)
218
219 return m
220
221 def __iter__(self):
222 yield self.issue_i
223 yield self.good_i
224 yield self.fail_i
225 yield self.br_i
226 yield self.br_good_i
227 yield self.br_fail_i
228 yield self.good_o
229 yield self.fail_o
230
231 def ports(self):
232 return list(self)
233
234
235
236 class WaWGrid(Elaboratable):
237 """ An NxM grid-selector which raises a 2D bit selected by N and M
238 """
239
240 def __init__(self, n_fus, shadow_wid):
241 self.n_fus = n_fus
242 self.shadow_wid = shadow_wid
243
244 self.shadow_i = Signal(shadow_wid, reset_less=True)
245 self.fu_i = Signal(n_fus, reset_less=True)
246
247 self.waw_o = Array(Signal(shadow_wid, name="waw_o", reset_less=True) \
248 for f in range(n_fus))
249
250 def elaborate(self, platform):
251 m = Module()
252 for i in range(self.n_fus):
253 v = Repl(self.fu_i[i], self.shadow_wid)
254 m.d.comb += self.waw_o[i].eq(v & self.shadow_i)
255 return m
256
257
258 def shadow_sim(dut):
259 yield dut.dest_i.eq(1)
260 yield dut.issue_i.eq(1)
261 yield
262 yield dut.issue_i.eq(0)
263 yield
264 yield dut.src1_i.eq(1)
265 yield dut.issue_i.eq(1)
266 yield
267 yield
268 yield
269 yield dut.issue_i.eq(0)
270 yield
271 yield dut.go_rd_i.eq(1)
272 yield
273 yield dut.go_rd_i.eq(0)
274 yield
275 yield dut.go_wr_i.eq(1)
276 yield
277 yield dut.go_wr_i.eq(0)
278 yield
279
280 def test_shadow():
281 dut = ShadowMatrix(4, 2)
282 vl = rtlil.convert(dut, ports=dut.ports())
283 with open("test_shadow.il", "w") as f:
284 f.write(vl)
285
286 dut = BranchSpeculationRecord(4)
287 vl = rtlil.convert(dut, ports=dut.ports())
288 with open("test_branchspecrecord.il", "w") as f:
289 f.write(vl)
290
291 run_simulation(dut, shadow_sim(dut), vcd_name='test_shadow.vcd')
292
293 if __name__ == '__main__':
294 test_shadow()