# Web UI for configuration of the CMSIS-DSP Build
#
# How to install
# pip install streamlit
#
# How to use
# streamlit run cmsisdspconfig.py
#
import streamlit as st
import textwrap
import re


st.set_page_config(page_title="CMSIS-DSP Configuration",layout="wide" )

# Options requiring a special management
NOTSTANDARD=["allTables","allInterpolations","allFFTs","Float16"]

HELIUM=False

config={}

# Used in UI
config["allTables"] = True
config["allFFTs"] = True
config["allInterpolations"] = True
config["MVEI"]=False
config["MVEF"]=False
config["NEON"]=False
config["HELIUM"]=False
config["HELIUMEXPERIMENTAL"]=False
config["Float16"]=True
config["HOST"]=False

config["COS_F32"]=False
config["COS_Q31"]=False
config["COS_Q15"]=False
config["SIN_F32"]=False
config["SIN_Q31"]=False
config["SIN_Q15"]=False
config["SIN_COS_F32"]=False
config["SIN_COS_Q31"]=False
config["LMS_NORM_Q31"]=False
config["LMS_NORM_Q15"]=False
config["CMPLX_MAG_Q31"]=False
config["CMPLX_MAG_Q15"]=False

config["CFFT_RADIX2_Q15"]=False
config["CFFT_RADIX4_Q15"]=False
config["CFFT_RADIX2_Q31"]=False
config["CFFT_RADIX4_Q31"]=False

config["BASICMATH"]=True  
config["COMPLEXMATH"]=True
config["CONTROLLER"]=True    
config["FASTMATH"]=True      
config["FILTERING"]=True     
config["MATRIX"]=True       
config["STATISTICS"]=True    
config["SUPPORT"]=True       
config["TRANSFORM"]=True   
config["SVM"]=True           
config["BAYES"]=True        
config["DISTANCE"]=True     
config["INTERPOLATION"]=True
config["QUATERNIONMATH"]=True

config["LOOPUNROLL"]=True
config["ROUNDING"]=False
config["MATRIXCHECK"]=False
config["AUTOVECTORIZE"] = False

# Used as options in command line
# in case the UI option is worded differently
realname={}
realname["COS_F32"]="ARM_COS_F32"
realname["COS_Q31"]="ARM_COS_Q31"
realname["COS_Q15"]="ARM_COS_Q15"
realname["SIN_F32"]="ARM_SIN_F32"
realname["SIN_Q31"]="ARM_SIN_Q31"
realname["SIN_Q15"]="ARM_SIN_Q15"
realname["SIN_COS_F32"]="ARM_SIN_COS_F32"
realname["SIN_COS_Q31"]="ARM_SIN_COS_Q31"
realname["LMS_NORM_Q31"]="ARM_LMS_NORM_Q31"
realname["LMS_NORM_Q15"]="ARM_LMS_NORM_Q15"
realname["CMPLX_MAG_Q31"]="ARM_CMPLX_MAG_Q31"
realname["CMPLX_MAG_Q15"]="ARM_CMPLX_MAG_Q15"
realname["CFFT_RADIX2_Q15"]="ARM_CFFT_RADIX2_Q15"
realname["CFFT_RADIX4_Q15"]="ARM_CFFT_RADIX4_Q15"
realname["CFFT_RADIX2_Q31"]="ARM_CFFT_RADIX2_Q31"
realname["CFFT_RADIX4_Q31"]="ARM_CFFT_RADIX4_Q31"

defaulton={}
defaulton["LOOPUNROLL"]=True 
defaulton["BASICMATH"]=True  
defaulton["COMPLEXMATH"]=True
defaulton["CONTROLLER"]=True   
defaulton["FASTMATH"]=True     
defaulton["FILTERING"]=True    
defaulton["MATRIX"]=True       
defaulton["STATISTICS"]=True   
defaulton["SUPPORT"]=True      
defaulton["TRANSFORM"]=True   
defaulton["SVM"]=True          
defaulton["BAYES"]=True        
defaulton["DISTANCE"]=True     
defaulton["INTERPOLATION"]=True
defaulton["QUATERNIONMATH"]=True


