Skip to content

Architecture

Duck Server is a heterogeneous streaming SoC. A 32-bit instruction stream arrives from a network fabric, is classified by the NoC router, executed by the CPU or NPU, and results are delivered wirelessly to remote displays. A MOESI coherency controller (v4) mediates shared memory between the CPU and NPU DMA.

The block diagram and module map below describe the logical dataflow, unchanged since v4. As of v5, the router/CPU/NPU/MOESI blocks are physically split across three dies (base die, CPU chiplet, NPU chiplet) connected by modeled UCIe links — see chiplets.md for the package-level view, link training, and flit channels.


System Block Diagram

┌────────────────────────────────────────────────────────────────┐
│                     NETWORK DATA STREAM                         │
│              (32-bit instruction words, net_valid/ready)        │
└───────────────────────────┬────────────────────────────────────┘
                            │
                            ▼
             ┌──────────────────────────┐
             │       NoC ROUTER         │
             │  classify by opcode[6:0] │
             │  cpu_accept single-pulse │
             └────────┬─────────────────┘
                      │                  │
               cpu    │           npu    │
                      ▼                  ▼
        ┌─────────────────────┐   ┌──────────────────────┐
        │    RV32IM CPU       │   │     NPU v3            │
        │  5-stage pipeline   │   │  16×16 MAC array      │
        │  MUL (3 cy)         │   │  INT8 / INT16         │
        │  DIV (34 cy)        │   │  Ping-pong weights    │
        │  Forwarding + flush │   │  COMPUTE_ACC (tiling) │
        └──────────┬──────────┘   └──────────────────────┘
                   │ cpu_read/write                │ npu_dma_read/addr
                   │ cpu_stall ◄────────┐          │
                   ▼                   │          ▼
        ┌──────────────────────────────────────────────────┐
        │          MOESI COHERENCY CONTROLLER               │
        │  4-way set-assoc L2, 16 sets, 8-word lines (2KB) │
        │  Write-back + write-allocate, round-robin LRU     │
        │  CPU master: read/write, stall on miss            │
        │  NPU DMA master: read-only, M→S flush on dirty   │
        └──────────────────────────┬───────────────────────┘
                                   │ dram_read/write/ack
                                   ▼
                        ┌─────────────────────┐
                        │    External DRAM     │
                        │  (ack-based, 1-cy)  │
                        └─────────────────────┘
                                   │
        NPU result bus ────────────┘
        (result_valid/index/data)
                        ▼
        ┌─────────────────────────────────┐
        │   Wireless Peripheral Driver    │
        │   (stub — interface defined)    │
        └─────────────────────────────────┘

Module Map

RTL File Role Version
system_top.sv Chip boundary; package assembly of the three dies below v5
base_die_top.sv Base/I-O die: NoC router, MOESI L2, external I/O new in v5
cpu_chiplet_top.sv CPU chiplet: riscv_core + imem + halt detect new in v5
npu_chiplet_top.sv NPU chiplet: npu + result buffering new in v5
ucie_chan.sv Generic UCIe mainband channel: credit flow control + crossing latency new in v5
ucie_link_ctrl.sv UCIe link training FSM (functional stub) new in v5
duck_ucie_pkg.sv Flit field/opcode reference (docs only, not imported) new in v5
duck_coherency_ctrl.sv MOESI L2 cache + 2-master coherency FSM (9 states) new in v4
noc_router.sv Opcode classifier, cpu_accept single-pulse, NPU dispatch v3
riscv_core.sv 5-stage RV32IM pipeline, stall/flush, mem_stall input v3+
alu.sv ADD/SUB/logic/SHL/SHR, MUL (3-cy), DIV/DIVU/REM/REMU (34-cy) v2+
npu.sv 16×16 MAC array, INT8/INT16, ping-pong banks, COMPUTE_ACC v3
pe.sv Single weight-stationary INT8 MAC cell v1
imm_decode.sv RV32 immediate extractor (I/S/B/U/J) v1
register_file.sv 32×32-bit regfile, x0 always 0 v1

See chiplets.md for how the top four rows fit together.


Key Design Decisions

Why write-back L2 (not write-through)? Write-back reduces DRAM bandwidth by absorbing repeated writes to the same cache line — important for weight update loops and accumulation patterns in the NPU. The tradeoff is the need for a dirty bit and a flush path (now handled by the MOESI controller).

Why a 34-cycle iterative divider (not pipelined)? A pipelined divider needs 32 pipeline stages or a complex radix-4/SRT design. The iterative approach uses one adder-subtractor per cycle and takes 34 cycles. For the target workload (matrix indexing, not hot-path integer divide), this is the right area-vs-performance tradeoff.

