Initial Chisel core implementation

This commit is contained in:
abnerhexu
2026-06-26 08:20:25 +00:00
commit 502803c37f
47 changed files with 2342 additions and 0 deletions

43
sim/verilator/Makefile Normal file
View File

@@ -0,0 +1,43 @@
VERILATOR = verilator
VERILATOR_FLAGS = --cc --exe --build -Wall --trace -Wno-fatal
VERILATOR_FLAGS += -CFLAGS "-std=c++14 -O2"
SBT = env SBT_OPTS="-Dsbt.boot.directory=/tmp/sbt-boot -Dsbt.ivy.home=/tmp/sbt-ivy" COURSIER_CACHE=/tmp/coursier-cache sbt
CHISEL_DIR = ../..
GENERATED_DIR = $(CHISEL_DIR)/generated
SRC_FILES = testbench.cpp memory.cpp
VERILOG_FILES = $(GENERATED_DIR)/Core.sv
TARGET = obj_dir/VCore
.PHONY: all verilog compile run clean
all: compile
verilog:
@echo "Generating Verilog from Chisel..."
cd $(CHISEL_DIR) && $(SBT) "runMain Core"
compile: verilog
@echo "Compiling with Verilator..."
$(VERILATOR) $(VERILATOR_FLAGS) \
-I$(GENERATED_DIR) \
--top-module Core \
-o VCore \
$(VERILOG_FILES) $(SRC_FILES)
run: compile
@if [ -z "$(TEST)" ]; then \
echo "Usage: make run TEST=<path/to/test>"; \
exit 1; \
fi
./$(TARGET) $(TEST)
test-simple: compile
@echo "Running simple test..."
./$(TARGET) ../../riscv-tests/isa/rv64ui-p-simple
clean:
rm -rf obj_dir
rm -rf $(GENERATED_DIR)

111
sim/verilator/memory.cpp Normal file
View File

@@ -0,0 +1,111 @@
#include "memory.h"
#include <cstring>
#include <cstdio>
#include <elf.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
Memory::Memory() : base_addr(MEM_BASE), entry_point(MEM_BASE) {
mem.resize(MEM_SIZE, 0);
}
uint64_t Memory::to_offset(uint64_t addr) {
if (addr < base_addr || addr >= base_addr + MEM_SIZE) {
fprintf(stderr, "Memory access out of bounds: 0x%lx\n", addr);
return 0;
}
return addr - base_addr;
}
uint8_t Memory::read8(uint64_t addr) {
return mem[to_offset(addr)];
}
uint16_t Memory::read16(uint64_t addr) {
uint64_t off = to_offset(addr);
return mem[off] | (mem[off + 1] << 8);
}
uint32_t Memory::read32(uint64_t addr) {
uint64_t off = to_offset(addr);
return mem[off] | (mem[off + 1] << 8) | (mem[off + 2] << 16) | (mem[off + 3] << 24);
}
uint64_t Memory::read64(uint64_t addr) {
uint64_t off = to_offset(addr);
uint64_t lo = read32(addr);
uint64_t hi = read32(addr + 4);
return lo | (hi << 32);
}
void Memory::write8(uint64_t addr, uint8_t data) {
mem[to_offset(addr)] = data;
}
void Memory::write16(uint64_t addr, uint16_t data) {
uint64_t off = to_offset(addr);
mem[off] = data & 0xFF;
mem[off + 1] = (data >> 8) & 0xFF;
}
void Memory::write32(uint64_t addr, uint32_t data) {
uint64_t off = to_offset(addr);
mem[off] = data & 0xFF;
mem[off + 1] = (data >> 8) & 0xFF;
mem[off + 2] = (data >> 16) & 0xFF;
mem[off + 3] = (data >> 24) & 0xFF;
}
void Memory::write64(uint64_t addr, uint64_t data) {
write32(addr, data & 0xFFFFFFFF);
write32(addr + 4, (data >> 32) & 0xFFFFFFFF);
}
bool Memory::load_elf(const std::string& filename) {
int fd = open(filename.c_str(), O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open ELF file: %s\n", filename.c_str());
return false;
}
Elf64_Ehdr ehdr;
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) {
fprintf(stderr, "Failed to read ELF header\n");
close(fd);
return false;
}
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) {
fprintf(stderr, "Not a valid ELF file\n");
close(fd);
return false;
}
entry_point = ehdr.e_entry;
// Load program headers
for (int i = 0; i < ehdr.e_phnum; i++) {
Elf64_Phdr phdr;
lseek(fd, ehdr.e_phoff + i * sizeof(phdr), SEEK_SET);
if (read(fd, &phdr, sizeof(phdr)) != sizeof(phdr)) {
fprintf(stderr, "Failed to read program header %d\n", i);
continue;
}
if (phdr.p_type == PT_LOAD) {
uint64_t offset = to_offset(phdr.p_paddr);
lseek(fd, phdr.p_offset, SEEK_SET);
if (read(fd, &mem[offset], phdr.p_filesz) != (ssize_t)phdr.p_filesz) {
fprintf(stderr, "Failed to load segment %d\n", i);
close(fd);
return false;
}
printf("Loaded segment: paddr=0x%lx size=%lu\n", phdr.p_paddr, phdr.p_filesz);
}
}
close(fd);
printf("ELF loaded: entry=0x%lx\n", entry_point);
return true;
}

