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