Examples
Sensor board
A regulated board with an MCU and an I2C sensor on it. This is the example to read to understand lowering: what happens between a typed interface and the pads it lands on.
The program
Section titled “The program”sensor_board.py gives the MCU two candidate pin pairs for
its I2C peripheral:
pinmap = PinMap({ "power.vcc": "VDD", "power.gnd": "VSS", "i2c.scl": ["PB8", "PB6"], "i2c.sda": ["PB9", "PB7"],})self.mcu.i2c >> self.imu.i2c is one line, and it has a choice inside it. The
lowering resolves that choice and records a Decision entity naming what it
picked and what it passed over. out/rationale.md is those
decisions, resolved back to the names in the program:
Which pin of
system.mcucarries i2c.scl? →system.mcu.PB8
The pull-ups are on the board rather than in the parts, because on an open-drain
bus they belong to the net. The regulator constraint states the intent
(output_current_max >= 100 * mA) instead of asserting an answer.
What comes out
Section titled “What comes out”7 parts, 4 nets, 71 entities, 11 checks. None failed, four undecided.
The undecided ones are the reason this example exists.
out/checks.txt says why each is undecided, naming the value
that is missing rather than the check that is failing:
UNDECIDED interface_compatibility: voltage domain is undecided on power_input: voltage unknown; on PORT-b2153fd0d1b8Nothing here assumed 3.3 V and moved on. Undecided is a third truth value, and it is the honest one until someone supplies the number.
out/sensor_board.net,out/netlist.txtout/rationale.md: the four lowering decisionsout/graph.txt: 71 entities, 19 of them pins
Running it
Section titled “Running it”fang check examples/sensor_board/sensor_board.pyfang view examples/sensor_board/sensor_board.py interfaces -o interfaces.svgfang build examples/sensor_board/sensor_board.pyThe whole program
Section titled “The whole program”"""A regulated sensor board: a rail, an MCU and an I2C sensor on it.Show 5 more lines
Shows the parts the toolchain is actually for: typed interfaces lowering topins, a decision recorded where the MCU offered a choice and constraints thatstay undecided until someone supplies the missing datasheet number."""
from fang.interfaces import I2CPort, Pin, PinMap, PowerInfrom fang.lang import A, Part, System, V, kHz, kOhm, mA, require, uFfrom fang.parts import Capacitor, Regulator, Resistor
class MCU(Part): """A microcontroller with two possible pin pairs for its I2C peripheral."""
designator_prefix = "U"
power = PowerIn(voltage=3.3 * V, current_demand=80 * mA) i2c = I2CPort( voh_min=2.4 * V, vol_max=0.4 * V, voltage=3.3 * V, bit_rate=400 * kHz, pull_up_resistance=4.7 * kOhm, pull_up_supply=3.3 * V, )
VDD = Pin("VDD", role="power", number="1") VSS = Pin("VSS", role="ground", number="2") PB8 = Pin("PB8", role="clock", number="61") PB9 = Pin("PB9", role="data", number="62") PB6 = Pin("PB6", role="clock", number="58") PB7 = Pin("PB7", role="data", number="59")
pinmap = PinMap( { "power.vcc": "VDD", "power.gnd": "VSS", # Two candidates each: the lowering picks one and records why. "i2c.scl": ["PB8", "PB6"], "i2c.sda": ["PB9", "PB7"], } )
class IMU(Part): """An inertial sensor. Its logic thresholds come from the datasheet."""
designator_prefix = "U"
power = PowerIn(voltage=3.3 * V, current_demand=3 * mA) i2c = I2CPort(vih_min=2.0 * V, vil_max=0.8 * V, voltage=3.3 * V, bit_rate=400 * kHz)
VDD = Pin("VDD", role="power", number="1") GND = Pin("GND", role="ground", number="2") SCL = Pin("SCL", role="clock", number="3") SDA = Pin("SDA", role="data", number="4")
pinmap = PinMap( {"power.vcc": "VDD", "power.gnd": "GND", "i2c.scl": "SCL", "i2c.sda": "SDA"} )
class SensorBoard(System): supply = PowerIn(voltage=5 * V, current_capability=1 * A)
regulator = Regulator( input_voltage_max=17 * V, output_voltage=3.3 * V, output_current_max=1000 * mA, package="SOT-23-5", ) mcu = MCU(package="LQFP-64") imu = IMU(package="LGA-14")
bulk = Capacitor(capacitance=10 * uF, voltage_rating=16 * V, package="C_0805") scl_pullup = Resistor(resistance=4.7 * kOhm, package="R_0402") sda_pullup = Resistor(resistance=4.7 * kOhm, package="R_0402")
def architecture(self): self.supply >> self.regulator.vin self.regulator.vout >> self.mcu.power self.regulator.vout >> self.imu.power self.mcu.i2c >> self.imu.i2c
self.regulator.vout.vcc >> self.bulk.p1 self.regulator.vout.gnd >> self.bulk.p2
# Open drain: without these the bus never comes back up. self.regulator.vout.vcc >> self.scl_pullup.p1 self.scl_pullup.p2 >> self.mcu.i2c.scl self.regulator.vout.vcc >> self.sda_pullup.p1 self.sda_pullup.p2 >> self.mcu.i2c.sda
def constraints(self): require(self.regulator.output_voltage == 3.3 * V) # The regulator must carry both loads. Stated as intent; the kernel # decides once every current figure is known. require(self.regulator.output_current_max >= 100 * mA)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 10 uF Package:C_0805R1 4.7 kOhm Package:R_0402R2 4.7 kOhm Package:R_0402U1 IMU Package:LGA-14U2 MCU Package:LQFP-64U3 Regulator Package:SOT-23-5Net-(C1-Pad1) C1.1 R1.1 R2.1 U1.VDD U2.VDD U3.VOUTNet-(C1-Pad2) C1.2 U1.GND U2.VSS U3.GNDNet-(R1-Pad2) R1.2 U1.SCL U2.PB8Net-(R2-Pad2) R2.2 U1.SDA U2.PB9Every check that ran, and every one left undecided.
UNDECIDED interface_compatibility: current capability is undecided on electrical: current_capability unknownUNDECIDED interface_compatibility: voltage domain is undecided on electrical: voltage unknown; on PORT-c75102d9787dUNDECIDED interface_compatibility: current capability is undecided on power_input: current_demand unknownUNDECIDED interface_compatibility: voltage domain is undecided on power_input: voltage unknown; on PORT-b2153fd0d1b811 checks, 0 failed, 4 undecidedWhat the elaborated graph contains, by entity kind.
1 block 6 component 22 connection 2 constraint 4 decision 4 interface 19 pin 13 port 71 totalsnapshot sha256:8570fadf033061093c00daeccc09e24d78b137a9940d71a7206cb5b7e50ac56cAll of it, including the KiCad netlist, is in
examples/sensor_board/out/. Rebuild it with:
fang build examples/sensor_board/sensor_board.py