Combining fingered MOS devices / LVS of combined MOS devices

Hello everyone,

Question first: How can I perform an LVS check of MOS transistors while treating fingered devices as equal to combined devices?

A while ago I started using KLayout LVS for verifying the output of the LibreCell standard-cell generator. Now I'm stuck with the following easy problem: The extracted netlist seems to have devices combined, while the reference netlist has not. This makes the LVS check fail. I tried to simplify() the reference netlist. But this has no effect while on the extracted netlist this works. (Maybe because S/D don't match in the reference netlist?)

A working solution is to not combine the devices in the extracted netlist. However, I would like to find a more generic approach because the layout generator may or may not decide to split or combine transistors.

This is how the netlists look:

LVS extracted netlist:
circuit INVX4 (A=A,GND=GND,Y=Y,VDD=VDD);
  device PMOS $1 (S=Y,G=A,D=VDD) (L=0.05,W=2,AS=0.45,AD=0.3525,PS=4.9,PD=2.75);
  device NMOS $3 (S=Y,G=A,D=GND) (L=0.05,W=1,AS=0.20125,AD=0.20125,PS=2.325,PD=2.325);
end;


LVS reference netlist:
circuit INVX4 (VDD=VDD,GND=GND,Y=Y,A=A);
  device PMOS '0' (S=Y,G=A,D=VDD) (L=0.05,W=1,AS=0,AD=0,PS=0,PD=0);
  device PMOS '1' (S=VDD,G=A,D=Y) (L=0.05,W=1,AS=0,AD=0,PS=0,PD=0);
  device NMOS '2' (S=Y,G=A,D=GND) (L=0.05,W=0.5,AS=0,AD=0,PS=0,PD=0);
  device NMOS '3' (S=GND,G=A,D=Y) (L=0.05,W=0.5,AS=0,AD=0,PS=0,PD=0);
end;

Here is the layout:

Thanks in advance!

