diff --git a/Makefile b/Makefile index cfcc3f9..a10a0f1 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ CC := gcc ifeq ($(DEBUG),1) CFLAGS := -Wall -Wextra -std=gnu23 -I include -O0 -g else - CFLAGS := -Wall -Wextra -std=gnu23 -I include -O3 -Wno-unused-variable + CFLAGS := -Wall -Wextra -std=gnu23 -I include -O3 endif # Directory for build outputs diff --git a/README.txt b/README.txt index be5e293..0476d2d 100644 --- a/README.txt +++ b/README.txt @@ -14,8 +14,8 @@ Emulator exit codes: To compile stuff: 0. Get the toolchain obviously -1. riscv32-unknown-elf-gcc -c -Oz program.c -2. riscv32-unknown-elf-objcopy -O binary program.o program.bin +1. riscv32-unknown-elf-gcc -ffreestanding -nostdlib -nostartfiles -Wl,--no-relax -Ttext=0x0 -e main -O0 -o program.elf program.c +2. riscv32-unknown-elf-objcopy -O binary program.elf program.bin 3. program.bin is the binary file with the program, pass it as an argument rv32i, ilp32 compatible toolchain for 64bit Linux: https://lfs.m724.eu/toolchain.tar.zst adaa74f263dcba430da588b1109bc3b90bd90a84c67b06213bd03a7bbacd1a2a diff --git a/programs/return2.bin b/programs/return2.bin old mode 100644 new mode 100755 index 685fc27..0fd42f9 Binary files a/programs/return2.bin and b/programs/return2.bin differ diff --git a/src/instruction_executor.c b/src/instruction_executor.c index 6a81984..a3f0b55 100644 --- a/src/instruction_executor.c +++ b/src/instruction_executor.c @@ -4,7 +4,7 @@ #define VERBOSE_LOGGING 0 -static void printBinary(int num) { +static void printBinary(uint32_t num) { if (num > 1) { printBinary(num / 2); } @@ -20,7 +20,7 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside uint8_t rd = instruction >> 7 & 0x1F; if (VERBOSE_LOGGING == 1) { - printf("\n\nPC: %d\n", cpu->programCounter); + printf("\n\nPC: 0x%X (%d)\n", cpu->programCounter, cpu->programCounter); printf("Instruction: "); printBinary(instruction); printf("\nOpcode: 0x%X (", opcode); @@ -29,7 +29,7 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside printf("Dest. reg.: x%d\n", rd); } else { // TODO perhaps no break between instructions? Would look better but there would be padding to fix - printf("\n\nPC: %d ", cpu->programCounter); + printf("\n\n0x%X ", cpu->programCounter); } switch (opcode) { @@ -88,7 +88,7 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside break; default: // TODO proper error handling - fprintf(stderr, "Unrecognized OP-IMM funct3!"); + fprintf(stderr, "Invalid OP-IMM funct3: 0x%X", funct3); return 2; break; } @@ -96,10 +96,10 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside break; } case 0b0110111: { // LUI load upper immediate (U type) - int32_t imm = instruction & 0xFFFFF000; // no need for cast because no sign extension + uint32_t imm = instruction & 0xFFFFF000; // no need for cast because no sign extension registers[rd] = imm; - printf("LUI - 0x%X (%d) -> x%u", imm, imm, rd); + printf("LUI: 0x%X -> x%u", imm, rd); break; } case 0b1100111: { // JALR for unconditional jump and link register (I type) @@ -113,7 +113,7 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside return 2; } - int result = registers[rs1] + imm; + uint32_t result = registers[rs1] + imm; result &= ~1; // set LSB to 0 registers[rd] = cpu->programCounter + 4; @@ -128,7 +128,6 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside uint8_t rs1 = instruction >> 15 & 0x1F; uint8_t rs2 = instruction >> 20 & 0x1F; int32_t imm = ((int32_t)instruction >> 20 & 0xffffffe0) | rd; - uint32_t og = registers[rs1]; // for log only uint32_t addr = registers[rs1] + imm; if (funct3 == 0b000) { // SB @@ -136,21 +135,22 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside uint8_t val = registers[rs2] & 0xFF; write_address_space(addressSpace, addr, 1, &val); - printf("SB (8bit): x%d 0x%X (%u) -> 0x%X + %d = 0x%X", rs2, rs2, val, og, imm, addr); + printf("SB (8bit): x%d 0x%X (%u) -> 0x%X + %d = 0x%X", rs2, rs2, val, registers[rs1], imm, addr); } else if (funct3 == 0b001) { // SH // SH stores a 16-bit value from the low bits of register rs2 to memory. uint16_t val = registers[rs2] & 0xFFFF; write_address_space(addressSpace, addr, 2, &val); - printf("SH (16bit): x%d 0x%X (%u) -> 0x%X + %d = 0x%X", rs2, rs2, val, og, imm, addr); + printf("SH (16bit): x%d 0x%X (%u) -> 0x%X + %d = 0x%X", rs2, rs2, val, registers[rs1], imm, addr); } else if (funct3 == 0b010) { // SW // SW stores a 32-bit value from the low bits of register rs2 to memory. uint32_t val = registers[rs2]; write_address_space(addressSpace, addr, 4, &val); - printf("SW (32bit): x%d 0x%X (%u) -> 0x%X + %d = 0x%X", rs2, rs2, val, og, imm, addr); + printf("SW (32bit): x%d 0x%X (%u) -> 0x%X + %d = 0x%X", rs2, rs2, val, registers[rs1], imm, addr); } else { - // TODO illegal funct3 + fprintf(stderr, "Invalid STORE funct3: 0x%X", funct3); + return 2; } break; } @@ -182,26 +182,79 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside } else if (funct3 == 0b100) { // LBU // LBU loads an 8-bit value from memory but then zero extends to 32-bits. read_address_space(addressSpace, addr, 1, &val); - val = (uint32_t)val >> 24; // TODO optimize that perhaps + val = (uint32_t)val >> 24; // casting for zero extension printf("LBU (8bit)"); } else if (funct3 == 0b101) { // LHU // LHU loads a 16-bit value from memory but then zero extends to 32-bits. read_address_space(addressSpace, addr, 2, &val); - val = (uint32_t)val >> 16; // TODO optimize that perhaps + val = (uint32_t)val >> 16; printf("LHU (16bit)"); } else { - // TODO illegal funct3 + fprintf(stderr, "Invalid LOAD funct3: 0x%X", funct3); + return 2; } - printf(" - 0x%X -> x%u: 0x%X", addr, rd, val); + printf(": 0x%X -> x%u: 0x%X", addr, rd, val); registers[rd] = val; break; } + case 0b0010111: { // AUIPC (Add Upper Immediate to PC) (U type) + int32_t imm = instruction & 0xFFFFF000; + registers[rd] = cpu->programCounter + imm; + + printf("AUIPC: pc %u + imm %d = %u -> x%d", cpu->programCounter, imm, registers[rd], rd); + break; + } + case 0b1101111: { // JAL for unconditional jump (J type) + int32_t imm = ((int32_t)instruction >> 31) << 20; // Extract imm[20] and sign-extend + imm |= (instruction >> 21) & 0x3FF; // Extract imm[10:1] + imm |= (instruction >> 20) & 0x1; // Extract imm[11] + imm |= (instruction >> 12) & 0xFF; // Extract imm[19:12] + imm <<= 1; // Left-shift by 1 to account for the implicit 0 + + registers[rd] = cpu->programCounter; // program counter is always incremented after executing instruction + cpu->programCounter += imm; + + printf("JAL: Jumped to %u + %d = %u (inst %d)", registers[rd], imm, cpu->programCounter, cpu->programCounter / 4); + break; + } + case 0b0110011: { // OP for Integer Register-Register Operations (R type) + uint8_t funct3 = instruction >> 12 & 0x7; + uint8_t funct7 = instruction >> 25 & 0x7F; + + uint32_t rs1 = instruction >> 15 & 0x1F; + uint32_t rs2 = instruction >> 20 & 0x1F; + + uint32_t n1 = registers[rs1]; + uint32_t n2 = registers[rs2]; + + // TODO maybe it would be better to check funct7 first + if (funct3 == 0b000) { // ADD + if (funct7 == 0b0000000) { + registers[rd] = (uint64_t)n1 + n2; // to not overflow + printf("ADD: x%d 0x%X (%d) + x%d 0x%X (%d) = 0x%X (%d) -> x%d", rs1, n1, n1, rs2, n2, n2, n1 + n2, n1 + n2, rd); + } else if (funct7 == 0b0100000) { + registers[rd] = (uint64_t)n1 - n2; // to not overflow + printf("SUB: x%d 0x%X (%d) - x%d 0x%X (%d) = 0x%X (%d) -> x%d", rs1, n1, n1, rs2, n2, n2, n1 - n2, n1 - n2, rd); + } else { + fprintf(stderr, "Invalid funct7 for ADD: 0x%X", funct7); + return 2; + } + } else if (funct3 == 0b110) { // OR + registers[rd] = n1 | n2; // no need for uint64 here because there's no overflow to truncate + printf("OR: x%d 0x%X (%d) | x%d 0x%X (%d) = 0x%X (%d) -> x%d", rs1, n1, n1, rs2, n2, n2, n1 | n2, n1 | n2, rd); + } else { + fprintf(stderr, "Invalid OP funct3: 0x%X", funct3); + return 2; + } + + break; + } default: { - // TODO illegal instruction + // TODO illegal instruction, proper error handling fprintf(stderr, "Unrecognized opcode!"); return 1; break; diff --git a/src/main.c b/src/main.c index 54411d3..23b251a 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,7 @@ int main(int argc, char *argv[]) { CPU cpu = create_cpu(addressSpace); cpu.registers[1] = addressSpace->romSize + addressSpace->ramSize; // make jumping to x1 end the program + cpu.programCounter = 0x70; printf("\n----- Start of program -----"); diff --git a/src/program_loader.c b/src/program_loader.c index d234223..7ec3da5 100644 --- a/src/program_loader.c +++ b/src/program_loader.c @@ -9,8 +9,9 @@ int load_to_rom(const char filename[], AddressSpace *addressSpace) { return 1; } + // size_t is not used because a program can't be larger than the 32bit address space int romSize = addressSpace->romSize; - fread(addressSpace->rom, 1, romSize, file); + int bytesRead = fread(addressSpace->rom, 1, romSize, file); fseek(file, 0, SEEK_END); int fileLen = ftell(file); @@ -18,6 +19,9 @@ int load_to_rom(const char filename[], AddressSpace *addressSpace) { if (fileLen > romSize) { fprintf(stderr, "File has %d bytes, and doesn't fit in ROM of capacity %d bytes\n", fileLen, romSize); return 1; + } else if (bytesRead != fileLen) { + fprintf(stderr, "File has %d bytes, but read %d bytes\n", fileLen, bytesRead); + return 1; } printf("%s has %d bytes or %d instructions\n", filename, fileLen, fileLen / 4);