Skip to content

Hands-on: tracing the stack from MicroPython

Module 7 — Under the hood and extending the firmware · Slides: slides.md · Module overview · Course page

Fill in five read-side calls of edge_ai in s18_under_the_hood.py — links, model, active, result, and latency — then press Trace to log the three stack layers (transport, control, and result), match each line to a function or field in the SDK’s headers, and compare the latency of an int8 model on the NPU with a float32 model on the CPU.

By the end of this lesson, you will:

  1. Fill the five points in practice/s18_under_the_hood.py until Trace logs all four parts (transport, registry, control, result), with active() matching the selected model.
  2. Match at least three log lines to a function or field in the SDK’s ai_engine.h or ipc_model_link_defs.h.
  3. On the board, compare latency() of an int8 model with a float32 model, and explain the difference via the NPU and CPU paths.

You’ve been through lesson 7.1, and know the three cores, the model registry, the IPC’s two planes, and ai_result_t. Keep the SDK’s ai_engine.h and ipc_model_link_defs.h open alongside your screen, as in the prior lesson.

This file doesn’t run any new inference. It’s a stack-tracing tool that calls the read-side API and logs it, one layer at a time. The five points to fill in are: (1) links = edge_ai.links(), returning ('ipc',), showing it talks to the CM55 over the IPC model link (2) desc = edge_ai.model(mi), fetching a single descriptor (name, sensor, labels) via Q_MODEL (3) cur = edge_ai.active(), after the already-provided select(sel) — normally cur should equal sel, since this is confirmation by observation, seen with your own eyes (4) r = edge_ai.result() via Q_RESULT, and (5) ms = edge_ai.latency(), the latest inference time from inference_us. All five are pull-style reads.

When Trace runs, the log proceeds transport → registry → control → result. Match each line to the SDK’s headers — for example, active() to ai_engine_active() and MODEL_LINK_Q_ACTIVE; select(n) to MODEL_LINK_CMD_SELECT_BASE + n; and the keys of result() to the fields of ai_result_t. The C source on the firmware side that the log mentions (ai_engine.c, deepcraft_task.c) isn’t yet public — the headers are the public, checkable evidence. On the board, try tracing an int8 model (for example, Motion), then switch to a float32 model (the author notes Push and Siren), and compare latency() — you’ll see the NPU and CPU paths genuinely differ. Don’t switch models rapidly; leave a second between switches so the control plane doesn’t jam.

s18_under_the_hood_full.py adds an on-screen stack map, a table matching the keys of result() to the fields in ai_result_t, an on_result callback (called on a class change, and repeating roughly once a second), and a latency comparison between int8 and float32.

File What this file teaches
examples/s18_under_the_hood_full.py A full Edge AI stack-tracing tool (for a Researcher)

The # TODO comments are at lines 38 (links), 75 (model), 109 (active), 134 (result), and 142 (latency). If the transport layer of the log is empty, point 38 is still empty. If active() always shows −1 even after selecting a model, check point 109.

Practice file Topic
practice/s18_under_the_hood.py Taking apart the Edge AI stack from the MicroPython end down to the NPU (the fill-in-the-code version)

Open the solution after trying on your own at least once, and read how to use the solutions first.

Solution Pairs with
solution/s18_under_the_hood.py practice/s18_under_the_hood.py

The same questions are in quiz.yaml for automated checking.

  1. On the emulator and on the board, what should edge_ai.links() return? (single choice · objective 1)

    • a) (‘ipc’,)
    • b) (‘wifi’,)
    • c) None
    • d) The number of models
    Solution

    a — MicroPython talks to ai_engine over the IPC model link, one path only. The emulator returns the same value so the code can move to the board unchanged, even though there’s no real IPC in the browser.

  2. After select(sel) succeeds, what should cur = edge_ai.active() be? (single choice · objective 1)

    • a) −1
    • b) sel, because select waits to see Q_ACTIVE equal the requested value
    • c) sel + 1
    • d) The number of models
    Solution

    b — select returns only once it’s confirmed by observation. If it never confirms, it raises OSError instead.

  3. What does the log “select(n)” match to in ipc_model_link_defs.h? (single choice · objective 2)

    • a) MODEL_LINK_Q_RESULT
    • b) MODEL_LINK_CMD_SELECT_BASE (0x90) plus n, on the control plane
    • c) MODEL_LINK_STREAM_MIC_PCM
    • d) BENTO_MODEL_LINK_VERSION
    Solution

    b — SELECT packs the model number into the command byte on OP_CTRL, then confirms via Q_ACTIVE on the query plane.

  4. On the board, Motion’s (int8) latency is far lower than Push’s (float32). Why? (single choice · objective 3)

    • a) Motion has fewer classes
    • b) A Vela-compiled int8 model runs on the Ethos-U55, while float32 runs on the CPU with a float kernel
    • c) Radar is always slower than the IMU
    • d) The radar’s IPC is slower
    Solution

    b — latency only measures the inference span. The main difference comes from whether the graph runs on the NPU or the CPU.

