80386 早期内存访问
当英特尔设计80386时,他们给它提供了一个隐藏内存延迟的技巧:早期启动。80386并不需要等待指令到达其内存微操作,而是在当前指令的最后一个周期开始下一个指令的地址处理——有效地址、段重定位、总线周期。英特尔将其计入整体性能的约9%。这也是POPAD错误的来源。我在5月发布的z386 FPGA核心运行了原始的386微代码,但没有早期启动。在过去的一个月里,我添加了这一功能以及一系列其他优化,z386现在达到了ao486级别的性能:核心Doom(FPS)3DBench Landmark z386 0.1(5月)16.6 33.7 147 z386 0.4(6月)23.0 44.5 170 ao486 21.0 43.8 204 Doom(原始,最高细节)提高了约39%(16.6 → 23.0),超过了ao486的21.0,而16位3DBench现在也超过了ao486。主板时钟仍然保持在v0.1的85 MHz,因此性能提升完全来自于降低CPI,每个时钟完成更多工作。就每条指令而言,z386的周期数从远高于386变为几乎所有情况下接近或低于386:指令计时:z386 0.1 → 0.4与原始80386的对比。该系列早期发布的内存流水线介绍了早期启动的概念。这篇文章是关于在FPGA上构建它,以及其他CPI工作的内容,使得z386达到了同级。英特尔在Slager的ICCD '86论文《80386的性能优化》中讨论了早期启动。它的工作原理线索在微代码中。以下是一个读取内存操作数的ALU指令的条目(ADD reg, [mem]):; ADD/OR/ADC/SBB/AND/SUB/XOR m,r 04A EFLAGS -> FLAGSB FLGSBA RD 9 04B DLY 04C OPR_R -> TMPB WRITE_RESULT JMP UNL 04D TMPB SRCREG +-&|^ 有趣的是,第一个微指令04A已经发出了RD——它开始了内存读取。在它之前没有微指令计算有效地址、添加段基址或检查限制。地址生成是隐式的,由硬件逻辑完成。一个具体的例子使得这一点更清晰:add eax, 16 mov ebx, [eax+4] 在执行顺序中,微代码如上表所示。023行运行ALU(EAX + 16)并断言RNI——“运行下一条指令”——因此机器已经承诺开始执行下一个MOV r,m。024行将结果写回EAX,且同一个024周期是下一个指令(加载)的早期启动窗口:周期 add eax, 16 mov ebx, [eax+4] 1 023:EAX + 16在ALU中,RNI—— 2 024:写入EAX(=旧EAX + 16)早期启动窗口:查看下一条指令,转发刚生成的EAX,计算EA = EAX + 4,重定位,并发出RD 3——019:RD微代码 4——01A:DLY数据到达,写入OPR_R 5——01B:RNI 6——01C:OPR_R -> EBX 这种重叠使得内存访问至少提前一个周期,从而减少加载/存储延迟。微妙之处在于,前一条指令的最后一个微指令可能会写回一个寄存器,造成数据风险。在这里,EAX在这个周期内被写入,因此它的新值尚未在寄存器文件中。解决办法是常见的——一个转发网络,所以早期启动看到的是最新值。386DX的转发网络有一个边界情况错误,导致了著名的POPAD错误:当POPAD后跟随一条使用[EAX+...]的指令时,早期启动机器会转发错误的值。另一种看待早期启动的方式是以宏指令的粒度进行粗略流水线,其中前一条指令的最后一个周期(RNI延迟槽)是该指令的写回阶段,并与下一条指令的第一个周期,即早期启动周期重叠。实现早期启动 z386通过小生命周期跟踪每条指令。这里两个重要事件是i_pop——指令从预取队列中提取的周期,正是前一条指令的RNI延迟槽——和i_first,自己微代码的第一个周期。i_pop正是上述第2周期的386早期启动窗口。因此在z386中,早期启动是:在i_pop计算有效地址和线性地址,转发在飞行中的寄存器写入。解码器生成基址/索引/位移选择器,并:wire [31:0] ea_early = calc_ea_core(fwd_onehot_gpr(ea_dec_base_sel_r), fwd_onehot_gpr(ea_dec_index_sel_r), ...); fwd_onehot_gpr是旁路。如果前一条指令的延迟槽写入目标为EA的基址或索引寄存器,它会将写回值(dest_value)替代寄存器文件的副本——单独处理字节、字和双字的写入,因为部分写入只更新寄存器的一部分:FWD_BLO: fwd_onehot_gpr = {cur[31:8], dest_value[7:0]}; // AL FWD_W: fwd_onehot_gpr = {cur[31:16], dest_value[15:0]}; // AX 默认:fwd_onehot_gpr = dest_value; // EAX 堆栈指针也通过forwarded_esp得到了相同的处理,因此在调整ESP之后立即进行的推送仍然会看到新值。ea_early随后在i_pop中注册到ea_reg,为i_first的加载/存储微代码做好准备。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