3. 常用压缩指令映射

任务目标

实现RISC-V压缩指令集中三条最常用指令——c.addi、c.li、c.lui——到标准(非压缩)RISC-V指令的转换逻辑。输入为压缩指令的16位编码,输出为等价的32位标准指令编码。

指令格式与转换规则

压缩指令格式立即数范围转换目标
c.addi000 nnnnn 00001 00101(rd=rd, 立即数5位有符号)-16~15addi rd, rd, imm
c.li010 nnnnn 00000 00101(rd≠0, 立即数5位有符号)-16~15addi rd, x0, imm
c.lui011 nnnnn 00000 00101(rd≠0, rd≠2, 立即数6位无符号)1~31(左移12位)lui rd, imm[5:0]

三条指令的编码格式均为:[15:13]为opcode(c.addi=000,c.li=010,c.lui=011),[12]固定为0,[11:7]为立即数(n字段),[6:2]为rd,[1:0]=01(压缩指令标识)。

关键完整代码

// c_to_std.h
#ifndef C_TO_STD_H
#define C_TO_STD_H
#include <stdint.h>
#include <stdbool.h>

// 返回true表示转换成功,false表示非法编码
bool c_to_std(uint16_t c_insn, uint32_t *std_insn);

#endif
// c_to_std.c
#include "c_to_std.h"

// RISC-V标准指令opcode
#define OPCODE_OPIMM  0x13  // addi
#define OPCODE_LUI    0x37  // lui
#define REG_X0        0

bool c_to_std(uint16_t c_insn, uint32_t *std_insn) {
    if ((c_insn & 0x3) != 0x1) return false;  // 低位必须是01
    
    uint8_t funct3 = (c_insn >> 13) & 0x7;
    uint8_t rd     = (c_insn >> 2)  & 0x1F;
    uint8_t imm5   = (c_insn >> 7)  & 0x1F;
    
    switch (funct3) {
        case 0x0: {  // c.addi
            if (rd == REG_X0) return false;  // c.addi要求rd≠0
            int32_t imm = (int8_t)(imm5 << 3) >> 3;  // 5位符号扩展
            *std_insn = ((uint32_t)imm & 0xFFF) << 20
                      | ((uint32_t)rd & 0x1F) << 7
                      | ((uint32_t)rd & 0x1F) << 15
                      | OPCODE_OPIMM;
            return true;
        }
        case 0x2: {  // c.li
            if (rd == REG_X0) return false;
            int32_t imm = (int8_t)(imm5 << 3) >> 3;
            *std_insn = ((uint32_t)imm & 0xFFF) << 20
                      | ((uint32_t)rd & 0x1F) << 7
                      | OPCODE_OPIMM;
            return true;
        }
        case 0x3: {  // c.lui
            if (rd == REG_X0 || rd == 0x2) return false;  // rd≠0且rd≠2
            if (imm5 == 0) return false;  // 立即数不能为0
            *std_insn = ((uint32_t)imm5 & 0x1F) << 12
                      | ((uint32_t)rd & 0x1F) << 7
                      | OPCODE_LUI;
            return true;
        }
        default:
            return false;  // 非本案例处理范围
    }
}
// test_c_to_std.c
#include <stdio.h>
#include <assert.h>
#include "c_to_std.h"

int main() {
    uint32_t out;
    
    // c.addi x5, -1  →  addi x5, x5, -1
    // 编码: 000 11111 00000 00101 01 = 0x0FA1
    assert(c_to_std(0x0FA1, &out));
    assert(out == 0xFFF28293);
    
    // c.li x6, 15  →  addi x6, x0, 15
    // 编码: 010 01111 00000 00110 01 = 0x27A1
    assert(c_to_std(0x27A1, &out));
    assert(out == 0x00F30313);
    
    // c.lui x7, 5  →  lui x7, 5
    // 编码: 011 00101 00000 00111 01 = 0x35A3
    assert(c_to_std(0x35A3, &out));
    assert(out == 0x000053B7);
    
    // 非法: c.addi x0, 1 (rd=0)
    assert(!c_to_std(0x0001, &out));
    
    printf("全部测试通过\n");
    return 0;
}

编译与运行

gcc -Wall -Wextra -o test_c2std test_c_to_std.c c_to_std.c
./test_c2std

验证方法

程序使用assert对三条指令的转换结果进行精确比对:

  • c.addi x5, -1:压缩编码0x0FA1,预期输出0xFFF28293(即addi x5, x5, -1)
  • c.li x6, 15:压缩编码0x27A1,预期输出0x00F30313(即addi x6, x0, 15)
  • c.lui x7, 5:压缩编码0x35A3,预期输出0x000053B7(即lui x7, 5)
  • 非法编码:c.addi x0, 1(rd=0)应返回false

若程序输出“全部测试通过”且退出码为0,说明转换逻辑正确。可自行添加更多测试向量(如边界立即数-16、15,以及rd=2的c.lui非法情况)进一步验证。

关键实现说明

  • 5位立即数通过(int8_t)(imm5 << 3) >> 3完成符号扩展,正确处理负数
  • c.addi的源寄存器与目标寄存器相同(均为rd),因此标准指令中rs1和rd字段都填入rd
  • c.lui的6位立即数实际只使用低5位(imm5),高1位恒为0,因此直接左移12位放入标准lui的imm字段
  • 标准指令编码中funct3字段(bits[14:12])对于addi为0,对于lui不使用(bits[31:12]全为立即数),代码中未显式设置funct3是因为addi的funct3=0,lui的funct3位被立即数覆盖