The .falcad project format — version 1
Status: draft, implemented by @falcad/format 0.1. Normative JSON Schemas are generated into ../schema from the zod definitions in ../src; those definitions are the single source of truth.
Goals
- Open and human-reviewable. Plain UTF-8 JSON and source files, readable in a code review and diffable in Git or Mercurial.
- Deterministic. Saving an unchanged model produces byte-identical files (canonical JSON, see below).
- Stable identity. Every entity has a random UUID v4 that survives edits, renames, re-ordering and merges. Numeric or sequential IDs are never used.
- Exact geometry. Lengths are integer nanometres.
- Safe to open. A project is untrusted input. Loaders bound file sizes, reject paths outside the project and never execute content.
- Versioned. Each structured document carries
schema+version. Older versions migrate forward through registered steps. Newer versions are refused rather than silently damaged.
Layout
A project is a directory with one entry file:
blinky/
blinky.falcad ← manifest (entry file)
falcad.lock ← pinned engines/toolchains/libraries
schematics/main.sch.json ← falcad.schematic
pcb/main.pcb.json ← falcad.board
simulation/main.sim.json ← falcad.simulation
assembly/robot.asm.json ← falcad.assembly (3D system assembly)
firmware/src/blinky.ino ← plain source
rtl/counter.v ← plain source (HDL)
scripts/led_bar.py ← plain source (circuit script)
libraries/project.library.json ← falcad.library
results/ ← generated output, Git-ignored
Only the file names inside the manifest are significant. The folder names above are conventions.
A packed project (.falcadpack) is optional and a ZIP archive of the same files with exactly one top-level .falcad entry. Packing is deterministic: entries are sorted and timestamps are fixed. Unpacking enforces an entry-count limit, a size limit per file and in total, and the path rules below.
Manifest (*.falcad)
| Field | Type | Notes |
|---|---|---|
format |
"falcad" |
|
formatVersion |
1 |
|
id |
UUID v4 | Project identity. |
name, description |
string | |
displayUnits |
mm | mil | in |
Display preference only; storage is nm. |
generator |
string? | Last writer, diagnostics only. |
documents[] |
entry | { id, kind, name, path, … }. kind is one of schematic, board, simulation, assembly, firmware (+language, target), hdl (+language, role), script (+language, target, params, top), notes, asset. |
libraries[] |
{ name, path } |
Project-local component libraries. |
toolchains[] |
{ id, version } |
Exact toolchain pins for reproducible builds. |
A structured document’s id must equal its manifest entry’s id.
Script documents (circuits-as-code)
A script entry is a plain source file that generates a schematic:
| Field | Type | Notes |
|---|---|---|
language |
python |
javascript |
target |
UUID v4? | The schematic document the script generates and updates. Absent until the first run creates it. |
params |
`{ [name]: string | number |
top |
string? | HDL scripts: top module (default: the only module no other module instantiates). |
Running a script produces a Circuit Description (parts, nets, no-connect pins; schema falcad.circuit, published by @falcad/script as circuit.schema.json). A reconciler turns it into an ordinary transaction on the target schematic. Generated components carry the reserved properties falcad.key (the stable part key from the script), falcad.links (ids of the stub wires, labels, power symbols and no-connect markers generated for each pin) and, on generated power symbols, falcad.owner; re-running a script reuses those entities, so component ids, board links and hand-made placement survive. Loaders never execute scripts: running one is always an explicit user action.
Paths
Paths are relative to the manifest’s directory and use /. These are rejected: empty segments, ., .., a leading /, \, : (so no drive letters), control characters, * ? " < > |, Windows device names (con, nul, com1, …), and segments ending in a dot or a space. On disk the loader also resolves symlinks and refuses anything that resolves outside the project root.
Lockfile (falcad.lock)
{ format: "falcad.lock", version: 1, entries: [{ kind, id, version, sha256?, source? }] }, where kind is one of engine, toolchain, library, model or extension. Tools write it and Git reviews it. It is how a project rebuilds with exactly the toolchain it was built with, instead of whatever happens to be installed.
Common conventions
- Lengths are integer nanometres (
1 mm = 1 000 000,1 mil = 25 400). The default schematic grid is 50 mil (1 270 000 nm). - Rotation: schematic symbols use 0/90/180/270. Board footprints and pads use any angle in degrees, counter-clockwise.
- Coordinates: +x is right. Schematic and board space use +y down, matching screen and Gerber-viewer conventions after export transforms. Symbol and footprint definitions use the same orientation.
- Engineering quantities in simulation documents and component values are strings such as
10k,4k7,100nFor1meg. In component markingsMmeans mega andmmeans milli.fis femto, while a capitalFis the farad unit. Fields with typed metadata are checked against their physical quantity, see Quantities. - Field lengths are not limited in the schema. Size is bounded per file (64 MiB) and per archive.
Schematic (falcad.schematic v1)
Top-level entity collections: sheets, components, wires, junctions, labels, noConnects, notes, ercSuppressions. Every entity except sheets and suppressions carries sheetId.
- Component:
reference,symbol {library, name},at,rotation,mirror,value, optionalfootprint, free-formproperties, and optionalsim {exclude, params}. The meaning ofvalueand of eachsim.paramsentry comes from the symbol’s typed metadata (see Library). Documents always load whatever text they hold. Text that does not fit its field is reported by ERC (invalid-value) and by the inspector, never rejected on load. - Wire: a polyline (
points[], at least 2). - Connectivity is geometric. Two items connect when a wire vertex, a pin connection point or a label anchor coincides exactly with another, or lies on a wire segment.
- Label: has a
scopeoflocal(same sheet) orglobal(whole design). Power symbols (power: truein the library) name a net by the componentvalue. A power symbol whose symbol has primitivepoweralso carries a rail voltage insim.params.voltage. Netlists still name the net by the value. - ERC suppression: needs a
rule,targetsand a non-emptyrationale. Silent suppression is not representable.
Board (falcad.board v1)
stackup[]lists copper layers (F.Cu,In1.Cu…In30.Cu,B.Cu) with their thickness.outlineis a list of polygons: the first is the outer edge, the rest are cut-outs.- Collections are
footprints(optionally linked to a schematiccomponentId),tracks,viasandzones. Copper items carry net names taken from the schematic netlist. rulesholds the board’s design-rule minimums.
Simulation (falcad.simulation v1)
analyses[]:op,tran {stop, step, method, initialState?, fourier?},dc {source, start, stop, step},ac {source, start, stop, pointsPerDecade, noise?},logic {stop}.probes[]: a voltage probe on a net name, or a current probe through a component.assertions[]: regression bounds{analysis, probe, at, min, max}, evaluated after every run.settings(optional):temperature, the circuit temperature in degrees Celsius (default 27).
Optional analysis fields. They were added in format 1 without a version change; older readers that reject unknown keys reject documents that use them.
-
sweep(onop,tran,dc,ac): one of{kind: "param", component, param, values}{kind: "param", component, param, start, stop, points, scale: "lin" | "log"}{kind: "temperature", values}(°C)
The analysis runs once per value.
paramnames a simulation parameter, orvaluefor the component value. -
monteCarlo(on the same kinds):{runs, seed?, distribution: "uniform" | "gaussian", tolerance?, tolerances: [{component, param, tolerance}]}. A tolerance is"5%"or a fraction. Gaussian uses σ = tolerance/3. Withouttolerance, the runs vary only the listed parameters and components with atolerancesimulation parameter; with it, every resistor, capacitor and inductor value varies too.seedis up to 32 hex digits; without it the engine draws one and records it. -
fourier(ontran):{fundamental, harmonics = 9, cycles = 1, probes?}. Fourier / THD of the lastcyclesperiods. -
noise(onac):{output, reference?}. Output noise density at a net, and input-referred noise through the AC source. -
initialState(ontran):"op"(default) or"zero"(start with capacitors discharged).
Assembly (falcad.assembly v1)
A project-level 3D scene: boards of the project and mechanical bodies placed in space, and the external wiring between them.
- Frame: right-handed, +Z up, integer nanometres.
units(mm|cm|in) is a display preference only. - Transform: every instance has
position(nm) androtation(Euler degrees), relative to itsparentinstance or to the assembly origin. Rotations are applied about the parent’s fixed axes in the order X, then Y, then Z (extrinsic X-Y-Z, R = Rz·Ry·Rx, three.js Euler order"ZYX"). World = parentWorld · (R · p + position). Parents must not form a cycle. - Board instance frame = the board’s fabrication frame, shared with the PCB 3D mesh: X = x − outline min x, Y = outline max y − y, Z = 0 at the bottom surface and the board thickness at the top.
instances[](discriminated bykind):board {board}: a board document of the project (by id); optional maskcolor.body {geometry, color?, material, terminals[]}:geometry.shapeisbox,cylinder,plate(with throughholes[]),wheel(axis local Y),standoff(hexagonal, axis +Z),bracket(L angle),battery {cell, cells, cellVoltage?}(built-in terminals+,-),motor(axis local Y, built-in terminals1,2) orasset {path, format: glb|gltf|stl|obj, unit, scale}naming anassetdocument.terminals[]adds named contacts{name, at, role: positive|negative|signal|passive, voltage?}.group: an empty node for grouping.- All kinds:
id,name,parent?,position,rotation,visible,locked?.
connectors[]:{board, footprint, label?, mate?, pins[{pin, pad, name?}]}marks a board footprint as an external connector. A connector pin is a pad number unlesspinsmaps it.wires[]:{fromInstance, fromFootprint?, fromPin, toInstance, toFootprint?, toPin}. On a board instance an end is a connector footprint (its id in the board document) and pin; on a body it is a terminal name. Pluscolor(IEC 60757 name) and optionalstripe,gauge({unit: "awg", value}or{unit: "mm2", value}),insulation, optional declaredcurrent(A),route[](intermediate control points, assembly frame),bendRadius(corner radius of the routed path),slack(% added for the cut list),harness?,fromTerminal?/toTerminal?(crimp contacts, for the cable BOM),label?(signal name).harnesses[]:{name, sleeveDiameter, sleeve, color?, route[]}. Members are the wires whoseharnessis the harness id (membership is stored once, on the wire). A member wire without its own route follows the harness route.bookmarks[]: camera views{name, eye, target, projection}.settings: snapgrid(nm) andangleStep(degrees).
Footprints on boards may carry model3d {path, format, unit, scale, offset, rotation}: a user model (GLB / glTF / STL / OBJ asset document) that replaces the parametric body in 3D. Binary models are stored as base64 text in an asset document whose mediaType ends in ;base64 (for example model/gltf-binary;base64).
Library (falcad.library v1)
Symbols have pins with electrical types, which ERC uses. Their graphics are polyline, rect, circle, arc or text. defaultValue is the value of a newly placed component. The optional value states what the value means (added in format 1, see below). An optional sim mapping names a solver primitive and its terminal order:
pinMap: symbol pin numbers in primitive terminal order. A pin may repeat; for example, a 3-pin MOSFET ties its body to the source.params: typed simulation parameters (see below). They are the single definition of each parameter’s label, quantity, range and default.defaults: untyped default texts of libraries written beforeparams. Readers merge them underparamsfor keys no typed parameter or preset covers (simParamDefaults). Writers useparams.- With primitive
subcircuit(multi-part ICs):parts: [{name, primitive, pins, params?}]lists internal instances. Each pin is a package pin number or an internal net written$name. Parameter precedence, lowest first: the symbol’s parameter defaults, the partparams, component params, then component params prefixedpart.(for exampleA.gbw). supply: {vcc, gnd}names the pins whose voltages set the levels of a digital IC in mixed-signal simulation.
The primitive list (SimPrimitiveSchema) is additive. It covers passive parts, sources, diodes / LEDs, BJTs, MOSFETs, JFETs, controlled sources, op-amps, comparators, the 555, regulators, magnetics, switches, relays, instruments, microcontrollers (mcu_avr, arduino_uno), logic gates and ICs, and power (power symbols, see below).
Value metadata (SymbolDef.value)
Added in format 1 without a version change. Readers that reject unknown keys reject libraries that use it.
kind |
Fields | Meaning |
|---|---|---|
quantity |
quantity, min?, max?, exclusiveMin?, allowNegative?, allowInfinite?, label? |
A physical value such as a resistance or a voltage. Bounds are in SI base units. |
netName |
label? |
The name of the net (power symbols). Not empty, no whitespace or control characters. |
enum |
options, custom?, label? |
One of the options. An option is a string or {value, label?, description?, params?}. With params it is a model preset: when the value selects it (case-insensitive), its params apply over the symbol’s parameter defaults. A value that matches no option keeps the preset of defaultValue. custom: true allows any other text (custom part names). |
text |
label? |
Free text (part numbers, labels). |
Without value, power symbols read as netName and other symbols as text. label names the value in editors (for example “Amplitude”). Without it editors use the quantity’s name. The default value stays in defaultValue (one source), and it must be valid for the value metadata.
Simulation parameters (SymbolDef.sim.params)
Each entry is {name, label, kind, description?, default?, advanced?, when?} plus fields for its kind:
kind |
Fields | Stored text |
|---|---|---|
quantity |
quantity and the same bounds as quantity values |
Engineering notation, unit-checked (see Quantities). |
enum |
options |
One option value. |
boolean |
true or false (editors also accept on / off, yes / no, 1 / 0). |
|
text |
Any text. | |
table |
columns (quantities) |
Rows of one value per column, separated by whitespace, commas or semicolons, for example PWL points 0 0, 1m 5. |
default is the text used when a component does not set the parameter. Without it the parameter is unset (the engine’s own fallback or none). Every default must be valid for its own metadata, and parameter names must be unique. advanced parameters are collapsed in editors. when: {param, values} shows a parameter only while another parameter has one of the values (pulse timing while waveform is pulse).
Power symbols: a non-ground power symbol has primitive power and a voltage quantity parameter. Its default follows the net name where the name states a voltage (+5V → 5, +3V3 → 3.3). VCC has no default. With a voltage, the symbol is an ideal supply between its net and ground in simulation, one per net. A net that another element already drives (a source, a regulator output) gets no second source. Power symbols that disagree on one net’s voltage are an error. Ground is the 0 V reference and has no voltage parameter.
Quantities
The quantity table (QUANTITIES in src/quantity.ts) is the single definition of each quantity’s SI unit, accepted spellings and display:
| Quantity | Unit | Also accepted | Negative by default |
|---|---|---|---|
resistance |
Ω | ohm, ohms, R (10R, 4R7, R47) |
no |
capacitance |
F | no | |
inductance |
H | no | |
voltage |
V | volt, volts |
yes |
current |
A | amp, amps |
yes |
frequency |
Hz | no | |
time |
s | sec |
no |
power |
W | no | |
temperature |
°C | ℃, degC, C (no prefixes) |
yes |
conductance |
S | mho, ℧ |
yes |
transconductance |
A/V² | A/V2, A/V^2 |
no |
per_voltage |
V⁻¹ | 1/V, /V, V^-1 |
no |
slew_rate |
V/s | V/ms, V/us, V/µs, V/ns |
no |
energy |
eV | no | |
length |
m | no | |
ratio |
(none) | % (50% = 0.5), no prefixes |
no |
dimensionless |
(none) | yes |
Parsing (parseQuantity) accepts plain and signed numbers (5, -10, +3.3, 1e-3, the typographic minus), prefixes (f p n u µ m k M meg G T, M is mega and m milli), infix notation (4k7), the field’s unit with or without a space (5V, 5 V), unit letters in any case when unambiguous (5v, 100nf), and inf / ∞ only where allowInfinite is set. Everything else is rejected with a reason: not a number, an unknown unit (with the valid leading value as a suggestion, '5vdc' → 5 V), a unit of another quantity ('10mA' is a current), negative, below the minimum or above the maximum.
Stored text. A value keeps the designer’s notation (4k7, 2k2, 100nF, 5 V, trimmed) whenever the lenient legacy parser parseSiValue reads it as the same value, so every FalCAD reader agrees on it. Anything else (5v, −10 with a typographic minus, 100nf, 50%, 0.5V/us) is stored in canonical form: compact engineering notation without the unit, ASCII, 12 significant digits, with meg for mega and u for micro so SPICE reads it the same way (5, -10, 100n, 0.5, 500k). Display text adds the unit and typographic signs (4.7 kΩ, −10 V, 2.2 µF).
Footprints have pads (smd/tht/npth; circle/rect/roundrect/oval), silkscreen, a courtyard and an optional parametric body for 3D preview.
Canonical JSON
Keys are ordered with identity keys first (format, formatVersion, schema, version, id, kind, name) and the rest in code-point order. The output uses two-space indentation and LF line endings with a trailing newline. undefined is omitted, -0 is written as 0, and non-finite numbers are an error. Array order is meaningful and preserved.
Collaboration model
schema/collections.json is generated from the same definitions. For each structured kind it lists the entity collections, the reference fields and the editable document properties. Collaboration transactions (see falcad-engine’s collab protocol) address entities as (collection, id). Both the TypeScript client and the C# server enforce them from this one description.
Compatibility promise
- Readers must reject unknown
schemavalues and versions newer than they support. - Writers always write the newest version they support. A file is only ever upgraded on save after the user has opened it.
- Removing or renaming a field requires a new version and a migration.