# AUTOGENERATED! DO NOT EDIT! File to edit: 01_Scripting.ipynb (unless otherwise specified).

__all__ = ['qshell', 'get_fpgas', 'get_insts', 'begin_mem', 'end_mem', 'read', 'format_mem', 'write', 'read_write',
           'read_write_all', 'program_proj', 'compile_proj', 'archive_proj', 'back_annotate', 'write_qsf', 'lut_mask',
           'analyze_timing']

# Cell
import warnings
with warnings.catch_warnings(): #ignore warnings
    warnings.simplefilter("ignore")
    import sidis
    import mif
    import quartustcl
    import numpy as np
    import os
    from typing import Optional, Tuple, Dict, Callable, Union
    import functools
    from functools import wraps

# Cell
def qshell(func):
    '''Function decorator that opens quartustcl shell if none open.'''
    @wraps(func)
    def wrap(q=None,*args,**kwargs):
        if q is None:
            with quartustcl.QuartusTcl() as q:
                result = func(q,*args,**kwargs)
        else:
            result = func(q,*args,**kwargs)
        return result
    return wrap

# Cell
@qshell
def get_fpgas(q : Optional[quartustcl.QuartusTcl] = None,
             parse : bool = False):
    '''
    Get all connected FPGAs and SOCs as lists of hardware names `hwnames` and device names `devnames`.
    Uses `q` shell if given, otherwise opens new shell. If `parse`, extracts only the FPGA, ignoring SoCs.
    Returns a dict keyed by hardware port (e.g USB) and valued with devices (e.g FPGA/SOC).
    '''
    hwnames = q.parse(q.get_hardware_names())
    devnames = [q.parse(q.get_device_names(hardware_name=h)) for h in hwnames]
    if parse:
        devnames=[d[-1] for d in devnames] #SoC is always first, if it exists
    return dict(list(zip(hwnames,devnames)))

# Cell
@qshell
def get_insts(q = None,
              N_levels=2,
              hwnames=None,
              devnames=None):
    '''
    Get all memory instances from `hwname` and `devname`. See `QuartusTcl` doc for `N_levels`.
    '''
    if hwnames is None or devnames is None:
        device_dict=get_fpgas(parse=True)
        hwnames=list(device_dict.keys())
        devnames=list(device_dict.values())
    'Finds instance index given a name `inst_name` (string).'
    memories={}
    for hwname,devname in zip(hwnames,devnames):
        memories_raw = q.get_editable_mem_instances(hardware_name=hwname,\
            device_name=devname)
        memories[hwname]=q.parse(memories_raw, levels=N_levels)
    return memories

# Cell
def begin_mem(q = None,
          hw='DE-SoC [USB-1]',
          dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)'):
    '''
    Open a memory edit. If one is already open, pass.
    '''
    try:
        q.begin_memory_edit(hardware_name=hw, device_name=dev)
    except:
        pass

def end_mem(q = None):
    '''
    End a memory edit. If there are none open, pass.
    '''
    try:
        q.end_memory_edit()
    except:
        pass

# Cell
@qshell
def read(q=None,
         inst=0,
         hw='DE-SoC [USB-1]',
         dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)',
         begin=True,
         end=True,
         fname=None,
         delete_mif=True):
    '''Reads memory from an instance `inst` into an array `data`.
    Option `delete_mif` will delete temporary .mif file if set to `True`.'''
    if begin:
        begin_mem(q,hw=hw,dev=dev)
    fname=fname or 'read_inst{0}'.format(inst)
    q.save_content_from_memory_to_file(
        instance_index=inst,
        mem_file_path=fname,
        mem_file_type='mif')
    with open(fname, 'r') as f:
        data = mif.load(f)
        f.close()
    if delete_mif:
        os.remove(fname)
    if end:
        end_mem(q)
    return np.array(data).astype(float).astype(int)

# Cell
def format_mem(data,bits=None):
    '''
    Format input data to nested array required for memory
    '''
    if (type(data) is int) or (type(data) is float):
        data=sidis.num2ar(data,bits=bits)
    data=np.array(data).astype(int)
    if len(data.shape)==1: #if not matrix, convert for tcl
        data=np.expand_dims(data,axis=0)
    return data

