RISCV学习(5)GD32VF103 MCU架构了解

RISCV学习(5)GD32VF103 MCU架构了解

1、芯片内核功能简介

GD32VF103 MCU架构,采用Bumblebee内核,芯来科技(Nuclei System Technology)与台湾晶心科技(Andes Technology)联合开发,由芯来科技(Nuclei System Technology)提供授权以及技术支持等服务。

架构特点:

  • CPU 内核(CPU Core)
    • 2 级变长流水线架构,采用一流的处理器架构设计,实现业界最高的能效比与最低的成本。
    • 简单的动态分支预测器。
    • 指令预取单元,能够按顺序预取两条指令, 从而隐藏指令的访存延迟。
    • 支持机器模式(Machine Mode)和用户模式(User Mode)。
  • 支持指令集架构(ISA, Instruction Set Architecture)
    • Bumblebee内核支持32位的RISC-V指令集架构,支持RV32IMAC指令子集的组合。
    • 硬件支持非对齐(Misalign)的存储器访问操作(Load/Store 指令)
  • 总线接口
    • 支持 32 比特宽的标准 AHB-Lite 系统总线接口,用于访问外部指令和数据。
    • 支持 32 比特宽的指令局部存储器(Instruction Local Memory, ILM)总线接口(支持标准的 AHB-Lite 或 SRAM 接口协议),用于连接私有的指令局部存储器
    • 支持 32 比特宽的数据局部存储器(Data Local Memory, DLM)总线接口(支持标准的 AHB-Lite 或 SRAM 接口协议),用于连接私有的数据局部存储器。
    • 支持 32 比特宽的私有设备总线(Private Peripheral Interface, PPI),支持标准的 APB接口协议,用于连接私有的外设
  • 调试功能
    • 支持标准 JTAG 接口。
    • 支持 RISC-V 调试标准。
    • 支持 4 个硬件断点(Hardware Breakpoints)。
    • 支持成熟的交互式调试工具
  • 低功耗管理
    • 支持 WFI(Wait For Interrupt)与 WFE(Wait For Event)进入休眠模式。
    • 支持两级休眠模式:浅度休眠与深度休眠
  • 内核私有的计时器单元(Machine Timer,简称 TIMER)
    • 64 比特宽的实时计时器,支持产生 RISC-V 标准定义的计时器中断。
  • 增强的内核中断控制器(Enhanced Core Level Interrupt Controller, ECLIC)
    • 支持 RISC-V 标准定义的的软件中断、计时器中断和外部中断。
    • 支持数十个外部中断源,中断源的数目和分配请参见具体 MCU 芯片的数据手册。
    • 支持 16 个中断级别和优先级,支持软件动态可编程修改中断级别和中断优先级的数值。
    • 支持基于中断级别的中断嵌套。
    • 支持快速向量中断处理机制。
    • 支持快速中断咬尾机制。
  • 支持NMI,不可屏蔽中断
    架构图如下图所示:
    • ILM 和DLM:指令/数据 局部memory,AHB Lite(AHB的简单版本,高性能/低功耗的嵌入式版本)接口访问,周期基本可达到1 Cycle,相当于ARM 的TCM。
    • 访问SRAM或者外部memory,则需要经过system bus总线,总线周期会高一些,
    • RISCV的版本为 1.1版本,更高级的版本模式等有变化,比如有4个模式等等。

2、芯片指令集

  • RV32 架构: 32 位地址空间,通用寄存器宽度 32 位。
  • I:支持 32 个通用整数寄存器。
  • M: 支持整数乘法与除法指令
  • C:支持编码长度为 16 位的压缩指令,提高代码密度。
  • A:支持原子操作指令。
    按照 RISC-V 架构命名规则,以上指令子集的组合可表示为 RV32IMAC

2.1 指令集举例说明

