<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ayush Dwivedi's Engineering Log]]></title><description><![CDATA[Ayush Dwivedi's Engineering Log]]></description><link>https://ayushd785.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 08:28:11 GMT</lastBuildDate><atom:link href="https://ayushd785.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From 641 to 35,000 AudioMarks: My Journey Optimizing EEMBC AudioMark for RISC-V Vector Extension]]></title><description><![CDATA[The Beginning: A Challenge Wrapped in an Opportunity
Pushing the SpacemiT K3 to the Limit: Vectorizing AudioMark for RISC-V"
When I started exploring RISC-V Vector extension (RVV) optimization, I had no idea I'd end up spending weeks wrestling with v...]]></description><link>https://ayushd785.hashnode.dev/from-641-to-35000-audiomarks-my-journey-optimizing-eembc-audiomark-for-risc-v-vector-extension</link><guid isPermaLink="true">https://ayushd785.hashnode.dev/from-641-to-35000-audiomarks-my-journey-optimizing-eembc-audiomark-for-risc-v-vector-extension</guid><category><![CDATA[spacemiT]]></category><category><![CDATA[LFX Mentorship]]></category><category><![CDATA[risc-v]]></category><category><![CDATA[audiomark]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Ayush Dwivedi]]></dc:creator><pubDate>Wed, 28 Jan 2026 22:38:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769639677773/df2e59aa-62b8-4c6e-aeed-53cd01fd69df.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h2 id="heading-the-beginning-a-challenge-wrapped-in-an-opportunity"><strong>The Beginning: A Challenge Wrapped in an Opportunity</strong></h2>
<p><em><mark>Pushing the SpacemiT K3 to the Limit: Vectorizing AudioMark for RISC-V"</mark></em></p>
<p>When I started exploring <strong>RISC-V Vector extension (RVV)</strong> optimization, I had no idea I'd end up spending weeks wrestling with vector intrinsics, Docker containers, and QEMU emulation. But that's exactly what happened , and the results were beyond anything I expected.</p>
<p>The goal was deceptively simple: <strong>Port EEMBC's AudioMark benchmark to RISC-V and optimize it using the Vector extension (RVV).</strong></p>
<p>AudioMark is an industry-standard benchmark that measures audio processing performance through four key components:</p>
<ul>
<li><p><strong>Beamforming (ABF)</strong> — Combining signals from multiple microphones</p>
</li>
<li><p><strong>Acoustic Echo Cancellation (AEC)</strong> — Removing echo in real-time</p>
</li>
<li><p><strong>Noise Reduction (ANR)</strong> — Filtering background noise</p>
</li>
<li><p><strong>Keyword Spotting (KWS)</strong> — "Hey Siri" style voice detection</p>
</li>
</ul>
<p>The catch? AudioMark was designed for <strong>ARM processors with CMSIS-DSP</strong>. RISC-V had no optimized port. Until now.</p>
<h3 id="heading-project-overview"><strong>Project Overview:</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769635312177/5ad168ca-7a7a-4840-a427-54e9bf4322fc.jpeg" alt class="image--center mx-auto" /></p>
<p><em>PS: My drawings look like hieroglyphs. The code is readable, though</em></p>
<p><strong>Phase 1: Foundation</strong></p>
<ul>
<li><p>Built the <strong>RISC-V GCC cross-compiler toolchain</strong> from source</p>
</li>
<li><p>Created a <strong>scalar</strong> (<em>non-vectorized</em>) baseline to measure initial performance</p>
</li>
<li><p><strong>Result:</strong> <mark>641 AudioMarks</mark> — our starting point</p>
</li>
</ul>
<p><strong>Phase 2: Optimization</strong></p>
<ul>
<li><p>Studied <strong>VOLK</strong> (<em>Vector-Optimized Library of Kernels</em>) to learn proven RVV patterns</p>
</li>
<li><p>Implemented <strong>12 vectorized DSP functions using RVV 1.0 intrinsics</strong></p>
</li>
<li><p><strong>Dockerized</strong> the entire toolchain for easy reproducibility</p>
</li>
</ul>
<p><strong>Phase 3: Validation</strong></p>
<ul>
<li><p>Tested on <strong>QEMU</strong> emulator with RVV enabled</p>
</li>
<li><p>Sent static binary to real <strong>SpacemiT K3</strong> hardware for validation</p>
</li>
<li><p><strong>Result:</strong> <strong>35,418 AudioMarks</strong> — a <strong><em>55x improvement</em></strong> over baseline!</p>
</li>
</ul>
<hr />
<h2 id="heading-phase-1-the-brain-without-a-body-problem"><strong>Phase 1: The "Brain Without a Body" Problem</strong></h2>
<h3 id="heading-the-painful-first-steps"><strong>The Painful First Steps</strong></h3>
<p>My first attempt was... humbling. I built the RISC-V GCC toolchain from source:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/riscv-collab/riscv-gnu-toolchain
<span class="hljs-built_in">cd</span> riscv-gnu-toolchain
./configure --prefix=/opt/riscv --disable-gdb
make linux -j$(nproc)  <span class="hljs-comment"># 45 minutes later...</span>
</code></pre>
<p>The compiler worked! I could cross-compile for RISC-V. But when I tried to run my binary on QEMU:</p>
<pre><code class="lang-bash">$ qemu-riscv64-static ./my_program
./my_program: error <span class="hljs-keyword">while</span> loading shared libraries: libc.so.6: cannot open shared object file
</code></pre>
<p><strong>The sysroot was missing.</strong> I had built a brain (the compiler) but forgot the body (the C library).</p>
<p>After hours of debugging, I discovered the <code>CMAKE_SYSROOT</code> variable and pointed it to <code>/opt/riscv/sysroot</code>. Finally, my "Hello World" ran on QEMU.</p>
<pre><code class="lang-bash">$ qemu-riscv64-static -L /opt/riscv/sysroot ./hello
Hello from RISC-V!
</code></pre>
<p><mark>My first scalar AudioMark score: </mark> <strong><mark>641 AudioMarks</mark></strong><mark>.</mark></p>
<p><em>Not impressive. But it was a baseline.</em></p>
<p>*You can give it a read here (Its fun)-<br />*<a target="_blank" href="https://ayushd785.hashnode.dev/from-scratch-to-sysroot-porting-the-eembc-audiomark-benchmark-to-risc-v-linux?showSharer=true"><em>From Scratch to Sysroot: Porting the EEMBC AudioMark Benchmark to RISC-V Linux</em></a></p>
<hr />
<h2 id="heading-milestone-1-the-portable-lab-dockerizing-everything"><strong>Milestone 1: The Portable Lab — Dockerizing Everything</strong></h2>
<h3 id="heading-the-problem-with-works-on-my-machine"><strong>The Problem with "Works on My Machine"</strong></h3>
<p>Feedback from the RISC-V community was clear: <em>"If someone wants to run your code on their Banana Pi F3, they shouldn't spend 60 minutes building a toolchain."</em></p>
<p>He was right. My setup was fragile. One wrong environment variable, one missing library, and everything breaks. The solution? <strong>Docker.</strong></p>
<h3 id="heading-building-the-ultimate-risc-v-development-container"><strong>Building the Ultimate RISC-V Development Container</strong></h3>
<p>I created a multi-stage Dockerfile that:</p>
<ol>
<li><p><strong>Stage 1 (Builder):</strong> Builds the RISC-V toolchain and QEMU 8.2 from source</p>
</li>
<li><p><strong>Stage 2 (Final):</strong> Contains only the compiled binaries (~1GB instead of 5GB)</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769636660158/37ab8c28-f15c-4ada-9122-16fd24c35b89.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769636887815/10deef68-1dd0-497d-8ab9-b3a5e8fcdaa2.png" alt class="image--center mx-auto" /></p>
<p><strong>Now anyone could run:</strong></p>
<pre><code class="lang-bash">docker pull ayushd785/riscv-audio-lab
docker run -it -v $(<span class="hljs-built_in">pwd</span>):/work riscv-audio-lab
<span class="hljs-comment"># Instant RISC-V development environment!</span>
</code></pre>
<hr />
<h2 id="heading-milestone-2-learning-from-the-masters-analyzing-volk"><strong>Milestone 2: Learning from the Masters — Analyzing VOLK</strong></h2>
<p><em>Before writing a single line of RVV code, I studied how the experts did it.</em> <strong><em>VOLK</em></strong> <em>(Vector-Optimized Library of Kernels) had recently merged an RVV port by Olaf Bernstein in</em> <a target="_blank" href="https://github.com/gnuradio/volk/pull/774"><em>PR #774</em></a><em>.</em></p>
<h3 id="heading-the-four-cheat-codes-i-discovered"><strong>The Four "Cheat Codes" I Discovered</strong></h3>
<h4 id="heading-cheat-code-1-strip-mining-vlen-independence"><strong>Cheat Code #1: Strip-Mining (VLEN Independence)</strong></h4>
<p>RISC-V Vector doesn't have a fixed vector length. Different chips have different VLEN:</p>
<ul>
<li><p>SpacemiT K1: VLEN=128 (16 bytes)</p>
</li>
<li><p>SiFive: VLEN=256 (32 bytes)</p>
</li>
<li><p>Future chips: VLEN=512+</p>
</li>
</ul>
<p>How do you write code that works on ALL of them? <strong>Strip-mining.</strong></p>
<pre><code class="lang-bash">// The magic loop pattern
size_t vl;
<span class="hljs-keyword">for</span> (; n &gt; 0; n -= vl, x += vl, y += vl, z += vl) {
    vl = __riscv_vsetvl_e32m8(n);  // Ask: <span class="hljs-string">"How many elements can you handle?"</span>
    // Process <span class="hljs-string">'vl'</span> elements...
}
</code></pre>
<p><em>The</em> <code>vsetvl</code> <em>instruction queries the hardware at runtime. Your code automatically adapts to any VLEN without recompilation</em>!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769636624047/df7d6ab5-8f62-46b4-a3c4-eb5d84724e7f.jpeg" alt class="image--center mx-auto" /></p>
<h4 id="heading-cheat-code-2-lmul-register-grouping"><strong>Cheat Code #2: LMUL (Register Grouping)</strong></h4>
<p>LMUL controls how many vector registers are grouped together:</p>
<ul>
<li><p><strong>LMUL=1:</strong> Use 1 register per operand (8 available)</p>
</li>
<li><p><strong>LMUL=8:</strong> Group 8 registers together (maximum throughput)</p>
</li>
</ul>
<p>For simple operations like addition, use LMUL=8:</p>
<pre><code class="lang-bash">vfloat32m8_t vx = __riscv_vle32_v_f32m8(x, vl);  // m8 = LMUL=8
vfloat32m8_t vy = __riscv_vle32_v_f32m8(y, vl);
vfloat32m8_t vz = __riscv_vfadd_vv_f32m8(vx, vy, vl);
__riscv_vse32_v_f32m8(z, vz, vl);
</code></pre>
<h4 id="heading-cheat-code-3-deferred-reduction-dot-products"><strong>Cheat Code #3: Deferred Reduction (Dot Products)</strong></h4>
<p>The naive approach reduces to scalar on every iteration — expensive!</p>
<pre><code class="lang-bash">// WRONG: Scalar reduction every iteration (slow)
<span class="hljs-built_in">float</span> sum = 0;
<span class="hljs-keyword">for</span> (int i = 0; i &lt; n; i++) {
    sum += a[i] * b[i];
}
</code></pre>
<p>The VOLK pattern: <strong>accumulate in vectors, reduce once at the end:</strong></p>
<pre><code class="lang-bash">// RIGHT: Vector accumulation, single reduction
vfloat32m8_t v_sum = __riscv_vfmv_v_f_f32m8(0.0f, __riscv_vsetvlmax_e32m8());