The MVP for lessons 7.1–7.2: being able to explain the stack, and point to the source function or field for at least one point in each of the three layers (transport, control, result).

  • All five points in the practice file are filled in. Press Trace on the emulator or the board until the log shows completely.
  • Match at least three log lines to a function or field in ai_engine.h or ipc_model_link_defs.h, and note them in your learning log.
  • On the board, trace one int8 model and one float32 model, note both latency() values, and explain the difference.
  • Explain in your own words why active() might not yet be the model most recently requested, if read from elsewhere during a switch.

In the next pair of lessons (7.3–7.4), we’ll use this map to add our own model so it shows up in edge_ai.models().

Next lesson: lesson 7.3 — Adding your own model: three edits, the four-function contract, and Vela

  • Which log line could you still not point to a source for, and what information is missing from what’s public?
  • If the board hung after switching models rapidly, how would you describe this symptom to the firmware team so they could find the root cause?

Review questions

Answer on your own first, then open the answer.

  1. What should edge_ai.links() return on the emulator and on the board? (Objective 1)

    1. ('ipc',)
    2. ('wifi',)
    3. None
    4. จำนวนโมเดล
    Show answer

    Answer: A. ('ipc',)

    MicroPython คุยกับ ai_engine ผ่าน IPC model link ทางเดียว Emulator คืนค่าเดียวกันเพื่อให้โค้ดย้ายไปบอร์ดได้ แม้ในเบราว์เซอร์จะไม่มี IPC จริง

  2. After select(sel) succeeds, what should cur = edge_ai.active() be? (Objective 1)

    1. −1
    2. sel เพราะ select รอจนเห็น Q_ACTIVE เท่ากับค่าที่ขอแล้ว
    3. sel + 1
    4. จำนวนโมเดล
    Show answer

    Answer: B. sel เพราะ select รอจนเห็น Q_ACTIVE เท่ากับค่าที่ขอแล้ว

    select คืนก็ต่อเมื่อยืนยันด้วยการสังเกตแล้ว ถ้าไม่ยืนยันมันโยน OSError แทน

  3. Which item in ipc_model_link_defs.h matches the "select(n)" log line? (Objective 2)

    1. MODEL_LINK_Q_RESULT
    2. MODEL_LINK_CMD_SELECT_BASE (0x90) บวก n บน control plane
    3. MODEL_LINK_STREAM_MIC_PCM
    4. BENTO_MODEL_LINK_VERSION
    Show answer

    Answer: B. MODEL_LINK_CMD_SELECT_BASE (0x90) บวก n บน control plane

    SELECT ใส่เลขโมเดลไว้ในไบต์คำสั่งบน OP_CTRL แล้วยืนยันด้วย Q_ACTIVE บน query plane

  4. On the board, Motion (int8) has much lower latency than Push (float32). Why? (Objective 3)

    1. Motion มีคลาสน้อยกว่า
    2. โมเดล int8 ที่ผ่าน Vela รันบน Ethos-U55 ส่วน float32 รันบน CPU ด้วย float kernel
    3. เรดาร์ช้ากว่า IMU เสมอ
    4. เพราะ IPC ของเรดาร์ช้ากว่า
    Show answer

    Answer: B. โมเดล int8 ที่ผ่าน Vela รันบน Ethos-U55 ส่วน float32 รันบน CPU ด้วย float kernel

    latency วัดเฉพาะช่วงอนุมาน ความต่างหลักจึงมาจากว่ากราฟรันบน NPU หรือบน CPU

Cite this lesson

If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.

"Hands-on: tracing the stack from MicroPython" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

Thai attribution: "ลงมือทำ: ส่องสแตกจาก MicroPython" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/edge-ai-developer/m07-under-the-hood/l02-trace-the-stack-lab/

Full guide: how to cite TESA

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA