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 中斷模式
筆者測試的異常代碼如下:
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:ecallret
筆者測試,即使非對齊訪問和寫入,不會觸發異常,依然程序正常進行,待后續研究。
再來一個例子,非法指令
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位的寄存器。
#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);
}
寄存器地址如下:
測試效果如下:主循環打印
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,仍然需要設置中斷模式以及向量等方式。
#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);
#elseeclic_set_nonvmode(CLIC_INT_SFT);
#endifeclic_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寄存器。
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 附錄參考
中斷向量號
/* 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;