寻址

  • mv,传送寄存器到寄存器

    mv rd,rs,rs源寄存器送到目标寄存器rd

  • lw,内存访问,load word,将32位寄存器加载到目标寄存器,4字节读出

    lw,x15,0x0(x23) --> x15 = [x23+0],x15和x23是寄存器

  • lwsp:出栈指令

    lwsp x1,0x8(x2),x1 = [x2+0x8],x2是sp指针,sp+8的值 赋值给x1

  • sw,内存访问,store word,将将32位寄存器写到目标寄存器地址,4字节写入,

    sw,x27,0x0(x23) --> [x23+0] = x27

  • swsp:压栈指令

    swsp x1,0x8(x2),[x2+0x8]=x1,x2是sp指针,将x1压栈到sp+8的地址

  • lui,直接传输 将立即数左移12位到目标寄存器

    lui x27,0x20000 ---> x27 = 0x20000<<12;

  • lbu,load byte unsigned,加载一个无符号数据到目标地址,lb是加载有符号数,会进行扩展

    lbu x27,0x0(x25) ---> x27 = [x25 + 0]

  • sb,store byte,存储一个byte字节。

    sb x27, 0x0(x25)

  • sd ,store dword,存储8字节

  • ld,双字加载

加减乘除法左移 右移指令

  • slli:左移指令

  • srli:右移指令

  • or:或指令

  • addi:加法指令, i代表immediate ,立即数

    addi,x8,x8,0xC9

跳转指令

  • j,直接跳转指令,后面直接跟地址

    j 0x080005ZF6

  • jalr,带连接的跳转指令,一般和auipc一起用,

    auipc x1,0x1 将x1=x1 +1 <<12,即加上0x1000,4k地址

    jalr x1,-0x5CA(x1),先计算跳转地址,pc = x1- 0x5CA,然后将返回地址保存到x1,x1 =当前pc+4

其他指令

  • ret 指令

  • mret,异常返回指令

  • ebreak

  • ecall

    来看一段汇编,理解一些基础指令

    函数调用,传参数使用 x10 -x17,总共8个参数。

  • 左边第250行代码,函数led_blink函数,参数是test_counter_g。

  • mv x10,x27,将27的数据给了x10,说明x27就是test_counter_g的值,那就看x27的值

  • lw x27,0x0(x22),说明x27的值是通过x22地址读出来的,那就看x22的地址,

  • lui x22,0x20000,addi x22,x22,0x70, x22 = 0x20000+0x70 = 0x20000070 地址,符合地址要求

  • 接下来,auipc x1, 0x1 那么 x1 = 080015FC

  • jalr x1,-0x3DA,那么x1 = 0x080045FC - 0x3DA = 0x08001222,就是led_blink 函数的地址

  • 接着再看上面的代码, lw x15,0x0(x23) , lw x27,0x0(x22) ,加载了x22和x23地址的数据到x15和x27,x22是test_counter_g的地址, x23 的地址是什么呢? 通过上面可以计算其地址
  • lui x23,0x20000,addi x23,x24,0x6C, x23 = 0x20000+0x6c = 0x2000006C 地址,是 current_test_count_g的地址
  • 这样就比较好理解,加载了这两个变量的值,就是用来判断是否相等的。
  • beq x15,x27,0x8000632 如果不相等,直接跳走
  • 否则, c.mv x10,x24 ,把x24的值给了x10,说明又有函数调用
  • lui x24,0x40001 , addi x24,x24,-0x800 x24的地址为,x24 = 0x40001000 - 0x800 = 0x40000800,其是一个外设地址,是TIMER3的base地址。
  • auipc x1,0x0 , jalr x1,0x4F6(x1) ,x1 = 0x08000612+0x4F6 = 0x08000B08,是timer_counter_read函数的地址,
  • 接着是一个打印,刚刚x10 作为函数的返回值,这次作为打印的第三个参数,所以就是x12 寄存器的值,mv x12,x10
  • mv x11,x27 x27是 test_counter_g的值,是第二个参数
  • addi x10,x26,0x350 ,x10 = x26+x350,x26= 08000000,所以x10 = 08000350,字符串的地址
  • 最后是一个跳转函数,auipc x1,0x1 jalr x1,-0x580(x1) ,跳转地址= 08001622 - 0x580 = 0x080010A2,则是test_printf的函数。

3、芯片特权架构

