The hardware language

A programme is a list of statements over one machine: the text the Machine page defines, whose sites, rails, loops and movement classes are the only names a statement may use. Each core statement is one instruction and one machine cycle: it says what moves where, or which ions a gate, a measurement or a cooling touches. It says nothing about cost. The replay computes the cost, the duration and the heating of every cycle from the machine's own primitive curves, and the rules judge each cycle as a whole. The same text runs in the studio's Write pane and in your own workspace.

On this page

The Physics page said what a trapped-ion machine can do: hold ions, carry them, pulse them, cool them. The Machine page made the machine a text, M, with sites, rails, loops, zones and prices. This page is the language in which M is told what to do, one cycle at a time.

Why does a hardware language need to exist at all? A quantum circuit already says what to compute.

Because a circuit says what and this machine needs where and when: which ion carries which qubit, along which rails it travels, in which trap it meets its partner, when it is cooled. A programme is that second description. The compiler writes it for you; you can also write it by hand, and the rules judge both the same way.

Syntax

In this section the whole grammar fits on one screen. Read it once, then read the two sentences under it; every example below is written in exactly this form.
programme  ::=  { statement NEWLINE }
statement  ::=  "p." verb "(" [ arguments ] ")"
verb       ::=  core | shorthand | "claim"
core       ::=  "init" | "simd" | "swap_or_stay" | "reorder_or_not" | "gate" | "cool"
             |  "measure" | "reset" | "barrier"
shorthand  ::=  "move" | "shuttle" | "rotate" | "fill"
arguments  ::=  argument { "," argument }
argument   ::=  literal | name "=" literal            (keywords after positionals)
literal    ::=  string | number | "True" | "False" | "None"
             |  "[" [ literal { "," literal } ] "]"
             |  "{" [ string ":" literal { "," string ":" literal } ] "}"

A statement is a call on a programme p, such as p.move("d0", "C1", "C2"): names in quotes, numbers as written, lists in square brackets. A statement may span lines while a bracket is open, and that is the only line-joining rule there is. Positional arguments come first, keywords after.

Semantics

In this section what a programme means: a machine state and the step each statement takes on it. Hold on to the three-part state; everything on the Rules and Evaluation pages reads it.

The state is the state space of the machine, ΣM: a map from ions to the sites of M, a motional excitation n̄ per ion, and a clock. init creates the map; every other statement is a transition of it. Transport statements move ions along M's rails and through its junctions; each crossing is priced from M's curves and adds to n̄. Gates leave positions alone and read n̄ to evaluate their error. Cooling lowers n̄. Instructions execute in order, each atomically; the next starts when the previous ends. A statement must be well-typed on M, naming only sites, rails, loops and classes M has; one that is not is refused by the replay with the name it could not find. Every cycle of a well-typed programme is then judged by the rules, which is where a legal-looking programme is refused.

The language has seven core statements, and every programme means a list of them. Four more are shorthands: each is defined as the core statements it stands for, is judged exactly as they are, and is there only to keep a programme short. Each shorthand below shows its example written out in core statements too, and the site is not built if the two are judged differently in any figure or verdict. One annotation, claim, records the totals an author claims.

That is the whole language in principle. The rest of the page is the statements one by one, each with what it does to the state, what it costs, which rules watch it, and a small programme you can play. Read the core statements in order: init places the ions, the transport statements move them, gate uses them, cool pays the heat back, measure and reset read and prepare.

Core statements

In this section the seven statements every programme is made of. Each card is one statement: its signature, its effect on the state, its cost, the rules that judge it, the IR it becomes, and a programme that uses it, running on the right.

p.init({ion: site, ...}, quanta=None)

Places the named ions on the named sites. The first statement of every programme: a machine starts empty.

state
position := placement; n̄(ion) := 0, or the quanta given. Nothing moves.
cost
0 hops, 0 µs.
rules
R1, R2
IR
init
passes

p.init({"d0": "C1", "d1": "C0"})
no rule failscost 0 · steps 0 · 0 µs · peak n̄ 0.00
C0d1C1d0C2C3
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.simd(cls, [[ion, from, to], [ion, from, to, via], ...], mode="inter")

Several ions in one cycle, driven together under one movement class. The rules judge the cycle as a whole: one waveform, one direction per loop, no exchanges.

