Backend Emission¶
Compilation produces Bloq IR. An emitter translates that program into what a particular backend executes. Bloq provides:
a static emitter that writes one Stim circuit for a program without runtime control, and
the Bloq VM, a simple verification simulator for adaptive choices, mock decoder queries, retries, and waiting.
flowchart LR ir["Bloq IR"] --> stim["Stim emitter"] --> circuit["Stim circuit<br/>sampling, DEM"] ir --> vm["VM lowering"] --> run["Verification simulator<br/>mock decoder + timing model"] ir --> custom["Custom dynamic backend"] --> hw["Device controller + decoder"]
The VM checks compiled programs. Practical execution requires a separate backend with device-specific instructions, a real decoder, and calibrated timing.
Stim¶
Stim emits static Clifford block-graph compilations and selected Clifford proxies of non-Clifford graphs. It supports linear measurement parities and record-controlled Pauli corrections.
Compile and emit a gallery CNOT:
import bloq
program = bloq.compile(bloq.GalleryItem.CNOT.load(), distance=3)
circuit = bloq.emit_stim(program)
circuit.to_file("cnot.stim")
noisy = bloq.emit_stim(program, noise=0.001)
use bloq::prelude::*;
use bloq::stim::emit_bloq_stim;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let program = compile(&GalleryItem::CNOT.build(), 3)?;
std::fs::write("cnot.stim", emit_bloq_stim(&program)?)?;
Ok(())
}
bloq compile --gallery cnot -d 3 --backend ir-text -o cnot.bloqir
bloq emit cnot.bloqir -o cnot.stim
The circuit contains qubit coordinates, physical gates and measurements,
detectors, and logical observables. noise=p applies a uniform circuit noise
model. The Logical CNOT tutorial
walks through sampling, detector error models, and decoding.
The emitter builds the circuit in five steps:
Plan each node. For every quantum node, instantiate its templates at their offsets, merge them into one node-local circuit, and attach the node’s detectors and bundle uses.
Annotate noise. When
noise=pis requested, apply the noise model to each merged node circuit. Add supported gate and reset errors, measurement flips, and idle errors on the node’s own qubits. This happens before concatenation, so other nodes do not introduce extra idle noise.Order the nodes. Visit nodes in
deterministic_emit_order(), a topological order of the dependency graph.Concatenate. Append each node’s circuit in that order. Resolve every detector and observable to Stim
rec[-k]references in one global measurement record.Write observables. Each complete
Observablebecomes anOBSERVABLE_INCLUDEof its raw parity. Linear classical feedback folds into these parities.
Segmented and aligned emission
Node circuits are concatenated without aligning TICKs. This preserves
simulation results when all noise is explicitly annotated in the Stim circuit
before concatenation, as in Bloq’s per-node noise model. TICK counts are not
hardware timings, and external idle-noise models may over-count errors.
Segmented emission stores a shared header and node chunks with one global measurement record. Sample the assembled circuit.
Aligned emission (align_moments=True) merges compatible moments in noiseless
whole-program emission. It adds no memory rounds or hardware durations.
Bloq VM¶
Bloq VM simulates the execution of compiled Clifford and non-Clifford programs for verification, without physical noise by default. It models adaptive measurements, resource retries, mock decoder queries, and QEC during waits to check control flow and physical states. Circuit and idle noise are optional.
Lower the IR once, then run verification shots:
import bloq
ir = bloq.compile(bloq.GalleryItem.T_GATE.load(), distance=3)
program = bloq.lower_vm(ir, decoder_latency_rounds=3)
result = program.run(seed=17, input_state="plus")
if not result.discarded:
print(result.finished_at, result.logical_bloch())
use bloq::prelude::*;
use bloq::vm::{LoweringConfig, lower};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let ir = compile(&GalleryItem::T.build(), 3)?;
let options = LoweringConfig { decoder_latency_rounds: 3, ..Default::default() };
let program = lower(&ir, &options)?;
let result = program.run(options.runtime_config(17))?;
println!("discarded: {}", result.artifact.discarded);
Ok(())
}
Rust applications enable the VM with cargo add bloq@=0.1.1 --features vm.
Lowering runs once per program. It resolves the IR into the VM’s internal instruction stream: dense task, bit, qubit, and record ids, physical gate streams, and structured control. Each task records its dependencies, earliest release time, physical qubits, and nominal duration.
Bloq IR |
VM instruction |
Runtime behavior |
|---|---|---|
Quantum node |
|
Runs the alternative selected by its guard values |
|
|
Runs a fixed number of QEC rounds |
Join or retry output seam |
|
Runs QEC rounds until its producers are ready |
Unindexed |
Parity and binding tasks |
Shared recipe fragment; no decoder request |
Indexed |
|
One solve exposes |
|
|
Boolean bytecode over bit registers |
|
|
Rejects the shot |
|
|
Retries the body and publishes the accepted resource |
The runtime then advances one causal clock. A task starts when its dependencies are complete, its classical inputs are available, and its qubits are free. Measurements go to the simulator, decoder queries go to a mock streaming decoder, and waiting patches keep running QEC rounds. Synchronization explains these timing rules.
Important
The default decoder is a stochastic policy model, not a syndrome decoder. VM runs test control behavior and timing. They do not estimate a calibrated logical error rate.
Custom Backend¶
A custom backend consumes Bloq IR and implements its own device integration. The VM’s instruction JSON and traces support inspection and verification. They are not hardware-controller instructions.
Requirement |
A practical backend must provide |
|---|---|
Physical operations |
Device-specific gates, measurements, and qubit mapping |
Decoder decisions |
A real decoder connected to live measurement results |
Synchronization |
Calibrated device timing, classical latency, and QEC during waits |