Why MOESI (not MESI or MOSI)? The "O" (Owned) state is not implemented here — the 2-master system simplifies to M→S (flush dirty line, then serve NPU). The protocol is called MOESI in the codebase because it follows the MOESI class of coherency protocols at the conceptual level and is designed to extend to the full protocol when more masters are added.

Why a single-cycle dram_ack model? The simulation DRAM model acks one cycle after the request. Real DRAM latency would be 100–200 cycles; the 1-cycle model lets the cocotb testbench run fast while still exercising the full fill/flush/ack protocol. The coherency controller FSM is correct regardless of ack latency — it simply waits in its fill/flush states until ack arrives.


PicoRV32 (Claire Wolf)

What it is: Minimal RV32IMC soft-core in ~2500 lines of Verilog. Single-cycle or two-stage pipeline, no cache.
Similarities: Both target embedded/SoC use cases with a clean, readable RTL style. riscv_core.sv is in the same spirit — compact and simulation-friendly.
Differences: Duck Server adds a 5-stage pipeline with forwarding, a 4-way L2 cache, and a co-processor (NPU) tightly coupled to the same memory system. PicoRV32 has no data cache or co-processor port.
Reference: https://github.com/YosysHQ/picorv32

VexRiscv (SpinalHDL)

What it is: Fully configurable RV32 pipeline written in SpinalHDL (Scala DSL). Plugins for MMU, FPU, cache, etc.
Similarities: Both implement 5-stage pipelines with optional extensions.
Differences: VexRiscv is a framework where the pipeline is assembled from plugins; duck_server is monolithic and purpose-built for the streaming SoC use case. VexRiscv has no equivalent of the NPU DMA coherency interface.
Reference: https://github.com/SpinalHDL/VexRiscv

Ibex (lowRISC)

What it is: 2-stage RV32IMC pipeline, formally verified, used in OpenTitan.
Similarities: Both target real verification (cocotb testbench here, FPV in Ibex).
Differences: Ibex prioritises security (side-channel hardening, formal proofs). Duck Server prioritises throughput (wide NPU MAC array, ping-pong weight banks, coherent DMA). Ibex has no data cache or NPU.
Reference: https://github.com/lowRISC/ibex

NVDLA (NVIDIA)

What it is: Full deep learning accelerator RTL (conv engine, pooling, activation, normalise) open-sourced in 2017.
Similarities: Both use a weight-stationary MAC array and a multi-master coherency mechanism between the CPU and the accelerator.
Differences: NVDLA is orders of magnitude more complex (millions of gates, full AXI interconnect, compiler toolchain). Duck Server's NPU is a 16×16 MAC slice — the same conceptual building block NVDLA uses for its "atomic" unit, but without the surrounding convolution buffer, CBUF, and CDMA infrastructure.
Reference: https://github.com/nvdla/hw

Rocket Chip (UC Berkeley)

What it is: Full RISC-V SoC generator in Chisel. Includes L1/L2 caches, TileLink coherency fabric, and extensible co-processor interface (RoCC).
Similarities: The MOESI coherency controller in duck_server serves the same role as the Rocket L2 + TileLink coherency fabric — mediating between CPU and accelerator DMA.
Differences: Rocket's coherency is a full directory protocol over TileLink with multiple agents. Duck Server's is a 2-master simplified FSM optimised for readability and simulation speed. Rocket targets production silicon; duck_server targets teaching and rapid verification.
Reference: https://github.com/chipsalliance/rocket-chip

CVA6 / Ariane (ETH Zürich)

What it is: 6-stage RV64GC out-of-order core with MMU, 32KB L1I/D caches, AXI interface.
Similarities: Both implement write-back caches with dirty bits and writeback on replacement.
Differences: CVA6 targets high-performance (OOO, 64-bit, Linux-capable). Duck Server's CPU is a simple in-order 5-stage for area efficiency; the compute heavy-lifting is done by the NPU, not the CPU.
Reference: https://github.com/openhwgroup/cva6

Summary Table

Project Pipeline Cache Coherency Co-processor Focus
Duck Server 5-stage RV32IM 4-way L2 write-back 2KB 2-master MOESI 16×16 NPU INT8/INT16 Streaming SoC, teaching
PicoRV32 1–2 stage RV32I(M)(C) None None None Minimal footprint
VexRiscv 5-stage RV32(I/M/C/F) Optional None None Configurable framework
Ibex 2-stage RV32IMC None None None Security, formal proof
Rocket Chip 5-stage+ RV64GC L1+L2 TileLink MESI RoCC interface Production SoC
CVA6 6-stage OOO RV64GC L1 I+D, optional L2 AXI coherency None High-performance
NVDLA N/A CBUF scratchpad AXI + SDP Full DLA pipeline DNN inference