<span class="hljs-keyword">for</span> (; n &gt; 0; n -= vl, x += vl, y += vl) {
    vl = __riscv_vsetvl_e32m8(n);
    vfloat32m8_t vx = __riscv_vle32_v_f32m8(x, vl);
    vfloat32m8_t vy = __riscv_vle32_v_f32m8(y, vl);
    v_sum = __riscv_vfmacc_vv_f32m8(v_sum, vx, vy, vl);  // FMA into accumulator
}

// Single horizontal reduction
vfloat32m1_t v_zero = __riscv_vfmv_v_f_f32m1(0.0f, 1);
vfloat32m1_t v_result = __riscv_vfredusum_vs_f32m8_f32m1(v_sum, v_zero, __riscv_vsetvlmax_e32m8());
<span class="hljs-built_in">float</span> result = __riscv_vfmv_f_s_f32m1_f32(v_result);
</code></pre>
<h4 id="heading-cheat-code-4-segmented-loads-complex-numbers"><strong>Cheat Code #4: Segmented Loads (Complex Numbers)</strong></h4>
<p>Complex numbers are stored interleaved: <code>[Real0, Imag0, Real1, Imag1, ...]</code></p>
<p>Normal loads would mix real and imaginary parts. The solution? <strong>Segmented loads:</strong></p>
<pre><code class="lang-bash">// Magic: Loads into two separate registers automatically!
vfloat32m4x2_t vx = __riscv_vlseg2e32_v_f32m4x2(ptr, vl);
vfloat32m4_t real = __riscv_vget_v_f32m4x2_f32m4(vx, 0);
vfloat32m4_t imag = __riscv_vget_v_f32m4x2_f32m4(vx, 1);
</code></pre>
<p><strong><em><mark>This single instruction de-interleaves complex numbers across two vector register groups.</mark></em></strong></p>
<hr />
<h2 id="heading-milestone-3-the-engine-upgrade-implementing-rvv-kernels"><strong>Milestone 3: The Engine Upgrade — Implementing RVV Kernels</strong></h2>
<p>Armed with the VOLK cheat codes, I created <code>audiomark/ports/riscv/th_api.c</code> — the heart of my optimization.</p>
<h3 id="heading-the-functions-i-vectorized"><strong>The Functions I Vectorized</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Function</strong></td><td><strong>Strategy</strong></td><td><strong>Key Intrinsics</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>th_add_f32</code></td><td>Element-wise, LMUL=8</td><td><code>vfadd_vv</code></td></tr>
<tr>
<td><code>th_subtract_f32</code></td><td>Element-wise, LMUL=8</td><td><code>vfsub_vv</code></td></tr>
<tr>
<td><code>th_multiply_f32</code></td><td>Element-wise, LMUL=8</td><td><code>vfmul_vv</code></td></tr>
<tr>
<td><code>th_dot_prod_f32</code></td><td>Deferred reduction</td><td><code>vfmacc_vv</code>, <code>vfredusum</code></td></tr>
<tr>
<td><code>th_cmplx_mult_cmplx_f32</code></td><td>Segmented, LMUL=4</td><td><code>vlseg2e32</code>, <code>vsseg2e32</code></td></tr>
<tr>
<td><code>th_cmplx_conj_f32</code></td><td>Segmented</td><td><code>vfneg</code></td></tr>
<tr>
<td><code>th_cmplx_mag_f32</code></td><td>Segmented</td><td><code>vfsqrt</code></td></tr>
<tr>
<td><code>th_int16_to_f32</code></td><td>Widening</td><td><code>vwcvt_x</code>, <code>vfcvt_f</code></td></tr>
<tr>
<td><code>th_f32_to_int16</code></td><td>Narrowing</td><td><code>vfcvt_x</code>, <code>vncvt</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-the-boss-fight-complex-multiplication"><strong>The "Boss Fight": Complex Multiplication</strong></h3>
<p>Complex multiplication is the most computationally intensive operation in audio processing. The formula:</p>
<pre><code class="lang-bash">(a + bi) × (c + di) = (ac - bd) + (ad + bc)i
</code></pre>
<p>My vectorized implementation:</p>
<pre><code class="lang-bash">void th_cmplx_mult_cmplx_f32(const <span class="hljs-built_in">float</span> *x, const <span class="hljs-built_in">float</span> *y, <span class="hljs-built_in">float</span> *z, uint32_t len) {
    size_t vl;
    int n = (int)len;

    <span class="hljs-keyword">for</span> (; n &gt; 0; n -= vl, x += 2*vl, y += 2*vl, z += 2*vl) {
        vl = __riscv_vsetvl_e32m4(n);

        // De-interleave: load [R0,I0,R1,I1,...] into separate real/imag registers
        vfloat32m4x2_t vx = __riscv_vlseg2e32_v_f32m4x2(x, vl);
        vfloat32m4x2_t vy = __riscv_vlseg2e32_v_f32m4x2(y, vl);

        vfloat32m4_t xr = __riscv_vget_v_f32m4x2_f32m4(vx, 0);  // x real
        vfloat32m4_t xi = __riscv_vget_v_f32m4x2_f32m4(vx, 1);  // x imaginary
        vfloat32m4_t yr = __riscv_vget_v_f32m4x2_f32m4(vy, 0);  // y real
        vfloat32m4_t yi = __riscv_vget_v_f32m4x2_f32m4(vy, 1);  // y imaginary

        // Real part: ac - bd
        vfloat32m4_t zr = __riscv_vfmul_vv_f32m4(xr, yr, vl);   // ac
        zr = __riscv_vfnmsac_vv_f32m4(zr, xi, yi, vl);          // ac - bd

        // Imaginary part: ad + bc
        vfloat32m4_t zi = __riscv_vfmul_vv_f32m4(xr, yi, vl);   // ad
        zi = __riscv_vfmacc_vv_f32m4(zi, xi, yr, vl);           // ad + bc

        // Re-interleave and store
        vfloat32m4x2_t vz = __riscv_vset_v_f32m4_f32m4x2(
            __riscv_vundefined_f32m4x2(), 0, zr);
        vz = __riscv_vset_v_f32m4_f32m4x2(vz, 1, zi);
        __riscv_vsseg2e32_v_f32m4x2(z, vz, vl);
    }
}
</code></pre>
<p>This single function processes hundreds of complex multiplications per loop iteration!</p>
<h3 id="heading-the-cmake-configuration"><strong>The CMake Configuration</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># port.cmake</span>
include_directories(<span class="hljs-variable">${PORT_DIR}</span>)

