minirvEMU的实现
minirvEMU的实现
本次实验通过C语言模拟了minirv的功能,并通过AM框架模拟外设进行测试.
下面进行详细介绍
- 实现了
lui jalr addi add sw lw sb lbu八条riscv指令 - 实现了取指、译码、执行、更新PC等功能
- 通过AM模拟外设进行验证
主要代码介绍
取值加译码模块
void instruction_fetch(){ int inst = M[PC>>2]; int opcode = inst & 0b00000000000000000111000001111111; if(inst == 0x00100073){ ebreak(inst); } switch(opcode){ case 0b00000000000000000000000000010011: addi(inst); break; case 0b00000000000000000000000001100111: jalr(inst); break; case 0b00000000000000000010000000000011: lw(inst); break; case 0b00000000000000000100000000000011: lbu(inst); break; case 0b00000000000000000010000000100011: sw(inst); break; case 0b00000000000000000000000000100011: sb(inst); break; default: break; } opcode = inst & 0b11111110000000000111000001111111; switch(opcode){ case 0b00000000000000000000000000110011: add(inst); break; default: break; } opcode = inst & 0b00000000000000000000000001111111; switch(opcode){ case 0b00000000000000000000000000110111: lui(inst); break; default: break; } }指令执行模块
void reg_write(int addr, int val){ if(addr == 0) return; else R[addr] = val; } void addi(int inst){ int rd = (inst & 0b00000000000000000000111110000000) >> 7; int rs = (inst & 0b00000000000011111000000000000000) >> 15; int imm = (inst & 0b11111111111100000000000000000000) >> 20; imm = imm << 20 >> 20; reg_write(rd, R[rs] + imm); } void add(int inst){ int rd = (inst & 0b00000000000000000000111110000000) >> 7; int rs1 = (inst & 0b00000000000011111000000000000000) >> 15; int rs2 = (inst & 0b00000001111100000000000000000000) >> 20; reg_write(rd, R[rs1] + R[rs2]); } void lui(int inst){ int rd = (inst & 0b00000000000000000000111110000000) >> 7; int imm = (inst & 0b11111111111111111111000000000000) >> 12; imm = imm << 12; reg_write(rd, imm); } void lw(int inst){ int rd = (inst & 0b00000000000000000000111110000000) >> 7; int rs1 = (inst & 0b00000000000011111000000000000000) >> 15; int imm = (inst & 0b11111111111100000000000000000000) >> 20; imm = imm << 20 >> 20; int addr = (R[rs1] + imm) >> 2; reg_write(rd, ROM[addr]); } void lbu(int inst){ int rd = (inst & 0b00000000000000000000111110000000) >> 7; int rs1 = (inst & 0b00000000000011111000000000000000) >> 15; int imm = (inst & 0b11111111111100000000000000000000) >> 20; imm = imm << 20 >>20; uint32_t sel = (R[rs1] + (int)imm) & 0b11; uint32_t addr = (R[rs1] + (int)imm) >> 2; unsigned int val = (unsigned int)ROM[addr]; if(sel == 0) val = val % 256; else if(sel == 1){ val = val >> 8; val = val % 256; }else if(sel == 2){ val = val >> 16; val = val % 256; }else{ val = val >> 24; } reg_write(rd, val); } void sw(int inst){ int rs1 = (inst & 0b00000000000011111000000000000000) >> 15; int rs2 = (inst & 0b00000001111100000000000000000000) >> 20; int imm1 = (inst & 0b00000000000000000000111110000000) >> 7; int imm2 = (inst & 0b11111110000000000000000000000000) >> 25; int imm = (imm2 << 5) + imm1; imm = imm << 20 >> 20; uint32_t addr = (imm + R[rs1]); if(addr >= 0x20000000 && addr <= 0x20040000){ rgb[(addr-0x20000000) >> 2] = R[rs2]; } else{ addr = addr >> 2; ROM[addr] = R[rs2]; } } void sb(int inst){ int rs1 = (inst & 0b00000000000011111000000000000000) >> 15; int rs2 = (inst & 0b00000001111100000000000000000000) >> 20; int imm1 = (inst & 0b00000000000000000000111110000000) >> 7; int imm2 = (inst & 0b11111110000000000000000000000000) >> 25; int imm = (imm2 << 5) + imm1; imm = imm << 20 >> 20; uint32_t sel = (R[rs1] + imm) & 0b11; uint32_t addr = (R[rs1] + imm) >> 2; uint32_t temp = (uint32_t)ROM[addr]; uint32_t temp1 = temp & 0xff; uint32_t temp2 = (temp >> 8) & 0xff; uint32_t temp3 = (temp >> 16) & 0xff; uint32_t temp4 = temp >> 24; uint32_t val; if(sel == 0){ val = (temp4 << 24) + (temp3 << 16) + (temp2 << 8) +((uint32_t)R[rs2] & 0xff); }else if(sel == 1){ val = (temp4 << 24) + (temp3 << 16) + (((uint32_t)R[rs2] & 0xff) << 8) + temp1; }else if(sel == 2){ val = (temp4 << 24) + (((uint32_t)R[rs2] & 0xff) << 16) + (temp2 << 8) + temp1; }else if(sel == 3){ val = (((uint32_t)R[rs2] & 0xff) << 24) + (temp3 << 16) + (temp2 << 8) + temp1; } ROM[addr] = val; } void jalr(int inst){ int rd = (inst & 0b00000000000000000000111110000000) >> 7; int rs = (inst & 0b00000000000011111000000000000000) >> 15; uint32_t imm = (inst & 0b11111111111100000000000000000000) >> 20; imm = imm << 20 >> 20; int temp = PC; PC = imm + R[rs]; reg_write(rd, temp + 4); jalr_en = 1; } void output(int inst){ int rs = (inst & 0b00001100) >> 2; printf("R[%d]: %d\n", rs, R[rs]); } void ebreak(int inst){ if(R[10] == 0){ printf("HIT GOOD TRAP\n"); flag = 0; }else{ printf("HIT BAD TRAP\n"); flag = 0; } }小技巧
imm = imm << 20 >>20;通过int特性进行符号拓展
寄存器写模块
void reg_write(uint_t addr, uint_t val){ if(addr == 0) return; else R[addr] = val; }由于R[0]寄存器只能为0,故用此函数进行写操作
对寄存器进行操作多用uint32_t数据类型,uint32_t用于模仿寄存器
如
uint32_t temp = (uint32_t)ROM[addr]; uint32_t temp1 = temp & 0xff; uint32_t temp2 = (temp >> 8) & 0xff; uint32_t temp3 = (temp >> 16) & 0xff; uint32_t temp4 = temp >> 24; uint32_t val;感觉很重要👇
| 不同 | int | uint32_t |
|---|---|---|
| 位宽 | 位宽不固定由编译器/平台决定,可能16/32/64位 | 标准强制32位 |
| 对负数 | 右移是算数右移,符号拓展 | 高位补0 |
| 溢出行为 | C标准是未定义行为,不同编译器结果随机 | 模2³²自动环绕与硬件行为相同 |
| 输出 | printf(“%d”, val) | printf(“inst = %“PRIx32”\n”, inst); |