40
sim/verilator/memory.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef MEMORY_H
#define MEMORY_H
#include <cstdint>
#include <vector>
#include <string>
#define TOHOST_ADDR 0x80001000ULL
#define FROMHOST_ADDR 0x80001040ULL
#define MEM_BASE 0x80000000ULL
#define MEM_SIZE (64 * 1024 * 1024) // 64MB
class Memory {
private:
std::vector<uint8_t> mem;
uint64_t base_addr;
public:
Memory();
bool load_elf(const std::string& filename);
uint8_t read8(uint64_t addr);
uint16_t read16(uint64_t addr);
uint32_t read32(uint64_t addr);
uint64_t read64(uint64_t addr);
void write8(uint64_t addr, uint8_t data);
void write16(uint64_t addr, uint16_t data);
void write32(uint64_t addr, uint32_t data);
void write64(uint64_t addr, uint64_t data);
uint64_t get_entry_point() const { return entry_point; }
private:
uint64_t entry_point;
uint64_t to_offset(uint64_t addr);
};
#endif

101
sim/verilator/testbench.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include <verilated.h>
#include "VCore.h"
#include "memory.h"
#include <cstdio>
#include <cstdlib>
#define MAX_CYCLES 100000
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <test.elf>\n", argv[0]);
return 1;
}
Verilated::commandArgs(argc, argv);
VCore* core = new VCore;
Memory* mem = new Memory();
if (!mem->load_elf(argv[1])) {
fprintf(stderr, "Failed to load test binary\n");
return 1;
}
// Reset
core->reset = 1;
core->clock = 0;
core->eval();
core->clock = 1;
core->eval();
core->clock = 0;
core->reset = 0;
core->eval();
uint64_t cycle = 0;
bool test_done = false;
int exit_code = 0;
while (cycle < MAX_CYCLES && !test_done) {
// Handle instruction memory interface
if (core->io_imem_req_valid) {
uint64_t pc = core->io_imem_req_bits;
core->io_imem_resp_valid = 1;
core->io_imem_resp_bits_0 = mem->read32(pc);
core->io_imem_resp_bits_1 = mem->read32(pc + 4);
} else {
core->io_imem_resp_valid = 0;
}
// Handle data memory interface
if (core->io_dmem_req_valid) {
uint64_t addr = core->io_dmem_req_bits_addr;
// Check for tohost write
if (core->io_dmem_req_bits_isStore && addr == TOHOST_ADDR) {
uint64_t tohost = core->io_dmem_req_bits_data;
if (tohost == 1) {
printf("[%lu] TEST PASSED\n", cycle);
test_done = true;
exit_code = 0;
} else if (tohost & 1) {
printf("[%lu] TEST FAILED: error code %lu\n", cycle, tohost >> 1);
test_done = true;
exit_code = 1;
}
}
if (core->io_dmem_req_bits_isStore) {
switch (core->io_dmem_req_bits_size) {
case 0: mem->write8(addr, core->io_dmem_req_bits_data & 0xff); break;
case 1: mem->write16(addr, core->io_dmem_req_bits_data & 0xffff); break;
case 2: mem->write32(addr, core->io_dmem_req_bits_data & 0xffffffff); break;
default: mem->write64(addr, core->io_dmem_req_bits_data); break;
}
core->io_dmem_resp_valid = 0;
} else {
core->io_dmem_resp_bits = mem->read64(addr);
core->io_dmem_resp_valid = 1;
}
} else {
core->io_dmem_resp_valid = 0;
}
// Clock cycle
core->clock = 0;
core->eval();
core->clock = 1;
core->eval();
cycle++;
}
if (!test_done) {
printf("[%lu] TEST TIMEOUT\n", cycle);
exit_code = 2;
}
delete core;
delete mem;
return exit_code;
}