From 2455c2bb0e35bb476ec073174b42f8c3adddc183 Mon Sep 17 00:00:00 2001 From: Sioni Summers <14807534+thesps@users.noreply.github.com> Date: Thu, 24 Nov 2022 16:37:07 +0100 Subject: [PATCH] Move VHDL backend simulators to separate module. Use C++ fixed point adapter in VHDL backend decision_function input/output handling --- conifer/backends/vhdl/__init__.py | 5 +- conifer/backends/vhdl/simulators.py | 99 ++++++++++++++++ conifer/backends/vhdl/writer.py | 131 +++------------------- conifer/utils/fixed_point.py | 11 +- conifer/utils/fixed_point_conversions.cpp | 7 ++ 5 files changed, 133 insertions(+), 120 deletions(-) create mode 100644 conifer/backends/vhdl/simulators.py diff --git a/conifer/backends/vhdl/__init__.py b/conifer/backends/vhdl/__init__.py index 4b198818..8307a926 100644 --- a/conifer/backends/vhdl/__init__.py +++ b/conifer/backends/vhdl/__init__.py @@ -1,2 +1,3 @@ -from .writer import write, auto_config, sim_compile, decision_function, build, Simulators -simulator = Simulators.xsim +from conifer.backends.vhdl.writer import write, auto_config, sim_compile, decision_function, build +from conifer.backends.vhdl.simulators import Modelsim, GHDL, Xsim +simulator = Xsim diff --git a/conifer/backends/vhdl/simulators.py b/conifer/backends/vhdl/simulators.py new file mode 100644 index 00000000..3a222dbc --- /dev/null +++ b/conifer/backends/vhdl/simulators.py @@ -0,0 +1,99 @@ +import os +import logging +logger = logging.getLogger(__name__) + + +def _compile_sim(simulator, odir): + logger.info(f'Compiling simulation for {simulator.__name__.lower()} simulator') + logger.debug(f'Compiling simulation with command "{simulator._compile_cmd}"') + cwd = os.getcwd() + os.chdir(odir) + success = os.system(simulator._compile_cmd) + os.chdir(cwd) + if(success > 0): + logger.error(f"'sim_compile' failed, check {simulator.__name__.lower()}_compile.log") + return success == 0 + +def _run_sim(simulator, odir): + logger.info(f'Running simulation for {simulator.__name__.lower()} simulator') + logger.debug(f'Running simulation with command "{simulator._run_cmd}"') + cwd = os.getcwd() + os.chdir(odir) + success = os.system(simulator._run_cmd) + os.chdir(cwd) + if(success > 0): + logger.error(f"'sim_compile' failed, check {simulator.__name__.lower()}.log") + return success == 0 + +class Modelsim: + _compile_cmd = 'sh modelsim_compile.sh > modelsim_compile.log' + _run_cmd = 'vsim -c -do "vsim -L BDT -L xil_defaultlib xil_defaultlib.testbench; run -all; quit -f" > vsim.log' + + def write_scripts(outputdir, filedir, n_classes): + f = open(os.path.join(filedir,'./scripts/modelsim_compile.sh'),'r') + fout = open(f'{outputdir}/modelsim_compile.sh','w') + for line in f.readlines(): + if 'insert arrays' in line: + for i in range(n_classes): + newline = f'vcom -2008 -work BDT ./firmware/Arrays{i}.vhd\n' + fout.write(newline) + else: + fout.write(line) + f.close() + fout.close() + + f = open(f'{outputdir}/test.tcl', 'w') + f.write('vsim -L BDT -L xil_defaultlib xil_defaultlib.testbench\n') + f.write('run 100 ns\n') + f.write('quit -f\n') + f.close() + + def compile(odir): + return _compile_sim(Modelsim, odir) + + def run_sim(odir): + return _run_sim(Modelsim, odir) + +class GHDL: + _compile_cmd = 'sh ghdl_compile.sh > ghdl_compile.log' + _run_cmd = 'ghdl -r --std=08 --work=xil_defaultlib testbench > ghdl.log' + def write_scripts(outputdir, filedir, n_classes): + f = open(os.path.join(filedir, './scripts/ghdl_compile.sh'), 'r') + fout = open(f'{outputdir}/ghdl_compile.sh', 'w') + for line in f.readlines(): + if 'insert arrays' in line: + for i in range(n_classes): + newline = f'ghdl -a --std=08 --work=BDT ./firmware/Arrays{i}.vhd\n' + fout.write(newline) + else: + fout.write(line) + f.close() + fout.close() + + def compile(odir): + return _compile_sim(GHDL, odir) + + def run_sim(odir): + return _run_sim(GHDL, odir) + +class Xsim: + _compile_cmd = 'sh xsim_compile.sh > xsim_compile.log' + _run_cmd = 'xsim -R bdt_tb > xsim.log' + def write_scripts(outputdir, filedir, n_classes): + f = open(os.path.join(filedir, './scripts/xsim_compile.sh'), 'r') + fout = open(f'{outputdir}/xsim_compile.sh', 'w') + for line in f.readlines(): + if 'insert arrays' in line: + for i in range(n_classes): + newline = f'xvhdl -2008 -work BDT ./firmware/Arrays{i}.vhd\n' + fout.write(newline) + else: + fout.write(line) + f.close() + fout.close() + + def compile(odir): + return _compile_sim(Xsim, odir) + + def run_sim(odir): + return _run_sim(Xsim, odir) diff --git a/conifer/backends/vhdl/writer.py b/conifer/backends/vhdl/writer.py index 126fa161..b537c91f 100644 --- a/conifer/backends/vhdl/writer.py +++ b/conifer/backends/vhdl/writer.py @@ -8,11 +8,6 @@ import logging logger = logging.getLogger(__name__) -class Simulators(Enum): - modelsim = 0 - xsim = 1 - ghdl = 2 - def write(model): model.save() @@ -72,6 +67,8 @@ def write(model): mult = 2**dtype_frac fp = FixedPointConverter(cfg['Precision']) + # TODO this should be attached to the model differently + model._fp_converter = fp # binary classification only uses one set of trees n_classes = 1 if ensembleDict['n_classes'] == 2 else ensembleDict['n_classes'] @@ -82,16 +79,12 @@ def write(model): for i in range(n_classes): fout[i].write(array_header_text) fout[i].write('package Arrays{} is\n\n'.format(i)) - #fout[i].write(' constant initPredict : ty := to_ty({});\n'.format(int(np.round(ensembleDict['init_predict'][i] * mult)))) fout[i].write(' constant initPredict : ty := to_ty({});\n'.format(fp.to_int(np.float64(ensembleDict['init_predict'][i])))) - #fout[i].write(' constant initPredict : ty := to_ty({});\n'.format(int(np.round(ensembleDict['init_predict'][i] * mult)))) - # Loop over fields (childrenLeft, childrenRight, threshold...) for field in ensembleDict['trees'][0][0].keys(): # Write the type for this field to each classes' file for iclass in range(n_classes): - #dtype = 'txArray2D' if field == 'threshold' else 'tyArray2D' if field == 'value' else 'intArray2D' fieldName = field # The threshold and value arrays are declared as integers, then cast # So need a separate constant @@ -99,7 +92,6 @@ def write(model): fieldName += '_int' # Convert the floating point values to integers for ii, trees in enumerate(ensembleDict['trees']): - #ensembleDict['trees'][ii][iclass][field] = np.round(np.array(ensembleDict['trees'][ii][iclass][field]) * mult).astype('int') ensembleDict['trees'][ii][iclass][field] = np.array([fp.to_int(x) for x in ensembleDict['trees'][ii][iclass][field]]) nElem = 'nLeaves' if field == 'iLeaf' else 'nNodes' fout[iclass].write(' constant {} : intArray2D{}(0 to nTrees - 1) := ('.format(fieldName, nElem)) @@ -117,7 +109,8 @@ def write(model): fout[i].write('end Arrays{};'.format(i)) fout[i].close() - write_sim_scripts(cfg, filedir, n_classes) + from conifer.backends.vhdl import simulator + simulator.write_scripts(cfg['OutputDir'], filedir, n_classes) f = open('{}/SimulationInput.txt'.format(cfg['OutputDir']), 'w') f.write(' '.join(map(str, [0] * ensembleDict['n_features']))) @@ -193,67 +186,24 @@ def auto_config(): def sim_compile(model): from conifer.backends.vhdl import simulator - config = copy.deepcopy(model.config) - xsim_cmd = 'sh xsim_compile.sh > xsim_compile.log' - msim_cmd = 'sh modelsim_compile.sh > modelsim_compile.log' - ghdl_cmd = 'sh ghdl_compile.sh > ghdl_compile.log' - cmdmap = {Simulators.modelsim : msim_cmd, - Simulators.xsim : xsim_cmd, - Simulators.ghdl : ghdl_cmd} - cmd = cmdmap[simulator] - logger.info(f'Compiling simulation for {simulator} simulator') - logger.debug(f'Compiling simulation with command "{cmd}"') - cwd = os.getcwd() - os.chdir(config['OutputDir']) - success = os.system(cmd) - os.chdir(cwd) - if(success > 0): - logger.error("'sim_compile' failed, check {}_compile.log".format(simulator.name)) - return + return simulator.compile(model.config['OutputDir']) def decision_function(X, model, trees=False): from conifer.backends.vhdl import simulator config = copy.deepcopy(model.config) - msim_cmd = 'vsim -c -do "vsim -L BDT -L xil_defaultlib xil_defaultlib.testbench; run -all; quit -f" > vsim.log' - xsim_cmd = 'xsim -R bdt_tb > xsim.log' - ghdl_cmd = 'ghdl -r --std=08 --work=xil_defaultlib testbench > ghdl.log' - cmdmap = {Simulators.modelsim : msim_cmd, - Simulators.xsim : xsim_cmd, - Simulators.ghdl : ghdl_cmd} - cmd = cmdmap[simulator] - msim_log = 'vsim.log' - xsim_log = 'xsim.log' - ghdl_log = 'ghdl.log' - logmap = {Simulators.modelsim : msim_log, - Simulators.xsim : xsim_log, - Simulators.ghdl : ghdl_log} - logfile = logmap[simulator] - logger.info(f'Running simulation for {simulator} simulator') - - dtype = config['Precision'] - if not 'ap_fixed' in dtype: - logger.error("Only ap_fixed is currently supported, exiting") - return - dtype = dtype.replace('ap_fixed<', '').replace('>', '') - dtype_n = int(dtype.split(',')[0].strip()) # total number of bits - dtype_int = int(dtype.split(',')[1].strip()) # number of integer bits - dtype_frac = dtype_n - dtype_int # number of fractional bits - mult = 2**dtype_frac - Xint = (X * mult).astype('int') - logger.debug(f'Converting X ({X.dtype}), to integers with scale factor {mult} from {config["Precision"]}') + Xint = np.array([model._fp_converter.to_int(x) for x in X.ravel()]).reshape(X.shape) np.savetxt('{}/SimulationInput.txt'.format(config['OutputDir']), Xint, delimiter=' ', fmt='%d') - cwd = os.getcwd() - os.chdir(config['OutputDir']) - logger.debug(f'Running simulation with command "{cmd}"') - success = os.system(cmd) - os.chdir(cwd) - if(success > 0): - logger.error("'decision_function' failed, see {}.log".format(logfile)) - return - y = np.loadtxt('{}/SimulationOutput.txt'.format(config['OutputDir'])) * 1. / mult + success = simulator.run_sim(config['OutputDir']) + if not success: + return + y = np.loadtxt('{}/SimulationOutput.txt'.format(config['OutputDir'])).astype(np.int32) + y = np.array([model._fp_converter.from_int(yi) for yi in y.ravel()]).reshape(y.shape) + if np.ndim(y) == 1: + y = np.expand_dims(y, 1) + if trees: logger.warn("Individual tree output (trees=True) not yet implemented for this backend") return y @@ -273,55 +223,4 @@ def build(config, **kwargs): logger.error("build failed, check build.log") return False return True - -def write_sim_scripts(cfg, filedir, n_classes): - from conifer.backends.vhdl import simulator - fmap = {Simulators.modelsim : write_modelsim_scripts, - Simulators.xsim : write_xsim_scripts, - Simulators.ghdl : write_ghdl_scripts,} - fmap[simulator](cfg, filedir, n_classes) - -def write_modelsim_scripts(cfg, filedir, n_classes): - f = open(os.path.join(filedir,'./scripts/modelsim_compile.sh'),'r') - fout = open('{}/modelsim_compile.sh'.format(cfg['OutputDir']),'w') - for line in f.readlines(): - if 'insert arrays' in line: - for i in range(n_classes): - newline = 'vcom -2008 -work BDT ./firmware/Arrays{}.vhd\n'.format(i) - fout.write(newline) - else: - fout.write(line) - f.close() - fout.close() - - f = open('{}/test.tcl'.format(cfg['OutputDir']), 'w') - f.write('vsim -L BDT -L xil_defaultlib xil_defaultlib.testbench\n') - f.write('run 100 ns\n') - f.write('quit -f\n') - f.close() - -def write_xsim_scripts(cfg, filedir, n_classes): - f = open(os.path.join(filedir, './scripts/xsim_compile.sh'), 'r') - fout = open('{}/xsim_compile.sh'.format(cfg['OutputDir']), 'w') - for line in f.readlines(): - if 'insert arrays' in line: - for i in range(n_classes): - newline = 'xvhdl -2008 -work BDT ./firmware/Arrays{}.vhd\n'.format(i) - fout.write(newline) - else: - fout.write(line) - f.close() - fout.close() - -def write_ghdl_scripts(cfg, filedir, n_classes): - f = open(os.path.join(filedir, './scripts/ghdl_compile.sh'), 'r') - fout = open('{}/ghdl_compile.sh'.format(cfg['OutputDir']), 'w') - for line in f.readlines(): - if 'insert arrays' in line: - for i in range(n_classes): - newline = 'ghdl -a --std=08 --work=BDT ./firmware/Arrays{}.vhd\n'.format(i) - fout.write(newline) - else: - fout.write(line) - f.close() - fout.close() \ No newline at end of file + \ No newline at end of file diff --git a/conifer/utils/fixed_point.py b/conifer/utils/fixed_point.py index 769e063f..1df67445 100644 --- a/conifer/utils/fixed_point.py +++ b/conifer/utils/fixed_point.py @@ -1,5 +1,6 @@ import os -import shutil +import logging +logger = logging.getLogger(__name__) class FixedPointConverter: ''' @@ -12,6 +13,7 @@ def __init__(self, type_string): args: type_string : string for the ap_ type, e.g. ap_fixed<16,6,AP_RND,AP_SAT> ''' + logger.info(f'Constructing converter for {type_string}') self.type_string = type_string self.sani_type = type_string.replace('<','_').replace('>','').replace(',','_') filedir = os.path.dirname(os.path.abspath(__file__)) @@ -32,6 +34,7 @@ def __init__(self, type_string): curr_dir = os.getcwd() os.chdir(cpp_filedir) cmd = f"g++ -O3 -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) -I/cvmfs/cms.cern.ch/slc7_amd64_gcc900/external/hls/2019.08/include/ {self.sani_type}.cpp -o {self.sani_type}.so" + logger.debug(f'Compiling with command {cmd}') try: ret_val = os.system(cmd) if ret_val != 0: @@ -40,6 +43,7 @@ def __init__(self, type_string): os.chdir(curr_dir) os.chdir(cpp_filedir) + logger.debug(f'Importing compiled module {self.sani_type}.so') try: import importlib.util spec = importlib.util.spec_from_file_location('fixed_point', f'{self.sani_type}.so') @@ -54,4 +58,7 @@ def to_int(self, x): return self.lib.to_int(x) def to_double(self, x): - return self.lib.to_double(x) \ No newline at end of file + return self.lib.to_double(x) + + def from_int(self, x): + return self.lib.from_int(x) \ No newline at end of file diff --git a/conifer/utils/fixed_point_conversions.cpp b/conifer/utils/fixed_point_conversions.cpp index c3a258bf..f9e7419b 100644 --- a/conifer/utils/fixed_point_conversions.cpp +++ b/conifer/utils/fixed_point_conversions.cpp @@ -13,9 +13,16 @@ double to_double(double x){ return y.to_double(); } +double from_int(int x){ + T y; + y.V = x; + return y.to_double(); +} + namespace py = pybind11; PYBIND11_MODULE(fixed_point, m) { m.doc() = "fixed point conversion"; m.def("to_int", &to_int, "Get the integer representation of the ap_fixed"); m.def("to_double", &to_double, "Get the double representation of the ap_fixed"); + m.def("from_int", &from_int, "Set the underlying bits of the ap_fixed from int, return the double"); } \ No newline at end of file