Initial Chisel core implementation
This commit is contained in:
43
sim/verilator/Makefile
Normal file
43
sim/verilator/Makefile
Normal 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
111
sim/verilator/memory.cpp
Normal 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
40
sim/verilator/memory.h
Normal 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
101
sim/verilator/testbench.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user