# Cell
@qshell
def write(q=None,
          inst=0,
          data=1,
          hw='DE-SoC [USB-1]',
          dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)',
          bits=None,
          begin=True,
          end=True,
          fname=None,
          delete_mif=True):
    '''Writes `data` array to memory instance `inst`.
    Option `delete_mif` will delete temporary .mif file if set to `True`.'''
    data = format_mem(data=data,bits=bits)
    fname=fname or 'write_inst{0}'.format(inst)
    if begin:
        begin_mem(q,hw,dev)
    with open(fname, 'w') as f:
        mif.dump(data, f)
        f.close()
    q.update_content_to_memory_from_file(
        instance_index=inst,
        mem_file_path=fname,
        mem_file_type='mif')
    if end:
        end_mem(q)
    if delete_mif:
        os.remove(fname)

# Cell
@qshell
def read_write(q=None,
              args=[[0,'w',1,1],[0,'r']],
              hw='DE-SoC [USB-1]',
              dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)',
              reps=1,
              begin=True,
              end=True):
    '''
    Read/write using `args`, a list of lists/tuples each of the form
    (instance index, 'r' or 'w', data to write if 'w', bits to use if 'w').
    Can repeat operation up to `reps`.
    '''

    data=[[] for i in range(reps)]
    if begin:
        begin_mem(q=q,hw=hw,dev=dev)
    for i in range(reps):
        for tup in args:
            inst=tup[0]
            if tup[1]=='r':
                data[i]+=[read(q=q,inst=inst,hw=hw,dev=dev,begin=False,end=False)]
            elif tup[1]=='w':
                x=tup[2]
                if len(tup)==3:
                    b=None
                else:
                    b=tup[3]
                x=format_mem(data=x,bits=b)
                write(q=q,inst=inst,data=x,hw=hw,dev=dev,begin=False,end=False)
    if end:
        end_mem(q)
    if data!=[]:
        data=np.squeeze(data).astype(int)
        if np.shape(data)==():
            data=np.array([data])
        return data

# Cell
@qshell
def read_write_all(q=None,
                 args=[[0,'w',1,1],[0,'r']],
                 fpgas=[['DE-SoC [USB-1]','@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)']],
                 reps=1,
                 begin=False,
                 end=False):
    data=[[] for i in range(len(fpgas))]
    for i,(hw,dev) in enumerate(fpgas):
        data[i]=read_write(q=q,args=args,hw=hw,dev=dev,reps=reps,begin=begin,end=end)
    if data!=[]:
        data=np.array(data).astype(int)
        return data

# Cell
def program_proj(projectname,hw=None,path='output_files'):
    "Program a single FPGA using a .cdf file."
    if hw is None:
        try:
            os.system(r'quartus_pgm output_files/{0}.cdf'.format(projectname))
        except FileNotFoundError:
            print("Use Quartus Programmer GUI to generate cdf file first.")
    else:
        filename=projectname+'.cdf'
        filename=path+'\\'+filename
        hdwname='\"'+hw+'\"'
        os.system(r'quartus_pgm -c {0} {1}'.format(hdwname,filename))

# Cell
def compile_proj(projectname):
    'Compiles `Quartus` project `projectname`.'
    os.system('quartus_sh --flow compile '+projectname)

# Cell
def archive_proj(projectname,filename,path=''):
    "Generates a `projectname-date.qar` file which Quartus can use to restore all project files."
    os.system('quartus_sh --archive {pr} -use_file_set sr -output "{pa}{fn}.qar" {pr}'.format(pr=projectname,
                                                                                       fn=filename,
                                                                                       pa=path))

# Cell
def back_annotate(projectname,routing=True,logic=True):
    '''
    Writes all routing and logic to the qsf file.
    '''
    if logic:
        os.system(f'quartus_cdb {projectname} --back_annotate=lc')
    if routing:
        os.system(f'quartus_cdb {projectname} --back_annotate=routing')

# Cell
def write_qsf(txt='',style='w',projectname='DE10',sourcename='qsf_source.qsf',set_source=False):
    '''Writes assignments `txt` to `sourcename` and includes them in `projectname`.qsf.'''
    with open(sourcename,style) as file:
        file.writelines(txt)
    if set_source: #making sure command to source is in qsf
        with open(projectname+'.qsf','r') as file:
            lines=file.readlines()
        if 'source {}'.format(sourcename) not in lines:
            with open(projectname+'.qsf','a') as file:
                file.write("\n")
                file.write('source {}'.format(sourcename))
                file.write("\n")

# Cell
def lut_mask(lut=lambda s:1 if np.sum(s)>=2 else 0, N=4): #majority vote
    '''
    Quartus LUT mask for primitive logic cell, takes function to input and returns hex encoding
    in:
        lut (callable or list or numpy.array or dict) : function mapping over the port entry.
        N (int) : number of ports. for standard 4-input LUT, 0=d, 1=c, 2=b, 3=a
    out:
        mask (str) : hex string encoding of the given mask
    '''
    mask=np.zeros(2**N)
    for i,s in enumerate(np.ndindex(tuple([2 for i in range(N)]))):
        if callable(lut):
            mask[i]=lut(s)
        else:
            mask[i]=lut[s]
    mask=sidis.ar2hex(mask)
    return mask

