"""
===========
VHDL_entity
===========
VHDL import features for RTL simulation package of The System Development Kit 
Provides utilities to import VHDL entities to 
python environment. Imported VHDL entities will be instantiated as verilog modules, 
and are intended to be simulated within verilog testbench with simulator supporting 
cross language compilations. 
Initially written by Marko Kosunen, 2017
Transferred from VHDL package in Dec 2019
"""
import os
import pdb
from thesdk import *
from copy import deepcopy
from rtl import *
from rtl.connector import rtl_connector
from rtl.connector import rtl_connector_bundle
from rtl.module_common import module_common
[docs]
class vhdl_entity(module_common,thesdk):
    """Objective:
        1) 
           a) Collect IO's to database
           b) collect parameters to dict
        2) Reconstruct the entity definition
        3) 
           a) Implement methods provide signal connections
           b) Implement methods to provide generic assingments   
              
        4) Create a method to create assigned module \
           definition, where signals are \
           
           a) assigned by name
           b) to arbitrary name vector.
        5) Add contents, if required, and include that to definition
            
    """
    def __init__(self, **kwargs):
        ''' Executes init of module_common, thus having the same attributes and 
        parameters.
        Parameters
        ----------
            **kwargs :
               See module module_common
        
        '''
        super().__init__(**kwargs)
    
    @property
    def ios(self):
        '''Rtl connector bundle containing connectors for all module IOS.
           All the IOs are connected to signal connectors that 
           have the same name than the IOs. This is due to fact the we have decided 
           that all signals are connectors. 
        '''
        if not hasattr(self,'_ios'):
            startmatch=re.compile(r"entity *(?="+self.name+r"\s*is)"+r".*.+$")
            iomatch=re.compile(r".*port\(.*$")
            #iomatch=re.compile(r".*$")
            parammatch=re.compile(r".*generic\(.*$")
            iostopmatch=re.compile(r'.*\);.*$')
            dut=''
            # Extract the module definition
            self._ios=rtl_connector_bundle()
            if os.path.isfile(self.file):
                with open(self.file) as infile:
                    wholefile=infile.readlines()
                    modfind=False
                    paramfind=False
                    iofind=False
                    for line in wholefile:
                        if (not modfind and startmatch.match(line)):
                            modfind=True
                        if modfind and parammatch.match(line):
                                paramfind=True
                        if modfind and iomatch.match(line):
                                iofind=True
                        if modfind and iofind: 
                            # We need to filter all (); combinations 
                            # from the line and check if ); still exists
                            testline=re.sub("\(.*?\)","",line)
                            if iostopmatch.match(testline):
                                modfind=False
                                iofind=False
                                paramfind=False
                                #Inclusive
                                line=re.sub(r"--.*;.*$","\);",line) +'\n'
                                #Force newline
                                line=re.sub(r"\);","",line) +'\n'
                                dut+=re.sub(r"--.*;.*$","\);",line) +'\n'
                            dut=dut+re.sub(r"--.*$","",line)
                    #Remove the EOL comments
                    dut=re.sub(r";.*$",",",dut)
                    dut=dut.replace("\n","")
                    #Generate lambda functions for pattern filtering
                    fils=[
                        re.compile(r"port\s*\(\s*"),
                        re.compile(r"^\s*"),
                        re.compile(r"--.*$"),
                      ]
                    func_list= [lambda s,fil=x: re.sub(fil,"",s) for x in fils]
                    dut=reduce(lambda s, func: func(s), func_list, dut)
                    dut=re.sub(r"\s+"," ",dut)
                    dut=re.sub(r"\s+in\s*","in :",dut)
                    dut=re.sub(r"\s+out\s*","out :",dut)
                    dut=re.sub(r"\s+inout\s*","inout :",dut)
                    dut=re.sub(r"\s*:\s*",":",dut)
                    dut=re.sub(r"\s*;\s*",";",dut)
                    if dut:
                        for ioline in dut.split(';'):
                            extr=ioline.split(':')
                            signal=rtl_connector(lang='vhdl')
                            if extr[1]=='in':
                                signal.cls='input'
                            elif extr[1]=='out':
                                signal.cls='output'
                            signal.name=extr[0]
                            signal.type=extr[2]
                            busdef=re.match(r"^.*\(\s*(.*)(\s+downto\s+|\s+to\s+)(.*)\s*\)",extr[2])
                            if busdef:
                                signal.ll=busdef.group(1)
                                signal.rl=busdef.group(3)
                            #By default, we create a connector that is cross connected to the input
                            signal.connect=deepcopy(signal)
                            if signal.cls=='input':
                                signal.connect.cls='reg'
                            if signal.cls=='output':
                                signal.connect.cls='wire'
                            self._ios.Members[signal.name]=signal
                            signal.connect.connect=signal
                            self._ios.Members[signal.name]=signal
        return self._ios
    # Setting principle, assign a dict
    # individual parameters can be set externally
    @ios.setter
    def ios(self,value):
        self._ios=deepcopy(value)
    @property
    def parameters(self):
        '''Generics of the VHDL entity
        '''
        if not hasattr(self,'_parameters'):
            startmatch=re.compile(r"entity *(?="+self.name+r"\s*is)"+r".*.+$")
            parammatch=re.compile(r".*(?<=generic)\(.*$")
            paramstopmatch=re.compile(r".*\);.*$")
            parablock=''
            self._parameters=Bundle()
            # Extract the module definition
            if os.path.isfile(self.file):
                with open(self.file) as infile:
                    wholefile=infile.readlines()
                    modfind=False
                    parafind=False
                    for line in wholefile:
                        if (not modfind and startmatch.match(line)):
                            modfind=True
                        if modfind and parammatch.match(line):
                              parafind=True
                        if ( modfind and parafind and paramstopmatch.match(line)):
                            modfind=False
                            parafind=False
                            line=re.sub(r"\);.*$","",line)
                            line=re.sub(r"--.*$","",line)
                            #Inclusive
                            parablock=parablock+line +'\n'
                        elif modfind and parafind:
                            line=re.sub(r"--.*$","",line)
                            parablock=parablock+line
                    # Eventually we need to generate at least a tuple,
                    # but we could also have a parameter class with more properties  
                    if parablock:
                        #Generate lambda functions for pattern filtering
                        parablock.replace("\n","")
                        #After these values we have name:type:value
                        fils=[
                            re.compile(r"generic\s*"),
                            re.compile(r"--"),
                            re.compile(r"\(*"),
                            re.compile(r"\)*"),
                            re.compile(r"\s*"),
                            re.compile(r"="),
                          ]
                        func_list= [lambda s,fil=x: re.sub(fil,"",s) for x in fils]
                        parablock=reduce(lambda s, func: func(s), func_list, parablock)
                        parablock=parablock.split(';')
                        for param in parablock:
                            extr=param.split(':')
                            self._parameters.Members[extr[0]]=extr[2]
        return self._parameters
    # Setting principle, assign a dict
    # individual parameters can be set externally
    @parameters.setter
    def parameters(self,value):
        self._parameters.Members=deepcopy(value)
    @property
    def contents(self):
        ''' Contents is extracted. We do not know what to do with it yet. 
        
        '''
        if not hasattr(self,'_contents'):
            startmatch=re.compile(r"\s*architecture\s+.*\s+of\s+"+self.name+r".*$")
            modulestopmatch=re.compile(r"\s*end\s+.* architecture\s*$")
            self._contents='\n'
            # Extract the module definition
            if os.path.isfile(self.file):
                modfind=False
                with open(self.file) as infile:
                    wholefile=infile.readlines()
                    for line in wholefile:
                        if startmatch.match(line):
                            self._contents=line
                            modfind=True
                        elif modfind and modulestopmatch.match(line):
                            modfind=False
                            #exclusive
                        elif modfind:
                            self._contents=self._contents+line
        return self._contents
    @contents.setter
    def contents(self,value):
        self._contents=value
    @property
    def io_signals(self):
        '''Bundle containing the signal connectors for IO connections.
        '''
        if not hasattr(self,'_io_signals'):
            self._io_signals=rtl_connector_bundle()
            for ioname, io in self.ios.Members.items():
                # Connectior is created already in io definitio
                # just point to it  
                self._io_signals.Members[ioname]=io.connect
        return self._io_signals
    @io_signals.setter
    def io_signals(self,value):
        for conn in value.Members :
            self._io_signals.Members[conn.name].connect=conn
        return self._io_signals
    @property
    def header(self):
        """Header configuring the e.g. libraries if needed"""
        if not hasattr(self,'_header'):
            self._header=''
        return self._header
    @header.setter
    def header(self,value):
        if not hasattr(self,'_header'):
            self._header=value
    @property
    def definition(self):
        '''Entity definition part extracted for the file. Contains generics and 
        IO definitions.
        '''
        if not hasattr(self,'_definition'):
            #First we print the parameter section
            if self.parameters.Members:
                parameters=''
                first=True
                for name, val in self.parameters.Members.items():
                    if type(val) is not tuple:
                        self.print_log(type='F', msg='Parameter %s must be defined as {\'<name>\': (\'<type>\',value)}' %(name))
                    if first:
                        parameters='generic(\n %s : %s := %s' %(name,val[0],val[1])
                        first=False
                    else:
                        parameters=parameters+';\n %s : %s := %s' %(name,val[0],val[1])
                parameters=parameters+'\n);'
                self._definition='entity %s is\n%s' %(self.name, parameters)
            else:
                self._definition='entity %s is\n' %(self.name)
            first=True
            if self.ios.Members:
                for ioname, io in self.ios.Members.items():
                    if first:
                        self._definition=self._definition+'\nport(\n'
                        first=False
                    else:
                        self._definition=self._definition+',\n'
                    if io.cls in [ 'input', 'output', 'inout' ]:
                        if io.width==1:
                            self._definition=(self._definition+
                                    ('    %s %s' %(io.cls, io.name)))
                        else:
                            self._definition=(self._definition+
                                    ('    %s [%s:%s] %s' %(io.cls, io.ll, io.rl, io.name)))
                    else:
                        self.print_log(type='F', msg='Assigning signal direction %s to verilog module IO.' %(io.cls))
                self._definition=self._definition+'\n)'
            self._definition=self._definition+'\nend entity;\n'
            if self.contents:
                self._definition=self._definition+self.contents
        return self._definition
    #Methods
[docs]
    def export(self,**kwargs):
        '''Method to export the module. Exports self.headers+self.definition to a given file.
        Parameters
        ----------
           **kwargs :
               force: Bool
        '''
        if not os.path.isfile(self.file):
            self.print_log(msg='Exporting vhdl_entity to %s.' %(self.file))
            with open(self.file, "w") as module_file:
                module_file.write(self.header+self.definition)
        elif os.path.isfile(self.file) and not kwargs.get('force'):
            self.print_log(type='F', msg=('Export target file %s exists.\n Force overwrite with force=True.' %(self.file)))
        elif kwargs.get('force'):
            self.print_log(msg='Forcing overwrite of vhdl_entity to %s.' %(self.file))
            with open(self.file, "w") as module_file:
                module_file.write(self.header+self.definition) 
 
if __name__=="__main__":
    pass