CFFTSIZE=[16,32,64,128,256,512,1024,2048,4096]
CFFTDATATYPE=['F64','F32','F16','Q31','Q15']

RFFTFASTSIZE=[32,64,128,256,512,1024,2048,4096]
RFFTFASTDATATYPE=['F64','F32','F16']

RFFTSIZE=[32,64,128,256,512,1024,2048,4096,8192]
RFFTDATATYPE=['F32','Q31','Q15']

DCTSIZE=[128,512,2048,8192]
DCTDATATYPE=['F32','Q31','Q15']

def joinit(iterable, delimiter):
    # Intersperse a delimiter between element of a list
    it = iter(iterable)
    yield next(it)
    for x in it:
        yield delimiter
        yield x

def options(l):
    return("".join(joinit(l," ")))

def computeCmakeOptions(config):
    global defaulton
    cmake={}
    if not config["allTables"]:
       cmake["CONFIGTABLE"]=True
       if config["allInterpolations"]:
          cmake["ALLFAST"]=True
       if config["allFFTs"]:
          cmake["ALLFFT"]=True
    if config["Float16"]:
       cmake["FLOAT16"]=True
    else:
       cmake["DISABLEFLOAT16"]=True

    for c in config:
        if not (c in NOTSTANDARD):
           if c in defaulton:
                if not config[c]:
                   if c in realname:
                      cmake[realname[c]]=False
                   else:
                      cmake[c]=False
           else:
                if config[c]:
                   if c in realname:
                      cmake[realname[c]]=True
                   else:
                      cmake[c]=True
    return cmake 

def removeDuplicates(l):
  return list(dict.fromkeys(l))

def genCMakeOptions(config):
    r=[]
    cmake = computeCmakeOptions(config)
    for c in cmake:
        if cmake[c]:
           r.append("-D%s=ON" % c)
        else:
           r.append("-D%s=OFF" % c)
    return(removeDuplicates(r),cmake)

def test(cmake,s):
    global defaulton
    if s in defaulton and not (s in cmake):
       return True 
    return(s in cmake and cmake[s])

def cfftCF32Config(cmake,size):
    result=[]
    if test(cmake,"CFFT_F32_%d" % size):
       a="-DARM_TABLE_TWIDDLECOEF_F32_%d" % size
       if HELIUM:
          b = "-DARM_TABLE_BITREVIDX_FXT_%d" % size
       else:
          b = "-DARM_TABLE_BITREVIDX_FLT_%d" % size 
       result=[a,b]
    return(result)

def cfftCF16Config(cmake,size):
    result=[]
    if test(cmake,"CFFT_F16_%d" % size):
       result =["-DARM_TABLE_TWIDDLECOEF_F16_%d" % size]
       result.append("-DARM_TABLE_BITREVIDX_FXT_%d" % size)
       result.append("-DARM_TABLE_BITREVIDX_FLT_%d" % size)
    return(result)

def cfftCF64Config(cmake,size):
    result=[]
    if test(cmake,"CFFT_F64_%d" % size):
       result =["-DARM_TABLE_TWIDDLECOEF_F64_%d" % size]
       result.append("-DARM_TABLE_BITREVIDX_FLT64_%d" % size)
    return(result)


def cfftCFixedConfig(cmake,dt,size):
    result=[]
    if test(cmake,"CFFT_%s_%d" % (dt,size)):
       a="-DARM_TABLE_TWIDDLECOEF_%s_%d" % (dt,size)
       b = "-DARM_TABLE_BITREVIDX_FXT_%d" % size
       result=[a,b]
    return(result)

