micro_asm.py revision 4483:1e62824dcc3d
1# Copyright (c) 2003-2005 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Gabe Black
28
29import os
30import sys
31import re
32import string
33import traceback
34# get type names
35from types import *
36
37# Prepend the directory where the PLY lex & yacc modules are found
38# to the search path.
39sys.path[0:0] = [os.environ['M5_PLY']]
40
41from ply import lex
42from ply import yacc
43
44##########################################################################
45#
46# Base classes for use outside of the assembler
47#
48##########################################################################
49
50class Micro_Container(object):
51    def __init__(self, name):
52        self.microops = []
53        self.name = name
54        self.directives = {}
55        self.micro_classes = {}
56        self.labels = {}
57
58    def add_microop(self, microop):
59        self.microops.append(microop)
60
61    def __str__(self):
62        string = "%s:\n" % self.name
63        for microop in self.microops:
64            string += "  %s\n" % microop
65        return string
66
67class Macroop(Micro_Container):
68    pass
69
70class Rom(Micro_Container):
71    def __init__(self, name):
72        super(Rom, self).__init__(name)
73        self.externs = {}
74
75##########################################################################
76#
77# Support classes
78#
79##########################################################################
80
81class Label(object):
82    def __init__(self):
83        self.extern = False
84        self.name = ""
85
86class Block(object):
87    def __init__(self):
88        self.statements = []
89
90class Statement(object):
91    def __init__(self):
92        self.is_microop = False
93        self.is_directive = False
94
95class Microop(Statement):
96    def __init__(self):
97        super(Microop, self).__init__()
98        self.mnemonic = ""
99        self.labels = []
100        self.is_microop = True
101        self.params = ""
102
103class Directive(Statement):
104    def __init__(self):
105        super(Directive, self).__init__()
106        self.name = ""
107        self.is_directive = True
108
109##########################################################################
110#
111# Functions that handle common tasks
112#
113##########################################################################
114
115def print_error(message):
116    print
117    print "*** %s" % message
118    print
119
120def handle_statement(parser, container, statement):
121    if statement.is_microop:
122        try:
123            microop = eval('parser.microops[statement.mnemonic](%s)' %
124                    statement.params)
125        except:
126            print_error("Error creating microop object.")
127            raise
128        try:
129            for label in statement.labels:
130                container.labels[label.name] = microop
131                if label.extern:
132                    container.externs[label.name] = microop
133            container.add_microop(microop)
134        except:
135            print_error("Error adding microop.")
136            raise
137    elif statement.is_directive:
138        try:
139            eval('container.%s()' % statement.name)
140        except:
141            print_error("Error executing directive.")
142            print container.directives
143            raise
144    else:
145        raise Exception, "Didn't recognize the type of statement", statement
146
147##########################################################################
148#
149# Lexer specification
150#
151##########################################################################
152
153# Error handler.  Just call exit.  Output formatted to work under
154# Emacs compile-mode.  Optional 'print_traceback' arg, if set to True,
155# prints a Python stack backtrace too (can be handy when trying to
156# debug the parser itself).
157def error(lineno, string, print_traceback = False):
158    # Print a Python stack backtrace if requested.
159    if (print_traceback):
160        traceback.print_exc()
161    if lineno != 0:
162        line_str = "%d:" % lineno
163    else:
164        line_str = ""
165    sys.exit("%s %s" % (line_str, string))
166
167reserved = ('DEF', 'MACROOP', 'ROM', 'EXTERN')
168
169tokens = reserved + (
170        # identifier
171        'ID',
172        # arguments for microops and directives
173        'PARAMS',
174
175        'LPAREN', 'RPAREN',
176        'LBRACE', 'RBRACE',
177        #'COMMA',
178        'COLON', 'SEMI', 'DOT',
179        'NEWLINE'
180        )
181
182# New lines are ignored at the top level, but they end statements in the
183# assembler
184states = (
185    ('asm', 'exclusive'),
186    ('params', 'exclusive'),
187)
188
189reserved_map = { }
190for r in reserved:
191    reserved_map[r.lower()] = r
192
193def t_params_COLON(t):
194    r':'
195    t.lexer.begin('asm')
196    return t
197
198def t_asm_ID(t):
199    r'[A-Za-z_]\w*'
200    t.type = reserved_map.get(t.value, 'ID')
201    t.lexer.begin('params')
202    return t
203
204def t_ANY_ID(t):
205    r'[A-Za-z_]\w*'
206    t.type = reserved_map.get(t.value, 'ID')
207    return t
208
209def t_params_PARAMS(t):
210    r'([^\n;]|((?<=\\)[\n;]))+'
211    t.lineno += t.value.count('\n')
212    t.lexer.begin('asm')
213    return t
214
215def t_INITIAL_LBRACE(t):
216    r'\{'
217    t.lexer.begin('asm')
218    return t
219
220def t_asm_RBRACE(t):
221    r'\}'
222    t.lexer.begin('INITIAL')
223    return t
224
225def t_INITIAL_NEWLINE(t):
226    r'\n+'
227    t.lineno += t.value.count('\n')
228
229def t_asm_NEWLINE(t):
230    r'\n+'
231    t.lineno += t.value.count('\n')
232    return t
233
234def t_params_NEWLINE(t):
235    r'\n+'
236    t.lineno += t.value.count('\n')
237    t.lexer.begin('asm')
238    return t
239
240def t_params_SEMI(t):
241    r';'
242    t.lexer.begin('asm')
243    return t
244
245# Basic regular expressions to pick out simple tokens
246t_ANY_LPAREN = r'\('
247t_ANY_RPAREN = r'\)'
248#t_COMMA  = r','
249t_ANY_SEMI   = r';'
250t_ANY_DOT    = r'\.'
251
252t_ANY_ignore = ' \t\x0c'
253
254def t_ANY_error(t):
255    error(t.lineno, "illegal character '%s'" % t.value[0])
256    t.skip(1)
257
258##########################################################################
259#
260# Parser specification
261#
262##########################################################################
263
264# Start symbol for a file which may have more than one macroop or rom
265# specification.
266def p_file(t):
267    'file : opt_rom_or_macros'
268
269def p_opt_rom_or_macros_0(t):
270    'opt_rom_or_macros : '
271
272def p_opt_rom_or_macros_1(t):
273    'opt_rom_or_macros : rom_or_macros'
274
275def p_rom_or_macros_0(t):
276    'rom_or_macros : rom_or_macro'
277
278def p_rom_or_macros_1(t):
279    'rom_or_macros : rom_or_macros rom_or_macro'
280
281def p_rom_or_macro_0(t):
282    '''rom_or_macro : rom_block'''
283
284def p_rom_or_macro_1(t):
285    '''rom_or_macro : macroop_def'''
286
287# A block of statements
288def p_block(t):
289    'block : LBRACE statements RBRACE'
290    block = Block()
291    block.statements = t[2]
292    t[0] = block
293
294# Defines a section of microcode that should go in the current ROM
295def p_rom_block(t):
296    'rom_block : DEF ROM block SEMI'
297    for statement in t[3].statements:
298        handle_statement(t.parser, t.parser.rom, statement)
299    t[0] = t.parser.rom
300
301# Defines a macroop that jumps to an external label in the ROM
302def p_macroop_def_0(t):
303    'macroop_def : DEF MACROOP LPAREN ID RPAREN SEMI'
304    t[0] = t[4]
305
306# Defines a macroop that is combinationally generated
307def p_macroop_def_1(t):
308    'macroop_def : DEF MACROOP ID block SEMI'
309    try:
310        curop = t.parser.macro_type(t[3])
311    except TypeError:
312        print_error("Error creating macroop object.")
313        raise
314    for statement in t[4].statements:
315        handle_statement(t.parser, curop, statement)
316    t.parser.macroops.append(curop)
317
318def p_statements_0(t):
319    'statements : statement'
320    if t[1]:
321        t[0] = [t[1]]
322    else:
323        t[0] = []
324
325def p_statements_1(t):
326    'statements : statements statement'
327    if t[2]:
328        t[1].append(t[2])
329    t[0] = t[1]
330
331def p_statement(t):
332    'statement : content_of_statement end_of_statement'
333    t[0] = t[1]
334
335# A statement can be a microop or an assembler directive
336def p_content_of_statement_0(t):
337    '''content_of_statement : microop
338                            | directive'''
339    t[0] = t[1]
340
341def p_content_of_statement_1(t):
342    'content_of_statement : '
343    pass
344
345# Statements are ended by newlines or a semi colon
346def p_end_of_statement(t):
347    '''end_of_statement : NEWLINE
348                        | SEMI'''
349    pass
350
351def p_microop_0(t):
352    'microop : labels ID'
353    microop = Microop()
354    microop.labels = t[1]
355    microop.mnemonic = t[2]
356    t[0] = microop
357
358def p_microop_1(t):
359    'microop : ID'
360    microop = Microop()
361    microop.mnemonic = t[1]
362    t[0] = microop
363
364def p_microop_2(t):
365    'microop : labels ID PARAMS'
366    microop = Microop()
367    microop.labels = t[1]
368    microop.mnemonic = t[2]
369    microop.params = t[3]
370    t[0] = microop
371
372def p_microop_3(t):
373    'microop : ID PARAMS'
374    microop = Microop()
375    microop.mnemonic = t[1]
376    microop.params = t[2]
377    t[0] = microop
378
379def p_labels_0(t):
380    'labels : label'
381    t[0] = [t[1]]
382
383def p_labels_1(t):
384    'labels : labels label'
385    t[1].append(t[2])
386    t[0] = t[1]
387
388def p_label_0(t):
389    'label : ID COLON'
390    label = Label()
391    label.is_extern = False
392    label.text = t[1]
393    t[0] = label
394
395def p_label_1(t):
396    'label : EXTERN ID COLON'
397    label = Label()
398    label.is_extern = True
399    label.text = t[2]
400    t[0] = label
401
402def p_directive(t):
403    'directive : DOT ID'
404    directive = Directive()
405    directive.name = t[2]
406    t[0] = directive
407
408# Parse error handler.  Note that the argument here is the offending
409# *token*, not a grammar symbol (hence the need to use t.value)
410def p_error(t):
411    if t:
412        error(t.lineno, "syntax error at '%s'" % t.value)
413    else:
414        error(0, "unknown syntax error", True)
415
416class MicroAssembler(object):
417
418    def __init__(self, macro_type, microops, rom):
419        self.lexer = lex.lex()
420        self.parser = yacc.yacc()
421        self.parser.macro_type = macro_type
422        self.parser.macroops = []
423        self.parser.microops = microops
424        self.parser.rom = rom
425
426    def assemble(self, asm):
427        self.parser.parse(asm, lexer=self.lexer)
428        for macroop in self.parser.macroops:
429            print macroop
430        print self.parser.rom
431        macroops = self.parser.macroops
432        self.parser.macroops = []
433        return macroops
434