{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/projects/gemm-accelerator-cyclonev",
    "result": {"data":{"mdx":{"frontmatter":{"title":"A GEMM Accelerator on the Cyclone V SoC","author":"Souleymane Dembele","date":"2026-06-13"},"body":"var _excluded = [\"components\"];\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\nfunction _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }\nfunction _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; }\n/* @jsxRuntime classic */\n/* @jsx mdx */\n\nvar _frontmatter = {\n  \"title\": \"A GEMM Accelerator on the Cyclone V SoC\",\n  \"slug\": \"projects/gemm-accelerator-cyclonev\",\n  \"author\": \"Souleymane Dembele\",\n  \"date\": \"2026-06-13\",\n  \"isDraft\": false\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n    props = _objectWithoutProperties(_ref, _excluded);\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"This is the project at the center of my MS ECE thesis: a custom general\\nmatrix-multiply (GEMM) accelerator taken from SystemVerilog RTL, through Qsys\\nintegration, to a full Linux bring-up on the ARM Cortex-A9 of a Terasic SoCKit\\n(Cyclone V SX). The software side is full-stack: a custom kernel build\\n(linux-socfpga), a device-tree overlay that enables the FPGA-HPS bridges and\\nbinds the accelerator's CSR window to a UIO device, and a user-space driver that\\nuses /dev/mem for register access and /dev/uio0 for done-IRQ synchronization.\"), mdx(\"h2\", null, \"The idea\"), mdx(\"p\", null, \"Edge inference spends most of its time in matrix multiply, and the precision a\\nmodel needs changes layer to layer. Rather than building a separate datapath per\\nprecision, I built one multi-precision multiply-accumulate (MAC) processing element\\nthat runs INT8, INT16, INT32, FP16, and FP32 in an 8x8 output-stationary\\nsystolic array. A 4x4 wide block carries the INT32 and FP32 modes; the\\nsurrounding 48 elements carry the narrow precisions.\"), mdx(\"h2\", null, \"How it runs\"), mdx(\"p\", null, \"A DMA engine streams tiles from shared DDR3 through a 16-entry load FIFO,\\npacking sub-word operands (four INT8 or two INT16 per 32-bit beat) into on-chip\\ntile buffers. A control plane exposes a CSR register file over Avalon-MM: the\\nhost writes the configuration, base addresses, and strides, then launches and\\neither polls or takes an interrupt on completion. Results drain row by row back\\nto DDR.\"), mdx(\"h2\", null, \"Results\"), mdx(\"p\", null, \"The design fits and runs on real silicon, with every DSP block in use (so the\\nmultipliers are hard DSP, not LUT fallback).\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Metric\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Value\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Device\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Cyclone V SX 5CSXFC6D6F31C6\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Logic (ALMs)\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"19,932 of 41,910 (48%)\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"DSP blocks\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"112 of 112 (100%)\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"On-chip RAM (M10K)\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"107 of 553 (19%)\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Fabric clock\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"50 MHz\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"INT8 GEMM, 512x512x512\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"3.15 GOP/s at 50 MHz, about 27x an ARM A9 software reference\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Compute utilization\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"60% of cycles, up from 19% on the naive baseline\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Memory stall\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"93% to 76% to 19% across the optimization ladder\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Correctness\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"60 of 60 on-board sweep tests pass, bit-exact\")))), mdx(\"p\", null, \"The design starts memory-bound and works its way off the bandwidth wall in four\\nsteps, all on the same 50 MHz fabric and the same DSP array. The naive\\none-tile-per-launch path reloads both operands from DDR on every output tile and\\nstalls about 93% of cycles, so the 32-bit FPGA-to-HPS bridge, not the PE array,\\nsets the ceiling. A wide-port DMA fast path (a wide-A tile buffer and parallel\\nB-bank fanout) brings the stall down to 76% and throughput to roughly 0.98 GOP/s.\\nTwo operand-locality optimizations then close most of the remaining gap. Keeping\\nthe A row-block resident across a tile row removes its redundant re-fetch and\\nlifts throughput to 1.55 GOP/s; double-buffering the B tile so the next K-tile\\nprefetches while the array computes the current one lifts it to 2.22 GOP/s and\\ncollapses the memory-stall ratio to 19%, moving the design from bandwidth-bound\\ntoward compute-bound. Finally, a hardware batch sequencer runs a full row of\\noutput tiles from one host launch instead of one launch per tile, cutting the\\nper-launch drain and dispatch overhead: hardware throughput reaches 3.15 GOP/s,\\ncompute occupancy climbs to 60% of cycles, and the workload runs about 27x an\\nARM A9 software reference, bit-exact. Every optimization is CSR-gated and\\ndefaults off, so the baseline binding is a strict subset. Functional correctness\\nis verified bit-exact against an ARM A9 software reference across all five\\nprecisions and a full K-sweep, with every optimization bit set.\"), mdx(\"h2\", null, \"What I took from it\"), mdx(\"p\", null, \"End-to-end ownership of a hardware accelerator: RTL design and verification\\n(leaf, wrapper, integration, and on-board layers), Qsys/Platform Designer\\nintegration, timing closure on real silicon, and the Linux bring-up that makes\\nit usable from software, from a custom kernel build and device-tree overlay\\nthrough the UIO binding and user-space driver.\"));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"slug":"projects/gemm-accelerator-cyclonev"}},
    "staticQueryHashes": ["3649515864","68838506"]}