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.
- What a programme is. A list of statements, each one machine cycle, over one machine M. There are 9 core statements and 4 shorthands, and you will meet every one with a running example you can play.
- What a statement does to the machine. The state is the Machine page's ΣM: where every ion is (a map into M's sites), how hot it is (n̄), and the clock. Every statement is a step of that state, and nothing else.
- What a statement may name. Only what M declares: a move goes along a rail of M, a rotation turns a closed loop of M, a gate happens at a site whose zone allows it. A statement that names what M lacks is ill-typed and refused before any rule runs.
- What a statement does not say. Cost, time and heat are never written in a programme. They are computed from the machine's own curves when the programme is replayed, so two programmes on two machines are priced by their machines, not by their authors.
- What lies beneath. Every statement becomes one record of the control IR, the JSON the compiler emits and the verifier replays: the text you write by hand and the compiler's output meet there.
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
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
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.
p.initp.simdp.swap_or_stayp.reorder_or_notp.gatep.coolp.measurep.resetp.barrierp.fillp.movep.shuttlep.rotatep.claim
Core statements
p.init({ion: site, ...}, quanta=None)
Places the named ions on the named sites. The first statement of every programme: a machine starts empty.
p.init({"d0": "C1", "d1": "C0"})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.
p.init({"d0": "S1", "d1": "S4"})
p.simd("shuttle", [["d0", "S1", "S2"], ["d1", "S4", "S5"]])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
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"])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).
p.init({"d0": "T0_1h", "d1": "T1_1v"})
p.reorder_or_not(["d0"])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.
p.init({"d0": "C0", "d1": "C0"})
p.cool()
p.gate("CX", [["d0", "d1"]])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.
p.init({"d0": "T0_0v"})
p.shuttle("d0", ["T0_0v", "J0_1", "T0_1h"])
p.cool()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
p.init({"d0": "S3"})
p.measure(["d0"])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
p.init({"d0": "C2"})
p.reset(["d0"])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
p.init({"d0": "S0"})
p.move("d0", "S0", "S1")
p.barrier()
p.move("d0", "S1", "S2")Shorthands
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.initthat 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"})p.fill()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"]])p.init({"d0": "C1"})
p.move("d0", "C1", "C2")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"]]])p.init({"d0": "T0_0v"})
p.shuttle("d0", ["T0_0v", "J0_1", "T0_1h"])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.simdcycles, each moving every ion on the loop one slot forward (back when delta is negative), in the loop’s classrotate_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"]])p.init({"d0": "S0", "d1": "S2", "d2": "S5"})
p.rotate(2)The annotation
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.
p.init({"d0": "C0"})
p.move("d0", "C0", "C1")
p.claim(total_cost=1, total_steps=1)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
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.
| statement | instruction type | carries |
|---|---|---|
init | init | placement, quanta |
fill | init | placement over a loop |
move / shuttle / simd | simd | class, mode, participants (ion, from, to, via) |
rotate | simd | class, a loop_shift template, holds |
swap_or_stay / reorder_or_not | simd | class, a C2LR template: the diagonal (or the checkerboard) and the crystals shifted toward; no participants |
gate | gate | gate, pairs, sites |
cool | cool | broadcast or ions |
measure / reset | measure / reset | ions |
barrier | barrier | nothing |
claim | metrics | claimed 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
- 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.
- 9 core statements are the whole language; the 4 shorthands stand for lists of them and are judged exactly as those lists.
claimonly records what the author believes the totals are. - 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.
- 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.