恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
龙芯杯CPU设计全流程:从LoongArch指令集到FPGA上板调试实战指南
首页
资讯中心
/
龙芯杯CPU设计全流程:从LoongArch指令集到FPGA上板调试实战指南
龙芯杯CPU设计全流程:从LoongArch指令集到FPGA上板调试实战指南
发布时间:2026/10/6 6:02:25
1. 从零理解龙芯杯这项赛事到底在比什么第一次听到“龙芯杯”这三个字很多人会以为这是一个单纯写Verilog的编程比赛。实际上它考察的是从指令集架构理解、处理器微架构设计、RTL编码、功能仿真、综合实现到FPGA上板调试的完整链路能力。说得直白一点你需要用硬件描述语言从零搭建一颗能跑程序的CPU核最终在FPGA开发板上让它真正运行起来。我参与过两届龙芯杯的指导工作也带过几支从零基础起步的队伍。最大的感受是这个比赛的门槛不在Verilog语法本身而在于你需要同时具备计算机体系结构的系统认知和数字逻辑设计的工程能力。很多同学Verilog写得不错但一上来就卡在“我该从哪里开始”这个问题上。所以这篇内容我会按照一条完整的路径来展开——从LoongArch指令集的理解到微架构设计再到FPGA实现和上板调试把每个阶段的核心要点和容易踩的坑都讲清楚。这篇文章适合三类人阅读第一类是想参加龙芯杯但还没有头绪的同学第二类是想通过一个完整项目来学习CPU设计的自学者第三类是已经有一定基础但卡在某个环节的参赛者。我会尽量用通俗的方式解释复杂概念同时保证技术细节的准确性让你看完之后能直接动手开干。2. LoongArch指令集你必须吃透的第一关2.1 为什么指令集理解是一切的前提CPU设计的本质就是“实现一套指令集”。你设计的硬件需要能够正确取指、译码、执行、访存、写回而每一步的行为都由指令集规范来定义。如果你对LoongArch指令集的理解有偏差后面所有的设计都会建立在错误的基础上。LoongArch是龙芯中科推出的自主指令集架构采用RISC风格定长32位指令编码支持64位和32位两种运行模式。和MIPS相比LoongArch在指令编码上做了大量精简和优化取消了分支延迟槽增加了更多的立即数运算指令和访存指令变体。对于参赛者来说你需要重点关注以下几个部分。指令编码格式。LoongArch的指令分为2R、3R、4R、2RI8、2RI12、2RI14、2RI16、1RI21、I26等多种格式。每种格式的位域划分不同译码逻辑需要根据opcode字段来区分。我建议你在动手写RTL之前先把所有需要实现的指令列一张表标注每条指令的格式、操作码、功能描述和异常行为。寄存器堆。LoongArch有32个通用寄存器r0-r31其中r0恒为零。还有独立的浮点寄存器堆和向量寄存器堆如果你要实现浮点或向量扩展的话。对于基础版本先实现通用寄存器堆即可。特权指令与例外。龙芯杯的评测环境通常会运行一个简化的操作系统或测试程序你需要实现基本的例外处理机制包括系统调用例外、断点例外、地址非对齐例外等。这部分容易被忽视但往往是决定能否跑通评测程序的关键。2.2 指令集手册的正确打开方式很多同学拿到指令集手册之后从第一页开始逐字阅读读了几天还在前几章。我的建议是带着问题去查手册而不是从头读到尾。具体做法是这样的先确定你要实现的是哪一档次的CPU比如是否支持浮点、是否支持MMU然后列出这个档次需要实现的所有指令。接着针对每条指令去手册里查它的编码格式、操作语义和例外条件。这样你的阅读是有目标的效率会高很多。另外龙芯杯官方通常会提供一套指令集模拟器或者参考模型。这个东西非常宝贵你可以用它来验证你对指令行为的理解是否正确。比如你不确定某条指令在溢出时应该怎么处理写一个小测试程序在模拟器上跑一下看结果是什么比翻手册快得多。注意LoongArch的指令集手册有多个版本务必确认你使用的是与比赛要求一致的版本。不同版本之间可能存在指令编码或行为的细微差异。2.3 从指令集到微架构的映射思路理解了指令集之后下一步是把它映射到微架构上。这里有一个核心决策你打算做单周期、多周期还是流水线单周期CPU每条指令在一个时钟周期内完成设计简单但频率极低基本不可能通过性能评测。多周期CPU把一条指令拆成多个周期执行频率有所提升但吞吐率仍然有限。流水线CPU是目前主流的选择通常采用经典的五级流水线取指IF、译码ID、执行EX、访存MEM、写回WB。对于龙芯杯来说我建议至少做五级流水线如果团队实力允许可以考虑加入动态分支预测、指令缓存、数据缓存等进阶特性。但切记先跑通再优化。我见过太多队伍一上来就想做乱序执行结果连最基本的流水线都没调通最后连初赛都没过。3. 微架构设计从纸面到RTL的关键决策3.1 五级流水线的数据通路设计五级流水线的核心是数据通路的设计。你需要确定每一级做什么级与级之间需要传递哪些信号以及如何处理数据冒险和控制冒险。取指级负责从指令存储器中读取指令同时更新PC。这里需要考虑PC的更新逻辑顺序执行时PC4遇到分支或跳转时需要根据译码结果或执行结果来更新。如果加入了分支预测取指级还需要根据预测结果来决定下一条指令的地址。译码级负责解析指令读取寄存器堆生成控制信号。这一级需要把指令的opcode、rs、rd、rt等字段提取出来并根据指令类型生成ALU操作码、访存使能、写回选择等控制信号。执行级是ALU真正做运算的地方。对于不同的指令类型ALU需要执行不同的操作算术运算、逻辑运算、移位运算、比较运算等。如果实现了乘除法还需要在这里或者单独的乘除法单元中完成。访存级负责处理load和store指令。你需要在这里实现数据存储器的读写接口同时处理地址对齐、字节/半字/字/双字的访问。写回级把执行结果或访存结果写回寄存器堆。这里需要注意写回端口和译码级读端口之间的冲突问题。3.2 数据冒险的三种解决方案流水线中最常见的问题就是数据冒险。比如一条指令需要用到前一条指令的计算结果但前一条指令还没有写回这时候就产生了RAWRead After Write冒险。第一种方案是插入气泡。检测到冒险时暂停流水线若干周期直到数据准备好。这种方法实现简单但性能损失大。第二种方案是数据前推。把执行级或访存级的结果直接前推到需要的地方而不必等到写回级。这是最常用的方案能覆盖大部分RAW冒险场景。第三种方案是寄存器堆写优先读。在同一个时钟周期内如果写端口和读端口访问同一个寄存器让读端口读到新写入的值。这可以解决一部分写回和译码之间的冒险。实际设计中这三种方案通常会组合使用。我的经验是先把数据前推做扎实覆盖EX到EX、MEM到EX、WB到EX这几条路径然后再用插入气泡处理前推覆盖不到的少数情况。3.3 控制冒险与分支预测的取舍控制冒险来自分支和跳转指令。在流水线中分支指令的结果要到执行级才能确定但取指级已经在取后面的指令了。如果分支跳转之前取的指令就要作废。最简单的处理方式是暂停流水线等分支结果出来再取指。但这样每条分支指令都要浪费几个周期性能损失明显。好一点的方案是静态预测。比如总是预测分支不跳转或者根据分支方向向前跳转预测不跳、向后跳转预测跳来预测。静态预测实现简单准确率大概在70%左右。更好的方案是动态预测。使用两位饱和计数器或者更复杂的预测器根据历史信息来预测分支方向。动态预测的准确率可以到90%以上但硬件复杂度也更高。对于龙芯杯参赛者我的建议是初赛阶段用静态预测就够了先把功能跑通。复赛阶段如果时间允许再考虑加入动态预测。不要为了追求性能而牺牲功能的正确性。3.4 例外与中断处理机制例外处理是很多队伍容易忽略的部分。在龙芯杯的评测中如果例外处理不正确可能会导致程序跑飞或者结果错误。LoongArch的例外处理涉及几个关键寄存器CSR.ERA保存例外返回地址CSR.CRMD保存当前模式信息CSR.ESTAT保存例外状态CSR.EENTRY保存例外入口地址。当例外发生时硬件需要自动完成以下操作保存当前PC到ERA更新CRMD中的模式位跳转到EENTRY指定的入口地址。例外处理的难点在于精确例外。也就是说当例外发生时例外指令之前的所有指令都已经完成例外指令之后的所有指令都没有执行。在流水线中实现精确例外需要在例外发生时冲刷流水线并确保已经进入流水线的指令不会修改 architectural state。4. FPGA实现从RTL到上板的完整流程4.1 开发环境搭建与工具链选择龙芯杯通常要求使用Vivado作为FPGA开发工具。Vivado的安装本身就是一个不小的工程尤其是对于第一次接触的人来说。首先你需要确认你的FPGA开发板型号然后下载对应版本的Vivado。龙芯杯官方通常会指定Vivado版本比如2020.2或2022.2。不同版本之间在IP核生成、综合策略、时序报告等方面可能有差异建议严格按照官方要求来。安装Vivado时有几个注意事项。第一安装路径不要有中文和空格否则可能在综合时出现莫名其妙的错误。第二安装过程中会提示你选择要安装的器件库务必勾选你开发板对应的器件系列否则后面创建工程时找不到器件。第三安装完成后需要安装license龙芯杯通常会提供license文件按照说明配置即可。提示Vivado安装需要大量磁盘空间建议预留至少50GB。安装过程可能需要1-2小时建议在晚上或者空闲时间进行。4.2 工程创建与约束文件编写创建Vivado工程时需要选择正确的器件型号、封装和速度等级。这些信息通常印在FPGA芯片表面或者可以在开发板手册中找到。约束文件XDC是FPGA实现中非常关键的一环。你需要在这里定义时钟频率、引脚分配、时序例外等。对于CPU设计来说时钟约束是最重要的。你需要根据你的流水线设计来确定目标频率然后在XDC中创建对应的时钟约束。引脚分配需要参考开发板的原理图。时钟输入引脚、复位引脚、UART引脚、LED引脚等都需要正确分配。如果引脚分配错误上板后可能没有任何反应或者出现奇怪的行为。# 时钟约束示例 create_clock -period 10.000 -name sys_clk [get_ports clk] # 引脚分配示例 set_property PACKAGE_PIN R4 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk]4.3 综合、实现与比特流生成综合是把RTL代码转换成门级网表的过程。Vivado的综合工具会根据你的代码推断出加法器、乘法器、寄存器、存储器等硬件资源。综合报告会告诉你资源使用情况和初步的时序估计。实现包括布局和布线两个步骤。布局是把网表中的逻辑单元映射到FPGA芯片的具体位置布线是连接这些单元之间的信号。实现完成后Vivado会生成时序报告告诉你设计是否满足时序要求。如果时序不满足你需要分析关键路径然后通过优化RTL代码、调整流水线级数、使用流水线寄存器等方式来改善。常见的问题包括组合逻辑路径太长、扇出过大、跨时钟域信号处理不当等。生成比特流之后就可以通过JTAG或者UART下载到FPGA开发板上了。下载方式取决于你的开发板支持哪种配置模式。4.4 上板调试的常用手段上板调试是龙芯杯中最考验人的环节。仿真通过的代码不一定能在板子上跑通因为仿真环境忽略了很多物理因素比如信号延迟、亚稳态、时钟抖动等。最常用的调试手段是ILA集成逻辑分析仪。你可以在Vivado中插入ILA核抓取内部信号的波形。比如你可以抓取PC值、指令内容、寄存器写回数据等通过观察波形来判断CPU是否在正确执行。另一个手段是UART输出。在CPU中实现一个简单的UART发送模块把关键信息打印到串口终端。这种方式虽然速度慢但信息直观适合观察程序的执行流程。还有一种方式是LED指示。把CPU的状态信号连接到LED上比如用LED表示当前处于哪个流水线级、是否发生了例外等。这种方式信息量有限但胜在简单直接。注意上板调试时一定要先确认时钟和复位信号是否正常。我遇到过好几次因为复位信号没有正确释放导致CPU一直处于复位状态的情况。5. 常见问题与排查技巧实录5.1 仿真通过但上板不运行的排查思路这是最经典的问题。仿真环境是理想化的而上板之后信号有延迟、时钟有抖动、复位释放时机不确定。排查这类问题我通常按照以下顺序进行。第一步检查时钟和复位。用示波器或者ILA确认时钟信号是否正常翻转复位信号是否在合适的时间释放。如果复位一直有效CPU当然不会运行。第二步检查引脚约束。确认时钟、复位、UART等关键信号的引脚分配是否正确。有时候引脚分配错了信号根本到不了FPGA内部。第三步检查综合和实现报告。看看有没有严重的警告比如组合逻辑环路、未连接的端口、时序违例等。第四步用ILA抓取关键信号。从PC开始看PC是否在变化。如果PC不变说明取指或者流水线控制有问题。如果PC在变化但结果不对逐步往后查译码、执行、访存、写回。5.2 时序违例的典型原因与修复方法时序违例是FPGA实现中的常见问题。典型原因包括以下几种。组合逻辑路径过长。比如在一个时钟周期内完成了译码、读寄存器、ALU运算、写回等多个操作。解决方法是在中间插入流水线寄存器把长路径切分成短路径。高扇出信号。比如复位信号、时钟使能信号等需要驱动大量寄存器。解决方法是插入BUFG或者复制寄存器减少单个信号的扇出。跨时钟域路径。如果设计中存在多个时钟域跨时钟域的信号需要做同步处理。否则可能出现亚稳态或者数据丢失。存储器接口时序问题。如果使用了Block RAM或者外部存储器接口时序需要仔细约束。特别是对于DDR3等高速接口时序约束非常复杂。5.3 常见问题速查表问题现象可能原因排查方法上板后无反应时钟未连接、复位未释放、引脚分配错误用ILA检查时钟和复位信号仿真通过但上板结果错误时序违例、亚稳态、初始化问题检查时序报告用ILA抓取关键信号综合报错代码语法错误、IP核配置错误查看综合日志定位错误行实现变红时序不满足、资源超限查看实现报告优化关键路径UART无输出波特率配置错误、引脚分配错误用示波器测量UART引脚程序跑飞例外处理错误、PC更新逻辑错误用ILA抓取PC和例外信号5.4 独家避坑经验分享坑一忽视复位信号的同步释放。异步复位、同步释放是推荐的做法。如果复位释放时没有和时钟同步可能导致寄存器进入亚稳态。坑二寄存器堆写回冲突。如果一条指令在写回的同时另一条指令在读取同一个寄存器需要确保读到的值是新的还是旧的。这个细节在仿真中可能看不出来但上板后可能表现为随机错误。坑三指令存储器初始化问题。FPGA中的Block RAM在上电后内容是随机的需要正确初始化。通常的做法是把程序编译成coe文件或者mif文件在生成IP核时加载。坑四忽略时序约束。很多人只写功能代码不写时序约束导致Vivado用默认的约束来综合实现结果时序一塌糊涂。时钟约束是必须的输入输出延迟约束也建议加上。坑五调试时修改代码后忘记重新生成比特流。这个听起来很蠢但我确实见过好几次。修改RTL后一定要重新综合、实现、生成比特流然后再下载。6. 性能优化与进阶方向6.1 从能跑到跑得快的优化路径功能跑通之后下一步就是提升性能。龙芯杯的评测通常会比较运行相同程序的周期数所以性能优化直接关系到成绩。最直接的优化是提高时钟频率。通过优化关键路径、增加流水线级数、使用更快的加法器/乘法器结构可以把频率从几十MHz提升到上百MHz。但频率提升会带来时序收敛的难度需要仔细权衡。其次是降低CPI每条指令的周期数。理想情况下CPI接近1但分支指令、访存指令、例外处理都会增加CPI。通过分支预测、指令缓存、数据缓存等手段可以有效降低CPI。还有一个方向是指令级并行。比如实现双发射或者乱序执行让多条指令同时执行。但这会大幅增加设计复杂度适合实力较强的队伍。6.2 加入Cache的考量与实现要点Cache是提升访存性能的关键。对于龙芯杯来说如果评测程序中有大量的访存操作没有Cache的话性能会非常差。实现Cache需要考虑几个问题Cache的容量和相联度、替换策略、写策略写回还是写穿、一致性维护等。对于单核CPU一致性不是问题重点是把命中率做上去。我建议先实现一个简单的直接映射Cache容量不用太大比如4KB或8KB。等调通了再考虑组相联或者更大的容量。6.3 分支预测的进阶实现静态预测的准确率有限如果想把CPI进一步降下来动态预测是必经之路。最简单的动态预测是一位预测器记录上次分支是否跳转下次预测相同的方向。准确率比静态预测好但遇到规律性不强的分支时容易预测错误。两位饱和计数器是更常用的方案。每个分支对应一个两位计数器根据连续两次的预测结果来调整预测方向。准确率可以到85%以上。再往上还有局部历史预测、全局历史预测、Tournament预测器等。这些方案准确率更高但硬件开销也更大。对于龙芯杯两位饱和计数器通常就够用了。6.4 后续可以扩展的方向如果你已经完成了基础版本并且性能不错可以考虑以下扩展方向。浮点单元。LoongArch有完整的浮点指令集实现一个符合IEEE 754标准的浮点单元是一个不小的挑战但也是加分项。MMU与地址翻译。如果评测环境需要运行操作系统MMU是必须的。你需要实现TLB、页表遍历、地址翻译等机制。多核。实现一个双核或者四核的CPU需要处理缓存一致性、核间通信等问题。这是最高难度的挑战适合实力很强的队伍。向量扩展。LoongArch有向量指令集扩展实现向量单元可以大幅提升数据并行计算的性能。我在实际带队伍的过程中发现很多同学一开始雄心勃勃想把所有高级特性都做进去结果最后连基础版本都没完成。我的建议永远是先做减法再做加法。先把一个能跑通的最小系统做出来然后逐步添加特性。每添加一个特性都要确保它不会破坏已有的功能。这样虽然看起来慢但实际上是最稳妥的路径。