在指令周期内如何执行微码?

时间:2019-05-20 09:49:11

标签: assembly cpu cpu-architecture

从开放资源中,我可以得出结论,微代码大约可以直接由CPU执行,并负责实现指令代码。 Wikipedia还指示指令代码的每次执行都将经历fetch-decode-execute指令周期。但是,我找不到任何参考资料来说明在此三个阶段中如何执行微代码。所以我的问题是,微代码执行与指令周期之间的关系是什么?微码在指令执行的获取,解码和执行阶段如何工作?

此外,stackoverflow anwser表示在现代Intel CPU中,即使最简单的指令,例如DIVMOV也将在执行前用微码编译,因此最好由任何人解释。如果确实是这样的话,则以此类CPU的示例为例。

1 个答案:

答案 0 :(得分:5)

div并不简单,它是最难计算的整数运算之一!它是在Intel CPU上进行微编码的,与movadd / sub甚至是imul都不一样,它们都是现代Intel的单核。有关说明表和微体系结构指南,请参见https://agner.org/optimize/。 (有趣的事实:AMD Ryzen不会对div进行微代码处理;它只有2个微指令,因为它必须写入2个输出寄存器。Piledriver和更高版本也将32和64位除法为2个微指令。)

所有指令均解码为1或1个以上的微指令(在大多数程序中,大多数指令在当前CPU上为1个微指令)。在Intel CPU上解码为4或更少的微指令的指令被称为“未微编码”,因为它们对多指令不使用特殊的MSROM机制。


没有将x86指令解码为uops的CPU使用简单的三相提取/解码/执行周期,因此问题的前提部分没有任何意义。再次,请参阅Agner Fog的微体系结构指南。

您确定要询问有关现代Intel CPU的信息吗?一些较旧的CPU在内部进行微编码,尤其是非流水线CPU,其中执行不同指令的过程可以按不同顺序激活不同的内部逻辑块。 控制该逻辑的逻辑也称为微代码,但它与流水线无序CPU上下文中该术语的现代含义不同。

如果这就是您要查找的内容,请参见How was microcode implemented in retro processors? ,以了解反向计算。SE适用于非流水线CPU(例如6502和Z80),其中记录了一些微代码内部定时周期。


微码指令如何在现代Intel CPU上执行?

当微编码的“间接uop”到达Sandybridge系列CPU的IDQ的头部时,它将接管issue / rename阶段,并从微码序列器MS-ROM中输入直到指令已发出所有微指令,前端才可以继续将其他微指令发布到无序的后端。

IDQ是指示发布/重命名阶段(将uops从前端发送到无序后端)的指令解码队列。它缓冲来自uop缓存+传统解码器的uops,以吸收气泡和突发。这是David Kanter's Haswell block diagram中的56个uop队列。 (但是这表明微码只能在队列中之前读取,这与英特尔对某些性能事件 1 的描述不符,或者与运行a的微码指令所发生的情况不符。数据相关的微码数。

这可能不是100%准确,但至少可以作为大多数性能影响的心理模型 2 。可能对此性能还有其他解释到目前为止我们已经观察到的效果。)

这仅在需要超过4微码的指令时发生;需要4个或更少解码的指令以在普通解码器中分离uops,并且可以正常发出。例如xchg eax, ecx在现代Intel上是3 uops:Why is XCHG reg, reg a 3 micro-op instruction on modern Intel architectures?详细介绍了我们可以弄清楚这些uops到底是什么。

微代码指令的特殊“间接” uop在解码uop缓存DSB(potentially causing code-alignment performance issue)中占据了整整一行。我不确定它们是否仅在从uop缓存和/或旧式解码器IDQ馈入问题阶段的队列中只接受1个条目。无论如何,我用术语“间接uop”来描述它。它实际上更像是尚未解码的指令或指向MS-ROM的指针。 (可能一些微码指令可能是一对“正常”微指令和一个微码指针;这可以解释为它把整个uop缓存行都带给了自己。)

我很确定它们在到达队列的开头之前不会完全扩展,因为某些微码指令的数量取决于寄存器中的数据,是可变的。值得注意的是rep movs,它基本上实现了memcpy。实际上,这很棘手。 rep movs根据对齐方式和大小使用不同的策略,实际上需要进行一些条件分支。但是它正在跳转到不同的MS-ROM位置,而不是跳转到不同的x86机器代码位置(RIP值)。参见Conditional jump instructions in MSROM procedures?

