Skip to main content

BOM generation

Bill-of-materials generation creates a list of the components used in the design in a text format (CSV). The components in the list are often grouped together if they share certain parameters, for example, the same manufacturer part number.

To generate the BOM, pass the -b [output-file-path] flag to the circuitscript CLI. The flag itself is required to enable BOM generation; the output path is optional. When the path is omitted, the BOM is written to <input>.bom.csv — the full input filename plus the .bom.csv suffix, so foo.cst produces foo.cst.bom.csv. When reading from stdin or --input, it defaults to output.bom.csv instead. The BOM is always generated as a .csv file.

BOM configuration

Two properties determine how the BOM is generated:

  • document.bom.columns sets the component parameters that are displayed as columns in the BOM.
  • document.bom.group_by sets how components are grouped. Components whose values for every group_by key are equal collapse into a single row, and their refdes cells are concatenated (for example "C1, C2").

Both properties have defaults defined in std, so setting them is optional:

document.bom.columns = ["refdes", "mpn", "manufacturer", "footprint"]
document.bom.group_by = ["mpn", "manufacturer"]

Two constraints apply:

  • Every group_by key must also appear in columns. Otherwise the build fails with error "Invalid group_by keys".
  • columns is open-ended — any parameter name works as a column, including refdes, value, size, footprint, description, mpn, manufacturer, or any custom parameter. refdes is always available even though it is not a stored parameter. Any parameter that is unknown or unset on a component renders as an empty cell.

Example:

from "std" import *
document.bom.columns = ["refdes", "mpn", "manufacturer", "footprint", "description"]
document.bom.group_by = ["mpn", "manufacturer"]

v5 = supply("5V")
gnd = dgnd()

at v5
wire down 100
add cap(100n)
..mpn = "CL05B104KP5VPNC"
..manufacturer = "Samsung Electro-Mechanics"
wire down 100
to gnd

at v5
wire down 100
add cap(100n)
..mpn = "CL05B104KP5VPNC"
..manufacturer = "Samsung Electro-Mechanics"
wire down 100
to gnd

at v5
wire down 100
add res(10k)
..mpn = "RC0402JR-0710KL"
..manufacturer = "Yageo"
wire down 100
to gnd
line 8:15VComponent C1: line 10:1C1100nline 14:4GNDline 16:15VComponent C2: line 18:1C2100nline 22:4GNDline 24:15VComponent R1: line 26:1R110kline 30:4GND

The ..<param> = <value> prefix sets a parameter on the most recently added or referenced component (it resolves against the last object reference in scope). It is order-sensitive — an intervening component reference changes what .. binds to, so place these assignments immediately after the component they target. See this page for more info about the double dot operator.

The output BOM csv file:

Refdes,Mpn,Manufacturer,Footprint,Description
R1,RC0402JR-0710KL,Yageo,Resistor_SMD:R_0402_1005Metric,RES 10k OHM 0402
"C1, C2",CL05B104KP5VPNC,Samsung Electro-Mechanics,Capacitor_SMD:C_0402_1005Metric,

The resistor sorts before the capacitors because res has a lower type-sort order than cap (see Row order, grouping, and column values). The res() component defines a description parameter ("RES {value} OHM {size}"), so R1's Description cell renders RES 10k OHM 0402. The cap() component has no description parameter at all, so the capacitor's Description cell is empty — this demonstrates the "unset parameter renders as an empty cell" behavior described above.

Row order, grouping, and column values

A few behaviors of the BOM output are worth calling out:

  • Row ordering is by component type first (res, cap, ind, diode, ic, conn, then everything else), and within a type by refdes in natural order — the refdes prefix alphabetically, then the number numerically. So R2 sorts before R10 (not the string order R10, R2).
  • Grouped rows merge each column across all members. When members share a value it appears once; when they differ, the distinct values are concatenated into a comma-separated cell (for example two capacitors with different mpn values render as "PART_A, PART_B").
  • Unset columns produce empty CSV cells; no error is raised.
  • CSV headers capitalize only the first letter of each column key (mpn becomes Mpn, lcsc becomes Lcsc).

Do not install (place)

Every component carries a boolean place parameter that defaults to installed. Setting ..place = false marks a component as not installed (a "do not install" part).

document.bom.only_placed (default true) controls whether not-placed components appear in the BOM. When it is true, not-placed components are excluded from the BOM entirely. Set it to false to keep them in the list.

from "std" import *
document.bom.columns = ["refdes", "value", "footprint"]
document.bom.group_by = ["value"]

v5 = supply("5V")
gnd = dgnd()

at v5
wire down 100
add res(10k)
wire down 100
to gnd

at v5
wire down 100
add res(1k)
..place = false
wire down 100
to gnd
line 8:15VComponent R1: line 10:1R110kline 12:4GNDline 14:15VComponent R2: line 16:1R21kline 19:4GND

