1 # Copyright (c) 2003-2005 The Regents of The University of Michigan
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.
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.
27 from __future__
import print_function
40 ##########################################################################
42 # Base classes for use outside of the assembler
44 ##########################################################################
46 class Micro_Container(object):
47 def __init__(self
, name
):
51 self
.micro_classes
= {}
54 def add_microop(self
, mnemonic
, microop
):
55 self
.microops
.append(microop
)
58 string
= "%s:\n" % self
.name
59 for microop
in self
.microops
:
60 string
+= " %s\n" % microop
63 class Combinational_Macroop(Micro_Container
):
66 class Rom_Macroop(object):
67 def __init__(self
, name
, target
):
72 return "%s: %s\n" % (self
.name
, self
.target
)
74 class Rom(Micro_Container
):
75 def __init__(self
, name
):
76 super(Rom
, self
).__init
__(name
)
79 ##########################################################################
83 ##########################################################################
94 class Statement(object):
96 self
.is_microop
= False
97 self
.is_directive
= False
100 class Microop(Statement
):
102 super(Microop
, self
).__init
__()
105 self
.is_microop
= True
107 class Directive(Statement
):
109 super(Directive
, self
).__init
__()
111 self
.is_directive
= True
113 ##########################################################################
115 # Functions that handle common tasks
117 ##########################################################################
119 def print_error(message
):
121 print("*** %s" % message
)
124 def handle_statement(parser
, container
, statement
):
125 if statement
.is_microop
:
126 if statement
.mnemonic
not in parser
.microops
.keys():
127 raise Exception, "Unrecognized mnemonic: %s" % statement
.mnemonic
128 parser
.symbols
["__microopClassFromInsideTheAssembler"] = \
129 parser
.microops
[statement
.mnemonic
]
131 microop
= eval('__microopClassFromInsideTheAssembler(%s)' %
132 statement
.params
, {}, parser
.symbols
)
134 print_error("Error creating microop object with mnemonic %s." % \
138 for label
in statement
.labels
:
139 container
.labels
[label
.text
] = microop
141 container
.externs
[label
.text
] = microop
142 container
.add_microop(statement
.mnemonic
, microop
)
144 print_error("Error adding microop.")
146 elif statement
.is_directive
:
147 if statement
.name
not in container
.directives
.keys():
148 raise Exception, "Unrecognized directive: %s" % statement
.name
149 parser
.symbols
["__directiveFunctionFromInsideTheAssembler"] = \
150 container
.directives
[statement
.name
]
152 eval('__directiveFunctionFromInsideTheAssembler(%s)' %
153 statement
.params
, {}, parser
.symbols
)
155 print_error("Error executing directive.")
156 print(container
.directives
)
159 raise Exception, "Didn't recognize the type of statement", statement
161 ##########################################################################
163 # Lexer specification
165 ##########################################################################
167 # Error handler. Just call exit. Output formatted to work under
168 # Emacs compile-mode. Optional 'print_traceback' arg, if set to True,
169 # prints a Python stack backtrace too (can be handy when trying to
170 # debug the parser itself).
171 def error(lineno
, string
, print_traceback
= False):
172 # Print a Python stack backtrace if requested.
173 if (print_traceback
):
174 traceback
.print_exc()
176 line_str
= "%d:" % lineno
179 sys
.exit("%s %s" % (line_str
, string
))
181 reserved
= ('DEF', 'MACROOP', 'ROM', 'EXTERN')
183 tokens
= reserved
+ (
186 # arguments for microops and directives
191 'COLON', 'SEMI', 'DOT',
195 # New lines are ignored at the top level, but they end statements in the
198 ('asm', 'exclusive'),
199 ('params', 'exclusive'),
204 reserved_map
[r
.lower()] = r
207 def t_ANY_COMMENT(t
):
210 def t_ANY_MULTILINECOMMENT(t
):
211 r
'/\*([^/]|((?<!\*)/))*\*/'
213 # A colon marks the end of a label. It should follow an ID which will
214 # put the lexer in the "params" state. Seeing the colon will put it back
215 # in the "asm" state since it knows it saw a label and not a mnemonic.
216 def t_params_COLON(t
):
221 # Parameters are a string of text which don't contain an unescaped statement
222 # statement terminator, ie a newline or semi colon.
223 def t_params_PARAMS(t
):
224 r
'([^\n;\\]|(\\[\n;\\]))+'
225 t
.lineno
+= t
.value
.count('\n')
226 unescapeParamsRE
= re
.compile(r
'(\\[\n;\\])')
227 def unescapeParams(mo
):
230 t
.value
= unescapeParamsRE
.sub(unescapeParams
, t
.value
)
234 # An "ID" in the micro assembler is either a label, directive, or mnemonic
235 # If it's either a directive or a mnemonic, it will be optionally followed by
236 # parameters. If it's a label, the following colon will make the lexer stop
237 # looking for parameters.
240 t
.type = reserved_map
.get(t
.value
, 'ID')
241 # If the ID is really "extern", we shouldn't start looking for parameters
242 # yet. The real ID, the label itself, is coming up.
243 if t
.type != 'EXTERN':
244 t
.lexer
.begin('params')
247 # If there is a label and you're -not- in the assembler (which would be caught
248 # above), don't start looking for parameters.
251 t
.type = reserved_map
.get(t
.value
, 'ID')
254 # Braces enter and exit micro assembly
255 def t_INITIAL_LBRACE(t
):
262 t
.lexer
.begin('INITIAL')
265 # At the top level, keep track of newlines only for line counting.
266 def t_INITIAL_NEWLINE(t
):
268 t
.lineno
+= t
.value
.count('\n')
270 # In the micro assembler, do line counting but also return a token. The
271 # token is needed by the parser to detect the end of a statement.
272 def t_asm_NEWLINE(t
):
274 t
.lineno
+= t
.value
.count('\n')
277 # A newline or semi colon when looking for params signals that the statement
278 # is over and the lexer should go back to looking for regular assembly.
279 def t_params_NEWLINE(t
):
281 t
.lineno
+= t
.value
.count('\n')
285 def t_params_SEMI(t
):
290 # Basic regular expressions to pick out simple tokens
296 t_ANY_ignore
= ' \t\x0c'
299 error(t
.lineno
, "illegal character '%s'" % t
.value
[0])
302 ##########################################################################
304 # Parser specification
306 ##########################################################################
308 # Start symbol for a file which may have more than one macroop or rom
311 'file : opt_rom_or_macros'
313 def p_opt_rom_or_macros_0(t
):
314 'opt_rom_or_macros : '
316 def p_opt_rom_or_macros_1(t
):
317 'opt_rom_or_macros : rom_or_macros'
319 def p_rom_or_macros_0(t
):
320 'rom_or_macros : rom_or_macro'
322 def p_rom_or_macros_1(t
):
323 'rom_or_macros : rom_or_macros rom_or_macro'
325 def p_rom_or_macro_0(t
):
326 '''rom_or_macro : rom_block
329 # Defines a section of microcode that should go in the current ROM
331 'rom_block : DEF ROM block SEMI'
333 print_error("Rom block found, but no Rom object specified.")
334 raise TypeError, "Rom block found, but no Rom object was specified."
335 for statement
in t
[3].statements
:
336 handle_statement(t
.parser
, t
.parser
.rom
, statement
)
339 # Defines a macroop that jumps to an external label in the ROM
340 def p_macroop_def_0(t
):
341 'macroop_def : DEF MACROOP ID LPAREN ID RPAREN SEMI'
342 if not t
.parser
.rom_macroop_type
:
343 print_error("ROM based macroop found, but no ROM macroop class was specified.")
344 raise TypeError, "ROM based macroop found, but no ROM macroop class was specified."
345 macroop
= t
.parser
.rom_macroop_type(t
[3], t
[5])
346 t
.parser
.macroops
[t
[3]] = macroop
349 # Defines a macroop that is combinationally generated
350 def p_macroop_def_1(t
):
351 'macroop_def : DEF MACROOP ID block SEMI'
353 curop
= t
.parser
.macro_type(t
[3])
355 print_error("Error creating macroop object.")
357 for statement
in t
[4].statements
:
358 handle_statement(t
.parser
, curop
, statement
)
359 t
.parser
.macroops
[t
[3]] = curop
361 # A block of statements
363 'block : LBRACE statements RBRACE'
365 block
.statements
= t
[2]
368 def p_statements_0(t
):
369 'statements : statement'
375 def p_statements_1(t
):
376 'statements : statements statement'
382 'statement : content_of_statement end_of_statement'
385 # A statement can be a microop or an assembler directive
386 def p_content_of_statement_0(t
):
387 '''content_of_statement : microop
391 # Ignore empty statements
392 def p_content_of_statement_1(t
):
393 'content_of_statement : '
396 # Statements are ended by newlines or a semi colon
397 def p_end_of_statement(t
):
398 '''end_of_statement : NEWLINE
402 # Different flavors of microop to avoid shift/reduce errors
404 'microop : labels ID'
406 microop
.labels
= t
[1]
407 microop
.mnemonic
= t
[2]
413 microop
.mnemonic
= t
[1]
417 'microop : labels ID PARAMS'
419 microop
.labels
= t
[1]
420 microop
.mnemonic
= t
[2]
421 microop
.params
= t
[3]
425 'microop : ID PARAMS'
427 microop
.mnemonic
= t
[1]
428 microop
.params
= t
[2]
431 # Labels in the microcode
437 'labels : labels label'
441 # labels on lines by themselves are attached to the following instruction.
443 'labels : labels NEWLINE'
449 label
.is_extern
= False
454 'label : EXTERN ID COLON'
456 label
.is_extern
= True
460 # Directives for the macroop
461 def p_directive_0(t
):
463 directive
= Directive()
464 directive
.name
= t
[2]
467 def p_directive_1(t
):
468 'directive : DOT ID PARAMS'
469 directive
= Directive()
470 directive
.name
= t
[2]
471 directive
.params
= t
[3]
474 # Parse error handler. Note that the argument here is the offending
475 # *token*, not a grammar symbol (hence the need to use t.value)
478 error(t
.lineno
, "syntax error at '%s'" % t
.value
)
480 error(0, "unknown syntax error", True)
482 class MicroAssembler(object):
484 def __init__(self
, macro_type
, microops
,
485 rom
= None, rom_macroop_type
= None):
486 self
.lexer
= lex
.lex()
487 self
.parser
= yacc
.yacc()
488 self
.parser
.macro_type
= macro_type
489 self
.parser
.macroops
= {}
490 self
.parser
.microops
= microops
491 self
.parser
.rom
= rom
492 self
.parser
.rom_macroop_type
= rom_macroop_type
493 self
.parser
.symbols
= {}
494 self
.symbols
= self
.parser
.symbols
496 def assemble(self
, asm
):
497 self
.parser
.parse(asm
, lexer
=self
.lexer
)
498 macroops
= self
.parser
.macroops
499 self
.parser
.macroops
= {}