Bumblebee 内核支持两个特权模式(Privilege Modes):

  • 机器模式(Machine Mode)是必须的模式,该 Privilege Mode 的编码是 0x3。
  • 用户模式(User Mode)是可配置的模式,该 Privilege Mode 的编码是 0x0。

RISCV的CSR 寄存器可以反应内核的状态,不是单个寄存器,是一系列寄存器。

3.1 机器模式(Machine Mode)

Bumblebee 内核有关 Machine Mode 的关键要点如下:

  • 处理器内核被复位后,默认处于 Machine Mode。
  • 在 Machine Mode 下,程序能够访问所有的 CSR 寄存器。
  • 跳转到用户模式。执行mret指令

3.2 用户模式(User Mode)

Bumblebee 内核有关 User Mode 的关键要点如下:

  • 在 User Mode 下只能够访问 User Mode 限定的 CSR 寄存器,
  • 从用户模式到机器模式,通过异常中断

3.3 机器子模式(Machine Sub-Mode)

Bumblebee 内核的 Machine Mode 可能处于四种不同的状态下,将之称之为机器子模式

(Machine Sub-Mode):

  • 正常机器模式(该 Machine Sub-Mode 的编码是 0x0):处理器内核被复位之后,处于此子模式之下。处理器复位后如果不产生异常、 NMI、中断,则一直正常运行于此模式之下。
  • 异常处理模式(该 Machine Sub-Mode 的编码是 0x2):响应异常后处理器内核处于此状态。
  • NMI 处理模式(该 Machine Sub-Mode 的编码是 0x3):响应 NMI 后处理器内核处于此状态。
  • 中断处理模式(该 Machine Sub-Mode 的编码是 0x1):响应中断后处理器内核处于此状态。

处理器内核当前处于的 Machine Sub-Mode 反映在 CSR 寄存器 msubm 的 TYP 域中,因此软件可以通过读取此 CSR 寄存器查看当前处于的 Machine Sub-Mode。

注意: 在 RISC-V 架构中,进入异常、 NMI 或者中断也被统称为 Trap

3.4 不支持PMP

  • PMP :physical Memory Protection,物理内存保护,即ARM 芯片里面的MPU

4、芯片中断机制

芯片中断主要有外部中断和内部中断,中断跳转模式主要有向量模式与非向量模式。

具体详情,可以参考笔者这篇文章RISCV学习(4)GD32VF103 MCU芯片学习

5、芯片异常机制

RISCV的芯片异常机制没有自动保存寄存器,需要用户自身保存。

异常处理的流程如下:均是硬件行为,一个时钟周期内完成。

  • 停止执行当前程序流,转而从 CSR 寄存器 mtvec 定义的 PC 地址开始执行。
  • 更新相关 CSR 寄存器,分别是以下几个寄存器:
    • mcause(Machine Cause Register)
    • mepc(Machine Exception Program Counter)
    • mtval(Machine Trap Value Register )
    • mstatus(Machine Status Register)
  • 更新处理器内核的 Privilege Mode 以及 Machine Sub-Mode。

    mtvec :硬件异常处理函数的统一入口,最低64位对齐。

    如下图所示:mtvec 为 x080013C3,
  • mtvec ADDR:0异常地址:0x080013C0,而该地址则为trap_entry的地址。
  • mtvec MODE:0x11,则为ECLIC 中断模式

笔者测试的异常代码如下:

c 复制代码
if(test_counter_g == 3)
 {
     current_test_count_g = 0;
     test_printf("%d\r\n",(test_counter_g/current_test_count_g));
 }

mepc:保存进入异常之前的PC值,作为异常返回的地址,当然也可以作为分析出错的地址,2字节对齐。

如下图所述,mepc值为0x080005fa,指令为ebreak,确实会产生异常,可能编译器认为笔者是想进入异常,则编译成了该指令。

mcause:异常原因寄存器,其值为0x38000003

  • INTERRUPT:其值为0,表示异常。
  • MPP:其值为3,表示特权模式,0为用户模式
  • MPIE:其值为1,表示允许中断,
  • EXCCODE:0x3,表示断点指令,可以进入异常。

    mtval:表示异常的访问地址或者指令编码,笔者这里为0,表示该数据没有意义。

