[智能芯片] 范例程序设计与程序编译
《智能芯片》课程笔记:范例程序设计与程序编译
📋 目录
课程教学目标
🔗 核心目标:理解C程序在计算机中的全栈运行过程
C范例程序
↓ 编译
汇编代码
↓ 汇编
机器码
↓ 存储
存储器
↓ 执行机器码
CPU运行机器码
🎯 四个关键理解点
| 序号 | 问题 | 涉及层面 |
|---|---|---|
| 1 | C程序如何转换成汇编程序? | 软件→软件 |
| 2 | 汇编程序如何转换成机器码? | 软件→硬件接口 |
| 3 | 机器码如何在存储器中存储? | 硬件存储 |
| 4 | CPU如何解析并运行机器码? | 硬件执行 |
💡 课程本质:打通软件与硬件的边界,理解程序从高级语言到硬件执行的完整链路
范例程序设计
📁 程序结构:5个源文件
📦 范例程序
├── main.c # 主程序入口
├── fun.c # 功能函数实现
├── linker_script_onestep.ld # 一步编译链接文件
├── linker_script_twosteps.ld # 两步编译链接文件
└── startup.s # 汇编启动代码(程序入口)
📝 main.c 核心代码
/* 源代码2:main.c */
float f = -60.6875;
unsigned short int s = 256, t;
char p = 10, q;
int a = -25;
extern unsigned int b;
extern int fun1(int x, int y, unsigned short int m, unsigned short int n);
int main(void) {
int i;
q = 8; /* 字节型赋值 */
b = 0x12345678; /* 整型赋值 */
t = s | 0x03; /* 短整形位运算赋值 */
for(i = 1; i < p; i++) /* 循环语句 */
a ^= i; /* 异或运算 */
a = fun1(a, -28, 0xFE, s); /* 函数调用 */
a += -20; /* 返回值处理 */
return a;
}
📝 fun.c 核心代码
/* 源代码3:fun.c */
unsigned int b;
extern int a;
extern char q;
int fun1(int x, int y, unsigned short int m, unsigned short int n) {
if(x < y)
return x - y; /* if/else条件分支 */
else if(m < n)
return x - 10;
else
return a;
}
int fun2(int x, int y, unsigned short int m, unsigned short int n) {
if(m < n)
q = 1;
else
q = 0; /* 比较置位 */
switch(x) { /* case/switch语句 */
case '+': x -= 3; break;
case '-': a = a >> 3; break; /* 算术右移 */
default: b = b >> q; /* 逻辑右移 */
}
return x + y;
}
📝 startup.s 汇编启动代码
/* 源代码1:startup.s */
_start:
la sp, _sp # 初始化栈指针
la a0, _data_lma
la a1, _data_start
la a2, _data_end
bgeu a1, a2, 2f
1: lw t0, 0(a0) # 数据段初始化
sw t0, 0(a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, 1b
2: la a0, _bss_start # BSS段清零
la a1, _bss_end
bgeu a0, a1, 2f
1: sw zero, 0(a0)
addi a0, a0, 4
bltu a0, a1, 1b
2: call main # 调用main函数
1: j 1b # 无限循环(程序结束)
📝 linker_script_onestep.ld 链接文件
展开代码
OUTPUT_ARCH( "riscv" )
/* instruction memory */
__ROM_BASE = 0x00000000;
__ROM_SIZE = 0x00000400;
/* data memory */
__RAM_BASE = 0x00000400;
__RAM_SIZE = 0x00000400;
/* stack size */
__STACK_SIZE = 0x000000200;
MEMORY
{
flash (rxai!w) : ORIGIN = __ROM_BASE, LENGTH = __ROM_SIZE
ram (wxa!ri) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
}
ENTRY(_start)
SECTIONS
{
__STACK_SIZE = DEFINED(__STACK_SIZE) ? __STACK_SIZE : 2K;
.init :
{
* (.init)
. = ALIGN(4);
} >flash AT>flash
.text :
{
* (.text)
. = ALIGN(4);
} >flash AT>flash
.rodata :
{
* (.rodata)
. = ALIGN(4);
} >flash AT>flash
PROVIDE( _data_lma = . );
.data :
{
PROVIDE( _data_start = . );
*(.data) /* DATA: Initialized Data Section */
*(.sdata) /* Small DATA Section */
. = ALIGN(4);
} >ram AT>flash
PROVIDE( _data_end = . );
PROVIDE( _bss_start = . );
.bss :
{
*(.bss) /* BSS: Block Started by Symbol */
*(.sbss*) /* Small BSS section*/
*(COMMON) /* Common Variable */
. = ALIGN(4);
} >ram AT>ram
PROVIDE( _bss_end = . );
.stack ORIGIN(ram) + LENGTH(ram) - __STACK_SIZE :
{
PROVIDE( _heap_end = . );
. = __STACK_SIZE;
PROVIDE( _sp = . );
} >ram AT>ram
}
📝 linker_script_twosteps.ld 链接文件
展开代码
OUTPUT_ARCH( "riscv" )
/* instruction memory */
__ROM_BASE = 0x00000000;
__ROM_SIZE = 0x00000400;
/* data memory */
__RAM_BASE = 0x00000400;
__RAM_SIZE = 0x00000400;
/* stack size */
__STACK_SIZE = 0x000000200;
MEMORY
{
flash (rxai!w) : ORIGIN = __ROM_BASE, LENGTH = __ROM_SIZE
ram (wxa!ri) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
}
ENTRY(_start)
SECTIONS
{
__STACK_SIZE = DEFINED(__STACK_SIZE) ? __STACK_SIZE : 2K;
.init :
{
startup.o (.init)
main.o(.init)
fun.o (.init)
. = ALIGN(4);
} >flash AT>flash
.text :
{
startup.o (.text)
fun.o (.text)
main.o(.text)
. = ALIGN(4);
} >flash AT>flash
.rodata :
{
startup.o (.rodata)
main.o (.rodata)
fun.o (.rodata)
. = ALIGN(4);
} >flash AT>flash
PROVIDE( _data_lma = . );
.data :
{
PROVIDE( _data_start = . );
*(.data) /* DATA: Initialized Data Section */
*(.sdata) /* Small DATA Section */
. = ALIGN(4);
} >ram AT>flash
PROVIDE( _data_end = . );
PROVIDE( _bss_start = . );
.bss :
{
*(.bss) /* BSS: Block Started by Symbol */
*(.sbss*) /* Small BSS section*/
*(COMMON) /* Common Variable */
. = ALIGN(4);
} >ram AT>ram
PROVIDE( _bss_end = . );
.stack ORIGIN(ram) + LENGTH(ram) - __STACK_SIZE :
{
PROVIDE( _heap_end = . );
. = __STACK_SIZE;
PROVIDE( _sp = . );
} >ram AT>ram
}
编译环境准备
🛠️ RISC-V编译器安装
1. 下载编译器
- 下载网址:https://gnutoolchains.com/risc-v/
- 工具链名称:RISC-V GCC编译工具链
- 默认安装路径:
C:\SysGCC\risc-v\
2. 安装后目录结构
📁 risc-v/
├── 📁 bin/ # 可执行文件
│ ├── riscv64-unknown-elf-gcc.exe # 编译器
│ ├── riscv64-unknown-elf-objdump.exe # 反汇编工具
│ ├── riscv64-unknown-elf-gdb.exe # 调试器
│ └── ...
├── 📁 include/ # 头文件
├── 📁 lib/ # 库文件
└── ...
✅ 编译器运行测试
# 在cmd终端执行
riscv64-unknown-elf-gcc.exe
# 预期输出(正常现象):
# riscv64-unknown-elf-gcc.exe: no input files
# (表示编译器正常调用,只是缺少输入文件)
⚠️ 常见问题:命令未找到
❌ 错误提示
'riscv64-unknown-elf-gcc.exe' 不是内部或外部命令,
也不是可运行的程序或批处理文件
🔍 错误原因
- Windows执行命令时,会在系统环境变量
PATH包含的目录中查找 - 若所有路径都查找完仍未找到,则报此错误
✅ 解决方案:配置PATH环境变量
- 打开:
我的电脑 → 属性 → 高级系统设置 → 环境变量 - 找到系统变量中的
Path - 点击"新建",添加编译器bin目录路径:
C:\SysGCC\risc-v\bin - 重要:添加完成后,重启cmd终端使配置生效
- 重新测试命令,应能正常调用编译器
二步到位编译法
🔄 编译流程图解
┌─────────────────────────────────────┐
│ 第一步:编译+汇编(生成目标文件) │
├─────────────────────────────────────┤
│ main.c ──编译/汇编──► main.o │
│ fun.c ──编译/汇编──► fun.o │
│ startup.s ──汇编────► startup.o │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 第二步:链接(生成可执行文件) │
├─────────────────────────────────────┤
│ main.o + fun.o + startup.o │
│ │ │
│ ▼ │
│ example.elf (可执行文件) │
└─────────────────────────────────────┘
📌 第一步:编译生成目标文件(不链接)
# 编译 main.c
riscv64-unknown-elf-gcc.exe -march=rv32i -mabi=ilp32 -Wall -O1 -g -c -o main.o main.c
# 编译 fun.c
riscv64-unknown-elf-gcc.exe -march=rv32i -mabi=ilp32 -Wall -O1 -g -c -o fun.o fun.c
# 汇编 startup.s
riscv64-unknown-elf-gcc.exe -march=rv32i -mabi=ilp32 -Wall -O1 -g -c -o startup.o startup.s
🔗 第二步:链接目标文件生成可执行文件
riscv64-unknown-elf-gcc.exe -march=rv32i -mabi=ilp32 -nostartfiles -T linker_script_twosteps.ld -Wl,-Map=example.map -o example.elf startup.o fun.o main.o
📚 编译选项详解
| 选项 | 全称/含义 | 作用说明 |
|---|---|---|
-march=rv32i |
Machine Architecture | 指定目标架构:32位RISC-V基础整数指令集 |
-mabi=ilp32 |
Application Binary Interface | 指定ABI规则:int/long/pointer均为32位 |
-Wall |
Warning All | 启用所有编译警告提示 |
-O1 |
Optimization Level 1 | 启用一级代码优化 |
-g |
Debug Info | 生成调试信息,支持GDB调试 |
-c |
Compile Only | 仅编译/汇编,不链接(二步法必需) |
-o <file> |
Output | 指定输出文件名 |
-nostartfiles |
No Start Files | 不使用默认启动文件(使用自定义startup.s时必需) |
-T linker_script.ld |
Linker Script | 指定自定义链接脚本 |
-Wl,-Map=example.map |
Map File | 生成内存映射文件,记录函数/变量地址 |
🔍 关于 -march 的指令集模块说明
rv32i = 32位 + 基础整数指令集
rv32im = + 乘除法指令集(m)
rv32if = + 单精度浮点指令集(f)
rv32id = + 双精度浮点指令集(d)
rv32ic = + 压缩指令集(c)
示例:-march=rv32imc → 支持基础整数+乘除法+压缩指令
🔍 关于 -mabi 的ABI规则
| ABI模式 | int | long | pointer | 适用场景 |
|---|---|---|---|---|
ilp32 |
32位 | 32位 | 32位 | 32位系统 |
lp64 |
32位 | 64位 | 64位 | 64位系统 |
一步到位编译法
⚡ 编译流程
源代码 (main.c + fun.c + startup.s)
│
▼
┌───────────────┐
│ 编译+汇编+链接 │
│ 一步完成 │
└───────────────┘
│
▼
example.elf (可执行文件)
📌 编译命令
riscv64-unknown-elf-gcc.exe -march=rv32i -mabi=ilp32 -Wall -O1 -g -nostartfiles -T linker_script_onestep.ld -Wl,-Map=example.map -o example.elf startup.s fun.c main.c
⚠️ 注意事项
❗ 一步到位编译法不能使用
-c选项
-c表示"仅编译不链接",与一步到位的目标矛盾
🔄 两种编译法对比
| 特性 | 二步到位法 | 一步到位法 |
|---|---|---|
| 命令数量 | 多条(编译+链接) | 单条命令 |
| 灵活性 | ✅ 高(可单独编译/调试模块) | ❌ 低 |
| 编译速度 | 增量编译时更快 | 每次都全量编译 |
| 适用场景 | 大型项目、模块化开发 | 小型项目、快速测试 |
| 关键选项 | 需 -c |
禁用 -c |
CMD命令行操作技巧
💡 提升效率的快捷键
1️⃣ Tab键:自动补全文件名
# 输入:riscv64-unkn[TAB]
# 自动补全为:riscv64-unknown-elf-gcc.exe
# 输入:main.[TAB]
# 自动补全为:main.c 或 main.o(根据文件存在情况)
2️⃣ ↑ / ↓ 方向键:浏览历史命令
# 按 ↑:逐条显示之前执行过的命令
# 按 ↓:向前浏览历史命令
# ✅ 避免重复输入长命令,提高操作效率
3️⃣ 其他实用技巧(补充)
# Ctrl+C:终止当前运行的命令
# Ctrl+L / cls:清屏
# Tab+Tab:显示所有可能的补全选项
# 文件名含空格时用引号包裹:"my file.c"
📎 附录:关键文件说明
🔗 linker_script_*.ld(链接脚本)
- 定义程序各段(.text, .data, .bss等)在内存中的布局
- 指定入口地址(_start)
- 控制变量/函数的内存对齐方式
🗺️ example.map(内存映射文件)
- 记录每个函数、变量的起始地址和大小
- 用于调试时定位代码位置
- 分析程序内存占用情况
🎯 example.elf(可执行文件)
- ELF(Executable and Linkable Format)格式
- 包含机器码、符号表、调试信息等
- 可直接加载到目标硬件或仿真器中运行
笔记整理基于《智能芯片》课程课件,适用于《智能芯片》课程学习参考 🚀
📚 主讲人:柳星
📧 邮箱:liu.xing@whut.edu.cn
🎯 课程核心:软硬件系统贯通

本文介绍了C程序在RISC-V架构下的编译全过程。通过范例程序展示了从源码到可执行文件的转换,并详细讲解了二步到位与一步到位两种编译方法及其命令选项,帮助理解程序从高级语言到机器码的全栈运行机制。
浙公网安备 33010602011771号