Package: hdl21
HDL21Analog Hardware Description Library in PythonHdl21 is ahardware description libraryembedded in Python.
It is targeted for analog and custom integrated circuits, and for maximum productivity with minimum fancy-programming skill.ContentsInstallationModulesSignals,Ports, andConnectionsGeneratorsandParametersPrimitive Elements and External ModulesSpice-Class SimulationProcess Technologies (PDKs)BundlesExamplesRelated ProjectsInstallationpip install hdl21That's it. No crazy build step, no crazy dependencies, no crazy EDA stuff, no "clone andjustmodify these 300 things", nosourceing, none of that. Hdl21 is pure Python, and is designed to be as easy to install as any other Python package.ModulesHdl21's primary unit of hardware reuse is theModule. Think of it as Verilog'smodule, or VHDL'sentity, or SPICE'ssubckt. Better yet if you are used to graphical schematics, think of it as the content of a schematic. Hdl21Modulesare containers of a handful ofhdl21types. Think of them as including:Instances of otherModulesConnections between them, defined bySignalsandPortsFancy combinations thereof, covered laterAn exampleModule:importhdl21ashm=h.Module(name="MyModule")m.i=h.Input()m.o=h.Output(width=8)m.s=h.Signal()m.a=AnotherModule()In addition to the procedural-syntax shown above,Modulescan also be defined through aclass-based syntax by applying thehdl21.moduledecorator to a class-definition.importhdl21ash@h.moduleclassMyModule:i=h.Input()o=h.Output(width=8)s=h.Signal()a=AnotherModule()This class-based syntax produces identical results to the procedural code-block above. Its declarative style can be much more natural and expressive in many contexts, especially for designers familiar with popular HDLs.Creation ofModulesignal-attributes is generally performed by the built-inSignal,Port,Input, andOutputconstructors. Each comes with a "plural version" (Input*s*etc.) which creates several identical objects at once:importhdl21ash@h.moduleclassMyModule:a,b=h.Inputs(2)c,d,e=h.Outputs(3,width=16)z,y,x,w=h.Signals(4)SignalsHdl21's primary connection type isSignal. Think of it as Verilog'swire, or a node in that schematic. EachSignalhas an integer-valued buswidthfield, and can be connected to any other equal-widthPort.A subset ofSignalsare exposed outside their parentModule. These externally-connectable signals are referred to asPorts. Hdl21 provides four port constructors:Input,Output,Inout, andPort. The last creates a directionless (or direction unspecified) port akin to those of common spice-level languages.ConnectionsPopular HDLs generally feature one of two forms of connection semantics. Verilog, VHDL, and most dedicated HDLs use "connect by call" semantics, in which signal-objects are first declared, then passed as function-call-style arguments to instances of other modules.modulemy_module();logica,b,c;// Declare signalsanother_modulei1(a,b,c);// Create an instanceanother_modulei2(.a(a),.b(b),.c(c));// Another instance, connected by-nameendmoduleChisel, in contrast, uses "connection by assignment" - more literally using the walrus:=operator. Instances of child modules are created first, and their ports are directly walrus-connected to one another. No local-signal objects ever need be declared in the instantiating parent module.classMyModuleextendsModule{// Create Module Instancesvali1=Module(newAnotherModule)vali2=Module(newAnotherModule)// Wire them directly to one anotheri1.io.a:=i2.io.ai1.io.b:=i2.io.bi1.io.c:=i2.io.c}Each can be more concise and expressive depending on context. Hdl21Modulessupportbothconnect-by-call and connect-by-assignment forms.Connections by assignment are performed by assigning either aSignalor another instance'sPortto an attribute of a Module-Instance.# Create a modulem=h.Module()# Create its internal Signalsm.a,m.b,m.c=h.Signals(3)# Create an Instancem.i1=AnotherModule()# And wire them upm.i1.a=m.am.i1.b=m.bm.i1.c=m.cThis also works without the parent-moduleSignals:# Create a modulem=h.Module()# Create the Instancesm.i1=AnotherModule()m.i2=AnotherModule()# And wire them upm.i1.a=m.i2.am.i1.b=m.i2.bm.i1.c=m.i2.cInstances can instead be connected by call:# Create a modulem=h.Module()# Create the Instancesm.i1=AnotherModule()m.i2=AnotherModule()# Call one to connect themm.i1(a=m.i2.a,b=m.i2.b,c=m.i2.c)These connection-calls can also be performed inline, as the instances are being created.# Create a modulem=h.Module()# Create the Instance `i1`m.i1=AnotherModule()# Create another Instance `i2`, and connect to `i1`m.i2=AnotherModule(a=m.i1.a,b=m.i1.b,c=m.i1.c)These methods hides some of what happens under the hood of HDL21 for ease-of-use. A more thorough method of defining objects, especially inGenerators seen below, leverage endpoints in theModuleandInstanceAPIs:h.Module.addis used to add eitherSignalorInstances instantiated in the usual way and also allows the use of an optionalnamekeyword argument which names the newly added object so it can be accessed using the methods we've already described above.h.Module.getis used to get theSignalorInstancewith a given name from a module via a single argument in string form.h.Instance.connecttakes two arguments, the first a string referring to anInstance's available ports and the second refers to any "connectable" object which can be of the typeSignal,PortRef,SliceorConcat.SlicingSignalobjects are equipped with awidthkeyword argument, which determines the width of a signal bus. This creates a 1D array that can accessed using Python's usual slicing syntax used with lists:sig1=h.Signal(width=12)sig2=h.Input(width=6)# Map sig2 signals to even numbered sig1 signalssig2=sig1[::2]NOTE: the slicing provided works by creating a reference to the underlying signals to be mapped, so at this time can't be used tosetconnections but onlygetconnections. That is, the following will raise an error:sig1=h.Signal(width=12)sig2=h.Input(width=6)# Map sig2 signals to even numbered sig1 signalssig1[::2]=sig2ConcatenationSignal's can be concatenated to make wider signal buses that you can use to interface with between buses of variable width. This is done using theConcatcommand:a=h.Signal()b=h.Signal(width=2)# This is a Concat with two parts# that is resolved into signal bus# with a width of 3.c=h.Concat(a,b)The Concat command can be used with an arbitrary number ofSignals, as well as recursively to create heirarchicalConcatstructures:a=h.Signal()b=h.Signal(width=2)c=h.Signal(width=3)# This is a Concat with three parts# it is resolved to a width-6 busd=h.Concat(a,b,c)# This is a Concat with two parts# with objects 2-part Concat and c# it is flattened to the same width-6 busd=h.Concat(h.Concat(a,b),c)Debugging TipsEachModulehas an attribute calledportsandsignalswhich store what they are labelled respectively. Taking either of these, you can examine individualSignals to see if they've been correctly connected by checking their individual_slices,_concatsand_connected_portsattributes.Whereas,Instancescontain attributesconnswhich list what objects anInstance's ports are connected to and_refswhich keeps track of wherePortRefs for a givenInstanceare being distributed to otherModules andInstances in your program.GeneratorsHdl21Modulesare "plain old data". They require no runtime or execution environment. They can be (and are!) fully represented in markup languages such as ProtoBuf, JSON, and YAML. The power of embeddingModulesin a general-purpose programming language lies in allowing code to create and manipulate them. Hdl21'sGeneratorsare functions which produceModules, and have a number of built-in features to aid embedding in a hierarchical hardware tree.In other words:Modulesare "structs".Generators arefunctionswhich returnModules.Generatorsare code.Modulesare data.Generatorsrequire a runtime environment.Modulesdo not.Creating a generator just requires applying the@hdl21.generatordecorator to a Python function:importhdl21ash@h.generatordefMyFirstGenerator(params:MyParams)->h.Module:# A very exciting first generator functionm=h.Module()m.i=h.Input(width=params.w)returnmThe generator-function body can define aModulehowever it likes - procedurally or via the class-style syntax.@h.generatordefMySecondGenerator(params:MyParams)->h.Module:# A very exciting (second) generator function@h.moduleclassMySecondGen:i=h.Input(width=params.w)returnMySecondGenOr any combination of the two:@h.generatordefMyThirdGenerator(params:MyParams)->h.Module:# Create an internal Module@h.moduleclassInner:i=h.Input(width=params.w)# Manipulate it a bitInner.o=h.Output(width=2*Inner.i.width)# Instantiate that in another Module@h.moduleclassOuter:inner=Inner()# And manipulate that some more tooOuter.inp=h.Input(width=params.w)returnOuterDebugging TipsGenerators when they're called returnGeneratorCalls, which are sufficient to validate them with respect to the rest of circuit, but don't contain the resolvedModulethat you might intuitively expect from the type-hinting. To get at this module the usual procedure is as follows:MyGen=MyGenerator(params)#  Explicitly elaborate your generatorh.elaborate(MyGen)# Extract the resolved Module withinMyGen=MyGen.resultYou can then manipulate thisModuleusing the debugging tips provided above at the end of theSignalsection.ParametersGeneratorsmust take a single argumentparamswhich is a collection ofhdl21.Params. Generator parameters are strongly type-checked at runtime. Each requires a data-typedtypeand description-stringdesc. Optional parameters include a default-value, which must be an instance ofdtype.# Example parameter:nf=h.Param(dtype=int,desc="Number of parallel fingers",default=1)The collections of these parameters used byGeneratorsare called param-classes, and are typically formed by applying thehdl21.paramclassdecorator to a class-body-full ofhdl21.Params:importhdl21ash@h.paramclassclassMyParams:# Requiredwidth=h.Param(dtype=int,desc="Width. Required")# Optional - including a default valuetext=h.Param(dtype=str,desc="Optional string",default="My Favorite Module")Each param-class is defined similarly to the Python standard-library'sdataclass. Theparamclassdecorator converts these class-definitions into type-checkeddataclasses, with fields using thedtypeof each parameter.p=MyParams(width=8,text="Your Favorite Module")assertp.width==8# Passes. Note this is an `int`, not a `Param`assertp.text=="Your Favorite Module"# Also passesSimilar todataclasses, param-class constructors use the field-order defined in the class body. Note Python's function-argument rules dictate that all required arguments be declared first, and all optional arguments come last.Param-classes can be nested, and can be converted to (potentially nested) dictionaries viadataclasses.asdict. The same conversion applies in reverse - (potentially nested) dictionaries can be expanded to serve as param-class constructor arguments:importhdl21ashfromdataclassesimportasdict@h.paramclassclassInner:i=h.Param(dtype=int,desc="Inner int-field")@h.paramclassclassOuter:inner=h.Param(dtype=Inner,desc="Inner fields")f=h.Param(dtype=float,desc="A float",default=3.14159)# Create from a (nested) dictionary literald1={"inner":{"i":11},"f":22.2}o=Outer(**d1)# Convert back to another dictionaryd2=asdict(o)# And check they line upassertd1==d2Generators include the capability to construct their param-classes inline, if provided a set of compatible keyword arguments. For example, defining a generator using theMyParamsparameters above:@h.generatordefMyGen(params:MyParams)->h.Module:...# Create a `Module` & return itThis typical invocation:p=MyParams(width=8,text="My Favorite Module")MyGen(p)is the same as calling:MyParams(width=8,text="My Favorite Module")Parameters may be provided as keywords, or as a single positional argument which is an instance of the generator's param-class. Combinations of the two are not supported.Names of Generated ModulesUsingParamswith aGeneratorwill generally produce a module with a name in the form of{Module_Name}_{long_string}, e.g.NmosIdac_46b3842dc8718a80a86891e28bc798e5_.
This 32-character hex-string is a hash of the parameters. This rule applies when parameters are "compound", i.e. not a simple scalar.In constrast, when theParamsare all-scalar, exported modules are named with a suffixed string of the directly concatenated values, e.g.NmosIdac_nbits_5.A Note on ParametrizationHdl21Generatorshave parameters.Modulesdo not.This is a deliberate decision, which in this sense makeshdl21.Moduleless feature-rich than the analogousmoduleconcepts in existing HDLs (Verilog, VHDL, and even SPICE). These languages support what might be called "static parameters" - relatively simple relationships between parent and child-module parameterization. Setting, for example, the width of a signal or number of instances in an array is straightforward. But more elaborate parametrization-cases are either highly cumbersome or altogether impossible to create. (As an example, try using Verilog parametrization to make a programmable-depth binary tree.) Hdl21, in contrast, exposes all parametrization to the full Python-power of its generators.Numeric ParametersPrefixedNumbersHdl21 provides anSI prefixednumeric typePrefixed, which is especially common for physical generator parameters. EachPrefixedvalue is a combination of the Python standard library'sDecimaland an enumerated SIPrefix:@dataclassclassPrefixed:number:Decimal# Numeric Portionprefix:Prefix# Enumerated SI PrefixMost of Hdl21's built-inGeneratorsandPrimitivesusePrefixedextensively, for a key reason: floating-point rounding. It is commonplace for physical parameter values - e.g. the physical width of a transistor - to haveallowedanddisallowedvalues. And those values do not necessarily land on IEEE floating-point values! Hdl21 generators are often used to produce legacy-HDL netlists and other code, which must convert these values to strings.Prefixedensures a way to do this at arbitrary scale without the possibility of rounding error.Prefixedvalues rarely need to be instantiated directly. Instead Hdl21 exposes a set of common prefixes via their typical single-character names:f=FEMTO=Prefix.FEMTOp=PICO=Prefix.PICOn=NANO=Prefix.NANOµ=u=MICRO=Prefix.MICRO# Note both `u` and `µ` are validm=MILLI=Prefix.MILLIK=KILO=Prefix.KILOM=MEGA=Prefix.MEGAG=GIGA=Prefix.GIGAT=TERA=Prefix.TERAP=PETA=Prefix.PETAUNIT=Prefix.UNITMultiplying by these values produces aPrefixedvalue.fromhdl21.prefiximportµ,n,f# Create a few parameter values using themMos.Params(w=1*µ,l=20*n,)Capacitor.Params(c=1*f,)These multiplications are the easiest and most common way to createPrefixedparameter values.
Note the single-character identifiersµ,n,f, et alare notexported by star-exports (from hdl21 import *). They must be imported explicitly fromhdl21.prefix.hdl21.prefixalso exposes ane()function which returns anExponenttype, which produces a prefix from an integer exponent value:fromhdl21.prefiximporte,µ11*e(-6)==11*µ# TrueThesee()values are also most common in multiplication expressions,
to createPrefixedvalues in "floating point" style such as11 * e(-9).Exponent ArithmeticThePrefixhas its own arithmetic which can be accessed with*,/and**, this allows users to chain together rescaling parameters. Behind the scenes, this is done by converting thePrefix's into anExponenttype (the default type returned by theefunction). For example, the following test passes:deftest_prefix_arithmetic:assert1*m*m==1*uassert1*(K/D)==0.1*Kassert1*(K**2)==1*MExponenttypes support floats and also have arithmetic with*,/and**which works using rules relating power arithmetic to these operators:deftest_exponent_arithmetic:assert1*e(0.5)*e(0.5)==1*e(1)assert1*e(0.5)/e(0.5)==1*e(0)assert1*e(0.2)**5==1*e(1)Mathematical TricksComparison and EqualityPrefixeddetermines if two values are the same by comparing their difference up to 20 decimal places in their SI unit, this means that a yottameter + yoctometer is indistinguishable from a plain yottameter inPrefixedcomparison logic. It's assumed this is safe because numerical errors at this scale difference compound very quickly with routine mathematics like square-roots, even with arbitrary precision arithmetic.At this time, comparison and equality operators are not supported for thePrefixorExponent, since these types describe scale rather than encode actual quantities, a Kilomilliwatt is just a Watt, after all.Scale AgnosticPrefixedin general is scale agnostic and can safely move across scales without any issue, with theprefixserving more as an indication of where the user would like to treat as units than explicit declaration of scale where no comparison can happen.Linear AlgebranumpysupportsPrefixedarrays out of the box, meaning that arrays can be used withPrefixarithmetic and used for simple linear algebra and any operations where anp.floatdtype is required.Zero DivisionDividing aPrefixedinstance by0will returnfloat('inf')regardless of sign.ScalarMany Hdl21 primitive parameters can be either numbers or string-literals.The combination is so common that Hdl21 defines aScalartype which is (roughly):Scalar=Union[Prefixed,Literal]With automatic conversions from each ofstr,int,float, andDecimal.Scalaris particularly designed for parameter-values ofPrimitives and of simulations.
Most such parameters "want" to be thePrefixedtype, for reasons outlinedabove. They often also need a string-valued escape hatch, e.g. when referring to out-of-Hdl21 quantities
such as parameters in external netlists or simulation decks.
These out-of-Hdl21 expressions are represented by theLiteraltype, a simple wrapper aroundstr.Where possibleScalarprefers to use thePrefixedvariant.
Built-in numbers(int, float, Decimal)are converted toPrefixedinline.
Strings are attempted to be converted toPrefixed, and fall back toLiteralif unsuccessful.
This conversion process is also available as the free-standingto_scalar()function.Example:importhdl21ashfromhdl21.prefiximportNANO,µfromdecimalimportDecimal@h.paramclassclassMyMosParams:w=h.Param(dtype=h.Scalar,desc="Width",default=1e-6)# Default `float` converts to a `Prefixed`l=h.Param(dtype=h.Scalar,desc="Length",default="w/5")# Default `str` converts to a `Literal`# Example instantiationsMyMosParams()# Default valuesMyMosParams(w=Decimal(1e-6),l=3*µ)MyMosParams(w=h.Literal("sim_param_width"),l=h.Prefixed.new(20,NANO))MyMosParams(w="11*l",l=11)When defining "primitive level" parameters - e.g. those that will be used in PDK-level devices -Scalaris generally the best datatype to use.Primitives and External ModulesThe leaf-nodes of each hierarchical Hdl21 circuit are generally defined in one of two places:Primitiveelements, defined in thehdl21.primitivespackage. Each is designed to be a technology-independent representation of an irreducible component.ExternalModules, defined outside Hdl21. Such "module wrappers", which might alternately be called "black boxes", are common for including circuits from other HDLs.PrimitivesHdl21's library of generic primitive elements is defined in thehdl21.primitivespackage. Its content is roughly equivalent to that built into a typical SPICE simulator.A summary ofhdl21.primitives:NameDescriptionTypeAliasesPortsMosMos TransistorPHYSICALMOSd, g, s, bIdealResistorIdeal ResistorIDEALR, Res, Resistor, IdealR, IdealResp, nPhysicalResistorPhysical ResistorPHYSICALPhyR, PhyRes, ResPhy, PhyResistorp, nThreeTerminalResistorThree Terminal ResistorPHYSICALRes3, PhyRes3, ResPhy3, PhyResistor3p, n, bIdealCapacitorIdeal CapacitorIDEALC, Cap, Capacitor, IdealC, IdealCapp, nPhysicalCapacitorPhysical CapacitorPHYSICALPhyC, PhyCap, CapPhy, PhyCapacitorp, nThreeTerminalCapacitorThree Terminal CapacitorPHYSICALCap3, PhyCap3, CapPhy3, PhyCapacitor3p, n, bIdealInductorIdeal InductorIDEALL, Ind, Inductor, IdealL, IdealIndp, nPhysicalInductorPhysical InductorPHYSICALPhyL, PhyInd, IndPhy, PhyInductorp, nThreeTerminalInductorThree Terminal InductorPHYSICALInd3, PhyInd3, IndPhy3, PhyInductor3p, n, bPhysicalShortShort-Circuit/Net-TiePHYSICALShortp, nDcVoltageSourceDC Voltage SourceIDEALV, Vdc, Vsrcp, nPulseVoltageSourcePulse Voltage SourceIDEALVpu, Vpulsep, nCurrentSourceIdeal DC Current SourceIDEALI, Idc, Isrcp, nVoltageControlledVoltageSourceVoltage Controlled Voltage SourceIDEALVcvs, VCVSp, n, cp, cnCurrentControlledVoltageSourceCurrent Controlled Voltage SourceIDEALCcvs, CCVSp, n, cp, cnVoltageControlledCurrentSourceVoltage Controlled Current SourceIDEALVccs, VCCSp, n, cp, cnCurrentControlledCurrentSourceCurrent Controlled Current SourceIDEALCccs, CCCSp, n, cp, cnBipolarBipolar TransistorPHYSICALBjt, BJTc, b, eDiodeDiodePHYSICALDp, nEach primitive is available in thehdl21.primitivesnamespace, either through its full name or any of its aliases. Most primitives have fairly verbose names (e.g.VoltageControlledCurrentSource,IdealResistor), but also expose short-form aliases (e.g.Vcvs,R). Each of the aliases in Table 1 above refer tothe samePython object, i.e.fromhdl21.primitivesimportR,Res,IdealResistorRisRes# evaluates to TrueRisIdealResistor# also evaluates to TrueHdl21Primitivescome inidealandphysicalflavors. The difference is most frequently relevant for passive elements, which can for example represent either(a) technology-specific passives, e.g. a MIM or MOS capacitor, or(b) anidealcapacitorSome element-types have solely physical implementations, some are solely ideal, and others include both.ExternalModulesAlternately Hdl21 includes anExternalModuletype which defines the interface to a module-implementation outside Hdl21. These external definitions are common for instantiating technology-specific modules and libraries. Think of them as a module "function header"; other popular modern HDLs refer to them as moduleblack boxes.An exampleExternalModule:importhdl21ashfromhdl21.prefiximportµfromhdl21.primitivesimportDiode@h.paramclassclassBandGapParams:self_destruct=h.Param(dtype=bool,desc="Whether to include the self-destruction feature",default=True,)BandGap=h.ExternalModule(name="BandGap",desc="Example ExternalModule, defined outside Hdl21",port_list=[h.Port(name="vref"),h.Port(name="enable")],paramtype=BandGapParams,)BothPrimitivesandExternalModuleshave names, orderedPorts, and a few other pieces of metadata, but no internal implementation: no internal signals, and no instances of other modules. UnlikeModules, bothdohave parameters.Primitiveseach have an associatedparamclass, whileExternalModulescan optionally declare one via theirparamtypeattribute. Their parameter-types are limited to a small subset of those possible forGenerators- generally "scalar" types such as numbers, strings, andScalar- primarily limited by the need to need to provide them to legacy HDLs. Parameters are applied in the same style as forGenerators, by calling thePrimitiveorExternalModule. Parameter-applications can either be an instance of the module'sparamtypeor a set of keyword arguments which validly construct one inline.# Continuing from the snippet above:params=BandGapParams(self_destruct=False)# Watch out there!PrimitivesandExternalModulescan be instantiated and connected in all the same styles asModules:@h.moduleclassBandGapPlus:vref,enable=h.Signals(2)# Instantiate the `ExternalModule` defined abovebg=BandGap(params)(vref=vref,enable=enable)# ...Anything else...@h.moduleclassDiodePlus:p,n=h.Signals(2)# Parameterize, instantiate, and connect a `primitives.Diode`d=Diode(w=1*µ,l=1*µ)(p=p,n=n)# ... Everything else ...Exporting and ImportingHdl21 generates hardware databases in theVLSIRinterchange formats, defined throughGoogle Protocol Buffers. ThroughVLSIR's Python toolsHdl21 also includes drivers for popular industry-standard data formats and popular spice-class simulation engines.Thehdl21.to_proto()function converts an Hdl21Moduleor group ofModulesinto a VLSIRPackage. Thehdl21.from_proto()function similarly imports a VLSIRPackageinto a Python namespace of Hdl21Modules.Exporting to industry-standard netlist formats is a particularly common operation for Hdl21 users. Thehdl21.netlist()function uses VLSIR to export any of its supported netlist formats.importsysimporthdl21ash@h.moduleclassRlc:p,n=h.Ports(2)res=h.Res(r=1e3)(p=p,n=n)cap=h.Cap(c=1e3)(p=p,n=n)ind=h.Ind(l=1e-9)(p=p,n=n)# Write a spice-format netlist to stdouth.netlist(Rlc,sys.stdout,fmt="spice")hdl21.netlisttakes a second destination argumentdest, which is commonly either an open file-handle orsys.stdout.EachModuleincludes a list ofLiteralcontents, designed to be included directly in exported netlists. These are commonly used to refer to out-of-Hdl21 quantities, or to include netlist-language features not first-class supported by Hdl21. Example:@h.moduleclassHasLiterals:a,b,c=h.Ports(3)# Add some literal contentHasLiterals.literals.extend([h.Literal("generate some_verilog_code"),h.Literal(".some_spice_attribute what=ever"),h.Literal("PRAGMA: some_pragma"),])Module.literalsis a Python built-in list and can be manipulated with any of its typical methods (append,extend, etc.). Literals are written to netlists in the order they appear in the list. Order between Literals and other Module content is not preserved.Spice-Class SimulationHdl21 includes drivers for popular spice-class simulation engines commonly used to evaluate analog circuits.
Thehdl21.simpackage includes a wide variety of spice-class simulation constructs, including:DC, AC, Transient, Operating-Point, Noise, Monte-Carlo, Parameter-Sweep and Custom (per netlist language) AnalysesControl elements for saving signals (Save), simulation options (Options), including external files and contents (Include,Lib), measurements (Meas), simulation parameters (Param), and literal netlist commands (Literal)The entrypoint to Hdl21-driven simulation is the simulation-input typehdl21.sim.Sim. EachSimincludes:A testbench Moduletb, andA list of simulation attributes (attrs), including any and all of the analyses, controls, and related elements listed above.Example:importhdl21ashfromhdl21.simimport*@h.moduleclassMyModulesTestbench:# ... Testbench content ...# Create simulation inputs=Sim(tb=MyModulesTestbench,attrs=[Param(name="x",val=5),Dc(var="x",sweep=PointSweep([1]),name="mydc"),Ac(sweep=LogSweep(1e1,1e10,10),name="myac"),Tran(tstop=11*h.prefix.p,name="mytran"),SweepAnalysis(inner=[Tran(tstop=1,name="swptran")],var="x",sweep=LinearSweep(0,1,2),name="mysweep",),MonteCarlo(inner=[Dc(var="y",sweep=PointSweep([1]),name="swpdc")],npts=11,name="mymc",),Save(SaveMode.ALL),Meas(analysis="mytr",name="a_delay",expr="trig_targ_something"),Include("/home/models"),Lib(path="/home/models",section="fast"),Options(reltol=1e-9),],)# And run it!s.run()Simalso includes a class-based syntax similar toModuleandBundle. This also allows for inline definition of a testbench module, which can be named eithertborTb:importhdl21ashfromhdl21.simimport*@simclassMySim:@h.moduleclassTb:# ... Testbench content ...x=Param(5)y=Param(6)mydc=Dc(var=x,sweep=PointSweep([1]))myac=Ac(sweep=LogSweep(1e1,1e10,10))mytran=Tran(tstop=11*h.prefix.PICO)mysweep=SweepAnalysis(inner=[mytran],var=x,sweep=LinearSweep(0,1,2),)mymc=MonteCarlo(inner=[Dc(var="y",sweep=PointSweep([1]),name="swpdc")],npts=11)delay=Meas(analysis=mytran,expr="trig_targ_something")opts=Options(reltol=1e-9)save_all=Save(SaveMode.ALL)a_path="/home/models"include_that_path=Include(a_path)fast_lib=Lib(path=a_path,section="fast")MySim.run()Note that in these class-based definitions, attributes whose names don't really matter such assave_allabove can benamedanything, but must beassignedinto the class, not just constructed.Class-basedSimdefinitions retain all class members which areSimAttrs and drop all others. Non-SimAttr-valued fields can nonetheless be handy for defining intermediate values upon which the ultimate SimAttrs depend, such as thea_pathfield in the example above.Classes decorated by@simhave a single special required field: a testbench attribute, named eithertborTb, which sets the simulation testbench. A handful of names are disallowed insimclass-definitions, generally corresponding to the names of theSimclass's fields and methods such asattrsandrun.Eachsimalso includes a set of methods to add simulation attributes from their keyword constructor arguments. These methods use the same names as the simulation attributes (Dc,Meas, etc.) but incorporating the python language convention that functions and methods be lowercase (dc,meas, etc.). Example:# Create a `Sim`s=Sim(tb=MyTb)# Add all the same attributes as abovep=s.param(name="x",val=5)dc=s.dc(var=p,sweep=PointSweep([1]),name="mydc")ac=s.ac(sweep=LogSweep(1e1,1e10,10),name="myac")tr=s.tran(tstop=11*h.prefix.p,name="mytran")noise=s.noise(output=MyTb.p,input_source=MyTb.v,sweep=LogSweep(1e1,1e10,10),name="mynoise",)sw=s.sweepanalysis(inner=[tr],var=p,sweep=LinearSweep(0,1,2),name="mysweep")mc=s.montecarlo(inner=[Dc(var="y",sweep=PointSweep([1]),name="swpdc"),],npts=11,name="mymc",)s.save(SaveMode.ALL)s.meas(analysis=tr,name="a_delay",expr="trig_targ_something")s.include("/home/models")s.lib(path="/home/models",section="fast")s.options(reltol=1e-9)# And run it!s.run()Process TechnologiesDesigning for a specific implementation technology (or "process development kit", or PDK) with Hdl21 can use either of (or a combination of) two routes:InstantiateExternalModulescorresponding to the target technology. These would commonly include its process-specific transistor and passive modules, and potentially larger cells, for example from a cell library. Such external modules are frequently defined as part of a PDK (python) package, but can also be defined anywhere else, including inline among Hdl21 generator code.Usehdl21.Primitives, each of which is designed to be a technology-independent representation of a primitive component. Moving to a particular technology then generally requires passing the design through anhdl21.pdk'scompilefunction.Hdl21 PDKs are Python packages which generally include two primary elements:(a) A libraryExternalModulesdescribing the technology's cells, and(b) Acompileconversion-method which transforms a hierarchical Hdl21 tree, mapping generichdl21.Primitivesinto the tech-specificExternalModules.Since PDKs are python packages, using them is as simple as importing them. Hdl21 includes a built-in sample PDK available viahdl21.pdk.sample_pdkwhich includes simulatable NMOS and PMOS transistors. Hdl21's source tree includes three additional PDK packages:PyPiSourceASAP7 Predictive/Academic PDKhttps://pypi.org/project/asap7-hdl21/pdks/Asap7SkyWater 130nm Open-Source PDKhttps://pypi.org/project/sky130-hdl21/pdks/Sky130GlobalFoundries 180nm Open-Source PDKhttps://pypi.org/project/gf180-hdl21/pdks/Gf180Each contain much more detail documentation on their specific installation and use.importhdl21ashimportsky130_hdl21@h.moduleclassSkyInv:""" An inverter, demonstrating using PDK modules """# Create some IOi,o,VDD,VSS=h.Ports(4)p=sky130_hdl21.Sky130MosParams(w=1,l=1)# And create some transistors!ps=sky130_hdl21.primitives.PMOS_1p8V_STD(p)(d=o,g=i,s=VDD,b=VDD)ns=sky130_hdl21.primitives.NMOS_1p8V_STD(p)(d=o,g=i,s=VSS,b=VSS)Process-portable modules instead use Hdl21Primitives, which can be compiled to a target technology:importhdl21ashfromhdl21.prefiximportµfromhdl21.primitivesimportNmos,Pmos,MosVth@h.moduleclassInv:""" An inverter, demonstrating instantiating PDK modules """# Create some IOi,o,VDD,VSS=h.Ports(4)# And now create some generic transistors!ps=Pmos(w=1*µ,l=1*µ,vth=MosVth.STD)(d=o,g=i,s=VDD,b=VDD)ns=Nmos(w=1*µ,l=1*µ,vth=MosVth.STD)(d=o,g=i,s=VSS,b=VSS)Compiling the generic devices to a target PDK then just requires a pass through the PDK'scompile()method:importhdl21ashimportsky130_hdl21sky130_hdl21.compile(Inv)# Produces the same content as `SkyInv` aboveHdl21 includes anhdl21.pdksubpackage which tracks the available in-memory PDKs. If there is a single PDK available, it need not be explicitly imported:hdl21.pdk.compile()will use it by default.importhdl21ashimportsky130# Note this import can be elsewhere in the program, i.e. in a configuration layer.h.pdk.compile(Inv)# With `sky130` in memory, this does the same thing as above.PDK CornersThehdl21.pdkpackage inclues a three-valuedCornerenumerated type and related classes for describing common process-corner variations. In pseudotype-unioncode:Corner = TYP | SLOW | FASTTypical technologies includes several quantities which undergo such variations. Values of theCornerenum can mean either the variations in a particular quantity, e.g. the "slow" versus "fast" variations of a poly resistor, or can just as oftern refer to a set of such variations within a given technology. In the latter caseCornervalues are often expanded by PDK-level code to include each constituent device variation. For examplemy.pdk.corner(Corner.FAST)may expand to definitions of "fast" Cmos transistors, resistors, and capacitors.Quantities which can be varied are often keyed by aCornerType. In similar pseudo-code:CornerType = MOS | CMOS | RES | CAP | ...A particularly common such use case pairs NMOS and PMOS transistors into aCmosCornerPair. CMOS circuits are then commonly evauated at its four extremes, plus their typical case. These five conditions are enumerated in theCmosCornertype:@dataclassclassCmosCornerPair:nmos:Cornerpmos:CornerCmosCorner = TT | FF | SS | SF | FSHdl21 exposes each of these corner-types as Python enumerations and combinations thereof. Each PDK package then defines its mapping from theseCornertypes to the content they include, typically in the form of external files.PDK Installations and SitesMuch of the content of a typical process technology - even the subset that Hdl21 cares about - is not defined in Python. Transistor models and SPICE "library" files, such as those defining thePMOSandNMOSabove, are common examples pertinent to Hdl21. Tech-files, layout libraries, and the like are similarly necessary for related pieces of EDA software. These PDK contents are commonly stored in a technology-specific arrangement of interdependent files. Hdl21 PDK packages structure this external content as aPdkInstallationtype.EachPdkInstallationis a runtime type-checked Pythondataclasswhich extends the basehdl21.pdk.PdkInstallationtype. Installations are free to define arbitrary fields and methods, which will be type-validated for eachInstallinstance. Example:""" A sample PDK package with an `Install` type """frompydantic.dataclassesimportdataclassfromhdl21.pdkimportPdkInstallation@dataclassclassInstall(PdkInstallation):"""Sample Pdk Installation Data"""model_lib:Path# Filesystem `Path` to transistor modelsThe name of each PDK's installation-type is by conventionInstallwith a capital I. PDK packages which include an installation-type also conventionally include anInstallinstance namedinstall, with a lower-case i. Code using the PDK package can then refer to the PDK'sinstallattribute. Extending the example above:""" A sample PDK package with an `Install` type """@dataclassclassInstall(PdkInstallation):"""Sample Pdk Installation Data"""model_lib:Path# Filesystem `Path` to transistor modelsinstall:Optional[Install]=None# The active installation, if anyThe content of this installation data varies from site to site. To enable "site-portable" code to use the PDK installation, Hdl21 PDK users conventionally define a "site-specific" module or package which:Imports the target PDK moduleCreates an instance of itsPdkInstallationsubtypeAffixes that instance to the PDK package'sinstallattributeFor example:# In "sitepdks.py" or similarimportmypdkmypdk.install=mypdk.Install(models="/path/to/models",path2="/path/2",# etc.)These "site packages" are namedsitepdksby convention. They can often be shared among several PDKs on a given filesystem. Hdl21 includes one built-in example such site-package,SampleSitePdks, which demonstrates setting up both built-in PDKs, Sky130 and ASAP7:# The built-in sample `sitepdks` packagefrompathlibimportPathimportsky130_hdl21sky130_hdl21.install=sky130_hdl21.Install(model_lib=Path("pdks")/"sky130"/.../"sky130.lib.spice")importasap7_hdl21asap7_hdl21.install=asap7_hdl21.Install(model_lib=Path("pdks")/"asap7"/.../"TT.pm")"Site-portable" code requiring external PDK content can then refer to the PDK package'sinstall, without being directly aware of its contents.
An example simulation usingmypdk's models with thesitepdks defined above:# sim_my_pdk.pyimporthdl21ashfromhdl21.simimportLibimportsitepdksas_# <= This sets up `mypdk.install`importmypdk@h.simclassSimMyPdk:# A set of simulation input using `mypdk`'s installationtb=MyTestBench()models=Lib(path=mypdk.install.models,# <- Heresection="ss")# And run it!SimMyPdk.run()Note thatsim_my_pdk.pyneed not necessarily import or directly depend uponsitepdksitself. So long assitepdksis imported and configures the PDK installation anywhere in the Python program, further code will be able to refer to the PDK'sinstallfields.Creating PDK PackagesHdl21's source tree includes acookiecutter templatefor creating a new PDK package, available atpdks/PdkTemplate.BundlesHdl21Bundles arestructured connection typeswhich can includeSignals and instances of otherBundles.
Think of them as "connection structs". Similar ideas are implemented by Chisel'sBundles and SystemVerilog'sinterfaces.@h.bundleclassDiff:p=h.Signal()n=h.Signal()@h.bundleclassQuadrature:i=Diff()q=Diff()LikeModules,Bundles can be defined either procedurally or as a class decorated by thehdl21.bundlefunction.# This creates the same stuff as the class-based definitions above:Diff=h.Bundle(name="Diff")Diff.add(h.Signal(name="p"))Diff.add(h.Signal(name="n"))Quadrature=h.Bundle(name="Quadrature")Quadrature.add(Diff(name="i"))Quadrature.add(Diff(name="q"))Calling aBundleas in the calls toDiff()andDiff(name=q)creates an instance of thatBundle.Bundle PortsBundles are commonly most valuable for shipping collections of relatedSignals betweenModules.
Modules can accordingly have Bundle-valued ports. To create a Bundle-port, set theportargument to either the booleanTrueor thehdl21.Visibility.PORTvalue.@h.moduleclassHasDiffs:d1=Diff(port=True)d2=Diff(port=h.Visbility.PORT)Port directions on bundle-ports can be set by either of two methods.
The first is to set the directions directly on the Bundle's constituentSignals.
To swap directions, pass the bundle-instances through thehdl21.flippedfunction,
or set theflippedargument to the instance-constructor.@h.bundleclassInner:i=h.Input()o=h.Output()@h.bundleclassOuter:b1=Inner()b2=h.flipped(Inner())b3=Inner(flipped=True)Here:AnInnerbundle defines anInputand anOutputAnOuterbundle instantiates three of themInstanceb1is not flipped; itsiis an input, and itsois an outputInstanceb2is flipped; itsiis anoutput, and itsois aninputInstanceb3is also flipped, via its constructor argumentThese "flipping based" bundles require that all constituent signals, including nested ones, have port-visibility.
The rules for flipping port directions are:Inputs become OutputsOutputs become InputsInouts and undirected ports (direction=NONE) retain their directions@h.bundleclassB:clk=h.Output()data=h.Input()@h.moduleclassX:# Module with a `clk` output and `data` inputb=B(port=True)@h.moduleclassY:# Module with a `clk` input and `data` outputb=B(flipped=True,port=True)@h.moduleclassZ:b=B()# Internal instance of the `B` bundlex=X(b=b)y=Y(b=b)The second method for setting bundle-port directions is withRoles.
Each Hdl21 bundle either explicitly or implicitly defines a set ofRoles, which might alternately be called "endpoints".
These are the expected "end users" of the Bundle.
Signal directions are then defined on each signal'ssrc(source) anddest(destination) arguments, which can be set to any of the bundle's roles.@h.rolesclassHostDevice(Enum):HOST=auto()DEVICE=auto()@h.bundleclassJtag:roles=HostDevice# Set the bundle's roles# Note each signal sets one of the roles as `src` and another as `dest`tck,tdi,tms=h.Signals(3,src=roles.HOST,dest=roles.DEVICE)tdo=h.Signal(src=roles.DEVICE,dest=roles.HOST)Bundle-valued ports are then assigned a role and associated signal-port directions via theirroleconstructor argument.@h.moduleclassWidget:# with a Jtag Device portjtag=Jtag(port=True,role=Jtag.roles.DEVICE)@h.moduleclassDebugger:# with a Jtag Host portjtag=Jtag(port=True,role=Jtag.roles.HOST)@h.moduleclassSystem:# combining the twowidget=Widget()debugger=Debugger(jtag=widget.jtag)The rules for port-directions of role-based bundles are:If the bundle's role is the signal's source, the signal is anOutputIf the bundle's role is the signal's destination, the signal is anInputOtherwise the signal is assigned no direction, i.e.direction=NONECollecting BundlesIt is often helpful or necessary to collect existing signals into a bundle, or to "re-arrange" signals from one bundle into another.
The primary mechanism for doing so is thehdl21.bundlizefunction which creates them.
Each call tobundlizecreates an "anonymous" bundle which lacks a backing bundle-definition type.@h.bundleclassUart:tx=h.Output()rx=h.Input()@h.moduleclassHasUart:# Module with a `Uart` portuart=Uart(port=True)@h.moduleclassConnectTwo:# Connect two `HasUart`s, swapping `tx` and `rx`.uart=Uart()m1=HasUart(uart=uart)m2=HasUart(uart=h.bundlize(tx=uart.rx,rx=uart.tx))ExamplesBuilt-In Examples LibraryHdl21's source tree includes a built-inexampleslibrary. Each is designed to be a straightforward but realistic use-case, and is a self-contained Python program which can be run directly, e.g. with:pythonexamples/rdac.pyThe built-in examples include:Current DACMOS Transistor SimulationRing Oscillator GeneratorResistor-Ladder DACRecursive Binary to One-Hot EncodersReading, copying, or cloning these example programs is generally among the best ways to get started.And adding an example is ahighlyencouraged form ofpull request!Featured Community ExamplesContinuous-Time Delta-Sigma ADC GeneratorsUSB 2.0 PHYVCO-Based ADC Generators(Coming soon!)Related ProjectsVLSIRHdl21 SchematicsLayout21Why Use Python?Custom IC design is a complicated field. Its practitioners have to knowa|lot|of|stuff,
independent of any programming background. Many have little or no programming experience at all. Python is renowned for its accessibility to new programmers, largely attributable to its concise syntax, prototyping-friendly execution model, and thriving community. Moreover, Python has also become a hotbed for many of the tasks hardware designers otherwise learn programming for: numerical analysis, data visualization, machine learning, and the like.Hdl21 exposes the ideas they're used to -Modules,Ports,Signals- via as simple of a Python interface as it can.Generatorsare just functions. For many, this fact alone is enough to create powerfully reusable hardware.WhyNotUse {X}?We know you have plenty of choice when you fly, and appreciate you choosing Hdl21.A few alternatives and how they compare:SchematicsGraphical schematics have been the lingua franca of the custom-circuit field for, well, as long as it's been around. Most practitioners are most comfortable in this graphical form. (For plenty of circuits, so are Hdl21's authors.) Their most obvious limitation is the lack of capacity for programmable manipulation via something like Hdl21Generators. Some schematic-GUI programs attempt to include "embedded scripting", perhaps even in Hdl21's own language (Python). We see those GUIs as entombing your programs in their badness. Hdl21 is instead a library, designed to be used by any Python program you like, sharable and runnable by anyone who has Python. (Which is everyone.)Netlists (Spice et al)Take all of the shortcomings listed for schematics above, and add to them an under-expressive, under-specified, ill-formed, incomplete suite of "programming languages", and you've got netlists. Their primary redeeming quality: existing EDA CAD tools take them as direct input. So Hdl21 Modules export netlists of most popular formats instead.(System)Verilog, VHDL, other Existing Dedicated HDLsThe industry's primary, 80s-born digital HDLs Verilog and VHDL have more of the good stuff we want here - notably an open, text-based format, and a more reasonable level of parametrization. And they have the desirable trait of being primary input to the EDA industry's core tools. They nonetheless lack the levels of programmability present here. And they generally require one of those EDA tools to execute and do, well, much of anything. Parsing and manipulating them is well-reknowned for requiring a high pain tolerance. Again Hdl21 sees these as export formats.ChiselExplicitly designed for digital-circuit generators at the same home as Hdl21 (UC Berkeley), Chisel encodes RTL-level hardware in Scala-language classes. It's the closest of the alternatives in spirit to Hdl21. (And it's aways more mature.) If you want big, custom, RTL-level circuits - processors, full SoCs, and the like - you should probably turn to Chisel instead. Chisel makes a number of decisions that make it less desirable for custom circuits, and have accordingly kept their designers' hands-off.The Chisel library's primary goal is producing a compiler-style intermediate representation (FIRRTL) to be manipulated by a series of compiler-style passes. We like the compiler-style IR (and may some day output FIRRTL). But custom circuits really don't want that compiler. The point of designing custom circuits is dictating exactly what comes out - the compileroutput. The compiler is, at best, in the way.Next, Chisel targetsRTL-levelhardware. This includes lots of things that would need something like a logic-synthesis tool to resolve to the structural circuits targeted by Hdl21. For example in Chisel (as well as Verilog and VHDL), it's semantically valid to perform an operation likeSignal + Signal. In custom-circuit-land, it's much harder to say what that addition-operator would mean. Should it infer a digital adder? Short two currents together? Stick two capacitors in series?
Many custom-circuit primitives such as individual transistors actively fight the signal-flow/RTL modeling style assumed by the Chisel semantics and compiler. Again, it's in the way. Perhaps more important, many of Chisel's abstractions actively hide much of the detail custom circuits are designed to explicitly create. Implicit clock and reset signals serve as prominent examples.Above all - Chisel is embedded in Scala. It's niche, it's complicated, it's subtle, it requires dragging around a JVM. It's not a language anyone would recommend to expert-designer/novice-programmers for any reason other than using Chisel. For Hdl21's goals, Scala itself is Chisel's biggest burden.Other Fancy Modern HDLsThere are lots of other very cool hardware-description projects out there which take Hdl21's big-picture approach - embedding hardware idioms as a library in a modern programming language. All focus on logical and/or RTL-level descriptions, unlike Hdl21's structural/custom/analog focus. We recommend checking them out:SpinalHDLMyHDLMigen/nMigenMagmaPyMtl/PyMtl3ClashDevelopmentClone this repository & navigate to it.bash scripts/install-dev.sh. See the note below.pytest -sshould yield something like:$ pytest -s
============================ test session starts =============================
platform darwin -- Python 3.10.0, pytest-7.1.2, pluggy-1.0.0
plugins: anyio-3.5.0, cov-3.0.0
collected 126 items

hdl21/pdk/test_pdk.py ...
hdl21/pdk/sample_pdk/test_sample_pdk.py ...
hdl21/sim/tests/test_sim.py .........s
hdl21/tests/test_builtin_generators.py ..
hdl21/tests/test_bundles.py ............
hdl21/tests/test_conns.py ..............
hdl21/tests/test_exports.py x............
hdl21/tests/test_hdl21.py ...............x..............x...x........x...
hdl21/tests/test_params.py .....x
hdl21/tests/test_prefix.py ..........
hdl21/tests/test_source_info.py .
pdks/Asap7/asap7/test_asap7.py .
pdks/Sky130/sky130/test_sky130.py ....

================= 119 passed, 1 skipped, 6 xfailed in 0.55s ==================Note: Hdl21 is commonly co-developed with theVLSIRinterchange formats.
Thescriptsfolder includes two short installation scripts which install VLSIR from either PyPi or GitHub. Tweaks to PyPi-released versions Hdl21 may be able to use the PyPi versions of VLSIR. Most Hdl21 development cannot, and should clone VLSIR from GitHub. Theinstall-devscript will install VLSIR alongsideHdl21/, i.e. in the parent directory of the Hdl21 clone.