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.

Class Is
Module A reusable unit owning parameters, surfaces, constraints and children
System The root module of a design
Part A 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
Domain Literals
Voltage V mV uV kV
Current A mA uA nA
Power W mW
Resistance Ohm mOhm kOhm MOhm
Capacitance F uF nF pF
Inductance H mH uH
Frequency Hz kHz MHz
Time s ms us ns
Length m mm
Other degC 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.

Surface For
Electrical An untyped electrical connection
Power A power connection
Ground A ground connection
Signal A signal connection
Mechanical A 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.