From f34238d92d85a392924488b5449f54d17c9d6396 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Mon, 10 May 2021 15:09:58 +0100 Subject: [PATCH 01/34] [nn] Added ability to gather activation stastitics during LUT inference. --- src/logicnets/nn.py | 49 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/logicnets/nn.py b/src/logicnets/nn.py index 6effa5cc9..6eb8ab2f9 100644 --- a/src/logicnets/nn.py +++ b/src/logicnets/nn.py @@ -44,10 +44,34 @@ def generate_truth_tables(model: nn.Module, verbose: bool = False) -> None: model.training = training # TODO: Create a container module which performs this function. -def lut_inference(model: nn.Module) -> None: +def lut_inference(model: nn.Module, track_used_luts: bool = False) -> None: for name, module in model.named_modules(): if type(module) == SparseLinearNeq: - module.lut_inference() + module.lut_inference(track_used_luts=track_used_luts) + +# TODO: Create a container module which performs this function. +def save_luts(model: nn.Module, path: str) -> None: + lut_dict = {} + for name, module in model.named_modules(): + if type(module) == SparseLinearNeq: + luts = module.neuron_truth_tables + indices = list(map(lambda x: x[0], luts)) + tt_inputs = list(map(lambda x: x[1], luts)) + tt_input_bin_str = list(map(lambda x: list(map(lambda y: list(map(lambda z: module.input_quant.get_bin_str(z), y)), x)), tt_inputs)) + tt_float_outputs = list(map(lambda x: x[2], luts)) + tt_bin_outputs = list(map(lambda x: x[3], luts)) + tt_outputs_bin_str = list(map(lambda x: list(map(lambda y: module.output_quant.get_bin_str(y), x)), tt_bin_outputs)) + histogram = module.used_luts_histogram + lut_dict[name] = { + 'indices': indices, + 'input_state_space': tt_inputs, + 'input_state_space_bin_str': tt_input_bin_str, + 'output_state_space_float': tt_float_outputs, + 'output_state_space_bin': tt_bin_outputs, + 'output_state_space_bin_str': tt_outputs_bin_str, + 'histogram': histogram, + } + torch.save(lut_dict, path) # TODO: Create a container module which performs this function. def neq_inference(model: nn.Module) -> None: @@ -111,6 +135,8 @@ def __init__(self, in_features: int, out_features: int, input_quant, output_quan self.neuron_truth_tables = None self.apply_input_quant = apply_input_quant self.apply_output_quant = apply_output_quant + self.track_used_luts = False + self.used_luts_histogram = None # TODO: Move the verilog string templates to elsewhere # TODO: Move this to another class @@ -158,8 +184,9 @@ def gen_neuron_verilog(self, index, module_name): lut_string += f"\t\t\t{int(cat_input_bitwidth)}'b{entry_str}: M1r = {int(output_bitwidth)}'b{res_str};\n" return generate_lut_verilog(module_name, int(cat_input_bitwidth), int(output_bitwidth), lut_string) - def lut_inference(self): + def lut_inference(self, track_used_luts=False): self.is_lut_inference = True + self.track_used_luts = track_used_luts self.input_quant.bin_output() self.output_quant.bin_output() @@ -169,7 +196,7 @@ def neq_inference(self): self.output_quant.float_output() # TODO: This function might be a useful utility outside of this class.. - def table_lookup(self, connected_input: Tensor, input_perm_matrix: Tensor, bin_output_states: Tensor) -> Tensor: + def table_lookup(self, connected_input: Tensor, input_perm_matrix: Tensor, bin_output_states: Tensor, neuron_lut_histogram=None) -> Tensor: fan_in_size = connected_input.shape[1] ci_bcast = connected_input.unsqueeze(2) # Reshape to B x Fan-in x 1 pm_bcast = input_perm_matrix.t().unsqueeze(0) # Reshape to 1 x Fan-in x InputStates @@ -178,17 +205,29 @@ def table_lookup(self, connected_input: Tensor, input_perm_matrix: Tensor, bin_o if not (matches == torch.ones_like(matches,dtype=matches.dtype)).all(): raise Exception(f"One or more vectors in the input is not in the possible input state space") indices = torch.argmax(eq.type(torch.int64),dim=1) + if self.track_used_luts: + # TODO: vectorize this loop + for i in indices: + neuron_lut_histogram[i] += 1 return bin_output_states[indices] def lut_forward(self, x: Tensor) -> Tensor: if self.apply_input_quant: x = self.input_quant(x) # Use this to fetch the bin output of the input, if the input isn't already in binary format + # TODO: Put this in a child class(?) + # TODO: Add support for non-uniform fan-in + if self.track_used_luts: + if self.used_luts_histogram is None: + self.used_luts_histogram = self.out_features * [None] + for i in range(self.out_features): + self.used_luts_histogram[i] = torch.zeros(size=(len(self.neuron_truth_tables[i][2]),), dtype=torch.int64) y = torch.zeros((x.shape[0],self.out_features)) # Perform table lookup for each neuron output for i in range(self.out_features): indices, input_perm_matrix, float_output_states, bin_output_states = self.neuron_truth_tables[i] + neuron_lut_histogram = self.used_luts_histogram[i] if self.track_used_luts else None connected_input = x[:,indices] - y[:,i] = self.table_lookup(connected_input, input_perm_matrix, bin_output_states) + y[:,i] = self.table_lookup(connected_input, input_perm_matrix, bin_output_states, neuron_lut_histogram=neuron_lut_histogram) return y def forward(self, x: Tensor) -> Tensor: From aa622a4bde728f5c0407f031cdff5a3334e1dfc8 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Mon, 10 May 2021 18:05:33 +0100 Subject: [PATCH 02/34] [jsc] Added script to save LUTs and activation statistics. --- examples/jet_substructure/dump_luts.py | 122 +++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 examples/jet_substructure/dump_luts.py diff --git a/examples/jet_substructure/dump_luts.py b/examples/jet_substructure/dump_luts.py new file mode 100644 index 000000000..c244ee1ba --- /dev/null +++ b/examples/jet_substructure/dump_luts.py @@ -0,0 +1,122 @@ +# Copyright (C) 2021 Xilinx, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from argparse import ArgumentParser + +import torch +from torch.utils.data import DataLoader + +from logicnets.nn import generate_truth_tables, \ + lut_inference, \ + save_luts, \ + module_list_to_verilog_module + +from train import configs, model_config, dataset_config, other_options, test +from dataset import JetSubstructureDataset +from models import JetSubstructureNeqModel, JetSubstructureLutModel +from logicnets.synthesis import synthesize_and_get_resource_counts + +if __name__ == "__main__": + parser = ArgumentParser(description="Synthesize convert a PyTorch trained model into verilog") + parser.add_argument('--arch', type=str, choices=configs.keys(), default="jsc-s", + help="Specific the neural network model to use (default: %(default)s)") + parser.add_argument('--batch-size', type=int, default=None, metavar='N', + help="Batch size for evaluation (default: %(default)s)") + parser.add_argument('--input-bitwidth', type=int, default=None, + help="Bitwidth to use at the input (default: %(default)s)") + parser.add_argument('--hidden-bitwidth', type=int, default=None, + help="Bitwidth to use for activations in hidden layers (default: %(default)s)") + parser.add_argument('--output-bitwidth', type=int, default=None, + help="Bitwidth to use at the output (default: %(default)s)") + parser.add_argument('--input-fanin', type=int, default=None, + help="Fanin to use at the input (default: %(default)s)") + parser.add_argument('--hidden-fanin', type=int, default=None, + help="Fanin to use for the hidden layers (default: %(default)s)") + parser.add_argument('--output-fanin', type=int, default=None, + help="Fanin to use at the output (default: %(default)s)") + parser.add_argument('--hidden-layers', nargs='+', type=int, default=None, + help="A list of hidden layer neuron sizes (default: %(default)s)") + parser.add_argument('--dataset-file', type=str, default='data/processed-pythia82-lhc13-all-pt1-50k-r1_h022_e0175_t220_nonu_truth.z', + help="The file to use as the dataset input (default: %(default)s)") + parser.add_argument('--dataset-config', type=str, default='config/yaml_IP_OP_config.yml', + help="The file to use to configure the input dataset (default: %(default)s)") + parser.add_argument('--log-dir', type=str, default='./log', + help="A location to store the log output of the training run and the output model (default: %(default)s)") + parser.add_argument('--checkpoint', type=str, required=True, + help="The checkpoint file which contains the model weights") + args = parser.parse_args() + defaults = configs[args.arch] + options = vars(args) + del options['arch'] + config = {} + for k in options.keys(): + config[k] = options[k] if options[k] is not None else defaults[k] # Override defaults, if specified. + + if not os.path.exists(config['log_dir']): + os.makedirs(config['log_dir']) + + # Split up configuration options to be more understandable + model_cfg = {} + for k in model_config.keys(): + model_cfg[k] = config[k] + dataset_cfg = {} + for k in dataset_config.keys(): + dataset_cfg[k] = config[k] + options_cfg = {} + for k in other_options.keys(): + if k == 'cuda': + continue + options_cfg[k] = config[k] + + # Fetch the test set + dataset = {} + dataset['train'] = JetSubstructureDataset(dataset_cfg['dataset_file'], dataset_cfg['dataset_config'], split="train") + train_loader = DataLoader(dataset["train"], batch_size=config['batch_size'], shuffle=False) + + # Instantiate the PyTorch model + x, y = dataset['train'][0] + dataset_length = len(dataset['train']) + model_cfg['input_length'] = len(x) + model_cfg['output_length'] = len(y) + model = JetSubstructureNeqModel(model_cfg) + + # Load the model weights + checkpoint = torch.load(options_cfg['checkpoint'], map_location='cpu') + model.load_state_dict(checkpoint['model_dict']) + + # Test the PyTorch model + print("Running inference of baseline model on training set (%d examples)..." % (dataset_length)) + model.eval() + baseline_accuracy = test(model, train_loader, cuda=False) + print("Baseline accuracy: %f" % (baseline_accuracy)) + + # Instantiate LUT-based model + lut_model = JetSubstructureLutModel(model_cfg) + lut_model.load_state_dict(checkpoint['model_dict']) + + # Generate the truth tables in the LUT module + print("Converting to NEQs to LUTs...") + generate_truth_tables(lut_model, verbose=True) + + # Test the LUT-based model + print("Running inference of LUT-based model training set (%d examples)..." % (dataset_length)) + lut_inference(lut_model, track_used_luts=True) + lut_model.eval() + lut_accuracy = test(lut_model, train_loader, cuda=False) + print("LUT-Based Model accuracy: %f" % (lut_accuracy)) + print("Saving LUTs to %s... " % (options_cfg["log_dir"] + "/luts.pth")) + save_luts(lut_model, options_cfg["log_dir"] + "/luts.pth") + print("Done!") + From 4d75911d263fda879c98587779f1cb3925e07965 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 May 2021 10:43:32 +0100 Subject: [PATCH 03/34] [nn] Added extra parameter to specify a cutoff, for if a TT entry should be included in the output verilog. --- src/logicnets/nn.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/logicnets/nn.py b/src/logicnets/nn.py index 6eb8ab2f9..27529e11a 100644 --- a/src/logicnets/nn.py +++ b/src/logicnets/nn.py @@ -73,6 +73,12 @@ def save_luts(model: nn.Module, path: str) -> None: } torch.save(lut_dict, path) +# TODO: Create a container module which performs this function. +def load_histograms(model: nn.Module, lut_dict: dict) -> None: + for name, module in model.named_modules(): + if name in lut_dict.keys(): + module.used_luts_histogram = lut_dict[name]['histogram'] + # TODO: Create a container module which performs this function. def neq_inference(model: nn.Module) -> None: for name, module in model.named_modules(): @@ -81,7 +87,7 @@ def neq_inference(model: nn.Module) -> None: # TODO: Should this go in with the other verilog functions? # TODO: Support non-linear topologies -def module_list_to_verilog_module(module_list: nn.ModuleList, module_name: str, output_directory: str): +def module_list_to_verilog_module(module_list: nn.ModuleList, module_name: str, output_directory: str, freq_thresh=None): input_bitwidth = None output_bitwidth = None module_contents = "" @@ -89,7 +95,7 @@ def module_list_to_verilog_module(module_list: nn.ModuleList, module_name: str, m = module_list[i] if type(m) == SparseLinearNeq: module_prefix = f"layer{i}" - module_input_bits, module_output_bits = m.gen_layer_verilog(module_prefix, output_directory) + module_input_bits, module_output_bits = m.gen_layer_verilog(module_prefix, output_directory, freq_thresh=freq_thresh) if i == 0: input_bitwidth = module_input_bits elif i == len(module_list)-1: @@ -141,7 +147,7 @@ def __init__(self, in_features: int, out_features: int, input_quant, output_quan # TODO: Move the verilog string templates to elsewhere # TODO: Move this to another class # TODO: Update this code to support custom bitwidths per input/output - def gen_layer_verilog(self, module_prefix, directory): + def gen_layer_verilog(self, module_prefix, directory, freq_thresh=None): _, input_bitwidth = self.input_quant.get_scale_factor_bits() _, output_bitwidth = self.output_quant.get_scale_factor_bits() input_bitwidth, output_bitwidth = int(input_bitwidth), int(output_bitwidth) @@ -152,7 +158,7 @@ def gen_layer_verilog(self, module_prefix, directory): for index in range(self.out_features): module_name = f"{module_prefix}_N{index}" indices, _, _, _ = self.neuron_truth_tables[index] - neuron_verilog = self.gen_neuron_verilog(index, module_name) # Generate the contents of the neuron verilog + neuron_verilog = self.gen_neuron_verilog(index, module_name, freq_thresh=freq_thresh) # Generate the contents of the neuron verilog with open(f"{directory}/{module_name}.v", "w") as f: f.write(neuron_verilog) connection_string = generate_neuron_connection_verilog(indices, input_bitwidth) # Generate the string which connects the synapses to this neuron @@ -168,7 +174,7 @@ def gen_layer_verilog(self, module_prefix, directory): # TODO: Move the verilog string templates to elsewhere # TODO: Move this to another class - def gen_neuron_verilog(self, index, module_name): + def gen_neuron_verilog(self, index, module_name, freq_thresh=None): indices, input_perm_matrix, float_output_states, bin_output_states = self.neuron_truth_tables[index] _, input_bitwidth = self.input_quant.get_scale_factor_bits() _, output_bitwidth = self.output_quant.get_scale_factor_bits() @@ -181,7 +187,8 @@ def gen_neuron_verilog(self, index, module_name): val = input_perm_matrix[i,idx] entry_str += self.input_quant.get_bin_str(val) res_str = self.output_quant.get_bin_str(bin_output_states[i]) - lut_string += f"\t\t\t{int(cat_input_bitwidth)}'b{entry_str}: M1r = {int(output_bitwidth)}'b{res_str};\n" + if (freq_thresh is None) or (self.used_luts_histogram[index][i] >= freq_thresh): + lut_string += f"\t\t\t{int(cat_input_bitwidth)}'b{entry_str}: M1r = {int(output_bitwidth)}'b{res_str};\n" return generate_lut_verilog(module_name, int(cat_input_bitwidth), int(output_bitwidth), lut_string) def lut_inference(self, track_used_luts=False): From 538281538cd43d0e040c67f10b0aeb7ef68d51ec Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 May 2021 10:52:07 +0100 Subject: [PATCH 04/34] [jsc] Made verification of verilog simulation optional with a flag. --- examples/jet_substructure/models.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/jet_substructure/models.py b/examples/jet_substructure/models.py index 7c0fba9f9..be6891bff 100644 --- a/examples/jet_substructure/models.py +++ b/examples/jet_substructure/models.py @@ -64,12 +64,14 @@ def __init__(self, model_config): self.verilog_dir = None self.top_module_filename = None self.dut = None + self.verify = True - def verilog_inference(self, verilog_dir, top_module_filename): + def verilog_inference(self, verilog_dir, top_module_filename, verify=True): self.verilog_dir = realpath(verilog_dir) self.top_module_filename = top_module_filename self.dut = PyVerilator.build(f"{self.verilog_dir}/{self.top_module_filename}", verilog_path=[self.verilog_dir], build_dir=f"{self.verilog_dir}/verilator") self.is_verilog_inference = True + self.verify = verify def pytorch_inference(self): self.is_verilog_inference = False @@ -92,11 +94,8 @@ def verilog_forward(self, x): self.dut.io.clk = 0 for i in range(x.shape[0]): x_i = x[i,:] - y_i = self.pytorch_forward(x[i:i+1,:])[0] xv_i = list(map(lambda z: input_quant.get_bin_str(z), x_i)) - ys_i = list(map(lambda z: output_quant.get_bin_str(z), y_i)) xvc_i = reduce(lambda a,b: a+b, xv_i[::-1]) - ysc_i = reduce(lambda a,b: a+b, ys_i[::-1]) self.dut["M0"] = int(xvc_i, 2) for j in range(self.latency + 1): #print(self.dut.io.M5) @@ -104,9 +103,13 @@ def verilog_forward(self, x): result = f"{res:0{int(total_output_bits)}b}" self.dut.io.clk = 1 self.dut.io.clk = 0 - expected = f"{int(ysc_i,2):0{int(total_output_bits)}b}" result = f"{res:0{int(total_output_bits)}b}" - assert(expected == result) + if self.verify: + y_i = self.pytorch_forward(x[i:i+1,:])[0] + ys_i = list(map(lambda z: output_quant.get_bin_str(z), y_i)) + ysc_i = reduce(lambda a,b: a+b, ys_i[::-1]) + expected = f"{int(ysc_i,2):0{int(total_output_bits)}b}" + assert(expected == result) res_split = [result[i:i+output_bitwidth] for i in range(0, len(result), output_bitwidth)][::-1] yv_i = torch.Tensor(list(map(lambda z: int(z, 2), res_split))) y[i,:] = yv_i From 45d7955eae74d3ca4af3f7752f7446c305c3a9f0 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 May 2021 10:53:03 +0100 Subject: [PATCH 05/34] [jsc] Added loading of calculated histograms and specifying a TT frequency threshold. --- examples/jet_substructure/neq2lut.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/jet_substructure/neq2lut.py b/examples/jet_substructure/neq2lut.py index 15b6d2b80..c106128aa 100644 --- a/examples/jet_substructure/neq2lut.py +++ b/examples/jet_substructure/neq2lut.py @@ -20,7 +20,8 @@ from logicnets.nn import generate_truth_tables, \ lut_inference, \ - module_list_to_verilog_module + module_list_to_verilog_module, \ + load_histograms from train import configs, model_config, dataset_config, other_options, test from dataset import JetSubstructureDataset @@ -55,6 +56,10 @@ help="A location to store the log output of the training run and the output model (default: %(default)s)") parser.add_argument('--checkpoint', type=str, required=True, help="The checkpoint file which contains the model weights") + parser.add_argument('--histograms', type=str, required=True, + help="The checkpoint histograms of LUT usage") + parser.add_argument('--freq-thresh', type=int, default=0, + help="Threshold to use to include this truth table into the model (default: %(default)s)") args = parser.parse_args() defaults = configs[args.arch] options = vars(args) @@ -118,13 +123,15 @@ 'test_accuracy': lut_accuracy} torch.save(modelSave, options_cfg["log_dir"] + "/lut_based_model.pth") + luts = torch.load(args.histograms) + load_histograms(lut_model, luts) print("Generating verilog in %s..." % (options_cfg["log_dir"])) - module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"]) + module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"], freq_thresh=args.freq_thresh) print("Top level entity stored at: %s/logicnet.v ..." % (options_cfg["log_dir"])) print("Running inference simulation of Verilog-based model...") - lut_model.verilog_inference(options_cfg["log_dir"], "logicnet.v") + lut_model.verilog_inference(options_cfg["log_dir"], "logicnet.v", verify=args.freq_thresh == 0) verilog_accuracy = test(lut_model, test_loader, cuda=False) print("Verilog-Based Model accuracy: %f" % (verilog_accuracy)) From dc302d1f8344c02bcbf312aecdeb88ab052723b4 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Wed, 19 May 2021 15:52:34 +0100 Subject: [PATCH 06/34] [nn] Added a default case to verilog LUT generation. --- examples/jet_substructure/models.py | 2 +- src/logicnets/nn.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/jet_substructure/models.py b/examples/jet_substructure/models.py index be6891bff..e8c99a612 100644 --- a/examples/jet_substructure/models.py +++ b/examples/jet_substructure/models.py @@ -69,7 +69,7 @@ def __init__(self, model_config): def verilog_inference(self, verilog_dir, top_module_filename, verify=True): self.verilog_dir = realpath(verilog_dir) self.top_module_filename = top_module_filename - self.dut = PyVerilator.build(f"{self.verilog_dir}/{self.top_module_filename}", verilog_path=[self.verilog_dir], build_dir=f"{self.verilog_dir}/verilator") + self.dut = PyVerilator.build(f"{self.verilog_dir}/{self.top_module_filename}", verilog_path=[self.verilog_dir], build_dir=f"{self.verilog_dir}/verilator", command_args=("--x-assign","0",)) self.is_verilog_inference = True self.verify = verify diff --git a/src/logicnets/nn.py b/src/logicnets/nn.py index 27529e11a..39af6a959 100644 --- a/src/logicnets/nn.py +++ b/src/logicnets/nn.py @@ -189,6 +189,9 @@ def gen_neuron_verilog(self, index, module_name, freq_thresh=None): res_str = self.output_quant.get_bin_str(bin_output_states[i]) if (freq_thresh is None) or (self.used_luts_histogram[index][i] >= freq_thresh): lut_string += f"\t\t\t{int(cat_input_bitwidth)}'b{entry_str}: M1r = {int(output_bitwidth)}'b{res_str};\n" + # Add a default "don't care" statement + default_string = int(output_bitwidth) * 'x' + lut_string += f"\t\t\tdefault: M1r = {int(output_bitwidth)}'b{default_string};\n" return generate_lut_verilog(module_name, int(cat_input_bitwidth), int(output_bitwidth), lut_string) def lut_inference(self, track_used_luts=False): From 536271bf27b9ae490153ce3434c161eed1072edc Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Thu, 20 May 2021 16:15:21 +0100 Subject: [PATCH 07/34] [nn/jsc] Made registers optional in verilog generation. Default is no registers. --- examples/jet_substructure/models.py | 2 +- src/logicnets/verilog.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/jet_substructure/models.py b/examples/jet_substructure/models.py index e8c99a612..49fec0e1b 100644 --- a/examples/jet_substructure/models.py +++ b/examples/jet_substructure/models.py @@ -60,7 +60,7 @@ def __init__(self, model_config): layer_list.append(layer) self.module_list = nn.ModuleList(layer_list) self.is_verilog_inference = False - self.latency = len(self.num_neurons) + self.latency = 1 self.verilog_dir = None self.top_module_filename = None self.dut = None diff --git a/src/logicnets/verilog.py b/src/logicnets/verilog.py index df33fa701..f073a4692 100644 --- a/src/logicnets/verilog.py +++ b/src/logicnets/verilog.py @@ -45,13 +45,15 @@ def generate_logicnets_verilog(module_name: str, input_name: str, input_bits: in output_bits_1=output_bits-1, module_contents=module_contents) -def layer_connection_verilog(layer_string: str, input_string: str, input_bits: int, output_string: str, output_bits: int, output_wire=True): - layer_connection_template = """\ +def layer_connection_verilog(layer_string: str, input_string: str, input_bits: int, output_string: str, output_bits: int, output_wire=True, register=False): + if register: + layer_connection_template = """\ wire [{input_bits_1:d}:0] {input_string}w; myreg #(.DataWidth({input_bits})) {layer_string}_reg (.data_in({input_string}), .clk(clk), .rst(rst), .data_out({input_string}w));\n""" -# layer_connection_template = """\ -#wire [{input_bits_1:d}:0] {input_string}w; -#assign {input_string}w = {input_string};\n""" + else: + layer_connection_template = """\ +wire [{input_bits_1:d}:0] {input_string}w; +assign {input_string}w = {input_string};\n""" layer_connection_template += "wire [{output_bits_1:d}:0] {output_string};\n" if output_wire else "" layer_connection_template += "{layer_string} {layer_string}_inst (.M0({input_string}w), .M1({output_string}));\n" return layer_connection_template.format( layer_string=layer_string, From f4e7810bc482bbeab012f327f23e2e62c35dae77 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Wed, 26 May 2021 15:29:26 +0100 Subject: [PATCH 08/34] [verilog] Added 'parallel case' statement to generated verilog. --- src/logicnets/verilog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/logicnets/verilog.py b/src/logicnets/verilog.py index f073a4692..742cb64f8 100644 --- a/src/logicnets/verilog.py +++ b/src/logicnets/verilog.py @@ -69,6 +69,7 @@ def generate_lut_verilog(module_name, input_fanin_bits, output_bits, lut_string) (*rom_style = "distributed" *) reg [{output_bits_1:d}:0] M1r; assign M1 = M1r; + (* parallel_case *) always @ (M0) begin case (M0) {lut_string} From 40d72e54102bd9ddb0a852b0662d11f0c9ec6445 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Mon, 31 May 2021 10:31:39 +0100 Subject: [PATCH 09/34] Revert "[verilog] Added 'parallel case' statement to generated verilog." This reverts commit f4e7810bc482bbeab012f327f23e2e62c35dae77. --- src/logicnets/verilog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logicnets/verilog.py b/src/logicnets/verilog.py index 742cb64f8..f073a4692 100644 --- a/src/logicnets/verilog.py +++ b/src/logicnets/verilog.py @@ -69,7 +69,6 @@ def generate_lut_verilog(module_name, input_fanin_bits, output_bits, lut_string) (*rom_style = "distributed" *) reg [{output_bits_1:d}:0] M1r; assign M1 = M1r; - (* parallel_case *) always @ (M0) begin case (M0) {lut_string} From 7970fce539a66ce90dd1412126cdaf43ad0c09b8 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Fri, 18 Jun 2021 15:19:58 +0100 Subject: [PATCH 10/34] [jsc] Bugfixes in setting histograms / frequency values --- examples/jet_substructure/neq2lut.py | 7 ++++--- examples/jet_substructure/train.py | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/jet_substructure/neq2lut.py b/examples/jet_substructure/neq2lut.py index e23a8d823..ac6c5ccc3 100644 --- a/examples/jet_substructure/neq2lut.py +++ b/examples/jet_substructure/neq2lut.py @@ -62,7 +62,7 @@ help="The checkpoint file which contains the model weights") parser.add_argument('--histograms', type=str, default=None, help="The checkpoint histograms of LUT usage (default: %(default)s)") - parser.add_argument('--freq-thresh', type=int, default=0, + parser.add_argument('--freq-thresh', type=int, default=None, help="Threshold to use to include this truth table into the model (default: %(default)s)") parser.add_argument('--generate-bench', action='store_true', default=False, help="Generate the truth table in BENCH format as well as verilog (default: %(default)s)") @@ -131,8 +131,9 @@ 'test_accuracy': lut_accuracy} torch.save(modelSave, options_cfg["log_dir"] + "/lut_based_model.pth") - luts = torch.load(args.histograms) - load_histograms(lut_model, luts) + if options_cfg["histograms"] is not None: + luts = torch.load(options_cfg["histograms"]) + load_histograms(lut_model, luts) print("Generating verilog in %s..." % (options_cfg["log_dir"])) module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"], freq_thresh=options_cfg["freq_thresh"], generate_bench=options_cfg["generate_bench"]) diff --git a/examples/jet_substructure/train.py b/examples/jet_substructure/train.py index 840fd4f18..be90902b6 100644 --- a/examples/jet_substructure/train.py +++ b/examples/jet_substructure/train.py @@ -44,6 +44,8 @@ "learning_rate": 1e-3, "seed": 2, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "jsc-m": { "hidden_layers": [64, 32, 32, 32], @@ -59,6 +61,8 @@ "learning_rate": 1e-3, "seed": 3, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "jsc-l": { "hidden_layers": [32, 64, 192, 192, 16], @@ -74,6 +78,8 @@ "learning_rate": 1e-3, "seed": 16, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, } @@ -107,6 +113,7 @@ "checkpoint": None, "generate_bench": None, "freq_thresh": None, + "histograms": None, } def train(model, datasets, train_cfg, options): From fbf8238eb2272afa34fb90288b1805bc852dd2e1 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Mon, 28 Jun 2021 16:12:23 +0100 Subject: [PATCH 11/34] [jsc] Updated default PCA to be 12 dimensions. --- examples/jet_substructure/config/yaml_IP_OP_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jet_substructure/config/yaml_IP_OP_config.yml b/examples/jet_substructure/config/yaml_IP_OP_config.yml index e238039bf..95befe1fe 100644 --- a/examples/jet_substructure/config/yaml_IP_OP_config.yml +++ b/examples/jet_substructure/config/yaml_IP_OP_config.yml @@ -45,5 +45,5 @@ L1Reg: 0.0001 NormalizeInputs: 1 InputType: Dense ApplyPca: false -PcaDimensions: 10 +PcaDimensions: 12 From 01f8d43b21d958a71cfc2884f221ecb133a7994b Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 31 Aug 2021 16:48:37 +0100 Subject: [PATCH 12/34] [jsc] Fixed description of commandline arguments --- examples/jet_substructure/dump_luts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jet_substructure/dump_luts.py b/examples/jet_substructure/dump_luts.py index c244ee1ba..05952268e 100644 --- a/examples/jet_substructure/dump_luts.py +++ b/examples/jet_substructure/dump_luts.py @@ -29,7 +29,7 @@ from logicnets.synthesis import synthesize_and_get_resource_counts if __name__ == "__main__": - parser = ArgumentParser(description="Synthesize convert a PyTorch trained model into verilog") + parser = ArgumentParser(description="Generate histograms of states used throughout LogicNets") parser.add_argument('--arch', type=str, choices=configs.keys(), default="jsc-s", help="Specific the neural network model to use (default: %(default)s)") parser.add_argument('--batch-size', type=int, default=None, metavar='N', @@ -53,7 +53,7 @@ parser.add_argument('--dataset-config', type=str, default='config/yaml_IP_OP_config.yml', help="The file to use to configure the input dataset (default: %(default)s)") parser.add_argument('--log-dir', type=str, default='./log', - help="A location to store the log output of the training run and the output model (default: %(default)s)") + help="A location to store the calculated histograms (default: %(default)s)") parser.add_argument('--checkpoint', type=str, required=True, help="The checkpoint file which contains the model weights") args = parser.parse_args() From 1ed85a561f0fb3e76ad057e8e980c0c0e4198af5 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 28 Sep 2021 20:19:21 +0100 Subject: [PATCH 13/34] [jsc] Initial basic code for abc intergration. --- src/logicnets/abc.py | 107 +++++++++++++++++++++++++++++++++++++ src/logicnets/synthesis.py | 45 ++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/logicnets/abc.py diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py new file mode 100644 index 000000000..dee7fa68b --- /dev/null +++ b/src/logicnets/abc.py @@ -0,0 +1,107 @@ +# Copyright (C) 2021 Xilinx, Inc +# Copyright (C) 2021 Alan Mishchenko +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def generate_prepare_script_string(num_layers, path): + prepare_script_template = """\ +# This script prepares experiments in ABC by deriving intermediate simulation patterns + +# Assuming that verilog/BENCH for each layer of the network are in files "ver/layer{{0,1,2,..}}.v" +# and input/output patterns are the network are in files {{train,test}}_{{input,output}}.txt + + +# ==================================================================================== +# Read the layers from Verilog/BENCH files +{read_layers_string} + +# ==================================================================================== +# Convert input patterns into the internal binary representation +&lnetread {path}/train_input.txt {path}/train.sim +&lnetread {path}/test_input.txt {path}/test.sim + + +# ==================================================================================== +# Generate training simulation info for the inputs of each layer +{simulate_layers_string} + +# ==================================================================================== +# Combine all layers into one monolithic AIG for the whole network (layers.aig) +{gen_monolithic_aig_string} +""" + read_layer_template = "&lnetread {path}/ver/layer{i}.v; &ps; &w {path}/layer{i}.aig\n" + simulate_layer_template = "&r {path}/layer{i}.aig; &lnetsim {path}/train{it}.sim {path}/train{ip1}.sim\n" + gen_monolithic_aig_template = "putontop {layers_aig_string}; st; ps; write {path}/layers.aig\n" + read_layers_string = "" + simulate_layers_string = "" + layers_aig_string = "" + for i in range(num_layers): + read_layers_string += read_layer_template.format(i=i, path=path) + simulate_layers_string += simulate_layer_template.format(i=i, it="" if i == 0 else i, ip1=i+1, path=path) + layers_aig_string += "{path}/layer{i}.aig ".format(i=i, path=path) + gen_monolithic_aig_string = gen_monolithic_aig_template.format(layers_aig_string=layers_aig_string.strip(), path=path) + return prepare_script_template.format( path=path, + read_layers_string=read_layers_string, + simulate_layers_string=simulate_layers_string, + gen_monolithic_aig_string=gen_monolithic_aig_string) + + +def generate_opt_script_string(model, path, num_registers, rarity=0): + opt_script_template = """\ +# Generating script with rarity = {rarity}. + +# ---- rarity = {rarity} ------------------------------------------------------------------------------------------------------- +{optimise_with_rarity_string} + +{gen_monolithic_aig_string} + +{technology_map_layers_string} + +{gen_monolithic_blif_string} + +read {path}/blif/layers_opt.blif; ps; pipe -L {num_registers}; ps; retime -M 4; ps; sweep; ps; write_verilog -fm {path}/ver/layers_opt_p{num_registers}.v + +&r {path}/aig/layers_opt.aig; &lnetsim {path}/train.sim {path}/train.simo +&r {path}/aig/layers_opt.aig; &lneteval -O 2 {path}/train.simo {path}/train_output.txt + +&r {path}/aig/layers_opt.aig; &lnetsim {path}/test.sim {path}/test.simo +&r {path}/aig/layers_opt.aig; &lneteval -O 2 {path}/test.simo {path}/test_output.txt + +""" + optimise_with_rarity_template = "&r {path}/layer{i}.aig; &ps; &lnetopt -I {fanin_bits} -O {fanout_bits} -R {rarity} {path}/train{it}.sim; &w {path}/aig/layer{i}_opt.aig; &ps; time\n" + technology_map_layer_template = "&r {path}/aig/layer{i}_opt.aig; &lnetmap -I {fanin_bits} -O {fanout_bits}; write {path}/blif/layer{i}_opt.blif; write_verilog -fm {path}/ver/layer{i}_opt.v\n" + gen_monolithic_aig_template = "putontop {aig_layers_string}; st; ps; write {path}/aig/layers_opt.aig\n" + gen_monolithic_blif_template = "putontop {blif_layers_string}; sw; ps; write {path}/blif/layers_opt.blif\n" + num_layers = 5 # TODO: fetch number of layers from the model + optimise_with_rarity_string = "" + technology_map_layers_string = "" + aig_layers_string = "" + blif_layers_string = "" + for i in range(num_layers): + fanin_bits = 6 # TODO: Read this from model + fanout_bits = 2 # TODO: Read this from model + optimise_with_rarity_string += optimise_with_rarity_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, it="" if i == 0 else i, i=i, path=path, rarity=rarity) + technology_map_layers_string += technology_map_layer_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, i=i, path=path) + aig_layers_string += "{path}/aig/layer{i}_opt.aig ".format(i=i, path=path) + blif_layers_string += "{path}/blif/layer{i}_opt.blif ".format(i=i, path=path) + gen_monolithic_aig_string = gen_monolithic_aig_template.format(aig_layers_string=aig_layers_string.strip(), path=path) + gen_monolithic_blif_string = gen_monolithic_blif_template.format(blif_layers_string=blif_layers_string.strip(), path=path) + return opt_script_template.format( rarity=rarity, + num_registers=num_registers, + path=path, + optimise_with_rarity_string=optimise_with_rarity_string, + gen_monolithic_aig_string=gen_monolithic_aig_string, + technology_map_layers_string=technology_map_layers_string, + gen_monolithic_blif_string=gen_monolithic_blif_string) + + diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index dc1551716..a95f2d2cb 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -14,10 +14,15 @@ import os import subprocess +import shutil from shutil import which +import glob + +from .abc import generate_prepare_script_string, generate_opt_script_string #xcvu9p-flgb2104-2-i # TODO: Add option to perform synthesis on a remote server +# Synthesise design with vivado and get resource counts def synthesize_and_get_resource_counts(verilog_dir, top_name, fpga_part = "xcku3p-ffva676-1-e", clk_name="clk", clk_period_ns=5.0): # old part : "xczu3eg-sbva484-1-i" @@ -56,3 +61,43 @@ def synthesize_and_get_resource_counts(verilog_dir, top_name, else: ret["fmax_mhz"] = 1000.0 / (clk_period_ns - ret["WNS"]) return ret + +# Optimize the design with ABC +def synthesize_and_get_resource_counts_with_abc(verilog_dir, model, pipeline_stages=0, freq_thresh=0): + if "ABC_ROOT" not in os.environ: + raise Exception("The environment variable ABC_ROOT is not defined.") + abc_path = os.environ["ABC_ROOT"] + + # Create directories and symlinks ready for processing with ABC + project_prefix = "logicnet" + abc_project_root = f"{verilog_dir}/{project_prefix}" + os.makedirs(f"{abc_project_root}/ver") + os.makedirs(f"{abc_project_root}/aig") + os.makedirs(f"{abc_project_root}/blif") + real_abc_project_root = os.path.realpath(abc_project_root) + project_symlink_path = f"{abc_path}/{project_prefix}" + os.symlink(real_abc_project_root, project_symlink_path) # Create a symlink to this folder in the ABC root. + # Fetch the right source files from the verilog directory + source_files = glob.glob(f"{verilog_dir}/*.v") + glob.glob(f"{verilog_dir}/*.bench") + for f in source_files: + shutil.copy(f, f"{abc_project_root}/ver") + # Fetch the I/O files + for f in glob.glob(f"{verilog_dir}/*.txt"): + shutil.copy(f, f"{abc_project_root}") + + # Create script files to pass to ABC + # TODO: Calculate number of layers from the model + with open(f"{abc_project_root}/prepare.script", "w") as f: + f.write(generate_prepare_script_string(num_layers=5, path=project_prefix)) + with open(f"{abc_project_root}/opt_all.script", "w") as f: + f.write(generate_opt_script_string(model=model, path=project_prefix, num_registers=pipeline_stages, rarity=freq_thresh)) + + #proc = subprocess.Popen(['./abc', '-c', '"x/jsc_s/prepare.script"', '-c', '"x/jsc_s/opt_all.script"'], cwd=abc_path, stdout=subprocess.PIPE, env=os.environ) + proc = subprocess.Popen(['./abc', '-c', f'"{project_prefix}/prepare.script"', '-c', f'"{project_prefix}/opt_all.script"'], cwd=abc_path, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + + with open(f"{abc_project_root}/abc.log", "w") as f: + f.write(out.decode("utf-8")) + + os.remove(project_symlink_path) + From 2857e710a10a37deadbed8ee46520f74dce54dd6 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 28 Sep 2021 22:10:32 +0100 Subject: [PATCH 14/34] [abc] Updated module to pull information from the input model. Created end-to-end example. --- examples/jet_substructure/neq2lut_abc.py | 154 +++++++++++++++++++++++ src/logicnets/abc.py | 16 ++- src/logicnets/synthesis.py | 20 +-- 3 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 examples/jet_substructure/neq2lut_abc.py diff --git a/examples/jet_substructure/neq2lut_abc.py b/examples/jet_substructure/neq2lut_abc.py new file mode 100644 index 000000000..bdd431c2e --- /dev/null +++ b/examples/jet_substructure/neq2lut_abc.py @@ -0,0 +1,154 @@ +# Copyright (C) 2021 Xilinx, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from argparse import ArgumentParser + +import torch +from torch.utils.data import DataLoader + +from logicnets.nn import generate_truth_tables, \ + lut_inference, \ + module_list_to_verilog_module +from logicnets.synthesis import synthesize_and_get_resource_counts_with_abc + +from train import configs, model_config, dataset_config, test +from dataset import JetSubstructureDataset +from models import JetSubstructureNeqModel, JetSubstructureLutModel +from dataset_dump import dump_io + +other_options = { + "cuda": None, + "log_dir": None, + "checkpoint": None, +} + +if __name__ == "__main__": + parser = ArgumentParser(description="Synthesize convert a PyTorch trained model into verilog") + parser.add_argument('--arch', type=str, choices=configs.keys(), default="jsc-s", + help="Specific the neural network model to use (default: %(default)s)") + parser.add_argument('--batch-size', type=int, default=None, metavar='N', + help="Batch size for evaluation (default: %(default)s)") + parser.add_argument('--input-bitwidth', type=int, default=None, + help="Bitwidth to use at the input (default: %(default)s)") + parser.add_argument('--hidden-bitwidth', type=int, default=None, + help="Bitwidth to use for activations in hidden layers (default: %(default)s)") + parser.add_argument('--output-bitwidth', type=int, default=None, + help="Bitwidth to use at the output (default: %(default)s)") + parser.add_argument('--input-fanin', type=int, default=None, + help="Fanin to use at the input (default: %(default)s)") + parser.add_argument('--hidden-fanin', type=int, default=None, + help="Fanin to use for the hidden layers (default: %(default)s)") + parser.add_argument('--output-fanin', type=int, default=None, + help="Fanin to use at the output (default: %(default)s)") + parser.add_argument('--hidden-layers', nargs='+', type=int, default=None, + help="A list of hidden layer neuron sizes (default: %(default)s)") + parser.add_argument('--dataset-file', type=str, default='data/processed-pythia82-lhc13-all-pt1-50k-r1_h022_e0175_t220_nonu_truth.z', + help="The file to use as the dataset input (default: %(default)s)") + parser.add_argument('--clock-period', type=float, default=1.0, + help="Target clock frequency to use during Vivado synthesis (default: %(default)s)") + parser.add_argument('--dataset-config', type=str, default='config/yaml_IP_OP_config.yml', + help="The file to use to configure the input dataset (default: %(default)s)") + parser.add_argument('--dataset-split', type=str, default='test', choices=['train', 'test'], + help="Dataset to use for evaluation (default: %(default)s)") + parser.add_argument('--log-dir', type=str, default='./log', + help="A location to store the log output of the training run and the output model (default: %(default)s)") + parser.add_argument('--checkpoint', type=str, required=True, + help="The checkpoint file which contains the model weights") + parser.add_argument('--num-registers', type=int, default=0, + help="The number of registers to add to the generated verilog (default: %(default)s)") + args = parser.parse_args() + defaults = configs[args.arch] + options = vars(args) + del options['arch'] + config = {} + for k in options.keys(): + config[k] = options[k] if options[k] is not None else defaults[k] # Override defaults, if specified. + + if not os.path.exists(config['log_dir']): + os.makedirs(config['log_dir']) + + # Split up configuration options to be more understandable + model_cfg = {} + for k in model_config.keys(): + model_cfg[k] = config[k] + dataset_cfg = {} + for k in dataset_config.keys(): + dataset_cfg[k] = config[k] + options_cfg = {} + for k in other_options.keys(): + if k == 'cuda': + continue + options_cfg[k] = config[k] + + # Fetch the test set + dataset = {} + dataset["train"] = JetSubstructureDataset(dataset_cfg['dataset_file'], dataset_cfg['dataset_config'], split="train") + dataset["test"] = JetSubstructureDataset(dataset_cfg['dataset_file'], dataset_cfg['dataset_config'], split="test") + train_loader = DataLoader(dataset["train"], batch_size=config['batch_size'], shuffle=False) + test_loader = DataLoader(dataset["test"], batch_size=config['batch_size'], shuffle=False) + + + # Instantiate the PyTorch model + x, y = dataset[args.dataset_split][0] + model_cfg['input_length'] = len(x) + model_cfg['output_length'] = len(y) + model = JetSubstructureNeqModel(model_cfg) + + # Load the model weights + checkpoint = torch.load(options_cfg['checkpoint'], map_location='cpu') + model.load_state_dict(checkpoint['model_dict']) + + # Test the PyTorch model + print("Running inference on baseline model...") + model.eval() + baseline_accuracy = test(model, test_loader, cuda=False) + print("Baseline accuracy: %f" % (baseline_accuracy)) + + # Run preprocessing on training set. + #train_input_file = config['log_dir'] + "/train_input.txt" + #train_output_file = config['log_dir'] + "/train_output.txt" + #test_input_file = config['log_dir'] + "/test_input.txt" + #test_output_file = config['log_dir'] + "/test_output.txt" + #print(f"Dumping train I/O to {train_input_file} and {train_output_file}") + #dump_io(model, train_loader, train_input_file, train_output_file) + #print(f"Dumping test I/O to {test_input_file} and {test_output_file}") + #dump_io(model, test_loader, test_input_file, test_output_file) + + # Instantiate LUT-based model + lut_model = JetSubstructureLutModel(model_cfg) + lut_model.load_state_dict(checkpoint['model_dict']) + + # Generate the truth tables in the LUT module + print("Converting to NEQs to LUTs...") + generate_truth_tables(lut_model, verbose=True) + + # Test the LUT-based model + print("Running inference on LUT-based model...") + lut_inference(lut_model) + lut_model.eval() + lut_accuracy = test(lut_model, test_loader, cuda=False) + print("LUT-Based Model accuracy: %f" % (lut_accuracy)) + modelSave = { 'model_dict': lut_model.state_dict(), + 'test_accuracy': lut_accuracy} + + torch.save(modelSave, options_cfg["log_dir"] + "/lut_based_model.pth") + + print("Generating verilog in %s..." % (options_cfg["log_dir"])) + module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"], generate_bench=True, add_registers=False) + print("Top level entity stored at: %s/logicnet.v ..." % (options_cfg["log_dir"])) + + print("Running synthesis and verilog technology-mapped verilog in ABC") + synthesize_and_get_resource_counts_with_abc(options_cfg["log_dir"], lut_model.module_list, pipeline_stages=args.num_registers, freq_thresh=0) + diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index dee7fa68b..c3b56e568 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -56,7 +56,7 @@ def generate_prepare_script_string(num_layers, path): gen_monolithic_aig_string=gen_monolithic_aig_string) -def generate_opt_script_string(model, path, num_registers, rarity=0): +def generate_opt_script_string(module_list, path, num_registers, rarity=0): opt_script_template = """\ # Generating script with rarity = {rarity}. @@ -82,14 +82,22 @@ def generate_opt_script_string(model, path, num_registers, rarity=0): technology_map_layer_template = "&r {path}/aig/layer{i}_opt.aig; &lnetmap -I {fanin_bits} -O {fanout_bits}; write {path}/blif/layer{i}_opt.blif; write_verilog -fm {path}/ver/layer{i}_opt.v\n" gen_monolithic_aig_template = "putontop {aig_layers_string}; st; ps; write {path}/aig/layers_opt.aig\n" gen_monolithic_blif_template = "putontop {blif_layers_string}; sw; ps; write {path}/blif/layers_opt.blif\n" - num_layers = 5 # TODO: fetch number of layers from the model + num_layers = len(module_list) # TODO: fetch number of layers from the model optimise_with_rarity_string = "" technology_map_layers_string = "" aig_layers_string = "" blif_layers_string = "" for i in range(num_layers): - fanin_bits = 6 # TODO: Read this from model - fanout_bits = 2 # TODO: Read this from model + # Read in fanin/fanout bits + # Add assertion that fanin/fanout bits for all neuron is that same + layer = module_list[i] + _, input_bitwidth = layer.input_quant.get_scale_factor_bits() + _, output_bitwidth = layer.output_quant.get_scale_factor_bits() + num_indices = len(layer.neuron_truth_tables[0]) + fanin_bits = input_bitwidth*num_indices + fanout_bits = output_bitwidth + + # Generate optimisation script. optimise_with_rarity_string += optimise_with_rarity_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, it="" if i == 0 else i, i=i, path=path, rarity=rarity) technology_map_layers_string += technology_map_layer_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, i=i, path=path) aig_layers_string += "{path}/aig/layer{i}_opt.aig ".format(i=i, path=path) diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index a95f2d2cb..50a5b5b12 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -63,7 +63,7 @@ def synthesize_and_get_resource_counts(verilog_dir, top_name, return ret # Optimize the design with ABC -def synthesize_and_get_resource_counts_with_abc(verilog_dir, model, pipeline_stages=0, freq_thresh=0): +def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeline_stages=0, freq_thresh=0): if "ABC_ROOT" not in os.environ: raise Exception("The environment variable ABC_ROOT is not defined.") abc_path = os.environ["ABC_ROOT"] @@ -71,16 +71,22 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, model, pipeline_sta # Create directories and symlinks ready for processing with ABC project_prefix = "logicnet" abc_project_root = f"{verilog_dir}/{project_prefix}" - os.makedirs(f"{abc_project_root}/ver") - os.makedirs(f"{abc_project_root}/aig") - os.makedirs(f"{abc_project_root}/blif") + verilog_bench_dir = f"{abc_project_root}/ver" + aig_dir = f"{abc_project_root}/aig" + blif_dir = f"{abc_project_root}/blif" + if not os.path.exists(verilog_bench_dir): + os.makedirs(verilog_bench_dir) + if not os.path.exists(aig_dir): + os.makedirs(aig_dir) + if not os.path.exists(blif_dir): + os.makedirs(blif_dir) real_abc_project_root = os.path.realpath(abc_project_root) project_symlink_path = f"{abc_path}/{project_prefix}" os.symlink(real_abc_project_root, project_symlink_path) # Create a symlink to this folder in the ABC root. # Fetch the right source files from the verilog directory source_files = glob.glob(f"{verilog_dir}/*.v") + glob.glob(f"{verilog_dir}/*.bench") for f in source_files: - shutil.copy(f, f"{abc_project_root}/ver") + shutil.copy(f, verilog_bench_dir) # Fetch the I/O files for f in glob.glob(f"{verilog_dir}/*.txt"): shutil.copy(f, f"{abc_project_root}") @@ -88,9 +94,9 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, model, pipeline_sta # Create script files to pass to ABC # TODO: Calculate number of layers from the model with open(f"{abc_project_root}/prepare.script", "w") as f: - f.write(generate_prepare_script_string(num_layers=5, path=project_prefix)) + f.write(generate_prepare_script_string(num_layers=len(module_list), path=project_prefix)) with open(f"{abc_project_root}/opt_all.script", "w") as f: - f.write(generate_opt_script_string(model=model, path=project_prefix, num_registers=pipeline_stages, rarity=freq_thresh)) + f.write(generate_opt_script_string(module_list=module_list, path=project_prefix, num_registers=pipeline_stages, rarity=freq_thresh)) #proc = subprocess.Popen(['./abc', '-c', '"x/jsc_s/prepare.script"', '-c', '"x/jsc_s/opt_all.script"'], cwd=abc_path, stdout=subprocess.PIPE, env=os.environ) proc = subprocess.Popen(['./abc', '-c', f'"{project_prefix}/prepare.script"', '-c', f'"{project_prefix}/opt_all.script"'], cwd=abc_path, stdout=subprocess.PIPE, env=os.environ) From 52f8c6425d0aafd95ef92b5c4f709dbfe9930243 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 2 Nov 2021 12:17:19 +0000 Subject: [PATCH 15/34] Added AMC depencency to Dockerfile. --- docker/Dockerfile.cpu | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker/Dockerfile.cpu b/docker/Dockerfile.cpu index 3eaba4b33..6b63945c5 100644 --- a/docker/Dockerfile.cpu +++ b/docker/Dockerfile.cpu @@ -37,6 +37,13 @@ RUN apt-get -qq update && apt-get -qq -y install verilator build-essential libx1 RUN git clone https://bitbucket.org/maltanar/oh-my-xilinx.git ENV OHMYXILINX=/workspace/oh-my-xilinx +# Adding LogicNets dependency on ABC +RUN git clone https://github.com/berkeley-abc/abc.git \ + && cd abc \ + && git checkout ecda331a2a921bcac30bf3210f56adf9152ca22f \ + && make -j`nproc` +ENV ABC_ROOT=/workspace/ABC + # Create the user account to run LogicNets RUN groupadd -g $GID $GNAME RUN useradd -m -u $UID $UNAME -g $GNAME From 691944eda9684231030de8392dd81878d0277ca3 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Thu, 18 Aug 2022 17:28:36 +0100 Subject: [PATCH 16/34] [nids] Initial version supporting histograms. --- examples/cybersecurity/dump_luts.py | 119 ++++++++++++++++++++++++++++ examples/cybersecurity/models.py | 17 ++-- examples/cybersecurity/neq2lut.py | 19 ++++- examples/cybersecurity/train.py | 12 +++ 4 files changed, 156 insertions(+), 11 deletions(-) create mode 100644 examples/cybersecurity/dump_luts.py diff --git a/examples/cybersecurity/dump_luts.py b/examples/cybersecurity/dump_luts.py new file mode 100644 index 000000000..01a57663f --- /dev/null +++ b/examples/cybersecurity/dump_luts.py @@ -0,0 +1,119 @@ +# Copyright (C) 2021 Xilinx, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from argparse import ArgumentParser + +import torch +from torch.utils.data import DataLoader + +from logicnets.nn import generate_truth_tables, \ + lut_inference, \ + save_luts, \ + module_list_to_verilog_module + +from train import configs, model_config, dataset_config, other_options, test +from dataset import get_preqnt_dataset +from models import UnswNb15NeqModel, UnswNb15LutModel + +if __name__ == "__main__": + parser = ArgumentParser(description="Generate histograms of states used throughout LogicNets") + parser.add_argument('--arch', type=str, choices=configs.keys(), default="jsc-s", + help="Specific the neural network model to use (default: %(default)s)") + parser.add_argument('--batch-size', type=int, default=None, metavar='N', + help="Batch size for evaluation (default: %(default)s)") + parser.add_argument('--input-bitwidth', type=int, default=None, + help="Bitwidth to use at the input (default: %(default)s)") + parser.add_argument('--hidden-bitwidth', type=int, default=None, + help="Bitwidth to use for activations in hidden layers (default: %(default)s)") + parser.add_argument('--output-bitwidth', type=int, default=None, + help="Bitwidth to use at the output (default: %(default)s)") + parser.add_argument('--input-fanin', type=int, default=None, + help="Fanin to use at the input (default: %(default)s)") + parser.add_argument('--hidden-fanin', type=int, default=None, + help="Fanin to use for the hidden layers (default: %(default)s)") + parser.add_argument('--output-fanin', type=int, default=None, + help="Fanin to use at the output (default: %(default)s)") + parser.add_argument('--hidden-layers', nargs='+', type=int, default=None, + help="A list of hidden layer neuron sizes (default: %(default)s)") + parser.add_argument('--dataset-file', type=str, default='data/unsw_nb15_binarized.npz', + help="The file to use as the dataset input (default: %(default)s)") + parser.add_argument('--log-dir', type=str, default='./log', + help="A location to store the calculated histograms (default: %(default)s)") + parser.add_argument('--checkpoint', type=str, required=True, + help="The checkpoint file which contains the model weights") + args = parser.parse_args() + defaults = configs[args.arch] + options = vars(args) + del options['arch'] + config = {} + for k in options.keys(): + config[k] = options[k] if options[k] is not None else defaults[k] # Override defaults, if specified. + + if not os.path.exists(config['log_dir']): + os.makedirs(config['log_dir']) + + # Split up configuration options to be more understandable + model_cfg = {} + for k in model_config.keys(): + model_cfg[k] = config[k] + dataset_cfg = {} + for k in dataset_config.keys(): + dataset_cfg[k] = config[k] + options_cfg = {} + for k in other_options.keys(): + if k == 'cuda': + continue + options_cfg[k] = config[k] + + # Fetch the test set + dataset = {} + dataset['train'] = get_preqnt_dataset(dataset_cfg['dataset_file'], split='train') + train_loader = DataLoader(dataset["train"], batch_size=config['batch_size'], shuffle=False) + + # Instantiate the PyTorch model + x, y = dataset['train'][0] + dataset_length = len(dataset['train']) + model_cfg['input_length'] = len(x) + model_cfg['output_length'] = 1 + model = UnswNb15NeqModel(model_cfg) + + # Load the model weights + checkpoint = torch.load(options_cfg['checkpoint'], map_location='cpu') + model.load_state_dict(checkpoint['model_dict']) + + # Test the PyTorch model + print("Running inference of baseline model on training set (%d examples)..." % (dataset_length)) + model.eval() + baseline_accuracy = test(model, train_loader, cuda=False) + print("Baseline accuracy: %f" % (baseline_accuracy)) + + # Instantiate LUT-based model + lut_model = UnswNb15LutModel(model_cfg) + lut_model.load_state_dict(checkpoint['model_dict']) + + # Generate the truth tables in the LUT module + print("Converting to NEQs to LUTs...") + generate_truth_tables(lut_model, verbose=True) + + # Test the LUT-based model + print("Running inference of LUT-based model training set (%d examples)..." % (dataset_length)) + lut_inference(lut_model, track_used_luts=True) + lut_model.eval() + lut_accuracy = test(lut_model, train_loader, cuda=False) + print("LUT-Based Model accuracy: %f" % (lut_accuracy)) + print("Saving LUTs to %s... " % (options_cfg["log_dir"] + "/luts.pth")) + save_luts(lut_model, options_cfg["log_dir"] + "/luts.pth") + print("Done!") + diff --git a/examples/cybersecurity/models.py b/examples/cybersecurity/models.py index b98ab5dc9..bfbaf2ca5 100644 --- a/examples/cybersecurity/models.py +++ b/examples/cybersecurity/models.py @@ -63,13 +63,15 @@ def __init__(self, model_config): self.verilog_dir = None self.top_module_filename = None self.dut = None + self.verify = True self.logfile = None - def verilog_inference(self, verilog_dir, top_module_filename, logfile: bool = False, add_registers: bool = False): + def verilog_inference(self, verilog_dir, top_module_filename, logfile: bool = False, add_registers: bool = False, verify: bool = True): self.verilog_dir = realpath(verilog_dir) self.top_module_filename = top_module_filename - self.dut = PyVerilator.build(f"{self.verilog_dir}/{self.top_module_filename}", verilog_path=[self.verilog_dir], build_dir=f"{self.verilog_dir}/verilator") + self.dut = PyVerilator.build(f"{self.verilog_dir}/{self.top_module_filename}", verilog_path=[self.verilog_dir], build_dir=f"{self.verilog_dir}/verilator", command_args=("--x-assign","0",)) self.is_verilog_inference = True + self.verify = verify self.logfile = logfile if add_registers: self.latency = len(self.num_neurons) @@ -95,11 +97,8 @@ def verilog_forward(self, x): self.dut.io.clk = 0 for i in range(x.shape[0]): x_i = x[i,:] - y_i = self.pytorch_forward(x[i:i+1,:])[0] xv_i = list(map(lambda z: input_quant.get_bin_str(z), x_i)) - ys_i = list(map(lambda z: output_quant.get_bin_str(z), y_i)) xvc_i = reduce(lambda a,b: a+b, xv_i[::-1]) - ysc_i = reduce(lambda a,b: a+b, ys_i[::-1]) self.dut["M0"] = int(xvc_i, 2) for j in range(self.latency + 1): #print(self.dut.io.M5) @@ -107,9 +106,13 @@ def verilog_forward(self, x): result = f"{res:0{int(total_output_bits)}b}" self.dut.io.clk = 1 self.dut.io.clk = 0 - expected = f"{int(ysc_i,2):0{int(total_output_bits)}b}" result = f"{res:0{int(total_output_bits)}b}" - assert(expected == result) + if self.verify: + y_i = self.pytorch_forward(x[i:i+1,:])[0] + ys_i = list(map(lambda z: output_quant.get_bin_str(z), y_i)) + ysc_i = reduce(lambda a,b: a+b, ys_i[::-1]) + expected = f"{int(ysc_i,2):0{int(total_output_bits)}b}" + assert(expected == result) res_split = [result[i:i+output_bitwidth] for i in range(0, len(result), output_bitwidth)][::-1] yv_i = torch.Tensor(list(map(lambda z: int(z, 2), res_split))) y[i,:] = yv_i diff --git a/examples/cybersecurity/neq2lut.py b/examples/cybersecurity/neq2lut.py index 4302ec304..bcc7ef049 100644 --- a/examples/cybersecurity/neq2lut.py +++ b/examples/cybersecurity/neq2lut.py @@ -20,7 +20,8 @@ from logicnets.nn import generate_truth_tables, \ lut_inference, \ - module_list_to_verilog_module + module_list_to_verilog_module, \ + load_histograms from logicnets.synthesis import synthesize_and_get_resource_counts from logicnets.util import proc_postsynth_file @@ -34,6 +35,8 @@ "checkpoint": None, "generate_bench": False, "add_registers": False, + "histograms": None, + "freq_thresh": None, "simulate_pre_synthesis_verilog": False, "simulate_post_synthesis_verilog": False, } @@ -68,6 +71,10 @@ help="A location to store the log output of the training run and the output model (default: %(default)s)") parser.add_argument('--checkpoint', type=str, required=True, help="The checkpoint file which contains the model weights") + parser.add_argument('--histograms', type=str, default=None, + help="The checkpoint histograms of LUT usage (default: %(default)s)") + parser.add_argument('--freq-thresh', type=int, default=None, + help="Threshold to use to include this truth table into the model (default: %(default)s)") parser.add_argument('--generate-bench', action='store_true', default=False, help="Generate the truth table in BENCH format as well as verilog (default: %(default)s)") parser.add_argument('--dump-io', action='store_true', default=False, @@ -141,9 +148,12 @@ 'test_accuracy': lut_accuracy} torch.save(modelSave, options_cfg["log_dir"] + "/lut_based_model.pth") + if options_cfg["histograms"] is not None: + luts = torch.load(options_cfg["histograms"]) + load_histograms(lut_model, luts) print("Generating verilog in %s..." % (options_cfg["log_dir"])) - module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"], generate_bench=options_cfg["generate_bench"], add_registers=options_cfg["add_registers"]) + module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"], generate_bench=options_cfg["generate_bench"], add_registers=options_cfg["add_registers"], freq_thresh=options_cfg["freq_thresh"]) print("Top level entity stored at: %s/logicnet.v ..." % (options_cfg["log_dir"])) if args.dump_io: @@ -154,9 +164,10 @@ else: io_filename = None + if args.simulate_pre_synthesis_verilog: print("Running inference simulation of Verilog-based model...") - lut_model.verilog_inference(options_cfg["log_dir"], "logicnet.v", logfile=io_filename, add_registers=options_cfg["add_registers"]) + lut_model.verilog_inference(options_cfg["log_dir"], "logicnet.v", logfile=io_filename, add_registers=options_cfg["add_registers"], verify=options_cfg["freq_thresh"] is None or options_cfg["freq_thresh"] == 0) verilog_accuracy = test(lut_model, test_loader, cuda=False) print("Verilog-Based Model accuracy: %f" % (verilog_accuracy)) @@ -166,7 +177,7 @@ if args.simulate_post_synthesis_verilog: print("Running post-synthesis inference simulation of Verilog-based model...") proc_postsynth_file(options_cfg["log_dir"]) - lut_model.verilog_inference(options_cfg["log_dir"]+"/post_synth", "logicnet_post_synth.v", io_filename, add_registers=options_cfg["add_registers"]) + lut_model.verilog_inference(options_cfg["log_dir"]+"/post_synth", "logicnet_post_synth.v", io_filename, add_registers=options_cfg["add_registers"], verify=options_cfg["freq_thresh"] is None or options_cfg["freq_thresh"] == 0) post_synth_accuracy = test(lut_model, test_loader, cuda=False) print("Post-synthesis Verilog-Based Model accuracy: %f" % (post_synth_accuracy)) diff --git a/examples/cybersecurity/train.py b/examples/cybersecurity/train.py index 3576ae15a..30dcba634 100644 --- a/examples/cybersecurity/train.py +++ b/examples/cybersecurity/train.py @@ -44,6 +44,8 @@ "learning_rate": 1e-1, "seed": 25, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "nid-s-comp": { "hidden_layers": [49, 7], @@ -59,6 +61,8 @@ "learning_rate": 1e-1, "seed": 81, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "nid-m": { "hidden_layers": [593, 256, 128, 128], @@ -74,6 +78,8 @@ "learning_rate": 1e-1, "seed": 20, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "nid-m-comp": { "hidden_layers": [593, 256, 49, 7], @@ -89,6 +95,8 @@ "learning_rate": 1e-1, "seed": 40, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "nid-l": { "hidden_layers": [593, 100, 100, 100], @@ -104,6 +112,8 @@ "learning_rate": 1e-1, "seed": 2, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, "nid-l-comp": { "hidden_layers": [593, 100, 25, 5], @@ -119,6 +129,8 @@ "learning_rate": 1e-1, "seed": 83, "checkpoint": None, + "histograms": None, + "freq_thresh": None, }, } From 60e121372db588fd9771b89b78c1d24ef2b148af Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 11 Oct 2022 17:28:16 +0100 Subject: [PATCH 17/34] [abc] Updated script generation function to support specifying the rarity optimization command. --- src/logicnets/abc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index c3b56e568..c0966ce8f 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -56,7 +56,7 @@ def generate_prepare_script_string(num_layers, path): gen_monolithic_aig_string=gen_monolithic_aig_string) -def generate_opt_script_string(module_list, path, num_registers, rarity=0): +def generate_opt_script_string(module_list, path, num_registers, rarity=0, opt_cmd="&lnetopt"): opt_script_template = """\ # Generating script with rarity = {rarity}. @@ -78,7 +78,7 @@ def generate_opt_script_string(module_list, path, num_registers, rarity=0): &r {path}/aig/layers_opt.aig; &lneteval -O 2 {path}/test.simo {path}/test_output.txt """ - optimise_with_rarity_template = "&r {path}/layer{i}.aig; &ps; &lnetopt -I {fanin_bits} -O {fanout_bits} -R {rarity} {path}/train{it}.sim; &w {path}/aig/layer{i}_opt.aig; &ps; time\n" + optimise_with_rarity_template = "&r {path}/layer{i}.aig; &ps; {opt_cmd} -I {fanin_bits} -O {fanout_bits} -R {rarity} {path}/train{it}.sim; &w {path}/aig/layer{i}_opt.aig; &ps; time\n" technology_map_layer_template = "&r {path}/aig/layer{i}_opt.aig; &lnetmap -I {fanin_bits} -O {fanout_bits}; write {path}/blif/layer{i}_opt.blif; write_verilog -fm {path}/ver/layer{i}_opt.v\n" gen_monolithic_aig_template = "putontop {aig_layers_string}; st; ps; write {path}/aig/layers_opt.aig\n" gen_monolithic_blif_template = "putontop {blif_layers_string}; sw; ps; write {path}/blif/layers_opt.blif\n" @@ -98,7 +98,7 @@ def generate_opt_script_string(module_list, path, num_registers, rarity=0): fanout_bits = output_bitwidth # Generate optimisation script. - optimise_with_rarity_string += optimise_with_rarity_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, it="" if i == 0 else i, i=i, path=path, rarity=rarity) + optimise_with_rarity_string += optimise_with_rarity_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, it="" if i == 0 else i, i=i, path=path, rarity=rarity, opt_cmd=opt_cmd) technology_map_layers_string += technology_map_layer_template.format(fanin_bits=fanin_bits, fanout_bits=fanout_bits, i=i, path=path) aig_layers_string += "{path}/aig/layer{i}_opt.aig ".format(i=i, path=path) blif_layers_string += "{path}/blif/layer{i}_opt.blif ".format(i=i, path=path) From 5347456fa2a13b22301c9481393f230d3c83faab Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 11 Oct 2022 17:36:03 +0100 Subject: [PATCH 18/34] [docker] Updated ABC version. --- docker/Dockerfile.cpu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.cpu b/docker/Dockerfile.cpu index a52bb626b..dc7cb5b83 100644 --- a/docker/Dockerfile.cpu +++ b/docker/Dockerfile.cpu @@ -44,7 +44,7 @@ ENV NITROPARTSLIB=/workspace/Nitro-Parts-lib-Xilinx # Adding LogicNets dependency on ABC RUN git clone https://github.com/berkeley-abc/abc.git \ && cd abc \ - && git checkout ecda331a2a921bcac30bf3210f56adf9152ca22f \ + && git checkout 813a0f1ff1ae7512cb7947f54cd3f2ab252848c8 \ && make -j`nproc` ENV ABC_ROOT=/workspace/ABC From f32fe89f2de604ff4d533a2dae53d158c029296f Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Wed, 12 Oct 2022 16:38:12 +0100 Subject: [PATCH 19/34] [abc] Initial ABC synthesis flow. Need to fetch results from output strings. --- src/logicnets/abc.py | 105 +++++++++++++++++++++++++++++++++++++ src/logicnets/synthesis.py | 79 +++++++++++++++++++++------- 2 files changed, 164 insertions(+), 20 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index c0966ce8f..80df04382 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -13,6 +13,111 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os +import subprocess + +def verilog_bench_to_aig(verilog_file, aig_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&lnetread {verilog_file}; &ps; &w {aig_file}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + nodes = 0 + if verbose: + print(nodes) + print(out) + print(err) + return nodes, out, err # TODO: return the number of nodes + +def txt_to_sim(txt_file, sim_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&lnetread {txt_file} {sim_file}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + if verbose: + print(out) + print(err) + return out, err + +def simulate_circuit(circuit_file, sim_input_file, sim_output_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lnetsim {sim_input_file} {sim_output_file}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + if verbose: + print(out) + print(err) + return out, err + +def putontop_aig(aig_files, output_aig_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"putontop {' '.join(aig_files)}; st; ps; write {output_aig_file}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + nodes = 0 + if verbose: + print(nodes) + print(out) + print(err) + return nodes, out, err # TODO: return the number of nodes + +def putontop_blif(blif_files, output_blif_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"putontop {' '.join(blif_files)}; sw; ps; write {output_blif_file}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + nodes = 0 + if verbose: + print(nodes) + print(out) + print(err) + return nodes, out, err # TODO: return the number of nodes + +def optimize_bdd_network(circuit_file, output_file, input_bitwidth, output_bitwidth, rarity, sim_file, opt_cmd="&lnetopt", abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &ps; {opt_cmd} -I {input_bitwidth} -O {output_bitwidth} -R {rarity} {sim_file}; &w {output_file}; &ps; time"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + nodes = 0 + tt_pct = 100. + time_s = 0.0 + if verbose: + print(nodes) + print(tt_pct) + print(time_s) + print(out) + print(err) + return nodes, tt_pct, time_s, out, err # TODO: return the number of nodes, tt%, time + +def tech_map_circuit(circuit_file, output_blif, output_verilog, input_bitwidth, output_bitwidth, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lnetmap -I {input_bitwidth} -O {output_bitwidth}; write {output_blif}; write_verilog -fm {output_verilog}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + if verbose: + print(out) + print(err) + return out, err + +def evaluate_accuracy(circuit_file, sim_output_file, reference_txt, output_bitwidth, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lneteval -O {output_bitwidth} {sim_output_file} {reference_txt}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + accuracy = 0.0 + if verbose: + print(accuracy) + print(out) + print(err) + return accuracy, out, err # TODO: accuracy %, time + def generate_prepare_script_string(num_layers, path): prepare_script_template = """\ # This script prepares experiments in ABC by deriving intermediate simulation patterns diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index 195c552d8..c5956a458 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -18,7 +18,14 @@ from shutil import which import glob -from .abc import generate_prepare_script_string, generate_opt_script_string +from .abc import verilog_bench_to_aig,\ + txt_to_sim,\ + simulate_circuit,\ + putontop_aig,\ + putontop_blif,\ + optimize_bdd_network,\ + evaluate_accuracy,\ + tech_map_circuit #xcvu9p-flgb2104-2-i # TODO: Add option to perform synthesis on a remote server @@ -62,47 +69,79 @@ def synthesize_and_get_resource_counts(verilog_dir, top_name, fpga_part = "xcku3 return ret # Optimize the design with ABC -def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeline_stages=0, freq_thresh=0): +def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeline_stages=0, freq_thresh=0, train_input_txt="train_input.txt", train_output_txt="train_output.txt", test_input_txt="test_input.txt", test_output_txt="test_output.txt", verbose=False): if "ABC_ROOT" not in os.environ: raise Exception("The environment variable ABC_ROOT is not defined.") abc_path = os.environ["ABC_ROOT"] # Create directories and symlinks ready for processing with ABC - project_prefix = "logicnet" + project_prefix = "abc" abc_project_root = f"{verilog_dir}/{project_prefix}" verilog_bench_dir = f"{abc_project_root}/ver" aig_dir = f"{abc_project_root}/aig" blif_dir = f"{abc_project_root}/blif" + veropt_dir = f"{abc_project_root}/veropt" if not os.path.exists(verilog_bench_dir): os.makedirs(verilog_bench_dir) if not os.path.exists(aig_dir): os.makedirs(aig_dir) if not os.path.exists(blif_dir): os.makedirs(blif_dir) - real_abc_project_root = os.path.realpath(abc_project_root) - project_symlink_path = f"{abc_path}/{project_prefix}" - os.symlink(real_abc_project_root, project_symlink_path) # Create a symlink to this folder in the ABC root. + if not os.path.exists(veropt_dir): + os.makedirs(veropt_dir) # Fetch the right source files from the verilog directory - source_files = glob.glob(f"{verilog_dir}/*.v") + glob.glob(f"{verilog_dir}/*.bench") + source_files = glob.glob(f"{verilog_dir}/logicnet.v") + [f"{verilog_dir}/layer{i}.v" for i in range(len(module_list))] + glob.glob(f"{verilog_dir}/*.bench") for f in source_files: shutil.copy(f, verilog_bench_dir) # Fetch the I/O files - for f in glob.glob(f"{verilog_dir}/*.txt"): + for f in list(map(lambda x: f"{verilog_dir}/{x}", [train_input_txt, train_output_txt, test_input_txt, test_output_txt])): shutil.copy(f, f"{abc_project_root}") - # Create script files to pass to ABC - # TODO: Calculate number of layers from the model - with open(f"{abc_project_root}/prepare.script", "w") as f: - f.write(generate_prepare_script_string(num_layers=len(module_list), path=project_prefix)) - with open(f"{abc_project_root}/opt_all.script", "w") as f: - f.write(generate_opt_script_string(module_list=module_list, path=project_prefix, num_registers=pipeline_stages, rarity=freq_thresh)) + # Preparation - model / I/O conversion + # Convert txt inputs into the sim format + out, err = txt_to_sim(train_input_txt, "train.sim", working_dir=abc_project_root, verbose=verbose) + out, err = txt_to_sim(test_input_txt, "test.sim", working_dir=abc_project_root) - #proc = subprocess.Popen(['./abc', '-c', '"x/jsc_s/prepare.script"', '-c', '"x/jsc_s/opt_all.script"'], cwd=abc_path, stdout=subprocess.PIPE, env=os.environ) - proc = subprocess.Popen(['./abc', '-c', f'"{project_prefix}/prepare.script"', '-c', f'"{project_prefix}/opt_all.script"'], cwd=abc_path, stdout=subprocess.PIPE, env=os.environ) - out, err = proc.communicate() + # Create AIGs from verilog + for i in range(len(module_list)): + nodes, out, err = verilog_bench_to_aig(f"ver/layer{i}.v", f"aig/layer{i}.aig", working_dir=abc_project_root, verbose=verbose) - with open(f"{abc_project_root}/abc.log", "w") as f: - f.write(out.decode("utf-8")) + # Simulate each layer + for i in range(len(module_list)): + out, err = simulate_circuit(f"aig/layer{i}.aig", f"train{i}.sim" if i != 0 else "train.sim", f"train{i+1}.sim", working_dir=abc_project_root, verbose=verbose) - os.remove(project_symlink_path) + # Synthesis + for i in range(len(module_list)): + _, input_bitwidth = module_list[i].input_quant.get_scale_factor_bits() + _, output_bitwidth = module_list[i].output_quant.get_scale_factor_bits() + indices, _, _, _ = module_list[i].neuron_truth_tables[0] + fanin = len(indices) + nodes, tt_pct, time, out, err = optimize_bdd_network(f"aig/layer{i}.aig", f"aig/layer{i}_full.aig", int(input_bitwidth*fanin), int(output_bitwidth), freq_thresh, f"train{i}.sim" if i != 0 else "train.sim", opt_cmd="&lnetopt", working_dir=abc_project_root, verbose=verbose) + + # Technology mapping + for i in range(len(module_list)): + _, input_bitwidth = module_list[i].input_quant.get_scale_factor_bits() + _, output_bitwidth = module_list[i].output_quant.get_scale_factor_bits() + indices, _, _, _ = module_list[i].neuron_truth_tables[0] + fanin = len(indices) + out, err = tech_map_circuit(f"aig/layer{i}_full.aig", f"blif/layer{i}_full.blif", f"veropt/layer{i}_full.v", int(input_bitwidth*fanin), int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) + + # Generate monolithic circuits + if len(module_list) > 1: + nodes, out, err = putontop_aig([f"aig/layer{i}_full.aig" for i in range(len(module_list))], f"aig/layers_full.aig", working_dir=abc_project_root, verbose=verbose) + nodes, out, err = putontop_blif([f"blif/layer{i}_full.blif" for i in range(len(module_list))], f"blif/layers_full.blif", working_dir=abc_project_root, verbose=verbose) + else: + shutil.copy(f"{aig_dir}/layer0_full.aig", f"{aig_dir}/layers_full.aig") + shutil.copy(f"{blif_dir}/layer0_full.blif", f"{blif_dir}/layers_full.blif") + + # Evaluation + # Training set: + _, output_bitwidth = module_list[-1].output_quant.get_scale_factor_bits() + out, err = simulate_circuit(f"aig/layers_full.aig", "train.sim", "train.simo", working_dir=abc_project_root, verbose=verbose) + train_accuracy, out, err = evaluate_accuracy(f"aig/layers_full.aig", "train.simo", train_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) + # Test set: + out, err = simulate_circuit(f"aig/layers_full.aig", "test.sim", "test.simo", working_dir=abc_project_root, verbose=verbose) + test_accuracy, out, err = evaluate_accuracy(f"aig/layers_full.aig", "test.simo", test_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) + + return train_accuracy, test_accuracy, nodes From 5da1be51c4901f1b28f07b836aee5f28b0eb6da5 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Wed, 12 Oct 2022 16:46:12 +0100 Subject: [PATCH 20/34] [abc] Added option to specify the BDD command for synthesis preset. --- src/logicnets/synthesis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index c5956a458..909d22965 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -69,7 +69,7 @@ def synthesize_and_get_resource_counts(verilog_dir, top_name, fpga_part = "xcku3 return ret # Optimize the design with ABC -def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeline_stages=0, freq_thresh=0, train_input_txt="train_input.txt", train_output_txt="train_output.txt", test_input_txt="test_input.txt", test_output_txt="test_output.txt", verbose=False): +def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeline_stages=0, freq_thresh=0, train_input_txt="train_input.txt", train_output_txt="train_output.txt", test_input_txt="test_input.txt", test_output_txt="test_output.txt", bdd_opt_cmd="lnetopt", verbose=False): if "ABC_ROOT" not in os.environ: raise Exception("The environment variable ABC_ROOT is not defined.") abc_path = os.environ["ABC_ROOT"] @@ -116,7 +116,7 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli _, output_bitwidth = module_list[i].output_quant.get_scale_factor_bits() indices, _, _, _ = module_list[i].neuron_truth_tables[0] fanin = len(indices) - nodes, tt_pct, time, out, err = optimize_bdd_network(f"aig/layer{i}.aig", f"aig/layer{i}_full.aig", int(input_bitwidth*fanin), int(output_bitwidth), freq_thresh, f"train{i}.sim" if i != 0 else "train.sim", opt_cmd="&lnetopt", working_dir=abc_project_root, verbose=verbose) + nodes, tt_pct, time, out, err = optimize_bdd_network(f"aig/layer{i}.aig", f"aig/layer{i}_full.aig", int(input_bitwidth*fanin), int(output_bitwidth), freq_thresh, f"train{i}.sim" if i != 0 else "train.sim", opt_cmd=bdd_opt_cmd, working_dir=abc_project_root, verbose=verbose) # Technology mapping for i in range(len(module_list)): From 580a04ffa54a1ebe85936502f5bc79f544883873 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Wed, 12 Oct 2022 18:04:12 +0100 Subject: [PATCH 21/34] [abc] Used re to extract important information for the ABC logs. --- src/logicnets/abc.py | 30 +++++++++++++++++++++++------- src/logicnets/synthesis.py | 4 +++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index 80df04382..b8382b461 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -15,6 +15,12 @@ import os import subprocess +import re + +_aig_re_str = r'and\s+=\s+\d+' +_acc_re_str = r'The\s+accuracy\s+is\s+\d+\.\d+' +_avg_cs_re_str = r'Average\s+care\s+set\s+is\s+\d+\.\d+' +_elapse_s_re_str = r'elapse:\s+\d+\.\d+' def verilog_bench_to_aig(verilog_file, aig_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): cmd = [f"{abc_path}/abc", '-c', f"&lnetread {verilog_file}; &ps; &w {aig_file}"] @@ -22,7 +28,8 @@ def verilog_bench_to_aig(verilog_file, aig_file, abc_path=os.environ["ABC_ROOT"] print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) out, err = proc.communicate() - nodes = 0 + aig_re = re.compile(_aig_re_str) + nodes = int(aig_re.search(str(out)).group().split(" ")[-1]) if verbose: print(nodes) print(out) @@ -57,7 +64,8 @@ def putontop_aig(aig_files, output_aig_file, abc_path=os.environ["ABC_ROOT"], wo print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) out, err = proc.communicate() - nodes = 0 + aig_re = re.compile(_aig_re_str) + nodes = int(aig_re.search(str(out)).group().split(" ")[-1]) if verbose: print(nodes) print(out) @@ -70,7 +78,8 @@ def putontop_blif(blif_files, output_blif_file, abc_path=os.environ["ABC_ROOT"], print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) out, err = proc.communicate() - nodes = 0 + aig_re = re.compile(_aig_re_str) + nodes = int(aig_re.search(str(out)).group().split(" ")[-1]) if verbose: print(nodes) print(out) @@ -83,9 +92,15 @@ def optimize_bdd_network(circuit_file, output_file, input_bitwidth, output_bitwi print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) out, err = proc.communicate() - nodes = 0 - tt_pct = 100. - time_s = 0.0 + aig_re = re.compile(_aig_re_str) + nodes = int(aig_re.search(str(out)).group().split(" ")[-1]) + if opt_cmd == "&lnetopt": + tt_pct_re = re.compile(_avg_cs_re_str) + tt_pct = float(tt_pct_re.search(str(out)).group().split(" ")[-1]) + else: + tt_pct = None + time_re = re.compile(_elapse_s_re_str) + time_s = float(time_re.search(str(out)).group().split(" ")[-1]) if verbose: print(nodes) print(tt_pct) @@ -111,7 +126,8 @@ def evaluate_accuracy(circuit_file, sim_output_file, reference_txt, output_bitwi print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) out, err = proc.communicate() - accuracy = 0.0 + acc_re = re.compile(_acc_re_str) + accuracy = float(acc_re.search(str(out)).group().split(" ")[-1]) if verbose: print(accuracy) print(out) diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index 909d22965..753b883f1 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -111,12 +111,14 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli out, err = simulate_circuit(f"aig/layer{i}.aig", f"train{i}.sim" if i != 0 else "train.sim", f"train{i+1}.sim", working_dir=abc_project_root, verbose=verbose) # Synthesis + average_tt_pcts = [] for i in range(len(module_list)): _, input_bitwidth = module_list[i].input_quant.get_scale_factor_bits() _, output_bitwidth = module_list[i].output_quant.get_scale_factor_bits() indices, _, _, _ = module_list[i].neuron_truth_tables[0] fanin = len(indices) nodes, tt_pct, time, out, err = optimize_bdd_network(f"aig/layer{i}.aig", f"aig/layer{i}_full.aig", int(input_bitwidth*fanin), int(output_bitwidth), freq_thresh, f"train{i}.sim" if i != 0 else "train.sim", opt_cmd=bdd_opt_cmd, working_dir=abc_project_root, verbose=verbose) + average_tt_pcts.append(tt_pct) # Technology mapping for i in range(len(module_list)): @@ -143,5 +145,5 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli out, err = simulate_circuit(f"aig/layers_full.aig", "test.sim", "test.simo", working_dir=abc_project_root, verbose=verbose) test_accuracy, out, err = evaluate_accuracy(f"aig/layers_full.aig", "test.simo", test_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) - return train_accuracy, test_accuracy, nodes + return train_accuracy, test_accuracy, nodes, average_tt_pcts From 2b4273e7267b9dc81b37e58f26d59092cd4288c2 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Thu, 13 Oct 2022 17:00:31 +0100 Subject: [PATCH 22/34] [abc] Added functions for generic synthesis optimizations and final tech mapping / pipelining. --- src/logicnets/abc.py | 89 +++++++++++++++++++++++++++++++++++++- src/logicnets/synthesis.py | 16 ++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index b8382b461..7e8e95902 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -16,8 +16,10 @@ import os import subprocess import re +import shutil _aig_re_str = r'and\s+=\s+\d+' +_lut_re_str = r'nd\s+=\s+\d+' _acc_re_str = r'The\s+accuracy\s+is\s+\d+\.\d+' _avg_cs_re_str = r'Average\s+care\s+set\s+is\s+\d+\.\d+' _elapse_s_re_str = r'elapse:\s+\d+\.\d+' @@ -109,8 +111,63 @@ def optimize_bdd_network(circuit_file, output_file, input_bitwidth, output_bitwi print(err) return nodes, tt_pct, time_s, out, err # TODO: return the number of nodes, tt%, time -def tech_map_circuit(circuit_file, output_blif, output_verilog, input_bitwidth, output_bitwidth, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lnetmap -I {input_bitwidth} -O {output_bitwidth}; write {output_blif}; write_verilog -fm {output_verilog}"] +def optimize_mfs2(circuit_file, output_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; if -K 6 -a; mfs2; write_blif {output_file}; print_stats"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + lut_re = re.compile(_lut_re_str) + nodes = int(lut_re.search(str(out)).group().split(" ")[-1]) + if verbose: + print(nodes) + print(out) + print(err) + return nodes, out, err # TODO: return the number of nodes + +def iterative_mfs2_optimize(circuit_file, output_file, tmp_file="tmp.blif", max_loop=100, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + tmp_file_path = tmp_file if working_dir is None else f"{working_dir}/{tmp_file}" + output_file_path = output_file if working_dir is None else f"{working_dir}/{output_file}" + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; sweep; write_blif {tmp_file}; print_stats"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + lut_re = re.compile(_lut_re_str) + nodes = int(lut_re.search(str(out)).group().split(" ")[-1]) + best = nodes + shutil.copy(tmp_file_path, output_file_path) + if verbose: + print(nodes) + print(best) + print(out) + print(err) + for i in range(max_loop): + if i == 0: + cmd = [f"{abc_path}/abc", '-c', f"read {tmp_file}; mfs2; write_blif {tmp_file}; print_stats"] + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + if verbose: + print(" ".join(cmd)) + out, err = proc.communicate() + lut_re = re.compile(_lut_re_str) + nodes = int(lut_re.search(str(out)).group().split(" ")[-1]) + if verbose: + print(nodes) + print(out) + print(err) + else: + nodes, out, err = optimize_mfs2(tmp_file, tmp_file, abc_path=abc_path, working_dir=working_dir, verbose=verbose) + if nodes >= best: + break + else: + print(best) + best = nodes + shutil.copy(tmp_file_path, output_file_path) + os.remove(tmp_file_path) + return best + +def tech_map_circuit(circuit_file, output_blif, input_bitwidth, output_bitwidth, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lnetmap -I {input_bitwidth} -O {output_bitwidth}; write {output_blif}"] if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) @@ -120,6 +177,34 @@ def tech_map_circuit(circuit_file, output_blif, output_verilog, input_bitwidth, print(err) return out, err +def pipeline_tech_mapped_circuit(circuit_file, output_verilog, num_registers, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; ps; pipe -L {num_registers}; ps; retime -M 4; ps; sweep; ps; write_verilog -fm {output_verilog}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + lut_re = re.compile(_lut_re_str) + nodes = int(lut_re.search(str(out)).group().split(" ")[-1]) + if verbose: + print(nodes) + print(out) + print(err) + return out, err + +def tech_map_to_verilog(circuit_file, output_verilog, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; print_stats; write_verilog -fm {output_verilog}"] + if verbose: + print(" ".join(cmd)) + proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) + out, err = proc.communicate() + lut_re = re.compile(_lut_re_str) + nodes = int(lut_re.search(str(out)).group().split(" ")[-1]) + if verbose: + print(nodes) + print(out) + print(err) + return nodes, out, err + def evaluate_accuracy(circuit_file, sim_output_file, reference_txt, output_bitwidth, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lneteval -O {output_bitwidth} {sim_output_file} {reference_txt}"] if verbose: diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index 753b883f1..7c4fe1eac 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -25,7 +25,10 @@ putontop_blif,\ optimize_bdd_network,\ evaluate_accuracy,\ - tech_map_circuit + tech_map_circuit,\ + iterative_mfs2_optimize,\ + pipeline_tech_mapped_circuit,\ + tech_map_to_verilog #xcvu9p-flgb2104-2-i # TODO: Add option to perform synthesis on a remote server @@ -126,7 +129,7 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli _, output_bitwidth = module_list[i].output_quant.get_scale_factor_bits() indices, _, _, _ = module_list[i].neuron_truth_tables[0] fanin = len(indices) - out, err = tech_map_circuit(f"aig/layer{i}_full.aig", f"blif/layer{i}_full.blif", f"veropt/layer{i}_full.v", int(input_bitwidth*fanin), int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) + out, err = tech_map_circuit(f"aig/layer{i}_full.aig", f"blif/layer{i}_full.blif", int(input_bitwidth*fanin), int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) # Generate monolithic circuits if len(module_list) > 1: @@ -136,6 +139,15 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli shutil.copy(f"{aig_dir}/layer0_full.aig", f"{aig_dir}/layers_full.aig") shutil.copy(f"{blif_dir}/layer0_full.blif", f"{blif_dir}/layers_full.blif") + # Generic logic synthesis optimizations + nodes = iterative_mfs2_optimize(circuit_file=f"blif/layers_full.blif", output_file=f"blif/layers_full_opt.blif", tmp_file="blif/tmp.blif", max_loop=100, working_dir=abc_project_root, verbose=verbose) + + # Generate verilog, with or without pipelining + if pipeline_stages == 0: + nodes, out, err = tech_map_to_verilog(circuit_file=f"blif/layers_full_opt.blif", output_verilog=f"veropt/layers_full_opt.v", working_dir=abc_project_root, verbose=verbose) + else: + nodes, out, err = pipeline_tech_mapped_circuit(circuit_file=f"blif/layers_full_opt.blif", output_verilog=f"veropt/layers_full_opt.v", num_registers=num_registers, working_dir=abc_project_root, verbose=verbose) + # Evaluation # Training set: _, output_bitwidth = module_list[-1].output_quant.get_scale_factor_bits() From 314949387e5515f34f0e4002b3c8f69cdc7122a8 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Thu, 13 Oct 2022 17:15:51 +0100 Subject: [PATCH 23/34] [abc] Updated simulation/evaluation to work on blif models. --- src/logicnets/abc.py | 16 +++++++++++++--- src/logicnets/synthesis.py | 8 ++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index 7e8e95902..b3aba9783 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -50,7 +50,12 @@ def txt_to_sim(txt_file, sim_file, abc_path=os.environ["ABC_ROOT"], working_dir= return out, err def simulate_circuit(circuit_file, sim_input_file, sim_output_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lnetsim {sim_input_file} {sim_output_file}"] + if circuit_file.endswith(".aig"): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lnetsim {sim_input_file} {sim_output_file}"] + elif circuit_file.endswith(".blif"): + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; strash; &get; &lnetsim {sim_input_file} {sim_output_file}"] + else: + raise ValueError(f"Unsupported file type: {circuit_file}") if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) @@ -178,7 +183,7 @@ def tech_map_circuit(circuit_file, output_blif, input_bitwidth, output_bitwidth, return out, err def pipeline_tech_mapped_circuit(circuit_file, output_verilog, num_registers, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; ps; pipe -L {num_registers}; ps; retime -M 4; ps; sweep; ps; write_verilog -fm {output_verilog}"] + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; print_stats; pipe -L {num_registers}; print_stats; retime -M 4; print_stats; sweep; print_stats; write_verilog -fm {output_verilog}"] if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) @@ -206,7 +211,12 @@ def tech_map_to_verilog(circuit_file, output_verilog, abc_path=os.environ["ABC_R return nodes, out, err def evaluate_accuracy(circuit_file, sim_output_file, reference_txt, output_bitwidth, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lneteval -O {output_bitwidth} {sim_output_file} {reference_txt}"] + if circuit_file.endswith(".aig"): + cmd = [f"{abc_path}/abc", '-c', f"&r {circuit_file}; &lneteval -O {output_bitwidth} {sim_output_file} {reference_txt}"] + elif circuit_file.endswith(".blif"): + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; strash; &get; &lneteval -O {output_bitwidth} {sim_output_file} {reference_txt}"] + else: + raise ValueError(f"Unsupported file type: {circuit_file}") if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index 7c4fe1eac..36ffbfcf3 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -151,11 +151,11 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli # Evaluation # Training set: _, output_bitwidth = module_list[-1].output_quant.get_scale_factor_bits() - out, err = simulate_circuit(f"aig/layers_full.aig", "train.sim", "train.simo", working_dir=abc_project_root, verbose=verbose) - train_accuracy, out, err = evaluate_accuracy(f"aig/layers_full.aig", "train.simo", train_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) + out, err = simulate_circuit(f"blif/layers_full_opt.blif", "train.sim", "train.simo", working_dir=abc_project_root, verbose=verbose) + train_accuracy, out, err = evaluate_accuracy(f"blif/layers_full_opt.blif", "train.simo", train_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) # Test set: - out, err = simulate_circuit(f"aig/layers_full.aig", "test.sim", "test.simo", working_dir=abc_project_root, verbose=verbose) - test_accuracy, out, err = evaluate_accuracy(f"aig/layers_full.aig", "test.simo", test_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) + out, err = simulate_circuit(f"blif/layers_full_opt.blif", "test.sim", "test.simo", working_dir=abc_project_root, verbose=verbose) + test_accuracy, out, err = evaluate_accuracy(f"blif/layers_full_opt.blif", "test.simo", test_output_txt, int(output_bitwidth), working_dir=abc_project_root, verbose=verbose) return train_accuracy, test_accuracy, nodes, average_tt_pcts From d345c38a8b94bb42573e445d18408952da2ae9b5 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 Oct 2022 12:14:32 +0100 Subject: [PATCH 24/34] [abc] Bugfixes for putontop commands. --- src/logicnets/abc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index b3aba9783..e53e7dccc 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -66,7 +66,7 @@ def simulate_circuit(circuit_file, sim_input_file, sim_output_file, abc_path=os. return out, err def putontop_aig(aig_files, output_aig_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"putontop {' '.join(aig_files)}; st; ps; write {output_aig_file}"] + cmd = [f"{abc_path}/abc", '-c', f"putontop {' '.join(aig_files)}; strash; print_stats; write {output_aig_file}"] if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) @@ -80,13 +80,13 @@ def putontop_aig(aig_files, output_aig_file, abc_path=os.environ["ABC_ROOT"], wo return nodes, out, err # TODO: return the number of nodes def putontop_blif(blif_files, output_blif_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"putontop {' '.join(blif_files)}; sw; ps; write {output_blif_file}"] + cmd = [f"{abc_path}/abc", '-c', f"putontop {' '.join(blif_files)}; sweep; print_stats; write {output_blif_file}"] if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) out, err = proc.communicate() - aig_re = re.compile(_aig_re_str) - nodes = int(aig_re.search(str(out)).group().split(" ")[-1]) + lut_re = re.compile(_lut_re_str) + nodes = int(lut_re.search(str(out)).group().split(" ")[-1]) if verbose: print(nodes) print(out) From 22736317e88e0f44a1fc8ab7b7de08b52e2e4aac Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 Oct 2022 12:15:07 +0100 Subject: [PATCH 25/34] [abc] Disabled print of the best model in the iterative optimizer. --- src/logicnets/abc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index e53e7dccc..b5c07c4cc 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -165,7 +165,8 @@ def iterative_mfs2_optimize(circuit_file, output_file, tmp_file="tmp.blif", max_ if nodes >= best: break else: - print(best) + if verbose: + print(best) best = nodes shutil.copy(tmp_file_path, output_file_path) os.remove(tmp_file_path) From 0d01979aff97d936e3937f0b1c4c4d8b1d371e26 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 Oct 2022 12:16:17 +0100 Subject: [PATCH 26/34] [abc] Added PyVerilator compatible verilog wrapper and post-process functions for ABC-generated verilog. --- src/logicnets/verilog.py | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/logicnets/verilog.py b/src/logicnets/verilog.py index f073a4692..9d4f0ab30 100644 --- a/src/logicnets/verilog.py +++ b/src/logicnets/verilog.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import numpy as np + def generate_register_verilog(module_name="myreg", param_name="DataWidth", input_name="data_in", output_name="data_out"): register_template = """\ module {module_name} #(parameter {param_name}=16) ( @@ -91,3 +93,44 @@ def generate_neuron_connection_verilog(input_indices, input_bitwidth): connection_string += ", " return connection_string +def fix_abc_module_name(input_verilog_file, output_verilog_file, old_module_name, new_module_name, add_timescale: bool = False): + with open(input_verilog_file, 'r') as f: + lines = f.readlines() + with open(output_verilog_file, 'w') as f: + if add_timescale: + f.write("`timescale 1 ps / 1 ps\n") + for l in lines: + if l.__contains__(f"module {old_module_name}"): + l = f"module {new_module_name} (\n" + f.write(l) + +def generate_abc_verilog_wrapper(module_name: str, input_name: str, input_bits: int, output_name: str, output_bits: int, submodule_name: str, num_registers: int, add_timescale: bool = True): + abc_wrapper_template = """\ +{timescale} +module {module_name} (input [{input_bits_1:d}:0] {input_name}, input clk, input rst, output[{output_bits_1:d}:0] {output_name}); +{module_contents} +endmodule\n""" + input_digits = int(np.ceil(np.log10(input_bits))) + output_digits = int(np.ceil(np.log10(output_bits))) + module_contents = [] + module_contents.append(f"{submodule_name} {submodule_name}_inst (") + # Connect inputs + if num_registers > 0: + module_contents.append(f" .clock(clk),") + for i in range(input_bits): + module_contents.append(f" .pi{i:0{input_digits}d}({input_name}[{i}]),") + for i in range(output_bits): + if i < output_bits-1: + module_contents.append(f" .po{i:0{output_digits}d}({output_name}[{i}]),") + else: + module_contents.append(f" .po{i:0{output_digits}d}({output_name}[{i}])") + module_contents.append(f" );\n") + module_contents = "\n".join(module_contents) + return abc_wrapper_template.format( module_name=module_name, + input_name=input_name, + input_bits_1=input_bits-1, + output_name=output_name, + output_bits_1=output_bits-1, + module_contents=module_contents, + timescale="`timescale 1 ps / 1 ps" if add_timescale else "") + From ebdb1f09a5d302a1a5385decb558f49084e1ff0a Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 Oct 2022 12:18:21 +0100 Subject: [PATCH 27/34] [synthesis] Updated ABC synthesis to fix the generated verilog and generate the verilog wrapper. --- src/logicnets/synthesis.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/logicnets/synthesis.py b/src/logicnets/synthesis.py index 36ffbfcf3..4856640c4 100644 --- a/src/logicnets/synthesis.py +++ b/src/logicnets/synthesis.py @@ -29,6 +29,8 @@ iterative_mfs2_optimize,\ pipeline_tech_mapped_circuit,\ tech_map_to_verilog +from .verilog import generate_abc_verilog_wrapper,\ + fix_abc_module_name #xcvu9p-flgb2104-2-i # TODO: Add option to perform synthesis on a remote server @@ -147,6 +149,18 @@ def synthesize_and_get_resource_counts_with_abc(verilog_dir, module_list, pipeli nodes, out, err = tech_map_to_verilog(circuit_file=f"blif/layers_full_opt.blif", output_verilog=f"veropt/layers_full_opt.v", working_dir=abc_project_root, verbose=verbose) else: nodes, out, err = pipeline_tech_mapped_circuit(circuit_file=f"blif/layers_full_opt.blif", output_verilog=f"veropt/layers_full_opt.v", num_registers=num_registers, working_dir=abc_project_root, verbose=verbose) + fix_abc_module_name(f"{veropt_dir}/layers_full_opt.v", f"{veropt_dir}/layers_full_opt.v", "\\aig", "layers_full_opt", add_timescale=True) + + # Generate top-level entity wrapper + _, input_bitwidth = module_list[0].input_quant.get_scale_factor_bits() + _, output_bitwidth = module_list[-1].output_quant.get_scale_factor_bits() + input_bitwidth, output_bitwidth = int(input_bitwidth), int(output_bitwidth) + total_input_bits = module_list[0].in_features*input_bitwidth + total_output_bits = module_list[-1].out_features*output_bitwidth + module_name="logicnet" + veropt_wrapper_str = generate_abc_verilog_wrapper(module_name=module_name, input_name="M0", input_bits=total_input_bits, output_name=f"M{len(module_list)}", output_bits=total_output_bits, submodule_name="layers_full_opt", num_registers=pipeline_stages) + with open(f"{veropt_dir}/{module_name}.v", "w") as f: + f.write(veropt_wrapper_str) # Evaluation # Training set: From 71bc3d7f782f4663f02c441cd64cc12206d94ef0 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Tue, 18 Oct 2022 13:30:36 +0100 Subject: [PATCH 28/34] [jsc] Updated neq2lut_abc scripts to work with new end-to-end ABC synthesis flow. --- examples/jet_substructure/neq2lut_abc.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/jet_substructure/neq2lut_abc.py b/examples/jet_substructure/neq2lut_abc.py index bdd431c2e..1f2cc4709 100644 --- a/examples/jet_substructure/neq2lut_abc.py +++ b/examples/jet_substructure/neq2lut_abc.py @@ -20,7 +20,8 @@ from logicnets.nn import generate_truth_tables, \ lut_inference, \ - module_list_to_verilog_module + module_list_to_verilog_module, \ + load_histograms from logicnets.synthesis import synthesize_and_get_resource_counts_with_abc from train import configs, model_config, dataset_config, test @@ -32,10 +33,12 @@ "cuda": None, "log_dir": None, "checkpoint": None, + "histograms": None, + "freq_thresh": None, } if __name__ == "__main__": - parser = ArgumentParser(description="Synthesize convert a PyTorch trained model into verilog") + parser = ArgumentParser(description="Synthesize convert a PyTorch trained model into verilog using ABC") parser.add_argument('--arch', type=str, choices=configs.keys(), default="jsc-s", help="Specific the neural network model to use (default: %(default)s)") parser.add_argument('--batch-size', type=int, default=None, metavar='N', @@ -66,6 +69,10 @@ help="A location to store the log output of the training run and the output model (default: %(default)s)") parser.add_argument('--checkpoint', type=str, required=True, help="The checkpoint file which contains the model weights") + parser.add_argument('--histograms', type=str, default=None, + help="The checkpoint histograms of LUT usage (default: %(default)s)") + parser.add_argument('--freq-thresh', type=int, default=None, + help="Threshold to use to include this truth table into the model (default: %(default)s)") parser.add_argument('--num-registers', type=int, default=0, help="The number of registers to add to the generated verilog (default: %(default)s)") args = parser.parse_args() @@ -144,11 +151,18 @@ 'test_accuracy': lut_accuracy} torch.save(modelSave, options_cfg["log_dir"] + "/lut_based_model.pth") + if options_cfg["histograms"] is not None: + luts = torch.load(options_cfg["histograms"]) + load_histograms(lut_model, luts) print("Generating verilog in %s..." % (options_cfg["log_dir"])) module_list_to_verilog_module(lut_model.module_list, "logicnet", options_cfg["log_dir"], generate_bench=True, add_registers=False) print("Top level entity stored at: %s/logicnet.v ..." % (options_cfg["log_dir"])) print("Running synthesis and verilog technology-mapped verilog in ABC") - synthesize_and_get_resource_counts_with_abc(options_cfg["log_dir"], lut_model.module_list, pipeline_stages=args.num_registers, freq_thresh=0) + train_accuracy, test_accuracy, nodes, average_care_set_size = synthesize_and_get_resource_counts_with_abc(options_cfg["log_dir"], lut_model.module_list, pipeline_stages=args.num_registers, freq_thresh=args.freq_thresh, train_input_txt="train_input.txt", train_output_txt="train_output.txt", test_input_txt="test_input.txt", test_output_txt="test_output.txt", bdd_opt_cmd="&ttopt", verbose=False) + print(f"Training set accuracy(%): {train_accuracy}") + print(f"Test set accuracy(%): {test_accuracy}") + print(f"LUT6(#): {nodes}") + print(f"Average care set sizes(%): {average_care_set_size}") From 98f8342f8449adb538a7279d14b3e99f1f9a2454 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Thu, 27 Oct 2022 17:48:26 +0100 Subject: [PATCH 29/34] [abc] Added option to specify the mfs2 command and mapping command. --- src/logicnets/abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index b5c07c4cc..14d74c48b 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -116,8 +116,8 @@ def optimize_bdd_network(circuit_file, output_file, input_bitwidth, output_bitwi print(err) return nodes, tt_pct, time_s, out, err # TODO: return the number of nodes, tt%, time -def optimize_mfs2(circuit_file, output_file, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): - cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; if -K 6 -a; mfs2; write_blif {output_file}; print_stats"] +def optimize_mfs2(circuit_file, output_file, abc_path=os.environ["ABC_ROOT"], command="mfs2", mapping="if -K 6 -a;", working_dir=None, verbose=False): + cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; {mapping} {command}; write_blif {output_file}; print_stats"] if verbose: print(" ".join(cmd)) proc = subprocess.Popen(cmd, cwd=working_dir, stdout=subprocess.PIPE, env=os.environ) From 410d4932876d47682a6a75aebdf3758c5e3be6c0 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Sat, 4 Mar 2023 12:28:57 +0000 Subject: [PATCH 30/34] [abc] Updated pipelining to return #nodes --- src/logicnets/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logicnets/abc.py b/src/logicnets/abc.py index 14d74c48b..07fd966bf 100644 --- a/src/logicnets/abc.py +++ b/src/logicnets/abc.py @@ -195,7 +195,7 @@ def pipeline_tech_mapped_circuit(circuit_file, output_verilog, num_registers, ab print(nodes) print(out) print(err) - return out, err + return nodes, out, err def tech_map_to_verilog(circuit_file, output_verilog, abc_path=os.environ["ABC_ROOT"], working_dir=None, verbose=False): cmd = [f"{abc_path}/abc", '-c', f"read {circuit_file}; print_stats; write_verilog -fm {output_verilog}"] From 0740d26c1f6c6487bc8d0a486dcc5dd018ecc3a8 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Sun, 5 Mar 2023 12:13:56 +0000 Subject: [PATCH 31/34] [verilog] Adds missing clock from ABC-generated verilog, if necessary --- src/logicnets/verilog.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/logicnets/verilog.py b/src/logicnets/verilog.py index 9d4f0ab30..2911979e1 100644 --- a/src/logicnets/verilog.py +++ b/src/logicnets/verilog.py @@ -101,7 +101,10 @@ def fix_abc_module_name(input_verilog_file, output_verilog_file, old_module_name f.write("`timescale 1 ps / 1 ps\n") for l in lines: if l.__contains__(f"module {old_module_name}"): - l = f"module {new_module_name} (\n" + if add_timescale: + l = f"module {new_module_name} (clock,\n" + else: + l = f"module {new_module_name} (\n" f.write(l) def generate_abc_verilog_wrapper(module_name: str, input_name: str, input_bits: int, output_name: str, output_bits: int, submodule_name: str, num_registers: int, add_timescale: bool = True): From 7e83a9c2ebb23fa41306a4243a0cd3b367771f26 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Sun, 5 Mar 2023 12:26:04 +0000 Subject: [PATCH 32/34] [ex/jsc] Bugfix / added AVG ROC-AUC to results --- examples/jet_substructure/dump_luts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/jet_substructure/dump_luts.py b/examples/jet_substructure/dump_luts.py index 05952268e..f99d21651 100644 --- a/examples/jet_substructure/dump_luts.py +++ b/examples/jet_substructure/dump_luts.py @@ -99,8 +99,9 @@ # Test the PyTorch model print("Running inference of baseline model on training set (%d examples)..." % (dataset_length)) model.eval() - baseline_accuracy = test(model, train_loader, cuda=False) + baseline_accuracy, baseline_avg_roc_auc = test(model, test_loader, cuda=False) print("Baseline accuracy: %f" % (baseline_accuracy)) + print("Baseline AVG ROC AUC: %f" % (baseline_avg_roc_auc)) # Instantiate LUT-based model lut_model = JetSubstructureLutModel(model_cfg) @@ -114,8 +115,9 @@ print("Running inference of LUT-based model training set (%d examples)..." % (dataset_length)) lut_inference(lut_model, track_used_luts=True) lut_model.eval() - lut_accuracy = test(lut_model, train_loader, cuda=False) + lut_accuracy, lut_avg_roc_auc = test(lut_model, test_loader, cuda=False) print("LUT-Based Model accuracy: %f" % (lut_accuracy)) + print("LUT-Based AVG ROC AUC: %f" % (lut_avg_roc_auc)) print("Saving LUTs to %s... " % (options_cfg["log_dir"] + "/luts.pth")) save_luts(lut_model, options_cfg["log_dir"] + "/luts.pth") print("Done!") From 3b53131bdab15447aa119631fb32f2e029d50d65 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Sun, 5 Mar 2023 12:35:01 +0000 Subject: [PATCH 33/34] [ex/jsc] Bugfix: added AVG ROC-AUC results --- examples/jet_substructure/neq2lut_abc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/jet_substructure/neq2lut_abc.py b/examples/jet_substructure/neq2lut_abc.py index 1f2cc4709..1d2bd7495 100644 --- a/examples/jet_substructure/neq2lut_abc.py +++ b/examples/jet_substructure/neq2lut_abc.py @@ -120,8 +120,9 @@ # Test the PyTorch model print("Running inference on baseline model...") model.eval() - baseline_accuracy = test(model, test_loader, cuda=False) + baseline_accuracy, baseline_avg_roc_auc = test(model, test_loader, cuda=False) print("Baseline accuracy: %f" % (baseline_accuracy)) + print("Baseline AVG ROC AUC: %f" % (baseline_avg_roc_auc)) # Run preprocessing on training set. #train_input_file = config['log_dir'] + "/train_input.txt" @@ -145,10 +146,12 @@ print("Running inference on LUT-based model...") lut_inference(lut_model) lut_model.eval() - lut_accuracy = test(lut_model, test_loader, cuda=False) + lut_accuracy, lut_avg_roc_auc = test(lut_model, test_loader, cuda=False) print("LUT-Based Model accuracy: %f" % (lut_accuracy)) + print("LUT-Based AVG ROC AUC: %f" % (lut_avg_roc_auc)) modelSave = { 'model_dict': lut_model.state_dict(), - 'test_accuracy': lut_accuracy} + 'test_accuracy': lut_accuracy, + 'test_avg_roc_auc': lut_avg_roc_auc} torch.save(modelSave, options_cfg["log_dir"] + "/lut_based_model.pth") if options_cfg["histograms"] is not None: From 1e0c9f2f20dcfb093806345abb967f58b93ff338 Mon Sep 17 00:00:00 2001 From: Nick Fraser Date: Mon, 18 Nov 2024 14:18:54 +0000 Subject: [PATCH 34/34] [docker] Bugfixes to ABC build --- docker/Dockerfile.cpu | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.cpu b/docker/Dockerfile.cpu index b42e49317..0467a8fa4 100644 --- a/docker/Dockerfile.cpu +++ b/docker/Dockerfile.cpu @@ -29,7 +29,7 @@ RUN apt-get -qq update && apt-get -qq -y install curl bzip2 \ && rm -rf /var/lib/apt/lists/* /var/log/dpkg.log # Install LogicNets system prerequisites -RUN apt-get -qq update && apt-get -qq -y install verilator build-essential libx11-6 git \ +RUN apt-get -qq update && apt-get -qq -y install verilator build-essential libx11-6 git libreadline-dev \ && apt-get autoclean \ && rm -rf /var/lib/apt/lists/* /var/log/dpkg.log @@ -42,11 +42,14 @@ RUN git clone https://github.com/dirjud/Nitro-Parts-lib-Xilinx.git ENV NITROPARTSLIB=/workspace/Nitro-Parts-lib-Xilinx # Adding LogicNets dependency on ABC +COPY examples/mnist/abc.patch /workspace/ RUN git clone https://github.com/berkeley-abc/abc.git \ && cd abc \ && git checkout 813a0f1ff1ae7512cb7947f54cd3f2ab252848c8 \ + && git apply /workspace/abc.patch \ + && rm -f /workspace/abc.patch \ && make -j`nproc` -ENV ABC_ROOT=/workspace/ABC +ENV ABC_ROOT=/workspace/abc # Create the user account to run LogicNets RUN groupadd -g $GID $GNAME