Bloqade Lanes Bytecode Instruction Specification

This document specifies the bytecode instruction set used by Bloqade Lanes to describe atom shuttling programs for neutral atom quantum processors. A bytecode program is a sequence of fixed-width instructions that drive the full lifecycle of a computation: loading atoms into an optical lattice, shuttling them between sites using AOD (Acousto-Optic Deflector) transport, applying quantum gates, and reading out measurement results.

The instruction set is organized around the physical structure of the hardware. Atoms occupy sites within words (rows of trapping positions in the lattice). Buses define the AOD transport channels that move atoms between sites (site buses) or between words (word buses). A lane is a single atom trajectory along a bus — one source site to one destination site. A zone groups words that share a global entangling interaction (e.g. a Rydberg pulse) or define locations where atoms are measured. These concepts map directly to the address types used in the bytecode: LocationAddr (word, site), LaneAddr (word, site, bus, direction), and ZoneAddr (zone).

Programs execute on a stack machine. Address constants and numeric parameters are pushed onto the stack, then consumed by operation instructions (fills, moves, gates, measurements). The bytecode is designed to be validated offline against an architecture specification (ArchSpec) that captures the geometry, bus topology, and zone layout of a specific device.

Instruction Format

Every instruction is a fixed 16 bytes: a 32-bit opcode word followed by three 32-bit data words, all little-endian.

┌──────────────┬──────────────┬──────────────┬──────────────┐
│ opcode (u32) │ data0 (u32)  │ data1 (u32)  │ data2 (u32)  │
├──────────────┼──────────────┼──────────────┼──────────────┤
│  bytes 0–3   │  bytes 4–7   │  bytes 8–11  │ bytes 12–15  │
└──────────────┴──────────────┴──────────────┴──────────────┘

Instructions that take no operands ignore the data words (should be zero). Instructions with operands encode them in the data words as described per-instruction below.

Opcode Packing

The opcode word is packed as a 1-byte instruction code and a 1-byte device code in the low 16 bits of the u32. The upper 16 bits are unused (must be zero). The device code occupies the least significant byte.

┌──────────────┬──────────────────┬──────────────────┐
│   unused     │ instruction code │   device code    │
│  (16 bits)   │    (8 bits)      │    (8 bits)      │
└──────────────┴──────────────────┴──────────────────┘
  bits 31–16       bits 15–8          bits 7–0

Full opcode = (instruction_code << 8) | device_code.

In little-endian memory layout:

byte[0] = device_code        (bits 7–0)
byte[1] = instruction_code   (bits 15–8)
byte[2] = 0x00               (unused)
byte[3] = 0x00               (unused)

Instruction codes can overlap across different devices — the device code byte disambiguates.

Device Codes

Device CodeNameDescription
0x00CpuStack manipulation, constants, control flow (FLAIR-aligned)
0x0FLaneConstantsLane-specific constant instructions
0x10AtomArrangementAtom filling and movement
0x11QuantumGateSingle- and multi-qubit gate operations
0x12MeasurementQubit measurement
0x13ArrayArray construction and indexing
0x14DetectorObservableDetector and observable setup

Device codes 0x010x0E are reserved for future FLAIR device types.

Address Encoding

All address field components are 16-bit, packed into the data words.

LocationAddr

Packed in a single data word (data0):

data0: [word_id:16][site_id:16]
        bits 31–16   bits 15–0

Total: 32 bits (u32).

LaneAddr

Packed across two data words (data0 + data1):

data0: [word_id:16][site_id:16]
        bits 31–16   bits 15–0

data1: [dir:1][mt:1][pad:14][bus_id:16]
       bit 31  bit 30  29–16  bits 15–0
  • dir — direction: 0 = Forward, 1 = Backward
  • mt — move type: 0 = SiteBus, 1 = WordBus

Total: 64 bits across two u32 words. Note that data0 shares the same layout as LocationAddr.

Lane address convention

The word_id and site_id fields in a LaneAddr always encode the forward-direction source — the position where the atom starts in a forward move. The direction field does not change which position is encoded; it only controls which endpoint is treated as source vs destination when the lane is resolved.

Endpoint resolution always starts by resolving the forward direction:

  1. Look up the bus (site bus or word bus, selected by move_type and bus_id)
  2. Find the index i where bus.src[i] matches the encoded site_id (for site buses) or word_id (for word buses)
  3. The forward source is (word_id, site_id) as encoded; the forward destination is (word_id, bus.dst[i]) for site buses or (bus.dst[i], site_id) for word buses
  4. If direction = Forward: return (fwd_source, fwd_destination)
  5. If direction = Backward: return (fwd_destination, fwd_source) — the endpoints are swapped

Example: Given a site bus with src=[0,1,2,3,4] dst=[5,6,7,8,9]:

LaneEncodedResolved src → dst
site_id=0, dir=ForwardForward source is site 0Site 0 → Site 5
site_id=0, dir=BackwardForward source is still site 0Site 5 → Site 0
site_id=2, dir=BackwardForward source is site 2Site 7 → Site 2

