'M' element must have four nodes in NetlistSpiceReaderDelegate::parse_element

Hello,

I'm working on LVS for a set of 3-terminal TFT devices, where the SPICE netlist uses 'M' as the device type (e.g. M1 D G S tft_model W=20u L=8u). This is appropriate for my technology, as there is no bulk terminal (thin film transistor).

I found this earlier discussion from 5 years ago where Matthias provided a NetlistSpiceReaderDelegate subclass with a custom element(...) method that builds a 3-terminal device manually, which makes sense to me.

However, as of v0.30.2, it looks like parse_element(...) is called _before _ element(...), and the default implementation now raises an error if an 'M' device doesn't have exactly four nodes:

https://github.com/KLayout/klayout/blob/5173a2aad77f46a652117028723e99a8f1c4ec93/src/db/db/dbNetlistSpiceReaderDelegate.cc

if (element == "M") {
  if (nn.size () != 4) {
    error (tl::to_string (tr ("'M' element must have four nodes")));
  }
}

So if I override element(...), the script fails earlier inside parse_element(...).

My questions:
Is overriding parse_element(...) now the recommended approach when handling 3-terminal 'M' devices like TFTs? Is there a reference implementation - I'm not so familiar with ruby so wouldn't know where to start!

Or... is the original suggestion in comment 6282 still valid for recent versions?

Thanks as always for the excellent tool! I'm just trying to make sure I'm following the correct conventions now that the parser logic seems to have evolved. Apologies if this should have gone in the original thread!

Comments

  • Are you sure that a different element
    (like JFET) wouldn't do?

    Or something like BSIMSOI rigged for
    fully depleted "bodyless" MOSFET?

    Check out what the CMOS RF switch
    people do.

  • Sigh ... Spice is a mess :(

    "parse_element" is based on the Spice comprehension of ngspice, which requires four terminals for "M" and there are many weird variants - I guess something like M d g s b model=nfet was causing an issue here. But I'd have to dig in the files to find the reason for this change.

    But you can basically reimplement "parse_element" in the SpiceReaderDelegate too. Note that there is a "parse_element_components" function available to simplify the task and you only want to change the way how "M" is parsed. So you could add some code like this to the reader delegate:

      def parse_element(s, element)
         if element != "M"
           super
         else
           data = parse_element_components(s)
           data_out = RBA::ParseElementData::new
           data_out.parameters = data.parameters
           data_out.model_name = data.strings[-1]
           data_out.net_names = data.strings[0..-2]
           return data_out
         end
       end
    

    (Disclaimer: not tested).

    This removes the restriction imposed by the default implementation.

    Matthias

  • I think ideally the "recognition" and "extraction"
    should be so "meta" that an arbitrary bundle of
    polygons can map to an arbitrary device which
    has bound to it an arbitrary netlisting format
    template (name, nodes, model, properties).

    Then this all falls to the user's "technology"
    definitions?

    On the schematic side this is pretty usual.

    I don't understand anything about the layout
    side.

  • Thanks Matthias and everyone.

    I've got it working. The code as you listed worked fine for parse_element. For element() I had to change the logic from if el != "M" || nets.size != 4, but I think that's just a typo in the original.

    The lines are of the kind M2 NET1 NET2 NET3 TFT l=8u w=20u - I think this is Silvaco, so hspice?! Agreed that SPICE is a mess!

    It took me a while to work out how to write the computed layers in a way that satisfies the "gate must be rectangular, sd must not overlap gate", etc. requirements, but I got there in the end and it's now working beautifully - it highlighted a couple of issues which would have been catastrophic! and a couple of lesser issues, but which are still nice to resolve.

    In case it's useful to others, I've left the code below.

    class MOS4To3ConvertingSpiceReader < RBA::NetlistSpiceReaderDelegate
    
      def parse_element(s, element)
         if element != "M"
           super
         else
           data = parse_element_components(s)
           data_out = RBA::ParseElementData::new
           data_out.parameters = data.parameters
           data_out.model_name = data.strings[-1]
           data_out.net_names = data.strings[0..-2]
           return data_out
         end
       end
    
      def element(circuit, el, name, model, value, nets, params)
        if el != "M" || nets.size == 4
          # all other elements are left to the standard implementation
          return super
        end
        # provide a device class
        cls = circuit.netlist.device_class_by_name(model)
        if ! cls
          cls = RBA::DeviceClassMOS3Transistor::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" ].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)
        # TODO: if required, add AS,AD,PS and PD ...
        return true
      end
    
    end
    

    and then during the read, I have

    # Instantiate a reader using the new delegate
    reader = RBA::NetlistSpiceReader::new(MOS4To3ConvertingSpiceReader::new)
    
    # Import the schematic with this reader
    schematic("test_circuit.net", reader)
    
Sign In or Register to comment.