再比如一个例子:机器模式下面直接调用ecall,会触发进入异常。

复制代码
test_entry:
  ecall
  ret


笔者测试,即使非对齐访问和写入,不会触发异常,依然程序正常进行,待后续研究。

再来一个例子,非法指令

c 复制代码
led_blink(test_counter_g);
 if(current_test_count_g != test_counter_g)
 {
     test_printf("test_counter_g=%d timer_counter=%d\r\n",test_counter_g,timer_counter_read(TIMER3));
     test_printf("mcause=0x%x, int num=%d, time=%d\r\n", int_num_g, (int_num_g&0x3FF), rdtime());
     current_test_count_g = test_counter_g;
 }

异常编码为2,代表非法指令,pc指针为0800061e,然后mtval记录的指令地址为0xC01026F2,就是这个非法指令的值。

6、芯片timer

6.1 系统TIMER

系统timer属于外设,外设的地址由厂商自己定义,没有定义成CSR寄存器。64位的寄存器。

c 复制代码
#define TIMER_MSIP 0xFFC
#define TIMER_MSIP_size   0x4
#define TIMER_MTIMECMP 0x8
#define TIMER_MTIMECMP_size 0x8
#define TIMER_MTIME 0x0
#define TIMER_MTIME_size 0x8

#define TIMER_CTRL_ADDR           0xd1000000
unsigned int mtime_lo(void)
{
  return *(volatile unsigned int *)(TIMER_CTRL_ADDR + TIMER_MTIME);
}


unsigned int mtime_hi(void)
{
  return *(volatile unsigned int *)(TIMER_CTRL_ADDR + TIMER_MTIME + 4);
}

寄存器地址如下:

测试效果如下:主循环打印

c 复制代码
test_printf("mtime low=%d, diff=%d\r\n",mtime_lo(), (mtime_lo()-mtime_low_g));
mtime_low_g = mtime_lo();

timer里面可以软件中断:msip软件中断

msip软件中断 中断号为3,仍然需要设置中断模式以及向量等方式。

c 复制代码
#if (INTERRUPT_MODE==INTERRUPT_VECTOR_MODE)
 __attribute__((interrupt)) void eclic_msip_handler()
#else
void eclic_msip_handler()
#endif
{
    msip_int_num_g =  read_csr(mcause);
    msip_int_clear();
}

void set_software_int()
{
    #if (INTERRUPT_MODE == INTERRUPT_VECTOR_MODE) 
    eclic_set_vmode(CLIC_INT_SFT);
#else
    eclic_set_nonvmode(CLIC_INT_SFT);
#endif
    eclic_set_posedge_trig(CLIC_INT_SFT);
    eclic_irq_enable(CLIC_INT_SFT, 1, 0);
}

void msip_int_set()
{
  *(volatile unsigned int *)(TIMER_CTRL_ADDR + TIMER_MSIP) = 1;
}

int msip_int_get()
{
  return *(volatile unsigned int *)(TIMER_CTRL_ADDR + TIMER_MSIP) ;
}

void msip_int_clear()
{
  *(volatile unsigned int *)(TIMER_CTRL_ADDR + TIMER_MSIP) = 0;
}

msip_int_set();
test_printf("set msip int,reg=%d, int num=%d\r\n",msip_int_get(),  (msip_int_num_g&0x3FF));

6.2 指令周期计数器和指令计数器

mcycle 为:指令周期计数器

instret:指令计数器

这两者均是CSR寄存器。

c 复制代码
cycle_start_g = read_csr(mcycle) ;
current_test_count_g = test_counter_g;
test_printf("start=%d end=%d, cycle=%d\r\n", cycle_start_g, read_csr(mcycle), (read_csr(mcycle))-cycle_start_g);

cycle_start_g = read_csr(instret) ;
current_test_count_g = test_counter_g;
test_printf("start=%d end=%d, cycle=%d\r\n", cycle_start_g, read_csr(instret), (read_csr(instret))-cycle_start_g);

