From 97fc37aec1be392cb26a8a8cd4aae70bbb6338b8 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 12 Dec 2018 23:59:32 -0800
Subject: [PATCH 01/77] Added checks for the bitline voltage at sense amp
enable 50%.
---
compiler/characterizer/bitline_delay.py | 47 ++++++++++++++++++++++---
compiler/modules/control_logic.py | 2 +-
technology/freepdk45/tech/tech.py | 4 +--
technology/scn4m_subm/tech/tech.py | 1 +
4 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py
index e5a50c16..e4f992fc 100644
--- a/compiler/characterizer/bitline_delay.py
+++ b/compiler/characterizer/bitline_delay.py
@@ -89,6 +89,7 @@ class bitline_delay(delay):
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address),
self.probe_address,data_zeros,read_port)
self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1
+
def get_data_bit_column_number(self, probe_address, probe_data):
"""Calculates bitline column number of data bit under test using bit position and mux size"""
if self.sram.col_addr_size>0:
@@ -128,6 +129,44 @@ class bitline_delay(delay):
# The delay is from the negative edge for our SRAM
return (True,result)
+ def check_bitline_all_results(self, results):
+ """Checks the bitline values measured for each tested port"""
+ for port in self.targ_read_ports:
+ self.check_bitline_port_results(results[port])
+
+ def check_bitline_port_results(self, port_results)
+ """Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing"""
+ bl_volt, br_volt = port_results["bl_volt"], port_results["br_volt"]
+ self.check_functionality(bl_volt,br_volt)
+ self.check_swing_from_vdd(bl_volt,br_volt)
+ self.check_differential_swing(bl_volt,br_volt)
+
+ def check_functionality(self, bl_volt, br_volt):
+ """Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0."""
+ if bl_volt > br_volt:
+ debug.error("Read failure. Value 1 was read instead of 0.",1)
+
+ def check_swing_from_vdd(self, bl_volt, br_volt):
+ """Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter."""
+ if bl_volt < br_volt:
+ discharge_volt = bl_volt
+ else:
+ discharge_volt = br_volt
+ desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage
+ debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt))
+ vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
+ if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage:
+ debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin))
+
+ def check_differential_swing(self, bl_volt, br_volt):
+ """This check looks at the difference between the bitline voltages. This needs to be large enough to prevent
+ sensing errors."""
+ bitline_swing = abs(bl_volt-br_volt)
+ debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing))
+ vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
+ if bitline_swing < vdd_error_margin*self.vdd_voltage:
+ debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin))
+
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """
self.set_probe(probe_address, probe_data)
@@ -141,10 +180,10 @@ class bitline_delay(delay):
debug.info(1,"Bitline swing test: corner {}".format(self.corner))
(success, results)=self.run_delay_simulation()
debug.check(success, "Bitline Failed: period {}".format(self.period))
- for mname in self.bitline_meas_names:
- bitline_swings[mname] = results[read_port][mname]
- debug.info(1,"Bitline values (bl/br): {}".format(bitline_swings))
- return bitline_swings
+ debug.info(1,"Bitline values (bl/br): {}".format(results[read_port]))
+ self.check_bitline_all_results(results)
+
+ return results
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index c30d91a0..3f5ac905 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -111,7 +111,7 @@ class control_logic(design.design):
replica_bitline = getattr(c, OPTS.replica_bitline)
delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
- bitcell_loads = int(math.ceil(self.num_rows / 2.0))
+ bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
self.replica_bitline = replica_bitline([delay_fanout_heuristic]*delay_stages_heuristic, bitcell_loads, name="replica_bitline_"+self.port_type)
if self.sram != None:
diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py
index 14ce1953..83d9b280 100644
--- a/technology/freepdk45/tech/tech.py
+++ b/technology/freepdk45/tech/tech.py
@@ -328,13 +328,13 @@ spice["nand2_transition_prob"] = .1875 # Transition probability of 2-input na
spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input nand.
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
-#Logical Effort relative values for the Handmade cells
+#Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter["dff_clk_cin"] = 30.6 #relative capacitance
parameter["6tcell_wl_cin"] = 3 #relative capacitance
parameter["min_inv_para_delay"] = .5 #Tau delay units
parameter["sa_en_pmos_size"] = .72 #micro-meters
parameter["sa_en_nmos_size"] = .27 #micro-meters
-
+parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
###################################################
##END Spice Simulation Parameters
###################################################
diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py
index 3b7e2142..0b4789ae 100755
--- a/technology/scn4m_subm/tech/tech.py
+++ b/technology/scn4m_subm/tech/tech.py
@@ -300,6 +300,7 @@ parameter["6tcell_wl_cin"] = 2
parameter["min_inv_para_delay"] = .5
parameter["sa_en_pmos_size"] = 24*_lambda_
parameter["sa_en_nmos_size"] = 9*_lambda_
+parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
###################################################
##END Spice Simulation Parameters
From e4065929c296bb51ed85867c41f61667ee77823b Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 13 Dec 2018 22:21:30 -0800
Subject: [PATCH 02/77] Added bitline threshold delay checks to delay tests.
---
compiler/characterizer/bitline_delay.py | 112 +++++++++++++++++-------
compiler/tests/21_hspice_delay_test.py | 14 ++-
2 files changed, 92 insertions(+), 34 deletions(-)
diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py
index e4f992fc..6368675c 100644
--- a/compiler/characterizer/bitline_delay.py
+++ b/compiler/characterizer/bitline_delay.py
@@ -22,11 +22,24 @@ class bitline_delay(delay):
self.period = tech.spice["feasible_period"]
self.is_bitline_measure = True
+ def create_signal_names(self):
+ delay.create_signal_names(self)
+ self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"]
+ self.sen_name = "Xsram.s_en"
+
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
- self.bitline_meas_names = ["bl_volt", "br_volt"]
+ self.bl_volt_meas_names = ["volt_bl", "volt_br"]
+ self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation
+ self.bl_delay_result_name = "delay_bl_vth" #Used in the return value
+ def set_probe(self,probe_address, probe_data):
+ """ Probe address and data can be set separately to utilize other
+ functions in this characterizer besides analyze."""
+ delay.set_probe(self,probe_address, probe_data)
+ self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data)
+
def write_delay_measures(self):
"""
Write the measure statements to quantify the bitline voltage at sense amp enable 50%.
@@ -38,26 +51,52 @@ class bitline_delay(delay):
self.sf.write("* {}\n".format(comment))
for read_port in self.targ_read_ports:
- self.write_bitline_measures_read_port(read_port)
+ self.write_bitline_voltage_measures(read_port)
+ self.write_bitline_delay_measures(read_port)
- def write_bitline_measures_read_port(self, port):
+ def write_bitline_voltage_measures(self, port):
+ """
+ Add measurments to capture the bitline voltages at 50% Sense amp enable
+ """
+ debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port))
+ if len(self.all_ports) == 1: #special naming case for single port sram bitlines
+ bitline_port = ""
+ else:
+ bitline_port = str(port)
+
+ sen_port_name = "{}{}".format(self.sen_name,port)
+ for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names):
+ bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column)
+ measure_port_name = "{}{}".format(measure_name,port)
+ self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
+
+ def write_bitline_delay_measures(self, port):
"""
Write the measure statements to quantify the delay and power results for a read port.
"""
# add measure statements for delays/slews
- measure_bitline = self.get_data_bit_column_number(self.probe_address, self.probe_data)
- debug.info(2, "Measuring bitline column={}".format(measure_bitline))
- for port in self.targ_read_ports:
- if len(self.all_ports) == 1: #special naming case for single port sram bitlines
- bitline_port = ""
- else:
- bitline_port = str(port)
-
- sen_name = "Xsram.s_en{}".format(port)
- bl_name = "Xsram.Xbank0.bl{}_{}".format(bitline_port, measure_bitline)
- br_name = "Xsram.Xbank0.br{}_{}".format(bitline_port, measure_bitline)
- self.stim.gen_meas_find_voltage("bl_volt", sen_name, bl_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
- self.stim.gen_meas_find_voltage("br_volt", sen_name, br_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
+ for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names):
+ meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port)
+ self.stim.gen_meas_delay(*meas_values)
+
+ def get_delay_meas_values(self, delay_name, bitline_name, port):
+ """Get the values needed to generate a Spice measurement statement based on the name of the measurement."""
+ if len(self.all_ports) == 1: #special naming case for single port sram bitlines
+ bitline_port = ""
+ else:
+ bitline_port = str(port)
+
+ meas_name="{0}{1}".format(delay_name, port)
+ targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column)
+ half_vdd = 0.5 * self.vdd_voltage
+ trig_val = half_vdd
+ targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"]
+ trig_name = "clk{0}".format(port)
+ trig_dir="FALL"
+ targ_dir="FALL"
+ #Half period added to delay measurement to negative clock edge
+ trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
+ return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
def gen_test_cycles_one_port(self, read_port, write_port):
"""Sets a list of key time-points [ns] of the waveform (each rising edge)
@@ -116,16 +155,29 @@ class bitline_delay(delay):
self.stim.run_sim() #running sim prodoces spice output file.
for port in self.targ_read_ports:
- bitlines_meas_vals = {}
- for mname in self.bitline_meas_names:
- bitlines_meas_vals[mname] = parse_spice_list("timing", mname)
- #Check that power parsing worked.
- for name, val in bitlines_meas_vals.items():
- if type(val)!=float:
- debug.error("Failed to Parse Bitline Values:\n\t\t{0}".format(bitlines_meas_vals),1) #Printing the entire dict looks bad.
- result[port].update(bitlines_meas_vals)
+ #Parse and check the voltage measurements
+ bl_volt_meas_dict = {}
+ for mname in self.bl_volt_meas_names:
+ mname_port = "{}{}".format(mname,port)
+ volt_meas_val = parse_spice_list("timing", mname_port)
+ if type(volt_meas_val)!=float:
+ debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1)
+ bl_volt_meas_dict[mname] = volt_meas_val
+ result[port].update(bl_volt_meas_dict)
+
+ #Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail.
+ bl_delay_meas_dict = {}
+ values_added = 0 #For error checking
+ for mname in self.bl_delay_meas_names: #Parse
+ mname_port = "{}{}".format(mname,port)
+ delay_meas_val = parse_spice_list("timing", mname_port)
+ if type(delay_meas_val)==float: #Only add if value is float, do not error.
+ bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns
+ values_added+=1
+ debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.")
+ debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.")
+ result[port].update(bl_delay_meas_dict)
-
# The delay is from the negative edge for our SRAM
return (True,result)
@@ -134,9 +186,9 @@ class bitline_delay(delay):
for port in self.targ_read_ports:
self.check_bitline_port_results(results[port])
- def check_bitline_port_results(self, port_results)
+ def check_bitline_port_results(self, port_results):
"""Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing"""
- bl_volt, br_volt = port_results["bl_volt"], port_results["br_volt"]
+ bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"]
self.check_functionality(bl_volt,br_volt)
self.check_swing_from_vdd(bl_volt,br_volt)
self.check_differential_swing(bl_volt,br_volt)
@@ -156,7 +208,7 @@ class bitline_delay(delay):
debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt))
vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage:
- debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin))
+ debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100))
def check_differential_swing(self, bl_volt, br_volt):
"""This check looks at the difference between the bitline voltages. This needs to be large enough to prevent
@@ -166,7 +218,7 @@ class bitline_delay(delay):
vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
if bitline_swing < vdd_error_margin*self.vdd_voltage:
debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin))
-
+
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """
self.set_probe(probe_address, probe_data)
@@ -180,7 +232,7 @@ class bitline_delay(delay):
debug.info(1,"Bitline swing test: corner {}".format(self.corner))
(success, results)=self.run_delay_simulation()
debug.check(success, "Bitline Failed: period {}".format(self.period))
- debug.info(1,"Bitline values (bl/br): {}".format(results[read_port]))
+ debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port]))
self.check_bitline_all_results(results)
return results
diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py
index edbd6a55..33f0ecc2 100755
--- a/compiler/tests/21_hspice_delay_test.py
+++ b/compiler/tests/21_hspice_delay_test.py
@@ -48,10 +48,10 @@ class timing_sram_test(openram_test):
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
- #bitline_swing = bl.analyze(probe_address, probe_data, slews, loads)
+ bitline_data = bl.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
data.update(port_data[0])
-
+ data.update(bitline_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.2011],
'delay_lh': [0.2011],
@@ -62,7 +62,10 @@ class timing_sram_test(openram_test):
'slew_hl': [0.10853],
'slew_lh': [0.10853],
'write0_power': [0.51742],
- 'write1_power': [0.51095]}
+ 'write1_power': [0.51095],
+ 'volt_bl': 0.045626,
+ 'volt_br': 1.0709,
+ 'delay_bl_vth': 0.1813}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.3911],
'delay_lh': [1.3911],
@@ -73,7 +76,10 @@ class timing_sram_test(openram_test):
'slew_hl': [0.7397553],
'slew_lh': [0.7397553],
'write0_power': [19.4103],
- 'write1_power': [20.1167]}
+ 'write1_power': [20.1167],
+ 'volt_bl': 1.8329,
+ 'volt_br': 5.081,
+ 'delay_bl_vth': 1.1141}
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results
From dc20bf9e114780390aef6b65b34f02f374ed0a66 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 13 Dec 2018 22:31:08 -0800
Subject: [PATCH 03/77] Added bitline measurements to ngspice delay test.
---
compiler/tests/21_hspice_delay_test.py | 1 +
compiler/tests/21_ngspice_delay_test.py | 15 ++++++++++++---
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py
index 33f0ecc2..c847210b 100755
--- a/compiler/tests/21_hspice_delay_test.py
+++ b/compiler/tests/21_hspice_delay_test.py
@@ -52,6 +52,7 @@ class timing_sram_test(openram_test):
#Combine info about port into all data
data.update(port_data[0])
data.update(bitline_data[0])
+
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.2011],
'delay_lh': [0.2011],
diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py
index 20ba14cc..16c3fb3a 100755
--- a/compiler/tests/21_ngspice_delay_test.py
+++ b/compiler/tests/21_ngspice_delay_test.py
@@ -23,7 +23,7 @@ class timing_sram_test(openram_test):
from importlib import reload
import characterizer
reload(characterizer)
- from characterizer import delay
+ from characterizer import delay, bitline_delay
from sram import sram
from sram_config import sram_config
c = sram_config(word_size=1,
@@ -43,12 +43,15 @@ class timing_sram_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
d = delay(s.s, tempspice, corner)
+ bl = bitline_delay(s.s, tempspice, corner)
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
+ bitline_data = bl.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
data.update(port_data[0])
+ data.update(bitline_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.20443139999999999],
@@ -60,7 +63,10 @@ class timing_sram_test(openram_test):
'slew_hl': [0.1138734],
'slew_lh': [0.1138734],
'write0_power': [0.5205761],
- 'write1_power': [0.5213689]}
+ 'write1_power': [0.5213689],
+ 'volt_bl': 0.03667602,
+ 'volt_br': 1.056013,
+ 'delay_bl_vth': 0.184373}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.610911],
'delay_lh': [1.610911],
@@ -71,7 +77,10 @@ class timing_sram_test(openram_test):
'slew_hl': [0.7986348999999999],
'slew_lh': [0.7986348999999999],
'write0_power': [17.58272],
- 'write1_power': [18.523419999999998]}
+ 'write1_power': [18.523419999999998],
+ 'volt_bl': 1.639692,
+ 'volt_br': 5.06107,
+ 'delay_bl_vth': 1.322235}
else:
self.assertTrue(False) # other techs fail
From 51b1bd46dadd007ce07127bfcbc306fdf9b5d122 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Fri, 14 Dec 2018 18:02:19 -0800
Subject: [PATCH 04/77] Added option to use delay chain size defined in tech.py
---
compiler/modules/control_logic.py | 47 ++++++++++++++++++++----------
compiler/options.py | 3 ++
technology/freepdk45/tech/tech.py | 3 ++
technology/scn4m_subm/tech/tech.py | 2 ++
4 files changed, 40 insertions(+), 15 deletions(-)
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 3f5ac905..8545480c 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -109,24 +109,41 @@ class control_logic(design.design):
from importlib import reload
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
-
- delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
- self.replica_bitline = replica_bitline([delay_fanout_heuristic]*delay_stages_heuristic, bitcell_loads, name="replica_bitline_"+self.port_type)
-
- if self.sram != None:
- self.set_sen_wl_delays()
-
- if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match(): #check condition based on resizing method
- #This resizes to match fall and rise delays, can make the delay chain weird sizes.
- # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
- # self.replica_bitline = replica_bitline(stage_list, bitcell_loads, name="replica_bitline_resized_"+self.port_type)
+ if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
+ delay_stages = parameter["static_delay_stages"]
+ delay_fanout = parameter["static_fanout_per_stage"]
+ debug.info(1, "Using tech parameters to size delay chain: stages={}, fanout={}".format(delay_stages,delay_fanout))
+ self.replica_bitline = replica_bitline([delay_fanout]*delay_stages,
+ bitcell_loads,
+ name="replica_bitline_"+self.port_type)
- #This resizes based on total delay.
- delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
- self.replica_bitline = replica_bitline([delay_fanout]*delay_stages, bitcell_loads, name="replica_bitline_resized_"+self.port_type)
+ else: #Otherwise, use a heuristic and/or model based sizing.
+ #First use a heuristic
+ delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
+ self.replica_bitline = replica_bitline([delay_fanout_heuristic]*delay_stages_heuristic,
+ bitcell_loads,
+ name="replica_bitline_"+self.port_type)
- self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
+ #Use a model to determine the delays with that heuristic
+ if self.sram != None:
+ self.set_sen_wl_delays()
+
+ #Resize if necessary
+ if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match(): #check condition based on resizing method
+ #This resizes to match fall and rise delays, can make the delay chain weird sizes.
+ # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
+ # self.replica_bitline = replica_bitline(stage_list,
+ # bitcell_loads,
+ # name="replica_bitline_resized_"+self.port_type)
+
+ #This resizes based on total delay.
+ delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
+ self.replica_bitline = replica_bitline([delay_fanout]*delay_stages,
+ bitcell_loads,
+ name="replica_bitline_resized_"+self.port_type)
+
+ self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
self.add_mod(self.replica_bitline)
diff --git a/compiler/options.py b/compiler/options.py
index bd4bf607..5293e053 100644
--- a/compiler/options.py
+++ b/compiler/options.py
@@ -71,6 +71,9 @@ class options(optparse.Values):
# You can manually specify banks, but it is better to auto-detect it.
num_banks = 1
+ #Uses the delay chain size in the tech.py file rather automatic sizing.
+ use_tech_delay_chain_size = False
+
# These are the default modules that can be over-riden
decoder = "hierarchical_decoder"
dff_array = "dff_array"
diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py
index 83d9b280..6b8f981c 100644
--- a/technology/freepdk45/tech/tech.py
+++ b/technology/freepdk45/tech/tech.py
@@ -329,12 +329,15 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
#Parameters related to sense amp enable timing and delay chain/RBL sizing
+parameter["static_delay_stages"] = 4
+parameter["static_fanout_per_stage"] = 3
parameter["dff_clk_cin"] = 30.6 #relative capacitance
parameter["6tcell_wl_cin"] = 3 #relative capacitance
parameter["min_inv_para_delay"] = .5 #Tau delay units
parameter["sa_en_pmos_size"] = .72 #micro-meters
parameter["sa_en_nmos_size"] = .27 #micro-meters
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
+
###################################################
##END Spice Simulation Parameters
###################################################
diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py
index 0b4789ae..fe2d686a 100755
--- a/technology/scn4m_subm/tech/tech.py
+++ b/technology/scn4m_subm/tech/tech.py
@@ -295,6 +295,8 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na
spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor.
#Logical Effort relative values for the Handmade cells
+parameter["static_delay_stages"] = 4
+parameter["static_fanout_per_stage"] = 3
parameter["dff_clk_cin"] = 27.5
parameter["6tcell_wl_cin"] = 2
parameter["min_inv_para_delay"] = .5
From 8eb4812e166571db8892ac64652fc5bc7a3fa985 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Mon, 17 Dec 2018 23:32:02 -0800
Subject: [PATCH 05/77] Made parasitic delay parameter in Freepdk45 more
accurate, added stage names to delay model.
---
compiler/characterizer/logical_effort.py | 14 ++++++++++----
compiler/pgates/pinv.py | 2 +-
compiler/pgates/pnand2.py | 2 +-
compiler/pgates/pnand3.py | 3 ++-
technology/freepdk45/tech/tech.py | 2 +-
5 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/compiler/characterizer/logical_effort.py b/compiler/characterizer/logical_effort.py
index bf8c1585..bbb0bd77 100644
--- a/compiler/characterizer/logical_effort.py
+++ b/compiler/characterizer/logical_effort.py
@@ -10,7 +10,8 @@ class logical_effort():
min_inv_cin = 1+beta
pinv=parameter["min_inv_para_delay"]
- def __init__(self, size, cin, cout, parasitic, out_is_rise=True):
+ def __init__(self, name, size, cin, cout, parasitic, out_is_rise=True):
+ self.name = name
self.cin = cin
self.cout = cout
self.logical_effort = (self.cin/size)/logical_effort.min_inv_cin
@@ -19,8 +20,13 @@ class logical_effort():
self.is_rise = out_is_rise
def __str__(self):
- return "g=" + str(self.logical_effort) + ", h=" + str(self.eletrical_effort) + ", p=" + str(self.parasitic_scale)+"*pinv, rise_delay="+str(self.is_rise)
-
+ return "Name={}, g={}, h={}, p={}*pinv, rise_delay={}".format(self.name,
+ self.logical_effort,
+ self.eletrical_effort,
+ self.parasitic_scale,
+ self.is_rise
+ )
+
def get_stage_effort(self):
return self.logical_effort*self.eletrical_effort
@@ -40,7 +46,7 @@ def calculate_relative_rise_fall_delays(stage_effort_list, pinv=parameter["min_i
debug.info(2, "Calculating rise/fall relative delays")
total_rise_delay, total_fall_delay = 0,0
for stage in stage_effort_list:
- debug.info(3, stage)
+ debug.info(2, stage)
if stage.is_rise:
total_rise_delay += stage.get_stage_delay(pinv)
else:
diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py
index 31682360..b1e1f033 100644
--- a/compiler/pgates/pinv.py
+++ b/compiler/pgates/pinv.py
@@ -293,4 +293,4 @@ class pinv(pgate.pgate):
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
"""
parasitic_delay = 1
- return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
+ return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py
index e767b87e..c9709864 100644
--- a/compiler/pgates/pnand2.py
+++ b/compiler/pgates/pnand2.py
@@ -260,4 +260,4 @@ class pnand2(pgate.pgate):
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
"""
parasitic_delay = 2
- return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
\ No newline at end of file
+ return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
\ No newline at end of file
diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py
index 4dab5264..d159a27e 100644
--- a/compiler/pgates/pnand3.py
+++ b/compiler/pgates/pnand3.py
@@ -5,6 +5,7 @@ from tech import drc, parameter, spice
from ptx import ptx
from vector import vector
from globals import OPTS
+import logical_effort
class pnand3(pgate.pgate):
"""
@@ -272,4 +273,4 @@ class pnand3(pgate.pgate):
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
"""
parasitic_delay = 3
- return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
\ No newline at end of file
+ return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
\ No newline at end of file
diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py
index 6b8f981c..ff9b5169 100644
--- a/technology/freepdk45/tech/tech.py
+++ b/technology/freepdk45/tech/tech.py
@@ -333,7 +333,7 @@ parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3
parameter["dff_clk_cin"] = 30.6 #relative capacitance
parameter["6tcell_wl_cin"] = 3 #relative capacitance
-parameter["min_inv_para_delay"] = .5 #Tau delay units
+parameter["min_inv_para_delay"] = 2.4 #Tau delay units
parameter["sa_en_pmos_size"] = .72 #micro-meters
parameter["sa_en_nmos_size"] = .27 #micro-meters
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
From b10ef3fb7e5038a67c5ad9f187fe1099ec4ba11e Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 19 Dec 2018 18:33:06 -0800
Subject: [PATCH 06/77] Replaced delay measure statement with object
implementation.
---
compiler/characterizer/__init__.py | 1 +
compiler/characterizer/delay.py | 31 ++++++++-
compiler/characterizer/measurements.py | 92 ++++++++++++++++++++++++++
3 files changed, 121 insertions(+), 3 deletions(-)
create mode 100644 compiler/characterizer/measurements.py
diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py
index 4a2f161e..009459b3 100644
--- a/compiler/characterizer/__init__.py
+++ b/compiler/characterizer/__init__.py
@@ -9,6 +9,7 @@ from .functional import *
from .worst_case import *
from .simulation import *
from .bitline_delay import *
+from .measurements import *
debug.info(1,"Initializing characterizer...")
OPTS.spice_exe = ""
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index 9e0543d1..d04ccf50 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -8,6 +8,7 @@ from .charutils import *
import utils
from globals import OPTS
from .simulation import simulation
+from .measurements import *
class delay(simulation):
"""Functions to measure the delay and power of an SRAM at a given address and
@@ -45,6 +46,16 @@ class delay(simulation):
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"]
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
+
+ def create_measurement_objects(self):
+ self.meas_objs = []
+ trig_delay_name = "clk{0}"
+ targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
+ self.meas_objs.append(delay_measure("delay_lh{}", trig_delay_name, targ_name, "RISE", "RISE"))
+ self.meas_objs.append(delay_measure("delay_hl{}", trig_delay_name, targ_name, "FALL", "FALL"))
+
+ self.meas_objs.append(slew_measure("slew_lh{}", targ_name, "RISE"))
+ self.meas_objs.append(slew_measure("slew_hl{}", targ_name, "FALL"))
def create_signal_names(self):
self.addr_name = "A"
@@ -235,15 +246,28 @@ class delay(simulation):
else:
debug.error(1, "Measure command {0} not recognized".format(delay_name))
return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
+
+ def get_measure_variants(self, port, measure_obj):
+ """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
+ #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
+ #vdd is arguably constant as that is true for a single lib file.
+ if measure_obj.targ_dir_str == "FALL":
+ meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]]
+ elif measure_obj.targ_dir_str == "RISE":
+ meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read1"]]
+ else:
+ debug.error("Unrecognised measurement direction={}".format(measure_obj.targ_dir_str),1)
+
+ return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
def write_delay_measures_read_port(self, port):
"""
Write the measure statements to quantify the delay and power results for a read port.
"""
# add measure statements for delays/slews
- for dname in self.delay_meas_names:
- meas_values = self.get_delay_meas_values(dname, port)
- self.stim.gen_meas_delay(*meas_values)
+ for measure in self.meas_objs:
+ measure_variant_tuple = self.get_measure_variants(port, measure)
+ measure.write_measure(self.stim, measure_variant_tuple)
# add measure statements for power
for pname in self.power_meas_names:
@@ -645,6 +669,7 @@ class delay(simulation):
char_sram_data = {}
self.set_probe(probe_address, probe_data)
+ self.create_measurement_objects()
self.load=max(loads)
self.slew=max(slews)
diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py
new file mode 100644
index 00000000..2822183c
--- /dev/null
+++ b/compiler/characterizer/measurements.py
@@ -0,0 +1,92 @@
+import debug
+from tech import drc, parameter, spice
+from abc import ABC, abstractmethod
+from .stimuli import *
+
+class spice_measurement(ABC):
+ """Base class for spice stimulus measurements."""
+ def __init__(self, measure_name):
+ #Names must be unique for correct spice simulation, but not enforced here.
+ self.name = measure_name
+
+ @abstractmethod
+ def get_measure_function(self):
+ return None
+
+ @abstractmethod
+ def get_measure_values(self):
+ return None
+
+ def write_measure(self, stim_obj, input_tuple):
+ measure_func = self.get_measure_function()
+ if measure_func == None:
+ debug.error("Did not set measure function",1)
+ measure_vals = self.get_measure_values(*input_tuple)
+ measure_func(stim_obj, *measure_vals)
+
+
+class delay_measure(spice_measurement):
+ """Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
+
+ def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str):
+ spice_measurement.__init__(self, measure_name)
+ self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str)
+
+ def get_measure_function(self):
+ return stimuli.gen_meas_delay
+
+ def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str):
+ """Set the values needed to generate a Spice measurement statement based on the name of the measurement."""
+ self.trig_dir_str = trig_dir_str
+ self.targ_dir_str = targ_dir_str
+
+ self.trig_val_of_vdd = 0.5
+ self.targ_val_of_vdd = 0.5
+
+ self.trig_name_no_port = trig_name
+ self.targ_name_no_port = targ_name
+
+ #Time delays and ports are variant and needed as inputs when writing the measurement
+
+ def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
+ """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
+ trig_val = self.trig_val_of_vdd * vdd_voltage
+ targ_val = self.targ_val_of_vdd * vdd_voltage
+
+ if port != None:
+ meas_name = self.name.format(port)
+ trig_name = self.trig_name_no_port.format(port)
+ targ_name = self.targ_name_no_port.format(port)
+ else:
+ meas_name = self.name
+ trig_name = self.trig_name_no_port
+ targ_name = self.targ_name_no_port
+
+ return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td)
+
+
+class slew_measure(delay_measure):
+
+ def __init__(self, measure_name, signal_name, slew_dir_str):
+ spice_measurement.__init__(self, measure_name)
+ self.set_meas_constants(signal_name, slew_dir_str)
+
+ def set_meas_constants(self, signal_name, slew_dir_str):
+ """Set the values needed to generate a Spice measurement statement based on the name of the measurement."""
+ self.trig_dir_str = slew_dir_str
+ self.targ_dir_str = slew_dir_str
+
+ if slew_dir_str == "RISE":
+ self.trig_val_of_vdd = 0.1
+ self.targ_val_of_vdd = 0.9
+ elif slew_dir_str == "FALL":
+ self.trig_val_of_vdd = 0.9
+ self.targ_val_of_vdd = 0.1
+ else:
+ debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1)
+
+ self.trig_name_no_port = signal_name
+ self.targ_name_no_port = signal_name
+
+ #Time delays and ports are variant and needed as inputs when writing the measurement
+
\ No newline at end of file
From 66b2fcdc91401e553ac494853f0b50b3dfe0f36a Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 20 Dec 2018 15:54:56 -0800
Subject: [PATCH 07/77] Added data parsing to measurement objects and adding
power measurements.
---
compiler/characterizer/charutils.py | 7 +
compiler/characterizer/delay.py | 190 +++++++++++--------------
compiler/characterizer/measurements.py | 52 +++++--
3 files changed, 135 insertions(+), 114 deletions(-)
diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py
index a2140e51..061972cf 100644
--- a/compiler/characterizer/charutils.py
+++ b/compiler/characterizer/charutils.py
@@ -80,3 +80,10 @@ def convert_to_float(number):
debug.error("Invalid number: {0}".format(number),1)
return float_value
+
+def check_dict_values_is_float(dict):
+ """Checks if all the values are floats. Useful for checking failed Spice measurements."""
+ for key, value in dict.items():
+ if type(value)!=float:
+ return False
+ return True
\ No newline at end of file
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index d04ccf50..7f25348b 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -48,15 +48,31 @@ class delay(simulation):
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
def create_measurement_objects(self):
- self.meas_objs = []
+ """Create the measurements used for read and write ports"""
+ self.create_read_port_measurement_objects()
+ self.create_write_port_measurement_objects()
+
+ def create_read_port_measurement_objects(self):
+ """Create the measurements used for read ports: delays, slews, powers"""
+ self.read_meas_objs = []
trig_delay_name = "clk{0}"
targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
- self.meas_objs.append(delay_measure("delay_lh{}", trig_delay_name, targ_name, "RISE", "RISE"))
- self.meas_objs.append(delay_measure("delay_hl{}", trig_delay_name, targ_name, "FALL", "FALL"))
+ self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9))
+ self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", 1e9))
- self.meas_objs.append(slew_measure("slew_lh{}", targ_name, "RISE"))
- self.meas_objs.append(slew_measure("slew_hl{}", targ_name, "FALL"))
+ self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9))
+ self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", 1e9))
+ self.read_meas_objs.append(power_measure("read1_power", "RISE", 1e3))
+ self.read_meas_objs.append(power_measure("read0_power", "FALL", 1e3))
+
+ def create_write_port_measurement_objects(self):
+ """Create the measurements used for read ports: delays, slews, powers"""
+ self.write_meas_objs = []
+
+ self.write_meas_objs.append(power_measure("write1_power", "RISE", 1e3))
+ self.write_meas_objs.append(power_measure("write0_power", "FALL", 1e3))
+
def create_signal_names(self):
self.addr_name = "A"
self.din_name = "DIN"
@@ -209,99 +225,68 @@ class delay(simulation):
self.sf.close()
- def get_delay_meas_values(self, delay_name, port):
- """Get the values needed to generate a Spice measurement statement based on the name of the measurement."""
- debug.check('lh' in delay_name or 'hl' in delay_name, "Measure command {0} does not contain direction (lh/hl)")
- trig_clk_name = "clk{0}".format(port)
- meas_name="{0}{1}".format(delay_name, port)
- targ_name = "{0}".format("{0}{1}_{2}".format(self.dout_name,port,self.probe_data))
- half_vdd = 0.5 * self.vdd_voltage
- trig_slew_low = 0.1 * self.vdd_voltage
- targ_slew_high = 0.9 * self.vdd_voltage
- if 'delay' in delay_name:
- trig_val = half_vdd
- targ_val = half_vdd
- trig_name = trig_clk_name
- if 'lh' in delay_name:
- trig_dir="RISE"
- targ_dir="RISE"
- trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]]
- else:
- trig_dir="FALL"
- targ_dir="FALL"
- trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]]
-
- elif 'slew' in delay_name:
- trig_name = targ_name
- if 'lh' in delay_name:
- trig_val = trig_slew_low
- targ_val = targ_slew_high
- targ_dir = trig_dir = "RISE"
- trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]]
- else:
- trig_val = targ_slew_high
- targ_val = trig_slew_low
- targ_dir = trig_dir = "FALL"
- trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]]
+ def get_read_measure_variants(self, port, measure_obj):
+ """Checks the measurement object and calls respective function for related measurement inputs."""
+ meas_type = type(measure_obj)
+ if meas_type is delay_measure or meas_type is slew_measure:
+ return self.get_delay_measure_variants(port, measure_obj)
+ elif meas_type is power_measure:
+ return self.get_power_measure_variants(port, measure_obj, "read")
else:
- debug.error(1, "Measure command {0} not recognized".format(delay_name))
- return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
-
- def get_measure_variants(self, port, measure_obj):
+ debug.error("Input function not defined for measurement type={}".format(meas_type))
+
+ def get_delay_measure_variants(self, port, delay_obj):
"""Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#vdd is arguably constant as that is true for a single lib file.
- if measure_obj.targ_dir_str == "FALL":
+ if delay_obj.targ_dir_str == "FALL":
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]]
- elif measure_obj.targ_dir_str == "RISE":
+ elif delay_obj.targ_dir_str == "RISE":
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read1"]]
else:
- debug.error("Unrecognised measurement direction={}".format(measure_obj.targ_dir_str),1)
+ debug.error("Unrecognised measurement direction={}".format(delay_obj.targ_dir_str),1)
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
-
+
+ def get_power_measure_variants(self, port, power_obj, operation):
+ """Get the measurement values that can either vary port to port (time delays)"""
+ #Return value is intended to match the power measure format: t_initial, t_final, port
+ if power_obj.power_type == "FALL":
+ t_initial = self.cycle_times[self.measure_cycles[port]["{}0".format(operation)]]
+ t_final = self.cycle_times[self.measure_cycles[port]["{}0".format(operation)]+1]
+ elif power_obj.power_type == "RISE":
+ t_initial = self.cycle_times[self.measure_cycles[port]["{}1".format(operation)]]
+ t_final = self.cycle_times[self.measure_cycles[port]["{}1".format(operation)]+1]
+ else:
+ debug.error("Unrecognised power measurement type={}".format(power_obj.power_type),1)
+
+ return (t_initial, t_final, port)
+
def write_delay_measures_read_port(self, port):
"""
Write the measure statements to quantify the delay and power results for a read port.
"""
# add measure statements for delays/slews
- for measure in self.meas_objs:
- measure_variant_tuple = self.get_measure_variants(port, measure)
- measure.write_measure(self.stim, measure_variant_tuple)
+ for measure in self.read_meas_objs:
+ measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
+ measure.write_measure(self.stim, measure_variant_inp_tuple)
+
+ def get_write_measure_variants(self, port, measure_obj):
+ """Checks the measurement object and calls respective function for related measurement inputs."""
+ meas_type = type(measure_obj)
+ if meas_type is power_measure:
+ return self.get_power_measure_variants(port, measure_obj, "write")
+ else:
+ debug.error("Input function not defined for measurement type={}".format(meas_type))
- # add measure statements for power
- for pname in self.power_meas_names:
- if "read" not in pname:
- continue
- #Different naming schemes are used for the measure cycle dict and measurement names.
- #TODO: make them the same so they can be indexed the same.
- if '1' in pname:
- t_initial = self.cycle_times[self.measure_cycles[port]["read1"]]
- t_final = self.cycle_times[self.measure_cycles[port]["read1"]+1]
- elif '0' in pname:
- t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
- t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1]
- self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port),
- t_initial=t_initial,
- t_final=t_final)
-
def write_delay_measures_write_port(self, port):
"""
Write the measure statements to quantify the power results for a write port.
"""
# add measure statements for power
- for pname in self.power_meas_names:
- if "write" not in pname:
- continue
- t_initial = self.cycle_times[self.measure_cycles[port]["write0"]]
- t_final = self.cycle_times[self.measure_cycles[port]["write0"]+1]
- if '1' in pname:
- t_initial = self.cycle_times[self.measure_cycles[port]["write1"]]
- t_final = self.cycle_times[self.measure_cycles[port]["write1"]+1]
-
- self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port),
- t_initial=t_initial,
- t_final=t_final)
+ for measure in self.write_meas_objs:
+ measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
+ measure.write_measure(self.stim, measure_variant_inp_tuple)
def write_delay_measures(self):
"""
@@ -451,28 +436,27 @@ class delay(simulation):
#Too much duplicate code here. Try reducing
for port in self.targ_read_ports:
debug.info(2, "Check delay values for port {}".format(port))
- delay_names = [mname for mname in self.delay_meas_names]
- delays = self.parse_values(delay_names, port, 1e9) # scale delays to ns
- if not self.check_valid_delays(delays):
- return (False,{})
- result[port].update(delays)
+ read_port_dict = {}
+ #Get measurements from output file
+ for measure in self.read_meas_objs:
+ read_port_dict[measure.name] = measure.retrieve_measure(port=port)
- power_names = [mname for mname in self.power_meas_names if 'read' in mname]
- powers = self.parse_values(power_names, port, 1e3) # scale power to mw
- #Check that power parsing worked.
- for name, power in powers.items():
- if type(power)!=float:
- debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad.
- result[port].update(powers)
-
+ #Check timing for read ports. Power is only checked if it was read correctly
+ if not self.check_valid_delays(read_port_dict):
+ return (False,{})
+ if not check_dict_values_is_float(read_port_dict):
+ debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad.
+
+ result[port].update(read_port_dict)
+
for port in self.targ_write_ports:
- power_names = [mname for mname in self.power_meas_names if 'write' in mname]
- powers = self.parse_values(power_names, port, 1e3) # scale power to mw
- #Check that power parsing worked.
- for name, power in powers.items():
- if type(power)!=float:
- debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad.
- result[port].update(powers)
+ write_port_dict = {}
+ for measure in self.read_meas_objs:
+ write_port_dict[measure.name] = measure.retrieve_measure(port=port)
+
+ if not check_dict_values_is_float(write_port_dict):
+ debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) #Printing the entire dict looks bad.
+ result[port].update(write_port_dict)
# The delay is from the negative edge for our SRAM
return (True,result)
@@ -502,13 +486,13 @@ class delay(simulation):
#key=raw_input("press return to continue")
return (leakage_power*1e3, trim_leakage_power*1e3)
- def check_valid_delays(self, delay_dict):
+ def check_valid_delays(self, result_dict):
""" Check if the measurements are defined and if they are valid. """
#Hard coded names currently
- delay_hl = delay_dict["delay_hl"]
- delay_lh = delay_dict["delay_lh"]
- slew_hl = delay_dict["slew_hl"]
- slew_lh = delay_dict["slew_lh"]
+ delay_hl = result_dict["delay_hl"]
+ delay_lh = result_dict["delay_lh"]
+ slew_hl = result_dict["slew_hl"]
+ slew_lh = result_dict["slew_lh"]
period_load_slew_str = "period {0} load {1} slew {2}".format(self.period,self.load, self.slew)
# if it failed or the read was longer than a period
diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py
index 2822183c..f065ac70 100644
--- a/compiler/characterizer/measurements.py
+++ b/compiler/characterizer/measurements.py
@@ -2,13 +2,15 @@ import debug
from tech import drc, parameter, spice
from abc import ABC, abstractmethod
from .stimuli import *
+from .charutils import *
class spice_measurement(ABC):
"""Base class for spice stimulus measurements."""
- def __init__(self, measure_name):
+ def __init__(self, measure_name, measure_scale=None):
#Names must be unique for correct spice simulation, but not enforced here.
self.name = measure_name
-
+ self.measure_scale = measure_scale
+
@abstractmethod
def get_measure_function(self):
return None
@@ -23,13 +25,19 @@ class spice_measurement(ABC):
debug.error("Did not set measure function",1)
measure_vals = self.get_measure_values(*input_tuple)
measure_func(stim_obj, *measure_vals)
-
+
+ def retrieve_measure(self, port=""):
+ value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
+ if type(value)!=float or self.measure_scale == None:
+ return value
+ else:
+ return value*self.measure_scale
class delay_measure(spice_measurement):
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
- def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str):
- spice_measurement.__init__(self, measure_name)
+ def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, measure_scale=None):
+ spice_measurement.__init__(self, measure_name, measure_scale)
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str)
def get_measure_function(self):
@@ -54,7 +62,8 @@ class delay_measure(spice_measurement):
targ_val = self.targ_val_of_vdd * vdd_voltage
if port != None:
- meas_name = self.name.format(port)
+ #For dictionary indexing reasons, the name is formatted differently than the signals
+ meas_name = "{}{}".format(self.name, port)
trig_name = self.trig_name_no_port.format(port)
targ_name = self.targ_name_no_port.format(port)
else:
@@ -63,12 +72,11 @@ class delay_measure(spice_measurement):
targ_name = self.targ_name_no_port
return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td)
-
-
+
class slew_measure(delay_measure):
- def __init__(self, measure_name, signal_name, slew_dir_str):
- spice_measurement.__init__(self, measure_name)
+ def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None):
+ spice_measurement.__init__(self, measure_name, measure_scale)
self.set_meas_constants(signal_name, slew_dir_str)
def set_meas_constants(self, signal_name, slew_dir_str):
@@ -89,4 +97,26 @@ class slew_measure(delay_measure):
self.targ_name_no_port = signal_name
#Time delays and ports are variant and needed as inputs when writing the measurement
-
\ No newline at end of file
+
+class power_measure(spice_measurement):
+ """Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
+
+ def __init__(self, measure_name, power_type="", measure_scale=None):
+ spice_measurement.__init__(self, measure_name, measure_scale)
+ self.set_meas_constants(power_type)
+
+ def get_measure_function(self):
+ return stimuli.gen_meas_power
+
+ def set_meas_constants(self, power_type):
+ """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
+ #Not needed for power simulation
+ self.power_type = power_type #Expected to be "RISE"/"FALL"
+
+ def get_measure_values(self, t_initial, t_final, port=None):
+ """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
+ if port != None:
+ meas_name = "{}{}".format(self.name, port)
+ else:
+ meas_name = self.name
+ return (meas_name,t_initial,t_final)
\ No newline at end of file
From 272267358f16eac7ffc689718814251a32f6306f Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 3 Jan 2019 05:51:28 -0800
Subject: [PATCH 08/77] Moved all bitline delay measurements to delay class.
Added measurements to check delay model.
---
compiler/characterizer/__init__.py | 1 +
compiler/characterizer/bitline_delay.py | 19 +++
compiler/characterizer/delay.py | 156 +++++++++++++-------
compiler/characterizer/logical_effort.py | 4 +
compiler/characterizer/measurements.py | 52 +++++--
compiler/characterizer/model_check.py | 174 +++++++++++++++++++++++
compiler/modules/control_logic.py | 22 ++-
compiler/tests/21_hspice_delay_test.py | 29 ++--
compiler/tests/21_ngspice_delay_test.py | 39 +++--
compiler/tests/28_delay_model_test.py | 61 ++++++++
10 files changed, 456 insertions(+), 101 deletions(-)
create mode 100644 compiler/characterizer/model_check.py
create mode 100755 compiler/tests/28_delay_model_test.py
diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py
index 009459b3..cbcf0a1a 100644
--- a/compiler/characterizer/__init__.py
+++ b/compiler/characterizer/__init__.py
@@ -10,6 +10,7 @@ from .worst_case import *
from .simulation import *
from .bitline_delay import *
from .measurements import *
+from .model_check import *
debug.info(1,"Initializing characterizer...")
OPTS.spice_exe = ""
diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py
index 6368675c..2af290f1 100644
--- a/compiler/characterizer/bitline_delay.py
+++ b/compiler/characterizer/bitline_delay.py
@@ -26,7 +26,26 @@ class bitline_delay(delay):
delay.create_signal_names(self)
self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"]
self.sen_name = "Xsram.s_en"
+
+ def create_measurement_objects(self):
+ """Create the measurements used for read and write ports"""
+ self.meas_objs = []
+ self.create_bitline_find_measurement_objects()
+ self.create_bitline_delay_measurement_objects()
+
+ def create_bitline_delay_measurement_objects(self):
+ self.find_meas_objs = []
+ trig_delay_name = "clk{0}"
+ targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
+ self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9))
+ self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", 1e9))
+ self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9))
+ self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", 1e9))
+
+ self.read_meas_objs.append(power_measure("read1_power", "RISE", 1e3))
+ self.read_meas_objs.append(power_measure("read0_power", "FALL", 1e3))
+
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index 7f25348b..a327c548 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -46,32 +46,77 @@ class delay(simulation):
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"]
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
+ self.voltage_when_names = ["volt_bl", "volt_br"]
+ self.bitline_delay_names = ["delay_bl", "delay_br"]
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
self.create_read_port_measurement_objects()
self.create_write_port_measurement_objects()
-
+
def create_read_port_measurement_objects(self):
"""Create the measurements used for read ports: delays, slews, powers"""
+
self.read_meas_objs = []
trig_delay_name = "clk{0}"
targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
- self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9))
- self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", 1e9))
+ self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9))
+ self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file.
+ self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9))
+ self.read_meas_objs[-1].meta_str = "read0"
- self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9))
- self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", 1e9))
+ self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9))
+ self.read_meas_objs[-1].meta_str = "read1"
+ self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9))
+ self.read_meas_objs[-1].meta_str = "read0"
- self.read_meas_objs.append(power_measure("read1_power", "RISE", 1e3))
- self.read_meas_objs.append(power_measure("read0_power", "FALL", 1e3))
-
+ self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3))
+ self.read_meas_objs[-1].meta_str = "read1"
+ self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3))
+ self.read_meas_objs[-1].meta_str = "read0"
+
+ trig_name = "Xsram.s_en{}" #Sense amp enable
+ if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
+ port_format = ""
+ else:
+ port_format = "{}"
+
+ bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column)
+ br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column)
+ self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5))
+ self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5))
+
+ #These are read values but need to be separated for unique error checking.
+ self.create_bitline_delay_measurement_objects()
+
+ def create_bitline_delay_measurement_objects(self):
+ """Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements.
+ These measurements are only associated with read values
+ """
+ self.bitline_delay_objs = []
+ trig_name = "clk{0}"
+ if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
+ port_format = ""
+ else:
+ port_format = "{}"
+ bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column)
+ br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column)
+ targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd
+
+ targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
+ self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
+ self.bitline_delay_objs[-1].meta_str = "read0"
+ self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
+ self.bitline_delay_objs[-1].meta_str = "read1"
+
def create_write_port_measurement_objects(self):
"""Create the measurements used for read ports: delays, slews, powers"""
self.write_meas_objs = []
- self.write_meas_objs.append(power_measure("write1_power", "RISE", 1e3))
- self.write_meas_objs.append(power_measure("write0_power", "FALL", 1e3))
+ self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3))
+ self.write_meas_objs[-1].meta_str = "read1"
+ self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3))
+ self.write_meas_objs[-1].meta_str = "write0"
def create_signal_names(self):
self.addr_name = "A"
@@ -232,6 +277,8 @@ class delay(simulation):
return self.get_delay_measure_variants(port, measure_obj)
elif meas_type is power_measure:
return self.get_power_measure_variants(port, measure_obj, "read")
+ elif meas_type is voltage_when_measure:
+ return self.get_volt_when_measure_variants(port, measure_obj)
else:
debug.error("Input function not defined for measurement type={}".format(meas_type))
@@ -239,35 +286,37 @@ class delay(simulation):
"""Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#vdd is arguably constant as that is true for a single lib file.
- if delay_obj.targ_dir_str == "FALL":
- meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]]
- elif delay_obj.targ_dir_str == "RISE":
- meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read1"]]
+ if delay_obj.meta_str == "read0":
+ #Falling delay are measured starting from neg. clk edge. Delay adjusted to that.
+ meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] + self.period/2
+ elif delay_obj.meta_str == "read1":
+ meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
else:
- debug.error("Unrecognised measurement direction={}".format(delay_obj.targ_dir_str),1)
+ debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1)
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
def get_power_measure_variants(self, port, power_obj, operation):
"""Get the measurement values that can either vary port to port (time delays)"""
#Return value is intended to match the power measure format: t_initial, t_final, port
- if power_obj.power_type == "FALL":
- t_initial = self.cycle_times[self.measure_cycles[port]["{}0".format(operation)]]
- t_final = self.cycle_times[self.measure_cycles[port]["{}0".format(operation)]+1]
- elif power_obj.power_type == "RISE":
- t_initial = self.cycle_times[self.measure_cycles[port]["{}1".format(operation)]]
- t_final = self.cycle_times[self.measure_cycles[port]["{}1".format(operation)]+1]
- else:
- debug.error("Unrecognised power measurement type={}".format(power_obj.power_type),1)
-
+ t_initial = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]]
+ t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]+1]
+
return (t_initial, t_final, port)
+ def get_volt_when_measure_variants(self, port, power_obj):
+ """Get the measurement values that can either vary port to port (time delays)"""
+ #Only checking 0 value reads for now.
+ t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]]
+
+ return (t_trig, self.vdd_voltage, port)
+
def write_delay_measures_read_port(self, port):
"""
Write the measure statements to quantify the delay and power results for a read port.
"""
# add measure statements for delays/slews
- for measure in self.read_meas_objs:
+ for measure in self.read_meas_objs+self.bitline_delay_objs:
measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
@@ -285,7 +334,7 @@ class delay(simulation):
"""
# add measure statements for power
for measure in self.write_meas_objs:
- measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
+ measure_variant_inp_tuple = self.get_write_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
def write_delay_measures(self):
@@ -394,28 +443,7 @@ class delay(simulation):
previous_period = self.period
debug.info(1, "Found feasible_period: {0}ns".format(self.period))
return feasible_delays
-
-
- def parse_values(self, values_names, port, mult = 1.0):
- """Parse multiple values in the timing output file. Optional multiplier.
- Return a dict of the input names and values. Port used for parsing file.
- """
- values = []
- all_values_floats = True
- for vname in values_names:
- #ngspice converts all measure characters to lowercase, not tested on other sims
- value = parse_spice_list("timing", "{0}{1}".format(vname.lower(), port))
- #Check if any of the values fail to parse
- if type(value)!=float:
- all_values_floats = False
- values.append(value)
-
- #Apply Multiplier only if all values are floats. Let other check functions handle this error.
- if all_values_floats:
- return {values_names[i]:values[i]*mult for i in range(len(values))}
- else:
- return {values_names[i]:values[i] for i in range(len(values))}
-
+
def run_delay_simulation(self):
"""
This tries to simulate a period and checks if the result works. If
@@ -448,10 +476,13 @@ class delay(simulation):
debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad.
result[port].update(read_port_dict)
-
+
+ bitline_delay_dict = self.evaluate_bitline_delay(port)
+ result[port].update(bitline_delay_dict)
+
for port in self.targ_write_ports:
write_port_dict = {}
- for measure in self.read_meas_objs:
+ for measure in self.write_meas_objs:
write_port_dict[measure.name] = measure.retrieve_measure(port=port)
if not check_dict_values_is_float(write_port_dict):
@@ -461,7 +492,17 @@ class delay(simulation):
# The delay is from the negative edge for our SRAM
return (True,result)
-
+ def evaluate_bitline_delay(self, port):
+ """Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function."""
+ bl_delay_meas_dict = {}
+ values_added = 0 #For error checking
+ for measure in self.bitline_delay_objs:
+ bl_delay_val = measure.retrieve_measure(port=port)
+ if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error.
+ debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1)
+ bl_delay_meas_dict[measure.name] = bl_delay_val
+ return bl_delay_meas_dict
+
def run_power_simulation(self):
"""
This simulates a disabled SRAM to get the leakage power when it is off.
@@ -618,9 +659,18 @@ class delay(simulation):
functions in this characterizer besides analyze."""
self.probe_address = probe_address
self.probe_data = probe_data
-
+ self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data)
+
self.prepare_netlist()
+ def get_data_bit_column_number(self, probe_address, probe_data):
+ """Calculates bitline column number of data bit under test using bit position and mux size"""
+ if self.sram.col_addr_size>0:
+ col_address = int(probe_address[0:self.sram.col_addr_size],2)
+ else:
+ col_address = 0
+ bl_column = int(self.sram.words_per_row*probe_data + col_address)
+ return bl_column
def prepare_netlist(self):
""" Prepare a trimmed netlist and regular netlist. """
@@ -899,7 +949,7 @@ class delay(simulation):
def get_empty_measure_data_dict(self):
"""Make a dict of lists for each type of delay and power measurement to append results to"""
- measure_names = self.delay_meas_names + self.power_meas_names
+ measure_names = self.delay_meas_names + self.power_meas_names + self.voltage_when_names + self.bitline_delay_names
#Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists.
measure_data = [{mname:[] for mname in measure_names} for i in self.all_ports]
return measure_data
diff --git a/compiler/characterizer/logical_effort.py b/compiler/characterizer/logical_effort.py
index bbb0bd77..c80e69a2 100644
--- a/compiler/characterizer/logical_effort.py
+++ b/compiler/characterizer/logical_effort.py
@@ -35,6 +35,10 @@ class logical_effort():
def get_stage_delay(self, pinv):
return self.get_stage_effort()+self.get_parasitic_delay(pinv)
+
+def calculate_delays(stage_effort_list, pinv):
+ """Convert stage effort objects to list of delay values"""
+ return [stage.get_stage_delay(pinv) for stage in stage_effort_list]
def calculate_relative_delay(stage_effort_list, pinv=parameter["min_inv_para_delay"]):
"""Calculates the total delay of a given delay path made of a list of logical effort objects."""
diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py
index f065ac70..aec4d769 100644
--- a/compiler/characterizer/measurements.py
+++ b/compiler/characterizer/measurements.py
@@ -10,6 +10,7 @@ class spice_measurement(ABC):
#Names must be unique for correct spice simulation, but not enforced here.
self.name = measure_name
self.measure_scale = measure_scale
+ self.meta_str = None #Some measurements set this, set here to be clear on existence
@abstractmethod
def get_measure_function(self):
@@ -36,20 +37,20 @@ class spice_measurement(ABC):
class delay_measure(spice_measurement):
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
- def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, measure_scale=None):
+ def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd=0.5, targ_vdd=0.5, measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
- self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str)
+ self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
def get_measure_function(self):
return stimuli.gen_meas_delay
- def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str):
- """Set the values needed to generate a Spice measurement statement based on the name of the measurement."""
+ def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd):
+ """Set the constants for this measurement: signal names, directions, and trigger scales"""
self.trig_dir_str = trig_dir_str
self.targ_dir_str = targ_dir_str
- self.trig_val_of_vdd = 0.5
- self.targ_val_of_vdd = 0.5
+ self.trig_val_of_vdd = trig_vdd
+ self.targ_val_of_vdd = targ_vdd
self.trig_name_no_port = trig_name
self.targ_name_no_port = targ_name
@@ -99,7 +100,7 @@ class slew_measure(delay_measure):
#Time delays and ports are variant and needed as inputs when writing the measurement
class power_measure(spice_measurement):
- """Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
+ """Generates a spice measurement for the average power between two time points."""
def __init__(self, measure_name, power_type="", measure_scale=None):
spice_measurement.__init__(self, measure_name, measure_scale)
@@ -119,4 +120,39 @@ class power_measure(spice_measurement):
meas_name = "{}{}".format(self.name, port)
else:
meas_name = self.name
- return (meas_name,t_initial,t_final)
\ No newline at end of file
+ return (meas_name,t_initial,t_final)
+
+class voltage_when_measure(spice_measurement):
+ """Generates a spice measurement to measure the voltage of a signal based on the voltage of another."""
+
+ def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None):
+ spice_measurement.__init__(self, measure_name, measure_scale)
+ self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
+
+ def get_measure_function(self):
+ return stimuli.gen_meas_find_voltage
+
+ def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd):
+ """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
+ self.trig_dir_str = trig_dir_str
+ self.trig_val_of_vdd = trig_vdd
+
+ self.trig_name_no_port = trig_name
+ self.targ_name_no_port = targ_name
+
+ def get_measure_values(self, trig_td, vdd_voltage, port=None):
+ """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
+
+ if port != None:
+ #For dictionary indexing reasons, the name is formatted differently than the signals
+ meas_name = "{}{}".format(self.name, port)
+ trig_name = self.trig_name_no_port.format(port)
+ targ_name = self.targ_name_no_port.format(port)
+ else:
+ meas_name = self.name
+ trig_name = self.trig_name_no_port
+ targ_name = self.targ_name_no_port
+
+ trig_voltage = self.trig_val_of_vdd*vdd_voltage
+
+ return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td)
\ No newline at end of file
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
new file mode 100644
index 00000000..7a06ab7a
--- /dev/null
+++ b/compiler/characterizer/model_check.py
@@ -0,0 +1,174 @@
+import sys,re,shutil
+import debug
+import tech
+import math
+from .stimuli import *
+from .trim_spice import *
+from .charutils import *
+import utils
+from globals import OPTS
+from .delay import delay
+from .measurements import *
+
+class model_check(delay):
+ """Functions to test for the worst case delay in a target SRAM
+
+ The current worst case determines a feasible period for the SRAM then tests
+ several bits and record the delay and differences between the bits.
+
+ """
+
+ def __init__(self, sram, spfile, corner):
+ delay.__init__(self,sram,spfile,corner)
+ self.period = tech.spice["feasible_period"]
+
+ def create_measurement_names(self):
+ """Create measurement names. The names themselves currently define the type of measurement"""
+ #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
+ self.wl_delay_meas_names = ["delay_wl_en_bar", "delay_wl_en", "delay_dvr_en_bar", "delay_wl"]
+ self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in", "delay_delay_chain_stage_1", "delay_delay_chain_stage_2"]
+ self.sae_delay_meas_names = ["delay_pre_sen", "delay_sen_bar", "delay_sen"]
+
+ def create_signal_names(self):
+ delay.create_signal_names(self)
+ #Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
+ self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xbuf_wl_en.zb_int", "Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_15", "Xsram.Xbank0.wl_15"]
+ self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in", "Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1", "Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
+ self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en", "Xsram.Xcontrol0.Xbuf_s_en.zb_int", "Xsram.s_en0"]
+
+ def create_measurement_objects(self):
+ """Create the measurements used for read and write ports"""
+ self.create_wordline_measurement_objects()
+ self.create_sae_measurement_objects()
+ self.all_measures = self.wl_meas_objs+self.sae_meas_objs
+
+ def create_wordline_measurement_objects(self):
+ """Create the measurements to measure the wordline path from the gated_clk_bar signal"""
+ self.wl_meas_objs = []
+ trig_dir = "RISE"
+ targ_dir = "FALL"
+
+ for i in range(1, len(self.wl_signal_names)):
+ self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1], self.wl_signal_names[i-1], self.wl_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
+ temp_dir = trig_dir
+ trig_dir = targ_dir
+ targ_dir = temp_dir
+
+ def create_sae_measurement_objects(self):
+ """Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two."""
+
+ self.sae_meas_objs = []
+ trig_dir = "RISE"
+ targ_dir = "FALL"
+ #Add measurements from gated_clk_bar to RBL
+ for i in range(1, len(self.rbl_en_signal_names)):
+ self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1], self.rbl_en_signal_names[i-1], self.rbl_en_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
+ temp_dir = trig_dir
+ trig_dir = targ_dir
+ targ_dir = temp_dir
+
+ #Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
+ trig_dir = "FALL"
+ targ_dir = "RISE"
+ #Add measurements from gated_clk_bar to RBL
+ for i in range(1, len(self.sae_signal_names)):
+ self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], self.sae_signal_names[i-1], self.sae_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
+ temp_dir = trig_dir
+ trig_dir = targ_dir
+ targ_dir = temp_dir
+
+ def write_delay_measures(self):
+ """
+ Write the measure statements to quantify the delay and power results for all targeted ports.
+ """
+ self.sf.write("\n* Measure statements for delay and power\n")
+
+ # Output some comments to aid where cycles start and what is happening
+ for comment in self.cycle_comments:
+ self.sf.write("* {}\n".format(comment))
+
+ for read_port in self.targ_read_ports:
+ self.write_measures_read_port(read_port)
+
+ def get_delay_measure_variants(self, port, delay_obj):
+ """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
+ #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
+ #Assuming only read 0 for now
+ meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
+ return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
+
+ def write_measures_read_port(self, port):
+ """
+ Write the measure statements for all nodes along the wordline path.
+ """
+ # add measure statements for delays/slews
+ for measure in self.all_measures:
+ measure_variant_inp_tuple = self.get_delay_measure_variants(port, measure)
+ measure.write_measure(self.stim, measure_variant_inp_tuple)
+
+
+ def run_delay_simulation(self):
+ """
+ This tries to simulate a period and checks if the result works. If
+ so, it returns True and the delays, slews, and powers. It
+ works on the trimmed netlist by default, so powers do not
+ include leakage of all cells.
+ """
+ #Sanity Check
+ debug.check(self.period > 0, "Target simulation period non-positive")
+
+ wl_result = [[] for i in self.all_ports]
+ sae_result = [[] for i in self.all_ports]
+ # Checking from not data_value to data_value
+ self.write_delay_stimulus()
+
+ self.stim.run_sim() #running sim prodoces spice output file.
+
+ for port in self.targ_read_ports:
+ #Parse and check the voltage measurements
+ wl_meas_list = []
+ for measure in self.wl_meas_objs:
+ wl_meas_list.append(measure.retrieve_measure(port=port))
+ if type(wl_meas_list[-1]) != float:
+ debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, wl_meas_list[-1]),1) #Printing the entire dict looks bad.
+ wl_result[port] = wl_meas_list
+
+ sae_meas_list = []
+ for measure in self.sae_meas_objs:
+ sae_meas_list.append(measure.retrieve_measure(port=port))
+ if type(sae_meas_list[-1]) != float:
+ debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, sae_meas_list[-1]),1) #Printing the entire dict looks bad.
+ sae_result[port] = sae_meas_list
+
+ # The delay is from the negative edge for our SRAM
+ return (True,wl_result, sae_result)
+
+ def get_model_delays(self, port):
+ """Get model delays based on port. Currently assumes single RW port."""
+ return self.sram.control_logic_rw.get_wl_sen_delays()
+
+ def analyze(self, probe_address, probe_data, slews, loads):
+ """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
+ self.set_probe(probe_address, probe_data)
+ self.load=max(loads)
+ self.slew=max(slews)
+ self.create_measurement_objects()
+
+ read_port = self.read_ports[0] #only test the first read port
+ self.targ_read_ports = [read_port]
+ self.targ_write_ports = [self.write_ports[0]]
+ debug.info(1,"Bitline swing test: corner {}".format(self.corner))
+ (success, wl_delays, sae_delays)=self.run_delay_simulation()
+ debug.check(success, "Model measurements Failed: period={}".format(self.period))
+ wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
+
+ debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port]))
+ debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
+ debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port]))
+ debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
+
+ return wl_delays, sae_delays
+
+
+
+
\ No newline at end of file
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 8545480c..9a945383 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -37,6 +37,8 @@ class control_logic(design.design):
#self.sram=None #disable re-sizing for debugging, FIXME: resizing is not working, needs to be adjusted for new control logic.
self.wl_timing_tolerance = 1 #Determines how much larger the sen delay should be. Accounts for possible error in model.
self.parasitic_inv_delay = parameter["min_inv_para_delay"] #Keeping 0 for now until further testing.
+ self.wl_stage_efforts = None
+ self.sen_stage_efforts = None
if self.port_type == "rw":
self.num_control_signals = 2
@@ -157,7 +159,7 @@ class control_logic(design.design):
elif self.words_per_row == 2:
delay_stages = 6
else:
- delay_stages = 4
+ delay_stages = 2
return (delay_stages, delay_fanout)
@@ -808,8 +810,8 @@ class control_logic(design.design):
def get_delays_to_wl(self):
"""Get the delay (in delay units) of the clk to a wordline in the bitcell array"""
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
- stage_efforts = self.determine_wordline_stage_efforts()
- clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay)
+ self.wl_stage_efforts = self.determine_wordline_stage_efforts()
+ clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts, self.parasitic_inv_delay)
total_delay = clk_to_wl_rise + clk_to_wl_fall
debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay))
return clk_to_wl_rise,clk_to_wl_fall
@@ -838,8 +840,8 @@ class control_logic(design.design):
This does not incorporate the delay of the replica bitline.
"""
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
- stage_efforts = self.determine_sa_enable_stage_efforts()
- clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay)
+ self.sen_stage_efforts = self.determine_sa_enable_stage_efforts()
+ clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts, self.parasitic_inv_delay)
total_delay = clk_to_sen_rise + clk_to_sen_fall
debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay))
return clk_to_sen_rise, clk_to_sen_fall
@@ -870,4 +872,12 @@ class control_logic(design.design):
last_stage_rise = stage_effort_list[-1].is_rise
return stage_effort_list
-
\ No newline at end of file
+
+ def get_wl_sen_delays(self):
+ """Gets a list of the stages and delays in order of their path."""
+ if self.sen_stage_efforts == None or self.wl_stage_efforts == None:
+ debug.error("Model delays not calculated for SRAM.", 1)
+ wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts, self.parasitic_inv_delay)
+ sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts, self.parasitic_inv_delay)
+ return wl_delays, sen_delays
+
\ No newline at end of file
diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py
index c847210b..26735870 100755
--- a/compiler/tests/21_hspice_delay_test.py
+++ b/compiler/tests/21_hspice_delay_test.py
@@ -23,7 +23,7 @@ class timing_sram_test(openram_test):
from importlib import reload
import characterizer
reload(characterizer)
- from characterizer import delay, bitline_delay
+ from characterizer import delay
from sram import sram
from sram_config import sram_config
c = sram_config(word_size=1,
@@ -43,15 +43,14 @@ class timing_sram_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
d = delay(s.s, tempspice, corner)
- bl = bitline_delay(s.s, tempspice, corner)
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
- bitline_data = bl.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
data.update(port_data[0])
- data.update(bitline_data[0])
+
+ print(data)
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.2011],
@@ -60,13 +59,14 @@ class timing_sram_test(openram_test):
'min_period': 0.41,
'read0_power': [0.63604],
'read1_power': [0.6120599999999999],
- 'slew_hl': [0.10853],
- 'slew_lh': [0.10853],
+ 'slew_hl': [0.07078999999999999],
+ 'slew_lh': [0.07078999999999999],
'write0_power': [0.51742],
'write1_power': [0.51095],
- 'volt_bl': 0.045626,
- 'volt_br': 1.0709,
- 'delay_bl_vth': 0.1813}
+ 'volt_bl': [0.2017],
+ 'volt_br': [1.0765],
+ 'delay_bl': [0.18114999999999998],
+ 'delay_br': [0.17763]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.3911],
'delay_lh': [1.3911],
@@ -74,13 +74,14 @@ class timing_sram_test(openram_test):
'min_period': 2.812,
'read0_power': [22.1183],
'read1_power': [21.4388],
- 'slew_hl': [0.7397553],
- 'slew_lh': [0.7397553],
+ 'slew_hl': [0.6],
+ 'slew_lh': [0.6],
'write0_power': [19.4103],
'write1_power': [20.1167],
- 'volt_bl': 1.8329,
- 'volt_br': 5.081,
- 'delay_bl_vth': 1.1141}
+ 'volt_bl': [3.1763],
+ 'volt_br': [5.5731],
+ 'delay_bl': [1.1133000000000002],
+ 'delay_br': [0.9958395]}
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results
diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py
index 16c3fb3a..98f01e53 100755
--- a/compiler/tests/21_ngspice_delay_test.py
+++ b/compiler/tests/21_ngspice_delay_test.py
@@ -23,7 +23,7 @@ class timing_sram_test(openram_test):
from importlib import reload
import characterizer
reload(characterizer)
- from characterizer import delay, bitline_delay
+ from characterizer import delay
from sram import sram
from sram_config import sram_config
c = sram_config(word_size=1,
@@ -43,30 +43,28 @@ class timing_sram_test(openram_test):
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
d = delay(s.s, tempspice, corner)
- bl = bitline_delay(s.s, tempspice, corner)
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
- bitline_data = bl.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
data.update(port_data[0])
- data.update(bitline_data[0])
if OPTS.tech_name == "freepdk45":
- golden_data = {'delay_hl': [0.20443139999999999],
- 'delay_lh': [0.20443139999999999],
- 'leakage_power': 0.0017840640000000001,
- 'min_period': 0.41,
- 'read0_power': [0.6435831],
- 'read1_power': [0.6233463],
- 'slew_hl': [0.1138734],
- 'slew_lh': [0.1138734],
- 'write0_power': [0.5205761],
- 'write1_power': [0.5213689],
- 'volt_bl': 0.03667602,
- 'volt_br': 1.056013,
- 'delay_bl_vth': 0.184373}
+ golden_data = {'delay_bl': [0.1840938],
+ 'delay_br': [0.1804373],
+ 'delay_hl': [0.2130831],
+ 'delay_lh': [0.2130831],
+ 'leakage_power': 0.001595639,
+ 'min_period': 0.527,
+ 'read0_power': [0.4852456],
+ 'read1_power': [0.46341889999999997],
+ 'slew_hl': [0.07351041999999999],
+ 'slew_lh': [0.07351041999999999],
+ 'volt_bl': [0.1954744],
+ 'volt_br': [1.058266],
+ 'write0_power': [0.4065201],
+ 'write1_power': [0.46341889999999997]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.610911],
'delay_lh': [1.610911],
@@ -78,9 +76,10 @@ class timing_sram_test(openram_test):
'slew_lh': [0.7986348999999999],
'write0_power': [17.58272],
'write1_power': [18.523419999999998],
- 'volt_bl': 1.639692,
- 'volt_br': 5.06107,
- 'delay_bl_vth': 1.322235}
+ 'volt_bl': [3.1763],
+ 'volt_br': [5.5731],
+ 'delay_bl': [1.1133000000000002],
+ 'delay_br': [0.9958395]}
else:
self.assertTrue(False) # other techs fail
diff --git a/compiler/tests/28_delay_model_test.py b/compiler/tests/28_delay_model_test.py
new file mode 100755
index 00000000..74ad22f3
--- /dev/null
+++ b/compiler/tests/28_delay_model_test.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+"""
+Run a regression test on various srams
+"""
+
+import unittest
+from testutils import header,openram_test
+import sys,os
+sys.path.append(os.path.join(sys.path[0],".."))
+import globals
+from globals import OPTS
+import debug
+
+class delay_model_test(openram_test):
+
+ def runTest(self):
+ globals.init_openram("config_20_{0}".format(OPTS.tech_name))
+ OPTS.spice_name="hspice"
+ OPTS.analytical_delay = False
+ OPTS.netlist_only = True
+
+ # This is a hack to reload the characterizer __init__ with the spice version
+ from importlib import reload
+ import characterizer
+ reload(characterizer)
+ from characterizer import model_check
+ from sram import sram
+ from sram_config import sram_config
+ c = sram_config(word_size=1,
+ num_words=16,
+ num_banks=1)
+ c.words_per_row=1
+ c.recompute_sizes()
+ debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
+ s = sram(c, name="sram1")
+
+ tempspice = OPTS.openram_temp + "temp.sp"
+ s.sp_write(tempspice)
+
+ probe_address = "1" * s.s.addr_size
+ probe_data = s.s.word_size - 1
+ debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data))
+
+ corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
+ mc = model_check(s.s, tempspice, corner)
+ import tech
+ loads = [tech.spice["msflop_in_cap"]*4]
+ slews = [tech.spice["rise_time"]*2]
+ wl_data, sae_data = mc.analyze(probe_address, probe_data, slews, loads)
+ #Combine info about port into all data
+
+ #debug.info(1,"Data:\n{}".format(wl_data))
+
+ globals.end_openram()
+
+# run the test from the command line
+if __name__ == "__main__":
+ (OPTS, args) = globals.parse_args()
+ del sys.argv[1:]
+ header(__file__, OPTS.tech_name)
+ unittest.main()
From 21663439ccc17456f566b7c8b5702241e22bd646 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 9 Jan 2019 22:42:34 -0800
Subject: [PATCH 09/77] Added slews measurements to the model checker. Removed
unused code in bitline delay class.
---
compiler/characterizer/bitline_delay.py | 21 +-------
compiler/characterizer/model_check.py | 69 ++++++++++++++++---------
2 files changed, 45 insertions(+), 45 deletions(-)
diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py
index 2af290f1..c85f06bc 100644
--- a/compiler/characterizer/bitline_delay.py
+++ b/compiler/characterizer/bitline_delay.py
@@ -26,26 +26,7 @@ class bitline_delay(delay):
delay.create_signal_names(self)
self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"]
self.sen_name = "Xsram.s_en"
-
- def create_measurement_objects(self):
- """Create the measurements used for read and write ports"""
- self.meas_objs = []
- self.create_bitline_find_measurement_objects()
- self.create_bitline_delay_measurement_objects()
-
- def create_bitline_delay_measurement_objects(self):
- self.find_meas_objs = []
- trig_delay_name = "clk{0}"
- targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
- self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9))
- self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", 1e9))
-
- self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9))
- self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", 1e9))
-
- self.read_meas_objs.append(power_measure("read1_power", "RISE", 1e3))
- self.read_meas_objs.append(power_measure("read0_power", "FALL", 1e3))
-
+
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 7a06ab7a..a12117d3 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -26,8 +26,12 @@ class model_check(delay):
"""Create measurement names. The names themselves currently define the type of measurement"""
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
self.wl_delay_meas_names = ["delay_wl_en_bar", "delay_wl_en", "delay_dvr_en_bar", "delay_wl"]
+ self.wl_slew_meas_names = ["slew_wl_gated_clk_bar","slew_wl_en_bar", "slew_wl_en", "slew_drv_en_bar", "slew_wl"]
+
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in", "delay_delay_chain_stage_1", "delay_delay_chain_stage_2"]
+ self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in", "slew_delay_chain_stage_1", "slew_delay_chain_stage_2"]
self.sae_delay_meas_names = ["delay_pre_sen", "delay_sen_bar", "delay_sen"]
+ self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen", "slew_sen_bar", "slew_sen"]
def create_signal_names(self):
delay.create_signal_names(self)
@@ -50,10 +54,12 @@ class model_check(delay):
for i in range(1, len(self.wl_signal_names)):
self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1], self.wl_signal_names[i-1], self.wl_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
+ self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1], self.wl_signal_names[i-1], trig_dir, measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
-
+ self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[-1], self.wl_signal_names[-1], trig_dir, measure_scale=1e9))
+
def create_sae_measurement_objects(self):
"""Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two."""
@@ -63,20 +69,24 @@ class model_check(delay):
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.rbl_en_signal_names)):
self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1], self.rbl_en_signal_names[i-1], self.rbl_en_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
+ self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1], self.rbl_en_signal_names[i-1], trig_dir, measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
-
+ self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1], self.rbl_en_signal_names[-1], trig_dir, measure_scale=1e9))
+
#Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
trig_dir = "FALL"
targ_dir = "RISE"
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.sae_signal_names)):
self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], self.sae_signal_names[i-1], self.sae_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
+ self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1], self.sae_signal_names[i-1], trig_dir, measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
-
+ self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1], self.sae_signal_names[-1], trig_dir, measure_scale=1e9))
+
def write_delay_measures(self):
"""
Write the measure statements to quantify the delay and power results for all targeted ports.
@@ -90,10 +100,12 @@ class model_check(delay):
for read_port in self.targ_read_ports:
self.write_measures_read_port(read_port)
- def get_delay_measure_variants(self, port, delay_obj):
+ def get_delay_measure_variants(self, port, measure_obj):
"""Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#Assuming only read 0 for now
+ if not (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure):
+ debug.error("Measurement not recognized by the model checker.",1)
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
@@ -106,7 +118,22 @@ class model_check(delay):
measure_variant_inp_tuple = self.get_delay_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_inp_tuple)
-
+ def get_measurement_values(self, meas_objs, port):
+ """Gets the delays and slews from a specified port from the spice output file and returns them as lists."""
+ delay_meas_list = []
+ slew_meas_list = []
+ for measure in meas_objs:
+ measure_value = measure.retrieve_measure(port=port)
+ if type(measure_value) != float:
+ debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, measure_value),1)
+ if type(measure) is delay_measure:
+ delay_meas_list.append(measure_value)
+ elif type(measure)is slew_measure:
+ slew_meas_list.append(measure_value)
+ else:
+ debug.error("Measurement object not recognized.",1)
+ return delay_meas_list, slew_meas_list
+
def run_delay_simulation(self):
"""
This tries to simulate a period and checks if the result works. If
@@ -117,31 +144,21 @@ class model_check(delay):
#Sanity Check
debug.check(self.period > 0, "Target simulation period non-positive")
- wl_result = [[] for i in self.all_ports]
- sae_result = [[] for i in self.all_ports]
+ wl_delay_result = [[] for i in self.all_ports]
+ wl_slew_result = [[] for i in self.all_ports]
+ sae_delay_result = [[] for i in self.all_ports]
+ sae_slew_result = [[] for i in self.all_ports]
# Checking from not data_value to data_value
self.write_delay_stimulus()
self.stim.run_sim() #running sim prodoces spice output file.
-
+
+ #Retrieve the results from the output file
for port in self.targ_read_ports:
#Parse and check the voltage measurements
- wl_meas_list = []
- for measure in self.wl_meas_objs:
- wl_meas_list.append(measure.retrieve_measure(port=port))
- if type(wl_meas_list[-1]) != float:
- debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, wl_meas_list[-1]),1) #Printing the entire dict looks bad.
- wl_result[port] = wl_meas_list
-
- sae_meas_list = []
- for measure in self.sae_meas_objs:
- sae_meas_list.append(measure.retrieve_measure(port=port))
- if type(sae_meas_list[-1]) != float:
- debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, sae_meas_list[-1]),1) #Printing the entire dict looks bad.
- sae_result[port] = sae_meas_list
-
- # The delay is from the negative edge for our SRAM
- return (True,wl_result, sae_result)
+ wl_delay_result[port], wl_slew_result[port] = self.get_measurement_values(self.wl_meas_objs, port)
+ sae_delay_result[port], sae_slew_result[port] = self.get_measurement_values(self.sae_meas_objs, port)
+ return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result)
def get_model_delays(self, port):
"""Get model delays based on port. Currently assumes single RW port."""
@@ -158,14 +175,16 @@ class model_check(delay):
self.targ_read_ports = [read_port]
self.targ_write_ports = [self.write_ports[0]]
debug.info(1,"Bitline swing test: corner {}".format(self.corner))
- (success, wl_delays, sae_delays)=self.run_delay_simulation()
+ (success, wl_delays, sae_delays, wl_slews, sae_slews)=self.run_delay_simulation()
debug.check(success, "Model measurements Failed: period={}".format(self.period))
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port]))
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
+ debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port]))
debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port]))
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
+ debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
return wl_delays, sae_delays
From cc0be510c75cd534e1312caf785f7297af43effc Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 16 Jan 2019 00:46:24 -0800
Subject: [PATCH 10/77] Added some data scaling and error calculation in model
check.
---
compiler/characterizer/model_check.py | 37 ++++++++++++++++++++++++++
compiler/tests/21_hspice_delay_test.py | 4 +--
compiler/tests/28_delay_model_test.py | 2 ++
compiler/tests/testutils.py | 1 +
4 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index a12117d3..94b200bc 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -164,6 +164,36 @@ class model_check(delay):
"""Get model delays based on port. Currently assumes single RW port."""
return self.sram.control_logic_rw.get_wl_sen_delays()
+ def scale_delays(self, delay_list):
+ """Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
+ converted_values = []
+ #Calculate average
+ total = 0
+ for meas_value in delay_list:
+ total+=meas_value
+ average = total/len(delay_list)
+
+ #Convert values
+ for meas_value in delay_list:
+ converted_values.append(meas_value/average)
+ return converted_values
+
+ def min_max_normalization(self, value_list):
+ """Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1"""
+ scaled_values = []
+ min_val = min(value_list)
+ min_max_diff = max(value_list) - min_val
+ for value in value_list:
+ scaled_values.append((value-min_val)/(min_max_diff))
+ return scaled_values
+
+ def calculate_error_l2_norm(self, list_a, list_b):
+ """Calculates error between two lists using the l2 norm"""
+ error_list = []
+ for val_a, val_b in zip(list_a, list_b):
+ error_list.append((val_a-val_b)**2)
+ return error_list
+
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
self.set_probe(probe_address, probe_data)
@@ -186,6 +216,13 @@ class model_check(delay):
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
+ scaled_wl_meas = self.min_max_normalization(wl_delays[read_port])
+ debug.info(1, "Scaled wordline delays:\n{}".format(scaled_wl_meas))
+ scaled_wl_model = self.min_max_normalization(wl_model_delays)
+ debug.info(1, "Scaled wordline model:\n{}".format(scaled_wl_model))
+ errors = self.calculate_error_l2_norm(scaled_wl_meas, scaled_wl_model)
+ debug.info(1, "Model errors:\n{}".format(errors))
+
return wl_delays, sae_delays
diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py
index 26735870..8be7152c 100755
--- a/compiler/tests/21_hspice_delay_test.py
+++ b/compiler/tests/21_hspice_delay_test.py
@@ -49,9 +49,7 @@ class timing_sram_test(openram_test):
data, port_data = d.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
data.update(port_data[0])
-
- print(data)
-
+
if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.2011],
'delay_lh': [0.2011],
diff --git a/compiler/tests/28_delay_model_test.py b/compiler/tests/28_delay_model_test.py
index 74ad22f3..8f9154e0 100755
--- a/compiler/tests/28_delay_model_test.py
+++ b/compiler/tests/28_delay_model_test.py
@@ -18,6 +18,8 @@ class delay_model_test(openram_test):
OPTS.spice_name="hspice"
OPTS.analytical_delay = False
OPTS.netlist_only = True
+ OPTS.trim_netlist = False
+ debug.info(1, "Trimming disabled for this test. Simulation could be slow.")
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py
index 6b2a7dcf..74d97960 100755
--- a/compiler/tests/testutils.py
+++ b/compiler/tests/testutils.py
@@ -62,6 +62,7 @@ class openram_test(unittest.TestCase):
delay_obj.set_load_slew(load, slew)
delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1))
test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period.
+ delay_obj.create_measurement_objects()
delay_obj.find_feasible_period_one_port(test_port)
return delay_obj.period
From 5bbc43d0a0cb9d4911388dd81d8088eace5b90b0 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 17 Jan 2019 01:59:41 -0800
Subject: [PATCH 11/77] Added data collection of wordline and s_en
measurements.
---
compiler/characterizer/model_check.py | 49 +++++++++++++++++++++------
compiler/tests/28_delay_model_test.py | 5 +--
2 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 94b200bc..03d7982a 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -21,6 +21,11 @@ class model_check(delay):
def __init__(self, sram, spfile, corner):
delay.__init__(self,sram,spfile,corner)
self.period = tech.spice["feasible_period"]
+ self.create_data_names()
+
+ def create_data_names(self):
+ self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model"
+ self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model"
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
@@ -40,6 +45,16 @@ class model_check(delay):
self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in", "Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1", "Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en", "Xsram.Xcontrol0.Xbuf_s_en.zb_int", "Xsram.s_en0"]
+ def get_all_signal_names(self):
+ """Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV."""
+ name_dict = {}
+ #Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
+ name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
+ name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
+ name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:]
+ name_dict[self.sae_model_name] = name_dict["sae_measures"]
+ return name_dict
+
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
self.create_wordline_measurement_objects()
@@ -181,10 +196,10 @@ class model_check(delay):
def min_max_normalization(self, value_list):
"""Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1"""
scaled_values = []
- min_val = min(value_list)
- min_max_diff = max(value_list) - min_val
+ min_max_diff = max(value_list) - min(value_list)
+ average = sum(value_list)/len(value_list)
for value in value_list:
- scaled_values.append((value-min_val)/(min_max_diff))
+ scaled_values.append((value-average)/(min_max_diff))
return scaled_values
def calculate_error_l2_norm(self, list_a, list_b):
@@ -193,6 +208,15 @@ class model_check(delay):
for val_a, val_b in zip(list_a, list_b):
error_list.append((val_a-val_b)**2)
return error_list
+
+ def compare_measured_and_model(self, measured_vals, model_vals):
+ """First scales both inputs into similar ranges and then compares the error between both."""
+ scaled_meas = self.min_max_normalization(measured_vals)
+ debug.info(1, "Scaled measurements:\n{}".format(scaled_meas))
+ scaled_model = self.min_max_normalization(model_vals)
+ debug.info(1, "Scaled model:\n{}".format(scaled_model))
+ errors = self.calculate_error_l2_norm(scaled_meas, scaled_model)
+ debug.info(1, "Errors:\n{}\n".format(errors))
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
@@ -200,6 +224,7 @@ class model_check(delay):
self.load=max(loads)
self.slew=max(slews)
self.create_measurement_objects()
+ data_dict = {}
read_port = self.read_ports[0] #only test the first read port
self.targ_read_ports = [read_port]
@@ -216,14 +241,18 @@ class model_check(delay):
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
- scaled_wl_meas = self.min_max_normalization(wl_delays[read_port])
- debug.info(1, "Scaled wordline delays:\n{}".format(scaled_wl_meas))
- scaled_wl_model = self.min_max_normalization(wl_model_delays)
- debug.info(1, "Scaled wordline model:\n{}".format(scaled_wl_model))
- errors = self.calculate_error_l2_norm(scaled_wl_meas, scaled_wl_model)
- debug.info(1, "Model errors:\n{}".format(errors))
+ data_dict[self.wl_meas_name] = wl_delays[read_port]
+ data_dict[self.wl_model_name] = wl_model_delays
+ data_dict[self.sae_meas_name] = sae_delays[read_port]
+ data_dict[self.sae_model_name] = sae_model_delays
- return wl_delays, sae_delays
+ #Some evaluations of the model and measured values
+ debug.info(1, "Comparing wordline measurements and model.")
+ self.compare_measured_and_model(wl_delays[read_port], wl_model_delays)
+ debug.info(1, "Comparing SAE measurements and model")
+ self.compare_measured_and_model(sae_delays[read_port], sae_model_delays)
+
+ return data_dict
diff --git a/compiler/tests/28_delay_model_test.py b/compiler/tests/28_delay_model_test.py
index 8f9154e0..a02a42e9 100755
--- a/compiler/tests/28_delay_model_test.py
+++ b/compiler/tests/28_delay_model_test.py
@@ -25,10 +25,11 @@ class delay_model_test(openram_test):
from importlib import reload
import characterizer
reload(characterizer)
+
from characterizer import model_check
from sram import sram
from sram_config import sram_config
- c = sram_config(word_size=1,
+ c = sram_config(word_size=4,
num_words=16,
num_banks=1)
c.words_per_row=1
@@ -48,7 +49,7 @@ class delay_model_test(openram_test):
import tech
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
- wl_data, sae_data = mc.analyze(probe_address, probe_data, slews, loads)
+ sram_data = mc.analyze(probe_address, probe_data, slews, loads)
#Combine info about port into all data
#debug.info(1,"Data:\n{}".format(wl_data))
From 4ced6be6bdf63985d4d9434a856700b4aafd1395 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 17 Jan 2019 09:54:34 -0800
Subject: [PATCH 12/77] Added data collection and some initial data
---
compiler/tests/delay_data_collection.py | 137 ++++++++++++++++++
.../tests/model_data/data_sae_measures.csv | 3 +
compiler/tests/model_data/data_sae_model.csv | 3 +
.../tests/model_data/data_wl_measures.csv | 3 +
compiler/tests/model_data/data_wl_model.csv | 3 +
5 files changed, 149 insertions(+)
create mode 100644 compiler/tests/delay_data_collection.py
create mode 100644 compiler/tests/model_data/data_sae_measures.csv
create mode 100644 compiler/tests/model_data/data_sae_model.csv
create mode 100644 compiler/tests/model_data/data_wl_measures.csv
create mode 100644 compiler/tests/model_data/data_wl_model.csv
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
new file mode 100644
index 00000000..b5af25fc
--- /dev/null
+++ b/compiler/tests/delay_data_collection.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+"""
+Run a regression test on various srams
+"""
+
+import unittest
+from testutils import header,openram_test
+import sys,os
+sys.path.append(os.path.join(sys.path[0],".."))
+import globals
+from globals import OPTS
+import debug
+import csv
+from sram import sram
+from sram_config import sram_config
+
+MODEL_DIR = "model_data/"
+
+class data_collection(openram_test):
+
+ def runTest(self):
+ self.init_data_gen()
+
+ #Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
+ sram_data = self.get_sram_data(1,16,1)
+ self.initialize_csv_file(sram_data)
+
+ self.add_sram_data_to_csv(sram_data, 1, 16, 1)
+
+ #Run openRAM for several size configurations
+ #word_size_list, num_word_list, words_per_row_list = self.get_sram_configs()
+ word_size_list, num_word_list, words_per_row_list = [4], [16], [1] #for quick testing.
+ for word_size in word_size_list:
+ for num_word in num_word_list:
+ for words_per_row in words_per_row_list:
+ #Unfortunately, init needs to be called everytime
+ self.init_data_gen()
+ sram_data = self.get_sram_data(word_size, num_word, words_per_row)
+ self.add_sram_data_to_csv(sram_data, word_size, num_word, words_per_row)
+
+ self.close_files()
+ debug.info(1,"Data Generated")
+ globals.end_openram()
+
+ def init_data_gen(self):
+ """Initialization for the data test to run"""
+ globals.init_openram("config_20_{0}".format(OPTS.tech_name))
+ OPTS.spice_name="hspice" #Much faster than ngspice.
+ OPTS.trim_netlist = False
+ OPTS.netlist_only = True
+ OPTS.analytical_delay = False
+ # This is a hack to reload the characterizer __init__ with the spice version
+ from importlib import reload
+ import characterizer
+ reload(characterizer)
+
+ def close_files(self):
+ """Closes all files stored in the file dict"""
+ for key,file in self.csv_files.items():
+ file.close()
+
+ def get_sram_configs(self):
+ """Generate lists of wordsizes, number of words, and column mux size (words per row) to be tested."""
+ min_word_size = 1
+ max_word_size = 16
+ min_num_words_log2 = 4
+ max_num_words_log2 = 8
+ word_sizes = [i for i in range(min_word_size,max_word_size+1)]
+ num_words = [2**i for i in range(min_num_words_log2,max_num_words_log2+1)]
+ words_per_row = [1]
+ return word_sizes, num_words, words_per_row
+
+ def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row):
+ """Writes data to its respective CSV file. There is a CSV for each measurement target (wordline, sense amp enable, and models)"""
+ sram_specs = [word_size,num_words,words_per_row]
+ for data_name, data_values in sram_data.items():
+ self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name])
+ debug.info(2,"Data Added to CSV file.")
+
+ def initialize_csv_file(self, sram_data):
+ """Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)"""
+ #CSV File writing
+ header_dict = self.delay_obj.get_all_signal_names()
+ self.csv_files = {}
+ self.csv_writers = {}
+ for data_name, header_list in header_dict.items():
+ self.csv_files[data_name] = open('{}data_{}.csv'.format(MODEL_DIR,data_name), 'w')
+ fields = ('word_size', 'num_words', 'words_per_row', *header_list)
+ self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
+ self.csv_writers[data_name].writerow(fields)
+
+ def get_sram_data(self, word_size, num_words, words_per_row):
+ """Generates the SRAM based on input configuration and returns the data."""
+ from characterizer import model_check
+ c = sram_config(word_size=word_size,
+ num_words=num_words,
+ num_banks=1)
+ #minimum 16 rows. Most sizes below 16*16 will try to automatically use less rows unless enforced.
+ #if word_size*num_words < 256:
+ c.words_per_row=words_per_row #Force no column mux until incorporated into analytical delay.
+
+ debug.info(1, "Getting data for {} bit, {} words SRAM with 1 bank".format(word_size, num_words))
+ s = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
+
+ tempspice = OPTS.openram_temp + "temp.sp"
+ s.sp_write(tempspice)
+
+ corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
+ self.delay_obj = model_check(s.s, tempspice, corner)
+
+ import tech
+ #Only 1 at a time
+ probe_address = "1" * s.s.addr_size
+ probe_data = s.s.word_size - 1
+ loads = [tech.spice["msflop_in_cap"]*4]
+ slews = [tech.spice["rise_time"]*2]
+
+ sram_data = self.delay_obj.analyze(probe_address,probe_data,slews,loads)
+ return sram_data
+
+ def remove_lists_from_dict(self, dict):
+ """Check all the values in the dict and replaces the list items with its first value."""
+ #This is useful because the tests performed here only generate 1 value but a list
+ #with 1 item makes writing it to a csv later harder.
+ for key in dict.keys():
+ if type(dict[key]) is list:
+ if len(dict[key]) > 0:
+ dict[key] = dict[key][0]
+ else:
+ del dict[key]
+
+# instantiate a copdsay of the class to actually run the test
+if __name__ == "__main__":
+ (OPTS, args) = globals.parse_args()
+ del sys.argv[1:]
+ header(__file__, OPTS.tech_name)
+ unittest.main()
diff --git a/compiler/tests/model_data/data_sae_measures.csv b/compiler/tests/model_data/data_sae_measures.csv
new file mode 100644
index 00000000..29b9ee07
--- /dev/null
+++ b/compiler/tests/model_data/data_sae_measures.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
+1,16,1,0.1073407,0.010667,0.1317698,0.0910598,0.04802989999999999,0.1304522,0.0164942
+4,16,1,0.10635089999999998,0.010598499999999999,0.1315219,0.0911604,0.048601,0.12349109999999999,0.05874360000000001
diff --git a/compiler/tests/model_data/data_sae_model.csv b/compiler/tests/model_data/data_sae_model.csv
new file mode 100644
index 00000000..25a56d9e
--- /dev/null
+++ b/compiler/tests/model_data/data_sae_model.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
+1,16,1,5.0,0.75,4.5,5.5,2.5,4.5,1.09375
+4,16,1,5.0,0.75,4.5,5.5,2.5,4.5,1.09375
diff --git a/compiler/tests/model_data/data_wl_measures.csv b/compiler/tests/model_data/data_wl_measures.csv
new file mode 100644
index 00000000..95aa4044
--- /dev/null
+++ b/compiler/tests/model_data/data_wl_measures.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+1,16,1,0.1257435,0.0364958,0.051171100000000004,0.020759299999999998
+4,16,1,0.124175,0.03654059999999999,0.0507837,0.0433494
diff --git a/compiler/tests/model_data/data_wl_model.csv b/compiler/tests/model_data/data_wl_model.csv
new file mode 100644
index 00000000..ae601f49
--- /dev/null
+++ b/compiler/tests/model_data/data_wl_model.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+1,16,1,1.5,21.833333333333332,2.0,1.1666666666666665
+4,16,1,2.5,11.166666666666666,2.0,3.1666666666666665
From 5885e3b635302d10e0d2f1bb527014cb99f44ec8 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Fri, 18 Jan 2019 00:23:50 -0800
Subject: [PATCH 13/77] Removed carriage returns, adjusted signal names
generation for variable delay chain size.
---
compiler/characterizer/model_check.py | 14 +-
compiler/tests/delay_data_collection.py | 274 ++++++++++++------------
2 files changed, 147 insertions(+), 141 deletions(-)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 03d7982a..059227e1 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -33,8 +33,10 @@ class model_check(delay):
self.wl_delay_meas_names = ["delay_wl_en_bar", "delay_wl_en", "delay_dvr_en_bar", "delay_wl"]
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar","slew_wl_en_bar", "slew_wl_en", "slew_drv_en_bar", "slew_wl"]
- self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in", "delay_delay_chain_stage_1", "delay_delay_chain_stage_2"]
- self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in", "slew_delay_chain_stage_1", "slew_delay_chain_stage_2"]
+ dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
+ self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
+ dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
+ self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
self.sae_delay_meas_names = ["delay_pre_sen", "delay_sen_bar", "delay_sen"]
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen", "slew_sen_bar", "slew_sen"]
@@ -42,7 +44,8 @@ class model_check(delay):
delay.create_signal_names(self)
#Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xbuf_wl_en.zb_int", "Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_15", "Xsram.Xbank0.wl_15"]
- self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in", "Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1", "Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
+ delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] + ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
+ self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"] + delay_chain_signal_names
self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en", "Xsram.Xcontrol0.Xbuf_s_en.zb_int", "Xsram.s_en0"]
def get_all_signal_names(self):
@@ -178,7 +181,10 @@ class model_check(delay):
def get_model_delays(self, port):
"""Get model delays based on port. Currently assumes single RW port."""
return self.sram.control_logic_rw.get_wl_sen_delays()
-
+
+ def get_num_delay_stages(self):
+ return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list)
+
def scale_delays(self, delay_list):
"""Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
converted_values = []
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index b5af25fc..ac10b98c 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -1,137 +1,137 @@
-#!/usr/bin/env python3
-"""
-Run a regression test on various srams
-"""
-
-import unittest
-from testutils import header,openram_test
-import sys,os
-sys.path.append(os.path.join(sys.path[0],".."))
-import globals
-from globals import OPTS
-import debug
-import csv
-from sram import sram
-from sram_config import sram_config
-
-MODEL_DIR = "model_data/"
-
-class data_collection(openram_test):
-
- def runTest(self):
- self.init_data_gen()
-
- #Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
- sram_data = self.get_sram_data(1,16,1)
- self.initialize_csv_file(sram_data)
-
- self.add_sram_data_to_csv(sram_data, 1, 16, 1)
-
- #Run openRAM for several size configurations
- #word_size_list, num_word_list, words_per_row_list = self.get_sram_configs()
- word_size_list, num_word_list, words_per_row_list = [4], [16], [1] #for quick testing.
- for word_size in word_size_list:
- for num_word in num_word_list:
- for words_per_row in words_per_row_list:
- #Unfortunately, init needs to be called everytime
- self.init_data_gen()
- sram_data = self.get_sram_data(word_size, num_word, words_per_row)
- self.add_sram_data_to_csv(sram_data, word_size, num_word, words_per_row)
-
- self.close_files()
- debug.info(1,"Data Generated")
- globals.end_openram()
-
- def init_data_gen(self):
- """Initialization for the data test to run"""
- globals.init_openram("config_20_{0}".format(OPTS.tech_name))
- OPTS.spice_name="hspice" #Much faster than ngspice.
- OPTS.trim_netlist = False
- OPTS.netlist_only = True
- OPTS.analytical_delay = False
- # This is a hack to reload the characterizer __init__ with the spice version
- from importlib import reload
- import characterizer
- reload(characterizer)
-
- def close_files(self):
- """Closes all files stored in the file dict"""
- for key,file in self.csv_files.items():
- file.close()
-
- def get_sram_configs(self):
- """Generate lists of wordsizes, number of words, and column mux size (words per row) to be tested."""
- min_word_size = 1
- max_word_size = 16
- min_num_words_log2 = 4
- max_num_words_log2 = 8
- word_sizes = [i for i in range(min_word_size,max_word_size+1)]
- num_words = [2**i for i in range(min_num_words_log2,max_num_words_log2+1)]
- words_per_row = [1]
- return word_sizes, num_words, words_per_row
-
- def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row):
- """Writes data to its respective CSV file. There is a CSV for each measurement target (wordline, sense amp enable, and models)"""
- sram_specs = [word_size,num_words,words_per_row]
- for data_name, data_values in sram_data.items():
- self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name])
- debug.info(2,"Data Added to CSV file.")
-
- def initialize_csv_file(self, sram_data):
- """Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)"""
- #CSV File writing
- header_dict = self.delay_obj.get_all_signal_names()
- self.csv_files = {}
- self.csv_writers = {}
- for data_name, header_list in header_dict.items():
- self.csv_files[data_name] = open('{}data_{}.csv'.format(MODEL_DIR,data_name), 'w')
- fields = ('word_size', 'num_words', 'words_per_row', *header_list)
- self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
- self.csv_writers[data_name].writerow(fields)
-
- def get_sram_data(self, word_size, num_words, words_per_row):
- """Generates the SRAM based on input configuration and returns the data."""
- from characterizer import model_check
- c = sram_config(word_size=word_size,
- num_words=num_words,
- num_banks=1)
- #minimum 16 rows. Most sizes below 16*16 will try to automatically use less rows unless enforced.
- #if word_size*num_words < 256:
- c.words_per_row=words_per_row #Force no column mux until incorporated into analytical delay.
-
- debug.info(1, "Getting data for {} bit, {} words SRAM with 1 bank".format(word_size, num_words))
- s = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
-
- tempspice = OPTS.openram_temp + "temp.sp"
- s.sp_write(tempspice)
-
- corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
- self.delay_obj = model_check(s.s, tempspice, corner)
-
- import tech
- #Only 1 at a time
- probe_address = "1" * s.s.addr_size
- probe_data = s.s.word_size - 1
- loads = [tech.spice["msflop_in_cap"]*4]
- slews = [tech.spice["rise_time"]*2]
-
- sram_data = self.delay_obj.analyze(probe_address,probe_data,slews,loads)
- return sram_data
-
- def remove_lists_from_dict(self, dict):
- """Check all the values in the dict and replaces the list items with its first value."""
- #This is useful because the tests performed here only generate 1 value but a list
- #with 1 item makes writing it to a csv later harder.
- for key in dict.keys():
- if type(dict[key]) is list:
- if len(dict[key]) > 0:
- dict[key] = dict[key][0]
- else:
- del dict[key]
-
-# instantiate a copdsay of the class to actually run the test
-if __name__ == "__main__":
- (OPTS, args) = globals.parse_args()
- del sys.argv[1:]
- header(__file__, OPTS.tech_name)
- unittest.main()
+#!/usr/bin/env python3
+"""
+Run a regression test on various srams
+"""
+
+import unittest
+from testutils import header,openram_test
+import sys,os
+sys.path.append(os.path.join(sys.path[0],".."))
+import globals
+from globals import OPTS
+import debug
+import csv
+from sram import sram
+from sram_config import sram_config
+
+MODEL_DIR = "model_data/"
+
+class data_collection(openram_test):
+
+ def runTest(self):
+ self.init_data_gen()
+
+ #Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
+ sram_data = self.get_sram_data(1,16,1)
+ self.initialize_csv_file(sram_data)
+
+ self.add_sram_data_to_csv(sram_data, 1, 16, 1)
+
+ #Run openRAM for several size configurations
+ #word_size_list, num_word_list, words_per_row_list = self.get_sram_configs()
+ word_size_list, num_word_list, words_per_row_list = [4], [16], [1] #for quick testing.
+ for word_size in word_size_list:
+ for num_word in num_word_list:
+ for words_per_row in words_per_row_list:
+ #Unfortunately, init needs to be called everytime
+ self.init_data_gen()
+ sram_data = self.get_sram_data(word_size, num_word, words_per_row)
+ self.add_sram_data_to_csv(sram_data, word_size, num_word, words_per_row)
+
+ self.close_files()
+ debug.info(1,"Data Generated")
+ globals.end_openram()
+
+ def init_data_gen(self):
+ """Initialization for the data test to run"""
+ globals.init_openram("config_20_{0}".format(OPTS.tech_name))
+ OPTS.spice_name="hspice" #Much faster than ngspice.
+ OPTS.trim_netlist = False
+ OPTS.netlist_only = True
+ OPTS.analytical_delay = False
+ # This is a hack to reload the characterizer __init__ with the spice version
+ from importlib import reload
+ import characterizer
+ reload(characterizer)
+
+ def close_files(self):
+ """Closes all files stored in the file dict"""
+ for key,file in self.csv_files.items():
+ file.close()
+
+ def get_sram_configs(self):
+ """Generate lists of wordsizes, number of words, and column mux size (words per row) to be tested."""
+ min_word_size = 1
+ max_word_size = 16
+ min_num_words_log2 = 4
+ max_num_words_log2 = 8
+ word_sizes = [i for i in range(min_word_size,max_word_size+1)]
+ num_words = [2**i for i in range(min_num_words_log2,max_num_words_log2+1)]
+ words_per_row = [1]
+ return word_sizes, num_words, words_per_row
+
+ def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row):
+ """Writes data to its respective CSV file. There is a CSV for each measurement target (wordline, sense amp enable, and models)"""
+ sram_specs = [word_size,num_words,words_per_row]
+ for data_name, data_values in sram_data.items():
+ self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name])
+ debug.info(2,"Data Added to CSV file.")
+
+ def initialize_csv_file(self, sram_data):
+ """Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)"""
+ #CSV File writing
+ header_dict = self.delay_obj.get_all_signal_names()
+ self.csv_files = {}
+ self.csv_writers = {}
+ for data_name, header_list in header_dict.items():
+ self.csv_files[data_name] = open('{}data_{}.csv'.format(MODEL_DIR,data_name), 'w')
+ fields = ('word_size', 'num_words', 'words_per_row', *header_list)
+ self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
+ self.csv_writers[data_name].writerow(fields)
+
+ def get_sram_data(self, word_size, num_words, words_per_row):
+ """Generates the SRAM based on input configuration and returns the data."""
+ from characterizer import model_check
+ c = sram_config(word_size=word_size,
+ num_words=num_words,
+ num_banks=1)
+ #minimum 16 rows. Most sizes below 16*16 will try to automatically use less rows unless enforced.
+ #if word_size*num_words < 256:
+ c.words_per_row=words_per_row #Force no column mux until incorporated into analytical delay.
+
+ debug.info(1, "Getting data for {} bit, {} words SRAM with 1 bank".format(word_size, num_words))
+ s = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
+
+ tempspice = OPTS.openram_temp + "temp.sp"
+ s.sp_write(tempspice)
+
+ corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
+ self.delay_obj = model_check(s.s, tempspice, corner)
+
+ import tech
+ #Only 1 at a time
+ probe_address = "1" * s.s.addr_size
+ probe_data = s.s.word_size - 1
+ loads = [tech.spice["msflop_in_cap"]*4]
+ slews = [tech.spice["rise_time"]*2]
+
+ sram_data = self.delay_obj.analyze(probe_address,probe_data,slews,loads)
+ return sram_data
+
+ def remove_lists_from_dict(self, dict):
+ """Check all the values in the dict and replaces the list items with its first value."""
+ #This is useful because the tests performed here only generate 1 value but a list
+ #with 1 item makes writing it to a csv later harder.
+ for key in dict.keys():
+ if type(dict[key]) is list:
+ if len(dict[key]) > 0:
+ dict[key] = dict[key][0]
+ else:
+ del dict[key]
+
+# instantiate a copdsay of the class to actually run the test
+if __name__ == "__main__":
+ (OPTS, args) = globals.parse_args()
+ del sys.argv[1:]
+ header(__file__, OPTS.tech_name)
+ unittest.main()
From 6d3884d60ddb6ac96c9ce57e11e87f538b138dda Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 22 Jan 2019 16:40:46 -0800
Subject: [PATCH 14/77] Added corner data collection.
---
compiler/characterizer/model_check.py | 2 +-
compiler/tests/config_data.py | 9 ++
compiler/tests/delay_data_collection.py | 92 +++++++++++--------
.../data_4b_16word_1way_sae_measures.csv | 3 +
.../data_4b_16word_1way_sae_model.csv | 3 +
.../data_4b_16word_1way_wl_measures.csv | 3 +
.../data_4b_16word_1way_wl_model.csv | 3 +
.../tests/model_data/data_sae_measures.csv | 3 -
compiler/tests/model_data/data_sae_model.csv | 3 -
.../tests/model_data/data_wl_measures.csv | 3 -
compiler/tests/model_data/data_wl_model.csv | 3 -
technology/freepdk45/tech/tech.py | 7 +-
technology/scn4m_subm/tech/tech.py | 7 +-
13 files changed, 90 insertions(+), 51 deletions(-)
create mode 100755 compiler/tests/config_data.py
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_sae_model.csv
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_wl_model.csv
delete mode 100644 compiler/tests/model_data/data_sae_measures.csv
delete mode 100644 compiler/tests/model_data/data_sae_model.csv
delete mode 100644 compiler/tests/model_data/data_wl_measures.csv
delete mode 100644 compiler/tests/model_data/data_wl_model.csv
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 059227e1..12174a4b 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -235,7 +235,7 @@ class model_check(delay):
read_port = self.read_ports[0] #only test the first read port
self.targ_read_ports = [read_port]
self.targ_write_ports = [self.write_ports[0]]
- debug.info(1,"Bitline swing test: corner {}".format(self.corner))
+ debug.info(1,"Model test: corner {}".format(self.corner))
(success, wl_delays, sae_delays, wl_slews, sae_slews)=self.run_delay_simulation()
debug.check(success, "Model measurements Failed: period={}".format(self.period))
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
new file mode 100755
index 00000000..31a28dfe
--- /dev/null
+++ b/compiler/tests/config_data.py
@@ -0,0 +1,9 @@
+word_size = 1
+num_words = 16
+
+tech_name = "freepdk45"
+process_corners = ["TT", "FF"]
+supply_voltages = [1.0]
+temperatures = [25]
+
+
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index ac10b98c..d3906d39 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -21,30 +21,33 @@ class data_collection(openram_test):
def runTest(self):
self.init_data_gen()
+ word_size, num_words, words_per_row = 4, 16, 1
+ self.evalulate_sram_on_corners(word_size, num_words, words_per_row)
+ globals.end_openram()
+
+ def evalulate_sram_on_corners(self, word_size, num_words, words_per_row):
+ """Performs corner analysis on a single SRAM configuration"""
+ self.create_sram(word_size, num_words, words_per_row)
#Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
- sram_data = self.get_sram_data(1,16,1)
- self.initialize_csv_file(sram_data)
-
- self.add_sram_data_to_csv(sram_data, 1, 16, 1)
-
- #Run openRAM for several size configurations
- #word_size_list, num_word_list, words_per_row_list = self.get_sram_configs()
- word_size_list, num_word_list, words_per_row_list = [4], [16], [1] #for quick testing.
- for word_size in word_size_list:
- for num_word in num_word_list:
- for words_per_row in words_per_row_list:
- #Unfortunately, init needs to be called everytime
- self.init_data_gen()
- sram_data = self.get_sram_data(word_size, num_word, words_per_row)
- self.add_sram_data_to_csv(sram_data, word_size, num_word, words_per_row)
+ corner_gen = self.corner_combination_generator()
+ init_corner = next(corner_gen)
+ sram_data = self.get_sram_data(init_corner)
+ self.initialize_csv_file(sram_data, word_size, num_words, words_per_row)
+ self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, init_corner)
+ #Run openRAM for all corners
+ for corner in corner_gen:
+ sram_data = self.get_sram_data(corner)
+ self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, corner)
+
self.close_files()
debug.info(1,"Data Generated")
- globals.end_openram()
def init_data_gen(self):
"""Initialization for the data test to run"""
- globals.init_openram("config_20_{0}".format(OPTS.tech_name))
+ globals.init_openram("config_data")
+ if OPTS.tech_name == "scmos":
+ debug.warning("Device models not up to date with scn4m technology.")
OPTS.spice_name="hspice" #Much faster than ngspice.
OPTS.trim_netlist = False
OPTS.netlist_only = True
@@ -58,7 +61,18 @@ class data_collection(openram_test):
"""Closes all files stored in the file dict"""
for key,file in self.csv_files.items():
file.close()
-
+
+ def corner_combination_generator(self):
+ """Generates corner using a combination of values from config file"""
+ processes = OPTS.process_corners
+ voltages = OPTS.supply_voltages
+ temperatures = OPTS.temperatures
+ for proc in processes:
+ for volt in voltages:
+ for temp in temperatures:
+ yield (proc, volt, temp)
+
+
def get_sram_configs(self):
"""Generate lists of wordsizes, number of words, and column mux size (words per row) to be tested."""
min_word_size = 1
@@ -70,28 +84,32 @@ class data_collection(openram_test):
words_per_row = [1]
return word_sizes, num_words, words_per_row
- def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row):
+ def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row, corner):
"""Writes data to its respective CSV file. There is a CSV for each measurement target (wordline, sense amp enable, and models)"""
- sram_specs = [word_size,num_words,words_per_row]
+ sram_specs = [word_size,num_words,words_per_row,*corner]
for data_name, data_values in sram_data.items():
self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name])
debug.info(2,"Data Added to CSV file.")
- def initialize_csv_file(self, sram_data):
+ def initialize_csv_file(self, sram_data, word_size, num_words, words_per_row):
"""Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)"""
#CSV File writing
header_dict = self.delay_obj.get_all_signal_names()
self.csv_files = {}
self.csv_writers = {}
for data_name, header_list in header_dict.items():
- self.csv_files[data_name] = open('{}data_{}.csv'.format(MODEL_DIR,data_name), 'w')
- fields = ('word_size', 'num_words', 'words_per_row', *header_list)
+ file_name = '{}data_{}b_{}word_{}way_{}.csv'.format(MODEL_DIR,
+ word_size,
+ num_words,
+ words_per_row,
+ data_name)
+ self.csv_files[data_name] = open(file_name, 'w')
+ fields = ('word_size', 'num_words', 'words_per_row', 'process', 'voltage', 'temp', *header_list)
self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
self.csv_writers[data_name].writerow(fields)
-
- def get_sram_data(self, word_size, num_words, words_per_row):
- """Generates the SRAM based on input configuration and returns the data."""
- from characterizer import model_check
+
+ def create_sram(self, word_size, num_words, words_per_row):
+ """Generates the SRAM based on input configuration."""
c = sram_config(word_size=word_size,
num_words=num_words,
num_banks=1)
@@ -99,19 +117,21 @@ class data_collection(openram_test):
#if word_size*num_words < 256:
c.words_per_row=words_per_row #Force no column mux until incorporated into analytical delay.
- debug.info(1, "Getting data for {} bit, {} words SRAM with 1 bank".format(word_size, num_words))
- s = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
+ debug.info(1, "Creating SRAM: {} bit, {} words, with 1 bank".format(word_size, num_words))
+ self.sram = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
- tempspice = OPTS.openram_temp + "temp.sp"
- s.sp_write(tempspice)
-
- corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
- self.delay_obj = model_check(s.s, tempspice, corner)
+ self.sram_spice = OPTS.openram_temp + "temp.sp"
+ self.sram.sp_write(self.sram_spice)
+
+ def get_sram_data(self, corner):
+ """Generates the delay object using the corner and runs a simulation for data."""
+ from characterizer import model_check
+ self.delay_obj = model_check(self.sram.s, self.sram_spice, corner)
import tech
#Only 1 at a time
- probe_address = "1" * s.s.addr_size
- probe_data = s.s.word_size - 1
+ probe_address = "1" * self.sram.s.addr_size
+ probe_data = self.sram.s.word_size - 1
loads = [tech.spice["msflop_in_cap"]*4]
slews = [tech.spice["rise_time"]*2]
diff --git a/compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv
new file mode 100644
index 00000000..5b11f78a
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
+4,16,1,TT,1.0,25,0.021103999999999998,0.0061908,0.018439,0.017329999999999998,0.0094258,0.018392000000000002,0.011755000000000002
+4,16,1,FF,1.0,25,0.019583,0.005128,0.017439,0.015281,0.008443599999999999,0.017213000000000003,0.010389
diff --git a/compiler/tests/model_data/data_4b_16word_1way_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_sae_model.csv
new file mode 100644
index 00000000..ed1b6dfb
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_sae_model.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
+4,16,1,TT,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
+4,16,1,FF,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
diff --git a/compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv
new file mode 100644
index 00000000..64329d3a
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,0.018438,0.0092547,0.013922,0.008679300000000001
+4,16,1,FF,1.0,25,0.017261,0.008002500000000001,0.012757,0.0077545
diff --git a/compiler/tests/model_data/data_4b_16word_1way_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_wl_model.csv
new file mode 100644
index 00000000..91363e48
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_wl_model.csv
@@ -0,0 +1,3 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,FF,1.0,25,4.4,12.4,5.8,5.4
diff --git a/compiler/tests/model_data/data_sae_measures.csv b/compiler/tests/model_data/data_sae_measures.csv
deleted file mode 100644
index 29b9ee07..00000000
--- a/compiler/tests/model_data/data_sae_measures.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-word_size,num_words,words_per_row,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
-1,16,1,0.1073407,0.010667,0.1317698,0.0910598,0.04802989999999999,0.1304522,0.0164942
-4,16,1,0.10635089999999998,0.010598499999999999,0.1315219,0.0911604,0.048601,0.12349109999999999,0.05874360000000001
diff --git a/compiler/tests/model_data/data_sae_model.csv b/compiler/tests/model_data/data_sae_model.csv
deleted file mode 100644
index 25a56d9e..00000000
--- a/compiler/tests/model_data/data_sae_model.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-word_size,num_words,words_per_row,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
-1,16,1,5.0,0.75,4.5,5.5,2.5,4.5,1.09375
-4,16,1,5.0,0.75,4.5,5.5,2.5,4.5,1.09375
diff --git a/compiler/tests/model_data/data_wl_measures.csv b/compiler/tests/model_data/data_wl_measures.csv
deleted file mode 100644
index 95aa4044..00000000
--- a/compiler/tests/model_data/data_wl_measures.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-word_size,num_words,words_per_row,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-1,16,1,0.1257435,0.0364958,0.051171100000000004,0.020759299999999998
-4,16,1,0.124175,0.03654059999999999,0.0507837,0.0433494
diff --git a/compiler/tests/model_data/data_wl_model.csv b/compiler/tests/model_data/data_wl_model.csv
deleted file mode 100644
index ae601f49..00000000
--- a/compiler/tests/model_data/data_wl_model.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-word_size,num_words,words_per_row,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-1,16,1,1.5,21.833333333333332,2.0,1.1666666666666665
-4,16,1,2.5,11.166666666666666,2.0,3.1666666666666665
diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py
index ff9b5169..a0ba8cbc 100644
--- a/technology/freepdk45/tech/tech.py
+++ b/technology/freepdk45/tech/tech.py
@@ -271,7 +271,12 @@ spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE
"FF" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"],
"SF" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"],
"FS" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"],
- "SS" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"]}
+ "SS" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"],
+ "ST" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_nom/NMOS_VTG.inc"],
+ "TS" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"],
+ "FT" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_nom/NMOS_VTG.inc"],
+ "TF" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"],
+ }
#spice stimulus related variables
spice["feasible_period"] = 5 # estimated feasible period in ns
diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py
index fe2d686a..1add9937 100755
--- a/technology/scn4m_subm/tech/tech.py
+++ b/technology/scn4m_subm/tech/tech.py
@@ -236,7 +236,12 @@ spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"
"FF" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"],
"FS" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"],
"SF" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"],
- "SS" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"] }
+ "SS" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"],
+ "ST" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/nom/nmos.sp"],
+ "TS" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"],
+ "FT" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/nom/nmos.sp"],
+ "TF" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"],
+ }
#spice stimulus related variables
From d527b7da62d3a85047632d8e7072d8be42ce724b Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 23 Jan 2019 13:19:35 -0800
Subject: [PATCH 15/77] Added delay error calculations
---
compiler/tests/delay_data_collection.py | 43 +++++++++++++++++++++++--
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index d3906d39..6a8c9fa8 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -13,6 +13,7 @@ import debug
import csv
from sram import sram
from sram_config import sram_config
+import pandas as pd
MODEL_DIR = "model_data/"
@@ -22,10 +23,43 @@ class data_collection(openram_test):
self.init_data_gen()
word_size, num_words, words_per_row = 4, 16, 1
- self.evalulate_sram_on_corners(word_size, num_words, words_per_row)
+ #Get data and write to CSV
+ self.save_data_sram_corners(word_size, num_words, words_per_row)
+
+ #Only care about the measured data for now, select from all file names. Names are defined in model_check
+ wl_dataframe, sae_dataframe = self.get_csv_data()
+ self.evaluate_data(wl_dataframe, sae_dataframe)
+
globals.end_openram()
- def evalulate_sram_on_corners(self, word_size, num_words, words_per_row):
+ def get_csv_data(self):
+ """Hardcoded Hack to get the measurement data from the csv into lists. """
+ wl_files_name = [file_name for file_name in self.file_names if "wl_measures" in file_name][0]
+ sae_files_name = [file_name for file_name in self.file_names if "sae_measures" in file_name][0]
+ wl_dataframe = pd.read_csv(wl_files_name,encoding='utf-8')
+ sae_dataframe = pd.read_csv(sae_files_name,encoding='utf-8')
+ return wl_dataframe,sae_dataframe
+
+ def evaluate_data(self, wl_dataframe, sae_dataframe):
+ """Analyze the delay error and variation error"""
+ delay_error = self.calculate_delay_error(wl_dataframe, sae_dataframe)
+ debug.info(1, "Delay errors:\n{}".format(delay_error))
+
+ def calculate_delay_error(self, wl_dataframe, sae_dataframe):
+ """Calculates the percentage difference in delays between the wordline and sense amp enable"""
+ start_data_pos = len(self.config_fields) #items before this point are configuration related
+ error_list = []
+ row_count = 0
+ for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
+ debug.info(1, "wl_row:\n{}".format(wl_row))
+ wl_sum = sum(wl_row[start_data_pos+1:])
+ debug.info(1, "wl_sum:\n{}".format(wl_sum))
+ sae_sum = sum(sae_row[start_data_pos+1:])
+ error_list.append(abs((wl_sum-sae_sum)/wl_sum))
+
+ return error_list
+
+ def save_data_sram_corners(self, word_size, num_words, words_per_row):
"""Performs corner analysis on a single SRAM configuration"""
self.create_sram(word_size, num_words, words_per_row)
#Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
@@ -97,14 +131,17 @@ class data_collection(openram_test):
header_dict = self.delay_obj.get_all_signal_names()
self.csv_files = {}
self.csv_writers = {}
+ self.file_names = []
for data_name, header_list in header_dict.items():
file_name = '{}data_{}b_{}word_{}way_{}.csv'.format(MODEL_DIR,
word_size,
num_words,
words_per_row,
data_name)
+ self.file_names.append(file_name)
self.csv_files[data_name] = open(file_name, 'w')
- fields = ('word_size', 'num_words', 'words_per_row', 'process', 'voltage', 'temp', *header_list)
+ self.config_fields = ['word_size', 'num_words', 'words_per_row', 'process', 'voltage', 'temp']
+ fields = (*self.config_fields, *header_list)
self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
self.csv_writers[data_name].writerow(fields)
From 65c5cc9fe72ab9028c7bab6a2fdba2e68aea62bf Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 24 Jan 2019 07:09:51 -0800
Subject: [PATCH 16/77] added support for more corner variations
---
compiler/datasheet/datasheet_gen.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 6786c7bf..618629ff 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -21,12 +21,27 @@ def process_name(corner):
"""
Expands the names of the characterization corner types into something human friendly
"""
+ if corner == "TS":
+ return "Typical - Slow"
if corner == "TT":
return "Typical - Typical"
+ if corner == "TF":
+ return "Typical - Fast"
+
if corner == "SS":
return "Slow - Slow"
+ if corner == "ST":
+ return "Slow - Typical"
+ if corner == "SF":
+ return "Slow - Fast"
+
+ if corner == "FS":
+ return "Fast - Slow"
+ if corner == "FT":
+ return "Fast - Typical"
if corner == "FF":
return "Fast - Fast"
+
else:
return "custom"
From ee03b4ecb80255f52a4f0c9a86b99810ea1ef8d6 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 24 Jan 2019 09:25:09 -0800
Subject: [PATCH 17/77] Added some data variation checking
---
compiler/modules/control_logic.py | 12 ++--
compiler/tests/config_data.py | 2 +-
compiler/tests/delay_data_collection.py | 68 +++++++++++++++----
...ata_4b_16word_1way_dc2x3_sae_measures.csv} | 3 +
...> data_4b_16word_1way_dc2x3_sae_model.csv} | 3 +
...data_4b_16word_1way_dc2x3_wl_measures.csv} | 3 +
...=> data_4b_16word_1way_dc2x3_wl_model.csv} | 3 +
...data_4b_16word_1way_dc4x2_sae_measures.csv | 6 ++
.../data_4b_16word_1way_dc4x2_sae_model.csv | 6 ++
.../data_4b_16word_1way_dc4x2_wl_measures.csv | 6 ++
.../data_4b_16word_1way_dc4x2_wl_model.csv | 6 ++
11 files changed, 96 insertions(+), 22 deletions(-)
rename compiler/tests/model_data/{data_4b_16word_1way_sae_measures.csv => data_4b_16word_1way_dc2x3_sae_measures.csv} (63%)
rename compiler/tests/model_data/{data_4b_16word_1way_sae_model.csv => data_4b_16word_1way_dc2x3_sae_model.csv} (71%)
rename compiler/tests/model_data/{data_4b_16word_1way_wl_measures.csv => data_4b_16word_1way_dc2x3_wl_measures.csv} (59%)
rename compiler/tests/model_data/{data_4b_16word_1way_wl_model.csv => data_4b_16word_1way_dc2x3_wl_model.csv} (69%)
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
create mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 9a945383..fbea31fb 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -112,6 +112,7 @@ class control_logic(design.design):
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
+ #Use a model to determine the delays with that heuristic
if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
delay_stages = parameter["static_delay_stages"]
delay_fanout = parameter["static_fanout_per_stage"]
@@ -119,6 +120,8 @@ class control_logic(design.design):
self.replica_bitline = replica_bitline([delay_fanout]*delay_stages,
bitcell_loads,
name="replica_bitline_"+self.port_type)
+ if self.sram != None: #Calculate model value even for specified sizes
+ self.set_sen_wl_delays()
else: #Otherwise, use a heuristic and/or model based sizing.
#First use a heuristic
@@ -126,13 +129,10 @@ class control_logic(design.design):
self.replica_bitline = replica_bitline([delay_fanout_heuristic]*delay_stages_heuristic,
bitcell_loads,
name="replica_bitline_"+self.port_type)
-
- #Use a model to determine the delays with that heuristic
- if self.sram != None:
+ if self.sram != None: #Calculate delays for potential re-sizing
self.set_sen_wl_delays()
-
- #Resize if necessary
- if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match(): #check condition based on resizing method
+ #Resize if necessary, condition depends on resizing method
+ if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
# stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
# self.replica_bitline = replica_bitline(stage_list,
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
index 31a28dfe..e605ee8c 100755
--- a/compiler/tests/config_data.py
+++ b/compiler/tests/config_data.py
@@ -2,7 +2,7 @@ word_size = 1
num_words = 16
tech_name = "freepdk45"
-process_corners = ["TT", "FF"]
+process_corners = ["TT", "FF", "SS", "SF", "FS"]
supply_voltages = [1.0]
temperatures = [25]
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index 6a8c9fa8..ff23a297 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -2,31 +2,35 @@
"""
Run a regression test on various srams
"""
+import csv,sys,os
+import pandas as pd
import unittest
from testutils import header,openram_test
-import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
from globals import OPTS
import debug
-import csv
from sram import sram
from sram_config import sram_config
-import pandas as pd
MODEL_DIR = "model_data/"
class data_collection(openram_test):
def runTest(self):
- self.init_data_gen()
-
- word_size, num_words, words_per_row = 4, 16, 1
- #Get data and write to CSV
- self.save_data_sram_corners(word_size, num_words, words_per_row)
- #Only care about the measured data for now, select from all file names. Names are defined in model_check
+ word_size, num_words, words_per_row = 4, 16, 1
+ self.init_data_gen()
+ self.set_delay_chain(2,3)
+ self.save_data_sram_corners(word_size, num_words, words_per_row)
+ wl_dataframe, sae_dataframe = self.get_csv_data()
+ self.evaluate_data(wl_dataframe, sae_dataframe)
+
+ #Run again but with different delay chain sizes
+ self.init_data_gen()
+ self.set_delay_chain(4,2)
+ self.save_data_sram_corners(word_size, num_words, words_per_row)
wl_dataframe, sae_dataframe = self.get_csv_data()
self.evaluate_data(wl_dataframe, sae_dataframe)
@@ -43,7 +47,9 @@ class data_collection(openram_test):
def evaluate_data(self, wl_dataframe, sae_dataframe):
"""Analyze the delay error and variation error"""
delay_error = self.calculate_delay_error(wl_dataframe, sae_dataframe)
- debug.info(1, "Delay errors:\n{}".format(delay_error))
+ debug.info(1, "Delay errors:{}".format(delay_error))
+ variation_error = self.calculate_delay_variation_error(wl_dataframe, sae_dataframe)
+ debug.info(1, "Variation errors:{}".format(variation_error))
def calculate_delay_error(self, wl_dataframe, sae_dataframe):
"""Calculates the percentage difference in delays between the wordline and sense amp enable"""
@@ -51,14 +57,36 @@ class data_collection(openram_test):
error_list = []
row_count = 0
for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
- debug.info(1, "wl_row:\n{}".format(wl_row))
+ debug.info(2, "wl_row:{}".format(wl_row))
wl_sum = sum(wl_row[start_data_pos+1:])
- debug.info(1, "wl_sum:\n{}".format(wl_sum))
+ debug.info(2, "wl_sum:{}".format(wl_sum))
sae_sum = sum(sae_row[start_data_pos+1:])
error_list.append(abs((wl_sum-sae_sum)/wl_sum))
-
return error_list
+ def calculate_delay_variation_error(self, wl_dataframe, sae_dataframe):
+ """Measures a base delay from the first corner then the variations from that base"""
+ start_data_pos = len(self.config_fields)
+ variation_error_list = []
+ count = 0
+ for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
+ if count == 0:
+ #Create a base delay, variation is defined as the difference between this base
+ wl_base = sum(wl_row[start_data_pos+1:])
+ debug.info(1, "wl_sum base:{}".format(wl_base))
+ sae_base = sum(sae_row[start_data_pos+1:])
+ variation_error_list.append(0.0)
+ else:
+ #Calculate the variation from the respective base and then difference between the variations
+ wl_sum = sum(wl_row[start_data_pos+1:])
+ wl_base_diff = abs((wl_base-wl_sum)/wl_base)
+ sae_sum = sum(sae_row[start_data_pos+1:])
+ sae_base_diff = abs((sae_base-sae_sum)/sae_base)
+ variation_diff = abs((wl_base_diff-sae_base_diff)/wl_base_diff)
+ variation_error_list.append(variation_diff)
+ count+=1
+ return variation_error_list
+
def save_data_sram_corners(self, word_size, num_words, words_per_row):
"""Performs corner analysis on a single SRAM configuration"""
self.create_sram(word_size, num_words, words_per_row)
@@ -80,17 +108,25 @@ class data_collection(openram_test):
def init_data_gen(self):
"""Initialization for the data test to run"""
globals.init_openram("config_data")
+ from tech import parameter
+ global parameter
if OPTS.tech_name == "scmos":
debug.warning("Device models not up to date with scn4m technology.")
OPTS.spice_name="hspice" #Much faster than ngspice.
OPTS.trim_netlist = False
OPTS.netlist_only = True
OPTS.analytical_delay = False
+ OPTS.use_tech_delay_chain_size = True
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
reload(characterizer)
+ def set_delay_chain(self, stages, fanout):
+ """Force change the parameter in the tech file to specify a delay chain configuration"""
+ parameter["static_delay_stages"] = stages
+ parameter["static_fanout_per_stage"] = fanout
+
def close_files(self):
"""Closes all files stored in the file dict"""
for key,file in self.csv_files.items():
@@ -133,10 +169,12 @@ class data_collection(openram_test):
self.csv_writers = {}
self.file_names = []
for data_name, header_list in header_dict.items():
- file_name = '{}data_{}b_{}word_{}way_{}.csv'.format(MODEL_DIR,
+ file_name = '{}data_{}b_{}word_{}way_dc{}x{}_{}.csv'.format(MODEL_DIR,
word_size,
num_words,
- words_per_row,
+ words_per_row,
+ parameter["static_delay_stages"],
+ parameter["static_fanout_per_stage"],
data_name)
self.file_names.append(file_name)
self.csv_files[data_name] = open(file_name, 'w')
diff --git a/compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
similarity index 63%
rename from compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv
rename to compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
index 5b11f78a..696761c8 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_sae_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
@@ -1,3 +1,6 @@
word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
4,16,1,TT,1.0,25,0.021103999999999998,0.0061908,0.018439,0.017329999999999998,0.0094258,0.018392000000000002,0.011755000000000002
4,16,1,FF,1.0,25,0.019583,0.005128,0.017439,0.015281,0.008443599999999999,0.017213000000000003,0.010389
+4,16,1,SS,1.0,25,0.022932,0.0074386999999999995,0.019891000000000002,0.019466,0.010501,0.019849,0.013432
+4,16,1,SF,1.0,25,0.019301,0.007507700000000001,0.016878999999999998,0.018834,0.010293,0.017156,0.01299
+4,16,1,FS,1.0,25,0.023601,0.0045925,0.020515,0.015586,0.0085521,0.019967,0.010449
diff --git a/compiler/tests/model_data/data_4b_16word_1way_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
similarity index 71%
rename from compiler/tests/model_data/data_4b_16word_1way_sae_model.csv
rename to compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
index ed1b6dfb..01c2e0e4 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_sae_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
@@ -1,3 +1,6 @@
word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
4,16,1,TT,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
4,16,1,FF,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
+4,16,1,SS,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
+4,16,1,SF,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
+4,16,1,FS,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
diff --git a/compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
similarity index 59%
rename from compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv
rename to compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
index 64329d3a..b3b65af3 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_wl_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
@@ -1,3 +1,6 @@
word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
4,16,1,TT,1.0,25,0.018438,0.0092547,0.013922,0.008679300000000001
4,16,1,FF,1.0,25,0.017261,0.008002500000000001,0.012757,0.0077545
+4,16,1,SS,1.0,25,0.019962,0.010683,0.015394,0.009734999999999999
+4,16,1,SF,1.0,25,0.017044,0.010483999999999999,0.012825,0.0094333
+4,16,1,FS,1.0,25,0.020398,0.0078018,0.015243999999999999,0.0079892
diff --git a/compiler/tests/model_data/data_4b_16word_1way_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
similarity index 69%
rename from compiler/tests/model_data/data_4b_16word_1way_wl_model.csv
rename to compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
index 91363e48..696f9952 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_wl_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
@@ -1,3 +1,6 @@
word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
4,16,1,TT,1.0,25,4.4,12.4,5.8,5.4
4,16,1,FF,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,SS,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,SF,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,FS,1.0,25,4.4,12.4,5.8,5.4
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
new file mode 100644
index 00000000..66366afd
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
@@ -0,0 +1,6 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
+4,16,1,TT,1.0,25,0.021141999999999998,0.006257400000000001,0.015622,0.014144,0.017741,0.013434,0.009287,0.018439999999999998,0.011276
+4,16,1,FF,1.0,25,0.019646,0.0052736,0.014601,0.012589,0.016537,0.011916999999999999,0.0083483,0.017246,0.0099574
+4,16,1,SS,1.0,25,0.022917,0.0074182,0.016901,0.015895,0.019267,0.015147,0.010306000000000001,0.01986,0.012834
+4,16,1,SF,1.0,25,0.019208,0.007500799999999999,0.014421999999999999,0.015359999999999999,0.016408,0.014695999999999999,0.010128,0.017086999999999998,0.012516000000000001
+4,16,1,FS,1.0,25,0.023644000000000002,0.0046118,0.017239,0.01283,0.019428,0.012081999999999999,0.0085141,0.020073,0.009944
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
new file mode 100644
index 00000000..c45a8df4
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
@@ -0,0 +1,6 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
+4,16,1,TT,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
+4,16,1,FF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
+4,16,1,SS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
+4,16,1,SF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
+4,16,1,FS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
new file mode 100644
index 00000000..e08c6fa5
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
@@ -0,0 +1,6 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,0.018481,0.0093054,0.013848,0.008683999999999999
+4,16,1,FF,1.0,25,0.017331,0.0080465,0.012700999999999999,0.0077613000000000005
+4,16,1,SS,1.0,25,0.019895,0.010660000000000001,0.015356999999999999,0.009745
+4,16,1,SF,1.0,25,0.016984000000000003,0.010501,0.012796,0.009405700000000001
+4,16,1,FS,1.0,25,0.020445,0.007772300000000001,0.015284,0.0079428
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
new file mode 100644
index 00000000..696f9952
--- /dev/null
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
@@ -0,0 +1,6 @@
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,FF,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,SS,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,SF,1.0,25,4.4,12.4,5.8,5.4
+4,16,1,FS,1.0,25,4.4,12.4,5.8,5.4
From ed901aba5f9f34fc4b6f3b98c255df584394ef8c Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Mon, 28 Jan 2019 10:29:27 -0800
Subject: [PATCH 18/77] changed datetime to date
---
compiler/characterizer/lib.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 156481ce..93592882 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -525,7 +525,7 @@ class lib:
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
- current_time = datetime.datetime.now()
+ current_time = datetime.date.today()
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
"sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name),
OPTS.num_words,
From 242a63accba79707646e3a19394cdb339d5898e9 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 29 Jan 2019 16:43:30 -0800
Subject: [PATCH 19/77] Fixed issues introduced by pdriver additions in model
unit test
---
compiler/characterizer/delay.py | 3 +-
compiler/characterizer/model_check.py | 50 ++++++++++++++++++---------
2 files changed, 35 insertions(+), 18 deletions(-)
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index a327c548..7bd36cd1 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -904,7 +904,8 @@ class delay(simulation):
port_data[port][mname].append(bank_delay[port].slew/1e3)
else:
debug.error("Measurement name not recognized: {}".format(mname),1)
- sram_data = { "min_period": 0,
+ period_margin = 0.1
+ sram_data = { "min_period": bank_delay[0]*2*period_margin,
"leakage_power": power.leakage}
return (sram_data,port_data)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 12174a4b..e76f5427 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -22,41 +22,44 @@ class model_check(delay):
delay.__init__(self,sram,spfile,corner)
self.period = tech.spice["feasible_period"]
self.create_data_names()
-
+
def create_data_names(self):
self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model"
self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model"
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
- #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
- self.wl_delay_meas_names = ["delay_wl_en_bar", "delay_wl_en", "delay_dvr_en_bar", "delay_wl"]
- self.wl_slew_meas_names = ["slew_wl_gated_clk_bar","slew_wl_en_bar", "slew_wl_en", "slew_drv_en_bar", "slew_wl"]
+ wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
+ wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
+ sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
+
+ wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
+ wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
+ sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
+
+ self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
+ self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
- self.sae_delay_meas_names = ["delay_pre_sen", "delay_sen_bar", "delay_sen"]
- self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen", "slew_sen_bar", "slew_sen"]
+ self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
+ self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"]
def create_signal_names(self):
+ """Creates list of the signal names used in the spice file along the wl and sen paths."""
delay.create_signal_names(self)
#Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
- self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xbuf_wl_en.zb_int", "Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_15", "Xsram.Xbank0.wl_15"]
+ wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
+ wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv0.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
+ sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] + ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
+
+ self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+wl_en_driver_signals+["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_15"]+wl_driver_signals+["Xsram.Xbank0.wl_15"]
self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"] + delay_chain_signal_names
- self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en", "Xsram.Xcontrol0.Xbuf_s_en.zb_int", "Xsram.s_en0"]
+ self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+sen_driver_signals+["Xsram.s_en0"]
- def get_all_signal_names(self):
- """Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV."""
- name_dict = {}
- #Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
- name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
- name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
- name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:]
- name_dict[self.sae_model_name] = name_dict["sae_measures"]
- return name_dict
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
@@ -183,8 +186,21 @@ class model_check(delay):
return self.sram.control_logic_rw.get_wl_sen_delays()
def get_num_delay_stages(self):
+ """Gets the number of stages in the delay chain from the control logic"""
return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list)
+ def get_num_wl_en_driver_stages(self):
+ """Gets the number of stages in the wl_en driver from the control logic"""
+ return self.sram.control_logic_rw.wl_en_driver.num_stages
+
+ def get_num_sen_driver_stages(self):
+ """Gets the number of stages in the sen driver from the control logic"""
+ return self.sram.control_logic_rw.s_en_driver.num_stages
+
+ def get_num_wl_driver_stages(self):
+ """Gets the number of stages in the wordline driver from the control logic"""
+ return self.sram.bank.wordline_driver.inv.num_stages
+
def scale_delays(self, delay_list):
"""Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
converted_values = []
From c10c9e400940c7fe72f5665eb0e687c453b4870e Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 29 Jan 2019 23:02:28 -0800
Subject: [PATCH 20/77] Refactored some code and other additional improvements.
---
compiler/characterizer/delay.py | 14 ++--
compiler/characterizer/model_check.py | 97 +++++++++++++++++++--------
2 files changed, 77 insertions(+), 34 deletions(-)
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index 7bd36cd1..14bdcd11 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -36,10 +36,6 @@ class delay(simulation):
self.period = 0
self.set_load_slew(0,0)
self.set_corner(corner)
- self.create_signal_names()
-
- #Create global measure names. Should maybe be an input at some point.
- self.create_measurement_names()
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
@@ -660,7 +656,7 @@ class delay(simulation):
self.probe_address = probe_address
self.probe_data = probe_data
self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data)
-
+ self.wordline_row = self.get_address_row_number(probe_address)
self.prepare_netlist()
def get_data_bit_column_number(self, probe_address, probe_data):
@@ -670,7 +666,11 @@ class delay(simulation):
else:
col_address = 0
bl_column = int(self.sram.words_per_row*probe_data + col_address)
- return bl_column
+ return bl_column
+
+ def get_address_row_number(self, probe_address):
+ """Calculates wordline row number of data bit under test using address and column mux size"""
+ return int(probe_address[self.sram.col_addr_size:],2)
def prepare_netlist(self):
""" Prepare a trimmed netlist and regular netlist. """
@@ -703,6 +703,8 @@ class delay(simulation):
char_sram_data = {}
self.set_probe(probe_address, probe_data)
+ self.create_signal_names()
+ self.create_measurement_names()
self.create_measurement_objects()
self.load=max(loads)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index e76f5427..2cf0d25d 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -29,22 +29,22 @@ class model_check(delay):
def create_measurement_names(self):
"""Create measurement names. The names themselves currently define the type of measurement"""
- wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
- wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
- sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
-
- wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
- wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
- sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
-
+ #Create delay measurement names
+ wl_en_driver_delay_names = ["delay_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
+ wl_driver_delay_names = ["delay_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
+ sen_driver_delay_names = ["delay_sen_dvr{}_".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
+ dc_delay_names = ["delay_delay_chain_stage{}_".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
- self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
-
- dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
- dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
- self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
+
+ #Create slew measurement names
+ wl_en_driver_slew_names = ["slew_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
+ wl_driver_slew_names = ["slew_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
+ sen_driver_slew_names = ["slew_sen_dvr{}_".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
+ dc_slew_names = ["slew_delay_chain_stage{}_".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
+ self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
+ self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"]
def create_signal_names(self):
@@ -52,13 +52,21 @@ class model_check(delay):
delay.create_signal_names(self)
#Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
- wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv0.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
+ wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
- delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] + ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
+ delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())]
- self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+wl_en_driver_signals+["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_15"]+wl_driver_signals+["Xsram.Xbank0.wl_15"]
- self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"] + delay_chain_signal_names
- self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+sen_driver_signals+["Xsram.s_en0"]
+ self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\
+ wl_en_driver_signals+\
+ ["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\
+ wl_driver_signals+\
+ ["Xsram.Xbank0.wl_{}".format(self.wordline_row)]
+ self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"]+\
+ delay_chain_signal_names+\
+ ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
+ self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\
+ sen_driver_signals+\
+ ["Xsram.s_en0"]
def create_measurement_objects(self):
@@ -74,8 +82,16 @@ class model_check(delay):
targ_dir = "FALL"
for i in range(1, len(self.wl_signal_names)):
- self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1], self.wl_signal_names[i-1], self.wl_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
- self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1], self.wl_signal_names[i-1], trig_dir, measure_scale=1e9))
+ self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1],
+ self.wl_signal_names[i-1],
+ self.wl_signal_names[i],
+ trig_dir,
+ targ_dir,
+ measure_scale=1e9))
+ self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1],
+ self.wl_signal_names[i-1],
+ trig_dir,
+ measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
@@ -89,24 +105,46 @@ class model_check(delay):
targ_dir = "FALL"
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.rbl_en_signal_names)):
- self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1], self.rbl_en_signal_names[i-1], self.rbl_en_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
- self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1], self.rbl_en_signal_names[i-1], trig_dir, measure_scale=1e9))
+ self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1],
+ self.rbl_en_signal_names[i-1],
+ self.rbl_en_signal_names[i],
+ trig_dir,
+ targ_dir,
+ measure_scale=1e9))
+ self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1],
+ self.rbl_en_signal_names[i-1],
+ trig_dir,
+ measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
- self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1], self.rbl_en_signal_names[-1], trig_dir, measure_scale=1e9))
+ self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1],
+ self.rbl_en_signal_names[-1],
+ trig_dir,
+ measure_scale=1e9))
#Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
trig_dir = "FALL"
targ_dir = "RISE"
#Add measurements from gated_clk_bar to RBL
for i in range(1, len(self.sae_signal_names)):
- self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], self.sae_signal_names[i-1], self.sae_signal_names[i], trig_dir, targ_dir, measure_scale=1e9))
- self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1], self.sae_signal_names[i-1], trig_dir, measure_scale=1e9))
+ self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1],
+ self.sae_signal_names[i-1],
+ self.sae_signal_names[i],
+ trig_dir,
+ targ_dir,
+ measure_scale=1e9))
+ self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1],
+ self.sae_signal_names[i-1],
+ trig_dir,
+ measure_scale=1e9))
temp_dir = trig_dir
trig_dir = targ_dir
targ_dir = temp_dir
- self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1], self.sae_signal_names[-1], trig_dir, measure_scale=1e9))
+ self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1],
+ self.sae_signal_names[-1],
+ trig_dir,
+ measure_scale=1e9))
def write_delay_measures(self):
"""
@@ -122,7 +160,8 @@ class model_check(delay):
self.write_measures_read_port(read_port)
def get_delay_measure_variants(self, port, measure_obj):
- """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
+ """Get the measurement values that can either vary from simulation to simulation (vdd, address)
+ or port to port (time delays)"""
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
#Assuming only read 0 for now
if not (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure):
@@ -242,9 +281,11 @@ class model_check(delay):
def analyze(self, probe_address, probe_data, slews, loads):
"""Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
- self.set_probe(probe_address, probe_data)
self.load=max(loads)
self.slew=max(slews)
+ self.set_probe(probe_address, probe_data)
+ self.create_signal_names()
+ self.create_measurement_names()
self.create_measurement_objects()
data_dict = {}
From 45fceb1f4eeab794109c7b3b4852cd751af47298 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 30 Jan 2019 11:43:47 -0800
Subject: [PATCH 21/77] Added word per row to sram config with a default
arguement to fix test.
---
compiler/characterizer/model_check.py | 11 ++++++++++-
compiler/modules/control_logic.py | 18 +++++++++---------
compiler/sram_config.py | 4 ++--
compiler/tests/delay_data_collection.py | 8 ++++----
.../data_4b_16word_1way_dc2x3_sae_measures.csv | 12 ++++++------
.../data_4b_16word_1way_dc2x3_sae_model.csv | 12 ++++++------
.../data_4b_16word_1way_dc2x3_wl_measures.csv | 12 ++++++------
.../data_4b_16word_1way_dc2x3_wl_model.csv | 12 ++++++------
.../data_4b_16word_1way_dc4x2_sae_measures.csv | 12 ++++++------
.../data_4b_16word_1way_dc4x2_sae_model.csv | 12 ++++++------
.../data_4b_16word_1way_dc4x2_wl_measures.csv | 12 ++++++------
.../data_4b_16word_1way_dc4x2_wl_model.csv | 12 ++++++------
12 files changed, 73 insertions(+), 64 deletions(-)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 2cf0d25d..b3e8452f 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -317,6 +317,15 @@ class model_check(delay):
return data_dict
-
+ def get_all_signal_names(self):
+ """Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV."""
+ name_dict = {}
+ #Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
+ name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
+ name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
+ name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:]
+ name_dict[self.sae_model_name] = name_dict["sae_measures"]
+ return name_dict
+
\ No newline at end of file
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 59aa25b9..948bf33a 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -141,9 +141,9 @@ class control_logic(design.design):
delay_stages = parameter["static_delay_stages"]
delay_fanout = parameter["static_fanout_per_stage"]
debug.info(1, "Using tech parameters to size delay chain: stages={}, fanout={}".format(delay_stages,delay_fanout))
- self.replica_bitline = replica_bitline([delay_fanout]*delay_stages,
- bitcell_loads,
- name="replica_bitline_"+self.port_type)
+ self.replica_bitline = replica_bitline("replica_bitline_"+self.port_type,
+ [delay_fanout]*delay_stages,
+ bitcell_loads)
if self.sram != None: #Calculate model value even for specified sizes
self.set_sen_wl_delays()
@@ -159,15 +159,15 @@ class control_logic(design.design):
if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
# stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
- # self.replica_bitline = replica_bitline(stage_list,
- # bitcell_loads,
- # name="replica_bitline_resized_"+self.port_type)
+ # self.replica_bitline = replica_bitline( "replica_bitline_resized_"+self.port_type
+ # stage_list,
+ # bitcell_loads)
#This resizes based on total delay.
delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
- self.replica_bitline = replica_bitline([delay_fanout]*delay_stages,
- bitcell_loads,
- name="replica_bitline_resized_"+self.port_type)
+ self.replica_bitline = replica_bitline("replica_bitline_resized_"+self.port_type,
+ [delay_fanout]*delay_stages,
+ bitcell_loads)
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
diff --git a/compiler/sram_config.py b/compiler/sram_config.py
index 24e3cbc9..5edf9282 100644
--- a/compiler/sram_config.py
+++ b/compiler/sram_config.py
@@ -7,13 +7,13 @@ from sram_factory import factory
class sram_config:
""" This is a structure that is used to hold the SRAM configuration options. """
- def __init__(self, word_size, num_words, num_banks=1):
+ def __init__(self, word_size, num_words, num_banks=1, words_per_row=None):
self.word_size = word_size
self.num_words = num_words
self.num_banks = num_banks
# This will get over-written when we determine the organization
- self.words_per_row = None
+ self.words_per_row = words_per_row
self.compute_sizes()
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index ff23a297..39f59141 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -187,10 +187,8 @@ class data_collection(openram_test):
"""Generates the SRAM based on input configuration."""
c = sram_config(word_size=word_size,
num_words=num_words,
- num_banks=1)
- #minimum 16 rows. Most sizes below 16*16 will try to automatically use less rows unless enforced.
- #if word_size*num_words < 256:
- c.words_per_row=words_per_row #Force no column mux until incorporated into analytical delay.
+ num_banks=1,
+ words_per_row=words_per_row)
debug.info(1, "Creating SRAM: {} bit, {} words, with 1 bank".format(word_size, num_words))
self.sram = sram(c, name="sram_{}ws_{}words".format(word_size, num_words))
@@ -198,6 +196,8 @@ class data_collection(openram_test):
self.sram_spice = OPTS.openram_temp + "temp.sp"
self.sram.sp_write(self.sram_spice)
+ debug.info(1, "SRAM column address size={}".format(self.sram.s.col_addr_size))
+
def get_sram_data(self, corner):
"""Generates the delay object using the corner and runs a simulation for data."""
from characterizer import model_check
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
index 696761c8..25f5359d 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
-4,16,1,TT,1.0,25,0.021103999999999998,0.0061908,0.018439,0.017329999999999998,0.0094258,0.018392000000000002,0.011755000000000002
-4,16,1,FF,1.0,25,0.019583,0.005128,0.017439,0.015281,0.008443599999999999,0.017213000000000003,0.010389
-4,16,1,SS,1.0,25,0.022932,0.0074386999999999995,0.019891000000000002,0.019466,0.010501,0.019849,0.013432
-4,16,1,SF,1.0,25,0.019301,0.007507700000000001,0.016878999999999998,0.018834,0.010293,0.017156,0.01299
-4,16,1,FS,1.0,25,0.023601,0.0045925,0.020515,0.015586,0.0085521,0.019967,0.010449
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
+4,16,1,TT,1.0,25,0.020618,0.0062215,0.018563000000000003,0.017233000000000002,0.007710799999999999,0.0099965,0.045221000000000004
+4,16,1,FF,1.0,25,0.019135,0.0052523000000000005,0.017398,0.015280000000000002,0.0067718,0.009288300000000001,0.042180999999999996
+4,16,1,SS,1.0,25,0.022393999999999997,0.0074892,0.019906,0.019521999999999998,0.0087409,0.010967000000000001,0.04836
+4,16,1,SF,1.0,25,0.01874,0.007554100000000001,0.016919,0.018821,0.0086205,0.0094092,0.049122
+4,16,1,FS,1.0,25,0.022926,0.0046388,0.02054,0.015555000000000001,0.0067794,0.010772,0.041583
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
index 01c2e0e4..b79d0fbf 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
-4,16,1,TT,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
-4,16,1,FF,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
-4,16,1,SS,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
-4,16,1,SF,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
-4,16,1,FS,1.0,25,8.8,2.65,6.4,7.4,4.4,6.4,2.99375
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
+4,16,1,TT,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
+4,16,1,FF,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
+4,16,1,SS,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
+4,16,1,SF,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
+4,16,1,FS,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
index b3b65af3..043fc729 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,0.018438,0.0092547,0.013922,0.008679300000000001
-4,16,1,FF,1.0,25,0.017261,0.008002500000000001,0.012757,0.0077545
-4,16,1,SS,1.0,25,0.019962,0.010683,0.015394,0.009734999999999999
-4,16,1,SF,1.0,25,0.017044,0.010483999999999999,0.012825,0.0094333
-4,16,1,FS,1.0,25,0.020398,0.0078018,0.015243999999999999,0.0079892
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,0.010512,0.0089216,0.014109,0.013643,0.014564,0.0086745
+4,16,1,FF,1.0,25,0.0096952,0.008041900000000001,0.013129,0.012268999999999999,0.013255999999999999,0.007759599999999999
+4,16,1,SS,1.0,25,0.011505,0.009873400000000001,0.015371,0.015194000000000001,0.016210000000000002,0.0097529
+4,16,1,SF,1.0,25,0.0097161,0.0096343,0.013210000000000001,0.014750000000000001,0.013464,0.0094366
+4,16,1,FS,1.0,25,0.011368999999999999,0.0082136,0.015231000000000001,0.012545,0.015907,0.0079376
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
index 696f9952..c323597a 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,FF,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,SS,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,SF,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,FS,1.0,25,4.4,12.4,5.8,5.4
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,FF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,SS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,SF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,FS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
index 66366afd..efc32260 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
-4,16,1,TT,1.0,25,0.021141999999999998,0.006257400000000001,0.015622,0.014144,0.017741,0.013434,0.009287,0.018439999999999998,0.011276
-4,16,1,FF,1.0,25,0.019646,0.0052736,0.014601,0.012589,0.016537,0.011916999999999999,0.0083483,0.017246,0.0099574
-4,16,1,SS,1.0,25,0.022917,0.0074182,0.016901,0.015895,0.019267,0.015147,0.010306000000000001,0.01986,0.012834
-4,16,1,SF,1.0,25,0.019208,0.007500799999999999,0.014421999999999999,0.015359999999999999,0.016408,0.014695999999999999,0.010128,0.017086999999999998,0.012516000000000001
-4,16,1,FS,1.0,25,0.023644000000000002,0.0046118,0.017239,0.01283,0.019428,0.012081999999999999,0.0085141,0.020073,0.009944
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
+4,16,1,TT,1.0,25,0.020572,0.006256,0.015678,0.014110000000000001,0.017755,0.013415,0.0076344,0.010162,0.04153
+4,16,1,FF,1.0,25,0.01907,0.0052328,0.014619000000000002,0.0126,0.016572,0.011862,0.0067348,0.0093334,0.03898
+4,16,1,SS,1.0,25,0.022189,0.0074532999999999995,0.016968,0.015854,0.019286,0.015139,0.0087264,0.011029,0.04439
+4,16,1,SF,1.0,25,0.018689,0.007557800000000001,0.014395999999999999,0.015354,0.016425000000000002,0.014665,0.0084936,0.009411300000000001,0.044714000000000004
+4,16,1,FS,1.0,25,0.022942,0.0046639,0.017356,0.012745999999999999,0.01949,0.012069,0.006808099999999999,0.010822,0.038710999999999995
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
index c45a8df4..12a0c9e8 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.zb_int,Xsram.s_en0
-4,16,1,TT,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
-4,16,1,FF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
-4,16,1,SS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
-4,16,1,SF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
-4,16,1,FS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,4.4,6.4,2.99375
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
+4,16,1,TT,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
+4,16,1,FF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
+4,16,1,SS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
+4,16,1,SF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
+4,16,1,FS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
index e08c6fa5..565bb307 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,0.018481,0.0093054,0.013848,0.008683999999999999
-4,16,1,FF,1.0,25,0.017331,0.0080465,0.012700999999999999,0.0077613000000000005
-4,16,1,SS,1.0,25,0.019895,0.010660000000000001,0.015356999999999999,0.009745
-4,16,1,SF,1.0,25,0.016984000000000003,0.010501,0.012796,0.009405700000000001
-4,16,1,FS,1.0,25,0.020445,0.007772300000000001,0.015284,0.0079428
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,0.010463,0.0089004,0.014107,0.013630000000000001,0.0146,0.0086902
+4,16,1,FF,1.0,25,0.0096519,0.008039899999999999,0.013115,0.012295,0.013235,0.0077621
+4,16,1,SS,1.0,25,0.011424,0.0098775,0.015351999999999998,0.015194000000000001,0.016227,0.0097375
+4,16,1,SF,1.0,25,0.009697500000000001,0.009592,0.013206,0.014738000000000001,0.013432,0.0094217
+4,16,1,FS,1.0,25,0.011432000000000001,0.0081985,0.015220000000000001,0.012544,0.015973,0.0079455
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
index 696f9952..c323597a 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
@@ -1,6 +1,6 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.zb_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,FF,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,SS,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,SF,1.0,25,4.4,12.4,5.8,5.4
-4,16,1,FS,1.0,25,4.4,12.4,5.8,5.4
+word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
+4,16,1,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,FF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,SS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,SF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+4,16,1,FS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
From 475db65d267fbf9c293ccf520949905051b70b64 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Wed, 30 Jan 2019 17:49:43 -0800
Subject: [PATCH 22/77] added units to AREA on datasheet
---
compiler/datasheet/datasheet_gen.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 618629ff..ad14801b 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -552,7 +552,7 @@ def parse_characterizer_csv(f, pages):
new_sheet.io_table.add_row(['NUM_RW_PORTS', NUM_RW_PORTS])
new_sheet.io_table.add_row(['NUM_R_PORTS', NUM_R_PORTS])
new_sheet.io_table.add_row(['NUM_W_PORTS', NUM_W_PORTS])
- new_sheet.io_table.add_row(['Area', AREA])
+ new_sheet.io_table.add_row(['Area (µm2)', AREA])
class datasheet_gen():
From 8d7823e4dd83b6039731ef139e79765ca7950bf2 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 31 Jan 2019 00:26:27 -0800
Subject: [PATCH 23/77] Added delay ratio comparisons between model and
measurements
---
compiler/modules/control_logic.py | 26 +++---
compiler/tests/config_data.py | 3 +-
compiler/tests/delay_data_collection.py | 79 +++++++++++++++----
...data_4b_16word_1way_dc2x3_sae_measures.csv | 8 +-
.../data_4b_16word_1way_dc2x3_sae_model.csv | 8 +-
.../data_4b_16word_1way_dc2x3_wl_measures.csv | 8 +-
.../data_4b_16word_1way_dc2x3_wl_model.csv | 8 +-
...data_4b_16word_1way_dc4x2_sae_measures.csv | 8 +-
.../data_4b_16word_1way_dc4x2_sae_model.csv | 8 +-
.../data_4b_16word_1way_dc4x2_wl_measures.csv | 8 +-
.../data_4b_16word_1way_dc4x2_wl_model.csv | 8 +-
11 files changed, 95 insertions(+), 77 deletions(-)
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 948bf33a..295bbca8 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -133,6 +133,7 @@ class control_logic(design.design):
if (self.port_type == "rw") or (self.port_type == "r"):
from importlib import reload
+ self.delay_chain_resized = False
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
@@ -141,35 +142,36 @@ class control_logic(design.design):
delay_stages = parameter["static_delay_stages"]
delay_fanout = parameter["static_fanout_per_stage"]
debug.info(1, "Using tech parameters to size delay chain: stages={}, fanout={}".format(delay_stages,delay_fanout))
- self.replica_bitline = replica_bitline("replica_bitline_"+self.port_type,
- [delay_fanout]*delay_stages,
- bitcell_loads)
+ self.replica_bitline = factory.create(module_type="replica_bitline",
+ delay_fanout_list=[delay_fanout]*delay_stages,
+ bitcell_loads=bitcell_loads)
if self.sram != None: #Calculate model value even for specified sizes
self.set_sen_wl_delays()
else: #Otherwise, use a heuristic and/or model based sizing.
#First use a heuristic
delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size()
- self.replica_bitline = replica_bitline("replica_bitline_"+self.port_type,
- [delay_fanout_heuristic]*delay_stages_heuristic,
- bitcell_loads)
+ self.replica_bitline = factory.create(module_type="replica_bitline",
+ delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
+ bitcell_loads=bitcell_loads)
if self.sram != None: #Calculate delays for potential re-sizing
self.set_sen_wl_delays()
#Resize if necessary, condition depends on resizing method
if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
# stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
- # self.replica_bitline = replica_bitline( "replica_bitline_resized_"+self.port_type
- # stage_list,
- # bitcell_loads)
+ # self.replica_bitline = factory.create(module_type="replica_bitline",
+ # delay_fanout_list=stage_list,
+ # bitcell_loads=bitcell_loads)
#This resizes based on total delay.
delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
- self.replica_bitline = replica_bitline("replica_bitline_resized_"+self.port_type,
- [delay_fanout]*delay_stages,
- bitcell_loads)
+ self.replica_bitline = factory.create(module_type="replica_bitline",
+ delay_fanout_list=[delay_fanout]*delay_stages,
+ bitcell_loads=bitcell_loads)
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
+ self.delay_chain_resized = True
self.add_mod(self.replica_bitline)
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
index e605ee8c..08168bc3 100755
--- a/compiler/tests/config_data.py
+++ b/compiler/tests/config_data.py
@@ -2,7 +2,8 @@ word_size = 1
num_words = 16
tech_name = "freepdk45"
-process_corners = ["TT", "FF", "SS", "SF", "FS"]
+#process_corners = ["TT", "FF", "SS", "SF", "FS"]
+process_corners = ["TT"]
supply_voltages = [1.0]
temperatures = [25]
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index 39f59141..bd2a61f7 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -28,12 +28,16 @@ class data_collection(openram_test):
self.evaluate_data(wl_dataframe, sae_dataframe)
#Run again but with different delay chain sizes
- self.init_data_gen()
- self.set_delay_chain(4,2)
- self.save_data_sram_corners(word_size, num_words, words_per_row)
- wl_dataframe, sae_dataframe = self.get_csv_data()
- self.evaluate_data(wl_dataframe, sae_dataframe)
+ # self.init_data_gen()
+ # self.set_delay_chain(4,2)
+ # self.save_data_sram_corners(word_size, num_words, words_per_row)
+ # wl_dataframe, sae_dataframe = self.get_csv_data()
+ # self.evaluate_data(wl_dataframe, sae_dataframe)
+ model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure()
+ debug.info(1, "model_delay_ratios={}".format(model_delay_ratios))
+ debug.info(1, "meas_delay_ratios={}".format(meas_delay_ratios))
+ debug.info(1, "ratio_error={}".format(ratio_error))
globals.end_openram()
def get_csv_data(self):
@@ -50,7 +54,36 @@ class data_collection(openram_test):
debug.info(1, "Delay errors:{}".format(delay_error))
variation_error = self.calculate_delay_variation_error(wl_dataframe, sae_dataframe)
debug.info(1, "Variation errors:{}".format(variation_error))
+
+ def compare_model_to_measure(self):
+ """Uses the last 4 recent data sets (wl_meas, sen_meas, wl_model, sen_model)
+ and compare the wl-sen delay ratio between model and measured.
+ """
+ model_delay_ratios = {}
+ meas_delay_ratios = {}
+ ratio_error = {}
+ #The full file name contains unrelated portions, separate them into the four that are needed
+ wl_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_measures" in file_name][0]
+ sae_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_measures" in file_name][0]
+ wl_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_model" in file_name][0]
+ sae_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_model" in file_name][0]
+ #Assume each csv has the same corners (and the same row order), use one of the dfs for corners
+ proc_pos, volt_pos, temp_pos = wl_meas_df.columns.get_loc('process'), wl_meas_df.columns.get_loc('voltage'), wl_meas_df.columns.get_loc('temp')
+ wl_sum_pos = wl_meas_df.columns.get_loc('sum')
+ sae_sum_pos = sae_meas_df.columns.get_loc('sum')
+
+ df_zip = zip(wl_meas_df.itertuples(),sae_meas_df.itertuples(),wl_model_df.itertuples(),sae_model_df.itertuples())
+ for wl_meas,sae_meas,wl_model,sae_model in df_zip:
+ #Use previously calculated position to index the df row.
+ corner = (wl_meas[proc_pos+1], wl_meas[volt_pos+1], wl_meas[temp_pos+1])
+ meas_delay_ratios[corner] = wl_meas[wl_sum_pos+1]/sae_meas[sae_sum_pos+1]
+ model_delay_ratios[corner] = wl_model[wl_sum_pos+1]/sae_model[sae_sum_pos+1]
+ debug.info(1,"wl_model sum={}, sae_model_sum={}".format(wl_model[wl_sum_pos+1], sae_model[sae_sum_pos+1]))
+ ratio_error[corner] = 100*abs(model_delay_ratios[corner]-meas_delay_ratios[corner])/meas_delay_ratios[corner]
+
+ return model_delay_ratios, meas_delay_ratios, ratio_error
+
def calculate_delay_error(self, wl_dataframe, sae_dataframe):
"""Calculates the percentage difference in delays between the wordline and sense amp enable"""
start_data_pos = len(self.config_fields) #items before this point are configuration related
@@ -94,13 +127,14 @@ class data_collection(openram_test):
corner_gen = self.corner_combination_generator()
init_corner = next(corner_gen)
sram_data = self.get_sram_data(init_corner)
- self.initialize_csv_file(sram_data, word_size, num_words, words_per_row)
- self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, init_corner)
+ dc_resized = self.was_delay_chain_resized()
+ self.initialize_csv_file(word_size, num_words, words_per_row)
+ self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, init_corner)
#Run openRAM for all corners
for corner in corner_gen:
sram_data = self.get_sram_data(corner)
- self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, corner)
+ self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, corner)
self.close_files()
debug.info(1,"Data Generated")
@@ -133,10 +167,10 @@ class data_collection(openram_test):
file.close()
def corner_combination_generator(self):
- """Generates corner using a combination of values from config file"""
processes = OPTS.process_corners
voltages = OPTS.supply_voltages
temperatures = OPTS.temperatures
+ """Generates corner using a combination of values from config file"""
for proc in processes:
for volt in voltages:
for temp in temperatures:
@@ -154,14 +188,21 @@ class data_collection(openram_test):
words_per_row = [1]
return word_sizes, num_words, words_per_row
- def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row, corner):
- """Writes data to its respective CSV file. There is a CSV for each measurement target (wordline, sense amp enable, and models)"""
- sram_specs = [word_size,num_words,words_per_row,*corner]
+ def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row, dc_resized, corner):
+ """Writes data to its respective CSV file. There is a CSV for each measurement target
+ (wordline, sense amp enable, and models)"""
+ sram_specs = [word_size,num_words,words_per_row,dc_resized,*corner]
for data_name, data_values in sram_data.items():
- self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name])
+ other_values = self.calculate_other_data_values(data_values)
+ self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name]+other_values)
debug.info(2,"Data Added to CSV file.")
- def initialize_csv_file(self, sram_data, word_size, num_words, words_per_row):
+ def calculate_other_data_values(self, sram_data_list):
+ """A function to calculate extra values related to the data. Only does the sum for now"""
+ data_sum = sum(sram_data_list)
+ return [data_sum]
+
+ def initialize_csv_file(self, word_size, num_words, words_per_row):
"""Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)"""
#CSV File writing
header_dict = self.delay_obj.get_all_signal_names()
@@ -178,8 +219,9 @@ class data_collection(openram_test):
data_name)
self.file_names.append(file_name)
self.csv_files[data_name] = open(file_name, 'w')
- self.config_fields = ['word_size', 'num_words', 'words_per_row', 'process', 'voltage', 'temp']
- fields = (*self.config_fields, *header_list)
+ self.config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_resized', 'process', 'voltage', 'temp']
+ self.other_data_fields = ['sum']
+ fields = (*self.config_fields, *header_list, *self.other_data_fields)
self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
self.csv_writers[data_name].writerow(fields)
@@ -223,6 +265,11 @@ class data_collection(openram_test):
dict[key] = dict[key][0]
else:
del dict[key]
+
+ def was_delay_chain_resized(self):
+ """Accesses the dc resize boolean in the control logic module."""
+ #FIXME:assumes read/write port only
+ return self.sram.s.control_logic_rw.delay_chain_resized
# instantiate a copdsay of the class to actually run the test
if __name__ == "__main__":
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
index 25f5359d..e8016aa1 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
-4,16,1,TT,1.0,25,0.020618,0.0062215,0.018563000000000003,0.017233000000000002,0.007710799999999999,0.0099965,0.045221000000000004
-4,16,1,FF,1.0,25,0.019135,0.0052523000000000005,0.017398,0.015280000000000002,0.0067718,0.009288300000000001,0.042180999999999996
-4,16,1,SS,1.0,25,0.022393999999999997,0.0074892,0.019906,0.019521999999999998,0.0087409,0.010967000000000001,0.04836
-4,16,1,SF,1.0,25,0.01874,0.007554100000000001,0.016919,0.018821,0.0086205,0.0094092,0.049122
-4,16,1,FS,1.0,25,0.022926,0.0046388,0.02054,0.015555000000000001,0.0067794,0.010772,0.041583
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
+4,16,1,False,TT,1.0,25,0.020618,0.0062215,0.018563000000000003,0.017233000000000002,0.007710799999999999,0.0099965,0.045221000000000004,0.12556380000000003
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
index b79d0fbf..3ed42e60 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
-4,16,1,TT,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
-4,16,1,FF,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
-4,16,1,SS,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
-4,16,1,SF,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
-4,16,1,FS,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
+4,16,1,False,TT,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15,42.949999999999996
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
index 043fc729..4c0ca0f3 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,0.010512,0.0089216,0.014109,0.013643,0.014564,0.0086745
-4,16,1,FF,1.0,25,0.0096952,0.008041900000000001,0.013129,0.012268999999999999,0.013255999999999999,0.007759599999999999
-4,16,1,SS,1.0,25,0.011505,0.009873400000000001,0.015371,0.015194000000000001,0.016210000000000002,0.0097529
-4,16,1,SF,1.0,25,0.0097161,0.0096343,0.013210000000000001,0.014750000000000001,0.013464,0.0094366
-4,16,1,FS,1.0,25,0.011368999999999999,0.0082136,0.015231000000000001,0.012545,0.015907,0.0079376
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
+4,16,1,False,TT,1.0,25,0.010512,0.0089216,0.014109,0.013643,0.014564,0.0086745,0.0704241
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
index c323597a..e9faaeb9 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,FF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,SS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,SF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,FS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
+4,16,1,False,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4,32.13333333333334
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
index efc32260..fe82d4e4 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
-4,16,1,TT,1.0,25,0.020572,0.006256,0.015678,0.014110000000000001,0.017755,0.013415,0.0076344,0.010162,0.04153
-4,16,1,FF,1.0,25,0.01907,0.0052328,0.014619000000000002,0.0126,0.016572,0.011862,0.0067348,0.0093334,0.03898
-4,16,1,SS,1.0,25,0.022189,0.0074532999999999995,0.016968,0.015854,0.019286,0.015139,0.0087264,0.011029,0.04439
-4,16,1,SF,1.0,25,0.018689,0.007557800000000001,0.014395999999999999,0.015354,0.016425000000000002,0.014665,0.0084936,0.009411300000000001,0.044714000000000004
-4,16,1,FS,1.0,25,0.022942,0.0046639,0.017356,0.012745999999999999,0.01949,0.012069,0.006808099999999999,0.010822,0.038710999999999995
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
+4,16,1,False,TT,1.0,25,0.020572,0.006256,0.015678,0.014110000000000001,0.017755,0.013415,0.0076344,0.010162,0.04153,0.14711239999999998
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
index 12a0c9e8..7cac207f 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0
-4,16,1,TT,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
-4,16,1,FF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
-4,16,1,SS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
-4,16,1,SF,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
-4,16,1,FS,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
+4,16,1,False,TT,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15,51.74999999999999
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
index 565bb307..97844e6d 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,0.010463,0.0089004,0.014107,0.013630000000000001,0.0146,0.0086902
-4,16,1,FF,1.0,25,0.0096519,0.008039899999999999,0.013115,0.012295,0.013235,0.0077621
-4,16,1,SS,1.0,25,0.011424,0.0098775,0.015351999999999998,0.015194000000000001,0.016227,0.0097375
-4,16,1,SF,1.0,25,0.009697500000000001,0.009592,0.013206,0.014738000000000001,0.013432,0.0094217
-4,16,1,FS,1.0,25,0.011432000000000001,0.0081985,0.015220000000000001,0.012544,0.015973,0.0079455
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
+4,16,1,False,TT,1.0,25,0.010463,0.0089004,0.014107,0.013630000000000001,0.0146,0.0086902,0.0703906
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
index c323597a..e9faaeb9 100644
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
+++ b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
@@ -1,6 +1,2 @@
-word_size,num_words,words_per_row,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15
-4,16,1,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,FF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,SS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,SF,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
-4,16,1,FS,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4
+word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
+4,16,1,False,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4,32.13333333333334
From 21868e1b60cd8cd1f468f45ef7c6132e54f70563 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 31 Jan 2019 08:09:00 -0800
Subject: [PATCH 24/77] removed expanded process names from corners
---
compiler/characterizer/lib.py | 2 +-
compiler/datasheet/datasheet_gen.py | 67 ++-
compiler/datasheet/wavedrom.py | 827 ----------------------------
3 files changed, 34 insertions(+), 862 deletions(-)
delete mode 100644 compiler/datasheet/wavedrom.py
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 93592882..a38ecd93 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -527,7 +527,7 @@ class lib:
current_time = datetime.date.today()
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
- "sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name),
+ OPTS.output_name,
OPTS.num_words,
OPTS.num_banks,
OPTS.num_rw_ports,
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index ad14801b..24bc3953 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -16,34 +16,34 @@ import csv
import datasheet
import table_gen
-
-def process_name(corner):
- """
- Expands the names of the characterization corner types into something human friendly
- """
- if corner == "TS":
- return "Typical - Slow"
- if corner == "TT":
- return "Typical - Typical"
- if corner == "TF":
- return "Typical - Fast"
-
- if corner == "SS":
- return "Slow - Slow"
- if corner == "ST":
- return "Slow - Typical"
- if corner == "SF":
- return "Slow - Fast"
-
- if corner == "FS":
- return "Fast - Slow"
- if corner == "FT":
- return "Fast - Typical"
- if corner == "FF":
- return "Fast - Fast"
-
- else:
- return "custom"
+# def process_name(corner):
+# """
+# Expands the names of the characterization corner types into something human friendly
+# """
+# if corner == "TS":
+# return "Typical - Slow"
+# if corner == "TT":
+# return "Typical - Typical"
+# if corner == "TF":
+# return "Typical - Fast"
+#
+# if corner == "SS":
+# return "Slow - Slow"
+# if corner == "ST":
+# return "Slow - Typical"
+# if corner == "SF":
+# return "Slow - Fast"
+#
+# if corner == "FS":
+# return "Fast - Slow"
+# if corner == "FT":
+# return "Fast - Typical"
+# if corner == "FF":
+# return "Fast - Fast"
+#
+# else:
+# return "custom"
+#
def parse_characterizer_csv(f, pages):
@@ -351,8 +351,7 @@ def parse_characterizer_csv(f, pages):
sheet.description.append(str(element))
break
- new_sheet.corners_table.add_row([PROC, process_name(
- PROC), VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.dlv_table.add_row(
['.lib', 'Synthesis models', '{1}'.format(LIB_NAME, LIB_NAME.replace(OUT_DIR, ''))])
@@ -371,9 +370,8 @@ def parse_characterizer_csv(f, pages):
new_sheet.corners_table = table_gen.table_gen("corners")
new_sheet.corners_table.add_row(
- ['Corner Name', 'Process', 'Power Supply', 'Temperature', 'Library Name Suffix'])
- new_sheet.corners_table.add_row([PROC, process_name(
- PROC), VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ ['Transistor Type', 'Power Supply', 'Temperature', 'Corner Name'])
+ new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.operating_table = table_gen.table_gen(
"operating_table")
new_sheet.operating_table.add_row(
@@ -552,7 +550,8 @@ def parse_characterizer_csv(f, pages):
new_sheet.io_table.add_row(['NUM_RW_PORTS', NUM_RW_PORTS])
new_sheet.io_table.add_row(['NUM_R_PORTS', NUM_R_PORTS])
new_sheet.io_table.add_row(['NUM_W_PORTS', NUM_W_PORTS])
- new_sheet.io_table.add_row(['Area (µm2)', AREA])
+ new_sheet.io_table.add_row(
+ ['Area (µm2)', AREA])
class datasheet_gen():
diff --git a/compiler/datasheet/wavedrom.py b/compiler/datasheet/wavedrom.py
deleted file mode 100644
index e8c68c56..00000000
--- a/compiler/datasheet/wavedrom.py
+++ /dev/null
@@ -1,827 +0,0 @@
-#!/usr/bin/python
-# The MIT License (MIT)
-#
-# Copyright (c) 2011-2016 Aliaksei Chapyzhenka
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# Translated to Python from original file:
-# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
-#
-
-import sys
-import json
-import math
-import waveskin
-
-font_width = 7
-
-lane = {
- "xs" : 20, # tmpgraphlane0.width
- "ys" : 20, # tmpgraphlane0.height
- "xg" : 120, # tmpgraphlane0.x
- "yg" : 0, # head gap
- "yh0" : 0, # head gap title
- "yh1" : 0, # head gap
- "yf0" : 0, # foot gap
- "yf1" : 0, # foot gap
- "y0" : 5, # tmpgraphlane0.y
- "yo" : 30, # tmpgraphlane1.y - y0
- "tgo" : -10, # tmptextlane0.x - xg
- "ym" : 15, # tmptextlane0.y - y0
- "xlabel" : 6, # tmptextlabel.x - xg
- "xmax" : 1,
- "scale" : 1,
- "head" : {},
- "foot" : {}
-}
-
-def genBrick (texts, extra, times) :
-
- R = []
- if len( texts ) == 4 :
- for j in range( times ):
-
- R.append(texts[0])
-
- for i in range ( extra ):
- R.append(texts[1])
-
- R.append(texts[2])
- for i in range ( extra ):
- R.append(texts[3])
-
- return R
-
- if len( texts ) == 1 :
- texts.append(texts[0])
-
- R.append(texts[0])
- for i in range (times * (2 * (extra + 1)) - 1) :
- R.append(texts[1])
- return R
-
-def genFirstWaveBrick (text, extra, times) :
-
- pattern = {
- 'p': ['pclk', '111', 'nclk', '000'],
- 'n': ['nclk', '000', 'pclk', '111'],
- 'P': ['Pclk', '111', 'nclk', '000'],
- 'N': ['Nclk', '000', 'pclk', '111'],
- 'l': ['000'],
- 'L': ['000'],
- '0': ['000'],
- 'h': ['111'],
- 'H': ['111'],
- '1': ['111'],
- '=': ['vvv-2'],
- '2': ['vvv-2'],
- '3': ['vvv-3'],
- '4': ['vvv-4'],
- '5': ['vvv-5'],
- 'd': ['ddd'],
- 'u': ['uuu'],
- 'z': ['zzz']
- }
-
- return genBrick( pattern.get( text, ['xxx'] ) , extra, times );
-
-def genWaveBrick (text, extra, times) :
-
- x1 = {'p':'pclk', 'n':'nclk', 'P':'Pclk', 'N':'Nclk', 'h':'pclk', 'l':'nclk', 'H':'Pclk', 'L':'Nclk'}
- x2 = {'0':'0', '1':'1', 'x':'x', 'd':'d', 'u':'u', 'z':'z', '=':'v', '2':'v', '3':'v', '4':'v', '5':'v' }
- x3 = {'0': '', '1': '', 'x': '', 'd': '', 'u': '', 'z': '', '=':'-2', '2':'-2', '3':'-3', '4':'-4', '5':'-5'}
- y1 = {
- 'p':'0', 'n':'1',
- 'P':'0', 'N':'1',
- 'h':'1', 'l':'0',
- 'H':'1', 'L':'0',
- '0':'0', '1':'1', 'x':'x', 'd':'d', 'u':'u', 'z':'z', '=':'v', '2':'v', '3':'v', '4':'v', '5':'v'}
-
- y2 = {
- 'p': '', 'n': '',
- 'P': '', 'N': '',
- 'h': '', 'l': '',
- 'H': '', 'L': '',
- '0': '', '1': '', 'x': '', 'd': '', 'u': '', 'z': '', '=':'-2', '2':'-2', '3':'-3', '4':'-4', '5':'-5'}
-
- x4 = {
- 'p': '111', 'n': '000',
- 'P': '111', 'N': '000',
- 'h': '111', 'l': '000',
- 'H': '111', 'L': '000',
- '0': '000', '1': '111', 'x': 'xxx', 'd': 'ddd', 'u': 'uuu', 'z': 'zzz',
- '=': 'vvv-2', '2': 'vvv-2', '3': 'vvv-3', '4': 'vvv-4', '5': 'vvv-5'}
-
- x5 = {'p':'nclk', 'n':'pclk', 'P':'nclk', 'N':'pclk'}
- x6 = {'p': '000', 'n': '111', 'P': '000', 'N': '111'}
- xclude = {'hp':'111', 'Hp':'111', 'ln': '000', 'Ln': '000', 'nh':'111', 'Nh':'111', 'pl': '000', 'Pl':'000'}
-
- #atext = text.split()
- atext = text
-
- tmp0 = x4.get(atext[1])
- tmp1 = x1.get(atext[1])
- if tmp1 == None :
- tmp2 = x2.get(atext[1])
- if tmp2 == None :
- # unknown
- return genBrick(['xxx'], extra, times)
- else :
- tmp3 = y1.get(atext[0])
- if tmp3 == None :
- # unknown
- return genBrick(['xxx'], extra, times)
-
- # soft curves
- return genBrick([tmp3 + 'm' + tmp2 + y2[atext[0]] + x3[atext[1]], tmp0], extra, times)
-
- else :
- tmp4 = xclude.get(text)
- if tmp4 != None :
- tmp1 = tmp4
-
- # sharp curves
- tmp2 = x5.get(atext[1])
- if tmp2 == None :
- # hlHL
- return genBrick([tmp1, tmp0], extra, times)
- else :
- # pnPN
- return genBrick([tmp1, tmp0, tmp2, x6[atext[1]]], extra, times)
-
-def parseWaveLane (text, extra) :
-
- R = []
- Stack = text
- Next = Stack[0]
- Stack = Stack[1:]
-
- Repeats = 1
- while len(Stack) and ( Stack[0] == '.' or Stack[0] == '|' ): # repeaters parser
- Stack=Stack[1:]
- Repeats += 1
-
- R.extend(genFirstWaveBrick(Next, extra, Repeats))
-
- while len(Stack) :
- Top = Next
- Next = Stack[0]
- Stack = Stack[1:]
- Repeats = 1
- while len(Stack) and ( Stack[0] == '.' or Stack[0] == '|' ) : # repeaters parser
- Stack=Stack[1:]
- Repeats += 1
- R.extend(genWaveBrick((Top + Next), extra, Repeats))
-
- for i in range( lane['phase'] ):
- R = R[1:]
- return R
-
-def parseWaveLanes (sig) :
-
- def data_extract (e) :
- tmp = e.get('data')
- if tmp == None : return None
- if is_type_str (tmp) : tmp=tmp.split()
- return tmp
-
- content = []
- for sigx in sig :
- lane['period'] = sigx.get('period',1)
- lane['phase'] = int( sigx.get('phase',0 ) * 2 )
- sub_content=[]
- sub_content.append( [sigx.get('name',' '), sigx.get('phase',0 ) ] )
- sub_content.append( parseWaveLane( sigx['wave'], int(lane['period'] * lane['hscale'] - 1 ) ) if sigx.get('wave') else None )
- sub_content.append( data_extract(sigx) )
- content.append(sub_content)
-
- return content
-
-def findLaneMarkers (lanetext) :
-
- lcount = 0
- gcount = 0
- ret = []
- for i in range( len( lanetext ) ) :
- if lanetext[i] == 'vvv-2' or lanetext[i] == 'vvv-3' or lanetext[i] == 'vvv-4' or lanetext[i] == 'vvv-5' :
- lcount += 1
- else :
- if lcount !=0 :
- ret.append(gcount - ((lcount + 1) / 2))
- lcount = 0
-
- gcount += 1
-
- if lcount != 0 :
- ret.append(gcount - ((lcount + 1) / 2))
-
- return ret
-
-def renderWaveLane (root, content, index) :
-
- xmax = 0
- xgmax = 0
- glengths = []
- svgns = 'http://www.w3.org/2000/svg'
- xlinkns = 'http://www.w3.org/1999/xlink'
- xmlns = 'http://www.w3.org/XML/1998/namespace'
- for j in range( len(content) ):
- name = content[j][0][0]
- if name : # check name
- g = [
- 'g',
- {
- 'id': 'wavelane_' + str(j) + '_' + str(index),
- 'transform': 'translate(0,' + str(lane['y0'] + j * lane['yo']) + ')'
- }
- ]
- root.append(g)
- title = [
- 'text',
- {
- 'x': lane['tgo'],
- 'y': lane['ym'],
- 'class': 'info',
- 'text-anchor': 'end',
- 'xml:space': 'preserve'
- },
- ['tspan', name]
- ]
- g.append(title)
-
- glengths.append( len(name) * font_width + font_width )
-
- xoffset = content[j][0][1]
- xoffset = math.ceil(2 * xoffset) - 2 * xoffset if xoffset > 0 else -2 * xoffset
- gg = [
- 'g',
- {
- 'id': 'wavelane_draw_' + str(j) + '_' + str(index),
- 'transform': 'translate(' + str( xoffset * lane['xs'] ) + ', 0)'
- }
- ]
- g.append(gg)
-
- if content[j][1] :
- for i in range( len(content[j][1]) ) :
- b = [
- 'use',
- {
- #'id': 'use_' + str(i) + '_' + str(j) + '_' + str(index),
- 'xmlns:xlink':xlinkns,
- 'xlink:href': '#' + str( content[j][1][i] ),
- 'transform': 'translate(' + str(i * lane['xs']) + ')'
- }
- ]
- gg.append(b)
-
- if content[j][2] and len(content[j][2]) :
- labels = findLaneMarkers(content[j][1])
- if len(labels) != 0 :
- for k in range( len(labels) ) :
- if content[j][2] and k < len(content[j][2]) :
- title = [
- 'text',
- {
- 'x': int(labels[k]) * lane['xs'] + lane['xlabel'],
- 'y': lane['ym'],
- 'text-anchor': 'middle',
- 'xml:space': 'preserve'
- },
- ['tspan',content[j][2][k]]
- ]
- gg.append(title)
-
-
- if len(content[j][1]) > xmax :
- xmax = len(content[j][1])
-
- lane['xmax'] = xmax
- lane['xg'] = xgmax + 20
- return glengths
-
-def renderMarks (root, content, index) :
-
- def captext ( g, cxt, anchor, y ) :
-
- if cxt.get(anchor) and cxt[anchor].get('text') :
- tmark = [
- 'text',
- {
- 'x': float( cxt['xmax'] ) * float( cxt['xs'] ) / 2,
- 'y': y,
- 'text-anchor': 'middle',
- 'fill': '#000',
- 'xml:space': 'preserve'
- }, cxt[anchor]['text']
- ]
- g.append(tmark)
-
- def ticktock ( g, cxt, ref1, ref2, x, dx, y, length ) :
- L = []
-
- if cxt.get(ref1) == None or cxt[ref1].get(ref2) == None :
- return
-
- val = cxt[ref1][ref2]
- if is_type_str( val ) :
- val = val.split()
- elif type( val ) is int :
- offset = val
- val = []
- for i in range ( length ) :
- val.append(i + offset)
-
- if type( val ) is list :
- if len( val ) == 0 :
- return
- elif len( val ) == 1 :
- offset = val[0]
- if is_type_str(offset) :
- L = val
- else :
- for i in range ( length ) :
- L[i] = i + offset
-
- elif len( val ) == 2:
- offset = int(val[0])
- step = int(val[1])
- tmp = val[1].split('.')
- if len( tmp ) == 2 :
- dp = len( tmp[1] )
-
- if is_type_str(offset) or is_type_str(step) :
- L = val
- else :
- offset = step * offset
- for i in range( length ) :
- L[i] = "{0:.",dp,"f}".format(step * i + offset)
-
- else :
- L = val
-
- else :
- return
-
- for i in range( length ) :
- tmp = L[i]
- tmark = [
- 'text',
- {
- 'x': i * dx + x,
- 'y': y,
- 'text-anchor': 'middle',
- 'class': 'muted',
- 'xml:space': 'preserve'
- }, str(tmp)
- ]
- g.append(tmark)
-
- mstep = 2 * int(lane['hscale'])
- mmstep = mstep * lane['xs']
- marks = int( lane['xmax'] / mstep )
- gy = len( content ) * int(lane['yo'])
-
- g = ['g', {'id': 'gmarks_' + str(index)}]
- root.insert(0,g)
-
- for i in range( marks + 1):
- gg = [
- 'path',
- {
- 'id': 'gmark_' + str(i) + '_' + str(index),
- 'd': 'm ' + str(i * mmstep) + ',' + '0' + ' 0,' + str(gy),
- 'style': 'stroke:#888;stroke-width:0.5;stroke-dasharray:1,3'
- }
- ]
- g.append( gg )
-
- captext(g, lane, 'head', -33 if lane['yh0'] else -13 )
- captext(g, lane, 'foot', gy + ( 45 if lane['yf0'] else 25 ) )
-
- ticktock( g, lane, 'head', 'tick', 0, mmstep, -5, marks + 1)
- ticktock( g, lane, 'head', 'tock', mmstep / 2, mmstep, -5, marks)
- ticktock( g, lane, 'foot', 'tick', 0, mmstep, gy + 15, marks + 1)
- ticktock( g, lane, 'foot', 'tock', mmstep / 2, mmstep, gy + 15, marks)
-
-def renderArcs (root, source, index, top) :
-
- Stack = []
- Edge = {'words': [], 'frm': 0, 'shape': '', 'to': 0, 'label': ''}
- Events = {}
- svgns = 'http://www.w3.org/2000/svg'
- xmlns = 'http://www.w3.org/XML/1998/namespace'
-
- if source :
- for i in range (len (source) ) :
- lane['period'] = source[i].get('period',1)
- lane['phase'] = int( source[i].get('phase',0 ) * 2 )
- text = source[i].get('node')
- if text:
- Stack = text
- pos = 0
- while len( Stack ) :
- eventname = Stack[0]
- Stack=Stack[1:]
- if eventname != '.' :
- Events[eventname] = {
- 'x' : str( int( float( lane['xs'] ) * (2 * pos * lane['period'] * lane['hscale'] - lane['phase'] ) + float( lane['xlabel'] ) ) ),
- 'y' : str( int( i * lane['yo'] + lane['y0'] + float( lane['ys'] ) * 0.5 ) )
- }
- pos += 1
-
- gg = [ 'g', { 'id' : 'wavearcs_' + str( index ) } ]
- root.append(gg)
-
- if top.get('edge') :
- for i in range( len ( top['edge'] ) ) :
- Edge['words'] = top['edge'][i].split()
- Edge['label'] = top['edge'][i][len(Edge['words'][0]):]
- Edge['label'] = Edge['label'][1:]
- Edge['frm'] = Edge['words'][0][0]
- Edge['to'] = Edge['words'][0][-1]
- Edge['shape'] = Edge['words'][0][1:-1]
- frm = Events[Edge['frm']]
- to = Events[Edge['to']]
- gmark = [
- 'path',
- {
- 'id': 'gmark_' + Edge['frm'] + '_' + Edge['to'],
- 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + to['x'] + ',' + to['y'],
- 'style': 'fill:none;stroke:#00F;stroke-width:1'
- }
- ]
- gg.append(gmark)
- dx = float( to['x'] ) - float( frm['x'] )
- dy = float( to['y'] ) - float( frm['y'] )
- lx = (float(frm['x']) + float(to['x'])) / 2
- ly = (float(frm['y']) + float(to['y'])) / 2
- pattern = {
- '~' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
- '-~' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
- '~-' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + '0' + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
- '-|' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
- '|-' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' 0,' + str(dy) + ' ' + str(dx) + ',0'},
- '-|-' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'},
- '->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none'},
- '~>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '-~>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '~->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + '0' + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '-|>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
- '|->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' 0,' + str(dy) + ' ' + str(dx) + ',0'},
- '-|->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'},
- '<->' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none'},
- '<~>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '<-~>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
- '<-|>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
- '<-|->': {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'}
- }
- gmark[1].update( pattern.get( Edge['shape'], { 'style': 'fill:none;stroke:#00F;stroke-width:1' } ) )
-
- if Edge['label']:
- if Edge['shape'] == '-~' :
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
- if Edge['shape'] == '~-' :
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.25
- if Edge['shape'] == '-|' :
- lx = float(to['x'])
- if Edge['shape'] == '|-' :
- lx = float(frm['x'])
- if Edge['shape'] == '-~>':
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
- if Edge['shape'] == '~->':
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.25
- if Edge['shape'] == '-|>' :
- lx = float(to['x'])
- if Edge['shape'] == '|->' :
- lx = float(frm['x'])
- if Edge['shape'] == '<-~>':
- lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
- if Edge['shape'] =='<-|>' :
- lx = float(to['x'])
-
- lwidth = len( Edge['label'] ) * font_width
- label = [
- 'text',
- {
- 'style': 'font-size:10px;',
- 'text-anchor': 'middle',
- 'xml:space': 'preserve',
- 'x': int( lx ),
- 'y': int( ly + 3 )
- },
- [ 'tspan', Edge['label'] ]
- ]
- underlabel = [
- 'rect',
- {
- 'height': 9,
- 'style': 'fill:#FFF;',
- 'width': lwidth,
- 'x': int( lx - lwidth / 2 ),
- 'y': int( ly - 5 )
- }
- ]
- gg.append(underlabel)
- gg.append(label)
-
- for k in Events:
- if k.islower() :
- if int( Events[k]['x'] ) > 0 :
- lwidth = len( k ) * font_width
- underlabel = [
- 'rect',
- {
- 'x': float( Events[k]['x'] ) - float(lwidth) / 2,
- 'y': int( Events[k]['y'] ) - 4,
- 'height': 8,
- 'width': lwidth,
- 'style': 'fill:#FFF;'
- }
- ]
- gg.append(underlabel)
- label = [
- 'text',
- {
- 'style': 'font-size:8px;',
- 'x': int( Events[k]['x'] ),
- 'y': int( Events[k]['y'] ) + 2,
- 'width': lwidth,
- 'text-anchor': 'middle'
- },
- k
- ]
- gg.append(label)
-
-def parseConfig (source) :
-
- lane['hscale'] = 1
- if lane.get('hscale0') :
- lane['hscale'] = lane['hscale0']
-
- if source and source.get('config') and source.get('config').get('hscale'):
- hscale = round(source.get('config').get('hscale'))
- if hscale > 0 :
- if hscale > 100 : hscale = 100
- lane['hscale'] = hscale
-
- lane['yh0'] = 0
- lane['yh1'] = 0
- if source and source.get('head') :
- lane['head'] = source['head']
- if source.get('head').get('tick',0) == 0 : lane['yh0'] = 20
- if source.get('head').get('tock',0) == 0 : lane['yh0'] = 20
- if source.get('head').get('text') : lane['yh1'] = 46; lane['head']['text'] = source['head']['text']
-
- lane['yf0'] = 0
- lane['yf1'] = 0
- if source and source.get('foot') :
- lane['foot'] = source['foot']
- if source.get('foot').get('tick',0) == 0 : lane['yf0'] = 20
- if source.get('foot').get('tock',0) == 0 : lane['yf0'] = 20
- if source.get('foot').get('text') : lane['yf1'] = 46; lane['foot']['text'] = source['foot']['text']
-
-def rec (tmp, state) :
-
- name = str( tmp[0] )
- delta_x = 25
-
- state['x'] += delta_x
- for i in range( len( tmp ) ) :
- if type( tmp[i] ) is list :
- old_y = state['y']
- rec( tmp[i], state )
- state['groups'].append( {'x':state['xx'], 'y':old_y, 'height':state['y'] - old_y, 'name': state['name'] } )
- elif type( tmp[i] ) is dict :
- state['lanes'].append(tmp[i])
- state['width'].append(state['x'])
- state['y'] += 1
-
- state['xx'] = state['x']
- state['x'] -= delta_x
- state['name'] = name
-
-def insertSVGTemplate (index, parent, source) :
-
- e = waveskin.WaveSkin['default']
-
- if source.get('config') and source.get('config').get('skin') :
- if waveskin.WaveSkin.get( source.get('config').get('skin') ) :
- e = waveskin.WaveSkin[ source.get('config').get('skin') ]
-
- if index == 0 :
- lane['xs'] = int( e[3][1][2][1]['width'] )
- lane['ys'] = int( e[3][1][2][1]['height'] )
- lane['xlabel'] = int( e[3][1][2][1]['x'] )
- lane['ym'] = int( e[3][1][2][1]['y'] )
-
- else :
- e = ['svg', {'id': 'svg', 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', 'height': '0'},
- ['g', {'id': 'waves'},
- ['g', {'id': 'lanes'}],
- ['g', {'id': 'groups'}]
- ]
- ]
-
- e[-1][1]['id'] = 'waves_' + str(index)
- e[-1][2][1]['id'] = 'lanes_' + str(index)
- e[-1][3][1]['id'] = 'groups_' + str(index)
- e[1]['id'] = 'svgcontent_' + str(index)
- e[1]['height'] = 0
-
- parent.extend(e)
-
-def renderWaveForm (index, source, output) :
-
- xmax = 0
- root = []
- groups = []
-
- if source.get('signal'):
- insertSVGTemplate(index, output, source)
- parseConfig( source )
- ret = {'x':0, 'y':0, 'xmax':0, 'width':[], 'lanes':[], 'groups':[] }
- rec( source['signal'], ret )
- content = parseWaveLanes(ret['lanes'])
- glengths = renderWaveLane(root, content, index)
- for i in range( len( glengths ) ):
- xmax = max( xmax, ( glengths[i] + ret['width'][i] ) )
- renderMarks(root, content, index)
- renderArcs(root, ret['lanes'], index, source)
- renderGaps(root, ret['lanes'], index)
- renderGroups(groups, ret['groups'], index)
- lane['xg'] = int( math.ceil( float( xmax - lane['tgo'] ) / float(lane['xs'] ) ) ) * lane['xs']
- width = (lane['xg'] + lane['xs'] * (lane['xmax'] + 1) )
- height = len(content) * lane['yo'] + lane['yh0'] + lane['yh1'] + lane['yf0'] + lane['yf1']
- output[1]={
- 'id' :'svgcontent_' + str(index),
- 'xmlns' :"http://www.w3.org/2000/svg",
- 'xmlns:xlink':"http://www.w3.org/1999/xlink",
- 'width' :str(width),
- 'height' :str(height),
- 'viewBox' :'0 0 ' + str(width) + ' ' + str(height),
- 'overflow' :"hidden"
- }
- output[-1][2][1]['transform']='translate(' + str(lane['xg'] + 0.5) + ', ' + str((float(lane['yh0']) + float(lane['yh1'])) + 0.5) + ')'
-
- output[-1][2].extend(root)
- output[-1][3].extend(groups)
-
-def renderGroups (root, groups, index) :
-
- svgns = 'http://www.w3.org/2000/svg',
- xmlns = 'http://www.w3.org/XML/1998/namespace'
-
- for i in range( len( groups ) ) :
- group = [
- 'path',
- {
- 'id': 'group_' + str(i) + '_' + str(index),
- 'd': 'm ' + str( groups[i]['x'] + 0.5 ) + ',' + str( groups[i]['y']* lane['yo'] + 3.5 + lane['yh0'] + lane['yh1'] ) + ' c -3,0 -5,2 -5,5 l 0,' + str( int( groups[i]['height'] * lane['yo'] - 16 ) ) + ' c 0,3 2,5 5,5',
- 'style': 'stroke:#0041c4;stroke-width:1;fill:none'
- }
- ]
- root.append(group)
-
- name = groups[i]['name']
- x = str( int( groups[i]['x'] - 10 ) )
- y = str( int( lane['yo'] * (groups[i]['y'] + (float(groups[i]['height']) / 2)) + lane['yh0'] + lane['yh1'] ) )
- label = [
- ['g',
- {'transform': 'translate(' + x + ',' + y + ')'},
- ['g', {'transform': 'rotate(270)'},
- 'text',
- {
- 'text-anchor': 'middle',
- 'class': 'info',
- 'xml:space' : 'preserve'
- },
- ['tspan',name]
- ]
- ]
- ]
- root.append(label)
-
-def renderGaps (root, source, index) :
-
- Stack = []
- svgns = 'http://www.w3.org/2000/svg',
- xlinkns = 'http://www.w3.org/1999/xlink'
-
- if source:
-
- gg = [
- 'g',
- { 'id': 'wavegaps_' + str(index) }
- ]
-
- for i in range( len( source )):
- lane['period'] = source[i].get('period',1)
- lane['phase'] = int( source[i].get('phase',0 ) * 2 )
-
- g = [
- 'g',
- {
- 'id': 'wavegap_' + str(i) + '_' + str(index),
- 'transform': 'translate(0,' + str(lane['y0'] + i * lane['yo']) + ')'
- }
- ]
- gg.append(g)
-
- if source[i].get('wave'):
- text = source[i]['wave']
- Stack = text
- pos = 0
- while len( Stack ) :
- c = Stack [0]
- Stack = Stack[1:]
- if c == '|' :
- b = [
- 'use',
- {
- 'xmlns:xlink':xlinkns,
- 'xlink:href':'#gap',
- 'transform': 'translate(' + str(int(float(lane['xs']) * ((2 * pos + 1) * float(lane['period']) * float(lane['hscale']) - float(lane['phase'])))) + ')'
- }
- ]
- g.append(b)
- pos += 1
-
- root.append( gg )
-
-def is_type_str( var ) :
- if sys.version_info[0] < 3:
- return type( var ) is str or type( var ) is unicode
- else:
- return type( var ) is str
-
-def convert_to_svg( root ) :
-
- svg_output = ''
-
- if type( root ) is list:
- if len(root) >= 2 and type( root[1] ) is dict:
- if len( root ) == 2 :
- svg_output += '<' + root[0] + convert_to_svg( root[1] ) + '/>\n'
- elif len( root ) >= 3 :
- svg_output += '<' + root[0] + convert_to_svg( root[1] ) + '>\n'
- if len( root ) == 3:
- svg_output += convert_to_svg( root[2] )
- else:
- svg_output += convert_to_svg( root[2:] )
- svg_output += '' + root[0] + '>\n'
- elif type( root[0] ) is list:
- for eleml in root:
- svg_output += convert_to_svg( eleml )
- else:
- svg_output += '<' + root[0] + '>\n'
- for eleml in root[1:]:
- svg_output += convert_to_svg( eleml )
- svg_output += '' + root[0] + '>\n'
- elif type( root ) is dict:
- for elemd in root :
- svg_output += ' ' + elemd + '="' + str(root[elemd]) + '"'
- else:
- svg_output += root
-
- return svg_output
-
-if __name__ == '__main__':
-
- if len( sys.argv ) != 5:
- print ( 'Usage : ' + sys.argv[0] + ' source svg ' )
- exit(1)
-
- if sys.argv[3] != 'svg' :
- print ( 'Error: only SVG format supported.' )
- exit(1)
-
- output=[]
- inputfile = sys.argv[2]
- outputfile = sys.argv[4]
-
- with open(inputfile,'r') as f:
- jinput = json.load(f)
-
- renderWaveForm(0,jinput,output)
- svg_output = convert_to_svg(output)
-
- with open(outputfile,'w') as f:
- f.write( svg_output )
From c22025839c5b10b022030574a006e667c54931cd Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 31 Jan 2019 08:28:51 -0800
Subject: [PATCH 25/77] datasheet now indicates if analytical or characterizer
is used
---
compiler/characterizer/lib.py | 5 +++--
compiler/datasheet/datasheet.py | 6 ++++++
compiler/datasheet/datasheet_gen.py | 5 +++++
3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index a38ecd93..a63c773c 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -526,7 +526,7 @@ class lib:
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
current_time = datetime.date.today()
- datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
+ datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
OPTS.num_words,
OPTS.num_banks,
@@ -542,7 +542,8 @@ class lib:
lib_name,
OPTS.word_size,
git_id,
- current_time
+ current_time,
+ OPTS.analytical_delay
))
# information of checks
diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py
index d15733d5..71d1f9dd 100644
--- a/compiler/datasheet/datasheet.py
+++ b/compiler/datasheet/datasheet.py
@@ -59,6 +59,12 @@ class datasheet():
self.html += self.operating_table.to_html()
self.html += 'Timing and Current Data
'
+ model = ''
+ if self.ANALYTICAL_MODEL:
+ model = "analytical model: results may not be percise"
+ else:
+ model = "spice characterizer"
+ self.html += 'Using '+model+'
'
# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += self.timing_table.to_html()
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 24bc3953..664f88c5 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -115,6 +115,10 @@ def parse_characterizer_csv(f, pages):
AREA = row[col]
col += 1
+
+ ANALYTICAL_MODEL = row[col]
+ col += 1
+
for sheet in pages:
if sheet.name == NAME:
@@ -365,6 +369,7 @@ def parse_characterizer_csv(f, pages):
new_sheet.time = DATETIME
new_sheet.DRC = DRC
new_sheet.LVS = LVS
+ new_sheet.ANALYTICAL_MODEL = ANALYTICAL_MODEL
new_sheet.description = [NAME, NUM_WORDS, NUM_BANKS, NUM_RW_PORTS, NUM_W_PORTS,
NUM_R_PORTS, TECH_NAME, MIN_PERIOD, WORD_SIZE, ORIGIN_ID, DATETIME]
From 12723adb0cdf9f311054d5164d3086e02bf2f5eb Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Mon, 4 Feb 2019 23:38:26 -0800
Subject: [PATCH 26/77] Modified some testing and initial delay chain sizes.
---
.gitignore | 1 +
compiler/characterizer/model_check.py | 6 +-
compiler/modules/control_logic.py | 40 ++++-----
compiler/tests/delay_data_collection.py | 83 ++++++++++++++-----
...data_4b_16word_1way_dc2x3_sae_measures.csv | 2 -
.../data_4b_16word_1way_dc2x3_sae_model.csv | 2 -
.../data_4b_16word_1way_dc2x3_wl_measures.csv | 2 -
.../data_4b_16word_1way_dc2x3_wl_model.csv | 2 -
...data_4b_16word_1way_dc4x2_sae_measures.csv | 2 -
.../data_4b_16word_1way_dc4x2_sae_model.csv | 2 -
.../data_4b_16word_1way_dc4x2_wl_measures.csv | 2 -
.../data_4b_16word_1way_dc4x2_wl_model.csv | 2 -
12 files changed, 88 insertions(+), 58 deletions(-)
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
delete mode 100644 compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
diff --git a/.gitignore b/.gitignore
index c4e15e9d..b16e3d0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
*.out
*.toc
*.synctex.gz
+**/model_data
\ No newline at end of file
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index b3e8452f..b791e520 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -227,7 +227,11 @@ class model_check(delay):
def get_num_delay_stages(self):
"""Gets the number of stages in the delay chain from the control logic"""
return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list)
-
+
+ def get_num_delay_stage_fanout(self):
+ """Gets fanout in each stage in the delay chain. Assumes each stage is the same"""
+ return self.sram.control_logic_rw.replica_bitline.delay_fanout_list[0]
+
def get_num_wl_en_driver_stages(self):
"""Gets the number of stages in the wl_en driver from the control logic"""
return self.sram.control_logic_rw.wl_en_driver.num_stages
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 295bbca8..e81e44ea 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -30,13 +30,13 @@ class control_logic(design.design):
self.port_type = port_type
self.num_cols = word_size*words_per_row
- self.num_words = num_rows * words_per_row
+ self.num_words = num_rows*words_per_row
- self.enable_delay_chain_resizing = False
+ self.enable_delay_chain_resizing = True
- #self.sram=None #disable re-sizing for debugging, FIXME: resizing is not working, needs to be adjusted for new control logic.
- self.wl_timing_tolerance = 1 #Determines how much larger the sen delay should be. Accounts for possible error in model.
- self.parasitic_inv_delay = parameter["min_inv_para_delay"] #Keeping 0 for now until further testing.
+ #Determines how much larger the sen delay should be. Accounts for possible error in model.
+ self.wl_timing_tolerance = 1
+ self.parasitic_inv_delay = parameter["min_inv_para_delay"]
self.wl_stage_efforts = None
self.sen_stage_efforts = None
@@ -159,16 +159,16 @@ class control_logic(design.design):
#Resize if necessary, condition depends on resizing method
if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
- # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
- # self.replica_bitline = factory.create(module_type="replica_bitline",
- # delay_fanout_list=stage_list,
- # bitcell_loads=bitcell_loads)
+ stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
+ self.replica_bitline = factory.create(module_type="replica_bitline",
+ delay_fanout_list=stage_list,
+ bitcell_loads=bitcell_loads)
#This resizes based on total delay.
- delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
- self.replica_bitline = factory.create(module_type="replica_bitline",
- delay_fanout_list=[delay_fanout]*delay_stages,
- bitcell_loads=bitcell_loads)
+ # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
+ # self.replica_bitline = factory.create(module_type="replica_bitline",
+ # delay_fanout_list=[delay_fanout]*delay_stages,
+ # bitcell_loads=bitcell_loads)
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
self.delay_chain_resized = True
@@ -177,13 +177,10 @@ class control_logic(design.design):
def get_heuristic_delay_chain_size(self):
"""Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """
- # FIXME: These should be tuned according to the additional size parameters
- delay_fanout = 3 # This can be anything >=2
+ delay_fanout = 2 # This can be anything >=2
# Delay stages Must be non-inverting
- if self.words_per_row >= 4:
- delay_stages = 8
- elif self.words_per_row == 2:
- delay_stages = 6
+ if self.words_per_row >= 2:
+ delay_stages = 4
else:
delay_stages = 2
@@ -247,6 +244,8 @@ class control_logic(design.design):
required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2)
debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise))
+ #If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
+ WARNING_FANOUT_DIFF = 5
#The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
while True:
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall)
@@ -254,7 +253,8 @@ class control_logic(design.design):
debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise))
if stages_fall == stages_rise:
break
- elif abs(stages_fall-stages_rise) == 1:
+ elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise):
+ debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
break
#There should also be a condition to make sure the fanout does not get too large.
#Otherwise, increase the fanout of delay with the most stages, calculate new stages
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index bd2a61f7..7c98bccf 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -4,6 +4,7 @@ Run a regression test on various srams
"""
import csv,sys,os
import pandas as pd
+import matplotlib.pyplot as plt
import unittest
from testutils import header,openram_test
@@ -19,13 +20,14 @@ MODEL_DIR = "model_data/"
class data_collection(openram_test):
def runTest(self):
-
- word_size, num_words, words_per_row = 4, 16, 1
- self.init_data_gen()
- self.set_delay_chain(2,3)
- self.save_data_sram_corners(word_size, num_words, words_per_row)
- wl_dataframe, sae_dataframe = self.get_csv_data()
- self.evaluate_data(wl_dataframe, sae_dataframe)
+ ratio_data = self.calculate_delay_ratios_of_srams()
+ self.display_data(ratio_data)
+ # word_size, num_words, words_per_row = 4, 16, 1
+ # self.init_data_gen()
+ # self.set_delay_chain(2,3)
+ # self.save_data_sram_corners(word_size, num_words, words_per_row)
+ # wl_dataframe, sae_dataframe = self.get_csv_data()
+ # self.evaluate_data(wl_dataframe, sae_dataframe)
#Run again but with different delay chain sizes
# self.init_data_gen()
@@ -34,12 +36,27 @@ class data_collection(openram_test):
# wl_dataframe, sae_dataframe = self.get_csv_data()
# self.evaluate_data(wl_dataframe, sae_dataframe)
- model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure()
- debug.info(1, "model_delay_ratios={}".format(model_delay_ratios))
- debug.info(1, "meas_delay_ratios={}".format(meas_delay_ratios))
- debug.info(1, "ratio_error={}".format(ratio_error))
- globals.end_openram()
+ # model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure()
+ # debug.info(1, "model_delay_ratios={}".format(model_delay_ratios))
+ # debug.info(1, "meas_delay_ratios={}".format(meas_delay_ratios))
+ # debug.info(1, "ratio_error={}".format(ratio_error))
+ # globals.end_openram()
+ def calculate_delay_ratios_of_srams(self):
+ """Runs delay measurements on several sram configurations.
+ Computes the delay ratio for each one."""
+ delay_ratio_data = {}
+ config_tuple_list = [(32, 1024, None)]
+ #config_tuple_list = [(1, 16, 1),(4, 16, 1), (16, 16, 1), (32, 32, 1)]
+ for sram_config in config_tuple_list:
+ word_size, num_words, words_per_row = sram_config
+ self.init_data_gen()
+ self.save_data_sram_corners(word_size, num_words, words_per_row)
+ model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure()
+ delay_ratio_data[sram_config] = ratio_error
+ debug.info(1, "Ratio percentage error={}".format(ratio_error))
+ return delay_ratio_data
+
def get_csv_data(self):
"""Hardcoded Hack to get the measurement data from the csv into lists. """
wl_files_name = [file_name for file_name in self.file_names if "wl_measures" in file_name][0]
@@ -79,11 +96,30 @@ class data_collection(openram_test):
corner = (wl_meas[proc_pos+1], wl_meas[volt_pos+1], wl_meas[temp_pos+1])
meas_delay_ratios[corner] = wl_meas[wl_sum_pos+1]/sae_meas[sae_sum_pos+1]
model_delay_ratios[corner] = wl_model[wl_sum_pos+1]/sae_model[sae_sum_pos+1]
- debug.info(1,"wl_model sum={}, sae_model_sum={}".format(wl_model[wl_sum_pos+1], sae_model[sae_sum_pos+1]))
- ratio_error[corner] = 100*abs(model_delay_ratios[corner]-meas_delay_ratios[corner])/meas_delay_ratios[corner]
+ #Not using absolute error, positive error means model was larger, negative error means it was smaller.
+ ratio_error[corner] = 100*(model_delay_ratios[corner]-meas_delay_ratios[corner])/meas_delay_ratios[corner]
return model_delay_ratios, meas_delay_ratios, ratio_error
-
+
+ def display_data(self, data):
+ """Displays the ratio data using matplotlib (requires graphics)"""
+ config_data = []
+ xticks = []
+ #Organize data
+ #First key level if the sram configuration (wordsize, num words, words per row)
+ for config,corner_data_dict in data.items():
+ #Second level is the corner data for that configuration.
+ for corner, corner_data in corner_data_dict.items():
+ #Right now I am only testing with a single corner, will not work with more than 1 corner
+ config_data.append(corner_data)
+ xticks.append("{}b,{}w,{}wpr".format(*config))
+ #plot data
+ data_range = [i+1 for i in range(len(data))]
+ shapes = ['ro', 'bo', 'go', 'co', 'mo']
+ plt.xticks(data_range, xticks)
+ plt.plot(data_range, config_data, 'ro')
+ plt.show()
+
def calculate_delay_error(self, wl_dataframe, sae_dataframe):
"""Calculates the percentage difference in delays between the wordline and sense amp enable"""
start_data_pos = len(self.config_fields) #items before this point are configuration related
@@ -123,6 +159,9 @@ class data_collection(openram_test):
def save_data_sram_corners(self, word_size, num_words, words_per_row):
"""Performs corner analysis on a single SRAM configuration"""
self.create_sram(word_size, num_words, words_per_row)
+ #Setting to none forces SRAM to determine the value. Must be checked after sram creation
+ if not words_per_row:
+ words_per_row = self.sram.s.words_per_row
#Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now.
corner_gen = self.corner_combination_generator()
init_corner = next(corner_gen)
@@ -150,7 +189,7 @@ class data_collection(openram_test):
OPTS.trim_netlist = False
OPTS.netlist_only = True
OPTS.analytical_delay = False
- OPTS.use_tech_delay_chain_size = True
+ #OPTS.use_tech_delay_chain_size = True
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
@@ -209,13 +248,16 @@ class data_collection(openram_test):
self.csv_files = {}
self.csv_writers = {}
self.file_names = []
+ delay_stages = self.delay_obj.get_num_delay_stages()
+ delay_stage_fanout = self.delay_obj.get_num_delay_stage_fanout()
+
for data_name, header_list in header_dict.items():
file_name = '{}data_{}b_{}word_{}way_dc{}x{}_{}.csv'.format(MODEL_DIR,
word_size,
num_words,
words_per_row,
- parameter["static_delay_stages"],
- parameter["static_fanout_per_stage"],
+ delay_stages,
+ delay_stage_fanout,
data_name)
self.file_names.append(file_name)
self.csv_files[data_name] = open(file_name, 'w')
@@ -238,13 +280,11 @@ class data_collection(openram_test):
self.sram_spice = OPTS.openram_temp + "temp.sp"
self.sram.sp_write(self.sram_spice)
- debug.info(1, "SRAM column address size={}".format(self.sram.s.col_addr_size))
-
def get_sram_data(self, corner):
"""Generates the delay object using the corner and runs a simulation for data."""
from characterizer import model_check
self.delay_obj = model_check(self.sram.s, self.sram_spice, corner)
-
+
import tech
#Only 1 at a time
probe_address = "1" * self.sram.s.addr_size
@@ -253,6 +293,7 @@ class data_collection(openram_test):
slews = [tech.spice["rise_time"]*2]
sram_data = self.delay_obj.analyze(probe_address,probe_data,slews,loads)
+
return sram_data
def remove_lists_from_dict(self, dict):
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
deleted file mode 100644
index e8016aa1..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_measures.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
-4,16,1,False,TT,1.0,25,0.020618,0.0062215,0.018563000000000003,0.017233000000000002,0.007710799999999999,0.0099965,0.045221000000000004,0.12556380000000003
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
deleted file mode 100644
index 3ed42e60..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_sae_model.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
-4,16,1,False,TT,1.0,25,8.8,2.65,6.4,7.4,3.4,7.15,7.15,42.949999999999996
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
deleted file mode 100644
index 4c0ca0f3..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_measures.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
-4,16,1,False,TT,1.0,25,0.010512,0.0089216,0.014109,0.013643,0.014564,0.0086745,0.0704241
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
deleted file mode 100644
index e9faaeb9..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc2x3_wl_model.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
-4,16,1,False,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4,32.13333333333334
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
deleted file mode 100644
index fe82d4e4..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_measures.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
-4,16,1,False,TT,1.0,25,0.020572,0.006256,0.015678,0.014110000000000001,0.017755,0.013415,0.0076344,0.010162,0.04153,0.14711239999999998
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
deleted file mode 100644
index 7cac207f..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_sae_model.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xand2_rbl_in.zb_int,Xsram.Xcontrol0.rbl_in,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_2,Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_3,Xsram.Xcontrol0.Xreplica_bitline.delayed_en,Xsram.Xcontrol0.pre_s_en,Xsram.Xcontrol0.Xbuf_s_en.Zb1_int,Xsram.s_en0,sum
-4,16,1,False,TT,1.0,25,8.8,2.65,5.4,5.4,5.4,6.4,3.4,7.15,7.15,51.74999999999999
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
deleted file mode 100644
index 97844e6d..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_measures.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
-4,16,1,False,TT,1.0,25,0.010463,0.0089004,0.014107,0.013630000000000001,0.0146,0.0086902,0.0703906
diff --git a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv b/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
deleted file mode 100644
index e9faaeb9..00000000
--- a/compiler/tests/model_data/data_4b_16word_1way_dc4x2_wl_model.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-word_size,num_words,words_per_row,dc_resized,process,voltage,temp,Xsram.Xcontrol0.Xbuf_wl_en.Zb1_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb2_int,Xsram.Xcontrol0.Xbuf_wl_en.Zb3_int,Xsram.wl_en0,Xsram.Xbank0.Xwordline_driver0.wl_bar_15,Xsram.Xbank0.wl_15,sum
-4,16,1,False,TT,1.0,25,5.4,5.4,5.733333333333333,4.4,5.8,5.4,32.13333333333334
From e3d003d410137f48f01c68dae28b4210221d2c6f Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 5 Feb 2019 00:43:16 -0800
Subject: [PATCH 27/77] Adjusted test values to account for recent changes.
---
compiler/tests/21_hspice_delay_test.py | 57 +++++++++++++------------
compiler/tests/21_ngspice_delay_test.py | 55 ++++++++++++------------
compiler/tests/config_data.py | 1 +
compiler/tests/testutils.py | 2 +
4 files changed, 60 insertions(+), 55 deletions(-)
diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py
index 8be7152c..a2c40a22 100755
--- a/compiler/tests/21_hspice_delay_test.py
+++ b/compiler/tests/21_hspice_delay_test.py
@@ -51,35 +51,36 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
- golden_data = {'delay_hl': [0.2011],
- 'delay_lh': [0.2011],
- 'leakage_power': 0.002,
- 'min_period': 0.41,
- 'read0_power': [0.63604],
- 'read1_power': [0.6120599999999999],
- 'slew_hl': [0.07078999999999999],
- 'slew_lh': [0.07078999999999999],
- 'write0_power': [0.51742],
- 'write1_power': [0.51095],
- 'volt_bl': [0.2017],
- 'volt_br': [1.0765],
- 'delay_bl': [0.18114999999999998],
- 'delay_br': [0.17763]}
+ golden_data = {'delay_bl': [0.19683],
+ 'delay_br': [0.19474],
+ 'delay_hl': [0.20646],
+ 'delay_lh': [0.20646],
+ 'leakage_power': 0.0013155,
+ 'min_period': 0.43,
+ 'read0_power': [0.50758],
+ 'read1_power': [0.48225999999999997],
+ 'slew_hl': [0.045869],
+ 'slew_lh': [0.045869],
+ 'volt_bl': [0.6563],
+ 'volt_br': [1.117],
+ 'write0_power': [0.46124000000000004],
+ 'write1_power': [0.48225999999999997]}
elif OPTS.tech_name == "scn4m_subm":
- golden_data = {'delay_hl': [1.3911],
- 'delay_lh': [1.3911],
- 'leakage_power': 0.0278488,
- 'min_period': 2.812,
- 'read0_power': [22.1183],
- 'read1_power': [21.4388],
- 'slew_hl': [0.6],
- 'slew_lh': [0.6],
- 'write0_power': [19.4103],
- 'write1_power': [20.1167],
- 'volt_bl': [3.1763],
- 'volt_br': [5.5731],
- 'delay_bl': [1.1133000000000002],
- 'delay_br': [0.9958395]}
+ golden_data = {'delay_bl': [1.1029],
+ 'delay_br': [0.9656455999999999],
+ 'delay_hl': [1.288],
+ 'delay_lh': [1.288],
+ 'leakage_power': 0.0273896,
+ 'min_period': 2.578,
+ 'read0_power': [16.9996],
+ 'read1_power': [16.2616],
+ 'slew_hl': [0.47891700000000004],
+ 'slew_lh': [0.47891700000000004],
+ 'volt_bl': [4.2155],
+ 'volt_br': [5.8142],
+ 'write0_power': [16.0656],
+ 'write1_power': [16.2616]}
+
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results
diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py
index 98f01e53..5af44e69 100755
--- a/compiler/tests/21_ngspice_delay_test.py
+++ b/compiler/tests/21_ngspice_delay_test.py
@@ -51,35 +51,36 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
- golden_data = {'delay_bl': [0.1840938],
- 'delay_br': [0.1804373],
- 'delay_hl': [0.2130831],
- 'delay_lh': [0.2130831],
- 'leakage_power': 0.001595639,
- 'min_period': 0.527,
- 'read0_power': [0.4852456],
- 'read1_power': [0.46341889999999997],
- 'slew_hl': [0.07351041999999999],
- 'slew_lh': [0.07351041999999999],
- 'volt_bl': [0.1954744],
- 'volt_br': [1.058266],
- 'write0_power': [0.4065201],
- 'write1_power': [0.46341889999999997]}
+ golden_data = {'delay_bl': [0.2003652],
+ 'delay_br': [0.198698],
+ 'delay_hl': [0.2108836],
+ 'delay_lh': [0.2108836],
+ 'leakage_power': 0.001564799,
+ 'min_period': 0.508,
+ 'read0_power': [0.43916689999999997],
+ 'read1_power': [0.4198608],
+ 'slew_hl': [0.0455126],
+ 'slew_lh': [0.0455126],
+ 'volt_bl': [0.6472883],
+ 'volt_br': [1.114024],
+ 'write0_power': [0.40681890000000004],
+ 'write1_power': [0.4198608]}
elif OPTS.tech_name == "scn4m_subm":
- golden_data = {'delay_hl': [1.610911],
- 'delay_lh': [1.610911],
- 'leakage_power': 0.0023593859999999998,
+ golden_data = {'delay_bl': [1.3937359999999999],
+ 'delay_br': [1.2596429999999998],
+ 'delay_hl': [1.5747600000000002],
+ 'delay_lh': [1.5747600000000002],
+ 'leakage_power': 0.00195795,
'min_period': 3.281,
- 'read0_power': [20.763569999999998],
- 'read1_power': [20.32745],
- 'slew_hl': [0.7986348999999999],
- 'slew_lh': [0.7986348999999999],
- 'write0_power': [17.58272],
- 'write1_power': [18.523419999999998],
- 'volt_bl': [3.1763],
- 'volt_br': [5.5731],
- 'delay_bl': [1.1133000000000002],
- 'delay_br': [0.9958395]}
+ 'read0_power': [14.92874],
+ 'read1_power': [14.369810000000001],
+ 'slew_hl': [0.49631959999999997],
+ 'slew_lh': [0.49631959999999997],
+ 'volt_bl': [4.132618],
+ 'volt_br': [5.573099],
+ 'write0_power': [13.79953],
+ 'write1_power': [14.369810000000001]}
+
else:
self.assertTrue(False) # other techs fail
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
index 08168bc3..f81a2451 100755
--- a/compiler/tests/config_data.py
+++ b/compiler/tests/config_data.py
@@ -1,3 +1,4 @@
+#Config file used for collecting data.
word_size = 1
num_words = 16
diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py
index 74d97960..ee7f1412 100755
--- a/compiler/tests/testutils.py
+++ b/compiler/tests/testutils.py
@@ -62,6 +62,8 @@ class openram_test(unittest.TestCase):
delay_obj.set_load_slew(load, slew)
delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1))
test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period.
+ delay_obj.create_signal_names()
+ delay_obj.create_measurement_names()
delay_obj.create_measurement_objects()
delay_obj.find_feasible_period_one_port(test_port)
return delay_obj.period
From 5f01a52113de8a5d987ff58ec76c88e5e6fcdd92 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 5 Feb 2019 21:15:12 -0800
Subject: [PATCH 28/77] Fixed some delay model bugs.
---
compiler/characterizer/delay.py | 6 ++++--
compiler/modules/control_logic.py | 15 +++++++++++----
compiler/modules/sense_amp.py | 1 +
compiler/modules/sense_amp_array.py | 2 +-
compiler/pgates/pdriver.py | 13 ++++++-------
5 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index 14bdcd11..9230be55 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -889,7 +889,8 @@ class delay(simulation):
"""
if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0:
debug.warning("Analytical characterization results are not supported for multiport.")
-
+ self.create_signal_names()
+ self.create_measurement_names()
power = self.analytical_power(slews, loads)
port_data = self.get_empty_measure_data_dict()
for slew in slews:
@@ -907,7 +908,8 @@ class delay(simulation):
else:
debug.error("Measurement name not recognized: {}".format(mname),1)
period_margin = 0.1
- sram_data = { "min_period": bank_delay[0]*2*period_margin,
+ risefall_delay = bank_delay[self.read_ports[0]].delay/1e3
+ sram_data = { "min_period":risefall_delay*2*period_margin,
"leakage_power": power.leakage}
return (sram_data,port_data)
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index e81e44ea..9d022324 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -154,10 +154,8 @@ class control_logic(design.design):
self.replica_bitline = factory.create(module_type="replica_bitline",
delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
bitcell_loads=bitcell_loads)
- if self.sram != None: #Calculate delays for potential re-sizing
- self.set_sen_wl_delays()
#Resize if necessary, condition depends on resizing method
- if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match():
+ if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match():
#This resizes to match fall and rise delays, can make the delay chain weird sizes.
stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
self.replica_bitline = factory.create(module_type="replica_bitline",
@@ -177,7 +175,8 @@ class control_logic(design.design):
def get_heuristic_delay_chain_size(self):
"""Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """
- delay_fanout = 2 # This can be anything >=2
+ #FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why?
+ delay_fanout = 3 # This can be anything >=3
# Delay stages Must be non-inverting
if self.words_per_row >= 2:
delay_stages = 4
@@ -246,15 +245,23 @@ class control_logic(design.design):
#If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
WARNING_FANOUT_DIFF = 5
+ stages_close = False
#The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
while True:
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall)
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise)
debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise))
+ if abs(stages_fall-stages_rise) == 1 and not stages_close:
+ stages_close = True
+ safe_fanout_rise = fanout_rise
+ safe_fanout_fall = fanout_fall
+
if stages_fall == stages_rise:
break
elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise):
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
+ fanout_rise = safe_fanout_rise
+ fanout_fall = safe_fanout_fall
break
#There should also be a condition to make sure the fanout does not get too large.
#Otherwise, increase the fanout of delay with the most stages, calculate new stages
diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py
index 8553e42c..9601259c 100644
--- a/compiler/modules/sense_amp.py
+++ b/compiler/modules/sense_amp.py
@@ -48,4 +48,5 @@ class sense_amp(design.design):
"""Get the relative capacitance of sense amp enable gate cin"""
pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx")
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx")
+ #sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
return 2*pmos_cin + nmos_cin
\ No newline at end of file
diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py
index ebf95c95..dc80a13b 100644
--- a/compiler/modules/sense_amp_array.py
+++ b/compiler/modules/sense_amp_array.py
@@ -142,4 +142,4 @@ class sense_amp_array(design.design):
def get_en_cin(self):
"""Get the relative capacitance of all the sense amp enable connections in the array"""
sense_amp_en_cin = self.amp.get_en_cin()
- return sense_amp_en_cin * self.words_per_row
+ return sense_amp_en_cin * self.word_size
diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py
index 3a0e4e78..23f3f4f8 100644
--- a/compiler/pgates/pdriver.py
+++ b/compiler/pgates/pdriver.py
@@ -196,17 +196,16 @@ class pdriver(pgate.pgate):
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A -> Z path"""
-
- cout_list = {}
+ cout_list = []
for prev_inv,inv in zip(self.inv_list, self.inv_list[1:]):
- cout_list[prev_inv]=inv.get_cin()
-
- cout_list[self.inv_list[-1]]=external_cout
+ cout_list.append(inv.get_cin())
+
+ cout_list.append(external_cout)
stage_effort_list = []
last_inp_is_rise = inp_is_rise
- for inv in self.inv_list:
- stage = inv.get_stage_effort(cout_list[inv], last_inp_is_rise)
+ for inv,cout in zip(self.inv_list,cout_list):
+ stage = inv.get_stage_effort(cout, last_inp_is_rise)
stage_effort_list.append(stage)
last_inp_is_rise = stage.is_rise
From 01c8405d12cd5a613005a57db6ed755c16e1e7db Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 6 Feb 2019 00:46:25 -0800
Subject: [PATCH 29/77] Fix bitline measurement delays and adjusted default
delay chain for column mux srams
---
compiler/characterizer/delay.py | 17 ++++++++++++++---
compiler/characterizer/measurements.py | 5 +++--
compiler/modules/control_logic.py | 4 ++--
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py
index 9230be55..7aa9641d 100644
--- a/compiler/characterizer/delay.py
+++ b/compiler/characterizer/delay.py
@@ -70,6 +70,11 @@ class delay(simulation):
self.read_meas_objs[-1].meta_str = "read1"
self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3))
self.read_meas_objs[-1].meta_str = "read0"
+
+ #This will later add a half-period to the spice time delay. Only for reading 0.
+ for obj in self.read_meas_objs:
+ if obj.meta_str is "read0":
+ obj.meta_add_delay = True
trig_name = "Xsram.s_en{}" #Sense amp enable
if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
@@ -104,13 +109,16 @@ class delay(simulation):
self.bitline_delay_objs[-1].meta_str = "read0"
self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
self.bitline_delay_objs[-1].meta_str = "read1"
-
+ #Enforces the time delay on the bitline measurements for read0 or read1
+ for obj in self.bitline_delay_objs:
+ obj.meta_add_delay = True
+
def create_write_port_measurement_objects(self):
"""Create the measurements used for read ports: delays, slews, powers"""
self.write_meas_objs = []
self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3))
- self.write_meas_objs[-1].meta_str = "read1"
+ self.write_meas_objs[-1].meta_str = "write1"
self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3))
self.write_meas_objs[-1].meta_str = "write0"
@@ -284,12 +292,15 @@ class delay(simulation):
#vdd is arguably constant as that is true for a single lib file.
if delay_obj.meta_str == "read0":
#Falling delay are measured starting from neg. clk edge. Delay adjusted to that.
- meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] + self.period/2
+ meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
elif delay_obj.meta_str == "read1":
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
else:
debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1)
+ if delay_obj.meta_add_delay:
+ meas_cycle_delay += self.period/2
+
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
def get_power_measure_variants(self, port, power_obj, operation):
diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py
index aec4d769..e3d16584 100644
--- a/compiler/characterizer/measurements.py
+++ b/compiler/characterizer/measurements.py
@@ -10,8 +10,9 @@ class spice_measurement(ABC):
#Names must be unique for correct spice simulation, but not enforced here.
self.name = measure_name
self.measure_scale = measure_scale
- self.meta_str = None #Some measurements set this, set here to be clear on existence
-
+ #Some meta values used externally. variables are added here for consistency accross the objects
+ self.meta_str = None
+ self.meta_add_delay = False
@abstractmethod
def get_measure_function(self):
return None
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 9d022324..d83cbd0d 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -177,9 +177,9 @@ class control_logic(design.design):
"""Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """
#FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why?
delay_fanout = 3 # This can be anything >=3
- # Delay stages Must be non-inverting
+ # Model poorly captures delay of the column mux. Be pessismistic for column mux
if self.words_per_row >= 2:
- delay_stages = 4
+ delay_stages = 8
else:
delay_stages = 2
From 56e79c050b133014955b7b2d01730c3028cab5f8 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 6 Feb 2019 15:27:29 -0800
Subject: [PATCH 30/77] Changed test values to fix tests.
---
compiler/modules/control_logic.py | 7 ++++++-
compiler/tests/21_hspice_delay_test.py | 26 +++++++++++++-------------
2 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index d83cbd0d..57e578f5 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -182,7 +182,12 @@ class control_logic(design.design):
delay_stages = 8
else:
delay_stages = 2
-
+
+ #Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference
+ #on certain sram configs.
+ if self.port_type == "r":
+ delay_stages+=2
+
return (delay_stages, delay_fanout)
def set_sen_wl_delays(self):
diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py
index a2c40a22..fa7fc35f 100755
--- a/compiler/tests/21_hspice_delay_test.py
+++ b/compiler/tests/21_hspice_delay_test.py
@@ -51,20 +51,20 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
- golden_data = {'delay_bl': [0.19683],
- 'delay_br': [0.19474],
- 'delay_hl': [0.20646],
- 'delay_lh': [0.20646],
- 'leakage_power': 0.0013155,
+ golden_data = {'delay_bl': [0.1980959],
+ 'delay_br': [0.1946091],
+ 'delay_hl': [0.2121267],
+ 'delay_lh': [0.2121267],
+ 'leakage_power': 0.0023761999999999998,
'min_period': 0.43,
- 'read0_power': [0.50758],
- 'read1_power': [0.48225999999999997],
- 'slew_hl': [0.045869],
- 'slew_lh': [0.045869],
- 'volt_bl': [0.6563],
- 'volt_br': [1.117],
- 'write0_power': [0.46124000000000004],
- 'write1_power': [0.48225999999999997]}
+ 'read0_power': [0.5139368],
+ 'read1_power': [0.48940979999999995],
+ 'slew_hl': [0.0516745],
+ 'slew_lh': [0.0516745],
+ 'volt_bl': [0.5374525],
+ 'volt_br': [1.1058],
+ 'write0_power': [0.46267169999999996],
+ 'write1_power': [0.4670826]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_bl': [1.1029],
'delay_br': [0.9656455999999999],
From 690055174d8f9df37118031244f626ca7b060350 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 6 Feb 2019 20:09:01 -0800
Subject: [PATCH 31/77] Fixed bug in control logic test with port configs.
---
compiler/tests/16_control_logic_test.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py
index 91ad0500..e0545af4 100755
--- a/compiler/tests/16_control_logic_test.py
+++ b/compiler/tests/16_control_logic_test.py
@@ -36,17 +36,21 @@ class control_logic_test(openram_test):
# Check port specific control logic
OPTS.num_rw_ports = 1
- OPTS.num_w_ports = 1
- OPTS.num_r_ports = 1
+ OPTS.num_w_ports = 0
+ OPTS.num_r_ports = 0
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="rw")
self.local_check(a)
+ OPTS.num_rw_ports = 0
+ OPTS.num_w_ports = 1
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="w")
self.local_check(a)
+ OPTS.num_w_ports = 0
+ OPTS.num_r_ports = 1
debug.info(1, "Testing sample for control_logic for multiport, only read control logic")
a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="r")
self.local_check(a)
From e131af2cc3bca7a8a3e957fd516379874ab0cdd2 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Wed, 6 Feb 2019 20:31:22 -0800
Subject: [PATCH 32/77] power added to datasheet (finally)
---
compiler/characterizer/lib.py | 40 +++++-
compiler/datasheet/datasheet.py | 5 +-
compiler/datasheet/datasheet_gen.py | 131 +++++++++++++-----
.../example_config_1rw_1r_scn4m_subm.py | 2 +-
4 files changed, 136 insertions(+), 42 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index a63c773c..76d8e3d8 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -507,7 +507,8 @@ class lib:
def parse_info(self,corner,lib_name):
""" Copies important characterization data to datasheet.info to be added to datasheet """
if OPTS.is_unit_test:
- git_id = 'AAAAAAAAAAAAAAAAAAAA'
+ git_id = 'FFFFFFFFFFFFFFFFFFFF'
+
else:
with open(os.devnull, 'wb') as devnull:
proc = subprocess.Popen(['git','rev-parse','HEAD'], cwd=os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/', stdout=subprocess.PIPE)
@@ -524,7 +525,7 @@ class lib:
git_id = 'Failed to retruieve'
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
-
+
current_time = datetime.date.today()
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
@@ -654,8 +655,41 @@ class lib:
))
+ for port in self.all_ports:
+ name = ''
+ read_write = ''
+ if port in self.read_ports:
+ web_name = " & !WEb{0}".format(port)
+ name = "!CSb{0} & clk{0}{1}".format(port, web_name)
+ read_write = 'Read'
+ datasheet.write("{0},{1},{2},{3},".format(
+ "power",
+ name,
+ read_write,
+
+ np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])/2
+ ))
+
+ if port in self.write_ports:
+ web_name = " & WEb{0}".format(port)
+ name = "!CSb{0} & !clk{0}{1}".format(port, web_name)
+ read_write = 'Write'
+
+ datasheet.write("{0},{1},{2},{3},".format(
+ 'power',
+ name,
+ read_write,
+ np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])/2
+
+ ))
+
+ control_str = 'CSb0'
+ for i in range(1, self.total_port_num):
+ control_str += ' & CSb{0}'.format(i)
+
+ datasheet.write("{0},{1},{2},".format('leak', control_str, self.char_sram_results["leakage_power"]))
+
datasheet.write("END\n")
datasheet.close()
-
diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py
index 71d1f9dd..84a6eafc 100644
--- a/compiler/datasheet/datasheet.py
+++ b/compiler/datasheet/datasheet.py
@@ -58,7 +58,7 @@ class datasheet():
# self.html += operating_conditions(self.operating,table_id='data').__html__()
self.html += self.operating_table.to_html()
- self.html += 'Timing and Current Data
'
+ self.html += 'Timing Data
'
model = ''
if self.ANALYTICAL_MODEL:
model = "analytical model: results may not be percise"
@@ -68,6 +68,9 @@ class datasheet():
# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += self.timing_table.to_html()
+ self.html += 'Power Data
'
+ self.html += self.power_table.to_html()
+
self.html += 'Characterization Corners
'
# self.html += characterization_corners(self.corners,table_id='data').__html__()
self.html += self.corners_table.to_html()
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 664f88c5..51c636c8 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -166,31 +166,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -205,31 +205,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('cell fall'):
+ if item[0].endswith('cell fall'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('rise transition'):
+ if item[0].endswith('rise transition'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('fall transition'):
+ if item[0].endswith('fall transition'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -244,31 +244,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -283,31 +283,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -322,31 +322,31 @@ def parse_characterizer_csv(f, pages):
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('setup falling'):
+ if item[0].endswith('setup falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold rising'):
+ if item[0].endswith('hold rising'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
- elif item[0].endswith('hold falling'):
+ if item[0].endswith('hold falling'):
if float(row[col+1]) < float(item[1]):
item[1] = row[col+1]
if float(row[col+2]) > float(item[2]):
item[2] = row[col+2]
- col += 2
+ col += 2
col += 1
@@ -354,8 +354,35 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start: col - 1]:
sheet.description.append(str(element))
break
+ while(True):
+ col_start = col
+ if row[col] == 'power':
+ for item in sheet.power_table.rows:
+ if item[0].startswith(row[col+1]):
+ if item[2].startswith('{0} Rising'.format(row[col+2])):
+ if float(item[2]) < float(row[col+3]):
+ item[2] = row[col+3]
+ if item[2].startswith('{0} Falling'.format(row[col+2])):
+ if float(item[2]) < float(row[col+3]):
+ item[2] = row[col+3]
+ col += 4
+ else:
+ break
- new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ while(True):
+ col_start = col
+ if row[col] == 'leak':
+ for item in sheet.power_table.rows:
+ if item[0].startswith(row[col+1]):
+ if float(item[2]) < float(row[col+2]):
+ item[2] = row[col+2]
+ col += 3
+
+ else:
+ break
+
+ new_sheet.corners_table.add_row(
+ [PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.dlv_table.add_row(
['.lib', 'Synthesis models', '{1}'.format(LIB_NAME, LIB_NAME.replace(OUT_DIR, ''))])
@@ -376,7 +403,8 @@ def parse_characterizer_csv(f, pages):
new_sheet.corners_table = table_gen.table_gen("corners")
new_sheet.corners_table.add_row(
['Transistor Type', 'Power Supply', 'Temperature', 'Corner Name'])
- new_sheet.corners_table.add_row([PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
+ new_sheet.corners_table.add_row(
+ [PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.operating_table = table_gen.table_gen(
"operating_table")
new_sheet.operating_table.add_row(
@@ -393,6 +421,9 @@ def parse_characterizer_csv(f, pages):
# failed to provide non-zero MIN_PERIOD
new_sheet.operating_table.add_row(
['Operating Frequency (F)', '', '', "not available in netlist only", 'MHz'])
+ new_sheet.power_table = table_gen.table_gen("power")
+ new_sheet.power_table.add_row(
+ ['Pins', 'Mode', 'Power', 'Units'])
new_sheet.timing_table = table_gen.table_gen("timing")
new_sheet.timing_table.add_row(
['Parameter', 'Min', 'Max', 'Units'])
@@ -523,6 +554,32 @@ def parse_characterizer_csv(f, pages):
sheet.description.append(str(element))
break
+ while(True):
+ start = col
+ if(row[col].startswith('power')):
+ new_sheet.power_table.add_row([row[col+1],
+ '{0} Rising'.format(
+ row[col+2]),
+ row[col+3][0:6],
+ 'mW']
+ )
+ new_sheet.power_table.add_row([row[col+1],
+ '{0} Falling'.format(
+ row[col+2]),
+ row[col+3][0:6],
+ 'mW']
+ )
+
+ col += 4
+
+ elif(row[col].startswith('leak')):
+ new_sheet.power_table.add_row(
+ [row[col+1], 'leakage', row[col+2], 'mW'])
+ col += 3
+
+ else:
+ break
+
new_sheet.dlv_table = table_gen.table_gen("dlv")
new_sheet.dlv_table.add_row(['Type', 'Description', 'Link'])
diff --git a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
index 4d09cfee..87c1304d 100644
--- a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
+++ b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
@@ -10,7 +10,7 @@ num_w_ports = 0
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
-temperatures = [25]
+temperatures = [25,50]
output_path = "temp"
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,num_words,tech_name)
From d0edda93adeed7e8fd5c23a09b0cee86ddfe5745 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 7 Feb 2019 02:27:22 -0800
Subject: [PATCH 33/77] Added more variance analysis for the delay data
---
compiler/characterizer/model_check.py | 5 +-
compiler/tests/config_data.py | 2 +-
compiler/tests/delay_data_collection.py | 74 ++++++++++++++++++-------
3 files changed, 57 insertions(+), 24 deletions(-)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index b791e520..88e0b96a 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -38,6 +38,7 @@ class model_check(delay):
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
+ self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names))
#Create slew measurement names
wl_en_driver_slew_names = ["slew_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
wl_driver_slew_names = ["slew_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
@@ -61,14 +62,14 @@ class model_check(delay):
["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\
wl_driver_signals+\
["Xsram.Xbank0.wl_{}".format(self.wordline_row)]
- self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"]+\
+ pre_delay_chain_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"]
+ self.rbl_en_signal_names = pre_delay_chain_names+\
delay_chain_signal_names+\
["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\
sen_driver_signals+\
["Xsram.s_en0"]
-
def create_measurement_objects(self):
"""Create the measurements used for read and write ports"""
self.create_wordline_measurement_objects()
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
index f81a2451..849d441d 100755
--- a/compiler/tests/config_data.py
+++ b/compiler/tests/config_data.py
@@ -4,7 +4,7 @@ num_words = 16
tech_name = "freepdk45"
#process_corners = ["TT", "FF", "SS", "SF", "FS"]
-process_corners = ["TT"]
+process_corners = ["TT", "FF", "SS"]
supply_voltages = [1.0]
temperatures = [25]
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index 7c98bccf..3b1fe938 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -20,28 +20,60 @@ MODEL_DIR = "model_data/"
class data_collection(openram_test):
def runTest(self):
- ratio_data = self.calculate_delay_ratios_of_srams()
- self.display_data(ratio_data)
- # word_size, num_words, words_per_row = 4, 16, 1
- # self.init_data_gen()
- # self.set_delay_chain(2,3)
- # self.save_data_sram_corners(word_size, num_words, words_per_row)
- # wl_dataframe, sae_dataframe = self.get_csv_data()
- # self.evaluate_data(wl_dataframe, sae_dataframe)
-
- #Run again but with different delay chain sizes
- # self.init_data_gen()
- # self.set_delay_chain(4,2)
- # self.save_data_sram_corners(word_size, num_words, words_per_row)
- # wl_dataframe, sae_dataframe = self.get_csv_data()
- # self.evaluate_data(wl_dataframe, sae_dataframe)
-
- # model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure()
- # debug.info(1, "model_delay_ratios={}".format(model_delay_ratios))
- # debug.info(1, "meas_delay_ratios={}".format(meas_delay_ratios))
- # debug.info(1, "ratio_error={}".format(ratio_error))
- # globals.end_openram()
+ #Uncomment this for model evaluation
+ # ratio_data = self.calculate_delay_ratios_of_srams()
+ # self.display_data(ratio_data)
+
+ self.run_delay_chain_variation_analysis()
+
+ globals.end_openram()
+
+ def run_delay_chain_variation_analysis(self):
+ """Generates sram with different delay chain configs over different corners and
+ analyzes delay variation."""
+ OPTS.use_tech_delay_chain_size = True
+ #Constant sram config for this test
+ word_size, num_words, words_per_row = 1, 16, 1
+ #Only change delay chain
+ dc_config_list = [(2,3), (3,3), (3,4), (4,2)]
+ dc_vars = []
+ for stages,fanout in dc_config_list:
+ self.init_data_gen()
+ self.set_delay_chain(stages,fanout)
+ self.save_data_sram_corners(word_size, num_words, words_per_row)
+ wl_dataframe, sae_dataframe = self.get_csv_data()
+ var = self.calculate_delay_chain_variance(sae_dataframe)
+ dc_vars.append(var)
+ debug.info(1,"DC config={}, variance={}".format((stages,fanout), var))
+
+ self.plot_data(dc_config_list, dc_vars)
+ #display data
+
+ def calculate_delay_chain_variance(self, sae_dataframe):
+ """Determines the delay variance of the delay chain over corners"""
+ (start_dc, end_dc) = self.delay_obj.delay_chain_indices
+ start_data_pos = len(self.config_fields)+1 #items before this point are configuration related
+ delay_sums = []
+ row_count = 0
+ #Get delay sums over different corners
+ for sae_row in sae_dataframe.itertuples():
+ dc_delays = sae_row[start_data_pos+start_dc:start_data_pos+end_dc]
+ delay_sums.append(sum(dc_delays))
+
+ #calculate mean then variance
+ m = sum(delay_sums) / len(delay_sums)
+ delay_variance = sum((xi - m) ** 2 for xi in delay_sums) / len(delay_sums)
+ return delay_variance
+
+ def plot_data(self, x_labels, y_values):
+ """Display a plot using matplot lib.
+ Assumes input x values are just labels and y values are actual data."""
+ data_range = [i+1 for i in range(len(x_labels))]
+ plt.xticks(data_range, x_labels)
+ plt.plot(data_range, y_values, 'ro')
+ plt.show()
+
def calculate_delay_ratios_of_srams(self):
"""Runs delay measurements on several sram configurations.
Computes the delay ratio for each one."""
From 6cde6beafa86f53ee64df4568679bb46f5d4924e Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 7 Feb 2019 06:33:39 -0800
Subject: [PATCH 34/77] added documetation to functions
---
compiler/characterizer/lib.py | 10 ++++++++--
compiler/datasheet/datasheet.py | 21 ++++++++++-----------
compiler/datasheet/datasheet_gen.py | 14 ++++++++------
compiler/datasheet/table_gen.py | 17 +++++++++++------
4 files changed, 37 insertions(+), 25 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 76d8e3d8..60452d76 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -511,6 +511,7 @@ class lib:
else:
with open(os.devnull, 'wb') as devnull:
+ # parses the mose recent git commit id - requres git is installed
proc = subprocess.Popen(['git','rev-parse','HEAD'], cwd=os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/', stdout=subprocess.PIPE)
git_id = str(proc.stdout.read())
@@ -519,7 +520,7 @@ class lib:
git_id = git_id[2:-3]
except:
pass
-
+ # check if git id is valid
if len(git_id) != 40:
debug.warning("Failed to retrieve git id")
git_id = 'Failed to retruieve'
@@ -527,6 +528,7 @@ class lib:
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
current_time = datetime.date.today()
+ # write static information to be parser later
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},".format(
OPTS.output_name,
OPTS.num_words,
@@ -558,6 +560,7 @@ class lib:
datasheet.write("{0},{1},".format(DRC, LVS))
datasheet.write(str(self.sram.width * self.sram.height)+',')
+ # write timing information for all ports
for port in self.all_ports:
#DIN timings
if port in self.write_ports:
@@ -654,10 +657,12 @@ class lib:
))
-
+ # write power information
for port in self.all_ports:
name = ''
read_write = ''
+
+ # write dynamic power usage
if port in self.read_ports:
web_name = " & !WEb{0}".format(port)
name = "!CSb{0} & clk{0}{1}".format(port, web_name)
@@ -684,6 +689,7 @@ class lib:
))
+ # write leakage power
control_str = 'CSb0'
for i in range(1, self.total_port_num):
control_str += ' & CSb{0}'.format(i)
diff --git a/compiler/datasheet/datasheet.py b/compiler/datasheet/datasheet.py
index 84a6eafc..4b5cb741 100644
--- a/compiler/datasheet/datasheet.py
+++ b/compiler/datasheet/datasheet.py
@@ -28,11 +28,12 @@ class datasheet():
# for item in self.description:
# self.html += item + ','
self.html += '-->'
-
+ # Add vlsida logo
vlsi_logo = 0
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/vlsi_logo.png', "rb") as image_file:
vlsi_logo = base64.b64encode(image_file.read())
+ # Add openram logo
openram_logo = 0
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/openram_logo_placeholder.png', "rb") as image_file:
openram_logo = base64.b64encode(image_file.read())
@@ -49,32 +50,30 @@ class datasheet():
'LVS errors: ' + str(self.LVS) + '
'
self.html += '' + \
'Git commit id: ' + str(self.git_id) + '
'
-
+ # print port table
self.html += 'Ports and Configuration
'
-# self.html += in_out(self.io,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")
self.html += self.io_table.to_html()
-
+
+ # print operating condidition information
self.html += 'Operating Conditions
'
-# self.html += operating_conditions(self.operating,table_id='data').__html__()
self.html += self.operating_table.to_html()
+ # check if analytical model is being used
self.html += 'Timing Data
'
model = ''
if self.ANALYTICAL_MODEL:
model = "analytical model: results may not be percise"
else:
model = "spice characterizer"
+ # display timing data
self.html += 'Using '+model+'
'
-# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += self.timing_table.to_html()
-
+ # display power data
self.html += 'Power Data
'
self.html += self.power_table.to_html()
-
+ # display corner information
self.html += 'Characterization Corners
'
-# self.html += characterization_corners(self.corners,table_id='data').__html__()
self.html += self.corners_table.to_html()
-
+ # display deliverables table
self.html += 'Deliverables
'
-# self.html += deliverables(self.dlv,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")
self.html += self.dlv_table.to_html()
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 51c636c8..44db3f9b 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -4,7 +4,6 @@ This is a script to load data from the characterization and layout processes int
a web friendly html datasheet.
"""
# TODO:
-# include power
# Diagram generation
# Improve css
@@ -152,7 +151,7 @@ def parse_characterizer_csv(f, pages):
1000/float(MIN_PERIOD)))
except Exception:
pass
-
+ # check current .lib file produces the slowest timing results
while(True):
col_start = col
if(row[col].startswith('DIN')):
@@ -354,6 +353,8 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start: col - 1]:
sheet.description.append(str(element))
break
+
+ #check if new power is worse the previous
while(True):
col_start = col
if row[col] == 'power':
@@ -368,7 +369,7 @@ def parse_characterizer_csv(f, pages):
col += 4
else:
break
-
+ # check if new leakge is worse the previous
while(True):
col_start = col
if row[col] == 'leak':
@@ -380,7 +381,7 @@ def parse_characterizer_csv(f, pages):
else:
break
-
+ # add new corner information
new_sheet.corners_table.add_row(
[PROC, VOLT, TEMP, LIB_NAME.replace(OUT_DIR, '').replace(NAME, '')])
new_sheet.dlv_table.add_row(
@@ -427,6 +428,7 @@ def parse_characterizer_csv(f, pages):
new_sheet.timing_table = table_gen.table_gen("timing")
new_sheet.timing_table.add_row(
['Parameter', 'Min', 'Max', 'Units'])
+ # parse initial timing information
while(True):
col_start = col
if(row[col].startswith('DIN')):
@@ -553,7 +555,7 @@ def parse_characterizer_csv(f, pages):
for element in row[col_start:col-1]:
sheet.description.append(str(element))
break
-
+ # parse initial power and leakage information
while(True):
start = col
if(row[col].startswith('power')):
@@ -618,7 +620,7 @@ def parse_characterizer_csv(f, pages):
class datasheet_gen():
def datasheet_write(name):
-
+ """writes the datasheet to a file"""
in_dir = OPTS.openram_temp
if not (os.path.isdir(in_dir)):
diff --git a/compiler/datasheet/table_gen.py b/compiler/datasheet/table_gen.py
index 18590739..8f94e896 100644
--- a/compiler/datasheet/table_gen.py
+++ b/compiler/datasheet/table_gen.py
@@ -1,24 +1,29 @@
class table_gen:
- def __init__(self,name):
+ """small library of functions to generate the html tables"""
+
+ def __init__(self, name):
self.name = name
self.rows = []
self.table_id = 'data'
- def add_row(self,row):
+ def add_row(self, row):
+ """add a row to table_gen object"""
self.rows.append(row)
def gen_table_head(self):
+ """generate html table header"""
html = ''
html += ''
html += ''
for col in self.rows[0]:
- html += '| ' + str(col) + ' | '
+ html += '' + str(col) + ' | '
html += '
'
html += ''
return html
def gen_table_body(self):
+ """generate html body (used after gen_table_head)"""
html = ''
html += ''
@@ -31,13 +36,13 @@ class table_gen:
html += ''
html += ''
return html
-
+
def to_html(self):
-
+ """writes table_gen object to inline html"""
html = ''
html += ''
html += self.gen_table_head()
html += self.gen_table_body()
html += '
'
-
+
return html
From bfc20a9aa9958308adc431bc399046639a440164 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 7 Feb 2019 06:38:07 -0800
Subject: [PATCH 35/77] removes debug corners
---
compiler/example_configs/example_config_1rw_1r_scn4m_subm.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
index 87c1304d..4d09cfee 100644
--- a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
+++ b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py
@@ -10,7 +10,7 @@ num_w_ports = 0
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
-temperatures = [25,50]
+temperatures = [25]
output_path = "temp"
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,num_words,tech_name)
From d9efb682dd662b874cd15fb00cb664f5efc26252 Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Thu, 7 Feb 2019 11:08:34 -0800
Subject: [PATCH 36/77] Do not clean up if preserve temp in local_drc_check
---
README.md | 2 +-
compiler/tests/testutils.py | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index f70a2d87..2141c480 100644
--- a/README.md
+++ b/README.md
@@ -216,7 +216,7 @@ If I forgot to add you, please let me know!
[Github issues]: https://github.com/VLSIDA/PrivateRAM/issues
[Github pull request]: https://github.com/VLSIDA/PrivateRAM/pulls
-[Github projects]: https://github.com/VLSIDA/PrivateRAM/projects
+[Github projects]: https://github.com/VLSIDA/PrivateRAM
[email me]: mailto:mrg+openram@ucsc.edu
[dev-group]: mailto:openram-dev-group@ucsc.edu
diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py
index 6b2a7dcf..274577b5 100755
--- a/compiler/tests/testutils.py
+++ b/compiler/tests/testutils.py
@@ -21,7 +21,8 @@ class openram_test(unittest.TestCase):
if result != 0:
self.fail("DRC failed: {}".format(w.name))
- self.cleanup()
+ if OPTS.purge_temp:
+ self.cleanup()
def local_check(self, a, final_verification=False):
From ebf43298c05097122ac347ea36955778933de8f8 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 7 Feb 2019 14:21:23 -0800
Subject: [PATCH 37/77] Added mean/variance plotting
---
compiler/tests/delay_data_collection.py | 60 +++++++++++++++++++------
1 file changed, 46 insertions(+), 14 deletions(-)
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index 3b1fe938..ce173b45 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -25,33 +25,38 @@ class data_collection(openram_test):
# ratio_data = self.calculate_delay_ratios_of_srams()
# self.display_data(ratio_data)
- self.run_delay_chain_variation_analysis()
+ self.run_delay_chain_analysis()
globals.end_openram()
- def run_delay_chain_variation_analysis(self):
+ def run_delay_chain_analysis(self):
"""Generates sram with different delay chain configs over different corners and
- analyzes delay variation."""
+ analyzes delay average and variation."""
OPTS.use_tech_delay_chain_size = True
#Constant sram config for this test
word_size, num_words, words_per_row = 1, 16, 1
#Only change delay chain
dc_config_list = [(2,3), (3,3), (3,4), (4,2)]
+ #dc_config_list = [(2,3), (3,3)]
+ dc_avgs = []
dc_vars = []
for stages,fanout in dc_config_list:
self.init_data_gen()
self.set_delay_chain(stages,fanout)
self.save_data_sram_corners(word_size, num_words, words_per_row)
wl_dataframe, sae_dataframe = self.get_csv_data()
- var = self.calculate_delay_chain_variance(sae_dataframe)
- dc_vars.append(var)
- debug.info(1,"DC config={}, variance={}".format((stages,fanout), var))
+ delay_sums = self.get_delay_chain_sums(sae_dataframe)
+ dc_avgs.append(self.get_average(delay_sums))
+ dc_vars.append(self.get_variance(delay_sums))
+ debug.info(1,"DC config={}: avg={} variance={}".format((stages,fanout), dc_avgs[-1], dc_vars[-1]))
- self.plot_data(dc_config_list, dc_vars)
- #display data
+ #plot data
+ self.plot_two_data_sets(dc_config_list, dc_avgs, dc_vars)
+ #self.plot_data(dc_config_list, dc_avgs)
+ #self.plot_data(dc_config_list, dc_vars)
- def calculate_delay_chain_variance(self, sae_dataframe):
- """Determines the delay variance of the delay chain over corners"""
+ def get_delay_chain_sums(self, sae_dataframe):
+ """Calculate the total delay of the delay chain over different corners"""
(start_dc, end_dc) = self.delay_obj.delay_chain_indices
start_data_pos = len(self.config_fields)+1 #items before this point are configuration related
delay_sums = []
@@ -60,11 +65,15 @@ class data_collection(openram_test):
for sae_row in sae_dataframe.itertuples():
dc_delays = sae_row[start_data_pos+start_dc:start_data_pos+end_dc]
delay_sums.append(sum(dc_delays))
+ return delay_sums
- #calculate mean then variance
- m = sum(delay_sums) / len(delay_sums)
- delay_variance = sum((xi - m) ** 2 for xi in delay_sums) / len(delay_sums)
+ def get_variance(self, nums):
+ avg = self.get_average(nums)
+ delay_variance = sum((xi - avg) ** 2 for xi in nums) / len(nums)
return delay_variance
+
+ def get_average(self,nums):
+ return sum(nums) / len(nums)
def plot_data(self, x_labels, y_values):
"""Display a plot using matplot lib.
@@ -73,7 +82,30 @@ class data_collection(openram_test):
plt.xticks(data_range, x_labels)
plt.plot(data_range, y_values, 'ro')
plt.show()
-
+
+ def plot_two_data_sets(self, x_labels, y1_values, y2_values):
+ """Plots two data sets on the same x-axis."""
+ data_range = [i for i in range(len(x_labels))]
+ fig, ax1 = plt.subplots()
+
+ color = 'tab:red'
+ ax1.set_xlabel('DC (Stages,Fanout)')
+ ax1.set_ylabel('Average Delay (ns)', color=color)
+ ax1.plot(data_range, y1_values, marker='o', color=color)
+ ax1.tick_params(axis='y', labelcolor=color)
+
+ ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
+
+ color = 'tab:blue'
+ #ax2.set_xticks(data_range, x_labels)
+ ax2.set_ylabel('Delay Variance (ns)', color=color) # we already handled the x-label with ax1
+ ax2.plot(data_range, y2_values, marker='*', color=color)
+ ax2.tick_params(axis='y', labelcolor=color)
+
+ fig.tight_layout() # otherwise the right y-label is slightly clipped
+ plt.xticks(data_range, x_labels)
+ plt.show()
+
def calculate_delay_ratios_of_srams(self):
"""Runs delay measurements on several sram configurations.
Computes the delay ratio for each one."""
From 9e23e6584aede75d74a2778268985341db02b290 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Thu, 7 Feb 2019 15:30:47 -0800
Subject: [PATCH 38/77] Made variance plot look slightly better.
---
compiler/tests/delay_data_collection.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index ce173b45..e893d8ec 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -36,7 +36,7 @@ class data_collection(openram_test):
#Constant sram config for this test
word_size, num_words, words_per_row = 1, 16, 1
#Only change delay chain
- dc_config_list = [(2,3), (3,3), (3,4), (4,2)]
+ dc_config_list = [(2,3), (3,3), (3,4), (4,2), (4,3), (4,4), (2,4), (2,5)]
#dc_config_list = [(2,3), (3,3)]
dc_avgs = []
dc_vars = []
@@ -91,7 +91,7 @@ class data_collection(openram_test):
color = 'tab:red'
ax1.set_xlabel('DC (Stages,Fanout)')
ax1.set_ylabel('Average Delay (ns)', color=color)
- ax1.plot(data_range, y1_values, marker='o', color=color)
+ ax1.plot(data_range, y1_values, marker='o', color=color, linestyle='')
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
@@ -99,7 +99,7 @@ class data_collection(openram_test):
color = 'tab:blue'
#ax2.set_xticks(data_range, x_labels)
ax2.set_ylabel('Delay Variance (ns)', color=color) # we already handled the x-label with ax1
- ax2.plot(data_range, y2_values, marker='*', color=color)
+ ax2.plot(data_range, y2_values, marker='*', color=color, linestyle='')
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # otherwise the right y-label is slightly clipped
From a4bb4816121528801bc27429a19e473ef530af0c Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 12 Feb 2019 16:28:13 -0800
Subject: [PATCH 39/77] Added tracking for available data.
---
compiler/characterizer/model_check.py | 4 ++
compiler/characterizer/stimuli.py | 5 +-
compiler/tests/config_data.py | 11 +++-
compiler/tests/delay_data_collection.py | 86 +++++++++++++++++++++----
4 files changed, 87 insertions(+), 19 deletions(-)
diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py
index 88e0b96a..874bb2d0 100644
--- a/compiler/characterizer/model_check.py
+++ b/compiler/characterizer/model_check.py
@@ -229,6 +229,10 @@ class model_check(delay):
"""Gets the number of stages in the delay chain from the control logic"""
return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list)
+ def get_num_delay_fanout_list(self):
+ """Gets the number of stages in the delay chain from the control logic"""
+ return self.sram.control_logic_rw.replica_bitline.delay_fanout_list
+
def get_num_delay_stage_fanout(self):
"""Gets fanout in each stage in the delay chain. Assumes each stage is the same"""
return self.sram.control_logic_rw.replica_bitline.delay_fanout_list[0]
diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py
index 8c6b059f..3b569b2a 100644
--- a/compiler/characterizer/stimuli.py
+++ b/compiler/characterizer/stimuli.py
@@ -258,14 +258,15 @@ class stimuli():
# UIC is needed for ngspice to converge
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep,end_time))
+ self.sf.write(".TEMP {}\n".format(self.temperature))
if OPTS.spice_name == "ngspice":
# ngspice sometimes has convergence problems if not using gear method
# which is more accurate, but slower than the default trapezoid method
# Do not remove this or it may not converge due to some "pa_00" nodes
# unless you figure out what these are.
- self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear TEMP={1}\n".format(reltol,self.temperature))
+ self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear\n".format(reltol))
else:
- self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature))
+ self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl))
# create plots for all signals
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
index 849d441d..9d89acd7 100755
--- a/compiler/tests/config_data.py
+++ b/compiler/tests/config_data.py
@@ -3,9 +3,14 @@ word_size = 1
num_words = 16
tech_name = "freepdk45"
-#process_corners = ["TT", "FF", "SS", "SF", "FS"]
-process_corners = ["TT", "FF", "SS"]
+#Default corner
+process_corners = ["TT"]
supply_voltages = [1.0]
-temperatures = [25]
+#temperatures = [25]
+
+#Corner options
+#process_corners = ["TT", "FF", "SS"]
+#supply_voltages = [.9, 1.0, 1.1]
+temperatures = [10,25,50]
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index e893d8ec..3c5193ea 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -16,6 +16,7 @@ from sram import sram
from sram_config import sram_config
MODEL_DIR = "model_data/"
+DATASET_CSV_NAME = MODEL_DIR+'datasets.csv'
class data_collection(openram_test):
@@ -24,11 +25,56 @@ class data_collection(openram_test):
#Uncomment this for model evaluation
# ratio_data = self.calculate_delay_ratios_of_srams()
# self.display_data(ratio_data)
-
+
+ self.setup_files()
+
self.run_delay_chain_analysis()
globals.end_openram()
+ def setup_files(self):
+ """Checks existence of files used for data collection"""
+ if not os.path.isdir(MODEL_DIR):
+ os.mkdir(MODEL_DIR)
+
+ #File requires names from the delay measurement object.
+ #Initialization is delayed until one configuration simulation has occurred.
+ self.dataset_initialized=False
+
+
+ def init_dataset_csv(self, file_fields):
+ """Creates csv which holds files names of all available datasets."""
+ debug.info(2,'Initializing dataset file and dataframe.')
+ self.file_fields = file_fields
+ self.file_fields.sort()
+ fields = ['word_size', 'num_words', 'words_per_row', 'dc_config']
+ self.dataset_fields = fields+self.file_fields
+
+ if not os.path.exists(DATASET_CSV_NAME):
+ debug.info(2,'No dataset file found. Creating dataset file.')
+ dataset_csv = open(DATASET_CSV_NAME, 'w')
+ csv_writer = csv.writer(dataset_csv, lineterminator = '\n')
+ csv_writer.writerow(self.dataset_fields)
+ dataset_csv.close()
+ self.dataset_initialized=True
+ self.datasets_df = pd.read_csv(DATASET_CSV_NAME, encoding='utf-8')
+
+ def add_dataset(self, word_size, num_words, words_per_row):
+ """Added filenames to DATASET_CSV_NAME"""
+ cur_len = len(self.datasets_df)
+ #list converted to str as lists as saved as str in the csv file.
+ #e.g. list=[2,2] -> csv entry = '[2,2]'
+ fanout_str = str(self.delay_obj.get_num_delay_fanout_list())
+ file_names = [self.file_name_dict[fname] for fname in self.file_fields]
+ new_df_row = [word_size, num_words, words_per_row,fanout_str]+file_names
+
+ df_bool = (self.datasets_df['word_size'] == word_size) & (self.datasets_df['num_words'] == num_words) & (self.datasets_df['words_per_row'] == words_per_row) & (self.datasets_df['dc_config'] == fanout_str)
+ if len(self.datasets_df.loc[df_bool]) == 0:
+ self.datasets_df = self.datasets_df.append(pd.Series(new_df_row, self.dataset_fields), ignore_index=True)
+ else:
+ self.datasets_df.loc[df_bool] = [new_df_row]
+
+
def run_delay_chain_analysis(self):
"""Generates sram with different delay chain configs over different corners and
analyzes delay average and variation."""
@@ -84,7 +130,7 @@ class data_collection(openram_test):
plt.show()
def plot_two_data_sets(self, x_labels, y1_values, y2_values):
- """Plots two data sets on the same x-axis."""
+ """Plots two data sets on the same x-axis. Uses hardcoded axis names."""
data_range = [i for i in range(len(x_labels))]
fig, ax1 = plt.subplots()
@@ -123,8 +169,9 @@ class data_collection(openram_test):
def get_csv_data(self):
"""Hardcoded Hack to get the measurement data from the csv into lists. """
- wl_files_name = [file_name for file_name in self.file_names if "wl_measures" in file_name][0]
- sae_files_name = [file_name for file_name in self.file_names if "sae_measures" in file_name][0]
+ file_list = self.file_name_dict.values()
+ wl_files_name = [file_name for file_name in file_list if "wl_measures" in file_name][0]
+ sae_files_name = [file_name for file_name in file_list if "sae_measures" in file_name][0]
wl_dataframe = pd.read_csv(wl_files_name,encoding='utf-8')
sae_dataframe = pd.read_csv(sae_files_name,encoding='utf-8')
return wl_dataframe,sae_dataframe
@@ -144,10 +191,11 @@ class data_collection(openram_test):
meas_delay_ratios = {}
ratio_error = {}
#The full file name contains unrelated portions, separate them into the four that are needed
- wl_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_measures" in file_name][0]
- sae_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_measures" in file_name][0]
- wl_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_model" in file_name][0]
- sae_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_model" in file_name][0]
+ file_list = self.file_name_dict.values()
+ wl_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in file_list if "wl_measures" in file_name][0]
+ sae_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in file_list if "sae_measures" in file_name][0]
+ wl_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in file_list if "wl_model" in file_name][0]
+ sae_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in file_list if "sae_model" in file_name][0]
#Assume each csv has the same corners (and the same row order), use one of the dfs for corners
proc_pos, volt_pos, temp_pos = wl_meas_df.columns.get_loc('process'), wl_meas_df.columns.get_loc('voltage'), wl_meas_df.columns.get_loc('temp')
@@ -238,7 +286,12 @@ class data_collection(openram_test):
for corner in corner_gen:
sram_data = self.get_sram_data(corner)
self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, corner)
-
+
+ #Save file names generated by this run
+ if not self.dataset_initialized:
+ self.init_dataset_csv(list(sram_data))
+ self.add_dataset(word_size, num_words, words_per_row)
+
self.close_files()
debug.info(1,"Data Generated")
@@ -266,8 +319,12 @@ class data_collection(openram_test):
def close_files(self):
"""Closes all files stored in the file dict"""
+ #Close the files holding data
for key,file in self.csv_files.items():
file.close()
+
+ #Write dataframe to the dataset csv
+ self.datasets_df.to_csv(DATASET_CSV_NAME, index=False)
def corner_combination_generator(self):
processes = OPTS.process_corners
@@ -311,19 +368,20 @@ class data_collection(openram_test):
header_dict = self.delay_obj.get_all_signal_names()
self.csv_files = {}
self.csv_writers = {}
- self.file_names = []
+ self.file_name_dict = {}
+ delay_fanout_list = self.delay_obj.get_num_delay_fanout_list()
+ fanout_str = '_'.join(str(fanout) for fanout in delay_fanout_list)
delay_stages = self.delay_obj.get_num_delay_stages()
delay_stage_fanout = self.delay_obj.get_num_delay_stage_fanout()
for data_name, header_list in header_dict.items():
- file_name = '{}data_{}b_{}word_{}way_dc{}x{}_{}.csv'.format(MODEL_DIR,
+ file_name = '{}data_{}b_{}word_{}way_dc{}_{}.csv'.format(MODEL_DIR,
word_size,
num_words,
words_per_row,
- delay_stages,
- delay_stage_fanout,
+ fanout_str,
data_name)
- self.file_names.append(file_name)
+ self.file_name_dict[data_name] = file_name
self.csv_files[data_name] = open(file_name, 'w')
self.config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_resized', 'process', 'voltage', 'temp']
self.other_data_fields = ['sum']
From 4faec524095ee156881eb0ff7f101977461e9a22 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Tue, 12 Feb 2019 20:58:50 -0800
Subject: [PATCH 40/77] Allowed data collection and analysis to run
independently.
---
compiler/tests/delay_data_collection.py | 60 +++++++++++++++++--------
1 file changed, 42 insertions(+), 18 deletions(-)
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index 3c5193ea..d58d13b8 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -75,6 +75,26 @@ class data_collection(openram_test):
self.datasets_df.loc[df_bool] = [new_df_row]
+ def get_filename_by_config(self, data_types, word_size, num_words, words_per_row, fanout_list):
+ """Searches the dataset csv for a config match. Extracts the filenames for the desired data."""
+ #Names used in file are hardcoded here. There is a check that these names match in the csv.
+ start_fname_ind = 4 # four query items
+
+ #data_types = ["wl_measures", "wl_model","sae_measures", "sae_model"]
+ fanout_str = str(fanout_list)
+ datasets_df = pd.read_csv(DATASET_CSV_NAME, encoding='utf-8')
+ df_bool = (datasets_df['word_size'] == word_size) & (datasets_df['num_words'] == num_words) & (datasets_df['words_per_row'] == words_per_row) & (datasets_df['dc_config'] == fanout_str)
+ df_filtered = self.datasets_df.loc[df_bool]
+ if len(df_filtered) != 1:
+ debug.info(1,"Found two dataset entries with the same configuration. Using the first found")
+ df_row = df_filtered.iloc[0]
+ #Check csv header against expected
+ csv_data_types = list(df_filtered)[start_fname_ind+1:]
+ if set(data_types) != set(csv_data_types):
+ debug.error("Dataset csv header does not match expected.")
+
+ return [df_row[dt] for dt in data_types]
+
def run_delay_chain_analysis(self):
"""Generates sram with different delay chain configs over different corners and
analyzes delay average and variation."""
@@ -82,25 +102,33 @@ class data_collection(openram_test):
#Constant sram config for this test
word_size, num_words, words_per_row = 1, 16, 1
#Only change delay chain
- dc_config_list = [(2,3), (3,3), (3,4), (4,2), (4,3), (4,4), (2,4), (2,5)]
- #dc_config_list = [(2,3), (3,3)]
- dc_avgs = []
- dc_vars = []
+ #dc_config_list = [(2,3), (3,3), (3,4), (4,2), (4,3), (4,4), (2,4), (2,5)]
+ dc_config_list = [(2,3), (3,3)]
+ self.save_delay_chain_data(word_size, num_words, words_per_row, dc_config_list)
+ self.analyze_delay_chain_data(word_size, num_words, words_per_row, dc_config_list)
+
+ def save_delay_chain_data(self, word_size, num_words, words_per_row, dc_config_list):
+ """Get the delay data by only varying the delay chain size."""
for stages,fanout in dc_config_list:
self.init_data_gen()
self.set_delay_chain(stages,fanout)
self.save_data_sram_corners(word_size, num_words, words_per_row)
- wl_dataframe, sae_dataframe = self.get_csv_data()
+
+ def analyze_delay_chain_data(self, word_size, num_words, words_per_row, dc_config_list):
+ """Compare and graph delay chain variations over different configurations."""
+ dc_avgs = []
+ dc_vars = []
+ for stages,fanout in dc_config_list:
+ data_types = ["wl_measures","sae_measures"]
+ filenames = self.get_filename_by_config(data_types, word_size, num_words, words_per_row, [fanout]*stages)
+ wl_dataframe, sae_dataframe = self.get_csv_data(filenames)
+
delay_sums = self.get_delay_chain_sums(sae_dataframe)
dc_avgs.append(self.get_average(delay_sums))
dc_vars.append(self.get_variance(delay_sums))
debug.info(1,"DC config={}: avg={} variance={}".format((stages,fanout), dc_avgs[-1], dc_vars[-1]))
-
- #plot data
self.plot_two_data_sets(dc_config_list, dc_avgs, dc_vars)
- #self.plot_data(dc_config_list, dc_avgs)
- #self.plot_data(dc_config_list, dc_vars)
-
+
def get_delay_chain_sums(self, sae_dataframe):
"""Calculate the total delay of the delay chain over different corners"""
(start_dc, end_dc) = self.delay_obj.delay_chain_indices
@@ -167,14 +195,10 @@ class data_collection(openram_test):
debug.info(1, "Ratio percentage error={}".format(ratio_error))
return delay_ratio_data
- def get_csv_data(self):
- """Hardcoded Hack to get the measurement data from the csv into lists. """
- file_list = self.file_name_dict.values()
- wl_files_name = [file_name for file_name in file_list if "wl_measures" in file_name][0]
- sae_files_name = [file_name for file_name in file_list if "sae_measures" in file_name][0]
- wl_dataframe = pd.read_csv(wl_files_name,encoding='utf-8')
- sae_dataframe = pd.read_csv(sae_files_name,encoding='utf-8')
- return wl_dataframe,sae_dataframe
+ def get_csv_data(self, filenames):
+ """Returns a dataframe for each file name. Returns as tuple for convenience"""
+ dataframes = [pd.read_csv(fname,encoding='utf-8') for fname in filenames]
+ return tuple(dataframes)
def evaluate_data(self, wl_dataframe, sae_dataframe):
"""Analyze the delay error and variation error"""
From e890c0e188ee8cf03bdf887d40289bd236207251 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Wed, 13 Feb 2019 15:21:16 -0800
Subject: [PATCH 41/77] fixed -v logging
---
compiler/debug.py | 36 +++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/compiler/debug.py b/compiler/debug.py
index e6c6a1bd..f25ff4b5 100644
--- a/compiler/debug.py
+++ b/compiler/debug.py
@@ -16,7 +16,7 @@ def check(check, str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- log("ERROR: file {0}: line {1}: {2}\n".format(
+ logger.log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert 0
@@ -27,7 +27,7 @@ def error(str, return_value=0):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- log("ERROR: file {0}: line {1}: {2}\n".format(
+ logger.log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert return_value == 0
@@ -38,7 +38,7 @@ def warning(str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- log("WARNING: file {0}: line {1}: {2}\n".format(
+ logger.log("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
@@ -48,17 +48,27 @@ def print_raw(str):
def log(str):
- if log.create_file:
- compile_log = open(globals.OPTS.output_path +
- globals.OPTS.output_name + '.log', "w")
- log.create_file = 0
+ if globals.OPTS.output_name != '':
+ if log.create_file:
+ compile_log = open(globals.OPTS.output_path +
+ globals.OPTS.output_name + '.log', "w+")
+ log.create_file = 0
+ else:
+ compile_log = open(globals.OPTS.output_path +
+ globals.OPTS.output_name + '.log', "a")
+
+ if len(log.setup_output) != 0:
+ for line in log.setup_output:
+ compile_log.write(line)
+ log.setup_output = []
+ compile_log.write(str + '\n')
else:
- compile_log = open(globals.OPTS.output_path +
- globals.OPTS.output_name + '.log', "a")
- compile_log.write(str + '\n')
-log.create_file = 1
+ log.setup_output.append(str + "\n")
+# use a static list of strings to store messages until the global paths are set up
+log.setup_output = []
+log.create_file = 1
def info(lev, str):
@@ -71,5 +81,5 @@ def info(lev, str):
class_name = ""
else:
class_name = mod.__name__
- print_raw("[{0}/{1}]: {2}".format(class_name, frm[0].f_code.co_name, str))
-
+ print_raw("[{0}/{1}]: {2}".format(class_name,
+ frm[0].f_code.co_name, str))
From c359bbf42ab42907134853214da020a3a7ff7433 Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Wed, 13 Feb 2019 17:01:26 -0800
Subject: [PATCH 42/77] Fix deprecation warnings in regex by converting to raw
strings. Add error if unable to find DRC errors in Magic.
---
compiler/verify/calibre.py | 10 +++++-----
compiler/verify/magic.py | 4 ++++
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py
index a1bbbc3e..dde7ea5a 100644
--- a/compiler/verify/calibre.py
+++ b/compiler/verify/calibre.py
@@ -126,9 +126,9 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
f.close()
# those lines should be the last 3
results = results[-3:]
- geometries = int(re.split("\W+", results[0])[5])
- rulechecks = int(re.split("\W+", results[1])[4])
- errors = int(re.split("\W+", results[2])[5])
+ geometries = int(re.split(r'\W+', results[0])[5])
+ rulechecks = int(re.split(r'\W+', results[1])[4])
+n errors = int(re.split(r'\W+', results[2])[5])
# always display this summary
if errors > 0:
@@ -227,7 +227,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
incorrect = list(filter(test.search, results))
# Errors begin with "Error:"
- test = re.compile("\s+Error:")
+ test = re.compile(r'\s+Error:')
errors = list(filter(test.search, results))
for e in errors:
debug.error(e.strip("\n"))
@@ -363,7 +363,7 @@ def correct_port(name, output_file_name, ref_file_name):
pex_file.seek(match_index_start)
rest_text = pex_file.read()
# locate the end of circuit definition line
- match = re.search("\* \n", rest_text)
+ match = re.search(r'\* \n', rest_text)
match_index_end = match.start()
# store the unchanged part of pex file in memory
pex_file.seek(0)
diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py
index 490a09a1..0af8e5ef 100644
--- a/compiler/verify/magic.py
+++ b/compiler/verify/magic.py
@@ -140,11 +140,15 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False):
debug.error("Unable to retrieve DRC results file. Is magic set up?",1)
results = f.readlines()
f.close()
+ errors=1
# those lines should be the last 3
for line in results:
if "Total DRC errors found:" in line:
errors = int(re.split(": ", line)[1])
break
+ else:
+ debug.error("Unable to find the total error line in Magic output.",1)
+
# always display this summary
if errors > 0:
From d4c21cd26e848b7cdf503edbdcbbd4639d42237f Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Wed, 13 Feb 2019 17:41:33 -0800
Subject: [PATCH 43/77] Remove extraneous character.
---
compiler/verify/calibre.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py
index dde7ea5a..2de76386 100644
--- a/compiler/verify/calibre.py
+++ b/compiler/verify/calibre.py
@@ -128,7 +128,7 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
results = results[-3:]
geometries = int(re.split(r'\W+', results[0])[5])
rulechecks = int(re.split(r'\W+', results[1])[4])
-n errors = int(re.split(r'\W+', results[2])[5])
+ errors = int(re.split(r'\W+', results[2])[5])
# always display this summary
if errors > 0:
From 8c1fe253d5656c85b4556c16a89de83ebe47f085 Mon Sep 17 00:00:00 2001
From: Hunter Nichols
Date: Wed, 13 Feb 2019 22:24:58 -0800
Subject: [PATCH 44/77] Added variable fanouts to delay testing.
---
compiler/modules/control_logic.py | 7 +-
compiler/tests/config_data.py | 8 +-
compiler/tests/delay_data_collection.py | 118 +++++++++++++++---------
technology/freepdk45/tech/tech.py | 1 +
technology/scn4m_subm/tech/tech.py | 1 +
5 files changed, 81 insertions(+), 54 deletions(-)
diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py
index 57e578f5..b8532ee9 100644
--- a/compiler/modules/control_logic.py
+++ b/compiler/modules/control_logic.py
@@ -139,11 +139,10 @@ class control_logic(design.design):
bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
#Use a model to determine the delays with that heuristic
if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
- delay_stages = parameter["static_delay_stages"]
- delay_fanout = parameter["static_fanout_per_stage"]
- debug.info(1, "Using tech parameters to size delay chain: stages={}, fanout={}".format(delay_stages,delay_fanout))
+ fanout_list = parameter["static_fanout_list"]
+ debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list))
self.replica_bitline = factory.create(module_type="replica_bitline",
- delay_fanout_list=[delay_fanout]*delay_stages,
+ delay_fanout_list=fanout_list,
bitcell_loads=bitcell_loads)
if self.sram != None: #Calculate model value even for specified sizes
self.set_sen_wl_delays()
diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py
index 9d89acd7..dd78ebcd 100755
--- a/compiler/tests/config_data.py
+++ b/compiler/tests/config_data.py
@@ -4,13 +4,13 @@ num_words = 16
tech_name = "freepdk45"
#Default corner
-process_corners = ["TT"]
+#process_corners = ["TT"]
supply_voltages = [1.0]
-#temperatures = [25]
+temperatures = [25]
#Corner options
-#process_corners = ["TT", "FF", "SS"]
+process_corners = ["TT", "FF", "SS"]
#supply_voltages = [.9, 1.0, 1.1]
-temperatures = [10,25,50]
+#temperatures = [10,25,50]
diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py
index d58d13b8..3e56909f 100644
--- a/compiler/tests/delay_data_collection.py
+++ b/compiler/tests/delay_data_collection.py
@@ -26,29 +26,34 @@ class data_collection(openram_test):
# ratio_data = self.calculate_delay_ratios_of_srams()
# self.display_data(ratio_data)
- self.setup_files()
+ self.run_setup()
self.run_delay_chain_analysis()
globals.end_openram()
- def setup_files(self):
- """Checks existence of files used for data collection"""
+ def run_setup(self):
+ """Checks file existence and sets some member variables"""
if not os.path.isdir(MODEL_DIR):
os.mkdir(MODEL_DIR)
#File requires names from the delay measurement object.
#Initialization is delayed until one configuration simulation has occurred.
self.dataset_initialized=False
+
+ #These help mark positions in the csv file for data collection and analysis
+ self.config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_resized', 'process', 'voltage', 'temp']
+ self.sae_config_fields = ['dc_start_ind', 'dc_end_ind']
+ self.other_data_fields = ['sum']
def init_dataset_csv(self, file_fields):
"""Creates csv which holds files names of all available datasets."""
debug.info(2,'Initializing dataset file and dataframe.')
- self.file_fields = file_fields
- self.file_fields.sort()
- fields = ['word_size', 'num_words', 'words_per_row', 'dc_config']
- self.dataset_fields = fields+self.file_fields
+ self.dataset_file_fields = file_fields
+ self.dataset_file_fields.sort()
+ config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_config']
+ self.dataset_fields = config_fields+self.dataset_file_fields
if not os.path.exists(DATASET_CSV_NAME):
debug.info(2,'No dataset file found. Creating dataset file.')
@@ -65,7 +70,7 @@ class data_collection(openram_test):
#list converted to str as lists as saved as str in the csv file.
#e.g. list=[2,2] -> csv entry = '[2,2]'
fanout_str = str(self.delay_obj.get_num_delay_fanout_list())
- file_names = [self.file_name_dict[fname] for fname in self.file_fields]
+ file_names = [self.file_name_dict[fname] for fname in self.dataset_file_fields]
new_df_row = [word_size, num_words, words_per_row,fanout_str]+file_names
df_bool = (self.datasets_df['word_size'] == word_size) & (self.datasets_df['num_words'] == num_words) & (self.datasets_df['words_per_row'] == words_per_row) & (self.datasets_df['dc_config'] == fanout_str)
@@ -77,21 +82,26 @@ class data_collection(openram_test):
def get_filename_by_config(self, data_types, word_size, num_words, words_per_row, fanout_list):
"""Searches the dataset csv for a config match. Extracts the filenames for the desired data."""
- #Names used in file are hardcoded here. There is a check that these names match in the csv.
start_fname_ind = 4 # four query items
- #data_types = ["wl_measures", "wl_model","sae_measures", "sae_model"]
fanout_str = str(fanout_list)
datasets_df = pd.read_csv(DATASET_CSV_NAME, encoding='utf-8')
df_bool = (datasets_df['word_size'] == word_size) & (datasets_df['num_words'] == num_words) & (datasets_df['words_per_row'] == words_per_row) & (datasets_df['dc_config'] == fanout_str)
- df_filtered = self.datasets_df.loc[df_bool]
- if len(df_filtered) != 1:
- debug.info(1,"Found two dataset entries with the same configuration. Using the first found")
+ df_filtered = datasets_df.loc[df_bool]
+ if len(df_filtered) > 1:
+ debug.info(1,"Found more than 1 dataset entry with the same configuration. Using the first found")
+ elif len(df_filtered) == 0:
+ debug.error("Dataset for configuration not found:\n \
+ word_size={}, num_words={}, words_per_row={}, fanout_list={}".format(word_size,
+ num_words,
+ words_per_row,
+ fanout_list), 1)
df_row = df_filtered.iloc[0]
#Check csv header against expected
- csv_data_types = list(df_filtered)[start_fname_ind+1:]
- if set(data_types) != set(csv_data_types):
- debug.error("Dataset csv header does not match expected.")
+ csv_data_types = list(df_filtered)[start_fname_ind:]
+ if not set(data_types).issubset(set(csv_data_types)):
+ debug.error("Dataset csv header does not match expected:\nExpected={}\nCSV={}".format(data_types,
+ csv_data_types),1)
return [df_row[dt] for dt in data_types]
@@ -103,40 +113,46 @@ class data_collection(openram_test):
word_size, num_words, words_per_row = 1, 16, 1
#Only change delay chain
#dc_config_list = [(2,3), (3,3), (3,4), (4,2), (4,3), (4,4), (2,4), (2,5)]
- dc_config_list = [(2,3), (3,3)]
- self.save_delay_chain_data(word_size, num_words, words_per_row, dc_config_list)
- self.analyze_delay_chain_data(word_size, num_words, words_per_row, dc_config_list)
+ #dc_config_list = [(2,3), (3,3)]
+
+ #fanout_configs = [[3,3], [3,3,3]]
+ previous_dc_configs = [[4,4,4], [2,2,2,2], [3,3,3,3], [4,4],[4,4,4,4], [5,5]]
+ fanout_configs = [[3,3], [3,3,3], [2,3,2,3], [2,4,2,4], [3,3,3,3]]+previous_dc_configs
+ #self.save_delay_chain_data(word_size, num_words, words_per_row, fanout_configs)
+ self.analyze_delay_chain_data(word_size, num_words, words_per_row, fanout_configs)
- def save_delay_chain_data(self, word_size, num_words, words_per_row, dc_config_list):
+ def save_delay_chain_data(self, word_size, num_words, words_per_row, fanout_configs):
"""Get the delay data by only varying the delay chain size."""
- for stages,fanout in dc_config_list:
+ for fanouts in fanout_configs:
self.init_data_gen()
- self.set_delay_chain(stages,fanout)
+ self.set_delay_chain(fanouts)
self.save_data_sram_corners(word_size, num_words, words_per_row)
- def analyze_delay_chain_data(self, word_size, num_words, words_per_row, dc_config_list):
+ def analyze_delay_chain_data(self, word_size, num_words, words_per_row, fanout_configs):
"""Compare and graph delay chain variations over different configurations."""
dc_avgs = []
dc_vars = []
- for stages,fanout in dc_config_list:
+ for fanouts in fanout_configs:
data_types = ["wl_measures","sae_measures"]
- filenames = self.get_filename_by_config(data_types, word_size, num_words, words_per_row, [fanout]*stages)
+ filenames = self.get_filename_by_config(data_types, word_size, num_words, words_per_row, fanouts)
wl_dataframe, sae_dataframe = self.get_csv_data(filenames)
delay_sums = self.get_delay_chain_sums(sae_dataframe)
dc_avgs.append(self.get_average(delay_sums))
dc_vars.append(self.get_variance(delay_sums))
- debug.info(1,"DC config={}: avg={} variance={}".format((stages,fanout), dc_avgs[-1], dc_vars[-1]))
- self.plot_two_data_sets(dc_config_list, dc_avgs, dc_vars)
+ debug.info(1,"DC config={}: avg={} variance={}".format(fanouts, dc_avgs[-1], dc_vars[-1]))
+ self.plot_two_data_sets(fanout_configs, dc_avgs, dc_vars)
def get_delay_chain_sums(self, sae_dataframe):
"""Calculate the total delay of the delay chain over different corners"""
- (start_dc, end_dc) = self.delay_obj.delay_chain_indices
- start_data_pos = len(self.config_fields)+1 #items before this point are configuration related
+ start_dc_pos = sae_dataframe.columns.get_loc('dc_start_ind')
+ end_dc_pos = sae_dataframe.columns.get_loc('dc_end_ind')
+ start_data_pos = len(self.config_fields)+len(self.sae_config_fields)+1 #items before this point are configuration related
delay_sums = []
row_count = 0
#Get delay sums over different corners
- for sae_row in sae_dataframe.itertuples():
+ for sae_row in sae_dataframe.itertuples():
+ start_dc, end_dc = sae_row[start_dc_pos+1], sae_row[end_dc_pos+1]
dc_delays = sae_row[start_data_pos+start_dc:start_data_pos+end_dc]
delay_sums.append(sum(dc_delays))
return delay_sums
@@ -167,6 +183,7 @@ class data_collection(openram_test):
ax1.set_ylabel('Average Delay (ns)', color=color)
ax1.plot(data_range, y1_values, marker='o', color=color, linestyle='')
ax1.tick_params(axis='y', labelcolor=color)
+ ax1.tick_params(axis='x', labelrotation=-90)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
@@ -177,7 +194,7 @@ class data_collection(openram_test):
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # otherwise the right y-label is slightly clipped
- plt.xticks(data_range, x_labels)
+ plt.xticks(data_range, x_labels, rotation=90)
plt.show()
def calculate_delay_ratios_of_srams(self):
@@ -258,34 +275,36 @@ class data_collection(openram_test):
def calculate_delay_error(self, wl_dataframe, sae_dataframe):
"""Calculates the percentage difference in delays between the wordline and sense amp enable"""
- start_data_pos = len(self.config_fields) #items before this point are configuration related
+ wl_start_data_pos = len(self.config_fields)
+ sae_start_data_pos = len(self.config_fields)+len(self.sae_config_fields)
error_list = []
row_count = 0
for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
debug.info(2, "wl_row:{}".format(wl_row))
- wl_sum = sum(wl_row[start_data_pos+1:])
+ wl_sum = sum(wl_row[wl_start_data_pos+1:])
debug.info(2, "wl_sum:{}".format(wl_sum))
- sae_sum = sum(sae_row[start_data_pos+1:])
+ sae_sum = sum(sae_row[sae_start_data_pos+1:])
error_list.append(abs((wl_sum-sae_sum)/wl_sum))
return error_list
def calculate_delay_variation_error(self, wl_dataframe, sae_dataframe):
"""Measures a base delay from the first corner then the variations from that base"""
- start_data_pos = len(self.config_fields)
+ wl_start_data_pos = len(self.config_fields)
+ sae_start_data_pos = len(self.config_fields)+len(self.sae_config_fields)
variation_error_list = []
count = 0
for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()):
if count == 0:
#Create a base delay, variation is defined as the difference between this base
- wl_base = sum(wl_row[start_data_pos+1:])
+ wl_base = sum(wl_row[wl_start_data_pos+1:])
debug.info(1, "wl_sum base:{}".format(wl_base))
- sae_base = sum(sae_row[start_data_pos+1:])
+ sae_base = sum(sae_row[sae_start_data_pos+1:])
variation_error_list.append(0.0)
else:
#Calculate the variation from the respective base and then difference between the variations
- wl_sum = sum(wl_row[start_data_pos+1:])
+ wl_sum = sum(wl_row[wl_start_data_pos+1:])
wl_base_diff = abs((wl_base-wl_sum)/wl_base)
- sae_sum = sum(sae_row[start_data_pos+1:])
+ sae_sum = sum(sae_row[sae_start_data_pos+1:])
sae_base_diff = abs((sae_base-sae_sum)/sae_base)
variation_diff = abs((wl_base_diff-sae_base_diff)/wl_base_diff)
variation_error_list.append(variation_diff)
@@ -336,10 +355,9 @@ class data_collection(openram_test):
import characterizer
reload(characterizer)
- def set_delay_chain(self, stages, fanout):
+ def set_delay_chain(self, fanout_list):
"""Force change the parameter in the tech file to specify a delay chain configuration"""
- parameter["static_delay_stages"] = stages
- parameter["static_fanout_per_stage"] = fanout
+ parameter["static_fanout_list"] = fanout_list
def close_files(self):
"""Closes all files stored in the file dict"""
@@ -375,10 +393,17 @@ class data_collection(openram_test):
def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row, dc_resized, corner):
"""Writes data to its respective CSV file. There is a CSV for each measurement target
(wordline, sense amp enable, and models)"""
+ dc_start_ind, dc_end_ind = self.delay_obj.delay_chain_indices
sram_specs = [word_size,num_words,words_per_row,dc_resized,*corner]
+ sae_specs = [dc_start_ind, dc_end_ind]
for data_name, data_values in sram_data.items():
+ if 'sae' in data_name:
+ all_specs = sram_specs+sae_specs
+ else:
+ all_specs = sram_specs
+
other_values = self.calculate_other_data_values(data_values)
- self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name]+other_values)
+ self.csv_writers[data_name].writerow(all_specs+sram_data[data_name]+other_values)
debug.info(2,"Data Added to CSV file.")
def calculate_other_data_values(self, sram_data_list):
@@ -407,9 +432,10 @@ class data_collection(openram_test):
data_name)
self.file_name_dict[data_name] = file_name
self.csv_files[data_name] = open(file_name, 'w')
- self.config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_resized', 'process', 'voltage', 'temp']
- self.other_data_fields = ['sum']
- fields = (*self.config_fields, *header_list, *self.other_data_fields)
+ if 'sae' in data_name:
+ fields = (*self.config_fields, *self.sae_config_fields, *header_list, *self.other_data_fields)
+ else:
+ fields = (*self.config_fields, *header_list, *self.other_data_fields)
self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n')
self.csv_writers[data_name].writerow(fields)
diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py
index a0ba8cbc..eccfd1c8 100644
--- a/technology/freepdk45/tech/tech.py
+++ b/technology/freepdk45/tech/tech.py
@@ -336,6 +336,7 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no
#Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3
+parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]]
parameter["dff_clk_cin"] = 30.6 #relative capacitance
parameter["6tcell_wl_cin"] = 3 #relative capacitance
parameter["min_inv_para_delay"] = 2.4 #Tau delay units
diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py
index 1add9937..8b6ee827 100755
--- a/technology/scn4m_subm/tech/tech.py
+++ b/technology/scn4m_subm/tech/tech.py
@@ -302,6 +302,7 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no
#Logical Effort relative values for the Handmade cells
parameter["static_delay_stages"] = 4
parameter["static_fanout_per_stage"] = 3
+parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]]
parameter["dff_clk_cin"] = 27.5
parameter["6tcell_wl_cin"] = 2
parameter["min_inv_para_delay"] = .5
From e3ff9b53e9bf21b190faa6171807c13b9861f69e Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Thu, 14 Feb 2019 07:01:35 -0800
Subject: [PATCH 45/77] fixed area not being found
---
compiler/characterizer/lib.py | 1 +
compiler/datasheet/datasheet_gen.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py
index 60452d76..3b4f465b 100644
--- a/compiler/characterizer/lib.py
+++ b/compiler/characterizer/lib.py
@@ -559,6 +559,7 @@ class lib:
LVS = str(total_lvs_errors)
datasheet.write("{0},{1},".format(DRC, LVS))
+ # write area
datasheet.write(str(self.sram.width * self.sram.height)+',')
# write timing information for all ports
for port in self.all_ports:
diff --git a/compiler/datasheet/datasheet_gen.py b/compiler/datasheet/datasheet_gen.py
index 44db3f9b..1644df5f 100644
--- a/compiler/datasheet/datasheet_gen.py
+++ b/compiler/datasheet/datasheet_gen.py
@@ -112,10 +112,10 @@ def parse_characterizer_csv(f, pages):
LVS = row[col]
col += 1
- AREA = row[col]
+ ANALYTICAL_MODEL = row[col]
col += 1
- ANALYTICAL_MODEL = row[col]
+ AREA = row[col]
col += 1
for sheet in pages:
From d533a8ae2684068edd9c55a9806087600d430151 Mon Sep 17 00:00:00 2001
From: Jesse Cirimelli-Low
Date: Fri, 15 Feb 2019 21:45:05 -0800
Subject: [PATCH 46/77] fixed logger typo
---
compiler/debug.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/compiler/debug.py b/compiler/debug.py
index f25ff4b5..b7819bcc 100644
--- a/compiler/debug.py
+++ b/compiler/debug.py
@@ -16,7 +16,7 @@ def check(check, str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- logger.log("ERROR: file {0}: line {1}: {2}\n".format(
+ log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert 0
@@ -27,7 +27,7 @@ def error(str, return_value=0):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- logger.log("ERROR: file {0}: line {1}: {2}\n".format(
+ log("ERROR: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
assert return_value == 0
@@ -38,7 +38,7 @@ def warning(str):
index) = inspect.getouterframes(inspect.currentframe())[1]
sys.stderr.write("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
- logger.log("WARNING: file {0}: line {1}: {2}\n".format(
+ log("WARNING: file {0}: line {1}: {2}\n".format(
os.path.basename(filename), line_number, str))
@@ -48,7 +48,7 @@ def print_raw(str):
def log(str):
- if globals.OPTS.output_name != '':
+ if globals.OPTS.output_name != '':
if log.create_file:
compile_log = open(globals.OPTS.output_path +
globals.OPTS.output_name + '.log', "w+")
From 39bcdc8a584b4fad1eb41c9cbf617c0fe5af34e4 Mon Sep 17 00:00:00 2001
From: Matt Guthaus
Date: Sun, 17 Feb 2019 10:35:56 -0800
Subject: [PATCH 47/77] Remove outdated docs. Add Docker info to README.md
---
README.md | 13 +
docs/Makefile | 6 -
docs/control.tex | 4 -
docs/debug.tex | 49 -
docs/figs/2t1_single_level_column_mux.pdf | Bin 33644 -> 0 bytes
docs/figs/2t4decoder.pdf | Bin 43903 -> 0 bytes
docs/figs/4t16decoder.pdf | Bin 134359 -> 0 bytes
docs/figs/4t1_single_level_column_mux.pdf | Bin 52409 -> 0 bytes
docs/figs/Col_mux.svg | 1401 ------
docs/figs/Logic Diagram.svg | 747 ---
docs/figs/ZBT.pdf | Bin 41365 -> 0 bytes
docs/figs/bank.pdf | Bin 76490 -> 0 bytes
docs/figs/bank2.pdf | Bin 34726 -> 0 bytes
docs/figs/bank4.pdf | Bin 47186 -> 0 bytes
docs/figs/cell_6t_schem.pdf | Bin 16855 -> 0 bytes
docs/figs/cell_6t_schem.svg | 522 ---
docs/figs/class_hierarchy.dot | 10 -
docs/figs/class_hierarchy.pdf | Bin 15244 -> 0 bytes
docs/figs/class_hierarchy.sh | 2 -
docs/figs/column_tree_mux.pdf | Bin 11716 -> 0 bytes
docs/figs/column_tree_mux.svg | 665 ---
docs/figs/control_logic.pdf | Bin 90757 -> 0 bytes
docs/figs/gds_file.pdf | Bin 13795 -> 0 bytes
docs/figs/layout_view_1024_16_annotated.eps | 4512 -------------------
docs/figs/layout_view_1024_16_annotated.pdf | Bin 98968 -> 0 bytes
docs/figs/methodology.eps | 866 ----
docs/figs/methodology.pdf | Bin 14709 -> 0 bytes
docs/figs/methodology.svg | 1053 -----
docs/figs/ms_flop_schem.pdf | Bin 39479 -> 0 bytes
docs/figs/ms_flop_schem.svg | 1562 -------
docs/figs/nand_decoder_schem.pdf | Bin 49650 -> 0 bytes
docs/figs/nand_decoder_schem.svg | 909 ----
docs/figs/nor2.pdf | Bin 34601 -> 0 bytes
docs/figs/overall_flow.pdf | Bin 35879 -> 0 bytes
docs/figs/overview.dia | Bin 5079 -> 0 bytes
docs/figs/overview.png | Bin 43911 -> 0 bytes
docs/figs/pinv.pdf | Bin 103664 -> 0 bytes
docs/figs/precharge_schem.pdf | Bin 6765 -> 0 bytes
docs/figs/precharge_schem.svg | 311 --
docs/figs/ptx.pdf | Bin 75391 -> 0 bytes
docs/figs/ptx.svg | 1557 -------
docs/figs/replica_bitline.pdf | Bin 60217 -> 0 bytes
docs/figs/replica_cell.pdf | Bin 20121 -> 0 bytes
docs/figs/sense_amp_schem.pdf | Bin 14383 -> 0 bytes
docs/figs/sense_amp_schem.svg | 113 -
docs/figs/sram_architecture.svg | 679 ---
docs/figs/sram_overview.eps | 3128 -------------
docs/figs/sram_overview.pdf | Bin 21232 -> 0 bytes
docs/figs/sram_overview.svg | 760 ----
docs/figs/timing_read.pdf | Bin 9066 -> 0 bytes
docs/figs/timing_read.svg | 633 ---
docs/figs/timing_write.pdf | Bin 13163 -> 0 bytes
docs/figs/timing_write.svg | 811 ----
docs/figs/tree_column_mux_schem.pdf | Bin 48611 -> 0 bytes
docs/figs/wordline_driver.pdf | Bin 45907 -> 0 bytes
docs/figs/write_driver_schem.pdf | Bin 7324 -> 0 bytes
docs/figs/write_driver_schem.svg | 643 ---
docs/figs/xsram_block.svg | 199 -
docs/gdsmill.tex | 33 -
docs/implementation.tex | 389 --
docs/intro.tex | 272 --
docs/modules.tex | 601 ---
docs/openram_manual.pdf | Bin 1260616 -> 0 bytes
docs/openram_manual.tex | 108 -
docs/overview.tex | 90 -
docs/parameterized.tex | 258 --
docs/porting.tex | 47 -
docs/timing.tex | 230 -
docs/unittests.tex | 100 -
69 files changed, 13 insertions(+), 23270 deletions(-)
delete mode 100644 docs/Makefile
delete mode 100644 docs/control.tex
delete mode 100644 docs/debug.tex
delete mode 100644 docs/figs/2t1_single_level_column_mux.pdf
delete mode 100644 docs/figs/2t4decoder.pdf
delete mode 100644 docs/figs/4t16decoder.pdf
delete mode 100644 docs/figs/4t1_single_level_column_mux.pdf
delete mode 100644 docs/figs/Col_mux.svg
delete mode 100644 docs/figs/Logic Diagram.svg
delete mode 100644 docs/figs/ZBT.pdf
delete mode 100644 docs/figs/bank.pdf
delete mode 100644 docs/figs/bank2.pdf
delete mode 100644 docs/figs/bank4.pdf
delete mode 100644 docs/figs/cell_6t_schem.pdf
delete mode 100644 docs/figs/cell_6t_schem.svg
delete mode 100644 docs/figs/class_hierarchy.dot
delete mode 100644 docs/figs/class_hierarchy.pdf
delete mode 100644 docs/figs/class_hierarchy.sh
delete mode 100644 docs/figs/column_tree_mux.pdf
delete mode 100644 docs/figs/column_tree_mux.svg
delete mode 100644 docs/figs/control_logic.pdf
delete mode 100644 docs/figs/gds_file.pdf
delete mode 100644 docs/figs/layout_view_1024_16_annotated.eps
delete mode 100644 docs/figs/layout_view_1024_16_annotated.pdf
delete mode 100644 docs/figs/methodology.eps
delete mode 100644 docs/figs/methodology.pdf
delete mode 100644 docs/figs/methodology.svg
delete mode 100644 docs/figs/ms_flop_schem.pdf
delete mode 100644 docs/figs/ms_flop_schem.svg
delete mode 100644 docs/figs/nand_decoder_schem.pdf
delete mode 100644 docs/figs/nand_decoder_schem.svg
delete mode 100644 docs/figs/nor2.pdf
delete mode 100644 docs/figs/overall_flow.pdf
delete mode 100644 docs/figs/overview.dia
delete mode 100644 docs/figs/overview.png
delete mode 100644 docs/figs/pinv.pdf
delete mode 100644 docs/figs/precharge_schem.pdf
delete mode 100644 docs/figs/precharge_schem.svg
delete mode 100644 docs/figs/ptx.pdf
delete mode 100644 docs/figs/ptx.svg
delete mode 100644 docs/figs/replica_bitline.pdf
delete mode 100644 docs/figs/replica_cell.pdf
delete mode 100644 docs/figs/sense_amp_schem.pdf
delete mode 100644 docs/figs/sense_amp_schem.svg
delete mode 100644 docs/figs/sram_architecture.svg
delete mode 100644 docs/figs/sram_overview.eps
delete mode 100644 docs/figs/sram_overview.pdf
delete mode 100644 docs/figs/sram_overview.svg
delete mode 100644 docs/figs/timing_read.pdf
delete mode 100644 docs/figs/timing_read.svg
delete mode 100644 docs/figs/timing_write.pdf
delete mode 100644 docs/figs/timing_write.svg
delete mode 100644 docs/figs/tree_column_mux_schem.pdf
delete mode 100644 docs/figs/wordline_driver.pdf
delete mode 100644 docs/figs/write_driver_schem.pdf
delete mode 100644 docs/figs/write_driver_schem.svg
delete mode 100644 docs/figs/xsram_block.svg
delete mode 100644 docs/gdsmill.tex
delete mode 100644 docs/implementation.tex
delete mode 100644 docs/intro.tex
delete mode 100644 docs/modules.tex
delete mode 100644 docs/openram_manual.pdf
delete mode 100644 docs/openram_manual.tex
delete mode 100644 docs/overview.tex
delete mode 100644 docs/parameterized.tex
delete mode 100644 docs/porting.tex
delete mode 100644 docs/timing.tex
delete mode 100644 docs/unittests.tex
diff --git a/README.md b/README.md
index 2141c480..aa5a1705 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,17 @@ predictive and fabricable technologies.
# Basic Setup
+## Docker Image
+
+We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
+available that has all tools installed for the [SCMOS] process. It is
+available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
+Please see
+[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
+for information on how to use this docker image.
+
+## Dependencies
+
The OpenRAM compiler has very few dependencies:
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
+ Python 3.5 or higher
@@ -40,6 +51,8 @@ You must set two environment variables:
+ OPENRAM\_HOME should point to the compiler source directory.
+ OPENERAM\_TECH should point to a root technology directory.
+## Environment
+
For example add this to your .bashrc:
```
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index b0d853fa..00000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-all: openram_manual.tex
- pdflatex openram_manual
-bib:
- bibtex openram_manual
-clean:
- rm -f openram_manual.pdf *.aux *.bbl *.blq *.dvi *.log *.lot *.toc *.lof *.blg
diff --git a/docs/control.tex b/docs/control.tex
deleted file mode 100644
index 472461d1..00000000
--- a/docs/control.tex
+++ /dev/null
@@ -1,4 +0,0 @@
-\section{Internal Control Signals}
-\label{sec:control}
-
-This section not needed... All information is in Section~\ref{sec:timing} (Timing).
diff --git a/docs/debug.tex b/docs/debug.tex
deleted file mode 100644
index 98bab10d..00000000
--- a/docs/debug.tex
+++ /dev/null
@@ -1,49 +0,0 @@
-\section{Debug Framework}
-\label{sec:debug}
-
-All output in OpenRAM should use the shared debug framework. This is
-still under development but is in a usable state. It is going to be
-replaced with the Python Logging framework which is quite simple.
-
-All of the debug framework is contained in debug.py and is based
-around the concept of a ``debug level'' which is a single global
-variable in this file. This level is, by default, 0 which will output
-normal minimal output. The general guidelines for debug output are:
-\begin{itemize}
-\item 0 Normal output
-\item 1 Verbose output
-\item 2 Detailed output
-\item 3+ Excessively detailed output
-\end{itemize}
-
-The debug level can be adjusted on the command line when arguments are parsed using the ``-v'' flag. Adding more ``-v'' flags will increase the debug level as in the following examples:
-\begin{verbatim}
-python tests/01_library_drc_test.py -vv
-python openram.py 4 16 -v -v
-\end{verbatim}
-which each put the program in debug level 2 (detailed output).
-
-Since every module may output a lot of information in the higher debug
-levels, the output format is standardized to allow easy searching via
-grep or other command-line tools. The standard output formatting is
-used through three interface functions:
-\begin{itemize}
-\item debug.info(int, msg)
-\item debug.warning(msg)
-\item debug.error(msg)
-\end{itemize}
-The msg string in each case can be any string format including data or
-other useful debug information. The string should also contain
-information to make it human understandable. {\bf It should not just be
- a number!} The warning and error messages are independent of debug
-levels while the info message will only print the message if the
-current debug level is above the parameter value.
-
-The output format of the debug info messages are:
-\begin{verbatim}
-[ module ]: msg
-\end{verbatim}
-where module is the calling module name and msg is the string
-provided. This enables a grep command to get the relevant lines. The
-warning and error messages include the file name and line number of
-the warning/error.
diff --git a/docs/figs/2t1_single_level_column_mux.pdf b/docs/figs/2t1_single_level_column_mux.pdf
deleted file mode 100644
index 949173590f1e8bdeeb948a0c7bed8b006328ec94..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 33644
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 ?X
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
HwD5ld(g3<%
diff --git a/docs/figs/2t4decoder.pdf b/docs/figs/2t4decoder.pdf
deleted file mode 100644
index acd551d7789881bac1b6bd4badb23ae39ce6fc6d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 43903
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 |