diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index b8369088..e106cb2a 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -11,6 +11,7 @@ import os import math import tech + class spice(): """ This provides a set of useful generic types for hierarchy @@ -132,13 +133,13 @@ class spice(): """Adds a subckt/submodule to the subckt hierarchy""" self.mods.append(mod) + def connect_inst(self, args, check=True): """Connects the pins of the last instance added It is preferred to use the function with the check to find if there is a problem. The check option can be set to false where we dynamically generate groups of connections after a group of modules are generated.""" - if (check and (len(self.insts[-1].mod.pins) != len(args))): from pprint import pformat modpins_string=pformat(self.insts[-1].mod.pins) diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index 1f2ebd7b..c4d4ee00 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -18,10 +18,18 @@ class verilog: def verilog_write(self,verilog_name): """ Write a behavioral Verilog model. """ self.vf = open(verilog_name, "w") + # Determine if optional write mask is used + self.wmask_enabled = False + if self.word_size != self.write_size: + self.wmask_enabled = True self.vf.write("// OpenRAM SRAM model\n") self.vf.write("// Words: {0}\n".format(self.num_words)) - self.vf.write("// Word size: {0}\n\n".format(self.word_size)) + self.vf.write("// Word size: {0}\n".format(self.word_size)) + if self.wmask_enabled: + self.vf.write("// Write size: {0}\n\n".format(self.write_size)) + else: + self.vf.write("\n") self.vf.write("module {0}(\n".format(self.name)) for port in self.all_ports: @@ -32,16 +40,25 @@ class verilog: elif port in self.write_ports: self.vf.write("// Port {0}: W\n".format(port)) if port in self.readwrite_ports: - self.vf.write(" clk{0},csb{0},web{0},ADDR{0},DIN{0},DOUT{0}".format(port)) + self.vf.write(" clk{0},csb{0},web{0},".format(port)) + if self.wmask_enabled: + self.vf.write("wmask{},".format(port)) + self.vf.write("ADDR{0},DIN{0},DOUT{0}".format(port)) elif port in self.write_ports: - self.vf.write(" clk{0},csb{0},ADDR{0},DIN{0}".format(port)) + self.vf.write(" clk{0},csb{0},".format(port)) + if self.wmask_enabled: + self.vf.write("wmask{},".format(port)) + self.vf.write("ADDR{0},DIN{0}".format(port)) elif port in self.read_ports: self.vf.write(" clk{0},csb{0},ADDR{0},DOUT{0}".format(port)) # Continue for every port on a new line if port != self.all_ports[-1]: self.vf.write(",\n") self.vf.write("\n );\n\n") - + + if self.wmask_enabled: + self.num_wmask = int(self.word_size/self.write_size) + self.vf.write(" parameter NUM_WMASK = {0} ;\n".format(self.num_wmask)) self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size)) self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size)) self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n") @@ -85,6 +102,9 @@ class verilog: self.vf.write(" reg csb{0}_reg;\n".format(port)) if port in self.readwrite_ports: self.vf.write(" reg web{0}_reg;\n".format(port)) + if port in self.write_ports: + if self.wmask_enabled: + self.vf.write(" reg [NUM_WMASK-1:0] wmask{0}_reg;\n".format(port)) self.vf.write(" reg [ADDR_WIDTH-1:0] ADDR{0}_reg;\n".format(port)) if port in self.write_ports: self.vf.write(" reg [DATA_WIDTH-1:0] DIN{0}_reg;\n".format(port)) @@ -102,6 +122,9 @@ class verilog: self.vf.write(" csb{0}_reg = csb{0};\n".format(port)) if port in self.readwrite_ports: self.vf.write(" web{0}_reg = web{0};\n".format(port)) + if port in self.write_ports: + if self.wmask_enabled: + self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port)) self.vf.write(" ADDR{0}_reg = ADDR{0};\n".format(port)) if port in self.write_ports: self.vf.write(" DIN{0}_reg = DIN{0};\n".format(port)) @@ -113,13 +136,19 @@ class verilog: elif port in self.read_ports: self.vf.write(" if ( !csb{0}_reg ) \n".format(port)) self.vf.write(" $display($time,\" Reading %m ADDR{0}=%b DOUT{0}=%b\",ADDR{0}_reg,mem[ADDR{0}_reg]);\n".format(port)) - if port in self.readwrite_ports: self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port)) - self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b\",ADDR{0}_reg,DIN{0}_reg);\n".format(port)) + if self.wmask_enabled: + self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b wmask{0}=%b\",ADDR{0}_reg,DIN{0}_reg,wmask{0}_reg);\n".format(port)) + else: + self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b\",ADDR{0}_reg,DIN{0}_reg);\n".format(port)) elif port in self.write_ports: self.vf.write(" if ( !csb{0}_reg )\n".format(port)) - self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b\",ADDR{0}_reg,DIN{0}_reg);\n".format(port)) + if self.wmask_enabled: + self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b wmask{0}=%b\",ADDR{0}_reg,DIN{0}_reg,wmask{0}_reg);\n".format(port)) + else: + self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b\",ADDR{0}_reg,DIN{0}_reg);\n".format(port)) + self.vf.write(" end\n\n") @@ -131,6 +160,8 @@ class verilog: self.vf.write(" input csb{0}; // active low chip select\n".format(port)) if port in self.readwrite_ports: self.vf.write(" input web{0}; // active low write control\n".format(port)) + if (self.wmask_enabled): + self.vf.write(" input [NUM_WMASK-1:0] wmask{0}; // write mask\n".format(port)) self.vf.write(" input [ADDR_WIDTH-1:0] ADDR{0};\n".format(port)) if port in self.write_ports: self.vf.write(" input [DATA_WIDTH-1:0] DIN{0};\n".format(port)) @@ -148,10 +179,25 @@ class verilog: self.vf.write(" always @ (negedge clk{0})\n".format(port)) self.vf.write(" begin : MEM_WRITE{0}\n".format(port)) if port in self.readwrite_ports: - self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port)) + if self.wmask_enabled: + self.vf.write(" if ( !csb{0}_reg && !web{0}_reg ) begin\n".format(port)) + else: + self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port)) else: - self.vf.write(" if (!csb{0}_reg)\n".format(port)) - self.vf.write(" mem[ADDR{0}_reg] = DIN{0}_reg;\n".format(port)) + if self.wmask_enabled: + self.vf.write(" if (!csb{0}_reg) begin\n".format(port)) + else: + self.vf.write(" if (!csb{0}_reg)\n".format(port)) + + if self.wmask_enabled: + for mask in range(0,self.num_wmask): + lower = mask * self.write_size + upper = lower + self.write_size-1 + self.vf.write(" if (wmask{0}_reg[{1}])\n".format(port,mask)) + self.vf.write(" mem[ADDR{0}_reg][{1}:{2}] = DIN{0}_reg[{1}:{2}];\n".format(port,upper,lower)) + self.vf.write(" end\n") + else: + self.vf.write(" mem[ADDR{0}_reg] = DIN{0}_reg;\n".format(port)) self.vf.write(" end\n") def add_read_block(self, port): diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 83d367b0..c5e26da1 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -24,6 +24,7 @@ class simulation(): self.name = self.sram.name self.word_size = self.sram.word_size self.addr_size = self.sram.addr_size + self.write_size = self.sram.write_size self.num_cols = self.sram.num_cols self.num_rows = self.sram.num_rows self.num_banks = self.sram.num_banks @@ -266,7 +267,9 @@ class simulation(): for port in range(total_ports): if (port in read_index) and (port in write_index): pin_names.append("WEB{0}".format(port)) - + if (self.write_size != self.word_size): + pin_names.append("WMASK{0}".format(port)) + for port in range(total_ports): pin_names.append("{0}{1}".format(tech.spice["clk"], port)) diff --git a/compiler/globals.py b/compiler/globals.py index c29cd081..245e34d0 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -20,8 +20,8 @@ import copy import importlib USAGE = "Usage: openram.py [options] \nUse -h for help.\n" - # Anonymous object that will be the options + OPTS = options.options() CHECKPOINT_OPTS=None @@ -470,6 +470,18 @@ def report_status(): debug.error("{0} is not an integer in config file.".format(OPTS.word_size)) if type(OPTS.num_words)!=int: debug.error("{0} is not an integer in config file.".format(OPTS.sram_size)) + if type(OPTS.write_size) != int and OPTS.write_size != None: + debug.error("{0} is not an integer in config file.".format(OPTS.write_size)) + + # Determine if a write mask is specified by the user; if it's not, the mask write size should + # be the same as the word size so that an entire word is written at once + if OPTS.write_size==None: + OPTS.write_size = OPTS.word_size + + if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size): + debug.error("Write size needs to be between 1 bit and {0} bits.".format(OPTS.word_size)) + if (OPTS.word_size % OPTS.write_size != 0): + debug.error("Write size needs to be an integer multiple of word size.") if not OPTS.tech_name: debug.error("Tech name must be specified in config file.") @@ -483,9 +495,12 @@ def report_status(): debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size, OPTS.num_words, OPTS.num_banks)) + if (OPTS.write_size != OPTS.word_size): + debug.print_raw("Write size: {}".format(OPTS.write_size)) debug.print_raw("RW ports: {0}\nR-only ports: {1}\nW-only ports: {2}".format(OPTS.num_rw_ports, OPTS.num_r_ports, OPTS.num_w_ports)) + if OPTS.netlist_only: debug.print_raw("Netlist only mode (no physical design is being done, netlist_only=False to disable).") diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index f9de4195..a9a47ec6 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -84,6 +84,9 @@ class bank(design.design): for port in self.write_ports: for bit in range(self.word_size): self.add_pin("din{0}_{1}".format(port,bit),"IN") + # if (self.word_size != self.write_size): + # for bit in range(self.word_size): + # self.add_pin() for port in self.all_ports: for bit in range(self.addr_size): self.add_pin("addr{0}_{1}".format(port,bit),"INPUT") diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index c795888a..e39dbb9f 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -21,7 +21,7 @@ class control_logic(design.design): Dynamically generated Control logic for the total SRAM circuit. """ - def __init__(self, num_rows, words_per_row, word_size, sram=None, port_type="rw", name=""): + def __init__(self, num_rows, words_per_row, word_size, write_size, sram=None, port_type="rw", name=""): """ Constructor """ name = "control_logic_" + port_type design.design.__init__(self, name) @@ -35,6 +35,7 @@ class control_logic(design.design): self.words_per_row = words_per_row self.word_size = word_size self.port_type = port_type + self.write_size = write_size self.num_cols = word_size*words_per_row self.num_words = num_rows*words_per_row @@ -319,7 +320,7 @@ class control_logic(design.design): else: self.input_list = ["csb"] self.rbl_list = [] - + if self.port_type == "rw": self.dff_output_list = ["cs_bar", "cs", "we_bar", "we"] else: @@ -748,7 +749,9 @@ class control_logic(design.design): def route_dffs(self): if self.port_type == "rw": - dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) + #print("hi") + #if (self.word_size == self.write_size): + dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) elif self.port_type == "r": dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) else: diff --git a/compiler/openram.py b/compiler/openram.py index b939b61e..9bd3b898 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -50,7 +50,8 @@ from sram_config import sram_config # Configure the SRAM organization c = sram_config(word_size=OPTS.word_size, - num_words=OPTS.num_words) + num_words=OPTS.num_words, + write_size=OPTS.write_size) debug.print_raw("Words per row: {}".format(c.words_per_row)) #from parser import * diff --git a/compiler/options.py b/compiler/options.py index 1882f8a1..ab337e31 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -8,6 +8,7 @@ import optparse import getpass import os +#import sram_config class options(optparse.Values): """ @@ -28,6 +29,9 @@ class options(optparse.Values): num_rw_ports = 1 num_r_ports = 0 num_w_ports = 0 + + # Write mask size, default will be overwritten with word_size if not user specified + write_size = None # These will get initialized by the user or the tech file supply_voltages = "" diff --git a/compiler/sram/sram.py b/compiler/sram/sram.py index f7c1111c..5e7ac08a 100644 --- a/compiler/sram/sram.py +++ b/compiler/sram/sram.py @@ -34,7 +34,6 @@ class sram(): start_time = datetime.datetime.now() self.name = name - if self.num_banks == 1: from sram_1bank import sram_1bank as sram @@ -84,8 +83,6 @@ class sram(): self.gds_write(gdsname) print_time("GDS", datetime.datetime.now(), start_time) - - # Save the spice file start_time = datetime.datetime.now() spname = OPTS.output_path + self.s.name + ".sp" @@ -133,4 +130,4 @@ class sram(): vname = OPTS.output_path + self.s.name + ".v" debug.print_raw("Verilog: Writing to {0}".format(vname)) self.verilog_write(vname) - print_time("Verilog", datetime.datetime.now(), start_time) + print_time("Verilog", datetime.datetime.now(), start_time) \ No newline at end of file diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 75308fe7..8d158bfe 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -46,6 +46,9 @@ class sram_1bank(sram_base): self.data_dff_insts = self.create_data_dff() + if (self.write_size != self.word_size): + self.wmask_dff_insts = self.create_wmask_dff() + def place_instances(self): """ @@ -65,6 +68,7 @@ class sram_1bank(sram_base): row_addr_pos = [None]*len(self.all_ports) col_addr_pos = [None]*len(self.all_ports) data_pos = [None]*len(self.all_ports) + wmask_pos = [None]*len(self.all_ports) # This is M2 pitch even though it is on M1 to help stem via spacings on the trunk # The M1 pitch is for supply rail spacings @@ -105,6 +109,29 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) + # Add the col address flops below the bank to the left of the lower-left of bank array + if self.col_addr_dff: + col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, + -max_gap_size - self.col_addr_dff_insts[port].height) + self.col_addr_dff_insts[port].place(col_addr_pos[port]) + + # Add the data flops below the bank to the right of the lower-left of bank array + # This relies on the lower-left of the array of the bank + # decoder in upper left, bank in upper right, sensing in lower right. + # These flops go below the sensing and leave a gap to channel route to the + # sense amps. + if port in self.write_ports: + data_pos[port] = vector(self.bank.bank_array_ll.x, + -max_gap_size - self.data_dff_insts[port].height) + self.data_dff_insts[port].place(data_pos[port]) + + # Add the write mask flops to the left of the din flops. + if (self.write_size != self.word_size): + if port in self.write_ports: + wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, + self.bank.height + max_gap_size + self.data_dff_insts[port].height) + self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") + if len(self.all_ports)>1: # Port 1 @@ -119,6 +146,13 @@ class sram_1bank(sram_base): data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, self.bank.height + max_gap_size + self.dff.height) self.data_dff_insts[port].place(data_pos[port], mirror="MX") + + # Add the write mask flops to the left of the din flops. + if (self.write_size != self.word_size): + if port in self.write_ports: + wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, + self.bank.height + max_gap_size + self.data_dff_insts[port].height) + self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") else: data_pos[port] = self.bank_inst.ur() @@ -143,7 +177,7 @@ class sram_1bank(sram_base): self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") - + def add_layout_pins(self): """ Add the top-level pins for a single bank SRAM with control. @@ -326,10 +360,13 @@ class sram_1bank(sram_base): offset=pin.center()) def graph_exclude_data_dff(self): - """Removes data dff from search graph. """ - #Data dffs are only for writing so are not useful for evaluating read delay. + """Removes data dff and wmask dff (if applicable) from search graph. """ + #Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay. for inst in self.data_dff_insts: self.graph_inst_exclude.add(inst) + if (self.write_size != self.word_size): + for inst in self.wmask_dff_insts: + self.graph_inst_exclude.add(inst) def graph_exclude_addr_dff(self): """Removes data dff from search graph. """ diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 97c9831b..096f4987 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -68,7 +68,10 @@ class sram_base(design, verilog, lef): self.add_pin("web{}".format(port),"INPUT") for port in self.all_ports: self.add_pin("clk{}".format(port),"INPUT") - + # add the optional write mask pins + if self.word_size != self.write_size: + for port in self.write_ports: + self.add_pin("wmask{}".format(port),"INPUT") for port in self.read_ports: for bit in range(self.word_size): self.add_pin("DOUT{0}[{1}]".format(port,bit),"OUTPUT") @@ -150,9 +153,6 @@ class sram_base(design, verilog, lef): rtr.route() - - - def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ @@ -278,7 +278,9 @@ class sram_base(design, verilog, lef): self.data_dff = dff_array(name="data_dff", rows=1, columns=self.word_size) self.add_mod(self.data_dff) - + self.wmask_dff = dff_array(name="wmask_dff", rows=1, columns=int(self.word_size/self.write_size)) + self.add_mod(self.wmask_dff) + # Create the bank module (up to four are instantiated) from bank import bank @@ -303,6 +305,7 @@ class sram_base(design, verilog, lef): self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, + write_size = self.write_size, sram=self, port_type="rw") self.add_mod(self.control_logic_rw) @@ -310,6 +313,7 @@ class sram_base(design, verilog, lef): self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, + write_size=self.write_size, sram=self, port_type="w") self.add_mod(self.control_logic_w) @@ -317,7 +321,8 @@ class sram_base(design, verilog, lef): self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, - sram=self, + write_size=self.write_size, + sram=self, port_type="r") self.add_mod(self.control_logic_r) @@ -448,6 +453,29 @@ class sram_base(design, verilog, lef): return insts + def create_wmask_dff(self): + """ Add and place all wmask flops """ + num_wmask = int(self.word_size/self.write_size) + insts = [] + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="wmask_dff{}".format(port), + mod=self.wmask_dff)) + else: + insts.append(None) + continue + + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(num_wmask): + inputs.append("wmask{}[{}]".format(port, bit)) + outputs.append("BANK_WMASK{}[{}]".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) + + return insts + def create_control_logic(self): """ Add control logic instances """ diff --git a/compiler/sram/sram_config.py b/compiler/sram/sram_config.py index c97d63b0..ded595e0 100644 --- a/compiler/sram/sram_config.py +++ b/compiler/sram/sram_config.py @@ -14,16 +14,20 @@ 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, words_per_row=None): + def __init__(self, word_size, num_words, write_size = None, num_banks=1, words_per_row=None): self.word_size = word_size self.num_words = num_words + self.write_size = write_size self.num_banks = num_banks # This will get over-written when we determine the organization self.words_per_row = words_per_row + if self.write_size == None: + self.write_size = self.word_size + self.compute_sizes() - + def set_local_config(self, module): """ Copy all of the member variables to the given module for convenience """ diff --git a/compiler/tests/16_control_logic_multiport_test.py b/compiler/tests/16_control_logic_multiport_test.py index 66c34d24..3ad898b1 100755 --- a/compiler/tests/16_control_logic_multiport_test.py +++ b/compiler/tests/16_control_logic_multiport_test.py @@ -34,19 +34,19 @@ class control_logic_test(openram_test): OPTS.num_r_ports = 1 debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, write_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 = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, write_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 = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, write_size=8, port_type="r") self.local_check(a) globals.end_openram() diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py index 2be0bf0f..13e6c46c 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -24,7 +24,7 @@ class control_logic_test(openram_test): # check control logic for single port debug.info(1, "Testing sample for control_logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, write_size=32) self.local_check(a) # run the test from the command line diff --git a/compiler/tests/26_hspice_pex_pinv_test.py b/compiler/tests/26_hspice_pex_pinv_test.py new file mode 100755 index 00000000..f0cccba3 --- /dev/null +++ b/compiler/tests/26_hspice_pex_pinv_test.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +""" +Run regression tests/pex test on an extracted pinv to ensure pex functionality +with HSPICE. +""" +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 hspice_pex_pinv_test(openram_test): + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + import pinv + + # load the hspice + OPTS.spice_name="hspice" + 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) + + # generate the pinv + prev_purge_value = OPTS.purge_temp + OPTS.purge_temp = False # force set purge to false to save the sp file + debug.info(2, "Checking 1x size inverter") + tx = pinv.pinv(name="pinv", size=1) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp,tx.name) + tx.gds_write(tempgds) + tempsp = "{0}{1}.sp".format(OPTS.openram_temp,tx.name) + tx.sp_write(tempsp) + + # make sure that the library simulation is successful\ + sp_delay = self.simulate_delay(test_module = tempsp, + top_level_name = tx.name) + if sp_delay is "Failed": + self.fail('Library Spice module did not behave as expected') + + # now generate its pex file + pex_file = self.run_pex(tx) + OPTS.purge_temp = prev_purge_value # restore the old purge value + # generate simulation for pex, make sure the simulation is successful + pex_delay = self.simulate_delay(test_module = pex_file, + top_level_name = tx.name) + # make sure the extracted spice simulated + if pex_delay is "Failed": + self.fail('Pex file did not behave as expected') + + # if pex data is bigger than original spice file then result is ok + # However this may not always be true depending on the netlist provided + # comment out for now + #debug.info(2,"pex_delay: {0}".format(pex_delay)) + #debug.info(2,"sp_delay: {0}".format(sp_delay)) + + #assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ + #.format(pex_delay,sp_delay) + + globals.end_openram() + + def simulate_delay(self, test_module, top_level_name): + from characterizer import charutils + from charutils import parse_spice_list + # setup simulation + sim_file = OPTS.openram_temp + "stim.sp" + log_file_name = "timing" + test_sim = self.write_simulation(sim_file, test_module, top_level_name) + test_sim.run_sim() + delay = parse_spice_list(log_file_name, "pinv_delay") + return delay + + def write_simulation(self, sim_file, cir_file, top_module_name): + """ write pex spice simulation for a pinv test""" + import tech + from characterizer import measurements, stimuli + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + sim_file = open(sim_file, "w") + simulation = stimuli(sim_file,corner) + + # library files + simulation.write_include(cir_file) + + # supply voltages + simulation.gen_constant(sig_name ="vdd", + v_val = tech.spice["nom_supply_voltage"]) + simulation.gen_constant(sig_name = "gnd", + v_val = "0v") + + run_time = tech.spice["feasible_period"] * 4 + # input voltage + clk_period = tech.spice["feasible_period"] + simulation.gen_pwl(sig_name ="input", + clk_times = [clk_period,clk_period], + data_values = [1,0], + period = clk_period, + slew = 0.001*tech.spice["feasible_period"], + setup = 0) + + # instantiation of simulated pinv + simulation.inst_model(pins = ["input", "output", "vdd", "gnd"], + model_name = top_module_name) + + # delay measurement + delay_measure = measurements.delay_measure(measure_name = "pinv_delay", + trig_name = "input", + targ_name = "output", + trig_dir_str = "FALL", + targ_dir_str = "RISE", + has_port = False) + trig_td = trag_td = 0.01 * run_time + rest_info = trig_td,trag_td,tech.spice["nom_supply_voltage"] + delay_measure.write_measure(simulation, rest_info) + + simulation.write_control(end_time = run_time) + sim_file.close() + return simulation + + +# 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() diff --git a/compiler/tests/26_ngspice_pex_pinv_test.py b/compiler/tests/26_ngspice_pex_pinv_test.py new file mode 100755 index 00000000..2eb95948 --- /dev/null +++ b/compiler/tests/26_ngspice_pex_pinv_test.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +""" +Run regression tests/pex test on an extracted pinv to ensure pex functionality +with Ngspice. +""" +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 ngspice_pex_pinv_test(openram_test): + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + import pinv + + # load the ngspice + OPTS.spice_name="ngspice" + 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) + + # generate the pinv module + prev_purge_value = OPTS.purge_temp + OPTS.purge_temp = False # force set purge to false to save the sp file + debug.info(2, "Checking 1x size inverter") + tx = pinv.pinv(name="pinv", size=1) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp,tx.name) + tx.gds_write(tempgds) + tempsp = "{0}{1}.sp".format(OPTS.openram_temp,tx.name) + tx.sp_write(tempsp) + + # make sure that the library simulation is successful + sp_delay = self.simulate_delay(test_module = tempsp, + top_level_name = tx.name) + if sp_delay is "Failed": + self.fail('Library Spice module did not behave as expected') + + # now generate its pex file + pex_file = self.run_pex(tx) + OPTS.purge_temp = prev_purge_value # restore the old purge value + # generate simulation for pex, make sure the simulation is successful + pex_delay = self.simulate_delay(test_module = pex_file, + top_level_name = tx.name) + # make sure the extracted spice simulated + if pex_delay is "Failed": + self.fail('Pex file did not behave as expected') + + # if pex data is bigger than original spice file then result is ok + # However this may not always be true depending on the netlist provided + # comment out for now + #debug.info(2,"pex_delay: {0}".format(pex_delay)) + #debug.info(2,"sp_delay: {0}".format(sp_delay)) + + #assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ + #.format(pex_delay,sp_delay) + + globals.end_openram() + + def simulate_delay(self, test_module, top_level_name): + from characterizer import charutils + from charutils import parse_spice_list + # setup simulation + sim_file = OPTS.openram_temp + "stim.sp" + log_file_name = "timing" + test_sim = self.write_simulation(sim_file, test_module, top_level_name) + test_sim.run_sim() + delay = parse_spice_list(log_file_name, "pinv_delay") + return delay + + def write_simulation(self, sim_file, cir_file, top_module_name): + """ write pex spice simulation for a pinv test""" + import tech + from characterizer import measurements, stimuli + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + sim_file = open(sim_file, "w") + simulation = stimuli(sim_file,corner) + + # library files + simulation.write_include(cir_file) + + # supply voltages + simulation.gen_constant(sig_name ="vdd", + v_val = tech.spice["nom_supply_voltage"]) + # The scn4m_subm and ngspice combination will have a gnd source error: + # "Fatal error: instance vgnd is a shorted VSRC" + # However, remove gnd power for all techa pass for this test + # simulation.gen_constant(sig_name = "gnd", + # v_val = "0v") + + + run_time = tech.spice["feasible_period"] * 4 + # input voltage + clk_period = tech.spice["feasible_period"] + simulation.gen_pwl(sig_name ="input", + clk_times = [clk_period,clk_period], + data_values = [1,0], + period = clk_period, + slew = 0.001*tech.spice["feasible_period"], + setup = 0) + + # instantiation of simulated pinv + simulation.inst_model(pins = ["input", "output", "vdd", "gnd"], + model_name = top_module_name) + + # delay measurement + delay_measure = measurements.delay_measure(measure_name = "pinv_delay", + trig_name = "input", + targ_name = "output", + trig_dir_str = "FALL", + targ_dir_str = "RISE", + has_port = False) + trig_td = trag_td = 0.01 * run_time + rest_info = trig_td,trag_td,tech.spice["nom_supply_voltage"] + delay_measure.write_measure(simulation, rest_info) + + simulation.write_control(end_time = run_time) + sim_file.close() + return simulation + +# 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() diff --git a/compiler/tests/sram_1rw_wmask_tb.v b/compiler/tests/sram_1rw_wmask_tb.v new file mode 100644 index 00000000..54aada6e --- /dev/null +++ b/compiler/tests/sram_1rw_wmask_tb.v @@ -0,0 +1,110 @@ +`define assert(signal, value) \ +if (!(signal === value)) begin \ + $display("ASSERTION FAILED in %m: signal != value"); \ + $finish;\ +end + +module sram_1rw_wmask_tb; + reg clk; + + reg [3:0] addr0; + reg [1:0] din0; + reg csb0; + reg web0; + reg [1:0] wmask0; + wire [1:0] dout0; + + sram_2b_16_1rw_freepdk45 U0 (.DIN0(din0), + .DOUT0(dout0), + .ADDR0(addr0), + .csb0(csb0), + .web0(web0), + .wmask0(wmask0), + .clk0(clk) + ); + + + initial + begin + //$monitor("%g addr0=%b din0=%b dout0=%b", + // $time, addr0, din0, dout0); + + + clk = 1; + csb0 = 1; + web0 = 1; + wmask0 = 2'b01; + addr0 = 0; + din0 = 0; + + // write + #10 din0=2'b10; + addr0=4'h1; + web0 = 0; + csb0 = 0; + wmask0 = 2'b10; + + // read + #10 din0=2'b11; + addr0=4'h1; + web0 = 1; + csb0 = 0; + + #10 `assert(dout0, 2'b1x) + + // write another + #10 din0=2'b01; + addr0=4'hC; + web0 = 0; + csb0 = 0; + wmask0 = 2'b01; + + // read undefined + #10 din0=2'b11; + addr0=4'h0; + web0 = 1; + csb0 = 0; + wmask0 = 2'b01; + + #10 `assert(dout0, 2'bxx) + + // read defined + din0=2'b11; + addr0=4'hC; + web0 = 1; + csb0 = 0; + wmask0 = 2'b01; + + #10 `assert(dout0, 2'bx1) + + // write another + din0=2'b01; + addr0=4'h1; + web0 = 0; + csb0 = 0; + + // read defined + #10 din0=2'b11; + addr0=4'h1; + web0 = 1; + csb0 = 0; + + + #10 `assert(dout0, 2'b11) + + // read undefined + din0=2'b11; + addr0=4'h0; + web0 = 1; + csb0 = 0; + + #10 `assert(dout0, 2'bxx) + + #10 $finish; + + end + + always + #5 clk = !clk; + +endmodule \ No newline at end of file diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index df865e00..95675c38 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -16,7 +16,7 @@ import debug class openram_test(unittest.TestCase): """ Base unit test that we have some shared classes in. """ - + def local_drc_check(self, w): self.reset() @@ -31,11 +31,11 @@ class openram_test(unittest.TestCase): if OPTS.purge_temp: self.cleanup() - + def local_check(self, a, final_verification=False): self.reset() - + tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) @@ -52,7 +52,7 @@ class openram_test(unittest.TestCase): #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("DRC failed: {}".format(a.name)) - + result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) if result != 0: #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) @@ -64,7 +64,18 @@ class openram_test(unittest.TestCase): #import pdb; pdb.set_trace() if OPTS.purge_temp: self.cleanup() - + + def run_pex(self, a, output=None): + if output == None: + output = OPTS.openram_temp + a.name + ".pex.netlist" + tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) + + import verify + result=verify.run_pex(a.name, tempgds, tempspice, output=output, final_verification=False) + if result != 0: + self.fail("PEX ERROR: {}".format(a.name)) + return output def find_feasible_test_period(self, delay_obj, sram, load, slew): """Creates a delay simulation to determine a feasible period for the functional tests to run. @@ -74,19 +85,19 @@ class openram_test(unittest.TestCase): delay_obj.set_load_slew(load, slew) test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) - delay_obj.find_feasible_period_one_port(test_port) - return delay_obj.period - + delay_obj.find_feasible_period_one_port(test_port) + return delay_obj.period + def cleanup(self): """ Reset the duplicate checker and cleanup files. """ files = glob.glob(OPTS.openram_temp + '*') for f in files: # Only remove the files if os.path.isfile(f): - os.remove(f) + os.remove(f) def reset(self): - """ + """ Reset everything after each test. """ # Reset the static duplicate name checker for unit tests. @@ -115,7 +126,7 @@ class openram_test(unittest.TestCase): data_string=pprint.pformat(data) debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string) return data_matches - + def isclose(self,key,value,actual_value,error_tolerance=1e-2): @@ -131,7 +142,7 @@ class openram_test(unittest.TestCase): return False def relative_diff(self, value1, value2): - """ Compute the relative difference of two values and normalize to the largest. + """ Compute the relative difference of two values and normalize to the largest. If largest value is 0, just return the difference.""" # Edge case to avoid divide by zero @@ -147,7 +158,7 @@ class openram_test(unittest.TestCase): # Edge case where greater is a zero if norm_value == 0: min_value = abs(min(value1, value2)) - + return abs(value1 - value2) / norm_value @@ -161,15 +172,15 @@ class openram_test(unittest.TestCase): """Compare two files. Arguments: - + filename1 -- First file name - + filename2 -- Second file name Return value: - + True if the files are the same, False otherwise. - + """ import re import debug @@ -202,7 +213,7 @@ class openram_test(unittest.TestCase): debug.info(3,"line1_floats: "+str(line1_floats)) debug.info(3,"line2_floats: "+str(line2_floats)) - + # 2. Remove the floats from the string for f in line1_floats: line1=line1.replace(f,"",1) @@ -214,10 +225,10 @@ class openram_test(unittest.TestCase): # 3. Convert to floats rather than strings line1_floats = [float(x) for x in line1_floats] line2_floats = [float(x) for x in line1_floats] - + # 4. Check if remaining string matches if line1 != line2: - #Uncomment if you want to see all the individual chars of the two lines + #Uncomment if you want to see all the individual chars of the two lines #print(str([i for i in line1])) #print(str([i for i in line2])) if mismatches==0: @@ -280,13 +291,13 @@ class openram_test(unittest.TestCase): debug.info(2,"MATCH {0} {1}".format(filename1,filename2)) return True - + def header(filename, technology): # Skip the header for gitlab regression import getpass if getpass.getuser() == "gitlab-runner": return - + tst = "Running Test for:" print("\n") print(" ______________________________________________________________________________ ") @@ -300,16 +311,18 @@ def header(filename, technology): def debugTestRunner(post_mortem=None): """unittest runner doing post mortem debugging on failing tests""" - if post_mortem is None: + if post_mortem is None and not OPTS.purge_temp: post_mortem = pdb.post_mortem class DebugTestResult(unittest.TextTestResult): def addError(self, test, err): # called before tearDown() traceback.print_exception(*err) - post_mortem(err[2]) + if post_mortem: + post_mortem(err[2]) super(DebugTestResult, self).addError(test, err) def addFailure(self, test, err): traceback.print_exception(*err) - post_mortem(err[2]) + if post_mortem: + post_mortem(err[2]) super(DebugTestResult, self).addFailure(test, err) return unittest.TextTestRunner(resultclass=DebugTestResult) diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 8698b248..8abca448 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -93,11 +93,11 @@ def write_calibre_lvs_script(cell_name, final_verification): 'cmnFDIUseLayerMap': 1, 'cmnTranscriptFile': './lvs.log', 'cmnTranscriptEchoToFile': 1, - 'lvsRecognizeGates': 'NONE', + 'lvsRecognizeGates': 'NONE', } # FIXME: Remove when vdd/gnd connected #'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee - # FIXME: Remove when vdd/gnd connected + # FIXME: Remove when vdd/gnd connected #'lvsAbortOnSupplyError' : 0 if not final_verification: @@ -106,7 +106,7 @@ def write_calibre_lvs_script(cell_name, final_verification): lvs_runset['cmnVConnectNames']='vdd gnd' else: lvs_runset['lvsAbortOnSupplyError']=1 - + # write the runset file @@ -127,20 +127,23 @@ def write_calibre_lvs_script(cell_name, final_verification): f.write("\n") f.close() os.system("chmod u+x {}".format(run_file)) - + return lvs_runset def write_calibre_pex_script(cell_name, extract, output, final_verification): - + if output == None: output = name + ".pex.netlist" # check if lvs report has been done # if not run drc and lvs if not os.path.isfile(cell_name + ".lvs.report"): + gds_name = OPTS.openram_temp +"/"+ cell_name + ".gds" + sp_name = OPTS.openram_temp +"/"+ cell_name + ".sp" run_drc(cell_name, gds_name) run_lvs(cell_name, gds_name, sp_name) + from tech import drc pex_rules = drc["xrc_rules"] pex_runset = { 'pexRulesFile': pex_rules, @@ -175,18 +178,18 @@ def write_calibre_pex_script(cell_name, extract, output, final_verification): os.system("chmod u+x {}".format(run_file)) return pex_runset - + def run_drc(cell_name, gds_name, extract=False, final_verification=False): """Run DRC check on a given top-level name which is implemented in gds_name.""" - + global num_drc_runs num_drc_runs += 1 # Copy file to local dir if it isn't already if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): shutil.copy(gds_name, OPTS.openram_temp) - + drc_runset = write_calibre_drc_script(cell_name, extract, final_verification) (outfile, errfile, resultsfile) = run_script(cell_name, "drc") @@ -209,12 +212,12 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): # always display this summary if errors > 0: - debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, errors)) else: - debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, errors)) @@ -225,10 +228,10 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): """Run LVS check on a given top-level name which is implemented in gds_name and sp_name. Final verification will ensure that there are no remaining virtual conections. """ - + global num_lvs_runs num_lvs_runs += 1 - + lvs_runset = write_calibre_lvs_script(cell_name, final_verification) # Copy file to local dir if it isn't already @@ -280,7 +283,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): # MRG - 9/26/17 - Change this to exclude warnings because of # multiple labels on different pins in column mux. ext_errors = len(exterrors) - ext_warnings = len(extwarnings) + ext_warnings = len(extwarnings) # also check the output file f = open(outfile, "r") @@ -297,16 +300,16 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): total_errors = summary_errors + out_errors + ext_errors if total_errors > 0: - debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, ext_errors)) else: - debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, ext_errors)) - + return total_errors @@ -317,10 +320,10 @@ def run_pex(cell_name, gds_name, sp_name, output=None, final_verification=False) global num_pex_runs num_pex_runs += 1 - write_calibre_pex_script() + write_calibre_pex_script(cell_name,True,output,final_verification) (outfile, errfile, resultsfile) = run_script(cell_name, "pex") - + # also check the output file f = open(outfile, "r") diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index fd082a45..63aeaabe 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -6,11 +6,11 @@ # All rights reserved. # """ -This is a DRC/LVS/PEX interface file for magic + netgen. +This is a DRC/LVS/PEX interface file for magic + netgen. We include the tech file for SCN4M_SUBM in the tech directory, -that is included in OpenRAM during DRC. -You can use this interactively by appending the magic system path in +that is included in OpenRAM during DRC. +You can use this interactively by appending the magic system path in your .magicrc file path sys /Users/mrg/openram/technology/scn3me_subm/tech @@ -33,7 +33,7 @@ num_drc_runs = 0 num_lvs_runs = 0 num_pex_runs = 0 - + def write_magic_script(cell_name, extract=False, final_verification=False): """ Write a magic script to perform DRC and optionally extraction. """ @@ -69,7 +69,7 @@ def write_magic_script(cell_name, extract=False, final_verification=False): if final_verification: f.write(pre+"extract unique all\n".format(cell_name)) f.write(pre+"extract\n".format(cell_name)) - #f.write(pre+"ext2spice hierarchy on\n") + #f.write(pre+"ext2spice hierarchy on\n") #f.write(pre+"ext2spice scale off\n") # lvs exists in 8.2.79, but be backword compatible for now #f.write(pre+"ext2spice lvs\n") @@ -82,18 +82,18 @@ def write_magic_script(cell_name, extract=False, final_verification=False): f.write(pre+"ext2spice blackbox on\n") f.write(pre+"ext2spice subcircuit top auto\n") f.write(pre+"ext2spice global off\n") - + # Can choose hspice, ngspice, or spice3, # but they all seem compatible enough. #f.write(pre+"ext2spice format ngspice\n") f.write(pre+"ext2spice {}\n".format(cell_name)) f.write("quit -noprompt\n") f.write("EOF\n") - + f.close() os.system("chmod u+x {}".format(run_file)) - + def write_netgen_script(cell_name): """ Write a netgen script to perform LVS. """ @@ -119,7 +119,7 @@ def write_netgen_script(cell_name): f.close() os.system("chmod u+x {}".format(run_file)) - + def run_drc(cell_name, gds_name, extract=True, final_verification=False): """Run DRC check on a cell which is implemented in gds_name.""" @@ -129,7 +129,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): # Copy file to local dir if it isn't already if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): shutil.copy(gds_name, OPTS.openram_temp) - + # Copy .magicrc file into temp dir magic_file = OPTS.openram_tech + "mag_lib/.magicrc" if os.path.exists(magic_file): @@ -151,7 +151,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): f = open(outfile, "r") except FileNotFoundError: debug.error("Unable to load DRC results file from {}. Is magic set up?".format(outfile),1) - + results = f.readlines() f.close() errors=1 @@ -162,7 +162,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): break else: debug.error("Unable to find the total error line in Magic output.",1) - + # always display this summary if errors > 0: @@ -189,19 +189,19 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): shutil.copy(gds_name, OPTS.openram_temp) if os.path.dirname(sp_name)!=OPTS.openram_temp.rstrip('/'): shutil.copy(sp_name, OPTS.openram_temp) - + write_netgen_script(cell_name) (outfile, errfile, resultsfile) = run_script(cell_name, "lvs") - + total_errors = 0 - + # check the result for these lines in the summary: try: f = open(resultsfile, "r") except FileNotFoundError: debug.error("Unable to load LVS results from {}".format(resultsfile),1) - + results = f.readlines() f.close() # Look for the results after the final "Subcircuit summary:" @@ -217,14 +217,14 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): test = re.compile("Property errors were found.") propertyerrors = list(filter(test.search, results)) total_errors += len(propertyerrors) - + # Require pins to match? # Cell pin lists for pnand2_1.spice and pnand2_1 altered to match. # test = re.compile(".*altered to match.") # pinerrors = list(filter(test.search, results)) # if len(pinerrors)>0: # debug.warning("Pins altered to match in {}.".format(cell_name)) - + #if len(propertyerrors)>0: # debug.warning("Property errors found, but not checking them.") @@ -232,7 +232,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): test = re.compile("Netlists do not match.") incorrect = list(filter(test.search, final_results)) total_errors += len(incorrect) - + # Netlists match uniquely. test = re.compile("match uniquely.") correct = list(filter(test.search, final_results)) @@ -244,7 +244,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): # Just print out the whole file, it is short. for e in results: debug.info(1,e.strip("\n")) - debug.error("{0}\tLVS mismatch (results in {1})".format(cell_name,resultsfile)) + debug.error("{0}\tLVS mismatch (results in {1})".format(cell_name,resultsfile)) else: debug.info(1, "{0}\tLVS matches".format(cell_name)) @@ -257,9 +257,9 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): global num_pex_runs num_pex_runs += 1 - - debug.warning("PEX using magic not implemented.") - return 1 + #debug.warning("PEX using magic not implemented.") + #return 1 + os.chdir(OPTS.openram_temp) from tech import drc if output == None: @@ -271,25 +271,67 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): run_drc(name, gds_name) run_lvs(name, gds_name, sp_name) - """ - 2. magic can perform extraction with the following: - #!/bin/sh - rm -f $1.ext - rm -f $1.spice - magic -dnull -noconsole << EOF - tech load SCN3ME_SUBM.30 - #scalegrid 1 2 - gds rescale no - gds polygon subcell true - gds warning default - gds read $1 - extract - ext2spice scale off - ext2spice - quit -noprompt - EOF - """ - + # pex_fix did run the pex using a script while dev orignial method + # use batch mode. + # the dev old code using batch mode does not run and is split into functions + #pex_runset = write_batch_pex_rule(gds_name,name,sp_name,output) + pex_runset = write_script_pex_rule(gds_name,name,output) + + errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name) + outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name) + + # bash mode command from dev branch + #batch_cmd = "{0} -gui -pex {1}pex_runset -batch 2> {2} 1> {3}".format(OPTS.pex_exe, + # OPTS.openram_temp, + # errfile, + # outfile) + script_cmd = "{0} 2> {1} 1> {2}".format(pex_runset, + errfile, + outfile) + cmd = script_cmd + debug.info(2, cmd) + os.system(cmd) + + # rename technology models + pex_nelist = open(output, 'r') + s = pex_nelist.read() + pex_nelist.close() + s = s.replace('pfet','p') + s = s.replace('nfet','n') + f = open(output, 'w') + f.write(s) + f.close() + + # also check the output file + f = open(outfile, "r") + results = f.readlines() + f.close() + out_errors = find_error(results) + debug.check(os.path.isfile(output),"Couldn't find PEX extracted output.") + + correct_port(name,output,sp_name) + return out_errors + +def write_batch_pex_rule(gds_name,name,sp_name,output): + """ + The dev branch old batch mode runset + 2. magic can perform extraction with the following: + #!/bin/sh + rm -f $1.ext + rm -f $1.spice + magic -dnull -noconsole << EOF + tech load SCN3ME_SUBM.30 + #scalegrid 1 2 + gds rescale no + gds polygon subcell true + gds warning default + gds read $1 + extract + ext2spice scale off + ext2spice + quit -noprompt + EOF + """ pex_rules = drc["xrc_rules"] pex_runset = { 'pexRulesFile': pex_rules, @@ -307,42 +349,89 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): } # write the runset file - f = open(OPTS.openram_temp + "pex_runset", "w") - for k in sorted(pex_runset.iterkeys()): + file = OPTS.openram_temp + "pex_runset" + f = open(file, "w") + for k in sorted(pex_runset.keys()): f.write("*{0}: {1}\n".format(k, pex_runset[k])) f.close() + return file - # run pex - cwd = os.getcwd() - os.chdir(OPTS.openram_temp) - errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name) - outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name) +def write_script_pex_rule(gds_name,cell_name,output): + global OPTS + run_file = OPTS.openram_temp + "run_pex.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + f.write("{} -dnull -noconsole << eof\n".format(OPTS.drc_exe[1])) + f.write("gds polygon subcell true\n") + f.write("gds warning default\n") + f.write("gds read {}\n".format(gds_name)) + f.write("load {}\n".format(cell_name)) + f.write("select top cell\n") + f.write("expand\n") + f.write("port makeall\n") + extract = True + if not extract: + pre = "#" + else: + pre = "" + f.write(pre+"extract\n".format(cell_name)) + #f.write(pre+"ext2spice hierarchy on\n") + #f.write(pre+"ext2spice format ngspice\n") + #f.write(pre+"ext2spice renumber off\n") + #f.write(pre+"ext2spice scale off\n") + #f.write(pre+"ext2spice blackbox on\n") + f.write(pre+"ext2spice subcircuit top on\n") + #f.write(pre+"ext2spice global off\n") + f.write(pre+"ext2spice {}\n".format(cell_name)) + f.write("quit -noprompt\n") + f.write("eof\n") + f.write("mv {0}.spice {1}\n".format(cell_name,output)) - cmd = "{0} -gui -pex {1}pex_runset -batch 2> {2} 1> {3}".format(OPTS.pex_exe, - OPTS.openram_temp, - errfile, - outfile) - debug.info(2, cmd) - os.system(cmd) - os.chdir(cwd) - - # also check the output file - f = open(outfile, "r") - results = f.readlines() f.close() + os.system("chmod u+x {}".format(run_file)) + return run_file +def find_error(results): # Errors begin with "ERROR:" test = re.compile("ERROR:") stdouterrors = list(filter(test.search, results)) for e in stdouterrors: debug.error(e.strip("\n")) - out_errors = len(stdouterrors) - - debug.check(os.path.isfile(output),"Couldn't find PEX extracted output.") - return out_errors +def correct_port(name, output_file_name, ref_file_name): + pex_file = open(output_file_name, "r") + contents = pex_file.read() + # locate the start of circuit definition line + match = re.search(".subckt " + str(name) + ".*", contents) + match_index_start = match.start() + pex_file.seek(match_index_start) + rest_text = pex_file.read() + # locate the end of circuit definition line + 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) + part1 = pex_file.read(match_index_start) + pex_file.seek(match_index_start + match_index_end) + part2 = pex_file.read() + pex_file.close() + + # obtain the correct definition line from the original spice file + sp_file = open(ref_file_name, "r") + contents = sp_file.read() + circuit_title = re.search(".SUBCKT " + str(name) + ".*\n", contents) + circuit_title = circuit_title.group() + sp_file.close() + + # write the new pex file with info in the memory + output_file = open(output_file_name, "w") + output_file.write(part1) + output_file.write(circuit_title) + output_file.write(part2) + output_file.close() + def print_drc_stats(): debug.info(1,"DRC runs: {0}".format(num_drc_runs)) def print_lvs_stats():