RISC-V处理器的C语言启动代码设计方法

嵌入式资讯精选 2020-02-18 00:00

随着微处理器市场竞争加剧,RISC-V指令集越来越受到关注。虽然RISC-V并非第一个开源的指令集(ISA),却是第一个可依据实际应用场景灵活选择指令集的指令集架构。RISC-V指令集架构可以满足从高性能服务器CPU直至超低功耗传感器内嵌CPU的全部应用场景。


通常情况下,一款处理器的启动代码多使用汇编语言设计。其原因包括:在处理器启动阶段,C运行环境还未初始化;汇编语言实现的代码不受编译器影响;某些特殊寄存器操作无法通过C编译得到对应汇编代码;处理器的某些特殊设计不利于C语言的使用等。本文将解决前述问题,展示一种使用C语言为RISC-V处理器设计启动代码的方法。


为了更清晰地讨论问题并最大程度的便于读者理解某些流程,本文以芯来科技基于RV32IMC指令集的N205系列内核作为目标处理器,从N205内核的对标架构——来自ARM的Cortex-M内核在IAR EmbeddedWorkbench for ARM[1](后文简称IAR)环境下的C语言启动代码切入,逐步引入并实现SEGGER Embedded Studio[2](后文简称SES)环境下N205系列内核的C语言启动代码。


01
Cortex-M内核在IAR环境下的C语言启动代码



Cortex系列内核是ARM公司迄今为止最成功的系列产品,包括A、R、M三类,其中M系列主要针对微控制器市场。Cortex-M内核具有以下特点:内核包含高级中断控制器;中断响应时,处理器硬件将相应的寄存器入栈和出栈;向量表中首单元内容为栈地址,其余均为异常或中断函数的入口地址;向量表中的内容均为硬件自动载入。


代码段1所示内容是Cortex-M内核在IAR环境下使用C语言开发的启动代码。


【代码段-1】


#pragma language=extended         

--snip--

voidResetISR(void);           

--snip--

externvoid __iar_program_start(void);   

staticunsigned long pulStack[64] @".noinit";

typedefunion         

{

    void (*pfnHandler)(void);

    unsigned long ulPtr;

}

uVectorEntry;

__rootconst uVectorEntry __vector_table[] @".intvec" =         

{

    { .ulPtr = (unsigned long)pulStack +sizeof(pulStack) },           

    ResetISR,         

--snip--

};

--snip--

voidResetISR(void)

{

    __iar_program_start();

}


此处对上述代码做简要分析:


是IAR的#pragma指导符。


是复位函数声明,复位函数是处理器复位后首先执行的代码,有时也称为复位入口函数。


是IAR系统函数声明,__iar_program_start是IAR的系统函数,主要作用是执行C运行环境初始化并调用系统主函数main。


使用IAR @操作符定义系统栈区。


声明向量表的联合类型。


使用IAR对象属性声明__root及@操作符定义向量表,其中,第一个元素保存了栈底地址,后续元素均为函数地址。


从上述分析过程可以看出启动代码的必要工作包括定义栈区、定义并初始化向量表,定义并实现系统复位函数,初始化栈指针或栈寄存器等。依据处理器的架构不同,上述操作中某些过程需要由软件完成,有些则由硬件自动加载。


另外,有关IAR的指导符、对象属属性等内容不属于本文讨论范畴,有需要可自行查阅。这里给出两点提示:IAR环境的编译系统为IAR自行开发,故示例代码中的指导符号不适用于GCC;某些指导符会因IAR环境版本不同而有所差异。


02
在SES环境下实现RISC-V内核C语言启动代码的必要知识



前文提到,RISC-V是指令集而不是具体的设计实现,这与之前讨论的Cortex-M系列内核有很大不同。简单地说,不同厂商基于同种Cortex-M内核的处理器,仅从内核的层面来看可能没有太大差异,但不同厂商开发的具有相同指令集的RISC-V处理器则各有千秋:一方面是相同功能的具体实现可能不同;另一方面,不同厂商可以实现不同的指令扩展。


 这里对比Cortex-M内核,列举RISC-V处理器的一些特点:不同厂商中断控制器的实现各有特色;中断响应时,处理器硬件不会保存上下文,需要软件完成该功能;向量表依据厂商不同而有明显差异,可能向量表的首地址保存的是指令而非地址。


在不同厂商的Cortex-M内核处理器间作切换时,由于处理器内核的一致性,启动代码几乎无需改动,因而使用汇编或者C语言来设计启动代码似乎差异不大,但要降低在不同厂商的RISC-V处理器间切换的复杂度,使用C语言开发启动代码是一种有效途径。


