diff --git a/.pdm-python b/.pdm-python new file mode 100644 index 000000000..37d75f578 --- /dev/null +++ b/.pdm-python @@ -0,0 +1 @@ +/dsk/l1/misc/roselyne/coriolis/src/coriolis/.venv/bin/python \ No newline at end of file diff --git a/cumulus/src/plugins/netlist/README.rst b/cumulus/src/plugins/netlist/README.rst new file mode 100644 index 000000000..6db141f52 --- /dev/null +++ b/cumulus/src/plugins/netlist/README.rst @@ -0,0 +1,135 @@ +.. -*- Mode: rst -*- + +.. |Python wheel builds| image:: https://github.com/lip6/coriolis/actions/workflows/wheels.yml/badge.svg + :target: https://github.com/lip6/coriolis/actions/workflows/wheels.yml + +.. image:: example.svg + :alt: Netlist example + :align: center + +====================================== +Utilities for netlist manipulation +====================================== + +Introduction +============== +This tutorial introduces all the notions needed to manipulate netlist throw |Coriolis| with Python. It can be useful for optimization technics which need to automatically modify the netlist. + +You can refer to the Hurricane+python tutorial for more advance concepts with the |Hurricane| database + +Generalities +============== +As shown with figure, a design in Coriolis is refered as a *cell*. It is composed with *instances* (dot lines), *nets* (in blue) and *plugs* (black squares). Each instance refers to a *model* (`MasterCell`) which, in this example, are `OR2` and `AND2`. A model is itself a cell with instances, nets and plugs. Each component of a design has a name which can be obtained by using the method `getName()`. Plugs, nets and instances are collections which can be obtained by the methods `getPlugs()`, `getNets()`, `getInstances()` respectively. + +Using Coriolis +================= +Setting up the environment +---------------------------- +Coriolis can be used throw Python3. First of all, the adequate environment has to be setup with the following command: +``crlenv.py -- shell`` +Where ``shell`` is the shell used by the user (bash, zsh...): + +Importing the modules +----------------------- +To use Coriolis with Python, including the Coriolis's modules is mandatory: +``from coriolis import CRL`` + +To make a design, we need to used a dedicated techno. The configuration of a design flow for a dedicated techno is done in the Python module ``coriolis.designflow.technos`` in which we need to import the techno. As example for SkyWater 130nm the import is: +:: + from coriolis.designflow.technos import setupSky130_c4m + +And after we use the ``setupSky130_c4m`` python function to run the configuration: +:: + setupSky130_c4m( '../../..', '../../../pdkmaster/C4M.Sky130' ) + +Beginning the session +----------------------- +All the flow in Coriolis is running in a Coriolis session which has to be open: +:: + af = AllianceFramework.get() + UpdateSession.open() + +Netlist manipulation +====================== +First of all the netlist (in blif format) have to be loaded with the following method: +:: + cell = CRL.Blif.load("example") + +Navigation +----------- +To navigate through the different components of the netlist (`cell`), the different following instructions can be used: +- for nets: ``for net in cell.getNets():`` +- for instances: ``for inst in cell.getInstances():`` +- for plugs: ``for plug in cell.getPlugs():`` + +Enter the hierarchy +--------------------- +Each instance can also be considered as a cell and follow the same navigation capacities. For that, it is necessary to refer to the model of is instance and access it by using the method ``getMasterCell()`` on the instance. For example, considering Figure |example.svg|, accessing to the nets and instances inside ``inst1`` can be done by the following code: +:: + cell1 = inst1.getMasterCell() + for net in cell1.getNets(): + +Components' properties +------------------------ +Each component in the Hurricane database, is a class and has specific properties. The Hurricane's documentation gives all the details. + +Net +^^^^^ +Nets have ``Direction`` and ``Type`` which are both Hurricane's classes. A direction can be ``IN``, ``OUT``, ``TRISTATE`` etc. A type can be ``LOGICAL``, ``CLOCK`` etc. Lot of method give access to the properties (``getName()``, ``getDirection()``, etc.), test them (``isLogical()``, ``isClock()``, etc.) or set them (``setType()``, ``setExternal()``, etc.) + +Instance +^^^^^^^^^^ +The main methods of this class concerning netlist manipulation are ``getMasterCell()`` and ``getPlugs()`` + +Plug +^^^^^^ +The method ``getInstance()`` allows to get the instance of the plug and ``getMasterNet()`` to get the net connected to this plug. + +Examples +---------- + +WARNING Hurricane collections are not mutable, so change values outside iterator !! +=> the next sections have to be modify in consequence + +Changing a cell +^^^^^^^^^^^^^^^^^ +For example a cell from which one output drives more than ``MAX`` cells can be amplified, that mean change a ``_x2`` cell by a ``_x4`` one (i.e: ``a2_x2`` is changed by ``a2_x4`` which is amplified by 2): +:: + for net in cell.getNets(): + if size(net.getPlugs()) > MAX: + for plug in net.getPlugs(): + if plug.getMasterNet().getDirection() == Net.Direction.OUT: + inst = plug.getInstance() + # replace _x1 or_x2 by _x4 in cell's name + cell_name = inst.getMasterCell().getName()[:-1] + cell_name += '4' + # get the corresponding cell (_x4) + new_cell = af.getCell(cell_name, Catalog.State.Views) + # change the cell + inst.setMasterCell(new_cell) + +Adding a cell +^^^^^^^^^^^^^^^ +For example, a buffer can be inserted to amplify a net which drives more than ``MAX`` cells: +:: + for net in cell.getNets(): + if size(net.getPlugs()) > MAX: + # get the buffer + buf = af.getCell('buf_x2', Catalog.State.Views) + # create its corresponding instance + buf_i = Instance.create(cell, 'buf_%s' %(net.getName()), buf) + # insert this instance to the net, i.e. cut the net into net and net_b (to be created) + buf_i.getPlug(buf.getNet('i')).setNet(net) + net_b = Net.create(cell, "%s_b" %(net.getName())) + buf_i.getPlug(buf.getNet('q')).setNet(net_b) + +A complete example +==================== +The ``amplify.py`` code is a concrete example of all the things described in the previous sections. It proposes 2 fonctions that can be applied to a given circuit: + +- `amplify_net` that changes the cells that drive a given net +- `bufferize` that bufferizes a given net + + + + diff --git a/cumulus/src/plugins/netlist/adjacence.py b/cumulus/src/plugins/netlist/adjacence.py new file mode 100644 index 000000000..00c610bd6 --- /dev/null +++ b/cumulus/src/plugins/netlist/adjacence.py @@ -0,0 +1,122 @@ +import sys, getopt + +import coriolis.technos.symbolic.cmos +from coriolis.Hurricane import * +from coriolis.CRL import * + +af = AllianceFramework.get() +UpdateSession.open() + +# transform a given netlist to its corresponding adjacence matrix where: +# - cell is the cell corresponding to the netlist +# - tf is a boolean indicating the adjacence matrix's direction, if True (default) +# + lines are the target (to) +# + and columns the source (from) +# the opposite if False +# return: +# - the list of the nets with their index in the adjacence matrix +# - the adjacence matrix +def cell2adj(cell, tf = True): + dnets = {} + mat =[] + n = 0 + # Matrix initialization + for n1 in cell.getNets(): + if n1.getType() == Net.Type.LOGICAL: + mat.append([]) + dnets[n1.getName()] = [n,"Input"] + n += 1 + for n2 in cell.getNets(): + if n2.getType() == Net.Type.LOGICAL: + mat[-1].append(0) + # adjacence's matrix filling + for ins in cell.getInstances(): + target = [] + source = [] + for plug in ins.getPlugs(): + net = plug.getMasterNet() + if net.getType() == Net.Type.LOGICAL: + if net.getDirection() == Net.Direction.IN: + source.append(dnets[plug.getNet().getName()][0]) + elif net.getDirection() == Net.Direction.OUT: + target.append(dnets[plug.getNet().getName()][0]) + dnets[plug.getNet().getName()][1] = ins.getMasterCell().getName() + for i in range(len(source)): + for j in range(len(target)): + if tf: + mat[target[j]][source[i]] = 1 + else: + mat[source[i]][target[j]] = 1 + #print(dnets) + #for i in range(len(dnets)): + # print(mat[i]) + return (dnets,mat) + +# get the adjassors' list of each net in the adjacence matrix mat: +# - if the adjacence matrix is to_from (tf = True) it is a predecessors list +# - if the adjacence matrix is from_to (tf = False) it is a successors list +# return a tuple with: +# - the index of the net in the adjacence matrix +# - the list of the corresponding adjassors +def adjassors(mat): + res = [] + l = -1 + for line in mat: + l += 1 + res.append((l,[])) + for i in range(len(line)): + if line[i] == 1: + res[-1][1].append(i) + if res[-1][1] == []: + res.pop() + #for i in range(len(res)): + # print(res[i]) + return res + +# write the result in a json file where: +# - fname is the name of the output file +# - nets is the list of the nets and their index +# - mat is the adjacence matrix +# - adj is the list of adjassors +def write_json(fname, nets, mat, adj): + fres = open(fname+'.json', 'wt') + fres.write("{\n") + fres.write(' "Nets" : ' + str(nets)) + #fres.write(',\n "Type" : ' + str(ltypes)) + fres.write(',\n "Mat" : ' + str(mat)) + fres.write(',\n "Adj" : ' + str(adj)) + fres.write('\n}') + fres.close() + +def usage(): + print("python blif2graph.py [option]") + print("Gaphs' utilities on a netlist") + print("-b (blif): the name of the blif netlist") + print("-p (pred): list of predecessors of each net") + print("-s (suc): list of sucessors of each net") + print("-h (help): this message") + +def main(argv): + try: + opts, args = getopt.getopt(sys.argv[1:], "hb:ps", ["help", "blif=", "pred", "suc"]) + except getopt.GetoptError as err: + print(err) + usage() + sys.exit(2) + + for o, a in opts: + if o == '-b': + cell = Blif.load(a) + fname = a + if o == '-p': + tf = True + if o == "-s": + tf = False + if o == "-h": + usage() + (nets,mat) = cell2adj(cell,tf) + adj = adjassors(mat) + write_json(fname,nets,mat,adj) + +if __name__ == "__main__": + main(sys.argv) diff --git a/cumulus/src/plugins/netlist/amplify.py b/cumulus/src/plugins/netlist/amplify.py new file mode 100644 index 000000000..eab86518f --- /dev/null +++ b/cumulus/src/plugins/netlist/amplify.py @@ -0,0 +1,226 @@ +# example of standalone use: +# python amplify.py -l gf180mcu -n arlet6502 -a amp:30 -o vlog + +from coriolis import Hurricane, CRL + +from liberty.parser import parse_liberty +from liberty.types import * +from sympy import parse_expr, Id +from coriolis.helpers.overlay import UpdateSession + +# Read liberty file to create a dictionary where: +# - key is the canonical logic function of the cell (thanks to sympy read_expr) +# - value is a list of tuples (cell's name,capa) +# This dictionary helps to choose the appropriate cell for amplification +# return: the created dictionary +# TODO: add decorator to Hurricane library? +def read_liberty(liberty_file): + #print("LIBERTY="+liberty_file) + library = parse_liberty(open(liberty_file).read()) + fdict = {} + for cell_group in library.get_groups('cell'): + #print(cell_group.__repr__()) + for pin_group in cell_group.get_groups('pin'): + #print(" " + pin_group.args[0]) + pin = select_pin(cell_group, pin_group.args[0]) + #print(pin) + #print(pin.__repr__()) + if pin['direction'] == 'output': + #print(pin['function']) + # lower() because I is considered as Imaginary by sympy + # and replace for coherence with sympy operators + f = parse_expr(str(pin['function']).lower().replace("!","~").replace("\"","")) + #print(f.args,f.func) + #print(cell_group.args,pin) + # replace to be coherent with Coriolis cells' naming + #val = (cell_group.args[0].replace("__","_"),float(pin['max_capacitance'])) + val = (cell_group.args[0],float(pin['max_capacitance'])) + #print(" f: " + str(f)) + #print(" cap: " + str(pin['max_capacitance'])) + if (type(f) == sympy.core.symbol.Symbol) and (str(f) != 'IQ'): # buffer + f = 'buf' + # threestate + if pin['three_state']: f="ts" + # latch + if cell_group.get_groups('latch'): f="latch" + # clock gating + if cell_group['clock_gating_integrated_cell']: f="clkg" + # flip-flop + ff = cell_group.get_groups('ff') + if ff: + f = "ff" + #print(ff.__repr__()) + if cell_group.get_groups('test_cell'): f+='_test' + if ff[0]['clear']: f+='_r' + if ff[0]['preset']: f+='_s' + try: + fdict[str(f)].append(val) + except KeyError: + fdict[str(f)] = [val] + # sort by capicitance values + for v in fdict.values(): + v.sort(key=lambda gate: gate[1]) + return fdict + +# Pretty display for the dictionary returned by read_liberty +def pretty_display(libdict): + for k in libdict.keys(): + print(k,':') + for e in libdict[k]: + print(" ",e) + +# Find a cell given by its model's name in a given liberty library +# return the corresponding (key,value), False if not found +def find_in_liberty(cell,lib): + for (k,v) in lib.items(): + for l in v: + if l[0] == cell: + return (k,v) + return False + +# TODO: move this function to a common place as it is usefull for lots of operations +# Return the number of elements (size) of a given collection +def size(collection): + total = 0 + for c in collection: + total += 1 + return total + +# TODO: see if this function is not already written +# Yes it is but doesn't work +# Return if a net is a clock or not based on its name +def isClock(net): + return net.getName() in ['ck', 'CK', 'clk', 'CLK'] + +# Amplify a net by: +# - adding a buffer (tech == 'buf') +# - or amplifying the previous cell (tech == 'amp') +# parameters: +# - net is the net to amplify +# - tech is the technic used (buffer or cell amplification) +# - lib is the liberty library +# - hlib is the Hurricane library +# raise ValueError if the technic is not defined +def amplify_net(net, tech, lib, hlib): + # find the source plug + p_found = False # keep False if the net is an input TODO: how to bufferize? + for p in net.getPlugs(): + if p.getMasterNet().getDirection() == Hurricane.Net.Direction.OUT: + p_found = p + break + # no output plug found mean net is an input of the circuit then exit + # TODO: think how to do on inputs + if not p_found: + return + # modify the source plug net + if tech == 'buf': + # Add a buffer + p_found.setNet(bufferize(net,lib,hlib)) + elif tech == 'amp': + # Get the model of the instance of the found plug + model = p_found.getInstance().getMasterCell().getName() + # Find the cell in the library + res = find_in_liberty(model,lib) + new_cell = choose_cell(res) + # Amplify the source cell + p_found.getInstance().setMasterCell(hlib.getCell(new_cell)) + else: + raise ValueError(f'Unknown technic {tech}') + +# Choose the remplacing cell (biggest one) +# TODO: think about a searching technic +def choose_cell(dict_elem): + # search the cell with the max capacitance: the last one according to the sorting creation of the dictionary + return dict_elem[1][-1][0] + +# Add a buffer to a given net to amplify it +# parameters: +# - net is the net to amplify +# - cell is the cell in which the net is defined +# - lib is the liberty library +# - hlib is the Hurricane library +def bufferize(net, lib, hlib): + # create the buffer instance + # take a buffer cell in the liberty library + # TODO: method to find a better one + buf_m = hlib.getCell(lib['buf'][2][0]) + with UpdateSession(): + buf_i = Hurricane.Instance.create(net.getCell(), f'buf_{net.getName()}', buf_m) + for n in buf_m.getExternalNets(): + if (n.getDirection() == Hurricane.Net.Direction.IN) and not n.isSupply(): + buf_in = n.getName() + elif n.getDirection() == Hurricane.Net.Direction.OUT: + buf_out = n.getName() + # insert this instance to the net, i.e. cut the net into net_b (source to be created) and net + buf_i.getPlug(buf_m.getNet(buf_out)).setNet(net) + with UpdateSession(): + net_b = Hurricane.Net.create(net.getCell(), "%s_b" %(net.getName())) + buf_i.getPlug(buf_m.getNet(buf_in)).setNet(net_b) + return net_b + +# Amplify a given cell using the corresponding technic +# Parameters: +# - lib is the liberty dictionary provided by read_liberty +# - cell is the cell to amplify +# - tech is the technic used (buffer or cell amplification) +# - threshold: if net has a load (in number of target cells) > threshold then amplify +def amplify(lib, hlib, cell, tech, threshold=0): + for net in cell.getNets(): + if not isClock(net): + s = size(net.getPlugs()) + if s > threshold: + print(net.getName() + " th: " + str(s)) + amplify_net(net,tech,lib,hlib) + + +def usage(): + print("python utilities.py [option]") + print("Utility functions to amplify nets from a netlist") + print("-n (blif): the name of the blif netlist") + print("-a (tech:th): the amplification technique used (buf to bufferize the nets and amp to amplify the cells)") + print(" - th is the threshold corresponding to the load of a signal (in number of targetting cells)") + print("-o (output format): vlog or vst") + print("-h (help): this message") + +# main for standalone usage +if __name__ == '__main__': + import getopt, sys + + tech = "" + try: + opts, args = getopt.getopt(sys.argv[1:], "hn:a:o:l:", ["help", "blif=", "amp", "buf", "vlog", "vst", "lib="]) + except getopt.GetoptError as err: + print(err) + usage() + sys.exit(2) + + for o, a in opts: + if o == "-h": + usage() + if o == '-n': + cell = CRL.Blif.load(a) + fname = a + if o == "-a": + tech,th = a.split(':') + if o == '-o': + output = a + if o == '-l': + from coriolis.designflow.yosys import Yosys + from pathlib import Path + import importlib + mod = importlib.import_module("pdks."+a) + + mod.setup( useHV=True ) + liberty = Yosys._liberty + libdict = read_liberty(liberty) + # stem[0:-1] due to bug in Coriolis: mcu9t5v insteed mcu9t5v0 + hlib = Hurricane.DataBase.getDB().getRootLibrary().getLibrary(Path(liberty).stem[0:-1]) + + print(f'Amplify the nets of {fname} with a threshold of {th} with {tech}') + amplify(libdict,hlib,cell,tech,int(th)) + if output == 'vlog': + CRL.Verilog.save(cell, True) + elif output == 'vst': + AF = CRL.AllianceFramework.get() + AF.saveCell(cell,CRL.Catalog.State.Logical) + else: raise ValueError("not implemented format " + output) diff --git a/cumulus/src/plugins/netlist/example.svg b/cumulus/src/plugins/netlist/example.svg new file mode 100644 index 000000000..caf01c75b --- /dev/null +++ b/cumulus/src/plugins/netlist/example.svg @@ -0,0 +1,464 @@ + + + + + + + + Or Gate + + + + And Gate + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cell + + inst3 + + inst1 + + inst2 + + OR2 + + AND2 + + OR2 + + + + + + + + + + + + + + + + + diff --git a/cumulus/src/plugins/netlist/tryliberty.py b/cumulus/src/plugins/netlist/tryliberty.py new file mode 100644 index 000000000..c083b73a2 --- /dev/null +++ b/cumulus/src/plugins/netlist/tryliberty.py @@ -0,0 +1,37 @@ +from liberty.parser import parse_liberty +from liberty.types import * +from sympy import parse_expr + +liberty_file = '/dsk/l1/misc/roselyne/coriolis/src/alliance-check-toolkit/cells/nsxlib/nsxlib.lib' +library = parse_liberty(open(liberty_file).read()) + +fdict = {} + +# cell = select_cell(library, 'nmx2_x4') +# pins = cell.get_groups('pin') +# pinq = select_pin(cell, 'nq') +# f = pinq['function'] +# expr = str(f).replace("!","~").replace("\"","") +# print(parse_expr(expr)) + +for cell_group in library.get_groups('cell'): + print("\n" + cell_group.args[0]) + #print(cell_group.__repr__() + + for pin_group in cell_group.get_groups('pin'): + print(" " + pin_group.args[0]) + pin = select_pin(cell_group, pin_group.args[0]) + #print(pin) + #print(pin.__repr__()) + if pin['direction'] == 'output': + f = str(parse_expr(str(pin['function']).replace("!","~").replace("\"",""))) + val = (cell_group.args[0],float(pin['capacitance'])) + print(" f: " + f) + print(" cap: " + str(pin['capacitance'])) + try: + fdict[f].append(val) + except KeyError: + fdict[f] = [val] + +for (k,v) in fdict.items(): + print(k,v)