Merge remote-tracking branch 'origin/dev' into rbl_revamp

This commit is contained in:
mrg 2019-07-12 11:10:07 -07:00
commit a189b325ed
20 changed files with 772 additions and 143 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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))

View File

@ -20,8 +20,8 @@ import copy
import importlib
USAGE = "Usage: openram.py [options] <config file>\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).")

View File

@ -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")

View File

@ -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:

View File

@ -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 *

View File

@ -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 = ""

View File

@ -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)

View File

@ -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. """

View File

@ -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 """

View File

@ -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 """

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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():