micro_asm.py revision 4507:487b70cfd58d
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 Combinational_Macroop(Micro_Container):
68    pass
69
70class Rom_Macroop(object):
71    def __init__(self, name, target):
72        self.name = name
73        self.target = target
74
75class Rom(Micro_Container):
76    def __init__(self, name):
77        super(Rom, self).__init__(name)
78        self.externs = {}
79
80##########################################################################
81#
82# Support classes
83#
84##########################################################################
85
86class Label(object):
87    def __init__(self):
88        self.extern = False
89        self.name = ""
90
91class Block(object):
92    def __init__(self):
93        self.statements = []
94
95class Statement(object):
96    def __init__(self):
97        self.is_microop = False
98        self.is_directive = False
99        self.params = ""
100
101class Microop(Statement):
102    def __init__(self):
103        super(Microop, self).__init__()
104        self.mnemonic = ""
105        self.labels = []
106        self.is_microop = True
107
108class Directive(Statement):
109    def __init__(self):
110        super(Directive, self).__init__()
111        self.name = ""
112        self.is_directive = True
113
114##########################################################################
115#
116# Functions that handle common tasks
117#
118##########################################################################
119
120def print_error(message):
121    print
122    print "*** %s" % message
123    print
124
125def handle_statement(parser, container, statement):
126    if statement.is_microop:
127        try:
128            microop = eval('parser.microops[statement.mnemonic](%s)' %
129                    statement.params)
130        except:
131            print_error("Error creating microop object.")
132            raise
133        try:
134            for label in statement.labels:
135                container.labels[label.name] = microop
136                if label.extern:
137                    container.externs[label.name] = microop
138            container.add_microop(microop)
139        except:
140            print_error("Error adding microop.")
141            raise
142    elif statement.is_directive:
143        try:
144            eval('container.directives[statement.name](%s)' % statement.params)
145        except:
146            print_error("Error executing directive.")
147            print container.directives
148            raise
149    else:
150        raise Exception, "Didn't recognize the type of statement", statement
151
152##########################################################################
153#
154# Lexer specification
155#
156##########################################################################
157
158# Error handler.  Just call exit.  Output formatted to work under
159# Emacs compile-mode.  Optional 'print_traceback' arg, if set to True,
160# prints a Python stack backtrace too (can be handy when trying to
161# debug the parser itself).
162def error(lineno, string, print_traceback = False):
163    # Print a Python stack backtrace if requested.
164    if (print_traceback):
165        traceback.print_exc()
166    if lineno != 0:
167        line_str = "%d:" % lineno
168    else:
169        line_str = ""
170    sys.exit("%s %s" % (line_str, string))
171
172reserved = ('DEF', 'MACROOP', 'ROM', 'EXTERN')
173
174tokens = reserved + (
175        # identifier
176        'ID',
177        # arguments for microops and directives
178        'PARAMS',
179
180        'LPAREN', 'RPAREN',
181        'LBRACE', 'RBRACE',
182        'COLON', 'SEMI', 'DOT',
183        'NEWLINE'
184        )
185
186# New lines are ignored at the top level, but they end statements in the
187# assembler
188states = (
189    ('asm', 'exclusive'),
190    ('params', 'exclusive'),
191)
192
193reserved_map = { }
194for r in reserved:
195    reserved_map[r.lower()] = r
196
197def t_ANY_COMMENT(t):
198    r'\#[^\n]*(?=\n)'
199    #print "t_ANY_COMMENT %s" % t.value
200
201def t_ANY_MULTILINECOMMENT(t):
202    r'/\*([^/]|((?<!\*)/))*\*/'
203    #print "t_ANY_MULTILINECOMMENT %s" % t.value
204
205def t_params_COLON(t):
206    r':'
207    t.lexer.begin('asm')
208    #print "t_params_COLON %s" % t.value
209    return t
210
211def t_asm_ID(t):
212    r'[A-Za-z_]\w*'
213    t.type = reserved_map.get(t.value, 'ID')
214    t.lexer.begin('params')
215    #print "t_asm_ID %s" % t.value
216    return t
217
218def t_ANY_ID(t):
219    r'[A-Za-z_]\w*'
220    t.type = reserved_map.get(t.value, 'ID')
221    #print "t_ANY_ID %s" % t.value
222    return t
223
224def t_params_PARAMS(t):
225    r'([^\n;]|((?<=\\)[\n;]))+'
226    t.lineno += t.value.count('\n')
227    t.lexer.begin('asm')
228    #print "t_params_PARAMS %s" % t.value
229    return t
230
231def t_INITIAL_LBRACE(t):
232    r'\{'
233    t.lexer.begin('asm')
234    #print "t_INITIAL_LBRACE %s" % t.value
235    return t
236
237def t_asm_RBRACE(t):
238    r'\}'
239    t.lexer.begin('INITIAL')
240    #print "t_asm_RBRACE %s" % t.value
241    return t
242
243def t_INITIAL_NEWLINE(t):
244    r'\n+'
245    t.lineno += t.value.count('\n')
246    #print "t_INITIAL_NEWLINE %s" % t.value
247
248def t_asm_NEWLINE(t):
249    r'\n+'
250    t.lineno += t.value.count('\n')
251    #print "t_asm_NEWLINE %s" % t.value
252    return t
253
254def t_params_NEWLINE(t):
255    r'\n+'
256    t.lineno += t.value.count('\n')
257    t.lexer.begin('asm')
258    #print "t_params_NEWLINE %s" % t.value
259    return t
260
261def t_params_SEMI(t):
262    r';'
263    t.lexer.begin('asm')
264    #print "t_params_SEMI %s" % t.value
265    return t
266
267# Basic regular expressions to pick out simple tokens
268t_ANY_LPAREN = r'\('
269t_ANY_RPAREN = r'\)'
270t_ANY_SEMI   = r';'
271t_ANY_DOT    = r'\.'
272
273t_ANY_ignore = ' \t\x0c'
274
275def t_ANY_error(t):
276    error(t.lineno, "illegal character '%s'" % t.value[0])
277    t.skip(1)
278
279##########################################################################
280#
281# Parser specification
282#
283##########################################################################
284
285# Start symbol for a file which may have more than one macroop or rom
286# specification.
287def p_file(t):
288    'file : opt_rom_or_macros'
289
290def p_opt_rom_or_macros_0(t):
291    'opt_rom_or_macros : '
292
293def p_opt_rom_or_macros_1(t):
294    'opt_rom_or_macros : rom_or_macros'
295
296def p_rom_or_macros_0(t):
297    'rom_or_macros : rom_or_macro'
298
299def p_rom_or_macros_1(t):
300    'rom_or_macros : rom_or_macros rom_or_macro'
301
302def p_rom_or_macro_0(t):
303    '''rom_or_macro : rom_block'''
304
305def p_rom_or_macro_1(t):
306    '''rom_or_macro : macroop_def'''
307
308# A block of statements
309def p_block(t):
310    'block : LBRACE statements RBRACE'
311    block = Block()
312    block.statements = t[2]
313    t[0] = block
314
315# Defines a section of microcode that should go in the current ROM
316def p_rom_block(t):
317    'rom_block : DEF ROM block SEMI'
318    if not t.parser.rom:
319        print_error("Rom block found, but no Rom object specified.")
320        raise TypeError, "Rom block found, but no Rom object was specified."
321    for statement in t[3].statements:
322        handle_statement(t.parser, t.parser.rom, statement)
323    t[0] = t.parser.rom
324
325# Defines a macroop that jumps to an external label in the ROM
326def p_macroop_def_0(t):
327    'macroop_def : DEF MACROOP ID LPAREN ID RPAREN SEMI'
328    if not t.parser.rom_macroop_type:
329        print_error("ROM based macroop found, but no ROM macroop class was specified.")
330        raise TypeError, "ROM based macroop found, but no ROM macroop class was specified."
331    macroop = t.parser.rom_macroop_type(t[3], t[5])
332    t[0] = macroop
333
334
335# Defines a macroop that is combinationally generated
336def p_macroop_def_1(t):
337    'macroop_def : DEF MACROOP ID block SEMI'
338    try:
339        curop = t.parser.macro_type(t[3])
340    except TypeError:
341        print_error("Error creating macroop object.")
342        raise
343    for statement in t[4].statements:
344        handle_statement(t.parser, curop, statement)
345    t.parser.macroops[t[3]] = curop
346
347def p_statements_0(t):
348    'statements : statement'
349    if t[1]:
350        t[0] = [t[1]]
351    else:
352        t[0] = []
353
354def p_statements_1(t):
355    'statements : statements statement'
356    if t[2]:
357        t[1].append(t[2])
358    t[0] = t[1]
359
360def p_statement(t):
361    'statement : content_of_statement end_of_statement'
362    t[0] = t[1]
363
364# A statement can be a microop or an assembler directive
365def p_content_of_statement_0(t):
366    '''content_of_statement : microop
367                            | directive'''
368    t[0] = t[1]
369
370def p_content_of_statement_1(t):
371    'content_of_statement : '
372    pass
373
374# Statements are ended by newlines or a semi colon
375def p_end_of_statement(t):
376    '''end_of_statement : NEWLINE
377                        | SEMI'''
378    pass
379
380def p_microop_0(t):
381    'microop : labels ID'
382    microop = Microop()
383    microop.labels = t[1]
384    microop.mnemonic = t[2]
385    t[0] = microop
386
387def p_microop_1(t):
388    'microop : ID'
389    microop = Microop()
390    microop.mnemonic = t[1]
391    t[0] = microop
392
393def p_microop_2(t):
394    'microop : labels ID PARAMS'
395    microop = Microop()
396    microop.labels = t[1]
397    microop.mnemonic = t[2]
398    microop.params = t[3]
399    t[0] = microop
400
401def p_microop_3(t):
402    'microop : ID PARAMS'
403    microop = Microop()
404    microop.mnemonic = t[1]
405    microop.params = t[2]
406    t[0] = microop
407
408def p_labels_0(t):
409    'labels : label'
410    t[0] = [t[1]]
411
412def p_labels_1(t):
413    'labels : labels label'
414    t[1].append(t[2])
415    t[0] = t[1]
416
417def p_label_0(t):
418    'label : ID COLON'
419    label = Label()
420    label.is_extern = False
421    label.text = t[1]
422    t[0] = label
423
424def p_label_1(t):
425    'label : EXTERN ID COLON'
426    label = Label()
427    label.is_extern = True
428    label.text = t[2]
429    t[0] = label
430
431def p_directive_0(t):
432    'directive : DOT ID'
433    directive = Directive()
434    directive.name = t[2]
435    t[0] = directive
436
437def p_directive_1(t):
438    'directive : DOT ID PARAMS'
439    directive = Directive()
440    directive.name = t[2]
441    directive.params = t[3]
442    t[0] = directive
443
444# Parse error handler.  Note that the argument here is the offending
445# *token*, not a grammar symbol (hence the need to use t.value)
446def p_error(t):
447    if t:
448        error(t.lineno, "syntax error at '%s'" % t.value)
449    else:
450        error(0, "unknown syntax error", True)
451
452class MicroAssembler(object):
453
454    def __init__(self, macro_type, microops,
455            rom = None, rom_macroop_type = None):
456        self.lexer = lex.lex()
457        self.parser = yacc.yacc()
458        self.parser.macro_type = macro_type
459        self.parser.macroops = {}
460        self.parser.microops = microops
461        self.parser.rom = rom
462        self.parser.rom_macroop_type = rom_macroop_type
463
464    def assemble(self, asm):
465        self.parser.parse(asm, lexer=self.lexer)
466        # Begin debug printing
467        for macroop in self.parser.macroops.values():
468            print macroop
469        print self.parser.rom
470        # End debug printing
471        macroops = self.parser.macroops
472        self.parser.macroops = {}
473        return macroops
474