From 718897e1235818a34c2916b6306f316c8982afc0 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 15 Aug 2018 14:30:55 -0700 Subject: [PATCH 1/7] Update section on local development contributions. --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c83a091a..6eceeaaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,13 @@ other OpenRAM features. Please see the README.md file on how to run the unit tests. Unit tests should work in all technologies. We will run the tests on your contributions before they will be accepted. +# Internally Development + +For internal development, follow all of the following steps EXCEPT +do not fork your own copy. Instead, create a branch in our private repository +and consult with me when you want to merge it into the dev branch. +All unit tests should pass first. + # Pull Request Process 1. One time, create a GitHub account at http://github.com From ea52af3747b71aa6efdc7fc25983a8b442008147 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Tue, 21 Aug 2018 09:28:07 -0700 Subject: [PATCH 2/7] Add sketch for power grid routing code --- compiler/globals.py | 2 +- compiler/router/regress.sh | 3 - compiler/router/tests/testutils.py | 43 -- compiler/sram_1bank.py | 80 ++- compiler/tests/03_contact_test.py | 3 - compiler/tests/03_path_test.py | 3 - compiler/tests/03_ptx_1finger_nmos_test.py | 3 - compiler/tests/03_ptx_1finger_pmos_test.py | 3 - compiler/tests/03_ptx_3finger_nmos_test.py | 3 - compiler/tests/03_ptx_3finger_pmos_test.py | 3 - compiler/tests/03_ptx_4finger_nmos_test.py | 3 - compiler/tests/03_ptx_4finger_pmos_test.py | 3 - compiler/tests/03_wire_test.py | 3 - compiler/tests/04_pbitcell_test.py | 3 - compiler/tests/04_pinv_10x_test.py | 3 - compiler/tests/04_pinv_1x_beta_test.py | 3 - compiler/tests/04_pinv_1x_test.py | 4 - compiler/tests/04_pinv_2x_test.py | 3 - compiler/tests/04_pinvbuf_test.py | 3 - compiler/tests/04_pnand2_test.py | 3 - compiler/tests/04_pnand3_test.py | 3 - compiler/tests/04_pnor2_test.py | 3 - compiler/tests/04_precharge_test.py | 3 - .../tests/04_single_level_column_mux_test.py | 3 - compiler/tests/05_bitcell_array_test.py | 3 - compiler/tests/05_pbitcell_array_test.py | 3 - .../tests/06_hierarchical_decoder_test.py | 3 - .../06_hierarchical_predecode2x4_test.py | 3 - .../06_hierarchical_predecode3x8_test.py | 3 - .../07_single_level_column_mux_array_test.py | 3 - compiler/tests/08_precharge_array_test.py | 3 - compiler/tests/08_wordline_driver_test.py | 3 - compiler/tests/09_sense_amp_array_test.py | 3 - compiler/tests/10_write_driver_array_test.py | 3 - compiler/tests/11_dff_array_test.py | 3 - compiler/tests/11_dff_buf_array_test.py | 3 - compiler/tests/11_dff_buf_test.py | 3 - compiler/tests/11_dff_inv_array_test.py | 3 - compiler/tests/11_dff_inv_test.py | 3 - compiler/tests/11_ms_flop_array_test.py | 3 - compiler/tests/12_tri_gate_array_test.py | 3 - compiler/tests/13_delay_chain_test.py | 3 - compiler/tests/14_replica_bitline_test.py | 3 - compiler/tests/16_control_logic_test.py | 3 - compiler/tests/19_bank_select_test.py | 3 - compiler/tests/19_multi_bank_test.py | 3 - compiler/tests/19_single_bank_test.py | 3 - compiler/tests/20_sram_1bank_test.py | 3 - compiler/tests/20_sram_2bank_test.py | 3 - compiler/tests/20_sram_4bank_test.py | 3 - compiler/tests/22_pex_test.py | 3 - compiler/tests/b3v33check.log | 4 + compiler/tests/missing_pin.gds | Bin 0 -> 20820 bytes compiler/tests/out.log | 0 compiler/tests/sram1.gds | Bin 0 -> 304042 bytes compiler/tests/sram1.lef | 19 + compiler/tests/sram1.sp | 602 ++++++++++++++++++ compiler/tests/sram1_TT_5p0V_25C.lib | 347 ++++++++++ {compiler/router => router}/cell.py | 0 {compiler/router => router}/grid.py | 0 {compiler/router => router}/router.py | 0 .../tests/01_no_blockages_test.py | 3 +- .../tests/01_no_blockages_test.sp | 0 .../tests/01_no_blockages_test_freepdk45.gds | Bin .../01_no_blockages_test_scn3me_subm.gds | Bin .../tests/02_blockages_test.py | 0 .../tests/02_blockages_test.sp | 0 .../tests/02_blockages_test_freepdk45.gds | Bin .../tests/02_blockages_test_scn3me_subm.gds | Bin .../tests/03_same_layer_pins_test.py | 0 .../tests/03_same_layer_pins_test.sp | 0 .../03_same_layer_pins_test_freepdk45.gds | Bin .../03_same_layer_pins_test_scn3me_subm.gds | Bin .../tests/04_diff_layer_pins_test.py | 0 .../tests/04_diff_layer_pins_test.sp | 0 .../04_diff_layer_pins_test_freepdk45.gds | Bin .../04_diff_layer_pins_test_scn3me_subm.gds | Bin .../tests/05_two_nets_test.py | 0 .../tests/05_two_nets_test.sp | 0 .../tests/05_two_nets_test_freepdk45.gds | Bin .../tests/05_two_nets_test_scn3me_subm.gds | Bin .../tests/06_pin_location_test.py | 0 .../tests/06_pin_location_test_freepdk45.gds | Bin .../06_pin_location_test_scn3me_subm.gds | Bin .../router => router}/tests/07_big_test.py | 0 .../tests/07_big_test_scn3me_subm.gds | Bin .../tests/08_expand_region_test.py | 0 .../tests/08_expand_region_test_freepdk45.gds | Bin .../08_expand_region_test_scn3me_subm.gds | Bin .../tests/config_freepdk45.py | 0 .../tests/config_scn3me_subm.py | 0 {compiler/router => router}/tests/regress.py | 21 +- router/tests/testutils.py | 257 ++++++++ {compiler/router => router}/vector3d.py | 0 94 files changed, 1326 insertions(+), 197 deletions(-) delete mode 100755 compiler/router/regress.sh delete mode 100644 compiler/router/tests/testutils.py create mode 100644 compiler/tests/b3v33check.log create mode 100644 compiler/tests/missing_pin.gds create mode 100644 compiler/tests/out.log create mode 100644 compiler/tests/sram1.gds create mode 100644 compiler/tests/sram1.lef create mode 100644 compiler/tests/sram1.sp create mode 100644 compiler/tests/sram1_TT_5p0V_25C.lib rename {compiler/router => router}/cell.py (100%) rename {compiler/router => router}/grid.py (100%) rename {compiler/router => router}/router.py (100%) rename {compiler/router => router}/tests/01_no_blockages_test.py (98%) mode change 100644 => 100755 rename {compiler/router => router}/tests/01_no_blockages_test.sp (100%) rename {compiler/router => router}/tests/01_no_blockages_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/01_no_blockages_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/02_blockages_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/02_blockages_test.sp (100%) rename {compiler/router => router}/tests/02_blockages_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/02_blockages_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/03_same_layer_pins_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/03_same_layer_pins_test.sp (100%) rename {compiler/router => router}/tests/03_same_layer_pins_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/03_same_layer_pins_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/04_diff_layer_pins_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/04_diff_layer_pins_test.sp (100%) rename {compiler/router => router}/tests/04_diff_layer_pins_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/04_diff_layer_pins_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/05_two_nets_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/05_two_nets_test.sp (100%) rename {compiler/router => router}/tests/05_two_nets_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/05_two_nets_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/06_pin_location_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/06_pin_location_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/06_pin_location_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/07_big_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/07_big_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/08_expand_region_test.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/08_expand_region_test_freepdk45.gds (100%) rename {compiler/router => router}/tests/08_expand_region_test_scn3me_subm.gds (100%) rename {compiler/router => router}/tests/config_freepdk45.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/config_scn3me_subm.py (100%) mode change 100644 => 100755 rename {compiler/router => router}/tests/regress.py (59%) mode change 100644 => 100755 create mode 100755 router/tests/testutils.py rename {compiler/router => router}/vector3d.py (100%) diff --git a/compiler/globals.py b/compiler/globals.py index 02168e37..faf5f97a 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -275,7 +275,7 @@ def setup_paths(): # Add all of the subdirs to the python path # These subdirs are modules and don't need to be added: characterizer, verify - for subdir in ["gdsMill", "tests", "router", "modules", "base", "pgates"]: + for subdir in ["gdsMill", "tests", "modules", "base", "pgates"]: full_path = "{0}/{1}".format(OPENRAM_HOME,subdir) debug.check(os.path.isdir(full_path), "$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path)) diff --git a/compiler/router/regress.sh b/compiler/router/regress.sh deleted file mode 100755 index 904ef9d8..00000000 --- a/compiler/router/regress.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -python tests/regress.py -t freepdk45 -python tests/regress.py -t scn3me_subm diff --git a/compiler/router/tests/testutils.py b/compiler/router/tests/testutils.py deleted file mode 100644 index 7b8e2cc6..00000000 --- a/compiler/router/tests/testutils.py +++ /dev/null @@ -1,43 +0,0 @@ - - -def isclose(value1,value2,error_tolerance=1e-2): - """ This is used to compare relative values. """ - import debug - relative_diff = abs(value1 - value2) / max(value1,value2) - check = relative_diff <= error_tolerance - if not check: - debug.info(1,"NOT CLOSE {0} {1} relative diff={2}".format(value1,value2,relative_diff)) - else: - debug.info(2,"CLOSE {0} {1} relative diff={2}".format(value1,value2,relative_diff)) - return (check) - -def isdiff(file1,file2): - """ This is used to compare two files and display the diff if they are different.. """ - import debug - import filecmp - import difflib - check = filecmp.cmp(file1,file2) - if not check: - debug.info(2,"MISMATCH {0} {1}".format(file1,file2)) - f1 = open(file1,"r") - s1 = f1.readlines() - f2 = open(file2,"r") - s2 = f2.readlines() - for line in difflib.unified_diff(s1, s2): - debug.error(line) - else: - debug.info(2,"MATCH {0} {1}".format(file1,file2)) - return (check) - -def header(filename, technology): - tst = "Running Test for:" - print "\n" - print " ______________________________________________________________________________ " - print "|==============================================================================|" - print "|=========" + tst.center(60) + "=========|" - print "|=========" + technology.center(60) + "=========|" - print "|=========" + filename.center(60) + "=========|" - import globals - OPTS = globals.get_opts() - print "|=========" + OPTS.openram_temp.center(60) + "=========|" - print "|==============================================================================|" diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index fe51d94f..6b2af57d 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -4,6 +4,7 @@ import debug from math import log,sqrt,ceil import datetime import getpass +import numpy as np from vector import vector from globals import OPTS, print_time @@ -175,7 +176,6 @@ class sram_1bank(sram_base): # the control logic to the bank self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, mid2_pos, control_clk_buf_pos]) - def route_vdd_gnd(self): """ Propagate all vdd/gnd pins up to this level for all modules """ @@ -192,6 +192,84 @@ class sram_1bank(sram_base): self.copy_layout_pin(inst, "vdd") self.copy_layout_pin(inst, "gnd") + def new_route_vdd_gnd(self): + """ Propagate all vdd/gnd pins up to this level for all modules """ + + # These are the instances that every bank has + top_instances = [self.bank_inst, + self.row_addr_dff_inst, + self.data_dff_inst, + self.control_logic_inst] + if self.col_addr_dff: + top_instances.append(self.col_addr_dff_inst) + + + # for inst in top_instances: + # self.copy_layout_pin(inst, "vdd") + # self.copy_layout_pin(inst, "gnd") + + blockages=self.get_blockages("metal3", top_level=True) + + # Gather all of the vdd/gnd pins + vdd_pins=[] + gnd_pins=[] + for inst in top_instances: + vdd_pins.extend([x for x in inst.get_pins("vdd") if x.layer == "metal3"]) + gnd_pins.extend([x for x in inst.get_pins("gnd") if x.layer == "metal3"]) + + # Create candidate stripes on M3/M4 + lowest=self.find_lowest_coords() + highest=self.find_highest_coords() + m3_y_coords = np.arange(lowest[1],highest[1],self.m2_pitch) + + # These are the rails that will be available for vdd/gnd + m3_rects = [] + # These are the "inflated" shapes for DRC checks + m3_drc_rects = [] + for y in m3_y_coords: + # This is just what metal will be drawn + ll = vector(lowest[0], y - 0.5*self.m3_width) + ur = vector(highest[0], y + 0.5*self.m3_width) + m3_rects.append([ll, ur]) + # This is a full m3 pitch for DRC conflict checking + ll = vector(lowest[0], y - 0.5*self.m3_pitch ) + ur = vector(highest[0], y + 0.5*self.m3_pitch) + m3_drc_rects.append([ll, ur]) + + vdd_rects = [] + gnd_rects = [] + + # Now, figure how if the rails intersect a blockage, vdd, or gnd pin + # Divide the rails up alternately + # This should be done in less than n^2 using a kd-tree or something + # for drc_rect,rect in zip(m3_drc_rects,m3_rects): + # for b in blockages: + # if rect_overlaps(b,drc_rect): + # break + # else: + # gnd_rects.append(rect) + + + + # Create the vdd and gnd rails + for rect in m3_rects: + (ll,ur) = rect + + for rect in gnd_rects: + (ll,ur) = rect + self.add_layout_pin(text="gnd", + layer="metal3", + offset=ll, + width=ur.x-ll.x, + height=ur.y-ll.y) + for rect in vdd_rects: + (ll,ur) = rect + self.add_layout_pin(text="vdd", + layer="metal3", + offset=ll, + width=ur.x-ll.x, + height=ur.y-ll.y) + def route_control_logic(self): """ Route the outputs from the control logic module """ for n in self.control_logic_outputs: diff --git a/compiler/tests/03_contact_test.py b/compiler/tests/03_contact_test.py index b5938cec..2fab1c4e 100755 --- a/compiler/tests/03_contact_test.py +++ b/compiler/tests/03_contact_test.py @@ -13,9 +13,6 @@ class contact_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import contact diff --git a/compiler/tests/03_path_test.py b/compiler/tests/03_path_test.py index 88a195c4..f70d00be 100755 --- a/compiler/tests/03_path_test.py +++ b/compiler/tests/03_path_test.py @@ -13,9 +13,6 @@ class path_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import path import tech import design diff --git a/compiler/tests/03_ptx_1finger_nmos_test.py b/compiler/tests/03_ptx_1finger_nmos_test.py index 02ef0f2b..727c24f0 100755 --- a/compiler/tests/03_ptx_1finger_nmos_test.py +++ b/compiler/tests/03_ptx_1finger_nmos_test.py @@ -13,9 +13,6 @@ class ptx_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ptx import tech diff --git a/compiler/tests/03_ptx_1finger_pmos_test.py b/compiler/tests/03_ptx_1finger_pmos_test.py index c00ccb72..04b9ab64 100755 --- a/compiler/tests/03_ptx_1finger_pmos_test.py +++ b/compiler/tests/03_ptx_1finger_pmos_test.py @@ -13,9 +13,6 @@ class ptx_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ptx import tech diff --git a/compiler/tests/03_ptx_3finger_nmos_test.py b/compiler/tests/03_ptx_3finger_nmos_test.py index 60293266..20343b2e 100755 --- a/compiler/tests/03_ptx_3finger_nmos_test.py +++ b/compiler/tests/03_ptx_3finger_nmos_test.py @@ -13,9 +13,6 @@ class ptx_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ptx import tech diff --git a/compiler/tests/03_ptx_3finger_pmos_test.py b/compiler/tests/03_ptx_3finger_pmos_test.py index 792de25f..37933702 100755 --- a/compiler/tests/03_ptx_3finger_pmos_test.py +++ b/compiler/tests/03_ptx_3finger_pmos_test.py @@ -13,9 +13,6 @@ class ptx_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ptx import tech diff --git a/compiler/tests/03_ptx_4finger_nmos_test.py b/compiler/tests/03_ptx_4finger_nmos_test.py index d4cc0da5..09788a5e 100755 --- a/compiler/tests/03_ptx_4finger_nmos_test.py +++ b/compiler/tests/03_ptx_4finger_nmos_test.py @@ -13,9 +13,6 @@ class ptx_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ptx import tech diff --git a/compiler/tests/03_ptx_4finger_pmos_test.py b/compiler/tests/03_ptx_4finger_pmos_test.py index c35390c9..f43d7dc7 100755 --- a/compiler/tests/03_ptx_4finger_pmos_test.py +++ b/compiler/tests/03_ptx_4finger_pmos_test.py @@ -13,9 +13,6 @@ class ptx_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ptx import tech diff --git a/compiler/tests/03_wire_test.py b/compiler/tests/03_wire_test.py index cf50dc10..557fee5b 100755 --- a/compiler/tests/03_wire_test.py +++ b/compiler/tests/03_wire_test.py @@ -13,9 +13,6 @@ class wire_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import wire import tech import design diff --git a/compiler/tests/04_pbitcell_test.py b/compiler/tests/04_pbitcell_test.py index 15b64f93..3ecd2762 100755 --- a/compiler/tests/04_pbitcell_test.py +++ b/compiler/tests/04_pbitcell_test.py @@ -20,9 +20,6 @@ class pbitcell_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pbitcell import tech diff --git a/compiler/tests/04_pinv_10x_test.py b/compiler/tests/04_pinv_10x_test.py index 09eef9c3..3a7f846a 100755 --- a/compiler/tests/04_pinv_10x_test.py +++ b/compiler/tests/04_pinv_10x_test.py @@ -15,9 +15,6 @@ class pinv_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pinv import tech diff --git a/compiler/tests/04_pinv_1x_beta_test.py b/compiler/tests/04_pinv_1x_beta_test.py index 17829e14..c1bb6aba 100755 --- a/compiler/tests/04_pinv_1x_beta_test.py +++ b/compiler/tests/04_pinv_1x_beta_test.py @@ -15,9 +15,6 @@ class pinv_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pinv import tech diff --git a/compiler/tests/04_pinv_1x_test.py b/compiler/tests/04_pinv_1x_test.py index a0db1100..555aa0e5 100755 --- a/compiler/tests/04_pinv_1x_test.py +++ b/compiler/tests/04_pinv_1x_test.py @@ -14,10 +14,6 @@ class pinv_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - - import pinv import tech diff --git a/compiler/tests/04_pinv_2x_test.py b/compiler/tests/04_pinv_2x_test.py index 66ea15da..6882a719 100755 --- a/compiler/tests/04_pinv_2x_test.py +++ b/compiler/tests/04_pinv_2x_test.py @@ -15,9 +15,6 @@ class pinv_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pinv import tech diff --git a/compiler/tests/04_pinvbuf_test.py b/compiler/tests/04_pinvbuf_test.py index 3b16c1c9..9c55ebe3 100755 --- a/compiler/tests/04_pinvbuf_test.py +++ b/compiler/tests/04_pinvbuf_test.py @@ -15,9 +15,6 @@ class pinvbuf_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pinvbuf debug.info(2, "Testing inverter/buffer 4x 8x") diff --git a/compiler/tests/04_pnand2_test.py b/compiler/tests/04_pnand2_test.py index af35bae8..b6739e4e 100755 --- a/compiler/tests/04_pnand2_test.py +++ b/compiler/tests/04_pnand2_test.py @@ -17,9 +17,6 @@ class pnand2_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pnand2 import tech diff --git a/compiler/tests/04_pnand3_test.py b/compiler/tests/04_pnand3_test.py index 6984a0e0..db3817f5 100755 --- a/compiler/tests/04_pnand3_test.py +++ b/compiler/tests/04_pnand3_test.py @@ -17,9 +17,6 @@ class pnand3_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pnand3 import tech diff --git a/compiler/tests/04_pnor2_test.py b/compiler/tests/04_pnor2_test.py index a15f6907..978c03ad 100755 --- a/compiler/tests/04_pnor2_test.py +++ b/compiler/tests/04_pnor2_test.py @@ -17,9 +17,6 @@ class pnor2_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import pnor2 import tech diff --git a/compiler/tests/04_precharge_test.py b/compiler/tests/04_precharge_test.py index 54f3dabb..bcf11473 100644 --- a/compiler/tests/04_precharge_test.py +++ b/compiler/tests/04_precharge_test.py @@ -15,9 +15,6 @@ class precharge_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import precharge import tech diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_single_level_column_mux_test.py index d282a11a..01dc4445 100755 --- a/compiler/tests/04_single_level_column_mux_test.py +++ b/compiler/tests/04_single_level_column_mux_test.py @@ -17,9 +17,6 @@ class single_level_column_mux_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import single_level_column_mux import tech diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index 37649ad5..4ea5c65a 100755 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -17,9 +17,6 @@ class array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import bitcell_array debug.info(2, "Testing 4x4 array for 6t_cell") diff --git a/compiler/tests/05_pbitcell_array_test.py b/compiler/tests/05_pbitcell_array_test.py index e8c16607..a0e3c7a2 100755 --- a/compiler/tests/05_pbitcell_array_test.py +++ b/compiler/tests/05_pbitcell_array_test.py @@ -16,9 +16,6 @@ class pbitcell_array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import bitcell_array debug.info(2, "Testing 4x4 array for multiport bitcell, with read ports at the edge of the bit cell") diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index eb0b105a..ab4f5f90 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -15,9 +15,6 @@ class hierarchical_decoder_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import hierarchical_decoder import tech diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index 79db3665..80b95b46 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -15,9 +15,6 @@ class hierarchical_predecode2x4_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import hierarchical_predecode2x4 as pre import tech diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index b6609829..0974ced3 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -15,9 +15,6 @@ class hierarchical_predecode3x8_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import hierarchical_predecode3x8 as pre import tech diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_single_level_column_mux_array_test.py index 1becd179..ea231292 100755 --- a/compiler/tests/07_single_level_column_mux_array_test.py +++ b/compiler/tests/07_single_level_column_mux_array_test.py @@ -14,9 +14,6 @@ class single_level_column_mux_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import single_level_column_mux_array debug.info(1, "Testing sample for 2-way column_mux_array") diff --git a/compiler/tests/08_precharge_array_test.py b/compiler/tests/08_precharge_array_test.py index 236d8cf1..9592b6c5 100644 --- a/compiler/tests/08_precharge_array_test.py +++ b/compiler/tests/08_precharge_array_test.py @@ -15,9 +15,6 @@ class precharge_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import precharge_array import tech diff --git a/compiler/tests/08_wordline_driver_test.py b/compiler/tests/08_wordline_driver_test.py index af97f018..9c91c7dd 100755 --- a/compiler/tests/08_wordline_driver_test.py +++ b/compiler/tests/08_wordline_driver_test.py @@ -17,9 +17,6 @@ class wordline_driver_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import wordline_driver import tech diff --git a/compiler/tests/09_sense_amp_array_test.py b/compiler/tests/09_sense_amp_array_test.py index ba483218..51620495 100755 --- a/compiler/tests/09_sense_amp_array_test.py +++ b/compiler/tests/09_sense_amp_array_test.py @@ -15,9 +15,6 @@ class sense_amp_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import sense_amp_array diff --git a/compiler/tests/10_write_driver_array_test.py b/compiler/tests/10_write_driver_array_test.py index d7f1f7ec..27538d0b 100755 --- a/compiler/tests/10_write_driver_array_test.py +++ b/compiler/tests/10_write_driver_array_test.py @@ -15,9 +15,6 @@ class write_driver_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import write_driver_array debug.info(2, "Testing write_driver_array for columns=8, word_size=8") diff --git a/compiler/tests/11_dff_array_test.py b/compiler/tests/11_dff_array_test.py index e85056e9..a55c6407 100755 --- a/compiler/tests/11_dff_array_test.py +++ b/compiler/tests/11_dff_array_test.py @@ -15,9 +15,6 @@ class dff_array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import dff_array debug.info(2, "Testing dff_array for 3x3") diff --git a/compiler/tests/11_dff_buf_array_test.py b/compiler/tests/11_dff_buf_array_test.py index 6c40e447..f0b75552 100755 --- a/compiler/tests/11_dff_buf_array_test.py +++ b/compiler/tests/11_dff_buf_array_test.py @@ -15,9 +15,6 @@ class dff_buf_array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import dff_buf_array debug.info(2, "Testing dff_buf_array for 3x3") diff --git a/compiler/tests/11_dff_buf_test.py b/compiler/tests/11_dff_buf_test.py index 44aca54c..f434f768 100755 --- a/compiler/tests/11_dff_buf_test.py +++ b/compiler/tests/11_dff_buf_test.py @@ -15,9 +15,6 @@ class dff_buf_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import dff_buf debug.info(2, "Testing dff_buf 4x 8x") diff --git a/compiler/tests/11_dff_inv_array_test.py b/compiler/tests/11_dff_inv_array_test.py index 3d2b8cac..2196a3f2 100755 --- a/compiler/tests/11_dff_inv_array_test.py +++ b/compiler/tests/11_dff_inv_array_test.py @@ -15,9 +15,6 @@ class dff_inv_array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import dff_inv_array debug.info(2, "Testing dff_inv_array for 3x3") diff --git a/compiler/tests/11_dff_inv_test.py b/compiler/tests/11_dff_inv_test.py index b09a6591..43d49246 100755 --- a/compiler/tests/11_dff_inv_test.py +++ b/compiler/tests/11_dff_inv_test.py @@ -15,9 +15,6 @@ class dff_inv_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import dff_inv debug.info(2, "Testing dff_inv 4x") diff --git a/compiler/tests/11_ms_flop_array_test.py b/compiler/tests/11_ms_flop_array_test.py index 97ef6ece..d6472a15 100755 --- a/compiler/tests/11_ms_flop_array_test.py +++ b/compiler/tests/11_ms_flop_array_test.py @@ -15,9 +15,6 @@ class dff_array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import ms_flop_array debug.info(2, "Testing ms_flop_array for columns=8, word_size=8") diff --git a/compiler/tests/12_tri_gate_array_test.py b/compiler/tests/12_tri_gate_array_test.py index 08d1596d..4f9cfa3e 100755 --- a/compiler/tests/12_tri_gate_array_test.py +++ b/compiler/tests/12_tri_gate_array_test.py @@ -15,9 +15,6 @@ class tri_gate_array_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import tri_gate_array debug.info(1, "Testing tri_gate_array for columns=8, word_size=8") diff --git a/compiler/tests/13_delay_chain_test.py b/compiler/tests/13_delay_chain_test.py index d8a2d67c..2cc745c2 100755 --- a/compiler/tests/13_delay_chain_test.py +++ b/compiler/tests/13_delay_chain_test.py @@ -15,9 +15,6 @@ class delay_chain_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import delay_chain debug.info(2, "Testing delay_chain") diff --git a/compiler/tests/14_replica_bitline_test.py b/compiler/tests/14_replica_bitline_test.py index b393c136..6ecd612a 100755 --- a/compiler/tests/14_replica_bitline_test.py +++ b/compiler/tests/14_replica_bitline_test.py @@ -15,9 +15,6 @@ class replica_bitline_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import replica_bitline stages=4 diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py index 7aeb54c1..3227a425 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -15,9 +15,6 @@ class control_logic_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import control_logic import tech diff --git a/compiler/tests/19_bank_select_test.py b/compiler/tests/19_bank_select_test.py index 955c7b4e..0f4cb05f 100755 --- a/compiler/tests/19_bank_select_test.py +++ b/compiler/tests/19_bank_select_test.py @@ -15,9 +15,6 @@ class bank_select_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - import bank_select debug.info(1, "No column mux") diff --git a/compiler/tests/19_multi_bank_test.py b/compiler/tests/19_multi_bank_test.py index e7e6c321..2169dcf1 100755 --- a/compiler/tests/19_multi_bank_test.py +++ b/compiler/tests/19_multi_bank_test.py @@ -15,9 +15,6 @@ class multi_bank_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - from bank import bank debug.info(1, "No column mux") diff --git a/compiler/tests/19_single_bank_test.py b/compiler/tests/19_single_bank_test.py index 697051ee..96291201 100755 --- a/compiler/tests/19_single_bank_test.py +++ b/compiler/tests/19_single_bank_test.py @@ -15,9 +15,6 @@ class single_bank_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - from bank import bank debug.info(1, "No column mux") diff --git a/compiler/tests/20_sram_1bank_test.py b/compiler/tests/20_sram_1bank_test.py index 8b83f54a..0ee98c60 100755 --- a/compiler/tests/20_sram_1bank_test.py +++ b/compiler/tests/20_sram_1bank_test.py @@ -15,9 +15,6 @@ class sram_1bank_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - from sram import sram debug.info(1, "Single bank, no column mux with control logic") diff --git a/compiler/tests/20_sram_2bank_test.py b/compiler/tests/20_sram_2bank_test.py index 993cae93..00be14df 100755 --- a/compiler/tests/20_sram_2bank_test.py +++ b/compiler/tests/20_sram_2bank_test.py @@ -16,9 +16,6 @@ class sram_2bank_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - from sram import sram debug.info(1, "Two bank, no column mux with control logic") diff --git a/compiler/tests/20_sram_4bank_test.py b/compiler/tests/20_sram_4bank_test.py index b94b660a..e4a28090 100755 --- a/compiler/tests/20_sram_4bank_test.py +++ b/compiler/tests/20_sram_4bank_test.py @@ -16,9 +16,6 @@ class sram_4bank_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - from sram import sram debug.info(1, "Four bank, no column mux with control logic") diff --git a/compiler/tests/22_pex_test.py b/compiler/tests/22_pex_test.py index 24cb7733..7755a2c7 100755 --- a/compiler/tests/22_pex_test.py +++ b/compiler/tests/22_pex_test.py @@ -27,9 +27,6 @@ class sram_func_test(openram_test): if not OPTS.spice_exe: debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) - global verify - import verify - self.func_test(bank_num=1) self.func_test(bank_num=2) self.func_test(bank_num=4) diff --git a/compiler/tests/b3v33check.log b/compiler/tests/b3v33check.log new file mode 100644 index 00000000..0cc058a3 --- /dev/null +++ b/compiler/tests/b3v33check.log @@ -0,0 +1,4 @@ +BSIM3v3.3.0 Parameter Checking. +Model = p +Warning: Pd = 0 is less than W. +Warning: Ps = 0 is less than W. diff --git a/compiler/tests/missing_pin.gds b/compiler/tests/missing_pin.gds new file mode 100644 index 0000000000000000000000000000000000000000..d73fd56fde5e599744bd2d22f346e254fb3e505c GIT binary patch literal 20820 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 F{{>=#MH~PC literal 0 HcmV?d00001 diff --git a/compiler/tests/out.log b/compiler/tests/out.log new file mode 100644 index 00000000..e69de29b diff --git a/compiler/tests/sram1.gds b/compiler/tests/sram1.gds new file mode 100644 index 0000000000000000000000000000000000000000..d6ed28eb6391318f026a98274c50b90601202712 GIT binary patch literal 304042 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 SUFciqRxbEw86)q=Nbi5xVB-z| literal 0 HcmV?d00001 diff --git a/compiler/tests/sram1.lef b/compiler/tests/sram1.lef new file mode 100644 index 00000000..c0563063 --- /dev/null +++ b/compiler/tests/sram1.lef @@ -0,0 +1,19 @@ +VERSION 5.4 ; +NAMESCASESENSITIVE ON ; +BUSBITCHARS "[]" ; +DIVIDERCHAR "/" ; +UNITS + DATABASE MICRONS 1000 ; +END UNITS +SITE MacroSite + CLASS Core ; + SIZE 324000.0 by 421500.0 ; +END MacroSite +MACRO sram1 + CLASS BLOCK ; + SIZE 324000.0 BY 421500.0 ; + SYMMETRY X Y R90 ; + SITE MacroSite ; + PIN DIN[0] + DIRECTION INPUT ; + PORT diff --git a/compiler/tests/sram1.sp b/compiler/tests/sram1.sp new file mode 100644 index 00000000..622af672 --- /dev/null +++ b/compiler/tests/sram1.sp @@ -0,0 +1,602 @@ +************************************************** +* OpenRAM generated memory. +* Words: 16 +* Data bits: 4 +* Banks: 1 +* Column mux: 1:1 +************************************************** +* Positive edge-triggered FF +.subckt dff D Q clk vdd gnd +M0 vdd clk a_2_6# vdd p w=12u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M1 a_17_74# D vdd vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M2 a_22_6# clk a_17_74# vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M3 a_31_74# a_2_6# a_22_6# vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M4 vdd a_34_4# a_31_74# vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M5 a_34_4# a_22_6# vdd vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M6 a_61_74# a_34_4# vdd vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M7 a_66_6# a_2_6# a_61_74# vdd p w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M8 a_76_84# clk a_66_6# vdd p w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M9 vdd Q a_76_84# vdd p w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M10 gnd clk a_2_6# gnd n w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M11 Q a_66_6# vdd vdd p w=12u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M12 a_17_6# D gnd gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M13 a_22_6# a_2_6# a_17_6# gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M14 a_31_6# clk a_22_6# gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M15 gnd a_34_4# a_31_6# gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M16 a_34_4# a_22_6# gnd gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M17 a_61_6# a_34_4# gnd gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M18 a_66_6# clk a_61_6# gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M19 a_76_6# a_2_6# a_66_6# gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M20 gnd Q a_76_6# gnd n w=3u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +M21 Q a_66_6# gnd gnd n w=6u l=0.6u ++ ad=0p pd=0u as=0p ps=0u +.ends dff + +* ptx M{0} {1} n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p + +* ptx M{0} {1} p m=1 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p + +.SUBCKT pinv_2 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p +Mpinv_nmos Z A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pinv_2 + +.SUBCKT dff_inv_2 D Q Qb clk vdd gnd +Xdff_inv_dff D Q clk vdd gnd dff +Xdff_inv_inv1 Q Qb vdd gnd pinv_2 +.ENDS dff_inv_2 + +.SUBCKT dff_array_3x1 din[0] din[1] din[2] dout[0] dout_bar[0] dout[1] dout_bar[1] dout[2] dout_bar[2] clk vdd gnd +XXdff_r0_c0 din[0] dout[0] dout_bar[0] clk vdd gnd dff_inv_2 +XXdff_r1_c0 din[1] dout[1] dout_bar[1] clk vdd gnd dff_inv_2 +XXdff_r2_c0 din[2] dout[2] dout_bar[2] clk vdd gnd dff_inv_2 +.ENDS dff_array_3x1 + +* ptx M{0} {1} p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p + +.SUBCKT pnand2_1 A B Z vdd gnd +Mpnand2_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos1 Z B net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos2 net1 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand2_1 + +.SUBCKT pnand3_1 A B C Z vdd gnd +Mpnand3_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_pmos3 Z C vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos1 Z C net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos2 net1 B net2 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos3 net2 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand3_1 + +* ptx M{0} {1} n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p + +.SUBCKT pinv_3 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_3 + +* ptx M{0} {1} n m=1 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p + +* ptx M{0} {1} p m=1 w=9.6u l=0.6u pd=20.4u ps=20.4u as=14.399999999999999p ad=14.399999999999999p + +.SUBCKT pinv_4 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=9.6u l=0.6u pd=20.4u ps=20.4u as=14.399999999999999p ad=14.399999999999999p +Mpinv_nmos Z A gnd gnd n m=1 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p +.ENDS pinv_4 + +* ptx M{0} {1} n m=4 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p + +* ptx M{0} {1} p m=4 w=9.6u l=0.6u pd=20.4u ps=20.4u as=14.399999999999999p ad=14.399999999999999p + +.SUBCKT pinv_5 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=4 w=9.6u l=0.6u pd=20.4u ps=20.4u as=14.399999999999999p ad=14.399999999999999p +Mpinv_nmos Z A gnd gnd n m=4 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p +.ENDS pinv_5 + +.SUBCKT pinvbuf_4_16 A Zb Z vdd gnd +Xbuf_inv1 A zb_int vdd gnd pinv_3 +Xbuf_inv2 zb_int z_int vdd gnd pinv_4 +Xbuf_inv3 z_int Zb vdd gnd pinv_5 +Xbuf_inv4 zb_int Z vdd gnd pinv_5 +.ENDS pinvbuf_4_16 + +.SUBCKT pinv_6 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_6 + +.SUBCKT pinv_7 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=9.6u l=0.6u pd=20.4u ps=20.4u as=14.399999999999999p ad=14.399999999999999p +Mpinv_nmos Z A gnd gnd n m=1 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p +.ENDS pinv_7 + +.SUBCKT pinv_8 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=4 w=9.6u l=0.6u pd=20.4u ps=20.4u as=14.399999999999999p ad=14.399999999999999p +Mpinv_nmos Z A gnd gnd n m=4 w=4.8u l=0.6u pd=10.799999999999999u ps=10.799999999999999u as=7.199999999999999p ad=7.199999999999999p +.ENDS pinv_8 + +*********************** "cell_6t" ****************************** +.SUBCKT replica_cell_6t bl br wl vdd gnd +M_1 gnd net_2 vdd vdd p W='0.9u' L=1.2u +M_2 net_2 gnd vdd vdd p W='0.9u' L=1.2u +M_3 br wl net_2 gnd n W='1.2u' L=0.6u +M_4 bl wl gnd gnd n W='1.2u' L=0.6u +M_5 net_2 gnd gnd gnd n W='2.4u' L=0.6u +M_6 gnd net_2 gnd gnd n W='2.4u' L=0.6u +.ENDS $ replica_cell_6t + +*********************** "cell_6t" ****************************** +.SUBCKT cell_6t bl br wl vdd gnd +M_1 net_1 net_2 vdd vdd p W='0.9u' L=1.2u +M_2 net_2 net_1 vdd vdd p W='0.9u' L=1.2u +M_3 br wl net_2 gnd n W='1.2u' L=0.6u +M_4 bl wl net_1 gnd n W='1.2u' L=0.6u +M_5 net_2 net_1 gnd gnd n W='2.4u' L=0.6u +M_6 net_1 net_2 gnd gnd n W='2.4u' L=0.6u +.ENDS $ cell_6t + +.SUBCKT bitline_load bl[0] br[0] wl[0] wl[1] wl[2] wl[3] vdd gnd +Xbit_r0_c0 bl[0] br[0] wl[0] vdd gnd cell_6t +Xbit_r1_c0 bl[0] br[0] wl[1] vdd gnd cell_6t +Xbit_r2_c0 bl[0] br[0] wl[2] vdd gnd cell_6t +Xbit_r3_c0 bl[0] br[0] wl[3] vdd gnd cell_6t +.ENDS bitline_load + +.SUBCKT pinv_9 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_9 + +.SUBCKT delay_chain in out vdd gnd +Xdinv0 in dout_1 vdd gnd pinv_9 +Xdload_0_0 dout_1 n_0_0 vdd gnd pinv_9 +Xdload_0_1 dout_1 n_0_1 vdd gnd pinv_9 +Xdload_0_2 dout_1 n_0_2 vdd gnd pinv_9 +Xdinv1 dout_1 dout_2 vdd gnd pinv_9 +Xdload_1_0 dout_2 n_1_0 vdd gnd pinv_9 +Xdload_1_1 dout_2 n_1_1 vdd gnd pinv_9 +Xdload_1_2 dout_2 n_1_2 vdd gnd pinv_9 +Xdinv2 dout_2 out vdd gnd pinv_9 +Xdload_2_0 out n_2_0 vdd gnd pinv_9 +Xdload_2_1 out n_2_1 vdd gnd pinv_9 +Xdload_2_2 out n_2_2 vdd gnd pinv_9 +.ENDS delay_chain + +.SUBCKT pinv_10 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_10 + +* ptx M{0} {1} p m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p + +.SUBCKT replica_bitline en out vdd gnd +Xrbl_inv bl[0] out vdd gnd pinv_10 +Mrbl_access_tx vdd delayed_en bl[0] vdd p m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +Xdelay_chain en delayed_en vdd gnd delay_chain +Xbitcell bl[0] br[0] delayed_en vdd gnd replica_cell_6t +Xload bl[0] br[0] gnd gnd gnd gnd vdd gnd bitline_load +.ENDS replica_bitline + +.SUBCKT control_logic csb web oeb clk s_en w_en tri_en tri_en_bar clk_buf_bar clk_buf vdd gnd +Xctrl_dffs csb web oeb cs_bar cs we_bar we oe_bar oe clk_buf vdd gnd dff_array_3x1 +Xclkbuf clk clk_buf_bar clk_buf vdd gnd pinvbuf_4_16 +Xnand3_w_en_bar clk_buf_bar cs we w_en_bar vdd gnd pnand3_1 +Xinv_pre_w_en w_en_bar pre_w_en vdd gnd pinv_6 +Xinv_pre_w_en_bar pre_w_en pre_w_en_bar vdd gnd pinv_7 +Xinv_w_en2 pre_w_en_bar w_en vdd gnd pinv_8 +Xinv_tri_en1 pre_tri_en_bar pre_tri_en1 vdd gnd pinv_7 +Xtri_en_buf1 pre_tri_en1 pre_tri_en_bar1 vdd gnd pinv_7 +Xtri_en_buf2 pre_tri_en_bar1 tri_en vdd gnd pinv_8 +Xnand2_tri_en clk_buf_bar oe pre_tri_en_bar vdd gnd pnand2_1 +Xtri_en_bar_buf1 pre_tri_en_bar pre_tri_en2 vdd gnd pinv_7 +Xtri_en_bar_buf2 pre_tri_en2 tri_en_bar vdd gnd pinv_8 +Xnand3_rblk_bar clk_buf_bar oe cs rblk_bar vdd gnd pnand3_1 +Xinv_rblk rblk_bar rblk vdd gnd pinv_6 +Xinv_s_en pre_s_en_bar s_en vdd gnd pinv_8 +Xinv_pre_s_en_bar pre_s_en pre_s_en_bar vdd gnd pinv_7 +Xreplica_bitline rblk pre_s_en vdd gnd replica_bitline +.ENDS control_logic + +.SUBCKT dff_array din[0] din[1] din[2] din[3] dout[0] dout[1] dout[2] dout[3] clk vdd gnd +XXdff_r0_c0 din[0] dout[0] clk vdd gnd dff +XXdff_r1_c0 din[1] dout[1] clk vdd gnd dff +XXdff_r2_c0 din[2] dout[2] clk vdd gnd dff +XXdff_r3_c0 din[3] dout[3] clk vdd gnd dff +.ENDS dff_array + +.SUBCKT bitcell_array bl[0] br[0] bl[1] br[1] bl[2] br[2] bl[3] br[3] wl[0] wl[1] wl[2] wl[3] wl[4] wl[5] wl[6] wl[7] wl[8] wl[9] wl[10] wl[11] wl[12] wl[13] wl[14] wl[15] vdd gnd +Xbit_r0_c0 bl[0] br[0] wl[0] vdd gnd cell_6t +Xbit_r1_c0 bl[0] br[0] wl[1] vdd gnd cell_6t +Xbit_r2_c0 bl[0] br[0] wl[2] vdd gnd cell_6t +Xbit_r3_c0 bl[0] br[0] wl[3] vdd gnd cell_6t +Xbit_r4_c0 bl[0] br[0] wl[4] vdd gnd cell_6t +Xbit_r5_c0 bl[0] br[0] wl[5] vdd gnd cell_6t +Xbit_r6_c0 bl[0] br[0] wl[6] vdd gnd cell_6t +Xbit_r7_c0 bl[0] br[0] wl[7] vdd gnd cell_6t +Xbit_r8_c0 bl[0] br[0] wl[8] vdd gnd cell_6t +Xbit_r9_c0 bl[0] br[0] wl[9] vdd gnd cell_6t +Xbit_r10_c0 bl[0] br[0] wl[10] vdd gnd cell_6t +Xbit_r11_c0 bl[0] br[0] wl[11] vdd gnd cell_6t +Xbit_r12_c0 bl[0] br[0] wl[12] vdd gnd cell_6t +Xbit_r13_c0 bl[0] br[0] wl[13] vdd gnd cell_6t +Xbit_r14_c0 bl[0] br[0] wl[14] vdd gnd cell_6t +Xbit_r15_c0 bl[0] br[0] wl[15] vdd gnd cell_6t +Xbit_r0_c1 bl[1] br[1] wl[0] vdd gnd cell_6t +Xbit_r1_c1 bl[1] br[1] wl[1] vdd gnd cell_6t +Xbit_r2_c1 bl[1] br[1] wl[2] vdd gnd cell_6t +Xbit_r3_c1 bl[1] br[1] wl[3] vdd gnd cell_6t +Xbit_r4_c1 bl[1] br[1] wl[4] vdd gnd cell_6t +Xbit_r5_c1 bl[1] br[1] wl[5] vdd gnd cell_6t +Xbit_r6_c1 bl[1] br[1] wl[6] vdd gnd cell_6t +Xbit_r7_c1 bl[1] br[1] wl[7] vdd gnd cell_6t +Xbit_r8_c1 bl[1] br[1] wl[8] vdd gnd cell_6t +Xbit_r9_c1 bl[1] br[1] wl[9] vdd gnd cell_6t +Xbit_r10_c1 bl[1] br[1] wl[10] vdd gnd cell_6t +Xbit_r11_c1 bl[1] br[1] wl[11] vdd gnd cell_6t +Xbit_r12_c1 bl[1] br[1] wl[12] vdd gnd cell_6t +Xbit_r13_c1 bl[1] br[1] wl[13] vdd gnd cell_6t +Xbit_r14_c1 bl[1] br[1] wl[14] vdd gnd cell_6t +Xbit_r15_c1 bl[1] br[1] wl[15] vdd gnd cell_6t +Xbit_r0_c2 bl[2] br[2] wl[0] vdd gnd cell_6t +Xbit_r1_c2 bl[2] br[2] wl[1] vdd gnd cell_6t +Xbit_r2_c2 bl[2] br[2] wl[2] vdd gnd cell_6t +Xbit_r3_c2 bl[2] br[2] wl[3] vdd gnd cell_6t +Xbit_r4_c2 bl[2] br[2] wl[4] vdd gnd cell_6t +Xbit_r5_c2 bl[2] br[2] wl[5] vdd gnd cell_6t +Xbit_r6_c2 bl[2] br[2] wl[6] vdd gnd cell_6t +Xbit_r7_c2 bl[2] br[2] wl[7] vdd gnd cell_6t +Xbit_r8_c2 bl[2] br[2] wl[8] vdd gnd cell_6t +Xbit_r9_c2 bl[2] br[2] wl[9] vdd gnd cell_6t +Xbit_r10_c2 bl[2] br[2] wl[10] vdd gnd cell_6t +Xbit_r11_c2 bl[2] br[2] wl[11] vdd gnd cell_6t +Xbit_r12_c2 bl[2] br[2] wl[12] vdd gnd cell_6t +Xbit_r13_c2 bl[2] br[2] wl[13] vdd gnd cell_6t +Xbit_r14_c2 bl[2] br[2] wl[14] vdd gnd cell_6t +Xbit_r15_c2 bl[2] br[2] wl[15] vdd gnd cell_6t +Xbit_r0_c3 bl[3] br[3] wl[0] vdd gnd cell_6t +Xbit_r1_c3 bl[3] br[3] wl[1] vdd gnd cell_6t +Xbit_r2_c3 bl[3] br[3] wl[2] vdd gnd cell_6t +Xbit_r3_c3 bl[3] br[3] wl[3] vdd gnd cell_6t +Xbit_r4_c3 bl[3] br[3] wl[4] vdd gnd cell_6t +Xbit_r5_c3 bl[3] br[3] wl[5] vdd gnd cell_6t +Xbit_r6_c3 bl[3] br[3] wl[6] vdd gnd cell_6t +Xbit_r7_c3 bl[3] br[3] wl[7] vdd gnd cell_6t +Xbit_r8_c3 bl[3] br[3] wl[8] vdd gnd cell_6t +Xbit_r9_c3 bl[3] br[3] wl[9] vdd gnd cell_6t +Xbit_r10_c3 bl[3] br[3] wl[10] vdd gnd cell_6t +Xbit_r11_c3 bl[3] br[3] wl[11] vdd gnd cell_6t +Xbit_r12_c3 bl[3] br[3] wl[12] vdd gnd cell_6t +Xbit_r13_c3 bl[3] br[3] wl[13] vdd gnd cell_6t +Xbit_r14_c3 bl[3] br[3] wl[14] vdd gnd cell_6t +Xbit_r15_c3 bl[3] br[3] wl[15] vdd gnd cell_6t +.ENDS bitcell_array + +* ptx M{0} {1} p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p + +.SUBCKT precharge bl br en vdd +Mlower_pmos bl en br vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mupper_pmos1 bl en vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mupper_pmos2 br en vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS precharge + +.SUBCKT precharge_array bl[0] br[0] bl[1] br[1] bl[2] br[2] bl[3] br[3] en vdd +Xpre_column_0 bl[0] br[0] en vdd precharge +Xpre_column_1 bl[1] br[1] en vdd precharge +Xpre_column_2 bl[2] br[2] en vdd precharge +Xpre_column_3 bl[3] br[3] en vdd precharge +.ENDS precharge_array +*********************** "sense_amp" ****************************** + +.SUBCKT sense_amp bl br dout en vdd gnd +M_1 dout net_1 vdd vdd p W='5.4*1u' L=0.6u +M_2 dout net_1 net_2 gnd n W='2.7*1u' L=0.6u +M_3 net_1 dout vdd vdd p W='5.4*1u' L=0.6u +M_4 net_1 dout net_2 gnd n W='2.7*1u' L=0.6u +M_5 bl en dout vdd p W='7.2*1u' L=0.6u +M_6 br en net_1 vdd p W='7.2*1u' L=0.6u +M_7 net_2 en gnd gnd n W='2.7*1u' L=0.6u +.ENDS sense_amp + + +.SUBCKT sense_amp_array data[0] bl[0] br[0] data[1] bl[1] br[1] data[2] bl[2] br[2] data[3] bl[3] br[3] en vdd gnd +Xsa_d0 bl[0] br[0] data[0] en vdd gnd sense_amp +Xsa_d1 bl[1] br[1] data[1] en vdd gnd sense_amp +Xsa_d2 bl[2] br[2] data[2] en vdd gnd sense_amp +Xsa_d3 bl[3] br[3] data[3] en vdd gnd sense_amp +.ENDS sense_amp_array +*********************** Write_Driver ****************************** +.SUBCKT write_driver din bl br en vdd gnd + +**** Inverter to conver Data_in to data_in_bar ****** +M_1 net_3 din gnd gnd n W='1.2*1u' L=0.6u +M_2 net_3 din vdd vdd p W='2.1*1u' L=0.6u + +**** 2input nand gate follwed by inverter to drive BL ****** +M_3 net_2 en net_7 gnd n W='2.1*1u' L=0.6u +M_4 net_7 din gnd gnd n W='2.1*1u' L=0.6u +M_5 net_2 en vdd vdd p W='2.1*1u' L=0.6u +M_6 net_2 din vdd vdd p W='2.1*1u' L=0.6u + + +M_7 net_1 net_2 vdd vdd p W='2.1*1u' L=0.6u +M_8 net_1 net_2 gnd gnd n W='1.2*1u' L=0.6u + +**** 2input nand gate follwed by inverter to drive BR****** + +M_9 net_4 en vdd vdd p W='2.1*1u' L=0.6u +M_10 net_4 en net_8 gnd n W='2.1*1u' L=0.6u +M_11 net_8 net_3 gnd gnd n W='2.1*1u' L=0.6u +M_12 net_4 net_3 vdd vdd p W='2.1*1u' L=0.6u + +M_13 net_6 net_4 vdd vdd p W='2.1*1u' L=0.6u +M_14 net_6 net_4 gnd gnd n W='1.2*1u' L=0.6u + +************************************************ + +M_15 bl net_6 net_5 gnd n W='3.6*1u' L=0.6u +M_16 br net_1 net_5 gnd n W='3.6*1u' L=0.6u +M_17 net_5 en gnd gnd n W='3.6*1u' L=0.6u + + + +.ENDS $ write_driver + + +.SUBCKT write_driver_array data[0] data[1] data[2] data[3] bl[0] br[0] bl[1] br[1] bl[2] br[2] bl[3] br[3] en vdd gnd +XXwrite_driver0 data[0] bl[0] br[0] en vdd gnd write_driver +XXwrite_driver1 data[1] bl[1] br[1] en vdd gnd write_driver +XXwrite_driver2 data[2] bl[2] br[2] en vdd gnd write_driver +XXwrite_driver3 data[3] bl[3] br[3] en vdd gnd write_driver +.ENDS write_driver_array + +.SUBCKT pinv_11 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_11 + +.SUBCKT pnand2_2 A B Z vdd gnd +Mpnand2_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos1 Z B net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos2 net1 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand2_2 + +.SUBCKT pnand3_2 A B C Z vdd gnd +Mpnand3_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_pmos3 Z C vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos1 Z C net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos2 net1 B net2 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos3 net2 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand3_2 + +.SUBCKT pinv_12 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_12 + +.SUBCKT pnand2_3 A B Z vdd gnd +Mpnand2_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos1 Z B net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos2 net1 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand2_3 + +.SUBCKT pre2x4 in[0] in[1] out[0] out[1] out[2] out[3] vdd gnd +XXpre_inv[0] in[0] inbar[0] vdd gnd pinv_12 +XXpre_inv[1] in[1] inbar[1] vdd gnd pinv_12 +XXpre_nand_inv[0] Z[0] out[0] vdd gnd pinv_12 +XXpre_nand_inv[1] Z[1] out[1] vdd gnd pinv_12 +XXpre_nand_inv[2] Z[2] out[2] vdd gnd pinv_12 +XXpre_nand_inv[3] Z[3] out[3] vdd gnd pinv_12 +XXpre2x4_nand[0] inbar[0] inbar[1] Z[0] vdd gnd pnand2_3 +XXpre2x4_nand[1] in[0] inbar[1] Z[1] vdd gnd pnand2_3 +XXpre2x4_nand[2] inbar[0] in[1] Z[2] vdd gnd pnand2_3 +XXpre2x4_nand[3] in[0] in[1] Z[3] vdd gnd pnand2_3 +.ENDS pre2x4 + +.SUBCKT pinv_13 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_13 + +.SUBCKT pnand3_3 A B C Z vdd gnd +Mpnand3_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_pmos3 Z C vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos1 Z C net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos2 net1 B net2 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand3_nmos3 net2 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand3_3 + +.SUBCKT pre3x8 in[0] in[1] in[2] out[0] out[1] out[2] out[3] out[4] out[5] out[6] out[7] vdd gnd +XXpre_inv[0] in[0] inbar[0] vdd gnd pinv_13 +XXpre_inv[1] in[1] inbar[1] vdd gnd pinv_13 +XXpre_inv[2] in[2] inbar[2] vdd gnd pinv_13 +XXpre_nand_inv[0] Z[0] out[0] vdd gnd pinv_13 +XXpre_nand_inv[1] Z[1] out[1] vdd gnd pinv_13 +XXpre_nand_inv[2] Z[2] out[2] vdd gnd pinv_13 +XXpre_nand_inv[3] Z[3] out[3] vdd gnd pinv_13 +XXpre_nand_inv[4] Z[4] out[4] vdd gnd pinv_13 +XXpre_nand_inv[5] Z[5] out[5] vdd gnd pinv_13 +XXpre_nand_inv[6] Z[6] out[6] vdd gnd pinv_13 +XXpre_nand_inv[7] Z[7] out[7] vdd gnd pinv_13 +XXpre3x8_nand[0] inbar[0] inbar[1] inbar[2] Z[0] vdd gnd pnand3_3 +XXpre3x8_nand[1] in[0] inbar[1] inbar[2] Z[1] vdd gnd pnand3_3 +XXpre3x8_nand[2] inbar[0] in[1] inbar[2] Z[2] vdd gnd pnand3_3 +XXpre3x8_nand[3] in[0] in[1] inbar[2] Z[3] vdd gnd pnand3_3 +XXpre3x8_nand[4] inbar[0] inbar[1] in[2] Z[4] vdd gnd pnand3_3 +XXpre3x8_nand[5] in[0] inbar[1] in[2] Z[5] vdd gnd pnand3_3 +XXpre3x8_nand[6] inbar[0] in[1] in[2] Z[6] vdd gnd pnand3_3 +XXpre3x8_nand[7] in[0] in[1] in[2] Z[7] vdd gnd pnand3_3 +.ENDS pre3x8 + +.SUBCKT hierarchical_decoder_16rows A[0] A[1] A[2] A[3] decode[0] decode[1] decode[2] decode[3] decode[4] decode[5] decode[6] decode[7] decode[8] decode[9] decode[10] decode[11] decode[12] decode[13] decode[14] decode[15] vdd gnd +Xpre[0] A[0] A[1] out[0] out[1] out[2] out[3] vdd gnd pre2x4 +Xpre[1] A[2] A[3] out[4] out[5] out[6] out[7] vdd gnd pre2x4 +XDEC_NAND[0] out[0] out[4] Z[0] vdd gnd pnand2_2 +XDEC_NAND[1] out[0] out[5] Z[1] vdd gnd pnand2_2 +XDEC_NAND[2] out[0] out[6] Z[2] vdd gnd pnand2_2 +XDEC_NAND[3] out[0] out[7] Z[3] vdd gnd pnand2_2 +XDEC_NAND[4] out[1] out[4] Z[4] vdd gnd pnand2_2 +XDEC_NAND[5] out[1] out[5] Z[5] vdd gnd pnand2_2 +XDEC_NAND[6] out[1] out[6] Z[6] vdd gnd pnand2_2 +XDEC_NAND[7] out[1] out[7] Z[7] vdd gnd pnand2_2 +XDEC_NAND[8] out[2] out[4] Z[8] vdd gnd pnand2_2 +XDEC_NAND[9] out[2] out[5] Z[9] vdd gnd pnand2_2 +XDEC_NAND[10] out[2] out[6] Z[10] vdd gnd pnand2_2 +XDEC_NAND[11] out[2] out[7] Z[11] vdd gnd pnand2_2 +XDEC_NAND[12] out[3] out[4] Z[12] vdd gnd pnand2_2 +XDEC_NAND[13] out[3] out[5] Z[13] vdd gnd pnand2_2 +XDEC_NAND[14] out[3] out[6] Z[14] vdd gnd pnand2_2 +XDEC_NAND[15] out[3] out[7] Z[15] vdd gnd pnand2_2 +XDEC_INV_[0] Z[0] decode[0] vdd gnd pinv_11 +XDEC_INV_[1] Z[1] decode[1] vdd gnd pinv_11 +XDEC_INV_[2] Z[2] decode[2] vdd gnd pinv_11 +XDEC_INV_[3] Z[3] decode[3] vdd gnd pinv_11 +XDEC_INV_[4] Z[4] decode[4] vdd gnd pinv_11 +XDEC_INV_[5] Z[5] decode[5] vdd gnd pinv_11 +XDEC_INV_[6] Z[6] decode[6] vdd gnd pinv_11 +XDEC_INV_[7] Z[7] decode[7] vdd gnd pinv_11 +XDEC_INV_[8] Z[8] decode[8] vdd gnd pinv_11 +XDEC_INV_[9] Z[9] decode[9] vdd gnd pinv_11 +XDEC_INV_[10] Z[10] decode[10] vdd gnd pinv_11 +XDEC_INV_[11] Z[11] decode[11] vdd gnd pinv_11 +XDEC_INV_[12] Z[12] decode[12] vdd gnd pinv_11 +XDEC_INV_[13] Z[13] decode[13] vdd gnd pinv_11 +XDEC_INV_[14] Z[14] decode[14] vdd gnd pinv_11 +XDEC_INV_[15] Z[15] decode[15] vdd gnd pinv_11 +.ENDS hierarchical_decoder_16rows +*********************** tri_gate ****************************** + +.SUBCKT tri_gate in out en en_bar vdd gnd + +M_1 net_2 in_inv gnd gnd n W='1.2*1u' L=0.6u +M_2 net_3 in_inv vdd vdd p W='2.4*1u' L=0.6u +M_3 out en_bar net_3 vdd p W='2.4*1u' L=0.6u +M_4 out en net_2 gnd n W='1.2*1u' L=0.6u +M_5 in_inv in vdd vdd p W='2.4*1u' L=0.6u +M_6 in_inv in gnd gnd n W='1.2*1u' L=0.6u + + +.ENDS + +.SUBCKT tri_gate_array in[0] in[1] in[2] in[3] out[0] out[1] out[2] out[3] en en_bar vdd gnd +XXtri_gate0 in[0] out[0] en en_bar vdd gnd tri_gate +XXtri_gate1 in[1] out[1] en en_bar vdd gnd tri_gate +XXtri_gate2 in[2] out[2] en en_bar vdd gnd tri_gate +XXtri_gate3 in[3] out[3] en en_bar vdd gnd tri_gate +.ENDS tri_gate_array + +.SUBCKT pinv_14 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_14 + +.SUBCKT pinv_15 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_15 + +.SUBCKT pnand2_4 A B Z vdd gnd +Mpnand2_pmos1 vdd A Z vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_pmos2 Z B vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos1 Z B net1 gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpnand2_nmos2 net1 A gnd gnd n m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +.ENDS pnand2_4 + +.SUBCKT wordline_driver in[0] in[1] in[2] in[3] in[4] in[5] in[6] in[7] in[8] in[9] in[10] in[11] in[12] in[13] in[14] in[15] wl[0] wl[1] wl[2] wl[3] wl[4] wl[5] wl[6] wl[7] wl[8] wl[9] wl[10] wl[11] wl[12] wl[13] wl[14] wl[15] en vdd gnd +Xwl_driver_inv_en0 en en_bar[0] vdd gnd pinv_15 +Xwl_driver_nand0 en_bar[0] in[0] net[0] vdd gnd pnand2_4 +Xwl_driver_inv0 net[0] wl[0] vdd gnd pinv_14 +Xwl_driver_inv_en1 en en_bar[1] vdd gnd pinv_15 +Xwl_driver_nand1 en_bar[1] in[1] net[1] vdd gnd pnand2_4 +Xwl_driver_inv1 net[1] wl[1] vdd gnd pinv_14 +Xwl_driver_inv_en2 en en_bar[2] vdd gnd pinv_15 +Xwl_driver_nand2 en_bar[2] in[2] net[2] vdd gnd pnand2_4 +Xwl_driver_inv2 net[2] wl[2] vdd gnd pinv_14 +Xwl_driver_inv_en3 en en_bar[3] vdd gnd pinv_15 +Xwl_driver_nand3 en_bar[3] in[3] net[3] vdd gnd pnand2_4 +Xwl_driver_inv3 net[3] wl[3] vdd gnd pinv_14 +Xwl_driver_inv_en4 en en_bar[4] vdd gnd pinv_15 +Xwl_driver_nand4 en_bar[4] in[4] net[4] vdd gnd pnand2_4 +Xwl_driver_inv4 net[4] wl[4] vdd gnd pinv_14 +Xwl_driver_inv_en5 en en_bar[5] vdd gnd pinv_15 +Xwl_driver_nand5 en_bar[5] in[5] net[5] vdd gnd pnand2_4 +Xwl_driver_inv5 net[5] wl[5] vdd gnd pinv_14 +Xwl_driver_inv_en6 en en_bar[6] vdd gnd pinv_15 +Xwl_driver_nand6 en_bar[6] in[6] net[6] vdd gnd pnand2_4 +Xwl_driver_inv6 net[6] wl[6] vdd gnd pinv_14 +Xwl_driver_inv_en7 en en_bar[7] vdd gnd pinv_15 +Xwl_driver_nand7 en_bar[7] in[7] net[7] vdd gnd pnand2_4 +Xwl_driver_inv7 net[7] wl[7] vdd gnd pinv_14 +Xwl_driver_inv_en8 en en_bar[8] vdd gnd pinv_15 +Xwl_driver_nand8 en_bar[8] in[8] net[8] vdd gnd pnand2_4 +Xwl_driver_inv8 net[8] wl[8] vdd gnd pinv_14 +Xwl_driver_inv_en9 en en_bar[9] vdd gnd pinv_15 +Xwl_driver_nand9 en_bar[9] in[9] net[9] vdd gnd pnand2_4 +Xwl_driver_inv9 net[9] wl[9] vdd gnd pinv_14 +Xwl_driver_inv_en10 en en_bar[10] vdd gnd pinv_15 +Xwl_driver_nand10 en_bar[10] in[10] net[10] vdd gnd pnand2_4 +Xwl_driver_inv10 net[10] wl[10] vdd gnd pinv_14 +Xwl_driver_inv_en11 en en_bar[11] vdd gnd pinv_15 +Xwl_driver_nand11 en_bar[11] in[11] net[11] vdd gnd pnand2_4 +Xwl_driver_inv11 net[11] wl[11] vdd gnd pinv_14 +Xwl_driver_inv_en12 en en_bar[12] vdd gnd pinv_15 +Xwl_driver_nand12 en_bar[12] in[12] net[12] vdd gnd pnand2_4 +Xwl_driver_inv12 net[12] wl[12] vdd gnd pinv_14 +Xwl_driver_inv_en13 en en_bar[13] vdd gnd pinv_15 +Xwl_driver_nand13 en_bar[13] in[13] net[13] vdd gnd pnand2_4 +Xwl_driver_inv13 net[13] wl[13] vdd gnd pinv_14 +Xwl_driver_inv_en14 en en_bar[14] vdd gnd pinv_15 +Xwl_driver_nand14 en_bar[14] in[14] net[14] vdd gnd pnand2_4 +Xwl_driver_inv14 net[14] wl[14] vdd gnd pinv_14 +Xwl_driver_inv_en15 en en_bar[15] vdd gnd pinv_15 +Xwl_driver_nand15 en_bar[15] in[15] net[15] vdd gnd pnand2_4 +Xwl_driver_inv15 net[15] wl[15] vdd gnd pinv_14 +.ENDS wordline_driver + +.SUBCKT pinv_16 A Z vdd gnd +Mpinv_pmos Z A vdd vdd p m=1 w=2.4u l=0.6u pd=6.0u ps=6.0u as=3.5999999999999996p ad=3.5999999999999996p +Mpinv_nmos Z A gnd gnd n m=1 w=1.2u l=0.6u pd=3.5999999999999996u ps=3.5999999999999996u as=1.7999999999999998p ad=1.7999999999999998p +.ENDS pinv_16 + +.SUBCKT bank DOUT[0] DOUT[1] DOUT[2] DOUT[3] DIN[0] DIN[1] DIN[2] DIN[3] A[0] A[1] A[2] A[3] s_en w_en tri_en_bar tri_en clk_buf_bar clk_buf vdd gnd +Xbitcell_array bl[0] br[0] bl[1] br[1] bl[2] br[2] bl[3] br[3] wl[0] wl[1] wl[2] wl[3] wl[4] wl[5] wl[6] wl[7] wl[8] wl[9] wl[10] wl[11] wl[12] wl[13] wl[14] wl[15] vdd gnd bitcell_array +Xprecharge_array bl[0] br[0] bl[1] br[1] bl[2] br[2] bl[3] br[3] clk_buf_bar vdd precharge_array +Xsense_amp_array sa_out[0] bl[0] br[0] sa_out[1] bl[1] br[1] sa_out[2] bl[2] br[2] sa_out[3] bl[3] br[3] s_en vdd gnd sense_amp_array +Xwrite_driver_array DIN[0] DIN[1] DIN[2] DIN[3] bl[0] br[0] bl[1] br[1] bl[2] br[2] bl[3] br[3] w_en vdd gnd write_driver_array +Xtri_gate_array sa_out[0] sa_out[1] sa_out[2] sa_out[3] DOUT[0] DOUT[1] DOUT[2] DOUT[3] tri_en tri_en_bar vdd gnd tri_gate_array +Xrow_decoder A[0] A[1] A[2] A[3] dec_out[0] dec_out[1] dec_out[2] dec_out[3] dec_out[4] dec_out[5] dec_out[6] dec_out[7] dec_out[8] dec_out[9] dec_out[10] dec_out[11] dec_out[12] dec_out[13] dec_out[14] dec_out[15] vdd gnd hierarchical_decoder_16rows +Xwordline_driver dec_out[0] dec_out[1] dec_out[2] dec_out[3] dec_out[4] dec_out[5] dec_out[6] dec_out[7] dec_out[8] dec_out[9] dec_out[10] dec_out[11] dec_out[12] dec_out[13] dec_out[14] dec_out[15] wl[0] wl[1] wl[2] wl[3] wl[4] wl[5] wl[6] wl[7] wl[8] wl[9] wl[10] wl[11] wl[12] wl[13] wl[14] wl[15] clk_buf vdd gnd wordline_driver +.ENDS bank + +.SUBCKT sram1 DIN[0] DIN[1] DIN[2] DIN[3] ADDR[0] ADDR[1] ADDR[2] ADDR[3] csb web oeb clk DOUT[0] DOUT[1] DOUT[2] DOUT[3] vdd gnd +Xbank0 DOUT[0] DOUT[1] DOUT[2] DOUT[3] DIN[0] DIN[1] DIN[2] DIN[3] A[0] A[1] A[2] A[3] s_en w_en tri_en_bar tri_en clk_buf_bar clk_buf vdd gnd bank +Xcontrol csb_s web_s oeb_s clk s_en w_en tri_en tri_en_bar clk_buf_bar clk_buf vdd gnd control_logic +Xaddress ADDR[0] ADDR[1] ADDR[2] ADDR[3] A[0] A[1] A[2] A[3] clk_buf vdd gnd dff_array +Xaddress ADDR[0] ADDR[1] ADDR[2] ADDR[3] A[0] A[1] A[2] A[3] clk_buf vdd gnd dff_array +.ENDS sram1 diff --git a/compiler/tests/sram1_TT_5p0V_25C.lib b/compiler/tests/sram1_TT_5p0V_25C.lib new file mode 100644 index 00000000..ddf17785 --- /dev/null +++ b/compiler/tests/sram1_TT_5p0V_25C.lib @@ -0,0 +1,347 @@ +library (sram1_TT_5p0V_25C_lib){ + delay_model : "table_lookup"; + time_unit : "1ns" ; + voltage_unit : "1v" ; + current_unit : "1mA" ; + resistance_unit : "1kohm" ; + capacitive_load_unit(1 ,fF) ; + leakage_power_unit : "1mW" ; + pulling_resistance_unit :"1kohm" ; + operating_conditions(OC){ + process : 1.0 ; + voltage : 5.0 ; + temperature : 25; + } + + input_threshold_pct_fall : 50.0 ; + output_threshold_pct_fall : 50.0 ; + input_threshold_pct_rise : 50.0 ; + output_threshold_pct_rise : 50.0 ; + slew_lower_threshold_pct_fall : 10.0 ; + slew_upper_threshold_pct_fall : 90.0 ; + slew_lower_threshold_pct_rise : 10.0 ; + slew_upper_threshold_pct_rise : 90.0 ; + + nom_voltage : 5.0; + nom_temperature : 25; + nom_process : 1.0; + default_cell_leakage_power : 0.0 ; + default_leakage_power_density : 0.0 ; + default_input_pin_cap : 1.0 ; + default_inout_pin_cap : 1.0 ; + default_output_pin_cap : 0.0 ; + default_max_transition : 0.5 ; + default_fanout_load : 1.0 ; + default_max_fanout : 4.0 ; + default_connection_class : universal ; + + lu_table_template(CELL_TABLE){ + variable_1 : input_net_transition; + variable_2 : total_output_net_capacitance; + index_1("0.0125, 0.05, 0.4"); + index_2("2.45605, 9.8242, 78.5936"); + } + + lu_table_template(CONSTRAINT_TABLE){ + variable_1 : related_pin_transition; + variable_2 : constrained_pin_transition; + index_1("0.0125, 0.05, 0.4"); + index_2("0.0125, 0.05, 0.4"); + } + + default_operating_conditions : OC; + + + type (DATA){ + base_type : array; + data_type : bit; + bit_width : 4; + bit_from : 0; + bit_to : 3; + } + + type (ADDR){ + base_type : array; + data_type : bit; + bit_width : 4; + bit_from : 0; + bit_to : 3; + } + +cell (sram1){ + memory(){ + type : ram; + address_width : 4; + word_width : 4; + } + interface_timing : true; + dont_use : true; + map_only : true; + dont_touch : true; + area : 136566.0; + + leakage_power () { + when : "CSb"; + value : 0.000202; + } + cell_leakage_power : 0; + bus(DATA){ + bus_type : DATA; + direction : inout; + max_capacitance : 78.5936; + min_capacitance : 2.45605; + three_state : "!OEb & !clk"; + memory_write(){ + address : ADDR; + clocked_on : clk; + } + memory_read(){ + address : ADDR; + } + pin(DATA[3:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + } + timing(){ + timing_sense : non_unate; + related_pin : "clk"; + timing_type : falling_edge; + cell_rise(CELL_TABLE) { + values("0.612, 0.66, 1.1",\ + "0.612, 0.66, 1.1",\ + "0.612, 0.66, 1.1"); + } + cell_fall(CELL_TABLE) { + values("0.612, 0.66, 1.1",\ + "0.612, 0.66, 1.1",\ + "0.612, 0.66, 1.1"); + } + rise_transition(CELL_TABLE) { + values("0.024, 0.081, 0.61",\ + "0.024, 0.081, 0.61",\ + "0.024, 0.081, 0.61"); + } + fall_transition(CELL_TABLE) { + values("0.024, 0.081, 0.61",\ + "0.024, 0.081, 0.61",\ + "0.024, 0.081, 0.61"); + } + } + } + } + + bus(ADDR){ + bus_type : ADDR; + direction : input; + capacitance : 9.8242; + max_transition : 0.4; + pin(ADDR[3:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + } + } + } + + pin(CSb){ + direction : input; + capacitance : 9.8242; + timing(){ + timing_type : setup_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + } + } + + pin(OEb){ + direction : input; + capacitance : 9.8242; + timing(){ + timing_type : setup_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + } + } + + pin(WEb){ + direction : input; + capacitance : 9.8242; + timing(){ + timing_type : setup_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + } + } + + pin(clk){ + clock : true; + direction : input; + capacitance : 9.8242; + internal_power(){ + when : "!CSb & clk & !WEb"; + rise_power(scalar){ + values("10.812808757533329"); + } + fall_power(scalar){ + values("10.812808757533329"); + } + } + internal_power(){ + when : "!CSb & !clk & WEb"; + rise_power(scalar){ + values("10.812808757533329"); + } + fall_power(scalar){ + values("10.812808757533329"); + } + } + internal_power(){ + when : "CSb"; + rise_power(scalar){ + values("0"); + } + fall_power(scalar){ + values("0"); + } + } + timing(){ + timing_type :"min_pulse_width"; + related_pin : clk; + rise_constraint(scalar) { + values("0.0"); + } + fall_constraint(scalar) { + values("0.0"); + } + } + timing(){ + timing_type :"minimum_period"; + related_pin : clk; + rise_constraint(scalar) { + values("0"); + } + fall_constraint(scalar) { + values("0"); + } + } + } + } +} diff --git a/compiler/router/cell.py b/router/cell.py similarity index 100% rename from compiler/router/cell.py rename to router/cell.py diff --git a/compiler/router/grid.py b/router/grid.py similarity index 100% rename from compiler/router/grid.py rename to router/grid.py diff --git a/compiler/router/router.py b/router/router.py similarity index 100% rename from compiler/router/router.py rename to router/router.py diff --git a/compiler/router/tests/01_no_blockages_test.py b/router/tests/01_no_blockages_test.py old mode 100644 new mode 100755 similarity index 98% rename from compiler/router/tests/01_no_blockages_test.py rename to router/tests/01_no_blockages_test.py index 99e5b601..1d0b4640 --- a/compiler/router/tests/01_no_blockages_test.py +++ b/router/tests/01_no_blockages_test.py @@ -8,7 +8,6 @@ sys.path.append(os.path.join(sys.path[0],"../..")) sys.path.append(os.path.join(sys.path[0],"..")) import globals import debug -import calibre OPTS = globals.OPTS @@ -19,6 +18,8 @@ class no_blockages_test(unittest.TestCase): def runTest(self): globals.init_openram("config_{0}".format(OPTS.tech_name)) + global verify + import verify import design import router diff --git a/compiler/router/tests/01_no_blockages_test.sp b/router/tests/01_no_blockages_test.sp similarity index 100% rename from compiler/router/tests/01_no_blockages_test.sp rename to router/tests/01_no_blockages_test.sp diff --git a/compiler/router/tests/01_no_blockages_test_freepdk45.gds b/router/tests/01_no_blockages_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/01_no_blockages_test_freepdk45.gds rename to router/tests/01_no_blockages_test_freepdk45.gds diff --git a/compiler/router/tests/01_no_blockages_test_scn3me_subm.gds b/router/tests/01_no_blockages_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/01_no_blockages_test_scn3me_subm.gds rename to router/tests/01_no_blockages_test_scn3me_subm.gds diff --git a/compiler/router/tests/02_blockages_test.py b/router/tests/02_blockages_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/02_blockages_test.py rename to router/tests/02_blockages_test.py diff --git a/compiler/router/tests/02_blockages_test.sp b/router/tests/02_blockages_test.sp similarity index 100% rename from compiler/router/tests/02_blockages_test.sp rename to router/tests/02_blockages_test.sp diff --git a/compiler/router/tests/02_blockages_test_freepdk45.gds b/router/tests/02_blockages_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/02_blockages_test_freepdk45.gds rename to router/tests/02_blockages_test_freepdk45.gds diff --git a/compiler/router/tests/02_blockages_test_scn3me_subm.gds b/router/tests/02_blockages_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/02_blockages_test_scn3me_subm.gds rename to router/tests/02_blockages_test_scn3me_subm.gds diff --git a/compiler/router/tests/03_same_layer_pins_test.py b/router/tests/03_same_layer_pins_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/03_same_layer_pins_test.py rename to router/tests/03_same_layer_pins_test.py diff --git a/compiler/router/tests/03_same_layer_pins_test.sp b/router/tests/03_same_layer_pins_test.sp similarity index 100% rename from compiler/router/tests/03_same_layer_pins_test.sp rename to router/tests/03_same_layer_pins_test.sp diff --git a/compiler/router/tests/03_same_layer_pins_test_freepdk45.gds b/router/tests/03_same_layer_pins_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/03_same_layer_pins_test_freepdk45.gds rename to router/tests/03_same_layer_pins_test_freepdk45.gds diff --git a/compiler/router/tests/03_same_layer_pins_test_scn3me_subm.gds b/router/tests/03_same_layer_pins_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/03_same_layer_pins_test_scn3me_subm.gds rename to router/tests/03_same_layer_pins_test_scn3me_subm.gds diff --git a/compiler/router/tests/04_diff_layer_pins_test.py b/router/tests/04_diff_layer_pins_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/04_diff_layer_pins_test.py rename to router/tests/04_diff_layer_pins_test.py diff --git a/compiler/router/tests/04_diff_layer_pins_test.sp b/router/tests/04_diff_layer_pins_test.sp similarity index 100% rename from compiler/router/tests/04_diff_layer_pins_test.sp rename to router/tests/04_diff_layer_pins_test.sp diff --git a/compiler/router/tests/04_diff_layer_pins_test_freepdk45.gds b/router/tests/04_diff_layer_pins_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/04_diff_layer_pins_test_freepdk45.gds rename to router/tests/04_diff_layer_pins_test_freepdk45.gds diff --git a/compiler/router/tests/04_diff_layer_pins_test_scn3me_subm.gds b/router/tests/04_diff_layer_pins_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/04_diff_layer_pins_test_scn3me_subm.gds rename to router/tests/04_diff_layer_pins_test_scn3me_subm.gds diff --git a/compiler/router/tests/05_two_nets_test.py b/router/tests/05_two_nets_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/05_two_nets_test.py rename to router/tests/05_two_nets_test.py diff --git a/compiler/router/tests/05_two_nets_test.sp b/router/tests/05_two_nets_test.sp similarity index 100% rename from compiler/router/tests/05_two_nets_test.sp rename to router/tests/05_two_nets_test.sp diff --git a/compiler/router/tests/05_two_nets_test_freepdk45.gds b/router/tests/05_two_nets_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/05_two_nets_test_freepdk45.gds rename to router/tests/05_two_nets_test_freepdk45.gds diff --git a/compiler/router/tests/05_two_nets_test_scn3me_subm.gds b/router/tests/05_two_nets_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/05_two_nets_test_scn3me_subm.gds rename to router/tests/05_two_nets_test_scn3me_subm.gds diff --git a/compiler/router/tests/06_pin_location_test.py b/router/tests/06_pin_location_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/06_pin_location_test.py rename to router/tests/06_pin_location_test.py diff --git a/compiler/router/tests/06_pin_location_test_freepdk45.gds b/router/tests/06_pin_location_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/06_pin_location_test_freepdk45.gds rename to router/tests/06_pin_location_test_freepdk45.gds diff --git a/compiler/router/tests/06_pin_location_test_scn3me_subm.gds b/router/tests/06_pin_location_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/06_pin_location_test_scn3me_subm.gds rename to router/tests/06_pin_location_test_scn3me_subm.gds diff --git a/compiler/router/tests/07_big_test.py b/router/tests/07_big_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/07_big_test.py rename to router/tests/07_big_test.py diff --git a/compiler/router/tests/07_big_test_scn3me_subm.gds b/router/tests/07_big_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/07_big_test_scn3me_subm.gds rename to router/tests/07_big_test_scn3me_subm.gds diff --git a/compiler/router/tests/08_expand_region_test.py b/router/tests/08_expand_region_test.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/08_expand_region_test.py rename to router/tests/08_expand_region_test.py diff --git a/compiler/router/tests/08_expand_region_test_freepdk45.gds b/router/tests/08_expand_region_test_freepdk45.gds similarity index 100% rename from compiler/router/tests/08_expand_region_test_freepdk45.gds rename to router/tests/08_expand_region_test_freepdk45.gds diff --git a/compiler/router/tests/08_expand_region_test_scn3me_subm.gds b/router/tests/08_expand_region_test_scn3me_subm.gds similarity index 100% rename from compiler/router/tests/08_expand_region_test_scn3me_subm.gds rename to router/tests/08_expand_region_test_scn3me_subm.gds diff --git a/compiler/router/tests/config_freepdk45.py b/router/tests/config_freepdk45.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/config_freepdk45.py rename to router/tests/config_freepdk45.py diff --git a/compiler/router/tests/config_scn3me_subm.py b/router/tests/config_scn3me_subm.py old mode 100644 new mode 100755 similarity index 100% rename from compiler/router/tests/config_scn3me_subm.py rename to router/tests/config_scn3me_subm.py diff --git a/compiler/router/tests/regress.py b/router/tests/regress.py old mode 100644 new mode 100755 similarity index 59% rename from compiler/router/tests/regress.py rename to router/tests/regress.py index 68ad49fe..02c077f1 --- a/compiler/router/tests/regress.py +++ b/router/tests/regress.py @@ -1,16 +1,16 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 import re import unittest import sys,os -sys.path.append(os.path.join(sys.path[0],"..")) -sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"../../compiler")) +print(sys.path) import globals (OPTS, args) = globals.parse_args() del sys.argv[1:] -from testutils import header +from testutils import header,openram_test header(__file__, OPTS.tech_name) # get a list of all files in the tests directory @@ -18,7 +18,7 @@ files = os.listdir(sys.path[0]) # assume any file that ends in "test.py" in it is a regression test nametest = re.compile("test\.py$", re.IGNORECASE) -tests = filter(nametest.search, files) +tests = list(filter(nametest.search, files)) tests.sort() # import all of the modules @@ -28,4 +28,13 @@ modules = map(__import__, moduleNames) suite = unittest.TestSuite() load = unittest.defaultTestLoader.loadTestsFromModule suite.addTests(map(load, modules)) -unittest.TextTestRunner(verbosity=2).run(suite) + +test_runner = unittest.TextTestRunner(verbosity=2,stream=sys.stderr) +test_result = test_runner.run(suite) + +import verify +verify.print_drc_stats() +verify.print_lvs_stats() +verify.print_pex_stats() + +sys.exit(not test_result.wasSuccessful()) diff --git a/router/tests/testutils.py b/router/tests/testutils.py new file mode 100755 index 00000000..64c1c2b4 --- /dev/null +++ b/router/tests/testutils.py @@ -0,0 +1,257 @@ +import unittest,warnings +import sys,os,glob,copy +sys.path.append(os.path.join(sys.path[0],"..")) +from globals import OPTS +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() + + tempgds = OPTS.openram_temp + "temp.gds" + w.gds_write(tempgds) + import verify + + result=verify.run_drc(w.name, tempgds) + if result != 0: + self.fail("DRC failed: {}".format(w.name)) + + self.cleanup() + + def local_check(self, a, final_verification=False): + + self.reset() + + tempspice = OPTS.openram_temp + "temp.sp" + tempgds = OPTS.openram_temp + "temp.gds" + + a.sp_write(tempspice) + a.gds_write(tempgds) + + import verify + result=verify.run_drc(a.name, tempgds) + if result != 0: + self.fail("DRC failed: {}".format(a.name)) + + + result=verify.run_lvs(a.name, tempgds, tempspice, final_verification) + if result != 0: + self.fail("LVS mismatch: {}".format(a.name)) + + if OPTS.purge_temp: + self.cleanup() + + 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) + + def reset(self): + """ + Reset everything after each test. + """ + # Reset the static duplicate name checker for unit tests. + import hierarchy_design + hierarchy_design.hierarchy_design.name_map=[] + + def check_golden_data(self, data, golden_data, error_tolerance=1e-2): + """ + This function goes through two dictionaries, key by key and compares + each item. It uses relative comparisons for the items and returns false + if there is a mismatch. + """ + + # Check each result + data_matches = True + for k in data.keys(): + if type(data[k])==list: + for i in range(len(data[k])): + if not self.isclose(k,data[k][i],golden_data[k][i],error_tolerance): + data_matches = False + else: + self.isclose(k,data[k],golden_data[k],error_tolerance) + if not data_matches: + import pprint + 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): + """ This is used to compare relative values. """ + import debug + relative_diff = self.relative_diff(value,actual_value) + check = relative_diff <= error_tolerance + if check: + debug.info(2,"CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100)) + return True + else: + debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100)) + return False + + def relative_diff(self, value1, value2): + """ 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 + if value1==0 and value2==0: + return 0.0 + + # Don't need relative, exact compare + if value1==value2: + return 0.0 + + # Get normalization value + norm_value = abs(max(value1, value2)) + # Edge case where greater is a zero + if norm_value == 0: + min_value = abs(min(value1, value2)) + + return abs(value1 - value2) / norm_value + + + def relative_compare(self, value,actual_value,error_tolerance): + """ This is used to compare relative values. """ + if (value==actual_value): # if we don't need a relative comparison! + return True + return (abs(value - actual_value) / max(value,actual_value) <= error_tolerance) + + def isapproxdiff(self, filename1, filename2, error_tolerance=0.001): + """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 + + numeric_const_pattern = r""" + [-+]? # optional sign + (?: + (?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc + | + (?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc + ) + # followed by optional exponent part if desired + (?: [Ee] [+-]? \d+ ) ? + """ + rx = re.compile(numeric_const_pattern, re.VERBOSE) + fp1 = open(filename1, 'rb') + fp2 = open(filename2, 'rb') + mismatches=0 + line_num=0 + while True: + line_num+=1 + line1 = fp1.readline().decode('utf-8') + line2 = fp2.readline().decode('utf-8') + #print("line1:",line1) + #print("line2:",line2) + + # 1. Find all of the floats using a regex + line1_floats=rx.findall(line1) + line2_floats=rx.findall(line2) + 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) + for f in line2_floats: + line2=line2.replace(f,"",1) + #print("line1:",line1) + #print("line2:",line2) + + # 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: + if mismatches==0: + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + mismatches += 1 + debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num,line1.rstrip('\n'),line2.rstrip('\n'))) + + # 5. Now compare that the floats match + elif len(line1_floats)!=len(line2_floats): + if mismatches==0: + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + mismatches += 1 + debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num,len(line1_floats),len(line2_floats))) + else: + for (float1,float2) in zip(line1_floats,line2_floats): + relative_diff = self.relative_diff(float1,float2) + check = relative_diff <= error_tolerance + if not check: + if mismatches==0: + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + mismatches += 1 + debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num,float1,float2,relative_diff*100)) + + # Only show the first 10 mismatch lines + if not line1 and not line2 or mismatches>10: + fp1.close() + fp2.close() + return mismatches==0 + + # Never reached + return False + + + def isdiff(self,filename1,filename2): + """ This is used to compare two files and display the diff if they are different.. """ + import debug + import filecmp + import difflib + check = filecmp.cmp(filename1,filename2) + if not check: + debug.error("MISMATCH file1={0} file2={1}".format(filename1,filename2)) + f1 = open(filename1,"r") + s1 = f1.readlines().decode('utf-8') + f1.close() + f2 = open(filename2,"r").decode('utf-8') + s2 = f2.readlines() + f2.close() + mismatches=0 + for line in difflib.unified_diff(s1, s2): + mismatches += 1 + self.error("DIFF LINES:",line) + if mismatches>10: + return False + return False + else: + 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(" ______________________________________________________________________________ ") + print("|==============================================================================|") + print("|=========" + tst.center(60) + "=========|") + print("|=========" + technology.center(60) + "=========|") + print("|=========" + filename.center(60) + "=========|") + from globals import OPTS + print("|=========" + OPTS.openram_temp.center(60) + "=========|") + print("|==============================================================================|") diff --git a/compiler/router/vector3d.py b/router/vector3d.py similarity index 100% rename from compiler/router/vector3d.py rename to router/vector3d.py From 2ae1e0234db5ec74ac8b26926cb1b5b154a8af77 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 22 Aug 2018 11:37:24 -0700 Subject: [PATCH 3/7] Update router to work with pin_layout structure. --- compiler/base/geometry.py | 7 +- compiler/base/hierarchy_design.py | 10 +- compiler/base/hierarchy_layout.py | 2 +- compiler/base/hierarchy_spice.py | 2 +- compiler/base/route.py | 25 +++- compiler/base/utils.py | 36 +++-- compiler/gdsMill/gdsMill/vlsiLayout.py | 9 +- {router => compiler/router}/cell.py | 0 {router => compiler/router}/grid.py | 52 +++---- {router => compiler/router}/router.py | 128 ++++++++---------- compiler/router/tests/01_no_blockages_test.py | 55 ++++++++ .../router}/tests/01_no_blockages_test.sp | 0 .../tests/01_no_blockages_test_freepdk45.gds | Bin 2048 -> 2048 bytes .../01_no_blockages_test_scn3me_subm.gds | Bin 2048 -> 2048 bytes compiler/router/tests/02_blockages_test.py | 59 ++++++++ .../router}/tests/02_blockages_test.sp | 0 .../tests/02_blockages_test_freepdk45.gds | Bin 2048 -> 2048 bytes .../tests/02_blockages_test_scn3me_subm.gds | Bin 2048 -> 2048 bytes .../router/tests/03_same_layer_pins_test.py | 54 ++++++++ .../router}/tests/03_same_layer_pins_test.sp | 0 .../03_same_layer_pins_test_freepdk45.gds | Bin .../03_same_layer_pins_test_scn3me_subm.gds | Bin .../router/tests/04_diff_layer_pins_test.py | 56 ++++++++ .../router}/tests/04_diff_layer_pins_test.sp | 0 .../04_diff_layer_pins_test_freepdk45.gds | Bin .../04_diff_layer_pins_test_scn3me_subm.gds | Bin compiler/router/tests/05_two_nets_test.py | 58 ++++++++ .../router}/tests/05_two_nets_test.sp | 0 .../tests/05_two_nets_test_freepdk45.gds | Bin .../tests/05_two_nets_test_scn3me_subm.gds | Bin .../router}/tests/06_pin_location_test.py | 51 ++----- .../tests/06_pin_location_test_freepdk45.gds | Bin .../06_pin_location_test_scn3me_subm.gds | Bin .../router}/tests/07_big_test.py | 51 ++----- .../router}/tests/07_big_test_scn3me_subm.gds | Bin .../router}/tests/08_expand_region_test.py | 50 ++----- .../tests/08_expand_region_test_freepdk45.gds | Bin .../08_expand_region_test_scn3me_subm.gds | Bin .../router}/tests/config_freepdk45.py | 0 .../router}/tests/config_scn3me_subm.py | 0 compiler/router/tests/gds_cell.py | 16 +++ {router => compiler/router}/tests/regress.py | 0 .../router}/tests/testutils.py | 5 +- {router => compiler/router}/vector3d.py | 0 compiler/verify/magic.py | 4 + router/tests/01_no_blockages_test.py | 81 ----------- router/tests/02_blockages_test.py | 80 ----------- router/tests/03_same_layer_pins_test.py | 80 ----------- router/tests/04_diff_layer_pins_test.py | 81 ----------- router/tests/05_two_nets_test.py | 82 ----------- 50 files changed, 493 insertions(+), 641 deletions(-) rename {router => compiler/router}/cell.py (100%) rename {router => compiler/router}/grid.py (88%) rename {router => compiler/router}/router.py (84%) create mode 100755 compiler/router/tests/01_no_blockages_test.py rename {router => compiler/router}/tests/01_no_blockages_test.sp (100%) rename {router => compiler/router}/tests/01_no_blockages_test_freepdk45.gds (75%) rename {router => compiler/router}/tests/01_no_blockages_test_scn3me_subm.gds (75%) create mode 100755 compiler/router/tests/02_blockages_test.py rename {router => compiler/router}/tests/02_blockages_test.sp (100%) rename {router => compiler/router}/tests/02_blockages_test_freepdk45.gds (62%) rename {router => compiler/router}/tests/02_blockages_test_scn3me_subm.gds (59%) create mode 100755 compiler/router/tests/03_same_layer_pins_test.py rename {router => compiler/router}/tests/03_same_layer_pins_test.sp (100%) rename {router => compiler/router}/tests/03_same_layer_pins_test_freepdk45.gds (100%) rename {router => compiler/router}/tests/03_same_layer_pins_test_scn3me_subm.gds (100%) create mode 100755 compiler/router/tests/04_diff_layer_pins_test.py rename {router => compiler/router}/tests/04_diff_layer_pins_test.sp (100%) rename {router => compiler/router}/tests/04_diff_layer_pins_test_freepdk45.gds (100%) rename {router => compiler/router}/tests/04_diff_layer_pins_test_scn3me_subm.gds (100%) create mode 100755 compiler/router/tests/05_two_nets_test.py rename {router => compiler/router}/tests/05_two_nets_test.sp (100%) rename {router => compiler/router}/tests/05_two_nets_test_freepdk45.gds (100%) rename {router => compiler/router}/tests/05_two_nets_test_scn3me_subm.gds (100%) rename {router => compiler/router}/tests/06_pin_location_test.py (54%) rename {router => compiler/router}/tests/06_pin_location_test_freepdk45.gds (100%) rename {router => compiler/router}/tests/06_pin_location_test_scn3me_subm.gds (100%) rename {router => compiler/router}/tests/07_big_test.py (63%) rename {router => compiler/router}/tests/07_big_test_scn3me_subm.gds (100%) rename {router => compiler/router}/tests/08_expand_region_test.py (51%) rename {router => compiler/router}/tests/08_expand_region_test_freepdk45.gds (100%) rename {router => compiler/router}/tests/08_expand_region_test_scn3me_subm.gds (100%) rename {router => compiler/router}/tests/config_freepdk45.py (100%) rename {router => compiler/router}/tests/config_scn3me_subm.py (100%) create mode 100644 compiler/router/tests/gds_cell.py rename {router => compiler/router}/tests/regress.py (100%) rename {router => compiler/router}/tests/testutils.py (98%) rename {router => compiler/router}/vector3d.py (100%) delete mode 100755 router/tests/01_no_blockages_test.py delete mode 100755 router/tests/02_blockages_test.py delete mode 100755 router/tests/03_same_layer_pins_test.py delete mode 100755 router/tests/04_diff_layer_pins_test.py delete mode 100755 router/tests/05_two_nets_test.py diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index d316e713..2bb7b8df 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -55,9 +55,12 @@ class geometry: self.compute_boundary(self.offset,self.mirror,self.rotate) def compute_boundary(self,offset=vector(0,0),mirror="",rotate=0): - """ Transform with offset, mirror and rotation to get the absolute pin location. - We must then re-find the ll and ur. The master is the cell instance. """ + """ + Transform with offset, mirror and rotation to get the absolute pin location. + We must then re-find the ll and ur. The master is the cell instance. + """ (ll,ur) = [vector(0,0),vector(self.width,self.height)] + if mirror=="MX": ll=ll.scale(1,-1) ur=ur.scale(1,-1) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 87659bd7..320276cc 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -16,8 +16,14 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): def __init__(self, name): - self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds" - self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp" + try: + self.gds_file + except AttributeError: + self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds" + try: + self.sp_file + except AttributeError: + self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp" self.name = name hierarchy_layout.layout.__init__(self, name) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 3aa21794..672f4d93 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -322,7 +322,7 @@ class layout(lef.lef): position_list=coordinates, width=width) - def add_route(self, design, layers, coordinates): + def add_route(self, layers, coordinates): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). add_wire assumes preferred direction routing whereas this includes layers in diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 0152a6c8..a82eba1b 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -117,7 +117,7 @@ class spice(verilog.verilog): def sp_read(self): """Reads the sp file (and parse the pins) from the library Otherwise, initialize it to null for dynamic generation""" - if os.path.isfile(self.sp_file): + if self.sp_file and os.path.isfile(self.sp_file): debug.info(3, "opening {0}".format(self.sp_file)) f = open(self.sp_file) self.spice = f.readlines() diff --git a/compiler/base/route.py b/compiler/base/route.py index 03b62cff..beff1a58 100644 --- a/compiler/base/route.py +++ b/compiler/base/route.py @@ -1,11 +1,12 @@ from tech import drc import debug +from design import design from contact import contact from itertools import tee from vector import vector from vector3d import vector3d -class route(): +class route(design): """ Object route (used by the router module) Add a route of minimium metal width between a set of points. @@ -14,10 +15,13 @@ class route(): The points are the center of the wire. This can have non-preferred direction routing. """ + + unique_route_id = 0 + def __init__(self, obj, layer_stack, path): name = "route_{0}".format(route.unique_route_id) route.unique_route_id += 1 - design.design.__init__(self, name) + design.__init__(self, name) debug.info(3, "create route obj {0}".format(name)) self.obj = obj @@ -52,7 +56,7 @@ class route(): next(b, None) return zip(a, b) - plist = pairwise(self.path) + plist = list(pairwise(self.path)) for p0,p1 in plist: if p0.z != p1.z: # via # offset if not rotated @@ -67,6 +71,13 @@ class route(): self.draw_corner_wire(p1) # draw the point to point wire self.draw_wire(p0,p1) + + + # Draw the layers on the ends of the wires to ensure full width + # connections + self.draw_corner_wire(plist[0][0]) + self.draw_corner_wire(plist[-1][1]) + @@ -99,10 +110,10 @@ class route(): height = end.y - start.y width = layer_width - deisgn.add_rect(layer=layer_name, - offset=offset, - width=width, - height=height) + self.obj.add_rect(layer=layer_name, + offset=vector(offset.x,offset.y), + width=width, + height=height) def draw_corner_wire(self, p0): diff --git a/compiler/base/utils.py b/compiler/base/utils.py index cb63b68a..b13f2f7e 100644 --- a/compiler/base/utils.py +++ b/compiler/base/utils.py @@ -37,12 +37,6 @@ def pin_center(boundary): """ return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])] -def pin_rect(boundary): - """ - This returns a LL,UR point pair. - """ - return [vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])] - def auto_measure_libcell(pin_list, name, units, layer): """ Open a GDS file and find the pins in pin_list as text on a given layer. @@ -66,15 +60,14 @@ def auto_measure_libcell(pin_list, name, units, layer): -def get_libcell_size(name, units, layer): +def get_gds_size(name, gds_filename, units, layer): """ - Open a GDS file and return the library cell size from either the + Open a GDS file and return the size from either the bounding box or a border layer. """ - cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" cell_vlsi = gdsMill.VlsiLayout(units=units) reader = gdsMill.Gds2reader(cell_vlsi) - reader.loadFromFile(cell_gds) + reader.loadFromFile(gds_filename) cell = {} measure_result = cell_vlsi.getLayoutBorder(layer) @@ -83,16 +76,23 @@ def get_libcell_size(name, units, layer): # returns width,height return measure_result +def get_libcell_size(name, units, layer): + """ + Open a GDS file and return the library cell size from either the + bounding box or a border layer. + """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" + return(get_gds_size(name, cell_gds, units, layer)) -def get_libcell_pins(pin_list, name, units, layer): + +def get_gds_pins(pin_list, name, gds_filename, units, layer): """ Open a GDS file and find the pins in pin_list as text on a given layer. Return these as a rectangle layer pair for each pin. """ - cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" cell_vlsi = gdsMill.VlsiLayout(units=units) reader = gdsMill.Gds2reader(cell_vlsi) - reader.loadFromFile(cell_gds) + reader.loadFromFile(gds_filename) cell = {} for pin in pin_list: @@ -100,11 +100,19 @@ def get_libcell_pins(pin_list, name, units, layer): label_list=cell_vlsi.getPinShapeByLabel(str(pin)) for label in label_list: (name,layer,boundary)=label - rect = pin_rect(boundary) + rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])] # this is a list because other cells/designs may have must-connect pins cell[str(pin)].append(pin_layout(pin, rect, layer)) return cell +def get_libcell_pins(pin_list, name, units, layer): + """ + Open a GDS file and find the pins in pin_list as text on a given layer. + Return these as a rectangle layer pair for each pin. + """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" + return(get_gds_pins(pin_list, name, cell_gds, units, layer)) + diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index cd00bfaf..076948bb 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -583,6 +583,7 @@ class VlsiLayout: print("Done\n\n") def getLayoutBorder(self,borderlayer): + cellSizeMicron=None for boundary in self.structures[self.rootStructureName].boundaries: if boundary.drawingLayer==borderlayer: if self.debug: @@ -722,7 +723,8 @@ class VlsiLayout: def getAllPinShapesByDBLocLayer(self, coordinate, layer): """ Return ALL the enclosing rectangles on the same layer - at the given coordinate. Coordinates should be in DB units. + at the given coordinate. Input coordinates should be in DB units. + Returns user unit shapes. """ pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer) @@ -732,8 +734,7 @@ class VlsiLayout: new_boundaries.append([pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0], pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]]) - # Make a name if we don't have the pin name - return ["p"+str(coordinate)+"_"+str(layer), layer, new_boundaries] + return new_boundaries def getPinShapeByLabel(self,label_name): """ @@ -758,7 +759,7 @@ class VlsiLayout: shape_list=[] for label in label_list: (label_coordinate,label_layer)=label - shape_list.append(self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer)) + shape_list.extend(self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer)) return shape_list def getAllPinShapesInStructureList(self,coordinates,layer): diff --git a/router/cell.py b/compiler/router/cell.py similarity index 100% rename from router/cell.py rename to compiler/router/cell.py diff --git a/router/grid.py b/compiler/router/grid.py similarity index 88% rename from router/grid.py rename to compiler/router/grid.py index 050826e8..d57be765 100644 --- a/router/grid.py +++ b/compiler/router/grid.py @@ -6,11 +6,7 @@ from vector3d import vector3d from cell import cell import os -try: - import Queue as Q # ver. < 3.0 -except ImportError: - import queue as Q - +from heapq import heappush,heappop class grid: """A two layer routing map. Each cell can be blocked in the vertical @@ -36,7 +32,7 @@ class grid: self.map={} # priority queue for the maze routing - self.q = Q.PriorityQueue() + self.q = [] def set_blocked(self,n): self.add_map(n) @@ -66,30 +62,34 @@ class grid: self.target=[] # clear the queue - while (not self.q.empty()): - self.q.get(False) - + while len(self.q)>0: + heappop(self.q) + self.counter = 0 def add_blockage_shape(self,ll,ur,z): debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + + block_list = [] for x in range(int(ll[0]),int(ur[0])+1): for y in range(int(ll[1]),int(ur[1])+1): - n = vector3d(x,y,z) - self.set_blocked(n) + block_list.append(vector3d(x,y,z)) + + self.add_blockage(block_list) def add_blockage(self,block_list): - debug.info(3,"Adding blockage list={0}".format(str(block_list))) + debug.info(2,"Adding blockage list={0}".format(str(block_list))) for n in block_list: self.set_blocked(n) def add_source(self,track_list): - debug.info(3,"Adding source list={0}".format(str(track_list))) + debug.info(2,"Adding source list={0}".format(str(track_list))) for n in track_list: if not self.is_blocked(n): + debug.info(3,"Adding source ={0}".format(str(n))) self.set_source(n) def add_target(self,track_list): - debug.info(3,"Adding target list={0}".format(str(track_list))) + debug.info(2,"Adding target list={0}".format(str(track_list))) for n in track_list: if not self.is_blocked(n): self.set_target(n) @@ -120,8 +120,8 @@ class grid: cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST # Make sure the queue is empty if we run another route - while not self.q.empty(): - self.q.get() + while len(self.q)>0: + heappop(self.q) # Put the source items into the queue self.init_queue() @@ -129,10 +129,10 @@ class grid: cheapest_cost = None # Keep expanding and adding to the priority queue until we are done - while not self.q.empty(): + while len(self.q)>0: # should we keep the path in the queue as well or just the final node? - (cost,path) = self.q.get() - debug.info(2,"Queue size: size=" + str(self.q.qsize()) + " " + str(cost)) + (cost,count,path) = heappop(self.q) + debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost)) debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path)) # expand the last element @@ -158,7 +158,8 @@ class grid: self.map[n].min_cost = predicted_cost debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath)) # add the cost to get to this point if we haven't reached it yet - self.q.put((predicted_cost,newpath)) + heappush(self.q,(predicted_cost,self.counter,newpath)) + self.counter += 1 debug.warning("Unable to route path. Expand the detour_scale to allow detours.") return (None,None) @@ -227,11 +228,16 @@ class grid: # uniquify the source (and target while we are at it) self.source = list(set(self.source)) self.target = list(set(self.target)) - + + # Counter is used to not require data comparison in Python 3.x + # Items will be returned in order they are added during cost ties + self.counter = 0 for s in self.source: cost = self.cost_to_target(s) - debug.info(4,"Init: cost=" + str(cost) + " " + str([s])) - self.q.put((cost,[s])) + debug.info(1,"Init: cost=" + str(cost) + " " + str([s])) + heappush(self.q,(cost,self.counter,[s])) + self.counter+=1 + def hpwl(self, src, dest): """ diff --git a/router/router.py b/compiler/router/router.py similarity index 84% rename from router/router.py rename to compiler/router/router.py index 43e5af90..6375decd 100644 --- a/router/router.py +++ b/compiler/router/router.py @@ -4,6 +4,7 @@ from contact import contact import math import debug import grid +from pin_layout import pin_layout from vector import vector from vector3d import vector3d from globals import OPTS @@ -24,13 +25,11 @@ class router: self.reader.loadFromFile(gds_name) self.top_name = self.layout.rootStructureName - self.source_pin_shapes = [] - self.source_pin_zindex = None - self.target_pin_shapes = [] - self.target_pin_zindex = None + self.source_pins = [] + self.target_pins = [] # the list of all blockage shapes self.blockages = [] - # all thepaths we've routed so far (to supplement the blockages) + # all the paths we've routed so far (to supplement the blockages) self.paths = [] # The boundary will determine the limits to the size of the routing grid @@ -94,20 +93,18 @@ class router: Pin can either be a label or a location,layer pair: [[x,y],layer]. """ - if type(pin)==str: - (pin_name,pin_layer,pin_shapes) = self.layout.getAllPinShapesByLabel(str(pin)) - else: - (pin_name,pin_layer,pin_shapes) = self.layout.getAllPinShapesByLocLayer(pin[0],pin[1]) + label_list=self.layout.getPinShapeByLabel(str(pin)) + pin_list = [] + for label in label_list: + (name,layer,boundary)=label + rect = [vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])] + # this is a list because other cells/designs may have must-connect pins + pin_list.append(pin_layout(pin, rect, layer)) + + debug.check(len(pin_list)>0,"Did not find any pin shapes for {0}.".format(str(pin))) + + return pin_list - new_pin_shapes = [] - for pin_shape in pin_shapes: - debug.info(2,"Find pin {0} layer {1} shape {2}".format(pin_name,str(pin_layer),str(pin_shape))) - # repack the shape as a pair of vectors rather than four values - new_pin_shapes.append([vector(pin_shape[0],pin_shape[1]),vector(pin_shape[2],pin_shape[3])]) - - debug.check(len(new_pin_shapes)>0,"Did not find any pin shapes for {0}.".format(str(pin))) - - return (pin_layer,new_pin_shapes) def find_blockages(self): """ @@ -126,17 +123,14 @@ class router: Convert the routed path to blockages. Keep the other blockages unchanged. """ - self.source_pin = None - self.source_pin_shapes = [] - self.source_pin_zindex = None - self.target_pin = None - self.target_pin_shapes = [] - self.target_pin_zindex = None - # DO NOT clear the blockages as these don't change + self.source_pin_name = None + self.source_pins = [] + self.target_pin_name = None + self.target_pins = [] # DO NOT clear the blockages as these don't change self.rg.reinit() - def route(self, cell, layers, src, dest, detour_scale=2): + def route(self, cell, layers, src, dest, detour_scale=5): """ Route a single source-destination net and return the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route. @@ -186,7 +180,7 @@ class router: return False - def write_debug_gds(self,): + def write_debug_gds(self): """ Write out a GDS file with the routing grid and search information annotated on it. """ @@ -195,7 +189,7 @@ class router: if OPTS.debug_level==0: return self.add_router_info() - debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin,self.target_pin)) + debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin_name,self.target_pin_name)) self.cell.gds_write("debug_route.gds") def add_router_info(self): @@ -204,7 +198,7 @@ class router: the boundary layer for debugging purposes. This can only be called once or the labels will overlap. """ - debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin,self.target_pin)) + debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin_name,self.target_pin_name)) grid_keys=self.rg.map.keys() partial_track=vector(0,self.track_width/6.0) for g in grid_keys: @@ -237,7 +231,8 @@ class router: offset=type_off) self.cell.add_label(text="{0},{1}".format(g[0],g[1]), layer="text", - offset=shape[0]) + offset=shape[0], + zoom=0.05) def add_route(self,path): """ @@ -255,7 +250,7 @@ class router: if False or path==None: self.write_debug_gds() - if 'Xout_4_1' in [self.source_pin, self.target_pin]: + if 'Xout_4_1' in [self.source_pin_name, self.target_pin_name]: self.write_debug_gds() @@ -263,12 +258,6 @@ class router: #debug.info(1,str(self.path)) contracted_path = self.contract_path(path) debug.info(1,str(contracted_path)) - - # Make sure there's a pin enclosure on the source and dest - add_src_via = contracted_path[0].z!=self.source_pin_zindex - self.add_grid_pin(contracted_path[0],add_src_via) - add_tgt_via = contracted_path[-1].z!=self.target_pin_zindex - self.add_grid_pin(contracted_path[-1],add_tgt_via) # convert the path back to absolute units from tracks abs_path = map(self.convert_point_to_units,contracted_path) @@ -382,14 +371,12 @@ class router: self.rg.set_blocked(grid) - def get_source(self,pin): + def get_source(self,pin_name): """ Gets the source pin shapes only. Doesn't add to grid. """ - self.source_pin = pin - (self.source_pin_layer,self.source_pin_shapes) = self.find_pin(pin) - zindex = 0 if self.source_pin_layer==self.horiz_layer_number else 1 - self.source_pin_zindex = zindex + self.source_pin_name = pin_name + self.source_pins = self.find_pin(pin_name) def add_source(self): """ @@ -398,10 +385,11 @@ class router: """ found_pin = False - for shape in self.source_pin_shapes: - (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(shape,self.source_pin_zindex,self.source_pin) - if (len(pin_in_tracks)>0): found_pin=True - debug.info(1,"Set source: " + str(self.source_pin) + " " + str(pin_in_tracks) + " z=" + str(self.source_pin_zindex)) + for pin in self.source_pins: + (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin) + if (len(pin_in_tracks)>0): + found_pin=True + debug.info(1,"Set source: " + str(self.source_pin_name) + " " + str(pin_in_tracks)) self.rg.add_source(pin_in_tracks) self.rg.add_blockage(blockage_in_tracks) @@ -409,14 +397,12 @@ class router: self.write_debug_gds() debug.check(found_pin,"Unable to find source pin on grid.") - def get_target(self,pin): + def get_target(self,pin_name): """ Gets the target pin shapes only. Doesn't add to grid. """ - self.target_pin = pin - (self.target_pin_layer,self.target_pin_shapes) = self.find_pin(pin) - zindex = 0 if self.target_pin_layer==self.horiz_layer_number else 1 - self.target_pin_zindex = zindex + self.target_pin_name = pin_name + self.target_pins = self.find_pin(pin_name) def add_target(self): """ @@ -424,10 +410,11 @@ class router: pin can be a location or a label. """ found_pin=False - for shape in self.target_pin_shapes: - (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(shape,self.target_pin_zindex,self.target_pin) - if (len(pin_in_tracks)>0): found_pin=True - debug.info(1,"Set target: " + str(self.target_pin) + " " + str(pin_in_tracks) + " z=" + str(self.target_pin_zindex)) + for pin in self.target_pins: + (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin) + if (len(pin_in_tracks)>0): + found_pin=True + debug.info(1,"Set target: " + str(self.target_pin_name) + " " + str(pin_in_tracks)) self.rg.add_target(pin_in_tracks) self.rg.add_blockage(blockage_in_tracks) @@ -438,15 +425,15 @@ class router: def add_blockages(self): """ Add the blockages except the pin shapes """ for blockage in self.blockages: - (shape,zlayer) = blockage + is_nonpin_blockage = True # Skip source pin shapes - if zlayer==self.source_pin_zindex and shape in self.source_pin_shapes: - continue - # Skip target pin shapes - if zlayer==self.target_pin_zindex and shape in self.target_pin_shapes: - continue - [ll,ur]=self.convert_blockage_to_tracks(shape) - self.rg.add_blockage_shape(ll,ur,zlayer) + for pin in self.source_pins + self.target_pins: + if blockage.overlaps(pin): + break + else: + [ll,ur]=self.convert_blockage_to_tracks(blockage.rect) + zlayer = 0 if blockage.layer_num==self.horiz_layer_number else 1 + self.rg.add_blockage_shape(ll,ur,zlayer) def get_blockages(self, sref, mirr = 1, angle = math.radians(float(0)), xyShift = (0, 0)): @@ -461,8 +448,8 @@ class router: # only consider the two layers that we are routing on if boundary.drawingLayer in [self.vert_layer_number,self.horiz_layer_number]: - zlayer = 0 if boundary.drawingLayer==self.horiz_layer_number else 1 - self.blockages.append((shape,zlayer)) + # store the blockages as pin layouts so they are easy to compare etc. + self.blockages.append(pin_layout("blockage",shape,boundary.drawingLayer)) # recurse given the mirror, angle, etc. @@ -507,13 +494,13 @@ class router: #debug.info(1,"Converted [ {0} , {1} ]".format(ll,ur)) return [ll,ur] - def convert_pin_to_tracks(self,shape,zindex,pin): + def convert_pin_to_tracks(self, pin): """ Convert a rectangular pin shape into a list of track locations,layers. If no on-grid pins are found, it searches for the nearest off-grid pin(s). If a pin has insufficent overlap, it returns the blockage list to avoid it. """ - [ll,ur] = shape + [ll,ur] = pin.rect ll = snap_to_grid(ll) ur = snap_to_grid(ur) @@ -524,6 +511,7 @@ class router: ur=ur.scale(self.track_factor).ceil() # width depends on which layer it is + zindex = 0 if pin.layer_num==self.horiz_layer_number else 1 if zindex==0: width = self.horiz_layer_width else: @@ -539,12 +527,12 @@ class router: # if dimension of overlap is greater than min width in any dimension, # it will be an on-grid pin rect = self.convert_track_to_pin(vector3d(x,y,zindex)) - max_overlap=max(self.compute_overlap(shape,rect)) + max_overlap=max(self.compute_overlap(pin.rect,rect)) # however, if there is not enough overlap, then if there is any overlap at all, # we need to block it to prevent routes coming in on that grid full_rect = self.convert_full_track_to_shape(vector3d(x,y,zindex)) - full_overlap=max(self.compute_overlap(shape,full_rect)) + full_overlap=max(self.compute_overlap(pin.rect,full_rect)) #debug.info(1,"Check overlap: {0} {1} max={2}".format(shape,rect,max_overlap)) if max_overlap >= width: @@ -552,7 +540,7 @@ class router: elif full_overlap>0: block_list.append(vector3d(x,y,zindex)) else: - debug.info(1,"No overlap: {0} {1} max={2}".format(shape,rect,max_overlap)) + debug.info(4,"No overlap: {0} {1} max={2}".format(pin.rect,rect,max_overlap)) #debug.warning("Off-grid pin for {0}.".format(str(pin))) #debug.info(1,"Converted [ {0} , {1} ]".format(ll,ur)) diff --git a/compiler/router/tests/01_no_blockages_test.py b/compiler/router/tests/01_no_blockages_test.py new file mode 100755 index 00000000..60cc6583 --- /dev/null +++ b/compiler/router/tests/01_no_blockages_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug + +OPTS = globals.OPTS + +class no_blockages_test(openram_test): + """ + Simplest two pin route test with no blockages. + """ + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router + + class routing(design, openram_test): + """ + A generic GDS design that we can route on. + """ + def __init__(self, name): + design.__init__(self, "top") + + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) + self.add_inst(name=name, + mod=cell, + offset=[0,0]) + self.connect_inst([]) + + r=router(gds_file) + layer_stack =("metal1","via1","metal2") + self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) + + r=routing("01_no_blockages_test_{0}".format(OPTS.tech_name)) + self.local_drc_check(r) + + # fails if there are any DRC errors on any cells + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/router/tests/01_no_blockages_test.sp b/compiler/router/tests/01_no_blockages_test.sp similarity index 100% rename from router/tests/01_no_blockages_test.sp rename to compiler/router/tests/01_no_blockages_test.sp diff --git a/router/tests/01_no_blockages_test_freepdk45.gds b/compiler/router/tests/01_no_blockages_test_freepdk45.gds similarity index 75% rename from router/tests/01_no_blockages_test_freepdk45.gds rename to compiler/router/tests/01_no_blockages_test_freepdk45.gds index cbad1838203f2d606bd649fabff854b779162e68..ec3c99d00a0e1d1650c6b27b2ae46b5333ecbd29 100644 GIT binary patch delta 93 zcmZn=Xb_kn?k~i^!63>Y$iT#)&%ndPmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 m-eHhoWnvIu;AY@w5Mhu4>Oj`Z!NyjST3oU*XA1iyCN=9RwA!R*Kn o3^J@tK$YAK{0t%tQVdKC$a?wM*m4b%a`Kb26Vp>SrcGf70GW6bm;e9( diff --git a/router/tests/01_no_blockages_test_scn3me_subm.gds b/compiler/router/tests/01_no_blockages_test_scn3me_subm.gds similarity index 75% rename from router/tests/01_no_blockages_test_scn3me_subm.gds rename to compiler/router/tests/01_no_blockages_test_scn3me_subm.gds index a94ec07c09475a019cc6f05b71c51e6056aff5d5..9d8b540e4fb315b934a3e31d77a606363d970fdc 100644 GIT binary patch delta 93 zcmZn=Xb_kn?k~i^!63>Y$iT$F%)rCMmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 mE?|&hWnvIu;AY@w5Mhu4>Oj`Z!NyjST3oU*XA1iyCN=<86A{z^ delta 96 zcmZn=Xb_kn9xBMd${@iY!obd;!@$GDmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 po?wt+WnvIu;AY?ls+VG5VnEi*$Htaxn3R*BoSm4Sx-o4EI{>Hd6W#y- diff --git a/compiler/router/tests/02_blockages_test.py b/compiler/router/tests/02_blockages_test.py new file mode 100755 index 00000000..7d46f02b --- /dev/null +++ b/compiler/router/tests/02_blockages_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug + +OPTS = globals.OPTS + +class blockages_test(openram_test): + """ + Simple two pin route test with multilayer blockages. + """ + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router + + class routing(design, openram_test): + """ + A generic GDS design that we can route on. + """ + def __init__(self, name): + design.__init__(self, "top") + + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) + self.add_inst(name=name, + mod=cell, + offset=[0,0]) + self.connect_inst([]) + + r=router(gds_file) + layer_stack =("metal1","via1","metal2") + self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) + + r=routing("02_blockages_test_{0}".format(OPTS.tech_name)) + self.local_drc_check(r) + + # fails if there are any DRC errors on any cells + globals.end_openram() + + + + + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/router/tests/02_blockages_test.sp b/compiler/router/tests/02_blockages_test.sp similarity index 100% rename from router/tests/02_blockages_test.sp rename to compiler/router/tests/02_blockages_test.sp diff --git a/router/tests/02_blockages_test_freepdk45.gds b/compiler/router/tests/02_blockages_test_freepdk45.gds similarity index 62% rename from router/tests/02_blockages_test_freepdk45.gds rename to compiler/router/tests/02_blockages_test_freepdk45.gds index 2e31ba2cff1360a454848edac4368f8ec4f94854..e6f868dc1c849f960fa7355608d9cadb055403a1 100644 GIT binary patch delta 93 zcmZn=Xb_kn?k~i^!63%K%^=EP%)rCMmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 meqfMcWnvHjs^w=8VUS{AVnEi*!NyjST3oU*XA1iyW;OtNMG~O^ delta 96 zcmZn=Xb_kn9xA}V%^<`e&LGR6#lXYFmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 ou`tN6GBJSE^D~GrNHIXzFuiIr+)iiRq~u)26Tk0F43@M*si- diff --git a/router/tests/02_blockages_test_scn3me_subm.gds b/compiler/router/tests/02_blockages_test_scn3me_subm.gds similarity index 59% rename from router/tests/02_blockages_test_scn3me_subm.gds rename to compiler/router/tests/02_blockages_test_scn3me_subm.gds index f9407ee4b4cfd79d8e6368c0b7c8885060dfe322..aec979e4ddb1ff7344358439819055fe43ebabf0 100644 GIT binary patch delta 93 zcmZn=Xb_kn?k~i^!63%K#lXj4%D}_KmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 po?wt+WnvIu;AY@w5MhvFU}8Y%HDlmlV=GB5F4>qfg?$n;8vt=B5^ewh delta 96 zcmZn=Xb_kn9xBMd${@iY!obO(!ob7CmXey5SejG9z#ziRYUkK#?tN_e8%viR`U_@9 p-eHhoWnvIu;AY?ls+VG5VnEi*$Htaxn3R*BoSm4Sx-o4EI{>Lx6XpN_ diff --git a/compiler/router/tests/03_same_layer_pins_test.py b/compiler/router/tests/03_same_layer_pins_test.py new file mode 100755 index 00000000..39a72990 --- /dev/null +++ b/compiler/router/tests/03_same_layer_pins_test.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug + +OPTS = globals.OPTS + +class same_layer_pins_test(openram_test): + """ + Checks two pins on the same layer with positive and negative coordinates. + """ + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router + + class routing(design, openram_test): + """ + A generic GDS design that we can route on. + """ + def __init__(self, name): + design.__init__(self, "top") + + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) + self.add_inst(name=name, + mod=cell, + offset=[0,0]) + self.connect_inst([]) + + r=router(gds_file) + layer_stack =("metal1","via1","metal2") + self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) + + r = routing("03_same_layer_pins_test_{0}".format(OPTS.tech_name)) + self.local_drc_check(r) + + # fails if there are any DRC errors on any cells + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/router/tests/03_same_layer_pins_test.sp b/compiler/router/tests/03_same_layer_pins_test.sp similarity index 100% rename from router/tests/03_same_layer_pins_test.sp rename to compiler/router/tests/03_same_layer_pins_test.sp diff --git a/router/tests/03_same_layer_pins_test_freepdk45.gds b/compiler/router/tests/03_same_layer_pins_test_freepdk45.gds similarity index 100% rename from router/tests/03_same_layer_pins_test_freepdk45.gds rename to compiler/router/tests/03_same_layer_pins_test_freepdk45.gds diff --git a/router/tests/03_same_layer_pins_test_scn3me_subm.gds b/compiler/router/tests/03_same_layer_pins_test_scn3me_subm.gds similarity index 100% rename from router/tests/03_same_layer_pins_test_scn3me_subm.gds rename to compiler/router/tests/03_same_layer_pins_test_scn3me_subm.gds diff --git a/compiler/router/tests/04_diff_layer_pins_test.py b/compiler/router/tests/04_diff_layer_pins_test.py new file mode 100755 index 00000000..0390a6b4 --- /dev/null +++ b/compiler/router/tests/04_diff_layer_pins_test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug + +OPTS = globals.OPTS + +class diff_layer_pins_test(openram_test): + """ + Two pin route test with pins on different layers and blockages. + Pins are smaller than grid size. + """ + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router + + class routing(design, openram_test): + """ + A generic GDS design that we can route on. + """ + def __init__(self, name): + design.__init__(self, "top") + + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) + self.add_inst(name=name, + mod=cell, + offset=[0,0]) + self.connect_inst([]) + + r=router(gds_file) + layer_stack =("metal1","via1","metal2") + self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) + + r = routing("04_diff_layer_pins_test_{0}".format(OPTS.tech_name)) + self.local_drc_check(r) + + # fails if there are any DRC errors on any cells + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/router/tests/04_diff_layer_pins_test.sp b/compiler/router/tests/04_diff_layer_pins_test.sp similarity index 100% rename from router/tests/04_diff_layer_pins_test.sp rename to compiler/router/tests/04_diff_layer_pins_test.sp diff --git a/router/tests/04_diff_layer_pins_test_freepdk45.gds b/compiler/router/tests/04_diff_layer_pins_test_freepdk45.gds similarity index 100% rename from router/tests/04_diff_layer_pins_test_freepdk45.gds rename to compiler/router/tests/04_diff_layer_pins_test_freepdk45.gds diff --git a/router/tests/04_diff_layer_pins_test_scn3me_subm.gds b/compiler/router/tests/04_diff_layer_pins_test_scn3me_subm.gds similarity index 100% rename from router/tests/04_diff_layer_pins_test_scn3me_subm.gds rename to compiler/router/tests/04_diff_layer_pins_test_scn3me_subm.gds diff --git a/compiler/router/tests/05_two_nets_test.py b/compiler/router/tests/05_two_nets_test.py new file mode 100755 index 00000000..e19f7d49 --- /dev/null +++ b/compiler/router/tests/05_two_nets_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug + +OPTS = globals.OPTS + +class two_nets_test(openram_test): + """ + Route two nets in the same GDS file. The routes will interact, + so they must block eachother. + """ + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router + + class routing(design, openram_test): + """ + A generic GDS design that we can route on. + """ + def __init__(self, name): + design.__init__(self, "top") + + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) + self.add_inst(name=name, + mod=cell, + offset=[0,0]) + self.connect_inst([]) + + r=router(gds_file) + layer_stack =("metal1","via1","metal2") + self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) + self.assertTrue(r.route(self,layer_stack,src="C",dest="D")) + + r = routing("05_two_nets_test_{0}".format(OPTS.tech_name)) + self.local_drc_check(r) + + # fails if there are any DRC errors on any cells + globals.end_openram() + + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/router/tests/05_two_nets_test.sp b/compiler/router/tests/05_two_nets_test.sp similarity index 100% rename from router/tests/05_two_nets_test.sp rename to compiler/router/tests/05_two_nets_test.sp diff --git a/router/tests/05_two_nets_test_freepdk45.gds b/compiler/router/tests/05_two_nets_test_freepdk45.gds similarity index 100% rename from router/tests/05_two_nets_test_freepdk45.gds rename to compiler/router/tests/05_two_nets_test_freepdk45.gds diff --git a/router/tests/05_two_nets_test_scn3me_subm.gds b/compiler/router/tests/05_two_nets_test_scn3me_subm.gds similarity index 100% rename from router/tests/05_two_nets_test_scn3me_subm.gds rename to compiler/router/tests/05_two_nets_test_scn3me_subm.gds diff --git a/router/tests/06_pin_location_test.py b/compiler/router/tests/06_pin_location_test.py similarity index 54% rename from router/tests/06_pin_location_test.py rename to compiler/router/tests/06_pin_location_test.py index 10cb854b..c157dcb8 100755 --- a/router/tests/06_pin_location_test.py +++ b/compiler/router/tests/06_pin_location_test.py @@ -1,57 +1,43 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 "Run a regresion test the library cells for DRC" import unittest -from testutils import header +from testutils import header,openram_test import sys,os sys.path.append(os.path.join(sys.path[0],"../..")) sys.path.append(os.path.join(sys.path[0],"..")) import globals import debug -import calibre OPTS = globals.OPTS -class pin_location_test(unittest.TestCase): +class pin_location_test(openram_test): """ Simplest two pin route test with no blockages using the pin locations instead of labels. """ def runTest(self): globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router - import design - import router - - class gdscell(design.design): + class routing(design, openram_test): """ A generic GDS design that we can route on. """ def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) + design.__init__(self, "top") - cell = gdscell(name) + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) self.add_inst(name=name, mod=cell, offset=[0,0]) self.connect_inst([]) - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) + r=router(gds_file) layer_stack =("metal1","via1","metal2") # these are user coordinates and layers src_pin = [[0.52, 4.099],11] @@ -62,21 +48,12 @@ class pin_location_test(unittest.TestCase): # This only works for freepdk45 since the coordinates are hard coded if OPTS.tech_name == "freepdk45": r = routing("06_pin_location_test_{0}".format(OPTS.tech_name)) - self.local_check(r) + self.local_drc_check(r) else: debug.warning("This test does not support technology {0}".format(OPTS.tech_name)) - + # fails if there are any DRC errors on any cells globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - diff --git a/router/tests/06_pin_location_test_freepdk45.gds b/compiler/router/tests/06_pin_location_test_freepdk45.gds similarity index 100% rename from router/tests/06_pin_location_test_freepdk45.gds rename to compiler/router/tests/06_pin_location_test_freepdk45.gds diff --git a/router/tests/06_pin_location_test_scn3me_subm.gds b/compiler/router/tests/06_pin_location_test_scn3me_subm.gds similarity index 100% rename from router/tests/06_pin_location_test_scn3me_subm.gds rename to compiler/router/tests/06_pin_location_test_scn3me_subm.gds diff --git a/router/tests/07_big_test.py b/compiler/router/tests/07_big_test.py similarity index 63% rename from router/tests/07_big_test.py rename to compiler/router/tests/07_big_test.py index 4e66c07e..e71e0bf4 100755 --- a/router/tests/07_big_test.py +++ b/compiler/router/tests/07_big_test.py @@ -1,58 +1,44 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 "Run a regresion test the library cells for DRC" import unittest -from testutils import header +from testutils import header,openram_test import sys,os sys.path.append(os.path.join(sys.path[0],"../..")) sys.path.append(os.path.join(sys.path[0],"..")) import globals import debug -import calibre OPTS = globals.OPTS -class big_test(unittest.TestCase): +class big_test(openram_test): """ Simplest two pin route test with no blockages using the pin locations instead of labels. """ def runTest(self): globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router - import design - import router - - class gdscell(design.design): + class routing(design, openram_test): """ A generic GDS design that we can route on. """ def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) + design.__init__(self, "top") - cell = gdscell(name) + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) self.add_inst(name=name, mod=cell, offset=[0,0]) self.connect_inst([]) - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) - layer_stack =("metal3","via2","metal2") + r=router(gds_file) + layer_stack =("metal1","via1","metal2") connections=[('out_0_2', 'a_0_0'), ('out_0_3', 'b_0_0'), ('out_0_0', 'a_0_1'), @@ -80,21 +66,12 @@ class big_test(unittest.TestCase): # This test only runs on scn3me_subm tech if OPTS.tech_name=="scn3me_subm": r = routing("07_big_test_{0}".format(OPTS.tech_name)) - self.local_check(r) + self.local_drc_check(r) else: debug.warning("This test does not support technology {0}".format(OPTS.tech_name)) # fails if there are any DRC errors on any cells globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - diff --git a/router/tests/07_big_test_scn3me_subm.gds b/compiler/router/tests/07_big_test_scn3me_subm.gds similarity index 100% rename from router/tests/07_big_test_scn3me_subm.gds rename to compiler/router/tests/07_big_test_scn3me_subm.gds diff --git a/router/tests/08_expand_region_test.py b/compiler/router/tests/08_expand_region_test.py similarity index 51% rename from router/tests/08_expand_region_test.py rename to compiler/router/tests/08_expand_region_test.py index f7d3805b..47941b92 100755 --- a/router/tests/08_expand_region_test.py +++ b/compiler/router/tests/08_expand_region_test.py @@ -1,57 +1,43 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python3 "Run a regresion test the library cells for DRC" import unittest -from testutils import header +from testutils import header,openram_test import sys,os sys.path.append(os.path.join(sys.path[0],"../..")) sys.path.append(os.path.join(sys.path[0],"..")) import globals import debug -import calibre OPTS = globals.OPTS -class expand_region_test(unittest.TestCase): +class expand_region_test(openram_test): """ Test an infeasible route followed by a feasible route with an expanded region. """ def runTest(self): globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from router import router - import design - import router - - class gdscell(design.design): + class routing(design, openram_test): """ A generic GDS design that we can route on. """ def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) + design.__init__(self, "top") - cell = gdscell(name) + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) self.add_inst(name=name, mod=cell, offset=[0,0]) self.connect_inst([]) - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) + r=router(gds_file) layer_stack =("metal1","via1","metal2") # This should be infeasible because it is blocked without a detour. self.assertFalse(r.route(self,layer_stack,src="A",dest="B",detour_scale=1)) @@ -59,22 +45,12 @@ class expand_region_test(unittest.TestCase): self.assertTrue(r.route(self,layer_stack,src="A",dest="B",detour_scale=3)) r = routing("08_expand_region_test_{0}".format(OPTS.tech_name)) - self.local_check(r) + self.local_drc_check(r) # fails if there are any DRC errors on any cells globals.end_openram() - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - - - - # instantiate a copy of the class to actually run the test if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/router/tests/08_expand_region_test_freepdk45.gds b/compiler/router/tests/08_expand_region_test_freepdk45.gds similarity index 100% rename from router/tests/08_expand_region_test_freepdk45.gds rename to compiler/router/tests/08_expand_region_test_freepdk45.gds diff --git a/router/tests/08_expand_region_test_scn3me_subm.gds b/compiler/router/tests/08_expand_region_test_scn3me_subm.gds similarity index 100% rename from router/tests/08_expand_region_test_scn3me_subm.gds rename to compiler/router/tests/08_expand_region_test_scn3me_subm.gds diff --git a/router/tests/config_freepdk45.py b/compiler/router/tests/config_freepdk45.py similarity index 100% rename from router/tests/config_freepdk45.py rename to compiler/router/tests/config_freepdk45.py diff --git a/router/tests/config_scn3me_subm.py b/compiler/router/tests/config_scn3me_subm.py similarity index 100% rename from router/tests/config_scn3me_subm.py rename to compiler/router/tests/config_scn3me_subm.py diff --git a/compiler/router/tests/gds_cell.py b/compiler/router/tests/gds_cell.py new file mode 100644 index 00000000..5c1e0f91 --- /dev/null +++ b/compiler/router/tests/gds_cell.py @@ -0,0 +1,16 @@ +from design import design +class gds_cell(design): + """ + A generic GDS design. + """ + def __init__(self, name, gds_file): + self.name = name + self.gds_file = gds_file + self.sp_file = None + + design.__init__(self, name) + + # The dimensions will not be defined, so do this... + self.width=0 + self.height=0 + diff --git a/router/tests/regress.py b/compiler/router/tests/regress.py similarity index 100% rename from router/tests/regress.py rename to compiler/router/tests/regress.py diff --git a/router/tests/testutils.py b/compiler/router/tests/testutils.py similarity index 98% rename from router/tests/testutils.py rename to compiler/router/tests/testutils.py index 64c1c2b4..4bea5d15 100755 --- a/router/tests/testutils.py +++ b/compiler/router/tests/testutils.py @@ -1,6 +1,6 @@ import unittest,warnings import sys,os,glob,copy -sys.path.append(os.path.join(sys.path[0],"..")) +sys.path.append(os.path.join(sys.path[0],"../..")) from globals import OPTS import debug @@ -19,7 +19,8 @@ class openram_test(unittest.TestCase): if result != 0: self.fail("DRC failed: {}".format(w.name)) - self.cleanup() + if OPTS.purge_temp: + self.cleanup() def local_check(self, a, final_verification=False): diff --git a/router/vector3d.py b/compiler/router/vector3d.py similarity index 100% rename from router/vector3d.py rename to compiler/router/vector3d.py diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 112b1257..0f025b2b 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -86,6 +86,10 @@ def write_magic_script(cell_name, gds_name, extract=False): f.write("gds warning default\n") f.write("gds read {}\n".format(gds_name)) f.write("load {}\n".format(cell_name)) + # Flatten the cell to get rid of DRCs spanning multiple layers + # (e.g. with routes) + f.write("flatten {}_new\n".format(cell_name)) + f.write("load {}_new\n".format(cell_name)) f.write("writeall force\n") f.write("drc check\n") f.write("drc catchup\n") diff --git a/router/tests/01_no_blockages_test.py b/router/tests/01_no_blockages_test.py deleted file mode 100755 index 1d0b4640..00000000 --- a/router/tests/01_no_blockages_test.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python2.7 -"Run a regresion test the library cells for DRC" - -import unittest -from testutils import header -import sys,os -sys.path.append(os.path.join(sys.path[0],"../..")) -sys.path.append(os.path.join(sys.path[0],"..")) -import globals -import debug - -OPTS = globals.OPTS - -class no_blockages_test(unittest.TestCase): - """ - Simplest two pin route test with no blockages. - """ - - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - global verify - import verify - - import design - import router - - class gdscell(design.design): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - - cell = gdscell(name) - self.add_inst(name=name, - mod=cell, - offset=[0,0]) - self.connect_inst([]) - - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) - layer_stack =("metal1","via1","metal2") - self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) - - r = routing("01_no_blockages_test_{0}".format(OPTS.tech_name)) - self.local_check(r) - - # fails if there are any DRC errors on any cells - globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - - - - -# instantiate a copy of the class to actually run the test -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main() diff --git a/router/tests/02_blockages_test.py b/router/tests/02_blockages_test.py deleted file mode 100755 index 2afefa4a..00000000 --- a/router/tests/02_blockages_test.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python2.7 -"Run a regresion test the library cells for DRC" - -import unittest -from testutils import header -import sys,os -sys.path.append(os.path.join(sys.path[0],"../..")) -sys.path.append(os.path.join(sys.path[0],"..")) -import globals -import debug -import calibre - -OPTS = globals.OPTS - -class blockages_test(unittest.TestCase): - """ - Simple two pin route test with multilayer blockages. - """ - - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - - import design - import router - - class gdscell(design.design): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - - cell = gdscell(name) - self.add_inst(name=name, - mod=cell, - offset=[0,0]) - self.connect_inst([]) - - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) - layer_stack =("metal1","via1","metal2") - self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) - - r = routing("02_blockages_test_{0}".format(OPTS.tech_name)) - self.local_check(r) - - # fails if there are any DRC errors on any cells - globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - - - - -# instantiate a copy of the class to actually run the test -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main() diff --git a/router/tests/03_same_layer_pins_test.py b/router/tests/03_same_layer_pins_test.py deleted file mode 100755 index dfd849c9..00000000 --- a/router/tests/03_same_layer_pins_test.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python2.7 -"Run a regresion test the library cells for DRC" - -import unittest -from testutils import header -import sys,os -sys.path.append(os.path.join(sys.path[0],"../..")) -sys.path.append(os.path.join(sys.path[0],"..")) -import globals -import debug -import calibre - -OPTS = globals.OPTS - -class same_layer_pins_test(unittest.TestCase): - """ - Checks two pins on the same layer with positive and negative coordinates. - """ - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - - import design - import router - - class gdscell(design.design): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - - cell = gdscell(name) - self.add_inst(name=name, - mod=cell, - offset=[0,0]) - self.connect_inst([]) - - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) - layer_stack =("metal1","via1","metal2") - self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) - - r = routing("03_same_layer_pins_test_{0}".format(OPTS.tech_name)) - self.local_check(r) - - - # fails if there are any DRC errors on any cells - globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - - - - -# instantiate a copy of the class to actually run the test -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main() diff --git a/router/tests/04_diff_layer_pins_test.py b/router/tests/04_diff_layer_pins_test.py deleted file mode 100755 index e0a54875..00000000 --- a/router/tests/04_diff_layer_pins_test.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python2.7 -"Run a regresion test the library cells for DRC" - -import unittest -from testutils import header -import sys,os -sys.path.append(os.path.join(sys.path[0],"../..")) -sys.path.append(os.path.join(sys.path[0],"..")) -import globals -import debug -import calibre - -OPTS = globals.OPTS - -class diff_layer_pins_test(unittest.TestCase): - """ - Two pin route test with pins on different layers and blockages. - Pins are smaller than grid size. - """ - - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - - import design - import router - - class gdscell(design.design): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - - cell = gdscell(name) - self.add_inst(name=name, - mod=cell, - offset=[0,0]) - self.connect_inst([]) - - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) - layer_stack =("metal1","via1","metal2") - self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) - - r = routing("04_diff_layer_pins_test_{0}".format(OPTS.tech_name)) - self.local_check(r) - - # fails if there are any DRC errors on any cells - globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - - - - -# instantiate a copy of the class to actually run the test -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main() diff --git a/router/tests/05_two_nets_test.py b/router/tests/05_two_nets_test.py deleted file mode 100755 index b59dd005..00000000 --- a/router/tests/05_two_nets_test.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python2.7 -"Run a regresion test the library cells for DRC" - -import unittest -from testutils import header -import sys,os -sys.path.append(os.path.join(sys.path[0],"../..")) -sys.path.append(os.path.join(sys.path[0],"..")) -import globals -import debug -import calibre - -OPTS = globals.OPTS - -class two_nets_test(unittest.TestCase): - """ - Route two nets in the same GDS file. The routes will interact, - so they must block eachother. - """ - - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - - import design - import router - - class gdscell(design.design): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - #design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - self.name = name - self.gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - self.sp_file = "{0}/{1}.sp".format(os.path.dirname(os.path.realpath(__file__)),name) - design.hierarchy_layout.layout.__init__(self, name) - design.hierarchy_spice.spice.__init__(self, name) - - class routing(design.design,unittest.TestCase): - """ - A generic GDS design that we can route on. - """ - def __init__(self, name): - design.design.__init__(self, name) - debug.info(2, "Create {0} object".format(name)) - - cell = gdscell(name) - self.add_inst(name=name, - mod=cell, - offset=[0,0]) - self.connect_inst([]) - - self.gdsname = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) - r=router.router(self.gdsname) - layer_stack =("metal1","via1","metal2") - self.assertTrue(r.route(self,layer_stack,src="A",dest="B")) - self.assertTrue(r.route(self,layer_stack,src="C",dest="D")) - - r = routing("05_two_nets_test_{0}".format(OPTS.tech_name)) - self.local_check(r) - - # fails if there are any DRC errors on any cells - globals.end_openram() - - - def local_check(self, r): - tempgds = OPTS.openram_temp + "temp.gds" - r.gds_write(tempgds) - self.assertFalse(calibre.run_drc(r.name, tempgds)) - os.remove(tempgds) - - - - - -# instantiate a copy of the class to actually run the test -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main() From 8f1e2675fe19c7e5f33d9e17845c05ca9465bc0f Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 22 Aug 2018 11:51:20 -0700 Subject: [PATCH 4/7] Remove extraneous files --- compiler/tests/b3v33check.log | 4 ---- compiler/tests/missing_pin.gds | Bin 20820 -> 0 bytes 2 files changed, 4 deletions(-) delete mode 100644 compiler/tests/b3v33check.log delete mode 100644 compiler/tests/missing_pin.gds diff --git a/compiler/tests/b3v33check.log b/compiler/tests/b3v33check.log deleted file mode 100644 index 0cc058a3..00000000 --- a/compiler/tests/b3v33check.log +++ /dev/null @@ -1,4 +0,0 @@ -BSIM3v3.3.0 Parameter Checking. -Model = p -Warning: Pd = 0 is less than W. -Warning: Ps = 0 is less than W. diff --git a/compiler/tests/missing_pin.gds b/compiler/tests/missing_pin.gds deleted file mode 100644 index d73fd56fde5e599744bd2d22f346e254fb3e505c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20820 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 F{{>=#MH~PC From 82833ef8f04b51dc016bbab8057777819a224bf4 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 22 Aug 2018 15:56:19 -0700 Subject: [PATCH 5/7] Initial refactor of signal and supply router classes. --- compiler/modules/multibank.py | 874 ++++++++++++++++++ compiler/router/astar_grid.py | 250 +++++ compiler/router/grid.py | 231 +---- compiler/router/router.py | 276 +----- compiler/router/signal_router.py | 216 +++++ compiler/router/supply_router.py | 140 +++ compiler/router/tests/01_no_blockages_test.py | 2 +- compiler/router/tests/02_blockages_test.py | 2 +- .../router/tests/03_same_layer_pins_test.py | 2 +- .../router/tests/04_diff_layer_pins_test.py | 2 +- compiler/router/tests/05_two_nets_test.py | 2 +- compiler/router/tests/06_pin_location_test.py | 2 +- compiler/router/tests/07_big_test.py | 2 +- .../router/tests/08_expand_region_test.py | 2 +- compiler/router/tests/10_supply_grid_test.py | 55 ++ 15 files changed, 1592 insertions(+), 466 deletions(-) create mode 100644 compiler/modules/multibank.py create mode 100644 compiler/router/astar_grid.py create mode 100644 compiler/router/signal_router.py create mode 100644 compiler/router/supply_router.py create mode 100755 compiler/router/tests/10_supply_grid_test.py diff --git a/compiler/modules/multibank.py b/compiler/modules/multibank.py new file mode 100644 index 00000000..3a63c890 --- /dev/null +++ b/compiler/modules/multibank.py @@ -0,0 +1,874 @@ +import sys +from tech import drc, parameter +import debug +import design +import math +from math import log,sqrt,ceil +import contact +from pinv import pinv +from pnand2 import pnand2 +from pnor2 import pnor2 +from vector import vector +from pinvbuf import pinvbuf + +from globals import OPTS + +class multibank(design.design): + """ + Dynamically generated a single bank including bitcell array, + hierarchical_decoder, precharge, (optional column_mux and column decoder), + write driver and sense amplifiers. + This module includes the tristate and bank select logic. + """ + + def __init__(self, word_size, num_words, words_per_row, num_banks=1, name=""): + + mod_list = ["tri_gate", "bitcell", "decoder", "ms_flop_array", "wordline_driver", + "bitcell_array", "sense_amp_array", "precharge_array", + "column_mux_array", "write_driver_array", "tri_gate_array", + "dff", "bank_select"] + from importlib import reload + for mod_name in mod_list: + config_mod_name = getattr(OPTS, mod_name) + class_file = reload(__import__(config_mod_name)) + mod_class = getattr(class_file , config_mod_name) + setattr (self, "mod_"+mod_name, mod_class) + + if name == "": + name = "bank_{0}_{1}".format(word_size, num_words) + design.design.__init__(self, name) + debug.info(2, "create sram of size {0} with {1} words".format(word_size,num_words)) + + self.word_size = word_size + self.num_words = num_words + self.words_per_row = words_per_row + self.num_banks = num_banks + + # The local control signals are gated when we have bank select logic, + # so this prefix will be added to all of the input signals to create + # the internal gated signals. + if self.num_banks>1: + self.prefix="gated_" + else: + self.prefix="" + + self.compute_sizes() + self.add_pins() + self.create_modules() + self.add_modules() + self.setup_layout_constraints() + + # FIXME: Move this to the add modules function + self.add_bank_select() + + self.route_layout() + + + # Can remove the following, but it helps for debug! + self.add_lvs_correspondence_points() + + # Remember the bank center for further placement + self.bank_center=self.offset_all_coordinates().scale(-1,-1) + + self.DRC_LVS() + + def add_pins(self): + """ Adding pins for Bank module""" + for i in range(self.word_size): + self.add_pin("DOUT[{0}]".format(i),"OUT") + for i in range(self.word_size): + self.add_pin("BANK_DIN[{0}]".format(i),"IN") + for i in range(self.addr_size): + self.add_pin("A[{0}]".format(i),"INPUT") + + # For more than one bank, we have a bank select and name + # the signals gated_*. + if self.num_banks > 1: + self.add_pin("bank_sel","INPUT") + for pin in ["s_en","w_en","tri_en_bar","tri_en", + "clk_buf_bar","clk_buf"]: + self.add_pin(pin,"INPUT") + self.add_pin("vdd","POWER") + self.add_pin("gnd","GROUND") + + def route_layout(self): + """ Create routing amoung the modules """ + self.route_central_bus() + self.route_precharge_to_bitcell_array() + self.route_col_mux_to_bitcell_array() + self.route_sense_amp_to_col_mux_or_bitcell_array() + #self.route_sense_amp_to_trigate() + #self.route_tri_gate_out() + self.route_sense_amp_out() + self.route_wordline_driver() + self.route_write_driver() + self.route_row_decoder() + self.route_column_address_lines() + self.route_control_lines() + self.add_control_pins() + if self.num_banks > 1: + self.route_bank_select() + + self.route_vdd_gnd() + + def add_modules(self): + """ Add modules. The order should not matter! """ + + # Above the bitcell array + self.add_bitcell_array() + self.add_precharge_array() + + # Below the bitcell array + self.add_column_mux_array() + self.add_sense_amp_array() + self.add_write_driver_array() + # Not needed for single bank + #self.add_tri_gate_array() + + # To the left of the bitcell array + self.add_row_decoder() + self.add_wordline_driver() + self.add_column_decoder() + + + + def compute_sizes(self): + """ Computes the required sizes to create the bank """ + + self.num_cols = int(self.words_per_row*self.word_size) + self.num_rows = int(self.num_words / self.words_per_row) + + self.row_addr_size = int(log(self.num_rows, 2)) + self.col_addr_size = int(log(self.words_per_row, 2)) + self.addr_size = self.col_addr_size + self.row_addr_size + + debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"Invalid bank sizes.") + debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,"Invalid address break down.") + + # Width for the vdd/gnd rails + self.supply_rail_width = 4*self.m2_width + # FIXME: This spacing should be width dependent... + self.supply_rail_pitch = self.supply_rail_width + 4*self.m2_space + + # Number of control lines in the bus + self.num_control_lines = 6 + # The order of the control signals on the control bus: + self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"] + # These will be outputs of the gaters if this is multibank, if not, normal signals. + if self.num_banks > 1: + self.control_signals = ["gated_"+str for str in self.input_control_signals] + else: + self.control_signals = self.input_control_signals + # The central bus is the column address (one hot) and row address (binary) + if self.col_addr_size>0: + self.num_col_addr_lines = 2**self.col_addr_size + else: + self.num_col_addr_lines = 0 + + # The width of this bus is needed to place other modules (e.g. decoder) + # A width on each side too + self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width + + # A space for wells or jogging m2 + self.m2_gap = max(2*drc["pwell_to_nwell"] + drc["well_enclosure_active"], + 2*self.m2_pitch) + + + + def create_modules(self): + """ Create all the modules using the class loader """ + self.tri = self.mod_tri_gate() + self.bitcell = self.mod_bitcell() + + self.bitcell_array = self.mod_bitcell_array(cols=self.num_cols, + rows=self.num_rows) + self.add_mod(self.bitcell_array) + + self.precharge_array = self.mod_precharge_array(columns=self.num_cols) + self.add_mod(self.precharge_array) + + if self.col_addr_size > 0: + self.column_mux_array = self.mod_column_mux_array(columns=self.num_cols, + word_size=self.word_size) + self.add_mod(self.column_mux_array) + + + self.sense_amp_array = self.mod_sense_amp_array(word_size=self.word_size, + words_per_row=self.words_per_row) + self.add_mod(self.sense_amp_array) + + self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols, + word_size=self.word_size) + self.add_mod(self.write_driver_array) + + self.row_decoder = self.mod_decoder(rows=self.num_rows) + self.add_mod(self.row_decoder) + + self.tri_gate_array = self.mod_tri_gate_array(columns=self.num_cols, + word_size=self.word_size) + self.add_mod(self.tri_gate_array) + + self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows) + self.add_mod(self.wordline_driver) + + self.inv = pinv() + self.add_mod(self.inv) + + if(self.num_banks > 1): + self.bank_select = self.mod_bank_select() + self.add_mod(self.bank_select) + + + def add_bitcell_array(self): + """ Adding Bitcell Array """ + + self.bitcell_array_inst=self.add_inst(name="bitcell_array", + mod=self.bitcell_array, + offset=vector(0,0)) + temp = [] + for i in range(self.num_cols): + temp.append("bl[{0}]".format(i)) + temp.append("br[{0}]".format(i)) + for j in range(self.num_rows): + temp.append("wl[{0}]".format(j)) + temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) + + + def add_precharge_array(self): + """ Adding Precharge """ + + # The wells must be far enough apart + # The enclosure is for the well and the spacing is to the bitcell wells + y_offset = self.bitcell_array.height + self.m2_gap + self.precharge_array_inst=self.add_inst(name="precharge_array", + mod=self.precharge_array, + offset=vector(0,y_offset)) + temp = [] + for i in range(self.num_cols): + temp.append("bl[{0}]".format(i)) + temp.append("br[{0}]".format(i)) + temp.extend([self.prefix+"clk_buf_bar", "vdd"]) + self.connect_inst(temp) + + def add_column_mux_array(self): + """ Adding Column Mux when words_per_row > 1 . """ + if self.col_addr_size > 0: + self.column_mux_height = self.column_mux_array.height + self.m2_gap + else: + self.column_mux_height = 0 + return + + y_offset = self.column_mux_height + self.col_mux_array_inst=self.add_inst(name="column_mux_array", + mod=self.column_mux_array, + offset=vector(0,y_offset).scale(-1,-1)) + temp = [] + for i in range(self.num_cols): + temp.append("bl[{0}]".format(i)) + temp.append("br[{0}]".format(i)) + for k in range(self.words_per_row): + temp.append("sel[{0}]".format(k)) + for j in range(self.word_size): + temp.append("bl_out[{0}]".format(j)) + temp.append("br_out[{0}]".format(j)) + temp.append("gnd") + self.connect_inst(temp) + + def add_sense_amp_array(self): + """ Adding Sense amp """ + + y_offset = self.column_mux_height + self.sense_amp_array.height + self.m2_gap + self.sense_amp_array_inst=self.add_inst(name="sense_amp_array", + mod=self.sense_amp_array, + offset=vector(0,y_offset).scale(-1,-1)) + temp = [] + for i in range(self.word_size): + temp.append("sa_out[{0}]".format(i)) + if self.words_per_row == 1: + temp.append("bl[{0}]".format(i)) + temp.append("br[{0}]".format(i)) + else: + temp.append("bl_out[{0}]".format(i)) + temp.append("br_out[{0}]".format(i)) + + temp.extend([self.prefix+"s_en", "vdd", "gnd"]) + self.connect_inst(temp) + + def add_write_driver_array(self): + """ Adding Write Driver """ + + y_offset = self.sense_amp_array.height + self.column_mux_height \ + + self.m2_gap + self.write_driver_array.height + self.write_driver_array_inst=self.add_inst(name="write_driver_array", + mod=self.write_driver_array, + offset=vector(0,y_offset).scale(-1,-1)) + + temp = [] + for i in range(self.word_size): + temp.append("BANK_DIN[{0}]".format(i)) + for i in range(self.word_size): + if (self.words_per_row == 1): + temp.append("bl[{0}]".format(i)) + temp.append("br[{0}]".format(i)) + else: + temp.append("bl_out[{0}]".format(i)) + temp.append("br_out[{0}]".format(i)) + temp.extend([self.prefix+"w_en", "vdd", "gnd"]) + self.connect_inst(temp) + + def add_tri_gate_array(self): + """ data tri gate to drive the data bus """ + y_offset = self.sense_amp_array.height+self.column_mux_height \ + + self.m2_gap + self.tri_gate_array.height + self.tri_gate_array_inst=self.add_inst(name="tri_gate_array", + mod=self.tri_gate_array, + offset=vector(0,y_offset).scale(-1,-1)) + + temp = [] + for i in range(self.word_size): + temp.append("sa_out[{0}]".format(i)) + for i in range(self.word_size): + temp.append("DOUT[{0}]".format(i)) + temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"]) + self.connect_inst(temp) + + def add_row_decoder(self): + """ Add the hierarchical row decoder """ + + + # The address and control bus will be in between decoder and the main memory array + # This bus will route address bits to the decoder input and column mux inputs. + # The wires are actually routed after we placed the stuff on both sides. + # The predecoder is below the x-axis and the main decoder is above the x-axis + # The address flop and decoder are aligned in the x coord. + + x_offset = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width) + self.row_decoder_inst=self.add_inst(name="row_decoder", + mod=self.row_decoder, + offset=vector(x_offset,0)) + + temp = [] + for i in range(self.row_addr_size): + temp.append("A[{0}]".format(i+self.col_addr_size)) + for j in range(self.num_rows): + temp.append("dec_out[{0}]".format(j)) + temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) + + def add_wordline_driver(self): + """ Wordline Driver """ + + # The wordline driver is placed to the right of the main decoder width. + x_offset = -(self.central_bus_width + self.wordline_driver.width) + self.m2_pitch + self.wordline_driver_inst=self.add_inst(name="wordline_driver", + mod=self.wordline_driver, + offset=vector(x_offset,0)) + + temp = [] + for i in range(self.num_rows): + temp.append("dec_out[{0}]".format(i)) + for i in range(self.num_rows): + temp.append("wl[{0}]".format(i)) + temp.append(self.prefix+"clk_buf") + temp.append("vdd") + temp.append("gnd") + self.connect_inst(temp) + + + def add_column_decoder_module(self): + """ + Create a 2:4 or 3:8 column address decoder. + """ + # Place the col decoder right aligned with row decoder + x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width) + y_off = -(self.col_decoder.height + 2*drc["well_to_well"]) + self.col_decoder_inst=self.add_inst(name="col_address_decoder", + mod=self.col_decoder, + offset=vector(x_off,y_off)) + + temp = [] + for i in range(self.col_addr_size): + temp.append("A[{0}]".format(i)) + for j in range(self.num_col_addr_lines): + temp.append("sel[{0}]".format(j)) + temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) + + def add_column_decoder(self): + """ + Create a decoder to decode column select lines. This could be an inverter/buffer for 1:2, + 2:4 decoder, or 3:8 decoder. + """ + if self.col_addr_size == 0: + return + elif self.col_addr_size == 1: + self.col_decoder = pinvbuf(height=self.mod_dff.height) + self.add_mod(self.col_decoder) + elif self.col_addr_size == 2: + self.col_decoder = self.row_decoder.pre2_4 + elif self.col_addr_size == 3: + self.col_decoder = self.row_decoder.pre3_8 + else: + # No error checking before? + debug.error("Invalid column decoder?",-1) + + self.add_column_decoder_module() + + + def add_bank_select(self): + """ Instantiate the bank select logic. """ + + if not self.num_banks > 1: + return + + x_off = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width) + if self.col_addr_size > 0: + y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by()) + else: + y_off = self.row_decoder_inst.by() + y_off -= (self.bank_select.height + drc["well_to_well"]) + self.bank_select_pos = vector(x_off,y_off) + self.bank_select_inst = self.add_inst(name="bank_select", + mod=self.bank_select, + offset=self.bank_select_pos) + + temp = [] + temp.extend(self.input_control_signals) + temp.append("bank_sel") + temp.extend(self.control_signals) + temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) + + def route_vdd_gnd(self): + """ Propagate all vdd/gnd pins up to this level for all modules """ + + # These are the instances that every bank has + top_instances = [self.bitcell_array_inst, + self.precharge_array_inst, + self.sense_amp_array_inst, + self.write_driver_array_inst, +# self.tri_gate_array_inst, + self.row_decoder_inst, + self.wordline_driver_inst] + # Add these if we use the part... + if self.col_addr_size > 0: + top_instances.append(self.col_decoder_inst) + top_instances.append(self.col_mux_array_inst) + + if self.num_banks > 1: + top_instances.append(self.bank_select_inst) + + + for inst in top_instances: + # Column mux has no vdd + if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst): + self.copy_layout_pin(inst, "vdd") + # Precharge has no gnd + if inst != self.precharge_array_inst: + self.copy_layout_pin(inst, "gnd") + + def route_bank_select(self): + """ Route the bank select logic. """ + for input_name in self.input_control_signals+["bank_sel"]: + self.copy_layout_pin(self.bank_select_inst, input_name) + + for gated_name in self.control_signals: + # Connect the inverter output to the central bus + out_pos = self.bank_select_inst.get_pin(gated_name).rc() + bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y) + self.add_path("metal3",[out_pos, bus_pos]) + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=bus_pos, + rotate=90) + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=out_pos, + rotate=90) + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=out_pos, + rotate=90) + + + def setup_layout_constraints(self): + """ After the modules are instantiated, find the dimensions for the + control bus, power ring, etc. """ + + #The minimum point is either the bottom of the address flops, + #the column decoder (if there is one) or the tristate output + #driver. + # Leave room for the output below the tri gate. + #tri_gate_min_y_offset = self.tri_gate_array_inst.by() - 3*self.m2_pitch + write_driver_min_y_offset = self.write_driver_array_inst.by() - 3*self.m2_pitch + row_decoder_min_y_offset = self.row_decoder_inst.by() + if self.col_addr_size > 0: + col_decoder_min_y_offset = self.col_decoder_inst.by() + else: + col_decoder_min_y_offset = row_decoder_min_y_offset + + if self.num_banks>1: + # The control gating logic is below the decoder + # Min of the control gating logic and tri gate. + self.min_y_offset = min(col_decoder_min_y_offset - self.bank_select.height, write_driver_min_y_offset) + else: + # Just the min of the decoder logic logic and tri gate. + self.min_y_offset = min(col_decoder_min_y_offset, write_driver_min_y_offset) + + # The max point is always the top of the precharge bitlines + # Add a vdd and gnd power rail above the array + self.max_y_offset = self.precharge_array_inst.uy() + 3*self.m1_width + self.max_x_offset = self.bitcell_array_inst.ur().x + 3*self.m1_width + self.min_x_offset = self.row_decoder_inst.lx() + + # # Create the core bbox for the power rings + ur = vector(self.max_x_offset, self.max_y_offset) + ll = vector(self.min_x_offset, self.min_y_offset) + self.core_bbox = [ll, ur] + + self.height = ur.y - ll.y + self.width = ur.x - ll.x + + + + def route_central_bus(self): + """ Create the address, supply, and control signal central bus lines. """ + + # Overall central bus width. It includes all the column mux lines, + # and control lines. + # The bank is at (0,0), so this is to the left of the y-axis. + # 2 pitches on the right for vias/jogs to access the inputs + control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0) + control_bus_length = self.bitcell_array_inst.uy() + self.bus_xoffset = self.create_vertical_bus(layer="metal2", + pitch=self.m2_pitch, + offset=control_bus_offset, + names=self.control_signals, + length=control_bus_length) + + + + def route_precharge_to_bitcell_array(self): + """ Routing of BL and BR between pre-charge and bitcell array """ + + for i in range(self.num_cols): + precharge_bl = self.precharge_array_inst.get_pin("bl[{}]".format(i)).bc() + precharge_br = self.precharge_array_inst.get_pin("br[{}]".format(i)).bc() + bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).uc() + bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).uc() + + yoffset = 0.5*(precharge_bl.y+bitcell_bl.y) + self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset), + vector(bitcell_bl.x,yoffset), bitcell_bl]) + self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset), + vector(bitcell_br.x,yoffset), bitcell_br]) + + + def route_col_mux_to_bitcell_array(self): + """ Routing of BL and BR between col mux and bitcell array """ + + # Only do this if we have a column mux! + if self.col_addr_size==0: + return + + for i in range(self.num_cols): + col_mux_bl = self.col_mux_array_inst.get_pin("bl[{}]".format(i)).uc() + col_mux_br = self.col_mux_array_inst.get_pin("br[{}]".format(i)).uc() + bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc() + bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).bc() + + yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y) + self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset), + vector(bitcell_bl.x,yoffset), bitcell_bl]) + self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset), + vector(bitcell_br.x,yoffset), bitcell_br]) + + def route_sense_amp_to_col_mux_or_bitcell_array(self): + """ Routing of BL and BR between sense_amp and column mux or bitcell array """ + + for i in range(self.word_size): + sense_amp_bl = self.sense_amp_array_inst.get_pin("bl[{}]".format(i)).uc() + sense_amp_br = self.sense_amp_array_inst.get_pin("br[{}]".format(i)).uc() + + if self.col_addr_size>0: + # Sense amp is connected to the col mux + connect_bl = self.col_mux_array_inst.get_pin("bl_out[{}]".format(i)).bc() + connect_br = self.col_mux_array_inst.get_pin("br_out[{}]".format(i)).bc() + else: + # Sense amp is directly connected to the bitcell array + connect_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc() + connect_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).bc() + + + yoffset = 0.5*(sense_amp_bl.y+connect_bl.y) + self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset), + vector(connect_bl.x,yoffset), connect_bl]) + self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset), + vector(connect_br.x,yoffset), connect_br]) + + def route_sense_amp_to_trigate(self): + """ Routing of sense amp output to tri_gate input """ + + for i in range(self.word_size): + # Connection of data_out of sense amp to data_in + tri_gate_in = self.tri_gate_array_inst.get_pin("in[{}]".format(i)).lc() + sa_data_out = self.sense_amp_array_inst.get_pin("data[{}]".format(i)).bc() + + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=tri_gate_in) + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=sa_data_out) + self.add_path("metal3",[sa_data_out,tri_gate_in]) + + def route_sense_amp_out(self): + """ Add pins for the sense amp output """ + for i in range(self.word_size): + data_pin = self.sense_amp_array_inst.get_pin("data[{}]".format(i)) + self.add_layout_pin_rect_center(text="DOUT[{}]".format(i), + layer=data_pin.layer, + offset=data_pin.center(), + height=data_pin.height(), + width=data_pin.width()), + + def route_tri_gate_out(self): + """ Metal 3 routing of tri_gate output data """ + for i in range(self.word_size): + data_pin = self.tri_gate_array_inst.get_pin("out[{}]".format(i)) + self.add_layout_pin_rect_center(text="DOUT[{}]".format(i), + layer=data_pin.layer, + offset=data_pin.center(), + height=data_pin.height(), + width=data_pin.width()), + + + def route_row_decoder(self): + """ Routes the row decoder inputs and supplies """ + + # Create inputs for the row address lines + for i in range(self.row_addr_size): + addr_idx = i + self.col_addr_size + decoder_name = "A[{}]".format(i) + addr_name = "A[{}]".format(addr_idx) + self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name) + + + def route_write_driver(self): + """ Connecting write driver """ + + for i in range(self.word_size): + data_name = "data[{}]".format(i) + din_name = "BANK_DIN[{}]".format(i) + self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name) + + + + def route_wordline_driver(self): + """ Connecting Wordline driver output to Bitcell WL connection """ + + # we don't care about bends after connecting to the input pin, so let the path code decide. + for i in range(self.num_rows): + # The pre/post is to access the pin from "outside" the cell to avoid DRCs + decoder_out_pos = self.row_decoder_inst.get_pin("decode[{}]".format(i)).rc() + driver_in_pos = self.wordline_driver_inst.get_pin("in[{}]".format(i)).lc() + mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) + mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) + self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + + # The mid guarantees we exit the input cell to the right. + driver_wl_pos = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc() + bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl[{}]".format(i)).lc() + mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0) + mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1) + self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + + + + def route_column_address_lines(self): + """ Connecting the select lines of column mux to the address bus """ + if not self.col_addr_size>0: + return + + + + if self.col_addr_size == 1: + + # Connect to sel[0] and sel[1] + decode_names = ["Zb", "Z"] + + # The Address LSB + self.copy_layout_pin(self.col_decoder_inst, "A", "A[0]") + + elif self.col_addr_size > 1: + decode_names = [] + for i in range(self.num_col_addr_lines): + decode_names.append("out[{}]".format(i)) + + for i in range(self.col_addr_size): + decoder_name = "in[{}]".format(i) + addr_name = "A[{}]".format(i) + self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name) + + + # This will do a quick "river route" on two layers. + # When above the top select line it will offset "inward" again to prevent conflicts. + # This could be done on a single layer, but we follow preferred direction rules for later routing. + top_y_offset = self.col_mux_array_inst.get_pin("sel[{}]".format(self.num_col_addr_lines-1)).cy() + for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)): + mux_name = "sel[{}]".format(i) + mux_addr_pos = self.col_mux_array_inst.get_pin(mux_name).lc() + + decode_out_pos = self.col_decoder_inst.get_pin(decode_name).center() + + # To get to the edge of the decoder and one track out + delta_offset = self.col_decoder_inst.rx() - decode_out_pos.x + self.m2_pitch + if decode_out_pos.y > top_y_offset: + mid1_pos = vector(decode_out_pos.x + delta_offset + i*self.m2_pitch,decode_out_pos.y) + else: + mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y) + mid2_pos = vector(mid1_pos.x,mux_addr_pos.y) + #self.add_wire(("metal1","via1","metal2"),[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) + self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) + + + + + + def add_lvs_correspondence_points(self): + """ This adds some points for easier debugging if LVS goes wrong. + These should probably be turned off by default though, since extraction + will show these as ports in the extracted netlist. + """ + # Add the wordline names + for i in range(self.num_rows): + wl_name = "wl[{}]".format(i) + wl_pin = self.bitcell_array_inst.get_pin(wl_name) + self.add_label(text=wl_name, + layer="metal1", + offset=wl_pin.center()) + + # Add the bitline names + for i in range(self.num_cols): + bl_name = "bl[{}]".format(i) + br_name = "br[{}]".format(i) + bl_pin = self.bitcell_array_inst.get_pin(bl_name) + br_pin = self.bitcell_array_inst.get_pin(br_name) + self.add_label(text=bl_name, + layer="metal2", + offset=bl_pin.center()) + self.add_label(text=br_name, + layer="metal2", + offset=br_pin.center()) + + # # Add the data output names to the sense amp output + # for i in range(self.word_size): + # data_name = "data[{}]".format(i) + # data_pin = self.sense_amp_array_inst.get_pin(data_name) + # self.add_label(text="sa_out[{}]".format(i), + # layer="metal2", + # offset=data_pin.center()) + + # Add labels on the decoder + for i in range(self.word_size): + data_name = "dec_out[{}]".format(i) + pin_name = "in[{}]".format(i) + data_pin = self.wordline_driver_inst.get_pin(pin_name) + self.add_label(text=data_name, + layer="metal1", + offset=data_pin.center()) + + + def route_control_lines(self): + """ Route the control lines of the entire bank """ + + # Make a list of tuples that we will connect. + # From control signal to the module pin + # Connection from the central bus to the main control block crosses + # pre-decoder and this connection is in metal3 + connection = [] + #connection.append((self.prefix+"tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").lc())) + #connection.append((self.prefix+"tri_en", self.tri_gate_array_inst.get_pin("en").lc())) + connection.append((self.prefix+"clk_buf_bar", self.precharge_array_inst.get_pin("en").lc())) + connection.append((self.prefix+"w_en", self.write_driver_array_inst.get_pin("en").lc())) + connection.append((self.prefix+"s_en", self.sense_amp_array_inst.get_pin("en").lc())) + + for (control_signal, pin_pos) in connection: + control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y) + self.add_path("metal1", [control_pos, pin_pos]) + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=control_pos, + rotate=90) + + # clk to wordline_driver + control_signal = self.prefix+"clk_buf" + pin_pos = self.wordline_driver_inst.get_pin("en").uc() + mid_pos = pin_pos + vector(0,self.m1_pitch) + control_x_offset = self.bus_xoffset[control_signal].x + control_pos = vector(control_x_offset + self.m1_width, mid_pos.y) + self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) + control_via_pos = vector(control_x_offset, mid_pos.y) + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=control_via_pos, + rotate=90) + + def add_control_pins(self): + """ Add the control signal input pins """ + + for ctrl in self.control_signals: + # xoffsets are the center of the rail + x_offset = self.bus_xoffset[ctrl].x - 0.5*self.m2_width + if self.num_banks > 1: + # it's not an input pin if we have multiple banks + self.add_label_pin(text=ctrl, + layer="metal2", + offset=vector(x_offset, self.min_y_offset), + width=self.m2_width, + height=self.max_y_offset-self.min_y_offset) + else: + self.add_layout_pin(text=ctrl, + layer="metal2", + offset=vector(x_offset, self.min_y_offset), + width=self.m2_width, + height=self.max_y_offset-self.min_y_offset) + + + def connect_rail_from_right(self,inst, pin, rail): + """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ + in_pin = inst.get_pin(pin).lc() + rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) + # Bring it up to M2 for M2/M3 routing + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + contact.m1m2.offset, + rotate=90) + self.add_via(layers=("metal2","via2","metal3"), + offset=in_pin + self.m2m3_via_offset, + rotate=90) + + + def connect_rail_from_left(self,inst, pin, rail): + """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ + in_pin = inst.get_pin(pin).rc() + rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y) + self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) + self.add_via(layers=("metal1","via1","metal2"), + offset=in_pin + contact.m1m2.offset, + rotate=90) + self.add_via(layers=("metal2","via2","metal3"), + offset=in_pin + self.m2m3_via_offset, + rotate=90) + + def analytical_delay(self, slew, load): + """ return analytical delay of the bank""" + decoder_delay = self.row_decoder.analytical_delay(slew, self.wordline_driver.input_load()) + + word_driver_delay = self.wordline_driver.analytical_delay(decoder_delay.slew, self.bitcell_array.input_load()) + + bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew) + + bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew, + self.bitcell_array.output_load()) + # output load of bitcell_array is set to be only small part of bl for sense amp. + + data_t_DATA_delay = self.tri_gate_array.analytical_delay(bl_t_data_out_delay.slew, load) + + result = decoder_delay + word_driver_delay + bitcell_array_delay + bl_t_data_out_delay + data_t_DATA_delay + return result + diff --git a/compiler/router/astar_grid.py b/compiler/router/astar_grid.py new file mode 100644 index 00000000..5715663a --- /dev/null +++ b/compiler/router/astar_grid.py @@ -0,0 +1,250 @@ +from itertools import tee +import debug +from vector3d import vector3d +import grid +from heapq import heappush,heappop + +class astar_grid(grid.grid): + """ + Expand the two layer grid to include A* search functions for a source and target. + """ + + def __init__(self): + """ Create a routing map of width x height cells and 2 in the z-axis. """ + grid.grid.__init__(self) + + # list of the source/target grid coordinates + self.source = [] + self.target = [] + + # priority queue for the maze routing + self.q = [] + + def set_source(self,n): + self.add_map(n) + self.map[n].source=True + self.source.append(n) + + def set_target(self,n): + self.add_map(n) + self.map[n].target=True + self.target.append(n) + + + def add_source(self,track_list): + debug.info(2,"Adding source list={0}".format(str(track_list))) + for n in track_list: + if not self.is_blocked(n): + debug.info(3,"Adding source ={0}".format(str(n))) + self.set_source(n) + + def add_target(self,track_list): + debug.info(2,"Adding target list={0}".format(str(track_list))) + for n in track_list: + if not self.is_blocked(n): + self.set_target(n) + + def is_target(self,point): + """ + Point is in the target set, so we are done. + """ + return point in self.target + + def reinit(self): + """ Reinitialize everything for a new route. """ + + # Reset all the cells in the map + for p in self.map.values(): + p.reset() + + # clear source and target pins + self.source=[] + self.target=[] + + # Clear the queue + while len(self.q)>0: + heappop(self.q) + self.counter = 0 + + def init_queue(self): + """ + Populate the queue with all the source pins with cost + to the target. Each item is a path of the grid cells. + We will use an A* search, so this cost must be pessimistic. + Cost so far will be the length of the path. + """ + debug.info(4,"Initializing queue.") + + # uniquify the source (and target while we are at it) + self.source = list(set(self.source)) + self.target = list(set(self.target)) + + # Counter is used to not require data comparison in Python 3.x + # Items will be returned in order they are added during cost ties + self.counter = 0 + for s in self.source: + cost = self.cost_to_target(s) + debug.info(1,"Init: cost=" + str(cost) + " " + str([s])) + heappush(self.q,(cost,self.counter,[s])) + self.counter+=1 + + + def astar_route(self,detour_scale): + """ + This does the A* maze routing with preferred direction routing. + """ + + # We set a cost bound of the HPWL for run-time. This can be + # over-ridden if the route fails due to pruning a feasible solution. + cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST + + # Make sure the queue is empty if we run another route + while len(self.q)>0: + heappop(self.q) + + # Put the source items into the queue + self.init_queue() + cheapest_path = None + cheapest_cost = None + + # Keep expanding and adding to the priority queue until we are done + while len(self.q)>0: + # should we keep the path in the queue as well or just the final node? + (cost,count,path) = heappop(self.q) + debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost)) + debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path)) + + # expand the last element + neighbors = self.expand_dirs(path) + debug.info(3,"Neighbors: " + str(neighbors)) + + for n in neighbors: + # node is added to the map by the expand routine + newpath = path + [n] + # check if we hit the target and are done + if self.is_target(n): + return (newpath,self.cost(newpath)) + elif not self.map[n].visited: + # current path cost + predicted cost + current_cost = self.cost(newpath) + target_cost = self.cost_to_target(n) + predicted_cost = current_cost + target_cost + # only add the cost if it is less than our bound + if (predicted_cost < cost_bound): + if (self.map[n].min_cost==-1 or current_cost=0 and not self.is_blocked(down) and not down in path: + neighbors.append(down) + + return neighbors + + + def hpwl(self, src, dest): + """ + Return half perimeter wire length from point to another. + Either point can have positive or negative coordinates. + Include the via penalty if there is one. + """ + hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x)) + hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y)) + hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z)) + if src.x!=dest.x or src.y!=dest.y: + hpwl += self.VIA_COST + return hpwl + + def cost_to_target(self,source): + """ + Find the cheapest HPWL distance to any target point ignoring + blockages for A* search. + """ + cost = self.hpwl(source,self.target[0]) + for t in self.target: + cost = min(self.hpwl(source,t),cost) + return cost + + + def cost(self,path): + """ + The cost of the path is the length plus a penalty for the number + of vias. We assume that non-preferred direction is penalized. + """ + + # Ignore the source pin layer change, FIXME? + def pairwise(iterable): + "s -> (s0,s1), (s1,s2), (s2, s3), ..." + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + + plist = pairwise(path) + cost = 0 + for p0,p1 in plist: + if p0.z != p1.z: # via + cost += self.VIA_COST + elif p0.x != p1.x: # horizontal + cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST + elif p0.y != p1.y: # vertical + cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST + else: + debug.error("Non-changing direction!") + + return cost + + def get_inertia(self,p0,p1): + """ + Sets the direction based on the previous direction we came from. + """ + # direction (index) of movement + if p0.x==p1.x: + return 1 + elif p0.y==p1.y: + return 0 + else: + # z direction + return 2 + + + diff --git a/compiler/router/grid.py b/compiler/router/grid.py index d57be765..8eb063cf 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -6,16 +6,14 @@ from vector3d import vector3d from cell import cell import os -from heapq import heappush,heappop - class grid: - """A two layer routing map. Each cell can be blocked in the vertical + """ + A two layer routing map. Each cell can be blocked in the vertical or horizontal layer. - """ def __init__(self): - """ Create a routing map of width x height cells and 2 in the z-axis. """ + """ Initialize the map and define the costs. """ # costs are relative to a unit grid # non-preferred cost allows an off-direction jog of 1 grid @@ -23,17 +21,10 @@ class grid: self.VIA_COST = 2 self.NONPREFERRED_COST = 4 self.PREFERRED_COST = 1 - - # list of the source/target grid coordinates - self.source = [] - self.target = [] # let's leave the map sparse, cells are created on demand to reduce memory self.map={} - # priority queue for the maze routing - self.q = [] - def set_blocked(self,n): self.add_map(n) self.map[n].blocked=True @@ -42,30 +33,6 @@ class grid: self.add_map(n) return self.map[n].blocked - def set_source(self,n): - self.add_map(n) - self.map[n].source=True - self.source.append(n) - - def set_target(self,n): - self.add_map(n) - self.map[n].target=True - self.target.append(n) - - def reinit(self): - """ Reinitialize everything for a new route. """ - - self.reset_cells() - - # clear source and target pins - self.source=[] - self.target=[] - - # clear the queue - while len(self.q)>0: - heappop(self.q) - self.counter = 0 - def add_blockage_shape(self,ll,ur,z): debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) @@ -81,134 +48,6 @@ class grid: for n in block_list: self.set_blocked(n) - def add_source(self,track_list): - debug.info(2,"Adding source list={0}".format(str(track_list))) - for n in track_list: - if not self.is_blocked(n): - debug.info(3,"Adding source ={0}".format(str(n))) - self.set_source(n) - - def add_target(self,track_list): - debug.info(2,"Adding target list={0}".format(str(track_list))) - for n in track_list: - if not self.is_blocked(n): - self.set_target(n) - - def reset_cells(self): - """ - Reset the path and costs for all the grid cells. - """ - for p in self.map.values(): - p.reset() - - - def add_path(self,path): - """ - Mark the path in the routing grid for visualization - """ - self.path=path - for p in path: - self.map[p].path=True - - def route(self,detour_scale): - """ - This does the A* maze routing with preferred direction routing. - """ - - # We set a cost bound of the HPWL for run-time. This can be - # over-ridden if the route fails due to pruning a feasible solution. - cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST - - # Make sure the queue is empty if we run another route - while len(self.q)>0: - heappop(self.q) - - # Put the source items into the queue - self.init_queue() - cheapest_path = None - cheapest_cost = None - - # Keep expanding and adding to the priority queue until we are done - while len(self.q)>0: - # should we keep the path in the queue as well or just the final node? - (cost,count,path) = heappop(self.q) - debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost)) - debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path)) - - # expand the last element - neighbors = self.expand_dirs(path) - debug.info(3,"Neighbors: " + str(neighbors)) - - for n in neighbors: - # node is added to the map by the expand routine - newpath = path + [n] - # check if we hit the target and are done - if self.is_target(n): - return (newpath,self.cost(newpath)) - elif not self.map[n].visited: - # current path cost + predicted cost - current_cost = self.cost(newpath) - target_cost = self.cost_to_target(n) - predicted_cost = current_cost + target_cost - # only add the cost if it is less than our bound - if (predicted_cost < cost_bound): - if (self.map[n].min_cost==-1 or current_cost=0 and not self.is_blocked(down) and not down in path: - neighbors.append(down) - - - - return neighbors - def add_map(self,p): """ Add a point to the map if it doesn't exist. @@ -216,52 +55,13 @@ class grid: if p not in self.map.keys(): self.map[p]=cell() - def init_queue(self): - """ - Populate the queue with all the source pins with cost - to the target. Each item is a path of the grid cells. - We will use an A* search, so this cost must be pessimistic. - Cost so far will be the length of the path. - """ - debug.info(4,"Initializing queue.") - - # uniquify the source (and target while we are at it) - self.source = list(set(self.source)) - self.target = list(set(self.target)) - - # Counter is used to not require data comparison in Python 3.x - # Items will be returned in order they are added during cost ties - self.counter = 0 - for s in self.source: - cost = self.cost_to_target(s) - debug.info(1,"Init: cost=" + str(cost) + " " + str([s])) - heappush(self.q,(cost,self.counter,[s])) - self.counter+=1 - - - def hpwl(self, src, dest): + def add_path(self,path): """ - Return half perimeter wire length from point to another. - Either point can have positive or negative coordinates. - Include the via penalty if there is one. + Mark the path in the routing grid for visualization """ - hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x)) - hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y)) - hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z)) - if src.x!=dest.x or src.y!=dest.y: - hpwl += self.VIA_COST - return hpwl - - def cost_to_target(self,source): - """ - Find the cheapest HPWL distance to any target point ignoring - blockages for A* search. - """ - cost = self.hpwl(source,self.target[0]) - for t in self.target: - cost = min(self.hpwl(source,t),cost) - return cost - + self.path=path + for p in path: + self.map[p].path=True def cost(self,path): """ @@ -291,15 +91,6 @@ class grid: return cost - def get_inertia(self,p0,p1): - """ - Sets the direction based on the previous direction we came from. - """ - # direction (index) of movement - if p0.x==p1.x: - return 1 - elif p0.y==p1.y: - return 0 - else: - # z direction - return 2 + + + diff --git a/compiler/router/router.py b/compiler/router/router.py index 6375decd..b46acfd8 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -3,15 +3,16 @@ import tech from contact import contact import math import debug -import grid from pin_layout import pin_layout from vector import vector from vector3d import vector3d from globals import OPTS class router: - """A router class to read an obstruction map from a gds and plan a + """ + A router class to read an obstruction map from a gds and plan a route on a given layer. This is limited to two layer routes. + It populates blockages on a grid class. """ def __init__(self, gds_name): @@ -25,8 +26,7 @@ class router: self.reader.loadFromFile(gds_name) self.top_name = self.layout.rootStructureName - self.source_pins = [] - self.target_pins = [] + self.pins = {} # the list of all blockage shapes self.blockages = [] # all the paths we've routed so far (to supplement the blockages) @@ -75,17 +75,6 @@ class router: - def create_routing_grid(self): - """ - Create a routing grid that spans given area. Wires cannot exist outside region. - """ - # We will add a halo around the boundary - # of this many tracks - size = self.ur - self.ll - debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) - - self.rg = grid.grid() - def find_pin(self,pin): """ @@ -123,179 +112,12 @@ class router: Convert the routed path to blockages. Keep the other blockages unchanged. """ - self.source_pin_name = None - self.source_pins = [] - self.target_pin_name = None - self.target_pins = [] # DO NOT clear the blockages as these don't change + self.pins = {} + # DO NOT clear the blockages as these don't change self.rg.reinit() - def route(self, cell, layers, src, dest, detour_scale=5): - """ - Route a single source-destination net and return - the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route. - This is used to speed up the routing when there is not much detouring needed. - """ - self.cell = cell - - # Clear the pins if we have previously routed - if (hasattr(self,'rg')): - self.clear_pins() - else: - # Set up layers and track sizes - self.set_layers(layers) - # Creat a routing grid over the entire area - # FIXME: This could be created only over the routing region, - # but this is simplest for now. - self.create_routing_grid() - # This will get all shapes as blockages - self.find_blockages() - - # Get the pin shapes - self.get_source(src) - self.get_target(dest) - - # Now add the blockages (all shapes except the src/tgt pins) - self.add_blockages() - # Add blockages from previous paths - self.add_path_blockages() - - # Now add the src/tgt if they are not blocked by other shapes - self.add_source() - self.add_target() - - - # returns the path in tracks - (path,cost) = self.rg.route(detour_scale) - if path: - debug.info(1,"Found path: cost={0} ".format(cost)) - debug.info(2,str(path)) - self.add_route(path) - return True - else: - self.write_debug_gds() - # clean up so we can try a reroute - self.clear_pins() - - - return False - - def write_debug_gds(self): - """ - Write out a GDS file with the routing grid and search information annotated on it. - """ - # Only add the debug info to the gds file if we have any debugging on. - # This is because we may reroute a wire with detours and don't want the debug information. - if OPTS.debug_level==0: return - - self.add_router_info() - debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin_name,self.target_pin_name)) - self.cell.gds_write("debug_route.gds") - - def add_router_info(self): - """ - Write the routing grid and router cost, blockage, pins on - the boundary layer for debugging purposes. This can only be - called once or the labels will overlap. - """ - debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin_name,self.target_pin_name)) - grid_keys=self.rg.map.keys() - partial_track=vector(0,self.track_width/6.0) - for g in grid_keys: - shape = self.convert_full_track_to_shape(g) - self.cell.add_rect(layer="boundary", - offset=shape[0], - width=shape[1].x-shape[0].x, - height=shape[1].y-shape[0].y) - # These are the on grid pins - #rect = self.convert_track_to_pin(g) - #self.cell.add_rect(layer="boundary", - # offset=rect[0], - # width=rect[1].x-rect[0].x, - # height=rect[1].y-rect[0].y) - - t=self.rg.map[g].get_type() - - # midpoint offset - off=vector((shape[1].x+shape[0].x)/2, - (shape[1].y+shape[0].y)/2) - if g[2]==1: - # Upper layer is upper right label - type_off=off+partial_track - else: - # Lower layer is lower left label - type_off=off-partial_track - if t!=None: - self.cell.add_label(text=str(t), - layer="text", - offset=type_off) - self.cell.add_label(text="{0},{1}".format(g[0],g[1]), - layer="text", - offset=shape[0], - zoom=0.05) - - def add_route(self,path): - """ - Add the current wire route to the given design instance. - """ - debug.info(3,"Set path: " + str(path)) - - # Keep track of path for future blockages - self.paths.append(path) - - # This is marked for debug - self.rg.add_path(path) - - # For debugging... if the path failed to route. - if False or path==None: - self.write_debug_gds() - - if 'Xout_4_1' in [self.source_pin_name, self.target_pin_name]: - self.write_debug_gds() - - - # First, simplify the path for - #debug.info(1,str(self.path)) - contracted_path = self.contract_path(path) - debug.info(1,str(contracted_path)) - - # convert the path back to absolute units from tracks - abs_path = map(self.convert_point_to_units,contracted_path) - debug.info(1,str(abs_path)) - self.cell.add_route(self.layers,abs_path) - - def add_grid_pin(self,point,add_via=False): - """ - Create a rectangle at the grid 3D point that is 1/2 DRC smaller - than the routing grid on all sides. - """ - pin = self.convert_track_to_pin(point) - self.cell.add_rect(layer=self.layers[2*point.z], - offset=pin[0], - width=pin[1].x-pin[0].x, - height=pin[1].y-pin[0].y) - - if add_via: - # offset this by 1/2 the via size - c=contact(self.layers, (1, 1)) - via_offset = vector(-0.5*c.width,-0.5*c.height) - self.cell.add_via(self.layers,vector(point[0],point[1])+via_offset) - - - def create_steiner_routes(self,pins): - """ - Find a set of steiner points and then return the list of - point-to-point routes. - """ - pass - - def find_steiner_points(self,pins): - """ - Find the set of steiner points and return them. - """ - pass - def translate_coordinates(self, coord, mirr, angle, xyShift): """ Calculate coordinates after flip, rotate, and shift @@ -371,63 +193,12 @@ class router: self.rg.set_blocked(grid) - def get_source(self,pin_name): - """ - Gets the source pin shapes only. Doesn't add to grid. - """ - self.source_pin_name = pin_name - self.source_pins = self.find_pin(pin_name) - - def add_source(self): - """ - Mark the grids that are in the pin rectangle ranges to have the source property. - pin can be a location or a label. - """ - - found_pin = False - for pin in self.source_pins: - (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin) - if (len(pin_in_tracks)>0): - found_pin=True - debug.info(1,"Set source: " + str(self.source_pin_name) + " " + str(pin_in_tracks)) - self.rg.add_source(pin_in_tracks) - self.rg.add_blockage(blockage_in_tracks) - - if not found_pin: - self.write_debug_gds() - debug.check(found_pin,"Unable to find source pin on grid.") - - def get_target(self,pin_name): - """ - Gets the target pin shapes only. Doesn't add to grid. - """ - self.target_pin_name = pin_name - self.target_pins = self.find_pin(pin_name) - - def add_target(self): - """ - Mark the grids that are in the pin rectangle ranges to have the target property. - pin can be a location or a label. - """ - found_pin=False - for pin in self.target_pins: - (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin) - if (len(pin_in_tracks)>0): - found_pin=True - debug.info(1,"Set target: " + str(self.target_pin_name) + " " + str(pin_in_tracks)) - self.rg.add_target(pin_in_tracks) - self.rg.add_blockage(blockage_in_tracks) - - if not found_pin: - self.write_debug_gds() - debug.check(found_pin,"Unable to find target pin on grid.") - def add_blockages(self): """ Add the blockages except the pin shapes """ for blockage in self.blockages: - is_nonpin_blockage = True - # Skip source pin shapes - for pin in self.source_pins + self.target_pins: + # Skip pin shapes + all_pins = [x[0] for x in list(self.pins.values())] + for pin in all_pins: if blockage.overlaps(pin): break else: @@ -601,6 +372,35 @@ class router: return [ll,ur] + def get_pin(self,pin_name): + """ + Gets the pin shapes only. Doesn't add to grid. + """ + self.pins[pin_name] = self.find_pin(pin_name) + + def add_pin(self,pin_name,is_source=False): + """ + Mark the grids that are in the pin rectangle ranges to have the pin property. + pin can be a location or a label. + """ + + found_pin = False + for pin in self.pins[pin_name]: + (pin_in_tracks,blockage_in_tracks)=self.convert_pin_to_tracks(pin) + if (len(pin_in_tracks)>0): + found_pin=True + if is_source: + debug.info(1,"Set source: " + str(pin_name) + " " + str(pin_in_tracks)) + self.rg.add_source(pin_in_tracks) + else: + debug.info(1,"Set target: " + str(pin_name) + " " + str(pin_in_tracks)) + self.rg.add_target(pin_in_tracks) + self.rg.add_blockage(blockage_in_tracks) + + if not found_pin: + self.write_debug_gds() + debug.check(found_pin,"Unable to find pin on grid.") + # FIXME: This should be replaced with vector.snap_to_grid at some point def snap_to_grid(offset): diff --git a/compiler/router/signal_router.py b/compiler/router/signal_router.py new file mode 100644 index 00000000..7d06262e --- /dev/null +++ b/compiler/router/signal_router.py @@ -0,0 +1,216 @@ +import gdsMill +import tech +from contact import contact +import math +import debug +from pin_layout import pin_layout +from vector import vector +from vector3d import vector3d +from globals import OPTS +from router import router + +class signal_router(router): + """A router class to read an obstruction map from a gds and plan a + route on a given layer. This is limited to two layer routes. + """ + + def __init__(self, gds_name): + """Use the gds file for the blockages with the top module topName and + layers for the layers to route on + """ + router.__init__(self, gds_name) + + # all the paths we've routed so far (to supplement the blockages) + self.paths = [] + + + def create_routing_grid(self): + """ + Create a routing grid that spans given area. Wires cannot exist outside region. + """ + # We will add a halo around the boundary + # of this many tracks + size = self.ur - self.ll + debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) + + import astar_grid + self.rg = astar_grid.astar_grid() + + + def route(self, cell, layers, src, dest, detour_scale=5): + """ + Route a single source-destination net and return + the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route. + This is used to speed up the routing when there is not much detouring needed. + """ + self.cell = cell + + # Clear the pins if we have previously routed + if (hasattr(self,'rg')): + self.clear_pins() + else: + # Set up layers and track sizes + self.set_layers(layers) + # Creat a routing grid over the entire area + # FIXME: This could be created only over the routing region, + # but this is simplest for now. + self.create_routing_grid() + # This will get all shapes as blockages + self.find_blockages() + + # Get the pin shapes + self.get_pin(src) + self.get_pin(dest) + + # Now add the blockages (all shapes except the src/tgt pins) + self.add_blockages() + # Add blockages from previous paths + self.add_path_blockages() + + # Now add the src/tgt if they are not blocked by other shapes + self.add_pin(src,True) + self.add_pin(dest,False) + + + # returns the path in tracks + (path,cost) = self.rg.astar_route(detour_scale) + if path: + debug.info(1,"Found path: cost={0} ".format(cost)) + debug.info(2,str(path)) + self.add_route(path) + return True + else: + self.write_debug_gds() + # clean up so we can try a reroute + self.clear_pins() + + + return False + + + def add_route(self,path): + """ + Add the current wire route to the given design instance. + """ + debug.info(3,"Set path: " + str(path)) + + # Keep track of path for future blockages + self.paths.append(path) + + # This is marked for debug + self.rg.add_path(path) + + # For debugging... if the path failed to route. + if False or path==None: + self.write_debug_gds() + + + # First, simplify the path for + #debug.info(1,str(self.path)) + contracted_path = self.contract_path(path) + debug.info(1,str(contracted_path)) + + # convert the path back to absolute units from tracks + abs_path = map(self.convert_point_to_units,contracted_path) + debug.info(1,str(abs_path)) + self.cell.add_route(self.layers,abs_path) + + + def get_inertia(self,p0,p1): + """ + Sets the direction based on the previous direction we came from. + """ + # direction (index) of movement + if p0.x!=p1.x: + return 0 + elif p0.y!=p1.y: + return 1 + else: + # z direction + return 2 + + def contract_path(self,path): + """ + Remove intermediate points in a rectilinear path. + """ + newpath = [path[0]] + for i in range(1,len(path)-1): + prev_inertia=self.get_inertia(path[i-1],path[i]) + next_inertia=self.get_inertia(path[i],path[i+1]) + # if we switch directions, add the point, otherwise don't + if prev_inertia!=next_inertia: + newpath.append(path[i]) + + # always add the last path + newpath.append(path[-1]) + return newpath + + + def add_path_blockages(self): + """ + Go through all of the past paths and add them as blockages. + This is so we don't have to write/reload the GDS. + """ + for path in self.paths: + for grid in path: + self.rg.set_blocked(grid) + + + + + + def write_debug_gds(self): + """ + Write out a GDS file with the routing grid and search information annotated on it. + """ + # Only add the debug info to the gds file if we have any debugging on. + # This is because we may reroute a wire with detours and don't want the debug information. + if OPTS.debug_level==0: return + + self.add_router_info() + pin_names = list(self.pins.keys()) + debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin_name,self.target_pin_name)) + self.cell.gds_write("debug_route.gds") + + def add_router_info(self): + """ + Write the routing grid and router cost, blockage, pins on + the boundary layer for debugging purposes. This can only be + called once or the labels will overlap. + """ + debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin_name,self.target_pin_name)) + grid_keys=self.rg.map.keys() + partial_track=vector(0,self.track_width/6.0) + for g in grid_keys: + shape = self.convert_full_track_to_shape(g) + self.cell.add_rect(layer="boundary", + offset=shape[0], + width=shape[1].x-shape[0].x, + height=shape[1].y-shape[0].y) + # These are the on grid pins + #rect = self.convert_track_to_pin(g) + #self.cell.add_rect(layer="boundary", + # offset=rect[0], + # width=rect[1].x-rect[0].x, + # height=rect[1].y-rect[0].y) + + t=self.rg.map[g].get_type() + + # midpoint offset + off=vector((shape[1].x+shape[0].x)/2, + (shape[1].y+shape[0].y)/2) + if g[2]==1: + # Upper layer is upper right label + type_off=off+partial_track + else: + # Lower layer is lower left label + type_off=off-partial_track + if t!=None: + self.cell.add_label(text=str(t), + layer="text", + offset=type_off) + self.cell.add_label(text="{0},{1}".format(g[0],g[1]), + layer="text", + offset=shape[0], + zoom=0.05) + diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py new file mode 100644 index 00000000..b58e6e16 --- /dev/null +++ b/compiler/router/supply_router.py @@ -0,0 +1,140 @@ +import gdsMill +import tech +from contact import contact +import math +import debug +import grid +from pin_layout import pin_layout +from vector import vector +from vector3d import vector3d +from globals import OPTS +from router import router + +class supply_router(router): + """ + A router class to read an obstruction map from a gds and + routes a grid to connect the supply on the two layers. + """ + + def __init__(self, gds_name): + """Use the gds file for the blockages with the top module topName and + layers for the layers to route on + """ + + router.__init__(self, gds_name) + + self.pins = {} + + + def clear_pins(self): + """ + Convert the routed path to blockages. + Keep the other blockages unchanged. + """ + self.pins = {} + self.rg.reinit() + + + def route(self, cell, layers, vdd_name="vdd", gnd_name="gnd"): + """ + Route a single source-destination net and return + the simplified rectilinear path. + """ + self.cell = cell + self.pins[vdd_name] = [] + self.pins[gnd_name] = [] + + # Clear the pins if we have previously routed + if (hasattr(self,'rg')): + self.clear_pins() + else: + # Set up layers and track sizes + self.set_layers(layers) + # Creat a routing grid over the entire area + # FIXME: This could be created only over the routing region, + # but this is simplest for now. + self.create_routing_grid() + # This will get all shapes as blockages + self.find_blockages() + + # Get the pin shapes + self.get_pin(vdd_name) + self.get_pin(gnd_name) + + # Now add the blockages (all shapes except the src/tgt pins) + self.add_blockages() + # Add blockages from previous routes + self.add_path_blockages() + + # Now add the src/tgt if they are not blocked by other shapes + self.add_pin(vdd_name,True) + #self.add_pin() + + + # returns the path in tracks + (path,cost) = self.rg.route(detour_scale) + if path: + debug.info(1,"Found path: cost={0} ".format(cost)) + debug.info(2,str(path)) + self.add_route(path) + return True + else: + self.write_debug_gds() + # clean up so we can try a reroute + self.clear_pins() + + + return False + + + def add_route(self,path): + """ + Add the current wire route to the given design instance. + """ + debug.info(3,"Set path: " + str(path)) + + # Keep track of path for future blockages + self.paths.append(path) + + # This is marked for debug + self.rg.add_path(path) + + # For debugging... if the path failed to route. + if False or path==None: + self.write_debug_gds() + + # First, simplify the path for + #debug.info(1,str(self.path)) + contracted_path = self.contract_path(path) + debug.info(1,str(contracted_path)) + + # convert the path back to absolute units from tracks + abs_path = map(self.convert_point_to_units,contracted_path) + debug.info(1,str(abs_path)) + self.cell.add_route(self.layers,abs_path) + + + + + ########################## + # Gridded supply route functions + ########################## + def create_grid(self, ll, ur): + """ Create alternating vdd/gnd lines horizontally """ + + self.create_horizontal_grid() + self.create_vertical_grid() + + + def create_horizontal_grid(self): + """ Create alternating vdd/gnd lines horizontally """ + + pass + + def create_vertical_grid(self): + """ Create alternating vdd/gnd lines horizontally """ + pass + + def route(self): + #self.create_grid() + pass diff --git a/compiler/router/tests/01_no_blockages_test.py b/compiler/router/tests/01_no_blockages_test.py index 60cc6583..c7344a64 100755 --- a/compiler/router/tests/01_no_blockages_test.py +++ b/compiler/router/tests/01_no_blockages_test.py @@ -20,7 +20,7 @@ class no_blockages_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/02_blockages_test.py b/compiler/router/tests/02_blockages_test.py index 7d46f02b..2e85b1c2 100755 --- a/compiler/router/tests/02_blockages_test.py +++ b/compiler/router/tests/02_blockages_test.py @@ -20,7 +20,7 @@ class blockages_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/03_same_layer_pins_test.py b/compiler/router/tests/03_same_layer_pins_test.py index 39a72990..98ce3a2a 100755 --- a/compiler/router/tests/03_same_layer_pins_test.py +++ b/compiler/router/tests/03_same_layer_pins_test.py @@ -19,7 +19,7 @@ class same_layer_pins_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/04_diff_layer_pins_test.py b/compiler/router/tests/04_diff_layer_pins_test.py index 0390a6b4..cbc21470 100755 --- a/compiler/router/tests/04_diff_layer_pins_test.py +++ b/compiler/router/tests/04_diff_layer_pins_test.py @@ -21,7 +21,7 @@ class diff_layer_pins_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/05_two_nets_test.py b/compiler/router/tests/05_two_nets_test.py index e19f7d49..166292d0 100755 --- a/compiler/router/tests/05_two_nets_test.py +++ b/compiler/router/tests/05_two_nets_test.py @@ -21,7 +21,7 @@ class two_nets_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/06_pin_location_test.py b/compiler/router/tests/06_pin_location_test.py index c157dcb8..e67fed53 100755 --- a/compiler/router/tests/06_pin_location_test.py +++ b/compiler/router/tests/06_pin_location_test.py @@ -20,7 +20,7 @@ class pin_location_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/07_big_test.py b/compiler/router/tests/07_big_test.py index e71e0bf4..5f844ec5 100755 --- a/compiler/router/tests/07_big_test.py +++ b/compiler/router/tests/07_big_test.py @@ -20,7 +20,7 @@ class big_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/08_expand_region_test.py b/compiler/router/tests/08_expand_region_test.py index 47941b92..96edab57 100755 --- a/compiler/router/tests/08_expand_region_test.py +++ b/compiler/router/tests/08_expand_region_test.py @@ -20,7 +20,7 @@ class expand_region_test(openram_test): globals.init_openram("config_{0}".format(OPTS.tech_name)) from gds_cell import gds_cell from design import design - from router import router + from signal_router import signal_router as router class routing(design, openram_test): """ diff --git a/compiler/router/tests/10_supply_grid_test.py b/compiler/router/tests/10_supply_grid_test.py new file mode 100755 index 00000000..7d196c6f --- /dev/null +++ b/compiler/router/tests/10_supply_grid_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug + +OPTS = globals.OPTS + +class no_blockages_test(openram_test): + """ + Simplest two pin route test with no blockages. + """ + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + from gds_cell import gds_cell + from design import design + from supply_router import supply_router as router + + class routing(design, openram_test): + """ + A generic GDS design that we can route on. + """ + def __init__(self, name): + design.__init__(self, "top") + + # Instantiate a GDS cell with the design + gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name) + cell = gds_cell(name, gds_file) + self.add_inst(name=name, + mod=cell, + offset=[0,0]) + self.connect_inst([]) + + r=router(gds_file) + layer_stack =("metal3","via1","metal2") + self.assertTrue(r.route(self,layer_stack)) + + r=routing("10_supply_grid_test_{0}".format(OPTS.tech_name)) + self.local_drc_check(r) + + # fails if there are any DRC errors on any cells + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() From 0dbc88dab2fb3f0db9b6a421743bb989622c9eb3 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 23 Aug 2018 13:13:07 -0700 Subject: [PATCH 6/7] Rename _new cell back to original for LVS comparison script --- .../tests/10_supply_grid_test_scn3me_subm.gds | Bin 0 -> 338592 bytes compiler/verify/magic.py | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 compiler/router/tests/10_supply_grid_test_scn3me_subm.gds diff --git a/compiler/router/tests/10_supply_grid_test_scn3me_subm.gds b/compiler/router/tests/10_supply_grid_test_scn3me_subm.gds new file mode 100644 index 0000000000000000000000000000000000000000..1c4742dd8a4c1845aaf338df79d0a1afd3b3c001 GIT binary patch literal 338592 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 c({Zuar*3Qq8872E9nXC7{@jfv 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 A(EtDd literal 0 HcmV?d00001 diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 0f025b2b..8c8118e4 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -90,6 +90,8 @@ def write_magic_script(cell_name, gds_name, extract=False): # (e.g. with routes) f.write("flatten {}_new\n".format(cell_name)) f.write("load {}_new\n".format(cell_name)) + f.write("cellname rename {0}_new {0}\n".format(cell_name)) + f.write("load {}\n".format(cell_name)) f.write("writeall force\n") f.write("drc check\n") f.write("drc catchup\n") From 95a86885061c78575e201476bcff45520d0b958b Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Tue, 28 Aug 2018 10:41:19 -0700 Subject: [PATCH 7/7] Rewrite blockage routines in router. Clean up GdsMill code. --- compiler/base/hierarchy_layout.py | 21 ++- compiler/gdsMill/gdsMill/gds2reader.py | 6 +- compiler/gdsMill/gdsMill/vlsiLayout.py | 109 ++++++++---- compiler/modules/delay_chain.py | 4 +- compiler/modules/replica_bitline.py | 2 +- compiler/router/astar_grid.py | 2 +- compiler/router/grid.py | 3 + compiler/router/router.py | 159 +++++++++++++----- compiler/router/signal_router.py | 62 +------ compiler/router/supply_router.py | 42 +++-- compiler/router/tests/10_supply_grid_test.py | 2 +- .../tests/10_supply_grid_test_scn3me_subm.gds | Bin 338592 -> 338352 bytes compiler/tests/19_single_bank_test.py | 2 +- compiler/tests/20_sram_1bank_test.py | 18 +- 14 files changed, 256 insertions(+), 176 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 672f4d93..211d5816 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -261,7 +261,7 @@ class layout(lef.lef): width=drc["minwidth_{0}".format(layer)] if height==None: height=drc["minwidth_{0}".format(layer)] - + new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer) try: @@ -400,13 +400,14 @@ class layout(lef.lef): elif rotate==90: corrected_offset = offset + vector(0.5*height,-0.5*width) elif rotate==180: - corrected_offset = offset + vector(-0.5*width,0.5*height) + corrected_offset = offset + vector(0.5*width,0.5*height) elif rotate==270: corrected_offset = offset + vector(-0.5*height,0.5*width) else: debug.error("Invalid rotation argument.",-1) + #print(rotate,offset,"->",corrected_offset) self.add_mod(via) inst=self.add_inst(name=via.name, mod=via, @@ -848,19 +849,21 @@ class layout(lef.lef): width=xmax-xmin, height=ymax-ymin) - def add_power_pin(self, name, loc, rotate=True): + def add_power_pin(self, name, loc, rotate=90): """ - Add a single power pin from M3 own to M1 + Add a single power pin from M3 own to M1 at the given center location """ self.add_via_center(layers=("metal1", "via1", "metal2"), offset=loc, - rotate=90 if rotate else 0) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=loc, - rotate=90 if rotate else 0) + rotate=rotate) + via=self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=loc, + rotate=rotate) self.add_layout_pin_rect_center(text=name, layer="metal3", - offset=loc) + offset=loc, + width=via.width, + height=via.height) def add_power_ring(self, bbox): """ diff --git a/compiler/gdsMill/gdsMill/gds2reader.py b/compiler/gdsMill/gdsMill/gds2reader.py index a162361e..3d63cf53 100644 --- a/compiler/gdsMill/gdsMill/gds2reader.py +++ b/compiler/gdsMill/gdsMill/gds2reader.py @@ -294,7 +294,7 @@ class Gds2reader: mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy rotateFlag = bool(transFlags&0x0002) magnifyFlag = bool(transFlags&0x0004) - thisSref.transFlags=(mirrorFlag,rotateFlag,magnifyFlag) + thisSref.transFlags=[mirrorFlag,rotateFlag,magnifyFlag] if(self.debugToTerminal==1): print("\t\t\tMirror X:"+str(mirrorFlag)) print( "\t\t\tRotate:"+str(rotateFlag)) @@ -345,7 +345,7 @@ class Gds2reader: mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy rotateFlag = bool(transFlags&0x0002) magnifyFlag = bool(transFlags&0x0004) - thisAref.transFlags=(mirrorFlag,rotateFlag,magnifyFlag) + thisAref.transFlags=[mirrorFlag,rotateFlag,magnifyFlag] if(self.debugToTerminal==1): print("\t\t\tMirror X:"+str(mirrorFlag)) print("\t\t\tRotate:"+str(rotateFlag)) @@ -408,7 +408,7 @@ class Gds2reader: mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy rotateFlag = bool(transFlags&0x0002) magnifyFlag = bool(transFlags&0x0004) - thisText.transFlags=(mirrorFlag,rotateFlag,magnifyFlag) + thisText.transFlags=[mirrorFlag,rotateFlag,magnifyFlag] if(self.debugToTerminal==1): print("\t\t\tMirror X:"+str(mirrorFlag)) print("\t\t\tRotate:"+str(rotateFlag)) diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index 076948bb..4b498bb8 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -152,7 +152,7 @@ class VlsiLayout: self.rootStructureName = structureNames[0] def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None, - transformPath = [], rotateAngle = 0, transFlags = (0,0,0), coordinates = (0,0)): + transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)): #since this is a recursive function, must deal with the default #parameters explicitly if startingStructureName == None: @@ -160,11 +160,11 @@ class VlsiLayout: #set up the rotation matrix if(rotateAngle == None or rotateAngle == ""): - rotateAngle = 0 + angle = 0 else: - rotateAngle = math.radians(float(rotateAngle)) - mRotate = matrix([[math.cos(rotateAngle),-math.sin(rotateAngle),0.0], - [math.sin(rotateAngle),math.cos(rotateAngle),0.0], + angle = math.radians(float(rotateAngle)) + mRotate = matrix([[math.cos(angle),-math.sin(angle),0.0], + [math.sin(angle),math.cos(angle),0.0], [0.0,0.0,1.0]]) #set up the translation matrix translateX = float(coordinates[0]) @@ -190,15 +190,12 @@ class VlsiLayout: #if not, return back to the caller (caller can be this function) for sref in self.structures[startingStructureName].srefs: #here, we are going to modify the sref coordinates based on the parent objects rotation -# if (sref.sName.count("via") == 0): self.traverseTheHierarchy(startingStructureName = sref.sName, delegateFunction = delegateFunction, transformPath = transformPath, rotateAngle = sref.rotateAngle, transFlags = sref.transFlags, coordinates = sref.coordinates) -# else: -# print("WARNING: via encountered, ignoring:", sref.sName) #MUST HANDLE AREFs HERE AS WELL #when we return, drop the last transform from the transformPath del transformPath[-1] @@ -225,8 +222,8 @@ class VlsiLayout: uVector = transform[0] * uVector #rotate vVector = transform[0] * vVector #rotate origin = transform[1] * origin #scale - uVector = transform[1] * uVector #rotate - vVector = transform[1] * vVector #rotate + uVector = transform[1] * uVector #scale + vVector = transform[1] * vVector #scale origin = transform[2] * origin #translate #we don't need to do a translation on the basis vectors self.xyTree+=[(startingStructureName,origin,uVector,vVector)] #populate the xyTree with each @@ -307,18 +304,9 @@ class VlsiLayout: for layerNumber in layoutToAdd.layerNumbersInUse: if layerNumber not in self.layerNumbersInUse: self.layerNumbersInUse += [layerNumber] - #Also, check if the user units / microns is the same as this Layout - #if (layoutToAdd.units != self.units): - #print("WARNING: VlsiLayout: Units from design to be added do not match target Layout") # if debug: print("DEBUG: vlsilayout: Using %d layers") - # If we can't find the structure, error - #if StructureFound == False: - #print("ERROR: vlsiLayout.addInstance: [%s] Name not found in local structures, "%(nameOfLayout)) - #return #FIXME: remove! - #exit(1) - #add a reference to the new layout structure in this layout's root layoutToAddSref = GdsSref() @@ -326,24 +314,27 @@ class VlsiLayout: layoutToAddSref.coordinates = offsetInLayoutUnits if mirror or rotate: - ########flags = (mirror around x-axis, absolute rotation, absolute magnification) - layoutToAddSref.transFlags = (False,False,False) - #Below angles are angular angles(relative), not absolute + # transFlags = (mirror around x-axis, magnification, rotation) + # If magnification or rotation is true, it is the flags are then + # followed by an amount in the record if mirror=="R90": rotate = 90.0 if mirror=="R180": rotate = 180.0 if mirror=="R270": rotate = 270.0 + + layoutToAddSref.transFlags = [0,0,0] if rotate: + layoutToAddSref.transFlags = [0,0,1] layoutToAddSref.rotateAngle = rotate if mirror == "x" or mirror == "MX": - layoutToAddSref.transFlags = (True,False,False) + layoutToAddSref.transFlags = [1,0,0] if mirror == "y" or mirror == "MY": #NOTE: "MY" option will override specified rotate angle - layoutToAddSref.transFlags = (True,False,False) + layoutToAddSref.transFlags = [1,0,1] layoutToAddSref.rotateAngle = 180.0 if mirror == "xy" or mirror == "XY": #NOTE: "XY" option will override specified rotate angle - layoutToAddSref.transFlags = (False,False,False) + layoutToAddSref.transFlags = [0,0,1] layoutToAddSref.rotateAngle = 180.0 #add the sref to the root structure @@ -410,14 +401,14 @@ class VlsiLayout: textToAdd.purposeLayer = purposeNumber textToAdd.dataType = 0 textToAdd.coordinates = [offsetInLayoutUnits] + textToAdd.transFlags = [0,0,0] if(len(text)%2 == 1): - #pad with a zero text = text + '\x00' textToAdd.textString = text - textToAdd.transFlags = (False,False,True) + textToAdd.transFlags[1] = 1 textToAdd.magFactor = magnification if rotate: - textToAdd.transFlags = (False,True,True) + textToAdd.transFlags[2] = 1 textToAdd.rotateAngle = rotate #add the sref to the root structure self.structures[self.rootStructureName].texts+=[textToAdd] @@ -620,10 +611,6 @@ class VlsiLayout: StructureOrigin=[Structure[1][0],Structure[1][1]] StructureuVector=[Structure[2][0],Structure[2][1],Structure[2][2]] StructurevVector=[Structure[3][0],Structure[3][1],Structure[3][2]] - #debug.info(debug_level,"Checking Structure: "+str(StructureName)) - #debug.info(debug_level,"-Structure Structure Origin:"+str(StructureOrigin)) - #debug.info(debug_level,"-Structure direction: uVector["+str(StructureuVector)+"]") - #debug.info(debug_level,"-Structure direction: vVector["+str(StructurevVector)+"]") for boundary in self.structures[str(StructureName)].boundaries: left_bottom=boundary.coordinates[0] @@ -808,6 +795,63 @@ class VlsiLayout: return boundaries + + def getAllShapesInStructureList(self,layer): + """ + Return all pin shapes on a given layer. + """ + boundaries = [] + for TreeUnit in self.xyTree: + #print(TreeUnit[0]) + boundaries += self.getShapesInStructure(layer,TreeUnit) + + # Remove duplicates without defining a hash + # (could be sped up by creating hash and using list(set()) + new_boundaries = [] + for boundary in boundaries: + if boundary not in new_boundaries: + new_boundaries.append(boundary) + + # Convert to user units + boundaries = [] + for boundary in new_boundaries: + boundaries.append([boundary[0]*self.units[0],boundary[1]*self.units[0], + boundary[2]*self.units[0],boundary[3]*self.units[0]]) + + return boundaries + + + def getShapesInStructure(self,layer,structure): + """ + Go through all the shapes in a structure and return the list of shapes. + """ + + # check if this is a rectangle + structureName=structure[0] + structureOrigin=[structure[1][0],structure[1][1]] + structureuVector=[structure[2][0],structure[2][1],structure[2][2]] + structurevVector=[structure[3][0],structure[3][1],structure[3][2]] + + boundaries = [] + + for boundary in self.structures[str(structureName)].boundaries: + # Pin enclosures only work on rectangular pins so ignore any non rectangle + # This may report not finding pins, but the user should fix this by adding a rectangle. + if len(boundary.coordinates)!=5: + continue + if layer==boundary.drawingLayer: + left_bottom=boundary.coordinates[0] + right_top=boundary.coordinates[2] + # Rectangle is [leftx, bottomy, rightx, topy]. + boundaryRect=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]] + boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector) + boundaryRect=[boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(), + boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item()] + + boundaries.append(boundaryRect) + + return boundaries + def transformRectangle(self,originalRectangle,uVector,vVector): """ Transforms the four coordinates of a rectangle in space @@ -849,6 +893,7 @@ class VlsiLayout: else: return False + def boundaryArea(A): """ Returns boundary area for sorting. diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index 18b5592d..c15afce0 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -170,7 +170,7 @@ class delay_chain(design.design): continue for pin_name in ["vdd", "gnd"]: pin = load.get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc()) + self.add_power_pin(pin_name, pin.rc(),rotate=0) else: # We have an even number of rows, so need to get the last gnd rail inv = self.driver_inst_list[-1] @@ -179,7 +179,7 @@ class delay_chain(design.design): continue pin_name = "gnd" pin = load.get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc()) + self.add_power_pin(pin_name, pin.rc(),rotate=0) # input is A pin of first inverter diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index 941304e8..0576bdd3 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -171,7 +171,7 @@ class replica_bitline(design.design): # Replica bitcell needs to be routed up to M3 pin=self.rbc_inst.get_pin("vdd") # Don't rotate this via to vit in FreePDK45 - self.add_power_pin("vdd", pin.center(), False) + self.add_power_pin("vdd", pin.center(), rotate=0) for pin in self.rbc_inst.get_pins("gnd"): self.add_power_pin("gnd", pin.center()) diff --git a/compiler/router/astar_grid.py b/compiler/router/astar_grid.py index 5715663a..65d9e245 100644 --- a/compiler/router/astar_grid.py +++ b/compiler/router/astar_grid.py @@ -89,7 +89,7 @@ class astar_grid(grid.grid): self.counter+=1 - def astar_route(self,detour_scale): + def route(self,detour_scale): """ This does the A* maze routing with preferred direction routing. """ diff --git a/compiler/router/grid.py b/compiler/router/grid.py index 8eb063cf..5f60da4e 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -36,6 +36,9 @@ class grid: def add_blockage_shape(self,ll,ur,z): debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + if ll[0]<42 and ll[0]>38 and ll[1]<3 and ll[1]>0: + debug.info(0,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + block_list = [] for x in range(int(ll[0]),int(ur[0])+1): for y in range(int(ll[1]),int(ur[1])+1): diff --git a/compiler/router/router.py b/compiler/router/router.py index b46acfd8..40e93df1 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -27,8 +27,7 @@ class router: self.top_name = self.layout.rootStructureName self.pins = {} - # the list of all blockage shapes - self.blockages = [] + self.blockages=[] # all the paths we've routed so far (to supplement the blockages) self.paths = [] @@ -101,9 +100,9 @@ class router: This doesn't consider whether the obstacles will be pins or not. They get reset later if they are not actually a blockage. """ - for layer in self.layers: - self.get_blockages(self.top_name) - + #for layer in [self.vert_layer_number,self.horiz_layer_number]: + # self.get_blockages(layer) + self.get_blockages(self.horiz_layer_number) def clear_pins(self): """ @@ -207,38 +206,50 @@ class router: self.rg.add_blockage_shape(ll,ur,zlayer) - def get_blockages(self, sref, mirr = 1, angle = math.radians(float(0)), xyShift = (0, 0)): + def get_blockages(self, layer_num): """ Recursive find boundaries as blockages to the routing grid. - Recurses for each Structure in GDS. """ - for boundary in self.layout.structures[sref].boundaries: - coord_trans = self.translate_coordinates(boundary.coordinates, mirr, angle, xyShift) - shape_coords = self.min_max_coord(coord_trans) - shape = self.convert_shape_to_units(shape_coords) - # only consider the two layers that we are routing on - if boundary.drawingLayer in [self.vert_layer_number,self.horiz_layer_number]: - # store the blockages as pin layouts so they are easy to compare etc. - self.blockages.append(pin_layout("blockage",shape,boundary.drawingLayer)) + shapes = self.layout.getAllShapesInStructureList(layer_num) + + for boundary in shapes: + rect = [vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])] + new_pin = pin_layout("blockage",rect,layer_num) + self.blockages.append(new_pin) + + + # for boundary in self.layout.structures[sref].boundaries: + # coord_trans = self.translate_coordinates(boundary.coordinates, mirr, angle, xyShift) + # shape_coords = self.min_max_coord(coord_trans) + # shape = self.convert_shape_to_units(shape_coords) + + # # only consider the two layers that we are routing on + # if boundary.drawingLayer in [self.vert_layer_number,self.horiz_layer_number]: + # # store the blockages as pin layouts so they are easy to compare etc. + # new_pin = pin_layout("blockage",shape,boundary.drawingLayer) + # # avoid repeated blockage pins + # if new_pin not in self.blockages: + # self.blockages.append(new_pin) - # recurse given the mirror, angle, etc. - for cur_sref in self.layout.structures[sref].srefs: - sMirr = 1 - if cur_sref.transFlags[0] == True: - sMirr = -1 - sAngle = math.radians(float(0)) - if cur_sref.rotateAngle: - sAngle = math.radians(float(cur_sref.rotateAngle)) - sAngle += angle - x = cur_sref.coordinates[0] - y = cur_sref.coordinates[1] - newX = (x)*math.cos(angle) - mirr*(y)*math.sin(angle) + xyShift[0] - newY = (x)*math.sin(angle) + mirr*(y)*math.cos(angle) + xyShift[1] - sxyShift = (newX, newY) + # # recurse given the mirror, angle, etc. + # for cur_sref in self.layout.structures[sref].srefs: + # sMirr = 1 + # if cur_sref.transFlags[0] == True: + # sMirr = -1 + # sAngle = math.radians(float(0)) + # if cur_sref.rotateAngle: + # sAngle = math.radians(float(cur_sref.rotateAngle)) + # sAngle += angle + # x = cur_sref.coordinates[0] + # y = cur_sref.coordinates[1] + # newX = (x)*math.cos(angle) - mirr*(y)*math.sin(angle) + xyShift[0] + # newY = (x)*math.sin(angle) + mirr*(y)*math.cos(angle) + xyShift[1] + # sxyShift = (newX, newY) - self.get_blockages(cur_sref.sName, sMirr, sAngle, sxyShift) + # self.get_blockages(cur_sref.sName, sMirr, sAngle, sxyShift) + def convert_point_to_units(self,p): """ @@ -248,21 +259,25 @@ class router: pt=pt.scale(self.track_widths[0],self.track_widths[1],1) return pt - def convert_blockage_to_tracks(self,shape,round_bigger=False): + def convert_blockage_to_tracks(self,shape): """ Convert a rectangular blockage shape into track units. """ - [ll,ur] = shape - ll = snap_to_grid(ll) - ur = snap_to_grid(ur) + (ll,ur) = shape + # ll = snap_to_grid(ll) + # ur = snap_to_grid(ur) # to scale coordinates to tracks - #debug.info(1,"Converting [ {0} , {1} ]".format(ll,ur)) + debug.info(3,"Converting [ {0} , {1} ]".format(ll,ur)) + old_ll = ll + old_ur = ur ll=ll.scale(self.track_factor) ur=ur.scale(self.track_factor) - ll = ll.floor() if round_bigger else ll.round() - ur = ur.ceil() if round_bigger else ur.round() - #debug.info(1,"Converted [ {0} , {1} ]".format(ll,ur)) + ll = ll.floor() + ur = ur.ceil() + if ll[0]<45 and ll[0]>35 and ll[1]<10 and ll[1]>0: + debug.info(0,"Converting [ {0} , {1} ]".format(old_ll,old_ur)) + debug.info(0,"Converted [ {0} , {1} ]".format(ll,ur)) return [ll,ur] def convert_pin_to_tracks(self, pin): @@ -271,9 +286,9 @@ class router: If no on-grid pins are found, it searches for the nearest off-grid pin(s). If a pin has insufficent overlap, it returns the blockage list to avoid it. """ - [ll,ur] = pin.rect - ll = snap_to_grid(ll) - ur = snap_to_grid(ur) + (ll,ur) = pin.rect + #ll = snap_to_grid(ll) + #ur = snap_to_grid(ur) #debug.info(1,"Converting [ {0} , {1} ]".format(ll,ur)) @@ -401,6 +416,68 @@ class router: self.write_debug_gds() debug.check(found_pin,"Unable to find pin on grid.") + def write_debug_gds(self): + """ + Write out a GDS file with the routing grid and search information annotated on it. + """ + # Only add the debug info to the gds file if we have any debugging on. + # This is because we may reroute a wire with detours and don't want the debug information. + if OPTS.debug_level==0: return + + self.add_router_info() + debug.error("Writing debug_route.gds") + self.cell.gds_write("debug_route.gds") + + def add_router_info(self): + """ + Write the routing grid and router cost, blockage, pins on + the boundary layer for debugging purposes. This can only be + called once or the labels will overlap. + """ + debug.info(0,"Adding router info") + grid_keys=self.rg.map.keys() + partial_track=vector(0,self.track_width/6.0) + for g in grid_keys: + continue # for now... + shape = self.convert_full_track_to_shape(g) + self.cell.add_rect(layer="boundary", + offset=shape[0], + width=shape[1].x-shape[0].x, + height=shape[1].y-shape[0].y) + # These are the on grid pins + #rect = self.convert_track_to_pin(g) + #self.cell.add_rect(layer="boundary", + # offset=rect[0], + # width=rect[1].x-rect[0].x, + # height=rect[1].y-rect[0].y) + + t=self.rg.map[g].get_type() + + # midpoint offset + off=vector((shape[1].x+shape[0].x)/2, + (shape[1].y+shape[0].y)/2) + if g[2]==1: + # Upper layer is upper right label + type_off=off+partial_track + else: + # Lower layer is lower left label + type_off=off-partial_track + if t!=None: + self.cell.add_label(text=str(t), + layer="text", + offset=type_off) + self.cell.add_label(text="{0},{1}".format(g[0],g[1]), + layer="text", + offset=shape[0], + zoom=0.05) + + for blockage in self.blockages: + self.cell.add_rect(layer="boundary", + offset=blockage.ll(), + width=blockage.width(), + height=blockage.height()) + + # FIXME: This should be replaced with vector.snap_to_grid at some point def snap_to_grid(offset): diff --git a/compiler/router/signal_router.py b/compiler/router/signal_router.py index 7d06262e..3358d75f 100644 --- a/compiler/router/signal_router.py +++ b/compiler/router/signal_router.py @@ -26,7 +26,7 @@ class signal_router(router): def create_routing_grid(self): """ - Create a routing grid that spans given area. Wires cannot exist outside region. + Create a sprase routing grid with A* expansion functions. """ # We will add a halo around the boundary # of this many tracks @@ -44,6 +44,8 @@ class signal_router(router): This is used to speed up the routing when there is not much detouring needed. """ self.cell = cell + self.source_pin_name = src + self.target_pin_name = dest # Clear the pins if we have previously routed if (hasattr(self,'rg')): @@ -73,7 +75,7 @@ class signal_router(router): # returns the path in tracks - (path,cost) = self.rg.astar_route(detour_scale) + (path,cost) = self.rg.route(detour_scale) if path: debug.info(1,"Found path: cost={0} ".format(cost)) debug.info(2,str(path)) @@ -158,59 +160,3 @@ class signal_router(router): - - def write_debug_gds(self): - """ - Write out a GDS file with the routing grid and search information annotated on it. - """ - # Only add the debug info to the gds file if we have any debugging on. - # This is because we may reroute a wire with detours and don't want the debug information. - if OPTS.debug_level==0: return - - self.add_router_info() - pin_names = list(self.pins.keys()) - debug.error("Writing debug_route.gds from {0} to {1}".format(self.source_pin_name,self.target_pin_name)) - self.cell.gds_write("debug_route.gds") - - def add_router_info(self): - """ - Write the routing grid and router cost, blockage, pins on - the boundary layer for debugging purposes. This can only be - called once or the labels will overlap. - """ - debug.info(0,"Adding router info for {0} to {1}".format(self.source_pin_name,self.target_pin_name)) - grid_keys=self.rg.map.keys() - partial_track=vector(0,self.track_width/6.0) - for g in grid_keys: - shape = self.convert_full_track_to_shape(g) - self.cell.add_rect(layer="boundary", - offset=shape[0], - width=shape[1].x-shape[0].x, - height=shape[1].y-shape[0].y) - # These are the on grid pins - #rect = self.convert_track_to_pin(g) - #self.cell.add_rect(layer="boundary", - # offset=rect[0], - # width=rect[1].x-rect[0].x, - # height=rect[1].y-rect[0].y) - - t=self.rg.map[g].get_type() - - # midpoint offset - off=vector((shape[1].x+shape[0].x)/2, - (shape[1].y+shape[0].y)/2) - if g[2]==1: - # Upper layer is upper right label - type_off=off+partial_track - else: - # Lower layer is lower left label - type_off=off-partial_track - if t!=None: - self.cell.add_label(text=str(t), - layer="text", - offset=type_off) - self.cell.add_label(text="{0},{1}".format(g[0],g[1]), - layer="text", - offset=shape[0], - zoom=0.05) - diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index b58e6e16..ff505a78 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -66,24 +66,22 @@ class supply_router(router): # Add blockages from previous routes self.add_path_blockages() - # Now add the src/tgt if they are not blocked by other shapes - self.add_pin(vdd_name,True) - #self.add_pin() - + # source pin will be a specific layout pin + # target pin will be the rails only # returns the path in tracks - (path,cost) = self.rg.route(detour_scale) - if path: - debug.info(1,"Found path: cost={0} ".format(cost)) - debug.info(2,str(path)) - self.add_route(path) - return True - else: - self.write_debug_gds() - # clean up so we can try a reroute - self.clear_pins() + # (path,cost) = self.rg.route(detour_scale) + # if path: + # debug.info(1,"Found path: cost={0} ".format(cost)) + # debug.info(2,str(path)) + # self.add_route(path) + # return True + # else: + # self.write_debug_gds() + # # clean up so we can try a reroute + # self.clear_pins() - + self.write_debug_gds() return False @@ -114,6 +112,17 @@ class supply_router(router): self.cell.add_route(self.layers,abs_path) + def create_routing_grid(self): + """ + Create a sprase routing grid with A* expansion functions. + """ + # We will add a halo around the boundary + # of this many tracks + size = self.ur - self.ll + debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) + + import supply_grid + self.rg = supply_grid.supply_grid() ########################## @@ -135,6 +144,3 @@ class supply_router(router): """ Create alternating vdd/gnd lines horizontally """ pass - def route(self): - #self.create_grid() - pass diff --git a/compiler/router/tests/10_supply_grid_test.py b/compiler/router/tests/10_supply_grid_test.py index 7d196c6f..91c892b0 100755 --- a/compiler/router/tests/10_supply_grid_test.py +++ b/compiler/router/tests/10_supply_grid_test.py @@ -38,7 +38,7 @@ class no_blockages_test(openram_test): self.connect_inst([]) r=router(gds_file) - layer_stack =("metal3","via1","metal2") + layer_stack =("metal3","via2","metal2") self.assertTrue(r.route(self,layer_stack)) r=routing("10_supply_grid_test_{0}".format(OPTS.tech_name)) diff --git a/compiler/router/tests/10_supply_grid_test_scn3me_subm.gds b/compiler/router/tests/10_supply_grid_test_scn3me_subm.gds index 1c4742dd8a4c1845aaf338df79d0a1afd3b3c001..7cfbc0cb0365e8fcdf249f979680d92aec3ac0a3 100644 GIT binary patch delta 23115 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 T9PN;!3OQ=k4894?e9iwKV(x}0 delta 23343 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 W{XA~%=2ord@Lk&+zDV;>%l`+$zZV(+ diff --git a/compiler/tests/19_single_bank_test.py b/compiler/tests/19_single_bank_test.py index 96291201..25567e82 100755 --- a/compiler/tests/19_single_bank_test.py +++ b/compiler/tests/19_single_bank_test.py @@ -20,7 +20,7 @@ class single_bank_test(openram_test): debug.info(1, "No column mux") a = bank(word_size=4, num_words=16, words_per_row=1, num_banks=1, name="bank1_single") self.local_check(a) - + debug.info(1, "Two way column mux") a = bank(word_size=4, num_words=32, words_per_row=2, num_banks=1, name="bank2_single") self.local_check(a) diff --git a/compiler/tests/20_sram_1bank_test.py b/compiler/tests/20_sram_1bank_test.py index 0ee98c60..7079b715 100755 --- a/compiler/tests/20_sram_1bank_test.py +++ b/compiler/tests/20_sram_1bank_test.py @@ -21,17 +21,17 @@ class sram_1bank_test(openram_test): a = sram(word_size=4, num_words=16, num_banks=1, name="sram1") self.local_check(a, final_verification=True) - debug.info(1, "Single bank two way column mux with control logic") - a = sram(word_size=4, num_words=32, num_banks=1, name="sram2") - self.local_check(a, final_verification=True) + # debug.info(1, "Single bank two way column mux with control logic") + # a = sram(word_size=4, num_words=32, num_banks=1, name="sram2") + # self.local_check(a, final_verification=True) - debug.info(1, "Single bank, four way column mux with control logic") - a = sram(word_size=4, num_words=64, num_banks=1, name="sram3") - self.local_check(a, final_verification=True) + # debug.info(1, "Single bank, four way column mux with control logic") + # a = sram(word_size=4, num_words=64, num_banks=1, name="sram3") + # self.local_check(a, final_verification=True) - debug.info(1, "Single bank, eight way column mux with control logic") - a = sram(word_size=2, num_words=128, num_banks=1, name="sram4") - self.local_check(a, final_verification=True) + # debug.info(1, "Single bank, eight way column mux with control logic") + # a = sram(word_size=2, num_words=128, num_banks=1, name="sram4") + # self.local_check(a, final_verification=True) globals.end_openram()