With the default only_placed = true, the not-placed resistor is omitted:

Refdes,Value,Footprint
R1,10k,Resistor_SMD:R_0402_1005Metric

Adding document.bom.only_placed = false keeps it in the list:

Refdes,Value,Footprint
R1,10k,Resistor_SMD:R_0402_1005Metric
R2,1k,Resistor_SMD:R_0402_1005Metric

Parameter assignment rules

Component parameters can be directly assigned for each component as shown in the example above. However, in large designs with many components, this is tedious to maintain and change when MPN/manufacturer changes are needed.

Instead of manually setting parameters for each component, rules can be used to assign parameters.

from "std" import *

v5 = supply("5V")
gnd = dgnd()

at v5
wire down 100
add cap(100n)
wire down 100
to gnd

at v5
wire down 100
branch:
wire down 100
add cap(100n)
wire down 100
to gnd

branch:
wire right 400 down 100
add cap(100n)
wire down 100
to gnd
branch:
wire right 800 down 100
add res(10k)
wire down 100
to gnd

# Parameter assignment rules
set: "mpn", "manufacturer":
type: "res", size: "0402", value:
10k: "RC0402JR-0710KL", "Yageo"
type: "cap", size: "0402", value:
100n: "CL05B104KP5VPNC", "Samsung Electro-Mechanics"
line 6:15VComponent C1: line 8:1C1100nline 10:4GNDline 12:15VComponent C2: line 16:5C2100nline 18:8GNDComponent C3: line 22:5C3100nline 24:8GNDComponent R1: line 27:5R110kline 29:8GND

In the example above, the following rules are defined:

  1. If component is type res (resistor), has a size parameter of value 0402 and a value parameter of value 10k:
    1. assign mpn to be RC0402JR-0710KL
    2. assign manufacturer to be Yageo
  2. If component is type cap (capacitor), has a size parameter of value 0402 and a value parameter of value 100n:
    1. assign mpn to be CL05B104KP5VPNC
    2. assign manufacturer to be Samsung Electro-Mechanics

Because no columns are set here, the default columns apply. The output BOM csv file (note that R1 sorts before the grouped capacitors, which share one row as "C1, C2, C3"):

Refdes,Mpn,Manufacturer,Footprint
R1,RC0402JR-0710KL,Yageo,Resistor_SMD:R_0402_1005Metric
"C1, C2, C3",CL05B104KP5VPNC,Samsung Electro-Mechanics,Capacitor_SMD:C_0402_1005Metric

How matching works

A rule assigns its parameters to a component only when all of the rule's conditions match (a logical AND). The matching semantics have several details worth knowing:

  • type: is special — it matches the component's type (res, cap, …), not a stored parameter.
  • Condition keys are bare identifiers (type, size, value), while the parameter names in the set: header are quoted strings ("mpn", "manufacturer"). Quoting a condition key silently prevents it from matching.
  • String values are compared exactly and case-sensitively (size: "0402"). Numeric values are normalized before comparison, so value: 100n also matches a component whose value is 0.1u, and 10k matches 10000.
  • Condition parameters such as size/value must exist on the component. res() and cap() default to size "0402", which is why size-keyed rules work; parts of other sizes silently do not match.
  • No match leaves the parameters unset and raises no error — the corresponding BOM cells are simply empty. Running the CLI with --verbose reports which values were missing.
  • Execution order matters. A set: block only affects components that already exist when the statement runs, so place set: blocks after the components they target. Within one block, the first matching branch wins; across blocks, a later set: overrides an earlier one (last write wins) for any component that exists when each block runs.

Creating parameter rules

The set command starts with the name of the parameters that should be assigned. Multiple set commands may be defined at any point in the code, and later set commands override earlier ones — but this override only applies to components that already exist when each set command runs.

The following example creates a rule that assigns the mpn and manufacturer parameter of components:

set: "mpn", "manufacturer"

Rules are defined in <parameterName>: <targetValue> pairs; these can be merged with commas to reduce the indentation needed.

The parameter value(s) to be assigned follow the last rule, separated by a colon. If there are multiple parameters to be assigned, each value is separated by commas.

The following parameter rules are all equivalent and perform the same parameter assignment.

# 2 parameters to be assigned
set: "mpn", "manufacturer":
# rules
type: "res", size: "0402", value:
10k: "RC0402JR-0710KL", "Yageo" # set first parameter, then second parameter
# 2 parameters to be assigned
set: "mpn", "manufacturer":
# rules
type: "res":
size: "0402":
# final rule and values to be assigned
value: 10k: "RC0402JR-0710KL", "Yageo"
# 2 parameters to be assigned
set: "mpn", "manufacturer":
# rules
type: "res", size: "0402":
value: 10k: "RC0402JR-0710KL", "Yageo"