From 4820d1c7552677b5ff4bc8fd55ea27469da5456b Mon Sep 17 00:00:00 2001 From: Linda Date: Tue, 6 May 2014 21:34:23 -0400 Subject: [PATCH] commited COFFE1.0 --- coffe.py | 212 +++ coffe/__init__.py | 0 coffe/basic_subcircuits.py | 72 + coffe/ff_subcircuits.py | 148 ++ coffe/fpga.py | 2732 ++++++++++++++++++++++++++++++++++++ coffe/hspice_extract.py | 289 ++++ coffe/load_subcircuits.py | 312 ++++ coffe/lut_subcircuits.py | 441 ++++++ coffe/mux_subcircuits.py | 283 ++++ coffe/spice.py | 1132 +++++++++++++++ coffe/top_level.py | 753 ++++++++++ coffe/tran_sizing.py | 1363 ++++++++++++++++++ coffe/utils.py | 290 ++++ input_files/example.txt | 108 ++ spice_models/ptm22hp.l | 149 ++ 15 files changed, 8284 insertions(+) create mode 100644 coffe.py create mode 100644 coffe/__init__.py create mode 100644 coffe/basic_subcircuits.py create mode 100644 coffe/ff_subcircuits.py create mode 100644 coffe/fpga.py create mode 100644 coffe/hspice_extract.py create mode 100644 coffe/load_subcircuits.py create mode 100644 coffe/lut_subcircuits.py create mode 100644 coffe/mux_subcircuits.py create mode 100644 coffe/spice.py create mode 100644 coffe/top_level.py create mode 100644 coffe/tran_sizing.py create mode 100644 coffe/utils.py create mode 100644 input_files/example.txt create mode 100644 spice_models/ptm22hp.l diff --git a/coffe.py b/coffe.py new file mode 100644 index 0000000..0415237 --- /dev/null +++ b/coffe.py @@ -0,0 +1,212 @@ +### COFFE: CIRCUIT OPTIMIZATION FOR FPGA EXPLORATION +# Created by: Charles Chiasson (charles.chiasson@gmail.com) +# at: University of Toronto +# in: 2013 + +# +# The big picture: +# +# COFFE will size the transistors of an FPGA based on some input architecture +# parameters. COFFE will produce area and delay results that can be used to +# create VPR architecture files for architecture exploration. By changing how +# COFFE designs circuitry, one could also use COFFE to explore different FPGA +# circuit designs. +# +# How it works (in a nutshell): +# +# We start off by creating an 'FPGA' object with the input architecture parameters. +# This FPGA object contains other objects that each represent part of the FPGA +# circuitry (like switch block, LUT, etc.). +# We use the FPGA object to generate SPICE netlists of the circuitry +# We also use it to calculate area and wire loads +# +# Once the FPGA object is created and the SPICE netlists are generated, +# we pass the FPGA object to COFFE's transistor sizing engine. The following paper +# explains COFFE's transistor sizing algorithm in detail. +# +# [1] C. Chiasson and V.Betz, "COFFE: Fully-Automated Transistor Sizing for FPGAs", FPT2013 +# + + +import os +import sys +import shutil +import argparse +import time +import coffe.fpga as fpga +import coffe.tran_sizing as tran_sizing +import coffe.hspice_extract as hspice_extract +import coffe.utils + +print "\nCOFFE 1.0\n" +print "Man is a tool-using animal." +print "Without tools he is nothing, with tools he is all." +print " - Thomas Carlyle\n\n" + +# Parse the input arguments with argparse +parser = argparse.ArgumentParser() +parser.add_argument('arch_description') +parser.add_argument('-n', '--no_sizing', help="don't perform transistor sizing", action='store_true') +parser.add_argument('-o', '--opt_type', type=str, choices=["global", "local"], default="global", help="choose optimization type") +parser.add_argument('-m', '--re_erf', type=int, default=1, help="choose how many sizing combos to re-erf") +parser.add_argument('-a', '--area_opt_weight', type=int, default=1, help="area optimization weight") +parser.add_argument('-d', '--delay_opt_weight', type=int, default=1, help="delay optimization weight") +parser.add_argument('-i', '--max_iterations', type=int, default=6, help="max FPGA sizing iterations") +args = parser.parse_args() +arch_description_filename = args.arch_description +is_size_transistors = not args.no_sizing +opt_type = args.opt_type +re_erf = args.re_erf +area_opt_weight = args.area_opt_weight +delay_opt_weight = args.delay_opt_weight +max_iterations = args.max_iterations + +# Print the options +print "RUN OPTIONS:" +if is_size_transistors: + print "Transistor sizing: on" +else: + print "Transistor sizing: off" +if opt_type == "global": + print "Optimization type: global" +else: + print "Optimization type: local" +print "Number of top combos to re-ERF: " + str(re_erf) +print "Area optimization weight: " + str(area_opt_weight) +print "Delay optimization weight: " + str(delay_opt_weight) +print "Maximum number of sizing iterations: " + str(max_iterations) +print "" + +# Load the input architecture description file +arch_params_dict = coffe.utils.load_arch_params(arch_description_filename) + +# Create some local variables +N = arch_params_dict['N'] +K = arch_params_dict['K'] +W = arch_params_dict['W'] +L = arch_params_dict['L'] +I = arch_params_dict['I'] +Fs = arch_params_dict['Fs'] +Fcin = arch_params_dict['Fcin'] +Fcout = arch_params_dict['Fcout'] +Or = arch_params_dict['Or'] +Ofb = arch_params_dict['Ofb'] +Rsel = arch_params_dict['Rsel'] +Rfb = arch_params_dict['Rfb'] +Fclocal = arch_params_dict['Fclocal'] +vdd = arch_params_dict['vdd'] +vsram = arch_params_dict['vsram'] +vsram_n = arch_params_dict['vsram_n'] +gate_length = arch_params_dict['gate_length'] +min_tran_width = arch_params_dict['min_tran_width'] +min_width_tran_area = arch_params_dict['min_width_tran_area'] +sram_cell_area = arch_params_dict['sram_cell_area'] +model_path = arch_params_dict['model_path'] +model_library = arch_params_dict['model_library'] +metal_stack = arch_params_dict['metal'] + +# Record start time +total_start_time = time.time() + +# Create an FPGA instance +fpga_inst = fpga.FPGA(N, K, W, L, I, Fs, Fcin, Fcout, Fclocal, Or, Ofb, Rsel, Rfb, + vdd, vsram, vsram_n, + gate_length, + min_tran_width, + min_width_tran_area, + sram_cell_area, + model_path, + model_library, + metal_stack) + +# Print basic FPGA specs +fpga_inst.print_specs() + +############################################################### +## GENERATE FILES +############################################################### + +# Make the top-level spice folder if it doesn't already exist +arch_desc_words = arch_description_filename.split('.') +arch_folder = arch_desc_words[0] +if not os.path.exists(arch_folder): + os.mkdir(arch_folder) +else: + # Delete contents of sub-directories + # COFFE generates several 'intermediate results' files during sizing + # so we delete them to avoid from having them pile up if we run COFFE + # more than once. + dir_contents = os.listdir(arch_folder) + for content in dir_contents: + if os.path.isdir(arch_folder + "/" + content): + shutil.rmtree(arch_folder + "/" + content) + +# Change to the architecture directory +os.chdir(arch_folder) + +# Generate FPGA and associated SPICE files +fpga_inst.generate(is_size_transistors) + +# Print FPGA implementation details +fpga_inst.print_details() + +############################################################### +## TRANSISTOR SIZING +############################################################### + +# Size FPGA transistors +num_hspice_sims = 0 +if is_size_transistors: + num_hspice_sims = tran_sizing.size_fpga_transistors(fpga_inst, + opt_type, + re_erf, + max_iterations, + area_opt_weight, + delay_opt_weight, + num_hspice_sims) + +# Update subcircuit delays (these are the final values) +num_hspice_sims = fpga_inst.update_delays(num_hspice_sims) + +print "|------------------------------------------------------------------------------|" +print "| Area and Delay Report |" +print "|------------------------------------------------------------------------------|" +print "" + +# Print area and delay per subcircuit +coffe.utils.print_area_and_delay(fpga_inst) + +# Print block areas +coffe.utils.print_block_area(fpga_inst) + +# Print VPR delays (to be used to make architecture file) +coffe.utils.print_vpr_delays(fpga_inst) + +# Print VPR areas (to be used to make architecture file) +coffe.utils.print_vpr_areas(fpga_inst) + +# Print area and delay summary +final_cost = fpga_inst.area_dict["tile"]*fpga_inst.delay_dict["rep_crit_path"] +print " SUMMARY" +print " -------" +print " Tile Area " + str(round(fpga_inst.area_dict["tile"]/1e6,2)) + " um^2" +print " Representative Critical Path Delay " + str(round(fpga_inst.delay_dict["rep_crit_path"]*1e12,2)) + " ps" +print (" Cost (area^" + str(area_opt_weight) + " x delay^" + str(delay_opt_weight) + ") " + + str(round(final_cost,5))) + +print "" +print "|------------------------------------------------------------------------------|" +print "" + +# Come back to top level directory +os.chdir("../") + +# Record end time +total_end_time = time.time() +total_time_elapsed = total_end_time - total_start_time +total_hours_elapsed = int(total_time_elapsed/3600) +total_minutes_elapsed = int((total_time_elapsed-3600*total_hours_elapsed)/60) +total_seconds_elapsed = int(total_time_elapsed - 3600*total_hours_elapsed - 60*total_minutes_elapsed) + +print "Number of HSPICE simulations performed: " + str(num_hspice_sims) +print "Total time elapsed: " + str(total_hours_elapsed) + " hours " + str(total_minutes_elapsed) + " minutes " + str(total_seconds_elapsed) + " seconds\n" diff --git a/coffe/__init__.py b/coffe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/coffe/basic_subcircuits.py b/coffe/basic_subcircuits.py new file mode 100644 index 0000000..7f07752 --- /dev/null +++ b/coffe/basic_subcircuits.py @@ -0,0 +1,72 @@ +def inverter_generate(filename): + """ Generates the SPICE subcircuit for an inverter. Appends it to file 'filename'. """ + + # Open the file for appending + spice_file = open(filename, 'a') + spice_file.write("******************************************************************************************\n") + spice_file.write("* Inverter\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT inv n_in n_out n_vdd n_gnd Wn=45n Wp=45n\n") + spice_file.write("MNDOWN n_out n_in n_gnd n_gnd nmos L=gate_length W=Wn\n") + spice_file.write("MPUP n_out n_in n_vdd n_vdd pmos L=gate_length W=Wp\n") + spice_file.write(".ENDS\n\n\n") + spice_file.close() + + +def rest_generate(filename): + """ Generates the SPICE subcircuit for a level-restorer. Appends it to file 'filename'. """ + + # Open the file for appending + spice_file = open(filename, 'a') + spice_file.write("******************************************************************************************\n") + spice_file.write("* Level-restorer PMOS\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT rest n_pull n_gate n_vdd n_gnd Wp=45n\n") + spice_file.write("MPREST n_pull n_gate n_vdd n_vdd pmos L=gate_length W=Wp\n") + spice_file.write(".ENDS\n\n\n") + spice_file.close() + + +def wire_generate(filename): + """ Generates the SPICE subcircuit for a wire. Appends it to file 'filename'. """ + + # Open the file for appending + spice_file = open(filename, 'a') + spice_file.write("******************************************************************************************\n") + spice_file.write("* Interconnect wire\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT wire n_in n_out Rw=1 Cw=1\n") + spice_file.write("CWIRE_1 n_in gnd Cw\n") + spice_file.write("RWIRE_1 n_in n_out Rw\n") + spice_file.write("CWIRE_2 n_out gnd Cw\n") + spice_file.write(".ENDS\n\n\n") + spice_file.close() + + +def ptran_generate(filename): + """ Generates the SPICE subcircuit for a pass-transistor. Appends it to file 'filename'. """ + + # Open the file for appending + spice_file = open(filename, 'a') + spice_file.write("******************************************************************************************\n") + spice_file.write("* Pass-transistor\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT ptran n_in n_out n_gate n_gnd Wn=45n\n") + spice_file.write("MNPASS n_in n_gate n_out n_gnd nmos L=gate_length W=Wn\n") + spice_file.write(".ENDS\n\n\n") + spice_file.close() + + +def tgate_generate(filename): + """ Generates the SPICE subcircuit for a transmission gate. Appends it to file 'filename'. """ + + # Open the file for appending + spice_file = open(filename, 'a') + spice_file.write("******************************************************************************************\n") + spice_file.write("* Transmission gate\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT tgate n_in n_out n_gate_nmos n_gate_pmos n_vdd n_gnd Wn=45n Wp=45n\n") + spice_file.write("MNTGATE n_in n_gate_nmos n_out n_gnd nmos L=gate_length W=Wn\n") + spice_file.write("MPTGATE n_in n_gate_pmos n_out n_vdd pmos L=gate_length W=Wp\n") + spice_file.write(".ENDS\n\n\n") + spice_file.close() \ No newline at end of file diff --git a/coffe/ff_subcircuits.py b/coffe/ff_subcircuits.py new file mode 100644 index 0000000..d43a31e --- /dev/null +++ b/coffe/ff_subcircuits.py @@ -0,0 +1,148 @@ +def generate_ptran_2_input_select_d_ff(spice_filename): + """ Generates a D Flip-Flop SPICE deck """ + + # This script has to create the SPICE subcircuits required. + # It has to return a list of the transistor names used as well as a list of the wire names used. + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the FF circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* FF subcircuit \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT ff n_in n_out n_gate n_gate_n n_clk n_clk_n n_set n_set_n n_reset n_reset_n n_vdd n_gnd\n") + spice_file.write("* Input selection MUX\n") + spice_file.write("Xptran_ff_input_select n_in n_1_1 n_gate n_gnd ptran Wn=ptran_ff_input_select_nmos\n") + spice_file.write("Xwire_ff_input_select n_1_1 n_1_2 wire Rw='wire_ff_input_select_res/2' Cw='wire_ff_input_select_cap/2'\n") + spice_file.write("Xwire_ff_input_select_h n_1_2 n_1_3 wire Rw='wire_ff_input_select_res/2' Cw='wire_ff_input_select_cap/2'\n") + spice_file.write("Xptran_ff_input_select_h n_gnd n_1_3 n_gate_n n_gnd ptran Wn=ptran_ff_input_select_nmos\n") + spice_file.write("Xrest_ff_input_select n_1_2 n_2_1 n_vdd n_gnd rest Wp=rest_ff_input_select_pmos\n") + spice_file.write("Xinv_ff_input_1 n_1_2 n_2_1 n_vdd n_gnd inv Wn=inv_ff_input_1_nmos Wp=inv_ff_input_1_pmos\n\n") + spice_file.write("* First T-gate and cross-coupled inverters\n") + spice_file.write("Xwire_ff_input_out n_2_1 n_2_2 wire Rw=wire_ff_input_out_res Cw=wire_ff_input_out_cap\n") + spice_file.write("Xtgate_ff_1 n_2_2 n_3_1 n_clk_n n_clk n_vdd n_gnd tgate Wn=tgate_ff_1_nmos Wp=tgate_ff_1_pmos\n") + spice_file.write("MPtran_ff_set_n n_3_1 n_set_n n_vdd n_vdd pmos L=gate_length W=tran_ff_set_n_pmos\n") + spice_file.write("MNtran_ff_reset n_3_1 n_reset n_gnd n_gnd nmos L=gate_length W=tran_ff_reset_nmos\n") + spice_file.write("Xwire_ff_tgate_1_out n_3_1 n_3_2 wire Rw=wire_ff_tgate_1_out_res Cw=wire_ff_tgate_1_out_cap\n") + spice_file.write("Xinv_ff_cc1_1 n_3_2 n_4_1 n_vdd n_gnd inv Wn=inv_ff_cc1_1_nmos Wp=inv_ff_cc1_1_pmos\n") + spice_file.write("Xinv_ff_cc1_2 n_4_1 n_3_2 n_vdd n_gnd inv Wn=inv_ff_cc1_2_nmos Wp=inv_ff_cc1_2_pmos\n") + spice_file.write("Xwire_ff_cc1_out n_4_1 n_4_2 wire Rw=wire_ff_cc1_out_res Cw=wire_ff_cc1_out_cap\n\n") + spice_file.write("* Second T-gate and cross-coupled inverters\n") + spice_file.write("Xtgate_ff_2 n_4_2 n_5_1 n_clk n_clk_n n_vdd n_gnd tgate Wn=tgate_ff_2_nmos Wp=tgate_ff_2_pmos\n") + spice_file.write("MPtran_ff_reset_n n_5_1 n_reset_n n_vdd n_vdd pmos L=gate_length W=tran_ff_reset_n_pmos\n") + spice_file.write("MNtran_ff_set n_5_1 n_set n_gnd n_gnd nmos L=gate_length W=tran_ff_set_nmos\n") + spice_file.write("Xwire_ff_tgate_2_out n_5_1 n_5_2 wire Rw=wire_ff_tgate_2_out_res Cw=wire_ff_tgate_2_out_cap\n") + spice_file.write("Xinv_ff_cc2_1 n_5_2 n_6_1 n_vdd n_gnd inv Wn=inv_ff_cc2_1_nmos Wp=inv_ff_cc2_1_pmos\n") + spice_file.write("Xinv_ff_cc2_2 n_6_1 n_5_2 n_vdd n_gnd inv Wn=inv_ff_cc2_2_nmos Wp=inv_ff_cc2_2_pmos\n") + spice_file.write("Xwire_ff_cc2_out n_6_1 n_6_2 wire Rw=wire_ff_cc2_out_res Cw=wire_ff_cc2_out_cap\n\n") + spice_file.write("* Output driver\n") + spice_file.write("Xinv_ff_output_driver n_6_2 n_out n_vdd n_gnd inv Wn=inv_ff_output_driver_nmos Wp=inv_ff_output_driver_pmos\n\n") + spice_file.write(".ENDS\n\n\n") + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("ptran_ff_input_select_nmos") + tran_names_list.append("rest_ff_input_select_pmos") + tran_names_list.append("inv_ff_input_1_nmos") + tran_names_list.append("inv_ff_input_1_pmos") + tran_names_list.append("tgate_ff_1_nmos") + tran_names_list.append("tgate_ff_1_pmos") + tran_names_list.append("tran_ff_set_n_pmos") + tran_names_list.append("tran_ff_reset_nmos") + tran_names_list.append("inv_ff_cc1_1_nmos") + tran_names_list.append("inv_ff_cc1_1_pmos") + tran_names_list.append("inv_ff_cc1_2_nmos") + tran_names_list.append("inv_ff_cc1_2_pmos") + tran_names_list.append("tgate_ff_2_nmos") + tran_names_list.append("tgate_ff_2_pmos") + tran_names_list.append("tran_ff_reset_n_pmos") + tran_names_list.append("tran_ff_set_nmos") + tran_names_list.append("inv_ff_cc2_1_nmos") + tran_names_list.append("inv_ff_cc2_1_pmos") + tran_names_list.append("inv_ff_cc2_2_nmos") + tran_names_list.append("inv_ff_cc2_2_pmos") + tran_names_list.append("inv_ff_output_driver_nmos") + tran_names_list.append("inv_ff_output_driver_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_ff_input_select") + wire_names_list.append("wire_ff_input_out") + wire_names_list.append("wire_ff_tgate_1_out") + wire_names_list.append("wire_ff_cc1_out") + wire_names_list.append("wire_ff_tgate_2_out") + wire_names_list.append("wire_ff_cc2_out") + + return tran_names_list, wire_names_list + + +def generate_ptran_d_ff(spice_filename): + """ Generates a D Flip-Flop SPICE deck """ + + # This script has to create the SPICE subcircuits required. + # It has to return a list of the transistor names used as well as a list of the wire names used. + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the FF circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* FF subcircuit \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT ff n_in n_out n_gate n_gate_n n_clk n_clk_n n_set n_set_n n_reset n_reset_n n_vdd n_gnd\n") + spice_file.write("* FF input driver\n") + spice_file.write("Xinv_ff_input n_1_2 n_2_1 n_vdd n_gnd inv Wn=inv_ff_input_1_nmos Wp=inv_ff_input_1_pmos\n\n") + spice_file.write("* First T-gate and cross-coupled inverters\n") + spice_file.write("Xwire_ff_input_out n_2_1 n_2_2 wire Rw=wire_ff_input_out_res Cw=wire_ff_input_out_cap\n") + spice_file.write("Xtgate_ff_1 n_2_2 n_3_1 n_clk_n n_clk n_vdd n_gnd tgate Wn=tgate_ff_1_nmos Wp=tgate_ff_1_pmos\n") + spice_file.write("MPtran_ff_set_n n_3_1 n_set_n n_vdd n_vdd pmos L=gate_length W=tran_ff_set_n_pmos\n") + spice_file.write("MNtran_ff_reset n_3_1 n_reset n_gnd n_gnd nmos L=gate_length W=tran_ff_reset_nmos\n") + spice_file.write("Xwire_ff_tgate_1_out n_3_1 n_3_2 wire Rw=wire_ff_tgate_1_out_res Cw=wire_ff_tgate_1_out_cap\n") + spice_file.write("Xinv_ff_cc1_1 n_3_2 n_4_1 n_vdd n_gnd inv Wn=inv_ff_cc1_1_nmos Wp=inv_ff_cc1_1_pmos\n") + spice_file.write("Xinv_ff_cc1_2 n_4_1 n_3_2 n_vdd n_gnd inv Wn=inv_ff_cc1_2_nmos Wp=inv_ff_cc1_2_pmos\n") + spice_file.write("Xwire_ff_cc1_out n_4_1 n_4_2 wire Rw=wire_ff_cc1_out_res Cw=wire_ff_cc1_out_cap\n\n") + spice_file.write("* Second T-gate and cross-coupled inverters\n") + spice_file.write("Xtgate_ff_2 n_4_2 n_5_1 n_clk n_clk_n n_vdd n_gnd tgate Wn=tgate_ff_2_nmos Wp=tgate_ff_2_pmos\n") + spice_file.write("MPtran_ff_reset_n n_5_1 n_reset_n n_vdd n_vdd pmos L=gate_length W=tran_ff_reset_n_pmos\n") + spice_file.write("MNtran_ff_set n_5_1 n_set n_gnd n_gnd nmos L=gate_length W=tran_ff_set_nmos\n") + spice_file.write("Xwire_ff_tgate_2_out n_5_1 n_5_2 wire Rw=wire_ff_tgate_2_out_res Cw=wire_ff_tgate_2_out_cap\n") + spice_file.write("Xinv_ff_cc2_1 n_5_2 n_6_1 n_vdd n_gnd inv Wn=inv_ff_cc2_1_nmos Wp=inv_ff_cc2_1_pmos\n") + spice_file.write("Xinv_ff_cc2_2 n_6_1 n_5_2 n_vdd n_gnd inv Wn=inv_ff_cc2_2_nmos Wp=inv_ff_cc2_2_pmos\n") + spice_file.write("Xwire_ff_cc2_out n_6_1 n_6_2 wire Rw=wire_ff_cc2_out_res Cw=wire_ff_cc2_out_cap\n\n") + spice_file.write("* Output driver\n") + spice_file.write("Xinv_ff_output_driver n_6_2 n_out n_vdd n_gnd inv Wn=inv_ff_output_driver_nmos Wp=inv_ff_output_driver_pmos\n\n") + spice_file.write(".ENDS\n\n\n") + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("inv_ff_input_1_nmos") + tran_names_list.append("inv_ff_input_1_pmos") + tran_names_list.append("tgate_ff_1_nmos") + tran_names_list.append("tgate_ff_1_pmos") + tran_names_list.append("tran_ff_set_n_pmos") + tran_names_list.append("tran_ff_reset_nmos") + tran_names_list.append("inv_ff_cc1_1_nmos") + tran_names_list.append("inv_ff_cc1_1_pmos") + tran_names_list.append("inv_ff_cc1_2_nmos") + tran_names_list.append("inv_ff_cc1_2_pmos") + tran_names_list.append("tgate_ff_2_nmos") + tran_names_list.append("tgate_ff_2_pmos") + tran_names_list.append("tran_ff_reset_n_pmos") + tran_names_list.append("tran_ff_set_nmos") + tran_names_list.append("inv_ff_cc2_1_nmos") + tran_names_list.append("inv_ff_cc2_1_pmos") + tran_names_list.append("inv_ff_cc2_2_nmos") + tran_names_list.append("inv_ff_cc2_2_pmos") + tran_names_list.append("inv_ff_output_driver_nmos") + tran_names_list.append("inv_ff_output_driver_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_ff_input_out") + wire_names_list.append("wire_ff_tgate_1_out") + wire_names_list.append("wire_ff_cc1_out") + wire_names_list.append("wire_ff_tgate_2_out") + wire_names_list.append("wire_ff_cc2_out") + + return tran_names_list, wire_names_list diff --git a/coffe/fpga.py b/coffe/fpga.py new file mode 100644 index 0000000..ad8ed05 --- /dev/null +++ b/coffe/fpga.py @@ -0,0 +1,2732 @@ +# This module contains classes that describe FPGA circuitry. +# +# The most important class (and the only one that should be instantiated outside this +# file) is the 'fpga' class defined at the bottom of the file. +# +# An 'fpga' object describes the FPGA that we want to design. A tile-based FPGA is +# assumed, which consists of a switch block, a connection block and a logic cluster. +# Likewise, the 'fpga' object contains a 'SwitchBlockMUX' object, a 'ConnectionBlockMUX' +# and a 'LogicCluster' object, each of which describe those parts of the FPGA in more detail. +# The 'LogicCluster' contains other objects that describe the various parts of its circuitry +# (local routing multiplexers, LUTs, FF, etc.) When you create an 'fpga' object, you +# specify architecture parameters along with a few process parameters which are stored +# in the 'fpga' object. +# +# The 'fpga' does more than just hold information about the FPGA. +# +# - It uses this information to generate the SPICE netlists that COFFE uses to +# measure delay. These netlists are generated with the appropriate transistor and +# wire loading, which are a function of architecture parameters, transistor sizes as +# well as some hard-coded layout assumptions (see [1] for layout assumption details). +# It is important to note that these netlists only contain 'transistor-size' and +# 'wire-load' VARIABLES and not hard-coded sizes and wire loads. These variables are +# defined in their own external files. This allows us to create a single set of netlists. +# As COFFE changes the sizes of transistors, it only has to modify these external +# files and the netlist will behave appropriately (transistor and wire loads depend on +# transistor sizes). +# +# - It can calculate the physical area of each circuit and structure inside the FPGA +# (transistors, MUXes, LUTs, BLE, Logic Cluster, FPGA tile, etc.) based on the sizes of +# transistors and circuit topologies. +# +# - It can calculate the length of wires in the FPGA circuitry based on the area of +# the circuitry and the layout assumptions. +# +# - It can report the delay of each subcircuit in the FPGA. +# +# COFFE's transistor sizing engine uses the 'fpga' object to evaluate the impact of different transistor +# sizing combinations on the area and delay of the FPGA. +# +# [1] C. Chiasson and V.Betz, "COFFE: Fully-Automated Transistor Sizing for FPGAs", FPT2013 + +import os +import sys +import math + +# Subcircuit Modules +import basic_subcircuits +import mux_subcircuits +import lut_subcircuits +import ff_subcircuits +import load_subcircuits + +# Top level file generation module +import top_level + +# HSPICE handling module +import spice + + +class _Specs: + """ General FPGA specs. """ + + def __init__(self, N, K, W, L, I, Fs, Fcin, Fcout, Fclocal, Or, Ofb, Rsel, Rfb, + vdd, vsram, vsram_n, gate_length, min_tran_width, min_width_tran_area, sram_cell_area, model_path, model_library): + self.N = N + self.K = K + self.W = W + self.L = L + self.I = I + self.Fs = Fs + self.Fcin = Fcin + self.Fcout = Fcout + self.Fclocal = Fclocal + self.num_ble_general_outputs = Or + self.num_ble_local_outputs = Ofb + self.num_cluster_outputs = N*Or + self.Rsel = Rsel + self.Rfb = Rfb + self.vdd = vdd + self.vsram = vsram + self.vsram_n = vsram_n + self.gate_length = gate_length + self.min_tran_width = min_tran_width + self.min_width_tran_area = min_width_tran_area + self.sram_cell_area = sram_cell_area + self.model_path = model_path + self.model_library = model_library + + +class _SizableCircuit: + """ This is a base class used to identify FPGA circuits that can be sized (e.g. transistor sizing on lut) + and declare attributes common to all SizableCircuits. + If a class inherits _SizableCircuit, it should override all methods (error is raised otherwise). """ + + # A list of the names of transistors in this subcircuit. This list should be logically sorted such + # that transistor names appear in the order that they should be sized. + transistor_names = [] + # A list of the names of wires in this subcircuit + wire_names = [] + # A dictionary of the initial transistor sizes + initial_transistor_sizes = {} + # Path to the top level spice file + top_spice_path = "" + # Fall time for this subcircuit + tfall = 1 + # Rise time for this subcircuit + trise = 1 + # Delay to be used for this subcircuit + delay = 1 + # Delay weight used to calculate delay of representative critical path + delay_weight = 1 + + + def generate(self): + """ Generate SPICE subcircuits. + Generate method for base class must be overridden by child. """ + msg = "Function 'generate' must be overridden in class _SizableCircuit." + raise NotImplementedError(msg) + + + def generate_top(self): + """ Generate top-level SPICE circuit. + Generate method for base class must be overridden by child. """ + msg = "Function 'generate_top' must be overridden in class _SizableCircuit." + raise NotImplementedError(msg) + + + def update_area(self, area_dict, width_dict): + """ Calculate area of circuit. + Update area method for base class must be overridden by child. """ + msg = "Function 'update_area' must be overridden in class _SizableCircuit." + raise NotImplementedError(msg) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + msg = "Function 'update_wires' must be overridden in class _SizableCircuit." + raise NotImplementedError(msg) + + +class _CompoundCircuit: + """ This is a base class used to identify FPGA circuits that should not be sized. These circuits are + usually composed of multiple smaller circuits, so we call them 'compound' circuits. + Examples: circuits representing routing wires and loads. + If a class inherits _CompoundCircuit, it should override all methods.""" + + def generate(self): + """ Generate method for base class must be overridden by child. """ + msg = "Function 'generate' must be overridden in class _CompoundCircuit." + raise NotImplementedError(msg) + + +class _SwitchBlockMUX(_SizableCircuit): + """ Switch Block MUX Class: Pass-transistor 2-level mux with output driver """ + + def __init__(self, required_size, num_per_tile): + # Subcircuit name + self.name = "sb_mux" + # How big should this mux be (dictated by architecture specs) + self.required_size = required_size + # How big did we make the mux (it is possible that we had to make the mux bigger for level sizes to work out, this is how big the mux turned out) + self.implemented_size = -1 + # This is simply the implemented_size-required_size + self.num_unused_inputs = -1 + # Number of switch block muxes in one FPGA tile + self.num_per_tile = num_per_tile + # Number of SRAM cells per mux + self.sram_per_mux = -1 + # Size of the first level of muxing + self.level1_size = -1 + # Size of the second level of muxing + self.level2_size = -1 + # Delay weight in a representative critical path + self.delay_weight = 0.3596 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + """ Generate switch block mux. Calculates implementation specific details about itself and write the SPICE subcircuit. """ + + print "Generating switch block mux" + + # Calculate level sizes and number of SRAMs per mux + self.level2_size = int(math.sqrt(self.required_size)) + self.level1_size = int(math.ceil(float(self.required_size)/self.level2_size)) + self.implemented_size = self.level1_size*self.level2_size + self.num_unused_inputs = self.implemented_size - self.required_size + self.sram_per_mux = self.level1_size + self.level2_size + + # Call MUX generation function + self.transistor_names, self.wire_names = mux_subcircuits.generate_ptran_2lvl_mux(subcircuit_filename, self.name, self.implemented_size, self.level1_size, self.level2_size) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + self.initial_transistor_sizes["ptran_" + self.name + "_L1_nmos"] = 3 + self.initial_transistor_sizes["ptran_" + self.name + "_L2_nmos"] = 4 + self.initial_transistor_sizes["rest_" + self.name + "_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 8 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 4 + self.initial_transistor_sizes["inv_" + self.name + "_2_nmos"] = 10 + self.initial_transistor_sizes["inv_" + self.name + "_2_pmos"] = 20 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + """ Generate top level SPICE file """ + + print "Generating top-level switch block mux" + self.top_spice_path = top_level.generate_switch_block_top(self.name) + + + def update_area(self, area_dict, width_dict): + """ Update area. To do this, we use area_dict which is a dictionary, maintained externally, that contains + the area of everything. It is expected that area_dict will have all the information we need to calculate area. + We update area_dict and width_dict with calculations performed in this function. """ + + # MUX area + area = ((self.level1_size*self.level2_size)*area_dict["ptran_" + self.name + "_L1"] + + self.level2_size*area_dict["ptran_" + self.name + "_L2"] + + area_dict["rest_" + self.name + ""] + + area_dict["inv_" + self.name + "_1"] + + area_dict["inv_" + self.name + "_2"]) + + # MUX area including SRAM + area_with_sram = (area + (self.level1_size + self.level2_size)*area_dict["sram"]) + + width = math.sqrt(area) + width_with_sram = math.sqrt(area_with_sram) + area_dict[self.name] = area + width_dict[self.name] = width + area_dict[self.name + "_sram"] = area_with_sram + width_dict[self.name + "_sram"] = width_with_sram + + # Update VPR areas + area_dict["switch_mux_trans_size"] = area_dict["ptran_" + self.name + "_L1"] + area_dict["switch_buf_size"] = area_dict["rest_" + self.name + ""] + area_dict["inv_" + self.name + "_1"] + area_dict["inv_" + self.name + "_2"] + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_" + self.name + "_driver"] = (width_dict["inv_" + self.name + "_1"] + width_dict["inv_" + self.name + "_2"])/4 + wire_lengths["wire_" + self.name + "_L1"] = width_dict[self.name] + wire_lengths["wire_" + self.name + "_L2"] = width_dict[self.name] + + # Update set wire layers + wire_layers["wire_" + self.name + "_driver"] = 0 + wire_layers["wire_" + self.name + "_L1"] = 0 + wire_layers["wire_" + self.name + "_L2"] = 0 + + + def print_details(self): + """ Print switch block details """ + + print " SWITCH BLOCK DETAILS:" + print " Style: two-level MUX" + print " Required MUX size: " + str(self.required_size) + ":1" + print " Implemented MUX size: " + str(self.implemented_size) + ":1" + print " Level 1 size = " + str(self.level1_size) + print " Level 2 size = " + str(self.level2_size) + print " Number of unused inputs = " + str(self.num_unused_inputs) + print " Number of MUXes per tile: " + str(self.num_per_tile) + print " Number of SRAM cells per MUX: " + str(self.sram_per_mux) + print "" + + +class _ConnectionBlockMUX(_SizableCircuit): + """ Connection Block MUX Class: Pass-transistor 2-level mux """ + + def __init__(self, required_size, num_per_tile): + # Subcircuit name + self.name = "cb_mux" + # How big should this mux be (dictated by architecture specs) + self.required_size = required_size + # How big did we make the mux (it is possible that we had to make the mux bigger for level sizes to work out, this is how big the mux turned out) + self.implemented_size = -1 + # This is simply the implemented_size-required_size + self.num_unused_inputs = -1 + # Number of switch block muxes in one FPGA tile + self.num_per_tile = num_per_tile + # Number of SRAM cells per mux + self.sram_per_mux = -1 + # Size of the first level of muxing + self.level1_size = -1 + # Size of the second level of muxing + self.level2_size = -1 + # Delay weight in a representative critical path + self.delay_weight = 0.176 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating connection block mux" + + # Calculate level sizes and number of SRAMs per mux + self.level2_size = int(math.sqrt(self.required_size)) + self.level1_size = int(math.ceil(float(self.required_size)/self.level2_size)) + self.implemented_size = self.level1_size*self.level2_size + self.num_unused_inputs = self.implemented_size - self.required_size + self.sram_per_mux = self.level1_size + self.level2_size + + # Call MUX generation function + self.transistor_names, self.wire_names = mux_subcircuits.generate_ptran_2lvl_mux(subcircuit_filename, self.name, self.implemented_size, self.level1_size, self.level2_size) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + self.initial_transistor_sizes["ptran_" + self.name + "_L1_nmos"] = 2 + self.initial_transistor_sizes["ptran_" + self.name + "_L2_nmos"] = 2 + self.initial_transistor_sizes["rest_" + self.name + "_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 2 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 2 + self.initial_transistor_sizes["inv_" + self.name + "_2_nmos"] = 6 + self.initial_transistor_sizes["inv_" + self.name + "_2_pmos"] = 12 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + print "Generating top-level connection block mux" + self.top_spice_path = top_level.generate_connection_block_top(self.name) + + + def update_area(self, area_dict, width_dict): + """ Update area. To do this, we use area_dict which is a dictionary, maintained externally, that contains + the area of everything. It is expected that area_dict will have all the information we need to calculate area. + We update area_dict and width_dict with calculations performed in this function. """ + + # MUX area + area = ((self.level1_size*self.level2_size)*area_dict["ptran_" + self.name + "_L1"] + + self.level2_size*area_dict["ptran_" + self.name + "_L2"] + + area_dict["rest_" + self.name + ""] + + area_dict["inv_" + self.name + "_1"] + + area_dict["inv_" + self.name + "_2"]) + + # MUX area including SRAM + area_with_sram = (area + (self.level1_size + self.level2_size)*area_dict["sram"]) + + width = math.sqrt(area) + width_with_sram = math.sqrt(area_with_sram) + area_dict[self.name] = area + width_dict[self.name] = width + area_dict[self.name + "_sram"] = area_with_sram + width_dict[self.name + "_sram"] = width_with_sram + + # Update VPR area numbers + area_dict["ipin_mux_trans_size"] = area_dict["ptran_" + self.name + "_L1"] + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_" + self.name + "_driver"] = (width_dict["inv_" + self.name + "_1"] + width_dict["inv_" + self.name + "_2"])/4 + wire_lengths["wire_" + self.name + "_L1"] = width_dict[self.name] + wire_lengths["wire_" + self.name + "_L2"] = width_dict[self.name] + + # Update set wire layers + wire_layers["wire_" + self.name + "_driver"] = 0 + wire_layers["wire_" + self.name + "_L1"] = 0 + wire_layers["wire_" + self.name + "_L2"] = 0 + + + def print_details(self): + """ Print connection block details """ + + print " CONNECTION BLOCK DETAILS:" + print " Style: two-level MUX" + print " Required MUX size: " + str(self.required_size) + ":1" + print " Implemented MUX size: " + str(self.implemented_size) + ":1" + print " Level 1 size = " + str(self.level1_size) + print " Level 2 size = " + str(self.level2_size) + print " Number of unused inputs = " + str(self.num_unused_inputs) + print " Number of MUXes per tile: " + str(self.num_per_tile) + print " Number of SRAM cells per MUX: " + str(self.sram_per_mux) + print "" + + +class _LocalMUX(_SizableCircuit): + """ Local MUX Class: Pass-transistor 2-level mux with no driver """ + + def __init__(self, required_size, num_per_tile): + # Subcircuit name + self.name = "local_mux" + # How big should this mux be (dictated by architecture specs) + self.required_size = required_size + # How big did we make the mux (it is possible that we had to make the mux bigger for level sizes to work out, this is how big the mux turned out) + self.implemented_size = -1 + # This is simply the implemented_size-required_size + self.num_unused_inputs = -1 + # Number of switch block muxes in one FPGA tile + self.num_per_tile = num_per_tile + # Number of SRAM cells per mux + self.sram_per_mux = -1 + # Size of the first level of muxing + self.level1_size = -1 + # Size of the second level of muxing + self.level2_size = -1 + # Delay weight in a representative critical path + self.delay_weight = 0.0862 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating local mux" + + # Calculate level sizes and number of SRAMs per mux + self.level2_size = int(math.sqrt(self.required_size)) + self.level1_size = int(math.ceil(float(self.required_size)/self.level2_size)) + self.implemented_size = self.level1_size*self.level2_size + self.num_unused_inputs = self.implemented_size - self.required_size + self.sram_per_mux = self.level1_size + self.level2_size + + # Call MUX generation function + self.transistor_names, self.wire_names = mux_subcircuits.generate_ptran_2lvl_mux_no_driver(subcircuit_filename, self.name, self.implemented_size, self.level1_size, self.level2_size) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + self.initial_transistor_sizes["ptran_" + self.name + "_L1_nmos"] = 2 + self.initial_transistor_sizes["ptran_" + self.name + "_L2_nmos"] = 2 + self.initial_transistor_sizes["rest_" + self.name + "_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 2 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 2 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + print "Generating top-level local mux" + self.top_spice_path = top_level.generate_local_mux_top(self.name) + + + def update_area(self, area_dict, width_dict): + """ Update area. To do this, we use area_dict which is a dictionary, maintained externally, that contains + the area of everything. It is expected that area_dict will have all the information we need to calculate area. + We update area_dict and width_dict with calculations performed in this function. """ + + # MUX area + area = ((self.level1_size*self.level2_size)*area_dict["ptran_" + self.name + "_L1"] + + self.level2_size*area_dict["ptran_" + self.name + "_L2"] + + area_dict["rest_" + self.name + ""] + + area_dict["inv_" + self.name + "_1"]) + + # MUX area including SRAM + area_with_sram = (area + (self.level1_size + self.level2_size)*area_dict["sram"]) + + width = math.sqrt(area) + width_with_sram = math.sqrt(area_with_sram) + area_dict[self.name] = area + width_dict[self.name] = width + area_dict[self.name + "_sram"] = area_with_sram + width_dict[self.name + "_sram"] = width_with_sram + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_" + self.name + "_L1"] = width_dict[self.name] + wire_lengths["wire_" + self.name + "_L2"] = width_dict[self.name] + + # Update wire layers + wire_layers["wire_" + self.name + "_L1"] = 0 + wire_layers["wire_" + self.name + "_L2"] = 0 + + + def print_details(self): + """ Print local mux details """ + + print " LOCAL MUX DETAILS:" + print " Style: two-level MUX" + print " Required MUX size: " + str(self.required_size) + ":1" + print " Implemented MUX size: " + str(self.implemented_size) + ":1" + print " Level 1 size = " + str(self.level1_size) + print " Level 2 size = " + str(self.level2_size) + print " Number of unused inputs = " + str(self.num_unused_inputs) + print " Number of MUXes per tile: " + str(self.num_per_tile) + print " Number of SRAM cells per MUX: " + str(self.sram_per_mux) + print "" + + +class _LUTInputDriver(_SizableCircuit): + """ LUT input driver class. LUT input drivers can optionally support register feedback. + They can also be connected to FF register input select. + Thus, there are 4 types of LUT input drivers: "default", "default_rsel", "reg_fb" and "reg_fb_rsel". + When a LUT input driver is created in the '__init__' function, it is given one of these types. + All subsequent processes (netlist generation, area calculations, etc.) will use this type attribute. + """ + + def __init__(self, name, type): + self.name = "lut_" + name + "_driver" + # LUT input driver type ("default", "default_rsel", "reg_fb" and "reg_fb_rsel") + self.type = type + # Delay weight in a representative critical path + self.delay_weight = 0.031 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + """ Generate SPICE netlist based on type of LUT input driver. """ + + self.transistor_names, self.wire_names = lut_subcircuits.generate_ptran_lut_driver(subcircuit_filename, self.name, self.type) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + if self.type != "default": + self.initial_transistor_sizes["inv_" + self.name + "_0_nmos"] = 2 + self.initial_transistor_sizes["inv_" + self.name + "_0_pmos"] = 2 + if self.type == "reg_fb" or self.type == "reg_fb_rsel": + self.initial_transistor_sizes["ptran_" + self.name + "_0_nmos"] = 2 + self.initial_transistor_sizes["rest_" + self.name + "_pmos"] = 1 + if self.type != "default": + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_2_nmos"] = 2 + self.initial_transistor_sizes["inv_" + self.name + "_2_pmos"] = 2 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + """ Generate top-level SPICE file based on type of LUT input driver. """ + + # Generate top level files based on what type of driver this is. + self.top_spice_path = top_level.generate_lut_driver_top(self.name, self.type) + # And, generate the LUT driver + LUT path top level file. We use this file to measure total delay through the LUT. + top_level.generate_lut_and_driver_top(self.name, self.type) + + + def update_area(self, area_dict, width_dict): + """ Update area. To do this, we use area_dict which is a dictionary, maintained externally, that contains + the area of everything. It is expected that area_dict will have all the information we need to calculate area. + We update area_dict and width_dict with calculations performed in this function. + We also return the area of this driver, which is calculated based on driver type. """ + + area = 0.0 + + # Calculate area based on input type + if self.type != "default": + area += area_dict["inv_" + self.name + "_0"] + if self.type == "reg_fb" or self.type == "reg_fb_rsel": + area += 2*area_dict["ptran_" + self.name + "_0"] + area += area_dict["rest_" + self.name] + if self.type != "default": + area += area_dict["inv_" + self.name + "_1"] + area += area_dict["inv_" + self.name + "_2"] + + # Add SRAM cell if this is a register feedback input + if self.type == "reg_fb" or self.type == "ref_fb_rsel": + area += area_dict["sram"] + + # Calculate layout width + width = math.sqrt(area) + + # Add to dictionaries + area_dict[self.name] = area + width_dict[self.name] = width + + return area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. + Wires differ based on input type. """ + + # Update wire lengths and wire layers + if self.type == "default_rsel" or self.type == "reg_fb_rsel": + wire_lengths["wire_" + self.name + "_0_rsel"] = width_dict[self.name]/4 + width_dict["lut"] + width_dict["ff"]/4 + wire_layers["wire_" + self.name + "_0_rsel"] = 0 + if self.type == "default_rsel": + wire_lengths["wire_" + self.name + "_0_out"] = width_dict["inv_" + self.name + "_0"]/4 + width_dict["inv_" + self.name + "_2"]/4 + wire_layers["wire_" + self.name + "_0_out"] = 0 + if self.type == "reg_fb" or self.type == "reg_fb_rsel": + wire_lengths["wire_" + self.name + "_0_out"] = width_dict["inv_" + self.name + "_0"]/4 + width_dict["ptran_" + self.name + "_0"]/4 + wire_layers["wire_" + self.name + "_0_out"] = 0 + wire_lengths["wire_" + self.name + "_0"] = width_dict["ptran_" + self.name + "_0"] + wire_layers["wire_" + self.name + "_0"] = 0 + if self.type == "default": + wire_lengths["wire_" + self.name] = width_dict["local_mux"]/4 + width_dict["inv_" + self.name + "_2"]/4 + wire_layers["wire_" + self.name] = 0 + else: + wire_lengths["wire_" + self.name] = width_dict["inv_" + self.name + "_1"]/4 + width_dict["inv_" + self.name + "_2"]/4 + wire_layers["wire_" + self.name] = 0 + + +class _LUTInputNotDriver(_SizableCircuit): + """ LUT input not-driver. This is the complement driver. """ + + def __init__(self, name, type): + self.name = "lut_" + name + "_driver_not" + # LUT input driver type ("default", "default_rsel", "reg_fb" and "reg_fb_rsel") + self.type = type + # Delay weight in a representative critical path + self.delay_weight = 0.031 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + """ Generate not-driver SPICE netlist """ + + self.transistor_names, self.wire_names = lut_subcircuits.generate_ptran_lut_not_driver(subcircuit_filename, self.name) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_2_nmos"] = 2 + self.initial_transistor_sizes["inv_" + self.name + "_2_pmos"] = 2 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + """ Generate top-level SPICE file for LUT not driver """ + + self.top_spice_path = top_level.generate_lut_driver_not_top(self.name, self.type) + + + def update_area(self, area_dict, width_dict): + """ Update area. To do this, we use area_dict which is a dictionary, maintained externally, that contains + the area of everything. It is expected that area_dict will have all the information we need to calculate area. + We update area_dict and width_dict with calculations performed in this function. + We also return the area of this not_driver.""" + + area = (area_dict["inv_" + self.name + "_1"] + + area_dict["inv_" + self.name + "_2"]) + width = math.sqrt(area) + area_dict[self.name] = area + width_dict[self.name] = width + + return area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_" + self.name] = (width_dict["inv_" + self.name + "_1"] + width_dict["inv_" + self.name + "_2"])/4 + # Update wire layers + wire_layers["wire_" + self.name] = 0 + + +class _LUTInput(_CompoundCircuit): + """ LUT input. It contains a LUT input driver and a LUT input not driver (complement). """ + + def __init__(self, name, Rsel, Rfb): + # Subcircuit name (should be driver letter like a, b, c...) + self.name = name + # The type is either 'default': a normal input or 'reg_fb': a register feedback input + # In addition, the input can (optionally) drive the register select circuitry: 'default_rsel' or 'reg_fb_rsel' + # Therefore, there are 4 different types, which are controlled by Rsel and Rfb + if name in Rfb: + if Rsel == name: + self.type = "reg_fb_rsel" + else: + self.type = "reg_fb" + else: + if Rsel == name: + self.type = "default_rsel" + else: + self.type = "default" + # Create LUT input driver + self.driver = _LUTInputDriver(name, self.type) + # Create LUT input not driver + self.not_driver = _LUTInputNotDriver(name, self.type) + + # LUT input delays are the delays through the LUT for specific input (doesn't include input driver delay) + self.tfall = 1 + self.trise = 1 + self.delay = 1 + self.delay_weight = 1 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + """ Generate both driver and not-driver SPICE netlists. """ + + print "Generating lut " + self.name + "-input driver (" + self.type + ")" + + # Generate the driver + self.driver.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + # Generate the not driver + self.not_driver.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + + def generate_top(self): + """ Generate top-level SPICE file for driver and not-driver. """ + + print "Generating top-level lut " + self.name + "-input" + + # Generate the driver top + self.driver.generate_top() + # Generate the not driver top + self.not_driver.generate_top() + + + def update_area(self, area_dict, width_dict): + """ Update area. We update the area of the the driver and the not driver by calling area update functions + inside these objects. We also return the total area of this input driver.""" + + # Calculate area of driver + driver_area = self.driver.update_area(area_dict, width_dict) + # Calculate area of not driver + not_driver_area = self.not_driver.update_area(area_dict, width_dict) + # Return the sum + return driver_area + not_driver_area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers for input driver and not_driver """ + + # Update driver wires + self.driver.update_wires(width_dict, wire_lengths, wire_layers) + # Update not driver wires + self.not_driver.update_wires(width_dict, wire_lengths, wire_layers) + + + def print_details(self): + """ Print LUT input driver details """ + + print " LUT input " + self.name + " type: " + self.type + + +class _LUTInputDriverLoad: + """ LUT input driver load. This load consists of a wire as well as the gates + of a particular level in the LUT. """ + + def __init__(self, name): + self.name = name + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_lut_" + self.name + "_driver_load"] = width_dict["lut"] + + # Update set wire layers + wire_layers["wire_lut_" + self.name + "_driver_load"] = 0 + + + def generate(self, subcircuit_filename, K): + + print "Generating LUT " + self.name + "-input driver load" + + # Call generation function based on input + if self.name == "a": + self.wire_names = lut_subcircuits.generate_ptran_lut_driver_load(subcircuit_filename, self.name, K) + elif self.name == "b": + self.wire_names = lut_subcircuits.generate_ptran_lut_driver_load(subcircuit_filename, self.name, K) + elif self.name == "c": + self.wire_names = lut_subcircuits.generate_ptran_lut_driver_load(subcircuit_filename, self.name, K) + elif self.name == "d": + self.wire_names = lut_subcircuits.generate_ptran_lut_driver_load(subcircuit_filename, self.name, K) + elif self.name == "e": + self.wire_names = lut_subcircuits.generate_ptran_lut_driver_load(subcircuit_filename, self.name, K) + elif self.name == "f": + self.wire_names = lut_subcircuits.generate_ptran_lut_driver_load(subcircuit_filename, self.name, K) + + + def print_details(self): + print "LUT input driver load details." + + +class _LUT(_SizableCircuit): + """ Lookup table. """ + + def __init__(self, K, Rsel, Rfb): + # Name of LUT + self.name = "lut" + # Size of LUT + self.K = K + # Register feedback parameter + self.Rfb = Rfb + # Dictionary of input drivers (keys: "a", "b", etc...) + self.input_drivers = {} + # Dictionary of input driver loads + self.input_driver_loads = {} + # Delay weight in a representative critical path + self.delay_weight = 0.1858 + + # Create a LUT input driver and load for each LUT input + for i in range(K): + name = chr(i+97) + self.input_drivers[name] = _LUTInput(name, Rsel, Rfb) + self.input_driver_loads[name] = _LUTInputDriverLoad(name) + + # Set delay weight, TODO: Need to find better way to do this. + self.input_drivers["a"].delay_weight = 0.0568 + self.input_drivers["b"].delay_weight = 0.0508 + self.input_drivers["c"].delay_weight = 0.0094 + self.input_drivers["d"].delay_weight = 0.0213 + if K >= 5: + self.input_drivers["e"].delay_weight = 0.0289 + if K >= 6: + self.input_drivers["f"].delay_weight = 0.0186 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + """ Generate LUT SPICE netlist based on LUT size. """ + + # Generate LUT differently based on K + if self.K == 6: + self._generate_6lut(subcircuit_filename, transistor_sizes_filename, min_tran_width) + elif self.K == 5: + self._generate_5lut(subcircuit_filename, transistor_sizes_filename, min_tran_width) + elif self.K == 4: + self._generate_4lut(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + + def generate_top(self): + print "Generating top-level lut" + if self.K == 6: + self.top_spice_path = top_level.generate_lut6_top(self.name) + elif self.K == 5: + self.top_spice_path = top_level.generate_lut5_top(self.name) + elif self.K == 4: + self.top_spice_path = top_level.generate_lut4_top(self.name) + + # Generate top-level driver files + for input_driver_name, input_driver in self.input_drivers.iteritems(): + input_driver.generate_top() + + + def update_area(self, area_dict, width_dict): + """ Update area. To do this, we use area_dict which is a dictionary, maintained externally, that contains + the area of everything. It is expected that area_dict will have all the information we need to calculate area. + We update area_dict and width_dict with calculations performed in this function. + We update the area of the LUT as well as the area of the LUT input drivers. """ + + area = 0.0 + + # Calculate area (differs with different values of K) + if self.K == 6: + area += (64*area_dict["inv_lut_0sram_driver_2"] + + 64*area_dict["ptran_lut_L1"] + + 32*area_dict["ptran_lut_L2"] + + 16*area_dict["ptran_lut_L3"] + + 8*area_dict["rest_lut_int_buffer"] + + 8*area_dict["inv_lut_int_buffer_1"] + + 8*area_dict["inv_lut_int_buffer_2"] + + 8*area_dict["ptran_lut_L4"] + + 4*area_dict["ptran_lut_L5"] + + 2*area_dict["ptran_lut_L6"] + + area_dict["rest_lut_out_buffer"] + + area_dict["inv_lut_out_buffer_1"] + + area_dict["inv_lut_out_buffer_2"] + + 64*area_dict["sram"]) + elif self.K == 5: + area += (32*area_dict["inv_lut_0sram_driver_2"] + + 32*area_dict["ptran_lut_L1"] + + 16*area_dict["ptran_lut_L2"] + + 8*area_dict["ptran_lut_L3"] + + 4*area_dict["rest_lut_int_buffer"] + + 4*area_dict["inv_lut_int_buffer_1"] + + 4*area_dict["inv_lut_int_buffer_2"] + + 4*area_dict["ptran_lut_L4"] + + 2*area_dict["ptran_lut_L5"] + + area_dict["rest_lut_out_buffer"] + + area_dict["inv_lut_out_buffer_1"] + + area_dict["inv_lut_out_buffer_2"] + + 32*area_dict["sram"]) + elif self.K == 4: + area += (16*area_dict["inv_lut_0sram_driver_2"] + + 16*area_dict["ptran_lut_L1"] + + 8*area_dict["ptran_lut_L2"] + + 4*area_dict["rest_lut_int_buffer"] + + 4*area_dict["inv_lut_int_buffer_1"] + + 4*area_dict["inv_lut_int_buffer_2"] + + 4*area_dict["ptran_lut_L3"] + + 2*area_dict["ptran_lut_L4"] + + area_dict["rest_lut_out_buffer"] + + area_dict["inv_lut_out_buffer_1"] + + area_dict["inv_lut_out_buffer_2"] + + 16*area_dict["sram"]) + + width = math.sqrt(area) + area_dict["lut"] = area + width_dict["lut"] = width + + # Calculate LUT driver areas + total_lut_area = 0.0 + for driver_name, input_driver in self.input_drivers.iteritems(): + driver_area = input_driver.update_area(area_dict, width_dict) + total_lut_area = total_lut_area + driver_area + + # Now we calculate total LUT area + total_lut_area = total_lut_area + area_dict["lut"] + + area_dict["lut_and_drivers"] = total_lut_area + width_dict["lut_and_drivers"] = math.sqrt(total_lut_area) + + return total_lut_area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + if self.K == 6: + # Update wire lengths + wire_lengths["wire_lut_sram_driver"] = (width_dict["inv_lut_0sram_driver_2"] + width_dict["inv_lut_0sram_driver_2"])/4 + wire_lengths["wire_lut_sram_driver_out"] = (width_dict["inv_lut_0sram_driver_2"] + width_dict["ptran_lut_L1"])/4 + wire_lengths["wire_lut_L1"] = width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L2"] = 2*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L3"] = 4*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_int_buffer"] = (width_dict["inv_lut_int_buffer_1"] + width_dict["inv_lut_int_buffer_2"])/4 + wire_lengths["wire_lut_int_buffer_out"] = (width_dict["inv_lut_int_buffer_2"] + width_dict["ptran_lut_L4"])/4 + wire_lengths["wire_lut_L4"] = 8*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L5"] = 16*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L6"] = 32*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_out_buffer"] = (width_dict["inv_lut_out_buffer_1"] + width_dict["inv_lut_out_buffer_2"])/4 + + # Update wire layers + wire_layers["wire_lut_sram_driver"] = 0 + wire_layers["wire_lut_sram_driver_out"] = 0 + wire_layers["wire_lut_L1"] = 0 + wire_layers["wire_lut_L2"] = 0 + wire_layers["wire_lut_L3"] = 0 + wire_layers["wire_lut_int_buffer"] = 0 + wire_layers["wire_lut_int_buffer_out"] = 0 + wire_layers["wire_lut_L4"] = 0 + wire_layers["wire_lut_L5"] = 0 + wire_layers["wire_lut_L6"] = 0 + wire_layers["wire_lut_out_buffer"] = 0 + + elif self.K == 5: + # Update wire lengths + wire_lengths["wire_lut_sram_driver"] = (width_dict["inv_lut_0sram_driver_2"] + width_dict["inv_lut_0sram_driver_2"])/4 + wire_lengths["wire_lut_sram_driver_out"] = (width_dict["inv_lut_0sram_driver_2"] + width_dict["ptran_lut_L1"])/4 + wire_lengths["wire_lut_L1"] = width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L2"] = 2*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L3"] = 4*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_int_buffer"] = (width_dict["inv_lut_int_buffer_1"] + width_dict["inv_lut_int_buffer_2"])/4 + wire_lengths["wire_lut_int_buffer_out"] = (width_dict["inv_lut_int_buffer_2"] + width_dict["ptran_lut_L4"])/4 + wire_lengths["wire_lut_L4"] = 8*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L5"] = 16*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_out_buffer"] = (width_dict["inv_lut_out_buffer_1"] + width_dict["inv_lut_out_buffer_2"])/4 + + # Update wire layers + wire_layers["wire_lut_sram_driver"] = 0 + wire_layers["wire_lut_sram_driver_out"] = 0 + wire_layers["wire_lut_L1"] = 0 + wire_layers["wire_lut_L2"] = 0 + wire_layers["wire_lut_L3"] = 0 + wire_layers["wire_lut_int_buffer"] = 0 + wire_layers["wire_lut_int_buffer_out"] = 0 + wire_layers["wire_lut_L4"] = 0 + wire_layers["wire_lut_L5"] = 0 + wire_layers["wire_lut_out_buffer"] = 0 + + elif self.K == 4: + # Update wire lengths + wire_lengths["wire_lut_sram_driver"] = (width_dict["inv_lut_0sram_driver_2"] + width_dict["inv_lut_0sram_driver_2"])/4 + wire_lengths["wire_lut_sram_driver_out"] = (width_dict["inv_lut_0sram_driver_2"] + width_dict["ptran_lut_L1"])/4 + wire_lengths["wire_lut_L1"] = width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L2"] = 2*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_int_buffer"] = (width_dict["inv_lut_int_buffer_1"] + width_dict["inv_lut_int_buffer_2"])/4 + wire_lengths["wire_lut_int_buffer_out"] = (width_dict["inv_lut_int_buffer_2"] + width_dict["ptran_lut_L4"])/4 + wire_lengths["wire_lut_L3"] = 4*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_L4"] = 8*width_dict["ptran_lut_L1"] + wire_lengths["wire_lut_out_buffer"] = (width_dict["inv_lut_out_buffer_1"] + width_dict["inv_lut_out_buffer_2"])/4 + + # Update wire layers + wire_layers["wire_lut_sram_driver"] = 0 + wire_layers["wire_lut_sram_driver_out"] = 0 + wire_layers["wire_lut_L1"] = 0 + wire_layers["wire_lut_L2"] = 0 + wire_layers["wire_lut_int_buffer"] = 0 + wire_layers["wire_lut_int_buffer_out"] = 0 + wire_layers["wire_lut_L3"] = 0 + wire_layers["wire_lut_L4"] = 0 + wire_layers["wire_lut_out_buffer"] = 0 + + + # Update input driver wires + for driver_name, input_driver in self.input_drivers.iteritems(): + input_driver.update_wires(width_dict, wire_lengths, wire_layers) + + # Update input driver load wires + for driver_load_name, input_driver_load in self.input_driver_loads.iteritems(): + input_driver_load.update_wires(width_dict, wire_lengths, wire_layers) + + + def print_details(self): + """ Print LUT details """ + + if self.K == 6: + print " LUT DETAILS:" + print " Style: Fully encoded MUX tree" + print " Size: 6-LUT" + print " Internal buffering: 2-stage buffer betweens levels 3 and 4" + print " Isolation inverters between SRAM and LUT inputs" + print "" + print " LUT INPUT DRIVER DETAILS:" + for driver_name, input_driver in self.input_drivers.iteritems(): + input_driver.print_details() + print "" + elif self.K == 5: + print " LUT DETAILS:" + print " Style: Fully encoded MUX tree" + print " Size: 5-LUT" + print " Internal buffering: 2-stage buffer betweens levels 3 and 4" + print " Isolation inverters between SRAM and LUT inputs" + print "" + print " LUT INPUT DRIVER DETAILS:" + for driver_name, input_driver in self.input_drivers.iteritems(): + input_driver.print_details() + print "" + elif self.K == 4: + print " LUT DETAILS:" + print " Style: Fully encoded MUX tree" + print " Size: 4-LUT" + print " Internal buffering: 2-stage buffer betweens levels 2 and 3" + print " Isolation inverters between SRAM and LUT inputs" + print "" + print " LUT INPUT DRIVER DETAILS:" + for driver_name, input_driver in self.input_drivers.iteritems(): + input_driver.print_details() + print "" + + + def _generate_6lut(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating 6-LUT" + + # Call the generation function + self.transistor_names, self.wire_names = lut_subcircuits.generate_ptran_lut6(subcircuit_filename, min_tran_width) + + # Give initial transistor sizes + self.initial_transistor_sizes["inv_lut_0sram_driver_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_0sram_driver_2_pmos"] = 6 + self.initial_transistor_sizes["ptran_lut_L1_nmos"] = 2 + self.initial_transistor_sizes["ptran_lut_L2_nmos"] = 2 + self.initial_transistor_sizes["ptran_lut_L3_nmos"] = 2 + self.initial_transistor_sizes["rest_lut_int_buffer_pmos"] = 1 + self.initial_transistor_sizes["inv_lut_int_buffer_1_nmos"] = 2 + self.initial_transistor_sizes["inv_lut_int_buffer_1_pmos"] = 2 + self.initial_transistor_sizes["inv_lut_int_buffer_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_int_buffer_2_pmos"] = 6 + self.initial_transistor_sizes["ptran_lut_L4_nmos"] = 3 + self.initial_transistor_sizes["ptran_lut_L5_nmos"] = 3 + self.initial_transistor_sizes["ptran_lut_L6_nmos"] = 3 + self.initial_transistor_sizes["rest_lut_out_buffer_pmos"] = 1 + self.initial_transistor_sizes["inv_lut_out_buffer_1_nmos"] = 2 + self.initial_transistor_sizes["inv_lut_out_buffer_1_pmos"] = 2 + self.initial_transistor_sizes["inv_lut_out_buffer_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_out_buffer_2_pmos"] = 6 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + # Generate input drivers (with register feedback if input is in Rfb) + self.input_drivers["a"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["b"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["c"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["d"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["e"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["f"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + # Generate input driver loads + self.input_driver_loads["a"].generate(subcircuit_filename, self.K) + self.input_driver_loads["b"].generate(subcircuit_filename, self.K) + self.input_driver_loads["c"].generate(subcircuit_filename, self.K) + self.input_driver_loads["d"].generate(subcircuit_filename, self.K) + self.input_driver_loads["e"].generate(subcircuit_filename, self.K) + self.input_driver_loads["f"].generate(subcircuit_filename, self.K) + + + def _generate_5lut(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating 5-LUT" + + # Call the generation function + self.transistor_names, self.wire_names = lut_subcircuits.generate_ptran_lut5(subcircuit_filename, min_tran_width) + + # Give initial transistor sizes + self.initial_transistor_sizes["inv_lut_0sram_driver_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_0sram_driver_2_pmos"] = 6 + self.initial_transistor_sizes["ptran_lut_L1_nmos"] = 2 + self.initial_transistor_sizes["ptran_lut_L2_nmos"] = 2 + self.initial_transistor_sizes["ptran_lut_L3_nmos"] = 2 + self.initial_transistor_sizes["rest_lut_int_buffer_pmos"] = 1 + self.initial_transistor_sizes["inv_lut_int_buffer_1_nmos"] = 2 + self.initial_transistor_sizes["inv_lut_int_buffer_1_pmos"] = 2 + self.initial_transistor_sizes["inv_lut_int_buffer_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_int_buffer_2_pmos"] = 6 + self.initial_transistor_sizes["ptran_lut_L4_nmos"] = 3 + self.initial_transistor_sizes["ptran_lut_L5_nmos"] = 3 + self.initial_transistor_sizes["rest_lut_out_buffer_pmos"] = 1 + self.initial_transistor_sizes["inv_lut_out_buffer_1_nmos"] = 2 + self.initial_transistor_sizes["inv_lut_out_buffer_1_pmos"] = 2 + self.initial_transistor_sizes["inv_lut_out_buffer_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_out_buffer_2_pmos"] = 6 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + # Generate input drivers (with register feedback if input is in Rfb) + self.input_drivers["a"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["b"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["c"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["d"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["e"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + # Generate input driver loads + self.input_driver_loads["a"].generate(subcircuit_filename, self.K) + self.input_driver_loads["b"].generate(subcircuit_filename, self.K) + self.input_driver_loads["c"].generate(subcircuit_filename, self.K) + self.input_driver_loads["d"].generate(subcircuit_filename, self.K) + self.input_driver_loads["e"].generate(subcircuit_filename, self.K) + + + def _generate_4lut(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating 4-LUT" + + # Call the generation function + self.transistor_names, self.wire_names = lut_subcircuits.generate_ptran_lut4(subcircuit_filename, min_tran_width) + + # Give initial transistor sizes + self.initial_transistor_sizes["inv_lut_0sram_driver_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_0sram_driver_2_pmos"] = 6 + self.initial_transistor_sizes["ptran_lut_L1_nmos"] = 2 + self.initial_transistor_sizes["ptran_lut_L2_nmos"] = 2 + self.initial_transistor_sizes["rest_lut_int_buffer_pmos"] = 1 + self.initial_transistor_sizes["inv_lut_int_buffer_1_nmos"] = 2 + self.initial_transistor_sizes["inv_lut_int_buffer_1_pmos"] = 2 + self.initial_transistor_sizes["inv_lut_int_buffer_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_int_buffer_2_pmos"] = 6 + self.initial_transistor_sizes["ptran_lut_L3_nmos"] = 2 + self.initial_transistor_sizes["ptran_lut_L4_nmos"] = 3 + self.initial_transistor_sizes["rest_lut_out_buffer_pmos"] = 1 + self.initial_transistor_sizes["inv_lut_out_buffer_1_nmos"] = 2 + self.initial_transistor_sizes["inv_lut_out_buffer_1_pmos"] = 2 + self.initial_transistor_sizes["inv_lut_out_buffer_2_nmos"] = 4 + self.initial_transistor_sizes["inv_lut_out_buffer_2_pmos"] = 6 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + # Generate input drivers (with register feedback if input is in Rfb) + self.input_drivers["a"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["b"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["c"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.input_drivers["d"].generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + # Generate input driver loads + self.input_driver_loads["a"].generate(subcircuit_filename, self.K) + self.input_driver_loads["b"].generate(subcircuit_filename, self.K) + self.input_driver_loads["c"].generate(subcircuit_filename, self.K) + self.input_driver_loads["d"].generate(subcircuit_filename, self.K) + + +class _FlipFlop: + """ FlipFlop class. + COFFE does not do transistor sizing for the flip flop. Therefore, the FF is not a SizableCircuit. + Regardless of that, COFFE has a FlipFlop object that is used to obtain FF area and delay. + COFFE creates a SPICE netlist for the FF. The 'initial_transistor_sizes', defined below, are + used when COFFE measures T_setup and T_clock_to_Q. Those transistor sizes were obtained + through manual design for PTM 22nm process technology. If you use a different process technology, + you may need to re-size the FF transistors. """ + + def __init__(self, Rsel): + # Flip-Flop name + self.name = "ff" + # Register select mux, Rsel = LUT input (e.g. 'a', 'b', etc.) or 'z' if no register select + self.register_select = Rsel + # A list of the names of transistors in this subcircuit. + self.transistor_names = [] + # A list of the names of wires in this subcircuit + self.wire_names = [] + # A dictionary of the initial transistor sizes + self.initial_transistor_sizes = {} + # Path to the top level spice file + self.top_spice_path = "" + # + self.t_setup = 1 + # + self.t_clk_to_q = 1 + # Delay weight used to calculate delay of representative critical path + self.delay_weight = 1 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + """ Generate FF SPICE netlists. Optionally includes register select. """ + + # Generate FF with optional register select + if self.register_select == 'z': + print "Generating FF" + self.transistor_names, self.wire_names = ff_subcircuits.generate_ptran_d_ff(subcircuit_filename) + else: + print "Generating FF with register select on BLE input " + self.register_select + self.transistor_names, self.wire_names = ff_subcircuits.generate_ptran_2_input_select_d_ff(subcircuit_filename) + + # Give initial transistor sizes + if self.register_select: + # These only exist if there is a register select MUX + self.initial_transistor_sizes["ptran_ff_input_select_nmos"] = 4 + self.initial_transistor_sizes["rest_ff_input_select_pmos"] = 1 + + # These transistors always exists regardless of register select + self.initial_transistor_sizes["inv_ff_input_1_nmos"] = 3 + self.initial_transistor_sizes["inv_ff_input_1_pmos"] = 8.2 + self.initial_transistor_sizes["tgate_ff_1_nmos"] = 1 + self.initial_transistor_sizes["tgate_ff_1_pmos"] = 1 + self.initial_transistor_sizes["tran_ff_set_n_pmos"] = 1 + self.initial_transistor_sizes["tran_ff_reset_nmos"] = 1 + self.initial_transistor_sizes["inv_ff_cc1_1_nmos"] = 3 + self.initial_transistor_sizes["inv_ff_cc1_1_pmos"] = 4 + self.initial_transistor_sizes["inv_ff_cc1_2_nmos"] = 1 + self.initial_transistor_sizes["inv_ff_cc1_2_pmos"] = 1.3 + self.initial_transistor_sizes["tgate_ff_2_nmos"] = 1 + self.initial_transistor_sizes["tgate_ff_2_pmos"] = 1 + self.initial_transistor_sizes["tran_ff_reset_n_pmos"] = 1 + self.initial_transistor_sizes["tran_ff_set_nmos"] = 1 + self.initial_transistor_sizes["inv_ff_cc2_1_nmos"] = 1 + self.initial_transistor_sizes["inv_ff_cc2_1_pmos"] = 1.3 + self.initial_transistor_sizes["inv_ff_cc2_2_nmos"] = 1 + self.initial_transistor_sizes["inv_ff_cc2_2_pmos"] = 1.3 + self.initial_transistor_sizes["inv_ff_output_driver_nmos"] = 4 + self.initial_transistor_sizes["inv_ff_output_driver_pmos"] = 9.7 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + """ """ + # TODO for T_setup and T_clock_to_Q + pass + + + def update_area(self, area_dict, width_dict): + """ Calculate FF area and update dictionaries. """ + + area = 0.0 + + # Calculates area of the FF input select if applicable (we add the SRAM bit later) + # If there is no input select, we just add the area of the input inverter + if self.register_select != 'z': + area += (2*area_dict["ptran_ff_input_select"] + + area_dict["rest_ff_input_select"] + + area_dict["inv_ff_input_1"]) + else: + area += area_dict["inv_ff_input_1"] + + # Add area of FF circuitry + area += (area_dict["tgate_ff_1"] + + area_dict["tran_ff_set_n"] + + area_dict["tran_ff_reset"] + + area_dict["inv_ff_cc1_1"] + + area_dict["inv_ff_cc1_2"] + + area_dict["tgate_ff_2"] + + area_dict["tran_ff_reset_n"] + + area_dict["tran_ff_set"] + + area_dict["inv_ff_cc2_1"] + + area_dict["inv_ff_cc2_2"]+ + area_dict["inv_ff_output_driver"]) + + # Add the SRAM bit if FF input select is on + if self.register_select != 'z': + area += area_dict["sram"] + + # Calculate width and add to dictionaries + width = math.sqrt(area) + area_dict["ff"] = area + width_dict["ff"] = width + + return area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + if self.register_select != 'z': + wire_lengths["wire_ff_input_select"] = width_dict["ptran_ff_input_select"] + + wire_lengths["wire_ff_input_out"] = (width_dict["inv_ff_input_1"] + width_dict["tgate_ff_1"])/4 + wire_lengths["wire_ff_tgate_1_out"] = (width_dict["tgate_ff_1"] + width_dict["inv_ff_cc1_1"])/4 + wire_lengths["wire_ff_cc1_out"] = (width_dict["inv_ff_cc1_1"] + width_dict["tgate_ff_2"])/4 + wire_lengths["wire_ff_tgate_2_out"] = (width_dict["tgate_ff_2"] + width_dict["inv_ff_cc1_2"])/4 + wire_lengths["wire_ff_cc2_out"] = (width_dict["inv_ff_cc1_2"] + width_dict["inv_ff_output_driver"])/4 + + # Update wire layers + if self.register_select != 'z': + wire_layers["wire_ff_input_select"] = 0 + + wire_layers["wire_ff_input_out"] = 0 + wire_layers["wire_ff_tgate_1_out"] = 0 + wire_layers["wire_ff_cc1_out"] = 0 + wire_layers["wire_ff_tgate_2_out"] = 0 + wire_layers["wire_ff_cc2_out"] = 0 + + + def print_details(self): + print " FF DETAILS:" + if self.register_select == 'z': + print " Register select: None" + else: + print " Register select: BLE input " + self.register_select + + +class _LocalBLEOutput(_SizableCircuit): + """ Local BLE Output class """ + + def __init__(self): + self.name = "local_ble_output" + # Delay weight in a representative critical path + self.delay_weight = 0.0928 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating local BLE output" + self.transistor_names, self.wire_names = mux_subcircuits.generate_ptran_2_to_1_mux(subcircuit_filename, self.name) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + self.initial_transistor_sizes["ptran_" + self.name + "_nmos"] = 2 + self.initial_transistor_sizes["rest_" + self.name + "_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_2_nmos"] = 4 + self.initial_transistor_sizes["inv_" + self.name + "_2_pmos"] = 4 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + print "Generating top-level " + self.name + self.top_spice_path = top_level.generate_local_ble_output_top(self.name) + + + def update_area(self, area_dict, width_dict): + area = (2*area_dict["ptran_" + self.name] + + area_dict["rest_" + self.name] + + area_dict["inv_" + self.name + "_1"] + + area_dict["inv_" + self.name + "_2"]) + area = area + area_dict["sram"] + width = math.sqrt(area) + area_dict[self.name] = area + width_dict[self.name] = width + + return area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_" + self.name] = width_dict["ptran_" + self.name] + wire_lengths["wire_" + self.name + "_driver"] = (width_dict["inv_" + self.name + "_1"] + width_dict["inv_" + self.name + "_1"])/4 + + # Update wire layers + wire_layers["wire_" + self.name] = 0 + wire_layers["wire_" + self.name + "_driver"] = 0 + + + def print_details(self): + print "Local BLE output details." + + +class _GeneralBLEOutput(_SizableCircuit): + """ General BLE Output """ + + def __init__(self): + self.name = "general_ble_output" + self.delay_weight = 0.0502 + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating general BLE output" + self.transistor_names, self.wire_names = mux_subcircuits.generate_ptran_2_to_1_mux(subcircuit_filename, self.name) + + # Initialize transistor sizes (to something more reasonable than all min size, but not necessarily a good choice, depends on architecture params) + self.initial_transistor_sizes["ptran_" + self.name + "_nmos"] = 2 + self.initial_transistor_sizes["rest_" + self.name + "_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_nmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_1_pmos"] = 1 + self.initial_transistor_sizes["inv_" + self.name + "_2_nmos"] = 5 + self.initial_transistor_sizes["inv_" + self.name + "_2_pmos"] = 5 + + # Add to transistor sizes file + _add_transistor_to_file(transistor_sizes_filename, self.name, min_tran_width, self.initial_transistor_sizes) + + + def generate_top(self): + print "Generating top-level " + self.name + self.top_spice_path = top_level.generate_general_ble_output_top(self.name) + + + def update_area(self, area_dict, width_dict): + area = (2*area_dict["ptran_" + self.name] + + area_dict["rest_" + self.name] + + area_dict["inv_" + self.name + "_1"] + + area_dict["inv_" + self.name + "_2"]) + area = area + area_dict["sram"] + width = math.sqrt(area) + area_dict[self.name] = area + width_dict[self.name] = width + + return area + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_" + self.name] = width_dict["ptran_" + self.name] + wire_lengths["wire_" + self.name + "_driver"] = (width_dict["inv_" + self.name + "_1"] + width_dict["inv_" + self.name + "_1"])/4 + + # Update wire layers + wire_layers["wire_" + self.name] = 0 + wire_layers["wire_" + self.name + "_driver"] = 0 + + + def print_details(self): + print "General BLE output details." + + +class _LUTOutputLoad: + + def __init__(self, num_local_outputs, num_general_outputs): + self.name = "lut_output_load" + self.num_local_outputs = num_local_outputs + self.num_general_outputs = num_general_outputs + self.wire_names = [] + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating LUT output load" + self.wire_names = load_subcircuits.generate_lut_output_load(subcircuit_filename, self.num_local_outputs, self.num_general_outputs) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_lut_output_load_1"] = (width_dict["ff"] + width_dict["lut_and_drivers"])/8 + wire_lengths["wire_lut_output_load_2"] = width_dict["ff"] + + # Update wire layers + wire_layers["wire_lut_output_load_1"] = 0 + wire_layers["wire_lut_output_load_2"] = 0 + + +class _BLE(_CompoundCircuit): + + def __init__(self, K, Or, Ofb, Rsel, Rfb): + # BLE name + self.name = "ble" + # Size of LUT + self.K = K + # Number of inputs to the BLE + self.num_inputs = K + # Number of local outputs + self.num_local_outputs = Ofb + # Number of general outputs + self.num_general_outputs = Or + # Create BLE local output object + self.local_output = _LocalBLEOutput() + # Create BLE general output object + self.general_output = _GeneralBLEOutput() + # Create LUT object + self.lut = _LUT(K, Rsel, Rfb) + # Create FF object + self.ff = _FlipFlop(Rsel) + # Create LUT output load object + self.lut_output_load = _LUTOutputLoad(self.num_local_outputs, self.num_general_outputs) + + + def generate(self, subcircuit_filename, transistor_sizes_filename, min_tran_width): + print "Generating BLE" + + # Generate LUT and FF + self.lut.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.ff.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + # Generate BLE outputs + self.local_output.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + self.general_output.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + load_subcircuits.generate_ble_outputs(subcircuit_filename, self.num_local_outputs, self.num_general_outputs) + + # Generate LUT load + self.lut_output_load.generate(subcircuit_filename, transistor_sizes_filename, min_tran_width) + + + def generate_top(self): + self.lut.generate_top() + self.local_output.generate_top() + self.general_output.generate_top() + + + def update_area(self, area_dict, width_dict): + + lut_area = self.lut.update_area(area_dict, width_dict) + ff_area = self.ff.update_area(area_dict, width_dict) + + # Calculate area of BLE outputs + local_ble_output_area = self.num_local_outputs*self.local_output.update_area(area_dict, width_dict) + general_ble_output_area = self.num_general_outputs*self.general_output.update_area(area_dict, width_dict) + + ble_output_area = local_ble_output_area + general_ble_output_area + ble_output_width = math.sqrt(ble_output_area) + area_dict["ble_output"] = ble_output_area + width_dict["ble_output"] = ble_output_width + + ble_area = lut_area + ff_area + ble_output_area + ble_width = math.sqrt(ble_area) + area_dict["ble"] = ble_area + width_dict["ble"] = ble_width + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire of member objects. """ + + # Update lut and ff wires. + self.lut.update_wires(width_dict, wire_lengths, wire_layers) + self.ff.update_wires(width_dict, wire_lengths, wire_layers) + + # Update BLE output wires + self.local_output.update_wires(width_dict, wire_lengths, wire_layers) + self.general_output.update_wires(width_dict, wire_lengths, wire_layers) + + # Wire connecting all BLE output mux-inputs together + wire_lengths["wire_ble_outputs"] = self.num_local_outputs*width_dict["local_ble_output"] + self.num_general_outputs*width_dict["general_ble_output"] + wire_layers["wire_ble_outputs"] = 0 + + # Update LUT load wires + self.lut_output_load.update_wires(width_dict, wire_lengths, wire_layers) + + + def print_details(self): + + self.lut.print_details() + + +class _LocalBLEOutputLoad: + + def __init__(self): + self.name = "local_ble_output_load" + + + def generate(self, subcircuit_filename): + load_subcircuits.generate_local_ble_output_load(subcircuit_filename) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_local_ble_output_feedback"] = width_dict["logic_cluster"] + + # Update wire layers + wire_layers["wire_local_ble_output_feedback"] = 0 + + +class _GeneralBLEOutputLoad: + """ Logic cluster output load (i.e. general BLE output load). + Made up of a wire loaded by SB muxes. """ + + def __init__(self): + # Subcircuit name + self.name = "general_ble_output_load" + # Assumed routing channel usage, we need this for load calculation + self.channel_usage_assumption = 0.5 + # Assumed number of 'on' SB muxes on cluster output, needed for load calculation + self.num_sb_mux_on_assumption = 1 + # Number of 'partially on' SB muxes on cluster output (calculated in compute_load) + self.num_sb_mux_partial = -1 + # Number of 'off' SB muxes on cluster output (calculated in compute_load) + self.num_sb_mux_off = -1 + # List of wires in this subcircuit + self.wire_names = [] + + + def generate(self, subcircuit_filename, specs, sb_mux): + """ Compute cluster output load load and generate SPICE netlist. """ + + self._compute_load(specs, sb_mux, self.channel_usage_assumption, self.num_sb_mux_on_assumption) + self.wire_names = load_subcircuits.generate_general_ble_output_load(subcircuit_filename, self.num_sb_mux_off, self.num_sb_mux_partial, self.num_sb_mux_on_assumption) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_general_ble_output"] = width_dict["tile"]/4 + + # Update wire layers + wire_layers["wire_general_ble_output"] = 0 + + + def print_details(self): + """ Print cluster output load details """ + + print " CLUSTER OUTPUT LOAD DETAILS" + print " Total number of SB inputs connected to cluster output: " + str(self.num_sb_mux_off + self.num_sb_mux_partial + self.num_sb_mux_on_assumption) + print " Number of 'on' SB MUXes (assumed): " + str(self.num_sb_mux_on_assumption) + print " Number of 'partial' SB MUXes: " + str(self.num_sb_mux_partial) + print " Number of 'off' SB MUXes: " + str(self.num_sb_mux_off) + print "" + + + def _compute_load(self, specs, sb_mux, channel_usage, sb_mux_on): + """ Calculate how many on/partial/off switch block multiplexers are connected to each cluster output. + Inputs are FPGA specs object, switch block mux object, assumed channel usage and assumed number of on muxes. + The function will update the object's off & partial attributes.""" + + # Size of second level of switch block mux, need this to figure out how many partially on muxes are connected + sb_level2_size = sb_mux.level2_size + + # Total number of switch block multiplexers connected to cluster output + total_load = int(specs.Fcout*specs.W) + + # Let's calculate how many partially on muxes are connected to each output + # Based on our channel usage assumption, we can determine how many muxes are in use in a tile. + # The number of used SB muxes equals the number of SB muxes per tile multiplied by the channel usage. + used_sb_muxes_per_tile = int(channel_usage*sb_mux.num_per_tile) + + # Each one of these used muxes comes with a certain amount of partially on paths. + # We calculate this based on the size of the 2nd muxing level of the switch block muxes + total_partial_paths = used_sb_muxes_per_tile*(sb_level2_size-1) + + # The partially on paths are connected to both routing wires and cluster outputs + # We assume that they are distributed evenly across both, which means we need to use the + # ratio of sb_mux inputs coming from routing wires and coming from cluster outputs to determine + # how many partially on paths would be connected to cluster outputs + sb_inputs_from_cluster_outputs = total_load*specs.num_cluster_outputs + # We use the required size here because we assume that extra inputs that may be present in the "implemented" mux + # might be connected to GND or VDD and not to routing wires + sb_inputs_from_routing = sb_mux.required_size*sb_mux.num_per_tile - sb_inputs_from_cluster_outputs + frac_partial_paths_on_cluster_out = float(sb_inputs_from_cluster_outputs)/(sb_inputs_from_cluster_outputs+sb_inputs_from_routing) + # The total number of partial paths on the cluster outputs is calculated using that fraction + total_cluster_output_partial_paths = int(frac_partial_paths_on_cluster_out*total_partial_paths) + # And we divide by the number of cluster outputs to get partial paths per output + cluster_output_partial_paths = int(math.ceil(float(total_cluster_output_partial_paths)/specs.num_cluster_outputs)) + + # Now assign these numbers to the object + self.num_sb_mux_partial = cluster_output_partial_paths + self.num_sb_mux_off = total_load - self.num_sb_mux_partial - sb_mux_on + + +class _LocalRoutingWireLoad: + """ Local routing wire load """ + + def __init__(self): + # Name of this wire + self.name = "local_routing_wire_load" + # How many LUT inputs are we assuming are used in this logic cluster? (%) + self.lut_input_usage_assumption = 0.85 + # Total number of local mux inputs per wire + self.mux_inputs_per_wire = -1 + # Number of on inputs connected to each wire + self.on_inputs_per_wire = -1 + # Number of partially on inputs connected to each wire + self.partial_inputs_per_wire = -1 + #Number of off inputs connected to each wire + self.off_inputs_per_wire = -1 + # List of wire names in the SPICE circuit + self.wire_names = [] + + + def generate(self, subcircuit_filename, specs, local_mux): + print "Generating local routing wire load" + # Compute load (number of on/partial/off per wire) + self._compute_load(specs, local_mux) + # Generate SPICE deck + self.wire_names = load_subcircuits.local_routing_load_generate(subcircuit_filename, self.on_inputs_per_wire, self.partial_inputs_per_wire, self.off_inputs_per_wire) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wire lengths and wire layers based on the width of things, obtained from width_dict. """ + + # Update wire lengths + wire_lengths["wire_local_routing"] = width_dict["logic_cluster"] + + # Update wire layers + wire_layers["wire_local_routing"] = 0 + + + def print_details(self): + print "LOCAL ROUTING WIRE LOAD DETAILS" + print "" + + + def _compute_load(self, specs, local_mux): + """ Compute the load on a local routing wire (number of on/partial/off) """ + + # The first thing we are going to compute is how many local mux inputs are connected to a local routing wire + # This is a function of local_mux size, N, K, I and Ofb + num_local_routing_wires = specs.I+specs.N*specs.num_ble_local_outputs + self.mux_inputs_per_wire = local_mux.implemented_size*specs.N*specs.K/num_local_routing_wires + + # Now we compute how many "on" inputs are connected to each routing wire + # This is a funtion of lut input usage, number of lut inputs and number of local routing wires + num_local_muxes_used = self.lut_input_usage_assumption*specs.N*specs.K + self.on_inputs_per_wire = int(num_local_muxes_used/num_local_routing_wires) + # We want to model for the case where at least one "on" input is connected to the local wire, so make sure it's at least 1 + if self.on_inputs_per_wire < 1: + self.on_inputs_per_wire = 1 + + # Now we compute how many partially on muxes are connected to each wire + # The number of partially on muxes is equal to (level2_size - 1)*num_local_muxes_used/num_local_routing_wire + # We can figure out the number of muxes used by using the "on" assumption and the number of local routing wires. + self.partial_inputs_per_wire = int((local_mux.level2_size - 1.0)*num_local_muxes_used/num_local_routing_wires) + # Make it at least 1 + if self.partial_inputs_per_wire < 1: + self.partial_inputs_per_wire = 1 + + # Number of off inputs is simply the difference + self.off_inputs_per_wire = self.mux_inputs_per_wire - self.on_inputs_per_wire - self.partial_inputs_per_wire + + +class _LogicCluster(_CompoundCircuit): + + def __init__(self, N, K, Or, Ofb, Rsel, Rfb, local_mux_size_required, num_local_mux_per_tile): + # Name of logic cluster + self.name = "logic_cluster" + # Cluster size + self.N = N + # Create BLE object + self.ble = _BLE(K, Or, Ofb, Rsel, Rfb) + # Create local mux object + self.local_mux = _LocalMUX(local_mux_size_required, num_local_mux_per_tile) + # Create local routing wire load object + self.local_routing_wire_load = _LocalRoutingWireLoad() + # Create local BLE output load object + self.local_ble_output_load = _LocalBLEOutputLoad() + + + def generate(self, subcircuits_filename, transistor_sizes_filename, min_tran_width, specs): + print "Generating logic cluster" + self.ble.generate(subcircuits_filename, transistor_sizes_filename, min_tran_width) + self.local_mux.generate(subcircuits_filename, transistor_sizes_filename, min_tran_width) + self.local_routing_wire_load.generate(subcircuits_filename, specs, self.local_mux) + self.local_ble_output_load.generate(subcircuits_filename) + + + def generate_top(self): + self.local_mux.generate_top() + self.ble.generate_top() + + + def update_area(self, area_dict, width_dict): + self.ble.update_area(area_dict, width_dict) + self.local_mux.update_area(area_dict, width_dict) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Update wires of things inside the logic cluster. """ + + # Call wire update functions of member objects. + self.ble.update_wires(width_dict, wire_lengths, wire_layers) + self.local_mux.update_wires(width_dict, wire_lengths, wire_layers) + self.local_routing_wire_load.update_wires(width_dict, wire_lengths, wire_layers) + self.local_ble_output_load.update_wires(width_dict, wire_lengths, wire_layers) + + + def print_details(self): + self.local_mux.print_details() + self.ble.print_details() + + +class _RoutingWireLoad: + """ This is the routing wire load for an architecture with direct drive and only one segment length. + Two-level muxes are assumed and we model for partially on paths. """ + + def __init__(self, wire_length): + # Name of this wire + self.name = "routing_wire_load" + # Length of wire (in tiles) + self.wire_length = wire_length + # We assume that half of the wires in a routing channel are used (limited by routability) + self.channel_usage_assumption = 0.5 + # We assume that half of the cluster inputs are used + self.cluster_input_usage_assumption = 0.5 + # Switch block load per wire + self.sb_load_on = -1 + self.sb_load_partial = -1 + self.sb_load_off = -1 + # Connection block load per wire + self.cb_load_on = -1 + self.cb_load_partial = -1 + self.cb_load_off = -1 + # Switch block per tile + self.tile_sb_on = [] + self.tile_sb_partial = [] + self.tile_sb_off = [] + # Connection block per tile + self.tile_cb_on = [] + self.tile_cb_partial = [] + self.tile_cb_off = [] + # List of wire names in the SPICE circuit + self.wire_names = [] + + + def generate(self, subcircuit_filename, specs, sb_mux, cb_mux): + """ Generate the SPICE circuit for general routing wire load + Need specs object, switch block object and connection block object """ + print "Generating routing wire load" + # Calculate wire load based on architecture parameters + self._compute_load(specs, sb_mux, cb_mux, self.channel_usage_assumption, self.cluster_input_usage_assumption) + # Generate SPICE deck + self.wire_names = load_subcircuits.general_routing_load_generate(subcircuit_filename, self.wire_length, self.tile_sb_on, self.tile_sb_partial, self.tile_sb_off, self.tile_cb_on, self.tile_cb_partial, self.tile_cb_off) + + + def update_wires(self, width_dict, wire_lengths, wire_layers): + """ Calculate wire lengths and wire layers. """ + + # Update wire lengths + wire_lengths["wire_gen_routing"] = self.wire_length*width_dict["tile"] + wire_lengths["wire_sb_load_on"] = width_dict["tile"]/2 + wire_lengths["wire_sb_load_partial"] = width_dict["tile"]/2 + wire_lengths["wire_sb_load_off"] = width_dict["tile"]/2 + wire_lengths["wire_cb_load_on"] = width_dict["tile"]/2 + wire_lengths["wire_cb_load_partial"] = width_dict["tile"]/2 + wire_lengths["wire_cb_load_off"] = width_dict["tile"]/2 + + # Update wire layers + wire_layers["wire_gen_routing"] = 1 + wire_layers["wire_sb_load_on"] = 0 + wire_layers["wire_sb_load_partial"] = 0 + wire_layers["wire_sb_load_off"] = 0 + wire_layers["wire_cb_load_on"] = 0 + wire_layers["wire_cb_load_partial"] = 0 + wire_layers["wire_cb_load_off"] = 0 + + + def print_details(self): + print " ROUTING WIRE LOAD DETAILS" + print " Number of SB inputs connected to routing wire = " + str(self.sb_load_on + self.sb_load_partial + self.sb_load_off) + print " Wire: SB (on = " + str(self.sb_load_on) + ", partial = " + str(self.sb_load_partial) + ", off = " + str(self.sb_load_off) + ")" + print " Number of CB inputs connected to routing wire = " + str(self.cb_load_on + self.cb_load_partial + self.cb_load_off) + print " Wire: CB (on = " + str(self.cb_load_on) + ", partial = " + str(self.cb_load_partial) + ", off = " + str(self.cb_load_off) + ")" + for i in range(self.wire_length): + print " Tile " + str(i+1) + ": SB (on = " + str(self.tile_sb_on[i]) + ", partial = " + str(self.tile_sb_partial[i]) + ", off = " + str(self.tile_sb_off[i]) + "); CB (on = " + str(self.tile_cb_on[i]) + ", partial = " + str(self.tile_cb_partial[i]) + ", off = " + str(self.tile_cb_off[i]) + ")" + print "" + + + def _compute_load(self, specs, sb_mux, cb_mux, channel_usage, cluster_input_usage): + """ Computes the load on a routing wire """ + + # Local variables + W = specs.W + L = specs.L + I = specs.I + Fs = specs.Fs + sb_mux_size = sb_mux.implemented_size + cb_mux_size = cb_mux.implemented_size + sb_level1_size = sb_mux.level1_size + sb_level2_size = sb_mux.level2_size + cb_level1_size = cb_mux.level1_size + cb_level2_size = cb_mux.level2_size + + # Calculate switch block load per tile + # Each tile has Fs-1 switch blocks hanging off of it exept the last one which has 3 (because the wire is ending) + sb_load_per_intermediate_tile = (Fs - 1) + # Calculate number of on/partial/off + # We assume that each routing wire is only driving one more routing wire (at the end) + self.sb_load_on = 1 + # Each used routing multiplexer comes with (sb_level2_size - 1) partially on paths. + # If all wires were used, we'd have (sb_level2_size - 1) partially on paths per wire, TODO: Is this accurate? See ble output load + # but since we are just using a fraction of the wires, each wire has (sb_level2_size - 1)*channel_usage partially on paths connected to it. + self.sb_load_partial = int(round(float(sb_level2_size - 1.0)*channel_usage)) + # The number of off sb_mux is (total - partial) + self.sb_load_off = sb_load_per_intermediate_tile*L - self.sb_load_partial + + # Calculate connection block load per tile + # We assume that cluster inputs are divided evenly between horizontal and vertical routing channels + # We can get the total number of CB inputs connected to the channel segment by multiplying cluster inputs by cb_mux_size, then divide by W to get cb_inputs/wire + cb_load_per_tile = int(round((I/2*cb_mux_size)/W)) + # Now we got to find out how many are on, how many are partially on and how many are off + # For each tile, we have half of the cluster inputs connecting to a routing channel and only a fraction of these inputs are actually used + # It is logical to assume that used cluster inputs will be connected to used routing wires, so we have I/2*input_usage inputs per tile, + # we have L tiles so, I/2*input_usage*L fully on cluster inputs connected to W*channel_usage routing wires + # If we look at the whole wire, we are selecting I/2*input_usage*L signals from W*channel_usage wires + cb_load_on_probability = float((I/2.0*cluster_input_usage*L))/(W*channel_usage) + self.cb_load_on = int(round(cb_load_on_probability)) + # If < 1, we round up to one because at least one wire will have a fully on path connected to it and we model for that case. + if self.cb_load_on == 0: + self.cb_load_on = 1 + # Each fully turned on cb_mux comes with (cb_level2_size - 1) partially on paths + # The number of partially on paths per tile is I/2*input_usage * (cb_level2_size - 1) + # Number of partially on paths per wire is (I/2*input_usage * (cb_level2_size - 1) * L) / W + cb_load_partial_probability = (I/2*cluster_input_usage * (cb_level2_size - 1) * L) / W + self.cb_load_partial = int(round(cb_load_partial_probability)) + # If < 1, we round up to one because at least one wire will have a partially on path connected to it and we model for that case. + if self.cb_load_partial == 0: + self.cb_load_partial = 1 + # Number of off paths is just number connected to routing wire - on - partial + self.cb_load_off = cb_load_per_tile*L - self.cb_load_partial - self.cb_load_on + + # Now we want to figure out how to distribute this among the tiles. We have L tiles. + tile_sb_on_budget = self.sb_load_on + tile_sb_partial_budget = self.sb_load_partial + tile_sb_off_budget = self.sb_load_off + tile_sb_total_budget = tile_sb_on_budget + tile_sb_partial_budget + tile_sb_off_budget + tile_sb_max = math.ceil(float(tile_sb_total_budget)/L) + tile_sb_on = [] + tile_sb_partial = [] + tile_sb_off = [] + tile_sb_total = [] + + # How this works: We have a certain amount of switch block mux connections to give to the wire, + # we start at the furthest tile from the drive point and we allocate one mux input per tile iteratively until we run out of mux inputs. + # The result of this is that on and partial mux inputs will be spread evenly along the wire with a bias towards putting + # them farthest away from the driver first (simulating a worst case). + while tile_sb_total_budget != 0: + # For each tile distribute load + for i in range(L): + # Add to lists + if len(tile_sb_on) < (i+1): + tile_sb_on.append(0) + if len(tile_sb_partial) < (i+1): + tile_sb_partial.append(0) + if len(tile_sb_off) < (i+1): + tile_sb_off.append(0) + if len(tile_sb_total) < (i+1): + tile_sb_total.append(0) + # Distribute loads + if tile_sb_on_budget != 0: + if tile_sb_total[i] != tile_sb_max: + tile_sb_on[i] = tile_sb_on[i] + 1 + tile_sb_on_budget = tile_sb_on_budget - 1 + tile_sb_total[i] = tile_sb_total[i] + 1 + tile_sb_total_budget = tile_sb_total_budget - 1 + if tile_sb_partial_budget != 0: + if tile_sb_total[i] != tile_sb_max: + tile_sb_partial[i] = tile_sb_partial[i] + 1 + tile_sb_partial_budget = tile_sb_partial_budget - 1 + tile_sb_total[i] = tile_sb_total[i] + 1 + tile_sb_total_budget = tile_sb_total_budget - 1 + if tile_sb_off_budget != 0: + if tile_sb_total[i] != tile_sb_max: + tile_sb_off[i] = tile_sb_off[i] + 1 + tile_sb_off_budget = tile_sb_off_budget - 1 + tile_sb_total[i] = tile_sb_total[i] + 1 + tile_sb_total_budget = tile_sb_total_budget - 1 + + # Assign these per-tile counts to the object + self.tile_sb_on = tile_sb_on + self.tile_sb_partial = tile_sb_partial + self.tile_sb_off = tile_sb_off + + tile_cb_on_budget = self.cb_load_on + tile_cb_partial_budget = self.cb_load_partial + tile_cb_off_budget = self.cb_load_off + tile_cb_total_budget = tile_cb_on_budget + tile_cb_partial_budget + tile_cb_off_budget + tile_cb_max = math.ceil(float(tile_cb_total_budget)/L) + tile_cb_on = [] + tile_cb_partial = [] + tile_cb_off = [] + tile_cb_total = [] + + while tile_cb_total_budget != 0: + # For each tile distribute load + for i in range(L): + # Add to lists + if len(tile_cb_on) < (i+1): + tile_cb_on.append(0) + if len(tile_cb_partial) < (i+1): + tile_cb_partial.append(0) + if len(tile_cb_off) < (i+1): + tile_cb_off.append(0) + if len(tile_cb_total) < (i+1): + tile_cb_total.append(0) + # Distribute loads + if tile_cb_on_budget != 0: + if tile_cb_total[i] != tile_cb_max: + tile_cb_on[i] = tile_cb_on[i] + 1 + tile_cb_on_budget = tile_cb_on_budget - 1 + tile_cb_total[i] = tile_cb_total[i] + 1 + tile_cb_total_budget = tile_cb_total_budget - 1 + if tile_cb_partial_budget != 0: + if tile_cb_total[i] != tile_cb_max: + tile_cb_partial[i] = tile_cb_partial[i] + 1 + tile_cb_partial_budget = tile_cb_partial_budget - 1 + tile_cb_total[i] = tile_cb_total[i] + 1 + tile_cb_total_budget = tile_cb_total_budget - 1 + if tile_cb_off_budget != 0: + if tile_cb_total[i] != tile_cb_max: + tile_cb_off[i] = tile_cb_off[i] + 1 + tile_cb_off_budget = tile_cb_off_budget - 1 + tile_cb_total[i] = tile_cb_total[i] + 1 + tile_cb_total_budget = tile_cb_total_budget - 1 + + # Assign these per-tile counts to the object + self.tile_cb_on = tile_cb_on + self.tile_cb_partial = tile_cb_partial + self.tile_cb_off = tile_cb_off + + +class FPGA: + """ This class describes an FPGA. """ + + def __init__(self, N, K, W, L, I, Fs, Fcin, Fcout, Fclocal, Or, Ofb, Rsel, Rfb, + vdd, vsram, vsram_n, gate_length, min_tran_width, min_width_tran_area, sram_cell_area, model_path, model_library, metal_stack): + + # Initialize the specs + self.specs = _Specs(N, K, W, L, I, Fs, Fcin, Fcout, Fclocal, Or, Ofb, Rsel, Rfb, + vdd, vsram, vsram_n, gate_length, min_tran_width, min_width_tran_area, sram_cell_area, model_path, model_library) + + + ### CREATE SWITCH BLOCK OBJECT + # Calculate switch block mux size (for direct-drive routing) + # The mux will need Fs + (Fs-1)(L-1) inputs for routing-to-routing connections + # The Fs term comes from starting wires, the (Fs-1) term comes from non-starting wires, of which there are (L-1) + r_to_r_sb_mux_size = Fs + (Fs-1)*(L-1) + # Then, each mux needs No*Fcout*L/2 additional inputs for logic cluster outputs (No = number of cluster outputs) + No = self.specs.num_cluster_outputs + clb_to_r_sb_mux_size = No*Fcout*L/2 + sb_mux_size_required = int(r_to_r_sb_mux_size + clb_to_r_sb_mux_size) + # Calculate number of switch block muxes per tile + num_sb_mux_per_tile = 2*W/L + # Initialize the switch block + self.sb_mux = _SwitchBlockMUX(sb_mux_size_required, num_sb_mux_per_tile) + + + ### CREATE CONNECTION BLOCK OBJECT + # Calculate connection block mux size + # Size is W*Fcin + cb_mux_size_required = int(W*Fcin) + num_cb_mux_per_tile = I + # Initialize the connection block + self.cb_mux = _ConnectionBlockMUX(cb_mux_size_required, num_cb_mux_per_tile) + + + ### CREATE LOGIC CLUSTER OBJECT + # Calculate local mux size + # Local mux size is (inputs + feedback) * population + local_mux_size_required = int((I + Ofb*N) * Fclocal) + num_local_mux_per_tile = N*K + # Create the logic cluster object + self.logic_cluster = _LogicCluster(N, K, Or, Ofb, Rsel, Rfb, local_mux_size_required, num_local_mux_per_tile) + + ### CREATE LOAD OBJECTS + # Create cluster output load object + self.cluster_output_load = _GeneralBLEOutputLoad() + # Create routing wire load object + self.routing_wire_load = _RoutingWireLoad(L) + + + ### INITIALIZE OTHER VARIABLES, LISTS AND DICTIONARIES + # Initialize SPICE library filenames + self.tran_sizes_filename = "transistor_sizes.l" + self.wire_RC_filename = "wire_RC.l" + self.process_data_filename = "process_data.l" + self.includes_filename = "includes.l" + self.basic_subcircuits_filename = "basic_subcircuits.l" + self.subcircuits_filename = "subcircuits.l" + self.sweep_data_filename = "sweep_data.l" + + # This is a dictionary of all the transistor sizes in the FPGA ('name': 'size') + # It will contain the data in xMin transistor width, e.g. 'inv_sb_mux_1_nmos': '2' + # That means inv_sb_mux_1_nmos is a transistor with 2x minimum width + self.transistor_sizes = {} + # This is a list of tuples containing area information for each transistor in the FPGA + # Tuple: (tran_name, tran_channel_width_nm, tran_drive_strength, tran_area_min_areas, tran_area_nm, tran_width_nm) + self.transistor_area_list = [] + + # A note on the following 5 dictionaries + # (area_dict, width_dict, wire_lengths, wire_layers, wire_rc_dict) + # + # Transistor sizes and wire lengths are needed at many different places in the SPICE netlists + # that COFFE creates (e.g. the size of a particular transistor might be needed in many + # different files or multiple times in the same file). Since it would be a pain to have to + # go through every single line in every single file each time we want to change the size of + # a transistor (which will happen many thousands of times), COFFE inserts variables in the + # SPICE netlists that it creates. These variables, which describe transistor sizes and wire + # loads, are assigned values in external files (one file for transistor sizes, one for wire loads). + # That way, when we change the size of a transistor (or a wire load), we only need to change + # it in one place, and this change is seen by all SPICE netlists. + # The data structures that COFFE uses to keep track of transistor/circuit areas and wire data + # use a similar philosophy. That is, the following 5 dictionaries contain information about + # all element in the FPGA (all in one place). For ex., if we want to know the area of a switch block + # multiplexer we ask 'area_dict' (e.g. area_dict['sb_mux']). One of the reasons for doing this + # is that it makes outputing this data easier. For example, when we want to update that 'wire + # load file' that the SPICE netlists use, all we need to do is write out wire_rc_dict to that file. + # But, the 'fpga' object does not know how to update the area and wire data of each subcircuit. + # Therefore, these dictionaries will be passed into member objects who will populate them as needed. + # So, that's just something to keep in mind as you go through this code. You'll likely see these + # dictionaries a lot. + # + # This is a dictionary that contains the area of everything for all levels of hierarchy in the FPGA. + # It has transistor area, inverter areas, mux areas, switch_block area, tile area.. etc. + # ('entity_name': area) All these areas are in nm^2 + self.area_dict = {} + # This is a dictionary that contains the width of everything (much like area_dict has the areas). + # ('entity_name': width) All widths are in nm. The width_dict is useful for figuring out wire lengths. + self.width_dict = {} + # This dictionary contains the lengths of all the wires in the FPGA. ('wire_name': length). Lengths in nm. + self.wire_lengths = {} + # This dictionary contains the metal layer for each wire. ('wire_name': layer) + # The layer number (an int) is an index that will be used to select the right metal data + # from the 'metal_stack' (list described below). + self.wire_layers = {} + # This dictionary contains wire resistance and capacitance for each wire as a tuple ('wire_name': (R, C)) + self.wire_rc_dict = {} + + # This dictionary contains the delays of all subcircuits (i.e. the max of rise and fall) + # Contrary to the above 5 dicts, this one is not passed down into the other objects. + # This dictionary is updated by calling 'update_delays()' + self.delay_dict = {} + + # Metal stack. Lowest index is lowest metal layer. COFFE assumes that wire widths increase as we use higher metal layers. + # For example, wires in metal_stack[1] are assumed to be wider (and/or more spaced) than wires in metal_stack[0] + # e.g. metal_stack[0] = (R0, C0) + self.metal_stack = metal_stack + + + def generate(self, is_size_transistors): + """ This function generates all SPICE netlists and library files. """ + + # Here's a file-stack that shows how COFFE organizes its SPICE files. + # We'll talk more about each one as we generate them below. + + # --------------------------------------------------------------------------------- + # | | + # | top-level spice files (e.g. sb_mux.sp) | + # | | + # --------------------------------------------------------------------------------- + # | | + # | includes.l | + # | | + # --------------------------------------------------------------------------------- + # | | + # | subcircuits.l | + # | | + # --------------------------------------------------------------------------------- + # | | | | | + # | process_data.l | basic_subcircuits.l | transistor_sizes.l | wire_RC.l | + # | | | | | + # --------------------------------------------------------------------------------- + + + # If transistor sizing is turned off, we want to keep the transistor sizes + # that are in 'transistor_sizes.l'. To do this, we keep a copy of the original file + # as COFFE will overwrite the original, and then we replace the overwritten file with + # our copy near the end of this function. + if is_size_transistors: + print "TRANSISTOR SIZING MODE" + else: + print "UPDATE MODE" + os.rename("transistor_sizes.l", "transistor_sizes_hold.l") + + # Generate basic subcircuit library (pass-transistor, inverter, wire, etc.). + # This library will be used to build other netlists. + self._generate_basic_subcircuits() + + # Create 'subcircuits.l' and 'transistor_sizes.l' libraries. + # The subcircuit generation functions between 'self._create_lib_files()' + # and 'self._end_lib_files()' will add things to these library files. + self._create_lib_files() + + # Generate the various subcircuits netlists of the FPGA (call members) + self.sb_mux.generate(self.subcircuits_filename, self.tran_sizes_filename, self.specs.min_tran_width) + self.cb_mux.generate(self.subcircuits_filename, self.tran_sizes_filename, self.specs.min_tran_width) + self.logic_cluster.generate(self.subcircuits_filename, self.tran_sizes_filename, self.specs.min_tran_width, self.specs) + self.cluster_output_load.generate(self.subcircuits_filename, self.specs, self.sb_mux) + self.routing_wire_load.generate(self.subcircuits_filename, self.specs, self.sb_mux, self.cb_mux) + + # Add file footers to 'subcircuits.l' and 'transistor_sizes.l' libraries. + self._end_lib_files() + + # Create SPICE library that contains process data and voltage level information + self._generate_process_data() + + # This generates an include file. Top-level SPICE netlists only need to include + # this 'include' file to include all libraries (for convenience). + self._generate_includes() + + # Create the sweep_data.l file. COFFE will use this to perform multi-variable sweeps. + self._generate_sweep_data() + + # Generate top-level files. These top-level files are the files that COFFE uses to measure + # the delay of FPGA circuitry. + self.sb_mux.generate_top() + self.cb_mux.generate_top() + self.logic_cluster.generate_top() + + # Delete new transistor sizes file if transistor sizing is turned off + # and replace with our copy. + if not is_size_transistors: + print "Restoring transistor sizes..." + os.remove("transistor_sizes.l") + os.rename("transistor_sizes_hold.l", "transistor_sizes.l") + + # Calculate area, and wire data. + print "Calculating area..." + # Load transistor sizes from spice file + self.transistor_sizes = self._load_transistor_sizes(self.tran_sizes_filename) + # Update area values + self.update_area() + print "Calculating wire lengths..." + self.update_wires() + print "Calculating wire resistance and capacitance..." + self.update_wire_rc() + print "Updating wire RC file..." + self.update_wire_rc_file() + + print "" + + + def update_area(self): + """ This function updates self.area_dict. It passes area_dict to member objects (like sb_mux) + to update their area. Then, with an up-to-date area_dict it, calculate total tile area. """ + + # We use the self.transistor_sizes to compute area. This dictionary has the form 'name': 'size' + # And it knows the transistor sizes of all transistors in the FPGA + # We first need to calculate the area for each transistor. + self._update_area_per_transistor() + # Now, we have to update area_dict and width_dict with the new transistor area values + self._update_area_and_width_dicts() + + # Calculate area of SRAM + self.area_dict["sram"] = self.specs.sram_cell_area*self.specs.min_width_tran_area + + # Call area calculation functions of sub-blocks + self.sb_mux.update_area(self.area_dict, self.width_dict) + self.cb_mux.update_area(self.area_dict, self.width_dict) + self.logic_cluster.update_area(self.area_dict, self.width_dict) + + # Calculate total area of switch block + switch_block_area = self.sb_mux.num_per_tile*self.area_dict[self.sb_mux.name + "_sram"] + self.area_dict["sb_total"] = switch_block_area + self.width_dict["sb_total"] = math.sqrt(switch_block_area) + + # Calculate total area of connection block + connection_block_area = self.cb_mux.num_per_tile*self.area_dict[self.cb_mux.name + "_sram"] + self.area_dict["cb_total"] = connection_block_area + self.width_dict["cb_total"] = math.sqrt(connection_block_area) + + # Calculate total area of local muxes + local_mux_area = self.logic_cluster.local_mux.num_per_tile*self.area_dict[self.logic_cluster.local_mux.name + "_sram"] + self.area_dict["local_mux_total"] = local_mux_area + self.width_dict["local_mux_total"] = math.sqrt(local_mux_area) + + # Calculate total lut area + lut_area = self.specs.N*self.area_dict["lut_and_drivers"] + self.area_dict["lut_total"] = lut_area + self.width_dict["lut_total"] = math.sqrt(lut_area) + + # Calculate total ff area + ff_area = self.specs.N*self.area_dict[self.logic_cluster.ble.ff.name] + self.area_dict["ff_total"] = ff_area + self.width_dict["ff_total"] = math.sqrt(ff_area) + + # Calcualte total ble output area + ble_output_area = self.specs.N*(self.area_dict["ble_output"]) + self.area_dict["ble_output_total"] = ble_output_area + self.width_dict["ble_output_total"] = math.sqrt(ble_output_area) + + # Calculate area of logic cluster + cluster_area = local_mux_area + lut_area + ff_area + ble_output_area + self.area_dict["logic_cluster"] = cluster_area + self.width_dict["logic_cluster"] = math.sqrt(cluster_area) + + # Calculate tile area + tile_area = switch_block_area + connection_block_area + cluster_area + self.area_dict["tile"] = tile_area + self.width_dict["tile"] = math.sqrt(tile_area) + + + def update_wires(self): + """ This function updates self.wire_lengths and self.wire_layers. It passes wire_lengths and wire_layers to member + objects (like sb_mux) to update their wire lengths and layers. """ + + # Update wire lengths and layers for all subcircuits + self.sb_mux.update_wires(self.width_dict, self.wire_lengths, self.wire_layers) + self.cb_mux.update_wires(self.width_dict, self.wire_lengths, self.wire_layers) + self.logic_cluster.update_wires(self.width_dict, self.wire_lengths, self.wire_layers) + self.cluster_output_load.update_wires(self.width_dict, self.wire_lengths, self.wire_layers) + self.routing_wire_load.update_wires(self.width_dict, self.wire_lengths, self.wire_layers) + + + def update_wire_rc(self): + """ This function updates self.wire_rc_dict based on the FPGA's self.wire_lengths and self.wire_layers.""" + + # Calculate R and C for each wire + for wire, length in self.wire_lengths.iteritems(): + # Get wire layer + layer = self.wire_layers[wire] + # Get R and C per unit length for wire layer + rc = self.metal_stack[layer] + # Calculate total wire R and C + resistance = rc[0]*length + capacitance = rc[1]*length/2 + # Add to wire_rc dictionary + self.wire_rc_dict[wire] = (resistance, capacitance) + + + def update_wire_rc_file(self): + """ Updates the wire_RC SPICE file with self.wire_rc_dict. """ + + # Create wire RC file (overwrites if it exists) + wire_rc_file = open(self.wire_RC_filename, 'w') + wire_rc_file.write("*** WIRE RESISTANCE AND CAPACITANCE LIBRARY\n\n") + wire_rc_file.write(".LIB WIRE_RC\n\n") + + # Write wire RC values to file + for wire, rc in self.wire_rc_dict.iteritems(): + wire_rc_file.write(".PARAM " + wire + "_res = " + str(rc[0]) + "\n") + wire_rc_file.write(".PARAM " + wire + "_cap = " + str(rc[1]) + "f\n\n") + + # End wire RC file + wire_rc_file = open(self.wire_RC_filename, 'a') + wire_rc_file.write(".ENDL WIRE_RC") + wire_rc_file.close() + + + def update_delays(self, num_hspice_sims): + """ Extract HSPICE delays for each subcircuit. """ + + crit_path_delay = 0 + + # Switch Block MUX + delays = spice.get_total_tfall_trise(self.sb_mux.name, self.sb_mux.top_spice_path) + num_hspice_sims += 1 + self.sb_mux.tfall = delays[0] + self.sb_mux.trise = delays[1] + self.sb_mux.delay = max(delays[0], delays[1]) + crit_path_delay += self.sb_mux.delay*self.sb_mux.delay_weight + self.delay_dict[self.sb_mux.name] = self.sb_mux.delay + + # Connection Block MUX + delays = spice.get_total_tfall_trise(self.cb_mux.name, self.cb_mux.top_spice_path) + num_hspice_sims += 1 + self.cb_mux.tfall = delays[0] + self.cb_mux.trise = delays[1] + self.cb_mux.delay = max(delays[0], delays[1]) + crit_path_delay += self.cb_mux.delay*self.cb_mux.delay_weight + self.delay_dict[self.cb_mux.name] = self.cb_mux.delay + + # Local MUX + delays = spice.get_total_tfall_trise(self.logic_cluster.local_mux.name, self.logic_cluster.local_mux.top_spice_path) + num_hspice_sims += 1 + self.logic_cluster.local_mux.tfall = delays[0] + self.logic_cluster.local_mux.trise = delays[1] + self.logic_cluster.local_mux.delay = max(delays[0], delays[1]) + crit_path_delay += self.logic_cluster.local_mux.delay*self.logic_cluster.local_mux.delay_weight + self.delay_dict[self.logic_cluster.local_mux.name] = self.logic_cluster.local_mux.delay + + # Local BLE output + delays = spice.get_total_tfall_trise(self.logic_cluster.ble.local_output.name, self.logic_cluster.ble.local_output.top_spice_path) + num_hspice_sims += 1 + self.logic_cluster.ble.local_output.tfall = delays[0] + self.logic_cluster.ble.local_output.trise = delays[1] + self.logic_cluster.ble.local_output.delay = max(delays[0], delays[1]) + crit_path_delay += self.logic_cluster.ble.local_output.delay*self.logic_cluster.ble.local_output.delay_weight + self.delay_dict[self.logic_cluster.ble.local_output.name] = self.logic_cluster.ble.local_output.delay + + # General BLE output + delays = spice.get_total_tfall_trise(self.logic_cluster.ble.general_output.name, self.logic_cluster.ble.general_output.top_spice_path) + num_hspice_sims += 1 + self.logic_cluster.ble.general_output.tfall = delays[0] + self.logic_cluster.ble.general_output.trise = delays[1] + self.logic_cluster.ble.general_output.delay = max(delays[0], delays[1]) + crit_path_delay += self.logic_cluster.ble.general_output.delay*self.logic_cluster.ble.general_output.delay_weight + self.delay_dict[self.logic_cluster.ble.general_output.name] = self.logic_cluster.ble.general_output.delay + + # LUT delay + delays = spice.get_total_tfall_trise(self.logic_cluster.ble.lut.name, self.logic_cluster.ble.lut.top_spice_path) + num_hspice_sims += 1 + self.logic_cluster.ble.lut.tfall = delays[0] + self.logic_cluster.ble.lut.trise = delays[1] + self.logic_cluster.ble.lut.delay = max(delays[0], delays[1]) + self.delay_dict[self.logic_cluster.ble.lut.name] = self.logic_cluster.ble.lut.delay + + # Get delay for all paths through the LUT. We get delay for each path through the LUT as well as for the LUT input drivers. + for lut_input_name, lut_input in self.logic_cluster.ble.lut.input_drivers.iteritems(): + driver = lut_input.driver + not_driver = lut_input.not_driver + # Get the delay for a path through the LUT (we do it for each input) + delays = spice.get_total_tfall_trise(driver.name.replace("_driver", ""), (driver.top_spice_path.rstrip(".sp") + "_with_lut.sp")) + num_hspice_sims += 1 + lut_input.tfall = delays[0] + lut_input.trise = delays[1] + lut_input.delay = max(delays[0], delays[1]) + self.delay_dict[lut_input.name] = lut_input.delay + + # Now, we want to get the delay and power for the driver + delays = spice.get_total_tfall_trise(driver.name, driver.top_spice_path) + num_hspice_sims += 1 + driver.tfall = delays[0] + driver.trise = delays[1] + driver.delay = max(delays[0], delays[1]) + self.delay_dict[driver.name] = driver.delay + # ... and the not_driver + delays = spice.get_total_tfall_trise(not_driver.name, not_driver.top_spice_path) + num_hspice_sims += 1 + not_driver.tfall = delays[0] + not_driver.trise = delays[1] + not_driver.delay = max(delays[0], delays[1]) + self.delay_dict[not_driver.name] = not_driver.delay + + lut_delay = lut_input.delay + max(driver.delay, not_driver.delay) + crit_path_delay += lut_delay*lut_input.delay_weight + + self.delay_dict["rep_crit_path"] = crit_path_delay + + print "" + + return num_hspice_sims + + + def print_specs(self): + print "FPGA ARCHITECTURE SPECS:" + print "Number of BLEs per cluster (N): " + str(self.specs.N) + print "LUT size (K): " + str(self.specs.K) + print "Channel width (W): " + str(self.specs.W) + print "Wire segment length (L): " + str(self.specs.L) + print "Number cluster inputs (I): " + str(self.specs.I) + print "Number of BLE outputs to general routing: " + str(self.specs.num_ble_general_outputs) + print "Number of BLE outputs to local routing: " + str(self.specs.num_ble_local_outputs) + print "Number of cluster outputs: " + str(self.specs.num_cluster_outputs) + print "Switch block flexibility (Fs): " + str(self.specs.Fs) + print "Cluster input flexibility (Fcin): " + str(self.specs.Fcin) + print "Cluster output flexibility (Fcout): " + str(self.specs.Fcout) + print "Local MUX population (Fclocal): " + str(self.specs.Fclocal) + print "" + + + def print_details(self): + + print "|------------------------------------------------------------------------------|" + print "| FPGA Implementation Details |" + print "|------------------------------------------------------------------------------|" + print "" + self.sb_mux.print_details() + self.cb_mux.print_details() + self.logic_cluster.print_details() + self.cluster_output_load.print_details() + self.routing_wire_load.print_details() + print "|------------------------------------------------------------------------------|" + print "" + + + def _area_model(self, tran_name, tran_size): + """ Transistor area model. 'tran_size' is the transistor drive strength in min. width transistor drive strengths. + Transistor area is calculated bsed on 'tran_size' and transistor type, which is determined by tags in 'tran_name'. + Return valus is the transistor area in minimum width transistor areas. """ + + # If inverter or transmission gate, use larger area to account for N-well spacing + # If pass-transistor, use regular area because they don't need N-wells. + if "inv_" in tran_name or "tgate_" in tran_name: + area = 0.518 + 0.127*tran_size + 0.428*math.sqrt(tran_size) + else: + area = 0.447 + 0.128*tran_size + 0.391*math.sqrt(tran_size) + + return area + + + def _create_lib_files(self): + """ Create SPICE library files and add headers. """ + + # Create Subcircuits file + sc_file = open(self.subcircuits_filename, 'w') + sc_file.write("*** SUBCIRCUITS\n\n") + sc_file.write(".LIB SUBCIRCUITS\n\n") + sc_file.close() + + # Create transistor sizes file + tran_sizes_file = open(self.tran_sizes_filename, 'w') + tran_sizes_file.write("*** TRANSISTOR SIZES LIBRARY\n\n") + tran_sizes_file.write(".LIB TRAN_SIZES\n\n") + tran_sizes_file.close() + + + def _end_lib_files(self): + """ End the SPICE library files. """ + + # Subcircuits file + sc_file = open(self.subcircuits_filename, 'a') + sc_file.write(".ENDL SUBCIRCUITS") + sc_file.close() + + # End transistor sizes file + tran_sizes_file = open(self.tran_sizes_filename, 'a') + tran_sizes_file.write(".ENDL TRAN_SIZES") + tran_sizes_file.close() + + + def _generate_basic_subcircuits(self): + """ Generates the basic subcircuits SPICE file (pass-transistor, inverter, etc.) """ + + print "Generating basic subcircuits" + + # Open basic subcircuits file and write heading + basic_sc_file = open(self.basic_subcircuits_filename, 'w') + basic_sc_file.write("*** BASIC SUBCIRCUITS\n\n") + basic_sc_file.write(".LIB BASIC_SUBCIRCUITS\n\n") + basic_sc_file.close() + + # Generate wire subcircuit + basic_subcircuits.wire_generate(self.basic_subcircuits_filename) + # Generate pass-transistor subcircuit + basic_subcircuits.ptran_generate(self.basic_subcircuits_filename) + # Generate transmission gate subcircuit + basic_subcircuits.tgate_generate(self.basic_subcircuits_filename) + # Generate level-restore subcircuit + basic_subcircuits.rest_generate(self.basic_subcircuits_filename) + # Generate inverter subcircuit + basic_subcircuits.inverter_generate(self.basic_subcircuits_filename) + + # Write footer + basic_sc_file = open(self.basic_subcircuits_filename, 'a') + basic_sc_file.write(".ENDL BASIC_SUBCIRCUITS") + basic_sc_file.close() + + + def _generate_process_data(self): + """ Write the process data library file. It contains voltage levels, gate length and device models. """ + + print "Generating process data file" + + process_data_file = open(self.process_data_filename, 'w') + process_data_file.write("*** PROCESS DATA AND VOLTAGE LEVELS\n\n") + process_data_file.write(".LIB PROCESS_DATA\n\n") + process_data_file.write("* Voltage levels\n") + process_data_file.write(".PARAM supply_v = " + str(self.specs.vdd) + "\n") + process_data_file.write(".PARAM sram_v = " + str(self.specs.vsram) + "\n") + process_data_file.write(".PARAM sram_n_v = " + str(self.specs.vsram_n) + "\n\n") + process_data_file.write("* Gate length\n") + process_data_file.write(".PARAM gate_length = " + str(self.specs.gate_length) + "n\n\n") + process_data_file.write("* We have two supply rails, vdd and vdd_subckt.\n") + process_data_file.write("* This allows us to measure power of a circuit under test without measuring the power of wave shaping and load circuitry\n") + process_data_file.write("VSUPPLY vdd gnd supply_v\n") + process_data_file.write("VSUBCKT vdd_subckt gnd supply_v\n\n") + process_data_file.write("* SRAM voltages connecting to gates\n") + process_data_file.write("VSRAM vsram gnd sram_v\n") + process_data_file.write("VSRAM_N vsram_n gnd sram_n_v\n\n") + process_data_file.write("* Device models\n") + process_data_file.write(".LIB \"" + self.specs.model_path + "\" " + self.specs.model_library + "\n\n") + process_data_file.write(".ENDL PROCESS_DATA") + process_data_file.close() + + + def _generate_includes(self): + """ Generate the includes file. Top-level SPICE decks should only include this file. """ + + print "Generating includes file" + + includes_file = open(self.includes_filename, 'w') + includes_file.write("*** INCLUDE ALL LIBRARIES\n\n") + includes_file.write(".LIB INCLUDES\n\n") + includes_file.write("* Include process data (voltage levels, gate length and device models library)\n") + includes_file.write(".LIB \"process_data.l\" PROCESS_DATA\n\n") + includes_file.write("* Include transistor parameters\n") + includes_file.write(".LIB \"transistor_sizes.l\" TRAN_SIZES\n\n") + includes_file.write("* Include wire resistance and capacitance\n") + includes_file.write(".LIB \"wire_RC.l\" WIRE_RC\n\n") + includes_file.write("* Include basic subcircuits\n") + includes_file.write(".LIB \"basic_subcircuits.l\" BASIC_SUBCIRCUITS\n\n") + includes_file.write("* Include subcircuits\n") + includes_file.write(".LIB \"subcircuits.l\" SUBCIRCUITS\n\n") + includes_file.write("* Include sweep data file for .DATA sweep analysis\n") + includes_file.write(".INCLUDE \"sweep_data.l\"\n\n") + includes_file.write(".ENDL INCLUDES") + includes_file.close() + + + def _generate_sweep_data(self): + """ Create the sweep_data.l file that COFFE uses to perform + multi-variable HSPICE parameter sweeping. """ + + sweep_data_file = open(self.sweep_data_filename, 'w') + sweep_data_file.close() + + + def _load_transistor_sizes(self, tran_size_filename): + """ Opens transistor size SPICE file and loads all transistor sizes into a dictionary + in xMin width format : 'name': 'size'. Ex: 'inv_sb_mux_1_nmos': '2' + Returns this dictionary.""" + + # Open the transistor size file + tran_file = open(tran_size_filename, 'r') + + # Go through file line by line, get size for each transistor + transistor_sizes = {} + for line in tran_file: + if ".PARAM" in line: + words = line.split() + # Get transistor name + tran_name = words[1] + # Get transistor size in nm + tran_size = words[3] + # Add to transistor_sizes dictionary + transistor_sizes[tran_name] = float(tran_size.replace("n", ""))/self.specs.min_tran_width + # Close the file + tran_file.close() + + return transistor_sizes + + + def _update_transistor_sizes(self, element_names, combo, inv_ratios=None): + """ This function is used to update self.transistor_sizes for a particular transistor sizing combination. + 'element_names' is a list of elements (ptran, inv, etc.) that need their sizes updated. + 'combo' is a particular transistor sizing combination for the transistors in 'element_names' + 'inv_ratios' are the inverter P/N ratios for this transistor sizing combination. + 'combo' will typically describe only a small group of transistors. Other transistors retain their current size.""" + + # We start by making a dictionary of the transistor sizes we need to update + new_sizes = {} + for i in range(len(combo)): + element_name = element_names[i] + # If it's a pass-transistor, we just add the NMOS size + if "ptran_" in element_name: + new_sizes[element_name + "_nmos"] = combo[i] + # If it's a level-restorer, we just add the PMOS size + elif "rest_" in element_name: + new_sizes[element_name + "_pmos"] = combo[i] + # If it's an inverter, we have to add both NMOS and PMOS sizes + elif "inv_" in element_name: + if inv_ratios == None: + # If no inverter ratios are specified, NMOS and PMOS are equal size + new_sizes[element_name + "_nmos"] = combo[i] + new_sizes[element_name + "_pmos"] = combo[i] + else: + # If there are inverter ratios, we use them to give different sizes to NMOS and PMOS + if inv_ratios[element_name] < 1: + # NMOS is larger than PMOS + new_sizes[element_name + "_nmos"] = combo[i]/inv_ratios[element_name] + new_sizes[element_name + "_pmos"] = combo[i] + else: + # PMOS is larger than NMOS + new_sizes[element_name + "_nmos"] = combo[i] + new_sizes[element_name + "_pmos"] = combo[i]*inv_ratios[element_name] + + # Now, update self.transistor_sizes with these new sizes + self.transistor_sizes.update(new_sizes) + + + def _update_area_per_transistor(self): + """ We use self.transistor_sizes to calculate area + Using the area model, we calculate the transistor area in minimum width transistor areas. + We also calculate area in nm and transistor width in nm. Nanometer values are needed for wire length calculations. + For each transistor, this data forms a tuple (tran_name, tran_channel_width_nm, tran_drive_strength, tran_area_min_areas, tran_area_nm, tran_width_nm) + The FPGAs transistor_area_list is updated once these values are computed.""" + + # Initialize transistor area list + tran_area_list = [] + + # For each transistor, calculate area + for tran_name, tran_size in self.transistor_sizes.iteritems(): + # Get transistor drive strength (drive strength is = xMin width) + tran_drive = tran_size + # Get tran area in min transistor widths + tran_area = self._area_model(tran_name, tran_drive) + # Get area in nm square + tran_area_nm = tran_area*self.specs.min_width_tran_area + # Get width of transistor in nm + tran_width = math.sqrt(tran_area_nm) + # Add this as a tuple to the tran_area_list + tran_area_list.append((tran_name, tran_size, tran_drive, tran_area, + tran_area_nm, tran_width)) + + + # Assign list to FPGA object + self.transistor_area_list = tran_area_list + + + def _update_area_and_width_dicts(self): + """ Calculate area for basic subcircuits like inverters, pass transistor, + transmission gates, etc. Update area_dict and width_dict with this data.""" + + # Initialize component area list + comp_area_list = [] + + # Create a dictionary to store component sizes for multi-transistor components + comp_dict = {} + + # For each transistor in the transistor_area_list + for tran in self.transistor_area_list: + if "inv_" in tran[0] or "tgate_" in tran[0]: + # Get the component name + comp_name = tran[0].replace("_nmos", "") + comp_name = comp_name.replace("_pmos", "") + + # If the component is already in the dictionary + if comp_name in comp_dict: + if "_nmos" in tran[0]: + comp_dict[comp_name]["nmos"] = tran[4] + else: + comp_dict[comp_name]["pmos"] = tran[4] + + # At this point we should have both NMOS and PMOS sizes in the dictionary + # We can calculate the area of the inverter or tgate by doing the sum + comp_area = comp_dict[comp_name]["nmos"] + comp_dict[comp_name]["pmos"] + comp_width = math.sqrt(comp_area) + comp_area_list.append((comp_name, comp_area, comp_width)) + + else: + # Create a dict for this component to store nmos and pmos sizes + comp_area_dict = {} + # Add either the nmos or pmos item + if "_nmos" in tran[0]: + comp_area_dict["nmos"] = tran[4] + else: + comp_area_dict["pmos"] = tran[4] + + # Add this inverter to the inverter dictionary + comp_dict[comp_name] = comp_area_dict + + elif "ptran_" in tran[0] or "rest_" in tran[0] or "tran_" in tran[0]: + # Get the comp name + comp_name = tran[0].replace("_nmos", "") + comp_name = comp_name.replace("_pmos", "") + + # Add this to comp_area_list directly + comp_area_list.append((comp_name, tran[4], tran[5])) + + # Convert comp_area_list to area_dict and width_dict + area_dict = {} + width_dict = {} + for component in comp_area_list: + area_dict[component[0]] = component[1] + width_dict[component[0]] = component[2] + + # Set the FPGA object area and width dict + self.area_dict = area_dict + self.width_dict = width_dict + + +def _add_transistor_to_file(transistor_sizes_filename, subcircuit_name, min_tran_width, transistor_sizes): + """ Utility function to write transistor sizes to transistor sizes file. """ + + # Open file for appending + transistor_sizes_file = open(transistor_sizes_filename, 'a') + + # Write each transistor with initial size + transistor_sizes_file.write("******************************************************************************************\n") + transistor_sizes_file.write("* " + subcircuit_name +"\n") + transistor_sizes_file.write("******************************************************************************************\n") + for tran_name, tran_size in transistor_sizes.iteritems(): + transistor_sizes_file.write(".PARAM " + tran_name + " = " + str(tran_size*min_tran_width) + "n\n") + transistor_sizes_file.write("\n\n") + + # Close file + transistor_sizes_file.close() \ No newline at end of file diff --git a/coffe/hspice_extract.py b/coffe/hspice_extract.py new file mode 100644 index 0000000..b4a62e0 --- /dev/null +++ b/coffe/hspice_extract.py @@ -0,0 +1,289 @@ +import os +import subprocess +import spice + + +def extract_hspice_delay_and_power(fpga_inst): + """ Extract HSPICE delays for one architecture """ + + # TODO: Should we actually incorporate this into FPGA.py? It would be a function called update_delays() + # The question is: should the fpga object be allowed to run HSPICE or not??? + # I'm not sure. The FPGA object knows everything about the FPGA structure, it can calculate its area and wire lengths. + # It creates SPICE decks. And it knows which spice deck is associated with which FPGA subcircuit. + + # Also, we might want to update area and wires here... just to be sure that when we update delay, it is most recent possible. + + print "UPDATING DELAY FOR ALL SUBCIRCUITS:" + + # Switch Block MUX + delays = spice.get_total_tfall_trise(fpga_inst.sb_mux.name, fpga_inst.sb_mux.top_spice_path) + fpga_inst.sb_mux.tfall = delays[0] + fpga_inst.sb_mux.trise = delays[1] + fpga_inst.sb_mux.delay = max(delays[0], delays[1]) + + # Connection Block MUX + delays = spice.get_total_tfall_trise(fpga_inst.cb_mux.name, fpga_inst.cb_mux.top_spice_path) + fpga_inst.cb_mux.tfall = delays[0] + fpga_inst.cb_mux.trise = delays[1] + fpga_inst.cb_mux.delay = max(delays[0], delays[1]) + + # Local MUX + delays = spice.get_total_tfall_trise(fpga_inst.logic_cluster.local_mux.name, fpga_inst.logic_cluster.local_mux.top_spice_path) + fpga_inst.logic_cluster.local_mux.tfall = delays[0] + fpga_inst.logic_cluster.local_mux.trise = delays[1] + fpga_inst.logic_cluster.local_mux.delay = max(delays[0], delays[1]) + + # Local BLE output + delays = spice.get_total_tfall_trise(fpga_inst.logic_cluster.ble.local_output.name, fpga_inst.logic_cluster.ble.local_output.top_spice_path) + fpga_inst.logic_cluster.ble.local_output.tfall = delays[0] + fpga_inst.logic_cluster.ble.local_output.trise = delays[1] + fpga_inst.logic_cluster.ble.local_output.delay = max(delays[0], delays[1]) + + # General BLE output + delays = spice.get_total_tfall_trise(fpga_inst.logic_cluster.ble.general_output.name, fpga_inst.logic_cluster.ble.general_output.top_spice_path) + fpga_inst.logic_cluster.ble.general_output.tfall = delays[0] + fpga_inst.logic_cluster.ble.general_output.trise = delays[1] + fpga_inst.logic_cluster.ble.general_output.delay = max(delays[0], delays[1]) + + # LUT delay + delays = spice.get_total_tfall_trise(fpga_inst.logic_cluster.ble.lut.name, fpga_inst.logic_cluster.ble.lut.top_spice_path) + fpga_inst.logic_cluster.ble.lut.tfall = delays[0] + fpga_inst.logic_cluster.ble.lut.trise = delays[1] + fpga_inst.logic_cluster.ble.lut.delay = max(delays[0], delays[1]) + + # Get delay for all paths through the LUT. We get delay for each path through the LUT as well as for the LUT input drivers. + for lut_input_name, lut_input in fpga_inst.logic_cluster.ble.lut.input_drivers.iteritems(): + driver = lut_input.driver + not_driver = lut_input.not_driver + # Get the delay for a path through the LUT (we do it for each input) + delays = spice.get_total_tfall_trise(driver.name.replace("_driver", ""), (driver.top_spice_path.rstrip(".sp") + "_with_lut.sp")) + lut_input.tfall = delays[0] + lut_input.trise = delays[1] + lut_input.delay = max(delays[0], delays[1]) + + # Now, we want to get the delay and power for the driver + delays = spice.get_total_tfall_trise(driver.name, driver.top_spice_path) + driver.tfall = delays[0] + driver.trise = delays[1] + driver.delay = max(delays[0], delays[1]) + # ... and the not_driver + delays = spice.get_total_tfall_trise(not_driver.name, not_driver.top_spice_path) + not_driver.tfall = delays[0] + not_driver.trise = delays[1] + not_driver.delay = max(delays[0], delays[1]) + + print "" + + +def get_tfall_trise(name, spice_path): + """ Run HSPICE on multiplexer SPICE decks and parse output + Returns this tuple: (tfall, trise) """ + + print 'Updating delay for ' + name + + # We are going to change the working directory to the SPICE deck directory so that files generated by SPICE area created there + saved_cwd = os.getcwd() + new_cwd = "" + spice_filename_nodir = "" + if "/" in spice_path: + words = spice_path.split("/") + for i in range(len(words)-1): + new_cwd = new_cwd + words[i] + "/" + os.chdir(new_cwd) + spice_filename_nodir = words[len(words)-1] + + # Run the SPICE simulation and capture console output + spice_output_filename = spice_filename_nodir.rstrip(".sp") + ".output" + output = open(spice_output_filename, "w") + subprocess.call(["hspice", spice_filename_nodir], stdout=output, stderr=output) + output.close() + + # Return to saved cwd + os.chdir(saved_cwd) + + # Parse output files + spice_output = open(spice_path.rstrip(".sp") + ".output", 'r') + tfall = 0.0 + trise = 0.0 + avg_power = 0.0 + for line in spice_output: + if 'meas_total_tfall' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + tfall = convert_spice_num(delay_str) + elif 'meas_total_trise' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + trise = convert_spice_num(delay_str) + elif 'avg_power_gen_routing' in line: + split1 = line.split("=") + split2 = split1[1].split() + power_str = split2[0] + avg_power = convert_spice_num(power_str) + spice_output.close() + + return (tfall, trise) + + + +def flip_flop(architecture_dir, delay_dict, power_dict): + """ Run HSPICE on flipflop spice decks and parse output + It will mutate delay_dict and power_dict so it contains right values """ + + print 'Extracting delay and power in flip_flop' + + # Save my current directory + saved_cwd = os.getcwd() + + # Change to the flip_flop directory + flip_flop_dir = architecture_dir + '/flip_flop/decks' + os.chdir(flip_flop_dir) + + # Run HSPICE on ff_input_select.sp and capture output + ff_input_select_output = open('ff_input_select.output', 'w') + subprocess.call(['hspice', 'ff_input_select.sp'], stdout=ff_input_select_output, stderr=ff_input_select_output) + ff_input_select_output.close() + + # Run HSPICE on ff_input_select2.sp and capture output + ff_input_select2_output = open('ff_input_select2.output', 'w') + subprocess.call(['hspice', 'ff_input_select2.sp'], stdout=ff_input_select2_output, stderr=ff_input_select2_output) + ff_input_select2_output.close() + + # Run HSPICE on flipflop.sp and capture output + flipflop_output = open('flipflop.output', 'w') + subprocess.call(['hspice', 'flipflop.sp'], stdout=flipflop_output, stderr=flipflop_output) + flipflop_output.close() + + # Return to saved directory + os.chdir(saved_cwd) + + # Parse output files + ff_input_select = open(architecture_dir + '/flip_flop/decks/ff_input_select.output', 'r') + lut_to_ff_tfall = 0.0 + lut_to_ff_trise = 0.0 + avg_power_ff_input_select = 0.0 + for line in ff_input_select: + if 'lut_to_ff_tfall' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + lut_to_ff_tfall = convert_spice_num(delay_str) + elif 'lut_to_ff_trise' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + lut_to_ff_trise = convert_spice_num(delay_str) + elif 'avg_power_ff_input_select' in line: + split1 = line.split("=") + split2 = split1[1].split() + power_str = split2[0] + avg_power_ff_input_select = convert_spice_num(power_str) + ff_input_select.close() + + ff_input_select2 = open(architecture_dir + '/flip_flop/decks/ff_input_select2.output', 'r') + C_to_ff_tfall = 0.0 + C_to_ff_trise = 0.0 + for line in ff_input_select2: + if 'c_to_ff_tfall' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + C_to_ff_tfall = convert_spice_num(delay_str) + elif 'c_to_ff_trise' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + C_to_ff_trise = convert_spice_num(delay_str) + ff_input_select2.close() + + flipflop = open(architecture_dir + '/flip_flop/decks/flipflop.output', 'r') + avg_power_ff = 0.0 + for line in flipflop: + if 'avg_power_ff' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + avg_power_ff = convert_spice_num(delay_str) + flipflop.close() + + # Now that we have everything we needed from the output files, + # We choose to keep either trise or tfall for delay value + lut_to_ff = compare_tfall_trise(lut_to_ff_tfall, lut_to_ff_trise) + delay_dict['lut_to_ff'] = lut_to_ff + + C_to_ff = compare_tfall_trise(C_to_ff_tfall, C_to_ff_trise) + delay_dict['C_to_ff'] = C_to_ff + + power_dict['avg_power_ff_input_select'] = avg_power_ff_input_select + power_dict['avg_power_ff'] = avg_power_ff + + +## THIS HAS BEEN MOVED TO UTILS +def compare_tfall_trise(tfall, trise): + """ Compare tfall and trise and returns largest value or -1.0 + -1.0 is return if something went wrong in SPICE """ + + # Initialize output delay + delay = -1.0 + + # Compare tfall and trise + if (tfall == 0.0) or (trise == 0.0): + # Couldn't find one of the values in output + # This is an error because maybe SPICE failed + delay = -1.0 + elif tfall > trise: + if tfall > 0.0: + # tfall is positive and bigger than the trise, this is a valid result + delay = tfall + else: + # Both tfall and trise are negative, this is an invalid result + delay = -1.0 + elif trise >= tfall: + if trise > 0.0: + # trise is positive and larger or equal to tfall, this is value result + delay = trise + else: + delay = -1.0 + else: + delay = -1.0 + + return delay + + +def convert_spice_num(spice_number): + """ Convert a numbers from SPICE output of the form 4.3423p to a float """ + + # Make dictionary of exponent values + exponent_values = {'u' : float(1e-6), + 'n' : float(1e-9), + 'p' : float(1e-12), + 'f' : float(1e-15), + 'a' : float(1e-18)} + # The coefficient is everything but the last character + coefficient = float(spice_number[0:len(spice_number)-1]) + # The exponent is the last char (convert to num with dict) + exponent = exponent_values[spice_number[len(spice_number)-1]] + + return coefficient*exponent + + +def check_no_errors(delay_dict): + """ This function checks for -1.0 in the HSPICE measurements """ + + print 'Checking HSPICE measurements for potential errors...' + + error_counter = 0 + + for key, value in delay_dict.iteritems(): + if value == -1.0: + print 'Found error for: ' + key + error_counter = error_counter + 1 + + if error_counter == 0: + print 'No errors found.' + else: + print 'Found ' + str(error_counter) + ' errors.' + + return error_counter \ No newline at end of file diff --git a/coffe/load_subcircuits.py b/coffe/load_subcircuits.py new file mode 100644 index 0000000..2fcd89b --- /dev/null +++ b/coffe/load_subcircuits.py @@ -0,0 +1,312 @@ +def general_routing_load_generate(spice_filename, wire_length, tile_sb_on, tile_sb_partial, tile_sb_off, tile_cb_on, tile_cb_partial, tile_cb_off): + """ Generates a routing wire load SPICE deck """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + ############################################################### + ## ROUTING WIRE LOAD + ############################################################### + + # First we write the individual tile loads + # Tiles are generated such that if you drive a wire from the left you get + # driver -> tile 4 -> tile 3 -> tile 2 -> tile 1 (driver) -> tile 4 -> etc. + for i in range(wire_length): + spice_file.write("******************************************************************************************\n") + spice_file.write("* Routing wire load tile " + str(i+1) + "\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT routing_wire_load_tile_" + str(i+1) + " n_in n_out n_cb_out n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("Xwire_gen_routing_1 n_in n_1_1 wire Rw='wire_gen_routing_res/" + str(2*wire_length) + "' Cw='wire_gen_routing_cap/" + str(2*wire_length) + "'\n\n") + + # SWITCH BLOCK LOAD + # Write the ON switch block loads + for sb_on in range(tile_sb_on[i]): + # Tile 1 is terminated by a on SB, if this is tile 1 and sb_on 1, we ignore it because we put that one at the end. + if i == 0: + if sb_on != 0: + spice_file.write("Xwire_sb_load_on_" + str(sb_on+1) + " n_1_1 n_1_sb_on_" + str(sb_on+1) + " wire Rw=wire_sb_load_on_res Cw=wire_sb_load_on_cap\n") + spice_file.write("Xsb_load_on_" + str(sb_on+1) + " n_1_sb_on_" + str(sb_on+1) + " n_sb_mux_on_" + str(sb_on+1) + "_hang n_gate n_gate_n n_vdd n_gnd sb_mux_on\n\n") + else: + spice_file.write("Xwire_sb_load_on_" + str(sb_on+1) + " n_1_1 n_1_sb_on_" + str(sb_on+1) + " wire Rw=wire_sb_load_on_res Cw=wire_sb_load_on_cap\n") + spice_file.write("Xsb_load_on_" + str(sb_on+1) + " n_1_sb_on_" + str(sb_on+1) + " n_sb_mux_on_" + str(sb_on+1) + "_hang n_gate n_gate_n n_vdd n_gnd sb_mux_on\n\n") + # Write partially on switch block loads + for sb_partial in range(tile_sb_partial[i]): + spice_file.write("Xwire_sb_load_partial_" + str(sb_partial+1) + " n_1_1 n_1_sb_partial_" + str(sb_partial+1) + " wire Rw=wire_sb_load_partial_res Cw=wire_sb_load_partial_cap\n") + spice_file.write("Xsb_load_partial_" + str(sb_partial+1) + " n_1_sb_partial_" + str(sb_partial+1) + " n_gate n_gate_n n_vdd n_gnd sb_mux_partial\n\n") + # Write off switch block loads + for sb_off in range(tile_sb_off[i]): + spice_file.write("Xwire_sb_load_off_" + str(sb_off+1) + " n_1_1 n_1_sb_off_" + str(sb_off+1) + " wire Rw=wire_sb_load_off_res Cw=wire_sb_load_off_cap\n") + spice_file.write("Xsb_load_off_" + str(sb_off+1) + " n_1_sb_off_" + str(sb_off+1) + " n_gate n_gate_n n_vdd n_gnd sb_mux_off\n\n") + + # CONNECTION BLOCK LOAD + # Write the ON connection block load + for cb_on in range(tile_cb_on[i]): + # If this is tile 1, we need to connect the connection block to the n_cb_out net + if i == 0: + # We only connect one of them + if cb_on == 0: + spice_file.write("Xwire_cb_load_on_" + str(cb_on+1) + " n_1_1 n_1_cb_on_" + str(cb_on+1) + " wire Rw=wire_cb_load_on_res Cw=wire_cb_load_on_cap\n") + spice_file.write("Xcb_load_on_" + str(cb_on+1) + " n_1_cb_on_" + str(cb_on+1) + " n_cb_out n_gate n_gate_n n_vdd n_gnd cb_mux_on\n\n") + else: + spice_file.write("Xwire_cb_load_on_" + str(cb_on+1) + " n_1_1 n_1_cb_on_" + str(cb_on+1) + " wire Rw=wire_cb_load_on_res Cw=wire_cb_load_on_cap\n") + spice_file.write("Xcb_load_on_" + str(cb_on+1) + " n_1_cb_on_" + str(cb_on+1) + " n_cb_mux_on_" + str(cb_on+1) + "_hang n_gate n_gate_n n_vdd n_gnd cb_mux_on\n\n") + # Write partially on connection block loads + for cb_partial in range(tile_cb_partial[i]): + spice_file.write("Xwire_cb_load_partial_" + str(cb_partial+1) + " n_1_1 n_1_cb_partial_" + str(cb_partial+1) + " wire Rw=wire_cb_load_partial_res Cw=wire_cb_load_partial_cap\n") + spice_file.write("Xcb_load_partial_" + str(cb_partial+1) + " n_1_cb_partial_" + str(cb_partial+1) + " n_gate n_gate_n n_vdd n_gnd cb_mux_partial\n\n") + # Write off connection block loads + for cb_off in range(tile_cb_off[i]): + spice_file.write("Xwire_cb_load_off_" + str(cb_off+1) + " n_1_1 n_1_cb_off_" + str(cb_off+1) + " wire Rw=wire_cb_load_off_res Cw=wire_cb_load_off_cap\n") + spice_file.write("Xcb_load_off_" + str(cb_off+1) + " n_1_cb_off_" + str(cb_off+1) + " n_gate n_gate_n n_vdd n_gnd cb_mux_off\n\n") + + # Tile 1 is terminated by a on switch block, other tiles just connect the wire to the output + if i == 0: + spice_file.write("Xwire_gen_routing_2 n_1_1 n_1_2 wire Rw='wire_gen_routing_res/" + str(2*wire_length) + "' Cw='wire_gen_routing_cap/" + str(2*wire_length) + "'\n") + spice_file.write("Xsb_mux_on_out n_1_2 n_out n_gate n_gate_n n_vdd n_gnd sb_mux_on\n") + else: + spice_file.write("Xwire_gen_routing_2 n_1_1 n_out wire Rw='wire_gen_routing_res/" + str(2*wire_length) + "' Cw='wire_gen_routing_cap/" + str(2*wire_length) + "'\n") + + spice_file.write(".ENDS\n\n\n") + + + # Now write a subcircuit for the complete routing wire + spice_file.write("******************************************************************************************\n") + spice_file.write("* Routing wire load tile " + str(i+1) + "\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT routing_wire_load n_in n_out n_cb_out n_gate n_gate_n n_vdd n_gnd\n") + # Iterate through tiles backwards + in_node = "n_in" + for tile in range(wire_length,1,-1): + out_node = "n_" + str(tile) + spice_file.write("Xrouting_wire_load_tile_" + str(tile) + " " + in_node + " " + out_node + " n_hang_" + str(tile) + " n_gate n_gate_n n_vdd n_gnd routing_wire_load_tile_" + str(tile) + "\n") + in_node = out_node + # Write tile 1 + spice_file.write("Xrouting_wire_load_tile_1 " + in_node + " n_out n_cb_out n_gate n_gate_n n_vdd n_gnd routing_wire_load_tile_1\n") + spice_file.write(".ENDS\n\n\n") + + spice_file.close() + + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_gen_routing") + wire_names_list.append("wire_sb_load_on") + wire_names_list.append("wire_sb_load_partial") + wire_names_list.append("wire_sb_load_off") + wire_names_list.append("wire_cb_load_on") + wire_names_list.append("wire_cb_load_partial") + wire_names_list.append("wire_cb_load_off") + + return wire_names_list + + +def local_routing_load_generate(spice_filename, num_on, num_partial, num_off): + """ """ + + # The first thing we want to figure out is the interval between each on load and each partially on load + # Number of partially on muxes between each on mux + interval_partial = int(num_partial/num_on) + # Number of off muxes between each partially on mux + interval_off = int(num_off/num_partial) + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + spice_file.write("******************************************************************************************\n") + spice_file.write("* Local routing wire load\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT local_routing_wire_load n_in n_out n_gate n_gate_n n_vdd n_gnd\n") + + num_total = num_on + num_partial + num_off + interval_counter_partial = 0 + interval_counter_off = 0 + on_counter = 0 + partial_counter = 0 + off_counter = 0 + + # Initialize nodes + current_node = "n_in" + next_node = "n_1" + + # Write SPICE file while keeping correct intervals between partial and on muxes + for i in range(num_total): + if interval_counter_partial == interval_partial and on_counter < num_on: + # Add an on mux + interval_counter_partial = 0 + on_counter = on_counter + 1 + if on_counter == num_on: + spice_file.write("Xwire_local_routing_" + str(i+1) + " " + current_node + " " + next_node + " wire Rw='wire_local_routing_res/" + str(num_total) + "' Cw='wire_local_routing_cap/" + str(num_total) + "'\n") + spice_file.write("Xlocal_mux_on_" + str(on_counter) + " " + next_node + " n_out n_gate n_gate_n n_vdd n_gnd local_mux_on\n") + else: + spice_file.write("Xwire_local_routing_" + str(i+1) + " " + current_node + " " + next_node + " wire Rw='wire_local_routing_res/" + str(num_total) + "' Cw='wire_local_routing_cap/" + str(num_total) + "'\n") + spice_file.write("Xlocal_mux_on_" + str(on_counter) + " " + next_node + " n_hang_" + str(on_counter) + " n_gate n_gate_n n_vdd n_gnd local_mux_on\n") + else: + if interval_counter_off == interval_off and partial_counter < num_partial: + # Add a partially on mux + interval_counter_off = 0 + interval_counter_partial = interval_counter_partial + 1 + partial_counter = partial_counter + 1 + spice_file.write("Xwire_local_routing_" + str(i+1) + " " + current_node + " " + next_node + " wire Rw='wire_local_routing_res/" + str(num_total) + "' Cw='wire_local_routing_cap/" + str(num_total) + "'\n") + spice_file.write("Xlocal_mux_partial_" + str(partial_counter) + " " + next_node + " n_gate n_gate_n n_vdd n_gnd local_mux_partial\n") + else: + # Add an off mux + interval_counter_off = interval_counter_off + 1 + off_counter = off_counter + 1 + spice_file.write("Xwire_local_routing_" + str(i+1) + " " + current_node + " " + next_node + " wire Rw='wire_local_routing_res/" + str(num_total) + "' Cw='wire_local_routing_cap/" + str(num_total) + "'\n") + spice_file.write("Xlocal_mux_off_" + str(off_counter) + " " + next_node + " n_gate n_gate_n n_vdd n_gnd local_mux_off\n") + # Update current and next nodes + current_node = next_node + next_node = "n_" + str(i+2) + spice_file.write(".ENDS\n\n\n") + + spice_file.close() + + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_local_routing") + + return wire_names_list + + +def generate_ble_outputs(spice_filename, num_local_out, num_gen_out): + """ Create the BLE outputs block. Contains 'num_local_out' local outputs and 'num_gen_out' general outputs. """ + + # Total number of BLE outputs + total_outputs = num_local_out + num_gen_out + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + spice_file.write("******************************************************************************************\n") + spice_file.write("* BLE outputs\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT ble_outputs n_1_" + str((total_outputs + 1)/2+1) + " n_local_out n_general_out n_gate n_gate_n n_vdd n_gnd\n") + # Create the BLE output bar + current_node = 2 + for i in range(num_local_out): + if i == 0: + spice_file.write("Xlocal_ble_output_" + str(i+1) + " n_1_" + str(current_node) + " n_local_out n_gate n_gate_n n_vdd n_gnd local_ble_output\n") + else: + spice_file.write("Xlocal_ble_output_" + str(i+1) + " n_1_" + str(current_node) + " n_hang_" + str(current_node) + " n_gate n_gate_n n_vdd n_gnd local_ble_output\n") + spice_file.write("Xwire_ble_outputs_" + str(i+1) + " n_1_" + str(current_node) + " n_1_" + str(current_node + 1) + " wire Rw='wire_ble_outputs_res/" + str(total_outputs-1) + "' Cw='wire_ble_outputs_cap/" + str(total_outputs-1) + "'\n") + current_node = current_node + 1 + for i in range(num_gen_out): + if i == 0: + spice_file.write("Xgeneral_ble_output_" + str(i+1) + " n_1_" + str(current_node) + " n_general_out n_gate n_gate_n n_vdd n_gnd general_ble_output\n") + else: + spice_file.write("Xgeneral_ble_output_" + str(i+1) + " n_1_" + str(current_node) + " n_hang_" + str(current_node) + " n_gate n_gate_n n_vdd n_gnd general_ble_output\n") + # Only add wire if this is not the last ble output. + if (i+1) != num_gen_out: + spice_file.write("Xwire_ble_outputs_" + str(num_local_out+i+1) + " n_1_" + str(current_node) + " n_1_" + str(current_node + 1) + " wire Rw='wire_ble_outputs_res/" + str(total_outputs-1) + "' Cw='wire_ble_outputs_cap/" + str(total_outputs-1) + "'\n") + current_node = current_node + 1 + spice_file.write(".ENDS\n\n\n") + + spice_file.close() + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_ble_outputs") + + return wire_names_list + + +def generate_lut_output_load(spice_filename, num_local_out, num_gen_out): + """ Create the LUT output load subcircuit. It consists of a FF and all BLE outputs. """ + + # Total number of BLE outputs + total_outputs = num_local_out + num_gen_out + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + spice_file.write("******************************************************************************************\n") + spice_file.write("* LUT output load\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT lut_output_load n_in n_local_out n_general_out n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("Xwire_lut_output_load_1 n_in n_1_1 wire Rw='wire_lut_output_load_1_res' Cw='wire_lut_output_load_1_cap'\n") + spice_file.write("Xff n_1_1 n_hang1 n_gate n_gate_n n_vdd n_gnd n_gnd n_vdd n_gnd n_vdd n_vdd n_gnd ff\n") + spice_file.write("Xwire_lut_output_load_2 n_1_1 n_1_2 wire Rw='wire_lut_output_load_2_res' Cw='wire_lut_output_load_2_cap'\n") + spice_file.write("Xble_outputs n_1_2 n_local_out n_general_out n_gate n_gate_n n_vdd n_gnd ble_outputs\n") + spice_file.write(".ENDS\n\n\n") + + spice_file.close() + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_lut_output_load_1") + wire_names_list.append("wire_lut_output_load_2") + + return wire_names_list + + +def generate_local_ble_output_load(spice_filename): + """ """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + spice_file.write("******************************************************************************************\n") + spice_file.write("* Local BLE output load\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT local_ble_output_load n_in n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("Xwire_local_ble_output_feedback n_in n_1_1 wire Rw='wire_local_ble_output_feedback_res' Cw='wire_local_ble_output_feedback_cap'\n") + spice_file.write("Xlocal_routing_wire_load_1 n_1_1 n_1_2 n_gate n_gate_n n_vdd n_gnd local_routing_wire_load\n") + spice_file.write("Xlut_a_driver_1 n_1_2 n_hang1 vsram vsram_n n_hang2 n_hang3 n_vdd n_gnd lut_a_driver\n\n") + spice_file.write(".ENDS\n\n\n") + + spice_file.close() + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_local_ble_output_feedback") + + return wire_names_list + + +def generate_general_ble_output_load(spice_filename, num_sb_mux_off, num_sb_mux_partial, num_sb_mux_on): + """ Create the cluster output load SPICE deck. We assume 2-level muxes. The load is distributed as + off, then partial, then on. + Inputs are SPICE file, number of SB muxes that are off, then partially on, then on. + Returns wire names used in this SPICE circuit.""" + + # Total number of sb muxes connected to this logic cluster output + sb_mux_total = num_sb_mux_off + num_sb_mux_partial + num_sb_mux_on + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + spice_file.write("******************************************************************************************\n") + spice_file.write("* General BLE output load\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT general_ble_output_load n_1_1 n_out n_gate n_gate_n n_vdd n_gnd\n") + current_node = "n_1_1" + next_node = "n_1_2" + for i in range(num_sb_mux_off): + spice_file.write("Xwire_general_ble_output_" + str(i+1) + " " + current_node + " " + next_node + " wire Rw='wire_general_ble_output_res/" + str(sb_mux_total) + "' Cw='wire_general_ble_output_cap/" + str(sb_mux_total) + "'\n") + spice_file.write("Xsb_mux_off_" + str(i+1) + " " + next_node + " n_gate n_gate_n n_vdd n_gnd sb_mux_off\n") + current_node = next_node + next_node = "n_1_" + str(i+3) + for i in range(num_sb_mux_partial): + spice_file.write("Xwire_general_ble_output_" + str(i+num_sb_mux_off+1) + " " + current_node + " " + next_node + " wire Rw='wire_general_ble_output_res/" + str(sb_mux_total) + "' Cw='wire_general_ble_output_cap/" + str(sb_mux_total) + "'\n") + spice_file.write("Xsb_mux_partial_" + str(i+1) + " " + next_node + " n_gate n_gate_n n_vdd n_gnd sb_mux_partial\n") + current_node = next_node + next_node = "n_1_" + str(i+num_sb_mux_off+3) + for i in range(num_sb_mux_on): + spice_file.write("Xwire_general_ble_output_" + str(i+num_sb_mux_off+num_sb_mux_partial+1) + " " + current_node + " " + next_node + " wire Rw='wire_general_ble_output_res/" + str(sb_mux_total) + "' Cw='wire_general_ble_output_cap/" + str(sb_mux_total) + "'\n") + if i == (num_sb_mux_on-1): + spice_file.write("Xsb_mux_on_" + str(i+1) + " " + next_node + " n_out n_gate n_gate_n n_vdd n_gnd sb_mux_on\n") + else: + spice_file.write("Xsb_mux_on_" + str(i+1) + " " + next_node + " n_hang_" + str(i) + " n_gate n_gate_n n_vdd n_gnd sb_mux_on\n") + current_node = next_node + next_node = "n_1_" + str(i+num_sb_mux_off+num_sb_mux_partial+3) + + spice_file.write(".ENDS\n\n\n") + + spice_file.close() + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_general_ble_output") + + return wire_names_list \ No newline at end of file diff --git a/coffe/lut_subcircuits.py b/coffe/lut_subcircuits.py new file mode 100644 index 0000000..1abd872 --- /dev/null +++ b/coffe/lut_subcircuits.py @@ -0,0 +1,441 @@ +import math + +# Note: We generate all LUT netlists with a 6-LUT interface regardless of LUT size. +# This makes it easier to include the LUT circuitry in other (top-level) netlists. +# For example, a 5-LUT has the following interface: +# "lut n_in n_out n_a n_b n_c n_d n_e n_f n_vdd n_gnd", which is the same thing as +# a 6-LUT, but in the 5-LUT case, there is nothing connected to "n_f" inside the LUT +# netlist itself. + +def generate_ptran_lut6(spice_filename, min_tran_width): + """ Generates a 6LUT SPICE deck """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the 6-LUT circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* 6-LUT subcircuit \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT lut n_in n_out n_a n_b n_c n_d n_e n_f n_vdd n_gnd\n") + Wn = min_tran_width + Wp = 1.667*min_tran_width + spice_file.write("Xinv_lut_sram_driver_1 n_in n_1_1 n_vdd n_gnd inv Wn=" + str(Wn) + "n Wp=" + str(Wp) + "n\n") + spice_file.write("Xwire_lut_sram_driver n_1_1 n_1_2 wire Rw=wire_lut_sram_driver_res Cw=wire_lut_sram_driver_cap\n") + spice_file.write("Xinv_lut_sram_driver_2 n_1_2 n_2_1 n_vdd n_gnd inv Wn=inv_lut_0sram_driver_2_nmos Wp=inv_lut_0sram_driver_2_pmos\n") + spice_file.write("Xwire_lut_sram_driver_out n_2_1 n_2_2 wire Rw=wire_lut_sram_driver_out_res Cw=wire_lut_sram_driver_out_cap\n\n") + + spice_file.write("* First chain\n") + spice_file.write("Xptran_lut_L1 n_2_2 n_3_1 n_a n_gnd ptran Wn=ptran_lut_L1_nmos\n") + spice_file.write("Xwire_lut_L1 n_3_1 n_3_2 wire Rw='wire_lut_L1_res/2' Cw='wire_lut_L1_cap/2'\n") + spice_file.write("Xwire_lut_L1h n_3_2 n_3_3 wire Rw='wire_lut_L1_res/2' Cw='wire_lut_L1_cap/2'\n") + spice_file.write("Xptran_lut_L1h n_gnd n_3_3 n_gnd n_gnd ptran Wn=ptran_lut_L1_nmos\n") + spice_file.write("Xptran_lut_L2 n_3_2 n_4_1 n_b n_gnd ptran Wn=ptran_lut_L2_nmos\n") + spice_file.write("Xwire_lut_L2 n_4_1 n_4_2 wire Rw='wire_lut_L2_res/2' Cw='wire_lut_L2_cap/2'\n") + spice_file.write("Xwire_lut_L2h n_4_2 n_4_3 wire Rw='wire_lut_L2_res/2' Cw='wire_lut_L2_cap/2'\n") + spice_file.write("Xptran_lut_L2h n_gnd n_4_3 n_gnd n_gnd ptran Wn=ptran_lut_L2_nmos\n") + spice_file.write("Xptran_lut_L3 n_4_2 n_5_1 n_c n_gnd ptran Wn=ptran_lut_L3_nmos\n") + spice_file.write("Xwire_lut_L3 n_5_1 n_5_2 wire Rw='wire_lut_L3_res/2' Cw='wire_lut_L3_cap/2'\n") + spice_file.write("Xwire_lut_L3h n_5_2 n_5_3 wire Rw='wire_lut_L3_res/2' Cw='wire_lut_L3_cap/2'\n") + spice_file.write("Xptran_lut_L3h n_gnd n_5_3 n_gnd n_gnd ptran Wn=ptran_lut_L3_nmos\n\n") + + spice_file.write("* Internal buffer \n") + spice_file.write("Xrest_lut_int_buffer n_5_2 n_6_1 n_vdd n_gnd rest Wp=rest_lut_int_buffer_pmos\n") + spice_file.write("Xinv_lut_int_buffer_1 n_5_2 n_6_1 n_vdd n_gnd inv Wn=inv_lut_int_buffer_1_nmos Wp=inv_lut_int_buffer_1_pmos\n") + spice_file.write("Xwire_lut_int_buffer n_6_1 n_6_2 wire Rw=wire_lut_int_buffer_res Cw=wire_lut_int_buffer_cap\n") + spice_file.write("Xinv_lut_int_buffer_2 n_6_2 n_7_1 n_vdd n_gnd inv Wn=inv_lut_int_buffer_2_nmos Wp=inv_lut_int_buffer_2_pmos\n") + spice_file.write("Xwire_lut_int_buffer_out n_7_1 n_7_2 wire Rw=wire_lut_int_buffer_out_res Cw=wire_lut_int_buffer_out_cap\n\n") + + spice_file.write("* Second chain\n") + spice_file.write("Xptran_lut_L4 n_7_2 n_8_1 n_d n_gnd ptran Wn=ptran_lut_L4_nmos\n") + spice_file.write("Xwire_lut_L4 n_8_1 n_8_2 wire Rw='wire_lut_L4_res/2' Cw='wire_lut_L4_cap/2'\n") + spice_file.write("Xwire_lut_L4h n_8_2 n_8_3 wire Rw='wire_lut_L4_res/2' Cw='wire_lut_L4_cap/2'\n") + spice_file.write("Xptran_lut_L4h n_gnd n_8_3 n_gnd n_gnd ptran Wn=ptran_lut_L4_nmos\n") + spice_file.write("Xptran_lut_L5 n_8_2 n_9_1 n_e n_gnd ptran Wn=ptran_lut_L5_nmos\n") + spice_file.write("Xwire_lut_L5 n_9_1 n_9_2 wire Rw='wire_lut_L5_res/2' Cw='wire_lut_L5_cap/2'\n") + spice_file.write("Xwire_lut_L5h n_9_2 n_9_3 wire Rw='wire_lut_L5_res/2' Cw='wire_lut_L5_cap/2'\n") + spice_file.write("Xptran_lut_L5h n_gnd n_9_3 n_gnd n_gnd ptran Wn=ptran_lut_L5_nmos\n") + spice_file.write("Xptran_lut_L6 n_9_2 n_10_1 n_f n_gnd ptran Wn=ptran_lut_L6_nmos\n") + spice_file.write("Xwire_lut_L6 n_10_1 n_10_2 wire Rw='wire_lut_L6_res/2' Cw='wire_lut_L6_cap/2'\n") + spice_file.write("Xwire_lut_L6h n_10_2 n_10_3 wire Rw='wire_lut_L6_res/2' Cw='wire_lut_L6_cap/2'\n") + spice_file.write("Xptran_lut_L6h n_gnd n_10_3 n_gnd n_gnd ptran Wn=ptran_lut_L6_nmos\n\n") + + spice_file.write("* Output buffer \n") + spice_file.write("Xrest_lut_out_buffer n_10_2 n_11_1 n_vdd n_gnd rest Wp=rest_lut_out_buffer_pmos\n") + spice_file.write("Xinv_lut_out_buffer_1 n_10_2 n_11_1 n_vdd n_gnd inv Wn=inv_lut_out_buffer_1_nmos Wp=inv_lut_out_buffer_1_pmos\n") + spice_file.write("Xwire_lut_out_buffer n_11_1 n_11_2 wire Rw=wire_lut_out_buffer_res Cw=wire_lut_out_buffer_cap\n") + spice_file.write("Xinv_lut_out_buffer_2 n_11_2 n_out n_vdd n_gnd inv Wn=inv_lut_out_buffer_2_nmos Wp=inv_lut_out_buffer_2_pmos\n\n") + spice_file.write(".ENDS\n\n\n") + + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("inv_lut_0sram_driver_2_nmos") + tran_names_list.append("inv_lut_0sram_driver_2_pmos") + tran_names_list.append("ptran_lut_L1_nmos") + tran_names_list.append("ptran_lut_L2_nmos") + tran_names_list.append("ptran_lut_L3_nmos") + tran_names_list.append("rest_lut_int_buffer_pmos") + tran_names_list.append("inv_lut_int_buffer_1_nmos") + tran_names_list.append("inv_lut_int_buffer_1_pmos") + tran_names_list.append("inv_lut_int_buffer_2_nmos") + tran_names_list.append("inv_lut_int_buffer_2_pmos") + tran_names_list.append("ptran_lut_L4_nmos") + tran_names_list.append("ptran_lut_L5_nmos") + tran_names_list.append("ptran_lut_L6_nmos") + tran_names_list.append("rest_lut_out_buffer_pmos") + tran_names_list.append("inv_lut_out_buffer_1_nmos") + tran_names_list.append("inv_lut_out_buffer_1_pmos") + tran_names_list.append("inv_lut_out_buffer_2_nmos") + tran_names_list.append("inv_lut_out_buffer_2_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_lut_sram_driver") + wire_names_list.append("wire_lut_sram_driver_out") + wire_names_list.append("wire_lut_L1") + wire_names_list.append("wire_lut_L2") + wire_names_list.append("wire_lut_L3") + wire_names_list.append("wire_lut_int_buffer") + wire_names_list.append("wire_lut_int_buffer_out") + wire_names_list.append("wire_lut_L4") + wire_names_list.append("wire_lut_L5") + wire_names_list.append("wire_lut_L6") + wire_names_list.append("wire_lut_out_buffer") + + return tran_names_list, wire_names_list + + +def generate_ptran_lut5(spice_filename, min_tran_width): + """ Generates a 5LUT SPICE deck """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the 5-LUT circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* 5-LUT subcircuit \n") + spice_file.write("******************************************************************************************\n") + # We use a 6-LUT interface even if this is a 5-LUT. We just won't connect anything to "n_f". + spice_file.write(".SUBCKT lut n_in n_out n_a n_b n_c n_d n_e n_f n_vdd n_gnd\n") + Wn = min_tran_width + Wp = 1.667*min_tran_width + spice_file.write("Xinv_lut_sram_driver_1 n_in n_1_1 n_vdd n_gnd inv Wn=" + str(Wn) + "n Wp=" + str(Wp) + "n\n") + spice_file.write("Xwire_lut_sram_driver n_1_1 n_1_2 wire Rw=wire_lut_sram_driver_res Cw=wire_lut_sram_driver_cap\n") + spice_file.write("Xinv_lut_sram_driver_2 n_1_2 n_2_1 n_vdd n_gnd inv Wn=inv_lut_0sram_driver_2_nmos Wp=inv_lut_0sram_driver_2_pmos\n") + spice_file.write("Xwire_lut_sram_driver_out n_2_1 n_2_2 wire Rw=wire_lut_sram_driver_out_res Cw=wire_lut_sram_driver_out_cap\n\n") + + spice_file.write("* First chain\n") + spice_file.write("Xptran_lut_L1 n_2_2 n_3_1 n_a n_gnd ptran Wn=ptran_lut_L1_nmos\n") + spice_file.write("Xwire_lut_L1 n_3_1 n_3_2 wire Rw='wire_lut_L1_res/2' Cw='wire_lut_L1_cap/2'\n") + spice_file.write("Xwire_lut_L1h n_3_2 n_3_3 wire Rw='wire_lut_L1_res/2' Cw='wire_lut_L1_cap/2'\n") + spice_file.write("Xptran_lut_L1h n_gnd n_3_3 n_gnd n_gnd ptran Wn=ptran_lut_L1_nmos\n") + spice_file.write("Xptran_lut_L2 n_3_2 n_4_1 n_b n_gnd ptran Wn=ptran_lut_L2_nmos\n") + spice_file.write("Xwire_lut_L2 n_4_1 n_4_2 wire Rw='wire_lut_L2_res/2' Cw='wire_lut_L2_cap/2'\n") + spice_file.write("Xwire_lut_L2h n_4_2 n_4_3 wire Rw='wire_lut_L2_res/2' Cw='wire_lut_L2_cap/2'\n") + spice_file.write("Xptran_lut_L2h n_gnd n_4_3 n_gnd n_gnd ptran Wn=ptran_lut_L2_nmos\n") + spice_file.write("Xptran_lut_L3 n_4_2 n_5_1 n_c n_gnd ptran Wn=ptran_lut_L3_nmos\n") + spice_file.write("Xwire_lut_L3 n_5_1 n_5_2 wire Rw='wire_lut_L3_res/2' Cw='wire_lut_L3_cap/2'\n") + spice_file.write("Xwire_lut_L3h n_5_2 n_5_3 wire Rw='wire_lut_L3_res/2' Cw='wire_lut_L3_cap/2'\n") + spice_file.write("Xptran_lut_L3h n_gnd n_5_3 n_gnd n_gnd ptran Wn=ptran_lut_L3_nmos\n\n") + + spice_file.write("* Internal buffer \n") + spice_file.write("Xrest_lut_int_buffer n_5_2 n_6_1 n_vdd n_gnd rest Wp=rest_lut_int_buffer_pmos\n") + spice_file.write("Xinv_lut_int_buffer_1 n_5_2 n_6_1 n_vdd n_gnd inv Wn=inv_lut_int_buffer_1_nmos Wp=inv_lut_int_buffer_1_pmos\n") + spice_file.write("Xwire_lut_int_buffer n_6_1 n_6_2 wire Rw=wire_lut_int_buffer_res Cw=wire_lut_int_buffer_cap\n") + spice_file.write("Xinv_lut_int_buffer_2 n_6_2 n_7_1 n_vdd n_gnd inv Wn=inv_lut_int_buffer_2_nmos Wp=inv_lut_int_buffer_2_pmos\n") + spice_file.write("Xwire_lut_int_buffer_out n_7_1 n_7_2 wire Rw=wire_lut_int_buffer_out_res Cw=wire_lut_int_buffer_out_cap\n\n") + + spice_file.write("* Second chain\n") + spice_file.write("Xptran_lut_L4 n_7_2 n_8_1 n_d n_gnd ptran Wn=ptran_lut_L4_nmos\n") + spice_file.write("Xwire_lut_L4 n_8_1 n_8_2 wire Rw='wire_lut_L4_res/2' Cw='wire_lut_L4_cap/2'\n") + spice_file.write("Xwire_lut_L4h n_8_2 n_8_3 wire Rw='wire_lut_L4_res/2' Cw='wire_lut_L4_cap/2'\n") + spice_file.write("Xptran_lut_L4h n_gnd n_8_3 n_gnd n_gnd ptran Wn=ptran_lut_L4_nmos\n") + spice_file.write("Xptran_lut_L5 n_8_2 n_9_1 n_e n_gnd ptran Wn=ptran_lut_L5_nmos\n") + spice_file.write("Xwire_lut_L5 n_9_1 n_9_2 wire Rw='wire_lut_L5_res/2' Cw='wire_lut_L5_cap/2'\n") + spice_file.write("Xwire_lut_L5h n_9_2 n_9_3 wire Rw='wire_lut_L5_res/2' Cw='wire_lut_L5_cap/2'\n") + spice_file.write("Xptran_lut_L5h n_gnd n_9_3 n_gnd n_gnd ptran Wn=ptran_lut_L5_nmos\n") + + spice_file.write("* Output buffer \n") + spice_file.write("Xrest_lut_out_buffer n_9_2 n_11_1 n_vdd n_gnd rest Wp=rest_lut_out_buffer_pmos\n") + spice_file.write("Xinv_lut_out_buffer_1 n_9_2 n_11_1 n_vdd n_gnd inv Wn=inv_lut_out_buffer_1_nmos Wp=inv_lut_out_buffer_1_pmos\n") + spice_file.write("Xwire_lut_out_buffer n_11_1 n_11_2 wire Rw=wire_lut_out_buffer_res Cw=wire_lut_out_buffer_cap\n") + spice_file.write("Xinv_lut_out_buffer_2 n_11_2 n_out n_vdd n_gnd inv Wn=inv_lut_out_buffer_2_nmos Wp=inv_lut_out_buffer_2_pmos\n\n") + spice_file.write(".ENDS\n\n\n") + + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("inv_lut_0sram_driver_2_nmos") + tran_names_list.append("inv_lut_0sram_driver_2_pmos") + tran_names_list.append("ptran_lut_L1_nmos") + tran_names_list.append("ptran_lut_L2_nmos") + tran_names_list.append("ptran_lut_L3_nmos") + tran_names_list.append("rest_lut_int_buffer_pmos") + tran_names_list.append("inv_lut_int_buffer_1_nmos") + tran_names_list.append("inv_lut_int_buffer_1_pmos") + tran_names_list.append("inv_lut_int_buffer_2_nmos") + tran_names_list.append("inv_lut_int_buffer_2_pmos") + tran_names_list.append("ptran_lut_L4_nmos") + tran_names_list.append("ptran_lut_L5_nmos") + tran_names_list.append("rest_lut_out_buffer_pmos") + tran_names_list.append("inv_lut_out_buffer_1_nmos") + tran_names_list.append("inv_lut_out_buffer_1_pmos") + tran_names_list.append("inv_lut_out_buffer_2_nmos") + tran_names_list.append("inv_lut_out_buffer_2_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_lut_sram_driver") + wire_names_list.append("wire_lut_sram_driver_out") + wire_names_list.append("wire_lut_L1") + wire_names_list.append("wire_lut_L2") + wire_names_list.append("wire_lut_L3") + wire_names_list.append("wire_lut_int_buffer") + wire_names_list.append("wire_lut_int_buffer_out") + wire_names_list.append("wire_lut_L4") + wire_names_list.append("wire_lut_L5") + wire_names_list.append("wire_lut_out_buffer") + + return tran_names_list, wire_names_list + + +def generate_ptran_lut4(spice_filename, min_tran_width): + """ Generates a 4LUT SPICE deck """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the 4-LUT circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* 4-LUT subcircuit \n") + spice_file.write("******************************************************************************************\n") + # We use a 6-LUT interface even if this is a 4-LUT. We just won't connect anything to "n_e" and "n_f". + spice_file.write(".SUBCKT lut n_in n_out n_a n_b n_c n_d n_e n_f n_vdd n_gnd\n") + Wn = min_tran_width + Wp = 1.667*min_tran_width + spice_file.write("Xinv_lut_sram_driver_1 n_in n_1_1 n_vdd n_gnd inv Wn=" + str(Wn) + "n Wp=" + str(Wp) + "n\n") + spice_file.write("Xwire_lut_sram_driver n_1_1 n_1_2 wire Rw=wire_lut_sram_driver_res Cw=wire_lut_sram_driver_cap\n") + spice_file.write("Xinv_lut_sram_driver_2 n_1_2 n_2_1 n_vdd n_gnd inv Wn=inv_lut_0sram_driver_2_nmos Wp=inv_lut_0sram_driver_2_pmos\n") + spice_file.write("Xwire_lut_sram_driver_out n_2_1 n_2_2 wire Rw=wire_lut_sram_driver_out_res Cw=wire_lut_sram_driver_out_cap\n\n") + + spice_file.write("* First chain\n") + spice_file.write("Xptran_lut_L1 n_2_2 n_3_1 n_a n_gnd ptran Wn=ptran_lut_L1_nmos\n") + spice_file.write("Xwire_lut_L1 n_3_1 n_3_2 wire Rw='wire_lut_L1_res/2' Cw='wire_lut_L1_cap/2'\n") + spice_file.write("Xwire_lut_L1h n_3_2 n_3_3 wire Rw='wire_lut_L1_res/2' Cw='wire_lut_L1_cap/2'\n") + spice_file.write("Xptran_lut_L1h n_gnd n_3_3 n_gnd n_gnd ptran Wn=ptran_lut_L1_nmos\n") + spice_file.write("Xptran_lut_L2 n_3_2 n_4_1 n_b n_gnd ptran Wn=ptran_lut_L2_nmos\n") + spice_file.write("Xwire_lut_L2 n_4_1 n_4_2 wire Rw='wire_lut_L2_res/2' Cw='wire_lut_L2_cap/2'\n") + spice_file.write("Xwire_lut_L2h n_4_2 n_4_3 wire Rw='wire_lut_L2_res/2' Cw='wire_lut_L2_cap/2'\n") + spice_file.write("Xptran_lut_L2h n_gnd n_4_3 n_gnd n_gnd ptran Wn=ptran_lut_L2_nmos\n\n") + + spice_file.write("* Internal buffer \n") + spice_file.write("Xrest_lut_int_buffer n_4_2 n_5_1 n_vdd n_gnd rest Wp=rest_lut_int_buffer_pmos\n") + spice_file.write("Xinv_lut_int_buffer_1 n_4_2 n_5_1 n_vdd n_gnd inv Wn=inv_lut_int_buffer_1_nmos Wp=inv_lut_int_buffer_1_pmos\n") + spice_file.write("Xwire_lut_int_buffer n_5_1 n_5_2 wire Rw=wire_lut_int_buffer_res Cw=wire_lut_int_buffer_cap\n") + spice_file.write("Xinv_lut_int_buffer_2 n_5_2 n_6_1 n_vdd n_gnd inv Wn=inv_lut_int_buffer_2_nmos Wp=inv_lut_int_buffer_2_pmos\n") + spice_file.write("Xwire_lut_int_buffer_out n_6_1 n_6_2 wire Rw=wire_lut_int_buffer_out_res Cw=wire_lut_int_buffer_out_cap\n\n") + + spice_file.write("* Second chain\n") + spice_file.write("Xptran_lut_L3 n_6_2 n_7_1 n_c n_gnd ptran Wn=ptran_lut_L3_nmos\n") + spice_file.write("Xwire_lut_L3 n_7_1 n_7_2 wire Rw='wire_lut_L3_res/2' Cw='wire_lut_L3_cap/2'\n") + spice_file.write("Xwire_lut_L3h n_7_2 n_7_3 wire Rw='wire_lut_L3_res/2' Cw='wire_lut_L3_cap/2'\n") + spice_file.write("Xptran_lut_L3h n_gnd n_7_3 n_gnd n_gnd ptran Wn=ptran_lut_L3_nmos\n") + spice_file.write("Xptran_lut_L4 n_7_2 n_8_1 n_d n_gnd ptran Wn=ptran_lut_L4_nmos\n") + spice_file.write("Xwire_lut_L4 n_8_1 n_8_2 wire Rw='wire_lut_L4_res/2' Cw='wire_lut_L4_cap/2'\n") + spice_file.write("Xwire_lut_L4h n_8_2 n_8_3 wire Rw='wire_lut_L4_res/2' Cw='wire_lut_L4_cap/2'\n") + spice_file.write("Xptran_lut_L4h n_gnd n_8_3 n_gnd n_gnd ptran Wn=ptran_lut_L4_nmos\n\n") + + spice_file.write("* Output buffer \n") + spice_file.write("Xrest_lut_out_buffer n_8_2 n_9_1 n_vdd n_gnd rest Wp=rest_lut_out_buffer_pmos\n") + spice_file.write("Xinv_lut_out_buffer_1 n_8_2 n_9_1 n_vdd n_gnd inv Wn=inv_lut_out_buffer_1_nmos Wp=inv_lut_out_buffer_1_pmos\n") + spice_file.write("Xwire_lut_out_buffer n_9_1 n_9_2 wire Rw=wire_lut_out_buffer_res Cw=wire_lut_out_buffer_cap\n") + spice_file.write("Xinv_lut_out_buffer_2 n_9_2 n_out n_vdd n_gnd inv Wn=inv_lut_out_buffer_2_nmos Wp=inv_lut_out_buffer_2_pmos\n\n") + spice_file.write(".ENDS\n\n\n") + + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("inv_lut_0sram_driver_2_nmos") + tran_names_list.append("inv_lut_0sram_driver_2_pmos") + tran_names_list.append("ptran_lut_L1_nmos") + tran_names_list.append("ptran_lut_L2_nmos") + tran_names_list.append("rest_lut_int_buffer_pmos") + tran_names_list.append("inv_lut_int_buffer_1_nmos") + tran_names_list.append("inv_lut_int_buffer_1_pmos") + tran_names_list.append("inv_lut_int_buffer_2_nmos") + tran_names_list.append("inv_lut_int_buffer_2_pmos") + tran_names_list.append("ptran_lut_L3_nmos") + tran_names_list.append("ptran_lut_L4_nmos") + tran_names_list.append("rest_lut_out_buffer_pmos") + tran_names_list.append("inv_lut_out_buffer_1_nmos") + tran_names_list.append("inv_lut_out_buffer_1_pmos") + tran_names_list.append("inv_lut_out_buffer_2_nmos") + tran_names_list.append("inv_lut_out_buffer_2_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_lut_sram_driver") + wire_names_list.append("wire_lut_sram_driver_out") + wire_names_list.append("wire_lut_L1") + wire_names_list.append("wire_lut_L2") + wire_names_list.append("wire_lut_int_buffer") + wire_names_list.append("wire_lut_int_buffer_out") + wire_names_list.append("wire_lut_L3") + wire_names_list.append("wire_lut_L4") + wire_names_list.append("wire_lut_out_buffer") + + return tran_names_list, wire_names_list + + +def generate_ptran_lut_driver(spice_filename, lut_input_name, lut_input_type): + """ Generate a pass-transistor LUT driver based on type. """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the LUT-input circuit header (same interface for all LUT input types) + spice_file.write("******************************************************************************************\n") + spice_file.write("* LUT " + lut_input_name + " subcircuit \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + lut_input_name + " n_in n_out n_gate n_gate_n n_rsel n_not_input n_vdd n_gnd\n") + + # The next element is the input driver. + # We only need an input driver if this is not a "default" input. + if lut_input_type != "default": + spice_file.write("Xinv_" + lut_input_name + "_0 n_in n_1_1 n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_0_nmos Wp=inv_" + lut_input_name + "_0_pmos\n") + # If the LUT input is connected to register select, we add a wire and connect it to the rsel node. + if lut_input_type == "default_rsel" or lut_input_type == "reg_fb_rsel": + spice_file.write("Xwire_" + lut_input_name + "_0_rsel n_1_1 n_rsel wire Rw=wire_" + lut_input_name + "_0_rsel_res Cw=wire_" + lut_input_name + "_0_rsel_cap\n") + # Next is the wire connecting the input inverter to either the output buffer (for "default_rsel") + # or the pass-transistor (for both "reg_fb" and "reg_fb_rsel"). + spice_file.write("Xwire_" + lut_input_name + "_0_out n_1_1 n_1_2 wire Rw=wire_" + lut_input_name + "_0_out_res Cw=wire_" + lut_input_name + "_0_out_cap\n") + # Now we add a sense inverter for "default_rsel" or the ptran level-restorer and sense inv in the case of both "reg_fb" + if lut_input_type == "default_rsel": + spice_file.write("Xinv_" + lut_input_name + "_1 n_1_2 n_3_1 n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_1_nmos Wp=inv_" + lut_input_name + "_1_pmos\n") + else: + spice_file.write("Xptran_" + lut_input_name + "_0 n_1_2 n_2_1 n_gate n_gnd ptran Wn=ptran_" + lut_input_name + "_0_nmos\n") + spice_file.write("Xwire_" + lut_input_name + "_0 n_2_1 n_2_2 wire Rw='wire_" + lut_input_name + "_0_res/2' Cw='wire_" + lut_input_name + "_0_cap/2'\n") + spice_file.write("Xwire_" + lut_input_name + "_0h n_2_2 n_2_3 wire Rw='wire_" + lut_input_name + "_0_res/2' Cw='wire_" + lut_input_name + "_0_cap/2'\n") + spice_file.write("Xptran_" + lut_input_name + "_0h n_gnd n_2_3 n_gate_n n_gnd ptran Wn=ptran_" + lut_input_name + "_0_nmos\n") + spice_file.write("Xrest_" + lut_input_name + " n_2_2 n_3_1 n_vdd n_gnd rest Wp=rest_" + lut_input_name + "_pmos\n") + spice_file.write("Xinv_" + lut_input_name + "_1 n_2_2 n_3_1 n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_1_nmos Wp=inv_" + lut_input_name + "_1_pmos\n") + # Now we add the wire that connects the sense inverter to the driving inverter + spice_file.write("Xwire_" + lut_input_name + " n_3_1 n_not_input wire Rw=wire_" + lut_input_name + "_res Cw=wire_" + lut_input_name + "_cap\n") + # Finally, the driving inverter + spice_file.write("Xinv_" + lut_input_name + "_2 n_not_input n_out n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_2_nmos Wp=inv_" + lut_input_name + "_2_pmos\n") + # If this is a default input, all we need is a wire and a driver. But, we are careful to use the same names for corresponding nodes. + # This is convenient for writing the top-level SPICE file. + else: + # Add the wire that connects the local mux to the driver + spice_file.write("Xwire_" + lut_input_name + " n_in n_not_input wire Rw=wire_" + lut_input_name + "_res Cw=wire_" + lut_input_name + "_cap\n") + # Add the driving inverter + spice_file.write("Xinv_" + lut_input_name + "_2 n_not_input n_out n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_2_nmos Wp=inv_" + lut_input_name + "_2_pmos\n") + + # End the subcircuit + spice_file.write(".ENDS\n\n\n") + + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + if lut_input_type != "default": + tran_names_list.append("inv_" + lut_input_name + "_0_nmos") + tran_names_list.append("inv_" + lut_input_name + "_0_pmos") + if lut_input_type == "reg_fb" or lut_input_type == "reg_fb_rsel": + tran_names_list.append("ptran_" + lut_input_name + "_0_nmos") + tran_names_list.append("rest_" + lut_input_name + "_pmos") + if lut_input_type != "default": + tran_names_list.append("inv_" + lut_input_name + "_1_nmos") + tran_names_list.append("inv_" + lut_input_name + "_1_pmos") + tran_names_list.append("inv_" + lut_input_name + "_2_nmos") + tran_names_list.append("inv_" + lut_input_name + "_2_pmos") + + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + if lut_input_type == "default_rsel" or lut_input_type == "reg_fb_rsel": + wire_names_list.append("wire_" + lut_input_name + "_0_rsel") + if lut_input_type != "default": + wire_names_list.append("wire_" + lut_input_name + "_0_out") + if lut_input_type == "reg_fb" or lut_input_type == "reg_fb_rsel": + wire_names_list.append("wire_" + lut_input_name + "_0") + wire_names_list.append("wire_" + lut_input_name) + + return tran_names_list, wire_names_list + + +def generate_ptran_lut_not_driver(spice_filename, lut_input_name): + """ Generate a pass-transistor LUT driver based on type. """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the LUT-input circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* LUT " + lut_input_name + " subcircuit \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + lut_input_name + " n_in n_out n_vdd n_gnd\n") + spice_file.write("Xinv_" + lut_input_name + "_1 n_in n_1_1 n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_1_nmos Wp=inv_" + lut_input_name + "_1_pmos\n") + spice_file.write("Xwire_" + lut_input_name + " n_1_1 n_1_2 wire Rw=wire_" + lut_input_name + "_res Cw=wire_" + lut_input_name + "_cap\n") + spice_file.write("Xinv_" + lut_input_name + "_2 n_1_2 n_out n_vdd n_gnd inv Wn=inv_" + lut_input_name + "_2_nmos Wp=inv_" + lut_input_name + "_2_pmos\n") + spice_file.write(".ENDS\n\n\n") + + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("inv_" + lut_input_name + "_1_nmos") + tran_names_list.append("inv_" + lut_input_name + "_1_pmos") + tran_names_list.append("inv_" + lut_input_name + "_2_nmos") + tran_names_list.append("inv_" + lut_input_name + "_2_pmos") + + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_" + lut_input_name) + + return tran_names_list, wire_names_list + + +def generate_ptran_lut_driver_load(spice_filename, lut_input_name, K): + """ Generates LUT input load SPICE deck """ + + # Calculate number of pass-transistors loading this input + max_num_ptran = math.pow(2, K) + if lut_input_name == "a": + num_ptran_load = int(max_num_ptran/2) + ptran_level = "L1" + elif lut_input_name == "b": + num_ptran_load = int(max_num_ptran/4) + ptran_level = "L2" + elif lut_input_name == "c": + num_ptran_load = int(max_num_ptran/8) + ptran_level = "L3" + elif lut_input_name == "d": + num_ptran_load = int(max_num_ptran/16) + ptran_level = "L4" + elif lut_input_name == "e": + num_ptran_load = int(max_num_ptran/32) + ptran_level = "L5" + elif lut_input_name == "f": + num_ptran_load = int(max_num_ptran/64) + ptran_level = "L6" + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the input load circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* LUT " + lut_input_name + "-input load subcircuit \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT lut_" + lut_input_name + "_driver_load n_1 n_vdd n_gnd\n") + for ptran in range(num_ptran_load): + ptran += 1 + spice_file.write("Xwire_lut_" + lut_input_name + "_driver_load_" + str(ptran) + " n_" + str(ptran) + " n_" + str(ptran+1) + " wire Rw='wire_lut_" + lut_input_name + "_driver_load_res/" + str(num_ptran_load) + "' Cw='wire_lut_" + lut_input_name + "_driver_load_cap/" + str(num_ptran_load) + "'\n") + spice_file.write("Xptran_lut_" + lut_input_name + "_driver_load_" + str(ptran) + " n_gnd n_gnd n_" + str(ptran+1) + " n_gnd ptran Wn=ptran_lut_" + ptran_level + "_nmos\n") + spice_file.write(".ENDS\n\n\n") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_lut_" + lut_input_name + "_driver_load") + + return wire_names_list \ No newline at end of file diff --git a/coffe/mux_subcircuits.py b/coffe/mux_subcircuits.py new file mode 100644 index 0000000..ebbed96 --- /dev/null +++ b/coffe/mux_subcircuits.py @@ -0,0 +1,283 @@ +def _generate_ptran_driver(spice_file, mux_name, implemented_mux_size): + """ Generate mux driver for pass-transistor based MUX (it has a level restorer) """ + + # Create the MUX output driver circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " driver subcircuit (" + str(implemented_mux_size) + ":1)\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_driver n_in n_out n_vdd n_gnd\n") + spice_file.write("Xrest_" + mux_name + " n_in n_1_1 n_vdd n_gnd rest Wp=rest_" + mux_name + "_pmos\n") + spice_file.write("Xinv_" + mux_name + "_1 n_in n_1_1 n_vdd n_gnd inv Wn=inv_" + mux_name + "_1_nmos Wp=inv_" + mux_name + "_1_pmos\n") + spice_file.write("Xwire_" + mux_name + "_driver n_1_1 n_1_2 wire Rw=wire_" + mux_name + "_driver_res Cw=wire_" + mux_name + "_driver_cap\n") + spice_file.write("Xinv_" + mux_name + "_2 n_1_2 n_out n_vdd n_gnd inv Wn=inv_" + mux_name + "_2_nmos Wp=inv_" + mux_name + "_2_pmos\n") + spice_file.write(".ENDS\n\n\n") + + +def _generate_ptran_sense_only(spice_file, mux_name, implemented_mux_size): + """ Generate mux driver for pass-transistor based MUX (it has a level restorer) """ + + # Create the MUX output sense inverter only circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " sense inverter subcircuit (" + str(implemented_mux_size) + ":1)\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_sense n_in n_out n_vdd n_gnd\n") + spice_file.write("Xrest_" + mux_name + " n_in n_out n_vdd n_gnd rest Wp=rest_" + mux_name + "_pmos\n") + spice_file.write("Xinv_" + mux_name + "_1 n_in n_out n_vdd n_gnd inv Wn=inv_" + mux_name + "_1_nmos Wp=inv_" + mux_name + "_1_pmos\n") + spice_file.write(".ENDS\n\n\n") + + +def _generate_ptran_2lvl_mux_off(spice_file, mux_name, implemented_mux_size): + """ Generate off pass-transistor 2-level mux """ + + # Create the off MUX circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " subcircuit (" + str(implemented_mux_size) + ":1), turned off \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_off n_in n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("Xptran_" + mux_name + "_L1 n_in n_gnd n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L1_nmos\n") + spice_file.write(".ENDS\n\n\n") + + +def _generate_ptran_2lvl_mux_partial(spice_file, mux_name, implemented_mux_size, level1_size): + """ Generate partially on pass-transistor 2-level mux """ + + # Create the partially-on MUX circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " subcircuit (" + str(implemented_mux_size) + ":1), partially turned on\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_partial n_in n_gate n_gate_n n_vdd n_gnd\n") + # Write level 1 netlist + spice_file.write("Xptran_" + mux_name + "_L1 n_in n_1 n_gate n_gnd ptran Wn=ptran_" + mux_name + "_L1_nmos\n") + current_node = "n_1" + for i in range(1, level1_size): + new_node = "n_1_" + str(i) + spice_file.write("Xwire_" + mux_name + "_L1_" + str(i) + " " + current_node + " " + new_node + + " wire Rw='wire_" + mux_name + "_L1_res/" + str(level1_size) + "' Cw='wire_" + + mux_name + "_L1_cap/" + str(level1_size) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L1_" + str(i) + "h n_gnd " + new_node + " n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L1_nmos\n") + current_node = new_node + new_node = "n_1_" + str(level1_size) + spice_file.write("Xwire_" + mux_name + "_L1_" + str(level1_size) + " " + current_node + " " + new_node + + " wire Rw='wire_" + mux_name + "_L1_res/" + str(level1_size) + "' Cw='wire_" + + mux_name + "_L1_cap/" + str(level1_size) + "'\n") + current_node = new_node + # Write level 2 netlist + spice_file.write("Xptran_" + mux_name + "_L2 " + current_node + " n_gnd n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + spice_file.write(".ENDS\n\n\n") + + +def _generate_ptran_2lvl_mux_on(spice_file, mux_name, implemented_mux_size, level1_size, level2_size): + """ Generate on pass-transistor 2-level mux, never call this outside this file """ + + # Create the fully-on MUX circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " subcircuit (" + str(implemented_mux_size) + ":1), fully turned on (mux only)\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_on_mux_only n_in n_out n_gate n_gate_n n_vdd n_gnd\n") + # Write level 1 netlist + spice_file.write("Xptran_" + mux_name + "_L1 n_in n_1 n_gate n_gnd ptran Wn=ptran_" + mux_name + "_L1_nmos\n") + current_node = "n_1" + for i in range(1, level1_size): + new_node = "n_1_" + str(i) + spice_file.write("Xwire_" + mux_name + "_L1_" + str(i) + " " + current_node + " " + new_node + + " wire Rw='wire_" + mux_name + "_L1_res/" + str(level1_size) + "' Cw='wire_" + + mux_name + "_L1_cap/" + str(level1_size) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L1_" + str(i) + "h n_gnd " + new_node + " n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L1_nmos\n") + current_node = new_node + new_node = "n_1_" + str(level1_size) + spice_file.write("Xwire_" + mux_name + "_L1_" + str(level1_size) + " " + current_node + " " + new_node + + " wire Rw='wire_" + mux_name + "_L1_res/" + str(level1_size) + "' Cw='wire_" + + mux_name + "_L1_cap/" + str(level1_size) + "'\n") + current_node = new_node + # Write level 2 netlist + spice_file.write("Xptran_" + mux_name + "_L2 " + current_node + " n_2 n_gate n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + current_node = "n_2" + wire_counter = 1 + tran_counter = 1 + # These are the switches below driver connection + for i in range(1, (level2_size/2)): + new_node = "n_2_" + str(i) + spice_file.write("Xwire_" + mux_name + "_L2_" + str(wire_counter) + " " + current_node + " " + new_node + + " wire Rw='wire_" + mux_name + "_L2_res/" + str(level2_size-1) + "' Cw='wire_" + + mux_name + "_L2_cap/" + str(level2_size-1) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L2_" + str(tran_counter) + "h n_gnd " + new_node + " n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + wire_counter = wire_counter + 1 + tran_counter = tran_counter + 1 + current_node = new_node + # These are the middle wires + # If level size is odd, we put a full size wire, if even, wire is only half size (wire is in middle of two switches) + if level2_size%2==0: + spice_file.write("Xwire_" + mux_name + "_L2_" + str(wire_counter) + " " + current_node + " n_out" + + " wire Rw='wire_" + mux_name + "_L2_res/" + str(2*(level2_size-1)) + "' Cw='wire_" + + mux_name + "_L2_cap/" + str(2*(level2_size-1)) + "'\n") + wire_counter = wire_counter + 1 + new_node = "n_2_" + str(level2_size/2) + spice_file.write("Xwire_" + mux_name + "_L2_" + str(wire_counter) + " " + " n_out " + new_node + + " wire Rw='wire_" + mux_name + "_L2_res/" + str(2*(level2_size-1)) + "' Cw='wire_" + + mux_name + "_L2_cap/" + str(2*(level2_size-1)) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L2_" + str(tran_counter) + "h n_gnd " + new_node + " n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + wire_counter = wire_counter + 1 + tran_counter = tran_counter + 1 + current_node = new_node + else: + spice_file.write("Xwire_" + mux_name + "_L2_" + str(wire_counter) + " " + current_node + " n_out" + + " wire Rw='wire_" + mux_name + "_L2_res/" + str(level2_size-1) + "' Cw='wire_" + + mux_name + "_L2_cap/" + str(level2_size-1) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L2_" + str(tran_counter) + "h n_gnd n_out n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + wire_counter = wire_counter + 1 + tran_counter = tran_counter + 1 + new_node = "n_2_" + str(level2_size/2) + spice_file.write("Xwire_" + mux_name + "_L2_" + str(wire_counter) + " " + " n_out " + new_node + + " wire Rw='wire_" + mux_name + "_L2_res/" + str(level2_size-1) + "' Cw='wire_" + + mux_name + "_L2_cap/" + str(level2_size-1) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L2_" + str(tran_counter) + "h n_gnd " + new_node + " n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + wire_counter = wire_counter + 1 + tran_counter = tran_counter + 1 + current_node = new_node + # These are the switches above driver connection + for i in range(1, (level2_size/2)): + new_node = "n_2_" + str(i+level2_size/2) + spice_file.write("Xwire_" + mux_name + "_L2_" + str(wire_counter) + " " + current_node + " " + new_node + + " wire Rw='wire_" + mux_name + "_L2_res/" + str(level2_size-1) + "' Cw='wire_" + + mux_name + "_L2_cap/" + str(level2_size-1) + "'\n") + spice_file.write("Xptran_" + mux_name + "_L2_" + str(tran_counter) + "h n_gnd " + new_node + " n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_L2_nmos\n") + wire_counter = wire_counter + 1 + tran_counter = tran_counter + 1 + current_node = new_node + spice_file.write(".ENDS\n\n\n") + + +def generate_ptran_2lvl_mux(spice_filename, mux_name, implemented_mux_size, level1_size, level2_size): + """ + Creates two-level MUX circuits + There are 3 different types of MUX that are generated depending on how 'on' the mux is + 1. Fully on (both levels are on) circuit name: mux_name + "_on" + 2. Partially on (only level 1 is on) circuit name: mux_name + "_partial" + 3. Off (both levels are off) circuit name: mux_name + "_off" + """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Generate SPICE subcircuits + _generate_ptran_driver(spice_file, mux_name, implemented_mux_size) + _generate_ptran_2lvl_mux_off(spice_file, mux_name, implemented_mux_size) + _generate_ptran_2lvl_mux_partial(spice_file, mux_name, implemented_mux_size, level1_size) + _generate_ptran_2lvl_mux_on(spice_file, mux_name, implemented_mux_size, level1_size, level2_size) + + # Create the fully-on MUX circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " subcircuit (" + str(implemented_mux_size) + ":1), fully turned on \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_on n_in n_out n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("X" + mux_name + "_on_mux_only n_in n_1_1 n_gate n_gate_n n_vdd n_gnd " + mux_name + "_on_mux_only\n") + spice_file.write("X" + mux_name + "_driver n_1_1 n_out n_vdd n_gnd " + mux_name + "_driver\n") + spice_file.write(".ENDS\n\n\n") + + # Close SPICE file + spice_file.close() + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("ptran_" + mux_name + "_L1_nmos") + tran_names_list.append("ptran_" + mux_name + "_L2_nmos") + tran_names_list.append("rest_" + mux_name + "_pmos") + tran_names_list.append("inv_" + mux_name + "_1_nmos") + tran_names_list.append("inv_" + mux_name + "_1_pmos") + tran_names_list.append("inv_" + mux_name + "_2_nmos") + tran_names_list.append("inv_" + mux_name + "_2_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_" + mux_name + "_driver") + wire_names_list.append("wire_" + mux_name + "_L1") + wire_names_list.append("wire_" + mux_name + "_L2") + + return tran_names_list, wire_names_list + + +def generate_ptran_2lvl_mux_no_driver(spice_filename, mux_name, implemented_mux_size, level1_size, level2_size): + """ + Creates two-level MUX files + There are 3 different types of MUX that are generated depending on how 'on' the mux is + 1. Fully on (both levels are on) circuit name: mux_name + "_on" + 2. Partially on (only level 1 is on) circuit name: mux_name + "_partial" + 3. Off (both levels are off) circuit name: mux_name + "_off" + + No driver is attached to the on mux (we need this for the local routing mux) + """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Generate SPICE subcircuits + _generate_ptran_sense_only(spice_file, mux_name, implemented_mux_size) + _generate_ptran_2lvl_mux_off(spice_file, mux_name, implemented_mux_size) + _generate_ptran_2lvl_mux_partial(spice_file, mux_name, implemented_mux_size, level1_size) + _generate_ptran_2lvl_mux_on(spice_file, mux_name, implemented_mux_size, level1_size, level2_size) + + # Create the fully-on MUX circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " subcircuit (" + str(implemented_mux_size) + ":1), fully turned on \n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + "_on n_in n_out n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("X" + mux_name + "_on_mux_only n_in n_1_1 n_gate n_gate_n n_vdd n_gnd " + mux_name + "_on_mux_only\n") + spice_file.write("X" + mux_name + "_sense n_1_1 n_out n_vdd n_gnd " + mux_name + "_sense\n") + spice_file.write(".ENDS\n\n\n") + + # Close SPICE file + spice_file.close() + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("ptran_" + mux_name + "_L1_nmos") + tran_names_list.append("ptran_" + mux_name + "_L2_nmos") + tran_names_list.append("rest_" + mux_name + "_pmos") + tran_names_list.append("inv_" + mux_name + "_1_nmos") + tran_names_list.append("inv_" + mux_name + "_1_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_" + mux_name + "_L1") + wire_names_list.append("wire_" + mux_name + "_L2") + + return tran_names_list, wire_names_list + + +def generate_ptran_2_to_1_mux(spice_filename, mux_name): + """ Generate a 2:1 pass-transistor MUX with shared SRAM """ + + # Open SPICE file for appending + spice_file = open(spice_filename, 'a') + + # Create the 2:1 MUX circuit + spice_file.write("******************************************************************************************\n") + spice_file.write("* " + mux_name + " subcircuit (2:1)\n") + spice_file.write("******************************************************************************************\n") + spice_file.write(".SUBCKT " + mux_name + " n_in n_out n_gate n_gate_n n_vdd n_gnd\n") + spice_file.write("Xptran_" + mux_name + " n_in n_1_1 n_gate n_gnd ptran Wn=ptran_" + mux_name + "_nmos\n") + spice_file.write("Xwire_" + mux_name + " n_1_1 n_1_2 wire Rw='wire_" + mux_name + "_res/2' Cw='wire_" + mux_name + "_cap/2'\n") + spice_file.write("Xwire_" + mux_name + "_h n_1_2 n_1_3 wire Rw='wire_" + mux_name + "_res/2' Cw='wire_" + mux_name + "_cap/2'\n") + spice_file.write("Xptran_" + mux_name + "_h n_gnd n_1_3 n_gnd n_gnd ptran Wn=ptran_" + mux_name + "_nmos\n") + spice_file.write("Xrest_" + mux_name + " n_1_2 n_2_1 n_vdd n_gnd rest Wp=rest_" + mux_name + "_pmos\n") + spice_file.write("Xinv_" + mux_name + "_1 n_1_2 n_2_1 n_vdd n_gnd inv Wn=inv_" + mux_name + "_1_nmos Wp=inv_" + mux_name + "_1_pmos\n") + spice_file.write("Xwire_" + mux_name + "_driver n_2_1 n_2_2 wire Rw=wire_" + mux_name + "_driver_res Cw=wire_" + mux_name + "_driver_cap\n") + spice_file.write("Xinv_" + mux_name + "_2 n_2_2 n_out n_vdd n_gnd inv Wn=inv_" + mux_name + "_2_nmos Wp=inv_" + mux_name + "_2_pmos\n") + spice_file.write(".ENDS\n\n\n") + spice_file.close() + + # Create a list of all transistors used in this subcircuit + tran_names_list = [] + tran_names_list.append("ptran_" + mux_name + "_nmos") + tran_names_list.append("rest_" + mux_name + "_pmos") + tran_names_list.append("inv_" + mux_name + "_1_nmos") + tran_names_list.append("inv_" + mux_name + "_1_pmos") + tran_names_list.append("inv_" + mux_name + "_2_nmos") + tran_names_list.append("inv_" + mux_name + "_2_pmos") + + # Create a list of all wires used in this subcircuit + wire_names_list = [] + wire_names_list.append("wire_" + mux_name) + wire_names_list.append("wire_" + mux_name + "_driver") + + return tran_names_list, wire_names_list \ No newline at end of file diff --git a/coffe/spice.py b/coffe/spice.py new file mode 100644 index 0000000..6d86245 --- /dev/null +++ b/coffe/spice.py @@ -0,0 +1,1132 @@ +# Python scripts for interfacing to HSPICE +# +# + +import os +import subprocess +from itertools import product + +# Global variable for minimum transistor width (45nm) +#min_tran_width = 45 + +# This global variable tells us how much variation we are willing to tolerate in ERF (in percentage, 0.01 = 1%) +global_erf_tolerance = 0.01 + +erf_monitor_verbose = True + +def expand_ranges(sizing_ranges): + """ The input to this function is a dictionary that describes the SPICE sweep + ranges. dict = {"name": (start_value, stop_value, increment)} + To make it easy to run all the described SPICE simulations, this function + creates a list of all possible combinations in the ranges. + It also produces a 'name' list that matches the order of + the parameters for each combination.""" + + # Use a copy of sizing ranges + sizing_ranges_copy = sizing_ranges.copy() + + # Expand the ranges into full list of values + for key, values in sizing_ranges_copy.items(): + # Initialization + start_value = values[0] + end_value = values[1] + increment = values[2] + current_value = start_value + value_list = [] + # Loop till current values is larger than end value + while current_value <= end_value: + value_list.append(current_value) + current_value = current_value + increment + # Replace the range with a list + sizing_ranges_copy[key] = value_list + + # Now we want to make a list of all possible combinations + sizing_combinations = [] + for combination in product(*[sizing_ranges_copy[i] for i in sorted(sizing_ranges_copy.keys())]): + sizing_combinations.append(combination) + + # Make a list of sorted parameter names to go with the list of + # combinations. The sorted list of names will match the order of + # items in each combinations of the list. + parameter_names = sorted(sizing_ranges_copy.keys()) + + return parameter_names, sizing_combinations + + +def get_middle_value_config(param_names, spice_ranges): + """ """ + + # Initialize config as a tuple + config = () + + # For each parameter, find the mid value and create a config tuple + for name in param_names: + # Replaced code with unexpdanded. + #range_len = len(spice_expanded_ranges[name]) + #mid_val = spice_expanded_ranges[name][range_len/2] + #config = config + (mid_val,) + range = spice_ranges[name] + min = range[0] + max = range[1] + mid = (max-min)/2 + min + config = config + (mid,) + + return config + + +def initialize_transistor_file(tran_size_filename, param_names, combo, min_tran_width): + """ Initializes the transistor sizes file to the values in combo. + It does not use any ERF ratios, so all inverters have equal + NMOS and PMOS sizes. """ + + spice_params_write = {} + + # Create spice params dictionary with the combo + for i in range(len(param_names)): + if param_names[i].startswith("inv_"): + spice_params_write[param_names[i] + "_nmos"] = str( + combo[i]*min_tran_width) + "n" + spice_params_write[param_names[i] + "_pmos"] = str( + combo[i]*min_tran_width) + "n" + elif param_names[i].startswith("ptran_"): + spice_params_write[param_names[i] + "_nmos"] = str( + combo[i]*min_tran_width) + "n" + elif param_names[i].startswith("rest_"): + spice_params_write[param_names[i] + "_pmos"] = str( + combo[i]*min_tran_width) + "n" + + # Change parameters transistor library + spice_params_read = change_params(tran_size_filename, spice_params_write) + + return spice_params_read + + +def make_data_block(element_names, sizing_combos, inv_ratios, wire_rc, min_tran_width): + """ """ + + # Get the wire names + wire_rc_names = wire_rc[0].keys() + + # Make the sweep parameters string needed for .DATA block + sweep_param_names = "" + for element_name in element_names: + if "ptran_" in element_name: + sweep_param_names += element_name + "_nmos " + elif "rest_" in element_name: + sweep_param_names += element_name + "_pmos " + elif "inv_" in element_name: + sweep_param_names += element_name + "_nmos " + sweep_param_names += element_name + "_pmos " + # Add the wire_rc names for both resistance and capacitance + for wire_rc_name in wire_rc_names: + sweep_param_names += wire_rc_name + "_res " + sweep_param_names += wire_rc_name + "_cap " + + + data_filename = "sweep_data.l" + data_file = open(data_filename, 'w') + data_file.write(".DATA sweep_data " + sweep_param_names + "\n") + for i in range(len(sizing_combos)): + combo = sizing_combos[i] + for i in range(len(combo)): + element_name = element_names[i] + # If it is ptran, just write one value (only 1 NMOS) + if "ptran_" in element_name: + data_file.write(str(combo[i]*min_tran_width) + "n ") + # If it is rest, just write one value (only 1 PMOS) + if "rest_" in element_name: + data_file.write(str(combo[i]*min_tran_width) + "n ") + # If element is an inverter, we have to place a NMOS as well as a PMOS value + elif "inv_" in element_name: + # If the NMOS is bigger than the PMOS + if inv_ratios[element_name] < 1: + data_file.write(str(int(combo[i]*min_tran_width/inv_ratios[element_name])) + "n ") + data_file.write(str(combo[i]*min_tran_width) + "n ") + # If the PMOS is bigger than the NMOS + else: + data_file.write(str(combo[i]*min_tran_width) + "n ") + data_file.write(str(int(combo[i]*min_tran_width*inv_ratios[element_name])) + "n ") + # Get wire RC data for this combo + wire_rc_combo = wire_rc[i] + # Add wire R and C + for wire_name in wire_rc_names: + data_file.write(str(wire_rc_combo[wire_name][0]) + " " + str(wire_rc_combo[wire_name][1]) + "f ") + data_file.write("\n") + + data_file.write(".ENDDATA") + data_file.close() + + +def change_params(tran_size_filename, write_params): + """Set/read parameters in parameter library file. + write_params is a dictionary {'param name', 'value to write'} + Returns a dictionary of ALL params and their current value""" + + # What we want to do is go through a file line by line and change certain values + # To do this, we will open two files, the original and a temp file. + # We copy the contents of the original file to the temp file but with the appropriate + # changes. When this is complete, we delete the original and rename the temp. + # We also read all other parameters that don't need to be written. + + # Open original file for reading + original_file = open(tran_size_filename, "r") + # Open new file for writing + new_file = open(tran_size_filename + ".tmp", "w") + + # Initialize the params_read dictionary + params_read = {} + + # Now we will go through the file line by line. + # If the line is not a parameter line, just write it to the new file + # If the line is a parameter line, change the value if required and write to new file + for line in original_file: + if ".PARAM" in line: + words = line.split() + if words[1] in write_params: + # Write line to file but with new value + new_file.write(words[0] + " " + words[1] + " " + words[2] + " " + + write_params[words[1]] + "\n") + # Add the parameters to the read list + params_read[words[1]] = write_params[words[1]] + else: + # Write it to file + new_file.write(line) + # Add the parameters to the read list + params_read[words[1]] = words[3] + else: + new_file.write(line) + + # Close the files + original_file.close() + new_file.close() + + # Delete the original file and rename new file + os.remove(tran_size_filename) + os.rename(tran_size_filename + ".tmp", tran_size_filename) + + # Return a dictionary of all the parameters (read and written) + return params_read + + +def sweep_setup(filename, tran_size_filename, sweep_settings): + """Setup a sweep analysis in SPICE file + sweet_settings is a tuple of the form ("name", "start", "stop", "inc") + filename is the name of the spice file to modify""" + + # What we want to do is go through a file line by line and change certain values + # To do this, we will open two files, the original and a temp file. + # We copy the contents of the original file to the temp file but with the appropriate + # changes. When this is complete, we delete the original and rename the temp. + + # Open original spice file for reading + original_file = open(filename, "r") + # Open new spice file for writing + new_file = open(filename + ".tmp", "w") + + # Create the sweep string to be added to SPICE file + sweep_str = (" SWEEP " + sweep_settings[0] + " " + sweep_settings[1] + " " + + sweep_settings[2] + " " + sweep_settings[3]) + + # Now we will go through the file line by line. + # If the line is not the .TRAN line, just write it to the new file + # If the line is the .TRAN line, add the SWEEP and write to new file + + for line in original_file: + # If .TRAN statement, modify it + if ".TRAN" in line: + line = line.strip() + sweep_str + "\n" + # Write the line back to new file + new_file.write(line) + + + # Close the files + original_file.close() + new_file.close() + + # Delete the original file and rename new file + os.remove(filename) + os.rename(filename + ".tmp", filename) + + # Read all spice params in a dictionary + params_read = change_params(tran_size_filename, {}) + + # Return a dictionary of all the parameters read + return params_read + + +def sweep_data_setup(filename): + """Setup a sweep analysis for .DATA block in SPICE file + The name of the .DATA block is assumed to be "sweep_data" + filename is the name of the spice file to modify""" + + # What we want to do is go through a file line by line and change certain values + # To do this, we will open two files, the original and a temp file. + # We copy the contents of the original file to the temp file but with the appropriate + # changes. When this is complete, we delete the original and rename the temp. + + # Open original spice file for reading + original_file = open(filename, "r") + # Open new spice file for writing + new_file = open(filename + ".tmp", "w") + + # Create the sweep string to be added to SPICE file + sweep_str = " SWEEP DATA=sweep_data" + + # Now we will go through the file line by line. + # If the line is not the .TRAN line, just write it to the new file + # If the line is the .TRAN line, add the SWEEP and write to new file + + for line in original_file: + # If .TRAN statement, modify it + if ".TRAN" in line: + line = line.strip() + sweep_str + "\n" + # Write the line back to new file + new_file.write(line) + + + # Close the files + original_file.close() + new_file.close() + + # Delete the original file and rename new file + os.remove(filename) + os.rename(filename + ".tmp", filename) + + +def sweep_remove(filename): + """Remove SWEEP analysis from SPICE file + filename is the name of the spice file to modify""" + + # What we want to do is go through a file line by line and change certain values + # To do this, we will open two files, the original and a temp file. + # We copy the contents of the original file to the temp file but with the appropriate + # changes. When this is complete, we delete the original and rename the temp. + + # Open original spice file for reading + original_file = open(filename, "r") + # Open new spice file for writing + new_file = open(filename + ".tmp", "w") + + # Now we will go through the file line by line. + # If the line is not a the .TRAN line, just write it to the new file + # If the line is a parameter line, add the SWEEP and write to new file + for line in original_file: + if ".TRAN" in line: + words = line.split() + line = words[0] + " " + words[1] + " " + words[2] + "\n" + new_file.write(line) + + # Close the files + original_file.close() + new_file.close() + + # Delete the original file and rename new file + os.remove(filename) + os.rename(filename + ".tmp", filename) + + +def read_output(filename): + """Reads the measurements from SPICE output file. + filename is the name of the SPICE output file + Returns a dictionary of all measurements found. + Measurements must start with 'meas_'""" + + # Open the file for reading + output_file = open(filename, "r") + + # Initialize the meas_read dictionary + meas_read = {} + + # Look for measurements and save to dictionary + for line in output_file: + if "meas_" in line: + if "failed" in line: + # Signal that SPICE failed + meas_read["failed"] = "failed" + break + else: + split1 = line.split("=") + measurement_name = split1[0].strip() + split2 = split1[1].split() + measurement_value = split2[0] + # Save the measured value + meas_read[measurement_name] = convert_num(measurement_value) + # Close the file + output_file.close() + + return meas_read + + +def read_sweep_output(filename, param_name): + """Reads the measurements from SPICE output file. + filename is the name of the SPICE output file + param_name is the parameter name + Returns a list of all measurements found (abs_diff, sweep_value, tfall, trise)""" + + # Open the file for reading + output_file = open(filename, "r") + + # Remove _pmos or _nmos from the parameter name (need to do this to read measurement) + if param_name.endswith("_pmos"): + param_name = param_name.replace("_pmos", "") + elif param_name.endswith("_nmos"): + param_name = param_name.replace("_nmos", "") + + # Initialize the list + sweep_erf_list = [] + + # Initialize the dictionary of all sweep results + sweep_meas = {} + meas_read = {} + + # Look for measurements and save to list + # Works by recognizing sweep parameter value and rise and fall measurements in sequence + # Remembers values and when trise is found, this completes one list entry so we add + # it to the list + sweep_value = -1 + tfall = -1 + trise = -1 + for line in output_file: + # Initialize the dictionary of measurements + if ("parameter " + param_name) in line: + words = line.split() + sweep_value = float(words[4]) + elif "meas_" in line: + if "meas_variable" in line: + # Hack to make work with other HSPICE versions + continue + elif "failed" in line: + # Signal that SPICE failed + meas_read["failed"] = "failed" + break + else: + # Add the measurement to dictionary + split1 = line.split("=") + measurement_name = split1[0].strip() + split2 = split1[1].split() + measurement_value = split2[0] + meas_read[measurement_name] = convert_num(measurement_value) + # Check if its the sweep results + if measurement_name == ("meas_" + param_name + "_tfall"): + tfall = convert_num(measurement_value) + elif measurement_name == ("meas_" + param_name + "_trise"): + trise = convert_num(measurement_value) + elif "***** job concluded" in line: + sweep_erf_list.append((abs(tfall-trise), sweep_value, tfall, trise)) + sweep_meas[sweep_value] = meas_read + # Create new dictionary + meas_read = dict() + + # Close the file + output_file.close() + + return sweep_erf_list, sweep_meas + + +def read_sweep_data_output(filename): + """Reads the measurements from SPICE output file. + filename is the name of the SPICE output file + It reads "meas_total" for tfall and trise + Returns list of (tfall, trise)""" + + # Open the file for reading + output_file = open(filename, "r") + + # Initialize the list + sweep_list = [] + + # Look for measurements and save to list + # Works by recognizing rise and fall measurements in sequence + # Remembers values and adds them as a tuple to the sweep list + tfall = 1 + trise = 1 + for line in output_file: + if "meas_total" in line: + if "meas_variable" in line: + # Hack to make work with other HSPICE versions + continue + if "failed" in line: + # If HSPICE simulation failed, set delay to 1s (huge delay) + tfall = 1 + trise = 1 + else: + # Add the measurement to dictionary + split1 = line.split("=") + measurement_name = split1[0].strip() + split2 = split1[1].split() + measurement_value = split2[0] + if measurement_value[0] == "-": + tfall = 1 + trise = 1 + elif "_tfall" in line: + tfall = convert_num(measurement_value) + elif "_trise" in line: + trise = convert_num(measurement_value) + elif "***** job concluded" in line: + sweep_list.append((tfall, trise)) + + # Close the file + output_file.close() + + return sweep_list + + +def convert_num(spice_number): + """ Convert a numbers from SPICE output of the form 4.3423p to a float """ + + # Make dictionary of exponent values + exponent_values = {"m" : float(1e-3), + "u" : float(1e-6), + "n" : float(1e-9), + "p" : float(1e-12), + "f" : float(1e-15), + "a" : float(1e-18)} + + # The number format depends on the HSPICE version. Some versions use scientific notation, + # other use letters (e.g. 4.342n) + + if spice_number[len(spice_number)-1].isalpha(): + # The coefficient is everything but the last character + coefficient = float(spice_number[0:len(spice_number)-1]) + # The exponent is the last char (convert to num with dict) + exponent = exponent_values[spice_number[len(spice_number)-1]] + number = coefficient*exponent + else: + number = float(spice_number) + + return number + + +def set_parameters(tran_size_filename, param_names, config, inv_ratios, min_tran_width): + """ The input to this function is the transistor sizes we wish to set. + But, they aren't necessarily in the format required by the SPICE file. + (i.e. inv NMOS and PMOS don't have distinct sizes yet, just one size for both, + and sizes are not in nanometer format, they are in xMin width format) + This function does this transistor size formating, and then calls + the change_params to change the parameters in the SPICE file. """ + + spice_params_write = {} + + # Create spice params dictionary with config + for i in range(len(param_names)): + if param_names[i].startswith("inv_"): + # If NMOS is bigger + if inv_ratios[param_names[i]] < 1: + spice_params_write[param_names[i] + "_nmos"] = str( + int(config[i]*min_tran_width/inv_ratios[param_names[i]])) + "n" + spice_params_write[param_names[i] + "_pmos"] = str( + config[i]*min_tran_width) + "n" + # If PMOS is bigger + else: + spice_params_write[param_names[i] + "_nmos"] = str( + config[i]*min_tran_width) + "n" + spice_params_write[param_names[i] + "_pmos"] = str( + int(config[i]*min_tran_width*inv_ratios[param_names[i]])) + "n" + elif param_names[i].startswith("ptran_"): + spice_params_write[param_names[i] + "_nmos"] = str( + config[i]*min_tran_width) + "n" + elif param_names[i].startswith("tgate_"): + spice_params_write[param_names[i] + "_nmos"] = str( + config[i]*min_tran_width) + "n" + spice_params_write[param_names[i] + "_pmos"] = str( + config[i]*min_tran_width) + "n" + elif param_names[i].startswith("rest_"): + spice_params_write[param_names[i] + "_pmos"] = str( + config[i]*min_tran_width) + "n" + + # Change parameters in spice file + spice_params_read = change_params(tran_size_filename, spice_params_write) + + return spice_params_read + +# TODO: I think we could merge these two functions +def set_all_parameters(tran_size_filename, transistor_sizes, min_tran_width): + """ The input to this function is the transistor sizes we wish to set. + But, they aren't necessarily in the format required by the SPICE file. + (i.e. sizes are not in nanometer format, they are in xMin width format) + This function does this transistor size formating, and then calls + the change_params to change the parameters in the SPICE file. """ + + spice_params_write = {} + + # Create spice params dictionary with config + for tran_name, tran_size in transistor_sizes.iteritems(): + spice_params_write[tran_name] = str(tran_size*min_tran_width) + "n" + + # Change parameters in spice file + spice_params_read = change_params(tran_size_filename, spice_params_write) + + return spice_params_read + + +def run(spice_filedir, spice_filename): + """ This function simply runs HSPICE on 'spice_filename'. + The function does not change any SPICE parameters (transistor sizes, etc.), + this has to be done before calling this function. + Returns a dictionary containing all the SPICE measurements. """ + + # Change working directory so that SPICE output files are created in circuit subdirectory + saved_cwd = os.getcwd() + os.chdir(spice_filedir) + + # Run the SPICE simulation and capture console output + spice_output_filename = spice_filename.rstrip(".sp") + ".output" + output = open(spice_output_filename, "w") + subprocess.call(["hspice", spice_filename], stdout=output, stderr=output) + output.close() + + # Read spice measurements + spice_measurements = read_output(spice_output_filename) + + # TODO: I just put this here for now, should figure out what is the best way to do this. + if "meas_logic_low_voltage" in spice_measurements: + logic_low_voltage = spice_measurements["meas_logic_low_voltage"] + if logic_low_voltage > 0.01: + print "ERROR!!! Logic-low voltage not low enough: " + str(logic_low_voltage) + spice_measurements["failed"] = "failed" + + # Return to saved cwd + os.chdir(saved_cwd) + + return spice_measurements + + +def spice_inverter(spice_filedir, spice_filename, tran_size_filename, param_name, nmos_size, pmos_size): + """ Run SPICE to get rise and fall measurements for an inverter. + spice_filename is the name of the SPICE file to use. + param_name is the inverter name. + nmos_size and pmos_size are integer values for nmos and pmos size + return (trise, tfall, all_read_spice_params)""" + + # Make a spice param dictionary + spice_params_write = {param_name + "_nmos": str(nmos_size) + "n", + param_name + "_pmos": str(pmos_size) + "n"} + + # Change parameters in transistor library file + spice_params_read = change_params(tran_size_filename, spice_params_write) + + # Change working directory so that SPICE output files are created in circuit subdirectory + saved_cwd = os.getcwd() + os.chdir(spice_filedir) + + # Run the SPICE simulation and capture console output + spice_output_filename = spice_filename.rstrip(".sp") + ".output" + output = open(spice_output_filename, "w") + subprocess.call(["hspice", spice_filename], stdout=output, stderr=output) + output.close() + + # Read spice measurements + spice_measurements = read_output(spice_output_filename) + + # Return to saved cwd + os.chdir(saved_cwd) + + return spice_params_read, spice_measurements + + +def sweep(spice_filedir, spice_filename, tran_size_filename, param_name, sweep_values): + """ Perform a SWEEP analysis with HSPICE. """ + + # Make sweep settings + sweep_settings = (param_name, (str(sweep_values[0]) + "n"), + (str(sweep_values[1]) + "n"), (str(sweep_values[2]) + "n")) + + # Add SWEEP analysis in SPICE file and read all SPICE params + spice_params_read = sweep_setup(spice_filedir + spice_filename, tran_size_filename, sweep_settings) + + # Change working directory so that SPICE output files are created in circuit subdirectory + saved_cwd = os.getcwd() + os.chdir(spice_filedir) + + # Run the SPICE simulation and capture console output + spice_output_filename = spice_filename.rstrip(".sp") + ".output" + output = open(spice_output_filename, "w") + subprocess.call(["hspice", spice_filename], stdout=output, stderr=output) + output.close() + + # Read the sweep results + sweep_erf, sweep_results = read_sweep_output(spice_output_filename, param_name) + + # Return to saved cwd + os.chdir(saved_cwd) + + # Remove the SWEEP analysis from the SPICE file + sweep_remove(spice_filedir + spice_filename) + + return spice_params_read, sweep_results, sweep_erf + + +def sweep_data(spice_filedir, spice_filename): + """ Perform a SWEEP .DATA analysis with HSPICE. """ + + + # Add SWEEP .DATA analysis in SPICE file + sweep_data_setup(spice_filedir + spice_filename) + + # Change working directory so that SPICE output files are created in circuit subdirectory + saved_cwd = os.getcwd() + os.chdir(spice_filedir) + + # Run the SPICE simulation and capture console output + spice_output_filename = spice_filename.rstrip(".sp") + ".output" + output = open(spice_output_filename, "w") + subprocess.call(["hspice", spice_filename], stdout=output, stderr=output) + output.close() + + # Read the sweep results + sweep_results = read_sweep_data_output(spice_output_filename) + + # Return to saved cwd + os.chdir(saved_cwd) + + # Remove the SWEEP analysis from the SPICE file + sweep_remove(spice_filedir + spice_filename) + + return sweep_results + + +def get_total_tfall_trise(name, spice_path): + """ Run HSPICE on SPICE decks and parse output + Returns this tuple: (tfall, trise) """ + + print 'Updating delay for ' + name + + # We are going to change the working directory to the SPICE deck directory so that files generated by SPICE area created there + saved_cwd = os.getcwd() + new_cwd = "" + spice_filename_nodir = "" + if "/" in spice_path: + words = spice_path.split("/") + for i in range(len(words)-1): + new_cwd = new_cwd + words[i] + "/" + os.chdir(new_cwd) + spice_filename_nodir = words[len(words)-1] + + # Run the SPICE simulation and capture console output + spice_output_filename = spice_filename_nodir.rstrip(".sp") + ".output" + output = open(spice_output_filename, "w") + subprocess.call(["hspice", spice_filename_nodir], stdout=output, stderr=output) + output.close() + + # Return to saved cwd + os.chdir(saved_cwd) + + # Parse output files + spice_output = open(spice_path.rstrip(".sp") + ".output", 'r') + tfall = 0.0 + trise = 0.0 + avg_power = 0.0 + for line in spice_output: + if 'meas_total_tfall' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + tfall = convert_num(delay_str) + elif 'meas_total_trise' in line: + split1 = line.split("=") + split2 = split1[1].split() + delay_str = split2[0] + trise = convert_num(delay_str) + elif 'avg_power_gen_routing' in line: + split1 = line.split("=") + split2 = split1[1].split() + power_str = split2[0] + avg_power = convert_num(power_str) + spice_output.close() + + return (tfall, trise) + + +def get_inverter_ratios(param_names, spice_params): + """ """ + + # Dictionary in which we will store the inverter ratios + inv_ratios = {} + # For each parameter, check if inverter, get inverter ratio PMOS/NMOS + for name in param_names: + if "inv_" in name: + pmos_size = int(spice_params[name + "_pmos"].replace("n", "")) + nmos_size = int(spice_params[name + "_nmos"].replace("n", "")) + ratio = float(pmos_size)/nmos_size + inv_ratios[name] = ratio + + return inv_ratios + + +def size_inverter_erf(spice_filedir, spice_filename, tran_size_filename, param_name, param_value, min_tran_width, num_hspice_sims): + """ Sizes an inverter for equal rise and fall + spice_filedir is the directory where the SPICE file is + spice_filename is the name of the SPICE file to use + param_name is the name of the inverter + param_value is the inverter size + The function will figure out whether it is nmos or pmos that has the + param_value size. It will then size the other to achieve equal rise + and fall.""" + + if erf_monitor_verbose: + print "ERF MONITOR: " + param_name + + # param_size is the true transistor width in nanometers + param_size = param_value*min_tran_width + nmos_size = param_size + pmos_size = param_size + + # Run SPICE on inverter for given size, get measurements + spice_params, spice_meas = spice_inverter(spice_filedir, spice_filename, tran_size_filename, param_name, + nmos_size, pmos_size) + num_hspice_sims += 1 + + # Check if SPICE failed + if "failed" in spice_meas: + print "SPICE failed on first ERF run" + return spice_params, spice_meas + + # Get trise and tfall measurements for this inverter + inv_tfall = spice_meas["meas_" + param_name + "_tfall"] + inv_trise = spice_meas["meas_" + param_name + "_trise"] + + # The first step in sizing for equal rise and fall is to find out which + # transistor needs to be larger, the nmos or the pmos. To do this, we + # make them both of equal size (size=param_value) and compare the rise + # and fall times. If the rise time is faster, nmos must be made bigger. + # If the fall time is faster, pmos must be made bigger. + if inv_trise > inv_tfall: + # PMOS must be larger than NMOS + + # The first thing we want to do is increase the PMOS size in fixed increments + # to get an upper bound on the PMOS size. + # We also monitor trise. We expect that increasing the PMOS size will decrease + # the rise time and increase the fall time. If at any time, we see that increasing the PMOS is increasing + # the rise time, we should stop increasing the PMOS size. This means we might be self-loading the inverter. + upper_bound_not_found = True + nmos_size = param_size + mult = 2 + previous_trise = 1 + self_loading = False + if erf_monitor_verbose: + print "Looking for PMOS size upper bound" + while upper_bound_not_found and not self_loading: + pmos_size = mult*nmos_size + sizing_bounds_str = "NMOS=" + str(nmos_size) + " PMOS=" + str(pmos_size) + spice_params, spice_meas = spice_inverter(spice_filedir, spice_filename, tran_size_filename, param_name, nmos_size, pmos_size) + num_hspice_sims += 1 + # Get trise and tfall measurements for this inverter + tfall = spice_meas["meas_" + param_name + "_tfall"] + trise = spice_meas["meas_" + param_name + "_trise"] + if erf_monitor_verbose: + print sizing_bounds_str + ": tfall=" + str(tfall) + " trise=" + str(trise) + " diff=" + str(tfall-trise) + # If fall time becomes bigger, we have found the upper bound + if tfall > trise: + upper_bound_not_found = False + if erf_monitor_verbose: + print "Upper bound found, PMOS=" + str(pmos_size) + else: + # Check if trise is increasing or decreasing + if previous_trise != 1: + if trise > previous_trise: + self_loading = True + if erf_monitor_verbose: + print "Increasing PMOS is no longer decreasing trise, using PMOS=" + str(pmos_size) + previous_trise = trise + + mult = mult+1 + + # If not self-loading, find the ERF size, if self loaded, just use current PMOS size + if not self_loading: + # Now, we know that the equality occurs somewhere in [pmos_size-nmos_size, pmos_size] + # We are going to search this range in intervals of min transistor width + if erf_monitor_verbose: + print "Running HSPICE sweep..." + incr = min_tran_width + spice_values = (pmos_size-nmos_size, pmos_size, incr) + spice_params, sweep_results, sweep_erf = sweep(spice_filedir, spice_filename, tran_size_filename, + (param_name + "_pmos"), spice_values) + num_hspice_sims += int(nmos_size/incr) + + # Find the interval that contains the ERF + imin = 0 + imax = 0 + for i in range(len(sweep_erf)): + # If tfall > trise + if sweep_erf[i][2] > sweep_erf[i][3]: + imax = i + imin = i-1 + break + + if imin == imax: + if erf_monitor_verbose: + print "Cannot skew P/N ratio enough for ERF on " + param_name + + # Min & Max PMOS sizes + pmos_min = sweep_erf[imin][1] + pmos_max = sweep_erf[imax][1] + if erf_monitor_verbose: + print "ERF PMOS size in range: [" + str(int(pmos_min/1e-9)) + ", " + str(int(pmos_max/1e-9)) + "]" + + # Now we know that ERF is in PMOS = [pmos_min, pmos_max] + # We will sweep this for every single value in the range + if erf_monitor_verbose: + print "Running HSPICE sweep..." + spice_values = (pmos_min, pmos_max, 1) + spice_params, sweep_results, sweep_erf = sweep(spice_filedir, spice_filename, tran_size_filename, + (param_name + "_pmos"), spice_values) + num_hspice_sims += int((pmos_max-pmos_min)/incr) + + # Sort list based on diff (first element of tuple) + sweep_erf.sort() + + # Get pmos_size from sorted results (in nm) + pmos_size = int(round(sweep_erf[0][1]/1e-9)) + + if erf_monitor_verbose: + print "ERF PMOS size is " + str(pmos_size) + "\n" + + # Get the SPICE measurements for this pmos size + spice_meas = sweep_results[sweep_erf[0][1]] + + # Change the spice_params to match this PMOS size + spice_params[param_name + "_pmos"] = str(pmos_size) + "n" + + # Write the SPICE params to the SPICE file such that we have an inverter sized for ERF + spice_params = change_params(tran_size_filename, spice_params) + + + else: + # NMOS must be larger than PMOS + # The first thing we want to do is increase the NMOS size in fixed increments + # to get an upper bound on the PNOS size. + # We also monitor tfall. We expect that increasing the NMOS size will decrease + # the fall time and increase the rise time. If at any time, we see that increasing the NMOS is increasing + # the fall time, we should stop increasing the NMOS size. This means we might be self-loading the inverter. + upper_bound_not_found = True + pmos_size = param_size + mult = 2 + previous_tfall = 1 + self_loading = False + if erf_monitor_verbose: + print "Looking for NMOS size upper bound" + while upper_bound_not_found: + nmos_size = mult*pmos_size + sizing_bounds_str = "NMOS=" + str(nmos_size) + " PMOS=" + str(pmos_size) + spice_params, spice_meas = spice_inverter(spice_filedir, spice_filename, tran_size_filename, param_name, nmos_size, pmos_size) + num_hspice_sims += 1 + # Get trise and tfall measurements for this inverter + tfall = spice_meas["meas_" + param_name + "_tfall"] + trise = spice_meas["meas_" + param_name + "_trise"] + if erf_monitor_verbose: + print sizing_bounds_str + ": tfall=" + str(tfall) + " trise=" + str(trise) + " diff=" + str(tfall-trise) + # If rise time becomes bigger, we have found the upper bound + if trise > tfall: + upper_bound_not_found = False + if erf_monitor_verbose: + print "Upper bound found, NMOS=" + str(nmos_size) + else: + # Check if tfall is increasing or decreasing + if previous_tfall != 1: + if tfall > previous_tfall: + self_loading = True + if erf_monitor_verbose: + print "Increasing NMOS is no longer decreasing tfall, using PMOS=" + str(nmos_size) + previous_trise = trise + + mult = mult+1 + + # If not self-loading, find the ERF size, if self loaded, just use current PMOS size + if not self_loading: + # Now, we know that the equality occurs somewhere in [nmos_size-pmos_size, nmos_size] + # We are going to search this range in intervals of min transistor width + if erf_monitor_verbose: + print "Running HSPICE sweep..." + incr = min_tran_width + spice_values = (nmos_size-pmos_size, nmos_size, incr) + spice_params, sweep_results, sweep_erf = sweep(spice_filedir, spice_filename, tran_size_filename, + (param_name + "_nmos"), spice_values) + num_hspice_sims += int(pmos_size/incr) + + # Find the interval that contains the ERF + imin = 0 + imax = 0 + for i in range(len(sweep_erf)): + # If tfall < trise + if sweep_erf[i][2] < sweep_erf[i][3]: + imax = i + imin = i-1 + break + + if imin == imax: + if erf_monitor_verbose: + print "Cannot skew P/N ratio enough for ERF on " + param_name + + # Min & Max NMOS sizes + nmos_min = sweep_erf[imin][1] + nmos_max = sweep_erf[imax][1] + if erf_monitor_verbose: + print "ERF NMOS size in range: [" + str(int(nmos_min/1e-9)) + ", " + str(int(nmos_max/1e-9)) + "]" + + # Now we know that ERF is in NMOS = [nmos_min, nmos_max] + # We will sweep this for every single value in the range + if erf_monitor_verbose: + print "Running HSPICE sweep..." + spice_values = (nmos_min, nmos_max, 1) + spice_params, sweep_results, sweep_erf = sweep(spice_filedir, spice_filename, tran_size_filename, + (param_name + "_nmos"), spice_values) + num_hspice_sims += int(nmos_max-nmos_min) + + # Sort list based on diff (first element of tuple) + sweep_erf.sort() + + # Get nmos_size from sorted results (in nm) + nmos_size = int(round(sweep_erf[0][1]/1e-9)) + + if erf_monitor_verbose: + print "ERF NMOS size is " + str(nmos_size) + "\n" + + # Change the spice_params to match this NMOS size + spice_params[param_name + "_nmos"] = str(nmos_size) + "n" + + # Get the SPICE measurements for this nmos size + spice_meas = sweep_results[sweep_erf[0][1]] + + # Write the SPICE params to the SPICE file such that we have an inverter sized for ERF + spice_params = change_params(tran_size_filename, spice_params) + + return spice_params, spice_meas, num_hspice_sims + + +def erf(spice_filedir, spice_filename, tran_size_filename, param_names, config, min_tran_width, num_hspice_sims): + """ Equalize rise and fall times of all inverters listed in 'params_names' for this 'config'. + Returns the inverter ratios that give equal rise and fall. """ + + # This function will equalize rise and fall times on all inverters in the input 'param_names' + # It iteratively finds P/N ratios that equalize the rise and fall times of each inverter. + # Iteration stops if we find the same P/N ratios more than once, or if all rise and fall times are + # found to be sufficiently equal. + + # Dictionary {'inv_name': (nmos_list, pmos_list)}, for each inverter, it holds an NMOS list and PMOS list. + # nmos_list[i] and pmos_list[i] give balanced rise and fall. Each ERF iteration adds a transistor size to these lists + inverter_sizes = {} + + # This list holds a string that we can print + iteration_str_list = [] + + # Size each inverter for equal rise and fall. + # Save inverter NMOS and PMOS sizes in a list. + # Assume inverter sizing will be stable after one iteration + inverter_sizing_stable = True + sizing_str_list = [] + for i in range(len(param_names)): + # Is the parameter an inverter? + if param_names[i].startswith("inv_"): + # Size the inverter for equal rise and fall + spice_params, spice_meas, num_hspice_sims = size_inverter_erf(spice_filedir, + spice_filename, + tran_size_filename, + param_names[i], + config[i], + min_tran_width, + num_hspice_sims) + + # Check to see if SPICE failed + if "failed" in spice_meas: + print "SPICE failed!" + return spice_params, spice_meas + + # Get NMOS and PMOS sizes for this inverter + nmos_size = str(spice_params[param_names[i] + "_nmos"].rstrip("n")) + pmos_size = str(spice_params[param_names[i] + "_pmos"].rstrip("n")) + nmos_list = [] + pmos_list = [] + nmos_list.append(nmos_size) + pmos_list.append(pmos_size) + inverter_sizes[param_names[i]] = (nmos_list, pmos_list) + tfall = spice_meas["meas_" + param_names[i] + "_tfall"] + trise = spice_meas["meas_" + param_names[i] + "_trise"] + # Make output strings + sizing_str_list.append(param_names[i] + "(N=" + str(nmos_size) + " P=" + str(pmos_size) + ", Fall=" + str(tfall) + ", Rise=" +str(trise) + ")") + + # Check to see if ERF meets tolerance constraint + erf_error = abs(tfall-trise)/tfall + if erf_error > global_erf_tolerance: + # ERF tolerance not met, sizing is not stable, need more iterations + inverter_sizing_stable = False + + # Print initial sizing results + if erf_monitor_verbose: + print "ERF Sizing Summary:" + print "Iteration 1" + for sizing_str in sizing_str_list: + print sizing_str + if inverter_sizing_stable: + print "ERF sizing complete" + else: + print "ERF results not satisfactory, doing it again." + print "" + iteration_str_list.append(sizing_str_list) + + # If sizing is not stable: + # Iteratively size inverters for equal rise and fall until inverter sizes stable + # We consider inverter sizes are stable as soon as we obtain the same inverter + # sizing twice or the sizing meets the ERF tolerance + while not inverter_sizing_stable: + sizing_str_list = [] + # Assume that inverter sizings are stable + inverter_sizing_stable = True + # For each inverter, size for ERF again and compare to previous sizes + for i in range(len(param_names)): + # Is the parameter an inverter? + if param_names[i].startswith("inv_"): + # Size the inverter for equal rise and fall + spice_params, spice_meas, num_hspice_sims = size_inverter_erf(spice_filedir, + spice_filename, + tran_size_filename, + param_names[i], + config[i], + min_tran_width, + num_hspice_sims) + + # Get NMOS and PMOS sizes for this inverter + nmos_size = str(spice_params[param_names[i] + "_nmos"].rstrip("n")) + pmos_size = str(spice_params[param_names[i] + "_pmos"].rstrip("n")) + + # Get the measurements + tfall = spice_meas["meas_" + param_names[i] + "_tfall"] + trise = spice_meas["meas_" + param_names[i] + "_trise"] + + # Check if this sizing has been obtained before + if (nmos_size not in inverter_sizes[param_names[i]][0]) or ( + pmos_size not in inverter_sizes[param_names[i]][1]): + # Inverter sizing has not been obtained before + # So, we can only exit loop if ERF tolerance is met + erf_error = abs(tfall - trise)/tfall + if erf_error > global_erf_tolerance: + # ERF tolerance not met, sizing is not stable, need more iterations + inverter_sizing_stable = False + + # Remember new transistor sizes + inverter_sizes[param_names[i]][0].append(nmos_size) + inverter_sizes[param_names[i]][1].append(pmos_size) + + # Add to output string + sizing_str_list.append(param_names[i] + "(N=" + str(nmos_size) + " P=" + str(pmos_size) + ", Fall=" + str(tfall) + ", Rise=" +str(trise) + ")") + + iteration_str_list.append(sizing_str_list) + + # Print status messages + if erf_monitor_verbose: + print "ERF Sizing Summary:" + for i in range(len(iteration_str_list)): + sizing_str_list = iteration_str_list[i] + print "Iteration " + str(i+1) + for sizing_str in sizing_str_list: + print sizing_str + if inverter_sizing_stable: + print "ERF sizing complete" + else: + print "ERF results not satisfactory, doing it again." + print "" + + # Get the ERF ratios + erf_ratios = get_inverter_ratios(param_names, spice_params) + + return erf_ratios, num_hspice_sims + diff --git a/coffe/top_level.py b/coffe/top_level.py new file mode 100644 index 0000000..040607e --- /dev/null +++ b/coffe/top_level.py @@ -0,0 +1,753 @@ +import os + +def generate_switch_block_top(mux_name): + """ Generate the top level switch block SPICE file """ + + # Create directories + if not os.path.exists(mux_name): + os.makedirs(mux_name) + # Change to directory + os.chdir(mux_name) + + switch_block_filename = mux_name + ".sp" + sb_file = open(switch_block_filename, 'w') + sb_file.write(".TITLE Switch block multiplexer\n\n") + + sb_file.write("********************************************************************************\n") + sb_file.write("** Include libraries, parameters and other\n") + sb_file.write("********************************************************************************\n\n") + sb_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + sb_file.write("********************************************************************************\n") + sb_file.write("** Setup and input\n") + sb_file.write("********************************************************************************\n\n") + sb_file.write(".TRAN 1p 4n\n") + sb_file.write(".OPTIONS BRIEF=1\n\n") + sb_file.write("* Input signal\n") + sb_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + sb_file.write("********************************************************************************\n") + sb_file.write("** Measurement\n") + sb_file.write("********************************************************************************\n\n") + sb_file.write("* inv_sb_mux_1 delay\n") + sb_file.write(".MEASURE TRAN meas_inv_sb_mux_1_tfall TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' RISE=1\n") + sb_file.write("+ TARG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.Xsb_mux_driver.n_1_1) VAL='supply_v/2' FALL=1\n") + sb_file.write(".MEASURE TRAN meas_inv_sb_mux_1_trise TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' FALL=1\n") + sb_file.write("+ TARG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.Xsb_mux_driver.n_1_1) VAL='supply_v/2' RISE=1\n\n") + sb_file.write("* inv_sb_mux_2 delays\n") + sb_file.write(".MEASURE TRAN meas_inv_sb_mux_2_tfall TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' FALL=1\n") + sb_file.write("+ TARG V(Xrouting_wire_load_2.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' FALL=1\n") + sb_file.write(".MEASURE TRAN meas_inv_sb_mux_2_trise TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' RISE=1\n") + sb_file.write("+ TARG V(Xrouting_wire_load_2.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' RISE=1\n\n") + sb_file.write("* Total delays\n") + sb_file.write(".MEASURE TRAN meas_total_tfall TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' FALL=1\n") + sb_file.write("+ TARG V(Xrouting_wire_load_2.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' FALL=1\n") + sb_file.write(".MEASURE TRAN meas_total_trise TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' RISE=1\n") + sb_file.write("+ TARG V(Xrouting_wire_load_2.Xrouting_wire_load_tile_1.Xsb_mux_on_out.n_in) VAL='supply_v/2' RISE=1\n\n") + + sb_file.write("********************************************************************************\n") + sb_file.write("** Circuit\n") + sb_file.write("********************************************************************************\n\n") + sb_file.write("Xsb_mux_on_1 n_in n_1_1 vsram vsram_n vdd gnd sb_mux_on\n\n") + sb_file.write("Xrouting_wire_load_1 n_1_1 n_2_1 n_hang_1 vsram vsram_n vdd gnd routing_wire_load\n\n") + sb_file.write("Xrouting_wire_load_2 n_2_1 n_3_1 n_hang_2 vsram vsram_n vdd gnd routing_wire_load\n\n") + sb_file.write(".END") + sb_file.close() + + # Come out of swich block directory + os.chdir("../") + + return (mux_name + "/" + mux_name + ".sp") + + +def generate_connection_block_top(mux_name): + """ Generate the top level switch block SPICE file """ + + # Create directories + if not os.path.exists(mux_name): + os.makedirs(mux_name) + # Change to directory + os.chdir(mux_name) + + connection_block_filename = mux_name + ".sp" + cb_file = open(connection_block_filename, 'w') + cb_file.write(".TITLE Connection block multiplexer\n\n") + + cb_file.write("********************************************************************************\n") + cb_file.write("** Include libraries, parameters and other\n") + cb_file.write("********************************************************************************\n\n") + cb_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + cb_file.write("********************************************************************************\n") + cb_file.write("** Setup and input\n") + cb_file.write("********************************************************************************\n\n") + cb_file.write(".TRAN 1p 4n\n") + cb_file.write(".OPTIONS BRIEF=1\n\n") + cb_file.write("* Input signal\n") + cb_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + cb_file.write("********************************************************************************\n") + cb_file.write("** Measurement\n") + cb_file.write("********************************************************************************\n\n") + cb_file.write("* inv_cb_mux_1 delay\n") + cb_file.write(".MEASURE TRAN meas_inv_cb_mux_1_tfall TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.n_in) VAL='supply_v/2' RISE=1\n") + cb_file.write("+ TARG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.Xcb_mux_driver.n_1_1) VAL='supply_v/2' FALL=1\n") + cb_file.write(".MEASURE TRAN meas_inv_cb_mux_1_trise TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.n_in) VAL='supply_v/2' FALL=1\n") + cb_file.write("+ TARG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.Xcb_mux_driver.n_1_1) VAL='supply_v/2' RISE=1\n\n") + cb_file.write("* inv_cb_mux_2 delays\n") + cb_file.write(".MEASURE TRAN meas_inv_cb_mux_2_tfall TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.n_in) VAL='supply_v/2' FALL=1\n") + cb_file.write("+ TARG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' FALL=1\n") + cb_file.write(".MEASURE TRAN meas_inv_cb_mux_2_trise TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.n_in) VAL='supply_v/2' RISE=1\n") + cb_file.write("+ TARG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' RISE=1\n\n") + cb_file.write("* Total delays\n") + cb_file.write(".MEASURE TRAN meas_total_tfall TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.n_in) VAL='supply_v/2' FALL=1\n") + cb_file.write("+ TARG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' FALL=1\n") + cb_file.write(".MEASURE TRAN meas_total_trise TRIG V(Xrouting_wire_load_1.Xrouting_wire_load_tile_1.Xcb_load_on_1.n_in) VAL='supply_v/2' RISE=1\n") + cb_file.write("+ TARG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' RISE=1\n\n") + + cb_file.write("********************************************************************************\n") + cb_file.write("** Circuit\n") + cb_file.write("********************************************************************************\n\n") + cb_file.write("Xsb_mux_on_1 n_in n_1_1 vsram vsram_n vdd gnd sb_mux_on\n") + cb_file.write("Xrouting_wire_load_1 n_1_1 n_1_2 n_1_3 vsram vsram_n vdd gnd routing_wire_load\n") + cb_file.write("Xlocal_routing_wire_load_1 n_1_3 n_1_4 vsram vsram_n vdd gnd local_routing_wire_load\n") + cb_file.write("Xlut_a_driver_1 n_1_4 n_hang1 vsram vsram_n n_hang2 n_hang3 vdd gnd lut_a_driver\n\n") + cb_file.write(".END") + cb_file.close() + + # Come out of connection block directory + os.chdir("../") + + return (mux_name + "/" + mux_name + ".sp") + + +def generate_local_mux_top(mux_name): + """ Generate the top level local mux SPICE file """ + + # Create directories + if not os.path.exists(mux_name): + os.makedirs(mux_name) + # Change to directory + os.chdir(mux_name) + + connection_block_filename = mux_name + ".sp" + local_mux_file = open(connection_block_filename, 'w') + local_mux_file.write(".TITLE Local routing multiplexer\n\n") + + local_mux_file.write("********************************************************************************\n") + local_mux_file.write("** Include libraries, parameters and other\n") + local_mux_file.write("********************************************************************************\n\n") + local_mux_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + local_mux_file.write("********************************************************************************\n") + local_mux_file.write("** Setup and input\n") + local_mux_file.write("********************************************************************************\n\n") + local_mux_file.write(".TRAN 1p 4n\n") + local_mux_file.write(".OPTIONS BRIEF=1\n\n") + local_mux_file.write("* Input signal\n") + local_mux_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + local_mux_file.write("********************************************************************************\n") + local_mux_file.write("** Measurement\n") + local_mux_file.write("********************************************************************************\n\n") + local_mux_file.write("* inv_local_mux_1 delay\n") + local_mux_file.write(".MEASURE TRAN meas_inv_local_mux_1_tfall TRIG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' RISE=1\n") + local_mux_file.write("+ TARG V(n_1_4) VAL='supply_v/2' FALL=1\n") + local_mux_file.write(".MEASURE TRAN meas_inv_local_mux_1_trise TRIG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' FALL=1\n") + local_mux_file.write("+ TARG V(n_1_4) VAL='supply_v/2' RISE=1\n\n") + local_mux_file.write("* Total delays\n") + local_mux_file.write(".MEASURE TRAN meas_total_tfall TRIG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' RISE=1\n") + local_mux_file.write("+ TARG V(n_1_4) VAL='supply_v/2' FALL=1\n") + local_mux_file.write(".MEASURE TRAN meas_total_trise TRIG V(Xlocal_routing_wire_load_1.Xlocal_mux_on_1.n_in) VAL='supply_v/2' FALL=1\n") + local_mux_file.write("+ TARG V(n_1_4) VAL='supply_v/2' RISE=1\n\n") + + local_mux_file.write("********************************************************************************\n") + local_mux_file.write("** Circuit\n") + local_mux_file.write("********************************************************************************\n\n") + local_mux_file.write("Xsb_mux_on_1 n_in n_1_1 vsram vsram_n vdd gnd sb_mux_on\n") + local_mux_file.write("Xrouting_wire_load_1 n_1_1 n_1_2 n_1_3 vsram vsram_n vdd gnd routing_wire_load\n") + local_mux_file.write("Xlocal_routing_wire_load_1 n_1_3 n_1_4 vsram vsram_n vdd gnd local_routing_wire_load\n") + local_mux_file.write("Xlut_A_driver_1 n_1_4 n_hang1 vsram vsram_n n_hang2 n_hang3 vdd gnd lut_A_driver\n\n") + local_mux_file.write(".END") + local_mux_file.close() + + # Come out of connection block directory + os.chdir("../") + + return (mux_name + "/" + mux_name + ".sp") + + +def generate_lut6_top(lut_name): + """ Generate the top level 6-LUT SPICE file """ + + # Create directory + if not os.path.exists(lut_name): + os.makedirs(lut_name) + # Change to directory + os.chdir(lut_name) + + lut_filename = lut_name + ".sp" + lut_file = open(lut_filename, 'w') + lut_file.write(".TITLE 6-LUT\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Include libraries, parameters and other\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Setup and input\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write(".TRAN 1p 4n\n") + lut_file.write(".OPTIONS BRIEF=1\n\n") + lut_file.write("* Input signal\n") + lut_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Measurement\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write("* inv_lut_0sram_driver_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_1_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_1_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* inv_lut_sram_driver_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_2_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_2_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_int_buffer_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_6_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_6_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_int_buffer_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_7_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_7_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_out_buffer_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_11_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_11_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_out_buffer_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Total delays\n") + lut_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + lut_file.write(".MEASURE TRAN meas_logic_low_voltage FIND V(n_out) AT=3n\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Circuit\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write("Xlut n_in n_out vdd vdd vdd vdd vdd vdd vdd gnd lut\n\n") + lut_file.write("Xlut_output_load n_out n_local_out n_general_out vsram vsram_n vdd gnd lut_output_load\n\n") + + lut_file.write(".END") + lut_file.close() + + # Come out of lut directory + os.chdir("../") + + return (lut_name + "/" + lut_name + ".sp") + + +def generate_lut5_top(lut_name): + """ Generate the top level 5-LUT SPICE file """ + + # Create directory + if not os.path.exists(lut_name): + os.makedirs(lut_name) + # Change to directory + os.chdir(lut_name) + + lut_filename = lut_name + ".sp" + lut_file = open(lut_filename, 'w') + lut_file.write(".TITLE 5-LUT\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Include libraries, parameters and other\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Setup and input\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write(".TRAN 1p 4n\n") + lut_file.write(".OPTIONS BRIEF=1\n\n") + lut_file.write("* Input signal\n") + lut_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Measurement\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write("* inv_lut_0sram_driver_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_1_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_1_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* inv_lut_sram_driver_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_2_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_2_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_int_buffer_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_6_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_6_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_int_buffer_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_7_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_7_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_out_buffer_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_11_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_11_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_out_buffer_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Total delays\n") + lut_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + lut_file.write(".MEASURE TRAN meas_logic_low_voltage FIND V(n_out) AT=3n\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Circuit\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write("Xlut n_in n_out vdd vdd vdd vdd vdd vdd vdd gnd lut\n\n") + lut_file.write("Xlut_output_load n_out n_local_out n_general_out vsram vsram_n vdd gnd lut_output_load\n\n") + + lut_file.write(".END") + lut_file.close() + + # Come out of lut directory + os.chdir("../") + + return (lut_name + "/" + lut_name + ".sp") + + +def generate_lut4_top(lut_name): + """ Generate the top level 4-LUT SPICE file """ + + # Create directory + if not os.path.exists(lut_name): + os.makedirs(lut_name) + # Change to directory + os.chdir(lut_name) + + lut_filename = lut_name + ".sp" + lut_file = open(lut_filename, 'w') + lut_file.write(".TITLE 4-LUT\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Include libraries, parameters and other\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Setup and input\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write(".TRAN 1p 4n\n") + lut_file.write(".OPTIONS BRIEF=1\n\n") + lut_file.write("* Input signal\n") + lut_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Measurement\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write("* inv_lut_0sram_driver_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_1_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_1_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* inv_lut_sram_driver_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_2_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_0sram_driver_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_2_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_int_buffer_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_5_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_5_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_int_buffer_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_6_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_int_buffer_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_6_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_out_buffer_1 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_1_tfall TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(Xlut.n_9_1) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_1_trise TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(Xlut.n_9_1) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Xinv_lut_out_buffer_2 delay\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_2_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_inv_lut_out_buffer_2_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + lut_file.write("* Total delays\n") + lut_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_in) VAL='supply_v/2' FALL=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + lut_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_in) VAL='supply_v/2' RISE=1\n") + lut_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + lut_file.write(".MEASURE TRAN meas_logic_low_voltage FIND V(n_out) AT=3n\n\n") + + lut_file.write("********************************************************************************\n") + lut_file.write("** Circuit\n") + lut_file.write("********************************************************************************\n\n") + lut_file.write("Xlut n_in n_out vdd vdd vdd vdd vdd vdd vdd gnd lut\n\n") + lut_file.write("Xlut_output_load n_out n_local_out n_general_out vsram vsram_n vdd gnd lut_output_load\n\n") + + lut_file.write(".END") + lut_file.close() + + # Come out of lut directory + os.chdir("../") + + return (lut_name + "/" + lut_name + ".sp") + + +def generate_lut_driver_top(input_driver_name, input_driver_type): + """ Generate the top level lut input driver SPICE file """ + + # Create directories + if not os.path.exists(input_driver_name): + os.makedirs(input_driver_name) + # Change to directory + os.chdir(input_driver_name) + + lut_driver_filename = input_driver_name + ".sp" + input_driver_file = open(lut_driver_filename, 'w') + input_driver_file.write(".TITLE " + input_driver_name + " \n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Include libraries, parameters and other\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Setup and input\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write(".TRAN 1p 4n\n") + input_driver_file.write(".OPTIONS BRIEF=1\n\n") + input_driver_file.write("* Input signal\n") + input_driver_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Measurement\n") + input_driver_file.write("********************************************************************************\n\n") + # We measure different things based on the input driver type + if input_driver_type != "default": + input_driver_file.write("* inv_" + input_driver_name + "_0 delays\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_0_tfall TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(X" + input_driver_name + "_1.n_1_1) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_0_trise TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(X" + input_driver_name + "_1.n_1_1) VAL='supply_v/2' RISE=1\n\n") + input_driver_file.write("* inv_" + input_driver_name + "_1 delays\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_1_tfall TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(X" + input_driver_name + "_1.n_3_1) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_1_trise TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(X" + input_driver_name + "_1.n_3_1) VAL='supply_v/2' RISE=1\n\n") + input_driver_file.write("* inv_" + input_driver_name + "_2 delays\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_2_tfall TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_2_trise TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + input_driver_file.write("* Total delays\n") + input_driver_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Circuit\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write("Xcb_mux_on_1 n_in n_1_1 vsram vsram_n vdd gnd cb_mux_on\n") + input_driver_file.write("Xlocal_routing_wire_load_1 n_1_1 n_1_2 vsram vsram_n vdd gnd local_routing_wire_load\n") + input_driver_file.write("X" + input_driver_name + "_1 n_1_2 n_out vsram vsram_n n_rsel n_2_1 vdd gnd " + input_driver_name + "\n") + if input_driver_type == "default_rsel" or input_driver_type == "reg_fb_rsel": + # Connect a load to n_rsel node + input_driver_file.write("Xff n_rsel n_ff_out vsram vsram_n gnd vdd gnd vdd gnd vdd vdd gnd ff\n") + input_driver_file.write("X" + input_driver_name + "_not_1 n_2_1 n_out_n vdd gnd " + input_driver_name + "_not\n") + input_driver_file.write("X" + input_driver_name + "_load_1 n_out vdd gnd " + input_driver_name + "_load\n") + input_driver_file.write("X" + input_driver_name + "_load_2 n_out_n vdd gnd " + input_driver_name + "_load\n\n") + input_driver_file.write(".END") + input_driver_file.close() + + # Come out of lut_driver directory + os.chdir("../") + + return (input_driver_name + "/" + input_driver_name + ".sp") + + +def generate_lut_driver_not_top(input_driver_name, input_driver_type): + """ Generate the top level lut input not driver SPICE file """ + + # Create directories + input_driver_name_no_not = input_driver_name.replace("_not", "") + if not os.path.exists(input_driver_name_no_not): + os.makedirs(input_driver_name_no_not) + # Change to directory + os.chdir(input_driver_name_no_not) + + lut_driver_filename = input_driver_name + ".sp" + input_driver_file = open(lut_driver_filename, 'w') + input_driver_file.write(".TITLE " + input_driver_name + " \n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Include libraries, parameters and other\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Setup and input\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write(".TRAN 1p 4n\n") + input_driver_file.write(".OPTIONS BRIEF=1\n\n") + input_driver_file.write("* Input signal\n") + input_driver_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Measurement\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write("* inv_" + input_driver_name + "_1 delays\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_1_tfall TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(X" + input_driver_name + "_1.n_1_1) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_1_trise TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(X" + input_driver_name + "_1.n_1_1) VAL='supply_v/2' RISE=1\n\n") + input_driver_file.write("* inv_" + input_driver_name + "_2 delays\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_2_tfall TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(n_out_n) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_inv_" + input_driver_name + "_2_trise TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(n_out_n) VAL='supply_v/2' RISE=1\n\n") + input_driver_file.write("* Total delays\n") + input_driver_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_1_2) VAL='supply_v/2' FALL=1\n") + input_driver_file.write("+ TARG V(n_out_n) VAL='supply_v/2' FALL=1\n") + input_driver_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_1_2) VAL='supply_v/2' RISE=1\n") + input_driver_file.write("+ TARG V(n_out_n) VAL='supply_v/2' RISE=1\n\n") + + input_driver_file.write("********************************************************************************\n") + input_driver_file.write("** Circuit\n") + input_driver_file.write("********************************************************************************\n\n") + input_driver_file.write("Xcb_mux_on_1 n_in n_1_1 vsram vsram_n vdd gnd cb_mux_on\n") + input_driver_file.write("Xlocal_routing_wire_load_1 n_1_1 n_1_2 vsram vsram_n vdd gnd local_routing_wire_load\n") + input_driver_file.write("X" + input_driver_name_no_not + "_1 n_1_2 n_out vsram vsram_n n_rsel n_2_1 vdd gnd " + input_driver_name_no_not + "\n") + if input_driver_type == "default_rsel" or input_driver_type == "reg_fb_rsel": + # Connect a load to n_rsel node + input_driver_file.write("Xff n_rsel n_ff_out vsram vsram_n gnd vdd gnd vdd gnd vdd vdd gnd ff\n") + input_driver_file.write("X" + input_driver_name + "_1 n_2_1 n_out_n vdd gnd " + input_driver_name + "\n") + input_driver_file.write("X" + input_driver_name_no_not + "_load_1 n_out n_vdd n_gnd " + input_driver_name_no_not + "_load\n") + input_driver_file.write("X" + input_driver_name_no_not + "_load_2 n_out_n n_vdd n_gnd " + input_driver_name_no_not + "_load\n\n") + input_driver_file.write(".END") + input_driver_file.close() + + # Come out of lut_driver directory + os.chdir("../") + + return (input_driver_name_no_not + "/" + input_driver_name + ".sp") + + +def generate_lut_and_driver_top(input_driver_name, input_driver_type): + """ Generate the top level lut with driver SPICE file. We use this to measure final delays of paths through the LUT. """ + + # Create directories + if not os.path.exists(input_driver_name): + os.makedirs(input_driver_name) + # Change to directory + os.chdir(input_driver_name) + + lut_driver_filename = input_driver_name + "_with_lut.sp" + spice_file = open(lut_driver_filename, 'w') + spice_file.write(".TITLE " + input_driver_name + " \n\n") + + spice_file.write("********************************************************************************\n") + spice_file.write("** Include libraries, parameters and other\n") + spice_file.write("********************************************************************************\n\n") + spice_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + spice_file.write("********************************************************************************\n") + spice_file.write("** Setup and input\n") + spice_file.write("********************************************************************************\n\n") + spice_file.write(".TRAN 1p 16n\n") + spice_file.write(".OPTIONS BRIEF=1\n\n") + spice_file.write("* Input signal\n") + spice_file.write("VIN_SRAM n_in_sram gnd PULSE (0 supply_v 4n 0 0 4n 8n)\n") + spice_file.write("VIN_GATE n_in_gate gnd PULSE (supply_v 0 3n 0 0 2n 4n)\n\n") + + spice_file.write("********************************************************************************\n") + spice_file.write("** Measurement\n") + spice_file.write("********************************************************************************\n\n") + spice_file.write("* Total delays\n") + spice_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_3_1) VAL='supply_v/2' RISE=2\n") + spice_file.write("+ TARG V(n_out) VAL='supply_v/2' FALL=1\n") + spice_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_3_1) VAL='supply_v/2' RISE=1\n") + spice_file.write("+ TARG V(n_out) VAL='supply_v/2' RISE=1\n\n") + + spice_file.write("********************************************************************************\n") + spice_file.write("** Circuit\n") + spice_file.write("********************************************************************************\n\n") + spice_file.write("Xcb_mux_on_1 n_in_gate n_1_1 vsram vsram_n vdd gnd cb_mux_on\n") + spice_file.write("Xlocal_routing_wire_load_1 n_1_1 n_1_2 vsram vsram_n vdd gnd local_routing_wire_load\n") + spice_file.write("X" + input_driver_name + "_1 n_1_2 n_3_1 vsram vsram_n n_rsel n_2_1 vdd gnd " + input_driver_name + "\n") + if input_driver_type == "default_rsel" or input_driver_type == "reg_fb_rsel": + # Connect a load to n_rsel node + spice_file.write("Xff n_rsel n_ff_out vsram vsram_n gnd vdd gnd vdd gnd vdd vdd gnd ff\n") + spice_file.write("X" + input_driver_name + "_not_1 n_2_1 n_1_4 vdd gnd " + input_driver_name + "_not\n") + + # Connect the LUT driver to a different LUT input based on LUT driver name + if input_driver_name == "lut_a_driver": + spice_file.write("Xlut n_in_sram n_out n_3_1 vdd vdd vdd vdd vdd vdd gnd lut\n") + elif input_driver_name == "lut_b_driver": + spice_file.write("Xlut n_in_sram n_out vdd n_3_1 vdd vdd vdd vdd vdd gnd lut\n") + elif input_driver_name == "lut_c_driver": + spice_file.write("Xlut n_in_sram n_out vdd vdd n_3_1 vdd vdd vdd vdd gnd lut\n") + elif input_driver_name == "lut_d_driver": + spice_file.write("Xlut n_in_sram n_out vdd vdd vdd n_3_1 vdd vdd vdd gnd lut\n") + elif input_driver_name == "lut_e_driver": + spice_file.write("Xlut n_in_sram n_out vdd vdd vdd vdd n_3_1 vdd vdd gnd lut\n") + elif input_driver_name == "lut_f_driver": + spice_file.write("Xlut n_in_sram n_out vdd vdd vdd vdd vdd n_3_1 vdd gnd lut\n") + + spice_file.write("Xlut_output_load n_out n_local_out n_general_out vsram vsram_n vdd gnd lut_output_load\n\n") + + + spice_file.write(".END") + spice_file.close() + + # Come out of lut_driver directory + os.chdir("../") + + +def generate_local_ble_output_top(name): + """ Generate the top level local ble output SPICE file """ + + # Create directories + if not os.path.exists(name): + os.makedirs(name) + # Change to directory + os.chdir(name) + + local_ble_output_filename = name + ".sp" + top_file = open(local_ble_output_filename, 'w') + top_file.write(".TITLE Local BLE output\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Include libraries, parameters and other\n") + top_file.write("********************************************************************************\n\n") + top_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Setup and input\n") + top_file.write("********************************************************************************\n\n") + top_file.write(".TRAN 1p 4n\n") + top_file.write(".OPTIONS BRIEF=1\n\n") + top_file.write("* Input signal\n") + top_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Measurement\n") + top_file.write("********************************************************************************\n\n") + top_file.write("* inv_local_ble_output_1 delay\n") + top_file.write(".MEASURE TRAN meas_inv_local_ble_output_1_tfall TRIG V(n_1_1) VAL='supply_v/2' RISE=1\n") + top_file.write("+ TARG V(Xlut_output_load.Xble_outputs.Xlocal_ble_output_1.n_2_1) VAL='supply_v/2' FALL=1\n") + top_file.write(".MEASURE TRAN meas_inv_local_ble_output_1_trise TRIG V(n_1_1) VAL='supply_v/2' FALL=1\n") + top_file.write("+ TARG V(Xlut_output_load.Xble_outputs.Xlocal_ble_output_1.n_2_1) VAL='supply_v/2' RISE=1\n\n") + top_file.write("* inv_local_ble_output_2 delays\n") + top_file.write(".MEASURE TRAN meas_inv_local_ble_output_2_tfall TRIG V(n_1_1) VAL='supply_v/2' FALL=1\n") + top_file.write("+ TARG V(Xlocal_ble_output_load.n_1_2) VAL='supply_v/2' RISE=1\n") + top_file.write(".MEASURE TRAN meas_inv_local_ble_output_2_trise TRIG V(n_1_1) VAL='supply_v/2' RISE=1\n") + top_file.write("+ TARG V(Xlocal_ble_output_load.n_1_2) VAL='supply_v/2' FALL=1\n\n") + top_file.write("* Total delays\n") + top_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_1_1) VAL='supply_v/2' FALL=1\n") + top_file.write("+ TARG V(Xlocal_ble_output_load.n_1_2) VAL='supply_v/2' RISE=1\n") + top_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_1_1) VAL='supply_v/2' RISE=1\n") + top_file.write("+ TARG V(Xlocal_ble_output_load.n_1_2) VAL='supply_v/2' FALL=1\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Circuit\n") + top_file.write("********************************************************************************\n\n") + top_file.write("Xlut n_in n_1_1 vdd vdd vdd vdd vdd vdd vdd gnd lut\n\n") + top_file.write("Xlut_output_load n_1_1 n_local_out n_general_out vsram vsram_n vdd gnd lut_output_load\n\n") + top_file.write("Xlocal_ble_output_load n_local_out vsram vsram_n vdd gnd local_ble_output_load\n") + top_file.write(".END") + top_file.close() + + # Come out of connection block directory + os.chdir("../") + + return (name + "/" + name + ".sp") + + +def generate_general_ble_output_top(name): + """ """ + + # Create directories + if not os.path.exists(name): + os.makedirs(name) + # Change to directory + os.chdir(name) + + general_ble_output_filename = name + ".sp" + top_file = open(general_ble_output_filename, 'w') + top_file.write(".TITLE General BLE output\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Include libraries, parameters and other\n") + top_file.write("********************************************************************************\n\n") + top_file.write(".LIB \"../includes.l\" INCLUDES\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Setup and input\n") + top_file.write("********************************************************************************\n\n") + top_file.write(".TRAN 1p 4n\n") + top_file.write(".OPTIONS BRIEF=1\n\n") + top_file.write("* Input signal\n") + top_file.write("VIN n_in gnd PULSE (0 supply_v 0 0 0 2n 4n)\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Measurement\n") + top_file.write("********************************************************************************\n\n") + top_file.write("* inv_general_ble_output_1 delay\n") + top_file.write(".MEASURE TRAN meas_inv_general_ble_output_1_tfall TRIG V(n_1_1) VAL='supply_v/2' RISE=1\n") + top_file.write("+ TARG V(Xlut_output_load.Xble_outputs.Xgeneral_ble_output_1.n_2_1) VAL='supply_v/2' FALL=1\n") + top_file.write(".MEASURE TRAN meas_inv_general_ble_output_1_trise TRIG V(n_1_1) VAL='supply_v/2' FALL=1\n") + top_file.write("+ TARG V(Xlut_output_load.Xble_outputs.Xgeneral_ble_output_1.n_2_1) VAL='supply_v/2' RISE=1\n\n") + top_file.write("* inv_general_ble_output_2 delays\n") + top_file.write(".MEASURE TRAN meas_inv_general_ble_output_2_tfall TRIG V(n_1_1) VAL='supply_v/2' FALL=1\n") + top_file.write("+ TARG V(Xgeneral_ble_output_load.n_1_9) VAL='supply_v/2' FALL=1\n") + top_file.write(".MEASURE TRAN meas_inv_general_ble_output_2_trise TRIG V(n_1_1) VAL='supply_v/2' RISE=1\n") + top_file.write("+ TARG V(Xgeneral_ble_output_load.n_1_9) VAL='supply_v/2' RISE=1\n\n") + top_file.write("* Total delays\n") + top_file.write(".MEASURE TRAN meas_total_tfall TRIG V(n_1_1) VAL='supply_v/2' FALL=1\n") + top_file.write("+ TARG V(Xgeneral_ble_output_load.n_1_9) VAL='supply_v/2' FALL=1\n") + top_file.write(".MEASURE TRAN meas_total_trise TRIG V(n_1_1) VAL='supply_v/2' RISE=1\n") + top_file.write("+ TARG V(Xgeneral_ble_output_load.n_1_9) VAL='supply_v/2' RISE=1\n\n") + + top_file.write("********************************************************************************\n") + top_file.write("** Circuit\n") + top_file.write("********************************************************************************\n\n") + top_file.write("Xlut n_in n_1_1 vdd vdd vdd vdd vdd vdd vdd gnd lut\n\n") + top_file.write("Xlut_output_load n_1_1 n_local_out n_general_out vsram vsram_n vdd gnd lut_output_load\n\n") + top_file.write("Xgeneral_ble_output_load n_general_out n_hang1 vsram vsram_n vdd gnd general_ble_output_load\n") + top_file.write(".END") + top_file.close() + + # Come out of connection block directory + os.chdir("../") + + return (name + "/" + name + ".sp") + + \ No newline at end of file diff --git a/coffe/tran_sizing.py b/coffe/tran_sizing.py new file mode 100644 index 0000000..84a077a --- /dev/null +++ b/coffe/tran_sizing.py @@ -0,0 +1,1363 @@ +# This module contains functions to perform FPGA transistor sizing +# +# The most important function is 'size_fpga_transisors' located +# at the bottom of this file. It performs transistor sizing on the +# 'fpga' object passed as an argument and performs transistor sizing +# on that FPGA based on certain transistor sizing settings, which are +# also passed as arguments. + +import os +import math +import time +import spice + + +def find_best_config(spice_results, area_exp, delay_exp): + """ """ + + # Find best area, delay and area_delay configs + best_area = spice_results[0][2] + best_delay = spice_results[0][3] + best_area_delay = spice_results[0][4] + best_area_config = 0 + best_delay_config = 0 + best_area_delay_config = 0 + for i in range(len(spice_results)): + if spice_results[i][2] < best_area: + best_area = spice_results[i][2] + best_area_config = i + if spice_results[i][3] < best_delay: + best_delay = spice_results[i][3] + best_delay_config = i + if spice_results[i][4] < best_area_delay: + best_area_delay = spice_results[i][4] + best_area_delay_config = i + + return best_area_delay_config + + +def export_spice_configs(results_filename, spice_param_names, spice_configs, lvl_rest_names, lvl_rest_configs): + """ Export all transistor sizing combinations to a file """ + + print "Exporting all configurations to: " + results_filename + + # Open file for writing + results_file = open(results_filename, 'a') + results_file.write("TRANSISTOR SIZING COMBINATIONS\n") + + # Write Header + header_str = "Config\t" + for name in lvl_rest_names: + header_str = header_str + name + "\t" + for name in spice_param_names: + header_str = header_str + name + "\t" + results_file.write(header_str + "\n") + + # Write all configs + config_number = 0 + for lvl_rest_config in lvl_rest_configs: + config_str2 = "" + for j in range(len(lvl_rest_names)): + config_str2 = config_str2 + str(lvl_rest_config[j]) + "\t\t\t" + for config in spice_configs: + config_str = "" + config_number = config_number + 1 + config_str = config_str + str(config_number) + "\t\t" + config_str2 + for i in range(len(spice_param_names)): + config_str = config_str + str(config[i]) + "\t\t\t" + results_file.write(config_str + "\n") + + # Close file + results_file.write("\n\n\n") + results_file.close() + +def export_transistor_sizes(filename, transistor_sizes): + + tran_size_file = open(filename, 'w') + + for tran_name, tran_size in transistor_sizes.iteritems(): + tran_size_file.write(tran_name + " " + str(tran_size) + "\n") + + tran_size_file.close() + + +def export_all_results(results_filename, element_names, sizing_combos, cost_list, area_list, eval_delay_list, tfall_trise_list): + """ Write all area and delay results to a file. """ + + # Open file for writing + results_file = open(results_filename, 'w') + + # Make header string + header_str = "Rank," + for name in element_names: + header_str += name + "," + header_str += "Cost,Area,EvalDelay,tfall,trise," + + # Write header string to the file + results_file.write(header_str + "\n") + + # Write out all results + for i in range(len(cost_list)): + # Get the combo index + combo_index = cost_list[i][1] + # Write rank + results_file.write(str(i+1) + ",") + # Write transistor sizes + for j in range(len(element_names)): + results_file.write(str(sizing_combos[combo_index][j]) + ",") + # Write Cost, area, delay, tfall, trise, erf_error + results_file.write(str(cost_list[i][0]) + ",") + results_file.write(str(area_list[combo_index]) + ",") + results_file.write(str(eval_delay_list[combo_index]) + ",") + results_file.write(str(tfall_trise_list[combo_index][0]) + ",") + results_file.write(str(tfall_trise_list[combo_index][1]) + ",") + results_file.write("\n") + + # Close file + results_file.close() + + +def export_erf_results(results_filename, element_names, sizing_combos, best_results): + """ Export the post-erfed results to a CSV file. + best_results is a tuple: (cost, combo_index, area, delay, tfall, trise, erf_ratios)""" + + # Open file for writing + results_file = open(results_filename, 'w') + + # Make header string + header_str = "Rank," + for name in element_names: + header_str += name + "," + header_str += "Cost,Area,EvalDelay,tfall,trise,erf_error," + erf_ratios_names = best_results[0][6].keys() + for name in erf_ratios_names: + header_str += name + "," + + # Write header string to the file + results_file.write(header_str + "\n") + + # Write out all results + rank_counter = 1 + for cost, combo_index, area, delay, tfall, trise, erf_ratios in best_results: + # Write rank + results_file.write(str(rank_counter) + ",") + # Write transistor sizes + for j in range(len(element_names)): + results_file.write(str(sizing_combos[combo_index][j]) + ",") + # Write Cost, area, delay, tfall, trise, erf_error + results_file.write(str(cost) + ",") + results_file.write(str(area) + ",") + results_file.write(str(delay) + ",") + results_file.write(str(tfall) + ",") + results_file.write(str(trise) + ",") + erf_error = abs(tfall - trise)/min(tfall, trise) + results_file.write(str(erf_error) + ",") + # Add ERF ratios + for name in erf_ratios_names: + results_file.write(str(erf_ratios[name]) + ",") + results_file.write("\n") + + rank_counter += 1 + + # Close file + results_file.close() + + +def get_eval_area(fpga_inst, opt_type, subcircuit): + + # Get area based on optimization type (subcircuit if local optimization, tile if global) + if opt_type == "local": + area = fpga_inst.area_dict[subcircuit.name] + else: + area = fpga_inst.area_dict["tile"] + + return area + +def get_final_area(fpga_inst, opt_type, subcircuit): + + return get_eval_area(fpga_inst, opt_type, subcircuit) + + +def get_eval_delay(fpga_inst, opt_type, subcircuit, tfall, trise): + + # Use average delay for evaluation + delay = (tfall + trise)/2 + + # if tfall > trise: + # delay = tfall + # else: + # delay = trise + + if opt_type == "local": + return delay + else: + # We need to get the delay of a representative critical path + # Let's first set the delay for this subcircuit + subcircuit.delay = delay + + path_delay = 0 + + # Switch block + path_delay += fpga_inst.sb_mux.delay*fpga_inst.sb_mux.delay_weight + # Connection block + path_delay += fpga_inst.cb_mux.delay*fpga_inst.cb_mux.delay_weight + # Local mux + path_delay += fpga_inst.logic_cluster.local_mux.delay*fpga_inst.logic_cluster.local_mux.delay_weight + # LUT + path_delay += fpga_inst.logic_cluster.ble.lut.delay*fpga_inst.logic_cluster.ble.lut.delay_weight + # LUT input drivers + for lut_input_name, lut_input in fpga_inst.logic_cluster.ble.lut.input_drivers.iteritems(): + path_delay += lut_input.driver.delay*lut_input.driver.delay_weight + path_delay += lut_input.not_driver.delay*lut_input.not_driver.delay_weight + # Local BLE output + path_delay += fpga_inst.logic_cluster.ble.local_output.delay*fpga_inst.logic_cluster.ble.local_output.delay_weight + # General BLE output + path_delay += fpga_inst.logic_cluster.ble.general_output.delay*fpga_inst.logic_cluster.ble.general_output.delay_weight + + return path_delay + + +def get_final_delay(fpga_inst, opt_type, subcircuit, tfall, trise): + + # Use largest delay for final results + if tfall > trise: + delay = tfall + else: + delay = trise + + if opt_type == "local": + return delay + else: + # We need to get the delay of a representative critical path + # Let's first set the delay for this subcircuit + subcircuit.delay = delay + + path_delay = 0 + + # Switch block + path_delay += fpga_inst.sb_mux.delay*fpga_inst.sb_mux.delay_weight + # Connection block + path_delay += fpga_inst.cb_mux.delay*fpga_inst.cb_mux.delay_weight + # Local mux + path_delay += fpga_inst.logic_cluster.local_mux.delay*fpga_inst.logic_cluster.local_mux.delay_weight + # LUT + path_delay += fpga_inst.logic_cluster.ble.lut.delay*fpga_inst.logic_cluster.ble.lut.delay_weight + # LUT input drivers + for lut_input_name, lut_input in fpga_inst.logic_cluster.ble.lut.input_drivers.iteritems(): + path_delay += lut_input.driver.delay*lut_input.driver.delay_weight + path_delay += lut_input.not_driver.delay*lut_input.not_driver.delay_weight + # Local BLE output + path_delay += fpga_inst.logic_cluster.ble.local_output.delay*fpga_inst.logic_cluster.ble.local_output.delay_weight + # General BLE output + path_delay += fpga_inst.logic_cluster.ble.general_output.delay*fpga_inst.logic_cluster.ble.general_output.delay_weight + + return path_delay + + +def cost_function(area, delay, area_opt_weight, delay_opt_weight): + + return pow(area,area_opt_weight)*pow(delay,delay_opt_weight) + + +def erf_combo(fpga_inst, spice_filedir, spice_filename, element_names, combo, num_hspice_sims): + """ Equalize the rise and fall of all inverters in a transistor sizing combination. + Returns the inverter ratios that give equal rise and fall for this combo. """ + + # We want to ERF a transistor sizing combination + # The first thing we need to do is initialize the transistor sizes file. + # We need to do this so that any elements that aren't inverters have the right value (like ptran for instance). + spice.initialize_transistor_file(fpga_inst.tran_sizes_filename, element_names, combo, fpga_inst.specs.min_tran_width) + # Update transistor sizes + fpga_inst._update_transistor_sizes(element_names, combo) + # Calculate area of everything + fpga_inst.update_area() + # Re-calculate wire lengths + fpga_inst.update_wires() + # Update wire resistance and capacitance + fpga_inst.update_wire_rc() + # Update wire R and C SPICE file + fpga_inst.update_wire_rc_file() + # Find ERF ratios + erf_ratios = spice.erf(spice_filedir, + spice_filename, + fpga_inst.tran_sizes_filename, + element_names, + combo, + fpga_inst.specs.min_tran_width, + num_hspice_sims) + + return erf_ratios + + +def run_combo(fpga_inst, spice_filedir, spice_filename, element_names, combo, erf_ratios): + """ Run HSPICE to measure delay for this transistor sizing combination. + Returns tfall, trise """ + + # Change transistor sizes in SPICE file + spice.set_parameters(fpga_inst.tran_sizes_filename, element_names, combo, erf_ratios, fpga_inst.specs.min_tran_width) + # Update transistor sizes + fpga_inst._update_transistor_sizes(element_names, combo, erf_ratios) + # Calculate area of everything + fpga_inst.update_area() + # Re-calculate wire lengths + fpga_inst.update_wires() + # Update wire resistance and capacitance + fpga_inst.update_wire_rc() + # Update wire R and C SPICE file + fpga_inst.update_wire_rc_file() + # Run HSPICE with the current transistor sizes + spice_meas = spice.run(spice_filedir, spice_filename) + # Extract total delay from measurements + tfall = spice_meas["meas_total_tfall"] + trise = spice_meas["meas_total_trise"] + + return tfall, trise + + +def search_ranges(sizing_ranges, fpga_inst, sizable_circuit, opt_type, re_erf, area_opt_weight, + delay_opt_weight, outer_iter, inner_iter, bunch_num, num_hspice_sims): + """ + Function for searching a range of transistor sizes. + The first thing we do is determine P/N ratios to use for these size ranges. + Then, we calculate area and wire loads for each transistor sizing combination. + An HSPICE simulation is performed for each transistor sizing combination with the + appropriate wire loading. + With the area and delay of each sizing combination we calculate the cost of each + and we sort based on cost to find the least cost transistor sizing within the + sizing ranges. + We re-balance the rise and fall times of M top transistor sizing combinations + (M = the 're_erf' argument). This re-balancing might change the final ranking. + So, we sort by cost again and choose the lowest cost sizing combination as the + best transistor sizing for the given ranges. + """ + + # Get the SPICE file name and the directory name + spice_filename = "" + spice_filedir = "" + if "/" in sizable_circuit.top_spice_path: + words = sizable_circuit.top_spice_path.split("/") + for i in range(len(words)-1): + spice_filedir = spice_filedir + words[i] + "/" + spice_filename = words[len(words)-1] + + # Export current transistor sizes + tran_sizes_filename = (spice_filedir + + "sizes_" + sizable_circuit.name + + "_o" + str(outer_iter) + + "_i" + str(inner_iter) + + "_b" + str(bunch_num) + ".txt") + export_transistor_sizes(tran_sizes_filename, fpga_inst.transistor_sizes) + + # Expand ranges to get a list of all possible sizing combinations from ranges + element_names, sizing_combos = spice.expand_ranges(sizing_ranges) + + # Find the combo that is near the middle of all ranges + middle_combo = spice.get_middle_value_config(element_names, sizing_ranges) + + # Find ERF ratios for middle combo + print "Determining initial inverter P/N ratios..." + erf_ratios, num_hspice_sims = erf_combo(fpga_inst, spice_filedir, spice_filename, element_names, middle_combo, num_hspice_sims) + + # Count number of HSPICE simulations (does not ERF sims) + num_hspice_sims += len(sizing_combos) + + # For each transistor sizing combination, we want to calculate area, wire sizes, and wire R and C + print "Calculating area and wire data for all transistor sizing combinations..." + + area_list = [] + wire_rc_list = [] + eval_delay_list = [] + + for combo in sizing_combos: + # Update FPGA transistor sizes + fpga_inst._update_transistor_sizes(element_names, combo, erf_ratios) + # Calculate area of everything + fpga_inst.update_area() + # Get evaluation area + area_list.append(get_eval_area(fpga_inst, opt_type, sizable_circuit)) + # Re-calculate wire lengths + fpga_inst.update_wires() + # Update wire resistance and capacitance + fpga_inst.update_wire_rc() + wire_rc = fpga_inst.wire_rc_dict + wire_rc_list.append(wire_rc) + + # Now we have area and all wire R and C for each transistor sizing combination + # All we have left to do is measure delay + # We are going to create a ".DATA" block for HSPICE simulation + # This data block contains all the transistor sizing combinations we are going to try + # as well as the correct R and C data for the wire loading + spice.make_data_block(element_names, sizing_combos, erf_ratios, wire_rc_list, fpga_inst.specs.min_tran_width) + + # Run HSPICE data sweep + print "Running HSPICE for " + str(len(sizing_combos)) + " transistor sizing combinations..." + tfall_trise_list = spice.sweep_data(spice_filedir, spice_filename) + + # Get delay metric used for evaluation for each transistor sizing combo as well as ERF error + for i in range(len(tfall_trise_list)): + # Calculate evaluation delay + tfall_trise = tfall_trise_list[i] + delay = get_eval_delay(fpga_inst, opt_type, sizable_circuit, tfall_trise[0], tfall_trise[1]) + eval_delay_list.append(delay) + + # len(area_list) should be equal to len(delay_list), make sure... + assert len(area_list) == len(eval_delay_list) + + # Calculate cost for each combo (area-delay product) + # Results list holds a tuple, (cost, combo_index, area, delay) + print "Calculating cost for each transistor sizing combinations..." + print "" + cost_list = [] + for i in range(len(eval_delay_list)): + area = area_list[i] + delay = eval_delay_list[i] + cost = cost_function(area, delay, area_opt_weight, delay_opt_weight) + cost_list.append((cost, i)) + + # Sort based on cost + cost_list.sort() + + # Print top 10 results + print "TOP 10 BEST COST RESULTS" + print "------------------------" + + for i in range(min(10, len(cost_list))): + combo_index = cost_list[i][1] + print ("Combo #" + str(combo_index).ljust(6) + + "cost=" + str(round(cost_list[i][0],6)).ljust(9) + + "area=" + str(round(area_list[combo_index]/1000000,3)).ljust(8) + + "delay=" + str(round(eval_delay_list[combo_index],13)).ljust(10) + + "tfall=" + str(round(tfall_trise_list[combo_index][0],13)).ljust(10) + + "trise=" + str(round(tfall_trise_list[combo_index][1],13)).ljust(10)) + print "" + + # Write results to file + export_filename = (spice_filedir + + sizable_circuit.name + + "_o" + str(outer_iter) + + "_i" + str(inner_iter) + + "_b" + str(bunch_num) + ".csv") + export_all_results(export_filename, + element_names, + sizing_combos, + cost_list, + area_list, + eval_delay_list, + tfall_trise_list) + + # Re-ERF some of the top results + # This is the number of top results to re-ERF + re_erf_num = min(re_erf, len(cost_list)) + best_results = [] + for i in range(re_erf_num): + # Re-ERF i-th best combo + print "Re-equalizing rise and fall times on combo #" + str(cost_list[i][1]) + " (ranked #" + str(i+1) + ")\n" + erf_ratios, num_hspice_sims = erf_combo(fpga_inst, spice_filedir, spice_filename, element_names, sizing_combos[cost_list[i][1]], num_hspice_sims) + # Measure delay for combo + trise, tfall = run_combo(fpga_inst, spice_filedir, spice_filename, element_names, sizing_combos[cost_list[i][1]], erf_ratios) + # Get final delay (we use final because ERFing is done for each combo) + delay = get_final_delay(fpga_inst, opt_type, sizable_circuit, tfall, trise) + # Get area (run_combo will have updated the area numbers for us so we can get it directly) + area = get_final_area(fpga_inst, opt_type, sizable_circuit) + # Calculate cost + cost = cost_function(area, delay, area_opt_weight, delay_opt_weight) + # Add to best results (cost, combo_index, area, delay, tfall, trise, erf_ratios) + best_results.append((cost, cost_list[i][1], area, delay, tfall, trise, erf_ratios)) + + # Sort the newly ERF results + best_results.sort() + + print "BEST COST RESULTS AFTER RE-BALANCING" + print "------------------------------------" + for result in best_results: + print ("Combo #" + str(result[1]).ljust(6) + + "cost=" + str(round(result[0],6)).ljust(9) + + "area=" + str(round(result[2]/1000000,3)).ljust(8) + + "delay=" + str(round(result[3],13)).ljust(10) + + "tfall=" + str(round(result[4],13)).ljust(10) + + "trise=" + str(round(result[5],13)).ljust(10)) + print "" + + # Write post-erf results to file + erf_export_filename = (spice_filedir + + sizable_circuit.name + + "_o" + str(outer_iter) + + "_i" + str(inner_iter) + + "_b" + str(bunch_num) + "_erf.csv") + export_erf_results(erf_export_filename, + element_names, + sizing_combos, + best_results) + + # Update transistor sizes file as well as area and wires to best combo + best_combo = sizing_combos[best_results[0][1]] + best_combo_erf_ratios = best_results[0][6] + # Change transistor sizes in SPICE file + spice.set_parameters(fpga_inst.tran_sizes_filename, element_names, best_combo, best_combo_erf_ratios, fpga_inst.specs.min_tran_width) + # Update transistor sizes + fpga_inst._update_transistor_sizes(element_names, best_combo, best_combo_erf_ratios) + # Calculate area of everything + fpga_inst.update_area() + # Re-calculate wire lengths + fpga_inst.update_wires() + # Update wire resistance and capacitance + fpga_inst.update_wire_rc() + # Update wire R and C SPICE file + fpga_inst.update_wire_rc_file() + + # We want to return the best combo, this must contain all NMOS and PMOS values + best_combo_detailed = {} + best_combo_dict = {} + for i in range(len(element_names)): + name = element_names[i] + if "ptran_" in name: + best_combo_dict[name] = best_combo[i] + best_combo_detailed[name + "_nmos"] = best_combo[i] + elif "rest_" in name: + best_combo_dict[name] = best_combo[i] + best_combo_detailed[name + "_pmos"] = best_combo[i] + elif "inv_" in name: + best_combo_dict[name] = best_combo[i] + # If the NMOS is bigger than the PMOS + if best_combo_erf_ratios[name] < 1: + best_combo_detailed[name + "_nmos"] = best_combo[i]/best_combo_erf_ratios[name] + best_combo_detailed[name + "_pmos"] = best_combo[i] + # If the PMOS is bigger than the NMOS + else: + best_combo_detailed[name + "_nmos"] = best_combo[i] + best_combo_detailed[name + "_pmos"] = best_combo[i]*best_combo_erf_ratios[name] + + return (best_combo_dict, best_combo_detailed, best_results[0], num_hspice_sims) + + +def format_transistor_names_to_basic_subcircuits(transistor_names): + """ The input is a list of transistor names with the '_nmos' and '_pmos' tags. + The output is a list of element names without tags. """ + + format_names = [] + for tran_name in transistor_names: + stripped_name = tran_name.replace("_nmos", "") + stripped_name = stripped_name.replace("_pmos", "") + if stripped_name not in format_names: + format_names.append(stripped_name) + + return format_names + + +def format_transistor_sizes_to_basic_subciruits(transistor_sizes): + """ This takes a dictionary of transistor sizes and removes the _nmos and _pmos tags. + Also, inverters and transmission gates are given a single size. """ + + format_sizes = {} + for tran_name, size in transistor_sizes.iteritems(): + stripped_name = tran_name.replace("_nmos", "") + stripped_name = stripped_name.replace("_pmos", "") + if "inv_" in stripped_name or "tgate_" in stripped_name: + if stripped_name in format_sizes.keys(): + if size < format_sizes[stripped_name]: + format_sizes[stripped_name] = size + else: + format_sizes[stripped_name] = size + else: + format_sizes[stripped_name] = size + + return format_sizes + + +def _print_sizing_ranges(subcircuit_name, sizing_ranges_list): + """ Print the sizing ranges for this subcircuit. """ + + print "Transistor size ranges for " + subcircuit_name + ":" + + # Calculate column width for each name + name_len = [] + sizing_strings = {} + for name in sizing_ranges_list[0].keys(): + name_len.append(len(name)) + sizing_strings[name] = "" + + # Find biggest name + col_width = max(name_len) + 2 + + # Make sizing strings + for sizing_ranges in sizing_ranges_list: + for name, size_range in sizing_ranges.iteritems(): + sizing_strings[name] = sizing_strings[name] + ("[" + str(size_range[0]) + " -> " + str(size_range[1]) + "]").ljust(11) + + # Print sizing ranges for each subcircuit/transistor + for name, size_string in sizing_strings.iteritems(): + print "".join(name.ljust(col_width)) + ": " + size_string + + print "" + + +def print_sizing_results(subcircuit_name, sizing_results_list): + """ """ + + print "Sizing results for " + subcircuit_name + ":" + + # Calculate column width for each name + name_list = sizing_results_list[0].keys() + name_list.sort() + name_len = [] + sizing_strings = {} + for name in name_list: + name_len.append(len(name)) + sizing_strings[name] = "" + + # Find biggest name + col_width = max(name_len) + 1 + + # Make results strings + for sizing_results in sizing_results_list: + for name in name_list: + size = sizing_results[name] + sizing_strings[name] = sizing_strings[name] + (str(round(size,1))).ljust(4) + + # Print sizing results for each subcircuit/transistor + for name, size_string in sizing_strings.iteritems(): + print "".join(name.ljust(col_width)) + ": " + size_string + + print "" + + +def _divide_problem_into_sets(transistor_names): + """ If there are too many elements to size, the number of different combinations to try will quickly blow up on us. + However, we want to size transistors together in as large groups as possible as this produces a more thorough search. + In general, groups of 5-6 transistors yields a good balance between these two competing factors. + This function looks at the 'transistor_names' argument and figures out how to divide the transistors to size in + more manageable groups (if indeed they do need to be divided up). + """ + + # The first thing we are going to do is count how many elements we need to size. + # We don't count level restorers because we always set them to minimum size. + count = 0 + for tran_name in transistor_names: + if "rest_" not in tran_name: + count = count + 1 + + print "Found " + str(count) + " elements to size" + + # Create the transistor groups + tran_names_set_list = [] + if count > 6: + print "Too many elements to size at once..." + # Let's divide this into sets of 5, the last set might have less than 5 elements. + tran_counter = 0 + tran_names_set = [] + for tran_name in transistor_names: + if tran_counter >= 5: + tran_names_set_list.append(tran_names_set) + tran_names_set = [] + tran_names_set.append(tran_name) + tran_counter = 1 + else: + tran_names_set.append(tran_name) + if "rest_" not in tran_name: + tran_counter = tran_counter + 1 + + # Add the last set to the set list + tran_names_set_list.append(tran_names_set) + + # Print out the sets + print "Creating the following groups:\n" + for i in range(len(tran_names_set_list)): + set = tran_names_set_list[i] + print "GROUP " + str(i+1) + for tran_name in set: + if "rest_" not in tran_name: + print tran_name + print "" + + else: + # We only have one item in the set list + tran_names_set_list.append(transistor_names) + for i in range(len(tran_names_set_list)): + set = tran_names_set_list[i] + for tran_name in set: + if "rest_" not in tran_name: + print tran_name + print "" + + return tran_names_set_list + + +def _find_initial_sizing_ranges(transistor_names, transistor_sizes): + """ This function finds the initial transistor sizing ranges. + The initial ranges are determined based on 'transistor_sizes' + I think it's a good idea for the initial transistor size ranges to be smaller + than subsequent ranges. We can look at the initial transistor sizing ranges as more of a test + that determines whether the sizes of these transistors will change or not. If we keep the + ranges smaller, we can more quickly perform this test. If we find + that we don't need to change these transistor sizes much, we didn't waste time exploring a + larger range of sizes. If we find that transistor sizes do need to change a lot, we can make + the next transistor size ranges larger. + """ + + # We have to figure out what ranges to use. + set_length = len(transistor_names) + if set_length >= 6: + sizes_per_element = 4 + elif set_length == 5: + sizes_per_element = 5 + else: + sizes_per_element = 8 + + # Figure out sizing ranges + # If the transistor is a level-restorer, keep the size at 1. + # If the transistor is anything else, create a transistor size range + # that keeps the current size near the center. + sizing_ranges = {} + for name in transistor_names: + # Current size of this transistor + size = transistor_sizes[name] + if "rest_" in name: + max = 1 + min = 1 + incr = 1 + sizing_ranges[name] = (min, max, incr) + else: + # Grow the range on both sides of the current size (i.e. both larger sizes and smaller sizes) + max = size + (sizes_per_element/2) + min = size - (sizes_per_element/2) + incr = 1 + # Transistor sizes smaller than 1 are not legal sizes. + # If this happens, make the min 1, and increase the size of max (to keep the same range size). + if min < 1: + max = max - min + min = 1 + sizing_ranges[name] = (min, max, incr) + + return sizing_ranges + + +def update_sizing_ranges(sizing_ranges, sizing_results): + """ + This function does two things. First, it checks whether the transistor sizing results are valid. + That is, if the results are on the boundaries of the ranges, they are not valid. In this case, + the function will adjust 'sizing_ranges' around the 'sizing_results'. + + Returns: True if 'sizing_results' are valid, False otherwise. + """ + + # Note: There are two tricks built into this function. + # + # Trick 1: When we evaluate whether a transistor size is on the range boundary or not, we also + # find out whether it is on the lower or upper boundary. So, when we adjust the transistor + # size range, we skew it towards one direction more than the other. + # For example, if tran_1_range = [3 -> 8] and we found that tran_1_size = 8. + # This would imply that we might need to increase the size of tran_1 because it is on the + # upper boundary. So when we adjust tran_1_range, it makes sense to do tran_1_range = [6 -> 13] + # rather than [5 -> 12]. That is, we skew the range towards growing the transistor size. + # + # Trick 2: The algorithms for skewing transistor sizing ranges for growing and for shrinking transistor + # sizes is not symmetrical. The reason for this is to avoid oscillation. We found that in some + # cases, the algorithm would get stuck in a state where it would want to grow a transistor size + # so it would adjust the transistor size ranges to RANGE_GROW (for example) then when it evaluated + # validity, if found that it now had to shrink transistor sizes, so it would adjust the ranges to + # RANGE_SHRINK. Because the adjustment was symmetrical, the algorithm would oscillate between these + # two ranges. + + + print "Validating transistor sizes" + + # The first thing we are going to do is count how many elements we need to size. + count = 0 + for tran_name in sizing_results.keys(): + if "rest_" not in tran_name: + count = count + 1 + + # Get a rough estimate on the number of sizes per element based on some target maximum number of transistor sizing combinations. + # The real number of transistor sizing combinations may end up being larger than this max due to some of the sizing range + # adjustments we make below. + max_combinations = 4000 + sizes_per_element = int(math.pow(max_combinations, 1.0/count)) + + # Now, we need to have an upper limit on sizing ranges. For example, if we only have one transistor, + # we might try thousands of sizes, which doesnt really make sense to do. + if sizes_per_element > 20: + sizes_per_element = 20 + + if sizes_per_element < 3: + sizes_per_element = 3 + + # Check each element for boundaries condition + valid = True + for name, size in sizing_results.iteritems(): + # Skip rest_ because we fix these at minimum size. + if "rest_" in name: + continue + # Check if equal to upper bound + if size == sizing_ranges[name][1]: + print name + " is on upper bound" + valid = False + # Update the ranges + max = size + sizes_per_element - 1 + min = size - 2 + incr = 1 + if min < 1: + max = max - min + min = 1 + sizing_ranges[name] = (min, max, incr) + # Check if equal to lower bound + elif size == sizing_ranges[name][0]: + # If lower bound = 1, can't make it any smaller. This is not a violation. + if sizing_ranges[name][0] != 1: + print name + " is on lower bound" + valid = False + # Update the ranges + max = size + 3 + min = size - sizes_per_element + 2 + incr = 1 + if min < 1: + max = max - min + min = 1 + sizing_ranges[name] = (min, max, incr) + + if not valid: + print "Sizing results not valid, computing new sizing ranges\n" + else: + print "Sizing results are valid\n" + + return valid + + +def size_subcircuit_transistors(fpga_inst, subcircuit, opt_type, re_erf, area_opt_weight, delay_opt_weight, outer_iter, + initial_transistor_sizes, num_hspice_sims): + """ + Size transistors for one subcircuit. + """ + + print "|------------------------------------------------------------------------------|" + print "| Transistor sizing on subcircuit: " + subcircuit.name.ljust(41) + "|" + print "|------------------------------------------------------------------------------|" + print "" + + # Get a list of element names in this subcircuit (ptran, inv, etc.). + # We expect this list to be sorted in the order in which things should be sized. + tran_names = format_transistor_names_to_basic_subcircuits(subcircuit.transistor_names) + + # Create groups of transistors to size to keep number of HSPICE sims manageable + tran_names_set_list = _divide_problem_into_sets(tran_names) + + sizing_ranges_set_list = [] + sizing_ranges_complete = {} + + # Find initial transistor sizing ranges for each set of transistors + for tran_names_set in tran_names_set_list: + sizing_ranges_set = _find_initial_sizing_ranges(tran_names_set, initial_transistor_sizes) + sizing_ranges_set_list.append(sizing_ranges_set.copy()) + sizing_ranges_complete.update(sizing_ranges_set) + + # Get the SPICE file name and the directory name + spice_filename = "" + spice_filedir = "" + if "/" in subcircuit.top_spice_path: + words = subcircuit.top_spice_path.split("/") + for i in range(len(words)-1): + spice_filedir = spice_filedir + words[i] + "/" + spice_filename = words[len(words)-1] + + # If there is more than one set to size, we should first ERF everything. + # Once that is done, we can work on individual sets. + if len(sizing_ranges_set_list) > 1: + element_names = sorted(sizing_ranges_complete.keys()) + print "Determining initial inverter P/N ratios for all transistor groups..." + # Find the combo that is near the middle of all ranges + middle_combo = spice.get_middle_value_config(element_names, sizing_ranges_complete) + # ERF mid-range transistor sizing combination + erf_ratios, num_hspice_sims = erf_combo(fpga_inst, spice_filedir, spice_filename, element_names, middle_combo, num_hspice_sims) + + # Perform transistor sizing on each set of transistors + sizing_results = {} + sizing_results_detailed = {} + for set_num in range(len(sizing_ranges_set_list)): + sizing_ranges = sizing_ranges_set_list[set_num] + + # Keep a list of the sizing ranges we tried. + sizing_ranges_list = [] + sizing_ranges_list.append(sizing_ranges.copy()) + + # Keep sizing until sizes are valid (i.e. not on the boundaries) + sizes_are_valid = False + sizing_results_list = [] + sizing_results_detailed_list = [] + area_delay_results_list = [] + inner_iter = 1 + while not sizes_are_valid: + + # Print past and current sizing ranges to terminal + _print_sizing_ranges(subcircuit.name, sizing_ranges_list) + + # Make a copy of the sizing ranges. size_ranges modifies the contents of the sizing_ranges dict. + # So we keep an unmodified copy up here (the original). + sizing_ranges_copy = sizing_ranges.copy() + + # Perform transistor sizing on ranges + search_ranges_return = search_ranges(sizing_ranges_copy, + fpga_inst, + subcircuit, + opt_type, + re_erf, + area_opt_weight, + delay_opt_weight, + outer_iter, + inner_iter, + set_num, + num_hspice_sims) + + sizing_results_set = search_ranges_return[0] + sizing_results_set_detailed = search_ranges_return[1] + area_delay_results = search_ranges_return[2] + num_hspice_sims = search_ranges_return[3] + + sizing_results_list.append(sizing_results_set) + sizing_results_detailed_list.append(sizing_results_set_detailed) + area_delay_results_list.append(area_delay_results) + print_sizing_results(subcircuit.name, sizing_results_detailed_list) + + # Update results so that we have results for all sets in one place + sizing_results.update(sizing_results_set) + sizing_results_detailed.update(sizing_results_set_detailed) + + # Now we need to check if this is a valid sizing (is it on the boundaries or not). + # If it is on the boundaries, we need to adjust the boundaries and do the sizing again. + # If it isn't on the boundaries, its a valid sizing, we store the results and move on to the next subcircuit. + sizes_are_valid = update_sizing_ranges(sizing_ranges, sizing_results_set) + + # Add a copy of sizing ranges to list + sizing_ranges_list.append(sizing_ranges.copy()) + + inner_iter += 1 + + return sizing_results, sizing_results_detailed, num_hspice_sims + + +def print_results(opt_type, sizing_results_list, area_results_list, delay_results_list): + """ Print sizing results to terminal """ + + print "FPGA TRANSISTOR SIZING RESULTS" + print "------------------------------" + + subcircuit_names = sorted(sizing_results_list[0].keys()) + + for subcircuit_name in subcircuit_names: + print subcircuit_name + ":" + + # Let's make the element names column such that everything lines up + # We'll also initialize the strings that will contain the sizing results + sizing_strings = {} + element_names = sizing_results_list[0][subcircuit_name].keys() + element_name_lengths = [] + for name in element_names: + element_name_lengths.append(len(name)) + sizing_strings[name] = "" + + # The length of the biggest element name is our minimum column width + col_width = max(element_name_lengths) + 1 + + # Create the sizing strings + for results in sizing_results_list: + subcircuit_results = results[subcircuit_name] + for name in element_names: + size = subcircuit_results[name] + sizing_strings[name] = sizing_strings[name] + str(size).ljust(4) + + # Now print the results + for name, size_string in sizing_strings.iteritems(): + print "".join(name.ljust(col_width)) + ": " + size_string + + print "" + + if opt_type == "local": + print "Area\t\tDelay\t\tArea-Delay Product" + area_delay_strings = [] + for i in range(len(area_results_list)): + area_results = area_results_list[i] + delay_results = delay_results_list[i] + subcircuit_area = area_results[subcircuit_name] + subcircuit_delay = delay_results[subcircuit_name] + subcircuit_cost = subcircuit_area*subcircuit_delay + area_delay_strings.append(str(round(subcircuit_area,3)) + "\t" + str(round(subcircuit_delay,14)) + "\t" + str(round(subcircuit_cost,5))) + + # Print results + for area_delay_str in area_delay_strings: + print area_delay_str + print "\n" + + print "" + print "TOTALS:" + print "Area\t\tDelay\t\tCost" + totals_strings = [] + for i in range(len(area_results_list)): + area_results = area_results_list[i] + delay_results = delay_results_list[i] + total_area = area_results["tile"] + total_delay = delay_results["rep_crit_path"] + total_cost = total_area*total_delay + totals_strings.append(str(round(total_area/1000000,3)) + "\t" + str(round(total_delay,14)) + "\t" + str(round(total_cost,5))) + for total_str in totals_strings: + print total_str + print "" + + +def export_sizing_results(results_filename, sizing_results_list, area_results_list, delay_results_list): + """ """ + + results_file = open(results_filename, 'w') + + subcircuit_names = sorted(sizing_results_list[0].keys()) + + for subcircuit_name in subcircuit_names: + results_file.write(subcircuit_name + ":\n") + + # Let's make the element names column such that everything lines up + # We'll also initialize the strings that will contain the sizing results + sizing_strings = {} + element_names = sizing_results_list[0][subcircuit_name].keys() + element_name_lengths = [] + for name in element_names: + element_name_lengths.append(len(name)) + sizing_strings[name] = "" + + # The length of the biggest element name is our minimum column width + col_width = max(element_name_lengths) + 1 + + # Create the sizing strings + for results in sizing_results_list: + subcircuit_results = results[subcircuit_name] + for name in element_names: + size = subcircuit_results[name] + sizing_strings[name] = sizing_strings[name] + str(size).ljust(4) + + # Now write the results + for name, size_string in sizing_strings.iteritems(): + results_file.write("".join(name.ljust(col_width)) + ": " + size_string + "\n") + results_file.write("\n") + + results_file.write("Area\t\tDelay\t\tAPD\n") + area_delay_strings = [] + for i in range(len(area_results_list)): + area_results = area_results_list[i] + delay_results = delay_results_list[i] + subcircuit_area = area_results[subcircuit_name] + subcircuit_delay = delay_results[subcircuit_name] + subcircuit_cost = subcircuit_area*subcircuit_delay + area_delay_strings.append(str(subcircuit_area) + "\t" + str(subcircuit_delay) + "\t" + str(subcircuit_cost)) + + # Write results + for area_delay_str in area_delay_strings: + results_file.write(area_delay_str + "\n") + results_file.write("\n") + + results_file.write("TOTALS:\n") + results_file.write("Area\t\tDelay\t\tAPD\n") + totals_strings = [] + for i in range(len(area_results_list)): + area_results = area_results_list[i] + delay_results = delay_results_list[i] + total_area = area_results["tile"] + total_delay = delay_results["rep_crit_path"] + total_cost = total_area*total_delay + totals_strings.append(str(total_area) + "\t" + str(total_delay) + "\t" + str(total_cost)) + for total_str in totals_strings: + results_file.write(total_str + "\n") + results_file.write("\n") + + results_file.close() + + +def check_if_done(sizing_results_list, area_results_list, delay_results_list, area_opt_weight, delay_opt_weight): + """ Check if the transistor sizing algorithm is done. + Returns true if done, false if not done. + """ + + print "Evaluating termination conditions..." + + # If the list has only one set of results, we've only done one iteration, + # we can't possibly know if the sizes are stable or not. + if len(sizing_results_list) <= 1: + print "Cannot terminate based on cost: at least one more FPGA sizing iteration required" + return False, 0 + + # Let's look at the cost of each iteration. + # What we are looking for as a terminating is an increase in the cost. + # Normally, that should never happen, the algorithm should always move + # to a solution with better cost. The reason that it does happen is that + # we are sometimes using stale data (or approximations) when we evaluate + # transistor sizes (for example: pre-computed P/N ratios for inverters, + # delays of subcircuits other than the one we are sizing, etc.). So, + # solution i might look better than solution i-1 during sizing, but in + # the end, when we update delays and re-balance P/N ratios, we find that + # i is actually a little bit worse than i-1. These 2 solutions will + # generally have very similar cost, and I think this suggests that we + # have a fairly minimal solution, but it's not necessarily globally + # minimal as we can't guarantee that with this algorithm. + + previous_cost = -1 + best_iteration = 0 + for i in range(len(area_results_list)): + # Get tile area and critical path for this sizing iteration + area_results = area_results_list[i] + delay_results = delay_results_list[i] + total_area = area_results["tile"] + total_delay = delay_results["rep_crit_path"] + # Calculate cost. + total_cost = cost_function(total_area, total_delay, area_opt_weight, delay_opt_weight) + + # First iteration, just update previous cost + if previous_cost == -1: + previous_cost = total_cost + continue + + # If we are moving to a higher cost solution, + # Stop, and choose the one with smaller cost (the previous one) + if total_cost > previous_cost: + print "Algorithm is terminating: cost has stopped decreasing\n" + best_iteration = i - 1 + return True, best_iteration + else: + previous_cost = total_cost + best_iteration = i + + return False, 0 + + +def size_fpga_transistors(fpga_inst, opt_type, re_erf, max_iterations, area_opt_weight, delay_opt_weight, num_hspice_sims): + """ Size FPGA transistors. + + Inputs: fpga_inst - fpga object instance + opt_type - optimization type (global or local) + re_erf - number of sizing combos to re-erf + max_iterations - maximum number of 'FPGA sizing iterations' (see [1]) + area_opt_weight - the 'b' in (cost = area^b * delay^c) + delay_opt_weight - the 'c' in (cost = area^b * delay^c) + num_hspice_sims - a counter that is incremented for every HSPICE simulation performed + + One 'FPGA sizing iteration' means sizing each subcircuits once. + We perform multiple sizing iterations. Stopping criteria is that + we have performed the 'max_iterations' or we found a minimum in cost. + A minimum cost is found when we observe that the cost of solution i + is larger than the cost of solution i-1. Now normally, that should + never happen, the algorithm should always move to a solution with better + cost. The reason that it does happen is that we are sometimes using stale + data (or approximations) when we evaluate transistor sizes (for example: + pre-computed P/N ratios for inverters, delays of subcircuits other than the + one we are sizing, etc.). So, solution i might look better than solution i-1 + during sizing, but in the end, when we update delays and re-balance P/N + ratios, we find that i is actually worse than i-1. These solutions will + generally have very similar cost, and I think this suggests that we have a + fairly minimal solution, but it's not necessarily globally minimal as we can't + guarantee that with this algorithm. + + [1] C. Chiasson and V.Betz, "COFFE: Fully-Automated Transistor Sizing for FPGAs", FPT2013 + + """ + + # Create results folder if it doesn't exist + if not os.path.exists("sizing_results"): + os.makedirs("sizing_results") + + # Initialize FPGA subcircuit delays + num_hspice_sims = fpga_inst.update_delays(num_hspice_sims) + + print "Starting transistor sizing...\n" + + # These lists store transistor sizing, area and delay results for each FPGA sizing iteration. + # Each entry in the list represents an FPGA sizing iteration. + # For example, area_results_list[0] has area results for the first FPGA sizing iteration. + sizing_results_list = [] + sizing_results_detailed_list = [] + area_results_list = [] + delay_results_list = [] + + # Keep performing FPGA sizing iterations until algorithm terminates + # Two conditions can make it terminate: + # 1 - Cost stops improving ('is_done') + # 2 - The max number of iterations of this while loop have been performed (max_iterations) + is_done = False + iteration = 1 + while not is_done: + + if iteration > max_iterations: + print "Algorithm is terminating: maximum number of iterations has been reached (" + str(max_iterations) + ")\n" + break + + print "FPGA TRANSISTOR SIZING ITERATION #" + str(iteration) + "\n" + + sizing_results_dict = {} + sizing_results_detailed_dict = {} + + # Now we are going to size the transistors of each subcircuit. + # The order we do this has an importance due to rise-fall balancing. + # We want the input signal to be rise-fall balanced as much as possible. + # So we start in the general routing, and move gradually deeper into the logic cluster, + # finally emerging back into the general routing when we reach the cluster outputs. + # This code is all basically the same, just repeated for each subcircuit. + + ############################################ + ## Size switch block mux transistors + ############################################ + name = fpga_inst.sb_mux.name + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(fpga_inst.sb_mux.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][name] + + # Size the transistors of this subcircuit + sizing_results_dict[name], sizing_results_detailed_dict[name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, fpga_inst.sb_mux, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + ############################################ + ## Size connection block mux transistors + ############################################ + name = fpga_inst.cb_mux.name + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(fpga_inst.cb_mux.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][name] + + # Size the transistors of this subcircuit + sizing_results_dict[name], sizing_results_detailed_dict[name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, fpga_inst.cb_mux, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + ############################################ + ## Size local routing mux transistors + ############################################ + name = fpga_inst.logic_cluster.local_mux.name + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(fpga_inst.logic_cluster.local_mux.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][name] + + # Size the transistors of this subcircuit + sizing_results_dict[name], sizing_results_detailed_dict[name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, fpga_inst.logic_cluster.local_mux, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + ############################################ + ## Size LUT + ############################################ + # We actually size the LUT before the LUT drivers even if this means going out of the normal signal propagation order because + # LUT driver sizing depends on LUT transistor sizes, and also, LUT rise-fall balancing doesn't depend on LUT drivers. + name = fpga_inst.logic_cluster.ble.lut.name + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(fpga_inst.logic_cluster.ble.lut.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][name] + + # Size the transistors of this subcircuit + sizing_results_dict[name], sizing_results_detailed_dict[name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, fpga_inst.logic_cluster.ble.lut, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + ############################################ + ## Size LUT input drivers + ############################################ + for input_driver_name, input_driver in fpga_inst.logic_cluster.ble.lut.input_drivers.iteritems(): + # Driver + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(input_driver.driver.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][input_driver.driver.name] + + # Size the transistors of this subcircuit + sizing_results_dict[input_driver.driver.name], sizing_results_detailed_dict[input_driver.driver.name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, input_driver.driver, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + # Not driver + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(input_driver.not_driver.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][input_driver.not_driver.name] + + # Size the transistors of this subcircuit + sizing_results_dict[input_driver.not_driver.name], sizing_results_detailed_dict[input_driver.not_driver.name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, input_driver.not_driver, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + ############################################ + ## Size local ble output transistors + ############################################ + name = fpga_inst.logic_cluster.ble.local_output.name + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(fpga_inst.logic_cluster.ble.local_output.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][name] + + # Size the transistors of this subcircuit + sizing_results_dict[name], sizing_results_detailed_dict[name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, fpga_inst.logic_cluster.ble.local_output, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + ############################################ + ## Size general ble output transistors + ############################################ + name = fpga_inst.logic_cluster.ble.general_output.name + # If this is the first iteration, use the 'initial_transistor_sizes' as the starting sizes. + # If it's not the first iteration, we use the transistor sizes of the previous iteration as the starting sizes. + if iteration == 1: + starting_transistor_sizes = format_transistor_sizes_to_basic_subciruits(fpga_inst.logic_cluster.ble.general_output.initial_transistor_sizes) + else: + starting_transistor_sizes = sizing_results_list[len(sizing_results_list)-1][name] + + # Size the transistors of this subcircuit + sizing_results_dict[name], sizing_results_detailed_dict[name], num_hspice_sims = size_subcircuit_transistors(fpga_inst, fpga_inst.logic_cluster.ble.general_output, opt_type, re_erf, area_opt_weight, delay_opt_weight, iteration, starting_transistor_sizes, num_hspice_sims) + + + ############################################ + ## Done sizing, update results lists + ############################################ + + print "FPGA transistor sizing iteration complete!\n" + + sizing_results_list.append(sizing_results_dict.copy()) + sizing_results_detailed_list.append(sizing_results_detailed_dict.copy()) + + num_hspice_sims = fpga_inst.update_delays(num_hspice_sims) + + # Add subcircuit delays and area to list (need to copy because of mutability) + area_results_list.append(fpga_inst.area_dict.copy()) + delay_results_list.append(fpga_inst.delay_dict.copy()) + + # Print all sizing results to terminal + print_results(opt_type, sizing_results_list, area_results_list, delay_results_list) + + # Export these same results to a file + sizing_results_filename = "sizing_results/sizing_results_o" + str(iteration) + ".txt" + export_sizing_results(sizing_results_filename, sizing_results_list, area_results_list, delay_results_list) + + # Check if transistor sizing is done + is_done, final_result_index = check_if_done(sizing_results_list, area_results_list, delay_results_list, area_opt_weight, delay_opt_weight) + + iteration += 1 + + + print "FPGA transistor sizing complete!\n" + + # final_result_index are the results we need to use + final_transistor_sizes = sizing_results_list[final_result_index] + final_transistor_sizes_detailed = sizing_results_detailed_list[final_result_index] + + # Make a dictionary that contains all transistor sizes (instead of a dictionary of dictionaries for each subcircuit) + final_transistor_sizes_detailed_full = {} + for subcircuit_name, subcircuit_sizes in final_transistor_sizes_detailed.iteritems(): + final_transistor_sizes_detailed_full.update(subcircuit_sizes) + + # Set the transistor sizes in the SPICE file + spice.set_all_parameters(fpga_inst.tran_sizes_filename, final_transistor_sizes_detailed_full, fpga_inst.specs.min_tran_width) + # Update FPGA transistor sizes + fpga_inst.transistor_sizes.update(final_transistor_sizes_detailed_full) + # Calculate area of everything + fpga_inst.update_area() + # Re-calculate wire lengths + fpga_inst.update_wires() + # Update wire resistance and capacitance + fpga_inst.update_wire_rc() + # Update wire R and C SPICE file + fpga_inst.update_wire_rc_file() + + return num_hspice_sims \ No newline at end of file diff --git a/coffe/utils.py b/coffe/utils.py new file mode 100644 index 0000000..cf38511 --- /dev/null +++ b/coffe/utils.py @@ -0,0 +1,290 @@ +import sys +import os + + +def compare_tfall_trise(tfall, trise): + """ Compare tfall and trise and returns largest value or -1.0 + -1.0 is return if something went wrong in SPICE """ + + # Initialize output delay + delay = -1.0 + + # Compare tfall and trise + if (tfall == 0.0) or (trise == 0.0): + # Couldn't find one of the values in output + # This is an error because maybe SPICE failed + delay = -1.0 + elif tfall > trise: + if tfall > 0.0: + # tfall is positive and bigger than the trise, this is a valid result + delay = tfall + else: + # Both tfall and trise are negative, this is an invalid result + delay = -1.0 + elif trise >= tfall: + if trise > 0.0: + # trise is positive and larger or equal to tfall, this is value result + delay = trise + else: + delay = -1.0 + else: + delay = -1.0 + + return delay + + +def print_area_and_delay(fpga_inst): + """ Print area and delay per subcircuit """ + + print " SUBCIRCUIT AREA & DELAY" + print " -----------------------" + + area_dict = fpga_inst.area_dict + + # I'm using 'ljust' to create neat columns for printing this data. + # If subcircuit names are changed, it might make this printing function + # not work as well. The 'ljust' constants would have to be adjusted accordingly. + + # Print the header + print " Subcircuit".ljust(24) + "Area (um^2)".ljust(13) + "Delay (ps)".ljust(13) + "trise (ps)".ljust(13) + "tfall (ps)".ljust(13) + + # Switch block mux + print " " + fpga_inst.sb_mux.name.ljust(22) + str(round(area_dict[fpga_inst.sb_mux.name]/1e6,3)).ljust(13) + str(round(fpga_inst.sb_mux.delay/1e-12,4)).ljust(13) + str(round(fpga_inst.sb_mux.tfall/1e-12,4)).ljust(13) + str(round(fpga_inst.sb_mux.trise/1e-12,4)).ljust(13) + + # Connection block mux + print " " + fpga_inst.cb_mux.name.ljust(22) + str(round(area_dict[fpga_inst.cb_mux.name]/1e6,3)).ljust(13) + str(round(fpga_inst.cb_mux.delay/1e-12,4)).ljust(13) + str(round(fpga_inst.cb_mux.tfall/1e-12,4)).ljust(13) + str(round(fpga_inst.cb_mux.trise/1e-12,4)).ljust(13) + + # Local mux + print " " + fpga_inst.logic_cluster.local_mux.name.ljust(22) + str(round(area_dict[fpga_inst.logic_cluster.local_mux.name]/1e6,3)).ljust(13) + str(round(fpga_inst.logic_cluster.local_mux.delay/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.local_mux.tfall/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.local_mux.trise/1e-12,4)).ljust(13) + + # Local BLE output + print " " + fpga_inst.logic_cluster.ble.local_output.name.ljust(22) + str(round(area_dict[fpga_inst.logic_cluster.ble.local_output.name]/1e6,3)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.local_output.delay/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.local_output.tfall/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.local_output.trise/1e-12,4)).ljust(13) + + # General BLE output + print " " + fpga_inst.logic_cluster.ble.general_output.name.ljust(22) + str(round(area_dict[fpga_inst.logic_cluster.ble.general_output.name]/1e6,3)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.general_output.delay/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.general_output.tfall/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.general_output.trise/1e-12,4)).ljust(13) + + # LUT + print " " + fpga_inst.logic_cluster.ble.lut.name.ljust(22) + str(round(area_dict[fpga_inst.logic_cluster.ble.lut.name]/1e6,3)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.lut.delay/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.lut.tfall/1e-12,4)).ljust(13) + str(round(fpga_inst.logic_cluster.ble.lut.trise/1e-12,4)).ljust(13) + + # Get LUT input names so that we can print inputs in sorted order + lut_input_names = fpga_inst.logic_cluster.ble.lut.input_drivers.keys() + lut_input_names.sort() + + # LUT input drivers + for input_name in lut_input_names: + driver = fpga_inst.logic_cluster.ble.lut.input_drivers[input_name].driver + not_driver = fpga_inst.logic_cluster.ble.lut.input_drivers[input_name].not_driver + print " " + driver.name.ljust(22) + str(round(area_dict[driver.name]/1e6,3)).ljust(13) + str(round(driver.delay/1e-12,4)).ljust(13) + str(round(driver.tfall/1e-12,4)).ljust(13) + str(round(driver.trise/1e-12,4)).ljust(13) + print " " + not_driver.name.ljust(22) + str(round(area_dict[not_driver.name]/1e6,3)).ljust(13) + str(round(not_driver.delay/1e-12,4)).ljust(13) + str(round(not_driver.tfall/1e-12,4)).ljust(13) + str(round(not_driver.trise/1e-12,4)).ljust(13) + + print "" + + +def print_block_area(fpga_inst): + """ Print physical area of important blocks (like SB, CB, LUT, etc.) in um^2 """ + + tile = fpga_inst.area_dict["tile"]/1000000 + lut = fpga_inst.area_dict["lut_total"]/1000000 + ff = fpga_inst.area_dict["ff_total"]/1000000 + ble_output = fpga_inst.area_dict["ble_output_total"]/1000000 + local_mux = fpga_inst.area_dict["local_mux_total"]/1000000 + cb = fpga_inst.area_dict["cb_total"]/1000000 + sb = fpga_inst.area_dict["sb_total"]/1000000 + sanity_check = lut+ff+ble_output+local_mux+cb+sb + + print " TILE AREA CONTRIBUTIONS" + print " -----------------------" + print " Block".ljust(20) + "Total Area (um^2)".ljust(20) + "Fraction of total tile area" + print " Tile".ljust(20) + str(round(tile,3)).ljust(20) + "100%" + print " LUT".ljust(20) + str(round(lut,3)).ljust(20) + str(round(lut/tile*100,3)) + "%" + print " FF".ljust(20) + str(round(ff,3)).ljust(20) + str(round(ff/tile*100,3)) + "%" + print " BLE output".ljust(20) + str(round(ble_output,3)).ljust(20) + str(round(ble_output/tile*100,3)) + "%" + print " Local mux".ljust(20) + str(round(local_mux,3)).ljust(20) + str(round(local_mux/tile*100,3)) + "%" + print " Connection block".ljust(20) + str(round(cb,3)).ljust(20) + str(round(cb/tile*100,3)) + "%" + print " Switch block".ljust(20) + str(round(sb,3)).ljust(20) + str(round(sb/tile*100,3)) + "%" + print "" + + +def print_vpr_delays(fpga_inst): + + print " VPR DELAYS" + print " ----------" + print " Path".ljust(50) + "Delay (ps)" + print " Tdel (routing switch)".ljust(50) + str(fpga_inst.sb_mux.delay) + print " T_ipin_cblock (connection block mux)".ljust(50) + str(fpga_inst.cb_mux.delay) + print " CLB input -> BLE input (local CLB routing)".ljust(50) + str(fpga_inst.logic_cluster.local_mux.delay) + print " LUT output -> BLE input (local feedback)".ljust(50) + str(fpga_inst.logic_cluster.ble.local_output.delay) + print " LUT output -> CLB output (logic block output)".ljust(50) + str(fpga_inst.logic_cluster.ble.general_output.delay) + + # Figure out LUT delays + lut_input_names = fpga_inst.logic_cluster.ble.lut.input_drivers.keys() + lut_input_names.sort() + for input_name in lut_input_names: + lut_input = fpga_inst.logic_cluster.ble.lut.input_drivers[input_name] + driver_delay = max(lut_input.driver.delay, lut_input.not_driver.delay) + path_delay = lut_input.delay + print (" lut_" + input_name).ljust(50) + str(driver_delay+path_delay) + + print "" + + +def print_vpr_areas(fpga_inst): + + print " VPR AREAS" + print " ----------" + print " grid_logic_tile_area".ljust(50) + str(fpga_inst.area_dict["logic_cluster"]/fpga_inst.specs.min_width_tran_area) + print " ipin_mux_trans_size (connection block mux)".ljust(50) + str(fpga_inst.area_dict["ipin_mux_trans_size"]/fpga_inst.specs.min_width_tran_area) + print " mux_trans_size (routing switch)".ljust(50) + str(fpga_inst.area_dict["switch_mux_trans_size"]/fpga_inst.specs.min_width_tran_area) + print " buf_size (routing switch)".ljust(50) + str(fpga_inst.area_dict["switch_buf_size"]/fpga_inst.specs.min_width_tran_area) + print "" + + +def load_arch_params(filename): + """ Parse the architecture description file and load values into dictionary. + Returns this dictionary. + Will print error message and terminate program if an invalid parameter is found + or if a parameter is missing. + """ + + # This is the dictionary of parameters we expect to find + arch_params = { + 'W': -1, + 'L': -1, + 'Fs': -1, + 'N': -1, + 'K': -1, + 'I': -1, + 'Fcin': -1.0, + 'Fcout': -1.0, + 'Or': -1, + 'Ofb': -1, + 'Fclocal': -1.0, + 'Rsel': "", + 'Rfb': "", + 'vdd': -1, + 'vsram': -1, + 'vsram_n': -1, + 'gate_length': -1, + 'min_tran_width': -1, + 'min_width_tran_area': -1, + 'sram_cell_area': -1, + 'model_path': "", + 'model_library': "", + 'metal' : [] + } + + params_file = open(filename, 'r') + for line in params_file: + + # Ignore comment lines + if line.startswith('#'): + continue + + # Remove line feeds and spaces + line = line.replace('\n', '') + line = line.replace('\r', '') + line = line.replace('\t', '') + line = line.replace(' ', '') + + # Ignore empty lines + if line == "": + continue + + # Split lines at '=' + words = line.split('=') + if words[0] not in arch_params.keys(): + print "ERROR: Found invalid architecture parameter (" + words[0] + ") in " + filename + sys.exit() + + param = words[0] + value = words[1] + + if param == 'W': + arch_params['W'] = int(value) + elif param == 'L': + arch_params['L'] = int(value) + elif param == 'Fs': + arch_params['Fs'] = int(value) + elif param == 'N': + arch_params['N'] = int(value) + elif param == 'K': + arch_params['K'] = int(value) + elif param == 'I': + arch_params['I'] = int(value) + elif param == 'Fcin': + arch_params['Fcin'] = float(value) + elif param == 'Fcout': + arch_params['Fcout'] = float(value) + elif param == 'Or': + arch_params['Or'] = int(value) + elif param == 'Ofb': + arch_params['Ofb'] = int(value) + elif param == 'Fclocal': + arch_params['Fclocal'] = float(value) + elif param == 'Rsel': + # Check if this is a valid value for Rsel + if value != 'z' and (value < 'a' or value > chr(arch_params['K']+96)): + print "ERROR: Invalid value (" + str(value) + ") for Rsel in " + filename + sys.exit() + arch_params['Rsel'] = value + elif param == 'Rfb': + # Check if this is a valid value for Rfb + # If the value is only Z, there are no register feedback muxes, that's valid + if value == 'z': + arch_params['Rfb'] = value + elif len(value) > arch_params['K']: + print "ERROR: Invalid value (" + str(value) + ") for Rfb in " + filename + " (string too long)" + sys.exit() + else: + # Now, let's make sure all these characters are valid characters + for character in value: + # The character has to be a valid LUT input + if (character < 'a' or character > chr(arch_params['K']+96)): + print "ERROR: Invalid value (" + str(value) + ") for Rfb in " + filename + " (" + character + " is not valid)" + sys.exit() + # The character should not appear twice + elif value.count(character) > 1: + print "ERROR: Invalid value (" + str(value) + ") for Rfb in " + filename + " (" + character + " appears more than once)" + sys.exit() + arch_params['Rfb'] = value + elif param == 'vdd': + arch_params['vdd'] = float(value) + elif param == 'vsram': + arch_params['vsram'] = float(value) + elif param == 'vsram_n': + arch_params['vsram_n'] = float(value) + elif param == 'gate_length': + arch_params['gate_length'] = int(value) + elif param == 'min_tran_width': + arch_params['min_tran_width'] = int(value) + elif param == 'min_width_tran_area': + arch_params['min_width_tran_area'] = int(value) + elif param == 'sram_cell_area': + arch_params['sram_cell_area'] = float(value) + elif param == 'model_path': + arch_params['model_path'] = os.path.abspath(value) + elif param == 'model_library': + arch_params['model_library'] = value + elif param == 'metal': + value_words = value.split(',') + r = value_words[0].replace(' ', '') + r = r.replace(' ', '') + r = r.replace('\n', '') + r = r.replace('\r', '') + r = r.replace('\t', '') + c = value_words[1].replace(' ', '') + c = c.replace(' ', '') + c = c.replace('\n', '') + c = c.replace('\r', '') + c = c.replace('\t', '') + arch_params['metal'].append((float(r),float(c))) + + params_file.close() + + # Check that we read everything + for param, value in arch_params.iteritems(): + if value == -1 or value == "": + print "ERROR: Did not find architecture parameter " + param + " in " + filename + sys.exit() + + return arch_params \ No newline at end of file diff --git a/input_files/example.txt b/input_files/example.txt new file mode 100644 index 0000000..a609e5e --- /dev/null +++ b/input_files/example.txt @@ -0,0 +1,108 @@ +# EXAMPLE COFFE INPUT FILE +# +# Note: COFFE interprets any line beginning with a '#' as a comment in it's input files. +# +# COFFE input parameters can be divided into 2 groups: +# 1- Parameters describing the FPGA architecture +# 2- Process technology-related parameters +# +# [1] C. Chiasson and V.Betz. "COFFE: Fully-Automated Transistor Sizing for FPGAs", +# IEEE Int. Conf. on Field-Programmable Technology (FPT), 2013 + + +############################## +##### Architecture Parameters +############################## + +# The following parameters are the classic VPR architecture parameters +N=10 +K=6 +W=320 +L=4 +I=40 +Fs=3 +Fcin=0.2 +Fcout=0.025 + +# The following architecture parameters are new and help COFFE describe +# a more flexible BLE. See [1] for more details. + +# Number of BLE outputs to general routing +Or=2 + +# Number of BLE outputs to local routing +Ofb=1 + +# Population of local routing MUXes +Fclocal=0.5 + +# Register select: +# Defines whether the FF can accept it's input directly from a BLE input or not. +# To turn register-select off, Rsel=z +# To turn register select on, Rsel= +# where = the name of the BLE input from which the FF can +# accept its input (e.g. a, b, c, etc...). +Rsel=c + +# Register feedback muxes: +# Defines which LUT inputs support register feedback. +# Set Rfb to a string of LUT input names. +# For example: +# Rfb=c tells COFFE to place a register feedback mux on LUT input C. +# Rfb=cd tells COFFE to place a register feedback mux on both LUT inputs C and D. +# Rfb=z tells COFFE that no LUT input should have register feedback muxes. +Rfb=c + +############################## +##### Process Technology Parameters +############################## + +# Supply voltage +vdd=0.8 + +# SRAM Vdd +vsram=1.0 + +# SRAM Vss +vsram_n=0 + +# Gate length (nm) +gate_length=22 + +# Minimum transistor width (nm) +min_tran_width=45 + +# Minimum-width transistor area (nm^2) +min_width_tran_area = 28851 + +# SRAM area (in number of minimum width transistor areas) +sram_cell_area = 4 + +# Path to SPICE device models file and library to use +model_path=spice_models/ptm22hp.l +model_library=22NM_HP + +############################## +##### Metal data +##### R in ohms/nm +##### C in fF/nm +##### format: metal=R,C +##### ex: metal=0.054825,0.000175 +############################## + +# Each 'metal' statement defines a new metal layer. +# COFFE uses two metal layers by default. The first metal layer is where COFFE +# implements all wires except for the general routing wires. They are implemented +# in the second metal layer. + +# All wires except the general routing wires are implemented in this layer. +metal=0.054825,0.000175 + +# General routing wires will be implemented in this layer +metal=0.007862,0.000215 + +# If you wanted to, you could define more metal layers by adding more 'metal' +# statements but, by default, COFFE would not use them because it only uses 2 layers. +# The functionality of being able to add any number of metal layers is here to allow +# you to investigate the use of more than 2 metal layers if you wanted to. However, +# making use of more metal layers would require changes to the COFFE source code. diff --git a/spice_models/ptm22hp.l b/spice_models/ptm22hp.l new file mode 100644 index 0000000..f0278be --- /dev/null +++ b/spice_models/ptm22hp.l @@ -0,0 +1,149 @@ +* SPICE models for used for COFFE's 'example.txt' +* The models are from PTM (http://ptm.asu.edu/) +* Please don't forget to acknowledge/cite PTM if you use these models. + +* PTM High Performance 22nm Metal Gate / High-K / Strained-Si +* nominal Vdd = 0.8V + +* 22NM_HP is the COFFE 'model_library' parameter. +.LIB 22NM_HP + +.model nmos nmos level = 54 + ++version = 4.0 binunit = 1 paramchk= 1 mobmod = 0 ++capmod = 2 igcmod = 1 igbmod = 1 geomod = 1 ++diomod = 1 rdsmod = 0 rbodymod= 1 rgatemod= 1 ++permod = 1 acnqsmod= 0 trnqsmod= 0 + ++tnom = 27 toxe = 1.05e-009 toxp = 8e-010 toxm = 1.05e-009 ++dtox = 2.5e-010 epsrox = 3.9 wint = 5e-009 lint = 2e-009 ++ll = 0 wl = 0 lln = 1 wln = 1 ++lw = 0 ww = 0 lwn = 1 wwn = 1 ++lwl = 0 wwl = 0 xpart = 0 toxref = 1.05e-009 ++xl = -9e-9 + ++vth0 = 0.50308 k1 = 0.4 k2 = 0 k3 = 0 ++k3b = 0 w0 = 2.5e-006 dvt0 = 1 dvt1 = 2 ++dvt2 = 0 dvt0w = 0 dvt1w = 0 dvt2w = 0 ++dsub = 0.1 minv = 0.05 voffl = 0 dvtp0 = 1e-011 ++dvtp1 = 0.1 lpe0 = 0 lpeb = 0 xj = 7.2e-009 ++ngate = 1e+023 ndep = 5.5e+018 nsd = 2e+020 phin = 0 ++cdsc = 0 cdscb = 0 cdscd = 0 cit = 0 ++voff = -0.13 nfactor = 2.3 eta0 = 0.004 etab = 0 ++vfb = -0.55 u0 = 0.04 ua = 6e-010 ub = 1.2e-018 ++uc = 0 vsat = 250000 a0 = 1 ags = 0 ++a1 = 0 a2 = 1 b0 = 0 b1 = 0 ++keta = 0.04 dwg = 0 dwb = 0 pclm = 0.02 ++pdiblc1 = 0.001 pdiblc2 = 0.001 pdiblcb = -0.005 drout = 0.5 ++pvag = 1e-020 delta = 0.01 pscbe1 = 8.14e+008 pscbe2 = 1e-007 ++fprout = 0.2 pdits = 0.01 pditsd = 0.23 pditsl = 2300000 ++rsh = 5 rdsw = 145 rsw = 75 rdw = 75 ++rdswmin = 0 rdwmin = 0 rswmin = 0 prwg = 0 ++prwb = 0 wr = 1 alpha0 = 0.074 alpha1 = 0.005 ++beta0 = 30 agidl = 0.0002 bgidl = 2.1e+009 cgidl = 0.0002 ++egidl = 0.8 aigbacc = 0.012 bigbacc = 0.0028 cigbacc = 0.002 ++nigbacc = 1 aigbinv = 0.014 bigbinv = 0.004 cigbinv = 0.004 ++eigbinv = 1.1 nigbinv = 3 aigc = 0.0213 bigc = 0.0025889 ++cigc = 0.002 aigsd = 0.0213 bigsd = 0.0025889 cigsd = 0.002 ++nigc = 1 poxedge = 1 pigcd = 1 ntox = 1 ++xrcrg1 = 12 xrcrg2 = 5 + ++cgso = 6.5e-011 cgdo = 6.5e-011 cgbo = 2.56e-011 cgdl = 2.653e-010 ++cgsl = 2.653e-010 ckappas = 0.03 ckappad = 0.03 acde = 1 ++moin = 15 noff = 0.9 voffcv = 0.02 + ++kt1 = -0.11 kt1l = 0 kt2 = 0.022 ute = -1.5 ++ua1 = 4.31e-009 ub1 = 7.61e-018 uc1 = -5.6e-011 prt = 0 ++at = 33000 + ++fnoimod = 1 tnoimod = 0 + ++jss = 0.0001 jsws = 1e-011 jswgs = 1e-010 njs = 1 ++ijthsfwd= 0.01 ijthsrev= 0.001 bvs = 10 xjbvs = 1 ++jsd = 0.0001 jswd = 1e-011 jswgd = 1e-010 njd = 1 ++ijthdfwd= 0.01 ijthdrev= 0.001 bvd = 10 xjbvd = 1 ++pbs = 1 cjs = 0.0005 mjs = 0.5 pbsws = 1 ++cjsws = 5e-010 mjsws = 0.33 pbswgs = 1 cjswgs = 3e-010 ++mjswgs = 0.33 pbd = 1 cjd = 0.0005 mjd = 0.5 ++pbswd = 1 cjswd = 5e-010 mjswd = 0.33 pbswgd = 1 ++cjswgd = 5e-010 mjswgd = 0.33 tpb = 0.005 tcj = 0.001 ++tpbsw = 0.005 tcjsw = 0.001 tpbswg = 0.005 tcjswg = 0.001 ++xtis = 3 xtid = 3 + ++dmcg = 0 dmci = 0 dmdg = 0 dmcgt = 0 ++dwj = 0 xgw = 0 xgl = 0 + ++rshg = 0.4 gbmin = 1e-010 rbpb = 5 rbpd = 15 ++rbps = 15 rbdb = 15 rbsb = 15 ngcon = 1 + + + +.model pmos pmos level = 54 + ++version = 4.0 binunit = 1 paramchk= 1 mobmod = 0 ++capmod = 2 igcmod = 1 igbmod = 1 geomod = 1 ++diomod = 1 rdsmod = 0 rbodymod= 1 rgatemod= 1 ++permod = 1 acnqsmod= 0 trnqsmod= 0 + ++tnom = 27 toxe = 1.1e-009 toxp = 8e-010 toxm = 1.1e-009 ++dtox = 3e-010 epsrox = 3.9 wint = 5e-009 lint = 2e-009 ++ll = 0 wl = 0 lln = 1 wln = 1 ++lw = 0 ww = 0 lwn = 1 wwn = 1 ++lwl = 0 wwl = 0 xpart = 0 toxref = 1.1e-009 ++xl = -9e-9 + ++vth0 = -0.4606 k1 = 0.4 k2 = -0.01 k3 = 0 ++k3b = 0 w0 = 2.5e-006 dvt0 = 1 dvt1 = 2 ++dvt2 = -0.032 dvt0w = 0 dvt1w = 0 dvt2w = 0 ++dsub = 0.1 minv = 0.05 voffl = 0 dvtp0 = 1e-011 ++dvtp1 = 0.05 lpe0 = 0 lpeb = 0 xj = 7.2e-009 ++ngate = 1e+023 ndep = 4.4e+018 nsd = 2e+020 phin = 0 ++cdsc = 0 cdscb = 0 cdscd = 0 cit = 0 ++voff = -0.126 nfactor = 2.1 eta0 = 0.0038 etab = 0 ++vfb = 0.55 u0 = 0.0095 ua = 2e-009 ub = 5e-019 ++uc = 0 vsat = 210000 a0 = 1 ags = 1e-020 ++a1 = 0 a2 = 1 b0 = 0 b1 = 0 ++keta = -0.047 dwg = 0 dwb = 0 pclm = 0.12 ++pdiblc1 = 0.001 pdiblc2 = 0.001 pdiblcb = 3.4e-008 drout = 0.56 ++pvag = 1e-020 delta = 0.01 pscbe1 = 8.14e+008 pscbe2 = 9.58e-007 ++fprout = 0.2 pdits = 0.08 pditsd = 0.23 pditsl = 2300000 ++rsh = 5 rdsw = 145 rsw = 72.5 rdw = 72.5 ++rdswmin = 0 rdwmin = 0 rswmin = 0 prwg = 0 ++prwb = 0 wr = 1 alpha0 = 0.074 alpha1 = 0.005 ++beta0 = 30 agidl = 0.0002 bgidl = 2.1e+009 cgidl = 0.0002 ++egidl = 0.8 aigbacc = 0.012 bigbacc = 0.0028 cigbacc = 0.002 ++nigbacc = 1 aigbinv = 0.014 bigbinv = 0.004 cigbinv = 0.004 ++eigbinv = 1.1 nigbinv = 3 aigc = 0.0213 bigc = 0.0025889 ++cigc = 0.002 aigsd = 0.0213 bigsd = 0.0025889 cigsd = 0.002 ++nigc = 1 poxedge = 1 pigcd = 1 ntox = 1 ++xrcrg1 = 12 xrcrg2 = 5 + ++cgso = 6.5e-011 cgdo = 6.5e-011 cgbo = 2.56e-011 cgdl = 2.653e-010 ++cgsl = 2.653e-010 ckappas = 0.03 ckappad = 0.03 acde = 1 ++moin = 15 noff = 0.9 voffcv = 0.02 + ++kt1 = -0.11 kt1l = 0 kt2 = 0.022 ute = -1.5 ++ua1 = 4.31e-009 ub1 = 7.61e-018 uc1 = -5.6e-011 prt = 0 ++at = 33000 + ++fnoimod = 1 tnoimod = 0 + ++jss = 0.0001 jsws = 1e-011 jswgs = 1e-010 njs = 1 ++ijthsfwd= 0.01 ijthsrev= 0.001 bvs = 10 xjbvs = 1 ++jsd = 0.0001 jswd = 1e-011 jswgd = 1e-010 njd = 1 ++ijthdfwd= 0.01 ijthdrev= 0.001 bvd = 10 xjbvd = 1 ++pbs = 1 cjs = 0.0005 mjs = 0.5 pbsws = 1 ++cjsws = 5e-010 mjsws = 0.33 pbswgs = 1 cjswgs = 3e-010 ++mjswgs = 0.33 pbd = 1 cjd = 0.0005 mjd = 0.5 ++pbswd = 1 cjswd = 5e-010 mjswd = 0.33 pbswgd = 1 ++cjswgd = 5e-010 mjswgd = 0.33 tpb = 0.005 tcj = 0.001 ++tpbsw = 0.005 tcjsw = 0.001 tpbswg = 0.005 tcjswg = 0.001 ++xtis = 3 xtid = 3 + ++dmcg = 0 dmci = 0 dmdg = 0 dmcgt = 0 ++dwj = 0 xgw = 0 xgl = 0 + ++rshg = 0.4 gbmin = 1e-010 rbpb = 5 rbpd = 15 ++rbps = 15 rbdb = 15 rbsb = 15 ngcon = 1 + +.ENDL 22NM_HP \ No newline at end of file