面向 FPGA 软核的 RISC-V 工具链配置

概述

本指南涵盖为 FPGA 软核开发搭建 RISC-V GCC 工具链,特别针对 /home/user/work/ip_riscv 中的 ip_riscvip_lcpu 软核项目。工作流包括:将 C 和汇编代码编译为 RISC-V ELF 二进制文件、提取存储器初始化文件(.hex / .mif),以及将编译后的固件与 Verilator 和 ModelSim 仿真集成。

源码构建 vs 预编译 RISC-V GCC

方案一:预编译(推荐用于快速上手)

RISC-V 协作组织提供适用于 Ubuntu 的预编译工具链:


# 下载最新的预编译工具链 (rv32imc / rv32imac)
TOOLCHAIN_VER="2024.04.12"
wget https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/${TOOLCHAIN_VER}/riscv32-elf-ubuntu-22.04-${TOOLCHAIN_VER}.tar.gz

# 安装到 tools 目录
mkdir -p /home/user/tools/riscv
tar -xzf riscv32-elf-ubuntu-22.04-${TOOLCHAIN_VER}.tar.gz -C /home/user/tools/riscv/

# 添加到 PATH(或创建激活脚本)
export PATH="/home/user/tools/riscv/bin:$PATH"

# 验证
riscv32-unknown-elf-gcc --version

优点: 缺点:

方案二:源码构建(完全控制)

当需要自定义 multilib 配置、最新编译器优化或非标准扩展时,从源码构建。


# 安装构建依赖
sudo apt install autoconf automake autotools-dev curl python3 \
    libmpc-dev libmpfr-dev libgmp-dev gawk build-essential \
    bison flex texinfo gperf libtool patchutils bc \
    zlib1g-dev libexpat-dev

# 克隆工具链仓库
git clone --depth 1 https://github.com/riscv-collab/riscv-gnu-toolchain.git \
/home/user/tools/riscv_src
cd /home/user/tools/riscv_src

# 配置为 rv32imc(无浮点,压缩指令)
# 使用 newlib(裸机嵌入式)
./configure \
--prefix=/home/user/tools/riscv \
--with-arch=rv32imc \
--with-abi=ilp32 \
--with-multilib-generator="rv32i-ilp32--;rv32im-ilp32--;rv32imc-ilp32--"

# 构建(根据 CPU 性能约需 30-60 分钟)
make -j$(nproc)

# 验证
/home/user/tools/riscv/bin/riscv32-unknown-elf-gcc --version

优点: 缺点:

自定义 Multilib 示例

如果你的软核项目需要同时支持最小 rv32i 配置(Boot ROM)和完整的 rv32imc 配置(应用程序代码):


./configure \
    --prefix=/home/user/tools/riscv \
    --with-arch=rv32imc \
    --with-abi=ilp32 \
    --with-multilib-generator="rv32i-ilp32--;rv32im-ilp32--;rv32imc-ilp32--;rv32imac-ilp32--"

然后使用相应的 -march 标志编译:


# 引导加载程序:最小 ISA
riscv32-unknown-elf-gcc -march=rv32i -mabi=ilp32 -c boot.S -o boot.o

# 应用程序:带乘法和压缩指令
riscv32-unknown-elf-gcc -march=rv32imc -mabi=ilp32 -c main.c -o main.o

ip_riscv / ip_lcpu 软核的交叉编译

激活工具链

创建专用的环境脚本:


# /home/user/tools/scripts/riscv_env.sh
# 用法: source riscv_env.sh

export RISCV_TOOLCHAIN="/home/user/tools/riscv"
export PATH="${RISCV_TOOLCHAIN}/bin:$PATH"

# Makefile 中的通用变量
export CROSS_COMPILE=riscv32-unknown-elf-
export RISCV_ARCH=rv32imc
export RISCV_ABI=ilp32

echo "RISC-V 工具链已激活"
echo " GCC: $(which riscv32-unknown-elf-gcc)"
echo " ARCH: ${RISCV_ARCH}"
echo " ABI: ${RISCV_ABI}"

裸机编译(无操作系统)


# 汇编启动代码
riscv32-unknown-elf-gcc -march=rv32imc -mabi=ilp32 \
    -nostartfiles -nostdlib \
    -c startup.S -o build/startup.o