def crfftFastCF64Config(cmake,size):
    result=[]
    s1 = size >> 1
    if test(cmake,"RFFT_FAST_F64_%d" % size):
       result =[]
       result.append("-DARM_TABLE_TWIDDLECOEF_F64_%d" % s1)
       result.append("-DARM_TABLE_BITREVIDX_FLT64_%d" % s1)
       result.append("-DARM_TABLE_TWIDDLECOEF_RFFT_F64_%d" % size)
       result.append("-DARM_TABLE_TWIDDLECOEF_F64_%d" % s1)
       
    return(result)

def crfftFastCF32Config(cmake,size):
    result=[]
    s1 = size >> 1
    if test(cmake,"RFFT_FAST_F32_%d" % size):
       result =[]
       result.append("-DARM_TABLE_TWIDDLECOEF_F32_%d" % s1)
       result.append("-DARM_TABLE_BITREVIDX_FLT_%d" % s1)
       result.append("-DARM_TABLE_TWIDDLECOEF_RFFT_F32_%d" % size)
       
    return(result)

def crfftFastCF16Config(cmake,size):
    result=[]
    s1 = size >> 1
    if test(cmake,"RFFT_FAST_F16_%d" % size):
       result =[]
       result.append("-DARM_TABLE_TWIDDLECOEF_F16_%d" % s1)
       result.append("-DARM_TABLE_BITREVIDX_FLT_%d" % s1)
       result.append("-DARM_TABLE_BITREVIDX_FXT_%d" % s1)
       result.append("-DARM_TABLE_TWIDDLECOEF_RFFT_F16_%d" % size)
       
    return(result)

# Deprecated RFFT used in DCT
def crfftF32Config(cmake,size):
    result=[]
    s1 = size >> 1
    if test(cmake,"RFFT_FAST_F16_%d" % size):
       result =[]
       result.append("-DARM_TABLE_REALCOEF_F32")
       result.append("-ARM_TABLE_BITREV_%d" % s1)
       result.append("-ARM_TABLE_TWIDDLECOEF_F32_%d" % s1)
       
    return(result)


def crfftFixedConfig(cmake,dt,size):
    result=[]
    s1 = size >> 1
    if test(cmake,"RFFT_%s_%d" % (dt,size)):
       result =[]
       result.append("-DARM_TABLE_REALCOEF_%s" % dt)
       result.append("-DARM_TABLE_TWIDDLECOEF_%s_%d" % (dt,s1))
       result.append("-DARM_TABLE_BITREVIDX_FXT_%d" % s1)
       
    return(result)


def dctConfig(cmake,dt,size):
    result=[]
    if test(cmake,"DCT4_%s_%d" % (dt,size)):
       result =[]
       result.append("-DARM_TABLE_DCT4_%s_%d" % (dt,size))
       result.append("-DARM_TABLE_REALCOEF_F32")
       result.append("-DARM_TABLE_BITREV_1024" )
       result.append("-DARM_TABLE_TWIDDLECOEF_%s_4096" % dt)
       
    return(result)

