dan-fritchman / Hdl21

Hardware Description Library
BSD 3-Clause "New" or "Revised" License
60 stars 13 forks source link

Calculating default `h.Param` value as function of another #165

Closed kcaisley closed 11 months ago

kcaisley commented 11 months ago

Context

I'm a bit stuck working on a paramclass meant to pass BSIM6 instance parameters to a corresponding ExternalModule for transistors in a PDK.

What I'd like to do

I'm curious if there is a (recommended) way to express the default value of a h.Param, as a function of other parameters, without having to rely on h.Literal strings? To be concrete, let's say we have this paramclass for example:

@h.paramclass
class MosParams:  

    # These instance parameters are used for 'schematic-level' analog design:
    w = h.Param(dtype=h.Scalar, desc="Channel Width (m)", default=100 * n)
    l = h.Param(dtype=h.Scalar, desc="Channel Length (m)", default=30 * n)
    multi = h.Param(dtype=h.Scalar, desc="Multiplier", default=1)
    nf = h.Param(dtype=h.Scalar, desc="Number of fingers", default=1)

    # Additional parameters shouldn't be set by designer
    ad = h.Param(
        dtype=h.Scalar,
        desc="Drain Area",
        default= .....  # <--- my question
    )
    As = h.Param(
        dtype=h.Scalar,
        desc="Source Area",
        default= .....  # <--- my question
    )

# plus another ~30 instance params...

And let's say I know the correct default expression for ad, in terms of width w and finger count nf, is:

ad = w * (7e-9 * (nf % 2) + 1.5e-7 * int(nf / 2)) / nf