前文曾提到启动代码的必要工作包括定义栈区、定义并初始化向量表,定义并实现系统复位函数,初始化栈指针或栈寄存器等。在前述Cortex-M内核的C启动代码中,IAR提供了接口__iar_program_start,该接口隐藏了几乎所有细节。在SES环境下并没有这样的接口可供使用,为了实现RISC-V处理器的C语言启动代码,需要如下的编译器及链接器相关知识。


(1)GCC内联汇编


RISC-V处理器中的CSR寄存器需要特殊的指令才能进行访问,C编译器无法产生类似的指令,故C语言启动代码中仍然需要插入数条汇编指令。为了实现汇编指令与C语言的交互,需要使用GCC内联汇编,实例介绍如下:


asmvolatile (     

"csrw0x307, %0"   

:                   

:"r"(vector_base)

:                   

);

其中: asm为GCC内联汇编关键字,volatile为修饰符; 双引号引用的汇编指令列表,如有多条指令,可以使用"\n"分割;其中%0代表输入操作数列表中的第一个值; 可选的输出操作数列表; 可选的输入操作数列表,此处"r"代表使用编译器自动分配的寄存器来存储变量vector_base; 可选的受影响寄存器列表。


(2)section与初始化


简单来讲,将目标文件中的sections链接起来就是可执行文件。在默认情况下,编译器会创建标准sections。表1是标准section的简单介绍。


表1   标准section概要




通过表1可以看出,程序的可执行代码存放于.text section,已初始化的全局和静态变量存放于.data section。


一个典型的SoC系统通常包含两类存储器,即ROM和RAM。对于当今的处理器来说,这两部分通常是Flash和SRAM。系统掉电情况下,SRAM中是无法保存数据的,因此C语言中的变量初始值需要保存于Flash中。系统上电后,由初始化代码将初始化数据从Flash拷贝到SRAM的目标地址。如前所述,这是初始化代码的重要工作之一。


接下来将阐述如何从Flash中找到初始化数据的位置并在C语言中引用。


(3)链接器变量的C语言访问


从链接器的观点看,初始值在Flash中的存放地址称为LMA(加载存储地址),对应变量在SRAM的运行时地址称为VMA(虚拟存储地址)。链接器脚本是用来描述处理器存储器分布、各section 及标准section的包含关系、相应LMA及VMA地址或存放区域等的文件。


代码段2是一个标准链接器脚本的片段。这里通过这个片段来讲述链接器变量的C语言访问。


【代码段-2】


MEMORY

{

  --snip--

}

SECTIONS

{

  --snip--

  __data_load_start__ = ALIGN(__srodata_end__ ,4);

  .data ALIGN(__RAM_segment_start__ , 4) :AT(ALIGN(__srodata_end__ , 4))

  {

    __data_start__ = .;

    *(.data .data.*)

  }

  __data_end__ = __data_start__ +SIZEOF(.data);

  __data_size__ = SIZEOF(.data);

  __data_load_end__ = __data_load_start__ +SIZEOF(.data);

  --snip--

}

在代码段2中,定义了链接器脚本变量__data_load_start__、__data_start__及__data_end__。其中,__data_load_start__代表LMA地址,__data_start__代表VMA地址。在C语言中访问这些变量有以下两种方法:将链接器脚本变量声明为数据类型,例如在C语言文件中声明extern uint32_t __data_load_start__;通过&__data_load_start__获取变量的值;将链接器脚本变量声明为数组,例如在C语言文件中声明externuint32_t __data_load_start__[];通过__data_load_start__获取变量的值。


(4)函数属性


在通常情况下,编译器会为每个函数自动产生序言和结尾序列,即在函数的头部进行一些入栈操作,在函数的末尾进行对应的出栈操作。一个明显的问题就是在C语言启动代码中,复位函数执行时可能栈指针或栈寄存器还没有进行初始化,这时的栈操作极有可能会导致处理器访问非法地址而使程序崩溃。此外,如前文所提到的RISC-V处理器的复位入口可能保存的是跳转指令而不是地址,短的跳转地址可以保证用一条指令完成跳转。


鉴于上述原因,需要使用相关的函数属性来通知编译器剔除默认的函数序列并指定section,如下形式的复位函数定义可满足该要求:


void __attribute__((section(".init"),naked)) reset_handler(){

--snip--

};

 

03
RISC-V内核的C语言启动代码实例



