近几年来,在ARM和Intel所垄断的处理器架构格局中,一种新的免费开源指令集架构RISC-V突然声名鹊起,成为处理器领域的新秀。目前正值中美芯片之争,国内对RISC-V的关注度也很火热,前景被无限看好。那么RISC-V到底与其他架构有什么不同,又会带来哪些机遇与挑战呢?下面我将对RISC-V进行简单的介绍:
一、指令集简介
一般来讲,先有指令集,才会有芯片来支持指令集。指令集类似一种标准,定义了芯片支持的功能。简单来说,指令集就是定义了一种计算机识别的编码,负责告诉计算机一编码的含义是什么。指令集的强弱是CPU的重要指标,是提高微处理器效率的最有效工具之一。一个好的指令集设计可以让CPU的设计更加方便,让编译器能够对这一架构生成效率更高的代码。
众所周知,处理器领域有两大指令集架构纵横天下,复杂指令集(Complex Intruction Set Computer, CISC)的代表英特尔x86在PC市场无出其右,精简指令集(Reduced Intruction Set Computer, RISC)的代表ARM则在移动终端独领风骚。两类指令集都“集”如其名:CISC指令集靠设计出尽可能复杂的指令来完成任务。约有20%的指令会被反复使用,占全程序代码的80%。特点是任务处理能力强,但高性能就会引入高功耗的问题;RISC则只包含处理器常用的指令,不常用的操作则通过执行多条常用指令方式来达到同样的效果。
指令集 | 全称 | 特点 | 行业代表 |
RISC | Reduced Intruction Set Computer | 低功耗,低性能 | ARM |
CISC | Complex Intruction Set Computer | 高功耗,高性能 | Intel |
二、RISC-V的诞生
RISC-V (英文读作"risk-five"),是一种全新的精简指令集架构。它出自美国UC Berkeley大学的一个研究团队。因为传统商业闭源指令集授权费高昂且专有利保护,像伯克利大学这样的研究团队竟选不出合适的指令集架构来使用。最终他们决定从零开始,设计一套全新的、开源的指令集。于是RISC-V指令集的雏形诞生了。
秉承“大道至简”的设计哲学,RISC-V基础指令集只有40多条,加上其他的模块化扩展指令总共几十条指令。RISC-V的规范文档仅有145页,而“特权架构文档”的篇幅也仅为91页。相比传言要上千万美元的ARM指令集架构的许可证,RISC-V以其完全开源、短小精悍的模块化设计、没有过时定义的束缚、没有严重的指令冗余、没有专利问题等优势开始吸引全球初创公司和研究机构的目光。大批公司开始加入对RISC-V的研究和二次开发之中。现在,围绕RISC-V的生态环境逐渐完善,并涌现了众多开源处理器及SoC采用RISC-V架构。
RISC-V的出现打破了沉闷的行业气氛,举起了反垄断的大旗,可以看成是一个年轻的搅局者。在科技巨头以及一大批创企对RISC-V的发展和生态建设的推动之下,或许RISC-V能够成为AI和IoT时代足以与ARM匹敌的新一代主流架构。
三、RISC-V的优势
简单来说,RISC-V的优势可以被概括成一句话:短小精悍、扩展性强。处理器经过这么多年的发展,路上的坑已经被研究的清清楚楚,但ARM和x86的指令集一路走来,需要兼容的东西太多了,导致指令集越来越复杂。RISC-V则站在巨人的肩膀上汲取了这些经验,指令集的每个功能都被划分的清清楚楚,所以够精简。
RISC-V是一个典型三操作数、加载-存储形式的RISC架构。指令集使用模块化的方式进行组织,其不同部分能以模块化的方式串在一起。因此通过组合不同的模块,可以对于不同处理需求的加速芯片实现兼容。它包括三个基本指令集和6个扩展指令集,每一个模块使用一个英文字母来表示,如下表所示。
名称 | 指令数 | 说明 | |
基本指令集 | RV32I | 47 | 整数指令,包含:算数、分支、访存。32位寻址空间,32个32位寄存器 |
RV32E | 47 | 指令与RV32I一样,只是寄存器数量变为16个,用于嵌入式环境。 | |
RV64I | 59 | 整数指令,64位寻址空间,32个64位寄存器。 | |
RV128I | 71 | 整数指令,128位寻址空间,32个128位寄存器。 | |
扩展指令集 | M | 8 | 包含4条乘法、2条除法、2条余数操作指令 |
A | 11 | 包含原子操作指令。比如读-修改-写,比较-交换等 | |
F | 26 | 包含单精度浮点指令 | |
D | 26 | 包含双精度浮点指令 | |
Q | 26 | 包含四倍精度浮点指令 | |
C | 46 | 压缩指令集,其中的指令长度为16位,主要目的是减少代码的大小 |
RISC-V分为32bit、64bit、128bit三个版本,常用的模块有以下几个:
I: 与整数相关的指令集。RISC-V唯一必须支持的模块,仅仅有40多条指令。只要是RISC-V处理器,必定支持这个模块。包含了整数的加减以及相关的逻辑跳转等指令。
M: 整数的乘法与除法指令。
A:包含了存储相关的原子操作等。比如想实现从存储里取数据,然后计算完写回存储可以用此类指令。
F:单精度浮点指令。
D:双精度浮点指令,如果要支持D,那F显然要支持。
C:压缩指令,16bit的短指令。用来减少指令体积。
一般来讲,RV32IMAFD比较常见,所以缩写一下就叫RV32G,G为通用(General Purpose)的意思。除了上述指令,RISC-V还预留了很大的空间用于自定义指令。如果官方提供的指令没有满足需求,用户完全可以自定义一条指令放进去,然后再修改一下编译链就可以了,扩展潜力巨大。目前所有指令集均在RISC-V官方网站公开,感兴趣的读者可以自行查看。
四、RISC-V自学准备
首先准备学习环境:
·一台安装了Linux系统的虚拟机或电脑
·IC设计必备软件,包括编译,仿真以及波形查看工具
·riscv-gcc工具包,用于生成risc-v版的可执行文件
在这里为大家推荐友晶科技《RISC-Von T-Core》公开课,代码和文档都在Github上开源,对初学者会很有帮助,在文末提供链接。同时搭配胡振波的《手把手教你设计CPU—RISC-V篇》。这本书主要是介绍国产开源RISC-V架构CPU—蜂鸟E200,通用CPU的设计流程和基于Verilog的代码具体实现,可以说是理论和实践相结合的一本好书。无论是对于嵌入式开发,还是IC设计验证,都有很大的参考价值。
五、《RISC-V on T-Core》自学笔记
友晶科技《RISC-V on T-Core》公开课的前七讲都是理论教学,从第八讲到第十二讲开始进行实战演练。下面我将对前七讲的重点知识进行总结,后五讲则侧重实践过程中遇到的问题。
第一讲:
第一讲介绍了主要介绍了RISC-V指令集架构的特点,主要分为以下几点:
(1)模块化的指令集
在前面第三章中,已经简要介绍过RISC-V架构。相比其他成熟的商业架构,它最大的特点就是模块化架构。不仅短小精悍,而且其不同的部分还能以模块化的方式组织在一起,从而试图通过一套统一的架构满足各种不同的应用。这种模块化是 x86与ARM架构所不具备的。它能够使得用户能够灵活选择不同的模块组合,以满足不同的应用场景,可以说是“老少咸宜”。譬如针对于小面积低功耗嵌入式场景,用户可以选择RV32IC 组合的指令集,仅使用 Machine Mode(机器模式);而高性能应用操作系统场景则可以选择譬如 RV32IMFDC的指令集,使用Machine Mode(机器模式)与UserMode(用户模式)两种模式。而他们共同的部分则可以相互兼容。
(2)统一的指令编码得益于后发优势和总结了多年来处理器发展的经验, RISC-V的指令集编码非常规整,指令所需的通用寄存器的索引(Index)都被放在固定的位置,如下图所示。因此指令译码器(Instruction Decoder)可以非常便捷地译码出寄存器索引,然后读取通用寄存器组(Register File, Regfile)。
(3)可配置的通用寄存器组
RISC-V架构支持32位或者64位的架构, 32位架构由RV32表示,其每个通用寄存器的宽度为32比特; 64位架构由RV64表示,其每个通用寄存器的宽度为64比特。
RISC-V架构的整数通用寄存器组,包含32个(I架构)或者16个(E架构)通用整数寄存器,其中整数寄存器0被预留为常数0,其他的31个(I架构)或者15个(E架构)为普通的通用整数寄存器。
如果使用浮点模块(F或者D),则需要另外一个独立的浮点寄存器组,包含32个通用浮点寄存器。如果仅使用F模块的浮点指令子集,则每个通用浮点寄存器的宽度为32比特;如果使用了D模块的浮点指令子集,则每个通用浮点寄存器的宽度为64比特。
(4)简洁的存储器访问指令
RISC-V架构的存储器访问指令具有如下显著特点:
·为了提高存储器读写的性能, RISC-V架构推荐使用地址对齐的存储器读写操作,但是也支持地址非对齐的存储器操作RISC-V架构。处理器既可以选择用硬件来支持,也可以选择用软件来支持。
·由于现在的主流应用是小端格式(Litte-Eendian ), RISC-V架构仅支持小端格式。有关小端格式和大端格式的定义和区别,在此不做过多介绍。若对此不太了解的初学者可以自行查阅学习。
·很多的RISC处理器都支持地址自增或者自减模式,这种自增或者自减的模式虽然能够提高处理器访问连续存储器地址区间的性能,但是也增加了设计处理器的难度。RISC-V架构的存储器读和存储器写指令不支持地址自增自减的模式。
·RISC-V架构采用松散存储器模型(Relaxed Memory Model),松散存储器模型对于访问不同地址的存储器读写指令的执行顺序不作要求,除非使用明确的存储器屏障Fence)指令加以屏蔽。有关存储器模型(MemoryModel)和存储器屏障指令的更多信息,请参见附录A13这些选择都清楚地反映了RISC-y架构力图简化基本指令集,从而简化硬件设计的哲学。
(5)中断处理机制
RISC V 中断被分为两类中断。局部中断和全局中断。
·局部中断,算是内部中断,标准是只规定了有两种,即使中断timer和软件中断software。局部中断连接在Core Local Interruptor(CLINT) 上。
·全局中断,也就是所说的外部中断,其他外设统统都是外部中断。外部中断连接在Platform-Level Interrupt Controller (PLIC)上。
中断流程只要是CPU都大同小异的。对于RISC-V来讲,中断流程是这样的:
·外设发出中断信号。
·PLIC或者CLINT响应中断,RISC-V核心保存此时的CSR。
·跳转到中断处理程序(直接换PC值取指令即可)。
·关闭其他中断响应使能(RISC-V不支持嵌套,所以一个中断要屏蔽其他中断)。
·软件保存通用的寄存器。
·然后处理中断(过程中会清掉外设的中断)。
·软件恢复通用的寄存器。
·恢复CSR。
·跳转PC跳回原来位置退出异常。
一个完整流程就结束了。其他细节直接阅读RISC-V core的说明书即可。
第二讲:ALU
ALU是处理器中具体完成每条指令运算的功能模块。经过分析归纳,ALU需要支持的运算包括对操作数加1,对操作数减1,两个操作数加和减,两个数的与或及异或等按位逻辑运算,操作数按位取反,左移右移。ALU 的实现需要包括上面的所有功能。考虑到面积优化,加减1和减法可以全部由加反来实现。
蜂鸟E200 的ALU 单元,包括5 个功能子单元,各自的功能分为以下5 种情况:
·普通ALU 运算:主要负责普通的ALU 指令(逻辑运算,加减法,移位等指令)的执行。
·访存地址生成:主要负责Load、Store和“A”扩展指令的地址生成。
·分支预测解析:主要负责Branch和Jump 指令的结果解析和执行。
·CSR读写控制:主要负责CSR指令的执行。
·多周期乘除法器:主要负责乘法和除法指令的执行。
以上5个功能子单元只负责具体指令执行的控制,它们均共享一份实际的运算数据通路,因此主要数据通路的面积开销只有一份,这也是蜂鸟E200追求低功耗小面积实现的亮点。
第三讲:
本讲主要介绍了单指令周期 CPU 的设计与实现。根据指令周期的五个执行步骤:取指、译码、执行、访存、写回,分别设计 RISC-V六种类型指令的数据通路和控制器。
在MIPS五级流水线中一条指令的生命周期分为如下步骤:
(1). 取指
•指令取指(instruction Fetch)是指将指令从存储器中读取出来的过程。
(2). 译码
•指令译码(Instruction Decode)是指将从存储器中取出的指令进行翻译的过程。经过译码之后得到指令需要的操作数寄存器索引,可以使用此索引从通用寄存器组(RegisterFile)中将操作数读出。
(3). 执行
指令译码之后所需要进行的计算类型都己得知,并且己经从通用寄存器组中读取出了所需的操作数,那么接下来便进行指令执行。指令执行是指对指令进行真正运算的过程。譬如,如果指令是一条加法运算指令,则对操作数进行加法操作;如果是减法运算指令,则进行减法操作。
•在“执行”阶段的最常见部件为算术逻辑部件运算器(ArithmeticLogical Unit,ALU),作为实施具体运算的硬件功能单元。
(4). 访存
•存储器访问指令往往是指令集中最重要的指令类型之一,访存是指存储器访问指令将数据从存储器中读出,或者写入存储器的过程。
(5). 写回
•写回(Write-Back)是指将指令执行的结果写回通用寄存器组的过程。如果是普通运算指令,该结果值来自于“执行”阶段计算的结果:如果是存储器读指令,该结果来自于“访存”阶段从存储器中读取出来的数据。
第四讲:
本讲主要介绍了处理器流水线的概念与结构,并对流水线与单指令周期进行了比较分析。此外,讲师还与大家一起探讨了流水线设计带来的冒险,诸如结构冒险、数据冒险、控制冒险等,并带着大家找到其解决方法。最后,讲师对RISC-V onT-Core流水线实现的代码进行了解析。
(1). 数据冒险:
l WAR(Write-After-Read)相关性,又称先读后写相关性:表示“后序执行的指令需要写回的结果寄存器索引”与“前序执行的指令需要读取的源操作数寄存器索引”相同造成的数据相关性。因此从理论上来讲,在流水线中“后序指令”一定不能比和它有WAR相关性的“前序指令”先执行,否则“后序指令”先写回了结果至通用寄存器组中,“前序指令”再读取操作数时,就会读到错误的数值。
l WAW(Write-After-Write)相关性,又称先写后写相关性:表示“后序执行的指令需要写回的结果寄存器索引”与“前序执行的指令需要写回的结果寄存器索引”相同造成的数据相关性。因此从理论上来讲,在流水线中“后序指令”一定不能比和它有WAW相关性的“前序指令”先执行,否则“后序指令”先写回了结果至通用寄存器组中,“前序指令”再写回结果至通用寄存器组中就会将其覆盖。
l RAW(Read-After-Write)相关性,又称先写后读相关性:表示“后序执行的指令需要读取的源操作数寄存器索引”与“前序执行的指令需要写回的结果寄存器索引”相同造成的数据相关性。因此从理论上来讲,在流水线中“后序指令”一定不能比和它有RAW相关性的“前序指令”先执行,否则“后序指令”便会从通用寄存器组中读回错误的源操作数。
(2). 结构冒险:
l 产生原因:
每条指令在解码阶段需从寄存器文件读取两个操作数;在写回阶段写入一个值到寄存器文件。
l 解决方法:
设计更多的独立端口,比如设计两个读端口,一个写端口来满足上面的需求。每个周期可同时进行3个端口的访问。
(3). 控制冒险:
在取指阶段的分支预测功能中,对于带条件分支指令,由于其条件的解析需要进行操作数运算(譬如大小比较操作),流水线在取指阶段无法得知该指令的条件跳转结果是跳还是不跳,只能进行预测。因此在执行阶段,通常需要使用ALU对该指令进行条件判断运算(譬如大小比较操作)。ALU进行条件判断运算的结果将用于解析该分支指令是否真的需要跳转,并且和之前预测的跳转结果进行比对。如果真实的结果和预测的结果不一致,则意味着之前的预测错误,需要进行流水线冲刷( PipelineFlush),将预测取指(Speculative Fetch)所取的指令都舍弃掉,重新按照真实的跳转方向进行取指。
由于分支预测错误造成的流水线冲刷会造成性能损失。流水线级数越深,流水线冲刷造成的性能损失越大。因此理论上来讲,分支解析如果能够发生在比较靠前端(取指)的流水线级数位置,则相对而言其带来的流水线冲刷的性能损失会相对小一些;反之,如果比较靠后,那造成的性能损失就会相对大一些。如何在功能正确且时序能够满足的情况下,尽量在比较靠前端的流水线位置进行分支解析,是处理器微架构设计经常需要考虑的问题。
第五讲:
本讲主要介绍了存储器的层次化结构。首先,从局部性原理出发,构建存储器的层次化结构;然后着重分析了Cache 的原理与改进方法、虚拟存储器的管理两大方面,比如 Cache 的三种映射机制、数据的一致性问题、虚拟存储器的两种管理方法以及 TLB的 Verilog 实现等;最后对 RISC-V onT-Core 的存储器系统的代码进行了讲解。
蜂鸟E200 处理器核的存储器子系统结构如下图中圆圈区域所示,主要包含如下4 个组件。
• AGU 主要为读和写指令,以及“ A "扩展指令生成存储器访问地址。
• LSU 主要作为存储器访问的控制模块。
• ITCM 主要作为存储器子系统的指令存储部件,但是也能够用于存储数据而被读和写指令访问。
• DTCM 作为存储器子系统的数据存储部件。
第六讲:
本讲主要介绍了CPU中断与异常的概念,包括中断与异常的屏蔽、等待、套嵌和优先级等。此外,还详细介绍了中断与异常相关的寄存器,以及单指令周期数据通路中断与异常的控制机制和流水线中断与异常的控制机制。同样,讲师在最后还对RISC-V onT-Core流水线的中断与异常控制代码进行了解析。
RISC-V 架构定义的中断类型分为4 种:
•外部中断
•计时器中断
•软件中断
•调试中断
蜂鸟E200 的中断和异常处理均在交付模块中进行处理。具体过程如下:
1.异常的处理交付
模块对于异常处理部分的要点如下:
• 交付模块接受来自ALU的交付请求,对于每一条ALU执行的指令,可能发生异常如果没有发生异常,则该指令顺利交付;如果发生了异常,则会造成流水线冲刷(PipelineFlush), ALU指令造成的异常均为同步异常,同步异常均来自于ALU模块。同步异常能够准确地定位于当前正在执行的ALU指令,因此mepc寄存器中更新的PC值即为当前正在交付指令(来自于ALU接口)的PC。
• 交付模块接受来自长指令写回时的交付请求,每一条长指令可能发生异常。如果没有发生异常,则该指令顺利交付;如果发生了异常,则会造成流水线冲刷(PipelineFlush),长指令写回的异常均为非精确异步异常,非精确异步异常均来自长指令写回时的请求。
长指令异常的“异常返回地址”将会使用此指令自己的PC,即mepc寄存器中更新的PC值为此指令的PC。但是由于这长指令可能已经被交付了,若干个周期过去了,且在这若干周期内可能又有新的后续指令已经写回了通用寄存器组,因此其响应异常后的处理器状态是一种非精确状态(无法定义为某一条指令的边界),属于非精确异步异常。
2.中断的处理
交付模块对于中断处理部分的要点如下:
• 交付模块接受来自CLINT和PLIC的3根中断信号的请求,蜂鸟E200的实现中将中断作为一种精确异步异常,这种异常的“异常返回地址”将会为下一条尚未交付的指令,即mepc寄存器中更新的PC值即为下一条待交付的指令(来自于ALU接口)的PC。
• 当异步异常和ALU造成的同步异常以及中断同时发生时,优先级依次为:长指令造成的异步异常优先级最高,中断造成的异步异常其次, ALU造成的同步异常最后。
• 异常一旦发生,便会冲刷流水线,将后续的指令取消掉,并向IFU模块发送冲刷请求(FlushRequest)和重新取指令的PC (称之为Flush PC),用以重新从新的PC地址开始取指令。
• 特殊的调试中断也在交付模块中处理,本章不做介绍,请参见第14章了解调试相关的信息。
第七讲:
本讲主要介绍了RISC-V 处理器的可扩展性设计与实现。首先,从指令集出发,分析了RISC-V 处理器可扩展性的体现;然后,着重介绍了 RISC-V 自定义指令的流程,并详细分析了自定义指令时对 RISC-V 处理器硬件的修改;最后,介绍了如何在 RISC-V on T-Core 上实现一个自定义的 DOT 指令的代码。这一部分在第八讲中会有更加详细的解释,因此不再赘述。
第八讲:环境配置与流水灯实验
第八讲主要介绍了学习环境的配置,同时演示了一个操作GPIO的简单的例程。
关于环境的配置,下面的链接中有详细的介绍。
http://www.myfpga.org/discuz/forum.php?mod=viewthread&tid=197198&page=1&extra=#pid271232
我采用了友晶提供的系统镜像文件,用U盘做一个系统引导盘,然后bios设置U盘启动就可以直接使用了,镜像中自带了JDK包、eclipse和quartus软件,不需要额外的配置。没有两个电脑的同学可以选择安装虚拟机,但友晶提供的系统镜像是.img格式,没有引导,vmware不能直接使用。在此提供一个格式转换工具StarWind V2VImage Converter,亲测可用。具体操作可以看这个博客:
https://oldtang.com/3424.html
另外一个问题是Ubuntu默认的是国外的软件更新源,在国内使用下载包的速度特别慢。所以需要手动换成国内的源,教程如下:
https://blog.csdn.net/u012308586/article/details/102953882
这个教程需要一点gedit的基础,没有基础的同学可以自行查阅相关内容。
还有一个需要注意的问题是,U盘做系统引导盘,掉电以后东西就会丢失,所以要记得及时备份程序,建议把eclipse的workspace放在硬盘里,这样就不需要反复保存程序了。
配置好环境后,按照视频中的引导设置eclipse需要的头文件和工具链等基本设置,应该可以正常的点亮流水灯。视频中的方法是从QSPI Flash 启动的应用程序,T-Core掉电重启后还可以看到流水灯。那如果下载时只想看一下现象,不烧录到QSPI Flash,可以在通过切换 GNU RISC-V Cross Linker 的link文件,将link_flash.lds文件切换至link_itcm.lds文件即可。
link脚本中的内容是包括 init,text,data等等程序片段及设定对应的存储器。所以link_flash/link_itcm两个link脚本可以切换固化 Flash和只在 ITCM 中运行两种方式。link_itcm 的原理是通过 OpenOCD 将程序加载进 ITCM 中,并跳转到 ITCM 执行,由于 ITCM 是 sram 架构,所以掉电后程序就丢失了。从而实现了只看一下现象而不固化程序的目的。这个对有FPGA基础的同学应该比较容易理解。
第九讲:GPIO读写、中断和UART通讯
这三个实验都比较简单,过程中没有遇到什么大的问题。在调试串口的时候遇到一个小问题,给板子连接好串口线和电源线,下载好程序后,准备在上位机中打开串口观察现象,发现终端报错:“Unable to open serial port /dev/ttyUSB0”,我的解决过程如下:
首先我猜测可能是当前用户没有权限,所以就按照搜索出来的办法通过增加udev规则来实现。步骤如下:
·创建文件/etc/udev/rules.d/70-ttyusb.rules
·在文件内增加一行
·KERNEL==”ttyUSB[0-9]*”, MODE=”0666”
重新插入USB转串口设备,发现仍然打不开串口。最后我发现电源线和串口线都是mini usb接口(串口线是我自己找的一根线),就交换了一下这两条线的位置。突然发现串口可以打开了。猜测可能是原来当串口线的兼容性不好,或者是串口驱动的问题,把它当做电源线就降低了要求。本来当做电源的那根线应该兼容性比较好,所以颠倒一下位置就可以打开串口了。所以遇到这个问题的同学,可以先检查一下是不是硬件导致的。
然后再简单说一下中断,通过友晶给的例程,可以看到中断产生的时候直接跳到handle_trap函数,传入的参数有mcause mepc;然后软件根据mcasue判断,如果是外部中断就跳转到handle_m_ext_interrupt函数,如果是定时器中断,跳转到handle_m_time_interrupt。也就是说,中断的位置只有一个,不是一个固定的中断向量表,而是完全由软件实现的。这个跟以前学过的C51不一样,希望大家注意一下这部分内容。
第十讲:timer中断、PWM呼吸灯、I2C通讯、SPI通讯
这一讲的内容也比较简单,没有遇到什么问题。这些程序也几乎是学习每块板子都要跑的例程,有些基础的同学应该都能看懂源码。友晶每个例程都给两种实现方法:一种是使用eclipse IDE,另一种是用Makefile直接编译。在这里我想推荐大家先用eclipse中调试,因为用make指令编译对没有基础的人可能用起来很困难,报错也看不懂问题出在哪里,有IDE能大大加快调试速度。但查阅得知makefile在熟练之后效率比IDE高,所以有时间的同学还是推荐学习一下的。
第十一讲:自定义指令集
这节实验是花费我最长时间的一个了,内容也比较重要,因为自定义指令集是RISC-V最大的特点。接下来我将对自定义指令的软件部分的流程展开详细的介绍。
·首先,需要定义指令的格式,根据硬件中确定的指令的类型和操作码给出想要定义的指令的格式;
·然后要修改GNU工具链,在没有修改GNU工具链之前,它是不认识这条自定义指令的,通过添加自定义指令相关的内容来修改GNU工具链,使工具链认识新定义的指令;
·最后编译C语言程序,并在T-Core上运行查看结果。
在本讲中,友晶以矩阵乘法加速为例,定义了一个dot指令,使原来多个周期的乘法运算,可以在一个周期内完成,这样就大大提高了矩阵乘法的计算速度。在硬件实现dot指令时,实际的操作数有8个(al-dl, a2-d2) ,因此在硬件设计的时候,使用a0-a7这8个寄存器。这样就完成了第一步。
现在硬件上已经可以实现dot指令了。然后就需要需要编写含有dot指令的应用程序,这个应用程序在使用编译器编译后会生成二进制文件,烧录到T-Core上后,硬件就会计算出结果,最后把结果返回。所以需要修改GNU-Toolchain。首先安装编译risc-opcodes的编译环境python。但万万没想到,我在这一步折腾了一晚上。下面讲一下我的曲折经历:
首先我用了apt-get安装pip,如下所示:
sudo apt-get installpython3-pip
但出现了如下错误(引用自别人的博客,当时的错误信息没有保存),大概意思是缺少一个叫setuptools的依赖。
按照提示,安装setuptools:
sudoapt-get install python3 -setuptools
随后又像上面一样,提示缺少几个依赖,我都按照提示慢慢安装了。之后再用apt-get装pip,还是报错了,提示找不到相关的版本。在谷歌上试了很多的解决办法,最后按照这个博客,成功安装好了:
https://blog.csdn.net/qq_19332527/article/details/78369407
简单来说就是使用以下三条命令:
sudo apt-get clean
sudo apt-get update //这一步会安装很长的时间
sudoapt-get install --reinstall python-minimal python-lockfile
博主给的解释我们在将python2.7升级到python3.3时,只是将/usr/local/bin目录下修改了(使用ln -s 或者其他),然而配置目录并没有修改,所以导致了配置不成功。
然后按照友晶指导文件的操作,继续进行。下载riscv-gnu-toolchain和其子模块时,ubuntu中clone github代码特别慢,几乎只有几kb。我找到的解决办法是注册一个码云账号,将github的库clone到码云中,然后再git clone码云中的仓库。具体教程如下:
https://www.zhihu.com/question/404613805
经过上述操作之后,主模块很快就能clone下来。但子模块还是使用了外网,所以速度还是特别慢。当时我就真的挂了一个晚上让它慢慢下完了,有兴趣的同学可以学习一下怎么在ubuntu上搭梯子(推荐clash,自己在用的windows版本体感良好,用教育邮箱注册的话,可以免费试用一年),感觉应该可以加快下载速度。
指令按照教程修改完毕后。在用make编译工具链时,发现make没有报错,但它只编译了dot_try文件夹下的一部分源文件。因为dot_try下面有两个文件夹总是带锁所以没有编译。即使我用chmod777改权限了,一使用make文件又锁上了,猜测肯定是因为用户权限不够的问题,经过反复的尝试,最终的解决办法是把工作空间换到硬盘里(一开始都是在U盘里建的),重复上述所有操作。我自己感觉用chown命令修改文件权限,效果应该是一样的。但最后还是没思考明白这个问题。希望有同学能为我解答一下这个问题。
第十二讲:FreeRTOS
FreeRTOS提供了5种内存管理方式,每种方式根据其特性各有其应用场景,本讲我们用的是heap_4.c。传统的中断程序是一个大while循环,将所有事情看作一个任务,顺序执行代码,遇到中断发生则响应中断(可能发生中断嵌套),响应完中断后会继续之前被中断的任务,如下图所示:
而在RTOS中,将所有事情分成各个模块,每一个模块的内容看作一个任务,任务的执行顺序是灵活的,根据相应的调度算法管理任务的运行,灵活性比上一种方法强,过程如下图所示:
FreeRTOS的任务和中断的优先级关系是移植FreeRTOS的难点,需要大家多加理解,主要有以下三个要点:
·任务总是可以被中断打断,任务之间具有的优先级,但是与“中断的优先级”没有关系,这两种优先级是相互独立的。
·不调用任何FreeRTOSAPI函数的中断,可以设置为任意的“中断优先级”,并且允许嵌套。
·在FreeRTOSConfig.h中预先定义configMAX_SYSCALL_INTERRUPT
_PRIORITY的值,调用API函数的中断优先级只能设置为不大于该值,支持嵌套,但是会被内核延迟。
当然还有很多详细的知识点这里没有介绍,具体可以翻看友晶的官方视频。接下来,就开始尝试将小操作系统移植到T-core上去。
首先在github上下载FreeRTOS的源代码。还是遇到了跟上一讲相同的问题,下载速度太慢了。首先还是用码云曲线救国:
gitclone 码云的仓库地址
很快就下好了,然后进入下载的文件在terminal中选择分支:
gitcheckout v10.3.1
但无法成功切换分支,然后用检查仓库所有分支:
gitbranch
发现当前文件夹下只有一个master分支。思考了一下,之前git克隆代码都是master分支的,但这次需要的代码不是master分支的,而且github上的git clone链接只是仓库的地址,没有关于分支的。我的处理方法如下:
选中master,在下拉列表中修改分支:
在右上角复制当前仓库地址,克隆到码云里,然后使用:
gitclone -b v10.3.1 +gitee url
解释一下这个命令:-b表示要从分支下载,v10.3.1就是具体的某个分支的名称。经过上述操作,就下载到指定分支的代码了。
FreeRTOS源代码和蜂鸟E203移植相关的代码结构如下所示。
如上所示,FreeRTOS的代码层次结构分明,只需要修改三个文件名为“port*”的源代码,完成基本的中断和异常的底层移植。然后就是配置工程,新建FreeRTOSConfig.h,主要就是配置时钟频率,栈空间大小等信息,这个自行体会。剩下的过程大家应该不会遇到太大的问题,我的两个小任务也转起来了。
五、总结
最后,虽然列举了RISC-V这么多优点,但ARM和x86毕竟经过这么多年的积累,里面汇聚了一代又一代人的劳动成果,也不可能没有精髓。RISC-V作为行业的新秀,缺乏很多必要的适配,生态也不够完善,目前还不够成熟,离商用还有很长一段路要走。到底是不是风口,还要持股观望。但它的价值是值得肯定的,也许我国在这个领域里会出现下一个Intel或者ARM般强大的处理器,实现强国梦。在文末提供了一些RISC-V相关的教程和资料,感兴趣的同学可以自行查阅。
版权归原作者所有,侵删。
参考文献及相关链接:
友晶科技《RISC-V on T-Core》公开课
https://space.bilibili.com/437451814?spm_id_from=333.788.b_765f7570696e666f.2
RISC-V基础资料
https://github.com/riscv
https://cnrv.io/resource
RISC-V蜂鸟E200系列源码
https://github.com/riscv-mcu/e203_hbirdv2
RISC-V中文社区
https://www.risc-v1.com/
友晶科技官网
http://www.terasic.com.cn
友晶科技MyFPGA论坛
http://www.myfpga.org/discuz/forum.php
看完本文有收获?请转发分享给更多人
NOW现在行动!
推荐阅读
【Vivado那些事】Vivado中常用的快捷键(一)F4键
SystemVerilog数字系统设计_夏宇闻 PDF
Verilog 里面,always,assign和always@(*)区别
点击上方字体即可跳转阅读哟