Skip to content
copperhead.sh
Get started

Reference

Language

The authoring surface, in full.

A Fang program is ordinary Python. What makes it a design is the declarations in a class body, which the kernel collects in the order they were written.

ClassIs
ModuleA reusable unit owning parameters, surfaces, constraints and children
SystemThe root module of a design
PartA leaf module that becomes a component rather than a block
from fang.lang import Part, System, V, kOhm, require
class Board(System):
...

A Module is instantiated with keyword overrides, as in Regulator(output_voltage=5 * V). A Part also accepts package=.

class Board(System):
def architecture(self):
"""Connections. Runs first."""
def constraints(self):
"""require() calls. Runs second."""

Both are optional. architecture() runs before constraints(), so a constraint may refer to anything the architecture created.

Parameter(unit, *, default=None, description="")

A parameter is declared with its unit. A bare number is not a parameter. The unit is not decoration. It is what makes the value checkable.

class Regulator(GenericPart):
output_voltage = Parameter("V", description="the rail it makes")

Unit literals multiply a number into a Quantity:

3.3 * V 10 * kOhm 100 * uF 1.25 * MHz 10 * ms
DomainLiterals
VoltageV mV uV kV
CurrentA mA uA nA
PowerW mW
ResistanceOhm mOhm kOhm MOhm
CapacitanceF uF nF pF
InductanceH mH uH
FrequencyHz kHz MHz
Times ms us ns
Lengthm mm
OtherdegC percent

Two helpers build non-scalar quantities:

between(3.0 * V, 3.6 * V) # a range, optional typical= third argument
tolerance(10 * kOhm, "1%") # a nominal and a tolerance

Combining unequal dimensions raises UNIT-0001 at the point the expression is constructed.

A surface is somewhere a connection can land.

SurfaceFor
ElectricalAn untyped electrical connection
PowerA power connection
GroundA ground connection
SignalA signal connection
MechanicalA non-electrical attachment

Typed interface ports are surfaces too, and they carry more: their signals, their parameters and their rules.

self.top.p2 >> self.bottom.p1 # the connect operator
connect(self.top.p2, self.bottom.p1) # the same thing, spelled out

>> is not directional in the electrical sense. It reads left to right and records a connection between two surfaces. A connection with no kind is ELAB-0005.

def constraints(self):
require(self.regulator.output_voltage == 3.3 * V)
require(self.led.forward_current <= 20 * mA)

require() records the expression; it does not evaluate it there. Evaluation happens against a snapshot, over interval arithmetic, with an unknown operand producing undecided.

class MCU(Part):
i2c = I2CPort(voltage=3.3 * V)
PB8 = Pin("PB8", role="clock")
PB9 = Pin("PB9", role="data")
pinmap = PinMap({"i2c.scl": "PB8", "i2c.sda": "PB9"})

In Pin(name, *, role="unknown", number=None), number records the physical pin number when it differs from the name.

A PinMap value is one pin name or a list of candidates. Candidates are what make a recorded decision possible.

Roles are drawn from a fixed set: power, ground, clock, data, control, reset, differential_p, differential_n, analog, phase, unknown.

def __init__(self, **overrides):
super().__init__(**overrides)
self.controller.select(
"Texas Instruments", "TPS62130RGTR",
distributor_ids={"digikey": "296-35088-1-ND"},
datasheet="SRC-DS-TPS62130",
evidence=("absolute_maximum",),
)

select() attaches sourcing to the instance, not the class template. The part’s logical identity is unchanged: the design still says “a 3.3 V regulator” and separately says which one was bought. part.selected reports whether one was.

def architecture(self):
layout = tools.layout(placers=["pyplacer"], routers=["freerouting"])
report = tools.check(profile="jlcpcb-2layer")
tools.export(layout=layout, format="kicad", require=report.passed)

Every tools.* call records an operation in the plan and returns a handle. Nothing runs during elaboration. Reading a handle is ELAB-0008, whether you branch on it, compare it or convert it to a boolean. report.passed is a symbolic condition resolved in the operation phase.

Calls take named arguments only. A positional argument is rejected, because a stored and replayed plan cannot depend on argument order.

Declared in the class body. See Rationale for the full set: Requires, Assumes, Cites, Chooses, Calculates, Verifies.