1. 识别RVC指令格式

任务目标

本案例要求解析RISC-V压缩指令(RVC)的编码格式,实现一个能够区分16位压缩指令与32位标准指令的解析器,并识别压缩指令中各字段(funct3、rd/rs1、imm等)的布局。最终通过一组预定义指令编码验证解析结果的正确性。

RVC指令格式要点

RVC指令长度为16位,其最高两位(bit[15:14])固定为00,这是与32位指令区分的核心标志。RVC指令按funct3字段(bit[15:13])分为8大类,每类内部根据操作码进一步细分。与32位指令相比,RVC的寄存器编号通常只占5位中的3~5位(取决于指令类型),立即数字段也经过重新编码以节省空间。

核心实现

// rvc_parser.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    uint16_t raw;
    int is_rvc;
    unsigned funct3;
    unsigned opcode;
    unsigned rd;
    unsigned rs1;
    unsigned rs2;
    unsigned imm;
    char desc[64];
} rvc_inst_t;

static const char* opcode_names[] = {
    "C.ADDI4SPN", "C.LW", "C.LD", "C.SW", "C.SD",
    "C.ADDI", "C.JAL", "C.LI", "C.ADDI16SP", "C.LUI",
    "C.SRLI", "C.SRAI", "C.ANDI", "C.SUB", "C.XOR",
    "C.OR", "C.AND", "C.J", "C.BEQZ", "C.BNEZ",
    "C.SLLI", "C.LWSP", "C.LDSP", "C.JR", "C.MV",
    "C.EBREAK", "C.JALR", "C.ADD", "C.SWSP", "C.SDSP"
};

int parse_rvc(uint16_t inst, rvc_inst_t* out) {
    memset(out, 0, sizeof(*out));
    out->raw = inst;
    
    // 区分16位与32位指令
    if ((inst & 0x0003) != 0) {
        out->is_rvc = 0;
        snprintf(out->desc, sizeof(out->desc), "32-bit instruction");
        return 0;
    }
    out->is_rvc = 1;
    
    out->funct3 = (inst >> 13) & 0x7;
    unsigned op = (inst >> 2) & 0x7;  // 低5位中的操作码(bit[6:2])
    out->opcode = op;
    
    // 按funct3分类解析
    switch (out->funct3) {
        case 0: // C.ADDI4SPN / C.LW / C.LD / C.SW / C.SD (quadrant 0)
            if (op == 0) {
                out->rd = 8 + ((inst >> 2) & 0x7);
                out->imm = ((inst >> 7) & 0x3C) | ((inst >> 6) & 0x3);
                snprintf(out->desc, sizeof(out->desc), "C.ADDI4SPN rd=x%d, imm=%u", out->rd, out->imm);
            } else if (op == 2) {
                out->rd = 8 + ((inst >> 2) & 0x7);
                out->rs1 = 8 + ((inst >> 7) & 0x7);
                out->imm = ((inst >> 10) & 0x1C) | ((inst >> 6) & 0x3);
                snprintf(out->desc, sizeof(out->desc), "C.LW rd=x%d, imm=%u(rs1=x%d)", out->rd, out->imm, out->rs1);
            } else if (op == 6) {
                out->rd = 8 + ((inst >> 2) & 0x7);
                out->rs1 = 8 + ((inst >> 7) & 0x7);
                out->imm = ((inst >> 10) & 0x38) | ((inst >> 6) & 0x7);
                snprintf(out->desc, sizeof(out->desc), "C.SW rs2=x%d, imm=%u(rs1=x%d)", out->rd, out->imm, out->rs1);
            }
            break;
        case 1: // C.ADDI / C.JAL / C.LI / C.ADDI16SP / C.LUI
            if (op == 0) {
                out->rd = (inst >> 7) & 0x1F;
                out->imm = ((inst >> 2) & 0x1F) | ((inst >> 12) & 0x20);
                if (out->imm & 0x20) out->imm |= 0xFFFFFFC0;
                snprintf(out->desc, sizeof(out->desc), "C.ADDI rd=x%d, imm=%d", out->rd, (int)out->imm);
            } else if (op == 2) {
                out->rd = (inst >> 7) & 0x1F;
                out->imm = ((inst >> 2) & 0x1F) | ((inst >> 12) & 0x20);
                if (out->imm & 0x20) out->imm |= 0xFFFFFFC0;
                snprintf(out->desc, sizeof(out->desc), "C.LI rd=x%d, imm=%d", out->rd, (int)out->imm);
            }
            break;
        case 2: // C.SLLI / C.LWSP / C.LDSP / C.JR / C.MV / C.EBREAK / C.JALR / C.ADD
            if (op == 0) {
                out->rd = (inst >> 7) & 0x1F;
                out->imm = (inst >> 2) & 0x1F;
                snprintf(out->desc, sizeof(out->desc), "C.SLLI rd=x%d, shamt=%u", out->rd, out->imm);
            } else if (op == 2) {
                out->rd = (inst >> 7) & 0x1F;
                out->imm = ((inst >> 2) & 0x1F) | ((inst >> 12) & 0x20);
                snprintf(out->desc, sizeof(out->desc), "C.LWSP rd=x%d, imm=%u", out->rd, out->imm);
            } else if (op == 4) {
                out->rs1 = (inst >> 7) & 0x1F;
                if (inst & 0x80) {
                    out->rs2 = (inst >> 2) & 0x1F;
                    snprintf(out->desc, sizeof(out->desc), "C.MV rd=x%d, rs2=x%d", out->rs1, out->rs2);
                } else {
                    snprintf(out->desc, sizeof(out->desc), "C.JR rs1=x%d", out->rs1);
                }
            }
            break;
        case 3: // 其他(C.J / C.BEQZ / C.BNEZ等)
            if (op == 0) {
                out->imm = ((inst >> 2) & 0x7) | ((inst >> 7) & 0x18) | ((inst >> 8) & 0x60) | ((inst >> 10) & 0x80);
                snprintf(out->desc, sizeof(out->desc), "C.J imm=%d", (int)out->imm);
            } else if (op == 1) {
                out->rs1 = 8 + ((inst >> 7) & 0x7);
                out->imm = ((inst >> 2) & 0x7) | ((inst >> 7) & 0x18) | ((inst >> 8) & 0x60) | ((inst >> 10) & 0x80);
                snprintf(out->desc, sizeof(out->desc), "C.BEQZ rs1=x%d, imm=%d", out->rs1, (int)out->imm);
            }
            break;
        default:
            snprintf(out->desc, sizeof(out->desc), "Unhandled funct3=%u", out->funct3);
    }
    return 0;
}

