cd4089d7902c1eeb36181d15a8a810ac93eb7f69
[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 = Array(Signal(shadow_wid, name="fl_i", reset_less=True) \
122 for f in range(n_fus))
123 self.s_good_i = Array(Signal(shadow_wid, name="gd_i", reset_less=True) \
124 for f in range(n_fus))
125 # outputs
126 self.go_die_o = Signal(n_fus, reset_less=True)
127 self.shadown_o = Signal(n_fus, reset_less=True)
128
129 def elaborate(self, platform):
130 m = Module()
131 shadows = []
132 for i in range(self.n_fus):
133 sh = Shadow(self.shadow_wid)
134 setattr(m.submodules, "sh%d" % i, sh)
135 shadows.append(sh)
136 # connect shadow/fail/good to all shadows
137 m.d.comb += sh.s_fail_i.eq(self.s_fail_i[i])
138 m.d.comb += sh.s_good_i.eq(self.s_good_i[i])
139 # this one is the matrix (shadow enables)
140 m.d.comb += sh.shadow_i.eq(self.shadow_i[i])
141
142 # connect all shadow outputs and issue input
143 issue_l = []
144 sho_l = []
145 rec_l = []
146 for l in shadows:
147 issue_l.append(l.issue_i)
148 sho_l.append(l.shadown_o)
149 rec_l.append(l.go_die_o)
150 m.d.comb += Cat(*issue_l).eq(self.issue_i)
151 m.d.comb += self.shadown_o.eq(Cat(*sho_l))
152 m.d.comb += self.go_die_o.eq(Cat(*rec_l))
153
154 return m
155
156 def __iter__(self):
157 yield self.issue_i
158 yield from self.shadow_i
159 yield from self.s_fail_i
160 yield from self.s_good_i
161 yield self.go_die_o
162 yield self.shadown_o
163
164 def ports(self):
165 return list(self)
166
167
168 class BranchSpeculationRecord(Elaboratable):
169 """ A record of which function units will be cancelled and which
170 allowed to proceed, on a branch.
171
172 Whilst the input is a pair that says whether the instruction is
173 under the "success" branch shadow (good_i) or the "fail" shadow
174 (fail_i path), when the branch result is known, the "good" path
175 must be cancelled if "fail" occurred, and the "fail" path cancelled
176 if "good" occurred.
177
178 therefore, use "good|~fail" and "fail|~good" respectively as
179 output.
180 """
181
182 def __init__(self, n_fus):
183 self.n_fus = n_fus
184
185 # inputs: record *expected* status
186 self.active_i = Signal(reset_less=True)
187 self.good_i = Signal(n_fus, reset_less=True)
188 self.fail_i = Signal(n_fus, reset_less=True)
189
190 # inputs: status of branch (when result was known)
191 self.br_i = Signal(reset_less=True)
192 self.br_ok_i = Signal(reset_less=True)
193
194 # outputs: true if the *expected* outcome matched the *actual* outcome
195 self.match_f_o = Signal(n_fus, reset_less=True)
196 self.match_g_o = Signal(n_fus, reset_less=True)
197
198 def elaborate(self, platform):
199 m = Module()
200
201 # registers to record *expected* status
202 good_r = Signal(self.n_fus)
203 fail_r = Signal(self.n_fus)
204
205 for i in range(self.n_fus):
206 with m.If(self.active_i):
207 m.d.sync += good_r[i].eq(good_r[i] | self.good_i[i])
208 m.d.sync += fail_r[i].eq(fail_r[i] | self.fail_i[i])
209 with m.If(self.br_i):
210 # we expected fail, return OK that fail was EXPECTED... OR...
211 # we expected good, return OK that good was EXPECTED
212 good = Signal(reset_less=True)
213 fail = Signal(reset_less=True)
214 with m.If(self.br_ok_i):
215 m.d.comb += good.eq(good_r[i])
216 m.d.comb += fail.eq(fail_r[i])
217 with m.Else():
218 m.d.comb += good.eq(~good_r[i])
219 m.d.comb += fail.eq(~fail_r[i])
220 # ... but only set these where a good or fail *is* expected...
221 with m.If(good_r[i]):
222 m.d.comb += self.match_g_o[i].eq(self.br_ok_i)
223 m.d.comb += self.match_f_o[i].eq(~self.br_ok_i)
224 with m.If(fail_r[i]):
225 m.d.comb += self.match_f_o[i].eq(~self.br_ok_i)
226 m.d.comb += self.match_g_o[i].eq(self.br_ok_i)
227 m.d.sync += good_r[i].eq(0) # might be set if issue set as well
228 m.d.sync += fail_r[i].eq(0) # might be set if issue set as well
229
230 return m
231
232 def __iter__(self):
233 yield self.active_i
234 yield self.good_i
235 yield self.fail_i
236 yield self.br_i
237 yield self.br_good_i
238 yield self.br_fail_i
239 yield self.good_o
240 yield self.fail_o
241
242 def ports(self):
243 return list(self)
244
245
246
247 class WaWGrid(Elaboratable):
248 """ An NxM grid-selector which raises a 2D bit selected by N and M
249 """
250
251 def __init__(self, n_fus, shadow_wid):
252 self.n_fus = n_fus
253 self.shadow_wid = shadow_wid
254
255 self.shadow_i = Signal(shadow_wid, reset_less=True)
256 self.fu_i = Signal(n_fus, reset_less=True)
257
258 self.waw_o = Array(Signal(shadow_wid, name="waw_o", reset_less=True) \
259 for f in range(n_fus))
260
261 def elaborate(self, platform):
262 m = Module()
263 for i in range(self.n_fus):
264 v = Repl(self.fu_i[i], self.shadow_wid)
265 m.d.comb += self.waw_o[i].eq(v & self.shadow_i)
266 return m
267
268
269 def shadow_sim(dut):
270 yield dut.dest_i.eq(1)
271 yield dut.issue_i.eq(1)
272 yield
273 yield dut.issue_i.eq(0)
274 yield
275 yield dut.src1_i.eq(1)
276 yield dut.issue_i.eq(1)
277 yield
278 yield
279 yield
280 yield dut.issue_i.eq(0)
281 yield
282 yield dut.go_rd_i.eq(1)
283 yield
284 yield dut.go_rd_i.eq(0)
285 yield
286 yield dut.go_wr_i.eq(1)
287 yield
288 yield dut.go_wr_i.eq(0)
289 yield
290
291 def test_shadow():
292 dut = ShadowMatrix(4, 2)
293 vl = rtlil.convert(dut, ports=dut.ports())
294 with open("test_shadow.il", "w") as f:
295 f.write(vl)
296
297 dut = BranchSpeculationRecord(4)
298 vl = rtlil.convert(dut, ports=dut.ports())
299 with open("test_branchspecrecord.il", "w") as f:
300 f.write(vl)
301
302 run_simulation(dut, shadow_sim(dut), vcd_name='test_shadow.vcd')
303
304 if __name__ == '__main__':
305 test_shadow()