Note that a backward lane with site_id=0 means the atom moves from site 5 to site 0 — not that site 0 is the destination of a forward move.

Lane validation rules

The validator (check_lane) checks the following for each LaneAddr:

RuleError condition
Bus must existbus_id out of range for the given move_type
word_id in rangeword_id >= num_words
site_id in rangesite_id >= sites_per_word
Bus membershipFor site buses: word_id must be in words_with_site_buses. For word buses: site_id must be in sites_with_word_buses.
Valid forward sourceFor site buses: bus.resolve_forward(site_id) must succeed (i.e. site_id is in bus.src). For word buses: bus.resolve_forward(word_id) must succeed (i.e. word_id is in bus.src).

Validation is always performed against the forward-direction source, regardless of the direction field.

ZoneAddr

Packed in a single data word (data0):

data0: [pad:16][zone_id:16]
       bits 31–16  bits 15–0

Total: 32 bits (u32).

Instructions

Cpu (0x00) — FLAIR-aligned shared opcodes

These instruction codes are shared with the FLAIR VM/IR spec and use identical values.

const_int — Push integer constant

FieldValue
Device Code0x00
Instruction Code0x02
Full Opcode0x0200
data0i64 LE low 32 bits
data1i64 LE high 32 bits
data2unused
Stack( -- int)

Pushes a signed 64-bit integer onto the stack. The value is stored as a little-endian i64 across data0 (low) and data1 (high).

const_float — Push float constant

FieldValue
Device Code0x00
Instruction Code0x03
Full Opcode0x0300
data0f64 LE low 32 bits
data1f64 LE high 32 bits
data2unused
Stack( -- float)

Pushes a 64-bit float onto the stack. The value is stored as a little-endian f64 across data0 (low) and data1 (high).

dup — Duplicate top of stack

FieldValue
Device Code0x00
Instruction Code0x04
Full Opcode0x0400
data0–2unused
Stack(a -- a a)

pop — Discard top of stack

FieldValue
Device Code0x00
Instruction Code0x05
Full Opcode0x0500
data0–2unused
Stack(a -- )

swap — Swap top two stack elements

FieldValue
Device Code0x00
Instruction Code0x06
Full Opcode0x0600
data0–2unused
Stack(a b -- b a)

return — Return from program

FieldValue
Device Code0x00
Instruction Code0x64
Full Opcode0x6400
data0–2unused
Stack( -- )

halt — Halt execution

FieldValue
Device Code0x00
Instruction Code0xFF
Full Opcode0xFF00
data0–2unused
Stack( -- )

LaneConstants (0x0F)

const_loc — Push location address

FieldValue
Device Code0x0F
Instruction Code0x00
Full Opcode0x000F
data0LocationAddr[word_id:16][site_id:16]
data1unused
data2unused
Stack( -- loc)

const_lane — Push lane address

FieldValue
Device Code0x0F
Instruction Code0x01
Full Opcode0x010F
data0[word_id:16][site_id:16]
data1[dir:1][mt:1][pad:14][bus_id:16]
data2unused
Stack( -- lane)

const_zone — Push zone address

FieldValue
Device Code0x0F
Instruction Code0x02
Full Opcode0x020F
data0ZoneAddr[pad:16][zone_id:16]
data1unused
data2unused
Stack( -- zone)

AtomArrangement (0x10)

initial_fill — Initial atom loading

FieldValue
Device Code0x10
Instruction Code0x00
Full Opcode0x0010
data0u32 LE arity
data1unused
data2unused
Stack(loc₁ loc₂ … locₙ -- )

Pops n location addresses and performs the initial atom fill at those sites.

fill — Atom refill

FieldValue
Device Code0x10
Instruction Code0x01
Full Opcode0x0110
data0u32 LE arity
data1unused
data2unused
Stack(loc₁ loc₂ … locₙ -- )

Pops n location addresses and refills atoms at those sites.

move — Atom transport

FieldValue
Device Code0x10
Instruction Code0x02
Full Opcode0x0210
data0u32 LE arity
data1unused
data2unused
Stack(lane₁ lane₂ … laneₙ -- )

Pops n lane addresses and performs atom moves along those lanes. All lanes in a single move instruction are executed simultaneously as one AOD transport operation.

Lane group validation

When an ArchSpec is provided, the validator checks the group of lanes as a whole — not just each lane individually. These constraints reflect the physical limitations of a single AOD (Acousto-Optic Deflector). Each move instruction corresponds to one AOD operation:

Consistency — all lanes in the group must share the same move_type, bus_id, and direction. A single AOD operation cannot mix site-bus and word-bus moves, use different buses, or move atoms in different directions simultaneously.

Bus membership — for site-bus moves, every lane's word_id must be in words_with_site_buses. For word-bus moves, every lane's site_id must be in sites_with_word_buses.

Grid constraint — the physical positions of the lane sources must form a complete grid (Cartesian product of unique X and Y coordinates). An AOD addresses rows and columns independently, so it cannot select an arbitrary subset of positions — it must address every intersection of the selected rows and columns.

