format code
[openpower-isa.git] / src / openpower / sv / sv_analysis.py
1 #!/usr/bin/env python2
2 #
3 # NOTE that this program is python2 compatible, please do not stop it
4 # from working by adding syntax that prevents that.
5 #
6 # Initial version written by lkcl Oct 2020
7 # This program analyses the Power 9 op codes and looks at in/out register uses
8 # The results are displayed:
9 # https://libre-soc.org/openpower/opcode_regs_deduped/
10 #
11 # It finds .csv files in the directory isatables/
12 # then goes through the categories and creates svp64 CSV augmentation
13 # tables on a per-opcode basis
14 #
15 # NOTE: this program is effectively part of the Simple-V Specification.
16 # it encapsulates the relationships of what can be SVP64-encoded and
17 # holds all of the information on how to encode and decode SVP64.
18 # By auto-generating tables that go into the Simple-V Specification
19 # this program *is* the specification. do not be confused just because
20 # it is in python: if you do not understand please ask questions and
21 # help create patches with explanatory comments.
22
23 import argparse
24 import csv
25 import enum
26 import os
27 from os.path import dirname, join
28 from glob import glob
29 from collections import defaultdict
30 from collections import OrderedDict
31 from openpower.decoder.power_svp64 import SVP64RM
32 from openpower.decoder.power_enums import find_wiki_file, get_csv
33 from openpower.util import log
34
35
36 # Ignore those containing: valid test sprs
37 def glob_valid_csvs(root):
38 def check_csv(fname):
39 _, name = os.path.split(fname)
40 if '-' in name:
41 return False
42 if 'valid' in fname:
43 return False
44 if 'test' in fname:
45 return False
46 if fname.endswith('insndb.csv'):
47 return False
48 if fname.endswith('sprs.csv'):
49 return False
50 if fname.endswith('minor_19_valid.csv'):
51 return False
52 if 'RM' in fname:
53 return False
54 return True
55
56 yield from filter(check_csv, glob(root))
57
58
59 # Write an array of dictionaries to the CSV file name:
60 def write_csv(name, items, headers):
61 file_path = find_wiki_file(name)
62 with open(file_path, 'w') as csvfile:
63 writer = csv.DictWriter(csvfile, headers, lineterminator="\n")
64 writer.writeheader()
65 writer.writerows(items)
66
67 # This will return True if all values are true.
68 # Not sure what this is about
69
70
71 def blank_key(row):
72 # for v in row.values():
73 # if 'SPR' in v: # skip all SPRs
74 # return True
75 for v in row.values():
76 if v:
77 return False
78 return True
79
80 # General purpose registers have names like: RA, RT, R1, ...
81 # Floating point registers names like: FRT, FRA, FR1, ..., FRTp, ...
82 # Return True if field is a register
83
84
85 def isreg(field):
86 return (field.startswith('R') or field.startswith('FR') or
87 field == 'SPR')
88
89
90 # These are the attributes of the instructions,
91 # register names
92 keycolumns = ['unit', 'in1', 'in2', 'in3', 'out', 'CR in', 'CR out',
93 ] # don't think we need these: 'ldst len', 'rc', 'lk']
94
95 tablecols = ['unit', 'in', 'outcnt', 'CR in', 'CR out', 'imm'
96 ] # don't think we need these: 'ldst len', 'rc', 'lk']
97
98
99 def create_key(row):
100 """ create an equivalent of a database key by which it is possible
101 to easily categorise an instruction. later this category is used
102 to decide what kind of EXTRA encoding is to be done because the
103 key contains the total number of input and output registers
104 """
105 res = OrderedDict()
106 #print ("row", row)
107 for key in keycolumns:
108 # registers IN - special-case: count number of regs RA/RB/RC/RS
109 if key in ['in1', 'in2', 'in3']:
110 if 'in' not in res:
111 res['in'] = 0
112 if row['unit'] == 'BRANCH': # branches must not include Vector SPRs
113 continue
114 if isreg(row[key]):
115 res['in'] += 1
116
117 # registers OUT
118 if key == 'out':
119 # If upd is 1 then increment the count of outputs
120 if 'outcnt' not in res:
121 res['outcnt'] = 0
122 if isreg(row[key]):
123 res['outcnt'] += 1
124 if row['upd'] == '1':
125 res['outcnt'] += 1
126
127 # CRs (Condition Register) (CR0 .. CR7)
128 if key.startswith('CR'):
129 if row[key].startswith('NONE'):
130 res[key] = '0'
131 else:
132 res[key] = '1'
133 if row['comment'].startswith('cr'):
134 res['crop'] = '1'
135 # unit
136 if key == 'unit':
137 if row[key] == 'LDST': # we care about LDST units
138 res[key] = row[key]
139 else:
140 res[key] = 'OTHER'
141 # LDST len (LoadStore length)
142 if key.startswith('ldst'):
143 if row[key].startswith('NONE'):
144 res[key] = '0'
145 else:
146 res[key] = '1'
147 # rc, lk
148 if key in ['rc', 'lk']:
149 if row[key] == 'ONE':
150 res[key] = '1'
151 elif row[key] == 'NONE':
152 res[key] = '0'
153 else:
154 res[key] = 'R'
155 if key == 'lk':
156 res[key] = row[key]
157
158 # Convert the numerics 'in' & 'outcnt' to strings
159 res['in'] = str(res['in'])
160 res['outcnt'] = str(res['outcnt'])
161
162 # constants
163 if row['in2'].startswith('CONST_'):
164 res['imm'] = "1" # row['in2'].split("_")[1]
165 else:
166 res['imm'] = ''
167
168 return res
169
170 #
171
172
173 def dformat(d):
174 res = []
175 for k, v in d.items():
176 res.append("%s: %s" % (k, v))
177 return ' '.join(res)
178
179
180 def tformat(d):
181 return "| " + ' | '.join(d) + " |"
182
183
184 def keyname(row):
185 """converts a key into a readable string. anything null or zero
186 is skipped, shortening the readable string
187 """
188 res = []
189 if row['unit'] != 'OTHER':
190 res.append(row['unit'])
191 if row['in'] != '0':
192 res.append('%sR' % row['in'])
193 if row['outcnt'] != '0':
194 res.append('%sW' % row['outcnt'])
195 if row['CR in'] == '1' and row['CR out'] == '1':
196 if 'crop' in row:
197 res.append("CR=2R1W")
198 else:
199 res.append("CRio")
200 elif row['CR in'] == '1':
201 res.append("CRi")
202 elif row['CR out'] == '1':
203 res.append("CRo")
204 elif 'imm' in row and row['imm']:
205 res.append("imm")
206 return '-'.join(res)
207
208
209 class Format(enum.Enum):
210 BINUTILS = enum.auto()
211 VHDL = enum.auto()
212
213 @classmethod
214 def _missing_(cls, value):
215 return {
216 "binutils": Format.BINUTILS,
217 "vhdl": Format.VHDL,
218 }[value.lower()]
219
220 def __str__(self):
221 return self.name.lower()
222
223 def declarations(self, values, lens):
224 def declaration_binutils(value, width):
225 yield f"/* TODO: implement binutils declaration (value={value!r}, width={width!r}) */"
226
227 def declaration_vhdl(value, width):
228 yield f" type sv_{value}_rom_array_t is " \
229 f"array(0 to {width}) of sv_decode_rom_t;"
230
231 for value in values:
232 if value not in lens:
233 todo = [f"TODO {value} (or no SVP64 augmentation)"]
234 todo = self.wrap_comment(todo)
235 yield from map(lambda line: f" {line}", todo)
236 else:
237 width = lens[value]
238 yield from {
239 Format.BINUTILS: declaration_binutils,
240 Format.VHDL: declaration_vhdl,
241 }[self](value, width)
242
243 def definitions(self, entries_svp64, fullcols):
244 def definitions_vhdl():
245 for (value, entries) in entries_svp64.items():
246 yield ""
247 yield f" constant sv_{value}_decode_rom_array :"
248 yield f" sv_{value}_rom_array_t := ("
249 yield f" -- {' '.join(fullcols)}"
250
251 for (op, insn, row) in entries:
252 yield f" {op:>13} => ({', '.join(row)}), -- {insn}"
253
254 yield f" {'others':>13} => sv_illegal_inst"
255 yield " );"
256 yield ""
257
258 def definitions_binutils():
259 yield f"/* TODO: implement binutils definitions */"
260
261 yield from {
262 Format.BINUTILS: definitions_binutils,
263 Format.VHDL: definitions_vhdl,
264 }[self]()
265
266 def wrap_comment(self, lines):
267 def wrap_comment_binutils(lines):
268 lines = tuple(lines)
269 if len(lines) == 1:
270 yield f"/* {lines[0]} */"
271 else:
272 yield "/*"
273 yield from map(lambda line: f" * {line}", lines)
274 yield " */"
275
276 def wrap_comment_vhdl(lines):
277 yield from map(lambda line: f"-- {line}", lines)
278
279 yield from {
280 Format.BINUTILS: wrap_comment_binutils,
281 Format.VHDL: wrap_comment_vhdl,
282 }[self](lines)
283
284
285 def read_csvs():
286 csvs = {}
287 csvs_svp64 = {}
288 bykey = {}
289 primarykeys = set()
290 dictkeys = OrderedDict()
291 immediates = {}
292 insns = {} # dictionary of CSV row, by instruction
293 insn_to_csv = {}
294
295 # Expand that (all .csv files)
296 pth = find_wiki_file("*.csv")
297
298 # Ignore those containing: valid test sprs
299 for fname in glob_valid_csvs(pth):
300 csvname = os.path.split(fname)[1]
301 csvname_ = csvname.split(".")[0]
302 # csvname is something like: minor_59.csv, fname the whole path
303 csv = get_csv(fname)
304 csvs[fname] = csv
305 csvs_svp64[csvname_] = []
306 for row in csv:
307 if blank_key(row):
308 continue
309 #print("row", row)
310 insn_name = row['comment']
311 condition = row['CONDITIONS']
312 # skip instructions that are not suitable
313 if insn_name.startswith("l") and insn_name.endswith("br"):
314 continue # skip pseudo-alias lxxxbr
315 if insn_name in ['mcrxr', 'mcrxrx', 'darn']:
316 continue
317 if insn_name in ['bctar', 'bcctr']: # for now. TODO
318 continue
319 if 'rfid' in insn_name:
320 continue
321 if 'addpcis' in insn_name: # skip for now
322 continue
323
324 # sv.bc is being classified as 2P-2S-1D by mistake due to SPRs
325 if insn_name.startswith('bc'):
326 # whoops: remove out reg (SPRs CTR etc)
327 row['in1'] = 'NONE'
328 row['in2'] = 'NONE'
329 row['in3'] = 'NONE'
330 row['out'] = 'NONE'
331
332 insns[(insn_name, condition)] = row # accumulate csv data
333 insn_to_csv[insn_name] = csvname_ # CSV file name by instruction
334 dkey = create_key(row)
335 key = tuple(dkey.values())
336 #print("key=", key, dkey)
337 dictkeys[key] = dkey
338 primarykeys.add(key)
339 if key not in bykey:
340 bykey[key] = []
341 bykey[key].append((csvname, row['opcode'], insn_name, condition,
342 row['form'].upper() + '-Form'))
343
344 # detect immediates, collate them (useful info)
345 if row['in2'].startswith('CONST_'):
346 imm = row['in2'].split("_")[1]
347 if key not in immediates:
348 immediates[key] = set()
349 immediates[key].add(imm)
350
351 primarykeys = list(primarykeys)
352 primarykeys.sort()
353
354 return (csvs, csvs_svp64, primarykeys, bykey, insn_to_csv, insns,
355 dictkeys, immediates)
356
357
358 def regs_profile(insn, res):
359 """get a more detailed register profile: 1st operand is RA,
360 2nd is RB, etc. etc
361 """
362 regs = []
363 for k in ['in1', 'in2', 'in3', 'out', 'CR in', 'CR out']:
364 if insn[k].startswith('CONST'):
365 res[k] = ''
366 regs.append('')
367 else:
368 res[k] = insn[k]
369 if insn[k] == 'RA_OR_ZERO':
370 regs.append('RA')
371 elif insn[k] != 'NONE':
372 regs.append(insn[k])
373 else:
374 regs.append('')
375 return regs
376
377
378 def extra_classifier(insn_name, value, name, res, regs):
379 """extra_classifier: creates the SVP64.RM EXTRA2/3 classification.
380 there is very little space (9 bits) to mark register operands
381 (RT RA RB, BA BB, BFA, FRS etc.) with the "extra" information
382 needed to tell if *EACH* operand (of which there can be up to five!)
383 is Vectorised, and whether its numbering is extended into the
384 0..127 range rather than the limited 3/5 bit of Scalar v3.0 Power ISA.
385
386 thus begins the rather tedious but by-rote examination of EVERY
387 Scalar instruction, working out how best to tell a decoder how to
388 extend the registers. EXTRA2 can have up to 4 slots (of 2 bit each)
389 where due to RM.EXTRA being 9 bits, EXTRA3 can have up to 3 slots
390 (of 3 bit each). the index REGNAME says which slot the register
391 named REGNAME must read its decoding from. d: means destination,
392 s: means source. some are *shared slots* especially LDST update.
393 some Rc=1 ops have the CR0/CR1 as a co-result which is also
394 obviously Vectorised if the result is Vectorised.
395
396 it is actually quite straightforward but the sheer quantity of
397 Scalar Power ISA instructions made it prudent to do this in an
398 intelligent way, almost by-rote, by analysing the register profiles.
399 """
400 # for LD/ST FP, use FRT/FRS not RT/RS, and use CR1 not CR0
401 if insn_name.startswith("lf"):
402 dRT = 'd:FRT'
403 dCR = 'd:CR1'
404 else:
405 dRT = 'd:RT'
406 dCR = 'd:CR0'
407 if insn_name.startswith("stf"):
408 sRS = 's:FRS'
409 dCR = 'd:CR1'
410 else:
411 sRS = 's:RS'
412 dCR = 'd:CR0'
413
414 # sigh now the fun begins. this isn't the sanest way to do it
415 # but the patterns are pretty regular. we start with the "profile"
416 # because that determines how much space is available (total num
417 # regs to decode) then if necessary begin apecialising either
418 # by the instruction name or through more detailed register
419 # profiling. example:
420 # if regs == ['RA', '', '', 'RT', '', '']:
421 # is in the order in1 in2 in3 out1 out2 Rc=1
422
423 # ********
424 # start with LD/ST
425
426 if value == 'LDSTRM-2P-1S1D':
427 res['Etype'] = 'EXTRA3' # RM EXTRA3 type
428 res['0'] = dRT # RT: Rdest_EXTRA3
429 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
430
431 elif value == 'LDSTRM-2P-1S2D':
432 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
433 res['0'] = dRT # RT: Rdest_EXTRA3
434 res['1'] = 'd:RA' # RA: Rdest2_EXTRA2
435 res['2'] = 's:RA' # RA: Rsrc1_EXTRA2
436
437 elif value == 'LDSTRM-2P-2S':
438 # stw, std, sth, stb
439 res['Etype'] = 'EXTRA3' # RM EXTRA3 type
440 res['0'] = sRS # RS: Rdest1_EXTRA3
441 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
442
443 elif value == 'LDSTRM-2P-2S1D':
444 if 'st' in insn_name and 'x' not in insn_name: # stwu/stbu etc
445 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
446 res['0'] = 'd:RA' # RA: Rdest1_EXTRA2
447 res['1'] = sRS # RS: Rdsrc1_EXTRA2
448 res['2'] = 's:RA' # RA: Rsrc2_EXTRA2
449 elif 'st' in insn_name and 'x' in insn_name: # stwux
450 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
451 res['0'] = 'd:RA' # RA: Rdest1_EXTRA2
452 # RS: Rdest2_EXTRA2, RA: Rsrc1_EXTRA2
453 res['1'] = "%s;%s" % (sRS, 's:RA')
454 res['2'] = 's:RB' # RB: Rsrc2_EXTRA2
455 elif 'u' in insn_name: # ldux etc.
456 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
457 res['0'] = dRT # RT: Rdest1_EXTRA2
458 res['1'] = 'd:RA' # RA: Rdest2_EXTRA2
459 res['2'] = 's:RB' # RB: Rsrc1_EXTRA2
460 else:
461 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
462 res['0'] = dRT # RT: Rdest1_EXTRA2
463 res['1'] = 's:RA' # RA: Rsrc1_EXTRA2
464 res['2'] = 's:RB' # RB: Rsrc2_EXTRA2
465
466 elif value == 'LDSTRM-2P-3S':
467 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
468 if 'cx' in insn_name:
469 res['0'] = "%s;%s" % (sRS, dCR) # RS: Rsrc1_EXTRA2 CR0: dest
470 else:
471 res['0'] = sRS # RS: Rsrc1_EXTRA2
472 res['1'] = 's:RA' # RA: Rsrc2_EXTRA2
473 res['2'] = 's:RB' # RA: Rsrc3_EXTRA2
474
475 # **********
476 # now begins,arithmetic
477
478 elif value == 'RM-2P-1S1D':
479 res['Etype'] = 'EXTRA3' # RM EXTRA3 type
480 if insn_name == 'mtspr':
481 res['0'] = 'd:SPR' # SPR: Rdest1_EXTRA3
482 res['1'] = 's:RS' # RS: Rsrc1_EXTRA3
483 elif insn_name == 'mfspr':
484 res['0'] = 'd:RS' # RS: Rdest1_EXTRA3
485 res['1'] = 's:SPR' # SPR: Rsrc1_EXTRA3
486 elif name == 'CRio' and insn_name == 'mcrf':
487 res['0'] = 'd:BF' # BFA: Rdest1_EXTRA3
488 res['1'] = 's:BFA' # BFA: Rsrc1_EXTRA3
489 elif 'mfcr' in insn_name or 'mfocrf' in insn_name:
490 res['0'] = 'd:RT' # RT: Rdest1_EXTRA3
491 res['1'] = 's:CR' # CR: Rsrc1_EXTRA3
492 elif insn_name == 'setb':
493 res['0'] = 'd:RT' # RT: Rdest1_EXTRA3
494 res['1'] = 's:BFA' # BFA: Rsrc1_EXTRA3
495 elif insn_name.startswith('cmp'): # cmpi
496 res['0'] = 'd:BF' # BF: Rdest1_EXTRA3
497 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
498 elif regs == ['RA', '', '', 'RT', '', '']:
499 res['0'] = 'd:RT' # RT: Rdest1_EXTRA3
500 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
501 elif regs == ['RA', '', '', 'RT', '', 'CR0']:
502 res['0'] = 'd:RT;d:CR0' # RT,CR0: Rdest1_EXTRA3
503 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
504 elif (regs == ['RS', '', '', 'RA', '', 'CR0'] or
505 regs == ['', '', 'RS', 'RA', '', 'CR0']):
506 res['0'] = 'd:RA;d:CR0' # RA,CR0: Rdest1_EXTRA3
507 res['1'] = 's:RS' # RS: Rsrc1_EXTRA3
508 elif regs == ['RS', '', '', 'RA', '', '']:
509 res['0'] = 'd:RA' # RA: Rdest1_EXTRA3
510 res['1'] = 's:RS' # RS: Rsrc1_EXTRA3
511 elif regs == ['', 'FRB', '', 'FRT', '0', 'CR1']:
512 res['0'] = 'd:FRT;d:CR1' # FRT,CR1: Rdest1_EXTRA3
513 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA3
514 elif regs == ['', 'FRB', '', '', '', 'CR1']:
515 res['0'] = 'd:CR1' # CR1: Rdest1_EXTRA3
516 res['1'] = 's:FRB' # FRA: Rsrc1_EXTRA3
517 elif regs == ['', 'FRB', '', '', '', 'BF']:
518 res['0'] = 'd:BF' # BF: Rdest1_EXTRA3
519 res['1'] = 's:FRB' # FRA: Rsrc1_EXTRA3
520 elif regs == ['', 'FRB', '', 'FRT', '', 'CR1']:
521 res['0'] = 'd:FRT;d:CR1' # FRT,CR1: Rdest1_EXTRA3
522 res['1'] = 's:FRB' # FRB: Rsrc1_EXTRA3
523 elif insn_name == 'fishmv':
524 # an overwrite instruction
525 res['0'] = 'd:FRS' # FRS: Rdest1_EXTRA3
526 res['1'] = 's:FRS' # FRS: Rsrc1_EXTRA3
527 elif insn_name == 'setvl':
528 res['0'] = 'd:RT' # RT: Rdest1_EXTRA3
529 res['1'] = 's:RA' # RS: Rsrc1_EXTRA3
530 else:
531 res['0'] = 'TODO'
532 print("regs TODO", insn_name, regs)
533
534 elif value == 'RM-1P-2S1D':
535 res['Etype'] = 'EXTRA3' # RM EXTRA3 type
536 if insn_name.startswith('cr'):
537 res['0'] = 'd:BT' # BT: Rdest1_EXTRA3
538 res['1'] = 's:BA' # BA: Rsrc1_EXTRA3
539 res['2'] = 's:BB' # BB: Rsrc2_EXTRA3
540 elif regs == ['FRA', '', 'FRC', 'FRT', '', 'CR1']:
541 res['0'] = 'd:FRT;d:CR1' # FRT,CR1: Rdest1_EXTRA3
542 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA3
543 res['2'] = 's:FRC' # FRC: Rsrc1_EXTRA3
544 # should be for fcmp
545 elif regs == ['FRA', 'FRB', '', '', '', 'BF']:
546 res['0'] = 'd:BF' # BF: Rdest1_EXTRA3
547 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA3
548 res['2'] = 's:FRB' # FRB: Rsrc1_EXTRA3
549 elif regs == ['FRA', 'FRB', '', 'FRT', '', '']:
550 res['0'] = 'd:FRT' # FRT: Rdest1_EXTRA3
551 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA3
552 res['2'] = 's:FRB' # FRB: Rsrc1_EXTRA3
553 elif regs == ['FRA', 'FRB', '', 'FRT', '', 'CR1']:
554 res['0'] = 'd:FRT;d:CR1' # FRT,CR1: Rdest1_EXTRA3
555 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA3
556 res['2'] = 's:FRB' # FRB: Rsrc1_EXTRA3
557 elif regs == ['FRA', 'RB', '', 'FRT', '', 'CR1']:
558 res['0'] = 'd:FRT;d:CR1' # FRT,CR1: Rdest1_EXTRA3
559 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA3
560 res['2'] = 's:RB' # RB: Rsrc1_EXTRA3
561 elif name == '2R-1W' or insn_name == 'cmpb': # cmpb
562 if insn_name in ['bpermd', 'cmpb']:
563 res['0'] = 'd:RA' # RA: Rdest1_EXTRA3
564 res['1'] = 's:RS' # RS: Rsrc1_EXTRA3
565 else:
566 res['0'] = 'd:RT' # RT: Rdest1_EXTRA3
567 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
568 res['2'] = 's:RB' # RB: Rsrc1_EXTRA3
569 elif insn_name.startswith('cmp'): # cmp
570 res['0'] = 'd:BF' # BF: Rdest1_EXTRA3
571 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
572 res['2'] = 's:RB' # RB: Rsrc1_EXTRA3
573 elif (regs == ['', 'RB', 'RS', 'RA', '', 'CR0'] or
574 regs == ['RS', 'RB', '', 'RA', '', 'CR0']):
575 res['0'] = 'd:RA;d:CR0' # RA,CR0: Rdest1_EXTRA3
576 res['1'] = 's:RB' # RB: Rsrc1_EXTRA3
577 res['2'] = 's:RS' # RS: Rsrc1_EXTRA3
578 elif regs == ['RA', 'RB', '', 'RT', '', 'CR0']:
579 res['0'] = 'd:RT;d:CR0' # RT,CR0: Rdest1_EXTRA3
580 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
581 res['2'] = 's:RB' # RB: Rsrc1_EXTRA3
582 elif regs == ['RA', '', 'RS', 'RA', '', 'CR0']:
583 res['0'] = 'd:RA;d:CR0' # RA,CR0: Rdest1_EXTRA3
584 res['1'] = 's:RA' # RA: Rsrc1_EXTRA3
585 res['2'] = 's:RS' # RS: Rsrc1_EXTRA3
586 else:
587 res['0'] = 'TODO'
588
589 elif value == 'RM-2P-2S1D':
590 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
591 if insn_name.startswith('mt'): # mtcrf
592 res['0'] = 'd:CR' # CR: Rdest1_EXTRA2
593 res['1'] = 's:RS' # RS: Rsrc1_EXTRA2
594 res['2'] = 's:CR' # CR: Rsrc2_EXTRA2
595 else:
596 res['0'] = 'TODO'
597
598 elif value == 'RM-1P-3S1D':
599 res['Etype'] = 'EXTRA2' # RM EXTRA2 type
600 if regs == ['RA', 'RB', 'RC', 'RT', '', '']: # madd*
601 res['0'] = 'd:RT' # RT,CR0: Rdest1_EXTRA2
602 res['1'] = 's:RA' # RA: Rsrc1_EXTRA2
603 res['2'] = 's:RB' # RT: Rsrc2_EXTRA2
604 res['3'] = 's:RC' # RT: Rsrc3_EXTRA2
605 elif regs == ['RA', 'RB', 'RT', 'RT', '', 'CR0']: # overwrite 3-in
606 res['0'] = 'd:RT;d:CR0' # RT,CR0: Rdest1_EXTRA2
607 res['1'] = 's:RA' # RA: Rsrc1_EXTRA2
608 res['2'] = 's:RB' # RT: Rsrc2_EXTRA2
609 res['3'] = 's:RT' # RT: Rsrc3_EXTRA2
610 elif insn_name == 'isel':
611 res['0'] = 'd:RT' # RT: Rdest1_EXTRA2
612 res['1'] = 's:RA' # RA: Rsrc1_EXTRA2
613 res['2'] = 's:RB' # RT: Rsrc2_EXTRA2
614 res['3'] = 's:BC' # BC: Rsrc3_EXTRA2
615 else: # fmadd*
616 res['0'] = 'd:FRT;d:CR1' # FRT, CR1: Rdest1_EXTRA2
617 res['1'] = 's:FRA' # FRA: Rsrc1_EXTRA2
618 res['2'] = 's:FRB' # FRB: Rsrc2_EXTRA2
619 res['3'] = 's:FRC' # FRC: Rsrc3_EXTRA2
620
621 elif value == 'RM-1P-1D':
622 res['Etype'] = 'EXTRA3' # RM EXTRA3 type
623 if insn_name == 'svstep':
624 res['0'] = 'd:RT;d:CR0' # RT,CR0: Rdest1_EXTRA3
625 if insn_name == 'fmvis':
626 res['0'] = 'd:FRS' # FRS: Rdest1_EXTRA3
627
628 # HACK! thos should be RM-1P-1S butvthere is a bug with sv.bc
629 elif value == 'RM-2P-1S':
630 res['Etype'] = 'EXTRA3' # RM EXTRA3 type
631 if insn_name.startswith('bc'):
632 res['0'] = 's:BI' # BI: Rsrc1_EXTRA3
633
634
635 def process_csvs(format):
636
637 print("# Draft SVP64 Power ISA register 'profile's")
638 print('')
639 print("this page is auto-generated, do not edit")
640 print("created by http://libre-soc.org/openpower/sv_analysis.py")
641 print('')
642
643 (csvs, csvs_svp64, primarykeys, bykey, insn_to_csv, insns,
644 dictkeys, immediates) = read_csvs()
645
646 # mapping to old SVPrefix "Forms"
647 mapsto = {'3R-1W-CRo': 'RM-1P-3S1D',
648 '3R-1W': 'RM-1P-3S1D',
649 '2R-1W-CRio': 'RM-1P-2S1D',
650 '2R-1W-CRi': 'RM-1P-3S1D',
651 '2R-1W-CRo': 'RM-1P-2S1D',
652 '2R': 'non-SV',
653 '2R-1W': 'RM-1P-2S1D',
654 '1R-CRio': 'RM-2P-2S1D',
655 '2R-CRio': 'RM-1P-2S1D',
656 '2R-CRo': 'RM-1P-2S1D',
657 '1R': 'non-SV',
658 '1R-1W-CRio': 'RM-2P-1S1D',
659 '1R-1W-CRo': 'RM-2P-1S1D',
660 '1R-1W': 'RM-2P-1S1D',
661 '1R-1W-imm': 'RM-2P-1S1D',
662 '1R-CRo': 'RM-2P-1S1D',
663 '1R-imm': 'RM-1P-1S',
664 '1W-CRo': 'RM-1P-1D',
665 '1W': 'non-SV',
666 '1W-imm': 'RM-1P-1D',
667 '1W-CRi': 'RM-2P-1S1D',
668 'CRio': 'RM-2P-1S1D',
669 'CR=2R1W': 'RM-1P-2S1D',
670 'CRi': 'RM-2P-1S', # HACK, bc here, it should be 1P
671 'imm': 'non-SV',
672 '': 'non-SV',
673 'LDST-2R-imm': 'LDSTRM-2P-2S',
674 'LDST-2R-1W-imm': 'LDSTRM-2P-2S1D',
675 'LDST-2R-1W': 'LDSTRM-2P-2S1D',
676 'LDST-2R-2W': 'LDSTRM-2P-2S1D',
677 'LDST-1R-1W-imm': 'LDSTRM-2P-1S1D',
678 'LDST-1R-2W-imm': 'LDSTRM-2P-1S2D',
679 'LDST-3R': 'LDSTRM-2P-3S',
680 'LDST-3R-CRo': 'LDSTRM-2P-3S', # st*x
681 'LDST-3R-1W': 'LDSTRM-2P-2S1D', # st*x
682 }
683 print("# map to old SV Prefix")
684 print('')
685 print('|internal key | public name |')
686 print('|----- | ---------- |')
687 for key in primarykeys:
688 name = keyname(dictkeys[key])
689 value = mapsto.get(name, "-")
690 print(tformat([name, value + " "]))
691 print('')
692 print('')
693
694 print("# keys")
695 print('')
696 print(tformat(tablecols) + " imms | name |")
697 print(tformat([" - "] * (len(tablecols)+2)))
698
699 # print out the keys and the table from which they're derived
700 for key in primarykeys:
701 name = keyname(dictkeys[key])
702 row = tformat(dictkeys[key].values())
703 imms = list(immediates.get(key, ""))
704 imms.sort()
705 row += " %s | " % ("/".join(imms))
706 row += " %s |" % name
707 print(row)
708 print('')
709 print('')
710
711 # print out, by remap name, all the instructions under that category
712 for key in primarykeys:
713 name = keyname(dictkeys[key])
714 value = mapsto.get(name, "-")
715 print("## %s (%s)" % (name, value))
716 print('')
717 print(tformat(['CSV', 'opcode', 'asm', 'flags', 'form']))
718 print(tformat(['---', '------', '---', '-----', '----']))
719 rows = bykey[key]
720 rows.sort()
721 for row in rows:
722 print(tformat(row))
723 print('')
724 print('')
725
726 # for fname, csv in csvs.items():
727 # print (fname)
728
729 # for insn, row in insns.items():
730 # print (insn, row)
731
732 print("# svp64 remaps")
733 svp64 = OrderedDict()
734 # create a CSV file, per category, with SV "augmentation" info
735 # XXX note: 'out2' not added here, needs to be added to CSV files
736 # KEEP TRACK OF THESE https://bugs.libre-soc.org/show_bug.cgi?id=619
737 csvcols = ['insn', 'mode', 'CONDITIONS', 'Ptype', 'Etype', 'SM']
738 csvcols += ['0', '1', '2', '3']
739 csvcols += ['in1', 'in2', 'in3', 'out', 'CR in', 'CR out'] # temporary
740 for key in primarykeys:
741 # get the decoded key containing row-analysis, and name/value
742 dkey = dictkeys[key]
743 name = keyname(dkey)
744 value = mapsto.get(name, "-")
745 if value == 'non-SV':
746 continue
747
748 # print out svp64 tables by category
749 print("* **%s**: %s" % (name, value))
750
751 # store csv entries by svp64 RM category
752 if value not in svp64:
753 svp64[value] = []
754
755 rows = bykey[key]
756 rows.sort()
757
758 for row in rows:
759 # for idx in range(len(row)):
760 # if row[idx] == 'NONE':
761 # row[idx] = ''
762 # get the instruction
763 #print(key, row)
764 insn_name = row[2]
765 condition = row[3]
766 insn = insns[(insn_name, condition)]
767
768 # start constructing svp64 CSV row
769 res = OrderedDict()
770 res['insn'] = insn_name
771 res['CONDITIONS'] = condition
772 res['Ptype'] = value.split('-')[1] # predication type (RM-xN-xxx)
773 # get whether R_xxx_EXTRAn fields are 2-bit or 3-bit
774 res['Etype'] = 'EXTRA2'
775 # go through each register matching to Rxxxx_EXTRAx
776 for k in ['0', '1', '2', '3']:
777 res[k] = ''
778 # create "fake" out2 (TODO, needs to be added to CSV files)
779 # KEEP TRACK HERE https://bugs.libre-soc.org/show_bug.cgi?id=619
780 res['out2'] = 'NONE'
781 if insn['upd'] == '1': # LD/ST with update has RA as out2
782 res['out2'] = 'RA'
783
784 # set the SVP64 mode to NORMAL, LDST, BRANCH or CR
785 crops = ['mfcr', 'mfocrf', 'mtcrf', 'mtocrf',
786 ]
787 mode = 'NORMAL'
788 if value.startswith('LDST'):
789 if 'x' in insn_name: # Indexed detection
790 mode = 'LDST_IDX'
791 else:
792 mode = 'LDST_IMM'
793 elif insn_name.startswith('bc'):
794 mode = 'BRANCH'
795 elif insn_name.startswith('cr') or insn_name in crops:
796 mode = 'CROP'
797 res['mode'] = mode
798
799 # create a register profile list (update res row as well)
800 regs = regs_profile(insn, res)
801
802 #print("regs", insn_name, regs)
803 extra_classifier(insn_name, value, name, res, regs)
804
805 # source-mask is hard to detect, it's part of RM-nn-nn.
806 # to make disassembler easier, create a yes/no decision here
807 # see https://libre-soc.org/openpower/sv/svp64/#extra_remap
808 # MASK_SRC
809 vstripped = value.replace("LDST", "")
810 if vstripped in ['RM-2P-1S1D', 'RM-2P-2S',
811 'RM-2P-2S1D', 'RM-2P-1S2D', 'RM-2P-3S',
812 ]:
813 res['SM'] = 'EN'
814 else:
815 res['SM'] = 'NO'
816 # add to svp64 csvs
817 # for k in ['in1', 'in2', 'in3', 'out', 'CR in', 'CR out']:
818 # del res[k]
819 # if res['0'] != 'TODO':
820 for k in res:
821 if k == 'CONDITIONS':
822 continue
823 if res[k] == 'NONE' or res[k] == '':
824 res[k] = '0'
825 svp64[value].append(res)
826 # also add to by-CSV version
827 csv_fname = insn_to_csv[insn_name]
828 csvs_svp64[csv_fname].append(res)
829
830 print('')
831
832 # now write out the csv files
833 for value, csv in svp64.items():
834 if value == '-':
835 continue
836 from time import sleep
837 print("WARNING, filename '-' should NOT exist. instrs missing")
838 print("TODO: fix this (and put in the bugreport number here)")
839 sleep(2)
840 # print out svp64 tables by category
841 print("## %s" % value)
842 print('')
843 cols = csvcols + ['out2']
844 print(tformat(cols))
845 print(tformat([" - "] * (len(cols))))
846 for d in csv:
847 row = []
848 for k in cols:
849 row.append(d[k])
850 print(tformat(row))
851 print('')
852
853 #csvcols = ['insn', 'Ptype', 'Etype', '0', '1', '2', '3']
854 write_csv("%s.csv" % value, csv, csvcols + ['out2'])
855
856 # okaaay, now we re-read them back in for producing microwatt SV
857
858 # get SVP64 augmented CSV files
859 svt = SVP64RM(microwatt_format=True)
860 # Expand that (all .csv files)
861 pth = find_wiki_file("*.csv")
862
863 # Ignore those containing: valid test sprs
864 for fname in glob_valid_csvs(pth):
865 svp64_csv = svt.get_svp64_csv(fname)
866
867 csvcols = ['insn', 'mode', 'Ptype', 'Etype', 'SM']
868 csvcols += ['in1', 'in2', 'in3', 'out', 'out2', 'CR in', 'CR out']
869
870 if format is Format.VHDL:
871 # and a nice microwatt VHDL file
872 file_path = find_wiki_file("sv_decode.vhdl")
873 elif format is Format.BINUTILS:
874 file_path = find_wiki_file("binutils.c")
875
876 with open(file_path, 'w') as stream:
877 output(format, svt, csvcols, insns, csvs_svp64, stream)
878
879
880 def output_autogen_disclaimer(format, stream):
881 lines = (
882 "this file is auto-generated, do not edit",
883 "http://libre-soc.org/openpower/sv_analysis.py",
884 "part of Libre-SOC, sponsored by NLnet",
885 )
886 for line in format.wrap_comment(lines):
887 stream.write(line)
888 stream.write("\n")
889 stream.write("\n")
890
891
892 def output(format, svt, csvcols, insns, csvs_svp64, stream):
893 lens = {
894 'major': 63,
895 'minor_4': 63,
896 'minor_19': 7,
897 'minor_30': 15,
898 'minor_31': 1023,
899 'minor_58': 63,
900 'minor_59': 31,
901 'minor_62': 63,
902 'minor_63l': 511,
903 'minor_63h': 16,
904 }
905
906 def svp64_canonicalize(item):
907 (value, csv) = item
908 value = value.lower().replace("-", "_")
909 return (value, csv)
910
911 csvs_svp64_canon = dict(map(svp64_canonicalize, csvs_svp64.items()))
912
913 # disclaimer
914 output_autogen_disclaimer(format, stream)
915
916 # declarations
917 for line in format.declarations(csvs_svp64_canon.keys(), lens):
918 stream.write(f"{line}\n")
919
920 # definitions
921 sv_cols = ['sv_in1', 'sv_in2', 'sv_in3', 'sv_out', 'sv_out2',
922 'sv_cr_in', 'sv_cr_out']
923 fullcols = csvcols + sv_cols
924
925 entries_svp64 = defaultdict(list)
926 for (value, csv) in filter(lambda kv: kv[0] in lens, csvs_svp64_canon.items()):
927 for entry in csv:
928 insn = str(entry['insn'])
929 condition = str(entry['CONDITIONS'])
930 mode = str(entry['mode'])
931 sventry = svt.svp64_instrs.get(insn, None)
932 if sventry is not None:
933 sventry['mode'] = mode
934 op = insns[(insn, condition)]['opcode']
935 # binary-to-vhdl-binary
936 if op.startswith("0b"):
937 op = "2#%s#" % op[2:]
938 row = []
939 for colname in csvcols[1:]:
940 re = entry[colname]
941 # zero replace with NONE
942 if re == '0':
943 re = 'NONE'
944 # 1/2 predication
945 re = re.replace("1P", "P1")
946 re = re.replace("2P", "P2")
947 row.append(re)
948 #print("sventry", sventry)
949 for colname in sv_cols:
950 if sventry is None:
951 re = 'NONE'
952 else:
953 re = sventry[colname]
954 row.append(re)
955 entries_svp64[value].append((op, insn, row))
956
957 for line in format.definitions(entries_svp64, fullcols):
958 stream.write(f"{line}\n")
959
960
961 def main():
962 import os
963 os.environ['SILENCELOG'] = '1'
964 parser = argparse.ArgumentParser()
965 parser.add_argument("-f", "--format",
966 type=Format, choices=Format, default=Format.VHDL,
967 help="format to be used (binutils or VHDL)")
968 args = parser.parse_args()
969 process_csvs(args.format)
970
971
972 if __name__ == '__main__':
973 # don't do anything other than call main() here, cuz this code is bypassed
974 # by the sv_analysis command created by setup.py
975 main()