# Cell
def analyze_timing(projectname,
           _from = '[get_clocks {MyClock}]',
           _to = '[get_cells {MyCell}]',
           detail='summary', #or 'full_path'
           filename='timing.txt',
           npaths = 1
          ):
    '''
    Runs the timing analyzer, getting path information from `_from` to `_to`.
    Gives `detail` (summary or full_path) on `npaths` and generates `filename`.
    Returns text of that file.
    '''
    cmd=f'report_timing -from {_from} -to {_to} -setup \
        -npaths {npaths} -detail {detail} -panel_name {{Report Timing}} -multi_corner -file {filename}'
    with open(filename,'w') as f: #generate intermediary tcl file
            f.write(cmd)
    os.system(f'quartus_sta --report_script={filename} {projectname}')
    with open(filename,'r') as f: #write over intermediate file with above command then read it
            txt=f.readlines()
    return txt
# AUTOGENERATED! DO NOT EDIT! File to edit: 01_Scripting.ipynb (unless otherwise specified).

__all__ = ['qshell', 'get_fpgas', 'get_insts', 'begin_mem', 'end_mem', 'read', 'format_mem', 'write', 'read_write',
           'read_write_all', 'program_proj', 'compile_proj', 'archive_proj', 'back_annotate', 'write_qsf', 'lut_mask',
           'analyze_timing']

# Cell
import warnings
with warnings.catch_warnings(): #ignore warnings
    warnings.simplefilter("ignore")
    import sidis
    import mif
    import quartustcl
    import numpy as np
    import os
    from typing import Optional, Tuple, Dict, Callable, Union
    import functools
    from functools import wraps

# Cell
def qshell(func):
    '''Function decorator that opens quartustcl shell if none open.'''
    @wraps(func)
    def wrap(q=None,*args,**kwargs):
        if q is None:
            with quartustcl.QuartusTcl() as q:
                result = func(q,*args,**kwargs)
        else:
            result = func(q,*args,**kwargs)
        return result
    return wrap

# Cell
@qshell
def get_fpgas(q : Optional[quartustcl.QuartusTcl] = None,
             parse : bool = False):
    '''
    Get all connected FPGAs and SOCs as lists of hardware names `hwnames` and device names `devnames`.
    Uses `q` shell if given, otherwise opens new shell. If `parse`, extracts only the FPGA, ignoring SoCs.
    Returns a dict keyed by hardware port (e.g USB) and valued with devices (e.g FPGA/SOC).
    '''
    hwnames = q.parse(q.get_hardware_names())
    devnames = [q.parse(q.get_device_names(hardware_name=h)) for h in hwnames]
    if parse:
        devnames=[d[-1] for d in devnames] #SoC is always first, if it exists
    return dict(list(zip(hwnames,devnames)))

# Cell
@qshell
def get_insts(q = None,
              N_levels=2,
              hwnames=None,
              devnames=None):
    '''
    Get all memory instances from `hwname` and `devname`. See `QuartusTcl` doc for `N_levels`.
    '''
    if hwnames is None or devnames is None:
        device_dict=get_fpgas(parse=True)
        hwnames=list(device_dict.keys())
        devnames=list(device_dict.values())
    'Finds instance index given a name `inst_name` (string).'
    memories={}
    for hwname,devname in zip(hwnames,devnames):
        memories_raw = q.get_editable_mem_instances(hardware_name=hwname,\
            device_name=devname)
        memories[hwname]=q.parse(memories_raw, levels=N_levels)
    return memories

# Cell
def begin_mem(q = None,
          hw='DE-SoC [USB-1]',
          dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)'):
    '''
    Open a memory edit. If one is already open, pass.
    '''
    try:
        q.begin_memory_edit(hardware_name=hw, device_name=dev)
    except:
        pass

def end_mem(q = None):
    '''
    End a memory edit. If there are none open, pass.
    '''
    try:
        q.end_memory_edit()
    except:
        pass

