非常感谢有机会接触澎峰科技出品的Perf-V开发板,让我有机会接触和探究RISC-V软核。玩Perf-V开发板的都会发现,其板载的4个LED灯中只有LED3能被用户程序访问操作,也就是用户无法完成最基本的入门程序--流水灯实验(就和学程序设计语言一样,helloworld程序一样)。所以经过一番折腾(看部分源码、看胡老师的两本书和自己对fpga的一些认识),总算将E203 SOC的I/O引脚进行了重新映射,使得在Perf-V开发板上也能完成基本的流水灯实验。当然,大家也可以参照此方法,做更多的尝试,这样就不必受限于出厂的SOC核了。现在将我的思路和工作步骤总结如下。
1. 因为我需要将E203 SOC的I/O引脚进行重新映射,这步工作是不牵扯GPIO接口部分的实现,更不牵扯RISC-V软核部分的实现,而它仅仅是E203 SOC引脚和板载物理设备的连接关系部分,所以从综合角度来说,它应该是最后被综合的那个文件。打开vivado2018.1可以看到综合顺序,如下图1中红色框1所示。
图1 综合顺序
从上图1中可以看到,红色框2以上就是E203软核和SOC对应的.v文件,而红色框2所对应的system.v文件就是上述曾分析的需要更改的文件,打开此文件后可以发现,它就是描述板载设备和E203 SOC接口的对应关系,当然这个和e203_0404.xdc文件还是有区别的(自行脑补吧)。搞清楚这个了,那么接下来工作就简单了。
当然也可以通过另一个角度来查看,如下图2中红色框所示。点击vivado2018.1左边执行栏中的原理图选项,就可以在图2右侧看到所生成的e203 SOC原理图,经过放大查到led3引脚,点击右键选择Go to Source选项,也能找到system.v文件(当初我便是这么查找的该文件)。
图5 查看执行后生成的原理图
2. 因为E203 SOC总共对外提供有32个I/O引脚,如下图3中红色框和绿色字所示。这里,经过对system.v文件的分析以及我将要进行的工作,我计划把Pin9分配给了led_0,Pin23分配给了led_1,Pin18分配给了led_2,Pin14分配给了led_3。
图3 更改system.v文件中的引脚描述
接下来的工作,只需点击综合和执行即可,不出意外即可执行成功(这里所花时间和机器有关,我这里花了近5分钟才搞定)。紧接着工作就是generate bitstream,再根据perf-v实验教程进行flash固化即可。
write_cfgmem -format mcs -interface spix4 -size 128 -loadbit "up 0x0 D:/exp/Perf-V-IDE/50T/impl_1/system.bit" -force D:/exp/Perf-V-IDE/50T/impl_1/system.mcs
3. 搞定前述步骤后,打开Perf-V IDE新建一个流水灯的工程。这里需要修改一个hifive1.h头文件,如下图4所示。其文件路径如图4左边所示,在其中增加了对led1-3的宏定义。这里的值和上面修改的I/O引脚值是对应的。当然,如果发现板载流水灯的灯序不对,那么你只需在这里更改下led宏定义即可,和第2步所做的没有关系。
个人猜测Perf-V IDE的问题,这里想使用定时中断方式使led灯流水,没有搞定,所以这里采用的查询方式,代码如下所示。
#include <stdio.h> #include <stdlib.h> #include "platform.h" #include <string.h> #include "plic/plic_driver.h" #include "hifive1.h" #include "encoding.h" #include <unistd.h> #include "stdatomic.h" #define uchar unsigned char #define uint unsigned int uint32_t count=0; uint32_t delay_mark=0; void delay(uint32_t times); void reset_demo (uint32_t times); void GPIO_SET(uint32_t pin_num,uint32_t pin_val,uint32_t pin_model); // Structures for registering different interrupt handlers // for different parts of the application. typedef void (*function_ptr_t) (void); void no_interrupt_handler (void) {}; function_ptr_t g_ext_interrupt_handlers[PLIC_NUM_INTERRUPTS]; // Instance data for the PLIC. plic_instance_t g_plic; /*Entry Point for PLIC Interrupt Handler*/ void handle_m_ext_interrupt(){ plic_source int_num = PLIC_claim_interrupt(&g_plic); if ((int_num >=1 ) && (int_num < PLIC_NUM_INTERRUPTS)) { g_ext_interrupt_handlers[int_num](); } else { exit(1 + (uintptr_t) int_num); } PLIC_complete_interrupt(&g_plic, int_num); } /*Entry Point for Machine Timer Interrupt Handler*/ void handle_m_time_interrupt(){ clear_csr(mie, MIP_MTIP); volatile uint64_t * mtime = (uint64_t*) (CLINT_CTRL_ADDR + CLINT_MTIME); volatile uint64_t * mtimecmp = (uint64_t*) (CLINT_CTRL_ADDR + CLINT_MTIMECMP); uint64_t now = *mtime; uint64_t then = now +0.5* RTC_FREQ; *mtimecmp = then; count++; set_csr(mie, MIP_MTIP); } void print_instructions() { //write (STDOUT_FILENO, instructions_msg, strlen(instructions_msg)); //write (STDOUT_FILENO, instructions_msg_sirv, strlen(instructions_msg_sirv)); // printf ("%s",instructions_msg_sirv); } void reset_demo (uint32_t times){ // Disable the machine & timer interrupts until setup is done. clear_csr(mie, MIP_MEIP); clear_csr(mie, MIP_MTIP); for (int ii = 0; ii < PLIC_NUM_INTERRUPTS; ii ++){ g_ext_interrupt_handlers[ii] = no_interrupt_handler; } // Set the machine timer to go off in 3 seconds. volatile uint64_t * mtime = (uint64_t*) (CLINT_CTRL_ADDR + CLINT_MTIME); volatile uint64_t * mtimecmp = (uint64_t*) (CLINT_CTRL_ADDR + CLINT_MTIMECMP); uint64_t now = *mtime; uint64_t then = now + times*RTC_FREQ; *mtimecmp = then; // modified by Bob ma // 2019-1-8 // Enable the Machine-External bit in MIE //set_csr(mie, MIP_MEIP); // Enable the Machine-Timer bit in MIE //set_csr(mie, MIP_MTIP); // Enable interrupts in general. //set_csr(mstatus, MSTATUS_MIE); } void GPIO_SET(uint32_t pin_num,uint32_t pin_val,uint32_t pin_model) { if(pin_model==output) { GPIO_REG(GPIO_OUTPUT_EN) |= (0x01<<pin_num); if(pin_val==1) { GPIO_REG(GPIO_OUTPUT_VAL) |=(0x01<<pin_num); } if(pin_val==0) { GPIO_REG(GPIO_OUTPUT_VAL) &=~(0x01<<pin_num); } } if(pin_model==input) { GPIO_REG(GPIO_INPUT_EN) |= (0x01<<pin_num); } } int main(int argc, char **argv) { int loop; /************************************************************************** * Set up the PLIC * *************************************************************************/ PLIC_init(&g_plic, PLIC_CTRL_ADDR, PLIC_NUM_INTERRUPTS, PLIC_NUM_PRIORITIES); reset_demo(1); GPIO_SET(ledD4_r,1,output); GPIO_SET(ledD4_g,1,output); GPIO_SET(ledD4_b,1,output); GPIO_SET(ledD5_r,1,output); GPIO_SET(ledD5_g,1,output); GPIO_SET(ledD5_b,1,output); GPIO_SET(ledD6_r,1,output); GPIO_SET(ledD6_g,1,output); GPIO_SET(ledD6_b,1,output); GPIO_SET(led4,1,output); GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); //GPIO_SET(BLUE_LED_OFFSET,1,output); while (1){ if(count==1) { GPIO_SET(led1,0,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); GPIO_SET(led4,1,output); } if(count==2) { GPIO_SET(led1,1,output); GPIO_SET(led2,0,output); GPIO_SET(led3,1,output); GPIO_SET(led4,1,output); } if(count==3) { GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,0,output); GPIO_SET(led4,1,output); } if(count==4) { GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); GPIO_SET(led4,0,output); } if(count==5) { GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); GPIO_SET(led4,1,output); } if(count==6) { GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); GPIO_SET(led4,0,output); } if(count==7) { GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,0,output); GPIO_SET(led4,1,output); } if(count==8) { GPIO_SET(led1,1,output); GPIO_SET(led2,0,output); GPIO_SET(led3,1,output); GPIO_SET(led4,1,output); } if(count==9) { GPIO_SET(led1,0,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); GPIO_SET(led4,1,output); } if(count==10) { GPIO_SET(led1,1,output); GPIO_SET(led2,1,output); GPIO_SET(led3,1,output); GPIO_SET(led4,1,output); } if(count==10) { count=0; } count++; for(loop = 0; loop < 500000; loop++); } return 0; }
演示效果如下所示。
4. 写这些看似简单,但是摸索的过程真的令人很痛苦。接下来的工作就是在读胡老师的姐妹书的基础上,依次搞定定时中断、按键中断、PWM以及UART吧,然后再逐步摸向E203软核内部。