From 981c35584c7f0cf794d39262f6fbfec7199306fc Mon Sep 17 00:00:00 2001 From: vhaudiquet Date: Wed, 4 Oct 2023 21:28:18 +0200 Subject: [PATCH] Initial commit Added base code, can run ELF files and simulate RV32I instructions --- .clang-format | 55 +++++ .gitignore | 1 + Makefile | 27 +++ README.md | 10 + src/bootloader/bootloader.c | 37 +++ src/bootloader/bootloader.h | 8 + src/bootloader/elf/elf.c | 101 ++++++++ src/bootloader/elf/elf.h | 83 +++++++ src/cpu/instruction.h | 63 +++++ src/cpu/rv32cpu.c | 452 ++++++++++++++++++++++++++++++++++++ src/cpu/rv32cpu.h | 194 ++++++++++++++++ src/main.c | 27 +++ src/memory/memory.c | 9 + src/memory/memory.h | 10 + src/memory/mmu/mmu.h | 6 + src/option.c | 178 ++++++++++++++ src/vriscv.h | 19 ++ 17 files changed, 1280 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/bootloader/bootloader.c create mode 100644 src/bootloader/bootloader.h create mode 100644 src/bootloader/elf/elf.c create mode 100644 src/bootloader/elf/elf.h create mode 100644 src/cpu/instruction.h create mode 100644 src/cpu/rv32cpu.c create mode 100644 src/cpu/rv32cpu.h create mode 100644 src/main.c create mode 100644 src/memory/memory.c create mode 100644 src/memory/memory.h create mode 100644 src/memory/mmu/mmu.h create mode 100644 src/option.c create mode 100644 src/vriscv.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3c172c4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,55 @@ +--- +Language: Cpp +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: AllIfsAndElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterStruct: true + AfterUnion: true + BeforeElse: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false +# BreakBeforeBraces: Allman +BreakStringLiterals: false +ColumnLimit: 0 +ContinuationIndentWidth: 2 +IndentCaseBlocks: false +IndentCaseLabels: true +PointerAlignment: Left + +# Indent +IndentWidth: 4 +TabWidth: 4 +UseTab: ForIndentation + +# Spaces +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +# Preprocessor +SortIncludes: Never diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b696ce1 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +NAME=vriscv +CC=gcc +CFLAGS=-O3 -Wall -I src +LDFLAGS= +BUILD_DIR=build + +C_FILES := $(shell find src/ -name '*.c') + +all: $(BUILD_DIR)/$(NAME) + +# Top-level targets +$(BUILD_DIR)/$(NAME): $(C_FILES) | $(BUILD_DIR) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +# Build directory +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +# Phony targets +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + +.PHONY: run +.SILENT: run +run: all + ./$(BUILD_DIR)/$(NAME) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6f88cf --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# vriscv - a risc-v simulator + +## Resources used + +Juraj's Blog, mostly: +- https://jborza.com/post/2021-04-04-riscv-supervisor-mode/ +- https://jborza.com/emulation/2021/04/22/ecalls-and-syscalls.html + +Buildroot fork for nommu linux: +- https://github.com/regymm/buildroot diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c new file mode 100644 index 0000000..ed41a60 --- /dev/null +++ b/src/bootloader/bootloader.c @@ -0,0 +1,37 @@ +#include "bootloader.h" +#include "elf/elf.h" + +#include +#include +#include + +uint32_t bootload(char* file_path) +{ + // Open the file + FILE* f = fopen(file_path, "r"); + if(!f) + { + fprintf(stderr, "Could not open file '%s': %s\n", file_path, strerror(errno)); + exit(EXIT_FAILURE); + } + + // Obtain file size + fseek(f, 0, SEEK_END); + size_t file_size = ftell(f); + fseek(f, 0, SEEK_SET); + + // Load the file in memory + void* file = malloc(file_size); + if(fread(file, file_size, 1, f) != 1) + { + fprintf(stderr, "Could not read file '%s': %s\n", file_path, strerror(errno)); + fclose(f); + exit(EXIT_FAILURE); + } + + // Close the file + fclose(f); + + // TODO: Check file type (for now we only bootload ELF) + return elf_32_load(file); +} diff --git a/src/bootloader/bootloader.h b/src/bootloader/bootloader.h new file mode 100644 index 0000000..f6b2c5c --- /dev/null +++ b/src/bootloader/bootloader.h @@ -0,0 +1,8 @@ +#ifndef BOOTLOADER_H +#define BOOTLOADER_H + +#include + +uint32_t bootload(char* file_path); + +#endif diff --git a/src/bootloader/elf/elf.c b/src/bootloader/elf/elf.c new file mode 100644 index 0000000..1d72f7e --- /dev/null +++ b/src/bootloader/elf/elf.c @@ -0,0 +1,101 @@ +#include "elf.h" +#include "memory/memory.h" +#include "vriscv.h" + +#include +#include +#include + +uint32_t elf_32_load(void* file) +{ + // Parse/verify the header + elf_header_32_t* header = file; + + // Verify magic number + uint8_t* m = header->ELF; + if((m[0] != 0x7F) || (m[1] != 'E') || (m[2] != 'L') || (m[3] != 'F')) + { + fprintf(stderr, "Not a valid ELF file (wrong magic)\n"); + exit(EXIT_FAILURE); + } + + // Verify architecture + if(header->instruction_set != INSTRUCTION_SET_RISCV) + { + switch(header->instruction_set) + { + case INSTRUCTION_SET_X86: + fprintf(stderr, "Provided ELF file targets x86 ; perhaps you forgot to cross-compile ? Please provide a RISC-V ELF\n"); + break; + case INSTRUCTION_SET_X86_64: + fprintf(stderr, "Provided ELF file targets x86_64; perhaps you forgot to cross-compile ? Please provide a RISC-V ELF\n"); + break; + default: + fprintf(stderr, "Provided ELF file is for an unknown instruction set architecture (0x%x), please provide a RISC-V ELF\n", header->instruction_set); + break; + } + exit(EXIT_FAILURE); + } + + // Verify bit count + if(header->bits != BITS_32) + { + switch(header->bits) + { + case BITS_64: + fprintf(stderr, "Provided ELF file targets RISC-V 64 bits ; please provide a 32-bits ELF\n"); + break; + default: + fprintf(stderr, "Provided ELF file targets an unknown bit count (not 32 bits) ; please provide a 32-bits ELF\n"); + break; + } + exit(EXIT_FAILURE); + } + + // Verify endianness + if(header->endianness != ELF_LITTLE_ENDIAN) + { + fprintf(stderr, "Provided ELF file is encoded in big endian ; please provide a little endian ELF\n"); + exit(EXIT_FAILURE); + } + + // File should be correct ; now load it, using program header table + elf_program_header_32_t* program_header = (elf_program_header_32_t*) (((uint8_t*) file) + header->program_header_table_32); + size_t program_header_count = header->program_entry_amount; + for(size_t i = 0; i < program_header_count; i++) + { + elf_program_header_32_t current = program_header[i]; + + // Check segment type + if(current.segment_type != SEGMENT_TYPE_LOAD) + { + fprintf(stderr, "WARNING: Unknown segment type %u in ELF file ; skipping\n", current.segment_type); + continue; + } + + // Check memory size + size_t memsz = current.segment_memory_size; + if(!memsz) + { + fprintf(stderr, "WARNING: LOAD segment with null size in ELF file ; skipping\n"); + continue; + } + + // Map the segment : first the part in the file, then the zeros + if(memory_size < (current.virtual_address + memsz)) + { + fprintf(stderr, "FATAL: Not enough memory while loading ELF file\n"); + exit(EXIT_FAILURE); + } + if(current.segment_file_size != 0) + { + memcpy(&memory[current.virtual_address], ((uint8_t*) file) + current.segment_offset, current.segment_file_size); + } + if(memsz > current.segment_file_size) + { + memset(&memory[current.virtual_address] + current.segment_file_size, 0, memsz - current.segment_file_size); + } + } + + return header->entry_32; +} diff --git a/src/bootloader/elf/elf.h b/src/bootloader/elf/elf.h new file mode 100644 index 0000000..070fa2c --- /dev/null +++ b/src/bootloader/elf/elf.h @@ -0,0 +1,83 @@ +#ifndef ELF_H +#define ELF_H + +/* + * ELF handling + * Valentin HAUDIQUET + * Sources are : + * - https://refspecs.linuxfoundation.org/elf/elf.pdf (ELF Reference) + * - https://wiki.osdev.org/ELF (OSDev) + */ + +#include +#include +#include + +typedef struct ELF_HEADER_32 +{ + uint8_t ELF[4]; + uint8_t bits; // 1 = 32bits, 2 = 64bits + uint8_t endianness; // 1 = little, 2 = big + uint8_t header_version; + uint8_t abi; + uint8_t padding0[8]; + uint16_t exec_type; // 1 = relocatable, 2 = executable, 3 = shared, 4 = core + uint16_t instruction_set; + uint32_t elf_version; + uint32_t entry_32; + uint32_t program_header_table_32; + uint32_t section_table_32; + uint32_t flags; + uint16_t header_size; + uint16_t program_entry_size; + uint16_t program_entry_amount; + uint16_t section_entry_size; + uint16_t section_entry_amount; + uint16_t section_str_index; // Index of string table associated with section names +} __attribute__((packed)) elf_header_32_t; + +#define ELF_LITTLE_ENDIAN 1 +#define ELF_BIG_ENDIAN 2 + +#define BITS_32 1 +#define BITS_64 2 + +#define INSTRUCTION_SET_X86 0x3 +#define INSTRUCTION_SET_X86_64 0x3E +#define INSTRUCTION_SET_RISCV 0xF3 + +typedef struct ELF_SECTION_HEADER_32 +{ + uint16_t section_name; + uint16_t section_type; + uint16_t section_flags; + uint32_t section_addr_32; + uint32_t section_offset_32; + uint16_t section_size; + uint16_t section_link; + uint16_t section_info; + uint16_t section_addralign; + uint16_t section_entrysize; +} __attribute__((packed)) elf_section_header_32_t; + +typedef struct ELF_PROGRAM_HEADER_32 +{ + uint32_t segment_type; + uint32_t segment_offset; + uint32_t virtual_address; + uint32_t undefined; + uint32_t segment_file_size; + uint32_t segment_memory_size; + uint32_t flags; + uint32_t align; +} __attribute__((packed)) elf_program_header_32_t; + +#define SEGMENT_TYPE_NULL 0 +#define SEGMENT_TYPE_LOAD 1 +#define SEGMENT_TYPE_DYNAMIC 2 +#define SEGMENT_TYPE_INTERP 3 +#define SEGMENT_TYPE_NOTE 4 + +uint32_t elf_32_load(void* file); + +#endif diff --git a/src/cpu/instruction.h b/src/cpu/instruction.h new file mode 100644 index 0000000..51a1255 --- /dev/null +++ b/src/cpu/instruction.h @@ -0,0 +1,63 @@ +#ifndef INSTRUCTION_H +#define INSTRUCTION_H + +/* RISC-V RV32 I Base Instruction Set */ +#define OPCODE_LUI 0x37 +#define OPCODE_AUIPC 0x17 +#define OPCODE_JAL 0x6F +#define OPCODE_JALR 0x67 +#define OPCODE_BRANCH 0x63 +#define OPCODE_LOAD 0x3 +#define OPCODE_STORE 0x23 +#define OPCODE_ARITHLOG_IMM 0x13 +#define OPCODE_ARITHLOG 0x33 +#define OPCODE_NOP 0xF +#define OPCODE_SYSTEM 0x73 + +/* OPCODE_BRANCH sub functions (func3) */ +#define FUNC3_BEQ 0x0 +#define FUNC3_BNE 0x1 +#define FUNC3_BLT 0x4 +#define FUNC3_BGE 0x5 +#define FUNC3_BLTU 0x6 +#define FUNC3_BGEU 0x7 + +/* OPCODE_LOAD sub functions (func3) */ +#define FUNC3_LB 0x0 +#define FUNC3_LH 0x1 +#define FUNC3_LW 0x2 +#define FUNC3_LBU 0x4 +#define FUNC3_LHU 0x5 + +/* OPCODE_STORE sub functions (func3) */ +#define FUNC3_SB 0x0 +#define FUNC3_SH 0x1 +#define FUNC3_SW 0x2 + +/* OPCODE_ARITHLOG_IMM sub functions (func3 + func7) */ +#define FUNC3_ADDI 0x0 +#define FUNC3_SLTI 0x2 +#define FUNC3_SLTIU 0x3 +#define FUNC3_XORI 0x4 +#define FUNC3_ORI 0x6 +#define FUNC3_ANDI 0x7 +#define FUNC3_SLLI 0x1 +#define FUNC3_SRLI_SRAI 0x5 +#define FUNC7_SRLI 0x0 +#define FUNC7_SRAI 0x20 + +/* OPCODE_ARITHLOG sub functions (func3 + func7) */ +#define FUNC3_ADD_SUB 0x0 +#define FUNC7_ADD 0x0 +#define FUNC7_SUB 0x20 +#define FUNC3_SLL 0x1 +#define FUNC3_SLT 0x2 +#define FUNC3_SLTU 0x3 +#define FUNC3_XOR 0x4 +#define FUNC3_SRL_SRA 0x5 +#define FUNC7_SRL 0x0 +#define FUNC7_SRA 0x20 +#define FUNC3_OR 0x7 +#define FUNC3_AND 0x8 + +#endif diff --git a/src/cpu/rv32cpu.c b/src/cpu/rv32cpu.c new file mode 100644 index 0000000..8ee2816 --- /dev/null +++ b/src/cpu/rv32cpu.c @@ -0,0 +1,452 @@ +#include "rv32cpu.h" +#include "instruction.h" + +#include "memory/memory.h" +#include "memory/mmu/mmu.h" +#include "vriscv.h" + +#include +#include + +rv32_cpu_t* cpu0; + +typedef union RAW_INSTRUCTION +{ + uint32_t data; + struct + { + uint8_t opcode : 7; + uint16_t rd : 5; + uint16_t func3 : 3; + uint16_t rs1 : 5; + uint16_t rs2 : 5; + uint16_t func7 : 7; + } __attribute__((packed)); +} __attribute__((packed)) raw_instruction_t; + +typedef struct INSTRUCTION +{ + uint8_t opcode; + uint32_t immediate; + uint8_t func3; + uint8_t func7; + uint8_t rd; + uint8_t rs1; + uint8_t rs2; +} instruction_t; + +void cpu_init() +{ + cpu0 = malloc(sizeof(rv32_cpu_t)); + cpu0->regs.zero = 0; +} + +static void cpu_decode(raw_instruction_t raw_instruction, instruction_t* output) +{ + output->opcode = raw_instruction.opcode; + output->immediate = 0; + output->func3 = raw_instruction.func3; + output->func7 = raw_instruction.func7; + output->rd = raw_instruction.rd; + output->rs1 = raw_instruction.rs1; + output->rs2 = raw_instruction.rs2; + + // Decode immediate, and make sure opcode is correct + switch(raw_instruction.opcode) + { + // U-type instructions + case OPCODE_LUI: + case OPCODE_AUIPC: + output->immediate = raw_instruction.data & 0xFFFFF000; + break; + // J-type instructions + case OPCODE_JAL: + // Last bit (31) of data is immediate bit 20 + output->immediate = (raw_instruction.data & 0x80000000) >> 11; + // Then following 10 bits (30-21) are immediate bits 10-1 + output->immediate |= (raw_instruction.data & 0x7FE00000) >> 20; + // Following bit (20) is immediate bit 11 + output->immediate |= (raw_instruction.data & 0x200000) >> 10; + // Last bits (19-12) are immediate bits 19-12 + output->immediate |= (raw_instruction.data & 0xFF000); + break; + // I-type instructions + case OPCODE_JALR: + case OPCODE_LOAD: + case OPCODE_ARITHLOG_IMM: + case OPCODE_SYSTEM: + // Bits 31-20 are immediate bits 11-0 + output->immediate = (raw_instruction.data & 0xFFF00000) >> 20; + break; + // B-type instructions + case OPCODE_BRANCH: + // Last bit (31) of data is immediate bit 12 + output->immediate = (raw_instruction.data & 0x80000000) >> 19; + // Then following 6 bits (30-25) are immediate bits 10-5 + output->immediate |= (raw_instruction.data & 0x7E000000) >> 20; + // On rd field, last 4 bits (4:1) are immediate bits 4:1 + output->immediate |= (raw_instruction.rd & 0x1E); + // On rd field, first bit (0) is immediate bit 11 + output->immediate |= (raw_instruction.rd & 0x01) << 11; + break; + // R-type instructions + case OPCODE_ARITHLOG: + break; + // S-type instructions + case OPCODE_STORE: + // Bits 31-25 (func7) are immediate bits 11:5 + output->immediate = raw_instruction.func7 << 5; + // Bits of rd are immediate bits 4:0 + output->immediate |= raw_instruction.rd; + break; + default: + fprintf(stderr, "Error: Unknown instruction opcode 0x%x, could not decode\n", raw_instruction.opcode); + exit(EXIT_FAILURE); + break; + } +} + +static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction) +{ + switch(instruction->opcode) + { + case OPCODE_LUI: + { + // Load Upper Immediate (load immediate(31:12 bits) in rd) + if(instruction->rd) + cpu->regs.x[instruction->rd] = instruction->immediate; + break; + } + case OPCODE_AUIPC: + { + // Add Upper Immediate to PC + if(instruction->rd) + cpu->regs.x[instruction->rd] = instruction->immediate + cpu->pc; + break; + } + case OPCODE_JAL: + { + // Jump And Link + if(instruction->rd) + cpu->regs.x[instruction->rd] = cpu->pc + 4; + // Sign extend immediate from 21 bits to 32 bits + uint32_t immediate = (instruction->immediate & 0x1FFFFF) | (instruction->immediate & 0x100000 ? 0xFFE00000 : 0); + cpu->pc += immediate - 4; + break; + } + case OPCODE_JALR: + { + // Jump And Link Register + if(instruction->rd) + cpu->regs.x[instruction->rd] = cpu->pc + 4; + // Sign extend immediate from 12 bits to 32 bits + uint32_t immediate = (instruction->immediate & 0xFFF) | (instruction->immediate & 0x800 ? 0xFFFFF000 : 0); + cpu->pc = ((cpu->regs.x[instruction->rs1] + immediate) & 0xFFFFFFFE) - 4; + break; + } + case OPCODE_BRANCH: + { + // Branches ; to know which one, we must analyse func3 + // Sign extend immediate from 13 bits to 32 bits + uint32_t immediate = (instruction->immediate & 0xFFF) | (instruction->immediate & 0x1000 ? 0xFFFFE000 : 0); + immediate -= 4; + + switch(instruction->func3) + { + case FUNC3_BEQ: + // Branch EQual + if(cpu->regs.x[instruction->rs1] == cpu->regs.x[instruction->rs2]) + cpu->pc = immediate; + break; + case FUNC3_BNE: + // Branch Not Equal + if(cpu->regs.x[instruction->rs1] != cpu->regs.x[instruction->rs2]) + cpu->pc = immediate; + break; + case FUNC3_BLT: + // Branch Less Than + if(((int32_t) cpu->regs.x[instruction->rs1]) < ((int32_t) cpu->regs.x[instruction->rs2])) + cpu->pc = immediate; + break; + case FUNC3_BLTU: + // Branch Less Than Unsigned + if(cpu->regs.x[instruction->rs1] < cpu->regs.x[instruction->rs2]) + cpu->pc = immediate; + break; + case FUNC3_BGE: + // Branch Greater Equal + if(((int32_t) cpu->regs.x[instruction->rs1]) >= ((int32_t) cpu->regs.x[instruction->rs2])) + cpu->pc = immediate; + break; + case FUNC3_BGEU: + // Branch Greater Equal Unsigned + if(cpu->regs.x[instruction->rs1] >= cpu->regs.x[instruction->rs2]) + cpu->pc = immediate; + break; + default: + fprintf(stderr, "FATAL: Unknown func3 0x%x for branch instruction, could not execute\n", instruction->func3); + exit(EXIT_FAILURE); + break; + } + break; + } + case OPCODE_LOAD: + { + // Loads ; to know which one, we must analyse func3 + // Sign extend immediate from 12 bits to 32 bits + uint32_t immediate = (instruction->immediate & 0xFFF) | (instruction->immediate & 0x800 ? 0xFFFFF000 : 0); + uint32_t address = cpu->regs.x[instruction->rs1] + immediate; + + switch(instruction->func3) + { + case FUNC3_LB: + // Load Byte (8-bits) + cpu->regs.x[instruction->rd] = memory[mmu_translate(address)]; + // Sign extend from 8 bits to 32 bits + cpu->regs.x[instruction->rd] |= (cpu->regs.x[instruction->rd] & 0x80 ? 0xFFFFFF00 : 0); + break; + case FUNC3_LH: + // Load Halfword (16-bits) + cpu->regs.x[instruction->rd] = *((uint16_t*) &memory[mmu_translate(address)]); + // Sign extend from 16 bits to 32 bits + cpu->regs.x[instruction->rd] |= (cpu->regs.x[instruction->rd] & 0x8000 ? 0xFFFF0000 : 0); + break; + case FUNC3_LW: + // Load Word (32-bits) + cpu->regs.x[instruction->rd] = *((uint32_t*) &memory[mmu_translate(address)]); + break; + case FUNC3_LBU: + // Load Byte Unsigned (8-bits) + cpu->regs.x[instruction->rd] = memory[mmu_translate(address)]; + break; + case FUNC3_LHU: + // Load Halfword Unsigned (16-bits) + cpu->regs.x[instruction->rd] = *((uint16_t*) &memory[mmu_translate(address)]); + break; + default: + fprintf(stderr, "FATAL: Unknown func3 0x%x for load instruction, could not execute\n", instruction->func3); + exit(EXIT_FAILURE); + break; + } + break; + } + case OPCODE_STORE: + { + // Store ; to know which one, we must analyse func3 + // Sign extend immediate from 12 bits to 32 bits + uint32_t immediate = (instruction->immediate & 0xFFF) | (instruction->immediate & 0x800 ? 0xFFFFF000 : 0); + uint32_t address = cpu->regs.x[instruction->rs1] + immediate; + + switch(instruction->func3) + { + case FUNC3_SB: + // Store Byte (8-bits) + memory[mmu_translate(address)] = cpu->regs.x[instruction->rs2] & 0xFF; + break; + case FUNC3_SH: + // Store Halfword (16-bits) + *((uint16_t*) &memory[mmu_translate(address)]) = cpu->regs.x[instruction->rs2] & 0xFFFF; + break; + case FUNC3_SW: + // Store Word (32-bits) + *((uint32_t*) &memory[mmu_translate(address)]) = cpu->regs.x[instruction->rs2]; + break; + default: + fprintf(stderr, "FATAL: Unknown func3 0x%x for store instruction, could not execute\n", instruction->func3); + exit(EXIT_FAILURE); + break; + } + + break; + } + case OPCODE_ARITHLOG_IMM: + { + // Arithmetic and logic instructions on immediate values + // To find out which operation, we must analyse func3 + // Sign extend immediate from 12 bits to 32 bits + uint32_t immediate = (instruction->immediate & 0xFFF) | (instruction->immediate & 0x800 ? 0xFFFFF000 : 0); + + switch(instruction->func3) + { + case FUNC3_ADDI: + // ADD Immediate + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] + immediate; + break; + case FUNC3_SLTI: + // Set Less Than Immediate + if(((int32_t) cpu->regs.x[instruction->rs1]) < ((int32_t) immediate)) + cpu->regs.x[instruction->rd] = 1; + else + cpu->regs.x[instruction->rd] = 0; + break; + case FUNC3_SLTIU: + // Set Less Than Immediate Unsigned + if(cpu->regs.x[instruction->rs1] < immediate) + cpu->regs.x[instruction->rd] = 1; + else + cpu->regs.x[instruction->rd] = 0; + break; + case FUNC3_XORI: + // XOR Immediate + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] ^ immediate; + break; + case FUNC3_ORI: + // OR Immediate + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] | immediate; + break; + case FUNC3_ANDI: + // AND Immediate + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] & immediate; + break; + case FUNC3_SLLI: + // Sign-extend immediate in rs2 from 5 bits to 32 bits + immediate = (cpu->regs.x[instruction->rs2] & 0x1F) | (cpu->regs.x[instruction->rs2] & 0x10 ? 0xFFFFFFE0 : 0); + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] << immediate; + break; + case FUNC3_SRLI_SRAI: + // Sign-extend immediate in rs2 from 5 bits to 32 bits + immediate = (cpu->regs.x[instruction->rs2] & 0x1F) | (cpu->regs.x[instruction->rs2] & 0x10 ? 0xFFFFFFE0 : 0); + // Analyse func7 to know which is it + switch(instruction->func7) + { + case FUNC7_SRLI: + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] >> immediate; + break; + case FUNC7_SRAI: + // Arithmetic slide + uint32_t sign_bit = cpu->regs.x[instruction->rs1] & 0x80000000; + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] >> immediate; + if(sign_bit) + cpu->regs.x[instruction->rd] |= ~(0xFFFFFFFF >> immediate); + break; + default: + fprintf(stderr, "FATAL: Unknown func7 0x%x for arithlog immediate SRLI/SRAI instruction, could not execute\n", instruction->func7); + exit(EXIT_FAILURE); + break; + } + break; + default: + fprintf(stderr, "FATAL: Unknown func3 0x%x for arithlog immediate instruction, could not execute\n", instruction->func3); + exit(EXIT_FAILURE); + break; + } + break; + } + case OPCODE_ARITHLOG: + { + // Arithmetic and logic instructions + // To find out which operation, we must analyse func3 and func7 + switch(instruction->func3) + { + case FUNC3_ADD_SUB: + switch(instruction->func7) + { + case FUNC7_ADD: + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] + cpu->regs.x[instruction->rs2]; + break; + case FUNC7_SUB: + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] - cpu->regs.x[instruction->rs2]; + break; + default: + fprintf(stderr, "FATAL: Unknown func7 0x%x for arithlog ADD/SUB instruction, could not execute\n", instruction->func7); + exit(EXIT_FAILURE); + break; + } + break; + case FUNC3_SLL: + // Slide Left Logical + uint32_t sll_value = cpu->regs.x[instruction->rs2] & 0x1F; + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] << sll_value; + break; + case FUNC3_SLT: + // Set Less Than + if(((int32_t) cpu->regs.x[instruction->rs1]) < ((int32_t) cpu->regs.x[instruction->rs2])) + cpu->regs.x[instruction->rd] = 1; + else + cpu->regs.x[instruction->rd] = 0; + break; + case FUNC3_SLTIU: + // Set Less Than Unsigned + if(cpu->regs.x[instruction->rs1] < cpu->regs.x[instruction->rs2]) + cpu->regs.x[instruction->rd] = 1; + else + cpu->regs.x[instruction->rd] = 0; + break; + case FUNC3_XOR: + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] ^ cpu->regs.x[instruction->rs2]; + break; + case FUNC3_OR: + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] | cpu->regs.x[instruction->rs2]; + break; + case FUNC3_AND: + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] & cpu->regs.x[instruction->rs2]; + break; + case FUNC3_SRL_SRA: + switch(instruction->func7) + { + case FUNC7_SRL: + // Slide Right Logical + uint32_t srl_value = cpu->regs.x[instruction->rs2] & 0x1F; + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] >> srl_value; + break; + case FUNC7_SRA: + // Slide Right Arithmetical + uint32_t sra_value = cpu->regs.x[instruction->rs2] & 0x1F; + uint32_t sign_bit = cpu->regs.x[instruction->rs1] & 0x80000000; + cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] >> sra_value; + if(sign_bit) + cpu->regs.x[instruction->rd] |= ~(0xFFFFFFFF >> sra_value); + break; + default: + fprintf(stderr, "FATAL: Unknown func7 0x%x for arithlog SRL/SRA instruction, could not execute\n", instruction->func7); + exit(EXIT_FAILURE); + break; + } + break; + default: + fprintf(stderr, "FATAL: Unknown func3 0x%x for arithlog instruction, could not execute\n", instruction->func3); + exit(EXIT_FAILURE); + break; + } + break; + } + case OPCODE_NOP: + { + // TODO : Implement PAUSE, FENCE, FENCE.TSO + break; + } + case OPCODE_SYSTEM: + { + // TODO : Implement ECALL, EBREAK + break; + } + default: + fprintf(stderr, "FATAL: Unknown instruction opcode 0x%x while executing; how could this decode ?\n", instruction->opcode); + exit(EXIT_FAILURE); + break; + } +} + +void cpu_loop(rv32_cpu_t* cpu) +{ + while(1) + { + // Fetch + raw_instruction_t raw_instruction; + if(cpu->pc > memory_size - 4) + { + fprintf(stderr, "Error: instruction fetch: pc is out of addressable memory\n"); + exit(EXIT_FAILURE); + } + raw_instruction.data = *((uint32_t*) (&memory[cpu->pc])); + + // Decode + instruction_t instruction; + cpu_decode(raw_instruction, &instruction); + + // Execute + cpu_execute(cpu, &instruction); + + cpu->pc += 4; + } +} diff --git a/src/cpu/rv32cpu.h b/src/cpu/rv32cpu.h new file mode 100644 index 0000000..5925c88 --- /dev/null +++ b/src/cpu/rv32cpu.h @@ -0,0 +1,194 @@ +#ifndef RV32CPU_H +#define RV32CPU_H + +#include + +/* +* This is a structure encoding for the registers of +* the rv32 cpu. +* It allows access of register x0 using : +* structname.x0, structname.zero, structname.x[0] +* This way, access can be really flexible +*/ +typedef struct RV32_CPU_REGS +{ + union + { + struct + { + union + { + uint32_t x0; + uint32_t zero; + }; + union + { + uint32_t x1; + uint32_t ra; + }; + union + { + uint32_t x2; + uint32_t sp; + }; + union + { + uint32_t x3; + uint32_t gp; + }; + union + { + uint32_t x4; + uint32_t tp; + }; + union + { + uint32_t x5; + uint32_t t0; + }; + union + { + uint32_t x6; + uint32_t t1; + }; + union + { + uint32_t x7; + uint32_t t2; + }; + union + { + uint32_t x8; + uint32_t s0; + }; + union + { + uint32_t x9; + uint32_t s1; + }; + union + { + uint32_t x10; + uint32_t a0; + }; + union + { + uint32_t x11; + uint32_t a1; + }; + union + { + uint32_t x12; + uint32_t a2; + }; + union + { + uint32_t x13; + uint32_t a3; + }; + union + { + uint32_t x14; + uint32_t a4; + }; + union + { + uint32_t x15; + uint32_t a5; + }; + union + { + uint32_t x16; + uint32_t a6; + }; + union + { + uint32_t x17; + uint32_t a7; + }; + union + { + uint32_t x18; + uint32_t s2; + }; + union + { + uint32_t x19; + uint32_t s3; + }; + union + { + uint32_t x20; + uint32_t s4; + }; + union + { + uint32_t x21; + uint32_t s5; + }; + union + { + uint32_t x22; + uint32_t s6; + }; + union + { + uint32_t x23; + uint32_t s7; + }; + union + { + uint32_t x24; + uint32_t s8; + }; + union + { + uint32_t x25; + uint32_t s9; + }; + union + { + uint32_t x26; + uint32_t s10; + }; + union + { + uint32_t x27; + uint32_t s11; + }; + union + { + uint32_t x28; + uint32_t t3; + }; + union + { + uint32_t x29; + uint32_t t4; + }; + union + { + uint32_t x30; + uint32_t t5; + }; + union + { + uint32_t x31; + uint32_t t6; + }; + }; + uint32_t x[32]; + }; +} rv32_cpu_regs_t; + +typedef struct RV32_CPU +{ + rv32_cpu_regs_t regs; + uint32_t pc; +} rv32_cpu_t; + +extern rv32_cpu_t* cpu0; +void cpu_init(); +void cpu_loop(rv32_cpu_t* cpu); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2bf27ad --- /dev/null +++ b/src/main.c @@ -0,0 +1,27 @@ +#include "vriscv.h" +#include "memory/memory.h" +#include "bootloader/bootloader.h" +#include "cpu/rv32cpu.h" + +char* CURRENT_NAME; + +int main(int argc, char** argv) +{ + CURRENT_NAME = argc ? argv[0] : NAME; + parse_options(argc, argv); + + // Initialize the memory + mem_init(); + + // Bootload the file passed as argument + uint32_t entry_point = bootload(file_path); + + // Initialize the CPU + cpu_init(); + cpu0->pc = entry_point; + + // CPU simulation + cpu_loop(cpu0); + + return 0; +} diff --git a/src/memory/memory.c b/src/memory/memory.c new file mode 100644 index 0000000..0bd3622 --- /dev/null +++ b/src/memory/memory.c @@ -0,0 +1,9 @@ +#include "memory.h" +#include "vriscv.h" + +uint8_t* memory; + +void mem_init() +{ + memory = malloc(memory_size); +} diff --git a/src/memory/memory.h b/src/memory/memory.h new file mode 100644 index 0000000..8fb8975 --- /dev/null +++ b/src/memory/memory.h @@ -0,0 +1,10 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include + +extern uint8_t* memory; + +void mem_init(); + +#endif diff --git a/src/memory/mmu/mmu.h b/src/memory/mmu/mmu.h new file mode 100644 index 0000000..8bdb923 --- /dev/null +++ b/src/memory/mmu/mmu.h @@ -0,0 +1,6 @@ +#ifndef MMU_H +#define MMU_H + +#define mmu_translate(vaddr) (vaddr) + +#endif diff --git a/src/option.c b/src/option.c new file mode 100644 index 0000000..0a6e896 --- /dev/null +++ b/src/option.c @@ -0,0 +1,178 @@ +/* + * Command-line option parsing + */ +#include "vriscv.h" + +#define OPTION_SEPARATOR "-" + +uint64_t memory_size = 512 * 1024 * 1024; +char* file_path; + +static void print_usage(); +static void print_help(); +static void print_version(); + +static int parse_long_option(char* str, char* argq); + +void parse_options(int argc, char** argv) +{ + for(int i = 1; i < argc; i++) + { + // Start option parsing for argument 'i' + if(argv[i][0] != *OPTION_SEPARATOR) continue; + + // Check for long options + if(argv[i][1] == *OPTION_SEPARATOR) + { + i += parse_long_option(argv[i] + 2, argv[i + 1]); + continue; + } + + // Parse short options + int k = 1; + while(argv[i][k]) + { + switch(argv[i][k]) + { + case 'h': + case '?': + { + print_help(); + exit(0); + } + case 'v': + { + print_version(); + exit(0); + } + case 'm': + { + // First try to convert next chars into int (ex. -m512) + char* end; + memory_size = strtol(&argv[i][k + 1], &end, 10); + if(*end != '\0' || end == &argv[i][k + 1]) + { + if(argv[i][k + 1]) + { + fprintf(stderr, "Error: Option -m needs an argument, but you used -m in a group of options without an integer next to it\n"); + exit(EXIT_FAILURE); + } + + // Try to parse next arg as integer + if(argc <= i + 1) + { + fprintf(stderr, "Error: Option " OPTION_SEPARATOR "m needs an argument\n"); + exit(EXIT_FAILURE); + } + + memory_size = strtol(argv[i + 1], &end, 10); + if(*end != '\0') + { + fprintf(stderr, "Error: Invalid argument '%s' for option " OPTION_SEPARATOR "m\n", argv[i + 1]); + exit(EXIT_FAILURE); + } + else + { + i++; + k = strlen(argv[i]) - 1; + } + } + else k += (end - &argv[i][k + 1]); + + if(memory_size <= 0) + { + fprintf(stderr, "Error: Memory size needs to be > 0\n"); + exit(EXIT_FAILURE); + } + + if(memory_size > 4096) + { + fprintf(stderr, "Error: Cannot address more than 4 GiB of memory on 32-bits !\n"); + exit(EXIT_FAILURE); + } + memory_size *= 1024 * 1024; + + break; + } + default: + { + fprintf(stderr, "Error: Unknown short option -%c\n", argv[i][k]); + exit(EXIT_FAILURE); + } + } + k++; + } + } + + if(argc <= 1) + { + print_usage(); + exit(EXIT_FAILURE); + } + else file_path = argv[argc - 1]; +} + +static int parse_long_option(char* str, char* argq) +{ + if(strcmp(str, "help") == 0) + { + print_help(); + exit(0); + } + else if(strcmp(str, "version") == 0) + { + print_version(); + exit(0); + } + else if(strcmp(str, "memory") == 0) + { + if(argq == NULL) + { + fprintf(stderr, "Error: No argument given for option " OPTION_SEPARATOR "-memory\n"); + exit(EXIT_FAILURE); + } + + // Convert argument to integer + char* end; + memory_size = strtol(argq, &end, 10); + if(*end != '\0') + { + fprintf(stderr, "Error: Invalid argument '%s' for option " OPTION_SEPARATOR "-memory\n", argq); + exit(EXIT_FAILURE); + } + + if(memory_size > 4096) + { + fprintf(stderr, "Error: Cannot address more than 4 GiB of memory on 32-bits !\n"); + exit(EXIT_FAILURE); + } + memory_size *= 1024 * 1024; + + return 1; + } + else + { + fprintf(stderr, "Error: Unknown long option " OPTION_SEPARATOR OPTION_SEPARATOR "%s\n", str); + exit(EXIT_FAILURE); + } +} + +static void print_usage() +{ + printf("Usage: %s [options] \n", CURRENT_NAME); +} + +static void print_help() +{ + print_usage(); + + printf("Options:\n"); + printf(" " OPTION_SEPARATOR "h, " OPTION_SEPARATOR "?, --help\t\tPrint this help message\n"); + printf(" " OPTION_SEPARATOR "v, --version\t\t\tPrint version information\n"); + printf(" " OPTION_SEPARATOR "m, --memory\t\t\tSet the simulated memory size, in MiB\n"); +} + +static void print_version() +{ + printf("%s (%s) version %s\n", CURRENT_NAME, NAME, VERSION); +} diff --git a/src/vriscv.h b/src/vriscv.h new file mode 100644 index 0000000..4009074 --- /dev/null +++ b/src/vriscv.h @@ -0,0 +1,19 @@ +#ifndef VRISCV_H +#define VRISCV_H + +#include +#include +#include +#include +#include + +#define NAME "vriscv" +#define VERSION "0.1" +extern char* CURRENT_NAME; + +/* Program options */ +extern size_t memory_size; +extern char* file_path; +void parse_options(int argc, char** argv); + +#endif