state
every listed ion moves at once; position := the listed destinations.
cost
cost sums over the participants; depth and duration take the maximum, because the cycle waits for its slowest ion.
rules
R1, R2, R3, R4, R4d, R5, R8, R11
IR
simd
passes

p.init({"d0": "S1", "d1": "S4"})
p.simd("shuttle", [["d0", "S1", "S2"], ["d1", "S4", "S5"]])
no rule failscost 2 · steps 1 · 5 µs · peak n̄ 0.10
S0S1d0S2S3S4d1S5S6S7
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.swap_or_stay(diagonal, [ion, ...], checkerboard=None)

One C2LR swap-or-stay step (arXiv:2403.00756): every junction plays the same shared waveform for one diagonal (W-S, W-N, E-S or E-N), and each crystal's only choice is its direction bit: shifted toward its pair's junction (the listed ones) or away. The pairs are the machine's; the programme names no route. checkerboard={"parity": 0, "pairing": "P1"} pairs two diagonals at the junctions of one colour instead: not demonstrated in the paper, allowed by the rule set c2lr and marked assumed in every report.

state
for each pair of legs, the crystal shifted toward the junction in one goes to the other: both toward is a swap, both away a stay, toward into an occupied leg an enter (two crystals, C1), the entered one toward again its exit back. Here d0 enters d1's leg, the two are gated there, and d0 leaves the way it came.
cost
the primitive's one time per step however many crystals move (312.5 µs for Ba-Sr); every crystal is heated, a mover by quanta_swap and every other by quanta_stay (C9); a gate on two crystals of one leg adds merge and split.
rules
C1, C2, C5, C6, C7, C9, R4
IR
simd with a swap_or_stay template
passes

p.init({"d0": "T0_1h", "d1": "T1_1v"})
p.swap_or_stay("WS", ["d0"])
p.cool()
p.gate("CX", [["d0", "d1"]])
p.swap_or_stay("WS", ["d0"])
no rule failscost 4 · steps 6 · 1030 µs · peak n̄ 0.36
J0_0J0_1J0_2J1_0J1_1J1_2J2_0J2_1J2_2T0_0hT0_1hd0T0_2hT1_0hT1_1hT1_2hT0_0vT0_1vT1_0vT1_1vd1T2_0vT2_1v
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.reorder_or_not([ion, ...])

One C2LR reorder-or-not step: the listed crystals' two wells turn 180 degrees in place, every other crystal holds. Priced only where the paper measures it (Yb-Ba, Fig. 5).

state
positions unchanged; the listed crystals' ion order is reversed.
cost
93 µs for Yb-Ba, and its heat to every crystal (C9).
rules
C9, R4
IR
simd with a reorder_or_not template
passes

p.init({"d0": "T0_1h", "d1": "T1_1v"})
p.reorder_or_not(["d0"])
no rule failscost 1 · steps 1 · 93 µs · peak n̄ 0.02
J0_0J0_1J0_2J1_0J1_1J1_2J2_0J2_1J2_2T0_0hT0_1hd0T0_2hT1_0hT1_1hT1_2hT0_0vT0_1vT1_0vT1_1vd1T2_0vT2_1v
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.gate(name, [[control, target], ...], sites=None)

A gate cycle. Two-qubit: every pair sits in one site whose zone allows gates, control first. Single-qubit: an empty pair list and the site named.

state
positions unchanged; the gate's error is evaluated from each ion's n̄ at gate time.
cost
0 hops; the gate's duration from the primitive table.
rules
R6, R6b, R7, R7c, R12, R13, R16
IR
gate
passes

p.init({"d0": "C0", "d1": "C0"})
p.cool()
p.gate("CX", [["d0", "d1"]])
no rule failscost 0 · steps 2 · 325 µs · peak n̄ 0.02
C0d0 d1C1C2C3
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.cool(ions=None)

Cools ions back toward the motional ground state: all of them when no list is given (a broadcast), else the listed ones.

state
n̄(ion) := what the cooling primitive leaves, for the cooled ions.
cost
the cooling primitive's duration; one op for every ion under broadcast.
rules
R6, R7, R7c
IR
cool
passes

p.init({"d0": "T0_0v"})
p.shuttle("d0", ["T0_0v", "J0_1", "T0_1h"])
p.cool()
no rule failscost 3 · steps 3 · 405 µs · peak n̄ 3.22
J0_0J0_1J0_2J1_0J1_1J1_2T0_0hT0_1hT0_2hT0_0vd0T0_1vT1_0vT1_1v
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.measure([ion, ...])

