736034f459898d7a3c4474c7cfeead62eed4f1a7
[openpower-isa.git] / 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 openpower.decoder.orderedset import OrderedSet
49 from collections import namedtuple, OrderedDict
50 from copy import copy
51 import os
52 import re
53
54 opfields = ("desc", "form", "opcode", "regs", "pcode", "sregs", "page",
55 "extra_uninit_regs", "english")
56 Ops = namedtuple("Ops", opfields)
57
58
59 def get_isa_dir():
60 fdir = os.path.abspath(os.path.dirname(__file__))
61 fdir = os.path.split(fdir)[0]
62 fdir = os.path.split(fdir)[0]
63 fdir = os.path.split(fdir)[0]
64 fdir = os.path.split(fdir)[0]
65 # print (fdir)
66 return os.path.join(fdir, "openpower", "isa")
67
68
69 def operands(opcode, desc):
70 if desc is None:
71 return
72 desc = desc.replace("(", "")
73 desc = desc.replace(")", "")
74 desc = desc.replace(",", " ")
75 for operand in desc.split(" "):
76 operand = operand.strip()
77 if operand:
78 yield operand
79
80
81 def get_indented_lines(lines, pagename, expect=None):
82 """gets a set of indented lines, plus a blank line as termination.
83 blank lines are permitted inside the block, but this requires a lookahead
84 at the next line to see if the next section is coming up (expected).
85 but... Description is currently *optional* sigh therefore it is
86 necessary to look for *either* "Special" *or* "Description" sigh
87 TODO: simplify once all Descriptions are added
88 """
89 li = []
90 while lines:
91 l = lines.pop(0).rstrip()
92 # grrrr skip comments
93 if l.strip().startswith('<!--'):
94 continue
95 # blank line check
96 if len(l) == 0:
97 # if not at the end of the block, add the extra 4 spaces
98 if expect and len(lines) >= 1:
99 found = False
100 for e in expect:
101 if lines[0].startswith(e):
102 found = True
103 if found:
104 break
105 else:
106 l = ' '
107 else:
108 break
109 assert l.startswith(' '), ("4spcs not found in line %s" % l +
110 "page %s" % pagename)
111 l = l[4:] # lose 4 spaces
112 li.append(l)
113 return li
114
115
116 class ISA:
117 def __init__(self):
118 self.instr = OrderedDict()
119 self.forms = {}
120 self.page = {}
121 self.verbose = False
122 for pth in os.listdir(os.path.join(get_isa_dir())):
123 if self.verbose:
124 print("examining", get_isa_dir(), pth)
125 if "swp" in pth:
126 continue
127 if not pth.endswith(".mdwn"):
128 if not os.path.isdir(os.path.join(get_isa_dir(), pth)):
129 log("warning, file not .mdwn, skipping", pth)
130 continue
131 self.read_file(pth)
132 continue
133 # code which helped add in the keyword "Pseudo-code:" automatically
134 rewrite = self.read_file_for_rewrite(pth)
135 name = os.path.join("/tmp", pth)
136 with open(name, "w") as f:
137 f.write('\n'.join(rewrite) + '\n')
138
139 def __iter__(self):
140 yield from self.instr.items()
141
142 def read_file_for_rewrite(self, fname):
143 pagename = fname.split('.')[0]
144 fname = os.path.join(get_isa_dir(), fname)
145 with open(fname) as f:
146 lines = f.readlines()
147 rewrite = []
148
149 l = lines.pop(0).rstrip() # get first line
150 rewrite.append(l)
151 while lines:
152 if self.verbose:
153 print(l)
154 # look for HTML comment, if starting, skip line.
155 # XXX this is braindead! it doesn't look for the end
156 # so please put ending of comments on one line:
157 # <!-- line 1 comment -->
158 # {some whitespace}<!-- line 2 comment -->
159 if l.strip().startswith('<!--'):
160 # print ("skipping comment", l)
161 l = lines.pop(0).rstrip() # get first line
162 continue
163
164 # Ignore blank lines before the first #
165 if len(l.strip()) == 0:
166 continue
167
168 # expect get heading
169 assert l.startswith('#'), ("# not found in line %s" % l)
170
171 # whitespace expected
172 l = lines.pop(0).strip()
173 if self.verbose:
174 print(repr(l))
175 assert len(l) == 0, ("blank line not found %s" % l)
176 rewrite.append(l)
177
178 # Form expected
179 l = lines.pop(0).strip()
180 assert l.endswith('-Form'), ("line with -Form expected %s" % l)
181 rewrite.append(l)
182
183 # whitespace expected
184 l = lines.pop(0).strip()
185 assert len(l) == 0, ("blank line not found %s" % l)
186 rewrite.append(l)
187
188 # get list of opcodes
189 while True:
190 l = lines.pop(0).strip()
191 rewrite.append(l)
192 if len(l) == 0:
193 break
194 assert l.startswith('*'), ("* not found in line %s" % l)
195
196 rewrite.append("Pseudo-code:")
197 rewrite.append("")
198 # get pseudocode
199 while True:
200 l = lines.pop(0).rstrip()
201 if l.strip().startswith('<!--'):
202 # print ("skipping comment", l)
203 l = lines.pop(0).rstrip() # get first line
204 continue
205 rewrite.append(l)
206 if len(l) == 0:
207 break
208 assert l.startswith(' '), ("4spcs not found in line %s" % l)
209
210 # "Special Registers Altered" expected
211 l = lines.pop(0).rstrip()
212 assert l.startswith("Special"), ("special not found %s" % l)
213 rewrite.append(l)
214
215 # whitespace expected
216 l = lines.pop(0).strip()
217 assert len(l) == 0, ("blank line not found %s" % l)
218 rewrite.append(l)
219
220 # get special regs
221 while lines:
222 l = lines.pop(0).rstrip()
223 rewrite.append(l)
224 if len(l) == 0:
225 break
226 assert l.startswith(' '), ("4spcs not found in line %s" % l)
227
228 # expect and drop whitespace
229 while lines:
230 l = lines.pop(0).rstrip()
231 rewrite.append(l)
232 if len(l) != 0 and not l.strip().startswith('<!--'):
233 break
234
235 return rewrite
236
237 def read_file(self, fname):
238 pagename = fname.split('.')[0]
239 fname = os.path.join(get_isa_dir(), fname)
240 with open(fname) as f:
241 lines = f.readlines()
242
243 # set up dict with current page name and blank english description
244 d = {'page': pagename, 'english': None}
245
246 # line-by-line lexer/parser, quite straightforward: pops one
247 # line off the list and checks it. nothing complicated needed,
248 # all sections are mandatory so no need for a full LALR parser.
249
250 l = lines.pop(0).rstrip() # get first line
251 while lines:
252 if self.verbose:
253 print(l)
254 # look for HTML comment, if starting, skip line.
255 # XXX this is braindead! it doesn't look for the end
256 # so please put ending of comments on one line:
257 # <!-- line 1 comment -->
258 # <!-- line 2 comment -->
259 if l.strip().startswith('<!--'):
260 # print ("skipping comment", l)
261 l = lines.pop(0).rstrip() # get next line
262 continue
263
264 # Ignore blank lines before the first #
265 if len(l) == 0:
266 l = lines.pop(0).rstrip() # get next line
267 continue
268
269 # expect get heading
270 assert l.startswith('#'), ("# not found in line '%s'" % l)
271 d['desc'] = l[1:].strip()
272
273 # whitespace expected
274 l = lines.pop(0).strip()
275 if self.verbose:
276 print(repr(l))
277 assert len(l) == 0, ("blank line not found %s" % l)
278
279 # Form expected
280 l = lines.pop(0).strip()
281 assert l.endswith('-Form'), ("line with -Form expected %s" % l)
282 d['form'] = l.split('-')[0]
283
284 # whitespace expected
285 l = lines.pop(0).strip()
286 assert len(l) == 0, ("blank line not found %s" % l)
287
288 # get list of opcodes
289 opcodes = []
290 while True:
291 l = lines.pop(0).strip()
292 if len(l) == 0:
293 break
294 assert l.startswith('*'), ("* not found in line %s" % l)
295 rest = l[1:].strip()
296
297 (opcode, _, rest) = map(str.strip, rest.partition(" "))
298 opcode = [opcode]
299
300 (dynamic, _, rest) = map(str.strip, rest.partition(" "))
301 if dynamic:
302 opcode.append(dynamic.split(","))
303
304 static = rest
305 if static:
306 opcode.extend(static[1:-1].split(" "))
307
308 opcodes.append(opcode)
309
310 # "Pseudocode" expected
311 l = lines.pop(0).rstrip()
312 assert l.startswith("Pseudo-code:"), ("pseudocode found %s" % l)
313
314 # whitespace expected
315 l = lines.pop(0).strip()
316 if self.verbose:
317 print(repr(l))
318 assert len(l) == 0, ("blank line not found %s" % l)
319
320 extra_uninit_regs = OrderedSet()
321
322 # get pseudocode
323 li = []
324 d['pcode'] = get_indented_lines(lines, pagename,
325 ['Description', 'Special'])
326 d['extra_uninit_regs'] = extra_uninit_regs
327
328 # check if (temporarily) optional "Description" exists
329 if lines[0].startswith("Description:"):
330 l = lines.pop(0).rstrip() # skip "Description"
331 d['english'] = get_indented_lines(lines, pagename,
332 ["Special"])
333
334 # "Special Registers Altered" expected
335 l = lines.pop(0).rstrip()
336 assert l.startswith("Special"), \
337 ("special not found %s (%s)" % (l, d['desc']))
338
339 # whitespace expected
340 l = lines.pop(0).strip()
341 assert len(l) == 0, ("blank line not found %s" % l)
342
343 # get special regs
344 li = get_indented_lines(lines, pagename)
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 if len(l) != 0 and not l.strip().startswith('<!--'):
355 break
356
357 def add_op(self, o, d):
358 opcode, regs = o[0], o[1:]
359 op = copy(d)
360 op['regs'] = regs
361 op['opcode'] = opcode
362 if opcode in self.instr:
363 raise IOError("Duplicate instruction: %s\nin both %s and %s" % (
364 opcode, self.instr[opcode].page, op['page']))
365 self.instr[opcode] = Ops(**op)
366
367 # create list of instructions by form
368 form = op['form']
369 fl = self.forms.get(form, [])
370 self.forms[form] = fl + [opcode]
371
372 # create list of instructions by page
373 page = op['page']
374 pl = self.page.get(page, [])
375 self.page[page] = pl + [opcode]
376
377 def pprint_ops(self):
378 for k, v in self.instr.items():
379 print("# %s %s" % (v.opcode, v.desc))
380 print("Form: %s Regs: %s" % (v.form, v.regs))
381 print('\n'.join(map(lambda x: " %s" % x, v.pcode)))
382 print("Specials")
383 print('\n'.join(map(lambda x: " %s" % x, v.sregs)))
384 print()
385 for k, v in isa.forms.items():
386 print(k, v)
387
388
389 if __name__ == '__main__':
390 isa = ISA()
391 isa.pprint_ops()
392 # example on how to access cmp regs:
393 print ("cmp regs:", isa.instr["cmp"].regs)