# Convert cmake options to make flags
def interpretCmakeOptions(cmake):
    r=[]
    if test(cmake,"CONFIGTABLE"):
       r.append("-DARM_DSP_CONFIG_TABLES")
       # In Make configuration we build all modules.
       # So the code for FFT and FAST maths may be included
       # so we allow the table to be included if they are needed.
       r.append("-DARM_FAST_ALLOW_TABLES")
       r.append("-DARM_FFT_ALLOW_TABLES")
       for size in CFFTSIZE:
           r += cfftCF32Config(cmake,size)
           r += cfftCF16Config(cmake,size)
           r += cfftCF64Config(cmake,size)
           r += cfftCFixedConfig(cmake,"Q31",size)
           r += cfftCFixedConfig(cmake,"Q15",size)

       for size in RFFTFASTSIZE:
          r += crfftFastCF64Config(cmake,size)
          r += crfftFastCF32Config(cmake,size)
          r += crfftFastCF16Config(cmake,size)

       for size in RFFTSIZE:
          r += crfftFixedConfig(cmake,"F32",size)
          r += crfftFixedConfig(cmake,"Q31",size)
          r += crfftFixedConfig(cmake,"Q15",size)

       for size in DCTSIZE:
          r += dctConfig(cmake,"F32",size)
          r += dctConfig(cmake,"Q31",size)
          r += dctConfig(cmake,"Q15",size)

        
    
    if test(cmake,"ALLFAST"):
       r.append("-DARM_ALL_FAST_TABLES")
    if test(cmake,"ALLFFT"):
       r.append("-DARM_ALL_FFT_TABLES")

    if test(cmake,"LOOPUNROLL"):
       r.append("-DARM_MATH_LOOPUNROLL")
    if test(cmake,"ROUNDING"):
       r.append("-DARM_MATH_ROUNDING")
    if test(cmake,"MATRIXCHECK"):
       r.append("-DARM_MATH_MATRIX_CHECK")
    if test(cmake,"AUTOVECTORIZE"):
       r.append("-DARM_MATH_AUTOVECTORIZE")
    if test(cmake,"DISABLEFLOAT16"):
       r.append("-DDISABLEFLOAT16")
    if test(cmake,"NEON"):
       r.append("-DARM_MATH_NEON")
       r.append("-DARM_MATH_NEON_EXPERIMENTAL")
    if test(cmake,"HOST"):
        r.append("-D__GNUC_PYTHON__")
    
    if test(cmake,"ARM_COS_F32"):
        r.append("-DARM_TABLE_SIN_F32")
    if test(cmake,"ARM_COS_Q31"):
        r.append("-DARM_TABLE_SIN_Q31")
    if test(cmake,"ARM_COS_Q15"):
        r.append("-DARM_TABLE_SIN_Q15")

    if test(cmake,"ARM_SIN_F32"):
        r.append("-DARM_TABLE_SIN_F32")
    if test(cmake,"ARM_SIN_Q31"):
        r.append("-DARM_TABLE_SIN_Q31")
    if test(cmake,"ARM_SIN_Q15"):
        r.append("-DARM_TABLE_SIN_Q15")

    if test(cmake,"ARM_SIN_COS_F32"):
        r.append("-DARM_TABLE_SIN_F32")
    if test(cmake,"ARM_SIN_COS_Q31"):
        r.append("-DARM_TABLE_SIN_Q31")

    if test(cmake,"ARM_LMS_NORM_Q31"):
        r.append("-DARM_TABLE_RECIP_Q31")

    if test(cmake,"ARM_LMS_NORM_Q15"):
        r.append("-DARM_TABLE_RECIP_Q15")

    if test(cmake,"ARM_CMPLX_MAG_Q31"):
        r.append("-DARM_TABLE_FAST_SQRT_Q31_MVE")

    if test(cmake,"ARM_CMPLX_MAG_Q15"):
        r.append("-DARM_TABLE_FAST_SQRT_Q15_MVE")

    if test(cmake,"MVEI"):
       r.append("-DARM_MATH_MVEI")

    if test(cmake,"MVEF"):
       r.append("-DARM_MATH_MVEF")

    if test(cmake,"HELIUMEXPERIMENTAL"):
       r.append("-DARM_MATH_HELIUM_EXPERIMENTAL")

    if test(cmake,"HELIUM") or test(cmake,"MVEF") or test(cmake,"MVEI"):
       r.append("-IPrivateInclude")

    if test(cmake,"NEON") or test(cmake,"NEONEXPERIMENTAL"):
       r.append("-IComputeLibrary/Include")

    if test(cmake,"ARM_CFFT_RADIX2_Q15") or test(cmake,"ARM_CFFT_RADIX4_Q15"):
        r.append("-DARM_TABLE_TWIDDLECOEF_Q15_4096")
        r.append("-DARM_TABLE_BITREV_1024")

    if test(cmake,"ARM_CFFT_RADIX2_Q31") or test(cmake,"ARM_CFFT_RADIX4_Q31"):
        r.append("-DARM_TABLE_TWIDDLECOEF_Q31_4096")
        r.append("-DARM_TABLE_BITREV_1024")

    return (removeDuplicates(r))