# Cell
@qshell
def read(q=None,
         inst=0,
         hw='DE-SoC [USB-1]',
         dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)',
         begin=True,
         end=True,
         fname=None,
         delete_mif=True):
    '''Reads memory from an instance `inst` into an array `data`.
    Option `delete_mif` will delete temporary .mif file if set to `True`.'''
    if begin:
        begin_mem(q,hw=hw,dev=dev)
    fname=fname or 'read_inst{0}'.format(inst)
    q.save_content_from_memory_to_file(
        instance_index=inst,
        mem_file_path=fname,
        mem_file_type='mif')
    with open(fname, 'r') as f:
        data = mif.load(f)
        f.close()
    if delete_mif:
        os.remove(fname)
    if end:
        end_mem(q)
    return np.array(data).astype(float).astype(int)

# Cell
def format_mem(data,bits=None):
    '''
    Format input data to nested array required for memory
    '''
    if (type(data) is int) or (type(data) is float):
        data=sidis.num2ar(data,bits=bits)
    data=np.array(data).astype(int)
    if len(data.shape)==1: #if not matrix, convert for tcl
        data=np.expand_dims(data,axis=0)
    return data

# Cell
@qshell
def write(q=None,
          inst=0,
          data=1,
          hw='DE-SoC [USB-1]',
          dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)',
          bits=None,
          begin=True,
          end=True,
          fname=None,
          delete_mif=True):
    '''Writes `data` array to memory instance `inst`.
    Option `delete_mif` will delete temporary .mif file if set to `True`.'''
    data = format_mem(data=data,bits=bits)
    fname=fname or 'write_inst{0}'.format(inst)
    if begin:
        begin_mem(q,hw,dev)
    with open(fname, 'w') as f:
        mif.dump(data, f)
        f.close()
    q.update_content_to_memory_from_file(
        instance_index=inst,
        mem_file_path=fname,
        mem_file_type='mif')
    if end:
        end_mem(q)
    if delete_mif:
        os.remove(fname)

# Cell
@qshell
def read_write(q=None,
              args=[[0,'w',1,1],[0,'r']],
              hw='DE-SoC [USB-1]',
              dev='@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)',
              reps=1,
              begin=True,
              end=True):
    '''
    Read/write using `args`, a list of lists/tuples each of the form
    (instance index, 'r' or 'w', data to write if 'w', bits to use if 'w').
    Can repeat operation up to `reps`.
    '''

    data=[[] for i in range(reps)]
    if begin:
        begin_mem(q=q,hw=hw,dev=dev)
    for i in range(reps):
        for tup in args:
            inst=tup[0]
            if tup[1]=='r':
                data[i]+=[read(q=q,inst=inst,hw=hw,dev=dev,begin=False,end=False)]
            elif tup[1]=='w':
                x=tup[2]
                if len(tup)==3:
                    b=None
                else:
                    b=tup[3]
                x=format_mem(data=x,bits=b)
                write(q=q,inst=inst,data=x,hw=hw,dev=dev,begin=False,end=False)
    if end:
        end_mem(q)
    if data!=[]:
        data=np.squeeze(data).astype(int)
        if np.shape(data)==():
            data=np.array([data])
        return data

# Cell
@qshell
def read_write_all(q=None,
                 args=[[0,'w',1,1],[0,'r']],
                 fpgas=[['DE-SoC [USB-1]','@2: 5CSEBA6(.|ES)/5CSEMA6/.. (0x02D020DD)']],
                 reps=1,
                 begin=False,
                 end=False):
    data=[[] for i in range(len(fpgas))]
    for i,(hw,dev) in enumerate(fpgas):
        data[i]=read_write(q=q,args=args,hw=hw,dev=dev,reps=reps,begin=begin,end=end)
    if data!=[]:
        data=np.array(data).astype(int)
        return data

# Cell
def program_proj(projectname,hw=None,path='output_files'):
    "Program a single FPGA using a .cdf file."
    if hw is None:
        try:
            os.system(r'quartus_pgm output_files/{0}.cdf'.format(projectname))
        except FileNotFoundError:
            print("Use Quartus Programmer GUI to generate cdf file first.")
    else:
        filename=projectname+'.cdf'
        filename=path+'\\'+filename
        hdwname='\"'+hw+'\"'
        os.system(r'quartus_pgm -c {0} {1}'.format(hdwname,filename))

# Cell
def compile_proj(projectname):
    'Compiles `Quartus` project `projectname`.'
    os.system('quartus_sh --flow compile '+projectname)

# Cell
def archive_proj(projectname,filename,path=''):
    "Generates a `projectname-date.qar` file which Quartus can use to restore all project files."
    os.system('quartus_sh --archive {pr} -use_file_set sr -output "{pa}{fn}.qar" {pr}'.format(pr=projectname,
                                                                                       fn=filename,
                                                                                       pa=path))