Intel's fast-strings patent也为P6中的原始实现提供了一些启示:最初的n复制迭代是在后端进行的;并指定后端时间以将ECX的值发送给MS。由此,如果需要更多的代码,微码定序器可以发送正确数量的副本,而无需在后端进行分支。也许处理几乎重叠的src和dst或其他特殊情况的机制毕竟不是基于分支的,但是Andy Glew确实提到缺乏微代码分支预测是实现的问题。因此,我们知道它们很特别。那又回到了P6天; rep movsb现在更加复杂。

根据指令的不同,它可能会也可能不会耗尽无序后端的预留站(又称为调度程序),同时整理出要执行的操作。 rep movs对副本执行此操作>不幸的是,Skylake上有96个字节(根据我对性能计数器的测试,将rep movs放在imul的独立链之间)。这可能是由于预测不正确的微代码分支所致,与常规分支不同。也许分支丢失快速恢复对它们不起作用,因此直到它们退休后才被发现/处理。 (有关详情,请参见微代码分支的问答)。


rep movsmov 非常不同。像mov这样的普通mov eax, [rdi + rcx*4]即使在复杂的寻址模式下也是单个uop。 mov存储区是1个微融合的uop,包括可以按任意顺序执行的存储地址和存储数据uop,将数据和物理地址写入存储缓冲区,以便存储区在之后可以提交到L1d指令从乱序的后端退出,成为非推测性的。 rep movs的微代码将包含许多加载和存储操作。


脚注1

我们知道在Skylake上有诸如idq.ms_dsb_cycles这样的性能事件:

  

[当微码序列器(MS)忙时,将由解码流缓冲区(DSB)发起的指令传送到指令解码队列(IDQ)的周期]

如果微码只是输入到IDQ前面的第三个可能的uops源,那将毫无意义。但是有一个事件的描述听起来像这样:

  

idq.ms_switches
        [来自DSB(解码流缓冲区)或MITE(旧版)的开关数量          解码管道)到微码定序器]

我认为这实际上意味着,当问题/重命名阶段切换到从微码定序器而不是IDQ (用于保存DSB和/或MITE的uops)中获取uops时,它就很重要。并不是说 IDQ 会切换其输入uops的来源。

脚注2

为验证该理论,我们可以构建一个测试用例,在微编码指令之后,通过很多容易预测的跳转到冷的i-cache行,并查看前端在缓存未命中和排队到uop后进入多远大rep scasb执行期间IDQ和其他内部缓冲区。

SCASB不支持快速字符串,因此它非常慢,并且每个周期不会占用大量内存。我们希望它能在L1d达到目标,因此计时是高度可预测的。大概4k页足以让前端有足够的时间跟踪许多i缓存未命中事件。我们甚至可以将连续的虚拟页面映射到相同的物理页面(例如,从用户空间中将mmap放在文件上)

如果微代码指令后面的IDQ空间在执行时可以被以后的指令所填充,那么这将为前端留出更多空间,以便前端可以在需要时从更多的i高速缓存行中获取数据。然后,我们可以希望在运行rep scasb加上一系列跳跃的情况下,检测总周期和/或其他性能计数器的差异。在每次测试之前,请在包含跳转说明的行上使用clflushopt

要以这种方式测试rep movs,我们也许可以通过虚拟内存玩弄技巧,以将连续的页面映射到同一物理页面,这再次使我们获得了加载和存储的L1d点击率,但是dTLB的延迟将很难控制。甚至在无填充模式下使用CPU引导,但这很难使用,并且需要自定义“内核”才能将结果放在可见的位置。

我非常有信心,当微码指令接管前端(如果尚未满载)时,我们会发现微指令进入IDQ。有一个表演事件

  

idq.ms_uops
        [当微码传送到指令解码队列(IDQ)时,          Sequenser(MS)忙]

和另外2个事件,例如仅计数来自MITE(传统解码)的uops或来自DSB(uop缓存)的uops的事件。英特尔对这些事件的描述与我对微代码指令(“间接uop”)如何接管发布阶段以从微码定序器/ ROM中读取uops的描述兼容,而前端的其余部分继续执行将uops传递至IDQ的另一端直到填满。