Make the C-compiled program actually work

This commit is contained in:
Konstantin Nazarov 2024-12-07 01:19:22 +00:00
parent 797a308764
commit 90a8c9c378
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
7 changed files with 71 additions and 40 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ Testing
result
build
example/example
example/example.raw

View file

@ -14,27 +14,21 @@ eval "$configurePhase"
ninja
```
You should get the `rve` binary. Run it:
```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.
As a result you should get an executable called `rve`
## 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
cd example
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
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`.

View file

@ -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-strip -R .riscv.attributes 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

View file

@ -1,6 +1,6 @@
.globl _boot
_boot:
lui x2, 0x80004
jal main
li x2, 0x8000
call main
sbreak
j .

View file

@ -7,13 +7,23 @@ SECTIONS {
*(.text) /* Place all .text sections (code) here */
}
. = 0x1000;
.data : {
*(.data) /* Place all .data sections (initialized data) here */
}
. = 0x2000;
.bss : {
*(.bss) /* Place all .bss sections (uninitialized data) here */
}
. = 0x3000;
.stack : {
*(.stack)
}
/DISCARD/ : { *(.note.GNU-stack) } /* Discard stack section */
}

View file

@ -3,40 +3,63 @@
#include <cstdlib>
#include <cassert>
#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;
uint8_t memory[MEMORY_SIZE] = {0};
uint32_t program[] = {
0x00000093, // addi x1, x0, 0
0x00100113, // addi x2, x0, 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)
};
if (argc <= 1) {
std::cerr << "Expected filename of program to execute" << std::endl;
return 1;
}
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 {
eval(memory, MEMORY_SIZE);
} catch (const std::exception& e) {
std::cerr << "Emulator error: " << e.what() << std::endl;
assert(false && "Emulator threw an exception!");
return 1;
}
// Check the result
uint32_t result = 0;
std::memcpy(&result, memory + 256, sizeof(uint32_t)); // Memory address 0x100 (256 in decimal)
uint32_t *res = (uint32_t *)&memory[0x1000];
// Expected result is the sum of 0 to 9 = 55
std::cout << "result: " << std::dec << result << "\n";
assert(result == 45 && "Test failed: Sum of 1 to 10 is incorrect!");
std::cout << "result: " << *res << std::endl;
// If we reach here, the test passed
std::cout << "Test passed: Sum of 1 to 10 = " << result << std::endl;
return 0;
}

View file

@ -27,7 +27,7 @@ void eval(uint8_t* memory, size_t memory_size) {
while (pc < memory_size && running) {
uint32_t instr = fetch_instruction();
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";
pc += 4;
@ -170,18 +170,19 @@ void eval(uint8_t* memory, size_t memory_size) {
}
case 0x03: { // I-type (loads)
imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate
registers[rd] = 0;
if (funct3 == 0x00) { // LB
uint32_t addr = registers[rs1] + imm;
if (addr + 1 > memory_size) {
throw std::runtime_error("Memory access out of bounds");
}
registers[rd] = 0;
std::memcpy(&registers[rd], memory + addr, sizeof(uint8_t));
} else if (funct3 == 0x01) { // LH
uint32_t addr = registers[rs1] + imm;
if (addr + 2 > memory_size) {
throw std::runtime_error("Memory access out of bounds");
}
registers[rd] = 0;
std::memcpy(&registers[rd], memory + addr, sizeof(uint16_t));
} else if (funct3 == 0x2) { // LW
uint32_t addr = registers[rs1] + imm;
@ -195,7 +196,7 @@ void eval(uint8_t* memory, size_t memory_size) {
break;
}
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
if (funct3 == 0x0) { // SB
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 >> 20) & 0x1) << 11 | // imm[11]
((instr & 0xFF000)); // imm[19:12]
offset <<= 1; // Multiply by 2 (JAL offsets are word-aligned)
registers[rd] = pc; // Save return address
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)
registers[rd] = pc; // Save return address
pc = target - 4;
pc = target;
break;
}
case 0x37: { // LUI