# Cell
def back_annotate(projectname,routing=True,logic=True):
    '''
    Writes all routing and logic to the qsf file.
    '''
    if logic:
        os.system(f'quartus_cdb {projectname} --back_annotate=lc')
    if routing:
        os.system(f'quartus_cdb {projectname} --back_annotate=routing')

# Cell
def write_qsf(txt='',style='w',projectname='DE10',sourcename='qsf_source.qsf',set_source=False):
    '''Writes assignments `txt` to `sourcename` and includes them in `projectname`.qsf.'''
    with open(sourcename,style) as file:
        file.writelines(txt)
    if set_source: #making sure command to source is in qsf
        with open(projectname+'.qsf','r') as file:
            lines=file.readlines()
        if 'source {}'.format(sourcename) not in lines:
            with open(projectname+'.qsf','a') as file:
                file.write("\n")
                file.write('source {}'.format(sourcename))
                file.write("\n")

# Cell
def lut_mask(lut=lambda s:1 if np.sum(s)>=2 else 0, N=4): #majority vote
    '''
    Quartus LUT mask for primitive logic cell, takes function to input and returns hex encoding
    in:
        lut (callable or list or numpy.array or dict) : function mapping over the port entry.
        N (int) : number of ports. for standard 4-input LUT, 0=d, 1=c, 2=b, 3=a
    out:
        mask (str) : hex string encoding of the given mask
    '''
    mask=np.zeros(2**N)
    for i,s in enumerate(np.ndindex(tuple([2 for i in range(N)]))):
        if callable(lut):
            mask[i]=lut(s)
        else:
            mask[i]=lut[s]
    mask=sidis.ar2hex(mask)
    return mask

# Cell
def analyze_timing(projectname,
           _from = '[get_clocks {MyClock}]',
           _to = '[get_cells {MyCell}]',
           detail='summary', #or 'full_path'
           filename='timing.txt',
           npaths = 1
          ):
    '''
    Runs the timing analyzer, getting path information from `_from` to `_to`.
    Gives `detail` (summary or full_path) on `npaths` and generates `filename`.
    Returns text of that file.
    '''
    cmd=f'report_timing -from {_from} -to {_to} -setup \
        -npaths {npaths} -detail {detail} -panel_name {{Report Timing}} -multi_corner -file {filename}'
    with open(filename,'w') as f: #generate intermediary tcl file
            f.write(cmd)
    os.system(f'quartus_sta --report_script={filename} {projectname}')
    with open(filename,'r') as f: #write over intermediate file with above command then read it
            txt=f.readlines()
    return txt
# AUTOGENERATED! DO NOT EDIT! File to edit: 01_Scripting.ipynb (unless otherwise specified). # Cell #ignore warnings # Cell Function decorator that opens quartustcl shell if none open. # Cell Get all connected FPGAs and SOCs as lists of hardware names `hwnames` and device names `devnames`.     Uses `q` shell if given, otherwise opens new shell. If `parse`, extracts only the FPGA, ignoring SoCs.     Returns a dict keyed by hardware port (e.g USB) and valued with devices (e.g FPGA/SOC). #SoC is always first, if it exists # Cell Get all memory instances from `hwname` and `devname`. See `QuartusTcl` doc for `N_levels`. # Cell Open a memory edit. If one is already open, pass. End a memory edit. If there are none open, pass. # Cell Reads memory from an instance `inst` into an array `data`.     Option `delete_mif` will delete temporary .mif file if set to `True`. # Cell Format input data to nested array required for memory #if not matrix, convert for tcl # Cell Writes `data` array to memory instance `inst`.     Option `delete_mif` will delete temporary .mif file if set to `True`. # Cell Read/write using `args`, a list of lists/tuples each of the form     (instance index, 'r' or 'w', data to write if 'w', bits to use if 'w').     Can repeat operation up to `reps`. # Cell # Cell # Cell # Cell # Cell Writes all routing and logic to the qsf file. # Cell Writes assignments `txt` to `sourcename` and includes them in `projectname`.qsf. #making sure command to source is in qsf # Cell #majority vote Quartus LUT mask for primitive logic cell, takes function to input and returns hex encoding     in:         lut (callable or list or numpy.array or dict) : function mapping over the port entry.         N (int) : number of ports. for standard 4-input LUT, 0=d, 1=c, 2=b, 3=a     out:         mask (str) : hex string encoding of the given mask # Cell #or 'full_path' Runs the timing analyzer, getting path information from `_from` to `_to`.     Gives `detail` (summary or full_path) on `npaths` and generates `filename`.     Returns text of that file. #generate intermediary tcl file #write over intermediate file with above command then read it