Reads the listed ions out. Each must sit in a zone with SPAM (state preparation and measurement).

state
positions unchanged.
cost
the measure primitive's duration.
rules
R6
IR
measure
passes

p.init({"d0": "S3"})
p.measure(["d0"])
no rule failscost 0 · steps 1 · 120 µs · peak n̄ 0.01
S0S1S2S3d0S4S5S6S7
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.reset([ion, ...])

Puts the listed ions back to |0>. The same zone requirement as measuring.

state
positions unchanged.
cost
the reset primitive's duration.
rules
R6
IR
reset
passes

p.init({"d0": "C2"})
p.reset(["d0"])
no rule failscost 0 · steps 1 · 50 µs · peak n̄ 0.00
C0C1C2d0C3
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.barrier()

A cycle in which nothing happens: an explicit synchronisation point, a marker between phases of a programme.

state
unchanged.
cost
0 hops, 0 µs.
rules
–
IR
barrier
passes

p.init({"d0": "S0"})
p.move("d0", "S0", "S1")
p.barrier()
p.move("d0", "S1", "S2")
no rule failscost 2 · steps 2 · 10 µs · peak n̄ 0.20
S0d0S1S2S3S4S5S6S7
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

Shorthands

In this section four statements that stand for lists of core statements. Each card also shows its example written out in core statements, judged to the same numbers; a shorthand saves typing and changes nothing else.

p.fill(loop=None, prefix="d")

Places one ion on every slot of a loop, d0 on its first site and so on: the packed ring in one line.

stands for
one p.init that puts an ion on every site of the loop, in the loop’s order: {"d0": its first site, "d1": its second, ...}.
state
position := every site of the loop, in order. Nothing moves.
cost
0 hops, 0 µs.
rules
R1
IR
init

The example on the right, written in core statements and judged the same: cost 0, 0 steps, 0 µs, peak n̄ 0.00, no rule fails.

p.init({"d0": "S0", "d1": "S1", "d2": "S2", "d3": "S3", "d4": "S4", "d5": "S5", "d6": "S6", "d7": "S7"})
passes

p.fill()
no rule failscost 0 · steps 0 · 0 µs · peak n̄ 0.00
S0d0S1d1S2d2S3d3S4d4S5d5S6d6S7d7
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.move(ion, src, dst, via=None, cls="shuttle")

One ion, one hop, in its own cycle: the shortest form of a transport instruction.

stands for
p.simd(cls, [[ion, src, dst]]), or [[ion, src, dst, via]] when a via is given: a cycle with one participant.
state
position(ion) := dst; the segment between src and dst, or the segments in via, is crossed once; n̄ grows by the primitives crossed.
cost
the segment's hop, plus a junction_cross priced by the degree of any junction transited; duration from the machine's curves.
rules
R1, R2, R3, R4, R8
IR
simd with one participant

The example on the right, written in core statements and judged the same: cost 1, 1 step, 5 µs, peak n̄ 0.10, no rule fails.

p.init({"d0": "C1"})
p.simd("shuttle", [["d0", "C1", "C2"]])
passes

p.init({"d0": "C1"})
p.move("d0", "C1", "C2")
no rule failscost 1 · steps 1 · 5 µs · peak n̄ 0.10
C0C1d0C2C3
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.shuttle(ion, [site, site, ...], cls="shuttle")

Walks one ion along a path of site ids, one trap-to-trap step per cycle. Junctions on the way are transited, never rested on.

stands for
one p.simd(cls, [[ion, a, b, via]]) for each trap-to-trap step a → b of the path, its via the rails through any junction between them.
state
one cycle per step of the path; after each, position(ion) is the next site.
cost
the sum of the hops; here the walk from T0_0v to T0_1h transits the T-junction J0_1, and the crossing is charged at degree 3.
rules
R1, R2, R3, R4, R8, R18
IR
one simd per step

The example on the right, written in core statements and judged the same: cost 3, 2 steps, 105 µs, peak n̄ 3.21, no rule fails.

p.init({"d0": "T0_0v"})
p.simd("shuttle", [["d0", "T0_0v", "T0_1h", ["T0_0v.b", "T0_1h.a"]]])
passes

