Examples
I2C bus
One controller and three targets on a shared I2C bus. A bus is multi-drop, so the same port is connected three times and the lowering resolves it into one net per signal, not three point-to-point links.
The program
Section titled “The program”i2c_bus.py keeps two things a netlist could not hold.
The addresses are parameters, so uniqueness is a constraint the kernel decides, not a rule inside a linter:
address = Parameter("", description="7-bit I2C address")...require(self.temperature.address != self.memory.address)The unread datasheet is recorded as unread. TempSensor and EEPROM cite
their thresholds with Cites(...). The RTC does not have them, and says so:
thresholds = Assumes( "The RTC accepts 3.3 V CMOS levels", rationale="every part in this family does; not yet read off the datasheet",)An assumption is not a value. The compatibility check over the RTC’s link stays undecided, which is the state that gets someone to open the datasheet.
What comes out
Section titled “What comes out”9 parts, 4 nets, 98 entities, 39 checks. None failed, seven undecided.
Six of those seven come from one missing pair of numbers: vih_min and
voh_min on the RTC’s I2C port, the only device on the bus whose datasheet
nobody has read. The seventh is the board’s supply, which states no current
demand. The program predicted both, and the checks found them.
out/i2c_bus.net,out/netlist.txtout/checks.txt: 39 checks, seven of them undecidedout/rationale.md: the address requirement, and the two datasheet claims that are cited
Four devices, one bus. The interfaces view draws each device’s link to the controller. The parts that carry no interface, the pull-ups and the bulk cap, go below the rule, because in this view nothing connects to them.
Running it
Section titled “Running it”fang check examples/i2c_bus/i2c_bus.pyfang view examples/i2c_bus/i2c_bus.py interfaces -o interfaces.svgThe whole program
Section titled “The whole program”"""One controller and three targets on a shared I2C bus.Show 8 more lines
A bus is a multi-drop interface, so the same port is connected several times andthe lowering resolves it into one net per signal. Two things the graph keeps thata netlist cannot: the addresses are parameters, so "no two devices answer to thesame address" is a constraint the kernel decides rather than a rule in a linter;and the RTC's logic thresholds are recorded as an assumption rather than asnumbers, which leaves the check over that link undecided instead of passed."""
from fang.interfaces import I2CPort, Pin, PinMap, PowerIn, PowerOutfrom fang.lang import ( Parameter, Part, System, UnitLiteral, V, kHz, kOhm, mA, require, uA, uF,)from fang.parts import Capacitor, Resistorfrom fang.rationale import Assumes, Cites, Requires
#: Dimensionless, for the things on a board that are counts rather than measures.addr = UnitLiteral("1")
class Target(Part): """What every device on this bus has in common: a rail, a bus, an address."""
designator_prefix = "U"
address = Parameter("", description="7-bit I2C address")
power = PowerIn(voltage=3.3 * V) i2c = I2CPort(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 TempSensor(Target): power = PowerIn(voltage=3.3 * V, current_demand=1 * mA) # On an open-drain bus the high level is the pull-up's, not the device's; # what the device owes the bus is vol_max, and what it needs is vih_min. i2c = I2CPort( voh_min=2.9 * V, vol_max=0.4 * V, vih_min=2.1 * V, vil_max=0.8 * V, voltage=3.3 * V, bit_rate=400 * kHz, )
thresholds = Cites( "VIH is 0.7 x VDD and VIL is 0.3 x VDD", document="SRC-DS-TMP102", locator="table 7.5, electrical characteristics", )
class EEPROM(Target): power = PowerIn(voltage=3.3 * V, current_demand=5 * mA) i2c = I2CPort( voh_min=2.9 * V, vol_max=0.4 * V, vih_min=2.0 * V, vil_max=0.8 * V, voltage=3.3 * V, bit_rate=400 * kHz, )
thresholds = Cites( "VIH is 2.0 V minimum over the 1.7-5.5 V supply range", document="SRC-DS-24AA02", locator="table 1-2, DC characteristics", )
class RTC(Target): """The part nobody has read the datasheet for yet.Show 5 more lines
Its thresholds are absent rather than guessed, so the compatibility check over its bus link is undecided. That is the honest answer, and it is the one that gets someone to open the datasheet. """
power = PowerIn(voltage=3.3 * V, current_demand=100 * uA)
thresholds = Assumes( "The RTC accepts 3.3 V CMOS levels", rationale="every part in this family does; not yet read off the datasheet", )
class Controller(Part): designator_prefix = "U"
power = PowerIn(voltage=3.3 * V, current_demand=40 * mA) i2c = I2CPort( voh_min=2.9 * V, vol_max=0.4 * V, vih_min=2.0 * V, vil_max=0.9 * V, voltage=3.3 * V, bit_rate=400 * kHz, pull_up_resistance=2.2 * kOhm, pull_up_supply=3.3 * V, )
VDD = Pin("VDD", role="power", number="1") VSS = Pin("VSS", role="ground", number="2") PB6 = Pin("PB6", role="clock", number="58") PB7 = Pin("PB7", role="data", number="59")
pinmap = PinMap( {"power.vcc": "VDD", "power.gnd": "VSS", "i2c.scl": "PB6", "i2c.sda": "PB7"} )
class PowerHeader(Part): """Where the 3V3 rail arrives, so the rail is a net and not a promise."""
designator_prefix = "J"
dc = PowerOut(voltage=3.3 * V, current_capability=500 * mA)
VCC = Pin("VCC", role="power", number="1") GND = Pin("GND", role="ground", number="2")
pinmap = PinMap({"dc.vcc": "VCC", "dc.gnd": "GND"})
class SensorBus(System): supply = PowerIn(voltage=3.3 * V, current_capability=500 * mA)
unique_addresses = Requires( "No two devices on the bus answer to the same address", validation="analysis", )
header = PowerHeader(package="PinHeader_1x02_P2.54mm") controller = Controller(package="LQFP-64") temperature = TempSensor(address=0x48 * addr, package="SOT-563") memory = EEPROM(address=0x50 * addr, package="SOT-23-5") clock = RTC(address=0x68 * addr, package="SOIC-8")
# 2.2k at 400 kHz: the bus is short and the capacitance is low, so the # smaller of the two usual values wins the rise-time argument. scl_pullup = Resistor(resistance=2.2 * kOhm, package="R_0402") sda_pullup = Resistor(resistance=2.2 * kOhm, package="R_0402") bulk = Capacitor(capacitance=1 * uF, voltage_rating=16 * V, package="C_0603")
def architecture(self): self.supply >> self.header.dc for device in (self.controller, self.temperature, self.memory, self.clock): self.header.dc >> device.power
# One port connected three times: the bus is multi-drop, so this is a # bus with four members and not three point-to-point links. self.controller.i2c >> self.temperature.i2c self.controller.i2c >> self.memory.i2c self.controller.i2c >> self.clock.i2c
self.header.dc.vcc >> self.scl_pullup.p1 self.scl_pullup.p2 >> self.controller.i2c.scl self.header.dc.vcc >> self.sda_pullup.p1 self.sda_pullup.p2 >> self.controller.i2c.sda
self.header.dc.vcc >> self.bulk.p1 self.header.dc.gnd >> self.bulk.p2
def constraints(self): # Address uniqueness, written in the language rather than built into a # checker: an address is a parameter, so this is a constraint like any # other and it is decided the same way. require(self.temperature.address != self.memory.address) require(self.temperature.address != self.clock.address) require(self.memory.address != self.clock.address)
# Open-drain: the pull-up sets the rise time and the sink current both. require(self.scl_pullup.resistance <= 4.7 * kOhm) require(self.scl_pullup.resistance >= 1 * kOhm) require(self.sda_pullup.resistance <= 4.7 * kOhm) require(self.sda_pullup.resistance >= 1 * kOhm)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 1 uF Package:C_0603J1 PowerHeader Package:PinHeader_1x02_P2.54mmR1 2.2 kOhm Package:R_0402R2 2.2 kOhm Package:R_0402U1 RTC Package:SOIC-8U2 Controller Package:LQFP-64U3 EEPROM Package:SOT-23-5U4 TempSensor Package:SOT-563Net-(C1-Pad1) C1.1 J1.VCC R1.1 R2.1 U1.VDD U2.VDD U3.VDD U4.VDDNet-(C1-Pad2) C1.2 J1.GND U1.GND U2.VSS U3.GND U4.GNDNet-(R1-Pad2) R1.2 U1.SCL U2.PB6 U3.SCL U4.SCLNet-(R2-Pad2) R2.2 U1.SDA U2.PB7 U3.SDA U4.SDAEvery check that ran, and every one left undecided.
UNDECIDED interface_compatibility: logic high margin is undecided on i2c: vih_min unknownUNDECIDED interface_compatibility: logic high margin is undecided on i2c: voh_min unknownUNDECIDED interface_compatibility: logic high margin is undecided on i2c: voh_min unknownUNDECIDED interface_compatibility: logic high margin is undecided on i2c: voh_min unknownUNDECIDED interface_compatibility: logic high margin is undecided on i2c: vih_min unknownUNDECIDED interface_compatibility: logic high margin is undecided on i2c: vih_min unknownUNDECIDED interface_compatibility: current capability is undecided on power_input: current_demand unknown39 checks, 0 failed, 7 undecidedWhat the elaborated graph contains, by entity kind.
1 assumption 1 block 8 component 34 connection 7 constraint 2 evidence 4 interface 24 pin 16 port 1 requirement 98 totalsnapshot sha256:9e4a535546786b3ae49fa860a97230058fdc09d65224a7af83ce69fbf4f5973dAll of it, including the KiCad netlist, is in
examples/i2c_bus/out/. Rebuild it with:
fang build examples/i2c_bus/i2c_bus.py