int main() {
    // 测试用例:0x0001是32位指令;0x0000是C.ADDI4SPN;0x5082是C.LW
    uint16_t test[] = {0x0001, 0x0000, 0x5082, 0x0141};
    for (int i = 0; i < 4; i++) {
        rvc_inst_t inst;
        parse_rvc(test[i], &inst);
        printf("0x%04X: %s\n", test[i], inst.desc);
    }
    return 0;
}

编译与运行

gcc -o rvc_parser rvc_parser.c
./rvc_parser

验证方法

程序输出应包含以下内容:

  • 0x0001: 32-bit instruction(验证16/32位区分)
  • 0x0000: C.ADDI4SPN rd=x8, imm=0(验证quadrant 0解析)
  • 0x5082: C.LW rd=x10, imm=0(rs1=x8)(验证字段提取)
  • 0x0141: C.ADDI rd=x8, imm=0(验证quadrant 1解析)

若输出与上述一致,说明解析器正确识别了RVC指令格式并完成了字段布局解析。可通过修改test[]数组添加更多指令编码来扩展验证范围。

RVC 16位指令格式 funct3[15:13] imm/rd[12:10] rs1[9:7] rs2/rd[6:5] op[4:2] [1:0] 15 12 9 7 5 2 32位指令: bit[1:0] ≠ 00 16位RVC: bit[1:0] = 00

上图展示了RVC指令的通用位域布局。实际解析时,不同funct3值对应不同的字段含义,例如quadrant 0(funct3=000)中bit[6:5]表示目标寄存器的高位,而quadrant 1(funct3=001)中bit[6:5]与bit[12]共同构成立即数。解析器通过funct3和op字段的组合确定具体指令类型,再按对应格式提取各字段。