Buses
A bus bundles several logical signals into a single thick line, so complex multi-signal connections stay readable. Instead of drawing four separate wires for an SPI link, you draw one bus carrying MOSI, MISO, CLK and NSS and tap off the individual signals only where a component needs them.
Reach for a bus whenever a group of signals travels together between blocks (SPI, I²C, parallel address/data buses), or whenever a tangle of parallel wires is making a schematic hard to read. A bus renders as one thick blue line with blue junction dots, and each signal branches off it as a labelled tap.
Creating a bus
A bus is created with a create bus: block. The only required property is pins, which defines the signals the bus carries. You can give the pins explicit names:
data_bus = create bus:
name: "SPI"
pins: "MOSI", "MISO", "CLK", "NSS"
at data_bus
wire right 200
Or pass a single integer N to auto-generate N signal pins named 1..N:
b = create bus:
pins: 4
at b
wire right 200
A bus reads only three properties from its block:
| Property | Required | Description |
|---|---|---|
pins | yes | Integer N (auto-generates pins named 1..N) or a comma-separated list of signal names |
name | no | Text label drawn next to the bus |
style | no | "down" (default) or "up" — the direction the signal taps branch off the main line |
Omitting pins raises the error Bus pins not defined.
Tap direction with style
style flips which side the signal taps branch off the main bus line. The default is "down"; set style: "up" to branch upward. Here the same bus is drawn both ways; notice the taps flip direction between the two frames:
from "std" import *
v5 = supply("5V")
frame:
..direction = "column"
..border = 0
..padding = 0
frame:
..border = 0
..padding = 0
down = create bus:
name: "down"
pins: 4
at v5
wire down 100 right 100 to down pin 1
at down
wire right 200
frame:
..border = 0
..padding = 0
up = create bus:
name: "up"
pins: 4
style: "up"
at v5
wire down 100 right 100 to up pin 1
at up
wire right 200
Connecting components to bus signals
Open an at <bus>: block to wire individual signals. Inside the block each signal name is a key mapping to a connection, so you can route each signal to its matching component pin:
def spi():
return create bus:
name: "SPI"
pins: "MOSI", "MISO", "CLK", "NSS"
main = create component:
type: "ic"
pins:
1: "SPI_MOSI"
2: "SPI_MISO"
3: "SPI_CLK"
4: "SPI_NSS"
width: 400
arrange:
left: 1,2,3,4
at spi1 = spi():
"MOSI": wire left 200 to main pin "SPI_MOSI"
"MISO": wire left 200 to main pin "SPI_MISO"
"CLK": wire left 200 to main pin "SPI_CLK"
"NSS": wire left 200 to main pin "SPI_NSS"
at spi1
wire right 300
The at spi1 = spi(): form creates the bus by calling the spi() function (defined with def, see Reusing bus definitions) and binds it to spi1 so the same block can both create the bus and wire its signals. Pins created with a numeric count are addressed by number instead — at tmp pin 1.
Wiring buses together
Two buses are connected along their main line with at busA … to busB. There is no need to specify a pin as the main bus pin is the default pin.
When two bus main pins are joined, CircuitScript compares both buses' sets of signal names. The order does not matter, but the sets must match: every signal present on one bus must be present on the other. On a match, each matching signal pin is electrically linked across the two buses. This is how a net routed into one bus reaches components wired to the other.
Two errors guard this:
Buses are different— the two buses expose different sets of signal names.Bus wire cannot be connected with wire— a bus main pin was joined to an ordinary (non-bus) pin.
Reusing bus definitions
Wrapping create bus: in a def that returns the bus lets you reuse the same signal layout everywhere it is needed, rather than repeating the pin list:
def spi():
return create bus:
name: "SPI"
pins: "MOSI", "MISO", "CLK", "NSS"
at spi1 = spi():
"MOSI": wire right 200
"MISO": wire right 200
"CLK": wire right 200
"NSS": wire right 200
Because bus-to-bus matching is order-independent, a variant that lists the same signals in a different order still connects — useful when two components label their pins in a different sequence.
Full example
This puts it together: a controller and a peripheral, each wired to its own SPI bus, with the two buses joined along the main line so the signals propagate end to end. The rendered schematic shows the thick blue bus line, the signal taps, and the vertical link between the two buses.
def spi():
return create bus:
name: "SPI"
pins: "MOSI", "MISO", "CLK", "NSS"
def spi_block():
return create component:
type: "ic"
pins:
1: "SPI_MOSI"
2: "SPI_MISO"
3: "SPI_CLK"
4: "SPI_NSS"
width: 400
arrange:
left: 1,2,3,4
controller = spi_block()
peripheral = spi_block()
at bus_a = spi():
"MOSI": wire left 300 to controller pin "SPI_MOSI"
"MISO": wire left 300 to controller pin "SPI_MISO"
"CLK": wire left 300 to controller pin "SPI_CLK"
"NSS": wire left 300 to controller pin "SPI_NSS"
at bus_b = spi():
"MOSI": wire right 300 to peripheral pin "SPI_MOSI"
"MISO": wire right 300 to peripheral pin "SPI_MISO"
"CLK": wire right 300 to peripheral pin "SPI_CLK"
"NSS": wire right 300 to peripheral pin "SPI_NSS"
at bus_a
wire right 500
to bus_b
Appearance
The main bus line renders thick and blue, with blue junction dots where taps meet it. style controls whether the signal taps branch downward (default) or upward, and name draws a text label next to the bus.