Make the C-compiled program actually work
This commit is contained in:
parent
797a308764
commit
90a8c9c378
7 changed files with 71 additions and 40 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,3 +19,4 @@ Testing
|
||||||
result
|
result
|
||||||
build
|
build
|
||||||
example/example
|
example/example
|
||||||
|
example/example.raw
|
16
README.md
16
README.md
|
@ -14,27 +14,21 @@ eval "$configurePhase"
|
||||||
ninja
|
ninja
|
||||||
```
|
```
|
||||||
|
|
||||||
You should get the `rve` binary. Run it:
|
As a result you should get an executable called `rve`
|
||||||
|
|
||||||
```sh
|
|
||||||
./rve
|
|
||||||
```
|
|
||||||
|
|
||||||
At the moment the program is hardcoded into the `main()` function directly in machine code. As a follow-up, I'm working on compiling programs with the gcc cross-toolchain, but it will require some time to fiddle with options.
|
|
||||||
|
|
||||||
## Cross-toolchain and an example program in C
|
## Cross-toolchain and an example program in C
|
||||||
|
|
||||||
There is an example program which you can compile, but not yet run. Do this:
|
There is an example program which you can compile. It requires some custom toolchain so currently not built with CMake. To compile it:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd example
|
cd example
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
As a result, you'll get an `example` binary. To view the disassembly:
|
As a result, you'll get an `example.raw` binary. To execute it:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
riscv32-none-elf-objdump -D example
|
./rve ../example/example.raw
|
||||||
```
|
```
|
||||||
|
|
||||||
As you can see, it's quite barebones, with the program starting at 0x1000. The reason it can't yet be run with the emulator is mostly in the absence of the boot section which initializes the stack and calls the main function. Stay tuned.
|
The expected output of the example program is `269`.
|
||||||
|
|
|
@ -4,3 +4,6 @@ example: example.c Makefile boot.s linker.ld
|
||||||
riscv32-none-elf-ld boot.o example.o -T linker.ld -o example
|
riscv32-none-elf-ld boot.o example.o -T linker.ld -o example
|
||||||
riscv32-none-elf-strip -R .riscv.attributes example
|
riscv32-none-elf-strip -R .riscv.attributes example
|
||||||
riscv32-none-elf-strip -R .comment example
|
riscv32-none-elf-strip -R .comment example
|
||||||
|
riscv32-none-elf-objcopy -O binary example example.raw
|
||||||
|
#riscv32-none-elf-objcopy -O binary -j .text example example.text
|
||||||
|
#riscv32-none-elf-objcopy -O binary -j .sdata example example.data
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.globl _boot
|
.globl _boot
|
||||||
_boot:
|
_boot:
|
||||||
lui x2, 0x80004
|
li x2, 0x8000
|
||||||
jal main
|
call main
|
||||||
sbreak
|
sbreak
|
||||||
j .
|
j .
|
||||||
|
|
|
@ -7,13 +7,23 @@ SECTIONS {
|
||||||
*(.text) /* Place all .text sections (code) here */
|
*(.text) /* Place all .text sections (code) here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
. = 0x1000;
|
||||||
|
|
||||||
.data : {
|
.data : {
|
||||||
*(.data) /* Place all .data sections (initialized data) here */
|
*(.data) /* Place all .data sections (initialized data) here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
. = 0x2000;
|
||||||
|
|
||||||
.bss : {
|
.bss : {
|
||||||
*(.bss) /* Place all .bss sections (uninitialized data) here */
|
*(.bss) /* Place all .bss sections (uninitialized data) here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
. = 0x3000;
|
||||||
|
|
||||||
|
.stack : {
|
||||||
|
*(.stack)
|
||||||
|
}
|
||||||
|
|
||||||
/DISCARD/ : { *(.note.GNU-stack) } /* Discard stack section */
|
/DISCARD/ : { *(.note.GNU-stack) } /* Discard stack section */
|
||||||
}
|
}
|
||||||
|
|
65
src/rve.cpp
65
src/rve.cpp
|
@ -3,40 +3,63 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
int main() {
|
void load_program(const std::string& filename, uint8_t* memory, size_t memory_size) {
|
||||||
|
std::ifstream file(filename, std::ios::binary|std::ios::ate);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t file_size = file.tellg();
|
||||||
|
|
||||||
|
if (file_size > memory_size) {
|
||||||
|
throw std::runtime_error("File is too big");
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
file.read(reinterpret_cast<char*>(memory), file_size);
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
throw std::runtime_error("Failed to read the complete program into memory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
const size_t MEMORY_SIZE = 128*1024;
|
const size_t MEMORY_SIZE = 128*1024;
|
||||||
uint8_t memory[MEMORY_SIZE] = {0};
|
uint8_t memory[MEMORY_SIZE] = {0};
|
||||||
|
|
||||||
uint32_t program[] = {
|
if (argc <= 1) {
|
||||||
0x00000093, // addi x1, x0, 0
|
std::cerr << "Expected filename of program to execute" << std::endl;
|
||||||
0x00100113, // addi x2, x0, 1
|
return 1;
|
||||||
0x00a00193, // addi x3, x0, 10
|
}
|
||||||
0x10000213, // addi x4, x0, 256
|
|
||||||
0x002080b3, // add x1, x1, x2 <- loop
|
|
||||||
0x00110113, // addi x2, x2, 1
|
|
||||||
0xfe311ce3, // bne x2, x3, loop
|
|
||||||
0x00122023, // sw x1, 0(x4)
|
|
||||||
};
|
|
||||||
|
|
||||||
std::memcpy(memory, reinterpret_cast<uint8_t*>(program), sizeof(program));
|
std::string program_filename = argv[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
load_program(program_filename, memory, MEMORY_SIZE);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t *res1 = (uint32_t *)&memory[0x1000];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
eval(memory, MEMORY_SIZE);
|
eval(memory, MEMORY_SIZE);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "Emulator error: " << e.what() << std::endl;
|
std::cerr << "Emulator error: " << e.what() << std::endl;
|
||||||
assert(false && "Emulator threw an exception!");
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the result
|
uint32_t *res = (uint32_t *)&memory[0x1000];
|
||||||
uint32_t result = 0;
|
|
||||||
std::memcpy(&result, memory + 256, sizeof(uint32_t)); // Memory address 0x100 (256 in decimal)
|
|
||||||
|
|
||||||
// Expected result is the sum of 0 to 9 = 55
|
std::cout << "result: " << *res << std::endl;
|
||||||
std::cout << "result: " << std::dec << result << "\n";
|
|
||||||
assert(result == 45 && "Test failed: Sum of 1 to 10 is incorrect!");
|
|
||||||
|
|
||||||
// If we reach here, the test passed
|
|
||||||
std::cout << "Test passed: Sum of 1 to 10 = " << result << std::endl;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
10
src/vm.cpp
10
src/vm.cpp
|
@ -27,7 +27,7 @@ void eval(uint8_t* memory, size_t memory_size) {
|
||||||
while (pc < memory_size && running) {
|
while (pc < memory_size && running) {
|
||||||
uint32_t instr = fetch_instruction();
|
uint32_t instr = fetch_instruction();
|
||||||
if (instr == 0) break;
|
if (instr == 0) break;
|
||||||
// std::cout << "pc: " << pc << "\n";
|
//std::cout << "pc: " << std::hex << pc << std::dec << "\n";
|
||||||
// std::cout << "instr: " << std::hex << instr << "\n";
|
// std::cout << "instr: " << std::hex << instr << "\n";
|
||||||
pc += 4;
|
pc += 4;
|
||||||
|
|
||||||
|
@ -170,18 +170,19 @@ void eval(uint8_t* memory, size_t memory_size) {
|
||||||
}
|
}
|
||||||
case 0x03: { // I-type (loads)
|
case 0x03: { // I-type (loads)
|
||||||
imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate
|
imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate
|
||||||
registers[rd] = 0;
|
|
||||||
if (funct3 == 0x00) { // LB
|
if (funct3 == 0x00) { // LB
|
||||||
uint32_t addr = registers[rs1] + imm;
|
uint32_t addr = registers[rs1] + imm;
|
||||||
if (addr + 1 > memory_size) {
|
if (addr + 1 > memory_size) {
|
||||||
throw std::runtime_error("Memory access out of bounds");
|
throw std::runtime_error("Memory access out of bounds");
|
||||||
}
|
}
|
||||||
|
registers[rd] = 0;
|
||||||
std::memcpy(®isters[rd], memory + addr, sizeof(uint8_t));
|
std::memcpy(®isters[rd], memory + addr, sizeof(uint8_t));
|
||||||
} else if (funct3 == 0x01) { // LH
|
} else if (funct3 == 0x01) { // LH
|
||||||
uint32_t addr = registers[rs1] + imm;
|
uint32_t addr = registers[rs1] + imm;
|
||||||
if (addr + 2 > memory_size) {
|
if (addr + 2 > memory_size) {
|
||||||
throw std::runtime_error("Memory access out of bounds");
|
throw std::runtime_error("Memory access out of bounds");
|
||||||
}
|
}
|
||||||
|
registers[rd] = 0;
|
||||||
std::memcpy(®isters[rd], memory + addr, sizeof(uint16_t));
|
std::memcpy(®isters[rd], memory + addr, sizeof(uint16_t));
|
||||||
} else if (funct3 == 0x2) { // LW
|
} else if (funct3 == 0x2) { // LW
|
||||||
uint32_t addr = registers[rs1] + imm;
|
uint32_t addr = registers[rs1] + imm;
|
||||||
|
@ -195,7 +196,7 @@ void eval(uint8_t* memory, size_t memory_size) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x23: { // S-type (SW)
|
case 0x23: { // S-type (SW)
|
||||||
imm = ((instr >> 7) & 0x1F) | ((instr >> 25) << 5);
|
imm = ((instr >> 7) & 0x1F) | (((instr >> 25) & 0x7F) << 5);
|
||||||
imm = sign_extend(imm, 12); // Sign-extend 12-bit immediate
|
imm = sign_extend(imm, 12); // Sign-extend 12-bit immediate
|
||||||
if (funct3 == 0x0) { // SB
|
if (funct3 == 0x0) { // SB
|
||||||
uint32_t addr = registers[rs1] + imm;
|
uint32_t addr = registers[rs1] + imm;
|
||||||
|
@ -225,7 +226,6 @@ void eval(uint8_t* memory, size_t memory_size) {
|
||||||
((instr >> 21) & 0x3FF) << 1 | // imm[10:1]
|
((instr >> 21) & 0x3FF) << 1 | // imm[10:1]
|
||||||
((instr >> 20) & 0x1) << 11 | // imm[11]
|
((instr >> 20) & 0x1) << 11 | // imm[11]
|
||||||
((instr & 0xFF000)); // imm[19:12]
|
((instr & 0xFF000)); // imm[19:12]
|
||||||
offset <<= 1; // Multiply by 2 (JAL offsets are word-aligned)
|
|
||||||
|
|
||||||
registers[rd] = pc; // Save return address
|
registers[rd] = pc; // Save return address
|
||||||
pc += offset - 4;
|
pc += offset - 4;
|
||||||
|
@ -236,7 +236,7 @@ void eval(uint8_t* memory, size_t memory_size) {
|
||||||
uint32_t target = (registers[rs1] + offset) & ~1; // Target address (LSB cleared)
|
uint32_t target = (registers[rs1] + offset) & ~1; // Target address (LSB cleared)
|
||||||
|
|
||||||
registers[rd] = pc; // Save return address
|
registers[rd] = pc; // Save return address
|
||||||
pc = target - 4;
|
pc = target;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x37: { // LUI
|
case 0x37: { // LUI
|
||||||
|
|
Loading…
Reference in a new issue