KLayout 0.29.8 (2024-10-28 63dd591e5) [master]

LVS Input/Output

LVS (and also DRC as far as netlist extraction is concerned) provides interfaces to write and read netlists/schematics, annotated layout and LVS results. There are three major categories of I/O:

Writing netlists

You can write a netlist file to supply netlists for (functional) simulators for example. Within LVS scripts, the global "target_netlist" statement triggers writing of a netlist (see target_netlist for details).

target_netlist("output.cir", write_spice, "Created by KLayout")

This statement can basically appear anywhere in the LVS script. The netlist will written after the script has executed successfully. The first argument is the file's path (by default relative to the original layout file). The second argument is the "writer". "write_spice" creates a netlist writer writing SPICE format with a limited degree of flexbility. See below for customizing the writer. The third argument finally is an (optional) comment which will be written into the netlist as a header.

The "write_spice" configuration function has two options:

write_spice(use_net_names, with_comments)

Both options are boolean values. If true and present, the first option will make the writer use the real net's names instead of numerical IDs. If true and present, "with_comments" will embed debug comments into the netlist showing instance locations, pin names etc.

Further customization can be achieved by providing an explicit SPICE writer with a delegate (see NetlistSpiceWriterDelegate). For doing so, subclass NetlistSpiceWriterDelegate and reimplement one or several of the methods provided for reimplementation. Those are NetlistSpiceWriterDelegate#write_device, NetlistSpiceWriterDelegate#write_device_intro and NetlistSpiceWriterDelegate#write_header.

Here is an example that supplied subcircuit models rather than device elements:

# Write extracted netlist to extracted.cir using a special
# writer delegate

# This delegate makes the writer emit subcicuit calls instead of 
# standard elements for the devices
class SubcircuitModels < RBA::NetlistSpiceWriterDelegate

  def write_header
    emit_line(".INCLUDE 'models.cir'")
  end

  def write_device(device)
    str = "X" + device.expanded_name
    device_class = device.device_class
    device_class.terminal_definitions.each do |td|
      str += " " + net_to_string(device.net_for_terminal(td.id))
    end
    str += " " + device_class.name
    str += " PARAMS:"
    device_class.parameter_definitions.each do |pd|
      str += " " + pd.name + ("=%.12g" % device.parameter(pd.id))
    end
    emit_line(str)
  end      

end

# Prepare a writer using the new delegate
custom_spice_writer = RBA::NetlistSpiceWriter::new(SubcircuitModels::new)
custom_spice_writer.use_net_names= true
custom_spice_writer.with_comments = false

# The declaration of netlist production using the new custom writer
target_netlist("extracted.cir", custom_spice_writer, "Extracted by KLayout")

This script will produce the following netlist for the simple inverter from the LVS introduction. Instead of printing "M" elements - which is the default - subcircuit calls are produced. This allows putting more elaborate models into subcircuits. The device class name addresses these model subcircuits:

* Extracted by KLayout
.INCLUDE 'models.cir'

.SUBCKT INVERTER
X$1 VDD IN OUT NWELL PMOS PARAMS: L=0.25 W=1.5 AS=0.675 AD=0.675 PS=3.9 PD=3.9
X$2 VSS IN OUT SUBSTRATE NMOS PARAMS: L=0.25 W=0.9 AS=0.405 AD=0.405 PS=2.7
+ PD=2.7
.ENDS INVERTER

Netlists can be written directly from the netlist object. Within the script, the netlist object can be obtained with the netlist function. This function will first trigger a netlist extraction unless this was done already and return a Netlist object. Use Netlist#write to write this netlist object then. Unlike "target_netlist", this method is executed immediately and this way, a single netlist can be written to multiple files in different flavours.

Reading netlists

The main use case for reading netlists is for comparison in LVS. Reference netlists are read with the "schematic" function (see schematic):

schematic("inverter.cir")

Currently SPICE is understood with some limitations: Only a subset of elements is implemented by default. These are "M" (gives "MOS4" device classes), "Q" (gives BJT3 or BJT4 device classes), "R" (gives Resistor device classes), "C" (gives Capacitor device classes) and "D" (gives diode device classes).

As for the SPICE reader, a delegate can be provided to customize the reader. For doing so, subclass the NetlistSpiceReaderDelegate class and reimplement the methods provided. These are: NetlistSpiceReaderDelegate#wants_subcircuit, NetlistSpiceReaderDelegate#element, NetlistSpiceReaderDelegate#finish and NetlistSpiceReaderDelegate#start

This example customizes a reader to pull MOS devices from subcircuit models rather than from "M" elements. Basically this customization does the opposite part of the writer customization before (only for MOS devices).

# Provides a SPICE netlist reader delegate which turns
# some subcircuit models (for subcircuits NMOS and PMOS)
# into devices

class SubcircuitModelsReader < RBA::NetlistSpiceReaderDelegate

  # implements the delegate interface:
  # says we want to catch these subcircuits as devices
  def wants_subcircuit(name)
    name == "NMOS" || name == "PMOS"
  end

  # implements the delegate interface: 
  # take and translate the element
  def element(circuit, el, name, model, value, nets, params)

    if el != "X"
      # all other elements are left to the standard implementation
      return super
    end

    if nets.size != 4
      error("Subcircuit #{model} needs four nodes")
    end

    # provide a device class
    cls = circuit.netlist.device_class_by_name(model)
    if ! cls
      cls = RBA::DeviceClassMOS4Transistor::new
      cls.name = model
      circuit.netlist.add(cls)
    end

    # create a device
    device = circuit.create_device(cls, name)

    # and configure the device
    [ "S", "G", "D", "B" ].each_with_index do |t,index|
      device.connect_terminal(t, nets[index])
    end

    # 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["W"] || 0.0) * 1e6)
    device.set_parameter("L", (params["L"] || 0.0) * 1e6)

    return true

  end

end

# Instantiate a reader using the new delegate
reader = RBA::NetlistSpiceReader::new(SubcircuitModelsReader::new)

# Import the schematic with this reader
schematic("inv_xmodels.cir", reader)

Layout-to-Netlist database/report

The layout-to-netlist database (L2N DB) is written using the global report_netlist function. This function can be put anywhere in the script. Writing will happen after the script executed successfully:

report_netlist("extracted.l2n")

Without the filename, only the netlist browser will be opened but no file will be written. The layout-to-netlist database is a KLayout-specific format. It contains the netlist information plus the shape and instance information from the layout. L2N databases can be read into the netlist browser for example. Hence exchange of extracted netlists is possible.

Layout-vs-Schematic database/report

The Layout-vs-schematic database (LVS DB) is written using the global report_lvs function. This function can be put anywhere in the script. Writing will happen after the script executed successfully:

report_lvs("extracted.lvsdb")

Without the filename, only the netlist browser will be opened but no file will be written. The LVS database is a KLayout-specific format. It contains the extracted netlist information, the reference netlist and the cross-reference table. LVS databases can be read into the netlist browser for example. Hence exchange of LVS reports is possible.