Allow the formal engine to perform a same-cycle result in the ALU
[soc.git] / src / soc / simple / core.py
1 """simple core
2
3 not in any way intended for production use. connects up FunctionUnits to
4 Register Files in a brain-dead fashion that only permits one and only one
5 Function Unit to be operational.
6
7 the principle here is to take the Function Units, analyse their regspecs,
8 and turn their requirements for access to register file read/write ports
9 into groupings by Register File and Register File Port name.
10
11 under each grouping - by regfile/port - a list of Function Units that
12 need to connect to that port is created. as these are a contended
13 resource a "Broadcast Bus" per read/write port is then also created,
14 with access to it managed by a PriorityPicker.
15
16 the brain-dead part of this module is that even though there is no
17 conflict of access, regfile read/write hazards are *not* analysed,
18 and consequently it is safer to wait for the Function Unit to complete
19 before allowing a new instruction to proceed.
20 (update: actually this is being added now:
21 https://bugs.libre-soc.org/show_bug.cgi?id=737)
22 """
23
24 from nmigen import (Elaboratable, Module, Signal, ResetSignal, Cat, Mux,
25 Const)
26 from nmigen.cli import rtlil
27
28 from openpower.decoder.power_decoder2 import PowerDecodeSubset
29 from openpower.decoder.power_regspec_map import regspec_decode
30 from openpower.sv.svp64 import SVP64Rec
31
32 from nmutil.picker import PriorityPicker
33 from nmutil.util import treereduce
34 from nmutil.singlepipe import ControlBase
35
36 from soc.fu.compunits.compunits import AllFunctionUnits, LDSTFunctionUnit
37 from soc.regfile.regfiles import RegFiles
38 from openpower.decoder.power_decoder2 import get_rdflags
39 from soc.experiment.l0_cache import TstL0CacheBuffer # test only
40 from soc.config.test.test_loadstore import TestMemPspec
41 from openpower.decoder.power_enums import MicrOp, Function
42 from soc.simple.core_data import CoreInput, CoreOutput
43
44 from collections import defaultdict, namedtuple
45 import operator
46
47 from nmutil.util import rising_edge
48
49 FUSpec = namedtuple("FUSpec", ["funame", "fu", "idx"])
50 ByRegSpec = namedtuple("ByRegSpec", ["okflag", "regport", "wid", "specs"])
51
52 # helper function for reducing a list of signals down to a parallel
53 # ORed single signal.
54 def ortreereduce(tree, attr="o_data"):
55 return treereduce(tree, operator.or_, lambda x: getattr(x, attr))
56
57
58 def ortreereduce_sig(tree):
59 return treereduce(tree, operator.or_, lambda x: x)
60
61
62 # helper function to place full regs declarations first
63 def sort_fuspecs(fuspecs):
64 res = []
65 for (regname, fspec) in fuspecs.items():
66 if regname.startswith("full"):
67 res.append((regname, fspec))
68 for (regname, fspec) in fuspecs.items():
69 if not regname.startswith("full"):
70 res.append((regname, fspec))
71 return res # enumerate(res)
72
73
74 # a hazard bitvector "remap" function which returns an AST expression
75 # that remaps read/write hazard regfile port numbers to either a full
76 # bitvector or a reduced subset one. SPR for example is reduced to a
77 # single bit.
78 # CRITICALLY-IMPORTANT NOTE: these bitvectors *have* to match up per
79 # regfile! therefore the remapping is per regfile, *NOT* per regfile
80 # port and certainly not based on whether it is a read port or write port.
81 # note that any reductions here will result in degraded performance due
82 # to conflicts, but at least it keeps the hazard matrix sizes down to "sane"
83 def bitvector_remap(regfile, rfile, port):
84 # 8-bits (at the moment, no SVP64), CR is unary: no remap
85 if regfile == 'CR':
86 return port
87 # 3 bits, unary alrady: return the port
88 if regfile == 'XER':
89 return port
90 # 3 bits, unary: return the port
91 if regfile == 'XER':
92 return port
93 # 5 bits, unary: return the port
94 if regfile == 'STATE':
95 return port
96 # 9 bits (9 entries), might be unary already
97 if regfile == 'FAST':
98 if rfile.unary: # FAST might be unary already
99 return port
100 else:
101 return 1 << port
102 # 10 bits (!!) - reduce to one
103 if regfile == 'SPR':
104 if rfile.unary: # FAST might be unary already
105 return port
106 else:
107 return 1 << port
108 if regfile == 'INT':
109 if rfile.unary: # INT, check if unary/binary
110 return port
111 else:
112 return 1 << port
113
114
115 # derive from ControlBase rather than have a separate Stage instance,
116 # this is simpler to do
117 class NonProductionCore(ControlBase):
118 def __init__(self, pspec):
119 self.pspec = pspec
120
121 # test is SVP64 is to be enabled
122 self.svp64_en = hasattr(pspec, "svp64") and (pspec.svp64 == True)
123
124 # test to see if regfile ports should be reduced
125 self.regreduce_en = (hasattr(pspec, "regreduce") and
126 (pspec.regreduce == True))
127
128 # test to see if overlapping of instructions is allowed
129 # (not normally enabled for TestIssuer FSM but useful for checking
130 # the bitvector hazard detection, before doing In-Order)
131 self.allow_overlap = (hasattr(pspec, "allow_overlap") and
132 (pspec.allow_overlap == True))
133
134 # test core type
135 self.make_hazard_vecs = self.allow_overlap
136 self.core_type = "fsm"
137 if hasattr(pspec, "core_type"):
138 self.core_type = pspec.core_type
139
140 super().__init__(stage=self)
141
142 # single LD/ST funnel for memory access
143 self.l0 = l0 = TstL0CacheBuffer(pspec, n_units=1)
144 pi = l0.l0.dports[0]
145
146 # function units (only one each)
147 # only include mmu if enabled in pspec
148 self.fus = AllFunctionUnits(pspec, pilist=[pi])
149
150 # link LoadStore1 into MMU
151 mmu = self.fus.get_fu('mmu0')
152 ldst0 = self.fus.get_fu('ldst0')
153 print ("core pspec", pspec.ldst_ifacetype)
154 print ("core mmu", mmu)
155 if mmu is not None:
156 lsi = l0.cmpi.lsmem.lsi # a LoadStore1 Interface object
157 print ("core lsmem.lsi", lsi)
158 mmu.alu.set_ldst_interface(lsi)
159 # urr store I-Cache in core so it is easier to get at
160 self.icache = lsi.icache
161
162 # alternative reset values for STATE regs
163 self.msr_at_reset = 0x0
164 if hasattr(pspec, "msr_reset") and isinstance(pspec.msr_reset, int):
165 self.msr_at_reset = pspec.msr_reset
166 state_resets = [0x0, # PC at reset
167 self.msr_at_reset, # MSR at reset
168 0x0, # SVSTATE at reset
169 0x0, # DEC at reset
170 0x0] # TB at reset
171
172 # register files (yes plural)
173 self.regs = RegFiles(pspec, make_hazard_vecs=self.make_hazard_vecs,
174 state_resets=state_resets)
175
176 # set up input and output: unusual requirement to set data directly
177 # (due to the way that the core is set up in a different domain,
178 # see TestIssuer.setup_peripherals
179 self.p.i_data, self.n.o_data = self.new_specs(None)
180 self.i, self.o = self.p.i_data, self.n.o_data
181
182 # actual internal input data used (captured)
183 self.ireg = self.ispec()
184
185 # create per-FU instruction decoders (subsetted). these "satellite"
186 # decoders reduce wire fan-out from the one (main) PowerDecoder2
187 # (used directly by the trap unit) to the *twelve* (or more)
188 # Function Units. we can either have 32 wires (the instruction)
189 # to each, or we can have well over a 200 wire fan-out (to 12
190 # ALUs). it's an easy choice to make.
191 self.decoders = {}
192 self.des = {}
193
194 # eep, these should be *per FU* i.e. for FunctionUnitBaseMulti
195 # they should be shared (put into the ALU *once*).
196
197 for funame, fu in self.fus.fus.items():
198 f_name = fu.fnunit.name
199 fnunit = fu.fnunit.value
200 opkls = fu.opsubsetkls
201 if f_name == 'TRAP':
202 # TRAP decoder is the *main* decoder
203 self.trapunit = funame
204 continue
205 assert funame not in self.decoders
206 self.decoders[funame] = PowerDecodeSubset(None, opkls, f_name,
207 final=True,
208 state=self.ireg.state,
209 svp64_en=self.svp64_en,
210 regreduce_en=self.regreduce_en)
211 self.des[funame] = self.decoders[funame].do
212 print ("create decoder subset", funame, opkls, self.des[funame])
213
214 # create per-Function Unit write-after-write hazard signals
215 # yes, really, this should have been added in ReservationStations
216 # but hey.
217 for funame, fu in self.fus.fus.items():
218 fu._waw_hazard = Signal(name="waw_%s" % funame)
219
220 # share the SPR decoder with the MMU if it exists
221 if "mmu0" in self.decoders:
222 self.decoders["mmu0"].mmu0_spr_dec = self.decoders["spr0"]
223
224 # allow pausing of the DEC/TB FSM back in Issuer, by spotting
225 # if there is an MTSPR instruction
226 self.pause_dec_tb = Signal()
227
228 # next 3 functions are Stage API Compliance
229 def setup(self, m, i):
230 pass
231
232 def ispec(self):
233 return CoreInput(self.pspec, self.svp64_en, self.regreduce_en)
234
235 def ospec(self):
236 return CoreOutput()
237
238 # elaborate function to create HDL
239 def elaborate(self, platform):
240 m = super().elaborate(platform)
241
242 # for testing purposes, to cut down on build time in coriolis2
243 if hasattr(self.pspec, "nocore") and self.pspec.nocore == True:
244 x = Signal() # dummy signal
245 m.d.sync += x.eq(~x)
246 return m
247 comb = m.d.comb
248
249 m.submodules.fus = self.fus
250 m.submodules.l0 = l0 = self.l0
251 self.regs.elaborate_into(m, platform)
252 regs = self.regs
253 fus = self.fus.fus
254
255 # amalgamate write-hazards into a single top-level Signal
256 self.waw_hazard = Signal()
257 whaz = []
258 for funame, fu in self.fus.fus.items():
259 whaz.append(fu._waw_hazard)
260 comb += self.waw_hazard.eq(Cat(*whaz).bool())
261
262 # connect decoders
263 self.connect_satellite_decoders(m)
264
265 # ssh, cheat: trap uses the main decoder because of the rewriting
266 self.des[self.trapunit] = self.ireg.e.do
267
268 # connect up Function Units, then read/write ports, and hazard conflict
269 self.issue_conflict = Signal()
270 fu_bitdict, fu_selected = self.connect_instruction(m)
271 raw_hazard = self.connect_rdports(m, fu_bitdict, fu_selected)
272 self.connect_wrports(m, fu_bitdict, fu_selected)
273 if self.allow_overlap:
274 comb += self.issue_conflict.eq(raw_hazard)
275
276 # note if an exception happened. in a pipelined or OoO design
277 # this needs to be accompanied by "shadowing" (or stalling)
278 el = []
279 for exc in self.fus.excs.values():
280 el.append(exc.happened)
281 if len(el) > 0: # at least one exception
282 comb += self.o.exc_happened.eq(Cat(*el).bool())
283
284 return m
285
286 def connect_satellite_decoders(self, m):
287 comb = m.d.comb
288 for k, v in self.decoders.items():
289 # connect each satellite decoder and give it the instruction.
290 # as subset decoders this massively reduces wire fanout given
291 # the large number of ALUs
292 m.submodules["dec_%s" % k] = v
293 comb += v.dec.raw_opcode_in.eq(self.ireg.raw_insn_i)
294 comb += v.dec.bigendian.eq(self.ireg.bigendian_i)
295 # sigh due to SVP64 RA_OR_ZERO detection connect these too
296 comb += v.sv_a_nz.eq(self.ireg.sv_a_nz)
297 if not self.svp64_en:
298 continue
299 comb += v.pred_sm.eq(self.ireg.sv_pred_sm)
300 comb += v.pred_dm.eq(self.ireg.sv_pred_dm)
301 if k == self.trapunit:
302 continue
303 comb += v.sv_rm.eq(self.ireg.sv_rm) # pass through SVP64 RM
304 comb += v.is_svp64_mode.eq(self.ireg.is_svp64_mode)
305 # only the LDST PowerDecodeSubset *actually* needs to
306 # know to use the alternative decoder. this is all
307 # a terrible hack
308 if not k.lower().startswith("ldst"):
309 continue
310 comb += v.use_svp64_ldst_dec.eq( self.ireg.use_svp64_ldst_dec)
311
312 def connect_instruction(self, m):
313 """connect_instruction
314
315 uses decoded (from PowerOp) function unit information from CSV files
316 to ascertain which Function Unit should deal with the current
317 instruction.
318
319 some (such as OP_ATTN, OP_NOP) are dealt with here, including
320 ignoring it and halting the processor. OP_NOP is a bit annoying
321 because the issuer expects busy flag still to be raised then lowered.
322 (this requires a fake counter to be set).
323 """
324 comb, sync = m.d.comb, m.d.sync
325 fus = self.fus.fus
326
327 # indicate if core is busy
328 busy_o = self.o.busy_o
329 any_busy_o = self.o.any_busy_o
330
331 # connect up temporary copy of incoming instruction. the FSM will
332 # either blat the incoming instruction (if valid) into self.ireg
333 # or if the instruction could not be delivered, keep dropping the
334 # latched copy into ireg
335 ilatch = self.ispec()
336 self.instr_active = Signal()
337
338 # enable/busy-signals for each FU, get one bit for each FU (by name)
339 fu_enable = Signal(len(fus), reset_less=True)
340 fu_busy = Signal(len(fus), reset_less=True)
341 fu_bitdict = {}
342 fu_selected = {}
343 for i, funame in enumerate(fus.keys()):
344 fu_bitdict[funame] = fu_enable[i]
345 fu_selected[funame] = fu_busy[i]
346
347 # identify function units and create a list by fnunit so that
348 # PriorityPickers can be created for selecting one of them that
349 # isn't busy at the time the incoming instruction needs passing on
350 by_fnunit = defaultdict(list)
351 for fname, member in Function.__members__.items():
352 for funame, fu in fus.items():
353 fnunit = fu.fnunit.value
354 if member.value & fnunit: # this FU handles this type of op
355 by_fnunit[fname].append((funame, fu)) # add by Function
356
357 # ok now just print out the list of FUs by Function, because we can
358 for fname, fu_list in by_fnunit.items():
359 print ("FUs by type", fname, fu_list)
360
361 # now create a PriorityPicker per FU-type such that only one
362 # non-busy FU will be picked
363 issue_pps = {}
364 fu_found = Signal() # take a note if no Function Unit was available
365 for fname, fu_list in by_fnunit.items():
366 i_pp = PriorityPicker(len(fu_list))
367 m.submodules['i_pp_%s' % fname] = i_pp
368 i_l = []
369 for i, (funame, fu) in enumerate(fu_list):
370 # match the decoded instruction (e.do.fn_unit) against the
371 # "capability" of this FU, gate that by whether that FU is
372 # busy, and drop that into the PriorityPicker.
373 # this will give us an output of the first available *non-busy*
374 # Function Unit (Reservation Statio) capable of handling this
375 # instruction.
376 fnunit = fu.fnunit.value
377 en_req = Signal(name="issue_en_%s" % funame, reset_less=True)
378 fnmatch = (self.ireg.e.do.fn_unit & fnunit).bool()
379 comb += en_req.eq(fnmatch & ~fu.busy_o &
380 self.instr_active)
381 i_l.append(en_req) # store in list for doing the Cat-trick
382 # picker output, gated by enable: store in fu_bitdict
383 po = Signal(name="o_issue_pick_"+funame) # picker output
384 comb += po.eq(i_pp.o[i] & i_pp.en_o)
385 comb += fu_bitdict[funame].eq(po)
386 comb += fu_selected[funame].eq(fu.busy_o | po)
387 # if we don't do this, then when there are no FUs available,
388 # the "p.o_ready" signal will go back "ok we accepted this
389 # instruction" which of course isn't true.
390 with m.If(i_pp.en_o):
391 comb += fu_found.eq(1)
392 # for each input, Cat them together and drop them into the picker
393 comb += i_pp.i.eq(Cat(*i_l))
394
395 # rdmask, which is for registers needs to come from the *main* decoder
396 for funame, fu in fus.items():
397 rdmask = get_rdflags(m, self.ireg.e, fu)
398 comb += fu.rdmaskn.eq(~rdmask)
399
400 # sigh - need a NOP counter
401 counter = Signal(2)
402 with m.If(counter != 0):
403 sync += counter.eq(counter - 1)
404 comb += busy_o.eq(1)
405
406 # default to reading from incoming instruction: may be overridden
407 # by copy from latch when "waiting"
408 comb += self.ireg.eq(self.i)
409 # always say "ready" except if overridden
410 comb += self.p.o_ready.eq(1)
411
412 with m.FSM():
413 with m.State("READY"):
414 with m.If(self.p.i_valid): # run only when valid
415 with m.Switch(self.ireg.e.do.insn_type):
416 # check for ATTN: halt if true
417 with m.Case(MicrOp.OP_ATTN):
418 m.d.sync += self.o.core_terminate_o.eq(1)
419
420 # fake NOP - this isn't really used (Issuer detects NOP)
421 with m.Case(MicrOp.OP_NOP):
422 sync += counter.eq(2)
423 comb += busy_o.eq(1)
424
425 with m.Default():
426 comb += self.instr_active.eq(1)
427 comb += self.p.o_ready.eq(0)
428 # connect instructions. only one enabled at a time
429 for funame, fu in fus.items():
430 do = self.des[funame]
431 enable = fu_bitdict[funame]
432
433 # run this FunctionUnit if enabled route op,
434 # issue, busy, read flags and mask to FU
435 with m.If(enable):
436 # operand comes from the *local* decoder
437 # do not actually issue, though, if there
438 # is a waw hazard. decoder has to still
439 # be asserted in order to detect that, tho
440 comb += fu.oper_i.eq_from(do)
441 if funame == 'mmu0':
442 # URRR this is truly dreadful.
443 # OP_FETCH_FAILED is a "fake" op.
444 # no instruction creates it. OP_TRAP
445 # uses the *main* decoder: this is
446 # a *Satellite* decoder that reacts
447 # on *insn_in*... not fake ops. gaah.
448 main_op = self.ireg.e.do
449 with m.If(main_op.insn_type ==
450 MicrOp.OP_FETCH_FAILED):
451 comb += fu.oper_i.insn_type.eq(
452 MicrOp.OP_FETCH_FAILED)
453 comb += fu.oper_i.fn_unit.eq(
454 Function.MMU)
455 # issue when valid (and no write-hazard)
456 comb += fu.issue_i.eq(~self.waw_hazard)
457 # instruction ok, indicate ready
458 comb += self.p.o_ready.eq(1)
459
460 if self.allow_overlap:
461 with m.If(~fu_found | self.waw_hazard):
462 # latch copy of instruction
463 sync += ilatch.eq(self.i)
464 comb += self.p.o_ready.eq(1) # accept
465 comb += busy_o.eq(1)
466 m.next = "WAITING"
467
468 with m.State("WAITING"):
469 comb += self.instr_active.eq(1)
470 comb += self.p.o_ready.eq(0)
471 comb += busy_o.eq(1)
472 # using copy of instruction, keep waiting until an FU is free
473 comb += self.ireg.eq(ilatch)
474 with m.If(fu_found): # wait for conflict to clear
475 # connect instructions. only one enabled at a time
476 for funame, fu in fus.items():
477 do = self.des[funame]
478 enable = fu_bitdict[funame]
479
480 # run this FunctionUnit if enabled route op,
481 # issue, busy, read flags and mask to FU
482 with m.If(enable):
483 # operand comes from the *local* decoder,
484 # which is asserted even if not issued,
485 # so that WaW-detection can check for hazards.
486 # only if the waw hazard is clear does the
487 # instruction actually get issued
488 comb += fu.oper_i.eq_from(do)
489 # issue when valid
490 comb += fu.issue_i.eq(~self.waw_hazard)
491 with m.If(~self.waw_hazard):
492 comb += self.p.o_ready.eq(1)
493 comb += busy_o.eq(0)
494 m.next = "READY"
495
496 print ("core: overlap allowed", self.allow_overlap)
497 # true when any FU is busy (including the cycle where it is perhaps
498 # to be issued - because that's what fu_busy is)
499 comb += any_busy_o.eq(fu_busy.bool())
500 if not self.allow_overlap:
501 # for simple non-overlap, if any instruction is busy, set
502 # busy output for core.
503 comb += busy_o.eq(any_busy_o)
504 else:
505 # sigh deal with a fun situation that needs to be investigated
506 # and resolved
507 with m.If(self.issue_conflict):
508 comb += busy_o.eq(1)
509 # make sure that LDST, SPR, MMU, Branch and Trap all say "busy"
510 # and do not allow overlap. these are all the ones that
511 # are non-forward-progressing: exceptions etc. that otherwise
512 # change CoreState for some reason (MSR, PC, SVSTATE)
513 for funame, fu in fus.items():
514 if (funame.lower().startswith('ldst') or
515 funame.lower().startswith('branch') or
516 funame.lower().startswith('mmu') or
517 funame.lower().startswith('spr') or
518 funame.lower().startswith('trap')):
519 with m.If(fu.busy_o):
520 comb += busy_o.eq(1)
521 # for SPR pipeline pause dec/tb FSM to avoid race condition
522 # TODO: really this should be much more sophisticated,
523 # spot MTSPR, spot that DEC/TB is what is to be updated.
524 # a job for PowerDecoder2, there
525 if funame.lower().startswith('spr'):
526 with m.If(fu.busy_o #& fu.oper_i.insn_type == OP_MTSPR
527 ):
528 comb += self.pause_dec_tb.eq(1)
529
530 # return both the function unit "enable" dict as well as the "busy".
531 # the "busy-or-issued" can be passed in to the Read/Write port
532 # connecters to give them permission to request access to regfiles
533 return fu_bitdict, fu_selected
534
535 def connect_rdport(self, m, fu_bitdict, fu_selected,
536 rdpickers, regfile, regname, fspec):
537 comb, sync = m.d.comb, m.d.sync
538 fus = self.fus.fus
539 regs = self.regs
540
541 rpidx = regname
542
543 # select the required read port. these are pre-defined sizes
544 rfile = regs.rf[regfile.lower()]
545 rport = rfile.r_ports[rpidx]
546 print("read regfile", rpidx, regfile, regs.rf.keys(),
547 rfile, rfile.unary)
548
549 # for checking if the read port has an outstanding write
550 if self.make_hazard_vecs:
551 wv = regs.wv[regfile.lower()]
552 wvchk = wv.q_int # write-vec bit-level hazard check
553
554 # if a hazard is detected on this read port, simply blithely block
555 # every FU from reading on it. this is complete overkill but very
556 # simple for now.
557 hazard_detected = Signal(name="raw_%s_%s" % (regfile, rpidx))
558
559 fspecs = fspec
560 if not isinstance(fspecs, list):
561 fspecs = [fspecs]
562
563 rdflags = []
564 pplen = 0
565 ppoffs = []
566 for i, fspec in enumerate(fspecs):
567 # get the regfile specs for this regfile port
568 print ("fpsec", i, fspec, len(fspec.specs))
569 name = "%s_%s_%d" % (regfile, regname, i)
570 ppoffs.append(pplen) # record offset for picker
571 pplen += len(fspec.specs)
572 rdflag = Signal(name="rdflag_"+name, reset_less=True)
573 comb += rdflag.eq(fspec.okflag)
574 rdflags.append(rdflag)
575
576 print ("pplen", pplen)
577
578 # create a priority picker to manage this port
579 rdpickers[regfile][rpidx] = rdpick = PriorityPicker(pplen)
580 m.submodules["rdpick_%s_%s" % (regfile, rpidx)] = rdpick
581
582 rens = []
583 addrs = []
584 wvens = []
585
586 for i, fspec in enumerate(fspecs):
587 (rf, _read, wid, fuspecs) = \
588 (fspec.okflag, fspec.regport, fspec.wid, fspec.specs)
589 # connect up the FU req/go signals, and the reg-read to the FU
590 # and create a Read Broadcast Bus
591 for pi, fuspec in enumerate(fspec.specs):
592 (funame, fu, idx) = (fuspec.funame, fuspec.fu, fuspec.idx)
593 pi += ppoffs[i]
594 name = "%s_%s_%s_%i" % (regfile, rpidx, funame, pi)
595 fu_active = fu_selected[funame]
596 fu_issued = fu_bitdict[funame]
597
598 # get (or set up) a latched copy of read register number
599 # and (sigh) also the read-ok flag
600 # TODO: use nmutil latchregister
601 rhname = "%s_%s_%d" % (regfile, regname, i)
602 rdflag = Signal(name="rdflag_%s_%s" % (funame, rhname),
603 reset_less=True)
604 if rhname not in fu.rf_latches:
605 rfl = Signal(name="rdflag_latch_%s_%s" % (funame, rhname))
606 fu.rf_latches[rhname] = rfl
607 with m.If(fu.issue_i):
608 sync += rfl.eq(rdflags[i])
609 else:
610 rfl = fu.rf_latches[rhname]
611
612 # now the register port
613 rname = "%s_%s_%s_%d" % (funame, regfile, regname, pi)
614 read = Signal.like(_read, name="read_"+rname)
615 if rname not in fu.rd_latches:
616 rdl = Signal.like(_read, name="rdlatch_"+rname)
617 fu.rd_latches[rname] = rdl
618 with m.If(fu.issue_i):
619 sync += rdl.eq(_read)
620 else:
621 rdl = fu.rd_latches[rname]
622
623 # make the read immediately available on issue cycle
624 # after the read cycle, otherwies use the latched copy.
625 # this captures the regport and okflag on issue
626 with m.If(fu.issue_i):
627 comb += read.eq(_read)
628 comb += rdflag.eq(rdflags[i])
629 with m.Else():
630 comb += read.eq(rdl)
631 comb += rdflag.eq(rfl)
632
633 # connect request-read to picker input, and output to go-rd
634 addr_en = Signal.like(read, name="addr_en_"+name)
635 pick = Signal(name="pick_"+name) # picker input
636 rp = Signal(name="rp_"+name) # picker output
637 delay_pick = Signal(name="dp_"+name) # read-enable "underway"
638 rhazard = Signal(name="rhaz_"+name)
639
640 # exclude any currently-enabled read-request (mask out active)
641 # entirely block anything hazarded from being picked
642 comb += pick.eq(fu.rd_rel_o[idx] & fu_active & rdflag &
643 ~delay_pick & ~rhazard)
644 comb += rdpick.i[pi].eq(pick)
645 comb += fu.go_rd_i[idx].eq(delay_pick) # pass in *delayed* pick
646
647 # if picked, select read-port "reg select" number to port
648 comb += rp.eq(rdpick.o[pi] & rdpick.en_o)
649 sync += delay_pick.eq(rp) # delayed "pick"
650 comb += addr_en.eq(Mux(rp, read, 0))
651
652 # the read-enable happens combinatorially (see mux-bus below)
653 # but it results in the data coming out on a one-cycle delay.
654 if rfile.unary:
655 rens.append(addr_en)
656 else:
657 addrs.append(addr_en)
658 rens.append(rp)
659
660 # use the *delayed* pick signal to put requested data onto bus
661 with m.If(delay_pick):
662 # connect regfile port to input, creating fan-out Bus
663 src = fu.src_i[idx]
664 print("reg connect widths",
665 regfile, regname, pi, funame,
666 src.shape(), rport.o_data.shape())
667 # all FUs connect to same port
668 comb += src.eq(rport.o_data)
669
670 if not self.make_hazard_vecs:
671 continue
672
673 # read the write-hazard bitvector (wv) for any bit that is
674 wvchk_en = Signal(len(wvchk), name="wv_chk_addr_en_"+name)
675 issue_active = Signal(name="rd_iactive_"+name)
676 # XXX combinatorial loop here
677 comb += issue_active.eq(fu_active & rdflag)
678 with m.If(issue_active):
679 if rfile.unary:
680 comb += wvchk_en.eq(read)
681 else:
682 comb += wvchk_en.eq(1<<read)
683 # if FU is busy (which doesn't get set at the same time as
684 # issue) and no hazard was detected, clear wvchk_en (i.e.
685 # stop checking for hazards). there is a loop here, but it's
686 # via a DFF, so is ok. some linters may complain, but hey.
687 with m.If(fu.busy_o & ~rhazard):
688 comb += wvchk_en.eq(0)
689
690 # read-hazard is ANDed with (filtered by) what is actually
691 # being requested.
692 comb += rhazard.eq((wvchk & wvchk_en).bool())
693
694 wvens.append(wvchk_en)
695
696 # or-reduce the muxed read signals
697 if rfile.unary:
698 # for unary-addressed
699 comb += rport.ren.eq(ortreereduce_sig(rens))
700 else:
701 # for binary-addressed
702 comb += rport.addr.eq(ortreereduce_sig(addrs))
703 comb += rport.ren.eq(Cat(*rens).bool())
704 print ("binary", regfile, rpidx, rport, rport.ren, rens, addrs)
705
706 if not self.make_hazard_vecs:
707 return Const(0) # declare "no hazards"
708
709 # enable the read bitvectors for this issued instruction
710 # and return whether any write-hazard bit is set
711 wvchk_and = Signal(len(wvchk), name="wv_chk_"+name)
712 comb += wvchk_and.eq(wvchk & ortreereduce_sig(wvens))
713 comb += hazard_detected.eq(wvchk_and.bool())
714 return hazard_detected
715
716 def connect_rdports(self, m, fu_bitdict, fu_selected):
717 """connect read ports
718
719 orders the read regspecs into a dict-of-dicts, by regfile, by
720 regport name, then connects all FUs that want that regport by
721 way of a PriorityPicker.
722 """
723 comb, sync = m.d.comb, m.d.sync
724 fus = self.fus.fus
725 regs = self.regs
726 rd_hazard = []
727
728 # dictionary of lists of regfile read ports
729 byregfiles_rdspec = self.get_byregfiles(m, True)
730
731 # okaay, now we need a PriorityPicker per regfile per regfile port
732 # loootta pickers... peter piper picked a pack of pickled peppers...
733 rdpickers = {}
734 for regfile, fuspecs in byregfiles_rdspec.items():
735 rdpickers[regfile] = {}
736
737 # argh. an experiment to merge RA and RB in the INT regfile
738 # (we have too many read/write ports)
739 if self.regreduce_en:
740 if regfile == 'INT':
741 fuspecs['rabc'] = [fuspecs.pop('rb')]
742 fuspecs['rabc'].append(fuspecs.pop('rc'))
743 fuspecs['rabc'].append(fuspecs.pop('ra'))
744 if regfile == 'FAST':
745 fuspecs['fast1'] = [fuspecs.pop('fast1')]
746 if 'fast2' in fuspecs:
747 fuspecs['fast1'].append(fuspecs.pop('fast2'))
748 if 'fast3' in fuspecs:
749 fuspecs['fast1'].append(fuspecs.pop('fast3'))
750
751 # for each named regfile port, connect up all FUs to that port
752 # also return (and collate) hazard detection)
753 for (regname, fspec) in sort_fuspecs(fuspecs):
754 print("connect rd", regname, fspec)
755 rh = self.connect_rdport(m, fu_bitdict, fu_selected,
756 rdpickers, regfile,
757 regname, fspec)
758 rd_hazard.append(rh)
759
760 return Cat(*rd_hazard).bool()
761
762 def make_hazards(self, m, regfile, rfile, wvclr, wvset,
763 funame, regname, idx,
764 addr_en, wp, fu, fu_active, wrflag, write,
765 fu_wrok):
766 """make_hazards: a setter and a clearer for the regfile write ports
767
768 setter is at issue time (using PowerDecoder2 regfile write numbers)
769 clearer is at regfile write time (when FU has said what to write to)
770
771 there is *one* unusual case here which has to be dealt with:
772 when the Function Unit does *NOT* request a write to the regfile
773 (has its data.ok bit CLEARED). this is perfectly legitimate.
774 and a royal pain.
775 """
776 comb, sync = m.d.comb, m.d.sync
777 name = "%s_%s_%d" % (funame, regname, idx)
778
779 # connect up the bitvector write hazard. unlike the
780 # regfile writeports, a ONE must be written to the corresponding
781 # bit of the hazard bitvector (to indicate the existence of
782 # the hazard)
783
784 # the detection of what shall be written to is based
785 # on *issue*. it is delayed by 1 cycle so that instructions
786 # "addi 5,5,0x2" do not cause combinatorial loops due to
787 # fake-dependency on *themselves*. this will totally fail
788 # spectacularly when doing multi-issue
789 print ("write vector (for regread)", regfile, wvset)
790 wviaddr_en = Signal(len(wvset), name="wv_issue_addr_en_"+name)
791 issue_active = Signal(name="iactive_"+name)
792 sync += issue_active.eq(fu.issue_i & fu_active & wrflag)
793 with m.If(issue_active):
794 if rfile.unary:
795 comb += wviaddr_en.eq(write)
796 else:
797 comb += wviaddr_en.eq(1<<write)
798
799 # deal with write vector clear: this kicks in when the regfile
800 # is written to, and clears the corresponding bitvector entry
801 print ("write vector", regfile, wvclr)
802 wvaddr_en = Signal(len(wvclr), name="wvaddr_en_"+name)
803 if rfile.unary:
804 comb += wvaddr_en.eq(addr_en)
805 else:
806 with m.If(wp):
807 comb += wvaddr_en.eq(1<<addr_en)
808
809 # XXX ASSUME that LDSTFunctionUnit always sets the data it intends to
810 # this may NOT be the case when an exception occurs
811 if isinstance(fu, LDSTFunctionUnit):
812 return wvaddr_en, wviaddr_en
813
814 # okaaay, this is preparation for the awkward case.
815 # * latch a copy of wrflag when issue goes high.
816 # * when the fu_wrok (data.ok) flag is NOT set,
817 # but the FU is done, the FU is NEVER going to write
818 # so the bitvector has to be cleared.
819 latch_wrflag = Signal(name="latch_wrflag_"+name)
820 with m.If(~fu.busy_o):
821 sync += latch_wrflag.eq(0)
822 with m.If(fu.issue_i & fu_active):
823 sync += latch_wrflag.eq(wrflag)
824 with m.If(fu.alu_done_o & latch_wrflag & ~fu_wrok):
825 if rfile.unary:
826 comb += wvaddr_en.eq(write) # addr_en gated with wp, don't use
827 else:
828 comb += wvaddr_en.eq(1<<addr_en) # binary addr_en not gated
829
830 return wvaddr_en, wviaddr_en
831
832 def connect_wrport(self, m, fu_bitdict, fu_selected,
833 wrpickers, regfile, regname, fspec):
834 comb, sync = m.d.comb, m.d.sync
835 fus = self.fus.fus
836 regs = self.regs
837
838 rpidx = regname
839
840 # select the required write port. these are pre-defined sizes
841 rfile = regs.rf[regfile.lower()]
842 wport = rfile.w_ports[rpidx]
843
844 print("connect wr", regname, "unary", rfile.unary, fspec)
845 print(regfile, regs.rf.keys())
846
847 # select the write-protection hazard vector. note that this still
848 # requires to WRITE to the hazard bitvector! read-requests need
849 # to RAISE the bitvector (set it to 1), which, duh, requires a WRITE
850 if self.make_hazard_vecs:
851 wv = regs.wv[regfile.lower()]
852 wvset = wv.s # write-vec bit-level hazard ctrl
853 wvclr = wv.r # write-vec bit-level hazard ctrl
854 wvchk = wv.q # write-after-write hazard check
855
856 fspecs = fspec
857 if not isinstance(fspecs, list):
858 fspecs = [fspecs]
859
860 pplen = 0
861 writes = []
862 ppoffs = []
863 wrflags = []
864 for i, fspec in enumerate(fspecs):
865 # get the regfile specs for this regfile port
866 (wf, _write, wid, fuspecs) = \
867 (fspec.okflag, fspec.regport, fspec.wid, fspec.specs)
868 print ("fpsec", i, "wrflag", wf, fspec, len(fuspecs))
869 ppoffs.append(pplen) # record offset for picker
870 pplen += len(fuspecs)
871
872 name = "%s_%s_%d" % (regfile, regname, i)
873 wrflag = Signal(name="wr_flag_"+name)
874 if wf is not None:
875 comb += wrflag.eq(wf)
876 else:
877 comb += wrflag.eq(0)
878 wrflags.append(wrflag)
879
880 # create a priority picker to manage this port
881 wrpickers[regfile][rpidx] = wrpick = PriorityPicker(pplen)
882 m.submodules["wrpick_%s_%s" % (regfile, rpidx)] = wrpick
883
884 wsigs = []
885 wens = []
886 wvsets = []
887 wvseten = []
888 wvclren = []
889 #wvens = [] - not needed: reading of writevec is permanently held hi
890 addrs = []
891 for i, fspec in enumerate(fspecs):
892 # connect up the FU req/go signals and the reg-read to the FU
893 # these are arbitrated by Data.ok signals
894 (wf, _write, wid, fuspecs) = \
895 (fspec.okflag, fspec.regport, fspec.wid, fspec.specs)
896 for pi, fuspec in enumerate(fspec.specs):
897 (funame, fu, idx) = (fuspec.funame, fuspec.fu, fuspec.idx)
898 fu_requested = fu_bitdict[funame]
899 pi += ppoffs[i]
900 name = "%s_%s_%s_%d" % (funame, regfile, regname, idx)
901 # get (or set up) a write-latched copy of write register number
902 write = Signal.like(_write, name="write_"+name)
903 rname = "%s_%s_%s_%d" % (funame, regfile, regname, idx)
904 if rname not in fu.wr_latches:
905 wrl = Signal.like(_write, name="wrlatch_"+rname)
906 fu.wr_latches[rname] = write
907 # do not depend on fu.issue_i here, it creates a
908 # combinatorial loop on waw checking. using the FU
909 # "enable" bitdict entry for this FU is sufficient,
910 # because the PowerDecoder2 read/write nums are
911 # valid continuously when the instruction is valid
912 with m.If(fu_requested):
913 sync += wrl.eq(_write)
914 comb += write.eq(_write)
915 with m.Else():
916 comb += write.eq(wrl)
917 else:
918 write = fu.wr_latches[rname]
919
920 # write-request comes from dest.ok
921 dest = fu.get_out(idx)
922 fu_dest_latch = fu.get_fu_out(idx) # latched output
923 name = "%s_%s_%d" % (funame, regname, idx)
924 fu_wrok = Signal(name="fu_wrok_"+name, reset_less=True)
925 comb += fu_wrok.eq(dest.ok & fu.busy_o)
926
927 # connect request-write to picker input, and output to go-wr
928 fu_active = fu_selected[funame]
929 pick = fu.wr.rel_o[idx] & fu_active
930 comb += wrpick.i[pi].eq(pick)
931 # create a single-pulse go write from the picker output
932 wr_pick = Signal(name="wpick_%s_%s_%d" % (funame, regname, idx))
933 comb += wr_pick.eq(wrpick.o[pi] & wrpick.en_o)
934 comb += fu.go_wr_i[idx].eq(rising_edge(m, wr_pick))
935
936 # connect the regspec write "reg select" number to this port
937 # only if one FU actually requests (and is granted) the port
938 # will the write-enable be activated
939 wname = "waddr_en_%s_%s_%d" % (funame, regname, idx)
940 addr_en = Signal.like(write, name=wname)
941 wp = Signal()
942 comb += wp.eq(wr_pick & wrpick.en_o)
943 comb += addr_en.eq(Mux(wp, write, 0))
944 if rfile.unary:
945 wens.append(addr_en)
946 else:
947 addrs.append(addr_en)
948 wens.append(wp)
949
950 # connect regfile port to input
951 print("reg connect widths",
952 regfile, regname, pi, funame,
953 dest.shape(), wport.i_data.shape())
954 wsigs.append(fu_dest_latch)
955
956 # now connect up the bitvector write hazard
957 if not self.make_hazard_vecs:
958 continue
959 res = self.make_hazards(m, regfile, rfile, wvclr, wvset,
960 funame, regname, idx,
961 addr_en, wp, fu, fu_active,
962 wrflags[i], write, fu_wrok)
963 wvaddr_en, wv_issue_en = res
964 wvclren.append(wvaddr_en) # set only: no data => clear bit
965 wvseten.append(wv_issue_en) # set data same as enable
966
967 # read the write-hazard bitvector (wv) for any bit that is
968 fu_requested = fu_bitdict[funame]
969 wvchk_en = Signal(len(wvchk), name="waw_chk_addr_en_"+name)
970 issue_active = Signal(name="waw_iactive_"+name)
971 whazard = Signal(name="whaz_"+name)
972 if wf is None:
973 # XXX EEK! STATE regfile (branch) does not have an
974 # write-active indicator in regspec_decode_write()
975 print ("XXX FIXME waw_iactive", issue_active,
976 fu_requested, wf)
977 else:
978 # check bits from the incoming instruction. note (back
979 # in connect_instruction) that the decoder is held for
980 # us to be able to do this, here... *without* issue being
981 # held HI. we MUST NOT gate this with fu.issue_i or
982 # with fu_bitdict "enable": it would create a loop
983 comb += issue_active.eq(wf)
984 with m.If(issue_active):
985 if rfile.unary:
986 comb += wvchk_en.eq(write)
987 else:
988 comb += wvchk_en.eq(1<<write)
989 # if FU is busy (which doesn't get set at the same time as
990 # issue) and no hazard was detected, clear wvchk_en (i.e.
991 # stop checking for hazards). there is a loop here, but it's
992 # via a DFF, so is ok. some linters may complain, but hey.
993 with m.If(fu.busy_o & ~whazard):
994 comb += wvchk_en.eq(0)
995
996 # write-hazard is ANDed with (filtered by) what is actually
997 # being requested. the wvchk data is on a one-clock delay,
998 # and wvchk_en comes directly from the main decoder
999 comb += whazard.eq((wvchk & wvchk_en).bool())
1000 with m.If(whazard):
1001 comb += fu._waw_hazard.eq(1)
1002
1003 #wvens.append(wvchk_en)
1004
1005 # here is where we create the Write Broadcast Bus. simple, eh?
1006 comb += wport.i_data.eq(ortreereduce_sig(wsigs))
1007 if rfile.unary:
1008 # for unary-addressed
1009 comb += wport.wen.eq(ortreereduce_sig(wens))
1010 else:
1011 # for binary-addressed
1012 comb += wport.addr.eq(ortreereduce_sig(addrs))
1013 comb += wport.wen.eq(ortreereduce_sig(wens))
1014
1015 if not self.make_hazard_vecs:
1016 return [], []
1017
1018 # return these here rather than set wvclr/wvset directly,
1019 # because there may be more than one write-port to a given
1020 # regfile. example: XER has a write-port for SO, CA, and OV
1021 # and the *last one added* of those would overwrite the other
1022 # two. solution: have connect_wrports collate all the
1023 # or-tree-reduced bitvector set/clear requests and drop them
1024 # in as a single "thing". this can only be done because the
1025 # set/get is an unary bitvector.
1026 print ("make write-vecs", regfile, regname, wvset, wvclr)
1027 return (wvclren, # clear (regfile write)
1028 wvseten) # set (issue time)
1029
1030 def connect_wrports(self, m, fu_bitdict, fu_selected):
1031 """connect write ports
1032
1033 orders the write regspecs into a dict-of-dicts, by regfile,
1034 by regport name, then connects all FUs that want that regport
1035 by way of a PriorityPicker.
1036
1037 note that the write-port wen, write-port data, and go_wr_i all need to
1038 be on the exact same clock cycle. as there is a combinatorial loop bug
1039 at the moment, these all use sync.
1040 """
1041 comb, sync = m.d.comb, m.d.sync
1042 fus = self.fus.fus
1043 regs = self.regs
1044 # dictionary of lists of regfile write ports
1045 byregfiles_wrspec = self.get_byregfiles(m, False)
1046
1047 # same for write ports.
1048 # BLECH! complex code-duplication! BLECH!
1049 wrpickers = {}
1050 wvclrers = defaultdict(list)
1051 wvseters = defaultdict(list)
1052 for regfile, fuspecs in byregfiles_wrspec.items():
1053 wrpickers[regfile] = {}
1054
1055 if self.regreduce_en:
1056 # argh, more port-merging
1057 if regfile == 'INT':
1058 fuspecs['o'] = [fuspecs.pop('o')]
1059 fuspecs['o'].append(fuspecs.pop('o1'))
1060 if regfile == 'FAST':
1061 fuspecs['fast1'] = [fuspecs.pop('fast1')]
1062 if 'fast2' in fuspecs:
1063 fuspecs['fast1'].append(fuspecs.pop('fast2'))
1064 if 'fast3' in fuspecs:
1065 fuspecs['fast1'].append(fuspecs.pop('fast3'))
1066
1067 # collate these and record them by regfile because there
1068 # are sometimes more write-ports per regfile
1069 for (regname, fspec) in sort_fuspecs(fuspecs):
1070 wvclren, wvseten = self.connect_wrport(m,
1071 fu_bitdict, fu_selected,
1072 wrpickers,
1073 regfile, regname, fspec)
1074 wvclrers[regfile.lower()] += wvclren
1075 wvseters[regfile.lower()] += wvseten
1076
1077 if not self.make_hazard_vecs:
1078 return
1079
1080 # for write-vectors: reduce the clr-ers and set-ers down to
1081 # a single set of bits. otherwise if there are two write
1082 # ports (on some regfiles), the last one doing comb += on
1083 # the reg.wv[regfile] instance "wins" (and all others are ignored,
1084 # whoops). if there was only one write-port per wv regfile this would
1085 # not be an issue.
1086 for regfile in wvclrers.keys():
1087 wv = regs.wv[regfile]
1088 wvset = wv.s # write-vec bit-level hazard ctrl
1089 wvclr = wv.r # write-vec bit-level hazard ctrl
1090 wvclren = wvclrers[regfile]
1091 wvseten = wvseters[regfile]
1092 comb += wvclr.eq(ortreereduce_sig(wvclren)) # clear (regfile write)
1093 comb += wvset.eq(ortreereduce_sig(wvseten)) # set (issue time)
1094
1095 def get_byregfiles(self, m, readmode):
1096
1097 mode = "read" if readmode else "write"
1098 regs = self.regs
1099 fus = self.fus.fus
1100 e = self.ireg.e # decoded instruction to execute
1101
1102 # dictionary of dictionaries of lists/tuples of regfile ports.
1103 # first key: regfile. second key: regfile port name
1104 byregfiles_spec = defaultdict(dict)
1105
1106 for (funame, fu) in fus.items():
1107 # create in each FU a receptacle for the read/write register
1108 # hazard numbers (and okflags for read). to be latched in
1109 # connect_rd/write_ports
1110 if readmode:
1111 fu.rd_latches = {} # read reg number latches
1112 fu.rf_latches = {} # read flag latches
1113 else:
1114 fu.wr_latches = {}
1115
1116 # construct regfile specs: read uses inspec, write outspec
1117 print("%s ports for %s" % (mode, funame))
1118 for idx in range(fu.n_src if readmode else fu.n_dst):
1119 (regfile, regname, wid) = fu.get_io_spec(readmode, idx)
1120 print(" %d %s %s %s" % (idx, regfile, regname, str(wid)))
1121
1122 # the PowerDecoder2 (main one, not the satellites) contains
1123 # the decoded regfile numbers. obtain these now
1124 decinfo = regspec_decode(m, readmode, e, regfile, regname)
1125 okflag, regport = decinfo.okflag, decinfo.regport
1126
1127 # construct the dictionary of regspec information by regfile
1128 if regname not in byregfiles_spec[regfile]:
1129 byregfiles_spec[regfile][regname] = \
1130 ByRegSpec(okflag, regport, wid, [])
1131
1132 # here we start to create "lanes" where each Function Unit
1133 # requiring access to a given [single-contended resource]
1134 # regfile port is appended to a list, so that PriorityPickers
1135 # can be created to give uncontested access to it
1136 fuspec = FUSpec(funame, fu, idx)
1137 byregfiles_spec[regfile][regname].specs.append(fuspec)
1138
1139 # ok just print that all out, for convenience
1140 for regfile, fuspecs in byregfiles_spec.items():
1141 print("regfile %s ports:" % mode, regfile)
1142 for regname, fspec in fuspecs.items():
1143 [okflag, regport, wid, fuspecs] = fspec
1144 print(" rf %s port %s lane: %s" % (mode, regfile, regname))
1145 print(" %s" % regname, wid, okflag, regport)
1146 for (funame, fu, idx) in fuspecs:
1147 fusig = fu.src_i[idx] if readmode else fu.dest[idx]
1148 print(" ", funame, fu.__class__.__name__, idx, fusig)
1149 print()
1150
1151 return byregfiles_spec
1152
1153 def __iter__(self):
1154 yield from self.fus.ports()
1155 yield from self.i.e.ports()
1156 yield from self.l0.ports()
1157 # TODO: regs
1158
1159 def ports(self):
1160 return list(self)
1161
1162
1163 if __name__ == '__main__':
1164 pspec = TestMemPspec(ldst_ifacetype='testpi',
1165 imem_ifacetype='',
1166 addr_wid=64,
1167 allow_overlap=True,
1168 mask_wid=8,
1169 reg_wid=64)
1170 dut = NonProductionCore(pspec)
1171 vl = rtlil.convert(dut, ports=dut.ports())
1172 with open("test_core.il", "w") as f:
1173 f.write(vl)