p.init({"d0": "T0_0v"})
p.shuttle("d0", ["T0_0v", "J0_1", "T0_1h"])
no rule failscost 3 · steps 2 · 105 µs · peak n̄ 3.21
J0_0J0_1J0_2J1_0J1_1J1_2T0_0hT0_1hT0_2hT0_0vd0T0_1vT1_0vT1_1v
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

p.rotate(delta, loop=None)

Turns a closed loop: every ion on it moves delta slots forward (negative: back), all of them together, as a conveyor belt does.

stands for
|delta| p.simd cycles, each moving every ion on the loop one slot forward (back when delta is negative), in the loop’s class rotate_cw (rotate_ccw). What it adds is brevity, not meaning: a compiled programme turns a loop of 144 ions in one line, and the IR keeps the turn as one instruction, so the number of movement templates a programme uses can be counted.
state
position(ion) := slot + delta (mod the loop) for every ion on the loop.
cost
|delta| unit cycles, each the hops of every ion on the loop summed and the deepest edge as its depth.
rules
R4, R11
IR
simd with a loop_shift template

The example on the right, written in core statements and judged the same: cost 6, 2 steps, 10 µs, peak n̄ 0.20, no rule fails.

p.init({"d0": "S0", "d1": "S2", "d2": "S5"})
p.simd("rotate_cw", [["d0", "S0", "S1"], ["d1", "S2", "S3"], ["d2", "S5", "S6"]])
p.simd("rotate_cw", [["d0", "S1", "S2"], ["d1", "S3", "S4"], ["d2", "S6", "S7"]])
passes

p.init({"d0": "S0", "d1": "S2", "d2": "S5"})
p.rotate(2)
no rule failscost 6 · steps 2 · 10 µs · peak n̄ 0.20
S0d0S1S2d1S3S4S5d2S6S7
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

The annotation

In this section one statement that does nothing to the machine: it records what the author claims the totals are, and the replay checks the claim (R9).

p.claim(total_cost=..., total_steps=..., ...)

Annotates the programme with the totals its author claims. Claims are not content: the replay recomputes everything and R9 rejects any claim it cannot reproduce.

state
unchanged; the claim is recorded on the programme.
cost
none.
rules
R9
IR
the programme's metrics
passes

p.init({"d0": "C0"})
p.move("d0", "C0", "C1")
p.claim(total_cost=1, total_steps=1)
no rule failscost 1 · steps 1 · 5 µs · peak n̄ 0.10
C0d0C1C2C3
Playthe programme, played once; then step it or scrub it
open the page, with its Report pane

Every card above named the IR its statement becomes. That is not a detail for implementers: it is the reason a programme you write by hand and a programme the compiler writes are judged by one verifier.

Beneath the text: the IR

In this section one table: each statement and the instruction record it becomes. The compiler never sees the text; it writes these records directly.

Every statement becomes one instruction of the control IR, the JSON the compiler emits and the verifier replays. The Write pane and the compiler meet there.

statementinstruction typecarries
initinitplacement, quanta
fillinitplacement over a loop
move / shuttle / simdsimdclass, mode, participants (ion, from, to, via)
rotatesimdclass, a loop_shift template, holds
swap_or_stay / reorder_or_notsimdclass, a C2LR template: the diagonal (or the checkerboard) and the crystals shifted toward; no participants
gategategate, pairs, sites
coolcoolbroadcast or ions
measure / resetmeasure / resetions
barrierbarriernothing
claimmetricsclaimed totals, checked by R9

The IR reference is the control IR; the machine language the programmes run on is the machine of the Machine page, whose file format is the architecture description.

What to remember

  1. A programme is a list of statements, one machine cycle each, over one machine. The state it steps is three things: where every ion is, how hot it is, and the clock.
  2. 9 core statements are the whole language; the 4 shorthands stand for lists of them and are judged exactly as those lists. claim only records what the author believes the totals are.
  3. A programme never states a cost. Time and heat are read from the machine's tables when it is replayed, which is why one programme can be priced on many machines and many programmes compared on one.
  4. Every statement is one record of the control IR, so hand-written and compiled programmes are judged by one verifier. The next page is that verifier's rules: what a cycle may and may not do.

To write one yourself, open the Design page: its Write pane takes exactly this text and plays it. The tutorial's Part B does it with you, one statement at a time.