跳转至

Y86-64的流水线实现

与SEQ处理器之间的区别: - PC的计算在取指阶段进行 - 每一个阶段都有一个流水线寄存器

SEQ+:重新安排计算阶段

  • 使更新PC阶段在一个时钟周期开始时执行,而不是结束时执行
  • 在SEQ+中,我们创建状态寄存器来保存在一条指令执行过程中计算出来的信号
    • 当一个新的时钟周期开始时,这些信号值通过同样的逻辑来计算当前指令的PC
    • 将寄存器标号为pIcodepCnd等,其保存的是前一个周期中产生的控制信号

      [!SEQ+中的PC] SEQ+中并没有硬件寄存器来存放程序计数器 而是根据从前一条指令保存下来的一些状态信息来动态的计算PC

插入流水线寄存器

- 每个流水线寄存器可以存放多个字节和字 - 标号: - F:保存程序计数器的预测值 - D: - 位于取指和译码阶段之间 - 保存关于最新取出的指令的信息 - 即将由译码阶段进行处理 - E: - 位于译码和执行阶段之间 - 保存关于最新译码的指令从寄存器文件读出的值的信息 - 即将由执行阶段进行处理 - M: - 位于执行和访存阶段之间 - 保存最新执行的指令的结果用于处理条件转移的条件分支和分支目标的信息 - 即将由访存阶段进行处理 - W: - 位于访存阶段和反馈路径之间 - 反馈路径将计算出来的值提供给寄存器文件写,即此时才进行新数据对寄存器进行更新 - 且若为ret指令,还要向PC选择逻辑提供返回地址

预测下一个PC

  • 要做到每一个时钟周期都有一条新的指令进入执行阶段并最终完成,我们必须在取出当前指令之后,马上确定下一条指令的位置
  • 如果取出的指令是条件分支指令,要到几个周期后,才能知道是否要选择分支
  • 类似的,如果取出的指令是ret,要到指令通过访存阶段,才能确定返回地址
    • 对于ret,我们不进行任何的预测,只是简单的暂停处理新的指令,直到ret指令通过写回阶段
  • 除了条件转移指令和ret以外,根据取指阶段中计算出的信息,我们能够确定下一条指令的地址
    • calljmp:下一条指令的地址是指令中的常数字valC
    • 对于其他指令来说,就是valP
  • 分支预测:猜测分支方向并根据猜测开始取指的技术被称为分支预测
  • 新的PC可能为三个值:
    • 预测的PC
    • 对于到达流水线寄存器M的不选择分支的指令来说是valP的值
    • ret指令到达流水线寄存器W时的返回地址的值

流水线冒险

当相邻的指令件存在相关的时候: 1. 数据相关:下一条指令会用到这一条指令计算出的结果 2. 控制相关:一条指令要确定下一条指令的位置(例如执行跳转或调用或返回) - 相关可能导致流水线产生计算错误,称为冒险 - 数据冒险 - 控制冒险

数据冒险

用暂停来避免数据冒险

  • 暂停时,处理器会停止流水线中一条或多条指令,直到冒险条件不再满足
  • 暂停技术就是让一组指令阻塞在他们所处的阶段,而允许其他指令继续通过流水线
  • 对于暂停的实现方法:插入一个气泡
    • 这个气泡类似于一个自动产生的nop指令:一个空操作,不改变任何东西
  • 简单,但是性能下降,降低了整体的吞吐量

使用转发来避免数据毛线

  • 与其暂停直到写完成,不如简单地将要写的值传到流水线寄存器E作为源操作数
  • 数据转发:将结果值直接从一个流水线阶段传到较早阶段
  • 转发技术的应用:
    • 当访存阶段中有对寄存器未进行的写时
    • 将新计算出来的值从执行阶段传到译码阶段
  • 几个转发源:
    • ALU产生的以及其目标为写端口E的值(valE)
    • 从内存中读出的以及其目标为写端口M的值(valM)
  • 两个转发目的:valA和valB
  • 是否需要转发的检测方法:逻辑会将目的寄存器ID与源寄存器ID比较

加载/使用数据冒险

  • 转发不能解决的数据冒险:内存读在流水线发生的比较晚
  • 解决方法:将暂停与转发相结合,在0x028的执行阶段插入一个气泡

避免控制冒险

  • 当处理器无法根据处于取指阶段的当前指令来确定下一条指令地址时,会出现控制冒险
  • 控制冒险只会发生在ret指令和跳转指令
  • 对于ret指令产生的控制冒险,我们通过插入气泡达到暂停的目的
  • 对于条件跳转指令
    • 先通过预测分支取出预测跳转目的地的指令,然后插入气泡
    • 在确定跳转目的地之后,再消除气泡