# 编译 C 应用程序
riscv32-unknown-elf-gcc -march=rv32imc -mabi=ilp32 \
-nostartfiles -nostdlib \
-Os -ffunction-sections -fdata-sections \
-Wall -Wextra \
-I src/ -I include/ \
-c src/main.c -o build/main.o

# 使用自定义链接脚本链接
riscv32-unknown-elf-gcc -march=rv32imc -mabi=ilp32 \
-nostartfiles -nostdlib \
-T linker.ld \
-Wl,--gc-sections \
-o build/firmware.elf \
build/startup.o build/main.o

常用编译器标志

标志 用途
-march=rv32imc ISA:I(基础)+ M(乘法)+ C(压缩)
-mabi=ilp32 ABI:32 位 int/long/pointer(软浮点)
-nostartfiles 不使用标准 C 启动代码 (crt0)
-nostdlib 不链接标准 C 库
-Os 优化体积(对 FPGA BRAM 至关重要)
-ffunction-sections 将每个函数放入独立段(使能 --gc-sections
-fdata-sections 将每个数据变量放入独立段
-Wl,--gc-sections 链接器垃圾回收——丢弃未引用的代码
-Wall -Wextra 启用所有警告
-T linker.ld 自定义链接脚本

链接脚本自定义

链接脚本是编译代码与软核内存映射之间的桥梁。每个 ip_riscv / ip_lcpu 项目都需要自定义的 linker.ld

链接脚本结构解析


/* /home/user/work/ip_riscv/fw/linker.ld */
/* ip_riscv 软核链接脚本 */

OUTPUT_ARCH(riscv)
ENTRY(_start)

MEMORY
{
/* 指令 RAM:64 KB,起始地址 0x0000_0000 */
IRAM (rx) : ORIGIN = 0x00000000, LENGTH = 64K

/* 数据 RAM:32 KB,起始地址 0x0001_0000 */
DRAM (rw) : ORIGIN = 0x00010000, LENGTH = 32K

/* Boot ROM:8 KB,起始地址 0x0002_0000(启动后只读) */
BOOTROM (r): ORIGIN = 0x00020000, LENGTH = 8K

/* 内存映射外设:0x8000_0000 */
MMIO (rw) : ORIGIN = 0x80000000, LENGTH = 4K
}

SECTIONS
{
/* ── 代码段 位于 IRAM ── */

.text : {
*(.text.init) /* _start 和早期初始化 */
*(.text*) /* 所有函数 */
*(.rodata*) /* 只读数据(字符串、常量) */
. = ALIGN(4);
} > IRAM

/* ── 已初始化数据 ── */
/* LMA(加载地址)在 IRAM,VMA(虚拟地址)在 DRAM */
/* 启动代码将 .data 从 IRAM 复制到 DRAM */

.data : {
_data_start = .;
*(.data*)
*(.sdata*)
. = ALIGN(4);
_data_end = .;
} > DRAM AT > IRAM

_data_lma = LOADADDR(.data); /* .data 在 IRAM 中的存储地址 */

/* ── 零初始化数据 ── */
/* 启动代码将此区域清零 */

.bss : {
_bss_start = .;
*(.bss*)
*(.sbss*)
*(COMMON)
. = ALIGN(4);
_bss_end = .;
} > DRAM

/* ── 栈(从 DRAM 末尾向下增长) ── */
_stack_top = ORIGIN(DRAM) + LENGTH(DRAM);
_stack_size = 4K;

/* ── 堆(可选,位于 BSS 之后、栈之前) ── */
_heap_start = _bss_end;
_heap_size = _stack_top - _stack_size - _bss_end;

/* ── Boot ROM 段 ── */
.boot : {
*(.boot*)
} > BOOTROM

/* ── 丢弃未用段 ── */
/DISCARD/ : {
*(.comment)
*(.note*)
*(.eh_frame*)
}
}

启动代码 (startup.S)


# /home/user/work/ip_riscv/fw/startup.S
# 最小化 RISC-V 裸机软核启动代码

.section .text.init, "ax"
.globl _start
_start:
# 初始化栈指针
la sp, _stack_top

# 将 .data 段从 IRAM 复制到 DRAM
la t0, _data_lma # 源地址 (IRAM)
la t1, _data_start # 目标地址 (DRAM)
la t2, _data_end # .data 在 DRAM 中的结束地址
1:
bge t1, t2, 2f
lw t3, 0(t0)
sw t3, 0(t1)
addi t0, t0, 4
addi t1, t1, 4
j 1b
2:

# 清零 .bss 段
la t0, _bss_start
la t1, _bss_end
1:
bge t0, t1, 2f
sw zero, 0(t0)
addi t0, t0, 4
j 1b
2:

# 跳转到 main
call main

# 如果 main 返回,则无限循环
halt:
wfi
j halt

为不同软核修改链接脚本

ip_riscv(64 KB IRAM,32 KB DRAM):

IRAM (rx)  : ORIGIN = 0x00000000, LENGTH = 64K
DRAM (rw)  : ORIGIN = 0x00010000, LENGTH = 32K

ip_lcpu(16 KB IRAM,8 KB DRAM,轻量级):

IRAM (rx)  : ORIGIN = 0x00000000, LENGTH = 16K
DRAM (rw)  : ORIGIN = 0x00004000, LENGTH = 8K

带外部 SDRAM 的自定义配置

IRAM (rx)   : ORIGIN = 0x00000000, LENGTH = 64K
DRAM (rw)   : ORIGIN = 0x00010000, LENGTH = 32K
SDRAM (rw)  : ORIGIN = 0x10000000, LENGTH = 32M

在链接脚本中添加 .sdram 段,将大型数据缓冲区放置到外部 SDRAM 中。

使用 objcopy/objdump 生成存储器初始化文件

FPGA 工具需要存储器初始化文件来预加载软核的指令和数据 RAM。使用 objcopy 提取原始二进制文件,并将其转换为 Intel HEX 或 Altera MIF 格式。

提取原始二进制文件


# 提取 .text 段(指令 + 只读数据)→ 原始二进制
riscv32-unknown-elf-objcopy -O binary -j .text firmware.elf build/iram.bin

# 提取 .data 段 → 原始二进制
riscv32-unknown-elf-objcopy -O binary -j .data firmware.elf build/dram.bin

# 提取整个固件为平坦二进制(适用于简单核心)
riscv32-unknown-elf-objcopy -O binary firmware.elf build/firmware.bin

生成 Intel HEX (.hex) 供 Vivado / ModelSim 使用


# 使用 Python 脚本将二进制转换为 Intel HEX
# (objcopy 仅支持 binary、ihex、srec;ihex 分段效果不佳)

python3 /home/user/work/ip_riscv/scripts/bin2hex.py \
build/iram.bin \
build/iram.hex \
--address 0x00000000 \
--wordsize 4

辅助脚本 bin2hex.py


#!/usr/bin/env python3
# /home/user/work/ip_riscv/scripts/bin2hex.py
"""将原始二进制文件转换为 Intel HEX 格式,用于 FPGA 存储器初始化。"""

import sys
import struct

def bin2hex(bin_path, hex_path, base_addr=0x00000000, wordsize=4):
with open(bin_path, 'rb') as f:
data = f.read()

# 按字对齐填充
remainder = len(data) % wordsize
if remainder:
data += b'\x00' * (wordsize - remainder)

with open(hex_path, 'w') as f:
addr = base_addr
for i in range(0, len(data), wordsize):
word_bytes = data[i:i+wordsize]
# 小端序字
word = struct.unpack('<I', word_bytes)[0] if wordsize == 4 else \
struct.unpack('<H', word_bytes)[0]
# Intel HEX 记录: @<addr> <hex_word>
f.write(f"@{addr:08X} {word:0{wordsize*2}X}\n")
addr += wordsize

print(f"已转换 {len(data)} 字节 → {hex_path}")

if __name__ == '__main__':
if len(sys.argv) < 3:
print("用法: bin2hex.py <输入.bin> <输出.hex> [--address 0xNN] [--wordsize 4]")
sys.exit(1)
base = 0x00000000
wordsize = 4
args = sys.argv[1:]
for i, a in enumerate(args):
if a == '--address' and i+1 < len(args):
base = int(args[i+1], 16)
if a == '--wordsize' and i+1 < len(args):
wordsize = int(args[i+1])
bin2hex(args[0], args[1], base_addr=base, wordsize=wordsize)

生成 Altera MIF (.mif) 供 Quartus 使用


python3 /home/user/work/ip_riscv/scripts/bin2mif.py \
    build/iram.bin \
    build/iram.mif \
    --depth 16384 \
    --width 32


#!/usr/bin/env python3
# /home/user/work/ip_riscv/scripts/bin2mif.py
"""将原始二进制文件转换为 Altera MIF 格式。"""

import sys
import struct

def bin2mif(bin_path, mif_path, depth=16384, width=32):
with open(bin_path, 'rb') as f:
data = f.read()

bytes_per_word = width // 8
num_words = min(len(data) // bytes_per_word, depth)

with open(mif_path, 'w') as f:
f.write(f"-- MIF 文件,由 {bin_path} 生成\n")
f.write(f"DEPTH = {depth};\n")
f.write(f"WIDTH = {width};\n")
f.write(f"ADDRESS_RADIX = HEX;\n")
f.write(f"DATA_RADIX = HEX;\n")
f.write(f"CONTENT BEGIN\n")

for i in range(num_words):
start = i * bytes_per_word
word_bytes = data[start:start+bytes_per_word]
if len(word_bytes) < bytes_per_word:
word_bytes += b'\x00' * (bytes_per_word - len(word_bytes))
word = struct.unpack('<I', word_bytes)[0]
f.write(f" {i:04X} : {word:08X};\n")

# 其余地址填充为零
if num_words < depth:
f.write(f" [{num_words:04X}..{depth-1:04X}] : 00000000;\n")

f.write(f"END;\n")

print(f"已转换 {len(data)} 字节 → {mif_path} ({num_words}/{depth} 个字)")

if __name__ == '__main__':
if len(sys.argv) < 3:
print("用法: bin2mif.py <输入.bin> <输出.mif> [--depth 16384] [--width 32]")
sys.exit(1)
depth = 16384
width = 32
args = sys.argv[1:]
for i, a in enumerate(args):
if a == '--depth' and i+1 < len(args):
depth = int(args[i+1])
if a == '--width' and i+1 < len(args):
width = int(args[i+1])
bin2mif(args[0], args[1], depth=depth, width=width)

反汇编调试


# 反汇编 ELF,验证代码布局
riscv32-unknown-elf-objdump -d firmware.elf > build/firmware.dis

# 显示段头(检查地址和大小)
riscv32-unknown-elf-objdump -h firmware.elf

# 显示符号表
riscv32-unknown-elf-objdump -t firmware.elf | sort

# 以十六进制显示所有段内容
riscv32-unknown-elf-objdump -s firmware.elf

.hex 生成后快速完整性检查


# 检查 IRAM 的前几个字是否与 ELF 匹配
xxd build/iram.bin | head -5
riscv32-unknown-elf-objdump -d firmware.elf | head -20

# 检查段大小是否在内存限制内
riscv32-unknown-elf-size firmware.elf
# 预期输出:
# text data bss dec hex filename
# 12340 512 2048 14900 3a34 firmware.elf

Makefile 工作流:C/ASM 到 ELF 再到存储器初始化文件

一个完整的固件构建 Makefile。


# /home/user/work/ip_riscv/fw/Makefile
# 为 ip_riscv 软核构建固件: C/ASM → ELF → .hex / .mif

# ── 工具链 ──
CROSS := riscv32-unknown-elf-
CC := $(CROSS)gcc
OBJCOPY := $(CROSS)objcopy
OBJDUMP := $(CROSS)objdump
SIZE := $(CROSS)size

# ── 架构 ──
ARCH := rv32imc
ABI := ilp32
MARCH := -march=$(ARCH) -mabi=$(ABI)

# ── 目录 ──
SRC_DIR := src
INC_DIR := include
BUILD_DIR := build

# ── 内存映射(必须与 linker.ld 匹配) ──
IRAM_BASE := 0x00000000
DRAM_BASE := 0x00010000
IRAM_SIZE := 65536 # 64 KB
DRAM_SIZE := 32768 # 32 KB

# ── 源文件 ──
C_SRCS := $(wildcard $(SRC_DIR)/*.c)
ASM_SRCS := $(wildcard $(SRC_DIR)/*.S)
C_OBJS := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(C_SRCS))
ASM_OBJS := $(patsubst $(SRC_DIR)/%.S, $(BUILD_DIR)/%.o, $(ASM_SRCS))
OBJS := $(ASM_OBJS) $(C_OBJS)

# ── 输出文件 ──
ELF := $(BUILD_DIR)/firmware.elf
IRAM_HEX := $(BUILD_DIR)/iram.hex
IRAM_MIF := $(BUILD_DIR)/iram.mif
DRAM_HEX := $(BUILD_DIR)/dram.hex
DIS := $(BUILD_DIR)/firmware.dis

# ── 编译标志 ──
CFLAGS := $(MARCH) -nostartfiles -nostdlib \
-Os -ffunction-sections -fdata-sections \
-Wall -Wextra \
-I$(INC_DIR)

LDFLAGS := $(MARCH) -nostartfiles -nostdlib \
-T linker.ld -Wl,--gc-sections

# ── 默认目标 ──
.PHONY: all
all: $(ELF) $(IRAM_HEX) $(IRAM_MIF) $(DRAM_HEX) $(DIS)

# ── 创建构建目录 ──
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)

# ── 编译 C ──
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
@echo " CC $<"
$(CC) $(CFLAGS) -c $< -o $@

# ── 汇编 ──
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.S | $(BUILD_DIR)
@echo " AS $<"
$(CC) $(MARCH) -c $< -o $@

# ── 链接 ──
$(ELF): $(OBJS) linker.ld
@echo " LD $@"
$(CC) $(LDFLAGS) $(OBJS) -o $@
@echo ""
$(SIZE) $@
@echo ""

# ── 提取 IRAM 二进制 ──
$(BUILD_DIR)/iram.bin: $(ELF)
@echo " OBJCOPY iram.bin"
$(OBJCOPY) -O binary -j .text $< $@

# ── 提取 DRAM 二进制 ──
$(BUILD_DIR)/dram.bin: $(ELF)
@echo " OBJCOPY dram.bin"
$(OBJCOPY) -O binary -j .data $< $@

# ── Intel HEX ──
$(IRAM_HEX): $(BUILD_DIR)/iram.bin
@echo " HEX $@"
python3 ../scripts/bin2hex.py $< $@ --address $(IRAM_BASE) --wordsize 4

$(DRAM_HEX): $(BUILD_DIR)/dram.bin
@echo " HEX $@"
python3 ../scripts/bin2hex.py $< $@ --address $(DRAM_BASE) --wordsize 4

# ── Altera MIF ──
$(IRAM_MIF): $(BUILD_DIR)/iram.bin
@echo " MIF $@"
python3 ../scripts/bin2mif.py $< $@ \
--depth $$(($(IRAM_SIZE) / 4)) --width 32

# ── 反汇编 ──
$(DIS): $(ELF)
@echo " OBJDUMP $@"
$(OBJDUMP) -d $< > $@

# ── 清理 ──
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)

# ── 帮助 ──
.PHONY: help
help:
@echo "构建目标:"
@echo " all — 构建固件: ELF → HEX → MIF → DIS"
@echo " clean — 删除构建产物"
@echo ""
@echo "build/ 目录下的输出文件:"
@echo " firmware.elf — ELF 二进制文件"
@echo " iram.hex — 指令 RAM 的 Intel HEX 文件"
@echo " dram.hex — 数据 RAM 的 Intel HEX 文件"
@echo " iram.mif — 指令 RAM 的 Altera MIF 文件"
@echo " firmware.dis — 反汇编清单"

与 Verilator / ModelSim 仿真集成

Verilator:将 HEX 加载到仿真存储器中


// 在 Verilator 的仿真包装中
// 在仿真开始时使用 $readmemh 加载 IRAM

reg [31:0] iram [0:16383]; // 64 KB / 4 字节 = 16K 字

initial begin
$readmemh("build/iram.hex", iram);
$display("IRAM 已加载: %0d 字节", $size(iram) * 4);
end

对于 Verilator,建议使用 C++ Testbench 加载二进制文件:


// sim_main.cpp — Verilator C++ Testbench
#include "Vfpga_top.h"
#include "verilated.h"
#include <fstream>
#include <vector>

void load_iram(Vfpga_top* top, const char* hex_file) {
std::ifstream f(hex_file);
std::string line;
while (std::getline(f, line)) {
if (line[0] != '@') continue;
uint32_t addr = std::stoul(line.substr(1, 8), nullptr, 16);
uint32_t data = std::stoul(line.substr(10), nullptr, 16);
// 写入仿真存储器模型
top->iram_mem[addr >> 2] = data;
}
printf("IRAM 已从 %s 加载\n", hex_file);
}

int main(int argc, char** argv) {
Verilated::commandArgs(argc, argv);
Vfpga_top* top = new Vfpga_top;

load_iram(top, "build/iram.hex");

// 运行仿真
top->clk = 0;
top->rst_n = 0;
top->eval();
// ... 仿真循环 ...

delete top;
return 0;
}

ModelSim:使用 Tcl 加载 HEX


# modelsim_sim.tcl — ModelSim 仿真脚本
vlib work
vlog +acc -work work -f verible.filelist
vlog +acc -work work sim/tb_fpga_top.sv
vsim -voptargs=+acc work.tb_fpga_top

# 在仿真开始时加载 IRAM
mem load -infile build/iram.hex -format hex /tb_fpga_top/u_dut/u_riscv_core/iram

# 运行直到超时或暂停
run -all
quit

带固件的自检仿真


// sim/tb_fpga_top.v — 带固件加载的软核 Testbench
module tb_fpga_top;

reg clk = 0;
reg rst_n = 0;

// DUT
fpga_top u_dut (
.clk(clk),
.rst_n(rst_n),
.o_uart_tx(uart_tx),
.o_gpio_led(gpio_led)
);

// 将固件加载到 IRAM 中
initial begin
$readmemh("build/iram.hex", u_dut.u_riscv_core.iram);
$readmemh("build/dram.hex", u_dut.u_riscv_core.dram);
$display("固件已加载到 IRAM + DRAM");
end

// 产生时钟
always #5 clk = ~clk; // 100 MHz 仿真时钟

// 复位序列
initial begin
rst_n = 0;
repeat (10) @(posedge clk);
rst_n = 1;
end

// 监控 UART 输出,检查测试通过/失败消息
// 固件应通过 UART 打印 "TEST_PASS" 或 "TEST_FAIL"
integer test_result = 0;
always @(posedge clk) begin
if (uart_rx_ready) begin
$write("%c", uart_rx_data);
if (uart_rx_data == "P") begin
// 从 UART 流中解析 "PASS" 或 "FAIL"
// ...
end
end
end

// 超时看门狗
initial begin
#10000000; // 100 MHz 下 1 ms
$display("超时: 仿真在 1 ms 内未完成");
$finish;
end

endmodule

验证检查清单

构建固件并将其加载到仿真环境后:


   riscv32-unknown-elf-objdump -h build/firmware.elf
   

   riscv32-unknown-elf-objdump -d build/firmware.elf | head -30
   # 地址 0x00000000 的第一条指令应该来自你的 _start 代码
   

   riscv32-unknown-elf-objdump -h build/firmware.elf | grep "\.data"
   # VMA 应在 DRAM 中,LMA 应在 IRAM 中
   

   riscv32-unknown-elf-size build/firmware.elf
   # text 应 < IRAM_SIZE, data+bss 应 < DRAM_SIZE
   

   head -5 build/iram.hex
   riscv32-unknown-elf-objdump -d build/firmware.elf | head -20
   # 地址 0 处的 HEX 字应与反汇编指令匹配
   

关键文件汇总

文件 用途
linker.ld 内存映射定义(IRAM、DRAM、BOOTROM、MMIO)
startup.S 复位向量、栈初始化、.data 复制、.bss 清零
Makefile 构建自动化:C/ASM → ELF → HEX → MIF
bin2hex.py 将二进制转换为 Intel HEX 供 Vivado/ModelSim 使用
bin2mif.py 将二进制转换为 Altera MIF 供 Quartus 使用
build/firmware.elf 链接后的可执行文件
build/iram.hex 指令 RAM 初始化文件
build/dram.hex 数据 RAM 初始化文件
build/firmware.dis 反汇编清单,用于调试