Vengineerの妄想(準備期間)

人生は短いけど、長いです。人生を楽しみましょう!

RISC-VなGPGPUであるVortexを深堀する (その5)

はじめに

RISC-VなGPGPUであるVortexを深堀する (その5)

(その2) から (その4) までは、RTL をみてみましたが、今回は software である runtime をみていきます。runtime は、Vortex が動作するときのソフトウェアです。

runtime

runtime の最初のコードは、vx_start.S の _start です。vx_link32.ld の中の

ENTRY(_start)

で定義されています。

.global _start
.type   _start, @function
_start:

  # initialize per-thread registers
  csrr a0, CSR_NW  # get num warps
  la a1, init_regs
  .insn s 0x6b, 1, a1, 0(a0)  # wspawn a0, a1
  jal init_regs

CSR_NW は、RTLではここ で下記のように定義されています。この定義を使って、ソフト側のマクロ定義を生成しているっぽいです。

// Machine SIMT CSRs
`define CSR_NT          12'hFC0
`define CSR_NW          12'hFC1
`define CSR_NC          12'hFC2

.insn を使って命令を実行しています。命令コードは、0x6b, 1 でこれは、wspawn 命令に対応します。wspawn 命令の第一引数は a0 (CSR_NW)、第二引数は a1 (init_regsのアドレス)になります。

0x6b は、VX_define.vh の中で下記のように定義されています。

`define INST_GPGPU      7'b1101011

VX_decode の下記の部分で、INST_GPGPU のデコードを行っています。

0x6b, 1 の 1 が func3 の部分で、op_type = INST_OP_BITS'(INST_GPU_WSPAWN); に対応します。

            `INST_GPGPU: begin 
                ex_type = `EX_GPU;
                case (func3)
                    3'h0: begin
                        op_type = rs2[0] ? `INST_OP_BITS'(`INST_GPU_PRED) : `INST_OP_BITS'(`INST_GPU_TMC);
                        is_wstall = 1;
                        `USED_IREG (rs1);
                    end
                    3'h1: begin
                        op_type = `INST_OP_BITS'(`INST_GPU_WSPAWN);
                        `USED_IREG (rs1);
                        `USED_IREG (rs2);
                    end
                    3'h2: begin
                        op_type = `INST_OP_BITS'(`INST_GPU_SPLIT);
                        is_wstall = 1;
                        `USED_IREG (rs1);
                    end
                    3'h3: begin 
                        op_type = `INST_OP_BITS'(`INST_GPU_JOIN);
                        is_join = 1;
                    end
                    3'h4: begin 
                        op_type = `INST_OP_BITS'(`INST_GPU_BAR);
                        is_wstall = 1;
                        `USED_IREG (rs1);
                        `USED_IREG (rs2);
                    end                
                    3'h5: begin
                        ex_type = `EX_LSU;
                        op_type = `INST_OP_BITS'(`INST_LSU_LW);
                        op_mod  = `INST_MOD_BITS'(2);
                        `USED_IREG (rs1);
                    end
                    default:;
                endcase
            end

VX_gpu_unit の中で下記のようにデコードされています。

    wire is_wspawn = (gpu_req_if.op_type == `INST_GPU_WSPAWN);

下記のように wspawn にまとめられて

    // wspawn

    wire [31:0] wspawn_pc = rs2_data;
    wire [`NUM_WARPS-1:0] wspawn_wmask;
    for (genvar i = 0; i < `NUM_WARPS; i++) begin
        assign wspawn_wmask[i] = (i < rs1_data);
    end
    assign wspawn.valid = is_wspawn;
    assign wspawn.wmask = wspawn_wmask;
    assign wspawn.pc    = wspawn_pc;

下記のように、warp_ctl_if として出力され、VX_fetch 内の VX_VX_warp_sched に接続されています。

    assign {warp_ctl_if.tmc, warp_ctl_if.wspawn, warp_ctl_if.split, warp_ctl_if.barrier} = rsp_data_r[WCTL_DATAW-1:0];
    VX_warp_sched #(
        .CORE_ID(CORE_ID)
    ) warp_sched (
        `SCOPE_BIND_VX_fetch_warp_sched

        .clk              (clk),
        .reset            (reset),     

        .warp_ctl_if      (warp_ctl_if),
        .wstall_if        (wstall_if),
        .join_if          (join_if),
        .branch_ctl_if    (branch_ctl_if),

        .ifetch_req_if    (ifetch_req_if),

        .fetch_to_csr_if  (fetch_to_csr_if),
        
        .busy             (busy)
    ); 

