-
Notifications
You must be signed in to change notification settings - Fork 1
/
future.tex
80 lines (50 loc) · 10.9 KB
/
future.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
\chapter{思考与展望}
\section{CPU性能}
目前,从IPC和频率两个维度考察我们的CPU性能,IPC和GS132处理器的IPC比值为27.044,频率为90M。我们深入探究了限制我们的IPC和频率的原因。
\subsection{IPC分析}
我们CPU设计为顺序双发射,在较多情况下发射两条指令使得我们的IPC有较大提升。限制IPC的主要是流水线中的暂停。
在我们的CPU中共有以下几种可能导致流水线的暂停:
\begin{enumerate}
\item 等待指令和数据访存完成;
\item 产生分支,流水线需要更新;
\item 数据相关,等待LOAD指令的数据结果返回才能发射;
\item 等待乘除法指令执行完成。
\end{enumerate}
通过在CPU中增加各级流水线的暂停拍数计数器和暂停原因计数器、查看仿真波形,我们分析以上因素的主次关系和解决方案。
首先,乘除法指令有提升的上限,且乘除法指令无论是在我们的测试集中还是在现实生活生产中,使用较少。可以通过将乘除法指令实现为不暂停流水线直至发起HILO访问得到较小的提升。
对于数据相关,为了保证CPU的频率, 只能在写回寄存器之后再进行前递,如此将导致相邻的访存数据相关指令之间需要暂停两拍。可以通过将部分不会在执行级发生例外的指令延迟至访存级执行。
对于分支,流水线的代价较大,共需要暂停三拍。这是我们的CPU设计决定的,在执行阶段产生分支信号后,需要经过取指、指令放入FIFO、译码并发射三拍,执行级才能恢复非空泡状态。这使得在分支较多且连续执行的指令段较短的程序当中,我们的双发射CPU的性能较差。可以通过增加分支预测器,较为明显得改善这一现状。
访存延迟是影响整个处理器性能的关键,这方面主要与cache的设计相关。我们在5.2节详细展开说明其中的原因。
分支和访存的两个原因,极大程度上造成了我们的IPC在十个测试样例中差距较大,IPC比值最高可达38.6,但最低仅为20.8。
\subsection{频率分析}
我们的CPU频率限制来自于以下几个方面:
\begin{enumerate}
\item 双发射由于实现为两条流水线的顺序不定,在写回寄存器和数据前递时判断逻辑较为复杂;
\item D-cache实现为实Index实Tag,CPU需要在执行阶段计算虚地址后查找TLB。
\end{enumerate}
双发射的两条流水线顺序不定起于设计初期考虑的在不占用更多资源的情况下尽可能多发射,对于频率的影响大于设计时的预期。可以折衷为两条流水线顺序固定,两条流水线功能大致相同,对于复杂模块(访存和乘除法)仅实现一个,两条流水线仲裁后使用。
\section{Cache的设计与开发}
\subsection{Cache的不足与优化}
\subsubsection{Cache性能优化}
我们的I-Cache与D-Cache都实现为2路、每路256行、每行8字。在连续命中时,可以不间断地流水返回数据。I-Cache只有在CPU的请求既不在Cache存储中,也不在AXI读缓冲中的时候,或是命中了Cache中的数据,但该数据所在行正在进行AXI读的时候,又或是这一拍将会把AXI读到的数据写近Cache存储中的时候会停止流水线。在其他情况下,都能够直接返回数据。D-Cache只有在CPU的请求既没有命中Cache存储也没有命中AXI读缓冲的时候,或是这一拍正在将AXI读到的数据填入某一行中的时候停止流水线。
Cache的性能有如下优化方式:
\begin{itemize}
\item I-Cache和D-Cache在进行 refill 的那一拍无法响应命中了Cache行的请求,原因是我们采用的是单个地址端口的存储器,在 refill 时必须占用地址端口,因而这一拍无法从存储器中读取数据。解决这个问题的方法是采用双地址端口的双端口存储器。
\item 无论是I-Cache还是D-Cache,当前都不支持并行的AXI读写,也即只能进行一个AXI读写事务。这导致在代码发生跳转,且跳转前后位于不同行,且都发生 miss 的情况下,跳转后的指令需要在前一行读完之后,再进行握手,再进行读取,非常影响性能。应当利用好AXI总线多个读写事务的功能,同时进行多个读写请求,加快响应时间。这是下一步的改进方向。
\end{itemize}
\subsubsection{实现可配置的Cache}
现在版本的Cache中,只有Chisel3实现的D-Cache能够做到可配置(路数、行数、行大小),而用SystemVerilog实现的I-Cache与Cache顶层模块并不直接支持可配置。不仅如此,由于需要将Chisel3生成的D-Cache整合到Cache顶层模块中,还是需要手动改动许多接线,非常不便。如果能实现所有参数可配置的Cache,将对性能调优以及在不同情况下的适用性有很大帮助。
事实上,用Chisel3完全重写的Cache能够实现路数、行数、行大小全部可配置,然而,由于在第 \ref{sec:cache-caveat-pitfalls} 节中提到的原因,这一版本的Cache没能成功引导系统。因而在以后的开发中,我们希望能够完全实现一个可以引导系统的全面可配置Cache。
\subsection{Cache的开发}
\label{sec:cache-caveat-pitfalls}
\paragraph{问题} 在本次系统的开发过程中,Cache的开发处于滞后状态,落后于原定计划近一个多月时间,在七月中下旬才完成开发与调试。不仅如此,由于开发时许多因素考虑欠妥,从七月中下旬到八月上旬这段时间中,为了优化系统性能,保持系统的可维护、可调试性,对Cache进行了多次重构。其中包括用Clash重写Cache,将单发I-Cache重写为双发,用Chisel3重写整个Cache模块。然而,由于Cache的开发进度原本就已经滞后,留给系统调试、引导系统的时间非常紧张,而对Cache频繁、多次、大规模的重构又导致系统、外设测试所用的代码版本与最新版本没有及时同步,再加上重写、改动Cache代码后,会给系统带来新的Bug,需要更多的时间调试系统。最终导致了最终使用的Cache版本并非最终版本,I-Cache采用了第一次重构的,用SystemVerilog实现的双发版本,D-Cache采用了用Chisel3重写的较早
版本。在后面的Cache开发中,为Cache实现的诸多优化与功能,都没有使用到最终的CPU中。
\paragraph{反思} 反思这一系列问题与遗憾的原因。Cache最为CPU中非常重要、不可或缺的一部分,理应在开始整体系统调试之前,就完全确定实现方式,敲定代码,完成测试与调试,保证其处于一个稳定、无Bug的状态。就算有性能优化与改动,也应当尽量在原有代码的基础上进行,而不是直接进行重构,甚至用另一种工具进行完全的重写。开发Cache的工具,Cache的整体实现方式,应当在开发之前就敲定,而不是在比赛这么有限的时间内进行不断迭代。对Cache频繁的迭代与重写,加大了系统测试的调试难度,也最终让我们没有使用最新的,添加了更多优化的Cache版本。硬件系统的开发如此,其他领域的开发工作也是同样道理。若时间有限,在实现之前就应当深思熟虑,而不是一开始想着快速实现出一个原型 (prototyping),然后之后在不断迭代优化——这样的策略只适用于有较充裕的时间,较多的迭代机会的项目,对于龙芯杯这样时间有限的比赛,这种思维并不
适用。
\paragraph{展望:Cache调试框架} 在Cache的开发过程中,我们常常感受到一个Cache测试框架的必要性。在龙芯提供的功能测试、性能测试乃至于系统测试中,对于Cache的测试都是间接的。这带来了两个问题,(1) 对Cache测试得不够彻底,不够全面;(2) 在以上测试中若出现了 Cache 的问题,定位起来较为麻烦,性能测试与系统测试尤为如此。因而在开发过程中,我们一直希望能有一个,或是自己开发一个 Cache 测试框架。但由于时间限制,这一想法一直搁置。Cache作为整个系统中较为重要,也较容易带来问题的一个部分,为它专门开发一个详尽的,全面的测试框架是有必要的。理想中的测试框架应当着重测试Cache的正确性与一致性。应当验证其读取的数据是否正确,对于 dirty 的处理是否得当,若实现了写回缓冲,那么能否正确返回写回缓冲中最新的副本。除此以外还有Cache指令,在现有的环境中,
对Cache指令的测试是困难的。除了自行编写测试程序之外,几乎只能在操作系统引导时测试。而Cache指令的测试,就算自行编写测试程序,用现有的测试框架也不那么直接,因为Cache指令并不直接涉及寄存器的写回。
\section{系统调试及外设}
\paragraph{问题} 在本次系统移植与调试中,尽管有Git的帮助,仍然花费了大量的时间在版本同步上。同时许多例如中断信号高低位有效、内核编译的kernel配置文件、没有make clean导致污染发生等外部的简单配置经常出错,进而让为原本就不充裕的时间就更紧张。同时,软件与硬件的移植过程中给,并没有提前按设计验证软件是否正常运行,导致问题定位的方向错误,白白浪费大量的时间去debug本不存在的硬件问题。在硬件稳定的前提下,系统软件移植过程可以采用快速的迭代的思想。但是在硬件仍未稳定的情况下,快速迭代造成了大量的可运行孤本存在,让后续开发陷入僵局。在前期进行开发时由于时间不紧张,我们还能正常执行稳定的分支维护,到后期commit记录过于混乱和跨长时间merge的问题给我们很大影响。
\paragraph{反思} 在移植过程中,尽管我们已经阅读过关于硬件设计应当以稳妥和稳定为主,我们仍然错误的将互联网应用开发的快速迭代思想应用于硬件编程。快速迭代已经变成了我们的一种潜意识中的习惯,这是非常可怕的。在快速迭代的过程中,许多小错误莫名奇妙的产生又消失,如果卡住时只能通过比对版本库进行检查。我们认为再次调试系统时,应当使用一个base commit,在当天内仅在该commit上操作,修复,并使用squash进行提交合并,保证commit信息的有效性,使版本库具有可读性才是最重要的。
\paragraph{展望} 在系统移植过程中,应当使用qemu配置一个系统工作的检测平台。但是受限于时间我们并没有做这个选项。然而后续在这类问题上多花费的时间远超配置所需的时间。所以构建系统的测试平台是有必要的,而通过交互监控可以检查系统的启动过程。同时,我们认为引入一个EJTAG来进行数据抓取也能提供很大帮助。可以节省ILA抓取的资源数量,查看内存的具体情况,便于在出现问题时进行debug,节省时间。
% \secti需要将on{}