[智能芯片] 范例程序设计与程序编译

《智能芯片》课程笔记:范例程序设计与程序编译

📋 目录

  1. 课程教学目标
  2. 范例程序设计
  3. 编译环境准备
  4. 二步到位编译法
  5. 一步到位编译法
  6. 终端操作技巧

课程教学目标

🔗 核心目标:理解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. 下载编译器

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环境变量

  1. 打开:我的电脑 → 属性 → 高级系统设置 → 环境变量
  2. 找到系统变量中的 Path
  3. 点击"新建",添加编译器bin目录路径:
    C:\SysGCC\risc-v\bin
    
  4. 重要:添加完成后,重启cmd终端使配置生效
  5. 重新测试命令,应能正常调用编译器

二步到位编译法

🔄 编译流程图解

┌─────────────────────────────────────┐
│  第一步:编译+汇编(生成目标文件)     │
├─────────────────────────────────────┤
│  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

🎯 课程核心:软硬件系统贯通

posted @ 2026-03-14 14:02  H_Elden  阅读(1)  评论(0)    收藏  举报