はじめに
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 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 関数の前に実行されるコードは、前処理です。
- wspawn で init_regs を 実行
- swpawn で __init_tls を実行
- memset にて、BSSを0 で初期化
- __libc_fini_array を実行
- atexit を実行
- __libc_init_array を実行
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 関数をみてみます。