#=========================================================================
# YosysBehavioralTranslatorL3.py
#=========================================================================
# Author : Peitian Pan
# Date   : June 9, 2019
"""Provide the yosys-compatible SystemVerilog L3 behavioral translator."""

from __future__ import absolute_import, division, print_function

from pymtl3.datatypes import Bits, BitStruct
from pymtl3.passes.rtlir import RTLIRDataType as rdt
from pymtl3.passes.rtlir import RTLIRType as rt
from pymtl3.passes.sverilog.errors import SVerilogTranslationError
from pymtl3.passes.sverilog.translation.behavioral.SVBehavioralTranslatorL3 import (
    BehavioralRTLIRToSVVisitorL3,
    SVBehavioralTranslatorL3,
)

from .YosysBehavioralTranslatorL2 import (
    YosysBehavioralRTLIRToSVVisitorL2,
    YosysBehavioralTranslatorL2,
)


class YosysBehavioralTranslatorL3(
    YosysBehavioralTranslatorL2, SVBehavioralTranslatorL3 ):

  def _get_rtlir2sv_visitor( s ):
    return YosysBehavioralRTLIRToSVVisitorL3

class YosysBehavioralRTLIRToSVVisitorL3(
    YosysBehavioralRTLIRToSVVisitorL2, BehavioralRTLIRToSVVisitorL3 ):
  """IR visitor that generates yosys-compatible SystemVerilog code.
  
  This visitor differs from the canonical SystemVerilog visitor in that
  it name-mangles each struct signal into multiple signals for all fields
  in the struct. We don't use SystemVerilog struct here because yosys
  does not support that yet.
  """

  def _literal_number( s, nbits, value ):
    value = int( value )
    return "{nbits}'d{value}".format( **locals() )

  def _struct_instance( s, dtype, struct ):
    def _gen_packed_array( dtype, n_dim, array ):
      if not n_dim:
        if isinstance( dtype, rdt.Vector ):
          return s._literal_number( dtype.nbits, array )
        elif isinstance( dtype, rdt.Struct ):
          return s._struct_instance( dtype, array )
        else:
          assert False, "unrecognized data type {}!".format( dtype )
      else:
        ret = []
        for i in reversed( range( n_dim[0] ) ):
          ret.append( _gen_packed_array( dtype, n_dim[1:], array[i] ) )
        if n_dim[0] > 1:
          cat_str = "{" + ", ".join( ret ) + "}"
        else:
          cat_str = ", ".join( ret )
        return cat_str
    fields = []
    all_properties = dtype.get_all_properties()
    for name, Type in all_properties:
      field = getattr( struct, name )
      if isinstance( Type, rdt.Vector ):
        _field = s._literal_number( Type.nbits, field )
      elif isinstance( Type, rdt.Struct ):
        _field = s._struct_instance( Type, field )
      elif isinstance( Type, rdt.PackedArray ):
        n_dim = Type.get_dim_sizes()
        sub_dtype = Type.get_sub_dtype()
        _field = _gen_packed_array( sub_dtype, n_dim, field )
      else:
        assert False, "unrecognized data type {}!".format( Type )
      fields.append( _field )
    if len( fields ) == 1:
      struct_str = fields[0]
    else:
      struct_str = "{" + ", ".join( fields ) + "}"
    return struct_str

  #-----------------------------------------------------------------------
  # visit_Attribute
  #-----------------------------------------------------------------------

  def visit_Attribute( s, node ):
    """Return the SystemVerilog representation of an attribute.
    
    Add support for accessing struct attribute in L3.
    """
    if isinstance( node.value.Type, rt.Signal ):
      if isinstance( node.value.Type, rt.Const ):
        try:
          obj = node.Type.get_object()
        except AttributeError:
          obj = None
        if obj is None:
          raise SVerilogTranslationError( s.blk, node,
            "attribute ({}) of constant struct instance ({}) is not supported!". \
              format( node.attr, node.value ) )
        else:
          if isinstance( obj, Bits ):
            s.signal_expr_prologue( node )
            node.sexpr['s_attr'] = \
                s._literal_number(obj.nbits, int(obj.value))
            node.sexpr["s_index"] = ""
            attr = node.attr
            return s.signal_expr_epilogue(node, "{attr}".format(**locals()))
          elif isinstance( obj, BitStruct ):
            s.signal_expr_prologue( node )
            node.sexpr['s_attr'] = \
                s._struct_instance(node.Type.get_dtype(), obj)
            node.sexpr["s_index"] = ""
            attr = node.attr
            return s.signal_expr_epilogue(node, "{attr}".format(**locals()))
          else:
            raise SVerilogTranslationError( s.blk, node,
              "attribute ({}) of constant struct instance ({}) is not supported!". \
                format( node.attr, node.value ) )

      elif isinstance( node.value.Type.get_dtype(), rdt.Struct ):
        value = s.visit( node.value )
        s.signal_expr_prologue( node )
        attr = node.attr
        s.check_res( node, attr )
        node.sexpr['s_attr'] += "${}"
        node.sexpr['attr'].append( attr )
        return s.signal_expr_epilogue(node, "{value}.{attr}".format(**locals()))

    return super( YosysBehavioralRTLIRToSVVisitorL3, s ).visit_Attribute( node )

  #-----------------------------------------------------------------------
  # visit_StructInst
  #-----------------------------------------------------------------------

  def visit_StructInst( s, node ):
    for value in node.values:
      value._top_expr = 1
    return super(YosysBehavioralRTLIRToSVVisitorL3, s).visit_StructInst( node )
