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;