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

gna*_*yil 2 cpu assembly cpu-architecture

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

同样,这个stackoverflow的答案是说,在现代的Intel CPU中,即使最简单的指令(例如DIV和)MOV也将在执行之前以微码进行编译,因此,如果有人真的可以用此类CPU的示例进行解释,那将是最好的。

Pet*_*des 5

div这并不简单,它是最难计算的整数运算之一!它是在Intel CPU上进行微码处理的,与mov,或add/ sub甚至imul都不是现代Intel上的单核处理器不同。请参阅https://agner.org/optimize/以获取指令表和Microarch指南。(有趣的事实:AMD Ryzen不会微代码div;它只有2微码,因为它必须写入2个输出寄存器。Piledriver和后来的版本也使32位和64位除法2微码。)

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


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

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

如果您要寻找的是,请参阅如何在复古处理器中实现微代码?SE适用于非流水线CPU(例如6502和Z80),其中记录了一些微代码内部计时周期。


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

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

IDQ是指令解码队列,用于输入问题/重命名阶段(它将uops从前端发送到无序的后端)。它缓冲来自uop缓存+传统解码器的uops,以吸收气泡和突发。这是David Kanter的Haswell框图中的56 uop队列。(但是,这表明微码仅在队列之前被读取,这与英特尔对某些性能事件1的描述不符,或者与运行与数据相关的uops数的微编码指令不符。)

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

这仅在需要超过4微秒的指令时才会发生;需要4个或更少解码的指令以在普通解码器中分离uops,并且可以正常发出。例如,xchg eax, ecx在现代Intel上是3微指令为什么在现代Intel架构上使用XCHG reg来注册3 micro-op指令?详细介绍了我们可以弄清楚这些微指令实际是什么。

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

我很确定它们只有在到达队列的最前面时才能完全扩展,因为某些微编码指令的数量是可变的,具体取决于寄存器中的数据。值得注意的是rep movs基本实现memcpy。实际上,这很棘手。根据对齐方式和大小使用不同的策略,rep movs实际上需要执行一些条件分支。但是它正在跳转到不同的MS-ROM位置,而不是跳转到不同的x86机器代码位置(RIP值)。请参阅MSROM过程中的条件跳转指令?

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

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


rep movs与完全不同mov。即使具有复杂的寻址模式,普通的mov像是mov eax, [rdi + rcx*4]单个uop。一个mov存储区是1个微融合的uop,包括可以按任意顺序执行的存储地址和存储数据uop,将数据和物理地址写入存储缓冲区,以便在指令从存储区中退出后,存储区可以提交到L1d。无序的后端,变得没有投机性。的微码rep movs将包含许多加载和存储单元。


脚注1

我们知道idq.ms_dsb_cycles在Skylake上有一些perf事件:

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

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

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

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

脚注2

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

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

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

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

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

idq.ms_uops
[微码定序器(MS)忙时传送到指令解码队列(IDQ)的Uop]

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