跳转至

块指令执行模型

基于灵犀指令集架构实现的处理器以块指令为单位执行程序。顺序执行的处理器需要按照程序语义的顺序一条条执行块指令。而乱序执行的处理器,可乱序执行块指令但要顺序提交。

块指令的执行过程包括三个步骤:

  1. 准备(Prepare):一层调度器取指和分配一层寄存器到二层执行单元,作为它的数据输入输出。
  2. 执行(Execute):二层执行单元执行本块的块体指令。
  3. 提交(Commit):二层执行单元提交结果到一层调度器,以支持后序块的执行。

块指令执行模型示意图如下:

block-execute

块指令初始化

块指令执行前,需要初始化块指令的内部状态,其中包括:

保存块参数

执行块指令前,处理器需要保存当前块的BPC和跳转参数等信息到私有寄存器BARG中,以便块执行结束后能够正确地跳转到下一个块指令。其中包括:

  • 设置本块的块类型到BARG寄存器的 BlockType 字段,用于分配到对应的块引擎执行以及异常处理。
  • 对于支持乱序执行的处理器,应将本块指令的BPC记录到BARG寄存器的BPC 字段。
  • 根据块头中的跳转参数保存下个块的地址到BARG寄存器的 BPCN 字段。对于实现了分支预测的处理器,可以根据分支预测单元记录的信息设置该字段。
  • 根据块头中的跳转参数设置BARG寄存器的 TYPE 字段。
  • 记录本块的屏障属性到BARG寄存器的 AQ,RL 字段。
  • 如果是分离块,将块内的本地返回地址记录到BARG寄存器的 LRA 字段。
    BlockType     -> BARG.BlockType;  // 记录本块的块类型
    CurrentBPC    -> BARG.BPC;        // 保存本块BPC
    NextBPC       -> BARG.BPCN;       // 记录下一个块的BPC
    BranchType    -> BARG.TYPE;       // 记录本块跳转方式
    aq,rl         -> BARG.AQ, RL;     // 记录本块的屏障属性
    TPC(B.TEXT)+4 -> BARG.LRA;        // 记录本地返回地址(分离块)

不同跳转类型下,NextBPC的计算方式有所不同:

  • FALL:NextBPC是顺延块的BPC。
  • DIRECT,CALL和CONDNextBPC = BPC + BNextOffset。其中BNextOffset表示块头中指示的跳转偏移。
  • IND,ICALL和RET:NextBPC通过分支预测单元的预测得到。

全局寄存器状态备份

块处理器在块初始化阶段根据块体或块头中给出的寄存器声明,识别输入/输出寄存器集合,并对相应的通用全局寄存器(GGPR)执行重命名或备份。重命名用于解依赖与提交期正确回写;备份用于在块内通过私有形参寄存器进行稳定访问。

一体块(块头和块体连续存储):

  • 解析块体指令的寄存器操作数,识别输入/输出GGPR,并在块初始化时对这些寄存器执行重命名。
  • 示例:以下为一条三输入、二输出的块指令。在初始化阶段,块处理器对 a0、a3、sp、ra 执行重命名。
    BSTART.STD [.foo.start, .foo.stop], [a0, a3, sp], [sp, ra]

分离块(块头和块体分开存储):

  • 按块头指令 B.IOR 指示的寄存器信息,对GGPR进行备份与记录:
    • 将输入GGPR的当前内容备份到块内输入形参寄存器 RI0~RI11
    • 将输出GGPR的当前内容备份到块内输出形参寄存器 RO0~RO3
  • 将输出寄存器编号写入 BARGRegDst0~RegDst3,供块提交时使用。
  • 若为数据(Tile)块,还需依据块头指令 B.IOT 建立 Tile 形参与实际 Tile 寄存器的映射:
    • 将输入 Tile 形参(如 TA、TB 等)映射到 B.IOT 指定的输入 Tile 寄存器;
    • 将输出 Tile 形参(如 TO 等)映射到 B.IOT 指定的输出 Tile 寄存器;

伪代码示例:

    // 拷贝输入GGPR内容,RegSrc[0~11]为B.IOR中输入GGPR的编号
    RI0 = RegSrc0;
    RI1 = RegSrc1;
    ...
    RI11 = RegSrc11;
    // 拷贝输出GGPR内容,RegDst[0~3]为B.IOR中输出GGPR的编号
    RO0 = RegDst0;
    RO1 = RegDst1;
    ...
    RO3 = RegDst3;
    // 记录输出GGPR编号至BARG
    RegDst0 -> BARG.RegDst0;
    RegDst1 -> BARG.RegDst1;
    ...
    RegDst3 -> BARG.RegDst3;
    // 建立输入输出Tile映射关系
    SrcTile0 -> TA;
    SrcTile1 -> TB;
    ... 
    SrcTile7 -> TH;

    DstTile0 -> TO;
    DstTile1 -> TO1;
    DstTile2 -> TO2;
    DstTile3 -> TO3;