VX_warp_schedの中で下記のような処理をしています。warp_ctl_if.wspawn.pc には、init_regsのアドレスが入っています。

            if (warp_ctl_if.valid && warp_ctl_if.wspawn.valid) begin
                use_wspawn <= warp_ctl_if.wspawn.wmask & (~`NUM_WARPS'(1));
                wspawn_pc  <= warp_ctl_if.wspawn.pc;
            end

init_regs 関数を実行します。

下記の .insn s 0x6b, 0, x0, 0(a0) は、tmc 命令を実行しています。

  # return back to single thread execution
  li a0, 1
  .insn s 0x6b, 0, x0, 0(a0)  # tmc a0

RTL では、VX_gpu_unit の下記の部分です。

    // tmc

    wire [`NUM_THREADS-1:0] pred_mask = (taken_tmask != 0) ? taken_tmask : gpu_req_if.tmask;

    assign tmc.valid = is_tmc || is_pred;
    assign tmc.tmask = is_pred ? pred_mask : rs1_data[`NUM_THREADS-1:0];

wspawn と同様に VX_warp_sched に渡しています。

    // pack warp ctl result
    assign warp_ctl_data = {tmc, wspawn, split, barrier};

VX_warp_shecd の中で次のように使われています

            if (warp_ctl_if.valid && warp_ctl_if.tmc.valid) begin
                thread_masks[warp_ctl_if.wid]  <= warp_ctl_if.tmc.tmask;
                stalled_warps[warp_ctl_if.wid] <= 0;
            end 

stalled_warps は、下記のように wspawn の時に 1 に設定されています

            if (warp_scheduled) begin
                // stall the warp until decode stage
                stalled_warps[schedule_wid] <= 1;

                // release wspawn
                use_wspawn[schedule_wid] <= 0;
                if (use_wspawn[schedule_wid]) begin
                    thread_masks[schedule_wid] <= 1;
                end

                issued_instrs <= issued_instrs + 1;
            end

同じように、__init_tls 関数を実行します。

  # initialize TLS for all warps
  csrr a0, CSR_NW  # get num warps
  la a1, __init_tls
  .insn s 0x6b, 1, a1, 0(a0)  # wspawn a0, a1
  call __init_tls
  # return back to single thread execution
  li a0, 1
  .insn s 0x6b, 0, x0, 0(a0)  # tmc a0

BSS (初期化されたデータ領域)を 0 でクリアします。

  # clear BSS segment
  la      a0, _edata
  la      a2, _end
  sub     a2, a2, a0
  li      a1, 0
  call    memset

その後、何個かの関数を実行しています。

  # Initialize trap vector
  # a t0, trap_entry
  # csrw mtvec, t0

  # Register global termination functions
  la      a0, __libc_fini_array

  # to be called upon exit
  call    atexit

  # Run global initialization functions
  call    __libc_init_array

main 関数の前に実行されるコードは、前処理です。

main 関数が実行される前に、atexit 関数を実行して、なんかの関数を設定して、main 関数の後で exit 関数を呼ばれるようになるんですね。

  # call main program routine
  call    main

  # call exit routine
  tail    exit

exit 関数は、vx_start.S の中で次のように定義されています。

  • vx_perf_dump :
  • tmc
.section .text
.type _exit, @function
.global _exit
_exit:
  mv s0, a0
  call vx_perf_dump 
  mv gp, s0
  li a0, 0
  .insn s 0x6b, 0, x0, 0(a0)  # tmc a0

vx_perf_dump 関数は、vx_perf の次のところで定義されています。CSRをダンプしています。

void vx_perf_dump() {
    int core_id = vx_core_id();
    uint32_t* const csr_mem = (uint32_t*)(IO_CSR_ADDR + 64 * sizeof(uint32_t) * core_id);
    DUMP_CSR_32(0,  CSR_MPM_BASE)
    DUMP_CSR_32(32, CSR_MPM_BASE_H)
}

tmc にて、 single thread execution に戻して、初期状態にしています。

おわりに

次回は、RISC-VなGPGPUである Vortex を深堀する (その6)として、上記の main 関数をみてみます。