Implement gdb single-stepping
This commit is contained in:
parent
0175ef0c80
commit
d0ff238cb0
3 changed files with 149 additions and 111 deletions
|
@ -1,5 +1,7 @@
|
||||||
#include "debug.hpp"
|
#include "debug.hpp"
|
||||||
|
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
GDBStub::GDBStub(VM &vm, int port)
|
GDBStub::GDBStub(VM &vm, int port)
|
||||||
: server_fd(-1), client_fd(-1), running(false), vm(vm) {
|
: server_fd(-1), client_fd(-1), running(false), vm(vm) {
|
||||||
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
@ -28,6 +30,12 @@ GDBStub::GDBStub(VM &vm, int port)
|
||||||
throw std::runtime_error("Failed to accept connection.");
|
throw std::runtime_error("Failed to accept connection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GDB protocol operates with small packets, and thus we need to disable
|
||||||
|
// the sending delay (Nagle's Algorithm). Otherwise gdb commands would have
|
||||||
|
// noticeable lag.
|
||||||
|
int flag = 1;
|
||||||
|
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
|
||||||
|
|
||||||
std::cout << "GDB connected!" << std::endl;
|
std::cout << "GDB connected!" << std::endl;
|
||||||
running = true;
|
running = true;
|
||||||
}
|
}
|
||||||
|
@ -135,10 +143,13 @@ void GDBStub::handle_packet(const std::string &packet) {
|
||||||
// Getting specific register
|
// Getting specific register
|
||||||
int regnum = std::stoi(packet.substr(1), nullptr, 16);
|
int regnum = std::stoi(packet.substr(1), nullptr, 16);
|
||||||
|
|
||||||
uint32_t reg_value = 0;
|
uint32_t reg_value = vm.read_register(regnum);
|
||||||
|
|
||||||
std::ostringstream response;
|
std::ostringstream response;
|
||||||
response << std::hex << std::setfill('0') << std::setw(8) << reg_value;
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
response << std::hex << std::setfill('0') << std::setw(2)
|
||||||
|
<< ((reg_value >> (i * 8)) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
send_packet(response.str());
|
send_packet(response.str());
|
||||||
break;
|
break;
|
||||||
|
@ -167,6 +178,7 @@ void GDBStub::handle_packet(const std::string &packet) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
|
vm.step();
|
||||||
send_packet("S05"); // TODO: step execution
|
send_packet("S05"); // TODO: step execution
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -203,7 +215,11 @@ bool GDBStub::parse_memory_request(const std::string &packet, size_t &addr,
|
||||||
std::string GDBStub::read_registers() {
|
std::string GDBStub::read_registers() {
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
for (int i = 0; i < 32; ++i) {
|
for (int i = 0; i < 32; ++i) {
|
||||||
out << std::hex << std::setfill('0') << std::setw(8) << vm.read_register(i);
|
uint32_t reg_value = vm.read_register(i);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
out << std::hex << std::setfill('0') << std::setw(2)
|
||||||
|
<< ((reg_value >> (i * 8)) & 0xFF);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
224
src/vm.cpp
224
src/vm.cpp
|
@ -2,22 +2,21 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
|
|
||||||
inline int32_t sign_extend(int32_t value, int bits) {
|
inline int32_t sign_extend(int32_t value, int bits) {
|
||||||
int32_t mask = 1 << (bits - 1);
|
int32_t mask = 1 << (bits - 1);
|
||||||
return (value ^ mask) - mask;
|
return (value ^ mask) - mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> load_program(const std::string& filename, size_t memory_size)
|
std::vector<uint8_t> load_program(const std::string& filename,
|
||||||
{
|
size_t memory_size) {
|
||||||
std::vector<uint8_t> memory(memory_size, 0);
|
std::vector<uint8_t> memory(memory_size, 0);
|
||||||
|
|
||||||
std::ifstream file(filename, std::ios::binary|std::ios::ate);
|
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||||
|
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
throw std::runtime_error("Failed to open file: " + filename);
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
@ -34,7 +33,8 @@ std::vector<uint8_t> load_program(const std::string& filename, size_t memory_s
|
||||||
file.read(reinterpret_cast<char*>(&memory[0]), file_size);
|
file.read(reinterpret_cast<char*>(&memory[0]), file_size);
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
throw std::runtime_error("Failed to read the complete program into memory.");
|
throw std::runtime_error(
|
||||||
|
"Failed to read the complete program into memory.");
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
@ -42,17 +42,19 @@ std::vector<uint8_t> load_program(const std::string& filename, size_t memory_s
|
||||||
return memory;
|
return memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
VM::VM(std::vector<uint8_t> memory)
|
VM::VM(std::vector<uint8_t> memory) : memory_(memory) {}
|
||||||
: memory_(memory) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> VM::read_memory(size_t start, size_t size) {
|
std::vector<uint8_t> VM::read_memory(size_t start, size_t size) {
|
||||||
|
if (start + size > memory_.size()) {
|
||||||
|
return std::vector<uint8_t>(size, 0);
|
||||||
|
}
|
||||||
return std::vector<uint8_t>(memory_.begin() + start,
|
return std::vector<uint8_t>(memory_.begin() + start,
|
||||||
memory_.begin() + start + size);
|
memory_.begin() + start + size);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t VM::read_register(size_t regnum) {
|
uint32_t VM::read_register(size_t regnum) {
|
||||||
|
if (regnum == 32) return pc;
|
||||||
|
|
||||||
if (regnum >= NUM_REGISTERS) {
|
if (regnum >= NUM_REGISTERS) {
|
||||||
throw std::runtime_error("Register out of range");
|
throw std::runtime_error("Register out of range");
|
||||||
}
|
}
|
||||||
|
@ -60,55 +62,55 @@ uint32_t VM::read_register(size_t regnum) {
|
||||||
return registers[regnum];
|
return registers[regnum];
|
||||||
}
|
}
|
||||||
|
|
||||||
void VM::eval() {
|
void VM::step() {
|
||||||
size_t memory_size = memory_.size();
|
size_t memory_size = memory_.size();
|
||||||
uint8_t *memory = &memory_[0];
|
uint8_t* memory = &memory_[0];
|
||||||
|
|
||||||
bool running = true;
|
uint32_t instr = *(uint32_t*)&memory[pc];
|
||||||
while (pc < memory_size && running) {
|
// std::cout << "pc: " << std::hex << pc << std::dec << "\n";
|
||||||
uint32_t instr = *(uint32_t*)&memory[pc];
|
// std::cout << "instr: " << std::hex << instr << "\n";
|
||||||
if (instr == 0) break;
|
pc += 4;
|
||||||
//std::cout << "pc: " << std::hex << pc << std::dec << "\n";
|
|
||||||
// std::cout << "instr: " << std::hex << instr << "\n";
|
|
||||||
pc += 4;
|
|
||||||
|
|
||||||
// Decode instruction
|
// Decode instruction
|
||||||
uint32_t opcode = instr & 0x7F;
|
uint32_t opcode = instr & 0x7F;
|
||||||
uint32_t rd = (instr >> 7) & 0x1F;
|
uint32_t rd = (instr >> 7) & 0x1F;
|
||||||
uint32_t funct3 = (instr >> 12) & 0x7;
|
uint32_t funct3 = (instr >> 12) & 0x7;
|
||||||
uint32_t rs1 = (instr >> 15) & 0x1F;
|
uint32_t rs1 = (instr >> 15) & 0x1F;
|
||||||
uint32_t rs2 = (instr >> 20) & 0x1F;
|
uint32_t rs2 = (instr >> 20) & 0x1F;
|
||||||
uint32_t funct7 = (instr >> 25);
|
uint32_t funct7 = (instr >> 25);
|
||||||
int32_t imm;
|
int32_t imm;
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case 0x33: { // R-type
|
case 0x33: { // R-type
|
||||||
if (funct7 == 0x00) {
|
if (funct7 == 0x00) {
|
||||||
if (funct3 == 0x0) { // ADD
|
if (funct3 == 0x0) { // ADD
|
||||||
registers[rd] = registers[rs1] + registers[rs2];
|
registers[rd] = registers[rs1] + registers[rs2];
|
||||||
} else if (funct3 == 0x04) { // XOR
|
} else if (funct3 == 0x04) { // XOR
|
||||||
registers[rd] = registers[rs1] ^ registers[rs2];
|
registers[rd] = registers[rs1] ^ registers[rs2];
|
||||||
} else if (funct3 == 0x06) { // OR
|
} else if (funct3 == 0x06) { // OR
|
||||||
registers[rd] = registers[rs1] | registers[rs2];
|
registers[rd] = registers[rs1] | registers[rs2];
|
||||||
} else if (funct3 == 0x07) { // AND
|
} else if (funct3 == 0x07) { // AND
|
||||||
registers[rd] = registers[rs1] & registers[rs2];
|
registers[rd] = registers[rs1] & registers[rs2];
|
||||||
} else if (funct3 == 0x01) { // SLL
|
} else if (funct3 == 0x01) { // SLL
|
||||||
registers[rd] = registers[rs1] << registers[rs2];
|
registers[rd] = registers[rs1] << registers[rs2];
|
||||||
} else if (funct3 == 0x05) { // SRL
|
} else if (funct3 == 0x05) { // SRL
|
||||||
uint32_t value = registers[rs1];
|
uint32_t value = registers[rs1];
|
||||||
uint32_t shift_amount = registers[rs2] & 0x1F;
|
uint32_t shift_amount = registers[rs2] & 0x1F;
|
||||||
registers[rd] = value << shift_amount;
|
registers[rd] = value << shift_amount;
|
||||||
} else if (funct3 == 0x02) { // SLT
|
} else if (funct3 == 0x02) { // SLT
|
||||||
registers[rd] = (static_cast<int32_t>(registers[rs1]) < static_cast<int32_t>(registers[rs2]))?0:1;
|
registers[rd] = (static_cast<int32_t>(registers[rs1]) <
|
||||||
} else if (funct3 == 0x03) { // SLTU
|
static_cast<int32_t>(registers[rs2]))
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
} else if (funct3 == 0x03) { // SLTU
|
||||||
registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0;
|
registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0;
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("Unknown R-type instruction");
|
throw std::runtime_error("Unknown R-type instruction");
|
||||||
}
|
}
|
||||||
} else if (funct7 == 0x20) {
|
} else if (funct7 == 0x20) {
|
||||||
if (funct3 == 0x0) { // SUB
|
if (funct3 == 0x0) { // SUB
|
||||||
registers[rd] = registers[rs1] - registers[rs2];
|
registers[rd] = registers[rs1] - registers[rs2];
|
||||||
} else if (funct3 == 0x05) { // SRA
|
} else if (funct3 == 0x05) { // SRA
|
||||||
// Only the lower 5 bits are used for shift
|
// Only the lower 5 bits are used for shift
|
||||||
int32_t value = static_cast<int32_t>(registers[rs1]);
|
int32_t value = static_cast<int32_t>(registers[rs1]);
|
||||||
int32_t shift_amount = registers[rs2] & 0x1F;
|
int32_t shift_amount = registers[rs2] & 0x1F;
|
||||||
|
@ -117,47 +119,51 @@ void VM::eval() {
|
||||||
throw std::runtime_error("Unknown R-type instruction");
|
throw std::runtime_error("Unknown R-type instruction");
|
||||||
}
|
}
|
||||||
} else if (funct7 == 0x01) {
|
} else if (funct7 == 0x01) {
|
||||||
if (funct3 == 0x0) { // MUL
|
if (funct3 == 0x0) { // MUL
|
||||||
int64_t result = static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
|
int64_t result =
|
||||||
static_cast<int64_t>(static_cast<int32_t>(registers[rs2]));
|
static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
|
||||||
|
static_cast<int64_t>(static_cast<int32_t>(registers[rs2]));
|
||||||
registers[rd] = static_cast<uint32_t>(result);
|
registers[rd] = static_cast<uint32_t>(result);
|
||||||
} else if (funct3 == 0x1) { // MULH
|
} else if (funct3 == 0x1) { // MULH
|
||||||
int64_t result = static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
|
int64_t result =
|
||||||
static_cast<int64_t>(static_cast<int32_t>(registers[rs2]));
|
static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
|
||||||
|
static_cast<int64_t>(static_cast<int32_t>(registers[rs2]));
|
||||||
registers[rd] = static_cast<uint32_t>(result >> 32);
|
registers[rd] = static_cast<uint32_t>(result >> 32);
|
||||||
} else if (funct3 == 0x2) { // MULSU
|
} else if (funct3 == 0x2) { // MULSU
|
||||||
int64_t result = static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
|
int64_t result =
|
||||||
static_cast<uint64_t>(registers[rs2]);
|
static_cast<int64_t>(static_cast<int32_t>(registers[rs1])) *
|
||||||
|
static_cast<uint64_t>(registers[rs2]);
|
||||||
registers[rd] = static_cast<uint32_t>(result >> 32);
|
registers[rd] = static_cast<uint32_t>(result >> 32);
|
||||||
} else if (funct3 == 0x3) { // MULU
|
} else if (funct3 == 0x3) { // MULU
|
||||||
uint64_t result = static_cast<uint64_t>(registers[rs1]) *
|
uint64_t result = static_cast<uint64_t>(registers[rs1]) *
|
||||||
static_cast<uint64_t>(registers[rs2]);
|
static_cast<uint64_t>(registers[rs2]);
|
||||||
registers[rd] = static_cast<uint32_t>(result >> 32); // Upper 32 bits
|
registers[rd] = static_cast<uint32_t>(result >> 32); // Upper 32 bits
|
||||||
} else if (funct3 == 0x4) { // DIV
|
} else if (funct3 == 0x4) { // DIV
|
||||||
int32_t dividend = static_cast<int32_t>(registers[rs1]);
|
int32_t dividend = static_cast<int32_t>(registers[rs1]);
|
||||||
int32_t divisor = static_cast<int32_t>(registers[rs2]);
|
int32_t divisor = static_cast<int32_t>(registers[rs2]);
|
||||||
if (divisor == 0) {
|
if (divisor == 0) {
|
||||||
registers[rd] = -1; // Division by zero result
|
registers[rd] = -1; // Division by zero result
|
||||||
} else if (dividend == INT32_MIN && divisor == -1) {
|
} else if (dividend == INT32_MIN && divisor == -1) {
|
||||||
registers[rd] = dividend; // Overflow case
|
registers[rd] = dividend; // Overflow case
|
||||||
} else {
|
} else {
|
||||||
registers[rd] = dividend / divisor;
|
registers[rd] = dividend / divisor;
|
||||||
}
|
}
|
||||||
} else if (funct3 == 0x5) { // DIVU
|
} else if (funct3 == 0x5) { // DIVU
|
||||||
uint32_t dividend = registers[rs1];
|
uint32_t dividend = registers[rs1];
|
||||||
uint32_t divisor = registers[rs2];
|
uint32_t divisor = registers[rs2];
|
||||||
registers[rd] = (divisor == 0) ? UINT32_MAX : dividend / divisor;
|
registers[rd] = (divisor == 0) ? UINT32_MAX : dividend / divisor;
|
||||||
} else if (funct3 == 0x6) { // REM
|
} else if (funct3 == 0x6) { // REM
|
||||||
int32_t dividend = static_cast<int32_t>(registers[rs1]);
|
int32_t dividend = static_cast<int32_t>(registers[rs1]);
|
||||||
int32_t divisor = static_cast<int32_t>(registers[rs2]);
|
int32_t divisor = static_cast<int32_t>(registers[rs2]);
|
||||||
if (divisor == 0) {
|
if (divisor == 0) {
|
||||||
registers[rd] = dividend; // Remainder with zero divisor is the dividend
|
registers[rd] =
|
||||||
|
dividend; // Remainder with zero divisor is the dividend
|
||||||
} else if (dividend == INT32_MIN && divisor == -1) {
|
} else if (dividend == INT32_MIN && divisor == -1) {
|
||||||
registers[rd] = 0; // Overflow case
|
registers[rd] = 0; // Overflow case
|
||||||
} else {
|
} else {
|
||||||
registers[rd] = dividend % divisor;
|
registers[rd] = dividend % divisor;
|
||||||
}
|
}
|
||||||
} else if (funct3 == 0x7) { // REMU
|
} else if (funct3 == 0x7) { // REMU
|
||||||
uint32_t dividend = registers[rs1];
|
uint32_t dividend = registers[rs1];
|
||||||
uint32_t divisor = registers[rs2];
|
uint32_t divisor = registers[rs2];
|
||||||
registers[rd] = (divisor == 0) ? dividend : dividend % divisor;
|
registers[rd] = (divisor == 0) ? dividend : dividend % divisor;
|
||||||
|
@ -169,63 +175,63 @@ void VM::eval() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x13: { // I-type (ADDI)
|
case 0x13: { // I-type (ADDI)
|
||||||
imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate
|
imm = sign_extend(instr >> 20, 12); // Extract 12-bit immediate
|
||||||
if (funct3 == 0x0) { // ADDI
|
if (funct3 == 0x0) { // ADDI
|
||||||
registers[rd] = registers[rs1] + imm;
|
registers[rd] = registers[rs1] + imm;
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("Unknown I-type instruction");
|
throw std::runtime_error("Unknown I-type instruction");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x63: { // B-type (branches)
|
case 0x63: { // B-type (branches)
|
||||||
imm = ((instr >> 7) & 0x1E) | ((instr >> 20) & 0x7E0) |
|
imm = ((instr >> 7) & 0x1E) | ((instr >> 20) & 0x7E0) |
|
||||||
((instr >> 19) & 0x800) | ((instr >> 31) << 12);
|
((instr >> 19) & 0x800) | ((instr >> 31) << 12);
|
||||||
imm = sign_extend(imm, 13); // Sign-extend 13-bit immediate
|
imm = sign_extend(imm, 13); // Sign-extend 13-bit immediate
|
||||||
if (funct3 == 0x0) { // BEQ
|
if (funct3 == 0x0) { // BEQ
|
||||||
if (registers[rs1] == registers[rs2]) {
|
if (registers[rs1] == registers[rs2]) {
|
||||||
pc += imm - 4; // Offset PC (adjust for pre-increment)
|
pc += imm - 4; // Offset PC (adjust for pre-increment)
|
||||||
}
|
}
|
||||||
} else if (funct3 == 0x1) { // BNE
|
} else if (funct3 == 0x1) { // BNE
|
||||||
if (registers[rs1] != registers[rs2]) {
|
if (registers[rs1] != registers[rs2]) {
|
||||||
pc += imm - 4; // Offset PC
|
pc += imm - 4; // Offset PC
|
||||||
}
|
}
|
||||||
} else if (funct3 == 0x4) { // BLT
|
} else if (funct3 == 0x4) { // BLT
|
||||||
if (static_cast<int32_t>(registers[rs1]) <
|
if (static_cast<int32_t>(registers[rs1]) <
|
||||||
static_cast<int32_t>(registers[rs2])) {
|
static_cast<int32_t>(registers[rs2])) {
|
||||||
pc += imm - 4;
|
pc += imm - 4;
|
||||||
}
|
}
|
||||||
} else if (funct3 == 0x5) { // BGE
|
} else if (funct3 == 0x5) { // BGE
|
||||||
if (static_cast<int32_t>(registers[rs1]) >=
|
if (static_cast<int32_t>(registers[rs1]) >=
|
||||||
static_cast<int32_t>(registers[rs2])) {
|
static_cast<int32_t>(registers[rs2])) {
|
||||||
pc += imm - 4;
|
pc += imm - 4;
|
||||||
}
|
}
|
||||||
} else if (funct3 == 0x6) { // BLTU
|
} else if (funct3 == 0x6) { // BLTU
|
||||||
if (registers[rs1] < registers[rs2]) pc += imm - 4;
|
if (registers[rs1] < registers[rs2]) pc += imm - 4;
|
||||||
} else if (funct3 == 0x7) { // BGEU
|
} else if (funct3 == 0x7) { // BGEU
|
||||||
if (registers[rs1] >= registers[rs2]) pc += imm - 4;
|
if (registers[rs1] >= registers[rs2]) pc += imm - 4;
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("Unknown B-type instruction");
|
throw std::runtime_error("Unknown B-type instruction");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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
|
||||||
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;
|
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;
|
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;
|
||||||
if (addr + 4 > memory_size) {
|
if (addr + 4 > memory_size) {
|
||||||
throw std::runtime_error("Memory access out of bounds");
|
throw std::runtime_error("Memory access out of bounds");
|
||||||
|
@ -236,22 +242,22 @@ void VM::eval() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x23: { // S-type (SW)
|
case 0x23: { // S-type (SW)
|
||||||
imm = ((instr >> 7) & 0x1F) | (((instr >> 25) & 0x7F) << 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;
|
||||||
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");
|
||||||
}
|
}
|
||||||
std::memcpy(memory + addr, ®isters[rs2], sizeof(uint8_t));
|
std::memcpy(memory + addr, ®isters[rs2], sizeof(uint8_t));
|
||||||
} else if (funct3 == 0x1) { // SH
|
} else if (funct3 == 0x1) { // SH
|
||||||
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");
|
||||||
}
|
}
|
||||||
std::memcpy(memory + addr, ®isters[rs2], sizeof(uint16_t));
|
std::memcpy(memory + addr, ®isters[rs2], sizeof(uint16_t));
|
||||||
} else if (funct3 == 0x2) { // SW
|
} else if (funct3 == 0x2) { // SW
|
||||||
uint32_t addr = registers[rs1] + imm;
|
uint32_t addr = registers[rs1] + imm;
|
||||||
if (addr + 4 > memory_size) {
|
if (addr + 4 > memory_size) {
|
||||||
throw std::runtime_error("Memory access out of bounds");
|
throw std::runtime_error("Memory access out of bounds");
|
||||||
|
@ -262,40 +268,52 @@ void VM::eval() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x6F: { // JAL
|
case 0x6F: { // JAL
|
||||||
int32_t offset = ((instr & 0x80000000) ? 0xFFF00000 : 0) | // Sign-extension for imm[20]
|
int32_t offset =
|
||||||
((instr >> 21) & 0x3FF) << 1 | // imm[10:1]
|
((instr & 0x80000000) ? 0xFFF00000
|
||||||
((instr >> 20) & 0x1) << 11 | // imm[11]
|
: 0) | // Sign-extension for imm[20]
|
||||||
((instr & 0xFF000)); // imm[19:12]
|
((instr >> 21) & 0x3FF) << 1 | // imm[10:1]
|
||||||
|
((instr >> 20) & 0x1) << 11 | // imm[11]
|
||||||
|
((instr & 0xFF000)); // imm[19:12]
|
||||||
|
|
||||||
registers[rd] = pc; // Save return address
|
registers[rd] = pc; // Save return address
|
||||||
pc += offset - 4;
|
pc += offset - 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x67: { // JALR
|
case 0x67: { // JALR
|
||||||
int32_t offset = (instr >> 20); // Sign-extended 12-bit immediate
|
int32_t offset = (instr >> 20); // Sign-extended 12-bit immediate
|
||||||
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;
|
pc = target;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x37: { // LUI
|
case 0x37: { // LUI
|
||||||
uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate
|
uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate
|
||||||
registers[rd] = imm << 12; // Shift the immediate to the upper 20 bits of the register
|
registers[rd] =
|
||||||
|
imm
|
||||||
|
<< 12; // Shift the immediate to the upper 20 bits of the register
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x17: { // AUIPC
|
case 0x17: { // AUIPC
|
||||||
uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate
|
uint32_t imm = (instr >> 12) & 0xFFFFF; // Extract 20-bit immediate
|
||||||
registers[rd] = pc + (imm << 12); // Add the immediate (shifted left) to the current PC
|
registers[rd] =
|
||||||
|
pc +
|
||||||
|
(imm << 12); // Add the immediate (shifted left) to the current PC
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x73: { // EBREAK
|
case 0x73: { // EBREAK
|
||||||
running = false; // Flag to stop the emulator
|
running = false; // Flag to stop the emulator
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Unknown opcode");
|
throw std::runtime_error("Unknown opcode");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VM::eval() {
|
||||||
|
while (running) {
|
||||||
|
step();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/vm.hpp
14
src/vm.hpp
|
@ -1,26 +1,30 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
const int NUM_REGISTERS = 32; // Standard RISC-V has 32 registers
|
const int NUM_REGISTERS = 32; // Standard RISC-V has 32 registers
|
||||||
|
|
||||||
std::vector<uint8_t> load_program(const std::string& filename, size_t memory_size);
|
std::vector<uint8_t> load_program(const std::string& filename,
|
||||||
|
size_t memory_size);
|
||||||
|
|
||||||
class VM {
|
class VM {
|
||||||
public:
|
public:
|
||||||
VM(std::vector<uint8_t> memory);
|
VM(std::vector<uint8_t> memory);
|
||||||
|
|
||||||
|
void step();
|
||||||
void eval();
|
void eval();
|
||||||
|
|
||||||
std::vector<uint8_t> read_memory(size_t start, size_t size);
|
std::vector<uint8_t> read_memory(size_t start, size_t size);
|
||||||
uint32_t read_register(size_t regnum);
|
uint32_t read_register(size_t regnum);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<uint8_t> memory_;
|
std::vector<uint8_t> memory_;
|
||||||
|
|
||||||
uint32_t registers[NUM_REGISTERS] = {0};
|
uint32_t registers[NUM_REGISTERS] = {0};
|
||||||
uint32_t pc = 0;
|
uint32_t pc = 0;
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue