1.1 SoC的配置与启动
1.1.1 地址空间映射
本文所设计的SoC采用32位RISC-V处理器XQ900RV,地址空间32位,最大寻址范围4GB空间。寻址空间分为五部分,分别是主存储空间、Bootloader空间、中断向量空间、AHB外设空间以及APB外设空间,如图4-7所示。
图4-7 SoC地址空间映射
由于XQ900RV为哈佛架构处理器,指令和数据分开访问,因此设置独立的指令存储和数据存储空间,每个空间大小为512KB(指令存储空间为0x00000000到0x0007ffff,数据存储空间为0x20000000到0x2007ffff),并预留一定的地址空间便于扩展。Bootloader区域存放启动程序,预留32KB(0x30000000~0x30007fff)。XQ900RV支持RISC-V标准中断机制,中断基地址存储在CSR中断控制寄存器中,中断向量表一共256项(0x3000a000~0x3000b000),只存储中断的偏移地址,每个中断向量表占用4字节空间存储一条跳转指令,如果发生中断则根据跳转指令指向的位置跳转到中断处理函数。
AHB总线起始地址为0x40000000。首先是1KB DMA地址空间;然后是SoC系统存放暂存程序运行临时数据的片上SRAM单元,空间大小为32KB。最后,预留了一部分AHB地址空间用于AHB外设扩充。
APB总线起始地址为0x50000000。首先是GPIO地址空间,预留1KB;其次是可用于交互仿真验证结果的UART地址空间,预留1KB;其他地址可分配用于其他外设的访问。
SoC已分配的地址空间从0x00000000到0x50000400,共约1.3GB地址空间。
1.1.2 编译环境与启动程序
SoC运行应用程序,首先要设计编译环境和启动程序,高级语言程序才能转变为机器代码并正确执行。RISC-V编译采用riscv-gun-toolchain工具链,本文3.4节讨论了该开源工具链,增加了加解密扩展指令集,并且成功编译出对应的程序执行代码。本章的编译工作基于3.4节工具链部分,完成应用程序的所有编译过程。XQ900RV支持IMAFDC以及部分加解密指令集,因此编译时指定“--with-arch=rv32gc_zknh_zksh”以及“--abi=ilp32d”,编译环境中使用到的工具如下表4-1所示。
表4-1 工具链功能描述
Tab. 4-1 Toolchain Functional Description
工具名称 | 功能描述 |
riscv32-sdu-elf-gcc | 32位RISC-V交叉汇编器,编译指定优化等级和指令集架构 |
riscv32-sdu-elf-asriscv32-sdu-elf-ldriscv32-sdu-elf-objcopyriscv32-sdu-elf-objdump | 32位RISC-V汇编器,将汇编代码转换为elf目标文件32位RISC-V链接器,将多个目标文件和库文件链接32位RISC-V格式转换器,将elf文件与bin文件转换32位RISC-V反汇编器,将二进制文件转换为反汇编代码 |
其中riscv32-sdu-elf-ld是链接器,在生成可执行文件时候需要使用链接脚本,用于指定指令或者数据的存放位置。如4.3.1节所述,SoC的主存储空间分为指令存储空间IMEM和数据存储空间DMEM,每部分大小为0x80000,即512KB空间。链接脚本如图4-8所示,其中boot文件为启动文件:
图4-8 链接脚本文件
图4-8脚本里定义了两个内存区域:IMEM和DMEM。IMEM的起始地址为0x00000000,大小为0x80000。DMEM的起始地址为0x20000000,大小为0x80000。内存区域IMEM 和DMEM是可读可写可执行的,因此它们的权限被标记为“RWX”。
其后,脚本定义了“__kernel_stack”变量,值为0x200bfff8,被用于设置内核堆栈的起始地址。
然后,脚本使用“ENTRY”命令指定了程序的入口点。程序的入口点是“__start”函数。
最后,脚本中的“SECTIONS”块描述了如何把各个段(例如.text、.data和.bss)放到内存区域中。例如,“.text”段被放到IMEM内存区域中,“.data”和“.bss”段被放到 DMEM内存区域中。
“.bss”段是一种特殊的段,它不包含任何有用的数据。在链接时,它的作用是把所有未初始化的全局变量和静态变量分配到内存中。在程序运行时,这些变量的值将被初始化为0。“.COMMON”段是另一种特殊的段,它用于处理未定义的全局变量。如果源文件引用了一个未定义的全局变量,那么链接器会把这个变量放到.COMMON段中。
最后,脚本中还定义了一些变量,例如__erodata、data_start、data_end、bss_start和bss_end。这些变量用于跟踪各个段的起始地址和结束地址,用于BootLoader启动程序的设计。
准备好链接脚本以后,使用Makefile脚本组织编译环境。部分脚本代码如图4-9所示:
图4-9 部分Makefile脚本
“${FILE}_inst.hex”指定编译后的指令存储部分,包括.text和.rodata字段, “${FILE}_data.hex”指定编译后的数据存储部分,包括.data和.bss字段。其中“.hex”文件为编译生成的二进制文件,“.dasm”和“.dump”文件为反汇编文件。
底层软件编程需要遵循RISC-V的编程规范。XQ900RV支持整数和浮点运算,包括32个整数和32个浮点寄存器,分别用x0~x31和f0~f31表示,每个寄存器都有自己的应用范围,使用范围如表4-2所示。
表4-2 RISC-V整数和浮点寄存器功能表
Tab. 4-2 RISC-V Integer and Floating Point Register Function Table
寄存器名 | 代号 | 描述 | 保存者 |
X0 X1 X2 X3 X4 X5~X7 X8 | Zero RA SP GP TP T0~T2 S0/FP | The constant value 0 Return address Stack pointer Global pointer Threadpointer Temporaries Saved register/Frame pointer | N.A. Caller Callee -- -- Caller Callee |
续表4-2 RISC-V整数和浮点寄存器功能表
Tab. 4-2 RISC-V Integer and Floating Point Register Function Table(continued)
寄存器名 | 代号 | 描述 | 保存者 |
X9 X10-X11 X12-X17 X18-X27 X28-X31 F0-F7 F8-F9 F10-F11 F12-F17 F18-F27 F28-F31 | S1 A0-A7 A2-A7 S2-S11 T3-T6 FT0-FT7 FS0-FS1 FA0-FA1 FS2-FS7 FS2-FS11 FT8-FT11 | Saved register Function arguments/Return values Function arguments Saved registers Temporaries FP Temporaries FP Saved registers FP Function arguments/Return values FP Function arguments FP Saved registers R[rd]=R[rs1]+R[rs2] | Callee Caller Caller Callee Caller Caller Callee Caller Caller Callee Caller |
准备好编译环境后,设计SoC的启动程序,即Bootloader。启动程序使用汇编编写boot.s程序。图4-10是本文实现的启动程序框架。
图4-10 Bootloader程序框架
“.text”和“.global __start”用于指定程序入口,使链接脚本识别。启动文件实现功能如下:
(1) 将寄存器x2设置为__kernel_stack的地址,将寄存器x3设置为__erodata的地址,将寄存器x4设置为__data_start__的地址,将寄存器x5设置为__data_end__的地址。
(2) 使用循环,拷贝__erodata区域的数据到__data_start__区域。
(3) 使用循环,将__bss_end__区域的数据清零。
(4) 设置trap_handler为异常处理函数,设置vector_table为中断向量表。
(5) 开启中断和浮点单元。
(6) 调用main函数。
(7) 在main函数结束后,调用__exit函数。
在编译环境中编译简单的helloworld程序,生成对应的十六进制hex文件如图4-11所示。
图4-11 编译生成的十六进制文件
图4-11中的二进制文件不便于调试。为了便于调试和定位指令,使用objdump工具生成对应的反汇编文件,如图4-12所示,可以看到反汇编后指令内容就是在boot.s文件中编写的内容。
图4-12 使用objdump工具生成反汇编代码
至此,SoC的编译环境和启动程序已经设计完毕并成功运行,可以继续开展软件开发和验证工作。
1.2 本章小结
本章基于XQ900RV处理器,设计SoC芯片。提出了一种针对传统时分复用总线优化的并发总线矩阵结构,通过逻辑优化,实现了多个主设备对不同从设备的并发访问,以及多个主设备访问同一从设备时将仲裁权限交给从设备。随后分析了SoC芯片中 DMA模块设计的关键点,以及设计细节。随后,讨论了SoC芯片软件开发环境的搭建流程,详细介绍了SoC地址空间划分后的编译和启动程序设计,包括使用Makefile脚本组织编译环境、链接脚本设计以及启动程序的设计等。