Wip of P&R of ls180 with C4M FreePDK45.
authorStaf Verhaegen <staf@stafverhaegen.be>
Sun, 11 Apr 2021 15:38:45 +0000 (17:38 +0200)
committerStaf Verhaegen <staf@stafverhaegen.be>
Sun, 11 Apr 2021 16:02:03 +0000 (18:02 +0200)
build_full.sh can run til after `make vst`,
`make lvx` fails.

experiments9/freepdk_c4m45/Makefile [new file with mode: 0755]
experiments9/freepdk_c4m45/build_full.sh [new file with mode: 0755]
experiments9/freepdk_c4m45/coriolis2/__init__.py [new file with mode: 0644]
experiments9/freepdk_c4m45/coriolis2/ioring.py [new file with mode: 0644]
experiments9/freepdk_c4m45/coriolis2/katana.py [new file with mode: 0644]
experiments9/freepdk_c4m45/coriolis2/settings.py [new file with mode: 0644]
experiments9/freepdk_c4m45/design-flow.mk [new file with mode: 0644]
experiments9/freepdk_c4m45/doDesign.py [new file with mode: 0644]
experiments9/freepdk_c4m45/non_generated [new symlink]

diff --git a/experiments9/freepdk_c4m45/Makefile b/experiments9/freepdk_c4m45/Makefile
new file mode 100755 (executable)
index 0000000..d41c688
--- /dev/null
@@ -0,0 +1,46 @@
+
+        LOGICAL_SYNTHESIS = Yosys
+       PHYSICAL_SYNTHESIS = Coriolis
+               DESIGN_KIT = FreePDK_C4M45
+             YOSYS_FLATTEN = No
+#            YOSYS_SET_TOP = Yes
+                    CHIP  = chip
+                    CORE  = ls180
+            USE_CLOCKTREE = Yes
+                USE_DEBUG = No
+                  RM_CHIP = Yes
+                VST_FLAGS = --vst-uniquify-uppercase
+
+                 #NETLISTS = $(shell cat cells.lst)
+                 NETLISTS = ls180
+#            YOSYS_FLATTEN = $(shell cat flatten.lst)
+
+ include ./mk/design-flow.mk
+
+chip_r.vst: ls180.vst
+       -$(call scl_cols,$(call c2env, cgt -tV --script=doDesign))
+
+chip_r.ap: chip_r.vst
+
+pinmux:
+       (cd coriolis2 && python ../../../pinmux/src/pinmux_generator.py -v -s ls180 -o ls180)
+       ln -f -s ../../../pinmux/src/parse.py coriolis2/pinparse.py
+       ln -f -s coriolis2/ls180 ls180
+
+blif:      ls180.blif
+vst:       ls180.vst
+
+lvx:       lvx-chip_r
+druc:      druc-chip_r
+dreal:     dreal-chip_r
+flatph:     flatph-chip_r
+view:      cgt-chip_r
+
+layout:    chip_r.ap
+gds:       chip_r.gds
+gds_flat:  chip_r_flat.gds
+cif:      chip_r.cif
+
+
+view:      cgt-chip_r
+sim:       asimut-ls180_r
diff --git a/experiments9/freepdk_c4m45/build_full.sh b/experiments9/freepdk_c4m45/build_full.sh
new file mode 100755 (executable)
index 0000000..87d19b5
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# full core build including QTY 4of 4k SRAMs: please remember to alter
+# doDesign.py before running!
+# change the settings to the larger chip/corona size
+echo "remember to check doDesign core size"
+
+pdk=`realpath ../../c4m-pdk-freepdk45`
+export NDA_TOP=${pdk}/coriolis/techno
+
+# initialise/update the pinmux & c4m-pdk-freepdk45 submodule
+pushd ..
+git submodule update --init --remote
+popd
+
+# makes symlinks to alliance
+../mksym.sh
+touch mk/users.d/user-${USER}.mk
+rm -f mk/design-flow.mk
+cp design-flow.mk mk/
+lib=${pdk}/views/FreePDK45/FlexLib/liberty/FlexLib_nom.lib
+echo "export REAL_MODE = Yes" > mk/dks.d/FreePDK_C4M45.mk
+echo "export LIBERTY_FILE = $lib" >> mk/dks.d/FreePDK_C4M45.mk
+
+# generates the io pads needed for ioring.py
+make pinmux
+
+# clear out
+make clean
+rm *.vst *.ap
+
+# copies over a "full" core
+cp non_generated/full_core_ls180.il ls180.il
+
+
+# make the vst from ilang
+make vst
+
+# starts the build.
+make lvx
+
diff --git a/experiments9/freepdk_c4m45/coriolis2/__init__.py b/experiments9/freepdk_c4m45/coriolis2/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/experiments9/freepdk_c4m45/coriolis2/ioring.py b/experiments9/freepdk_c4m45/coriolis2/ioring.py
new file mode 100644 (file)
index 0000000..ddebb71
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from helpers import l, u, n
+import os
+import json
+
+def _byteify(data, ignore_dicts = False):
+    # if this is a unicode string, return its string representation
+    if isinstance(data, unicode):
+        return data.encode('utf-8')
+    # if this is a list of values, return list of byteified values
+    if isinstance(data, list):
+        return [ _byteify(item, ignore_dicts=True) for item in data ]
+    # if this is a dictionary, return dictionary of byteified keys and values
+    # but only if we haven't already byteified it
+    if isinstance(data, dict) and not ignore_dicts:
+        return dict((_byteify(key, ignore_dicts=True),
+                    _byteify(value, ignore_dicts=True))
+                        for key, value in data.iteritems())
+    # if it's anything else, return it in its original form
+    return data
+
+# load JSON-formatted pad info from pinmux
+pth = os.path.abspath(__file__)
+pth = os.path.split(pth)[0]
+print "path", pth
+with open("%s/ls180/litex_pinpads.json" % pth) as f:
+    txt = f.read()
+chip = json.loads(txt, object_hook=_byteify)
+chip = _byteify(chip, ignore_dicts=True)
+print chip
+
+chip.update({ 'pads.ioPadGauge' : 'pxlib',
+          # core option (big, time-consuming)
+          #'core.size'       : ( l(28000), l(28000) ),
+          #'chip.size'       : ( l(30200), l(30200) ),
+          # no-core option (test_issuer but no actual core)
+          'core.size'       : ( l(13000), l(13000) ),
+          'chip.size'       : ( l(14400), l(14400) ),
+          'pads.useCoreSize': True,
+          'chip.clockTree'  : True,
+       })
diff --git a/experiments9/freepdk_c4m45/coriolis2/katana.py b/experiments9/freepdk_c4m45/coriolis2/katana.py
new file mode 100644 (file)
index 0000000..7a5103f
--- /dev/null
@@ -0,0 +1,6 @@
+from Hurricane import DebugSession
+
+#DebugSession.addToTrace( katana.getCell().getNet( 'cu_issue_i' ) )
+#DebugSession.addToTrace( katana.getCell().getNet( 'cu_wr_rel_o(2)' ) )
+#DebugSession.addToTrace( katana.getCell().getNet( 'xer_so_ok' ) )
+
diff --git a/experiments9/freepdk_c4m45/coriolis2/settings.py b/experiments9/freepdk_c4m45/coriolis2/settings.py
new file mode 100644 (file)
index 0000000..cc10a52
--- /dev/null
@@ -0,0 +1,44 @@
+# -*- Mode:Python -*-
+
+import os
+import socket
+import helpers
+
+NdaDirectory = None
+if os.environ.has_key('NDA_TOP'):
+    NdaDirectory = os.environ['NDA_TOP']
+if not NdaDirectory:
+    hostname = socket.gethostname()
+    if hostname.startswith('lepka'):
+        NdaDirectory = '/dsk/l1/jpc/crypted/soc/techno'
+        if not os.path.isdir(NdaDirectory):
+            print '[ERROR] You forgot to mount the NDA encrypted directory, stupid!'
+    else:
+        NdaDirectory = '/users/soft/techno/techno'
+helpers.setNdaTopDir( NdaDirectory )
+
+import Cfg
+from   CRL     import AllianceFramework
+from   helpers import overlay, l, u, n
+from   NDA.node45.freepdk45_c4m import techno, FlexLib, LibreSOCIO
+
+techno.setup()
+FlexLib.setup()
+LibreSOCIO.setup()
+
+with overlay.CfgCache(priority=Cfg.Parameter.Priority.UserFile) as cfg:
+    cfg.misc.catchCore           = False
+    cfg.misc.minTraceLevel       = 12300
+    cfg.misc.maxTraceLevel       = 12400
+    cfg.misc.info                = False
+    cfg.misc.paranoid            = False
+    cfg.misc.bug                 = False
+    cfg.misc.logMode             = True
+    cfg.misc.verboseLevel1       = True
+    cfg.misc.verboseLevel2       = True
+    cfg.etesian.graphics         = 3
+    cfg.etesian.spaceMargin      = 0.10
+    cfg.katana.eventsLimit       = 4000000
+    af  = AllianceFramework.get()
+    env = af.getEnvironment()
+    env.setCLOCK( '^sys_clk$|^ck|^jtag_tck$' )
diff --git a/experiments9/freepdk_c4m45/design-flow.mk b/experiments9/freepdk_c4m45/design-flow.mk
new file mode 100644 (file)
index 0000000..e8b0d12
--- /dev/null
@@ -0,0 +1,55 @@
+
+ include ./mk/os.mk
+ include ./mk/users.mk
+ include ./mk/binaries.mk
+ include ./mk/alliance.mk
+
+# Some useful functions.
+ run_if_older = if [ \( ! -e "$(1)" \) -o \( "$(1)" -ot "$(2)" \) ]; then $(3); else echo "\"$(1)\" newer than \"$(2)\" (skip rule)."; fi 
+
+# Select the Design Kit.
+ ifeq ($(DESIGN_KIT),sxlib)
+   include ./mk/dks.d/sxlib.mk
+ else ifeq ($(DESIGN_KIT),nsxlib)
+   include ./mk/dks.d/nsxlib.mk
+ else ifeq ($(DESIGN_KIT),nsxlib45)
+   include ./mk/dks.d/nsxlib45.mk
+ else ifeq ($(DESIGN_KIT),Hibikino)
+   include ./mk/dks.d/sxlib-hibikino.mk
+ else ifeq ($(DESIGN_KIT),FreePDK_45)
+   include ./mk/dks.d/FreePDK_45.mk
+ else ifeq ($(DESIGN_KIT),phenitec06)
+   include ./mk/dks.d/phenitec06.mk
+ else ifeq ($(DESIGN_KIT),cmos45)
+   include ./mk/dks.d/nsxlib45.mk
+ else ifeq ($(DESIGN_KIT),c35b4)
+   include ./mk/dks.d/c35b4.mk
+ else ifeq ($(DESIGN_KIT),FlexLib018)
+   include ./mk/dks.d/FlexLib018.mk
+ else ifeq ($(DESIGN_KIT),FreePDK_C4M45)
+   include ./mk/dks.d/FreePDK_C4M45.mk
+ else
+   $(error DESIGN_KIT variable has not been set or has an unsupported value)
+ endif
+
+# Select the logical synthesis tools.
+ ifeq ($(LOGICAL_SYNTHESIS),Yosys)
+   include ./mk/synthesis-yosys.mk
+ else ifeq ($(LOGICAL_SYNTHESIS),Alliance)
+   include ./mk/synthesis-alliance.mk
+ else
+   include ./mk/synthesis-disabled.mk
+ endif
+
+# Select the physical synthesis tools.
+ ifeq ($(PHYSICAL_SYNTHESIS),Coriolis)
+   include ./mk/pr-coriolis.mk
+ else ifeq ($(PHYSICAL_SYNTHESIS),Alliance)
+   include ./mk/pr-alliance.mk
+ else ifeq ($(PHYSICAL_SYNTHESIS),Hibikino)
+   include ./mk/pr-hibikino.mk
+ else
+   $(error PHYSICAL_SYNTHESIS variable has not been set or has an unsupported value)
+ endif
+
+ $(info Using RDS_TECHNO_NAME = "${RDS_TECHNO_NAME}")
diff --git a/experiments9/freepdk_c4m45/doDesign.py b/experiments9/freepdk_c4m45/doDesign.py
new file mode 100644 (file)
index 0000000..dbd4dde
--- /dev/null
@@ -0,0 +1,308 @@
+
+from   __future__ import print_function
+
+import os
+import re
+import json
+import sys
+import traceback
+import collections
+import CRL
+import helpers
+from   helpers         import trace, l, u, n
+from   helpers.io      import ErrorMessage, WarningMessage
+from   helpers.overlay import UpdateSession
+import plugins
+from   Hurricane  import Breakpoint, DataBase, DbU, Transformation, Point, Box, \
+                         Cell, Instance
+from   plugins.alpha.block.matrix         import RegisterMatrix
+from   plugins.alpha.macro.macro          import Macro
+from   plugins.alpha.block.iospecs        import IoSpecs
+from   plugins.alpha.block.block          import Block
+from   plugins.alpha.block.configuration  import IoPin, GaugeConf
+from   plugins.alpha.core2chip.libresocio import CoreToChip
+from   plugins.alpha.chip.configuration   import ChipConf
+from   plugins.alpha.chip.chip            import Chip
+
+
+af         = CRL.AllianceFramework.get()
+powerCount = 0
+
+
+def isiterable ( pyobj ):
+    if isinstance(pyobj,collections.Iterable): return True
+    return False
+
+
+def doIoPowerCap ( flags ):
+    global powerCount
+    side = flags & IoPin.SIDE_MASK
+    if flags & IoPin.A_BEGIN:
+        ioPadPower = [ (side , None,    'power_{}'.format(powerCount),   'vdd' )
+                     , (side , None,   'ground_{}'.format(powerCount),   'vss' )
+                     , (side , None, 'ioground_{}'.format(powerCount), 'iovss' )
+                     , (side , None,  'iopower_{}'.format(powerCount), 'iovdd' )
+                     ]
+    else:
+        ioPadPower = [ (side , None,  'iopower_{}'.format(powerCount), 'iovdd' )
+                     , (side , None, 'ioground_{}'.format(powerCount), 'iovss' )
+                     , (side , None,   'ground_{}'.format(powerCount),   'vss' )
+                     , (side , None,    'power_{}'.format(powerCount),   'vdd' )
+                     ]
+    powerCount += 1
+    return ioPadPower
+
+
+def doIoPinVector ( ioSpec, bits ):
+    v = []
+    if not isiterable(bits): bits = range(bits)
+    if not bits:
+        raise ErrorMessage( 1, [ 'doIoPinVector(): Argument "bits" is neither a width nor an iterable.'
+                               , '(bits={})'.format(bits)
+                               ] )
+    if len(ioSpec) == 5:
+        for bit in bits:
+            v.append(( ioSpec[0]
+                     , ioSpec[1]
+                     , ioSpec[2].format(bit)
+                     , ioSpec[3].format(bit)
+                     , ioSpec[4].format(bit) ))
+    elif len(ioSpec) == 6:
+        for bit in bits:
+            v.append(( ioSpec[0]
+                     , ioSpec[1]
+                     , ioSpec[2].format(bit)
+                     , ioSpec[3].format(bit)
+                     , ioSpec[4].format(bit)
+                     , ioSpec[5].format(bit) ))
+    elif len(ioSpec) == 7:
+        for bit in bits:
+            v.append(( ioSpec[0]
+                     , ioSpec[1]
+                     , ioSpec[2].format(bit)
+                     , ioSpec[3].format(bit)
+                     , ioSpec[4].format(bit)
+                     , ioSpec[5].format(bit)
+                     , ioSpec[6].format(bit) ))
+    else:
+        raise ErrorMessage( 1, [ 'doIoPinVector(): Argument "ioSpec" must have between 5 and 7 fields ({})'.format(len(ioSpec))
+                               , '(ioSpec={})'.format(ioSpec)
+                               ] )
+    return v
+
+
+def rgetInstance ( cell, path ):
+    """
+    Get the instance designated by path (recursively). The path argument can be
+    either a string of instance names separated by dots or directly a list of
+    instances names.
+    """
+    if isinstance(path,str):
+        path = path.split( '.' )
+    elif not isinstance(path,list):
+        raise ErrorMessage( 1, 'rgetInstance(): "path" argument is neither a string or a list ({})"' \
+                               .format(path) )
+    instance = cell.getInstance( path[0] )
+    if instance is None:
+        raise ErrorMessage( 1, 'rgetInstance(): no instance "{}" in cell "{}"' \
+                               .format(path[0],cell.getName()) )
+    if len(path) == 1:
+        return instance
+    return rgetInstance( instance.getMasterCell(), path[1:] )
+
+
+def rsetAbutmentBox ( cell, ab ):
+    for occurrence in cell.getNonTerminalNetlistInstanceOccurrences():
+        masterCell = occurrence.getEntity().getMasterCell()
+        masterCell.setAbutmentBox( ab )
+
+def addPllPlaceHolder ( cell ):
+    pllPlaceHolder = Cell.create( af.getLibrary(0), 'pllplaceholder' )
+    pllPlaceHolder.setAbutmentBox( Box( u(0.0), u(0.0), u(200.0), u(200.0) ))
+    pllPlaceHolder.setTerminalNetlist( True )
+    instance = Instance.create( cell, 'pllPlaceholder', pllPlaceHolder )
+    return instance
+
+
+def scriptMain (**kw):
+    """The mandatory function to be called by Coriolis CGT/Unicorn."""
+    global af
+   #helpers.setTraceLevel( 550 )
+   #Breakpoint.setStopLevel( 100 )
+    rvalue     = True
+   #coreSize   = u(37*90.0)
+    coreSize   = u(59*90.0)
+    chipBorder = u(2*214.0 + 10*13.0)
+    ioSpecs    = IoSpecs()
+   #pinmuxFile = './non_generated/litex_pinpads.json'
+   #pinmuxFile = './coriolis2/ls180/litex_pinpads.json'
+   #ioSpecs.loadFromPinmux( pinmuxFile )
+   # I/O pads, East side.
+    ioPadsSpec  = []
+    ioPadsSpec += doIoPowerCap( IoPin.EAST|IoPin.A_BEGIN )
+    ioPadsSpec += doIoPinVector( (IoPin.EAST , None, 'sdram_a_{}', 'sdram_a({})', 'sdram_a({})'), 13 )
+    ioPadsSpec += doIoPinVector( (IoPin.EAST , None,    'gpio_{}',    'gpio({})',  'gpio_i({})',  'gpio_oe({})',  'gpio_o({})'), range(8,16) )
+    ioPadsSpec += [ (IoPin.EAST , None, 'jtag_tms', 'jtag_tms', 'jtag_tms' )
+                  , (IoPin.EAST , None, 'jtag_tdi', 'jtag_tdi', 'jtag_tdi' )
+                  , (IoPin.EAST , None, 'jtag_tdo', 'jtag_tdo', 'jtag_tdo' )
+                  , (IoPin.EAST , None, 'jtag_tck', 'jtag_tck', 'jtag_tck' )
+                  ]
+    ioPadsSpec += doIoPinVector( (IoPin.EAST , None, 'nc_{}', 'nc({})', 'nc({})'), range(4) )
+    ioPadsSpec += doIoPowerCap( IoPin.EAST|IoPin.A_END )
+   # I/O pads, West side.
+    ioPadsSpec += doIoPowerCap( IoPin.WEST|IoPin.A_BEGIN )
+    ioPadsSpec += [ (IoPin.WEST, None, 'pwm_1', 'pwm(1)', 'pwm(1)' ) ]
+    ioPadsSpec += doIoPinVector( (IoPin.WEST , None, 'eint_{}', 'eint({})', 'eint({})'),  3 )
+    ioPadsSpec += [ (IoPin.WEST , None, 'spimaster_clk' , 'spimaster_clk' , 'spimaster_clk' )
+                  , (IoPin.WEST , None, 'spimaster_cs_n', 'spimaster_cs_n', 'spimaster_cs_n' )
+                  , (IoPin.WEST , None, 'spimaster_mosi', 'spimaster_mosi', 'spimaster_mosi' )
+                  , (IoPin.WEST , None, 'spimaster_miso', 'spimaster_miso', 'spimaster_miso' )
+                  , (IoPin.WEST , None, 'sdcard_cmd'    , 'sdcard_cmd'    , 'sdcard_cmd_i', 'sdcard_cmd_oe', 'sdcard_cmd_o' )
+                  , (IoPin.WEST , None, 'sdcard_clk'    , 'sdcard_clk'    , 'sdcard_clk' )
+                  ]
+    ioPadsSpec += doIoPinVector( (IoPin.WEST , None, 'sdcard_data_{}', 'sdcard_data({})', 'sdcard_data_i({})', 'sdcard_data_oe', 'sdcard_data_o({})'),  4 )
+    ioPadsSpec += doIoPinVector( (IoPin.WEST , None, 'nc_{}', 'nc({})', 'nc({})'), range(4,16) )
+    ioPadsSpec += doIoPowerCap( IoPin.WEST|IoPin.A_END )
+   # I/O pads, North side.
+    ioPadsSpec += doIoPowerCap( IoPin.NORTH|IoPin.A_BEGIN )
+    ioPadsSpec += doIoPinVector( (IoPin.NORTH, None, 'sdram_dm_{}', 'sdram_dm({})', 'sdram_dm({})'),  2 )
+    ioPadsSpec += doIoPinVector( (IoPin.NORTH, None, 'sdram_dq_{}', 'sdram_dq({})', 'sdram_dq_i({})', 'sdram_dq_oe', 'sdram_dq_o({})'), range(0,16) )
+    ioPadsSpec += doIoPinVector( (IoPin.NORTH, None, 'sdram_ba_{}', 'sdram_ba({})', 'sdram_ba({})'), 2 )
+    ioPadsSpec += [ (IoPin.NORTH, None, 'sdram_clock'   , 'sdram_clock'   , 'sdram_clock' )
+                  , (IoPin.NORTH, None, 'sdram_cke'     , 'sdram_cke'     , 'sdram_cke' )
+                  , (IoPin.NORTH, None, 'sdram_ras_n'   , 'sdram_ras_n'   , 'sdram_ras_n' )
+                  , (IoPin.NORTH, None, 'sdram_cas_n'   , 'sdram_cas_n'   , 'sdram_cas_n' )
+                  , (IoPin.NORTH, None, 'sdram_we_n'    , 'sdram_we_n'    , 'sdram_we_n' )
+                  , (IoPin.NORTH, None, 'sdram_cs_n'    , 'sdram_cs_n'    , 'sdram_cs_n' )
+                  ]
+    ioPadsSpec += doIoPinVector( (IoPin.NORTH, None, 'nc_{}', 'nc({})', 'nc({})'), range(16,18) )
+    ioPadsSpec += doIoPowerCap( IoPin.NORTH|IoPin.A_END )
+   # I/O pads, South side.
+    ioPadsSpec += doIoPowerCap( IoPin.SOUTH|IoPin.A_BEGIN )
+    ioPadsSpec += [ (IoPin.SOUTH, None, 'i2c_sda_i'     , 'i2c_sda_i'     , 'i2c_sda_i' ) ]
+    ioPadsSpec += doIoPinVector( (IoPin.SOUTH, None, 'nc_{}', 'nc({})', 'nc({})'), range(18,22) )
+    ioPadsSpec += [ (IoPin.SOUTH, None, 'spisdcard_clk' , 'spisdcard_clk' , 'spisdcard_clk' )
+                  , (IoPin.SOUTH, None, 'spisdcard_cs_n', 'spisdcard_cs_n', 'spisdcard_cs_n' )
+                  , (IoPin.SOUTH, None, 'spisdcard_mosi', 'spisdcard_mosi', 'spisdcard_mosi' )
+                  , (IoPin.SOUTH, None, 'spisdcard_miso', 'spisdcard_miso', 'spisdcard_miso' )
+                  ]
+    ioPadsSpec += doIoPinVector( (IoPin.SOUTH, None, 'nc_{}', 'nc({})', 'nc({})'), range(22,23) )
+    ioPadsSpec += [ (IoPin.SOUTH, None, 'uart_tx', 'uart_tx', 'uart_tx' )
+                  , (IoPin.SOUTH, None, 'uart_rx', 'uart_rx', 'uart_rx' )
+                  ]
+    ioPadsSpec += doIoPinVector( (IoPin.SOUTH, None, 'gpio_{}', 'gpio({})', 'gpio_i({})', 'gpio_oe({})',  'gpio_o({})'), range(0,8) )
+    ioPadsSpec += [ (IoPin.SOUTH, None, 'sys_clk', 'sys_clk', 'sys_clk' )
+                  , (IoPin.SOUTH, None, 'sys_rst', 'sys_rst', 'sys_rst' )
+                  ]
+    ioPadsSpec += doIoPinVector( (IoPin.SOUTH, None, 'nc_{}', 'nc({})', 'nc({})'), range(23,24) )
+    ioPadsSpec += [ (IoPin.SOUTH, None, 'sys_pll_18_o'  , 'sys_pll_18_o'  , 'sys_pll_18_o' ) ]
+    ioPadsSpec += doIoPinVector( (IoPin.SOUTH, None,   'sys_clksel_i{}',  'sys_clksel_i({})',  'sys_clksel_i({})'),  2 )
+    ioPadsSpec += [ (IoPin.SOUTH, None, 'sys_pll_lck_o' , 'sys_pll_lck_o' , 'sys_pll_lck_o' ) ]
+    ioPadsSpec += doIoPowerCap( IoPin.SOUTH|IoPin.A_END )
+    try:
+        cell, editor = plugins.kwParseMain( **kw )
+        cell = af.getCell( 'ls180', CRL.Catalog.State.Logical )
+        if cell is None:
+            print( ErrorMessage( 2, 'doDesign.scriptMain(): Unable to load cell "{}".' \
+                                    .format('ls180') ))
+            sys.exit(1)
+        if editor: editor.setCell( cell )
+       #ls180Conf = ChipConf( cell, ioPads=ioSpecs.ioPadsSpec )
+        ls180Conf = ChipConf( cell, ioPads=ioPadsSpec )
+        ls180Conf.cfg.etesian.bloat = 'nsxlib'
+        ls180Conf.cfg.etesian.uniformDensity = True
+        ls180Conf.cfg.etesian.aspectRatio = 1.0
+        ls180Conf.cfg.etesian.spaceMargin = 0.05
+        ls180Conf.cfg.anabatic.searchHalo = 2
+        ls180Conf.cfg.anabatic.globalIterations = 20
+        ls180Conf.cfg.anabatic.topRoutingLayer = 'METAL5'
+        ls180Conf.cfg.katana.hTracksReservedLocal = 6
+        ls180Conf.cfg.katana.vTracksReservedLocal = 3
+        ls180Conf.cfg.katana.hTracksReservedMin = 3
+        ls180Conf.cfg.katana.vTracksReservedMin = 1
+        ls180Conf.cfg.block.spareSide = u(7*13)
+        ls180Conf.cfg.chip.supplyRailWidth = u(35)
+        ls180Conf.cfg.chip.supplyRailPitch = u(90)
+        ls180Conf.editor = editor
+        ls180Conf.useSpares = True
+        ls180Conf.useClockTree = True
+        ls180Conf.useHFNS = True
+        ls180Conf.bColumns = 2
+        ls180Conf.bRows = 2
+        ls180Conf.chipConf.name = 'chip'
+        ls180Conf.chipConf.ioPadGauge = 'LibreSOCIO'
+        ls180Conf.coreSize = (coreSize, coreSize)
+        ls180Conf.chipSize = (coreSize + chipBorder, coreSize + chipBorder)
+
+        with UpdateSession():
+            sliceHeight = ls180Conf.sliceHeight
+            coreAb      = Box( 0, 0, coreSize, coreSize )
+            rsetAbutmentBox( cell, coreAb )
+
+        ls180ToChip = CoreToChip( ls180Conf )
+        ls180ToChip.buildChip()
+        chipBuilder = Chip( ls180Conf )
+        chipBuilder.doChipFloorplan()
+
+        with UpdateSession():
+            # Thoses ids are dependent on Yosys. They need to be adjusted whenever
+            # the design changes.
+            #tiId   = 38695
+            tiId   = 38381
+            #sramId = 3695
+            sramId = 3300
+            tiPath = 'subckt_{}_test_issuer.subckt_1_ti.'.format(tiId)
+            sramPaths = [ tiPath+'subckt_{}_sram4k_0.subckt_144_SPBlock_512W64B8W'.format(sramId)
+                        , tiPath+'subckt_{}_sram4k_1.subckt_144_SPBlock_512W64B8W'.format(sramId+1)
+                        , tiPath+'subckt_{}_sram4k_2.subckt_144_SPBlock_512W64B8W'.format(sramId+2)
+                        , tiPath+'subckt_{}_sram4k_3.subckt_144_SPBlock_512W64B8W'.format(sramId+3)
+                        ]
+            sram = DataBase.getDB().getCell( 'SPBlock_512W64B8W' )
+            if not sram:
+                raise ErrorMessage( 1, 'SRAM instance "{}" not found.'.format(sramPaths[i]) )
+            sramAb = sram.getAbutmentBox()
+            coreAb = cell.getAbutmentBox()
+            sliceHeight = chipBuilder.conf.sliceHeight
+            originX = coreAb.getXMin() + 2*chipBuilder.conf.sliceStep
+            for i in range(4):
+                sram = rgetInstance( cell, sramPaths[i] )
+                chipBuilder.placeMacro \
+                    ( sramPaths[i]
+                    , Transformation( originX
+                                    , coreAb.getYMax() - sramAb.getHeight() - 2*sliceHeight
+                                    , Transformation.Orientation.ID )
+                    )
+                originX += sramAb.getWidth () + 3*sliceHeight
+            pll = addPllPlaceHolder( cell )
+            pllAb = pll.getAbutmentBox()
+            pll.setTransformation( Transformation( coreAb.getXMax() - pllAb.getWidth()
+                                                 , coreAb.getYMax() - pllAb.getHeight()
+                                                 , Transformation.Orientation.ID ) )
+            pll.setPlacementStatus( Instance.PlacementStatus.FIXED )
+           #ls180Conf.placeArea = Box( coreAb.getXMin()
+           #                         , coreAb.getYMin()
+           #                         , coreAb.getXMax() - chipBuilder.conf.sliceStep
+           #                         , coreAb.getYMax() - sramAb.getHeight() - 2*sliceHeight
+           #                         )
+           #memPatterns = [ r'^mem_(?P<i>\d+)__(?P<j>[\d+])$'
+           #              , r'^mem_1_(?P<i>\d+)__(?P<j>[\d+])$' 
+           #              , r'^mem_2_(?P<i>\d+)__(?P<j>[\d+])$' 
+           #              , r'^mem_3_(?P<i>\d+)__(?P<j>[\d+])$' 
+           #              ]
+           #originX += 2*sliceHeight
+           #originY  = coreAb.getYMax()
+           #for i in range(len(memPatterns)):
+           #    mem = RegisterMatrix( ls180Conf, cell, memPatterns[i] )
+           #    originY -= mem.getHeight()
+           #    mem.place( Point(originX,originY) )
+        Breakpoint.stop( 99, 'After core placement.' )
+
+        rvalue = chipBuilder.doPnR()
+        chipBuilder.save()
+        CRL.Gds.save( ls180Conf.chip )
+    except Exception, e:
+        helpers.io.catch(e)
+        rvalue = False
+    sys.stdout.flush()
+    sys.stderr.flush()
+    return rvalue
diff --git a/experiments9/freepdk_c4m45/non_generated b/experiments9/freepdk_c4m45/non_generated
new file mode 120000 (symlink)
index 0000000..f4c261f
--- /dev/null
@@ -0,0 +1 @@
+../non_generated
\ No newline at end of file