前面内容介绍了相关背景知识和技术手段,下面通过一个实际的框架程序来展示RISC-V处理器的C语言启动代码。其中,代码段3是C语言启动代码的实现,代码段4是向量表。代码中的所有关键点前文均有介绍,在此不在赘述。


【代码段-3】


#include"riscv_encoding.h"     

#include<stdint.h>

--snip--

externuint32_t __data_load_start__;   

--snip--

externuint32_t __bss_start__;

--snip--

externvoid (*const vector_base[])(void);

externvoid main(void);

--snip--

conststruct {

  uint32_t* load;

  uint32_t* start;

  uint32_t* end;

}dsection[3] = {

    --snip--

};

conststruct {

  uint32_t* start;

  uint32_t* end;

}bsection[3] = {

    --snip--

};

void __attribute__((section(".init"),naked)) reset_handler() {

  register uint32_t *src, *dst;

  --snip--

    /* 嵌入汇编 */

    asm volatile("csrw 0x307,%0"::"r"(vector_base));

--snip--

   asm volatile("la gp, __sdata_start__+0x800");

   asm volatile("la sp,__stack_end__");

--snip--

  /* 进行系统时钟初始化等 */

  init();

   /* 将数据的初始化值拷贝至RAM */

   if(&__vectors_load_start__ !=&__RAM_segment_start__){      

    for(uint8_t idx = 0; idx < 3; idx++){

      src=dsection[idx].load;

      dst=dsection[idx].start;

      while(dst < dsection[idx].end){

        *dst=*src;

        dst++;

        src++;

      }

    }

  }

    /* 将.bss区域清零 */

    for(uint8_t idx=0;idx < 3;idx++){

      dst=bsection[idx].start;

      while(dst<bsection[idx].end){

        *dst=0U;

        dst++;

      }

    }

    /* 调用主函数 */

    main(); 

  }

--snip--


【代码段-4】


.section .vectors, "ax"

  --snip--

 .globl vector_base

vector_base:

  jreset_handler

 .align     2

 .word     0

  --snip--

04
结  语



通常半导体厂商会在配套的软件开发包中提供处理器的启动代码,这导致多数嵌入式开发人员可能更关注应用部分的代码实现而忽视启动代码的存在。鉴于厂商提供的启动代码几乎都用汇编语言编写,这使得很多开发人员误以为启动代码必须使用汇编语言开发。


事实上,大多数处理器的启动代码都可以使用C语言进行开发且代码效率与汇编几乎没有差异。在工程实践中,很多深层次开发都需要对启动代码进行修改或重写,基于C语言的代码可以节省开发人员在学习汇编指令方面的时间和精力,同时在后续的升级维护中更加高效。


补充知识点:


[1]考虑到Cortex-M系列架构的开发多使用IAR、MDK等环境,此处以IAR环境为例。


[2]考虑到当前RISC-V的集成开发环境多基于Eclipse构建,SEGGER Embedded Studio环境基于自有构架且使用方便、功能强大,故此处以SES为例。另外,包括SES在内的RISC-V开发环境下的编译系统均基于GCC,故本文讨论的方法也适用于其他开发环境。


[3]如果需要在GCC内联汇编代码中使用宏定义,就需要使用一种称为双重宏定义的方法,示例如下:

 

#defineCSR_MTVT  0x307

#defineSTR(R) #R

#defineXSTR(R) STR(R)

 

/*asm volatile("csrw 0x307, %0"::"r"(vector_base));  */

asmvolatile("csrw "XSTR(CSR_MTVT)",%0"::"r"(vector_base));



作者简介:

唐思超,现任北京知存科技有限公司软件开发经理,负责人工智能芯片工具链及嵌入式开发,具有14年硬件电路设计及软件开发经验,擅长处理器、编译系统及操作系统的相关设计开发及底层机制的综合运用。




1.嵌入式就业的那些事~

2.FreeRTOS 10.3.0正式发布,源代码迁到Github!

3.2020年第2期《单片机与嵌入式系统应用》电子刊新鲜出炉!

4.简单快捷又可靠!一起来体验KE系列MCU的电容式触摸方案

5.Arm宣布推出Cortex-M55核心和Ethos-U55 microNPU,瞄准低功耗Edge AI

6.ANSI C、ISO C、Standard 是什么关系?


嵌入式资讯精选 掌握最鲜资讯,尽领行业新风
评论
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 41浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 108浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 108浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 208浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 47浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 45浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 159浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 137浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 93浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 192浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 152浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 81浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