def genMakeOptions(config):
    cmake = computeCmakeOptions(config)
    r=interpretCmakeOptions(cmake)
    return(r,cmake)


def check(config,s,name=None,comment=None):
    if comment is not None:
       st.sidebar.text(comment)
    if name is None:
       config[s]=st.sidebar.checkbox(s,value=config[s])
    else:
       config[s]=st.sidebar.checkbox(name,value=config[s])
    return(config[s])

def genconfig(config,transform,sizes,datatypes):
    global realname
    for size in sizes:
        for dt in datatypes:
            s="%s_%s_%s" % (transform,dt,size)
            config[s] = False
            realname[s] = s

def hasDCTF32(config):
    result=False
    for size in DCTSIZE:
            s="DCT4_F32_%s" % size
            if config[s]:
               result = True
    return(result)

def multiselect(config,name,options):
    default=[]
    for r in options:
        if config[r]:
            default.append(r)
    result=st.sidebar.multiselect(name,options,default=default)
    for r in options:
        config[r] = False
    for r in result:
        config[r] = True

def genui(config,transform,sizes,datatypes):
    keepF32 = True
    # RFFT F32 is deprecated and needed only for DCT4
    if transform == "RFFT":
       keepF32 = hasDCTF32(config)
    selected=st.sidebar.multiselect("Sizes",sizes)
    for size in selected:
        options=[]
        for dt in datatypes:
            if dt != "F32" or keepF32:
               s="%s_%s_%s" % (transform,dt,size)
               options.append(s)
        multiselect(config,"Nb = %d" % size,options)


def configMake(config):
    st.sidebar.header('Table Configuration')
    st.sidebar.info("Several options to include only the tables needed in an app and minimize code size.")
    if not check(config,"allTables","All tables included"):

        if not check(config,"allFFTs","All FFT tables included"):
           st.sidebar.markdown("#### CFFT")
           genui(config,"CFFT",CFFTSIZE,CFFTDATATYPE)

           st.sidebar.info("Following transforms are using the CFFT. You need to enable the needed CFFTs above.")

           st.sidebar.markdown("#### RFFT FAST")
           genui(config,"RFFT_FAST",RFFTFASTSIZE,RFFTFASTDATATYPE)
           st.sidebar.markdown("#### DCT4")
           genui(config,"DCT4",DCTSIZE,DCTDATATYPE)
           st.sidebar.markdown("#### RFFT")
           genui(config,"RFFT",RFFTSIZE,RFFTDATATYPE)

           st.sidebar.markdown("#### Radix2 and Radix4 CFFT")
           st.sidebar.info("Those functions are deprecated")
           multiselect(config,"Radix",["CFFT_RADIX2_Q15","CFFT_RADIX4_Q15","CFFT_RADIX2_Q31","CFFT_RADIX4_Q31"])

           



        if not check(config,"allInterpolations",'All interpolation tables included'):
           selected=st.sidebar.multiselect("Functions",["Cosine","Sine","SineCosine","Normalized LMS"])
           for s in selected:
               if s == "Cosine":
                  multiselect(config,"Cosine",["COS_F32","COS_Q31","COS_Q15"])
               if s == "Sine":
                  multiselect(config,"Sine",["SIN_F32","SIN_Q31","SIN_Q15"])
               if s == "SineCosine":
                  multiselect(config,"SineCosine",["SIN_COS_F32","SIN_COS_Q31"])
               if s == "Normalized LMS":
                  multiselect(config,"Normalized LMS",["LMS_NORM_Q31","LMS_NORM_Q15"])

           if config["MVEI"]:
              st.sidebar.markdown("#### Complex Magnitude")
              multiselect(config,"Complex Magnitude",["CMPLX_MAG_Q31","CMPLX_MAG_Q15"])