<span class="hljs-comment"># Enable RVV in compiler</span>
<span class="hljs-built_in">set</span>(CMAKE_C_FLAGS <span class="hljs-string">"<span class="hljs-variable">${CMAKE_C_FLAGS}</span> -march=rv64gcv -mabi=lp64d"</span>)

<span class="hljs-comment"># Use SMALLFT for FFT (SpeexDSP built-in)</span>
add_definitions(-DUSE_SMALLFT)

<span class="hljs-comment"># Work around upstream AudioMark missing includes</span>
<span class="hljs-built_in">set</span>(CMAKE_C_FLAGS <span class="hljs-string">"<span class="hljs-variable">${CMAKE_C_FLAGS}</span> -Wno-implicit-function-declaration"</span>)

<span class="hljs-built_in">set</span>(PORT_SOURCE
    <span class="hljs-variable">${PORT_DIR}</span>/th_api.c
)
</code></pre>
<h3 id="heading-building-audiomark"><strong>Building AudioMark</strong></h3>
<p>Inside the Docker container:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> audiomark
mkdir build &amp;&amp; <span class="hljs-built_in">cd</span> build

cmake .. \
    -DPORT_DIR=/work/audiomark/ports/riscv \
    -DCMAKE_TOOLCHAIN_FILE=/work/riscv_toolchain.cmake