For example, if a move group contains lanes at positions (0,0), (0,1), (1,0), and (1,1), this is a valid 2x2 grid. But (0,0), (0,1), (1,0) alone is invalid — the AOD would also address (1,1), so the group must include it.

CheckError
All lanes share move_type, bus_id, directionInconsistent
Site-bus lane word_id in words_with_site_busesWordNotInSiteBusList
Word-bus lane site_id in sites_with_word_busesSiteNotInWordBusList
Lane positions form a complete gridAODConstraintViolation

QuantumGate (0x11)

local_r — Local R rotation

FieldValue
Device Code0x11
Instruction Code0x00
Full Opcode0x0011
data0u32 LE arity
data1unused
data2unused
Stack(loc₁ loc₂ … locₙ θ φ -- )

Pops 2 float parameters (φ = axis angle, θ = rotation angle) then n location addresses, and applies a local R rotation. The call convention matches the SSA IR: local_r(%φ, %θ, %loc₁, …) — first argument (φ) is pushed last and popped first.

local_rz — Local Rz rotation

FieldValue
Device Code0x11
Instruction Code0x01
Full Opcode0x0111
data0u32 LE arity
data1unused
data2unused
Stack(loc₁ loc₂ … locₙ θ -- )

Pops 1 float parameter (θ = rotation angle) then n location addresses, and applies a local Rz rotation. The call convention matches the SSA IR: local_rz(%θ, %loc₁, …).

global_r — Global R rotation

FieldValue
Device Code0x11
Instruction Code0x02
Full Opcode0x0211
data0–2unused
Stack(θ φ -- )

Pops 2 float parameters (φ = axis angle, θ = rotation angle), applies a global R rotation. The call convention matches the SSA IR: global_r(%φ, %θ).

global_rz — Global Rz rotation

FieldValue
Device Code0x11
Instruction Code0x03
Full Opcode0x0311
data0–2unused
Stack(θ -- )

Pops 1 float parameter (θ = rotation angle), applies a global Rz rotation. Since there is only one parameter, it is both pushed last and popped first.

cz — Controlled-Z gate

FieldValue
Device Code0x11
Instruction Code0x04
Full Opcode0x0411
data0–2unused
Stack(zone -- )

Pops a zone address and applies a CZ gate across the zone.

Measurement (0x12)

measure — Initiate measurement

FieldValue
Device Code0x12
Instruction Code0x00
Full Opcode0x0012
data0u32 LE arity
data1unused
data2unused
Stack(zone₁ zone₂ … zoneₙ -- future₁ future₂ … futureₙ)

Pops n zone addresses and pushes n measure futures.

await_measure — Wait for measurement result

FieldValue
Device Code0x12
Instruction Code0x01
Full Opcode0x0112
data0–2unused
Stack(future -- array_ref)

Pops a measure future and pushes an array reference containing the measurement results.

Array (0x13)

new_array — Construct array from stack

FieldValue
Device Code0x13
Instruction Code0x00
Full Opcode0x0013
data0[type_tag:8][pad:8][dim0:16]
data1[pad:16][dim1:16]
data2unused
Stack(elem₁ elem₂ … elemₙ -- array_ref)

Constructs an array of dim0 × dim1 elements with element type type_tag. If dim1 is 0, the array is 1-dimensional with dim0 elements.

get_item — Index into array

FieldValue
Device Code0x13
Instruction Code0x01
Full Opcode0x0113
data0u16 LE ndims (upper 16 bits unused)
data1unused
data2unused
Stack(array_ref idx₁ … idxₙ -- value)

Pops ndims index values then the array reference, and pushes the indexed element.

DetectorObservable (0x14)

set_detector — Set detector

FieldValue
Device Code0x14
Instruction Code0x00
Full Opcode0x0014
data0–2unused
Stack(array_ref -- detector_ref)

Pops an array reference and pushes a detector reference.

set_observable — Set observable

FieldValue
Device Code0x14
Instruction Code0x01
Full Opcode0x0114
data0–2unused
Stack(array_ref -- observable_ref)

Pops an array reference and pushes an observable reference.

Reserved Opcode Ranges

RangeOwner
Device 0x00, inst codes 0x000x8FReserved for FLAIR. This project uses only 0x020x06, 0x64, 0xFF.
Device codes 0x010x0EReserved for future FLAIR device types
Device codes 0x0F0xFFProject-specific (currently 0x0F0x14 allocated)

Known FLAIR allocations (device 0x00)

Instruction CodePurpose
0x01const.bool
0x100x17Arithmetic (arith.add_int, arith.add_float, etc.)
0x200x23Comparison (cmp.gt_int, cmp.eq_float, etc.)
0x280x2ABoolean (bool.not, bool.and, bool.or)
0x300x33Waveform (waveform.poly4, waveform.delay, etc.)
0x400x43Channel (channel.emit, channel.play, etc.)
0x500x52Peer messaging
0x600x63Control flow (cf.jump, cf.branch, cf.call)
0x80debug.trace