Many of the other BSIM6 instance parameters have a similarly complicated expression, so if I use h.Literal(), I think the resulting raw output netlist will become quite convoluted. Instead, I'd like to be able to match the behavior of commercial EDA tools I'm using, which calculate the exact BSIM instance parameters before netlisting. (I'm also not sure if ngspice / spectre simulators even support operators like % ?)

What I've tried so far

  1. I've learned I shouldn't try to place standalone Python expressions in the MosParams class. The decorator function behind @paramclass in params.py gives RuntimeError: Invalid class-attribute 'x' in paramclass <class 'pdk.MosParams'>. All attributes should be 'hdl21.Params'.
  2. And I've learned I also can't place Python expressions the default field of a h.Param. That yields TypeError: unsupported operand type(s) for %: 'Param' and 'Prefixed'
  3. Reading the docstring for def paramclass(cls: Type[T]) -> Type[T], I see: *All* non-Python-internal fields must be of type Param. This, and the errors before, make sense to me as a paramclass is immutable after creation.
  4. As a workaround, I wrote a function to pre-calculate the values for instance parameters that depend on w, l, and nf: def calc_inst_params(w: float, l: float, nf: int) -> h.Prefixed. I suppose I can use it's return values to create instances of MosParams, but this feels inelegant as it sort of overlaps with the defaults functionality already built into Hdl21.

I suspect this could be a justified instance of "You're holding it wrong." If anyone has any guidance, I would really appreciate it. Thanks!

dan-fritchman commented 11 months ago

I think there are a few potential cases there.

  1. If you know the values of all the right-hand-side parameters, calculate the dependent ones in a generator, or any other function that returns a Module or ExternalModule. E.g.
SomeExternalModule = h.ExternalModule(
    paramtype=MosParams, # the `MosParams` class from above
    # ... all the other fields
) 

@h.paramclass
class ParamsYouActuallySpecify:  
    w = ... 
    l = ... 
    # Note "dependent" things like `ad` are not parameters

def something(params: ParamsYouActuallySpecify) -> h.ExternalModule:
  ad = params.w * params.l * whatever_else 
  # ... 
  return SomeExternalModule(ad=ad, ..., **asdict(params))
  1. If you don't know their values - e.g. if they are provided in some external netlist or other file - them h.Literal is the way to tell SPICE to make combinations such as w * l.

  2. You... don't actually need the dependent parameters at all? If you dig into those models, you'll often find this default-value expression is embedded somewhere further down, so long as it's provided with a good value for required parameters such as w and l. It is very unlikely that you'll ever want to set them to anything but that default expression. (In code that you actually write. An extraction program is another matter).

dan-fritchman commented 11 months ago

And separately, while it's different for each simulator, it's pretty common for netlist-language-support to include fairly elaborate mathematical-expressions, such as that % modulo operator, ternary a ? b : c expressions, and in some cases conditionals (if/ else) and function definitions. I.e. everything in here:
https://github.com/dan-fritchman/Netlist/blob/56790e83efd50725f8648f1afa3e3634d49e022d/netlist/data.py#L609

kcaisley commented 11 months ago

Thank you so much for the feedback. I think my use case squarely falls under the Case 1 you outlined. I will try writing a function which computes these other parameters, and reply back with my solution for posterity.

Just in case I missed something, here's how I arrived where I am now:

When I use my PDK's devices to draw a schematic in a commercial tool, I just specify w, l, multi and nf. For example:

circuit

Then, when I convert this schematic to a netlist, I get the following .scs file with additional BSIM6 instance parameters already pre-computed:

subckt inv vdd vin vout vss
    M0 (vout vin vdd vdd) pch_mac l=50n w=400n multi=1 nf=2 sd=100n \
        ad=4e-14 as=5.5e-14 pd=3.2u ps=4.5u nrd=2.391274 nrs=1.391274 \
        sa=339.369n sb=339.369n sa1=235.441n sa2=327.889n sa3=322.265n \
        sa4=128.196n sb1=435.441n sb2=427.889n sb3=422.265n spa=100n \
        spa1=100n spa2=100n spa3=100n sap=232.843n sapb=250.777n \
        spba=215.715n spba1=417.043n dfm_flag=0 spmt=1.21111e+14 spomt=0 \
        spomt1=1.121111e+30 spmb=2.21111e+15 spomb=0 spomb1=2.11111e+30
    M1 (vout vin vss vss) nch_mac l=50n w=400n multi=1 nf=2 sd=100n \
        ad=4e-14 as=5.5e-14 pd=3.2u ps=4.5u nrd=2.391274 nrs=1.391274 \
        sa=339.369n sb=339.369n sa1=235.441n sa2=327.889n sa3=322.265n \
        sa4=128.196n sb1=435.441n sb2=427.889n sb3=422.265n spa=100n \
        spa1=100n spa2=100n spa3=100n sap=232.843n sapb=250.777n \
        spba=215.715n spba1=417.043n dfm_flag=0 spmt=1.21111e+14 spomt=0 \
        spomt1=1.121111e+30 spmb=2.21111e+15 spomb=0 spomb1=2.11111e+30
ends inv

// not real PDK values; don't sue me please!

I hoped (as you mentioned in your Case 3) that I could instead rely on the values of these parameters being determined by the lower-level .scs device macro models imported with include from my PDK. In fact, I did find sections in the macro models which appeared to do that. But when I factored out the additional parameters in my test netlist, like so..

subckt inv vdd vin vout vss
    M0 (vout vin vdd vdd) pch_mac l=50n w=400n multi=1 nf=2
    M1 (vout vin vss vss) nch_mac l=50n w=400n multi=1 nf=2
ends inv

... and re-simulated the circuit, I got different results. This led me to believe that something else (OA cellviews?) was probably auto-magically pre-filling these additional values before writing the netlist.

∴ This led to me figuring out the function between w, l, nf and these other "dependent" values, and then trying to get my Hdl21 PDK package to replicate the same.

dan-fritchman commented 11 months ago

Right, so - there's another layer to dig in then.

I expect I know just which piece of commercial software (which shall not be named) produced this netlist. And it, and similar programs, can in general have custom netlisting routines, transforming a relatively terse set of parameters into that very verbose one.

So the next layer is - what do all those verbose parameters mean?
Generally, they are for what (your) foundry calls "LDE" - layout-dependent effects.
There is not great public info about what the advanced technology LDE's are. Here's an OK-looking summary from AMD:
https://ewh.ieee.org/r5/denver/sscs/Presentations/2009_04_Faricelli.pdf. (Your foundry-provided docs will have more detailed & better versions.)

So, these parameters include things like:

Those questions all effect the device characteristics, and ultimately matter.

But what you should be wondering is: how could you possibly get those values from a schematic?
And the short answer is, you can't.

If you have a layout, or know a priori how you're going to lay the whole circuit out, then yes, you implicitly (or explicitly) know good values for these parameters. (Extraction programs will set them, often plus a bunch more.) I imagine that netlist above is, like, "the values that the default layout p-cells would generate, maybe if placed in an otherwise infinite empty universe, or maybe if placed in whatever we think is like 'the average layout'". I don't know how much better of a guess one could make? Nor have I ever really followed why they insist on putting them there.

So anyway - whatever these values are, don't get too attached to them at the schematic level. If you really believe your design is sufficiently sensitive to them, do some layout and extract it. That time will be better-spent than reverse-engineering any schematic-based estimations!

kcaisley commented 11 months ago

Perfect, the deeper explanation really helped. Your comment that "Extraction programs will set them, often plus a bunch more" was precisely on point.

I've tested the foundry-provided decks from my PDK. When fed through a DRC->LVS->PEX flow, the aforementioned and many additional layout-dependent effect (LDE) parameters are "back-annotated" to the device instances. The delta between having and not having these effects at the schematic-level certainly isn't worth the trouble.

I'll simply follow the mantra "extract early and often".