make -j4
</code></pre>
<p>The moment of truth:</p>
<pre><code class="lang-bash">$ file audiomark
audiomark: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI...
</code></pre>
<p>It compiled! But would it run?</p>
<hr />
<h2 id="heading-milestone-4-the-reality-check-emulation-and-hardware"><strong>Milestone 4: The Reality Check — Emulation and Hardware</strong></h2>
<h3 id="heading-qemu-emulation-results"><strong>QEMU Emulation Results</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769636952593/e3605e6d-cd67-4b0c-bd5d-3b04de678886.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769636967728/23b106ab-f06f-452a-a98a-ee4aa83d99af.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash">$ qemu-riscv64 -cpu rv64,v=<span class="hljs-literal">true</span>,vlen=128 -L /opt/riscv/sysroot ./audiomark
Initializing
Memory alloc summary:
 bmf = 14876
 aec = 68100
 anr = 45250
 kws = 8264
Computing run speed
Measuring
Total runtime    : 10.526 seconds
Total iterations : 25 iterations
Score            : 1583.437256 AudioMarks
</code></pre>
<p><strong>1,583 AudioMarks</strong> under emulation. A 2.5x improvement over my scalar baseline!</p>
<p>But QEMU emulation is slow. The real test would be on actual hardware.</p>
<h3 id="heading-the-hybrid-distribution-strategy"><strong>The Hybrid Distribution Strategy</strong></h3>
<p>To get hardware testing, I needed to make it <strong>easy</strong> for volunteers. I adopted a hybrid approach:</p>
<ol>
<li><p><strong>Static Binary:</strong> Self-contained, no dependencies</p>
</li>
<li><p><strong>Docker Image:</strong> Full toolchain for rebuilding</p>
</li>
<li><p><strong>GitHub Release:</strong> Easy download with one command</p>
</li>
</ol>
<p>Building the static binary:</p>
<pre><code class="lang-bash">cmake .. \
    -DPORT_DIR=/work/audiomark/ports/riscv \
    -DCMAKE_TOOLCHAIN_FILE=/work/riscv_toolchain.cmake \
    -DCMAKE_C_FLAGS=<span class="hljs-string">"-static -march=rv64gcv -mabi=lp64d"</span> \
    -DCMAKE_EXE_LINKER_FLAGS=<span class="hljs-string">"-static"</span>

make -j4

$ file audiomark
audiomark: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, 
           version 1 (GNU/Linux), statically linked
</code></pre>
<p><strong><em>I created a</em></strong> <a target="_blank" href="https://github.com/Ayushd785/riscv-Audiomark-lab/releases/tag/v0.2-rvv-alpha"><strong><em>GitHub Release with the bina</em></strong></a><strong><em>ry attached.</em></strong></p>
<h3 id="heading-real-hardware-results-the-moment-of-truth"><strong>Real Hardware Results — The Moment of Truth</strong></h3>
<p>I reached out to <strong>sanderjo</strong>, who generously offered to test on his SpacemiT hardware. I created a issue in his repo link to the issue - <a target="_blank" href="https://github.com/sanderjo/SpacemiT-K3-X100-A100/issues/6">https://github.com/sanderjo/SpacemiT-K3-X100-A100/issues/6</a></p>
<p><strong>SpacemiT K3 (Banana Pi F3):</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769637239153/12065e71-7e37-4f29-8d02-910168aeb6bb.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash">superkoning@spacemit:~/eembc-audiomark$ ./audiomark-rvv-static
Initializing
Memory alloc summary:
 bmf = 14876
 aec = 68100
 anr = 45250
 kws = 8264
Computing run speed
Measuring
Total runtime    : 11.011 seconds
Total iterations : 585 iterations
Score            : 35418.703125 AudioMarks
</code></pre>
<p><strong><mark>35,418 AudioMarks.</mark></strong></p>
<p><em>I had to read it twice. That's</em> <strong><em><mark>22x faster than QEMU emulation</mark>!</em></strong></p>
<p><strong><em>Sanderjo was so kind he ran it on his old harware too (<mark>SpacemiT K1 (X60)</mark>)</em></strong></p>
<h4 id="heading-spacemit-k1-x60"><strong>SpacemiT K1 (X60):</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769640083528/3870d407-6e9d-4f60-8b72-46d7072f2165.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-bash">sander@bananapif3:~/eembc-audiomark$ ./audiomark-rvv-static
Initializing
Memory alloc summary:
 bmf = 14876
 aec = 68100
 anr = 45250
 kws = 8264
