In short: NVMe replaces AHCI's single 32-slot command queue with up to 65,535 paired submission/completion queues of up to 65,536 entries each, pinned per CPU core and driven by lightweight doorbell registers, so an SSD can service thousands of commands in parallel instead of serializing them through one lock.
If you have ever wondered why the same NAND flash feels sluggish behind a SATA cable but screams on a PCIe lane, the answer is almost never the media. It is the command delivery model. AHCI, the protocol SATA inherited from the spinning-disk era, was designed when one command at a time and a seek arm were the bottleneck. NVMe was designed from a blank sheet for flash and for many-core CPUs. This article walks through the NVMe queue model, the doorbell mechanism, and exactly where AHCI's design assumptions fall apart.
The core abstraction: paired queues in host memory
NVMe is built on circular queues that live in host DRAM, not on the device. Each pair consists of a Submission Queue (SQ) and a Completion Queue (CQ). The host writes 64-byte command entries into an SQ; the controller writes 16-byte completion entries into the matching CQ. Because the queues are ring buffers in shared memory, the device uses DMA (bus-master PCIe transactions) to pull commands and push results without the CPU copying anything byte by byte.
There are two tiers. First, exactly one Admin Submission/Completion Queue pair handles controller management: creating and deleting I/O queues, identifying the namespace, setting features, firmware updates. Second, up to 65,535 I/O queue pairs carry the actual reads and writes. The admin queue is created at reset via the ASQ/ACQ base-address registers; I/O queues are then created with admin commands that tell the controller where each new ring lives in memory.
Key fact: NVMe 1.x permits up to 65,535 I/O queues, each holding up to 65,536 entries. AHCI permits exactly one command list of 32 slots. That is roughly 4.3 billion potential outstanding NVMe commands versus 32.
Doorbells: how the host and device signal each other
Queues are rings, so each side tracks a head and a tail pointer. The producer advances the tail; the consumer advances the head. The clever part is that neither side polls the other's pointer over the bus. Instead NVMe exposes a small array of doorbell registers in the controller's PCIe BAR (base address register) memory space.
When the host has placed one or more new commands in an SQ, it does a single MMIO write to that queue's SQ Tail Doorbell, telling the controller the new tail index. That one write is the only mandatory CPU-to-device transaction per batch. The controller then DMA-fetches the fresh entries, executes them in whatever order its arbitration allows, and DMA-writes completions into the CQ. It raises an MSI-X interrupt, and the host driver, after processing completions, writes the CQ Head Doorbell to release those slots. A phase tag bit in each completion entry flips every time the ring wraps, so the driver knows which entries are new without re-reading a shared pointer.
Key fact: Because many commands can sit between two doorbell writes, the amortized cost of a single doorbell ring across a batch of, say, 64 commands can be a fraction of a microsecond per I/O — a figure SATA's register-handshake command issue never approached.
Parallelism: one queue per core kills the lock
The architectural win is not raw queue depth, it is affinity. A modern NVMe driver creates one I/O queue pair per logical CPU and pins that pair's MSI-X interrupt vector to the same core. Core 0 submits to SQ0 and reaps completions from CQ0; core 16 uses SQ16/CQ16. Nothing is shared, so there is no global lock, no cache-line bouncing, and no inter-core synchronization on the hot path. Sixty-four cores can issue I/O simultaneously at full speed.
AHCI, by contrast, has a single command list behind a single set of registers. Every core that wants to issue I/O must take a lock on that shared structure. On a busy many-core server this lock contention, not the flash, caps throughput. This is precisely why a SATA SSD tops out around 100K IOPS while a single enterprise NVMe drive delivers well over a million, and why NVMe latency stays flat as thread count rises.
NVMe also defines weighted round-robin and urgent-priority arbitration across queues, so a latency-sensitive database queue can be favored over a bulk-backup queue — control that AHCI simply has no vocabulary for. If you are building or validating controllers at this level, the same host-interface thinking shows up in newer fabrics; see our companion piece on CXL protocol basics and the PCIe TLP fundamentals that carry every NVMe doorbell and DMA.
NVMe versus AHCI at a glance
| Attribute | AHCI (SATA) | NVMe (PCIe) |
|---|---|---|
| Max queues | 1 | 65,535 I/O + 1 admin |
| Queue depth | 32 | up to 65,536 per queue |
| Per-core affinity | No (shared, locked) | Yes (one pair per core) |
| Register writes per command | Multiple handshake reads/writes | One doorbell per batch |
| Interrupt model | Single / shared | Up to 2,048 MSI-X vectors |
| Transport | SATA link (~600 MB/s) | PCIe lanes (GB/s per lane) |
Where this matters in silicon design
For verification and physical-design engineers, the queue model is not just OS trivia. The controller's command arbiter, DMA engines, and doorbell register file are real RTL blocks that must be verified for corner cases: ring wrap, phase-tag coherency, SQ-full/CQ-full backpressure, and out-of-order completion. Teams routinely build UVM testbenches in Synopsys VCS or Cadence Xcelium that model 128 concurrent queues, and run coverage and lint through Siemens Questa to prove no doorbell write is lost under maximum contention. Power and timing closure of the doorbell BAR and MSI-X table is itself a non-trivial job in tools like Cadence Innovus or Synopsys Fusion Compiler, because those registers sit on the latency-critical path from PCIe ingress to NAND dispatch.
If your team needs that depth of protocol verification or controller design support, Avecas offers VLSI design and verification services that cover exactly these storage-interface blocks.
Want to master NVMe, PCIe, and CXL at RTL and verification level? CourseTron's protocol and interface courses take you from the queue model to a full UVM testbench. Explore the CourseTron course catalog →
Frequently asked questions
Why does NVMe need both a submission and a completion queue instead of one?
Separating them decouples the two data flows. The host can keep filling the SQ while the controller independently drains it and posts results to the CQ, with their own head/tail pointers. A single shared queue would force serialization and shared-pointer contention — the exact problem NVMe was designed to avoid.
What exactly is a doorbell register?
It is a small write-only MMIO location in the controller's PCIe BAR space. Writing the new tail (or head) index to it is how the host and device notify each other that a ring pointer moved, without polling each other's memory over the bus.
Does more queue depth always mean more performance?
No. Depth hides latency by keeping the pipe full, but once the device is saturated, adding depth only raises queuing latency. The bigger NVMe win is per-core queue affinity, which removes lock contention regardless of absolute depth.
Can NVMe run over something other than PCIe?
Yes. NVMe over Fabrics (NVMe-oF) carries the same queue model over RDMA, Fibre Channel, or TCP, replacing PCIe doorbells with fabric-specific capsules while preserving the SQ/CQ abstraction.