第二章 开发环境搭建:微码编译器安装、仿真器配置、调试工具链介绍、第一个Hello World微码程序
说实话,很多初学者一上来就急着写微码,结果连编译器都没装对,折腾半天报错一堆。我当年也干过这种事——拿到一块新的NPU开发板,兴冲冲地敲了一下午代码,最后发现是工具链版本不匹配。嗯,咱们先把地基打牢。
2.1 微码编译器安装
微码编译器,说白了就是把咱们写的微码程序翻译成NPU能理解的机器指令。不同厂商的NPU,编译器差别还挺大。我这里以业界主流的XLP系列为例,讲讲通用流程。
安装前准备:
- 操作系统:Ubuntu 20.04 LTS 或 CentOS 7.9(我个人习惯用Ubuntu,包管理方便)
- 依赖库:gcc、g++、make、flex、bison(缺一不可)
- 磁盘空间:至少留出5GB(编译器本身不大,但中间文件挺占地方)
安装步骤:
- 解压编译器包:
tar -xzf xlp_microcode_compiler_v2.3.tar.gz - 进入目录:
cd xlp_microcode_compiler - 运行安装脚本:
sudo ./install.sh - 设置环境变量:
export PATH=$PATH:/opt/xlp_mc/bin - 验证安装:
mc_compile --version
~/.bashrc 里,省得每次开终端都要重新 export。另外,编译器版本和NPU芯片型号一定要对应,我曾经因为用v2.1的编译器给v2.3的芯片编译,结果跑出来的包转发全是乱序。
2.2 仿真器配置
仿真器这东西,你想想看,没有它你怎么调试?总不能每次改一行代码就烧到板子上吧。仿真器能让你在PC上模拟NPU的行为,单步执行、看寄存器、查内存,方便得很。
仿真器类型:
| 类型 | 速度 | 精度 | 适用场景 |
|---|---|---|---|
| 指令级仿真器(ISS) | 快 | 中 | 功能验证、算法调试 |
| 周期级仿真器(CCS) | 中 | 高 | 性能分析、时序优化 |
| RTL级仿真器 | 慢 | 极高 | 硬件验证、流片前检查 |
咱们做微码开发,用ISS就够了。配置起来也不复杂:
# 仿真器配置文件 sim_config.yaml
simulator:
type: iss
model: xlp_npu_v2
clock_freq: 1GHz
memory_size: 64MB
trace_level: 2 # 0=关闭, 1=错误, 2=警告, 3=信息, 4=调试
启动仿真器:mc_sim -c sim_config.yaml
2.3 调试工具链介绍
调试工具链,说白了就是帮你找bug的那套家伙事儿。微码调试和普通软件调试不太一样,你得同时关注流水线状态、线程切换、内存访问这些硬件层面的东西。
核心工具:
- mc_debug:命令行调试器,支持断点、单步、反汇编
- mc_trace:跟踪分析工具,记录每条指令的执行情况
- mc_prof:性能剖析工具,告诉你哪段代码最耗时
- mc_memview:内存查看器,可以看NPU内部的各种缓存和队列
我个人最常用的是 mc_debug,它的命令和GDB很像,上手很快:
# 启动调试
mc_debug hello_world.elf
# 设置断点
(mc) break main
# 运行
(mc) run
# 单步执行
(mc) step
# 查看寄存器
(mc) info registers
# 查看内存
(mc) x/32x 0x80000000
避坑指南: 我曾经调试一个包处理微码,发现数据总是不对。单步执行了上百次,寄存器值看起来都正常。后来用 mc_trace 一看,才发现是流水线冲突导致指令执行顺序和我想的不一样。所以啊,遇到诡异问题,别光盯着寄存器,trace一下指令流往往能发现问题。
2.4 第一个Hello World微码程序
好了,环境搭好了,工具也认识了,咱们来写第一个微码程序。微码的Hello World和C语言不一样——你不能直接打印字符串,因为NPU没有显示器。咱们换个方式:往内存里写一串数据,然后通过仿真器读出来。
代码示例:
// hello_world.mc
// 第一个微码程序:向内存写入"Hello World"字符串
.section .text
.global main
main:
// 加载字符串基地址到寄存器r0
ldi r0, #msg_addr
// 加载字符'H'到r1
ldi r1, #0x48
// 写入内存
stw r1, [r0 + #0]
// 加载字符'e'
ldi r1, #0x65
stw r1, [r0 + #1]
// 加载字符'l'
ldi r1, #0x6C
stw r1, [r0 + #2]
stw r1, [r0 + #3] // 第二个'l'
// 加载字符'o'
ldi r1, #0x6F
stw r1, [r0 + #4]
// 加载空格
ldi r1, #0x20
stw r1, [r0 + #5]
// ... 后面的字符类似,这里省略
// 程序结束
halt
.section .data
msg_addr: .word 0x90000000 // 字符串存放地址
编译和运行:
# 编译
mc_compile hello_world.mc -o hello_world.elf
# 仿真运行
mc_sim -e hello_world.elf
# 查看结果
mc_memview -a 0x90000000 -l 20
你会看到内存地址0x90000000处依次存放着48 65 6C 6C 6F 20... 这就是咱们的"Hello World"。
知识体系总览
下面这张图,把咱们这章讲的内容串起来了。你一看就明白:
这张图把咱们这章的内容分成了四大块:编译器、仿真器、调试工具、Hello World程序。每一块之间都有联系,你顺着箭头看,就能明白整个开发流程是怎么串起来的。