玄铁C910在平头哥的产品阵容中属于 “高性能” 类别。除了跻身少数已实现硬件化的乱序执行RISC - V内核之列,C910还是RISC - V向量扩展的早期采用者。它支持RVV 0.7.1,该版本具备掩码和可变向量长度支持功能。此后,平头哥发布了C920内核,将RVV支持提升到1.0版本,但在其他方面C910并未改变。
C910 的目标是“AI、边缘服务器、工业控制和 ADAS”等可能的应用。这也是平头哥的第一代乱序设计,因此要接手所有这些应用是雄心勃勃的。C910 以最多四个内核的集群形式实现,每个内核都有一个共享的 L2 缓存。平头哥的目标是在台积电 12nm FinFET 工艺上实现 2 到 2.5 GHz 的速度,其中 C910 内核占用 0.8 平方毫米。内核电压在 2 GHz 时为 0.8V,在 2.5 GHz 时为 1.0V。在台积电 7nm 工艺上,平头哥成功将内核频率推至 2.8 GHz。平头哥的论文进一步声称动态功率约为 100 微瓦/MHz,在 2 GHz 时为 0.2W。当然,这个数字不包括静态功率或内核外的功耗。然而,所有这些特点共同表明 C910 是一种低功耗、小面积的设计。
本文将借助荔枝派单板计算机,对平头哥TH1520中的C910进行研究。TH1520采用台积电12纳米鳍式场效应晶体管工艺制造,拥有一个四核C910集群,配备1MB的二级缓存,运行频率为1.85GHz。它连接着8GB的LPDDR4X - 3733内存。C910已开源,所以本文将尝试通过阅读部分源代码来更深入了解内核细节。
玄铁C910是 3 线宽、乱序核心,具有 12 级流水线的处理器。
与 Arm 的 Cortex A73 一样,C910 可以提前释放无序资源。对于微基准测试,本文使用了依赖分支和不完整加载来阻止退出。
C910 的前端经过量身定制,可处理 16 位和 32 位 RISC-V 指令,以及 RISC-V 矢量扩展的要求。该核心具有 64 KB、2 路组相联指令缓存,并采用 FIFO 替换策略。除了缓存指令数据外,C910 还为每个可能的 16 位指令槽存储四位预解码数据。两位暂时指示指令是否从该位置开始,而另外两位提供分支信息。总的来说,C910 使用 83.7 KB 的原始位存储进行指令缓存。
L1i 访问从两个方向读取指令字节、预解码数据和标签。因此,指令提取 (IF) 阶段将 256 位指令字节与 64 位预解码数据一起放入临时寄存器中。检查两个方向的标签以确定哪条方向有 L1i 命中(如果有)。同时,IF 阶段检查 16 个条目、完全关联的 L0 BTB,这让核心能够以有效的单周期延迟处理少量已执行的分支。
双向的指令字节和预解码数据被传递到下一个指令包 (IP) 阶段。所有这些都被输入到一对 8 宽的早期解码块中,在源代码中称为 IP 解码器。16 个早期解码槽中的每一个处理双向 16 位边界上的可能指令起始位置。这些早期解码器进行简单检查以对指令进行分类。对于矢量指令,IP 解码器还会计算出 VLEN(矢量长度)、VSEW(选定元素宽度)和 VLMAX(元素数量)。
尽管 IP 阶段消耗 256 位指令数据和 64 位预解码数据,并使用 16 个早期解码时隙处理所有这些数据,但其中一半数据始终会被丢弃,因为 L1i 只能以一种方式命中。以正确方式处理的 8 宽解码块的输出将传递到下一阶段,而另一个 8 宽解码器的输出将被丢弃。
C910 的主要分支预测器机制也位于 IP 阶段。条件分支由双模式预测器处理,具有 1024 个条目选择表、两个包含 2 位计数器的 16384 个条目历史表和一个 22 位全局历史寄存器。选择表通过对分支地址和全局历史寄存器的低位进行哈希处理来索引,而历史表则通过对历史寄存器的高命中进行哈希处理来索引。选择表的输出用于在两个历史表(标记为“taken”和“ntaken”)之间进行选择。返回使用 12 个条目返回堆栈进行处理,而 256 个条目间接目标数组处理间接分支。总而言之,分支预测器使用大约 17.3 KB 的存储空间。因此,按照今天的标准,它是一个小型分支预测器,非常适合 C910 的低功耗和小面积设计目标。从角度来看,像高通的 Oryon 这样的高性能核心单独为其条件(方向)预测器就使用了 80 KB,另外还为间接预测器使用了 40 KB。
使用不同长度的随机模式进行测试表明,C910 可以处理中等长度的模式。这与我在其他低功耗核心上看到的测试结果一致。当有大量分支时,C910 和 A73 都会遇到困难,但它们可以在没有过长历史的情况下为少数分支保持相当好的准确性。
C910 的主 BTB 有 1024 个条目,并且是 4 路组相联的。从 IP 阶段重定向管道会产生单个管道气泡,或者实际上需要 2 个周期的分支延迟。只要代码保持在指令缓存内,溢出 1024 个条目 BTB 的分支就有 4 个周期的延迟。
指令包阶段将最多 8 条 16 位指令连同解码器输出一起输入到下一个指令缓冲器 (IB) 阶段。此阶段的工作是平滑指令传递,尽可能弥补前端带宽中的任何故障。为此,IB 阶段有一个 32 项指令队列和一个单独的 16 项循环缓冲器。两者都有 16 位条目,因此 32 位指令将占用两个插槽。C910 的循环缓冲器与 Pentium 4 的跟踪缓存具有相同的用途,寻求在执行分支后填补丢失的前端插槽。当然,16 项循环缓冲器只能为最小的循环执行此操作。
为了向后续解码阶段提供数据,IB 阶段可以从循环缓冲区、指令队列或旁路路径中挑选指令,以减少延迟(如果不需要排队)。每条指令及其相关的早期解码元数据都被打包成 73 位格式,并发送到解码阶段。
指令解码 (ID) 阶段包含 C910 的主要解码器。来自 IP 阶段的三个 73 位输入被馈送到这些解码器,解码器解析出寄存器信息并在必要时将指令拆分为多个微操作。
只有第一个解码槽可以处理解码成四个或更多微操作的指令。所有解码槽都可以为更简单的指令发出 1-2 个微操作,但解码阶段总共不能在每个周期发出超过四个微操作。输出微操作被打包成 178 位格式,并直接传递到重命名阶段。与许多其他内核不同,C910 在解码器和重命名器之间没有微操作队列。因此,重命名宽度和解码器输出宽度必须匹配,这解释了为什么重命名器是 4 宽以及为什么解码器每个周期被限制为 4 个微操作。任何解码成四个或更多微操作的指令都会阻止并行解码。
然后,C910 的指令重命名 (IR) 阶段会检查架构寄存器之间的匹配情况,以查找指令间的依赖关系。然后,它会从相应的池(整数或 FP 寄存器)中分配空闲寄存器,或者选择退出阶段后新释放的寄存器。IR 阶段还会进行进一步解码。指令会进一步标记为它们是否是多周期 ALU 操作、它们可以转到哪些端口等等。重命名后,微操作为 271 位。
从软件上看,只要代码适合 64 KB 指令缓存,C910 的前端就可以维持每周期 3 条指令。L2 代码带宽较低,低于 1 IPC。SiFive 的 P550 为更大的代码占用空间提供了更一致的前端带宽,即使运行 L3 代码也可以维持 1 IPC。
C910 的后端使用基于物理寄存器文件 (PRF) 的无序执行方案,其中待处理和已知良好的指令结果都存储在与 ROB 分开的寄存器文件中。C910的源代码 ( ct_rtu_rob.v)定义了 64 个 ROB 条目,但平头哥的论文称 ROB 最多可容纳 192 条指令。微基准测试大部分符合这一观点。
C910 的重新排序缓冲区容量与英特尔 2013 年推出的 Haswell 相当,理论上可以比 P550 或 Goldmont Plus 保留更多正在运行的指令。但是,其他结构的大小不适合充分利用该 ROB 容量。
RISC-V 有 32 个整数寄存器和 32 个浮点寄存器,因此每个寄存器文件中的 32 个条目通常必须保留以保存已知良好的结果。这样就只剩下 64 个整数寄存器和 32 个浮点寄存器来保存正在执行的指令的结果。英特尔的 Haswell 支持其 192 个条目 ROB,在整数和浮点方面都有更大的寄存器文件。
C910 有八个执行端口。标量整数端的两个端口处理最常见的 ALU 操作,而第三个端口专用于分支。C910 的整数寄存器文件有 10 个读取端口,用于为五个执行管道提供数据,其中包括三个用于处理内存操作的管道。分布式调度程序设置为 C910 的执行端口提供数据。除了操作码和寄存器匹配信息外,每个调度程序条目都有一个 7 位年龄向量,用于实现基于年龄的优先级。
与 Goldmont Plus 和 P550 相比,调度程序容量较低,最常见的 ALU 操作仅有 16 个条目可用。P550 在其三个 ALU 端口上有 40 个调度程序条目可用,而 Goldmont Plus 有 30 个条目。
C910 的 FPU 具有简单的双管道设计。两个端口都可以处理最常见的浮点运算,如加法、乘法和融合乘法。两个管道也可以处理 128 位矢量运算。每个端口的馈送需要来自 FP/矢量寄存器文件的最多四个输入。融合乘法指令 (a*b+c) 需要三个输入。第四个输入提供掩码寄存器。与 AVX-512 和 SVE 不同,RISC-V 没有定义单独的掩码寄存器,因此所有输入都必须来自 FP/矢量寄存器文件。因此,尽管馈送的端口较少,但 C910 的 FP 寄存器文件的读取端口几乎与整数寄存器文件一样多。
浮点执行延迟是可以接受的,对于最常见的操作,延迟范围为 3 到 5 个周期。一些最新的内核,如 Arm 的 Cortex X2、英特尔的 Golden Cove 和 AMD 的 Zen 5,可以在 2 个周期的延迟内完成浮点加法。
C910 上的两个地址生成单元 (AGU) 计算内存访问的有效地址。一个 AGU 处理加载,另一个处理存储。C910 的加载/存储单元通常分为两个管道,旨在每个周期处理最多一个加载和一个存储。与许多其他内核一样,存储指令分为存储地址和存储数据微操作。
将 39 位虚拟地址生成为 40 位物理地址。C910 的 L1 DTLB 有 17 个条目并且是完全关联的。1024 个条目、4 路 L2 TLB 处理数据和指令访问的 L1 TLB 未命中,并在 L1 命中的基础上增加 4 个周期的延迟。物理上,L2 TLB 有两个存储体,均为 256×84 SRAM 实例。标签阵列是一个 256×196 位 SRAM 实例,196 位访问包括所有四个路径的标签以及四个“FIFO”位,可能用于实现 FIFO 替换策略。除了虚拟页码和有效位等必要信息外,每个标签还包括一个地址空间 ID 和一个全局位。这些可以使条目免受某些 TLB 刷新的影响,从而减少上下文切换时的 TLB 抖动。总的来说,L2 TLB 的标签和数据需要 8.96 KB 的位存储。
物理地址被写入加载和存储队列,具体取决于地址是加载还是存储。不确定加载队列有多大,C910 的源代码表明有 12 个条目,微基准测试结果令人困惑。
在源代码中,每个加载队列条目存储 36 位加载物理地址以及 16 位以指示哪些位有效,以及 7 位指令 ID 以确保正确排序。存储队列条目存储 40 位物理地址、待处理的存储数据、16 字节有效位、7 位指令 ID 和大量其他字段。举几个例子:
wakeup_queue:12位,可能表示当数据准备就绪时应该唤醒哪些依赖负载
sdid:4位,可能存储数据id
age_vec, age_vec_1:12 位年龄向量,可能用于跟踪商店订单
为了检查内存依赖性,加载/存储单元会比较内存地址的 11:4 位。从软件测试来看,C910 可以对完全包含在存储中的任何加载进行存储转发,而不管存储内的对齐情况如何。但是,如果加载跨越 16B 对齐边界,或者存储跨越 8B 对齐边界,则转发会失败。存储转发失败会导致 20+ 周期惩罚。
与 P550 不同,C910 可以很好地处理未对齐的访问。如果加载不跨越 16B 边界或存储不跨越 8B 边界,则基本上是免费的。如果跨越了这些对齐边界,则除了在后台进行额外的 L1D 访问之外,不会面临性能损失。总体而言,C910 的加载/存储单元和转发行为与英特尔和 AMD 的最新内核相比略有不足。但它与 AMD 的 Piledriver 处于同一水平,Piledriver 本身就是一个非常先进且高性能的内核。这是一个不错的水平。
64 KB 双向数据缓存具有 3 个周期延迟,并分为 4 个字节宽的存储体。它每个周期最多可处理一次加载和一次存储,但 128 位存储需要两个周期。L1D 标签分为两个独立的阵列,一个用于加载,一个用于存储。
数据缓存未命中由八个行填充缓冲区条目之一跟踪,这些条目存储未命中地址。重新填充数据保存在两个 512 位宽的填充缓冲寄存器中。与指令缓存一样,数据缓存使用简单的 FIFO 替换策略。
每个 C910 核心通过“PIU”(即处理器接口单元)与外界进行交互。在另一端,C910 集群具有一致性接口单元 (CIU),它最多可接收四个 PIU 的请求并保持缓存一致性。CIU 分为两个“snb”实例,每个实例都有一个 24 个条目请求队列。SNB 根据年龄在请求之间进行仲裁,并具有一个 512 位的 L2 缓存接口。
C910 的 L2 缓存既是 L1 未命中的第一站,也是集群范围内共享的最后一级缓存。在 TH1520 上,它具有 1 MB 的容量,并且是 16 路组相联缓存,采用 FIFO 替换策略。为了在每个周期内处理多次访问,L2 由两个存储体组成,由物理地址的第 6 位选择。L2 包括上级缓存,并使用 ECC 保护来确保数据完整性。
L2 延迟为 60 个周期,这对于具有有限重新排序能力且没有中级缓存的内核来说是个问题。即使是 P550 的 4 MB L3 缓存,从周期数和真实延迟的角度来看,其延迟也比 C910 的 L2 要好。英特尔的 Goldmont Plus 也使用共享 L2 作为最后一级缓存,并且具有大约 28 个周期的 L2 延迟(计算 uTLB 未命中)。
C910 的 L2 带宽也令人失望。单个内核的读取速度略高于 10 GB/s,即每周期 5.5 字节。所有四个内核加起来可以从 L2 读取 12.6 GB/s,即每个内核每周期仅读取 1.7 字节。四个内核的写入带宽更好,为 23.81 GB/s,但总共仍低于每周期 16 字节,而且写入通常不如读取常见。
再次,C910 的 L2 性能优于 P550 的 L3 和 Goldmont Plus 的 L2。我怀疑多线程应用程序将轻松突破 C910 的 L2 带宽限制。
集群外请求通过 128 位 AXI4 总线。在 Lichee Pi 4A 中,TH1520 的 64 位 LPDDR4X-3733 接口的理论 DRAM 带宽略低于 30 GB/s。实际读取带宽要低得多。多线程应用程序可能会发现 4.2 GB/s 有点紧张,尤其是当四个内核共享的末级缓存只有 1 MB 时。
使用 2 MB 页和 1 GB 阵列进行测试,DRAM 延迟至少可控制在 133.9 纳秒。它不及台式机 CPU 的水平,但比奕斯伟和英特尔的低功耗实现要好。
有时,内存子系统必须执行核心到核心的传输以保持缓存一致性。Anandtech 等网站使用核心到核心的延迟测试来探测此行为。
平头哥的 CIU 可以在核心之间以合理的速度传递数据。它比 P550 好得多,后者在四核集群中的延迟超过 300 纳秒。
C910 是平头哥的第一款乱序核心。从一开始,C910 在某些方面就比 P550 更精致。核心到核心的延迟更好,未对齐的访问得到正确处理,并且有矢量支持。与 P550 一样,C910 旨在扩展到广泛的低功耗应用。L2 容量可配置为高达 8 MB,多集群设置允许扩展到高核心数。
本文转自媒体报道或网络平台,系作者个人立场或观点。我方转载仅为分享,不代表我方赞成或认同。若来源标注错误或侵犯了您的合法权益,请及时联系客服,我们作为中立的平台服务者将及时更正、删除或依法处理。