Comments

  • Hi Thomas,

    my suggestion would be to combine the devices on the reference netlist before compare. So the "normalized" way is to see the devices as combined ones. At least for compare.

    I don't know if there is a reason to keep devices separate in the schematic. Maybe for convenience or maybe for simulation. Or it's just a matter of how the netlists are written.

    In the extracted netlist, device combination is part of the "simplify" scheme. In the reference netlist, I'd not use "simplify" because it has side effects such as introducing pins you might not want to see. But you can use "combine_devices" on the reference netlist to achieve the normalization to singe devices:

    schematic.combine_devices
    

    (Caution: badly documented feature - "schematic" without arguments returns the schematic Netlist object)

    Best regards,

    Matthias

  • Thanks Matthias for answering!

    Unfortunately, in my case combine_devices()` seems to have no effect on the reference netlist. But on the extracted netlist it does.
    I'm using the Python API.

    The following code shows how I extract the netlist (all the connectivity setup is omitted) and how I compare the netlists. This is the version that works as long as both netlists have exactly the same transistors. It is sensitive to transistor folding/combining though.

    The reference.combine_devices() somehow does not combine anything. Could it be that combine_devices() is not aware that it is allowed to swap source and drain? I'm also using a custom db.NetlistSpiceReaderDelegate implementation for reading 4-terminal MOS transistors as 3-terminal transistors. Maybe I'm doing something wrong there.


    def extract_netlist(layout: db.Layout, top_cell: db.Cell) -> db.Netlist: """ Extract a device level netlist of 3-terminal MOSFETs from the cell `top_cell` of layout `layout`. :param layout: Layout object. :param top_cell: The top cell of the circuit. :return: Netlist as a `klayout.db.Circuit` object. """ ... # Many things omitted here for brevity. netlist = l2n.netlist() netlist.make_top_level_pins() netlist.purge() # netlist.combine_devices() netlist.purge_nets() # netlist.simplify() assert netlist.top_circuit_count() == 1, "A well formed netlist should have exactly one top circuit." return netlist.dup() def compare_netlist(extracted: db.Netlist, reference: db.Netlist) -> bool: """ Check if two netlists are equal. :param extracted: :param reference: :return: Returns True iff the two netlists are equivalent. """ ... # reference.combine_devices() # Seems to have no effect. # extracted.combine_devices() cmp = db.NetlistComparer() compare_result = cmp.compare(extracted, reference) ... return compare_result

    This is what I use to read the netlist.

    class MOS4To3NetlistSpiceReader(db.NetlistSpiceReaderDelegate):
        """
        Read SPICE netlists and convert 4-terminal MOS into 3-terminal MOS by dropping the body net.
        This is required for the LVS step when the standard cells are lacking well taps and therefore
        the body terminal of the transistors is unconnected.
        """
    
        def element(self, circuit: db.Circuit, el: str, name: str, model: str, value, nets: List[db.Net],
                    params: Dict[str, float]):
            """
            Process a SPICE element. All elements except 4-terminal MOS transistors are left unchanged.
            :return: True iff the device has not been ignored and put into the netlist.
            """
            if el != 'M' or len(nets) != 4:
                # All other elements are left to the standard implementation.
                return super().element(circuit, el, name, model, value, nets, params)
            else:
                # Provide a device class.
                cls = circuit.netlist().device_class_by_name(model)
                if not cls:
                    # Create MOS3Transistor device class if it does not yet exist.
                    cls = db.DeviceClassMOS3Transistor()
                    cls.name = model
                    circuit.netlist().add(cls)
    
                # Create MOS3 device.
                device: db.Device = circuit.create_device(cls, name)
                # Configure the MOS3 device.
                for terminal_name, net in zip(['S', 'G', 'D'], nets):
                    device.connect_terminal(terminal_name, net)
    
                # Parameters in the model are given in micrometer units, so
                # we need to translate the parameter values from SI to um values.
                device.set_parameter('W', params.get('W', 0) * 1e6)
                device.set_parameter('L', params.get('L', 0) * 1e6)
    
                return True
    
    def read_netlist_mos4_to_mos3(netlist_path: str) -> db.Netlist:
        """
        Read a SPICE netlist and convert all MOS4 transistors to MOS3 transistors.
        """
        netlist = db.Netlist()
        netlist.read(netlist_path, db.NetlistSpiceReader(MOS4To3NetlistSpiceReader()))
        return netlist
    

    The full file can be found here: https://codeberg.org/tok/librecell/src/commit/0632cf17429c37b152f1b1f356e13c5a762a921a/librecell-layout/lclayout/lvs/lvs.py

    Thanks a lot for your help!

  • edited June 13

    Hi Thomas,

    Hmm ... I don't see an obvious reason it shouldn't work ...

    To start with debugging, I can give a positive example. I do the following using your Spice reader delegate class and the function:

    nl = read_netlist_mos4_to_mos3("test.cir")
    
    print("Before combine_devices:")
    print(str(nl))
    
    nl.combine_devices()
    
    print("After combine_devices:")
    print(str(nl))
    

    With this netlist in "test.cir":

    .SUBCKT TOP 1 2 4 7
    M1 6 4 7 7 MHVPMOS L=0.25U W=1.5U
    M2 7 4 6 7 MHVPMOS L=0.25U W=1.5U
    .ENDS TOP
    

    I get the following results:

    Before combine_devices:
    circuit TOP ('1'='1','2'='2','4'='4','7'='7');
      device MHVPMOS '1' (S='6',G='4',D='7') (L=0.25,W=1.5,AS=0,AD=0,PS=0,PD=0);
      device MHVPMOS '2' (S='7',G='4',D='6') (L=0.25,W=1.5,AS=0,AD=0,PS=0,PD=0);
    end;
    
    After combine_devices:
    circuit TOP ('1'='1','2'='2','4'='4','7'='7');
      device MHVPMOS '1' (S='6',G='4',D='7') (L=0.25,W=3,AS=0,AD=0,PS=0,PD=0);
    end;
    

    So basically the function should work.

    One requirement is that the transistor lengths are identical. So this will not combine the devices:

    .SUBCKT TOP 1 2 4 7
    * These devices will not be combined!
    M1 6 4 7 7 MHVPMOS L=0.25U W=1.5U
    M2 7 4 6 7 MHVPMOS L=0.251U W=1.5U
    .ENDS TOP
    

    Maybe this is the case here?

    Matthias

  • Thanks for the example! I will try this.
    The transistor length are all equal.

  • Hi Matthias,

    I found out what causes my problem but I don't know yet what happens exactly.

    In the example above I forgot about a detail: The netlist I load contains possibly many circuits but I'm interested only in one. The extracted layout also contains only one circuit of a standard-cell. Therefore I isolate a single circuit and put it into a new netlist. This is what seems to make combine_devices() fail.

    The simplest solution I found is to run combine_devices() on the full netlist before isolating the circuit.

    Is there a better way how to do this? Ideally I would not have to care about how the reference netlist object was created.

    Here is an example to reproduce the behaviour.

    import klayout.db as db
    netlist_path = 'INVX4_simple.sp'
    
    netlist = db.Netlist()
    netlist.read(netlist_path, db.NetlistSpiceReader())
    
    circuit = netlist.circuit_by_name('INVX4')
    
    # Create a netlist which contains only the circuit of interest.
    sub_netlist = db.Netlist()
    sub_netlist.add(circuit)
    
    #sub_netlist = netlist
    
    # `combine_devices()` now does not do anything.
    print("Before combine_devices:")
    print(sub_netlist)
    sub_netlist.combine_devices()
    print("After combine_devices:")
    print(sub_netlist)
    
    
    *
    .subckt INVX4 vdd gnd Y A
    M0 Y A vdd vdd pmos w=1u l=0.05u
    + ad=0p pd=0u as=0p ps=0u
    M1 Y A vdd vdd pmos w=1u l=0.05u
    + ad=0p pd=0u as=0p ps=0u
    M2 Y A gnd gnd nmos w=0.5u l=0.05u
    + ad=0p pd=0u as=0p ps=0u
    M3 Y A gnd gnd nmos w=0.5u l=0.05u
    + ad=0p pd=0u as=0p ps=0u
    .ends INVX4
    

    Thanks so much! :)

  • Hi Thomas,

    sorry for this long delay - I was entirely absorbed by this nasty MacOS issue: https://github.com/KLayout/klayout/issues/589 ...

    Regarding your question: adding a foreign Circuit into a new Netlist is not supported I'm afraid. A circuit will carry more references, namely into subcircuits and device classes. When you add a device into a different netlist, these references are not translated and will point to the old netlist. This makes combine_devices fail.

    But there is a very simple solution to combine_devices on a single circuit: simply use Circuit#combine_devices :)

    circuit = netlist.circuit_by_name('INVX4')
    
    print("Before combine_devices:")
    print(netlist)
    
    circuit.combine_devices()
    
    print("After combine_devices:")
    print(netlist)
    

    If you have to extract a circuit from a netlist, the only way currently is to deleted every other circuit you don't need :(

    Best regards,

    Matthias

  • Hello Matthias!
    No problem ! :smiley:
    Thanks for the hint! Just changed my code and now do it that way by deleting all non-used circuits. Works fine! :)

    Bests,
    Thomas

Sign In or Register to comment.