def _gen_input_to_array(ports): if isinstance(ports[0], (Wire, InPort, OutPort)): stmts = ' wire [{:4}:0] {}[0:{}];\n' \ .format(ports[0].nbits-1, ports.name, len(ports)-1) for i, port in enumerate(ports): stmts += ' assign {1}[{2:3}] = {0};\n' \ .format(signal_to_str(port, None, model), ports.name, i) elif isinstance(ports[0], (PortList, WireList)): if not isinstance(ports[0][0], (Wire, InPort, OutPort)): raise VerilogTranslationError( 'Port lists with more than 2 dimenstions are not supported!' ) stmts = ' wire [{bw:4}:0] {name}[0:{sz0}][0:{sz1}];\n' \ .format( bw = ports[0][0].nbits-1, name = ports.name, sz0 = len(ports)-1, sz1 = len(ports[0])-1 ) for i, plist in enumerate(ports): for j, port in enumerate(plist): stmts += ' assign {1}[{2:3}][{3:3}] = {0};\n' \ .format(signal_to_str(port, None, model), ports.name, i, j) else: raise VerilogTranslationError( 'An internal error occured when translating array accesses!\n' 'Expected a list of Wires or Ports, instead got: {}'.format( type(ports[0]))) return stmts
def visit_Slice(self, node): if node.step: raise VerilogTranslationError( 'An unexpected error when translating Slices was encountered!\n' 'Please inform the PyMTL developers!', node.lineno) # handle open slices [:upper], [lower:], and [:] if node.lower == None: node.lower = ast.Num(0) if node.upper == None: node.upper = ast.Num(node._nbits) # Special handling of variable part-selects # TODO: make this more resilient? # http://www.sutherland-hdl.com/papers/2000-HDLCon-paper_Verilog-2000.pdf if isinstance(node.upper, _ast.BinOp): if not isinstance(node.upper.op, (_ast.Add, _ast.Sub)): raise VerilogTranslationError( 'Slicing in behavioral blocks cannot contain arbitrary arithmetic!\n' 'Variable slices must be of the form [x:x+N] or [x:x-N]!\n' '(and N must be constant!)\n', node.lineno) lower = self.visit(node.lower) left_op = self.visit(node.upper.left) right_num = node.upper.right if lower != left_op: raise VerilogTranslationError( 'Slicing in behavioral blocks cannot contain arbitrary arithmetic!\n' 'Variable slices must be of the form [x:x+N] or [x:x-N]!\n' '(and N must be constant!)\n', node.lineno) # Determine the width of the part-select. # FIXME: elif needed b/c Num AST nodes dont have _objects... if hasattr(right_num, '_object'): width = right_num._object elif isinstance(node.upper.right, _ast.Num): width = right_num.n else: raise VerilogTranslationError( 'Slicing in behavioral blocks cannot contain arbitrary arithmetic!\n' 'Variable slices must be of the form [x:x+N] or [x:x-N]!\n' '(and N must be constant!)\n', node.lineno) # Widths for part selects can't be 0 (ie. select a single bit), work # around this limitation by converting into a normal index access. if width == 1: return '{}'.format(lower) else: op = opmap[type(node.upper.op)] upper = self.visit(node.upper.right) return '{} {}: {}'.format(lower, op, upper) # Normal slices lower = self.visit(node.lower) upper = self.visit(node.upper) return '({})-1:{}'.format(upper, lower)
def visit_For( self, node ): self.generic_visit( node ) if not ( isinstance( node.iter, _ast.Call ) and isinstance( node.iter.func, _ast.Name ) and node.iter.func.id in ['range', 'xrange'] ): raise VerilogTranslationError( 'For loops are only translatable when using range or xrange!\n' 'Please use "for i in range(...)/xrange(...)".', node.lineno ) call = node.iter if len( call.args ) == 1: start = _ast.Num( n=0 ) stop = call.args[0] step = _ast.Num( n=1 ) elif len( call.args ) == 2: start = call.args[0] stop = call.args[1] step = _ast.Num( n=1 ) # TODO: should be an expression elif len( call.args ) == 3: start = call.args[0] stop = call.args[1] step = call.args[2] else: raise VerilogTranslationError( 'An invalid number of arguments provided to (x)range function!\n', node.lineno ) # Must know if the step is negative or positive in order to set the # correct bound check. This is because of Python's range behavior. try: if hasattr( step, '_object' ): step_val = step._object elif hasattr( step, 'n' ): step_val = step.n assert step_val != 0 except (UnboundLocalError,AssertionError): raise VerilogTranslationError( 'An error occurred when translating a "for loop"!\n' 'The "step" parameter to range must be a constant integer value != 0!', node.lineno ) node.iter = _ast.Slice( lower=start, upper=stop, step=step ) node.iter.lt_gt = '<' if step_val > 0 else '>' return node
def visit_Assign( self, node ): if len(node.targets) != 1: raise VerilogTranslationError( 'Chained assignments are not supported!\n' 'Please modify "x = y = ..." to be two separate lines.', node.lineno ) self._is_lhs = True self.visit( node.targets[0] ) self._is_lhs = False self.visit( node.value ) obj = node.targets[0]._object # NOTE: # - currently possible to have inferences with different bitwidths # - currently possible for a signal to be stored as both a reg and loopvar # handle this in verilog_structural.create_declarations if obj in self.arrayelms: return elif isinstance( obj, Signal ): self.store[ obj.fullname ] = obj elif isinstance( obj, tuple ): self.loopvar.add( obj[0] ) # FIXME: # - if one field of a bitstruct is assigned in a behavioral block, # the **entire** bitstruct is assumed to be a reg! elif isinstance( obj, _SignalSlice ): self.store[ obj._signal.fullname ] = obj._signal
def visit_Expr(self, node): raise VerilogTranslationError( 'An unexpected Expr node was encountered!\n' 'Please inform the PyMTL developers!', node.lineno ) return '{}{};\n'.format( self.indent, self.visit(node.value) )
def visit_For(self, node): if not (isinstance( node.iter, _ast.Slice ) or isinstance( node.target, _ast.Name )): raise VerilogTranslationError( 'An unexpected error occurred when translating a "for loop"!\n' 'Please inform the PyMTL developers!', node.lineno ) i = self.visit( node.target ) lower = self.visit( node.iter.lower ) upper = self.visit( node.iter.upper ) step = self.visit( node.iter.step ) lt_gt = node.iter.lt_gt body = self.fmt_body( node.body ) x = fmt(""" for ({i}={lower}; {i} {lt_gt} {upper}; {i}={i}+{step}) begin {body} end """, self.indent ).format( **locals() ) return x
def _gen_array_to_output( ports ): if isinstance( ports[0], (Wire,InPort,OutPort) ): # Annotate arrays if vannotate_arrays is available annotation = '' if model.vannotate_arrays: if ports.name in model.vannotate_arrays: annotation = model.vannotate_arrays[ports.name] + ' ' stmts = ' {}reg [{:4}:0] {}[0:{}];\n' \ .format(annotation, ports[0].nbits-1, ports.name, len(ports)-1) # Generate declarations gen_declarations = not model.vmark_as_bram # unless marked as BRAM if gen_declarations: for i, port in enumerate(ports): stmts += ' assign {0} = {1}[{2:3}];\n' \ .format(signal_to_str(port, None, model), ports.name, i) elif isinstance( ports[0], (PortList,WireList) ): if not isinstance( ports[0][0], (Wire,InPort,OutPort) ): raise VerilogTranslationError( 'Port lists with more than 2 dimenstions are not supported!' ) stmts = ' reg [{bw:4}:0] {name}[0:{sz0}][0:{sz1}];\n' \ .format( bw = ports[0][0].nbits-1, name = ports.name, sz0 = len(ports)-1, sz1 = len(ports[0])-1 ) for i, plist in enumerate(ports): for j, port in enumerate(plist): stmts += ' assign {0} = {1}[{2:3}][{3:3}];\n' \ .format(signal_to_str(port, None, model), ports.name, i, j) else: raise VerilogTranslationError( 'An internal error occured when translating array accesses!\n' 'Expected a list of Wires or Ports, instead got: {}' .format( type(ports[0]) ) ) return stmts
def visit_Compare(self, node): if len(node.ops) != 1: raise VerilogTranslationError( 'Chained comparisons are currently not translatable!\n' 'Please change "x < y < z" to the form "(x < y) and (y < z)!', node.lineno) left = self.visit(node.left) op = opmap[type(node.ops[0])] right = self.visit(node.comparators[0]) return '({} {} {})'.format(left, op, right)
def visit_For( self, node ): if not (isinstance( node.iter, _ast.Slice ) and isinstance( node.target, _ast.Name )): raise VerilogTranslationError( 'An internal error occured when translating a for loop!\n' 'Please contact the PyMTL developers!', node.lineno ) self.loopvar.add( node.target.id ) self.generic_visit( node )
def visit_Name( self, node ): self.generic_visit( node ) if isinstance( node._object, slice ): if node._object.step: raise VerilogTranslationError( 'Slices with steps ([start:stop:step]) are not translatable!\n', node.lineno ) new_node = ast.Slice( ast.Num( node._object.start ), ast.Num( node._object.stop ), None ) return ast.copy_location( new_node, node ) return node
def visit_Assign(self, node): # NOTE: this should really be caught earlier if len(node.targets) != 1 or isinstance(node.targets[0], (ast.Tuple)): raise VerilogTranslationError( 'Assignments can only have one item on the left-hand side!\n' 'Please modify "x,y = ..." to be two separate lines.', node.lineno) lhs = self.visit(node.targets[0]) rhs = self.visit(node.value) assign = '=' if node._is_blocking else '<=' indent = self.indent return '{indent}{lhs} {assign} {rhs};\n'.format(**vars())
def visit_Attribute( self, node ): self.generic_visit( node ) if isinstance( node._object, _SignalSlice ): if node._object.slice.step: raise VerilogTranslationError( 'Slices with steps ([start:stop:step]) are not translatable!\n', node.lineno ) new_node = ast.Subscript( node.value, ast.Slice( ast.Num( node._object.slice.start ), ast.Num( node._object.slice.stop ), None ), None, ) new_node._object = node._object return ast.copy_location( new_node, node ) return node
def visit_Assign( self, node ): # catch untranslatable constructs if len(node.targets) != 1: raise VerilogTranslationError( 'Chained assignments are not supported!\n' 'Please modify "x = y = ..." to be two separate lines.', node.lineno ) # annotate the assignment with _is_blocking if not sequential update lhs = node.targets[0] seq = isinstance( lhs, ast.Attribute ) and lhs.attr in ['next','n'] node._is_blocking = not seq self.generic_visit( node ) return node
def visit_FunctionDef(self, node): # Don't bother translating undecorated functions if not node.decorator_list: return # Combinational Block if 'combinational' in node.decorator_list: sensitivity = '*' # Posedge Clock elif ('posedge_clk' in node.decorator_list or 'tick_rtl' in node.decorator_list): sensitivity = 'posedge clk' # Unsupported annotation else: def get_dec_name(dec): if hasattr(dec, 'id'): return dec.id elif hasattr(dec, 'attr'): return dec.attr else: return dec raise VerilogTranslationError( 'An invalid decorator was encountered!\nOnly @s.combinational,\n' '@s.tick_rtl, and @s.posedge_clk are currently translatable.\n' 'Current decorators: {}'.format( [get_dec_name(x) for x in node.decorator_list]), node.lineno) # Visit the body of the function body = self.fmt_body(node.body) return fmt( ''' // logic for {}() always @ ({}) begin {} end ''', self.indent).format(node.name, sensitivity, body)
def visit_FunctionDef( self, node ): #self.generic_visit( node ) # visit children, uneeded? # TODO: currently assume decorator is of the form 'self.dec_name' if len( node.decorator_list ) != 1: def get_dec_name( dec ): if hasattr( dec, 'id' ): return dec.id elif hasattr( dec, 'attr' ): return dec.attr else: return dec raise VerilogTranslationError( 'Translated behavioral blocks should only have one decorator!\n' 'Current decorators: {}'.format( [ get_dec_name( x ) for x in node.decorator_list ] ), node.lineno ) dec = node.decorator_list[0].attr # create a new FunctionDef node that deletes the decorators new_node = ast.FunctionDef( name=node.name, args=node.args, body=node.body, decorator_list=[dec]) return ast.copy_location( new_node, node )
def visit_Call(self, node): # Can only translate calls generated from Name nodes if not isinstance( node.func, _ast.Name ): raise VerilogTranslationError( 'Encountered a non-translatable function call!', node.lineno ) if node.keywords: raise VerilogTranslationError( 'Cannot translate function calls with keyword arguments!\n' 'Please replace "f( a, argname=b )" with "f( a, b )".', node.lineno ) if node.starargs: raise VerilogTranslationError( 'Cannot translate function calls with arg unpacking!\n' 'Please replace "f( *arg_list )" with "f( a, b )".', node.lineno ) if node.kwargs: raise VerilogTranslationError( 'Cannot translate function calls with kwarg unpacking!\n' 'Please replace "f( **arg_dict )" with "f( a, b )".', node.lineno ) # Handle sign extension func_name = self.visit( node.func ) # Handle sign extension if func_name == 'sext': if len(node.args) != 2: raise VerilogTranslationError( 'Encountered a non-translatable sext call!\n' 'sext(in, nbits) must have exactly two arguments!', node.lineno ) try: if isinstance( node.args[1], ast.Num ): nbits = node.args[1].n else: nbits = node.args[1]._object assert isinstance( nbits, int ) except (AssertionError,AttributeError): raise VerilogTranslationError( 'Encountered a non-translatable sext call!\n' 'Argument "nbits" of sext(in,nbits) is not a constant int!', node.lineno ) sig_name = self.visit( node.args[0] ) sig_nbits = node.args[0]._object.nbits ext_nbits = self.visit( node.args[1] ) return '{{ {{ {3}-{1} {{ {0}[{2}] }} }}, {0} }}' \ .format( sig_name, sig_nbits, sig_nbits-1, ext_nbits ) # Handle zero extension if func_name == 'zext': if len(node.args) != 2: raise VerilogTranslationError( 'Encountered a non-translatable zext call!\n' 'zext(in, nbits) must have exactly two arguments!', node.lineno ) try: if isinstance( node.args[1], ast.Num ): nbits = node.args[1].n else: nbits = node.args[1]._object assert isinstance( nbits, int ) except (AssertionError,AttributeError): raise VerilogTranslationError( 'Encountered a non-translatable zext call!\n' 'Argument "nbits" of zext(in,nbits) is not a constant int!', node.lineno ) sig_name = self.visit( node.args[0] ) sig_nbits = node.args[0]._object.nbits ext_nbits = self.visit( node.args[1] ) return "{{ {{ {3}-{1} {{ 1'b0 }} }}, {0} }}" \ .format( sig_name, sig_nbits, sig_nbits-1, ext_nbits ) # Handle concatentation if func_name == 'concat': signal_names = [ self.visit(x) for x in node.args ] return "{{ {signals} }}" \ .format( signals=','.join(signal_names) ) # Handle reduce and if func_name == 'reduce_and': sig_name = self.visit( node.args[0] ) return "(&{sig_name})".format( sig_name=sig_name ) # Handle reduce or if func_name == 'reduce_or': sig_name = self.visit( node.args[0] ) return "(|{sig_name})".format( sig_name=sig_name ) # Handle reduce xor if func_name == 'reduce_xor': sig_name = self.visit( node.args[0] ) return "(^{sig_name})".format( sig_name=sig_name ) # Handle Bits if func_name == 'Bits': if len(node.args) > 2: raise VerilogTranslationError( 'Encountered a non-translatable Bits call!\n' 'Bits(nbits,value) must have exactly two arguments!', node.lineno ) if not isinstance( node.args[0], (ast.Num, ast.Name, ast.Attribute)): raise VerilogTranslationError( 'Encountered a non-translatable Bits call!\n' 'Argument "nbits" of Bits(nbits,value) is not a constant int!', node.lineno ) if len(node.args) == 2 and \ not isinstance( node.args[1], (ast.Num, ast.Name, ast.Attribute)): raise VerilogTranslationError( 'Encountered a non-translatable Bits call!\n' 'Argument "value" of Bits(nbits,value) is not a constant int!', node.lineno ) try: if isinstance( node.args[0], ast.Num ): nbits = node.args[0].n else: nbits = node.args[0]._object if len(node.args) == 1: value = 0 elif isinstance( node.args[1], ast.Num ): value = node.args[1].n else: value = node.args[1]._object assert isinstance( nbits, int ) assert isinstance( value, int ) except (AssertionError,AttributeError): raise VerilogTranslationError( 'Encountered a non-translatable Bits call!\n' 'Arguments of Bits(nbits,value) are not both constant ints!', node.lineno ) return "{nbits}'d{value}".format( nbits=nbits, value=value ) raise VerilogTranslationError( 'Encountered a non-translatable function call: {}!'.format( func_name ), node.lineno )
def visit_Assign( self, node ): # Catch untranslatable constructs if len(node.targets) != 1: raise VerilogTranslationError( 'Chained assignments are not supported!\n' 'Please modify "x = y = ..." to be two separate lines.', node.lineno ) if isinstance(node.targets[0], ast.Tuple): raise VerilogTranslationError( 'Multiple items on the left of an assignment are not supported!\n' 'Please modify "x,y = ..." to be two separate lines.', node.lineno ) # First visit the RHS to update Name nodes that have been inferred self.visit( node.value ) # Need this to visit potential temporaries used in slice indices! self.visit( node.targets[0] ) # The LHS doesn't have a type, we need to infer it if node.targets[0]._object == None: # The LHS should be a Name node! if not isinstance(node.targets[0], _ast.Name): raise VerilogTranslationError( 'An internal error occured when performing type inference!\n' 'Please contact the PyMTL developers!', node.lineno ) # Assign unique name to this temporary in case the same temporary # name is used in another concurrent block. node.targets[0].id = self._uniq_name( node.targets[0].id ) # Copy the object returned by the RHS, set the name appropriately if isinstance( node.value, ast.Name ): if isinstance( node.value._object, int ): self._insert( node, (node.targets[0].id, node.value._object ) ) else: obj = copy.copy( node.value._object ) obj.name = node.targets[0].id obj.parent = None self._insert( node, obj ) elif isinstance( node.value, ast.Attribute ): if isinstance( node.value._object, int ): self._insert( node, (node.targets[0].id, node.value._object ) ) else: obj = copy.copy( node.value._object ) obj.name = node.targets[0].id obj.parent = None self._insert( node, obj ) elif isinstance( node.value, ast.Num ): self._insert( node, (node.targets[0].id, int( node.value.n )) ) elif isinstance( node.value, ast.BoolOp ): obj = Wire( 1 ) obj.name = node.targets[0].id self._insert( node, obj ) elif isinstance( node.value, ast.Compare ): obj = Wire( 1 ) obj.name = node.targets[0].id self._insert( node, obj ) elif isinstance( node.value, ast.Subscript ): # TODO: assumes ast.Index does NOT contain a slice object if not isinstance( node.value.slice, ast.Index ): raise VerilogTranslationError( 'Type inference from slices > 1-bit is not currently supported!' '\nCannot infer type of temporary variable "{}".' .format( node.targets[0].id ), node.lineno ) if isinstance( node.value._object, Signal ): obj = Wire( 1 ) elif isinstance( node.value._object, list ) and \ isinstance( node.value._object[0], Signal ): obj = Wire( node.value._object[0].nbits ) else: raise VerilogTranslationError( 'Type inference from unsupported list construct!' '\nCannot infer type of temporary variable "{}".' .format( node.targets[0].id ), node.lineno ) obj.name = node.targets[0].id self._insert( node, obj ) elif isinstance( node.value, ast.Call ): func_name = node.value.func.id if func_name in ['sext', 'zext']: nbits_arg = node.value.args[1] if isinstance( nbits_arg, ast.Num ): nbits = nbits_arg.n else: nbits = nbits_arg._object if not isinstance( nbits, int ): raise VerilogTranslationError( 'The second argument to function "{}" must be an int!' .format( func_name ), node.lineno ) obj = Wire( nbits ) elif func_name == 'concat': nbits = sum( [x._object.nbits for x in node.value.args ] ) obj = Wire( nbits ) elif func_name in ['reduce_and', 'reduce_or', 'reduce_xor']: obj = Wire( 1 ) elif func_name == 'Bits': nbits_arg = node.value.args[0] if isinstance( nbits_arg, ast.Num ): nbits = nbits_arg.n else: nbits = nbits_arg._object if not isinstance( nbits, int ): raise VerilogTranslationError( 'The first argument to the Bits constructor must be an int!', node.lineno ) obj = Wire( nbits ) else: print_simple_ast( node ) raise VerilogTranslationError( 'Type inference from the function "{}" is not currently supported!' '\nCannot infer type of temporary variable "{}".' .format( func_name, node.targets[0].id ), node.lineno ) obj.name = node.targets[0].id self._insert( node, obj ) else: print_simple_ast( node ) raise VerilogTranslationError( 'Type inference of "{}" AST nodes is not currently supported!' '\nCannot infer type of temporary variable "{}".' .format( type(node.value).__name__, node.targets[0].id ), node.lineno ) return node