Skip to main content

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
Component J1: line 1:1MOSIMISOCLKNSSSPI

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
Component J1: line 1:11234

A bus reads only three properties from its block:

PropertyRequiredDescription
pinsyesInteger N (auto-generates pins named 1..N) or a comma-separated list of signal names
namenoText label drawn next to the bus
styleno"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
line 16:95VComponent J1: line 12:91234downline 31:95VComponent J2: line 26:91234up

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
Component J1: line 17:4MOSIMISOCLKNSSSPIComponent U1: line 6:1SPI_MOSI1SPI_MISO2SPI_CLK3SPI_NSS4U1

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 busAto 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
Component J1: line 6:4MOSIMISOCLKNSSSPI

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
Component J1: line 21:4MOSIMISOCLKNSSSPIComponent U1: line 18:1SPI_MOSI1SPI_MISO2SPI_CLK3SPI_NSS4U1Component J2: line 27:4MOSIMISOCLKNSSSPIComponent U2: line 19:1SPI_MOSI1SPI_MISO2SPI_CLK3SPI_NSS4U2

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.