作者:重走此间路
链接:https://zhuanlan.zhihu.com/p/32366520
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
捋顺了芯片的基础知识,现在终于可以开始攒机了。
首先,我们跑去ARM,问它有没有现成的系统。ARM说有啊,A73/G71/视频/显示/ISP/总线/系统控制/内存控制器/Trustzone全都帮你集成好了,CPU和GPU后端也做了,还是16nm的,包你性能和功耗不出问题。然后我们再跑到Synopsys或者Cadence买EDA工具,把仿真平台也一起打包了,顺带捎上周边IP和PHY。至于基带,Wifi和蓝牙,先不做吧,毕竟是第一次攒,要求不能太高。
在掏了几亿银子出来后,我们拿到一个系统框图:
A73MP4@3Ghz,[email protected],G71MP8@850Mhz,显示4K分辨率双路输出,视频4K,支持VR,支持Trustzone,内存带宽25.6GB/s(LPDDR4x64@3200Gbps)。
本来可以乐呵呵的一手交钱一手交货的,可我们是来攒机的,不是买整机,我们得知道这个系统到底是怎么搭起来的,免得被坑。于是ARM给了一个更详细的图:
有了这张图,我们就可以对每个模块的性能,功耗,面积还有系统性能进行详细分析了。
首先是大核模块,这是一个手机芯片最受人关注的焦点。A73MP4的PPA如下:
以下数据经过修改,并不是真实数据。
工艺:TSMC16FFLL+
频率:2.0GHz@SSG/0.72C/0C, 2.5GHz@TT/0.8V/85C,3GHz@TT/1.0C/85C
MP4静态功耗:250mW@TT/0.8V/85C
动态功耗:200mW/Ghz@TT/0.8V/85C
面积:7mm,MP4, 64KB L1D, 2MB L2, No ECC, with NEON&FPU
跑分:835/Ghz SPECINT2K
也就是说,跑在极限3Ghz的时候,动态功耗是200x1.25x1.25x3=937mW,四核得上4W,而手机SoC最多也就能稳定跑在3W。跑在2.5G时候,动态功耗是200x2.5=500mW,MP4总功耗2.25W,可以接受。
A53的如下:
以下数据经过修改,并不是真实数据。
工艺:TSMC16FFLL+
频率:1.5GHz@SSG/0.72C/0C, 1.6GHz@TT/0.8V/85C
MP4静态功耗:60mW@TT/0.8V/85C
动态功耗:90mW/Ghz@TT/0.8V/85C
面积:4mm,MP4, 32KB L1D, 1MB L2, No ECC, with NEON&FP
跑分:500/Ghz SPECINT2K
四核跑在1.6Ghz时功耗463毫瓦,加上A73MP4,一共2.7瓦。
这里的缓存大小是可以配置的,面积越大,性能收益其实是递减的,可以根据需要自行选取。
A53的:
A73的:
作为CPU,集成到SoC中的时候,一个重要的参数是访存延迟。有个数据,在A73上,每减少5ns的访存时间,SPECINT2K分数就可以提高1%。总的延迟如下:
上图只是一个例子,频率和参考设计并不一样。在上图,我们可以算出,访存在CPU之外,也就是总线和DDR上总共需要58.7ns,这个时间也就是静态延时,在做系统设计时可以控制。要缩短它,一个是提高时钟频率,一个是减少路径上的模块。在ARM给的系统中总线使用了CCI550,如果跑在1Ghz,一个Shareable的传输从进去到DDR打个来回需要10cycle,也就是10ns(上图是CCI500跑在800Mhz,所以是12.5ns)。CCI550和CPU之间还需要一个异步桥,来隔绝时钟,电源和电压域。这个异步桥其实也很花时间,来回需要7.5ns,6个cycle,快赶上总线延迟了。但是没办法,它是省不掉的,只有通过增加总线频率来减少绝对延迟。只有一种情况例外,就是异步桥连接的两端时钟频率是整数倍,比如这里的内存控制器和CCI之间频率相同,那可以去掉。
有的设计会把CCI550以及它上面的CPU,GPU作为一个子网挂在NoC下,由NoC连到内存控制器。这样的好处是可以把交织和调度交给NoC,坏处是凭空增加额外一层总线10-20ns的延迟,CPU的跑分就要低了2-4%。在ARM的方案中,交织由CCI直接完成,而调度交给内存控制器。既然所有主设备的访问都需要到DDR,那由DMC来调度更合适。再加上没有采用两层总线的链接方式,一共可以去掉两层异步桥,省掉12个cycle,已经快占到整个通路静态延迟的五分之一了。所以,现在我看到的主流总线拓扑,都是把CCI直接连内存控制器。那可不可以直接把CPU连内存控制器?只要DMC的端口够多,也没什么不可以,但是这样一来大小核就不能形成硬件支持的SMP了,功能上不允许。
路径上还有DMC和PHY的延迟,也是将近15ns,20cycle,这部分挺难降低。如果实现Trustzone,还要加上DMC和CCI之间的TZC400延迟,还会再多几个cycle。至于DDR颗粒间的延迟(行选择,命令和预充电),可以通过准确的DMC预测和调度来减少,稍后再讲。
静态延迟算完,我们来看带宽和动态延迟。
CCI和CPU/GPU之间是ACE口相连,数据宽度读写各128bit,16Byte。如果总线跑在800Mhz,单口的理论读或者写带宽分别是12.8GB/s。这个速度够吗?可以通过CPU端和总线端的Outstanding Transaction个数来判断。A73的手册上明确指出,ACE接口同时支持48个Cacheable的读请求,14x4(CPU核数量)设备和non-Cacheable的TLB/指令读,7x4+16写,这是固定的。而对应的CCI550的ACE接口支持OT数量是可配置的,配多大?有个简单公式可以计算。假设从CPU出来的传输全都是64字节的(Cacheable的一定是,而non-Cacheable的未必),而我们的带宽是12.8GB/s(假设是读),而一个传输从进入ACE到离开是51.3ns(按照上图计算),那么最大OT数是12.8x51.3/64=10.也就是说,只要10个OT就可以应付CPU那里100多个读的OT,多了也没用,因为总线数据传输率在那。从这点看,瓶颈在总线的频率。
那为什么不通过增加总线的数据位宽来移除这个瓶颈呢?主要是是位宽增加,总线的最大频率相应的会降低,这是物理特性。同时我们可能会想,真的有需要做到更高的带宽吗,CPU那里发的出来吗?我们可以计算一下。先看单个A73核,A73是个乱序CPU,它的读来自于三类方式,一个是读指令本身,一个是PLD指令,一个是一级缓存的预取。A73的一级缓存支持8路预取,每路预取范围+/-32,深度是4,预取请求会送到PLD单元一起执行,如下图。所以他们同时受到BIU上Cacheable Linefill数目的限制,也就是8.此外还有一个隐含的瓶颈,就是A73的LSU有12个槽,读写共享,所有的读写请求(包括读写指令,PLD和反馈过来的预取)都挂在这12个槽中独立维护。
言归正传,假设系统里全是读请求,地址连续;此时A73每一条读指令的延迟是3个cycle(PLD可能只需要1个cycle,我不确定)跑在3Ghz的A73,一每秒可以发出1G次读,每个8字节(LDM)的话,就是8GB/s带宽,相当于每秒128M次Linefill请求,除以BIU的8个Linefill数量,也就是每个请求可以完成的时间是60ns这和我们看到的系统延迟接近。此时一级缓存预取也会起作用,所以8个Linefill肯定用满,瓶颈其实还是在在BIU的Linefill数量,按照50ns延迟算,差不多是10GB/s。假设都是写,指令一个周期可以完成,每cycle写8字节(STM),带宽24GB/s。如果写入的地址随机,会引起大量的Linefill,于是又回到BIU的Linefill瓶颈。地址连续的话,会触发Streaming模式,在BIU内部只有一个传输ID号,由于有竞争,肯定是按次序的,所以显然这里会成为写的瓶颈。由于写的确认来自于二级缓存,而不是DDR(是cacheable的数据,只是没分配一级缓存),延迟较小,假设是8个cycle,每秒可以往外写64B/8=8GB的数据。总的来说,A73核内部,瓶颈在BIU。
到了二级缓存这,预取可以有27次。当一级缓存的BIU发现自己到了4次的极限,它可以告诉二级缓存去抓取(Hint)。单个核的时候,4+27<48,瓶颈是在一级缓存的BIU;如果四个核同时发多路请求,那就是4x8=32个OT,再加上每路都可以请求二级缓存,其总和大于ACE的48个Cacheable读请求。按照带宽算,每核是10GB/s,然后CPU的ACE口的读是48*64B/50ns=60GB/s。虽然60GB/s大于4x10GB/s,看上去像是用不满,但是一级缓存会提示二级缓存自动去预取,所以,综合一级二级缓存,簇内的瓶颈还是在ACE接口上,也意味着总线和核之间的瓶颈在总线带宽上,并且60GB/s远高于12.8GB/s。
A53是顺序执行的,读取数据支持miss-under-miss,没有A73上槽位的设计,就不具体分析了。
这里我简单计算下各个模块需要的带宽。
CPU簇x2,每个接口理论上总线接口共提供读写43.2GB/s带宽(我没有SPECINT2K时的带宽统计)。
G71MP8在跑Manhattan时每一帧需要370M带宽(读加写未压缩时),850Mhz可以跑到45帧,接近17GB/s,压缩后需要12GB/s。
4K显示模块需要4096x2160x4(Bytes)x60(帧)x4(图层)的输入,未压缩,共需要8GB/s的带宽,压缩后可能可以做到5GB/s。这还都是单向输入的,没有计算反馈给其他模块的。
视频需求带宽少些,解压后是2GB/s,加上解压过程当中对参考帧的访问,压缩后算2GB/s。
还有ISP的,拍摄视频时会大些,算2GB/s。
当然,以上这些模块不会全都同时运行,复杂的场景下,视频播放,GPU渲染,CPU跑驱动,显示模块也在工作,带宽需求可以达到20GB/s。
而在参考设计里,我们的内存控制器物理极限是3.2Gbpsx8Bytes=25.6GB/s,这还只是理论值,有些场景下系统设计不当,带宽利用率只有70%,那只能提供18GB/s的带宽了,显然不够。
而我们也不能无限制的增加内存控制器和内存通道,一个是内存颗粒成本高,还有一个原因是功耗会随之上升。光是内存控制和PHY,其功耗就可能超过1瓦。所以我们必须走提高带宽利用率的路线。
在这个前提下,我们需要分析下每个模块数据流的特征。
对于CPU,先要保证它的延迟,然后再是带宽。
对于GPU,需要保证它的带宽,然后再优化延迟,低延迟对性能跑分也是有提高的。
对于视频和ISP,带宽相对来说不大,我们要保证它的实时性,间接的需要保证带宽。
对于显示模块,它的实时性要求更高,相对于应用跑的慢,跳屏可能更让人难以容忍。
其余的模块可以相对放在靠后的位置考虑。
在CCI550上,每个端口的带宽是读写共21.6GB/s,大小核簇各需要一个端口,GPU每四个核也需要一个端口。显示和视频并没有放到CCI550,原因稍后解释。CCI550的结构如下:
它一共可以有7个ACE/ACE-Lite进口,读写通道分开,地址共用,并且会进行竞争检查,每cycle可以仲裁2个地址请求。之前我们只计算了独立的读写通道带宽,那共用的地址会是瓶颈吗?算一下就知道。对于CPU和GPU,所有从端口出来的传输,无论是不是Cacheable的,都不超过64字节,也就是16x4的突发。一拍地址对应四拍数据,那就是可以同时有八个端口发起传输,或者4个通道同时发起读和写。假设在最差情况下,CPU+GPU同时发起shareable读请求,并且,读回去的数据全都引起了eviction,造成同等数量的写。此时数据通道上不冲突,地址通道也正好符合。如果是CPU+GPU同时发起shareable写请求,并且全都命中别人的缓存,引起invalidate,读通道此时空闲,但是invalidate占用地址,造成双倍地址请求,也符合上限。到DMC的地址不会成为瓶颈,因为共四个出口,每周期可以出4个。这里,我们使用的都是shareable传输,每次都会去Snoop Filter查找,每次可能需要两个对TAG
RAM的访问(一次判断,一次更新标志位,比如Invalidate),那就是每cycle四次(地址x2)。而我们的Tagram用的是2cycle访问延迟的2块ram,也就是说,需要同时支持8个OT。这一点,CCI550已经做到了。在以上的计算中,DMC是假设为固定延迟,并且OT足够,不成为瓶颈。实际情况中不会这么理想,光是带宽就不可能满足。
在CCI550中,有两处OT需要计算,一个是入口,每个端口独立,一个是做Snooping的时候,所有通道放在一起。之前我们计算过,如果静态延迟是50ns,单口的读需要10个OT。但是,上面的静态延迟算的是DDR命中一个打开的bank,并且也不需要预充电。如果加上这两步,延迟就需要85ns,而CCI必须为最差的情况作考虑。这样,OT就变成了12.8x85/64= 17。写的话需要的更少,因为写无需等待数据返回,只需要DMC给Early response就可以,也就是穿过CCI550(10cycle)加上少于DMC的静态延迟(10cycle),不超过20ns,OT算3。所以,单口OT就是20,意味着需要4x20=80个来存储请求(四个入口)。
在出口,如果是shareable的传输,会需要去查表实现snooping,此时所有的请求会放在一起,它的大小按照四个出口,每个带宽2x12.8GB/s,共100GB/s。但是,由于我们DDR最多也就25.6GB/s,计算OT时候延迟按照读的75ns(85-10,CCI本身的延迟可以去掉了,写延迟小于读,按照读来计算),75x25.6/64=30。如果是non-shareable传输,那就还是使用入口的OT。
上面计算出的OT都是用来存储读写请求的,其数据是要暂存于CCI550内部的buffer。对于写来说如果数据不能暂存,那就没法给上一级early response,减少上一层的OT;对于读,可以在乱序地址访问时,提高效率。由于DMC会做一定程度的调度,所以CCI550发送到DMC的读写请求,很多时候不是按照读请求的次序完成的,需要额外的缓冲来存储先返回的数据。下面是读buffer大小和带宽的关系图。我们可以看到,对于随机地址,buffer越深带宽越高。而对于顺序地址访问,几乎没有影响。这个深度根据队列原理是可以算出来的,我们这里就不写了。
同样,增加缓冲也可以减少总线动态延迟,下图一目了然:
至于设置缓冲的大小,根据队列原理,由于受到DMC调度的影响,数据回到CCI500的延迟期望值不容易计算,而直接设比较大的值又有些浪费(面积相对请求缓冲较大),比较靠谱的方法是跑仿真。
关于CCI550的资源配置,还有最后一项,即tag ram的大小。CCI550的特点就是把各个主设备内部的缓存标志位记录下来提供给Snooping操作,那这个tag ram的大小该如何定。理论上,最大的配置是等同于各级exclusive缓存的tag ram总合。如果小于这个值,就需要一个操作,叫做back invalidation,通知相应的缓存,把它内部的标志位去掉,免得标志位不一致。这个是由于tag ram本身的大小限制引入的操作,在执行这个操作过程中,同地址是有竞争的,这个竞争是多余的,并且会阻塞所有缓存对这一地址的snooping操作,所以需要尽量避免。ARM定义了一个CCI550中的tag ram和原始大小的比例,使得back invalidation保持在1-2%以下,参考下图:
简单来说,可以用所有exclusive cache总大小的10%,把back invalidation限制在1%以下。
其他的配置参数还有地址线宽度,决定了物理地址的范围,这个很容易理解。还有传输ID的宽度,太小的话没法支持足够的OT。
到这里为止,我们已经设置好了硬件资源的大小,尽量使得动态延迟接近静态延迟。下图是全速运行时的带宽和功耗(这里移除了内存的瓶颈)。
在这里,读写比例是2:1,也就是说出口收到的请求最多64+32=96GB/s。但我们可以看到有时是可以做到超越100GB/s的,为什么呢?因为有相当部分是命中了内部的tag,转而从缓存进行读操作。我们之前算过,Snooping操作在CCI550内部访问tag ram是足够的,但是如果命中,就需要从别人的缓存读数据,而这就收到上级缓存访问窗口的限制。所以我们看到当命中率是100%,总带宽反而大大下降,此时的瓶颈在访问上级缓存。所幸的是,对于SMP的CPU,通常命中率都在10%以下,正处于带宽最高的那段。至于通用计算,是有可能超过10%的,等到GPU之后再讨论。
第二副图显示了动态功耗,在25GB/s时为110毫瓦,还是远小于CPU的功耗,可以接受。静态功耗通常是10-20毫瓦,对于待机功耗其实并不低,而且tag ram是没法关闭的(300KB左右),只要有一个小核在运作,就必须打开。所以CCI550的静态功耗会比CCI400高一些,这就是性能的代价,需要有部分关闭tag ram之类的设计来优化。
不过,一个没法避免的事实是,总线的入口带宽是100GB/s,出口却只能是25.6GB/s。我们需要一些方法来来达到理论带宽。在总线上,可以使用的方法有调度和交织。调度之前已经提到过,使用乱序来提高不存在竞争问题的读写传输,CCI550自已有内嵌策略,不需要配置。
而交织在之前的文章中也提过。 在CCI550上,只接了CPU和GPU,而他们发出的所有传输都不会超过64字节,也就是一个缓存行长度。只有一种可能的例外,就是exclusive传输,在spin lock中会用到。如果有一个变量跨缓存行,那么就有可能产生一个128字节的传输。而由于这类可能存在的传输,CCI550可以设置的最小粒度是128字节,来避免它被拆开到两个内存控制器,破坏原子性。不过到目前为止,CPU/GPU最多发出64字节的传输,不会有128字节,如果有跨缓存行的变量,直接切断并报异常。这样虽然会造成软件逻辑上的错误,但这错误是软件不遵守规范引起的,ARM软件规范里明确禁止了这类问题。
对于视频和显示模块,它们并没有连在CCI550。因为CCI550内部只有两个优先级,不利于QoS。QoS我们稍后再讨论,先看系统中的NIC400总线。参考设计中NIC400链接了视频/显示和DMC,但其实NIC400并没有高效的交织功能,它的交织必须把整个物理地址空间按照4KB的页一个个写到配置文件。此外,它也没有调度和缓冲功能,所以在复杂系统中并不推荐使用。NIC400更适合简单的系统,比如只有一个内存控制器,可以用更低的功耗和面积实现互联。在这里我们可以使用NoC来替换。NoC还有一个适合传输长数据的功能,就是分割,可以把265字节甚至4KB的突发传输隔成小块,更利于交织。不过分割需要注意,必须把传输的标识符改了,并且要自己维护,并维持数据完整性。
这里就引出了一个问题,到底我们交织的粒度是多少合适?粒度越大,越不容易分散到各个DMC,而粒度越小,越不容易形成对某个DMC和DDR Bank的连续访问。
在四个通道的DMC上,CCI550使用了这样一个哈希变换,来使得各个模块的传输能够平均分布:
Channel_Sel[0] = Addr[8] ^ Addr[10] ^ Addr [13] ^ Addr [21]
Channel_Sel[1] = Addr[9] ^ Addr [11] ^ Addr [16] ^ Addr[29]
然后在DMC,使用了这样的变化来使访问分散到各个Bank:
For four channel memory: Addr[29:0] = {Addr[31:10], Addr[7:0]}
经过哈希化后,在CPU的顺序地址上效果如下:
在显示模块的上效果如下:
显然,在256字节的交织颗粒时效果最好,尤其是对于连续地址。对于随机地址,有个细节我们可以注意下,在颗粒度64/128字节的时候最好,这其实也符合预期。并且,由此还可以引出一个新的优化:对于随机访问多的地址段,我们使用128字节交织,而对于顺序访问多的地址段,使用256字节交织。但是请注意,这个编码必须对所有的主设备都保持一致,否则一定会出现数据一致性或者死锁问题。
刚刚我们提到了在DMC中的哈希优化。DMC最重要的功能是调度,这是提高带宽利用率的关键。ARM的DMC可以做到在多个主设备的64字节随机地址访问时做到94%的利用率(读),写也可以到88%。我们可以通过一些参数的设置来影响它的调度判断:读写操作切换时间,Rank(CS信号)切换时间以及页内连续命中切换阈值,高优先超时切换时间等,这些看名字就能理解,不详细解释了。
调整完这么多参数,还有一个最根本的问题没有解决:DDR理论带宽也只有25.6GB/s,各个主设备一起访问,总会有拥挤的时候。这时该采取什么策略来最大程度上保证传输?答案是QoS。QoS的基本策略是设置优先级,其次还有一些辅助策略,比如动态优先级调整,整流等。
上图是优先等级表,左边是总线的(包括CCI和NoC/NIC),高低依次是显示模块>视频>CPU>GPU>PCIe>DMA,基带跳过。在CCI550内部,实际上只有高和第两种优先级,CPU高,GPU低,没法区分更多的主设备。CCI550的优先级高低是给外部用的,可以拿来给DMC,让DMC定一个阈值,告诉CCI哪些级别之上优先发送,哪些暂缓。
当中的表是DMC内部对QoS优先级的重映射。DMC500支持把某个模块的优先级提高,并动态调整。这里,CPU在每秒发送1.6GB之后,优先级会被降低,和外部总线一样。下图中,我们可以看到在没有QoS的时候,CPU的延迟最大(最左边noqos),有了QoS,由于CPU的优先级相对较高,延迟大大降低(qos_only)。而用了重映射之后,在1.6GB之下,延迟又进一步较大降低。
让我们再看一个例子,更好的说明如何通过调整优先级来在有限的带宽下保证实时性:
在上图,我们看最右边,由于显示和视频总带宽需求不高,所以都能满足。
而这两张图里面,带宽需求上升,我们看到Video的带宽反而下降,而CPU不变。可在我们设置的表里,CPU的优先级是低于视频模块的。于是我们利用DMC的门限,告诉CCI不要送低优先级的,并把CPU中小核的优先级降低到门限之下,这样只要有视频流,CCI送到DMC的数据就会减少,如下图:
当然,我们也可以通过别的机制来达到类似的效果,比如限流等。攒机是个细致活,功夫够了肯定能调好,就是需要耐心和积累。