Build RISC-V Instruction Set Simulator from scratch
黃詠筑, 劉欣宜
GitHub
Mission
Task: Develop a RISC-V instruction set simulator from scratch - Based on the foundation from Problem A, gradually expand it to support RV32I
, RV32M
, and RV32C
, and pass the tests from riscv-tests. Additionally, select at least five RISC-V programs from Quiz 1 to Quiz 6, rewrite them, and ensure they work correctly on the developed RISC-V instruction set simulator.
RV32I, RV32M, and RV32C
1. RV32I: This is the basic version of the RISC-V instruction set, supporting 32-bit operations. It includes the core instructions of the RISC-V instruction set, such as arithmetic operations, logical operations, and control flow. RV32I is the foundation of the RISC-V architecture, and other extensions are built upon it.
2. RV32M: This is an extended version of RV32I, adding support for multiplication and division instructions. The RV32M extension provides M instructions that allow the processor to perform more efficient multiplication (MUL) and division (DIV) operations.
3. RV32C: This is another extension of RV32I that provides a compressed instruction set. The C extension adds smaller, more compact instructions to reduce program size and improve performance, especially in embedded systems and resource-constrained devices. These instructions are usually simplified versions of RV32I instructions and help save storage space while enhancing execution efficiency.
Quiz3 Problem A
Summarize the program, addressing its purpose and behavior.
This code primarily covers the basic instruction set of RV32I
and implements support for instructions like ADDI, LUI, AUIPC, etc. It does not implement the multiplication and division instructions of RV32M
, nor does it include the compressed instruction set of RV32C
. To extend it to RV32M
or RV32C
, the corresponding instruction handling logic would need to be added.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define fatal(args...) \
do { \
printf("Fatal: "); \
printf(args); \
exit(-1); \
} while (0)
enum {
INTEGER_COMP_RI = 0x13,
LUI = 0x37
AUIPC = 0x17
ECALL = 0x73,
};
enum {
ADDI = 0x0,
};
enum { OPCODE_MASK = 0x7F, REG_ADDR_MASK = 0x1F };
enum {
RD_SHIFT = 7,
RS1_SHIFT = 15,
RS2_SHIFT = 20,
FUNCT3_SHIFT = 12,
FUNCT7_SHIFT = 25
};
enum { FUNCT3_MASK = 0x7, FUNCT7_MASK = 0x7F };
enum {
RAM_SIZE = 1204 * 1024 * 2,
RAM_BASE = 0x0,
};
typedef struct {
uint8_t *mem;
} ram_t;
typedef struct {
uint32_t regs[32];
size_t pc;
ram_t *ram;
} cpu_t;
typedef struct {
uint8_t opcode;
uint8_t rd, rs1, rs2, funct3, funct7;
} insn_t;
ram_t *ram_new(uint8_t *code, size_t len)
{
ram_t *ram = malloc(sizeof(ram_t));
ram->mem = malloc(RAM_SIZE);
memcpy(ram->mem, code, len);
return ram;
}
void ram_free(ram_t *mem)
{
free(mem->mem);
free(mem);
}
static inline void check_addr(int32_t addr)
{
size_t index = addr - RAM_BASE;
if (index >= RAM_SIZE) fatal("out of memory.\n");
}
static uint32_t ram_load8(ram_t *mem, uint32_t addr)
{
size_t index = addr - RAM_BASE;
return (uint32_t) mem->mem[index];
}
static uint32_t ram_load16(ram_t *mem, uint32_t addr)
{
size_t index = addr - RAM_BASE;
return ((uint32_t) mem->mem[index] | ((uint32_t) mem->mem[index + 1] << 8));
}
static uint32_t ram_load32(ram_t *mem, uint32_t addr)
{
size_t index = addr - RAM_BASE;
return ((uint32_t) mem->mem[index] | ((uint32_t) mem->mem[index + 1] << 8) |
((uint32_t) mem->mem[index + 2] << 16) |
((uint32_t) mem->mem[index + 3] << 24));
}
uint32_t ram_load(ram_t *mem, uint32_t addr, uint8_t size)
{
check_addr(addr);
uint32_t r = 0;
switch (size) {
case 8: r = ram_load8(mem, addr); break;
case 16: r = ram_load16(mem, addr); break;
case 32: r = ram_load32(mem, addr); break;
default: fatal("RAM: invalid load size(%d)\n", size);
}
return r;
}
cpu_t *cpu_new(uint8_t *code, size_t len)
{
cpu_t *cpu = malloc(sizeof(cpu_t));
memset(cpu->regs, 0, sizeof(cpu->regs));
cpu->regs[0] = 0;
cpu->regs[2] = RAM_BASE + RAM_SIZE;
cpu->pc = RAM_BASE;
cpu->ram = ram_new(code, len);
return cpu;
}
void cpu_free(cpu_t *cpu)
{
ram_free(cpu->ram);
free(cpu);
}
uint32_t cpu_load(cpu_t *cpu, uint32_t addr, uint8_t size)
{
return ram_load(cpu->ram, addr, size);
}
uint32_t cpu_fetch(cpu_t *cpu) { return cpu_load(cpu, cpu->pc, 32); }
void cpu_decode(uint32_t raw, insn_t *inst)
{
uint8_t opcode = raw & OPCODE_MASK;
uint8_t rd = (raw >> RD_SHIFT) & REG_ADDR_MASK;
uint8_t rs1 = (raw >> RS1_SHIFT) & REG_ADDR_MASK;
uint8_t rs2 = (raw >> RS2_SHIFT) & REG_ADDR_MASK;
uint8_t funct3 = (raw >> FUNCT3_SHIFT) & FUNCT3_MASK;
uint8_t funct7 = (raw >> FUNCT7_SHIFT) & FUNCT7_MASK;
inst->opcode = opcode;
inst->rd = rd, inst->rs1 = rs1, inst->rs2 = rs2;
inst->funct3 = funct3;
inst->funct7 = funct7;
}
static inline int32_t i_imm(uint32_t raw)
{
return ((int32_t) raw) >> 20;
}
static inline int32_t u_imm(uint32_t raw)
{
return ((int32_t) raw & 0xFFFFF000);
}
enum { SYSCALL_WRITE = 64, SYSCALL_EXIT = 93 };
enum { STDOUT = 1, STDERR = 2 };
void ecall_handler(cpu_t *cpu)
{
uint32_t syscall_nr = cpu->regs[17];
switch (syscall_nr) {
case SYSCALL_WRITE: {
uint32_t fd = cpu->regs[10];
uint32_t addr = cpu->regs[11];
uint32_t count = cpu->regs[12];
FILE *stream;
switch (fd) {
case STDOUT: stream = stdout; break;
case STDERR: stream = stderr; break;
default: fatal("invalid file-descriptor %d.\n", fd);
}
for (uint32_t i = 0; i < count; i++)
fprintf(stream, "%c", cpu_load(cpu, addr + i, 8));
break;
}
case SYSCALL_EXIT: {
int32_t exit_code = cpu->regs[10];
exit(exit_code);
break;
}
default: fatal("unkown syscall number %d.\n", syscall_nr);
}
}
static insn_t inst;
void cpu_execute(cpu_t *cpu, uint32_t raw)
{
cpu_decode(raw, &inst);
cpu->regs[0] = 0;
switch (inst.opcode) {
case INTEGER_COMP_RI: {
int32_t imm = i_imm(raw);
switch (inst.funct3) {
case ADDI: cpu->regs[inst.rd] = cpu->regs[inst.rs1] + imm; break;
default: fatal("Unknown FUNCT3 for INTEGER_COMP_RI");
}
break;
}
case LUI: {
int32_t imm = u_imm(raw);
cpu->regs[inst.rd] = imm;
break;
}
case AUIPC: {
int32_t imm = u_imm(raw);
cpu->regs[inst.rd] = (cpu->pc + (-4)) + imm;
break;
}
case ECALL:
if (raw == 0x100073) break;
ecall_handler(cpu);
break;
default: fatal("Illegal Instruction 0x%x\n", inst.opcode);
}
}
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("Usage: %s [filename]\n", argv[0]);
exit(-1);
}
const char *filename = argv[1];
FILE *file = fopen(filename, "rb");
if (!file) {
printf("Failed to open %s\n", filename);
exit(-1);
}
fseek(file, 0L, SEEK_END);
size_t code_size = ftell(file);
rewind(file);
uint8_t *code = malloc(code_size);
fread(code, sizeof(uint8_t), code_size, file);
fclose(file);
cpu_t *cpu = cpu_new(code, code_size);
while (1) {
uint32_t raw = cpu_fetch(cpu);
cpu->pc += 4;
if (cpu->pc > code_size) break;
cpu_execute(cpu, raw);
if (!cpu->pc) break;
}
cpu_free(cpu);
return 0;
}
- Add support for
RV32M
extension instructions:
RV32M
provides multiplication and division instructions, such as MUL
, MULH
, DIV
, DIVU
, REM
, and REMU
.
- These instructions need to be handled during the decode and execution stages.
- Add support for
RV32C
extension instructions:
RV32C
is a compressed instruction set that includes 16-bit instructions. These instructions need to be handled separately and identified as compressed instructions during the decode stage.
RV32C
instructions typically use fewer bits for encoding and can be aligned to 16 bits, so additional checks and processing are required.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define fatal(args...) \
do { \
printf("Fatal: "); \
printf(args); \
exit(-1); \
} while (0)
enum {
INTEGER_COMP_RI = 0x13,
LUI = 0x37,
AUIPC = 0x17,
ECALL = 0x73,
MUL = 0x33,
MULH = 0x33,
DIV = 0x33,
DIVU = 0x33,
REM = 0x33,
REMU = 0x33,
C_ADDI4SPN = 0x10,
C_LW = 0x00,
C_SW = 0x01,
};
enum {
ADDI = 0x0,
MUL_FUNCT3 = 0x0,
DIV_FUNCT3 = 0x4,
REM_FUNCT3 = 0x5,
};
enum {
MUL_FUNCT7 = 0x1,
MULH_FUNCT7 = 0x2,
};
enum { FUNCT3_MASK = 0x7, FUNCT7_MASK = 0x7F };
enum {
RAM_SIZE = 1204 * 1024 * 2,
RAM_BASE = 0x0,
};
typedef struct {
uint8_t *mem;
} ram_t;
typedef struct {
uint32_t regs[32];
size_t pc;
ram_t *ram;
} cpu_t;
typedef struct {
uint8_t opcode;
uint8_t rd, rs1, rs2, funct3, funct7;
} insn_t;
ram_t *ram_new(uint8_t *code, size_t len)
{
ram_t *ram = malloc(sizeof(ram_t));
ram->mem = malloc(RAM_SIZE);
memcpy(ram->mem, code, len);
return ram;
}
void ram_free(ram_t *mem)
{
free(mem->mem);
free(mem);
}
static inline void check_addr(int32_t addr)
{
size_t index = addr - RAM_BASE;
if (index >= RAM_SIZE) fatal("out of memory.\n");
}
static uint32_t ram_load8(ram_t *mem, uint32_t addr)
{
size_t index = addr - RAM_BASE;
return (uint32_t) mem->mem[index];
}
static uint32_t ram_load16(ram_t *mem, uint32_t addr)
{
size_t index = addr - RAM_BASE;
return ((uint32_t) mem->mem[index] | ((uint32_t) mem->mem[index + 1] << 8));
}
static uint32_t ram_load32(ram_t *mem, uint32_t addr)
{
size_t index = addr - RAM_BASE;
return ((uint32_t) mem->mem[index] | ((uint32_t) mem->mem[index + 1] << 8) |
((uint32_t) mem->mem[index + 2] << 16) |
((uint32_t) mem->mem[index + 3] << 24));
}
uint32_t ram_load(ram_t *mem, uint32_t addr, uint8_t size)
{
check_addr(addr);
uint32_t r = 0;
switch (size) {
case 8: r = ram_load8(mem, addr); break;
case 16: r = ram_load16(mem, addr); break;
case 32: r = ram_load32(mem, addr); break;
default: fatal("RAM: invalid load size(%d)\n", size);
}
return r;
}
cpu_t *cpu_new(uint8_t *code, size_t len)
{
cpu_t *cpu = malloc(sizeof(cpu_t));
memset(cpu->regs, 0, sizeof(cpu->regs));
cpu->regs[0] = 0;
cpu->regs[2] = RAM_BASE + RAM_SIZE;
cpu->pc = RAM_BASE;
cpu->ram = ram_new(code, len);
return cpu;
}
void cpu_free(cpu_t *cpu)
{
ram_free(cpu->ram);
free(cpu);
}
uint32_t cpu_load(cpu_t *cpu, uint32_t addr, uint8_t size)
{
return ram_load(cpu->ram, addr, size);
}
uint32_t cpu_fetch(cpu_t *cpu) { return cpu_load(cpu, cpu->pc, 32); }
void cpu_decode(uint32_t raw, insn_t *inst)
{
uint8_t opcode = raw & OPCODE_MASK;
uint8_t rd = (raw >> RD_SHIFT) & REG_ADDR_MASK;
uint8_t rs1 = (raw >> RS1_SHIFT) & REG_ADDR_MASK;
uint8_t rs2 = (raw >> RS2_SHIFT) & REG_ADDR_MASK;
uint8_t funct3 = (raw >> FUNCT3_SHIFT) & FUNCT3_MASK;
uint8_t funct7 = (raw >> FUNCT7_SHIFT) & FUNCT7_MASK;
inst->opcode = opcode;
inst->rd = rd, inst->rs1 = rs1, inst->rs2 = rs2;
inst->funct3 = funct3;
inst->funct7 = funct7;
}
static inline int32_t i_imm(uint32_t raw)
{
return ((int32_t) raw) >> 20;
}
static inline int32_t u_imm(uint32_t raw)
{
return ((int32_t) raw & 0xFFFFF000);
}
enum { SYSCALL_WRITE = 64, SYSCALL_EXIT = 93 };
enum { STDOUT = 1, STDERR = 2 };
void ecall_handler(cpu_t *cpu)
{
uint32_t syscall_nr = cpu->regs[17];
switch (syscall_nr) {
case SYSCALL_WRITE: {
uint32_t fd = cpu->regs[10];
uint32_t addr = cpu->regs[11];
uint32_t count = cpu->regs[12];
FILE *stream;
switch (fd) {
case STDOUT: stream = stdout; break;
case STDERR: stream = stderr; break;
default: fatal("invalid file-descriptor %d.\n", fd);
}
for (uint32_t i = 0; i < count; i++)
fprintf(stream, "%c", cpu_load(cpu, addr + i, 8));
break;
}
case SYSCALL_EXIT: {
int32_t exit_code = cpu->regs[10];
exit(exit_code);
break;
}
default: fatal("unkown syscall number %d.\n", syscall_nr);
}
}
static insn_t inst;
void cpu_execute(cpu_t *cpu, uint32_t raw)
{
cpu_decode(raw, &inst);
cpu->regs[0] = 0;
switch (inst.opcode) {
case INTEGER_COMP_RI: {
int32_t imm = i_imm(raw);
switch (inst.funct3) {
case ADDI: cpu->regs[inst.rd] = cpu->regs[inst.rs1] + imm; break;
default: fatal("Unknown FUNCT3 for INTEGER_COMP_RI");
}
break;
}
case MUL:
switch (inst.funct7) {
case MUL_FUNCT7: cpu->regs[inst.rd] = cpu->regs[inst.rs1] * cpu->regs[inst.rs2]; break;
default: fatal("Unknown FUNCT7 for MUL");
}
break;
case DIV:
switch (inst.funct3) {
case DIV_FUNCT3: cpu->regs[inst.rd] = cpu->regs[inst.rs1] / cpu->regs[inst.rs2]; break;
default: fatal("Unknown FUNCT3 for DIV");
}
break;
case REM:
switch (inst.funct3) {
case REM_FUNCT3: cpu->regs[inst.rd] = cpu->regs[inst.rs1] % cpu->regs[inst.rs2]; break;
default: fatal("Unknown FUNCT3 for REM");
}
break;
case MULH:
switch (inst.funct7) {
case MULH_FUNCT7: cpu->regs[inst.rd] = (int32_t) cpu->regs[inst.rs1] * (int32_t) cpu->regs[inst.rs2] >> 32; break;
default: fatal("Unknown FUNCT7 for MULH");
}
break;
case DIVU:
switch (inst.funct3) {
case 0x4: cpu->regs[inst.rd] = (uint32_t) cpu->regs[inst.rs1] / (uint32_t) cpu->regs[inst.rs2]; break;
default: fatal("Unknown FUNCT3 for DIVU");
}
break;
case REMU:
switch (inst.funct3) {
case 0x5: cpu->regs[inst.rd] = (uint32_t) cpu->regs[inst.rs1] % (uint32_t) cpu->regs[inst.rs2]; break;
default: fatal("Unknown FUNCT3 for REMU");
}
break;
case LUI: {
int32_t imm = u_imm(raw);
cpu->regs[inst.rd] = imm;
break;
}
case AUIPC: {
int32_t imm = u_imm(raw);
cpu->regs[inst.rd] = (cpu->pc + (-4)) + imm;
break;
}
case ECALL:
if (raw == 0x100073) break;
ecall_handler(cpu);
break;
default: fatal("Illegal Instruction 0x%x\n", inst.opcode);
}
}
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("Usage: %s [filename]\n", argv[0]);
exit(-1);
}
const char *filename = argv[1];
FILE *file = fopen(filename, "rb");
if (!file) {
printf("Failed to open %s\n", filename);
exit(-1);
}
fseek(file, 0L, SEEK_END);
size_t code_size = ftell(file);
rewind(file);
uint8_t *code = malloc(code_size);
fread(code, sizeof(uint8_t), code_size, file);
fclose(file);
cpu_t *cpu = cpu_new(code, code_size);
while (1) {
uint32_t raw = cpu_fetch(cpu);
cpu->pc += 4;
if (cpu->pc > code_size) break;
cpu_execute(cpu, raw);
if (!cpu->pc) break;
}
cpu_free(cpu);
return 0;
}
Makefile
Build_and_run.sh
chmod +x build_and_run.sh
./build_and_run.sh filename.s
riscv.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define fatal(args...) \
do { \
printf("Fatal: "); \
printf(args); \
exit(-1); \
} while (0)
enum {
INTEGER_COMP_RI = 0x13,
LUI = 0x37,
AUIPC = 0x17,
ECALL = 0x73,
};
enum {
ADDI = 0x0,
MUL = 0x0,
MULH = 0x1,
DIV = 0x4,
DIVU = 0x5,
REM = 0x6,
REMU = 0x7,
};
enum { OPCODE_MASK = 0x7F, REG_ADDR_MASK = 0x1F };
enum { RD_SHIFT = 7, RS1_SHIFT = 15, RS2_SHIFT = 20, FUNCT3_SHIFT = 12, FUNCT7_SHIFT = 25 };
enum { FUNCT3_MASK = 0x7, FUNCT7_MASK = 0x7F };
enum { RAM_SIZE = 1204 * 1024 * 2, RAM_BASE = 0x0 };
typedef struct {
uint8_t *mem;
} ram_t;
typedef struct {
uint32_t regs[32];
size_t pc;
ram_t *ram;
} cpu_t;
typedef struct {
uint8_t opcode;
uint8_t rd, rs1, rs2, funct3, funct7;
} insn_t;
ram_t *ram_new(uint8_t *code, size_t len) {
ram_t *ram = malloc(sizeof(ram_t));
ram->mem = malloc(RAM_SIZE);
memcpy(ram->mem, code, len);
return ram;
}
void ram_free(ram_t *mem) {
free(mem->mem);
free(mem);
}
static inline void check_addr(int32_t addr) {
size_t index = addr - RAM_BASE;
if (index >= RAM_SIZE) fatal("out of memory.\n");
}
static uint32_t ram_load8(ram_t *mem, uint32_t addr) {
size_t index = addr - RAM_BASE;
return (uint32_t) mem->mem[index];
}
static uint32_t ram_load16(ram_t *mem, uint32_t addr) {
size_t index = addr - RAM_BASE;
return ((uint32_t) mem->mem[index] | ((uint32_t) mem->mem[index + 1] << 8));
}
static uint32_t ram_load32(ram_t *mem, uint32_t addr) {
size_t index = addr - RAM_BASE;
return ((uint32_t) mem->mem[index] | ((uint32_t) mem->mem[index + 1] << 8) |
((uint32_t) mem->mem[index + 2] << 16) |
((uint32_t) mem->mem[index + 3] << 24));
}
uint32_t ram_load(ram_t *mem, uint32_t addr, uint8_t size) {
check_addr(addr);
uint32_t r = 0;
switch (size) {
case 8: r = ram_load8(mem, addr); break;
case 16: r = ram_load16(mem, addr); break;
case 32: r = ram_load32(mem, addr); break;
default: fatal("RAM: invalid load size(%d)\n", size);
}
return r;
}
cpu_t *cpu_new(uint8_t *code, size_t len) {
cpu_t *cpu = malloc(sizeof(cpu_t));
memset(cpu->regs, 0, sizeof(cpu->regs));
cpu->regs[0] = 0;
cpu->regs[2] = RAM_BASE + RAM_SIZE;
cpu->pc = RAM_BASE;
cpu->ram = ram_new(code, len);
return cpu;
}
void cpu_free(cpu_t *cpu) {
ram_free(cpu->ram);
free(cpu);
}
uint32_t cpu_load(cpu_t *cpu, uint32_t addr, uint8_t size) {
return ram_load(cpu->ram, addr, size);
}
uint32_t cpu_fetch(cpu_t *cpu) { return cpu_load(cpu, cpu->pc, 32); }
void cpu_decode(uint32_t raw, insn_t *inst) {
uint8_t opcode = raw & OPCODE_MASK;
uint8_t rd = (raw >> RD_SHIFT) & REG_ADDR_MASK;
uint8_t rs1 = (raw >> RS1_SHIFT) & REG_ADDR_MASK;
uint8_t rs2 = (raw >> RS2_SHIFT) & REG_ADDR_MASK;
uint8_t funct3 = (raw >> FUNCT3_SHIFT) & FUNCT3_MASK;
uint8_t funct7 = (raw >> FUNCT7_SHIFT) & FUNCT7_MASK;
inst->opcode = opcode;
inst->rd = rd, inst->rs1 = rs1, inst->rs2 = rs2;
inst->funct3 = funct3;
inst->funct7 = funct7;
}
static inline int32_t i_imm(uint32_t raw) {
return ((int32_t) raw) >> 20;
}
static inline int32_t u_imm(uint32_t raw) {
return ((int32_t) raw & 0xFFFFF000);
}
enum { SYSCALL_WRITE = 64, SYSCALL_EXIT = 93 };
enum { STDOUT = 1, STDERR = 2 };
void ecall_handler(cpu_t *cpu) {
uint32_t syscall_nr = cpu->regs[17];
switch (syscall_nr) {
case SYSCALL_WRITE: {
uint32_t fd = cpu->regs[10];
uint32_t addr = cpu->regs[11];
uint32_t count = cpu->regs[12];
FILE *stream;
switch (fd) {
case STDOUT: stream = stdout; break;
case STDERR: stream = stderr; break;
default: fatal("invalid file-descriptor %d.\n", fd);
}
for (uint32_t i = 0; i < count; i++) {
fprintf(stream, "%c", cpu_load(cpu, addr + i, 8));
}
break;
}
case SYSCALL_EXIT: {
int32_t exit_code = cpu->regs[10];
exit(exit_code);
break;
}
default: fatal("unkown syscall number %d.\n", syscall_nr);
}
}
static insn_t inst;
void cpu_execute(cpu_t *cpu, uint32_t raw) {
cpu_decode(raw, &inst);
cpu->regs[0] = 0;
switch (inst.opcode) {
case INTEGER_COMP_RI: {
int32_t imm = i_imm(raw);
switch (inst.funct3) {
case ADDI: cpu->regs[inst.rd] = cpu->regs[inst.rs1] + imm; break;
default: fatal("Unknown FUNCT3 for INTEGER_COMP_RI");
}
break;
}
case LUI: {
int32_t imm = u_imm(raw);
cpu->regs[inst.rd] = imm;
break;
}
case AUIPC: {
int32_t imm = u_imm(raw);
cpu->regs[inst.rd] = (cpu->pc + (-4)) + imm;
break;
}
case 0x33: {
switch (inst.funct3) {
case 0x0:
if (inst.funct7 == 0x01) {
cpu->regs[inst.rd] = (int32_t)cpu->regs[inst.rs1] * (int32_t)cpu->regs[inst.rs2];
}
break;
case 0x1:
if (inst.funct7 == 0x01) {
int64_t result = (int64_t)(int32_t)cpu->regs[inst.rs1] * (int32_t)cpu->regs[inst.rs2];
cpu->regs[inst.rd] = (result >> 32) & 0xFFFFFFFF;
}
break;
case 0x4:
if (inst.funct7 == 0x01) {
cpu->regs[inst.rd] = cpu->regs[inst.rs1] / cpu->regs[inst.rs2];
}
break;
case 0x5:
if (inst.funct7 == 0x01) {
cpu->regs[inst.rd] = (unsigned int)cpu->regs[inst.rs1] / (unsigned int)cpu->regs[inst.rs2];
}
break;
case 0x6:
if (inst.funct7 == 0x01) {
cpu->regs[inst.rd] = cpu->regs[inst.rs1] % cpu->regs[inst.rs2];
}
break;
case 0x7:
if (inst.funct7 == 0x01) {
cpu->regs[inst.rd] = (unsigned int)cpu->regs[inst.rs1] % (unsigned int)cpu->regs[inst.rs2];
}
break;
default:
fatal("Unknown funct3 for RV32M instruction\n");
}
break;
}
case 0x1: {
switch (inst.funct3) {
case 0x0:
cpu->regs[inst.rd] = cpu->regs[inst.rs1] + i_imm(raw);
break;
default:
fatal("Unknown FUNCT3 for RV32C instruction\n");
}
break;
}
case ECALL:
if (raw == 0x100073) break;
ecall_handler(cpu);
break;
default:
fatal("Illegal Instruction 0x%x\n", inst.opcode);
}
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s [filename]\n", argv[0]);
exit(-1);
}
const char *filename = argv[1];
FILE *file = fopen(filename, "rb");
if (!file) {
printf("Failed to open %s\n", filename);
exit(-1);
}
fseek(file, 0L, SEEK_END);
size_t code_size = ftell(file);
rewind(file);
uint8_t *code = malloc(code_size);
fread(code, sizeof(uint8_t), code_size, file);
fclose(file);
cpu_t *cpu = cpu_new(code, code_size);
while (1) {
uint32_t raw = cpu_fetch(cpu);
cpu->pc += 4;
if (cpu->pc > code_size) break;
cpu_execute(cpu, raw);
if (!cpu->pc) break;
}
cpu_free(cpu);
return 0;
}
The test from riscv-tests
Quiz2-ProblemA
Quiz2-ProblemB
.data
md: .string "Move Disk "
from: .string " from '"
to: .string "' to '"
newline: .string "'\n"
src: .string "A"
aux: .string "B"
dst: .string "C"
n: .word 3
.text
.globl main
main:
lw a1, n
la t0, src
la t1, dst
la t2, aux
lbu a2, 0(t0)
lbu a3, 0(t2)
lbu a4, 0(t1)
jal x1, hanoi
li a7, 10
ecall
hanoi:
addi sp, sp, -20
sw x1, 0(sp)
sw a1, 4(sp)
sw a2, 8(sp)
sw a3, 12(sp)
sw a4, 16(sp)
addi t0, x0, 1
beq a1, t0, return
lw a1, 4(sp)
addi a1, a1, -1
lbu a2, 8(sp)
lbu a3, 16(sp)
lbu a4, 12(sp)
jal x1, hanoi
lw a1, 4(sp)
lbu a2, 8(sp)
lbu a3, 16(sp)
jal x1, print
lw a1, 4(sp)
addi a1, a1, -1
lbu a2, 12(sp)
lbu a3, 8(sp)
lbu a4, 16(sp)
jal x1, hanoi
lw x1, 0(sp)
addi sp, sp, 20
jalr x0, x1, 0
return:
lw a1, 4(sp)
lbu a2, 8(sp)
lbu a3, 16(sp)
jal x1, print
lw x1, 0(sp)
addi sp, sp, 20
jalr x0, x1, 0
print:
la a0, md
li a7, 4
ecall
addi a0, a1, 0
li a7, 1
ecall
la a0, from
li a7, 4
ecall
addi a0, a2, 0
li a7, 11
ecall
la a0, to
li a7, 4
ecall
addi a0, a3, 0
li a7, 11
ecall
la a0, newline
li a7, 4
ecall
jalr x0, x1, 0
- Initialization:
- The main function initializes the number of disks (n = 3) and the pole labels (A, B, and C).
- It calls the hanoi function with these values.
- Recursive Execution:
- The hanoi function recursively solves the Tower of Hanoi problem.
- When n = 1, it directly prints the move using the print function.
- For n > 1, it breaks the problem into smaller subproblems:
- Move n-1 disks from the source pole (A) to the auxiliary pole (B).
- Move the largest disk from the source pole (A) to the destination pole ©.
- Move the n-1 disks from the auxiliary pole (B) to the destination pole ©.
- Result
Quiz2-ProblemD
- This program implements an integer multiplication using the “Shift and Add Algorithm.” It multiplies two integers (num1 and num2) and stores the result in result.
- Execution Steps
- Data Initialization
- num1 is set to 13 (binary: 0b1101).
- num2 is set to 7.
- result is initialized to 0.
- Algorithm Logic
- The program uses the “Shift and Add Algorithm” to perform multiplication by iterating through each bit of num1.
- During each loop:
- If the least significant bit (LSB) of num1 is 1, the value of num2 is added to result (t2).
- num1 is right-shifted (srli), effectively dividing it by 2.
- num2 is left-shifted (slli), effectively multiplying it by 2.
- The loop continues until num1 becomes 0.
- The final result is stored in the memory location pointed to by result.
- After all bits are processed, the result (91) is stored in memory at the address of result.
- Result:
91
Quiz4-ProblemH
i2f:
srli a1, a0, 31
slli a1, a1, 31
beq a1, x0, positive
sub a0, x0, a0
positive:
beq a0, x0, zero_result
add a2, x0, a1
add a1, x0, a0
add t0, x0, x0
clz_loop:
sll t1, a1, 1
addi t0, t0, 1
mv a1, t1
bgez a1, clz_loop
li t1, 32
blt t0, t1, clz_done
li a0, 32
j normalize
clz_done:
add a0, x0, t0
normalize:
sub t1, x0, a0
sll a1, a1, t1
li t1, 158
sub a0, t1, a0
addi a1, a1, 128
bgez a1, rounding_ok
addi a0, a0, 1
j adjust_mantissa
rounding_ok:
andi a3, a1, 0xFF
beq a3, x0, round_even
adjust_mantissa:
slli a1, a1, 1
j compose_result
round_even:
srli a1, a1, 9
slli a1, a1, 10
j compose_result
compose_result:
srli a1, a1, 9
slli a0, a0, 23
or a0, a0, a1
or a0, a0, a2
jalr x0, ra, 0
zero_result:
or a0, x0, a2
jalr x0, ra, 0
Quiz5-ProblemD
logint:
addi sp, sp, -4
sw t0, 0(sp)
add t0, a0, zero
add a0, zero, zero
logloop:
beq t0, zero, logloop_end
srai t0, t0, 1
addi a0, a0, 1
j logloop
logloop_end:
addi a0, a0, -1
lw t0, 0(sp)
addi sp, sp, 4
jr ra
reverse:
addi sp, sp, -28
sw ra, 0(sp)
sw s0, 4(sp)
sw s1, 8(sp)
sw s2, 12(sp)
sw s3, 16(sp)
sw s4, 20(sp)
sw s5, 24(sp)
call logint
addi s0, zero, 1
add s1, zero, zero
forloop_reverse:
bgt s0, a0, forloop_reverse_end
sub s2, a0, s0
add is3, zero, 1
sll s3, s3, s2
and s3, a1, s3
beq s3, zero, elses3
ifs3:
addi s4, s0, -1
addi s5, zero, 1
sll s5, s5, s4
or s1, s1, s5
elses3:
addi s0, s0, 1
j forloop_reverse
forloop_reverse_end:
add a0, s1, zero
lw ra, 0(sp)
lw s0, 4(sp)
lw s1, 8(sp)
lw s2, 12(sp)
lw s3, 16(sp)
lw s4, 20(sp)
lw s5, 24(sp)
addi sp, sp, 28
jr ra
1. Corrected Execution of logint
- The purpose of logint is to calculate the integer base-2 logarithm (log2) of N.
- Initial value: a0 = 42
- Execution steps:
- After exiting the loop, addi a0, a0, -1 is executed. Final result:
- Thus, the result of logint is a0 = 4.
2. Corrected Execution of reverse
- Final result:
- Thus, the result of reverse is a0 = 11.
- Conclusion Result
- logint result: a0 = 4
- reverse result: a0 = 11
References
https://github.com/riscv/riscv-isa-manual/releases
https://acsa.ustc.edu.cn/ics/download/riscv/RISC-V-Reader-Chinese-v2p1.pdf
https://blog.csdn.net/qq_38798111/article/details/129718824
https://github.com/riscv-collab/riscv-gnu-toolchain
https://github.com/riscv-software-src/riscv-tests
https://hackmd.io/@sysprog/arch2024-quiz2-sol
https://hackmd.io/@sysprog/arch2024-quiz4-sol
https://hackmd.io/@sysprog/arch2024-quiz5-sol