add support for *_flag global variables needed by bfp_* functions
[openpower-isa.git] / src / openpower / decoder / pseudo / pagereader.py
1 # Reads OpenPOWER ISA pages from http://libre-soc.org/openpower/isa
2 """OpenPOWER ISA page parser
3
4 returns an OrderedDict of namedtuple "Ops" containing details of all
5 instructions listed in markdown files.
6
7 format must be strictly as follows (no optional sections) including whitespace:
8
9 # Compare Logical
10
11 X-Form
12
13 * cmpl BF,L,RA,RB
14
15 if L = 0 then a <- [0]*32 || (RA)[32:63]
16 b <- [0]*32 || (RB)[32:63]
17 else a <- (RA)
18 b <- (RB)
19 if a <u b then c <- 0b100
20 else if a >u b then c <- 0b010
21 else c <- 0b001
22 CR[4*BF+32:4*BF+35] <- c || XER[SO]
23
24 Special Registers Altered:
25
26 CR field BF
27 Another field
28
29 this translates to:
30
31 # heading
32 blank
33 Some-Form
34 blank
35 * instruction registerlist
36 * instruction registerlist
37 blank
38 4-space-indented pseudo-code
39 4-space-indented pseudo-code
40 blank
41 Special Registers Altered:
42 4-space-indented register description
43 blank
44 blank(s) (optional for convenience at end-of-page)
45 """
46
47 from openpower.util import log
48 from collections import namedtuple, OrderedDict
49 from copy import copy
50 import os
51 import re
52
53 opfields = ("desc", "form", "opcode", "regs", "pcode", "sregs", "page")
54 Ops = namedtuple("Ops", opfields)
55
56
57 def get_isa_dir():
58 fdir = os.path.abspath(os.path.dirname(__file__))
59 fdir = os.path.split(fdir)[0]
60 fdir = os.path.split(fdir)[0]
61 fdir = os.path.split(fdir)[0]
62 fdir = os.path.split(fdir)[0]
63 # print (fdir)
64 return os.path.join(fdir, "openpower", "isa")
65
66
67 pattern_opcode = r"[A-Za-z0-9_\.]+\.?"
68 pattern_dynamic = r"[A-Za-z0-9_]+(?:\([A-Za-z0-9_]+\))*"
69 pattern_static = r"[A-Za-z0-9]+\=[01]"
70 regex_opcode = re.compile(f"^{pattern_opcode}$")
71 regex_dynamic = re.compile(f"^{pattern_dynamic}(?:,{pattern_dynamic})*$")
72 regex_static = re.compile(f"^\({pattern_static}(?:\s{pattern_static})*\)$")
73
74
75 def operands(opcode, desc):
76 if desc is None:
77 return
78 desc = desc.replace("(", "")
79 desc = desc.replace(")", "")
80 desc = desc.replace(",", " ")
81 for operand in desc.split(" "):
82 operand = operand.strip()
83 if operand:
84 yield operand
85
86
87 class ISA:
88 def __init__(self):
89 self.instr = OrderedDict()
90 self.forms = {}
91 self.page = {}
92 self.verbose = False
93 for pth in os.listdir(os.path.join(get_isa_dir())):
94 if self.verbose:
95 print("examining", get_isa_dir(), pth)
96 if "swp" in pth:
97 continue
98 if not pth.endswith(".mdwn"):
99 log ("warning, file not .mdwn, skipping", pth)
100 continue
101 self.read_file(pth)
102 continue
103 # code which helped add in the keyword "Pseudo-code:" automatically
104 rewrite = self.read_file_for_rewrite(pth)
105 name = os.path.join("/tmp", pth)
106 with open(name, "w") as f:
107 f.write('\n'.join(rewrite) + '\n')
108
109 def __iter__(self):
110 yield from self.instr.items()
111
112 def read_file_for_rewrite(self, fname):
113 pagename = fname.split('.')[0]
114 fname = os.path.join(get_isa_dir(), fname)
115 with open(fname) as f:
116 lines = f.readlines()
117 rewrite = []
118
119 l = lines.pop(0).rstrip() # get first line
120 rewrite.append(l)
121 while lines:
122 if self.verbose:
123 print(l)
124 # look for HTML comment, if starting, skip line.
125 # XXX this is braindead! it doesn't look for the end
126 # so please put ending of comments on one line:
127 # <!-- line 1 comment -->
128 # {some whitespace}<!-- line 2 comment -->
129 if l.strip().startswith('<!--'):
130 # print ("skipping comment", l)
131 l = lines.pop(0).rstrip() # get first line
132 continue
133
134 # Ignore blank lines before the first #
135 if len(l.strip()) == 0:
136 continue
137
138 # expect get heading
139 assert l.startswith('#'), ("# not found in line %s" % l)
140
141 # whitespace expected
142 l = lines.pop(0).strip()
143 if self.verbose:
144 print(repr(l))
145 assert len(l) == 0, ("blank line not found %s" % l)
146 rewrite.append(l)
147
148 # Form expected
149 l = lines.pop(0).strip()
150 assert l.endswith('-Form'), ("line with -Form expected %s" % l)
151 rewrite.append(l)
152
153 # whitespace expected
154 l = lines.pop(0).strip()
155 assert len(l) == 0, ("blank line not found %s" % l)
156 rewrite.append(l)
157
158 # get list of opcodes
159 while True:
160 l = lines.pop(0).strip()
161 rewrite.append(l)
162 if len(l) == 0:
163 break
164 assert l.startswith('*'), ("* not found in line %s" % l)
165
166 rewrite.append("Pseudo-code:")
167 rewrite.append("")
168 # get pseudocode
169 while True:
170 l = lines.pop(0).rstrip()
171 if l.strip().startswith('<!--'):
172 # print ("skipping comment", l)
173 l = lines.pop(0).rstrip() # get first line
174 continue
175 rewrite.append(l)
176 if len(l) == 0:
177 break
178 assert l.startswith(' '), ("4spcs not found in line %s" % l)
179
180 # "Special Registers Altered" expected
181 l = lines.pop(0).rstrip()
182 assert l.startswith("Special"), ("special not found %s" % l)
183 rewrite.append(l)
184
185 # whitespace expected
186 l = lines.pop(0).strip()
187 assert len(l) == 0, ("blank line not found %s" % l)
188 rewrite.append(l)
189
190 # get special regs
191 while lines:
192 l = lines.pop(0).rstrip()
193 rewrite.append(l)
194 if len(l) == 0:
195 break
196 assert l.startswith(' '), ("4spcs not found in line %s" % l)
197
198 # expect and drop whitespace
199 while lines:
200 l = lines.pop(0).rstrip()
201 rewrite.append(l)
202 if len(l) != 0 and not l.strip().startswith('<!--'):
203 break
204
205 return rewrite
206
207 def read_file(self, fname):
208 pagename = fname.split('.')[0]
209 fname = os.path.join(get_isa_dir(), fname)
210 with open(fname) as f:
211 lines = f.readlines()
212
213 # set up dict with current page name
214 d = {'page': pagename}
215
216 # line-by-line lexer/parser, quite straightforward: pops one
217 # line off the list and checks it. nothing complicated needed,
218 # all sections are mandatory so no need for a full LALR parser.
219
220 l = lines.pop(0).rstrip() # get first line
221 prefix_lines = 0
222 while lines:
223 if self.verbose:
224 print(l)
225 # look for HTML comment, if starting, skip line.
226 # XXX this is braindead! it doesn't look for the end
227 # so please put ending of comments on one line:
228 # <!-- line 1 comment -->
229 # <!-- line 2 comment -->
230 if l.strip().startswith('<!--'):
231 # print ("skipping comment", l)
232 l = lines.pop(0).rstrip() # get next line
233 prefix_lines += 1
234 continue
235
236 # Ignore blank lines before the first #
237 if len(l) == 0:
238 l = lines.pop(0).rstrip() # get next line
239 prefix_lines += 1
240 continue
241
242 # expect get heading
243 assert l.startswith('#'), ("# not found in line '%s'" % l)
244 d['desc'] = l[1:].strip()
245
246 # whitespace expected
247 l = lines.pop(0).strip()
248 prefix_lines += 1
249 if self.verbose:
250 print(repr(l))
251 assert len(l) == 0, ("blank line not found %s" % l)
252
253 # Form expected
254 l = lines.pop(0).strip()
255 prefix_lines += 1
256 assert l.endswith('-Form'), ("line with -Form expected %s" % l)
257 d['form'] = l.split('-')[0]
258
259 # whitespace expected
260 l = lines.pop(0).strip()
261 prefix_lines += 1
262 assert len(l) == 0, ("blank line not found %s" % l)
263
264 # get list of opcodes
265 opcodes = []
266 while True:
267 l = lines.pop(0).strip()
268 prefix_lines += 1
269 if len(l) == 0:
270 break
271 assert l.startswith('*'), ("* not found in line %s" % l)
272 rest = l[1:].strip()
273
274 (opcode, _, rest) = map(str.strip, rest.partition(" "))
275 if regex_opcode.match(opcode) is None:
276 raise IOError(repr(opcode))
277 opcode = [opcode]
278
279 (dynamic, _, rest) = map(str.strip, rest.partition(" "))
280 if regex_dynamic.match(dynamic) is None and dynamic:
281 raise IOError(f"{l!r}: {dynamic!r}")
282 if dynamic:
283 opcode.append(dynamic.split(","))
284
285 static = rest
286 if regex_static.match(static) is None and static:
287 raise IOError(f"{l!r}: {static!r}")
288 if static:
289 opcode.extend(static[1:-1].split(" "))
290
291 opcodes.append(opcode)
292
293 # "Pseudocode" expected
294 l = lines.pop(0).rstrip()
295 prefix_lines += 1
296 assert l.startswith("Pseudo-code:"), ("pseudocode found %s" % l)
297
298 # whitespace expected
299 l = lines.pop(0).strip()
300 prefix_lines += 1
301 if self.verbose:
302 print(repr(l))
303 assert len(l) == 0, ("blank line not found %s" % l)
304
305 # get pseudocode
306
307 # fix parser line numbers by prepending the right number of
308 # blank lines to the parser input
309 li = [""] * prefix_lines
310 li += [l[4:]] # first line detected with 4-space
311 while True:
312 l = lines.pop(0).rstrip()
313 prefix_lines += 1
314 if len(l) == 0:
315 li.append(l)
316 break
317 if l.strip().startswith('<!--'):
318 li.append("")
319 continue
320 assert l.startswith(' '), ("4spcs not found in line %s" % l)
321 l = l[4:] # lose 4 spaces
322 li.append(l)
323 d['pcode'] = li
324
325 # "Special Registers Altered" expected
326 l = lines.pop(0).rstrip()
327 prefix_lines += 1
328 assert l.startswith("Special"), ("special not found %s" % l)
329
330 # whitespace expected
331 l = lines.pop(0).strip()
332 prefix_lines += 1
333 assert len(l) == 0, ("blank line not found %s" % l)
334
335 # get special regs
336 li = []
337 while lines:
338 l = lines.pop(0).rstrip()
339 prefix_lines += 1
340 if len(l) == 0:
341 break
342 assert l.startswith(' '), ("4spcs not found in line %s" % l)
343 l = l[4:] # lose 4 spaces
344 li.append(l)
345 d['sregs'] = li
346
347 # add in opcode
348 for o in opcodes:
349 self.add_op(o, d)
350
351 # expect and drop whitespace and comments
352 while lines:
353 l = lines.pop(0).rstrip()
354 prefix_lines += 1
355 if len(l) != 0 and not l.strip().startswith('<!--'):
356 break
357
358 def add_op(self, o, d):
359 opcode, regs = o[0], o[1:]
360 op = copy(d)
361 op['regs'] = regs
362 op['opcode'] = opcode
363 self.instr[opcode] = Ops(**op)
364
365 # create list of instructions by form
366 form = op['form']
367 fl = self.forms.get(form, [])
368 self.forms[form] = fl + [opcode]
369
370 # create list of instructions by page
371 page = op['page']
372 pl = self.page.get(page, [])
373 self.page[page] = pl + [opcode]
374
375 def pprint_ops(self):
376 for k, v in self.instr.items():
377 print("# %s %s" % (v.opcode, v.desc))
378 print("Form: %s Regs: %s" % (v.form, v.regs))
379 print('\n'.join(map(lambda x: " %s" % x, v.pcode)))
380 print("Specials")
381 print('\n'.join(map(lambda x: " %s" % x, v.sregs)))
382 print()
383 for k, v in isa.forms.items():
384 print(k, v)
385
386
387 if __name__ == '__main__':
388 isa = ISA()
389 isa.pprint_ops()
390 # example on how to access cmp regs:
391 print ("cmp regs:", isa.instr["cmp"].regs)