异常处理

  • 异常可以由程序执行从内部产生,也可以由某个外部信号从外部产生
  • 在指令集体系结构中,包括三种不同的内部产生的异常:
    • halt指令
    • 非法指令和功能码组合的指令
    • 取指或数据读写试图访问一个非法地址
  • 异常处理的一些细节问题:
    • 可能同时有多条指令会引起异常
      • 例如:在一个流水线操作的周期内,取指阶段中有halt指令,而数据内存会报告访存阶段中的指令数据地址越界
      • 解决方法:由流水线中最深的指令引起的异常,优先级最高,所以应该报告访存阶段中的指令地址越界
    • 当首先取出一条指令,开始执行时,导致了一个异常,而后由于分支预测错误,取消了该指令
      • 预测阶段,预测的指令一直处于取指阶段
    • 流水线话的处理器会在不同的阶段更新系统状态的不同部分
      • 例如:一条指令导致了一个异常,它后面的指令在异常指令完成之前改变了部分状态,类似如下的代码:
      • 解决方法:在每个流水线寄存器中都包括了一个状态码stat如果一条指令再起处理中某个阶段产生了一个异常,这个状态字段就被设置成指示异常的种类。异常状态与该指令的其他信息一起沿着流水线传播,直到它到达写回阶段。此时,流水线控制逻辑发现了异常,并停止执行。为了避免异常指令之后的指令更新任何程序员可见的状态,当处于访存或写回阶段中的指令异常导致时,流水线控制逻辑必须禁止更新条件码寄存器或是数据内存,在上面的例子中,将禁止addq指令更新条件码寄存器

PIPE各阶段的实现

流水线控制逻辑

  • 在流水线控制逻辑中,有四种特殊的控制情况:
    • 加载/使用冒险:在一条从内存中读出一个值的指令(mrmovqpopq)和一条使用该值的指令之间,流水线必须暂停一个周期
    • 处理ret:流水线必须暂停直到ret指令到达写回阶段
    • 预测错误分支:在分支逻辑发现不应该选择分支之前,分支目标处的几条指令已经进入流水线了。必须取消这些指令,并从跳转指令后面的那条指令开始取指
    • 异常:当一条指令导致异常,进行后面的指令更新程序员可见的状态,并且在异常指令到达写回阶段时,停止执行

特殊控制情况所期望的处理

  • 对于加载/使用冒险,我们需要在加载和使用的执行阶段中之间插入气泡,然后再使用转发逻辑来解决这个数据冒险
    • 这样可以将流水线寄存器D保持为固定状态,从而讲一个指令阻塞在译码阶段
  • 对于ret指令:利用气泡,将流水线寄存器F保持为固定状态,阻止后续的指令执行
  • 对于分支预测错误:取消两条不正确的已取指令(最多到达译码阶段)
  • 对于异常的指令
    • 目标:在前面的所有指令结束前,后面的指令不能影响程序的状态
    • 防止后面的指令修改程序员可见状态的措施:
      • 禁止执行阶段中的指令设置条件码
      • 向内存阶段中插入气泡,以禁止想数据内存中写入
      • 当写回阶段中有异常指令时,暂停写回阶段,从而暂停流水线

发现特殊控制条件

条件 触发条件
处理ret IRET\(\in\){D_icode, E_icode, M_icode}
加载/使用冒险 E_icode \(\in\){IMRMOVL, IPOPL} && E_dstM\(\in\){d_srcA, d_srcB}
预测错误的分支 E_icode == IJXX && !e_Cnd
异常 m_stat\(\in\){SADR, SINS,SHLT}

流水线控制机制

  • 各个流水线寄存器在三种特殊情况下应该采取的行动

控制条件的组合

- 对于组合A:在执行阶段有一条不选择分支的跳转指令,而译码阶段中有一条ret指令 - 当ret指令位于不选择分支的目标处,流水线控制逻辑应该发现分支预测错误,因此要取消ret指令 - 即组合情况A的处理与预测错误的分支相似,只不过是在取指阶段暂停。在下一个周期,PC选择器逻辑会选择跳转后的指令地址,而不是预测的程序计数器值 - 对于组合B:包括了一个加载/使用冒险,其中加载指令设置寄存器%rsp,然后ret指令用这个寄存器作为源操作数 - 流水线控制逻辑应该将ret指令阻塞在译码阶段

控制逻辑实现

bool F_stall = 
    // Condition for a load/use hazard
    E_icode in {IMRMOVQ, IPOPQ} && 
    E_dstM in {d_srcA, d_srcB} ||
    // Stalling at fetch while ret passes through pipeline
    IRET in {D_icode, E_icode, M_icode};

bool D_stall = 
    // COndition for a load/use hazard
    E_icode in {IMRMOVQ, IPOPQ} &&
    E_dstM in {d_srcA, d_srcB};

bool E_bubble = 
    // Mispredict branch
    (E_icode == IJXX && !e_Cnd) ||
    // Condition for a load/use hazard
    E_icode in {IMRMOVQ, IPOPQ} &&
    E_dstM in {d_srcA, d_srcB};

// Exception 
bool set_cc = E_icode == IOPQ &&
    !M_stat in {SADR, SINS, SHLT} && !W_stat in {SADR, SINS, SHLT};

bool M_bubble = m_stat in {SADR, SINS, SHLT} || W_stat in {SADE, SINS, SHLT};

bool W_stall = W_Stat in {SADR, SINS, SHLT};