def configCMake(config):
    multiselect(config,"Folders",["BASICMATH",     
                       "COMPLEXMATH",   
                       "CONTROLLER",    
                       "FASTMATH",      
                       "FILTERING",     
                       "MATRIX",        
                       "STATISTICS",    
                       "SUPPORT",       
                       "TRANSFORM",     
                       "SVM",           
                       "BAYES",         
                       "DISTANCE",      
                       "INTERPOLATION","QUATERNIONMATH"])  
    configMake(config)

genconfig(config,"CFFT",CFFTSIZE,CFFTDATATYPE)
genconfig(config,"RFFT_FAST",RFFTFASTSIZE,RFFTFASTDATATYPE)
genconfig(config,"RFFT",RFFTSIZE,RFFTDATATYPE)
genconfig(config,"DCT4",DCTSIZE,DCTDATATYPE)

st.title('CMSIS-DSP Configuration')

st.warning("It is a work in progress. Only a small subset of the combinations has been tested.")

st.sidebar.header('Feature Configuration')
st.sidebar.info("To build on host. All features will be enabled.")
forHost=check(config,"HOST")

if not forHost:
   st.sidebar.info("Enable or disable float16 support")
   check(config,"Float16")

   st.sidebar.info("Some configurations for the CMSIS-DSP code.")
   check(config,"LOOPUNROLL")
   st.sidebar.text("Decrease performances when selected:")
   check(config,"ROUNDING")
   check(config,"MATRIXCHECK")
   
   st.sidebar.header('Vector extensions')
   st.sidebar.info("Enable vector code. It is not automatic for Neon. Use of Helium will enable new options to select some interpolation tables.")
   archi=st.sidebar.selectbox("Vector",('None','Helium','Neon'))
   if archi == 'Neon':
       config["NEON"]=True
   if archi == 'Helium':
      multiselect(config,"MVE configuration",["MVEI","MVEF"])
      HELIUM=True
      st.sidebar.info("When checked some experimental versions will be enabled and may be less performant than scalar version depending on the architecture.")
      check(config,"HELIUMEXPERIMENTAL")
   if archi != 'None':
       st.sidebar.info("When autovectorization is on, pure C code will be compiled. The version with C intrinsics won't be compiled.")
       check(config,"AUTOVECTORIZE")



st.sidebar.header('Build Method')

st.sidebar.info("With cmake, some folders can be removed from the build.")
selected=st.sidebar.selectbox('Select', ("Make","Cmake"),index=1)



if selected == "Make":
    if not forHost:
       configMake(config)
    result,cmake=genMakeOptions(config)
else:
    if not forHost:
       configCMake(config)
    result,cmake=genCMakeOptions(config)

st.header('Build options for %s command line' % selected)

if selected == "Make":
    if test(cmake,"FLOAT16"):
            st.info("Float16 is selected. You may need to pass compiler specific options for the compiler to recognize the float16 type.")

mode=st.selectbox("Mode",["txt","MDK","sh","bat"])

if mode=="txt":
   st.code(textwrap.fill(options(result)))

if mode=="MDK":
   opts=options(result)
   includes=""
   maybeincludes=re.findall(r'\-I([^\s]+)',opts)
   # Managed in MDK pack file
   #if maybeincludes:
   #   includes = maybeincludes
   #   st.text("Following include directories must be added")
   #   st.code(includes)
   opts=re.sub(r'\-D','',opts)
   opts=re.sub(r'\-I[^\s]+','',opts)
   st.text("MDK Preprocessor Symbols ")
   st.code(opts)


if mode=="sh":
   lines=options(result).split() 
   txt=""
   for l in lines:
       txt += " %s \\\n" % l
   txt += "\n"
   st.code(txt)

if mode=="bat":
   lines=options(result).split() 
   txt=""
   for l in lines:
       txt += " %s ^\n" % l
   txt += "\n"
   st.code(txt)

