一转眼,已经是八月的最后一天了,暑假也快结束了。这段时间里,我主要的任务就是贵系的专业实践小学期,我们组的选题是参与第三届“龙芯杯”全国大学生系统能力培养大赛。忙活了一个暑假,最终不负众望得到了第一名。按照惯例,比赛后还是想回顾一下整个过程。
缘起
最早听说龙芯杯其实是两年以前的事情,宇翔、坏人和昌老师参加了第一届龙芯杯。在当时大家几乎只能写出一个简单的 MIPS CPU 的情况下,他们成功地用上了实验板上的大部分外设,还移植了 Linux(可以参见 GitHub 上的代码和报告)。毫无疑问地,当年的特等奖被他们摘得。当时才大一的我听说奖金整整有五万,立刻心动了起来。去年暑假,我和 miskcoo 决定提前完成计原的项目,于是在 ThinPad 实验板上写了一个双发射的 MIPS32 CPU(代码在 GitHub。暑假里,我也一直在关注参加龙芯杯的同学们的动向。去年的成员是唐适之、蔡子熙和刘熙航,他们做的是双核 MIPS CPU。然而,由于性能测试只能在一个核心上跑,而双核也不可避免地影响了 CPU 的频率,因此最终他们在性能测试上吃了大亏,最后拿了第二名。
去年的比赛刚结束,宇翔就找到我们,说吴老板当了教指委的主任,想要把这个奖拿回来。一方面出于兴趣和对输掉的不甘心,也冲着五万的奖金,我答应了下来,也就算正式开始准备比赛了。
赛前准备
由于我们已经有了一个双发射 CPU 的原型,我们原本打算从今年春季的操作系统课程上就开始进行比赛的准备,但是由于与老师有一些方向上的争议,所以事实上课程中并没有进行任何龙芯杯的准备工作,而是专心地在做 rCore 教学操作系统在 MIPS 平台上的移植。当然,这并不意味着我们毫无进展,至少过程中我们修复了 CPU 的好几个 bug,并且越来越清晰地认识到,它已经有点积重难返了,恐怕不得不需要全部重写了。
过了一阵子出了正式的比赛通知,今年初赛还是功能和性能两部分分别计分,测例都没有换,性能测试也还是只看相对于龙芯 GS132 的加速比。但是决赛的评分方式有了一些调整,指令集答题 20%(基本是送分),性能分占 40%,展示和答辩表现各 20%。其中性能分中,主频和 IPC 各占 20 分,这与前两届单独计算加速比或者运行时间都不一样。事实上,主频和 IPC 基本在一个翘翘板的两端(两者分别和逻辑复杂程度基本呈负相关/正相关),很难同时取得进步。或许主办方这样做就是想要遏制一下去年疯狂提频的做法(在 这里 可以看到去年所有队伍的展示 PPT,还有北理的参赛总结文档,可以看到西工大的主频还是比较恐怖的),但这也给我们的准备带来了新的难题。
四月底比赛就开始报名了,我又拉上了刘晓义和我们一起参赛。因为系里想让我们出两个队,思来想去我又找了杰哥和一位大二同学、一位大一同学一起组了一个队(事后证明其实不是非常明智)。报名完一段时间以后我们就拿到了实验箱,不过我一直把它扔在 308,直到期末考试前都没摸过。
暑假小学期的第一节课,我们讨论了一下究竟要做些什么。因为去年在性能上丢了分,因此刘卫东老师要求我们要把这部分分数抢回来;而在功能展示上,康总指出我们可以跑图形界面(X Window),宇翔提到了有 Qt Embedded 可以直接工作在 Framebuffer 上写比较 fancy 的 GUI 程序。杰哥则一心想把实验板上的 USB 接口用起来,然后也需要用 Linux 来演示。确定了小目标以后,我们的比赛就算正式开始了。
初赛
整个七月份我们都在准备初赛,miskcoo 负责写 CPU(这次称为 NonTrivialMIPS),刘喵喵写 cache,而我在折腾 Vivado 和 CI 这些比较杂的东西。于是事实上我并没有写几行 RTL,而是不停在和 Vivado 进行着斗争。我遇到的 bug 之多简直 罄竹难书,尤其是有一段时间 Vivado 经常由于奇怪的原因崩溃,或者表现奇怪(比如所有时钟都消失然后综合出错误的东西来)。这让我的心态也不太好,因此没有什么太实质的进展。虽然参考了宇翔的设计,但是我还是没法用起来板子上的外接 Flash,这让我也比较沮丧。杰哥由于并没有 CPU 的基础,拉上了两个队友一起写 CPU。但是其实大家都知道,CPU 基本没法切开成几个人实现;外加杰哥的队友总是很忙(比如参与了 NOI 相关工作),因此实际上主力还是杰哥,不过开会的时候也还能看到他队友。
另一方面,和我并行进行的 CPU 设计倒是比较顺利。miskcoo 把 CPU 的取指和译码阶段重新设计了,使得双发射的逻辑控制更清晰了,而后面的执行等部分基本都用的原来的代码(这也埋下了一些隐患)。刘喵喵也实现了听起来比较科学的三级流水 cache,指令部分比较简单,数据部分实现成了 write-back 和 write-allocate,带一个简单的写缓冲。7/19,我们第一次把带着 cache 的 CPU 接入到了龙芯的测试环境中,还是 50MHz 的默认主频,得到了大概 33 左右的性能分(和去年清华队的最终成绩持平)。这时候杰哥的 CPU 已经实现完了,正在努力攻克 USB(此时已经只剩下杰哥一个人了)。他的调试过程要比我们困难,用上了物理的逻辑分析仪一个一个抓信号,说是使用了开源代码,实际上是重写了大部分。
接下来的两周每次和康总开会的时候,我们总是能够提升一点性能分(包括直接提升频率,或者优化一点路径)。到 7/29,我们的频率达到了 110MHz,性能分在 71 左右。这已经超过了前一届的最高性能分(也就是西工大的分数)。7/30 是一个神奇的日子,我去了一趟沈阳面谈美签,在回来的路上看了一眼我们的讨论群,发现性能分突然猛涨到了 80 分!一问才知道 miskcoo 进行了比较本质优化,把一些指令的执行延迟到访存阶段(也就是 dcache 的三级流水阶段),这样能够让流水线的阻塞大幅减少,自然提高了性能。这时候杰哥也实现了一个使用 AXI System Cache 的版本,性能大概在 4 分左右。
8/5 是初赛提交的日子,考虑到诸多因素,我们最终提交的版本并不是现有的最快版本,而是一个主频 80MHz,分数大概在 50 左右的保守版本。不得不提的是,从 QQ 群中的交流来看,这一届大家的实力的确都很强,性能分达到数十分的人比比皆是。甚至还有人匿名在群里散布一些耸人听闻的消息,比如性能分已经达到了89分;这让几乎所有人都吃了一惊,我虽然觉得并不怎么可信,但也产生了隐隐的担心。八月初我把现状转告了杰哥以后,他决定抛弃 AXI System Cache 自己写。于是在也就是提交截止那一天,他也成功地完成了一个大约 15 分的版本(速度实在是让人钦佩)。
决赛准备
交完初赛以后,我顺理成章地摸了几天鱼,而后宇翔打听到了一个消息:今年的初赛门槛会比较高。由于教指委设定了获奖比例的限制,因此进入决赛的队伍从往年的 25 支减少到了 21 支。经过两年的经验积累,大家对于性能优化也有了一定的概念。于是我们很遗憾地得知,杰哥并没有进入决赛(相较去年只要能够运行性能测试都能进入决赛)。我们提交的版本性能是初赛第五名(在我们前面的有国科大、北邮、哈工大两支队伍),第一名的分数依旧在 70 左右。得知这一消息,我们心情也是喜忧参半:高兴的是我们的实际性能其实已经超过了初赛的第一;担心的是是否也有别的队伍像我们一样并没有提交最好的结果,或者他们在这两周时间中也取得大的突破。
事实上,从初赛结束以后到决赛开始之前这段时间,我在 Linux 移植上已经做了比较多的工作,内核态已经能够比较正常的运行。其中也花了不少时间修 CPU 的 bug,不外乎就是使用 ILA 或者跟着代码,外加一些 printk
,定位到每个问题基本上都要花一天左右。好几个问题都是由于 CPU 的实现并不标准(和规范有出入)造成的,也基本都是去年遗留的问题,包括对外部中断的处理、对 TLB 标志位的处理等等,大多来自于 CP0。调了几天之后,我们已经能够正常从 U-Boot 引导起 Linux kernel,并且到达启动用户态程序的阶段了。但是此时 Flash 还是不可用的,于是我们只能用 initramfs,导致每次引导需要传的文件都比较大,因此需要较多的时间(当然,没有修改了 ILA 或者 RTL 之后重新综合布线需要的时间多)。我也找杰哥要来了他的 USB 支持代码并且移植到了我的 SoC 和内核里,但是不知为何也并不工作。
结果一出来,组委会要求我们提交决赛名单,并且说可以更改成员。我得知以后大喜过望,和组委会二次确认了可以增加成员后,立刻把杰哥拉到了我们的队伍中,他花了一天多时间就解决了我们的 Flash 和 USB 的可用性问题,分别是我约束和 IP 配置写得有问题,以及错误地连接了信号导致的(这让我十分愧疚)。
由于使用了标准的 Xilinx IP,所以用上 Framebuffer 并没有什么难度。但我们试图移植宇翔的 frambuffer 加速模块的时候遇到了一些麻烦,一开始它总是完全不工作,后来才发现坏人写的代码其实是错的,只是刚好在旧版 IP 上能工作。修复了这个问题以后以后,终端的滚屏突然变得诡异了起来。我和杰哥研究了好几天也没能发现是什么问题,考虑到我们的滚屏事实上不是很慢,最终我们没有启用它。不过我把 FB Console 默认的 Tux (Linux 小企鹅)换成了清华的校徽,也算是一点特色。
我们用 Buildroot 来编译用户态程序,由于组件非常多(特别是 Qt 体积巨大),最终的 rootfs 体积甚至超过了 100MB,因此 Flash 肯定是没法用了。而我们尝试 U盘 + ext4 也遭遇了很多问题(硬件或者驱动依旧存在 bug),所以最终决定用 NFS 来挂载系统的 rootfs。
运行用户态程序都比较顺利,但是我们发现一些程序偶尔会不明原因地 SIGSEGV,甚至整个内核 panic。我们一开始没有在意这个问题,直到尝试使用 fbdump
来抓 framebuffer 截图的时候,才意识到问题严重到了不得不重视的地步。这个 bug 的解决花了非常久,调试过程也极其艰难。我并没有帮上什么忙,猜测的错误来源也并不是正确的。关于bug 的诱因和解决方案,可以参见 miskcoo 写的这个 知乎回答。非常钦佩他和杰哥的毅力还有想象力,最终没有把这个定时炸弹一样的 bug 带到决赛现场。下面这张图就是在我们的 SoC 上运行的 htop
,是使用 fbdump
抓下来的。
这段时间里,喵喵做了一个非常有用的事情:全自动化内卷。事实上,由于我们已经把频率提高到了接近极限(120MHz),因此任何逻辑和参数(包括 cache 尺寸、相连度等)的变化都可能大幅影响最终的时序结果。于是他写了一个脚本,能够生成参数矩阵,并自动在各个频率下综合性能测试的 SoC,并得到最终的时序结果。一旦有了这样的工具,我们就可以安心地把它丢上服务器运行,然后找一个最好的版本继续迭代。同时,喵喵还改进了 cache 替换算法等一些小细节,miskcoo 也对流水线地动态调度和多周期指令等进行了进一步的优化。在这些共同努力之下,我们最终把频率提高到了 123MHz,加速比达到了 89.326(居然真的能到89!),决赛的另一个性能指标(IPC 比)也达到了 36.3 左右。
为了展示,喵喵还用 Qt 写了一个叫 TrivialDashboard 的程序,能够把系统性能指标绘制出来,也能用 ssh 连接到其他机器抓数据。杰哥当然是顺利地跑起了 Xorg Server,并且运行了 xeys
和 xcalc
之类的小程序,USB 键盘鼠标键盘也都能正常用。值得一提的是,miskcoo 还借助 MIPS 的 CP2 协处理器实现了 AES 运算加速,杰哥修改了 OpenSSL 的代码,得到了 11x 左右的加速比。他还把 TrivialMIPS 的 CP1 浮点处理器(FPU)移植了过来。到这时候,我们终于可以说,我们把想做的部分都实现了。
这段时间里,我们还一直在撰写决赛要求的设计文档,在此按下不表。
决赛现场
决赛 8/19 到 8/22 在青海大学举行,我们一行六个人(含康总和宇翔) 18 号下午一起飞到了西宁。虽然是一个省会城市,西宁还是稍欠发达。我们住的地方离学校有 3 公里多,只能公交车前往;离市中心更是有十多公里,想打车去吃饭都没打到,还是只能坐公交(不过可以用交通互联卡)。第一天晚上我们去老城区(猜测,因为附近有公园、博物馆之类的)吃了一顿炕锅羊肉,还去超市屯了不少零食(因为酒店周围似乎并没有)。晚上我们简单移植了 FPU 的一个 demo,绘制著名的分形图案 Mandelbrot 集;我和杰哥也最后确认了一下第二天演示的程序还有流程。
19号下午才开始比赛,上午康总替我们去报到,也领了比赛的衣服;中午大家一起在酒店楼下吃了一顿牛肉面,比东方宫之类的便宜并且量大,吃完就坐公交车去了学校。下午是比赛的第一个环节:指令集答题,比较简单也比较无聊,所有队伍都成功拿到了这 20 分。而后就是自由演示环节了,趁这个机会我也去各处闲逛了一下。西工大这次的加速比并没有很突出,但是他们实现了乱序四发射地处理器并且运行起来了 Linux,所以 IPC 还是比较突出的,感觉也相当厉害。也有不少队伍做了双发射,比如北邮,与我们之前看性能分的猜测比较类似。在应用展示上,有几个队伍(比如国科大、哈工大)着力于 IoT 硬件,国科大直接搬出了一个 Arduino(用舵机接了一个十分神秘的小毛刷)。我们的展示主要由杰哥主导,有好多人围观,其中龙芯的汪文祥老师显得特别感兴趣,还提了不少问题。晚上在青海大学的食堂吃了一顿(差评!),就回宾馆了。
由于一开始康总抽签比较不巧,我们抽中了第16个答辩,在20号下午两点左右。因此19号晚上我简单修改了一下 PPT,讲了一遍,第二天早上起来又拽着康总讲了一遍。事实上我们原本是按着 15 分钟准备的,到最后几天才得知是 10 分钟,我当时对着 30 页陷入了沉思。好在最后,经过精细的控制和裁剪,我们在没有减少页面的情况下最终把演讲时间控制在了 9 分钟左右,这还包含一个 30s 左右的视频(因为现场并不方便展示,我们就剪了一个小视频把一些比较重要的成果都放了上去)。那天中午吃了一种神奇的叫“炮仗”的面,其实就是切成小段的面条。下午讲得也比较顺利,剩下的 10 分钟时间里面评委也问了不少问题,不过都非常友善,到最后甚至像是在聊天了。
讲完以后才三点多,我们去市中心与出去玩的宇翔会合。他带着我们在人民公园转了一圈,也去了西宁著名的东关清真寺参观。在门口,收到了刘卫东老师发来的喜讯,告诉我要开始准备后天的 talk 了(下面会详述)。这时候,我才真正松了一口气。其实没有想象中的激动的心情,更多的是欣慰吧。
在美食城吃了一些当地特色以后我们就回了宾馆。这时候剩下的三个人已经没有任务了,而我还需要准备一些东西。第二天(22日)的颁奖典礼上,冠军队伍需要发表获奖感言;23日有一个系统类课程教学研讨会,会邀请冠军队伍进行一个分享,这也要准备。于是我简短地摸了一会儿鱼以后,就开始绞尽脑汁思考应该说些什么。最后折腾到一点多,终于勉强算是糊了一些出来。虽然一切都尘埃落定了,那天晚上我直到三点都没有睡着,但并不是因为和比赛相关的原因。
22号上午颁奖,宇翔替代李山山老师领了一个优秀指导教师奖,康总甚至充当了送奖品的工作人员。龙芯的 CTO 和我们介绍了当前龙芯的构成和研发进展,可以看到已经慢慢地在追上世界先进水平了。中途拍照的时候,他们也成了全场最突出的人:其他人都分层站在台阶上,他们两个人站在台阶高处,有一种掌控一切的感觉。而后的比赛回顾环节中,我们欣喜地发现不管是主频还是 IPC 比,我们都是所有决赛队伍中最高的!最终的特等奖颁奖环节,汪老师对我们的评价非常高,甚至给了我以重自己真的很厉害的错觉。直到发表感言的时候,我才意识到自己真的很激动,即便对着看了好几遍的稿子,也会紧张地读错字。颁奖的时候也直接发了奖金,有一个小小的遗憾是今年的奖金都是税前的,因此大家最终拿到手的只有四万,不过分起来就很简单了。
22号下午,我和宇翔跟着国科大组织的包车一起去了青海湖(此处有我搞错集合的校门于是两个人咕了一车人十多分钟的故事)。杰哥和喵喵因为第二天要考 Java 晚上先飞回去了,康总也飞走了(我们把奖金交给了他),miskcoo 回宾馆看剧。去的路程花了两个半小时,玩的时间也只有两个多小时。事实上景区并不大,门票却比较贵,我们只好默念四字真言“来都来了”交出了智商税。巧合的是我们在景区了遇到了自己包车旅游的另一拨人,大家还一起拍了合影。回去的路程又花了两个半小时,我们去新城区的万达附近吃了一家烧烤。由于朱老师第二天要开课程研讨会,所以他也到了西宁,我们叫上了他一起吃烧烤。吃完,我们在万达逛了逛就回去休整了。
23号,我们收拾行李去了会议现场,这是教指委第一次组织这类会议,与会的几乎全都是全国各地高校系统类课程(计算机组成原理、操作系统、编译原理)的相关老师。上午是全体大会,主席是刘卫东老师,我的 talk 被安排在大会最后一项,前面是陈文光老师的一个关于编译技术的 talk。虽然比较努力地控制时间,最后还是没有讲完,尤其是在介绍我们的成果后关于杰哥课改的部分。中午吃了(实在是难吃的)会议自助,下午听了关于编译的几个 talk,还在高教社和机械工业出版社的展位上要了几本书。当天的会议结束以后,晚上和朱老师一起跑出去吃了一顿小火锅,就打车去机场了。
原本这一部分到这里应当结束,然而出现了并不令人愉快的小插曲。就在起飞前,机长广播告知我们坐的飞机有两位旅客在登机以后终止了行程,一位是生病,而另一位的原因并没有公开告知。总之,按照民航安全规程需要清舱,所有人重新安检。最终我们的起飞时间比预计的 20:45 晚了将近三个小时,到达北京已经一点多了。等到到达宿舍安顿下来,已经是三点了。我的整个龙芯杯周期,也就随之结束了。
参赛感想
就像我在获奖感言和会议上分享时两次都提到的,这次能赢比赛,兴趣是最关键的。若不然,我们也不会从一年前就开始准备,不会连续数周几乎每天都奋战到天明。很惭愧的是,作为队长,我其实没有多少实质性的工作;如果没有杰哥、喵喵和 miskcoo 高明的构思和过硬的水平,凭我自己绝无可能得到如此成就。当然,我们要感谢宇翔,感谢李山山老师、康总、陈渝老师、向勇老师、陈文光老师等一系列在全过程中指导、帮助、支持我们的人。
作为我个人的博客,我还想感谢更多的人,包括 mvs、lcy、zjq、wxj、wyy。在每一个我“调板子”不顺利的白天或者夜里,你们听着我无尽的抱怨、吐槽、怀疑人生甚至是咒骂,安慰我或者是鼓励我,给我继续努力下去的动力。正如我说的,如果没有你们,大概我真的会自闭的。
写了洋洋洒洒六千多字,其实绝大部分都是流水账,权当是等今后忘记的时候帮助回忆吧。
附录
- 所有相关代码都开源在 GitHub,也包括一份详细的设计报告
- miskcoo 撰写的 关于 CPU 设计的博客
- 我在系统类课程研讨会议上分享的 slides