若块内形参寄存器没有被初始化,那么块体内访问时其结果是未定义的,并且架构不保证执行结果的正确性。

模版块(仅块头描述,无块体)

  • 依据块头指令 B.IOR/B.IOT 表达的寄存器信息执行重命名与解依赖,确保模板实例化或后续拼接时的寄存器一致性。
  • 由于模板块没有块体指令,不需要将寄存器内容备份到块内私有寄存器(不使用 RI/RO 等私有备份)。

块指令执行

块指令作为灵犀指令集的基本组织单元,其执行流程依据块类型和块体执行模式有所差异。并且,一体块和分离块形式下定义的块指令在控制流、寄存器访问和指令调度等方面具有不同的执行机制,详见块指令执行机制

块体的具体执行方式分为标量模式、串行模式和并行模式,详见块体执行方式。在支持乱序执行的块引擎中,块体内的指令可乱序调度执行,但需保证指令按顺序提交,确保执行结果的正确性。

指令输入输出

  1. 一体块内的块体指令可以直接使用全局寄存器(GGPR)作为输入输出,并且写是立即生效的,读的是前序块体指令更新后的值。
  2. 一体块内的块体指令允许重复对同一个全局寄存器进行写操作。
  3. 分离块内的块体指令不支持直接读写全局寄存器,只能读写块内形参寄存器RI和RO。
  4. 分离块内的块体指令不允许重复写同一个全局寄存器,否则触发 重复设置全局寄存器异常

例如如下的一体块指令:

block (a0, a3, sp) -> (sp, a1):
    I1 : subi sp, 32,  ->sp
    I2 : add a0, a3,   ->t
    I3 : ld [sp, t#1], ->a1

在执行第一条指令I1时,对 sp寄存器的更新是立即生效的,不需要等待整个块提交。后续I3指令使用的sp值即是I1指令输出的sp值。

访问系统状态

块体内可以通过以下指令访问系统状态:

访存和访问系统寄存器都是立即生效的,不会等待块指令提交。

灵犀指令集定义的指令块是一种弱化的指令块。块指令只定义了寄存器的申请和释放,不对访存有强的原子性约束。

除非对块指令额外定义了原子属性,否则块内访存系统看到的是独立的Load和Store请求,访存请求已经没有了块的概念。对于带有原子属性的块指令,其块内有对访存额外的限制,详细请看B.CATR中关于原子属性的定义。

跳转参数设置

对于间接跳转类型的 INDICALLRET 块,需要通过块体指令来计算其跳转目标块的地址,并设置到BARG寄存器的BPCN字段。

    setc.tgt SrcL  # SrcL的内容设置到 BARG.BPCN字段

对于条件跳转类型的 COND 块,块内动态判断是否满足跳转条件,并设置到BARG寄存器的TAKEN字段。

    setc.cond SrcL, SrcR # 比较结果设置到 BARG.TAKEN

对于调用类型的 CALLICALL 块,块内需要将其顺延块的BPC保存到Ra寄存器中,以便在调用返回时能够正确地跳转到下一个块指令。

    setret <label>, ->ra

异常和中断

在块指令执行的过程中,允许触发中断和异常。处理方法请见中断和异常介绍。

块指令提交

块指令提交时会根据BARG寄存器的内容,对本块中的指令进行提交。其中包括:

  • 对于分离块,块指令按照BARG寄存器中记录的 RegDst0~RegDst3 信息,将块内形参寄存器 RO0~RO3 的内容更新到对应的全局寄存器中。
  • 块指令按照BARG寄存器的 BPCNTaken 字段更新BPC,并跳转。
  • 块指令提交后,BARG寄存器的内容会被清空,以便下一次块指令执行时重新初始化。

需要特殊注意的是,块指令提交时 不允许释放块内私有寄存器资源,并且继承给执行在相同块引擎的下个块指令使用。

block_a:
    BSTART.STD FALL
    inst0 xx, xx, ->t
    inst1 xx,     ->u
    inst2 xx, xx, ->Rx 
    inst3 xx, xx, ->t
# block_a执行结束后保留T和U寄存器,继承给后序块指令
block_b:
    BSTART.STD DIRECT
    inst4 t#2, 10, ->t     ; 输入t#2索引指令inst0的结果
    inst5 u#1, xx, ->Rx    ; 输入u#1索引指令inst1的结果
    ...

基于这种继承机制,当一个块指令触发异常并跳转到异常处理程序时,软件可以通过一个新块来保存异常块的状态,以便进行灵活的状态迁移和调度。