## Implement Vector extension for rv32emu contributed by [<yuchen>](https://github.com/yuchen0620) ### Feature First, I add the **Standard Extension for Vector Instructions** in `feature.h` make it can perform the Feature test macro. ```c /* Standard Extension for Vector Instructions */ #ifndef RV32_FEATURE_EXT_V #define RV32_FEATURE_EXT_V 1 #endif /* Feature test macro */ #define RV32_HAS(x) RV32_FEATURE_##x ``` ### CSRS The vector extension adds 32 vector registers, and seven unprivileged CSRs (`vstart`, `vxsat`, `vxrm`, `vcsr`, `vtype`, `vl`,`vlenb`) to a base scalar RISC-V ISA. | Address | Privilege | Name | Description | | ------- | --------- | ------ | --------------------- | | 0x008 | URW | vstart | Vector start position | | 0x009 | URW | vxsat | Fixed-Point Saturate Flag | | 0x00A | URW | vxrm | Fixed-Point Rounding Mode | | 0x00F | URW | vcsr | Vector control and status register | | 0xC20 | URO | vl | Vector length | | 0xC21 | URO | vtype | Vector data type register | | 0xC22 | URO | vlenb | VLEN/8 (vector register length in bytes) | Hence I add the CSRs of vector extension in `riscv_private.h` and vector registers in `#define RV_REGS_LIST` of `riscv.h`. ```diff /* CSRs */ enum { /* floating point */ CSR_FFLAGS = 0x001, /* Floating-point accrued exceptions */ CSR_FRM = 0x002, /* Floating-point dynamic rounding mode */ CSR_FCSR = 0x003, /* Floating-point control and status register */ /* Machine trap setup */ CSR_MSTATUS = 0x300, /* Machine status register */ CSR_MISA = 0x301, /* ISA and extensions */ CSR_MEDELEG = 0x302, /* Machine exception delegate register */ CSR_MIDELEG = 0x303, /* Machine interrupt delegate register */ CSR_MIE = 0x304, /* Machine interrupt-enable register */ CSR_MTVEC = 0x305, /* Machine trap-handler base address */ CSR_MCOUNTEREN = 0x306, /* Machine counter enable */ /* machine trap handling */ CSR_MSCRATCH = 0x340, /* Scratch register for machine trap handlers */ CSR_MEPC = 0x341, /* Machine exception program counter */ CSR_MCAUSE = 0x342, /* Machine trap cause */ CSR_MTVAL = 0x343, /* Machine bad address or instruction */ CSR_MIP = 0x344, /* Machine interrupt pending */ /* low words */ CSR_CYCLE = 0xC00, /* Cycle counter for RDCYCLE instruction */ CSR_TIME = 0xC01, /* Timer for RDTIME instruction */ CSR_INSTRET = 0xC02, /* high words */ CSR_CYCLEH = 0xC80, CSR_TIMEH = 0xC81, CSR_INSTRETH = 0xC82, CSR_MVENDORID = 0xF11, /* Vendor ID */ CSR_MARCHID = 0xF12, /* Architecture ID */ CSR_MIMPID = 0xF13, /* Implementation ID */ CSR_MHARTID = 0xF14, /* Hardware thread ID */ + /*vector extension*/ + CSR_VSTART = 0x008, + CSR_VXSAT = 0x009, + CSR_VXRM = 0x00A, + CSR_VCSR = 0x00F, + CSR_VL = 0xC20, + CSR_VTYPE = 0xC21, + CSR_LENB = 0xC22, }; ``` Because the size of vector registers is typically not fixed and can be dynamically set by instructions such as `vsetvli`. The `vsetvli` instruction adjusts the vector length (VL) and can change the effective size of vector registers during program execution. Hence we have to define a struct for dynamic vector register. ```c // Define a structure to represent vector registers typedef struct { uint32_t* data; size_t size; }VectorRegister; ``` Change `riscv_internal` for vector register and new vector CSRs. ```diff struct riscv_internal { bool halt; /* indicate whether the core is halted */ /* I/O interface */ riscv_io_t io; /* integer registers */ riscv_word_t X[N_RV_REGS]; riscv_word_t PC; /* user provided data */ riscv_user_t userdata; /* csr registers */ uint64_t csr_cycle; /* Machine cycle counter */ uint32_t csr_time[2]; /* Performance conter */ uint32_t csr_mstatus; /* Machine status regester */ uint32_t csr_mtvec; /* Machine trap-handler base address */ uint32_t csr_misa; /* ISA and extensions */ uint32_t csr_mtval; /* Machine bad address or instruction */ uint32_t csr_mcause; /* Machine trap cause */ uint32_t csr_mscratch; /* Scartch register for machine trap handler */ uint32_t csr_mepc; /* Machine exception program counter */ uint32_t csr_mip; /* Machine interrupt pending */ uint32_t csr_mbadaddr; +#if RV32_HAS(EXT_V) + VectorRegister +vector_registers[N_RV_REGS]; + uint32_t csr_vstart; /* +Vector start position */ + uint32_t csr_vxsat; /* +Fixed-Point Saturate Flag */ + uint32_t csr_vxrm; /* +Fixed-Point Rounding Mode */ + uint32_t csr_vcsr; /* +Vector control and status +register */ + uint32_t csr_vl; /* +Vector length */ + uint32_t csr_vtype; /* +Vector data type register */ + uint32_t csr_vlenb; /* VLEN/8 (vector register length in bytes) */ +#endif ``` ### Decode The first thing I do is to add the vector instruction into the `RV_INSN_LIST` of `decode.h`. ```c #define RV_INSN_LIST \ _(nop, 0, 4, ENC(rs1, rd)) \ /* RV32I Base Instruction Set */ \ _(lui, 0, 4, ENC(rd)) \ _(auipc, 0, 4, ENC(rd)) \ _(jal, 1, 4, ENC(rd)) \ _(jalr, 1, 4, ENC(rs1, rd)) \ _(beq, 1, 4, ENC(rs1, rs2)) \ _(bne, 1, 4, ENC(rs1, rs2)) \ _(blt, 1, 4, ENC(rs1, rs2)) \ _(bge, 1, 4, ENC(rs1, rs2)) \ _(bltu, 1, 4, ENC(rs1, rs2)) \ . . . /* RV32V Standard Extension */ \ IIF(RV32_HAS(EXT_V))( \ _(vsetvli, 0, 4, ENC(rs1, rd)) \ _(vsetivli, 0, 4, ENC(rd)) \ _(vsetvl, 0, 4, ENC(rs1, rs2, rd)) \ _(vle8_v, 0, 4, ENC(rs1, vd)) \ _(vle16_v, 0, 4, ENC(rs1, vd)) \ _(vle32_v, 0, 4, ENC(rs1, vd)) \ _(vse8_v, 0, 4, ENC(rs1, vs3)) \ _(vse16_v, 0, 4, ENC(rs1, vs3)) \ _(vse32_v, 0, 4, ENC(rs1, vs3)) \ _(vadd_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vadd_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vadd_vi, 0, 4, ENC(vs2, vd)) \ _(vsub_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vsub_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vand_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vand_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vand_vi, 0, 4, ENC(vs2, vd)) \ _(vor_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vor_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vor_vi, 0, 4, ENC(vs2, vd)) \ _(vxor_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vxor_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vxor_vi, 0, 4, ENC(vs2, vd)) \ _(vsll_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vsll_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vsll_vi, 0, 4, ENC(vs2, vd)) \ _(vsrl_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vsrl_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vsrl_vi, 0, 4, ENC(vs2, vd)) \ _(vsra_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vsra_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vsra_vi, 0, 4, ENC(vs2, vd)) \ _(vmseq_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vmseq_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vmseq_vi, 0, 4, ENC(vs2, vd)) \ _(vmsne_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vmsne_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vmsne_vi, 0, 4, ENC(vs2, vd)) \ _(vmsltu_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vmsltu_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vmslt_vv, 0, 4, ENC(vs1, vs2, vd)) \ _(vmslt_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vmsgtu_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vmsgtu_vi, 0, 4, ENC(vs2, vd)) \ _(vmsgt_vx, 0, 4, ENC(rs1, vs2, vd)) \ _(vmsgt_vi, 0, 4, ENC(vs2, vd)) \ ) ``` #### Configuration-Setting Instructions(vsetvli/vsetivli/vsetvl) ![image](https://hackmd.io/_uploads/BkopTapup.png) For the zimm and uimm field, I add some masks into instruciotn decode masks in `decode.h`. ```diff /* instruction decode masks */ enum { // ....xxxx....xxxx....xxxx....xxxx INSN_6_2 = 0b00000000000000000000000001111100, // ....xxxx....xxxx....xxxx....xxxx FR_OPCODE = 0b00000000000000000000000001111111, // R-type FR_RD = 0b00000000000000000000111110000000, FR_FUNCT3 = 0b00000000000000000111000000000000, FR_RS1 = 0b00000000000011111000000000000000, FR_RS2 = 0b00000001111100000000000000000000, FR_FUNCT7 = 0b11111110000000000000000000000000, // ....xxxx....xxxx....xxxx....xxxx + VSETVLI_ZIMM = 0b01111111111100000000000000000000, //Vector + VSETIVLI_ZIMM = 0b00111111111100000000000000000000, }; ``` I also revise the `rv_insn` struct for zimm and uimm. ```diff typedef struct rv_insn { union { int32_t imm; uint8_t rs3; }; uint8_t rd, rs1, rs2; /* store IR list */ uint8_t opcode; #if RV32_HAS(EXT_C) uint8_t shamt; #endif +#if RV32_HAS(EXT_V) + int32_t zimm; + uint8_t uimm; +#endif ``` In order to decode `vsetvli` and `vsetivli`, I create decode function for zimm in decode.c. ```c #if RV32_HAS(EXT_V) /* decode vsetvli zimm[10:0] field * zimm = inst[30:20] */ static inline int32_t decode_vsetvli_zimm(const uint32_t insn) { return (insn & VSETVLI_ZIMM) >> 20; } /* decode vsetivli zimm[9:0] field * zimm = inst[29:20] */ static inline int32_t decode_vsetivli_zimm(const uint32_t insn) { return (insn & VSETIVLI_ZIMM) >> 20; } #endif ``` Because we have to distinguish `vsetvli`、`vsetivli` and `vsetvl` by the `inst[31]` and `inst[30]`. Hence, I define the following function to get inst[30]. ```c static inline int32_t decode_31(const uint32_t insn) { return (insn & 0x40000000 ) >> 30; } ``` By using the function below and the original decode function from rv32emu we can decode `vsetvli`、`vsetivli` and `vsetvl` correctly. ```c #if RV32_HAS(EXT_V) /* decode vsetvli * 31 30 20 19 15 14 12 11 7 6 0 * |0| zimm[11:0] | rs1 | 111 | rd | opcode | */ static inline void decode_vsetvli(rv_insn_t *ir, const uint32_t insn) { ir->zimm = decode_vsetvli_zimm(insn); ir->rs1 = decode_rs1(insn); ir->rd = decode_rd(insn); } /* decode vsetivli * 31 30 29 20 19 15 14 12 11 7 6 0 * |1|1| zimm[11:0] | rs1 | 111 | rd | opcode | */ static inline void decode_vsetivli(rv_insn_t *ir, const uint32_t insn) { ir->zimm = decode_vsetivli_zimm(insn); ir->uimm = decode_rs1(insn); ir->rd = decode_rd(insn); } /* decode vsetvl * 31 30 25 24 20 19 15 14 12 11 7 6 0 * |1| 000000| rs2 | rs1 | 111 | rd | opcode | */ static inline void decode_vsetvl(rv_insn_t *ir, const uint32_t insn) { ir->rs2 = decode_rs2(insn); ir->rs1 = decode_rs1(insn); ir->rd = decode_rd(insn); } static inline bool op_cs(rv_insn_t *ir, const uint32_t insn) { //configruation-setting switch(insn>>31){ case 0: //vsetvli decode_vsetvli(ir,insn); ir->opcode = rv_insn_vsetvli; break; case 1: switch(decode_31(insn)){ case 0: //vsetvl decode_vsetvl(ir,insn); ir->opcode = rv_insn_vsetvl; break; case 1: //vsetivli decode_vsetivli(ir,insn); ir->opcode = rv_insn_vsetivli; break; } default: /* illegal instruction */ return false; } return true; } #endif ``` The vsetvli instruction will update the `vector length register` (vl) and `vtype register` based on the specified values and extract SEW and LMUL values from the zimm field. ![image](https://hackmd.io/_uploads/SyqIp_eKT.png) For `rs1` = x0 and `rd` != x0, we have to set `vl` to `VLMAX`(VLMAX = VLEN/SEW/LMUL). In order to get VLMAX, changing SEW and LMUL from encoding to real number is necessary. Therefore, I define a function `get_LMUL` to meet our need. ```c float get_LMUL(uint32_t LMUL_ENCODE){ float LMUL = 1; //default switch(LMUL_ENCODE){ case 0b000: LMUL = 1; break; case 0b001: LMUL = 2; break; case 0b010: LMUL = 4; break; case 0b011: LMUL = 8; break; case 0b111: LMUL = 1/2; break; case 0b110: LMUL = 1/4; break; case 0b101: LMUL = 1/8; break; default: // Illegal LMUL values LMUL = 0; break; } return LMUL; } ``` Now, we can implement the `RVOP` of `vsetvli` in `rv32_template.c` by using the above function and the AVL table. | zimm[10:0] of vsetvli | [10:8] | [7] | [6] | [5:3] | [2:0] | | --------------------- | ------ | ------------------------- | ------------------------ | ------------------------------------------ | --- | | Text | 000 | vma | vta | vsew | vlmul | vma = Vector mask agnostic vta = Vector tail agnostic vsew = Selected element width (SEW) setting vlmul = Vector register group multiplier (LMUL) setting. ```c #define VLEN 512 RVOP(vsetvli, { uint32_t LMUL_ENCODE = ir->zimm & 0b111; uint32_t SEW_ENCODE = ir->zimm >> 3 & 0b111; float LMUL = 1; //default uint32_t SEW = 8; //default uint32_t VLMAX; LMUL = get_LMUL(LMUL_ENCODE); if(LMUL==0){ // Illegal LMUL values rv->csr_vl = 0; rv->csr_vtype = 0x80000000; // Set vill bit return true; } SEW = SEW*pow(2,SEW_ENCODE); VLMAX = VLEN/SEW/LMUL; if(ir->rs1==0){ if(ir->rd==0){ rv->csr_vtype = ir->zimm; } else if(ir->rd!=0){ rv->csr_vl = VLMAX; rv->csr_vtype = ir->zimm; } } else{ // rs1!x0 // Update the vector length (vl) and vector type (vtype) registers rv->csr_vl = rv->X[ir->rs1]; rv->csr_vtype = ir->zimm; } rv->X[ir->rd] = rv->X[ir->rs1]; return true; }) ``` The difference between `vsetvli` and `vsetivli` is the value passed to rd register. `vsetvli` is passed from `rs1`. `vsetivli` is passed from `uimm`. ```diff -rv->X[ir->rd] = rv->X[ir->rs1]; +rv->X[ir->rd] = ir->uimm; ``` The difference between `vsetvli` and `vsetvl` is the value to `vtype` register. `vsetvli` is passed from `zimm`. `vsetivli` is passed from `rs2`. ```diff -rv->csr_vtype = ir->zimm;; +rv->csr_vtype = rv->X[ir->rs2]; ``` ### Testing For checking the accuracy of my implement, I refer to `hello.S` and write a testing assembly code for `vsetvli` instruction named `vector.S`. ```assembly # RISC-V assembly program to print "t0 is 32" to stdout. .org 0 # Provide program starting address to linker .global _start /* newlib system calls */ .set SYSEXIT, 93 .set SYSWRITE, 64 .section .rodata str: .ascii "t0 is" .set str_size, .-str .text _start: li a3, 32 vsetvli t0, a3, e32, m8 li a7, SYSWRITE # "write" syscall li a0, 1 # 1 = standard output (stdout) la a1, str # load address of string li a2, str_size # length of string ecall # invoke syscall to print the string li a7, SYSWRITE li a0, 1 la a1, t0 li a2, 4 ecall li a7, SYSEXIT add a0, x0, x0 # Use 0 return code ecall # invoke syscall to terminate the program ``` Generate the ELF file by the following command ``` $ riscv-none-elf-as -march=rv32iv -mabi=ilp32 vector.S -o vector.elf ``` Modify the Makefile for the testing file. ```diff OBJS_EXT := +# Vector extension instructions +ENABLE_EXT_V ?= 1 +$(call set-feature, EXT_V) # RISC-V Architecture Test include mk/riscv-arch-test.mk include mk/tests.mk CHECK_ELF_FILES := \ hello \ puzzle \ ifeq ($(call has, EXT_M), 1) CHECK_ELF_FILES += \ pi endif +ifeq ($(call has, EXT_V), 1) +CHECK_ELF_FILES += \ + vector +endif EXPECTED_hello = Hello World! EXPECTED_puzzle = success in 2005 trials EXPECTED_pi = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086 +EXPECTED_vector = t0 is 32 check: $(BIN) $(Q)$(foreach e,$(CHECK_ELF_FILES),\ $(PRINTF) "Running $(e).elf ... "; \ if [ "$(shell $(BIN) $(OUT)/$(e).elf | uniq)" = "$(strip $(EXPECTED_$(e))) inferior exit code 0" ]; then \ $(call notice, [OK]); \ else \ $(PRINTF) "Failed.\n"; \ exit 1; \ fi; \ ) ``` I add the rv32v opcode map and decode vector instruction in `decode.c` for rv32emu to determind the instruction is vector instruction or not. ```c // In riscv_private.h /* Detect the instruction is RV32V or not */ FORCE_INLINE bool is_vector(uint32_t insn) { uint8_t v_op = insn & FR_OPCODE; return v_op == 0b111 || v_op == 0b0100111 || v_op == 0b1010111; } ``` ```c //In decode.c #if RV32_HAS(EXT_V) /* RV32V opcode map */ /* clang-format off */ static const decode_t rvv_jump_table[] = { // 000 001 010 011 100 101 110 111 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 000 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 001 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 010 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 011 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 100 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 101 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), // 110 OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(unimp), OP(cs), OP(unimp), OP(unimp), // 111 }; #endif /* clang-format on */ /* Vector Extension Instruction */ #if RV32_HAS(EXT_V) if (is_vector(insn)) { const uint32_t v_index = (insn & FR_FUNCT3) >> 9 | (insn & FR_OPCODE) >> 4; /* decode instruction (vector instructions) */ const decode_t op = rvv_jump_table[v_index]; assert(op); return op(ir, insn); } #endif ``` ### Reference [vector extension](https://github.com/riscv/riscv-v-spec) [rv32emu](https://github.com/sysprog21/rv32emu)