QubiC Example

Connect a QubiC backend to Coda with coda-qubic.

coda-qubic is an example coda-node integration for QubiC systems. Use it as a reference for connecting your own QPU with a device YAML, executor factory, native IR translator, hardware runner, and node startup flow.

About QubiC

QubiC is an open-source FPGA-based control and measurement system for superconducting quantum processors, developed at Lawrence Berkeley National Laboratory’s Advanced Quantum Testbed. It provides a modular hardware and software stack for implementing qubit control, measurement, and characterization protocols.

For more details, see the QubiC documentation and the QubiC paper on arXiv.

Runtime Flow

At runtime, the integration follows this flow:

  • Coda sends a compiled NativeGateIR job to the node.
  • coda-qubic translates the IR into QubiC gate programs.
  • The runner executes the program on QubiC hardware, a QubiC RPC server, or a simulator.

coda-qubic requires Python 3.12 or newer. coda-node itself supports Python 3.11 or newer.

What coda-qubic provides

  • coda_qubic.executor_factory:create_executor for coda-node
  • QubiCConfig for validating device YAML
  • QubiCJobRunner, which implements executor.run(ir, shots)
  • Translation from Coda NativeGateIR to QubiC gate-level programs
  • RPC, local hardware, pulse simulator, and Qiskit noisy simulator modes
  • Optional cancellation support through cancel_current_job()

Install

Clone the integration and install dependencies:

$git clone https://github.com/conductorquantum/coda-qubic.git
$cd coda-qubic
$uv sync --dev

For QubiC hardware, install the QubiC stack as well:

$./scripts/install-qubic-stack.sh

For the Qiskit simulator path, install the optional Qiskit dependency group:

$uv pip install 'coda-qubic[qiskit]'

Configure a QubiC lab

For a real QubiC setup, collect these files from the lab:

  • qubitcfg.json: qubit calibration and gate definitions
  • channel_config.json: FPGA channel mapping
  • classifier.pkl or classifier JSON: readout discrimination

Create site/device.yaml next to those files:

1target: cnot
2num_qubits: 20
3calibration_path: ./qubitcfg.json
4channel_config_path: ./channel_config.json
5classifier_path: ./classifier.pkl
6
7runner_mode: rpc
8rpc_host: 192.168.1.120
9rpc_port: 9095

All file paths are resolved relative to the YAML file. The num_qubits value must match the device derived from the calibration file.

Simulator configuration

For end-to-end testing without hardware or QubiC vendor dependencies, use Qiskit noisy simulation:

1framework: qubic
2target: cnot
3num_qubits: 3
4runner_mode: qiskit_sim
5
6# Optional noise parameters
7# single_qubit_error_rate: 0.001
8# two_qubit_error_rate: 0.01
9# measurement_error_rate: 0.01

This mode reports a synthetic device and executes jobs with Qiskit AerSimulator.

Validate locally

Validate the YAML:

$uv run python -c "
>from coda_qubic.config import QubiCConfig
>
>config = QubiCConfig.from_yaml('site/device.yaml')
>print('OK:', config.target, config.num_qubits, 'qubits')
>"

Run a small circuit through the QubiC executor:

$uv run python -c "
>import asyncio
>from coda_node.server.ir import GateOp, IRMetadata, NativeGateIR
>from coda_qubic.config import QubiCConfig
>from coda_qubic.executor_factory import build_executor
>
>config = QubiCConfig.from_yaml('site/device.yaml')
>executor = build_executor(config)
>
>ir = NativeGateIR(
> num_qubits=config.num_qubits,
> target=config.target,
> gates=[
> GateOp(gate='x90', qubits=[0], params=[]),
> GateOp(gate='cnot', qubits=[0, 1], params=[]),
> ],
> measurements=[0, 1],
> metadata=IRMetadata(source_hash='test', compiled_at='2026-04-27T00:00:00Z'),
>)
>
>result = asyncio.run(executor.run(ir, shots=100))
>print(result.counts)
>"

Run through coda-node

If coda-qubic is the only backend package installed and ./site/device.yaml exists, coda-node can discover the executor factory automatically:

$uv run coda-node start --token <node-token>

To be explicit:

$CODA_EXECUTOR_FACTORY=coda_qubic.executor_factory:create_executor \
>CODA_DEVICE_CONFIG=./site/device.yaml \
>uv run coda-node start --token <node-token>

If your node token requires VPN mode, OpenVPN may need elevated permissions to create the tunnel interface:

$sudo env CODA_DEVICE_CONFIG=./site/device.yaml uv run coda-node start --token <node-token>

After first connect, credentials are persisted. Restart without a token:

$uv run coda-node start

How the example maps to the Node model

coda-qubic is a reference for the main integration points:

  • Device config: QubiCConfig.from_yaml("site/device.yaml")
  • Factory: coda_qubic.executor_factory:create_executor
  • Executor: QubiCJobRunner.run(ir, shots)
  • Translation: Coda native gate IR to QubiC gate programs
  • Results: QubiC counts normalized into ExecutionResult
  • Cancellation: cancel_current_job() sets a cancel flag checked by the runner

Use the same shape for your own backend package, replacing QubiC-specific config, translation, and hardware calls with your device stack.