Computing run speed
Measuring
Total runtime    : 10.983 seconds
Total iterations : 208 iterations
Score            : 12625.140625 AudioMarks
</code></pre>
<p><strong>12,625 AudioMarks</strong> on the older K1 — proving <strong>VLEN independence works!</strong></p>
<hr />
<h2 id="heading-the-final-scorecard"><strong>The Final Scorecard</strong></h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Platform</strong></td><td><strong>Score (AudioMarks)</strong></td><td><strong>Runtime</strong></td><td><strong>Speedup</strong></td></tr>
</thead>
<tbody>
<tr>
<td>RISC-V Scalar (Phase 1)</td><td>641</td><td>~10s</td><td>1x (baseline)</td></tr>
<tr>
<td>QEMU Emulation (VLEN=128)</td><td>1,583</td><td>10.5s</td><td>2.5x</td></tr>
<tr>
<td><strong>SpacemiT K1 (Real HW)</strong></td><td><strong>12,625</strong></td><td>11.0s</td><td><strong>20x</strong></td></tr>
<tr>
<td><strong>SpacemiT K3 (Real HW)</strong></td><td><strong>35,418</strong></td><td>11.0s</td><td><strong>55x</strong></td></tr>
</tbody>
</table>
</div><p>Yayyyyyyyyyyy <strong>641 to 35,418 AudioMarks</strong> — a <strong>55x improvement</strong> from scalar to optimized hardware!🎉🎉🎉</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTZjMDliOTUyMW40NmZuNnJ0cnVja2w3Z2kwNTNuNXJtZ3RsOTEwaGM5NW9sNWhuYyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/YRuFixSNWFVcXaxpmX/200.gif" alt="It Work GIFs - Find &amp; Share on GIPHY" class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-resources"><strong>Resources</strong></h2>
<ul>
<li><p><strong>GitHub Repository:</strong> <a target="_blank" href="https://github.com/Ayushd785/riscv-Audiomark-lab">Ayushd785/riscv-Audiomark-lab</a></p>
</li>
<li><p><strong>Docker Image:</strong> <code>docker pull ayushd785/riscv-audio-lab</code></p>
</li>
<li><p><strong>Static Binary Release:</strong> <a target="_blank" href="https://github.com/Ayushd785/riscv-Audiomark-lab/releases/tag/v0.2-rvv-alpha">v0.2-rvv-alpha</a></p>
</li>
<li><p><strong>EEMBC AudioMark:</strong> <a target="_blank" href="https://github.com/eembc/audiomark">github.com/eembc/audiomark</a></p>
</li>
<li><p><strong>VOLK RVV Port (Reference):</strong> <a target="_blank" href="https://github.com/gnuradio/volk/pull/774">PR #774</a></p>
</li>
</ul>
<hr />
<h2 id="heading-acknowledgments"><strong>Acknowledgments</strong></h2>
<ul>
<li><p><strong>Edward</strong> <strong>Vielmetti</strong> — Community guidance and support</p>
</li>
<li><p><strong>sanderjo</strong> — Hardware testing on SpacemiT K1 and K3</p>
</li>
<li><p><strong>Olaf Bernstein</strong> — The VOLK RVV implementation that taught me the patterns</p>
</li>
<li><p><strong>EEMBC</strong> — For creating the AudioMark benchmark</p>
</li>
</ul>
<hr />
<p><em>Thanks for reading! If you're working on RISC-V or audio DSP, I'd love to connect.</em></p>
<p><strong>Author:</strong> Ayush Dwivedi<br /><strong>Github:</strong> <a target="_blank" href="https://github.com/Ayushd785"><strong>Ayushd785</strong></a><br /><strong>Mail</strong>: ayushd785@gmail.com<br /><strong>Date:</strong> 29th January 2026</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExMXc4dWNqdnh6aDBsdnprMGUzczlyaGRwZjlpd3g4bGozNWk0eXBpdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gdYtZijeagnK7VQZR9/giphy.gif" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[From Scratch to Sysroot: Porting the EEMBC AudioMark Benchmark to RISC-V Linux]]></title><description><![CDATA[Introduction: The Mission

The target: EEMBC AudioMark — an industry-standard benchmark that simulates a real-world audio pipeline with beamforming, echo cancellation, noise reduction, and keyword spotting. It's the perfect testbed for DSP optimizati...]]></description><link>https://ayushd785.hashnode.dev/from-scratch-to-sysroot-porting-the-eembc-audiomark-benchmark-to-risc-v-linux</link><guid isPermaLink="true">https://ayushd785.hashnode.dev/from-scratch-to-sysroot-porting-the-eembc-audiomark-benchmark-to-risc-v-linux</guid><category><![CDATA[audiomark]]></category><category><![CDATA[risc-v]]></category><category><![CDATA[LFX Mentorship]]></category><category><![CDATA[c programming]]></category><category><![CDATA[low level programming]]></category><dc:creator><![CDATA[Ayush Dwivedi]]></dc:creator><pubDate>Wed, 21 Jan 2026 18:43:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769020864604/f7532d63-226c-4e73-8771-d75f3ca93295.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction-the-mission"><strong>Introduction: The Mission</strong></h2>
<hr />
<p><strong>The target</strong>: <strong>EEMBC AudioMark</strong> — an industry-standard benchmark that simulates a real-world audio pipeline with beamforming, echo cancellation, noise reduction, and keyword spotting. It's the perfect testbed for DSP optimization.</p>
<p><strong>The problem</strong>: AudioMark didn't have a ready-to-go Linux/RISC-V port. My laptop runs Fedora on x86_64. I don't own RISC-V hardware. But I need RISC-V code running <em>today</em>.</p>
<p><strong>This post documents the journey from "broken build" to "working benchmark."</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769001789459/a8b3dab2-90c4-43cf-acaf-57734e7025f3.png" alt class="image--center mx-auto" /></p>
<p>This flowchart illustrates the <strong>Cross-Compilation Pipeline</strong> required when your development machine (x86) is different from your target architecture (RISC-V).</p>
<ol>
<li><p>It starts with your C <strong>Source Code</strong>, which cannot run on RISC-V as-is.</p>
</li>
<li><p>The <strong>Cross-Compiler</strong> acts as a "Bridge," translating that code into a machine-readable <strong>RISC-V Binary</strong> (ELF file).</p>
</li>
<li><p>Since your laptop cannot natively run this binary, it is passed to the <strong>QEMU Emulator</strong>.</p>
</li>
<li><p>QEMU translates the RISC-V instructions in real-time, allowing the program to execute on your laptop and establishing "The Proof" that the build works.</p>
</li>
</ol>
<hr />
<h2 id="heading-the-environment-setting-the-stage"><strong>The Environment: Setting the Stage</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769002214490/ed460b93-8dfa-4849-a8fb-be6726217ca4.png" alt class="image--center mx-auto" /></p>
<p><code>My Setup:</code></p>
<ul>
<li><p><code>Host: Fedora Linux (x86_64)</code></p>
</li>
<li><p><code>Goal: Port the AudioMark benchmark to RISC-V</code></p>
</li>
<li><p><code>Reality: I have no physical RISC-V hardware, so I must rely on emulation.</code></p>
</li>
</ul>
<hr />
<h3 id="heading-the-native-sanity-check">The Native "Sanity Check"</h3>
<p>Before diving into the complexities of cross-compilation (where things break easily), I needed to establish a <strong>Golden Baseline</strong>. I needed to verify that the benchmark logic itself—the beamforming, the neural net, the error checking—worked correctly on my reliable x86 laptop.</p>
<p><strong>1. Install Prerequisites</strong></p>
<p>First, I grabbed the standard build tools from the Fedora repositories:</p>
<pre><code class="lang-bash">sudo dnf install git cmake make gcc gcc-c++
</code></pre>
<p><strong>2. Clone the Repository</strong></p>
<p>I pulled the official EEMBC AudioMark source code:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/eembc/audiomark.git
<span class="hljs-built_in">cd</span> audiomark
</code></pre>
<p><strong>3. Configure and Build (The "Reference" Build)</strong></p>
<p>Here is the trick: AudioMark doesn't come with a generic "Linux" port out of the box. However, the ports/arm directory contains a standard C reference implementation that is compatible with x86 GCC.</p>
<p>I compiled it using the host's native compiler:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> audiomark &amp;&amp; mkdir build &amp;&amp; <span class="hljs-built_in">cd</span> build
$ cmake .. -DPORT_DIR=<span class="hljs-variable">$HOME</span>/opensource/audiomark/ports/arm
$ make audiomark
</code></pre>
<p><strong>4. Run the Benchmark</strong></p>
<p>With the binary built, I executed it locally to see what "success" looks like:</p>
<pre><code class="lang-bash">$ ./audiomark
</code></pre>
<p><strong>Native Result (x86_64):</strong></p>
<pre><code class="lang-basic">Score: ~<span class="hljs-number">8275</span> AudioMarks
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769003151069/b5d2aa55-7196-499d-9690-a6d12b6a1e32.png" alt class="image--center mx-auto" /></p>
<p>This result was crucial. It proved that <strong>the code is bug-free</strong>. Any errors I encounter later are guaranteed to be caused by my RISC-V porting efforts, not the benchmark itself.</p>
<hr />
<h2 id="heading-the-first-hurdle-the-brain-without-a-body"><strong>The First Hurdle: The Brain Without a Body</strong></h2>
<p>Like any developer, I tried the easy way first:</p>
<pre><code class="lang-bash">$ sudo dnf install gcc-riscv64-linux-gnu
</code></pre>
<p><mark>Installation succeeded. I had a compiler! I updated my CMake toolchain file, hit build, and...</mark></p>
<pre><code class="lang-bash">/opt/riscv/bin/riscv64-unknown-linux-gnu-gcc -c main.c
In file included from main.c:1:
ee_audiomark.h:15:10: fatal error: stdint.h: No such file or directory
   15 | <span class="hljs-comment">#include &lt;stdint.h&gt;</span>
      |          ^~~~~~~~~~
