Single-FPGA job queue with per-user fair share. Multiple users / projects
share one physical FPGA via firesim — without this queue, concurrent
firesim runworkload invocations clobber each other. With it, jobs land
in a queue and execute one at a time, weighted round-robin across users.
Currently installed at: $FIRESIM_QUEUE_ROOT/
(testing location — once validated we can move to /opt/firesim-queue/
for a proper global install. See Promoting to a global install below.)
- FIFO with priority hints + weighted round-robin by user. Higher
priority (
--priority 10) jumps the queue. Within a tier, alice doesn't get to run 10 jobs in a row while bob waits. - No preemption. Running jobs finish on their own — critical jobs jump the queue, not the FPGA.
- Per-job ETA from past similar runs. Submits print expected wall; the interactive dashboard shows projected start times in a Gantt chart.
- Per-user / per-job colors + project auto-detection. The dashboard
is readable when 5 users + 3 projects are jostling. Project name comes
from cwd (
/scratch2/<user>/<project>/...) — generic, no hardcoded list. - Chipyard-agnostic. Each user keeps their own chipyard tree(s); the
queue just locks the FPGA, doesn't know about chipyard. A user with
/scratch2/<user>/chipyard-v1/and/scratch2/<user>/chipyard-v2/sees both as distinct projects (chipyard-v1,chipyard-v2).
$FIRESIM_QUEUE_ROOT/
├── bin/
│ ├── firesim-queue # CLI shim (calls firesim_queue.py)
│ └── firesim_queue.py # the actual tool
├── jobs/<job_id>/ # per-job artifacts
│ ├── cmd.sh # exact bash command that ran
│ ├── stdout.log
│ └── stderr.log
├── queue.db # SQLite (WAL mode)
├── daemon.{pid,log}
├── fpga.lock # flock'd while a job runs the FPGA
└── README.md
PATH="$FIRESIM_QUEUE_ROOT/bin:$PATH" # optional
# Start the daemon (in another shell — leave it running).
firesim-queue daemon
# Submit a fake "FireSim run" that just sleeps. Returns when job completes.
firesim-queue submit --priority 5 -- bash -c 'echo hello; sleep 5'
# Watch the queue live.
firesim-queue interactive
# Keys: s submit · c cancel · t tail · u cycle usage window · r refresh · q quit
# Plain text listings.
firesim-queue status # active jobs
firesim-queue status --all # incl. terminal
firesim-queue usage --window 86400 # last 24h per-userbenchmarks/firesim_shuttle/run_all.sh honors FIRESIM_QUEUE=1. When set,
the script routes firesim infrasetup/runworkload/kill through
firesim-queue submit. Default (env unset) is byte-identical to the
pre-queue behavior — no surprise routing.
# In two different shells (or two different users):
FIRESIM_QUEUE=1 ./benchmarks/firesim_shuttle/run_all.sh dronet gemmini
FIRESIM_QUEUE=1 ./benchmarks/firesim_shuttle/run_all.sh yolov8n rvv
# The second one blocks behind the first. Check progress:
firesim-queue interactivefiresim-queue submit [--priority N] [--project NAME] [--cwd PATH] [--background] -- <command>
firesim-queue status [--user USER] [--all]
firesim-queue cancel <job_id> [--force]
firesim-queue tail <job_id> [-f]
firesim-queue interactive [--window SECONDS]
firesim-queue usage [--window SECONDS]
firesim-queue daemon
firesim-queue stop-daemon
--priority 0— low (background work, courtesy to others)--priority 5— normal (default)--priority 10— high (deadline-driven; jumps the queue but does NOT preempt)
Auto-inferred from cwd: /scratch2/<user>/<project>/... → <project>.
Override with --project NAME (useful for ad-hoc test invocations).
- Among
QUEUEDjobs, pick the highestpriority. - Within that tier, pick the user not served most recently (round-robin).
- Ties → oldest
submitted_at.
Daemon's choice is logged to daemon.log and reflected in interactive.
When you're ready to make this a system-wide tool (any user, any path):
sudo cp -r $FIRESIM_QUEUE_ROOT /opt/firesim-queuesudo chgrp -R <fpga-group> /opt/firesim-queue && sudo chmod -R g+rwX /opt/firesim-queuesudo ln -sf /opt/firesim-queue/bin/firesim-queue /usr/local/bin/firesim-queue- (Optional) Add a systemd unit so the daemon starts on boot:
# /etc/systemd/system/firesim-queue.service [Unit] Description=FireSim job queue After=network.target [Service] Type=simple User=<fpga-group-member> ExecStart=/opt/firesim-queue/bin/firesim-queue daemon Restart=on-failure [Install] WantedBy=multi-user.target
- Set
FIRESIM_QUEUE_ROOT=/opt/firesim-queuein/etc/profile.d/firesim-queue.shsofiresim-queuefinds the shared queue.db.
Once you're comfortable with the explicit FIRESIM_QUEUE=1 opt-in, you
can transparently intercept every firesim runworkload by dropping a
shim earlier on $PATH than the chipyard firesim:
# /usr/local/bin/firesim (or wherever's earliest on PATH)
#!/bin/bash
# Resolve the user's real chipyard firesim — the next firesim on PATH
# after this shim.
real=$(which -a firesim 2>/dev/null | grep -v "$(readlink -f "$0")" | head -1)
if [[ "$1" == "runworkload" ]]; then
exec /opt/firesim-queue/bin/firesim-queue submit \
--cwd "$PWD" -- "$real" "$@"
else
exec "$real" "$@"
fiThis is opt-in too — any user who doesn't want the queue just doesn't
have /usr/local/bin/firesim shadowing their chipyard one.
- SQLite WAL mode for concurrent reader/writer between daemon and multiple clients.
- flock on
fpga.lockheld for the entire duration a job runs. Even if a misbehaving caller bypasses the queue, the lock blocks concurrent FPGA access. - Daemon heartbeat kv row updated every 30s. If clients see the
heartbeat is older than 5 min, they assume the daemon died and the
next submitter should restart it (
systemctl restart firesim-queueorfiresim-queue daemon &). - Crash safety: jobs in
RUNNINGstate with a stale daemon are cleaned up on next daemon start (TODO — currently they just stayRUNNINGand confusestatus).
Every job's full stdout + stderr is at:
$FIRESIM_QUEUE_ROOT/jobs/<job_id>/{stdout,stderr}.log
Tail live with firesim-queue tail <job_id> -f.
When firesim runworkload is the job command, FireSim itself spawns a
screen session (typically named fsim0, sometimes derived from the
workload name — check screen -ls) to host the simulator. There are
three ways to watch the live output, with different reach:
| Method | Who can use it | Notes |
|---|---|---|
screen -r fsim0 |
only the user who owns the daemon (= the user the screen socket lives under: /run/screen/S-<daemon-user>/...) |
interactive, full terminal access to the simulator |
sudo -u <daemon-user> screen -r fsim0 |
any user with sudo to that account | same as above |
firesim-queue tail <job_id> --uartlog -f |
any user with read perms on the FireSim results dir | non-interactive, but identical content to the screen session |
The --uartlog mode finds the latest results-workload/.../uartlog under
the job's cwd and tails it. Same UART bytes as the screen, just streamed
through a regular file instead of a screen socket — works cross-user
without sudo. Recommended for monitoring; reserve screen -r for cases
where you actually need to interact with the simulator console.
If you want to know which screen session FireSim spawned, run
screen -ls as the daemon user — the name is printed there.
firesim-queue stop-daemon
# (then optionally rm -rf $FIRESIM_QUEUE_ROOT/ — keeps no
# user data outside that dir)