diff --git a/zh/advanced/Bus.md b/zh/advanced/Bus.md new file mode 100644 index 0000000..d0d4826 --- /dev/null +++ b/zh/advanced/Bus.md @@ -0,0 +1,37 @@ +--- +sidebar_position: 4 +--- +## 总线 + +:::info[视频录播和课件] +* 录播 - [总线选讲](https://www.bilibili.com/video/BV1wP4y1q7fF/) +* 课件 - [总线选讲](https://ysyx.oscc.cc/slides/2205/17.html#/) +::: +新讲义还没写好, 大家可以先看上面的课件和视频录播. + + +:::info[总线资料] +* [**第五期“一生一芯”系列视频**](https://space.bilibili.com/2107852263/channel/collectiondetail?sid=690279) +* [**总线英文手册**](https://developer.arm.com/Architectures/AMBA) +* [**总线中文手册及总结**](https://www.lzrnote.cn/2021/10/08/axi%e6%80%bb%e7%ba%bf%e6%80%bb%e7%bb%93/) +* [**深入AXI4总线**](https://zhuanlan.zhihu.com/p/44766356) +::: + +:::warning[将IFU的取指接口改造成AXI-Lite] +1. 用RTL编写一个AXI-Lite的SRAM模块, 地址位宽为32bit, 数据位宽为64bit +1. 收到读请求后, 通过DPI-C调用`pmem_read()`, 并延迟一周期返回读出的数据 + * IFU每次取指都要等待一个周期, 才能取到指令交给IDU +::: + + +:::warning[将LSU的访存接口改造成AXI-Lite] +1. 用RTL编写一个AXI-Lite的SRAM模块, 地址位宽为32bit, 数据位宽为64bit +1. 收到读写请求后, 通过DPI-C调用`pmem_read()`/`pmem_write()`, 并延迟一周期返回读出的数据/写回复 + * LSU每次访存都要等待一个周期 + * 此时可以保留`pmem_read()`/`pmem_write()`中的设备访问功能, 我们将在SoC章节介绍如何通过总线访问外设 +::: + + +:::warning[实现AXI-Lite仲裁器] +保留一个AXI-Lite的SRAM模块, 编写一个AXI-Lite的Arbiter, 从IFU和LSU的AXI-Lite中选一个master与SRAM模块交互. +::: \ No newline at end of file diff --git a/zh/advanced/Cache/2.8_booth.png b/zh/advanced/Cache/2.8_booth.png new file mode 100644 index 0000000..6eb5d8c Binary files /dev/null and b/zh/advanced/Cache/2.8_booth.png differ diff --git a/zh/advanced/Cache/2.8_div_module.png b/zh/advanced/Cache/2.8_div_module.png new file mode 100644 index 0000000..64a6ddc Binary files /dev/null and b/zh/advanced/Cache/2.8_div_module.png differ diff --git a/zh/advanced/Cache/2.8_walloc_X_booth.png b/zh/advanced/Cache/2.8_walloc_X_booth.png new file mode 100644 index 0000000..e2f9b63 Binary files /dev/null and b/zh/advanced/Cache/2.8_walloc_X_booth.png differ diff --git a/zh/advanced/Cache/2.8_walloc_tree.png b/zh/advanced/Cache/2.8_walloc_tree.png new file mode 100644 index 0000000..136afbc Binary files /dev/null and b/zh/advanced/Cache/2.8_walloc_tree.png differ diff --git a/zh/advanced/Cache/fig.1.png b/zh/advanced/Cache/fig.1.png new file mode 100755 index 0000000..f68fab3 Binary files /dev/null and b/zh/advanced/Cache/fig.1.png differ diff --git a/zh/advanced/Cache/fig.2.png b/zh/advanced/Cache/fig.2.png new file mode 100755 index 0000000..1ac86a7 Binary files /dev/null and b/zh/advanced/Cache/fig.2.png differ diff --git a/zh/advanced/Cache/fig.3.png b/zh/advanced/Cache/fig.3.png new file mode 100755 index 0000000..abeec16 Binary files /dev/null and b/zh/advanced/Cache/fig.3.png differ diff --git a/zh/advanced/Cache/fig.4.png b/zh/advanced/Cache/fig.4.png new file mode 100755 index 0000000..9247d62 Binary files /dev/null and b/zh/advanced/Cache/fig.4.png differ diff --git a/zh/advanced/Cache/fig.5.png b/zh/advanced/Cache/fig.5.png new file mode 100755 index 0000000..dfc71ce Binary files /dev/null and b/zh/advanced/Cache/fig.5.png differ diff --git a/zh/advanced/Cache/fig.6.png b/zh/advanced/Cache/fig.6.png new file mode 100755 index 0000000..e375e40 Binary files /dev/null and b/zh/advanced/Cache/fig.6.png differ diff --git a/zh/advanced/Cache/fig.7.png b/zh/advanced/Cache/fig.7.png new file mode 100755 index 0000000..41c2f00 Binary files /dev/null and b/zh/advanced/Cache/fig.7.png differ diff --git a/zh/advanced/Cache/fig.8.png b/zh/advanced/Cache/fig.8.png new file mode 100755 index 0000000..1f08a8a Binary files /dev/null and b/zh/advanced/Cache/fig.8.png differ diff --git a/zh/advanced/FancyUserProgram.md b/zh/advanced/FancyUserProgram.md new file mode 100644 index 0000000..f0f24e1 --- /dev/null +++ b/zh/advanced/FancyUserProgram.md @@ -0,0 +1,49 @@ +--- +sidebar_position: 3 +--- +## 精彩纷呈的用户程序 + +本节需要更多的[RTFM](https://www.libsdl.org/release/SDL-1.2.15/docs/)来学习多媒体库的使用,当你足够熟练的时候,也许你也能写出[NVBOARD](https://github.com/NJU-ProjectN/nvboard)。同时需要现更多功能来支持Navy中的应用程序,运行仙剑奇侠传。南京大学的操作系统课程实现了一些基于AM的[小游戏](https://github.com/NJU-ProjectN/oslab0-collection), 尝试在NAVY上运行。 + +:::info[可选:自制小游戏] +参考 [amgame](http://jyywiki.cn/OS/2022/labs/L0) +::: +为了提升效率,你还需要实现更多的基础设施:自由开关Difftest模式和快照,展示批处理系统等等。 + +:::warning[根据PA讲义完成除选做之外的内容,完成必答题] +直到你看到如下提示框: +::: + +:::tip[温馨提示] +PA3阶段2到此结束. +::: + +:::warning[在NPC中运行仙剑奇侠传] +到目前为止,我们已经实现了PA3的全部内容。 +我们需要在NPC上通过DPI-C机制运行仙剑奇侠传,验证自己的处理器代码。 +::: +:::danger[验证自己的NPC——先完成后完美] +在B线的PA和NPC中,我们往往会通过cpu-tests对代码进行测试。 +有些同学可能会有疑问:为什么我的代码已经通过了cpu-tests的测试,但在后续实验中,例如运行仙剑时依然出现了difftest报错的问题呢? +事实上,如果大家阅读过cpu-tests的源码,可以看到其实这些测试只是一些很简单的程序。 +也就是说,这些测试程序的强度很弱,完备性很差,往往并不能检测出代码中存在的错误。 + +在PA和一生一芯的讲义中多次提到,大家可以编写自己的测试集。 +我们可以针对一些边界情况、特殊情况进行单独测试,尽量保证代码的正确性。 +如果我们的代码无法通过这些测试,一定可以说明我们的代码中存在问题;但即使我们的代码通过了全部测试,我们也无法保证代码是一定正确的。 +感兴趣的同学可以学习一些形式化验证的相关内容,或许在未来,形式化验证也可以应用在我们的代码中。 + +在后续讲义中,我们会对NPC添加AXI总线、乘除法模块、高速缓存、流水线等相关内容,大家也会频繁对自己的处理器结构进行优化和改进。 +倘若我们一直使用cpu-tests进行测试,由于cpu-tests的测试强度比较弱,在我们添加这些功能时,可能没有办法及时发现代码中隐藏的bug。 +当我们实现所有的功能并优化处理器结构后,最终在NPC上运行仙剑奇侠传时,由于仙剑是比较复杂的程序,这些残留的bug可能会同时触发。 +同时,由于这些bug分布在NPC的各个功能模块中,有些是关于总线,有些关于缓存,有些关于流水化,所有bug混杂在一起,导致调试十分困难。 + +因此,我们希望大家在NPC中添加每个功能之后,都通过运行仙剑奇侠传测试自己的代码。 +一方面,仙剑是比较复杂的程序,比较容易检测出代码中的bug; +同时,我们也贯彻落实了先完成后完美的观念,每完成一个功能或模块都对该模块进行测试。 +当我们实现AXI总线后,我们通过运行仙剑验证总线实现的正确性; +加入乘除法模块后验证乘除法模块的正确性; +添加高速缓存后验证cache实现的正确性; +接入流水线后验证流水线的正确性。 +这样,可以把bug尽量限制在比较小的范围内,方便我们的调试。 +::: diff --git a/zh/advanced/IntroToCache.md b/zh/advanced/IntroToCache.md new file mode 100644 index 0000000..2425aac --- /dev/null +++ b/zh/advanced/IntroToCache.md @@ -0,0 +1,103 @@ +--- +sidebar_position: 7 +--- +## Cache简介 + +:::info[视频录播和课件] +* 录播 - [性能和缓存](https://www.bilibili.com/video/BV1FY41127tA/) +* 课件 - [性能和缓存](https://ysyx.oscc.cc/slides/2205/19.html#/) +::: +Cache指的是高速缓存简称缓存,原始意义是指访问速度比一般随机存取存储器(RAM)快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。 + +在CPU中,Cache是介于内存和处理器之间的缓存,用于存放CPU即将可能使用的数据,当CPU要使用的数据在Cache中取到时,便不再需要到内存去存取数据,从而减少处理器访问内存的次数。 +提供“缓存”的目的是为了让数据访问的速度适应CPU的处理速度,由于CPU的运行速度远快于存储器的存储速度,当处理器通过内存存取一次数据将耗费数十个时钟周期,而处理器访问Cache则只需要一个或者几个时钟周期,所以Cache的出现极大的减小了CPU运行速度和存储速度的差距。设想一下一个主频2GHz的4-way超标量处理器访问一个100ns的DRAM, 在访问一次DRAM时间内,处理器内部可以执行800条指令,这是不可以接受的,所以就有了和处理器一样工艺制造的L1Cache,紧密耦合在处理器内,得以让处理器能够直接访问Cache得到数据。虽然Cache能极大的提升处理器的性能,但是在一块芯片中,Cache的容量大小一般只有1KB到32KB,而这相较于内存存储的几个GB甚至TB的数据来说,Cache的容量是微乎其微的,Cache只能缓存内存的一小部分数据,但是Cache依然能够让处理器取到大部分需要的数据,其中的原理是内存中“程序执行与数据访问的局域性行为”,即一定程序执行时间和空间内,被访问的代码集中于一部分,所以Cache中缓存的数据都是被近期访问过数据或者可能会被访问的数据,这样就使得Cache的命中率大大提高。 + +![image](./Cache/fig.2.png) + +前面提到的Cache,称之为L1 Cache(第一级Cache)。我们在L1 Cache后面连接L2 Cache,在L2 Cache和主存之间连接L3 Cache。等级越高,速度越慢,容量越大。但是速度相比较主存而言,依然很快,但是本次一生一芯只需要完成L1Cache,且提供的大小暂定为8K,同时为了减小面积和改善后端物理设计结果,需要替换RAM。关于SoC对接要求请查看[第四期一生一芯计划仿真用SoC工程](https://github.com/OSCPU/ysyxSoC),在CheckList中会提到共享RAM的规格,其具体原语句可在这里查看[带写掩码的单口RAM模型](https://github.com/OSCPU/ysyxSoC/blob/ysyx4/ysyx/ram/S011HD1P_X32Y2D128_BW.v) +## 直接映射缓存(Direct mapped cache) +我们继续引入一些cache相关的名词。cache的大小称之为cache size,代表cache可以缓存最大数据的大小。我们将cache平均分成相等的很多块,每一个块大小称之为cache line,其大小是cache line size。例如一个64 Bytes大小的cache。如果我们将64 Bytes平均分成64块,那么cache line就是1字节,总共64行cache line。如果我们将64 Bytes平均分成8块,那么cache line就是8字节,总共8行cache line。现在的硬件设计中,一般cache line的大小是4-128 Bytes。为什么没有1 byte呢?原因我们后面讨论。 +我们假设下面的讲解都是针对64 Bytes大小的cache,并且cache line大小是8字节。我们可以类似把这块cache想想成一个数组,数组总共8个元素,每个元素大小是8字节。 + +![image](./Cache/fig.1.png) + + +现在我们考虑一个问题,CPU从0x0654地址读取一个字节,cache控制器是如何判断数据是否在cache中命中呢?cache大小相对于主存来说,可谓是小巫见大巫。所以cache肯定是只能缓存主存中极小一部分数据。我们如何根据地址在有限大小的cache中查找数据呢?现在硬件采取的做法是对地址进行散列(可以理解成地址取模操作)。我们接下来看看是如何做到的? + + + +![image](./Cache/fig.3.png) + +我们一共有8行cache line,cache line大小是8 Bytes。所以我们可以利用地址低3 bits(如上图地址红色部分)用来寻址8 bytes中某一字节,我们称这部分bit组合为offset。同理,8行cache line,为了覆盖所有行。我们需要3 bits(如上图地址蓝色部分)查找某一行,这部分地址部分称之为index。现在我们知道,如果两个不同的地址,其地址的bit3-bit5如果完全一样的话,那么这两个地址经过硬件散列之后都会找到同一个cache line。所以,当我们找到cache line之后,只代表我们访问的地址对应的数据可能存在这个cache line中,但是也有可能是其他地址对应的数据。所以,我们又引入tag array区域,tag array和data array一一对应。每一个cache line都对应唯一一个tag,tag中保存的是整个地址位宽去除index和offset使用的bit剩余部分(如上图地址绿色部分)。tag和index、offset三者组合就可以唯一确定一个地址了。因此,当我们根据地址中index位找到cache line后,取出当前cache line对应的tag,然后和地址中的tag进行比较,如果相等,这说明cache命中。如果不相等,说明当前cache line存储的是其他地址的数据,这就是cache缺失。在上述图中,我们看到tag的值是0x19,和地址中的tag部分相等,因此在本次访问会命中。由于tag的引入,因此解答了我们之前的一个疑问“为什么硬件cache line不做成一个字节?”。这样会导致硬件成本的上升,因为原本8个字节对应一个tag,现在需要8个tag,占用了很多内存。tag也是cache的一部分,但是我们谈到cache size的时候并不考虑tag占用的内存部分,我们在一生一芯中的tag是用寄存器reg来实现的。 + +我们可以从图中看到tag旁边还有一个valid bit,这个bit用来表示cache line中数据是否有效(例如:1代表有效;0代表无效)。当系统刚启动时,cache中的数据都应该是无效的,因为还没有缓存任何数据。cache控制器可以根据valid bit确认当前cache line数据是否有效。所以,上述比较tag确认cache line是否命中之前还会检查valid bit是否有效。只有在有效的情况下,比较tag才有意义。如果无效,直接判定cache缺失。 + +上面的例子中,cache size是64 Bytes并且cache line size是8 bytes。offset、index和tag分别使用3 bits、3 bits和26 bits(假设地址宽度是32 bits)。那么根据一生一芯提供的RAM,你的index、offset和tag应该如何划分呢? + +## 直接映射缓存的优缺点 +直接映射缓存在硬件设计上会更加简单,因此成本上也会较低。根据直接映射缓存的工作方式,我们可以画出主存地址0x00-0x88地址对应的cache分布图。 + + +![image](./Cache/fig.4.png) + + +我们可以看到,地址0x00-0x3f地址处对应的数据可以覆盖整个cache。0x40-0x7f地址的数据也同样是覆盖整个cache。我们现在思考一个问题,如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?首先我们应该明白0x00、0x40、0x80地址中index部分是一样的。因此,这3个地址对应的cache line是同一个。所以,当我们访问0x00地址时,cache会缺失,然后数据会从主存中加载到cache中第0行cache line。当我们访问0x40地址时,依然索引到cache中第0行cache line,由于此时cache line中存储的是地址0x00地址对应的数据,所以此时依然会cache缺失。然后从主存中加载0x40地址数据到第一行cache line中。同理,继续访问0x80地址,依然会cache缺失。这就相当于每次访问数据都要从主存中读取,所以cache的存在并没有对性能有什么提升。访问0x40地址时,就会把0x00地址缓存的数据替换。这种现象叫做cache颠簸(cache thrashing)。针对这个问题,我们引入多路组相连缓存。我们首先研究下最简单的两路组相连缓存的工作原理。 +## 两路组相连缓存(Two-way set associative cache) +我们依然假设64 Bytes cache size,cache line size是8 Bytes。什么是路(way)的概念。我们将cache平均分成多份,每一份就是一路。因此,两路组相连缓存就是将cache平均分成2份,每份32 Bytes。如下图所示。 + +![image](./Cache/fig.5.png) + + +cache被分成2路,每路包含4行cache line。我们将所有索引一样的cache line组合在一起称之为组。例如,上图中一个组有两个cache line,总共4个组。我们依然假设从地址0x0654地址读取一个字节数据。由于cache line size是8 Bytes,因此offset需要3 bits,这和之前直接映射缓存一样。不一样的地方是index,在两路组相连缓存中,index只需要2 bits,因为一路只有4行cache line。上面的例子根据index找到第2行cache line(从0开始计算),第2行对应2个cache line,分别对应way 0和way 1。因此index也可以称作set index(组索引)。先根据index找到set,然后将组内的所有cache line对应的tag取出来和地址中的tag部分对比,如果其中一个相等就意味着命中。 +因此,两路组相连缓存较直接映射缓存最大的差异就是:第一个地址对应的数据可以对应2个cache line,而直接映射缓存一个地址只对应一个cache line。那么这究竟有什么好处呢? +两路组相连缓存优缺点 +两路组相连缓存的硬件成本相对于直接映射缓存更高。因为其每次比较tag的时候需要比较多个cache line对应的tag(某些硬件可能还会做并行比较,增加比较速度,这就增加了硬件设计复杂度)。为什么我们还需要两路组相连缓存呢?因为其可以有助于降低cache颠簸可能性。那么是如何降低的呢?根据两路组相连缓存的工作方式,我们可以画出主存地址0x00-0x4f地址对应的cache分布图。 + +![image](./Cache/fig.6.png) + + +我们依然考虑直接映射缓存一节的问题“如果一个程序试图依次访问地址0x00、0x40、0x80,cache中的数据会发生什么呢?”。现在0x00地址的数据可以被加载到way 1,0x40可以被加载到way 0。这样是不是就在一定程度上避免了直接映射缓存的尴尬境地呢?在两路组相连缓存的情况下,0x00和0x40地址的数据都缓存在cache中。试想一下,如果我们是4路组相连缓存,后面继续访问0x80,也可能被被缓存。 +因此,当cache size一定的情况下,组相连缓存对性能的提升最差情况下也和直接映射缓存一样,在大部分情况下组相连缓存效果比直接映射缓存好。同时,其降低了cache颠簸的频率。从某种程度上来说,直接映射缓存是组相连缓存的一种特殊情况,每个组只有一个cache line而已。因此,直接映射缓存也可以称作单路组相连缓存。 + + +## 全相连缓存(Full associative cache) +既然组相连缓存那么好,如果所有的cache line都在一个组内。岂不是性能更好。是的,这种缓存就是全相连缓存。由于所有的cache line都在一个组内,因此地址中不需要set index部分。因为,只有一个组让你选择,间接来说就是你没得选。我们根据地址中的tag部分和所有的cache line对应的tag进行比较(硬件上可能并行比较也可能串行比较)。哪个tag比较相等,就意味着命中某个cache line。因此,在全相连缓存中,任意地址的数据可以缓存在任意的cache line中。所以,这可以最大程度的降低cache颠簸的频率,但是硬件成本上也是更高。 +## Cache分配策略(Cache allocation policy) +cache的分配策略是指我们什么情况下应该为数据分配cache line。cache分配策略分为读和写两种情况。 +读分配(read allocation) +当CPU读数据时,发生cache缺失,这种情况下都会分配一个cache line缓存从主存读取的数据。默认情况下,cache都支持读分配。 +## 写分配(write allocation) +当CPU写数据发生cache缺失时,才会考虑写分配策略。当我们不支持写分配的情况下,写指令只会更新主存数据,然后就结束了。当支持写分配的时候,我们首先从主存中加载数据到cache line中(相当于先做个读分配动作),然后会更新cache line中的数据。 +## Cache更新策略(Cache update policy) +cache更新策略是指当发生cache命中时,写操作应该如何更新数据。cache更新策略分成两种:写直通和回写。 +## 写直通(write through) +当CPU执行store指令并在cache命中时,我们更新cache中的数据并且更新主存中的数据。cache和主存的数据始终保持一致。 +## 写回(write back) +当CPU执行store指令并在cache命中时,我们只更新cache中的数据。并且每个cache line中会有一个bit位记录数据是否被修改过,称之为dirty bit。我们会将dirty bit置位。主存中的数据只会在cache line被替换或者显示的clean操作时更新。因此,主存中的数据可能是未修改的数据,而修改的数据躺在cache中。cache和主存的数据可能不一致。 + +同时思考个问题,为什么cache line大小是cache控制器和主存之间数据传输的最小单位呢?这也是因为每个cache line只有一个dirty bit。这一个dirty bit代表着整个cache line是否被修改的状态。 + +:::danger[实现你的I-Cache] +以上就是Cache基本知识介绍,其他更多更专业的知识(Cache流水化、替换算法、非阻塞Cache等...)需要你自己阅读相关书籍和资料,例如《超标量处理器设计》、《计算机体系结构量化研究方法》。 + + +下面我们来梳理一次数据是否命中对应的状态转化,当你的CPU发出一次读请求应该首先到Cache中进行数据读。根据读地址,你应该会通过index寻找到对应的tag和cache line,然后进行tag比对。如果tag相同则表示Cache命中(hit),那么直接将数据读回CPU。如果未命中,则说明Cache中并未缓存对应地址的数据,就必须通过总线(如果未实现可以通过DPI-C访问数组)去完成数据的读写,一般来说访问总线的延迟是不固定的,这个时候你必须暂停你的CPU。当读数据从总线返回时,先将读取的数据写入Cache中并更新tag内容,使Cache能够转入hit状态,然后将数据从Cache中读出发给CPU,并让CPU继续运行。这样就完成一次数据读取操作。其中控制CPU暂停和运行的信号由tag比对的结果决定,所以你需要完成一个Cache的状态机,你可以尝试构建你自己Cache的读写状态。对于初学者,建议先完成I-Cache(指令cache),再实现D-Cache(数据cache),即先完成读逻辑,再补充写逻辑。对于较为熟练的同学,可以直接实现D-Cache,然后屏蔽D-Cache的写端口实现I-Cache。下面是命中判断和状态转化的示意图: + +![image](./Cache/fig.7.png) + + +![image](./Cache/fig.8.png) +::: +## fence.i 指令 + 对于完成Cache的同学可以查看一生一芯SOC四期规范理解第一个程序加载过程,然后保持Cache一致性,来实现fence.i指令,当然这是后续接入SOC才需要实现,你也可以到最后在实现它。(助教提醒:思考一下你流水线里的访存指令是否需要冲刷) + + + 本文引用自 [知乎](https://zhuanlan.zhihu.com/p/102293437) + + +:::warning[实现Cache] +* 实现[缓存模拟器](http://jyywiki.cn/ICS/2021/labs/Lab4),参考[代码框架](https://github.com/NJU-ProjectN/ics-workbench/tree/lab4)。 +* 在NPC框架中实现Cache。 +* 进阶:参考[NEMU-Cache](https://www.bilibili.com/video/BV14A411b7XB?spm_id_from=333.999.0.0&vd_source=38024886289f4efc2c6167eacd5361b7)视频,在NEMU中添加Cache。 +* 运行仙剑奇侠传,验证自己的Cache。 +::: \ No newline at end of file diff --git a/zh/advanced/MultiplierDivider.md b/zh/advanced/MultiplierDivider.md new file mode 100644 index 0000000..61c9ee0 --- /dev/null +++ b/zh/advanced/MultiplierDivider.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 1 +--- +## 乘除法器 + +建设中 diff --git a/zh/advanced/MultiplierDividerFunctionalUnit.md b/zh/advanced/MultiplierDividerFunctionalUnit.md new file mode 100644 index 0000000..607bdd0 --- /dev/null +++ b/zh/advanced/MultiplierDividerFunctionalUnit.md @@ -0,0 +1,495 @@ +--- +sidebar_position: 8 +--- +## 乘除法功能单元 + +:::info[视频录播和课件] +* 录播 - [功能单元设计](https://www.bilibili.com/video/BV1ET411m7cZ/) +* 课件 - [功能单元设计](https://ysyx.oscc.cc/slides/2205/20.html#/) +::: +### 概述 + +出于面积和成本的限制,建议同学们乘除法器各实现两种,可以通过宏定义实现两种乘除法器的切换,一种是简单的移位乘除法器,一种是高性能的乘除法器。高性能是通过面积换时间,而且高性能往往比较复杂,建议大家有时间之后才考虑高性能。 + +这里建议采用握手方法处理乘除法,方便后续处理流水线,模块替换和总线相关的问题。 + +### 建议实现的模块接口 + +除法器模块信号 + +| 信号 | 方向 | 位宽 | 说明 | +| ---------- | ------ | ---- | ------------------------------------------------------------ | +| clock | input | 1 | 时钟信号 | +| reset | input | 1 | 复位信号(高有效) | +| dividend | input | xlen | 被除数( xlen 表示要实现的位数,ysyx 中是 64) | +| divisor | input | xlen | 除数 | +| div_valid | input | 1 | 为高表示输入的数据有效,如果没有新的除法输入,在除法被接受的下一个周期要置低 | +| divw | input | 1 | 为高表示输入的是 32 位除法 | +| div_signed | input | 1 | 表示是不是有符号除法,为高表示是有符号除法 | +| flush | input | 1 | 为高表示要取消除法(修改一下除法器状态就行) | +| div_ready | output | 1 | 为高表示除法器空闲,可以输入数据 | +| out_valid | output | 1 | 为高表示除法器输出了有效结果 | +| quotient | output | xlen | 商 | +| remainder | output | xlen | 余数 | + +乘法器端口信号 + +| 信号 | 方向 | 位宽 | 说明 | +| ------------ | ------ | ---- | ------------------------------------------------------------ | +| clock | input | 1 | 时钟信号 | +| reset | input | 1 | 复位信号(高有效) | +| mul_valid | input | 1 | 为高表示输入的数据有效,如果没有新的乘法输入,在乘法被接受的下一个周期要置低 | +| flush | input | 1 | 为高表示取消乘法 | +| mulw | input | 1 | 为高表示是 32 位乘法 | +| mul_signed | input | 2 | 2’b11(signed x signed);2’b10(signed x unsigned);2’b00(unsigned x unsigned); | +| multiplicand | input | xlen | 被乘数,xlen 表示乘法器位数 | +| multiplier | input | xlen | 乘数 | +| mul_ready | output | 1 | 为高表示乘法器准备好,表示可以输入数据 | +| out_valid | output | 1 | 为高表示乘法器输出的结果有效 | +| result_hi | output | xlen | 高 xlen bits 结果 | +| result_lo | output | xlen | 低 xlen bits 结果 | + +补充说明:一生一芯中 xlen 为 64 + +### 乘法器的实现 + +以两个32位数的乘法为例子 + +#### 一位移位乘法器(移位加)的实现 + +将乘法结果设置为零,并将被乘数存入一个 64 bits 寄存器的低 32 bits 中(非 GPR ),将乘数存入一个 32 bits 寄存器中。 + +在每个时钟周期判断乘数的最低位,如果是 1,将被乘数的加到结果中;如果乘数的最低位是 0,则不需要加入结果中。然后将乘数右移一位,被乘数左移一位。然后进入到下一个时钟周期 + +执行 32 次步骤,注意这是补码乘法,最高位为1代表的是负数,所以如果一开始的乘数高位是 1 的话,最后一步需要执行补码减法。最后的结果输出的时候,要把结果有效信号拉高。 + +##### 支持无符号乘法 + +我们不仅需要支持有符号乘法,还需要支持无符号乘法,那我们应该怎么支持无符号乘法呢? + +1. 分别实例化有符号乘法和无符号乘法模块? +2. 改造一下乘法器让其支持有符号和无符号? + +**思考**:这里到底两种方法到底**哪种更好**呢?到底是为什么呢? + +下面介绍改造乘法器的方法,我认为有两种比较直接的方法。 + +1. 把是不是有无符号的乘法的信息记录下来,根据这个信息决定最后一步到底是减法还是加法。 +2. 把 32bits 数额外扩展一位符号位,无符号数将这一位置零,有符号数就是根据符号位来扩展。这样可以将无符号数转化成有符号数了。 + +##### 关于测试 + +强烈建议大家先单独对乘法模块进行随机测试,功能验证正确后才将乘法器接入到你流水线中。 + +#### booth两位移位乘法器 + +Booth乘法器由英国的Booth夫妇提出的,上面的补码乘法器,需要特别地挑出最后一个部分积进行特别处理,有点复杂。而且要计算一个乘法需要 33 个周期(32bits),可以对补码乘法公式进行变换,可以找出更合适硬件实现的乘法。 + +Booth一位变换,以 8 bits 数为例 + +$$ +\begin{align} +& +- y_7 * 2 ^ 7 ++ y_6 * 2 ^ 6 ++ y_5 * 2 ^ 5 ++ y_4 * 2 ^ 4 ++ y_3 * 2 ^ 3 ++ y_2 * 2 ^ 2 ++ y_1 * 2 ^ 1 ++ y_0 * 2 ^ 0 +\nonumber \\ += & +- y_7 * 2 ^ 7 +- y_6 * 2 ^ 6 +- y_5 * 2 ^ 5 +- y_4 * 2 ^ 4 +- y_3 * 2 ^ 3 +- y_2 * 2 ^ 2 +- y_1 * 2 ^ 1 +- y_0 * 2 ^ 0 +\nonumber \\ +& ++ y_6 * 2 ^ 7 ++ y_5 * 2 ^ 6 ++ y_4 * 2 ^ 5 ++ y_3 * 2 ^ 4 ++ y_2 * 2 ^ 3 ++ y_1 * 2 ^ 2 ++ y_0 * 2 ^ 1 +\nonumber \\ += & + (y_6 - y_7) * 2 ^ 7 ++ (y_5 - y_6) * 2 ^ 6 ++ (y_4 - y_5) * 2 ^ 5 ++ (y_3 - y_4) * 2 ^ 4 ++ (y_2 - y_3) * 2 ^ 3 ++ (y_1 - y_2) * 2 ^ 2 ++ (y_0 - y_1) * 2 ^ 1 ++ (y_{-1} - y_0) * 2 ^ 0 +\end{align} +$$ + +其中 $y_{-1}$ 为 0,经过变换后,公式变得更加工整,更加适合硬件的实现,新公式称为 booth 一位乘算法。 + +booth一位乘法需要根据乘数的最低两位才能确定如何将被乘数加到结果中,根据公式可以得出如下规则。 + +(注意:需要在乘数最低位(最右侧)添零) + +| $y_i$ | $y_{i-1}$ | 操作 | +| ----- | --------- | ------------------------ | +| 0 | 0 | 不需要加(+0) | +| 0 | 1 | 补码加X($+[X]_补\quad$) | +| 1 | 0 | 补码减X($-[X]_补\quad$) | +| 1 | 1 | 不需要加(+0) | + +Booth 一位乘法的补码加法器,面积较大,电路延迟较长,限制了硬件乘法器的速度,对补码乘法公式进行变换,得到如下的 booth 两位乘算法(以 8bits 数为例)。 + +$$ +\begin{align} +& +- y_7 * 2 ^ 7 ++ y_6 * 2 ^ 6 ++ y_5 * 2 ^ 5 ++ y_4 * 2 ^ 4 ++ y_3 * 2 ^ 3 ++ y_2 * 2 ^ 2 ++ y_1 * 2 ^ 1 ++ y_0 * 2 ^ 0 +\\ = & +- 2 * y_7 * 2 ^ 6 ++ y_6 * 2 ^ 6 ++ y_5 * 2 ^ 6 +- 2 * y_5 * 2 ^ 4 ++ y_4 * 2 ^ 4 ++ y_3 * 2 ^ 4 +\\ & +- 2 * y_3*y2 ++ y_2 * 2 ^ 2 ++ y_1 * 2 ^ 2 +- 2 * y_1 * 2 ^ 0 ++ y_0 * 2 ^ 0 ++ y_{-1} * 2 ^ 0 +\\ = & +(y_5 + y_6 - 2 * y_7) * 2 ^ 6 ++ (y_3 + y_4 - 2 * y_5) * 2 ^ 4 ++ (y_1 + y_2 - 2 * y_3) * 2 ^ 2 ++ (y_{-1} + y_0 - 2 * y_1) * 2 ^ 0 +\end{align} +$$ + +根据 Booth 两位乘算法,需要**每次扫描 3 位**的乘数,并在每次累加完成后,将被乘数和乘数移 2 位。根据算法公式,可以推导出操作的方式。注意被扫描的 3 位是当前操作阶数 i 加 上其左右各 1 位。 (举例子,如果是 8 位的数字,起始位为 0 位。那么 i 就为0,2,4,6) + +#### booth两位运算规则 + +| $y_{i+1}$ | $y_i$ | $y_{i-1}$ | 操作 | +| --------- | ----- | --------- | ------------------------------ | +| 0 | 0 | 0 | 不需要加(+0) | +| 0 | 0 | 1 | 补码加 X($+[X]_补\quad$) | +| 0 | 1 | 0 | 补码加 X($+[X]_补\quad$) | +| 0 | 1 | 1 | 补码加 2X($+[X]_补\quad$左移) | +| 1 | 0 | 0 | 补码减 2X($-[X]_补\quad$左移) | +| 1 | 0 | 1 | 补码减 X($-[X]_补\quad$) | +| 1 | 1 | 0 | 补码减 X($-[X]_补\quad$) | +| 1 | 1 | 1 | 不需要加(+0) | + + Booth 乘法的核心是部分积的生成,共需要生成 N/2 个部分积。每个部分积与 [X]补 相关,总共 有 ‑X、‑2X、+X、+2X 和 0 五种可能,而其中减去 [X]补 的操作,可以视作加上按位取反的 [X]补 再末位加 1。 + +假设$[X]_补\quad$的二进制格式可以写成$x_7x_6x_5x_4x_3x_2x_1x_0$假设部分积P等于$p_7p_6p_5p_4p_3p_2p_1p_0+c\quad$可以有如下情况 + +| | | 选择 | +| ------- | ---------- | --------- | +| $p_i$== | ~$x_i$ | 选择 -x | +| | ~$x_{i-1}$ | 选择 -2x | +| | $x_i$ | 选择 +x | +| | $x_{i-1}$ | 选择 +2x | +| | 0 | 选择 0 | + +当部分积的选择为 2X 时,可以视作 X 输入左移 1 位,此时 $p_i$就与 $x_{i-1}$ 相等。如果部分积的选择 是 ‑X 或者 ‑2X,则此处对 $x_i$ 或者 $x_{i-1}$ 取反,并设置最后的末位进位 c 为 1。 + +#### 结果选择逻辑 + +根据卡诺图分析可以得到每一位 $p_i$ 的表达式 + +$ +p_i = \neg \left[\begin{array}{l} + \neg (S_{-x} \wedge \neg x_i) \\ +\wedge \neg (S_{-2x} \wedge \neg x_{i-1}) \\ +\wedge \neg (S_{+x} \wedge x_i) \\ +\wedge \neg (S_{2x} \wedge x_{i-1}) +\end{array}\right] +$ + +```verilog +assign p = ~(~(sel_negative & ~x) & ~(sel_double_negative & ~x_sub) + & ~(sel_positive & x ) & ~(sel_double_positive & x_sub)); +``` + +#### booth选择信号的生成 + +```verilog +///y+1,y,y-1/// +wire y_add,y,y_sub; +wire sel_negative,sel_double_negative,sel_positive,sel_double_positive; + +assign {y_add,y,y_sub} = src; + +assign sel_negative = y_add & (y & ~y_sub | ~y & y_sub); +assign sel_positive = ~y_add & (y & ~y_sub | ~y & y_sub); +assign sel_double_negative = y_add & ~y & ~y_sub; +assign sel_double_positive = ~y_add & y & y_sub; +``` + +#### Booth部分积生成模块 + +把上面的结果选择模块和booth信号生成模块封装成一个模块之后你就有了一个部分积生成模块。 + +建议大家把上面的两个部分别都封装成一个模块,最后再把部分积模块也封装成一个模块。这样你就有了一个部分积生成模块。 + +#### 移位加实现booth两位乘法(以32bits数为例) + +同时支持无符号和有符号乘法 + +:::info[思考] + +1.有符号乘法与无符号乘法有什么区别? + +2.怎么让一个乘法模块同时支持有符号和无符号乘法? + +3.为什么要把 32 bits 数转换为 34 bits数? 转换成 33bits 数行吗? +::: + + + +1. 乘法开始的时候需要在乘数的最右边补一个零,把结果全部置零。 +2. 将乘数的最低 3bits 和被乘数的 68bits 输入到部分积生成模块,把部分积生成模块生成的数与结果用 68bits 加法器相加,并把和暂存到结果寄存器中,并把乘数右移两位,把被乘数左移两位。 +3. 重复上面的步骤,直至 34bits 的乘数全部为零。 + +![image](./Cache/2.8_booth.png) + +注意:由于两位 booth 每次需要扫描三位乘数,此时不能只扩展一位符号位,只能扩展两位符号位。 + +#### 华莱士树 X booth (以32bits乘法为例) + +即使采用了 Booth 两位乘法,使用移位加策略来完成一个 64 位乘法,也需要 32 个时钟周期,并且还不支持流水。由于乘法具有完整的交换律,可以通过面积换时间的方式,将各个部分积并行的加在一起,而非串行迭代累加。 + +:::info[思考] +1.乘法和除法有什么不同? + +2.除法器有没有类似于华莱士树一样的能完全并行的计算方法? +::: +##### 缺少的模块 + +我们之前已经完成了 booth两位 的部分积模块。所以我们目前只差两个关键模块。 + +**第一个switch模块** + +其作用类似于矩阵的转置,在 32bits(扩展成 34bits )乘法中是将 17 个 68bits 部分积转换成 68 个 17bits 数,其实这个模块就是纯连线没有额外的元件。 + +**第二个模块是17bits华莱士树。** + +17bits 华莱士树共有6层,使用了15个全加器。 + +![image](./Cache/2.8_walloc_tree.png) + +```verilog +//一位全加器 +module csa( + input [2:0] in, + output cout,s + +); +wire a,b,cin; +assign a=in[2]; +assign b=in[1]; +assign cin=in[0]; +assign s = a ^ b ^ cin; +assign cout = a & b | b & cin | a & cin; +endmodule +``` + +| 信号 | 方向 | 描述 | +| ---------- | ------ | ------------------------------------------------------------ | +| src_in | input | 输入的 17bits 数据 | +| cin | input | 来自右边华莱士树的进位输入,最右边的华莱士树的 cin 是来自 switch 模块 | +| cout_group | output | 输入到左边的华莱士树的进位输出,最左边的华莱士树的忽略即可 | +| cout | output | 输出到加法器的 src1 | +| s | output | 输出到加法器的 src2 | + + + +```verilog +module walloc_17bits( + input [16:0] src_in, + input [13:0] cin, + output [13:0] cout_group, + output cout,s +); +wire [13:0] c; +///////////////first//////////////// +wire [4:0] first_s; +csa csa0 (.in (src_in[16:14]), .cout (c[4]), .s (first_s[4]) ); +csa csa1 (.in (src_in[13:11]), .cout (c[3]), .s (first_s[3]) ); +csa csa2 (.in (src_in[10:08]), .cout (c[2]), .s (first_s[2]) ); +csa csa3 (.in (src_in[07:05]), .cout (c[1]), .s (first_s[1]) ); +csa csa4 (.in (src_in[04:02]), .cout (c[0]), .s (first_s[0]) ); + +///////////////secnod////////////// +wire [3:0] secnod_s; +csa csa5 (.in ({first_s[4:2]} ), .cout (c[8]), .s (secnod_s[3])); +csa csa6 (.in ({first_s[1:0],src_in[1]} ), .cout (c[7]), .s (secnod_s[2])); +csa csa7 (.in ({src_in[0],cin[4:3]} ), .cout (c[6]), .s (secnod_s[1])); +csa csa8 (.in ({cin[2:0]} ), .cout (c[5]), .s (secnod_s[0])); + +//////////////thrid//////////////// +wire [1:0] thrid_s; +csa csa9 (.in (secnod_s[3:1] ), .cout (c[10]), .s (thrid_s[1])); +csa csaA (.in ({secnod_s[0],cin[6:5]} ), .cout (c[09]), .s (thrid_s[0])); + +//////////////fourth//////////////// +wire [1:0] fourth_s; + +csa csaB (.in ({thrid_s[1:0],cin[10]} ), .cout (c[12]), .s (fourth_s[1])); +csa csaC (.in ({cin[9:7] }), .cout (c[11]), .s (fourth_s[0])); + +//////////////fifth///////////////// +wire fifth_s; + +csa csaD (.in ({fourth_s[1:0],cin[11]}), .cout (c[13]), .s (fifth_s)); + +///////////////sixth/////////////// +csa csaE (.in ({fifth_s,cin[13:12]} ), .cout (cout), .s (s)); + +///////////////output/////////////// +assign cout_group = c; +endmodule +``` + +注意:最左边的华莱士树的 cout 不接入到最后的加法器的 src1 中,src1 的最低位来自 switch 的输出,然后剩下的高67bits,来自除了最左边华莱士树的的cout. + +下面是 32 bits walloc X booth的示意图 + +![image](./Cache/2.8_walloc_X_booth.png) + +#### 时序问题 + +上面的移位加乘法每个周期只需计算部分内容,移位加乘法器基本不会成为处理器的关键路径,所以暂时不需要考虑这部分的时序问题,处理器的瓶颈主要是在访存(包括取指),大家可以先考虑优化访存部件。 + +现代处理器通常可以实现全流水、4 个时钟周期延迟的定点乘法指令。如果使 walloc X booth 乘法器单周期输出结果,会极大的拖累处理器的频率,我建议大家该乘法器进行流水切分。其实流水切分也很简单,只需要向流水线处理器那样插入级间寄存器,并稍微加入几个握手的控制信号即可。 + +以上参考资料[《计算机体系结构基础(第三版》](https://foxsen.github.io/archbase/运算器设计.html#定点补码乘法器),《CPU设计实战》 + +### 除法器实现 + +由于除法不具备完全交换律,并且除法也往往不是处理器性能的瓶颈。这里建议大家先实现一个radix-2除法器就行。如果想实现radix-4和radix-16除法器,可以参考 [香山](https://github.com/OpenXiangShan/XS-Verilog-Library/tree/main/int_div_radix_4_v1) 或者这篇 [论文](https://ieeexplore.ieee.org/document/1432667) 。 + +#### 一位恢复余数绝对值迭代除法器 + +除法器依据是否将源操作数转换为绝对值分成绝对值除法器和补码除法器。绝对值除法器最后得到的是商和余数的绝对值,最后需要计算一下商和余数的补码;补码除法器最后的结果是补码,但是存在多减多除的情况,需要对余数进行调整。 + +简单的迭代除法是试商法。 依据迭代过程中,在不够减时(商为 0),是否恢复余数分为:恢复余数法(循环减法),不恢复余数法(加减交替)。 + +##### 迭代主要步骤和操作 + +本除法器为一位恢复余数绝对值迭代除法器。运算主要有三大步。 + +###### (一) 根据被除数和除数确定商和余数的符号,并计算除数和被除数的绝对值 + +| 被除数 | 除数 | 商 | 余数 | +| :----: | ---- | ---- | ---- | +| 正 | 正 | 正 | 正 | +| 正 | 负 | 负 | 正 | +| 负 | 正 | 负 | 负 | +| 负 | 负 | 正 | 负 | + +对于无符号数,计算机存的数就是其源码,因此绝对值不需要变换。对于有符号正数,存的是补码但是其补码等于源码,对于有符号数负数,存的是补码,对齐取反加一后可得到绝对值。总结:只有有符号负数需要调整。 + +###### (二) 迭代运算得到商和余数的绝对值 + +1. **将被除数前面补32个0, 记为A[63:0],记除数为B[31:0],得到的商记为S[31:0], 余数即为 R[31:0] .** +2. **第一次迭代,取 A 的高 33 位,即 A[63:31],与 B 高位补 0 的结果\{1'b0,B[31:0]\}做减法:如 果结果为负数,则商的相应位(S[31])为 0,被除数保持不变;如果结果为正数,则商的相应 位记为 1,将被除数的相应位(A[63:31])更新为减法的结果。** +3. **随后进行第二次迭代,此时就需要取 A[62:30]与\{1'b0,B[31:0]\}作减法,依据结果更新 S[30], 并更新 A[62:30]。** +4. **依此类推,直到算完第 0 位。** + +第3点中,依次取不同位置的33位数,在硬件上不太方便实现,可以考虑每周期把被除数的寄存器左移一位,由于移动的位数固定,相当于直接用连线实现。 + +###### (三) 调整最终商和余数 + +需要根据上面的表格进行调整,有符号数的绝对值转换为有符号数 + +此文章参考《CPU设计实战》第五章 + + + +:::warning[在NPC中实现可配置的乘除法器] +* 运行仙剑奇侠传,验证自己的乘除法器。 +::: +### 关于直接写 ”*“ 和 ”/“ + +Q: 可以直接写 ”*“ 和 ”/“ 吗 ? + +A: 这个是可以的,建议在搭建单周期 NPC 、流水框架和验证乘除法模块功能的时候使用。最后提交用于流片的代码中就不能直接写 ”*“ 和 ”/“。 + +Q: 验证乘除法模块的怎么使用 ”*“ 和 ”/“ ? + +A: 如果你写的是 test bench的话,可以就像在C语言中直接用使用即可,这里有 32 bits 的[除法模块的test bench]( https://github.com/markaulunGH/alu/blob/master/div_model/base_div/div_tb.v) 和 32 bits 的 [乘法模块的 test bench](https://github.com/markaulunGH/alu/blob/master/mul_model/base_mul/mul_tb.v) + +### 多周期乘除法器接入流水线 + +由于部分同学是新接触流水暂停,在这里给出一点接入的小建议,如果同学们有更好更通俗易懂的方法也欢迎在我们的 [讨论区](https://ysyx.oscc.cc/login?from=%2Fredirect%2Fforum%2F) 讨论。 + +多周期除法器不能在一个周期内计算出结果,在接入顺序流水后,在除法结果未计算完成的时候需要将流水线暂停。 + +#### 除法器模块部分控制信号 + +| 信号 | 方向 | 位宽 | 说明 | +| --------- | ------ | ---- | ------------------------------------------------------------ | +| div_valid | input | 1 | 为高表示输入的数据有效,如果没有新的除法输入,在除法被接受的下一个周期要置低 | +| div_ready | output | 1 | 为高表示除法器空闲,可以输入数据 | +| out_valid | output | 1 | 为高表示除法器输出了有效结果 | + +#### 除法器模块在流水线中的位置 + +![](./Cache/2.8_div_module.png) + +#### ALU中增加的信号 + +| 信号 | 类型 | 位宽 | 说明 | +| --------- | ---- | ---- | ---------------------------------------------------------- | +| div_valid | wire | 1 | 输入除法器的 div_valid 信号 | +| dv_ready | wire | 1 | 来自除法器的 div_ready 信号 | +| out_valid | wire | 1 | 来自除法器的 out_valid 信号 | +| div_doing | reg | 1 | 为高表示除法器正在计算 | +| op_div | wire | 1 | 来自 ID 与 EXE 的级间寄存器输出,为高表示 EXE 的指令是除法 | +| alu_busy | wire | 1 | 表示 ALU 正忙,需要阻塞流水线 | + +````verilog +/*除法器接受数据之后到下一个除法到来之前,div_valid都应该是零*/ +assign div_valid = op_div && !div_doing && !out_valid; +/*在除法期间,out_valid 不为高表示计算结果还为得出,ALU正忙,需要阻塞流水*/ +assign alu_busy = op_div && !out_valid; +```` + +```verilog +always @(posedge clk) begin + if (reset) begin + div_doing <= 1'b0; + end + /*除法结果输出后需要将div_doing置零*/ + else if (out_valid) begin + div_doing <= 1'b0; + end + /*握手成功后,也就是除法器接受输入的数据后需要把div_doing置高*/ + else if(div_valid && div_ready) begin + div_doing <= 1'b1; + end +end +``` + +上面就是一个简单的控制逻辑,建议将除法器和ALU封装成上图中的模块,这样层次比较分明,便于实现和维护。 + +#### 更多问题 + +1.`out_valid`为高时到底会维持几个周期呢?如果只维持一个周期(我也是比较建议是维持一个周期),就需要考虑当除法计算完成并把结果输出完毕之后,流水级的`MEM`和`WB`模块在某些情况下也会把流水线暂停的情况,这个时候如果不进行处理的话,会导致严重的错误。那么我们该怎么处理呢?这个也比较简单,我们需要增加一些寄存器把除法结果和除法已经计算完成的信息存储下来,并修改对应的`alu_busy`等信号就可以实现了。 + +2.多周期乘除法器可不可以采用非握手方法接入流水线? diff --git a/zh/advanced/PerfProfiler.md b/zh/advanced/PerfProfiler.md new file mode 100644 index 0000000..8210645 --- /dev/null +++ b/zh/advanced/PerfProfiler.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 6 +--- +## 性能计数器 + +在基础阶段你已经使用过基础的性能评测工具,如马里奥的FPS,NEMU运行得到的IPC数据,本节你需要了解有哪些性能评测工具,如何使用这些性能评测工具。我们在am-kernels中已经提供了coremark、dhrystone、microbench,了解三者的异同。不仅仅局限于上面已知的性能评测方式,你还需要了解更多相关内容,并且需要进行性能调优的挑战。 + +:::info[性能评测相关资料] +* **课件资料** + * [计算机系统性能评价指标](https://foxsen.github.io/archbase/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E6%80%A7%E8%83%BD%E8%AF%84%E4%BB%B7%E4%B8%8E%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.html#%E6%B5%8B%E8%AF%95%E7%A8%8B%E5%BA%8F%E9%9B%86) + * [测试程序集](https://foxsen.github.io/archbase/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E6%80%A7%E8%83%BD%E8%AF%84%E4%BB%B7%E4%B8%8E%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.html#%E6%B5%8B%E8%AF%95%E7%A8%8B%E5%BA%8F%E9%9B%86) + * [性能分析方法](https://foxsen.github.io/archbase/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E6%80%A7%E8%83%BD%E8%AF%84%E4%BB%B7%E4%B8%8E%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.html#%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E6%96%B9%E6%B3%95) + * [性能测试和分析实例](https://foxsen.github.io/archbase/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E6%80%A7%E8%83%BD%E8%AF%84%E4%BB%B7%E4%B8%8E%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.html#%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95%E5%92%8C%E5%88%86%E6%9E%90%E5%AE%9E%E4%BE%8B) + * An Overview of Common Benchmarks论文 +::: + +:::warning[完成以下课后习题 (参考北京大学体系结构实习课程和JYY老师课程)] +* 使用Linux下的剖视工具(例如 `gprof`)对 `dhrystone`和 `whetstone`进行剖视,参考论文Table 1形式给出数据,你的结果和该论文是否一致,为什么? +* 对dhrystone代码做少量修改,使其运行结果不变但“性能”提升。 +* 采用dhrystone进行评测有哪些可改进的地方?对其做出修改、评测和说明。 +* 在linux下使用编译器分别采用-O0、-O2、-O3选项对whetstone程序进行编译并执行,记录评测结果。 +* 进一步改进whetstone程序性能(例如新的编译选项),用实验结果回答。 +* 了解SPEC CPU2000 +* [性能调优实验](http://jyywiki.cn/ICS/2021/labs/Lab3), [代码框架](https://github.com/NJU-ProjectN/ics-workbench/tree/lab3/perftune) +::: diff --git a/zh/advanced/PipelinedProcessor.md b/zh/advanced/PipelinedProcessor.md new file mode 100644 index 0000000..e4b87ff --- /dev/null +++ b/zh/advanced/PipelinedProcessor.md @@ -0,0 +1,99 @@ +--- +sidebar_position: 9 +--- +## 流水线处理器 + +:::info[视频录播和课件] +* 录播 - [流水线处理器](https://www.bilibili.com/video/BV1zM411t7Q3/) +* 课件 - [流水线处理器](https://ysyx.oscc.cc/slides/2205/21.html#/) +::: +在此之前,你已经实现了单周期处理器,为了提高主频我们需要实现处理器的流水化,以下将提供五级流水线的实现思路与实现步骤作为参考。 + +流水线的实现划分为以下几个小阶段: +* 实现不考虑相关性冲突,不考虑跳转指令的流水线 +* 考虑跳转指令 +* 解决指令相关性冲突 +* 在流水线中添加更多部件 + +如果你从来没有接触过流水线的实现,可以根据上面提到的几个小阶段来逐步实现流水线,避免在同一时间产生大量bug。如果你已经接触过流水线了,你可以按照自己的思路来做,并在实现流水线后跑通之前的所有测试。 + +## 介绍流水线的基本概念 + +:::info[参考资料] +* 《CPU设计实战》第四章。 +* 《计算机组成与设计 硬件软件接口(第五版)》第四章中关于流水线的部分。 +* [Build your pc](https://zanpu.spencerwoo.com/3_Pipelining/3-3_Hazards.html#data-hazard-%E2%80%94%E2%80%94-%E6%95%B0%E6%8D%AE%E5%86%B2%E7%AA%81) +* 《计算机体系结构-量化研究方法(第六版)》附录c。 +::: + +## 实现不考虑相关性冲突,不考虑跳转指令的流水线 + +五级流水线将处理器划分为五个部分:取指级、译码级、执行级、访存级、写回级。每个流水线的功能如下: +* 取指级(IF):取指令,输出next_pc。 +* 译码级(ID):译码,分配功能,获取源操作数。 +* 执行级(EXE):执行指令,计算结果。 +* 访存级(MEM):执行内存读写指令。 +* 写回级(WB):写回结果。 + +我们知道将电路流水线化的初衷是缩短时序器件之间组合逻辑关键路径的时延,在不降低电路处理吞吐率的情况下提升电路的时钟频率。从电路设计最终的实现形式来看,是将一段组合逻辑按照功能划分为若干阶段,在各功能阶段的组合逻辑之间插入时序器件(通常是寄存器),前一阶段的组合逻辑输出接入时序器件的输入,后一阶段的组合逻辑输入来自这些时序器件的输出。 + +同样处理器的每一级流水线之间也要插入寄存器来保持级间数据,这些寄存器称为流水线寄存器,这里给出一个流水线寄存器的命名规则:取指级和译码级之间的流水线寄存器记作“ID reg”,其他的同理。 + +流水线寄存器保存的内容分为两类,控制内容和数据内容。控制内容只有一个1比特的valid信号,用于表示当前流水线寄存器中保存的值是否为有效的。数据内容需要根据各个流水级的输出信号来考虑,为了方便debug这里建议将PC和INST加入到各级流水线寄存器的数据内容中。另外,流水线寄存器中的所有寄存器都要定义为带有enable的寄存器(当enable为true时寄存器的输入有效,否则寄存器保持原值),这在流水线的阻塞中将会用到。 + +:::warning[实现不考虑相关性冲突,不考虑跳转指令的流水线] +我们先考虑最简单的情况,也就是没有跳转指令,且没有指令相关性冲突的情况。 +* 考虑各级流水线寄存器需要保持哪些数据内容。 +* 编写rtl代码实现最简单的流水线。 +* 写一个简单的测试用例来看看流水线的实现是否正确。(测试用例中不包括跳转指令,且不存在指令相关性冲突) +::: +## 实现考虑跳转指令的流水线 + +现在需要考虑跳转指令了。跳转指令的计算可以放到译码级(也就是说在译码级就计算出是否需要跳转以及跳转的地址),然后将跳转信号连接到取指级让其生成正确的next_pc。 + +:::warning[实现考虑跳转指令的流水线] +* 在流水线中正确处理跳转指令。 +* 写一个简单的测试用例来看看跳转指令是否能被正确执行。(测试用例中不存在指令相关性冲突) +::: +## 解决指令相关性冲突 + +指令相关性冲突有三种RAW、WAR和WAW,由于现在的流水线是顺序执行的,所以只会出现RAW冲突(read after write,写后读冲突)。 + +关于相关性冲突的原理可以参考《计算机组成与设计 硬件软件接口(第五版)》中4.7章的内容。以下是一个RAW冲突的例子,后四条指令需要在第一条指令完成写入后才能正确读出$2的值。 + +![image](./pipeline/fig.1.png) + +要解决指令相关性冲突的问题首先要检测流水线中是否存在指令相关性冲突。我们需要在译码级检查当前的流水线是否存在指令相关性冲突,更具体一点,我们需要依次检查处于执行级的指令、处于访存级的指令、处于写回级的指令是否与处于译码级的指令发生相关性冲突。 + +在检测到流水线存在指令相关性冲突后,我们要如何解决这个问题呢?答案是流水线阻塞。这里介绍一个流水线阻塞的实现思路。假设我们要阻塞执行级,也是说执行级、译码级和取指级“停住”,访存级和写回级可以继续“流动”。要实现这个效果需要: +* MEM reg的valid的输入为false,其他级的流水线寄存器的valid的输入为上一级的流水线寄存器的valid的输出。 +* ID reg和EXE reg的enable置为false,MEM reg和WB reg的enable置为true。 + +如果你对此感到疑惑,你可以通过打草稿的方式来进行验证。回到最开始的问题,我们如何解决指令相关性冲突?答案是当检测到存在指令相关性冲突时我们需要阻塞译码级直到相关性冲突消失。 + +:::warning[解决指令相关性冲突] +* 实现指令相关性冲突的检测电路。 +* 实现流水线的阻塞。 +* 跑通所有cpu_test测试用例。 +::: +另外,你也可以为每一个流水线寄存器添加一个名为flush的控制端口,当flush为true时,流水线寄存器将在下一个时钟上升沿清空该流水线寄存器中保存的值。 + +## 在流水线中添加更多部件 + +你已经有了流水线的知识基础,并学会了如何处理跳转指令和阻塞流水线。现在带着这些学到的知识、思路和技巧来为流水线添加更多的部件吧。 + +:::warning[为流水线添加乘法器和除法器] +* 在流水线中添加乘法器和除法器。 +* 跑通所有测试用例。 +* 运行仙剑奇侠传,验证自己的乘除法器。 +::: +:::warning[为流水线添加中断异常机制。] +* 在流水线中添加中断异常机制。 +* 跑通所有测试用例。 +* 运行仙剑奇侠传,验证自己的中断异常机制。 +::: +:::warning[为流水线添加总线和Cache。] +* 在流水线中添加总线和Cache。 +* 跑通所有测试用例。 +* 运行仙剑奇侠传,验证自己的总线和Cache。 +::: diff --git a/zh/advanced/SoC/1.jpg b/zh/advanced/SoC/1.jpg new file mode 100644 index 0000000..86e202a Binary files /dev/null and b/zh/advanced/SoC/1.jpg differ diff --git a/zh/advanced/SoC/10.jpg b/zh/advanced/SoC/10.jpg new file mode 100644 index 0000000..c2febf6 Binary files /dev/null and b/zh/advanced/SoC/10.jpg differ diff --git a/zh/advanced/SoC/11.jpg b/zh/advanced/SoC/11.jpg new file mode 100644 index 0000000..1b92d5f Binary files /dev/null and b/zh/advanced/SoC/11.jpg differ diff --git a/zh/advanced/SoC/12.jpg b/zh/advanced/SoC/12.jpg new file mode 100644 index 0000000..3a51234 Binary files /dev/null and b/zh/advanced/SoC/12.jpg differ diff --git a/zh/advanced/SoC/13.jpg b/zh/advanced/SoC/13.jpg new file mode 100644 index 0000000..edba2a3 Binary files /dev/null and b/zh/advanced/SoC/13.jpg differ diff --git a/zh/advanced/SoC/14.jpg b/zh/advanced/SoC/14.jpg new file mode 100644 index 0000000..6815ee6 Binary files /dev/null and b/zh/advanced/SoC/14.jpg differ diff --git a/zh/advanced/SoC/15.jpg b/zh/advanced/SoC/15.jpg new file mode 100644 index 0000000..4c78f7d Binary files /dev/null and b/zh/advanced/SoC/15.jpg differ diff --git a/zh/advanced/SoC/16.jpg b/zh/advanced/SoC/16.jpg new file mode 100644 index 0000000..44f9919 Binary files /dev/null and b/zh/advanced/SoC/16.jpg differ diff --git a/zh/advanced/SoC/17.jpg b/zh/advanced/SoC/17.jpg new file mode 100644 index 0000000..fe2072f Binary files /dev/null and b/zh/advanced/SoC/17.jpg differ diff --git a/zh/advanced/SoC/2.jpg b/zh/advanced/SoC/2.jpg new file mode 100644 index 0000000..9dd2425 Binary files /dev/null and b/zh/advanced/SoC/2.jpg differ diff --git a/zh/advanced/SoC/3.jpg b/zh/advanced/SoC/3.jpg new file mode 100644 index 0000000..035f6c3 Binary files /dev/null and b/zh/advanced/SoC/3.jpg differ diff --git a/zh/advanced/SoC/4.jpg b/zh/advanced/SoC/4.jpg new file mode 100644 index 0000000..6cd6ae0 Binary files /dev/null and b/zh/advanced/SoC/4.jpg differ diff --git a/zh/advanced/SoC/5.jpg b/zh/advanced/SoC/5.jpg new file mode 100644 index 0000000..e002e2d Binary files /dev/null and b/zh/advanced/SoC/5.jpg differ diff --git a/zh/advanced/SoC/6.jpg b/zh/advanced/SoC/6.jpg new file mode 100644 index 0000000..a82bf24 Binary files /dev/null and b/zh/advanced/SoC/6.jpg differ diff --git a/zh/advanced/SoC/7.jpg b/zh/advanced/SoC/7.jpg new file mode 100644 index 0000000..2b25e70 Binary files /dev/null and b/zh/advanced/SoC/7.jpg differ diff --git a/zh/advanced/SoC/8.jpg b/zh/advanced/SoC/8.jpg new file mode 100644 index 0000000..9b1d90b Binary files /dev/null and b/zh/advanced/SoC/8.jpg differ diff --git a/zh/advanced/SoC/9.jpg b/zh/advanced/SoC/9.jpg new file mode 100644 index 0000000..bb20618 Binary files /dev/null and b/zh/advanced/SoC/9.jpg differ diff --git a/zh/advanced/SoCComputerSystem.md b/zh/advanced/SoCComputerSystem.md new file mode 100644 index 0000000..1779a29 --- /dev/null +++ b/zh/advanced/SoCComputerSystem.md @@ -0,0 +1,206 @@ +--- +sidebar_position: 5 +--- +## SoC计算机系统 + +:::info[视频录播和课件] +* 录播 - [SoC计算机系统](https://www.bilibili.com/video/BV1ZK41117MB/) +* 课件 - [SoC计算机系统](https://ysyx.oscc.cc/slides/2205/18.html#/) +::: +System on a Chip (SoC),即系统级芯片或片上系统。 + +我们在之前的讲义中已经了解了总线的概念,也已经尝试了编写和验证自己的总线。 +尽管我们已经做了许多工作,但都还处于处理器核的范畴内,并没有涉及计算机系统的概念。 +从集成电路的角度入手,SoC是把整个系统集成在单个(或几个)芯片上,从而完成复杂系统功能的集成电路。 + +:::info[什么是计算机系统?] +经过这么长时间的一生一芯学习,你是否对于计算机系统有了进一步的理解呢? + +我们可以把计算机系统分成应用程序、操作系统、硬件系统、晶体管四个大的层次。 +应用程序与操作系统之间的界面是应用程序编程接口API (Application Programming Interface)和应用程序二进制接口ABI(Application Binary Interface); +操作系统和硬件系统之间的界面是指令系统ISA (Instruction Set Architecture); +硬件系统和晶体管之间的界面是工艺模型。 + +![img](./SoC/1.jpg) +::: +:::warning[了解API和ABI的区别] + +## 计算机系统硬件结构的发展 +刚刚我们已经介绍过SoC的基本概念,但似乎依然缺乏对于SoC和CPU的直观理解。 +我们可以尝试从冯·诺依曼结构入手,进一步展开SoC的细节。 + +![img](./SoC/2.jpg) + +简要来说,计算机由存储器、运算器、控制器、输入设备和输出设备五部分组成, +其中运算器和控制器合称为中央处理器 (Central Processing Processor),也就是CPU。 +冯·诺依曼结构以运算器为中心,输入输出设备与存储器之间的数据传送都需要经过运算器。 +当然,随着技术的进步,冯·诺依曼结构也得到了持续改进,由以运算器为中心改进为以存储器为中心。 + +随着应用需求的变化和工艺水平的不断提升,冯·诺依曼结构中的控制器和运算器逐渐演变为计算机系统中的中央处理器部分; +输入、输出设备统一通过北桥和南桥与中央处理器连接; +中央处理器中的图形处理功能由中央处理器中分化出来,形成专用的图形处理器。 +因此,现代计算机系统的硬件结构主要包括了中央处理器、图形处理器、北桥及南桥等部分。 + +* 中央处理器:主要包含控制器和运算器,传统的中央处理器在SoC中更多地体现为处理器核。 +* 图形处理器:面向2D和3D图形、视频、可视化计算和显示优化的处理器。 +* 北桥:离CPU最近的芯片,主要负责控制显卡、内存与CPU之间的数据交换。向上连接处理器,向下连接南桥。 +* 南桥:主要负责硬盘、键盘以及各种对带宽要求较低的IO接口与内存、CPU之间的数据交换。 + +### CPU‑GPU‑北桥‑南桥四片结构 +现代计算机的一种早期结构是CPU‑GPU‑北桥‑南桥结构。 +在该结构中,计算机系统包含四个主要芯片,其中CPU芯片、北桥芯片和南桥芯片一般是直接以芯片的形式安装或焊接在计算机主板上,而GPU则以显卡的形式安装在计算机主板的插槽上。 + +计算机的各个部件根据速度快慢以及与处理器交换数据的频繁程度被安排在北桥和南桥中,CPU通过处理器总线和北桥直接相连,北桥再通过南北桥总线和南桥相连。 +GPU一般以显卡的形式连接北桥,内存控制器集成在北桥芯片中;硬盘接口、音频接口以及鼠标、键盘等接口放在南桥芯片中。 + +![img](./SoC/3.jpg) + +### CPU‑北桥‑南桥三片结构 +现代计算机的一种典型结构是CPU‑北桥‑南桥结构。 +在该结构中,系统包含三个主要芯片,分别为CPU芯片、北桥芯片和南桥芯片。 +三片结构与四片结构最大的区别是,前者GPU功能被集成到北桥中,即一般所说的集成显卡。 + +和四片结构类似,CPU通过处理器总线和北桥直接相连,北桥再通过南北桥总线和南桥相连。 +内存控制器、显示功能以及高速IO接口(如PCIE等)集成在北桥芯片中;硬盘接口、音频接口以及鼠标、键盘等接口仍旧放在南桥芯片中。 +随着计算机技术的发展,更多的高速接口被引入计算机体系结构中,在北桥上集成的IO接口的速率也不断提升。 + +![img](./SoC/4.jpg) + +### CPU‑弱北桥‑南桥三片结构 +随着芯片的集成度不断提高,单一芯片中能够实现的功能越来越复杂。 +内存接口的带宽需求超过了处理器与北桥之间连接的处理器总线接口,导致内存的实际访问性能受限于处理器总线的性能。 +伴随着处理器核计算性能的大幅提升,存储器的性能提升却显得幅度较小,这两者的差异导致计算机系统性能受到存储器系统发展的制约,也就是所谓存储墙问题。 + +因此,对计算机系统性能影响显著的内存控制器开始被集成到CPU芯片中,从而大幅降低了内存访问延迟,提升了内存访问带宽。 +于是,北桥的功能被弱化,主要集成了GPU、显示接口、高速IO接口(如PCIE等)。 + +![img](./SoC/5.jpg) + +### CPU‑南桥两片结构 +随着芯片集成度的进一步提高,图形处理器也开始被集成到CPU芯片中。 +因此,北桥存在的必要性就进一步降低,开始和南桥合二为一,形成CPU‑南桥结构。 + +在该结构中,CPU芯片集成处理器核、内存控制器和GPU等主要部件,对外提供显示接口、内存接口等,并通过处理器总线和南桥相连; +南桥芯片则包含硬盘、USB、网络控制器以及PCIE/PCI、LPC等总线接口。 +当然,也有一些两片结构是将GPU集成在南桥芯片中,这样在南桥上可以实现独立的显存供GPU使用,某些条件下更有利于GPU性能的发挥。 + +![img](./SoC/6.jpg) + +### SoC单片结构 + +SoC是一种单片计算机系统解决方案,在单个芯片上集成了处理器、内存控制器、GPU以及硬盘、USB、网络等IO接口,用户在搭建计算机系统时只需要使用单个主要芯片即可。 +目前SoC主要应用于移动处理器和工业控制领域,相比于多片结构,单片SoC的集成度更高,功耗控制方法更加灵活,有利于系统的小型化和低功耗设计。 +不过,由于全系统都在一个芯片上实现,系统的扩展性没有多片结构好,升级的开销也更大。 + +![img](./SoC/7.jpg) + +目前,主流商用处理器中,面向中高端领域的处理器普遍采用两片结构,而面向中低端及嵌入式领域的处理器普遍采用单片结构。 +SoC单片结构最常见的是在手机等移动设备中,例如Apple M1 Chip。 + +![img](./SoC/8.jpg) + +![img](./SoC/9.jpg) + +## 一生一芯SoC介绍] +:::info[一生一芯SoC资料] +目前本节讲义的内容参考了第四期一生一芯的SoC工程,更多相关信息请浏览[第四期一生一芯计划仿真用SoC工程](https://github.com/OSCPU/ysyxSoC) +::: + +一生一芯的SoC系统中包括了处理器核,CPU选择模块、UART、SPI、VGA、SDRAM、PS2、ETHMAC和Chiplink等外设。 + +![img](./SoC/10.jpg) + +### PCB板 +PCB 板分为上下两层,分别为SoC板和FPGA板,上下板之间通过Chiplink连接。 + +SoC板包括SoC芯片、晶振、8位拨码开关等元件。 + +![img](./SoC/11.jpg) + +出于成本的考量,多个同学的CPU会被集成到一块SoC上,根据拨码开关指定选择信号`core_dip`,在实际芯片中选择其中一个CPU运行。 + +![img](./SoC/13.jpg) + +晶振为各种外设提供25MHZ的时钟。 +为将低频的晶振时钟倍频到更高的频率输入给CPU使用,处理器集成了PLL(锁相环)模块,上电时通过不同的`pll_cfg`选择CPU使用的频率。 + +![img](./SoC/14.jpg) + +FPGA板包括ZYNQ芯片、DDR等元件。 + +![img](./SoC/12.jpg) + +### 外设介绍 +* [UART N16550](http://byterunner.com/16550.html):十分常见的UART IP,在软件上易于配置和使用,可以灵活地配置例如波特率、FIFO、中断等功能。 + + 初始化UART的流程: + 1. 关闭中断 + 2. 配置LC寄存器,以使能divisor latches + 3. 配置波特率 + 4. 释放divisor latches + 5. 数据传输 + +* [SPI FLASH](https://www.fpga4fun.com/SPI.html):SPI控制器与Flash相连接,读取存储在flash上的程序与数据。我们的PC复位值位于SPI-flash中,第一条指令从flash中取出。 +* Chiplink:将芯片与FPGA之间的AXI请求进行分片传输及重新组合,从而实现芯片与FPGA之间的连接。 +Chiplink易于验证,可以在保证正确性的前提下提供丰富的外设。 +不过,尽管Chiplink有sifive的开源实现,但没有文档;同时Chiplink的带宽很低。 + +![img](./SoC/15.jpg) + +### 仿真测试 +SoC团队通过VCS和Verdi对集成后的SoC进行测试,其中VCS用于仿真,Verdi用于调试。 + +在VCS仿真测试框架中,`asic_top`为设计和流片的顶层,即PCB板上的SoC芯片,同时测试框架中还包含了一些其他仿真模块。 +在进行仿真测试时,程序被加载进Flash和RAM中。 + +![img](./SoC/16.jpg) + +SoC团队采用三种类型的仿真测试。 +* RTL仿真:在RTL级别对设计进行仿真,验证设计的功能正确性 +* 网表仿真:对综合后的网表进行仿真,验证功能正确性。 +* 带时序信息的网表仿真:将时序信息反标到网表上进行仿真,验证设计的时序与功能正确性。 + +![img](./SoC/17.jpg) + +## 一生一芯后端概述 +后端设计一般指芯片设计中从网表或RTL代码生成到可生产制造的GDS的设计过程。主要包括综合、形式化验证、布局与布线、以及Signoff阶段的静态时序分析、物理验证等阶段。 + +* 综合:将RTL通过综合工具、工艺库等生成门级网表代码,为后续布局布线提供必要条件。 +通过面积、时序、功能三方面对综合的结果进行评估。 + +* 形式化验证:比较前后两个电路代码的设计逻辑是否等价,实现数据同参考数据的所有compare point进行比较,得出是否所有点逻辑一致。 +当每次网表发生变更时,均需要进行形式化验证。 + +* 布局与布线:布局主要分为如下两个阶段: + 1. Floorplan:规划芯片的面积,规划电源网络,摆放物理单元等。 + 2. Place:摆放综合后的门级网表,进行时序优化,判断是否有足够的绕线资源等。 + + 布线主要分为如下两个阶段: + 1. CTS:生成实际的时钟树,把时钟信号送到时序单元,使时钟到每个寄存器的延时趋于一致。 + 2. Route:通过走线通道完成所有绕线工作,计算实际延迟,判断时序是否满足要求。 + +* 静态时序分析:通过遍历所有的约束路径,基于时序的最差条件进行时序分析。 + 1. setup检查:保证数据比时钟触发沿提前到达寄存器。 + 2. hold检查:让下个时钟触发沿采集的数据不在当前时钟触发沿到达。 + +* 物理验证:检查是否满足工艺节点可制造的要求。需要验证以下三项内容: + 1. DRC:验证是否满足工艺的设计规则 + 2. Antenna Check:排除会产生电荷聚集的点,避免离子注入和刻蚀等工艺的影响,同时避免聚集的电荷破坏击穿薄的氧化绝缘层,导致电路失效。 + 3. LVS:保证gds所代表的电路和设计的网表逻辑的一致性,电气性能的正确性。 + +:::warning[将自己的CPU核接入SoC环境中] +目前,第四期一生一芯的仿真用SoC工程已发布,大家可以参考[第四期一生一芯计划仿真用SoC工程](https://github.com/OSCPU/ysyxSoC)以及工程中的说明, +将自己的CPU核接入SoC环境中进行测试。 +当然,可能需要对自己现有的CPU核进行一些修改与兼容。 + +在完成A线的其他内容时,你的CPU核可能会有很大改动,所以如果你目前还没有完成A线的其他内容,建议你在完成后再将CPU核接入SoC环境。但是你可以提前看看SoC环境的对接要求和CheckList,对之后要做的事情心中有数。 + +我们也十分欢迎大家根据[第四期一生一芯计划仿真用SoC工程](https://github.com/OSCPU/ysyxSoC)搭建自己的仿真用SoC测试平台,后续再根据后端的要求进行修改。 +::: + +:::info[参考资料] +* [计算机体系结构基础 胡伟武等](https://github.com/foxsen/archbase) +* [第三期一生一芯系列视频](https://www.bilibili.com/video/BV1PU4y1V7X3?p=23&vd_source=38024886289f4efc2c6167eacd5361b7) +* [第四期一生一芯计划仿真用SoC工程](https://github.com/OSCPU/ysyxSoC) +* [第五期一生一芯系列视频](https://space.bilibili.com/2107852263/channel/collectiondetail?sid=690279) +::: diff --git a/zh/advanced/UserProgramAndSystemCall.md b/zh/advanced/UserProgramAndSystemCall.md new file mode 100644 index 0000000..5c02cb4 --- /dev/null +++ b/zh/advanced/UserProgramAndSystemCall.md @@ -0,0 +1,33 @@ +--- +sidebar_position: 2 +--- +## 用户程序与系统调用 + +:::info[视频录播和课件] +* 录播 - [计算机系统软件栈](https://www.bilibili.com/video/BV1oe4y1K7Ge/) +* 课件 - [计算机系统软件栈](https://ysyx.oscc.cc/slides/2205/16.html#/) +::: +通过上一小节的学习,我们已经了解了异常处理机制和中断的相关概念,同时可以跳转到操作系统所指定的入口。 +不过,为了加载用户程序以及实现系统调用,我们还需要继续本节的学习。 + +在本节内容中,我们将首先实现用户程序的加载。 +`Navy-apps`中包括C运行库,系统调用接口,NDL、SDL等运行库,我们可以通过`Navy-apps`编译出运行在操作系统上的用户程序。 +我们需要加载的用户程序包含在编译得到的ramdisk镜像文件中,我们需要将用户程序加载到正确的内存位置, 并执行用户程序。 + +在实现用户程序的加载之后,我们需要提供操作系统的运行时环境。 +操作系统对系统中的资源进行统一管理,用户程序通过系统调用这一接口请求服务。 +我们在上一小节中已经了解了系统调用的流程,但对于系统调用的具体过程还需要更多进行一些细节化的处理。 +在本节中,我们需要实现更多的系统调用,深入了解程序在操作系统中运行的前世今生。 + +:::warning[完成PA3.2] +根据PA讲义完成以下内容: + +用户程序和系统调用 + + +直到你看到如下提示框: +::: + +:::tip[温馨提示] +PA3阶段2到此结束. +::: diff --git a/zh/advanced/pipeline/fig.1.png b/zh/advanced/pipeline/fig.1.png new file mode 100644 index 0000000..495efdc Binary files /dev/null and b/zh/advanced/pipeline/fig.1.png differ