指令完成计数器:总共完成了5条指令,

时钟周期:总共花费5个时钟周期,

7 附录参考

中断向量号

c 复制代码
/* define interrupt number */
typedef enum IRQn
{
	CLIC_INT_RESERVED        	 = 0,      			/*!< RISC-V reserved		*/
	CLIC_INT_SFT         		 = 3,				/*!< Software interrupt		*/
	CLIC_INT_TMR         		 = 7,				/*!< CPU Timer interrupt	*/
	CLIC_INT_BWEI        		 = 17,				/*!< Bus Error interrupt	*/
	CLIC_INT_PMOVI       		 = 18,				/*!< Performance Monitor	*/

    /* interruput numbers */
    WWDGT_IRQn                   = 19,      /*!< window watchDog timer interrupt                          */
    LVD_IRQn                     = 20,      /*!< LVD through EXTI line detect interrupt                   */
    TAMPER_IRQn                  = 21,      /*!< tamper through EXTI line detect                          */
    RTC_IRQn                     = 22,      /*!< RTC alarm interrupt                                      */
    FMC_IRQn                     = 23,      /*!< FMC interrupt                                            */
    RCU_CTC_IRQn                 = 24,      /*!< RCU and CTC interrupt                                    */
    EXTI0_IRQn                   = 25,      /*!< EXTI line 0 interrupts                                   */
    EXTI1_IRQn                   = 26,      /*!< EXTI line 1 interrupts                                   */
    EXTI2_IRQn                   = 27,      /*!< EXTI line 2 interrupts                                   */
    EXTI3_IRQn                   = 28,      /*!< EXTI line 3 interrupts                                   */
    EXTI4_IRQn                   = 29,     /*!< EXTI line 4 interrupts                                   */
    DMA0_Channel0_IRQn           = 30,     /*!< DMA0 channel0 interrupt                                  */
    DMA0_Channel1_IRQn           = 31,     /*!< DMA0 channel1 interrupt                                  */
    DMA0_Channel2_IRQn           = 32,     /*!< DMA0 channel2 interrupt                                  */
    DMA0_Channel3_IRQn           = 33,     /*!< DMA0 channel3 interrupt                                  */
    DMA0_Channel4_IRQn           = 34,     /*!< DMA0 channel4 interrupt                                  */
    DMA0_Channel5_IRQn           = 35,     /*!< DMA0 channel5 interrupt                                  */
    DMA0_Channel6_IRQn           = 36,     /*!< DMA0 channel6 interrupt                                  */
    ADC0_1_IRQn                  = 37,     /*!< ADC0 and ADC1 interrupt                                  */
    CAN0_TX_IRQn                 = 38,     /*!< CAN0 TX interrupts                                       */
    CAN0_RX0_IRQn                = 39,     /*!< CAN0 RX0 interrupts                                      */
    CAN0_RX1_IRQn                = 40,     /*!< CAN0 RX1 interrupts                                      */
    CAN0_EWMC_IRQn               = 41,     /*!< CAN0 EWMC interrupts                                     */
    EXTI5_9_IRQn                 = 42,     /*!< EXTI[9:5] interrupts                                     */
    TIMER0_BRK_IRQn              = 43,     /*!< TIMER0 break interrupts                                  */
    TIMER0_UP_IRQn               = 44,     /*!< TIMER0 update interrupts                                 */
    TIMER0_TRG_CMT_IRQn          = 45,     /*!< TIMER0 trigger and commutation interrupts                */
    TIMER0_Channel_IRQn          = 46,     /*!< TIMER0 channel capture compare interrupts                */
    TIMER1_IRQn                  = 47,     /*!< TIMER1 interrupt                                         */
    TIMER2_IRQn                  = 48,     /*!< TIMER2 interrupt                                         */
    TIMER3_IRQn                  = 49,     /*!< TIMER3 interrupts                                        */
    I2C0_EV_IRQn                 = 50,     /*!< I2C0 event interrupt                                     */
    I2C0_ER_IRQn                 = 51,     /*!< I2C0 error interrupt                                     */
    I2C1_EV_IRQn                 = 52,     /*!< I2C1 event interrupt                                     */
    I2C1_ER_IRQn                 = 53,     /*!< I2C1 error interrupt                                     */
    SPI0_IRQn                    = 54,     /*!< SPI0 interrupt                                           */
    SPI1_IRQn                    = 55,     /*!< SPI1 interrupt                                           */
    USART0_IRQn                  = 56,     /*!< USART0 interrupt                                         */
    USART1_IRQn                  = 57,     /*!< USART1 interrupt                                         */
    USART2_IRQn                  = 58,     /*!< USART2 interrupt                                         */
    EXTI10_15_IRQn               = 59,     /*!< EXTI[15:10] interrupts                                   */
    RTC_ALARM_IRQn               = 60,     /*!< RTC alarm interrupt EXTI                                 */
    USBFS_WKUP_IRQn              = 61,     /*!< USBFS wakeup interrupt                                   */

    EXMC_IRQn                    = 67,     /*!< EXMC global interrupt                                    */

    TIMER4_IRQn                  = 69,     /*!< TIMER4 global interrupt                                  */
    SPI2_IRQn                    = 70,     /*!< SPI2 global interrupt                                    */
    UART3_IRQn                   = 71,     /*!< UART3 global interrupt                                   */
    UART4_IRQn                   = 72,     /*!< UART4 global interrupt                                   */
    TIMER5_IRQn                  = 73,     /*!< TIMER5 global interrupt                                  */
    TIMER6_IRQn                  = 74,     /*!< TIMER6 global interrupt                                  */
    DMA1_Channel0_IRQn           = 75,     /*!< DMA1 channel0 global interrupt                           */
    DMA1_Channel1_IRQn           = 76,     /*!< DMA1 channel1 global interrupt                           */
    DMA1_Channel2_IRQn           = 77,     /*!< DMA1 channel2 global interrupt                           */
    DMA1_Channel3_IRQn           = 78,     /*!< DMA1 channel3 global interrupt                           */
    DMA1_Channel4_IRQn           = 79,     /*!< DMA1 channel3 global interrupt                           */

    CAN1_TX_IRQn                 = 82,     /*!< CAN1 TX interrupt                                        */
    CAN1_RX0_IRQn                = 83,     /*!< CAN1 RX0 interrupt                                       */
    CAN1_RX1_IRQn                = 84,     /*!< CAN1 RX1 interrupt                                       */
    CAN1_EWMC_IRQn               = 85,     /*!< CAN1 EWMC interrupt                                      */
    USBFS_IRQn                   = 86,     /*!< USBFS global interrupt                                   */

	ECLIC_NUM_INTERRUPTS
} IRQn_Type;
相关推荐
落雨封海2 天前
【1】GD32 系统架构、内核、中断系统、存储器系统
单片机·gd32
DOMINICHZL1 个月前
国产MCU替代STM32全解析:主流方案对比与实战指南
stm32·单片机·嵌入式硬件·gd32
老子姓李!1 个月前
【GD32】《当RISC-V撞上Wi-Fi 6:GD32VW553的“芯“动初体验》
单片机·嵌入式·risc-v·iot·gd32
格桑阿sir2 个月前
Kubernetes控制平面组件:APIServer 基于 X509 证书的认证机制
kubernetes·ssl/tls·kubeadm·x509·csr·认证机制·apiserver
不想写代码的我3 个月前
梁山派入门指南3——串口使用详解,包括串口发送数据、重定向、中断接收不定长数据、DMA+串口接收不定长数据,以及对应的bsp文件和使用示例
单片机·学习·gd32·梁山派
jiuri_12153 个月前
GD32F303 GCC 环境搭建
gd32·gcc
kainx4 个月前
AWS EKS 相关错误修复 - remote error: tls: internal error - CSR pending
kubernetes·云计算·k8s·aws·eks·csr
qq_459730035 个月前
1-7 GD32函数指针应用
c语言·stm32·单片机·嵌入式硬件·gd32
KGback7 个月前
【项目记录】大模型基于llama.cpp在Qemu-riscv64向量扩展指令下的部署
人工智能·llama·riscv