3. 常用压缩指令映射
任务目标
实现RISC-V压缩指令集中三条最常用指令——c.addi、c.li、c.lui——到标准(非压缩)RISC-V指令的转换逻辑。输入为压缩指令的16位编码,输出为等价的32位标准指令编码。
指令格式与转换规则
| 压缩指令 | 格式 | 立即数范围 | 转换目标 |
|---|---|---|---|
c.addi | 000 nnnnn 00001 00101(rd=rd, 立即数5位有符号) | -16~15 | addi rd, rd, imm |
c.li | 010 nnnnn 00000 00101(rd≠0, 立即数5位有符号) | -16~15 | addi rd, x0, imm |
c.lui | 011 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字段都填入rdc.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位被立即数覆盖