compilation terminated.
</code></pre>
<p><strong>The dreaded "No such file or directory."</strong></p>
<p><em>I spent nearly 2 hours debugging this</em>. <code>stdint.h</code> is one of the most basic files in C programming. How could a compiler not know what an integer is?</p>
<h3 id="heading-what-went-wrong"><strong>What Went Wrong?</strong></h3>
<p>The Fedora package gave me a <strong>compiler</strong> (the brain), but not a <strong>sysroot</strong> (the vocabulary).</p>
<p>A sysroot is a directory containing all the standard library headers (<code>stdint.h</code>, <code>stdio.h</code>, <code>string.h</code>) and libraries (<a target="_blank" href="http://libc.so"><code>libc.so</code></a>) that your code needs. Without it, the compiler can translate your code into machine instructions, but it doesn't know what <code>printf</code> or <code>malloc</code> mean.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769004476891/3b6ba40f-37e3-4678-9019-abddc3ffaa76.png" alt class="image--center mx-auto" /></p>
<p><em><mark>The Fedora package is designed for kernel development (where you don't use libc), not userspace applications like AudioMark.</mark></em></p>
<hr />
<h2 id="heading-the-solution-building-the-toolchain-from-source"><strong>The Solution: Building the Toolchain from Source</strong></h2>
<p><em>If distributions won't give us a complete toolchain, we build our own. — Yes i felt that kick</em></p>
<p><mark>The </mark> <a target="_blank" href="https://github.com/riscv-collab/riscv-gnu-toolchain"><mark>riscv-gnu-toolchain is the official RI</mark></a><mark>SC-V compiler infrastructure. It builds GCC, Binutils, GDB, and (crucially) </mark> <strong><mark>glibc for RISC-V</mark></strong><mark>.</mark></p>
<h3 id="heading-step-1-install-build-dependencies"><strong>Step 1: Install Build Dependencies</strong></h3>
<pre><code class="lang-bash">$ sudo dnf install -y autoconf automake python3 libmpc-devel mpfr-devel \
    gmp-devel gawk bison flex texinfo patchutils gcc gcc-c++ zlib-devel \
    expat-devel libslirp-devel
</code></pre>
<h3 id="heading-step-2-clone-the-repository"><strong>Step 2: Clone the Repository</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create installation directory</span>
$ sudo mkdir -p /opt/riscv
$ sudo chown <span class="hljs-variable">$USER</span> /opt/riscv

<span class="hljs-comment"># Configure for Linux userspace (this builds glibc!)</span>
$ ./configure --prefix=/opt/riscv --disable-gdb

<span class="hljs-comment"># Build (grab coffee, this takes 30-60 minutes)</span>
$ make linux -j$(nproc)
</code></pre>
<p>The <code>make linux</code> target is critical — it builds a toolchain for Linux userspace applications with full glibc support. The alternative, <code>make</code>, builds a "bare-metal" toolchain without libc.</p>
<h3 id="heading-step-4-verify-the-installation"><strong>Step 4: Verify the Installation</strong></h3>
<pre><code class="lang-bash">$ ls /opt/riscv/bin/
riscv64-unknown-linux-gnu-gcc
riscv64-unknown-linux-gnu-g++
riscv64-unknown-linux-gnu-ld
...

$ ls /opt/riscv/sysroot/usr/include/
stdint.h  stdio.h  stdlib.h  string.h  ...
</code></pre>
<p><strong>We have our sysroot!</strong> The missing <code>stdint.h</code> is now at <code>/opt/riscv/sysroot/usr/include/stdint.h</code>.</p>
<hr />
<h2 id="heading-the-bridge-cmake-and-cross-compilation"><strong>The Bridge: CMake and Cross-Compilation</strong></h2>
<p>CMake defaults to the host compiler. We need to force it to use our RISC-V toolchain. This is done with a <strong>toolchain file</strong>.</p>
<p><code>riscv_toolchain.cmake</code></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Tell CMake we're building for Linux on RISC-V</span>
<span class="hljs-built_in">set</span>(CMAKE_SYSTEM_NAME Linux)
<span class="hljs-built_in">set</span>(CMAKE_SYSTEM_PROCESSOR riscv64)

<span class="hljs-comment"># THE KEY FIX: Point to our sysroot</span>
<span class="hljs-built_in">set</span>(CMAKE_SYSROOT /opt/riscv/sysroot)

<span class="hljs-comment"># Use our custom compilers</span>
<span class="hljs-built_in">set</span>(CMAKE_C_COMPILER /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc)
<span class="hljs-built_in">set</span>(CMAKE_CXX_COMPILER /opt/riscv/bin/riscv64-unknown-linux-gnu-g++)

<span class="hljs-comment"># Don't try to run test programs (they're RISC-V, we're x86!)</span>
<span class="hljs-built_in">set</span>(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

<span class="hljs-comment"># Ensure compiler finds sysroot libraries</span>
<span class="hljs-built_in">set</span>(CMAKE_C_FLAGS <span class="hljs-string">"<span class="hljs-variable">${CMAKE_C_FLAGS}</span> --sysroot=<span class="hljs-variable">${CMAKE_SYSROOT}</span>"</span>)
<span class="hljs-built_in">set</span>(CMAKE_CXX_FLAGS <span class="hljs-string">"<span class="hljs-variable">${CMAKE_CXX_FLAGS}</span> --sysroot=<span class="hljs-variable">${CMAKE_SYSROOT}</span>"</span>)
</code></pre>
<p><strong><mark>The critical line is </mark></strong> <code>CMAKE_SYSROOT</code><mark>. This tells CMake and GCC where to find headers and libraries. Without it, we're back to the "brain without a body" problem.</mark></p>
<h3 id="heading-building-with-the-toolchain-file"><strong>Building with the Toolchain File</strong></h3>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> build
$ rm -rf *  <span class="hljs-comment"># Clean previous x86 build</span>

$ cmake .. -DPORT_DIR=<span class="hljs-variable">$HOME</span>/opensource/audiomark/ports/arm \
           -DCMAKE_TOOLCHAIN_FILE=../riscv_toolchain.cmake

$ make audiomark -j$(nproc)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769007137678/9800cf40-c842-4fef-8151-f87f6a249995.png" alt class="image--center mx-auto" /></p>
<p><em>This was an Aha! moment for me to be honest!</em></p>
<p>Let's verify we actually built a RISC-V binary:</p>
<pre><code class="lang-bash">$ file audiomark
audiomark: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, 
           version 1 (SYSV), dynamically linked, 
           interpreter /lib/ld-linux-riscv64-lp64d.so.1, 
           <span class="hljs-keyword">for</span> GNU/Linux 4.15.0, not stripped
</code></pre>
<p><mark>We have a RISC-V executable. But how do we run it on an x86 laptop?</mark></p>
<hr />
<h2 id="heading-the-climax-emulation-and-the-dynamic-linker-crash"><strong>The Climax: Emulation and The Dynamic Linker Crash</strong></h2>
<h3 id="heading-enter-qemu"><strong>Enter QEMU</strong></h3>
<p><a target="_blank" href="https://www.qemu.org/"><mark>QEMU</mark></a> <a target="_blank" href="https://www.qemu.org/"><mark>is </mark></a> <mark>a processor emulator. It can run RISC-V binaries on x86 by translating instructions in real-time.</mark></p>
<pre><code class="lang-bash">$ sudo dnf install qemu-user-static
</code></pre>
<p>The <code>qemu-riscv64-static</code> binary will execute our RISC-V code. Is it that easy??</p>
<pre><code class="lang-bash">$ qemu-riscv64-static ./audiomark
</code></pre>
<p><strong>Crash: Obviously no</strong></p>
<pre><code class="lang-bash">qemu-riscv64-static: Could not open <span class="hljs-string">'/lib/ld-linux-riscv64-lp64d.so.1'</span>: 
No such file or directory
</code></pre>
<h3 id="heading-the-problem"><strong>The Problem</strong></h3>
<p>Our binary needs shared libraries (<a target="_blank" href="http://libc.so"><code>libc.so</code></a>, etc.). But it's looking for them in <code>/lib/</code> — which contains x86 libraries, not RISC-V ones!</p>
<p>QEMU is smart enough to detect the binary is RISC-V, but it still searches default paths for dependencies.</p>
<h3 id="heading-the-fix"><strong>The Fix</strong></h3>
<p>We use the <code>-L</code> flag to redirect QEMU to our sysroot:</p>
<pre><code class="lang-bash">$ qemu-riscv64-static -L /opt/riscv/sysroot ./audiomark -verbose
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-bash">Initializing
Memory alloc summary:
 bmf = 14952
 aec = 68100
 anr = 45250
 kws = 8308
Computing run speed
Measuring
Total runtime    : 10.385 seconds
Total iterations : 10 iterations
Score            : 641.929016 AudioMarks
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769017997212/3352de28-2eaa-4b9f-addb-c543559b99c0.png" alt class="image--center mx-auto" /></p>
<p>YAyyyyyyyyyyyy <strong>IT WORKED!!!</strong> 🎉</p>
<p><img src="https://media1.giphy.com/media/v1.Y2lkPTZjMDliOTUyMW40NmZuNnJ0cnVja2w3Z2kwNTNuNXJtZ3RsOTEwaGM5NW9sNWhuYyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/YRuFixSNWFVcXaxpmX/200.gif" alt="It Work GIFs - Find &amp; Share on GIPHY" class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-results-amp-analysis"><strong>Results &amp; Analysis:</strong></h2>
<h3 id="heading-performance-comparison"><strong>Performance Comparison</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Feature</strong></td><td><strong>Native (x86_64)</strong></td><td><strong>RISC-V (QEMU)</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Architecture</strong></td><td>Intel/AMD CPU</td><td>Emulated RV64GC</td></tr>
<tr>
<td><strong>Compiler</strong></td><td>System GCC</td><td>riscv64-gcc 15.2.0</td></tr>
<tr>
<td><strong>Execution Mode</strong></td><td>Direct</td><td>QEMU Translation</td></tr>
<tr>
<td><strong>Score</strong></td><td>~8,275 AudioMarks</td><td><strong>641.93 AudioMarks</strong></td></tr>
<tr>
<td><strong>Runtime</strong></td><td>~1.3 seconds</td><td>~10.4 seconds</td></tr>
</tbody>
</table>
</div><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769018041401/8ed57ddd-efc8-431f-9fe8-e21a03920f27.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-interpretation-why-the-12x-slowdown-is-actually-a-win">Interpretation: Why the 12x Slowdown is Actually a Win</h2>
<p><em>At first glance, dropping from</em> <strong><em>~8275</em></strong> <em>to</em> <strong><em>~641</em></strong> <em>AudioMarks looks like a performance regression. However, this is entirely expected and, in fact, represents a successful port. Here is the technical breakdown of that performance delta:</em></p>
<p><strong>1. The Cost of Dynamic Binary Translation (DBT)</strong></p>
<p><mark>We are running this binary on QEMU User Mode, which is an emulator, not a virtualizer.</mark></p>
<ul>
<li><p><strong>How it works:</strong> QEMU uses a Tiny Code Generator (TCG) to translate RISC-V instructions into x86_64 instructions in real-time.</p>
</li>
<li><p><strong>The Overhead:</strong> Every time the benchmark tries to execute a simple RISC-V <code>fadd.s</code> (floating point add), QEMU has to pause, look up the equivalent x86 machine code, generate it, and then execute it. This adds massive latency to every single cycle.</p>
</li>
</ul>
<p><strong>2. Scalar Bottlenecks (The "One at a Time" Problem)</strong></p>
<p>The current build uses the generic C implementation of the DSP kernels (originally from the ARM port).</p>
<ul>
<li><p><strong>The Math:</strong> This code is <strong>Scalar</strong>, meaning it processes data elements one by one (Single Instruction, Single Data).</p>
</li>
<li><p><strong>The Consequence:</strong> To process a 1024-sample audio buffer, the CPU has to fetch, decode, and execute instructions 1024 times.</p>
</li>
<li><p><strong>The Future Fix:</strong> In Phase 2, we will implement <strong>RISC-V Vectors (RVV)</strong>. This will allow us to process vast chunks of that buffer (e.g., 8 or 16 floats) in a <em>single</em> CPU cycle, drastically reducing the instruction fetch overhead.</p>
</li>
</ul>
<p><strong>3. Functional Correctness &gt; Raw Speed</strong></p>
<p>The most important metric right now isn't the speed (641), but the Score itself.</p>
<ul>
<li><p><mark>AudioMark only generates a score if the output audio passes strict </mark> <strong><mark>Signal-to-Noise Ratio (SNR)</mark></strong> <mark>and </mark> <strong><mark>Distortion</mark></strong> <mark>thresholds</mark>.</p>
</li>
<li><p>The fact that we got a valid score proves that our cross-compiled math is bit-exact. The floating-point unit (FPU), memory allocation, and pointer arithmetic are all functioning correctly on the RISC-V architecture.</p>
</li>
</ul>
<hr />
<h2 id="heading-memory-allocation-breakdown"><strong>Memory Allocation Breakdown</strong></h2>
<p><em>The benchmark allocates memory for each component:</em></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Component</strong></td><td><strong>Memory</strong></td><td><strong>Purpose</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>BMF</strong></td><td>14,952 bytes</td><td>Beamformer - Direction of arrival</td></tr>
<tr>
<td><strong>AEC</strong></td><td>68,100 bytes</td><td>Acoustic Echo Cancellation</td></tr>
<tr>
<td><strong>ANR</strong></td><td>45,250 bytes</td><td>Automatic Noise Reduction</td></tr>
<tr>
<td><strong>KWS</strong></td><td>8,308 bytes</td><td>Keyword Spotting (Neural Net)</td></tr>
</tbody>
</table>
</div><p><mark>These values are identical between x86 and RISC-V, confirming the port is functionally correct.</mark></p>
<hr />
<h2 id="heading-lessons-learned"><strong>Lessons Learned</strong></h2>
<h3 id="heading-1-distributions-dont-always-have-what-you-need"><strong>1. Distributions Don't Always Have What You Need</strong></h3>
<p>The "easy" <code>dnf install</code> gave me a compiler without a sysroot. Sometimes you have to build from source.</p>
<h3 id="heading-2-cmakesysroot-is-your-best-friend"><strong>2. CMAKE_SYSROOT is Your Best Friend</strong></h3>
<p>Cross-compilation breaks in mysterious ways without it. Always set it explicitly in your toolchain file.</p>
<h3 id="heading-3-qemu-needs-library-paths-too"><strong>3. QEMU Needs Library Paths Too</strong></h3>
<p>The <code>-L</code> flag is essential. QEMU won't magically find your sysroot libraries.</p>
<h3 id="heading-4-emulation-performance-testing"><strong>4. Emulation ≠ Performance Testing</strong></h3>
<p>QEMU proves your code <em>works</em>. It doesn't tell you how fast it'll run on real hardware.</p>
<hr />
<h2 id="heading-conclusion-from-broken-to-baseline"><strong>Conclusion: From Broken to Baseline</strong></h2>
<p>We went from:</p>
<ul>
<li><p>"fatal error: stdint.h not found"</p>
</li>
<li><p>"Could not open ld-linux-riscv64"</p>
</li>
</ul>
<p>To:</p>
<ul>
<li><p>A complete RISC-V toolchain at <code>/opt/riscv</code></p>
</li>
<li><p>A working CMake cross-compilation setup</p>
</li>
<li><p>AudioMark running on RISC-V via QEMU</p>
</li>
<li><p>A <strong>baseline score of 641.93 AudioMarks</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-whats-next-the-sequel"><strong>What's Next: The Sequel</strong></h2>
<p>This was <strong>Phase 1: Make it work.</strong></p>
<p><strong>Phase 2: Make it fast.</strong></p>
<p>In the next post, I'll implement custom DSP kernels using <strong>RISC-V Vector (RVV) intrinsics</strong>. Functions like:</p>
<ul>
<li><p><code>th_add_f32</code> → <code>vfadd.vv</code> (vector addition)</p>
</li>
<li><p><code>th_dot_prod_f32</code> → <code>vfmacc</code> + reduction</p>
</li>
<li><p><code>th_cmplx_mult_cmplx_f32</code> → Segmented loads + fused multiply-add</p>
</li>
</ul>
<p>The goal: Beat that 641 score by leveraging SIMD parallelism. Stay tuned.</p>
<hr />
<p><em>Thanks for reading! If you're working on RISC-V or audio DSP, I'd love to connect.</em></p>
<p><strong>Author:</strong> Ayush Dwivedi<br /><strong>Github:</strong> <a target="_blank" href="https://github.com/Ayushd785">Ayushd785</a><br /><strong>Mail</strong>: ayushd785@gmail.com<br /><strong>Date:</strong> January 2026</p>
<p><img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTXI70iDMnUFneUCAj6Pd2GVFV5b3X3LSXtbw&amp;s" alt="YARN | I did not sign off. | The Simpsons (1989) - S28E22 | Video gifs by  quotes | 0eb888e4 | 紗" class="image--center mx-auto" /></p>
]]></content:encoded></item></channel></rss>