Last active
January 16, 2021 18:05
-
-
Save dlandahl/55b15c24424eeb05390fbb1d8e2fdaf8 to your computer and use it in GitHub Desktop.
Interpreting the RISC instruction set from H.S. Warren Jr.'s book "Hacker's Delight"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdint> | |
#include <cassert> | |
#include <algorithm> | |
#include <iostream> | |
namespace DASM { | |
using u64 = uint64_t; | |
using u32 = uint32_t; | |
using u16 = uint16_t; | |
using u8 = uint8_t; | |
using i64 = int64_t; | |
using i32 = int32_t; | |
using i16 = int16_t; | |
using i8 = int8_t; | |
const i64 word_size = 4; | |
class Memory { | |
public: | |
Memory(i64 _size): size(_size) { | |
data = new u8[size]; | |
}; | |
~Memory() { | |
delete data; | |
}; | |
template<class Int> | |
Int& operator[](Int pos) { | |
return *reinterpret_cast<Int*>(&data[pos]); | |
} | |
void dump() { | |
std::cout << std::hex << " "; | |
for (i64 n = 0; n < size;) { | |
std::cout << (data[n] < 16 ? " 0" : " ") << (i32) data[n]; | |
if (!(++n % 16)) std::cout << std::endl; | |
if (!( n % 4)) std::cout << " "; | |
} | |
} | |
void write_data(u8* new_data, i64 pos, i64 count) { | |
assert(((void) "Not enough memory", pos + count < size)); | |
std::copy(new_data, new_data + count, data); | |
} | |
const i64 size; | |
private: | |
u8* data; | |
}; | |
class Register { | |
public: | |
void uwrite(u32 new_value) { | |
if (zero_reg) return; | |
value = new_value; | |
} | |
u32 uread() { | |
if (zero_reg) return 0; | |
return value; | |
} | |
void iwrite(i32 new_value) { | |
if (zero_reg) return; | |
value = *reinterpret_cast<u32*>(&new_value); | |
} | |
i32 iread() { | |
if (zero_reg) return 0; | |
return *reinterpret_cast<i32*>(&value); | |
} | |
void inc() { value++; } | |
void dec() { value--; } | |
void make_zero_reg() { zero_reg = true; } | |
private: | |
u32 value = 0; | |
bool zero_reg = false; | |
}; | |
class Processor; | |
using Instruction = void(*)(Processor*); | |
enum Opcode : u32 { | |
add, sub, mul, | |
div, divu, | |
rem, remu, | |
addi, muli, | |
addis, | |
// and, or, xor are C++ keywords | |
_and, _or, _xor, | |
andi, ori, xori, | |
beq, bne, blt, | |
ble, bgt, bge, | |
bt, bf, | |
cmpeq, cmpne, | |
cmplt, cmple, cmpltu, cmpleu, | |
cmpgt, cmpge, cmpgtu, cmpgeu, | |
cmpieq, cmpine, | |
cmpilt, cmpile, | |
cmpigt, cmpige, | |
cmpiequ, cmpineu, | |
cmpiltu, cmpileu, | |
cmpigtu, cmpigeu, | |
ldb, ldbu, ldh, ldhu, ldw, | |
stbu, sth, sthu, stw, | |
_not, | |
b, li, | |
mov, neg, | |
subi | |
}; | |
enum General_Register : u32 { | |
R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15, R_count | |
}; | |
void init_opcodes(); | |
class Processor { | |
public: | |
Register pc; | |
Register R[R_count]; | |
Memory memory; | |
const i64 clock_speed = 10e6; | |
bool debug = false; | |
Processor(): memory(256) { | |
R[0].make_zero_reg(); | |
pc.uwrite(-1); | |
if (!Processor::instructions) init_opcodes(); | |
} | |
static inline Instruction* instructions = nullptr; | |
static void add_instruction(i64 code, Instruction op) { | |
instructions[code] = { op }; | |
} | |
void fetch_and_exec() { | |
const u32 opcode = read_code_uword(); | |
assert(((void) "Invalid instruction", instructions[opcode])); | |
instructions[opcode](this); | |
} | |
i32 read_code_iword() { | |
pc.inc(); | |
return memory[(i32) pc.uread() * word_size]; | |
} | |
u32 read_code_uword() { | |
pc.inc(); | |
return memory[(u32) pc.uread() * word_size]; | |
} | |
void repr_state() { | |
std::cout << "Program Counter:\t" << std::dec << pc.uread() << "\n"; | |
std::cout << "Registers:" << "\n"; | |
for (i64 n = 0; n < R_count;) { | |
std::cout << "\tR" << n << ": " << (n<10 ? " " : "") << std::hex << "0x" << R[n].uread() << ", " << std::dec << R[n].uread(); | |
if (!(++n % 4)) std::cout << "\n"; | |
} | |
std::cout << "\n"; | |
std::cout << "Memory:" << "\n"; | |
memory.dump(); | |
} | |
}; | |
#define INSTRUCTION(mnemonic) Processor::add_instruction(mnemonic, [] (Processor* p) | |
void init_opcodes() { | |
Processor::instructions = new Instruction[256]; | |
#define ARITHMETIC(mnemonic, op, s) \ | |
INSTRUCTION(mnemonic) { \ | |
const u32 RT = p->read_code_##s##word(); \ | |
const u32 RA = p->read_code_##s##word(); \ | |
const u32 RB = p->read_code_##s##word(); \ | |
if (p->debug) std::cout << "Operation '" << #op << "' of register " << RA << " and " << RB << " Into " << RT << std::endl; \ | |
p->R[RT].s##write(p->R[RA].s##read() op p->R[RB].s##read()); \ | |
}) \ | |
#define IMMEDIATE(mnemonic, op, s) \ | |
INSTRUCTION(mnemonic) { \ | |
const u32 RT = p->read_code_##s##word(); \ | |
const u32 RA = p->read_code_##s##word(); \ | |
const s##16 I = p->read_code_##s##word(); \ | |
if (p->debug) std::cout << "Operation '" << #op << "' of register " << RA << " and immediate " << I << " Into " << RT << std::endl; \ | |
p->R[RT].s##write(p->R[RA].s##read() op I); \ | |
}) \ | |
ARITHMETIC(add, +, i); | |
ARITHMETIC(sub, -, i); | |
ARITHMETIC(mul, *, i); | |
ARITHMETIC(div, /, i); | |
ARITHMETIC(divu, /, u); | |
ARITHMETIC(rem, %, i); | |
ARITHMETIC(remu, %, u); | |
IMMEDIATE(addi, +, i); | |
IMMEDIATE(muli, *, i); | |
INSTRUCTION(addis) { | |
const u32 RT = p->read_code_iword(); | |
const u32 RA = p->read_code_iword(); | |
const i32 I = p->read_code_iword(); | |
p->R[RT].iwrite(p->R[RA].iread() + (I << 16)); | |
}); | |
#define BRANCH(mnemonic, cmp) \ | |
INSTRUCTION(mnemonic) { \ | |
const u32 RT = p->read_code_uword(); \ | |
const u32 target = p->read_code_uword() - 1; \ | |
if (p->R[RT].iread() cmp 0) { \ | |
p->pc.uwrite(target); \ | |
if (p->debug) std::cout << "Jumping to: " << target << std::endl; \ | |
} \ | |
}) \ | |
BRANCH(beq, ==); | |
BRANCH(bne, !=); | |
BRANCH(blt, <); | |
BRANCH(bgt, >); | |
BRANCH(ble, <=); | |
BRANCH(bge, >=); | |
BRANCH(bt, !=); | |
BRANCH(bf, ==); | |
ARITHMETIC(_and, &, i); | |
ARITHMETIC(_or, |, i); | |
ARITHMETIC(_xor, ^, i); | |
IMMEDIATE(andi, &, u); | |
IMMEDIATE(ori, |, u); | |
IMMEDIATE(xori, ^, u); | |
ARITHMETIC(cmpeq, ==, i); | |
ARITHMETIC(cmpne, !=, i); | |
ARITHMETIC(cmplt, <, i); | |
ARITHMETIC(cmple, <=, i); | |
ARITHMETIC(cmpgt, >, i); | |
ARITHMETIC(cmpge, >=, i); | |
ARITHMETIC(cmpltu, <, u); | |
ARITHMETIC(cmpleu, <=, u); | |
ARITHMETIC(cmpgtu, >, u); | |
ARITHMETIC(cmpgeu, >=, u); | |
IMMEDIATE(cmpieq, ==, i); | |
IMMEDIATE(cmpine, !=, i); | |
IMMEDIATE(cmpilt, <, i); | |
IMMEDIATE(cmpile, <=, i); | |
IMMEDIATE(cmpigt, >, i); | |
IMMEDIATE(cmpige, >=, i); | |
IMMEDIATE(cmpiequ, ==, u); | |
IMMEDIATE(cmpineu, !=, u); | |
IMMEDIATE(cmpiltu, <, u); | |
IMMEDIATE(cmpileu, <=, u); | |
IMMEDIATE(cmpigtu, >, u); | |
IMMEDIATE(cmpigeu, >=, u); | |
#define LOAD(mnemonic, size, s) \ | |
INSTRUCTION(mnemonic) { \ | |
const u32 RT = p->read_code_uword(); \ | |
const i16 d = p->read_code_iword(); \ | |
const u32 RA = p->read_code_uword(); \ | |
const u32 location = p->R[RA].uread() + d; \ | |
const s##size value = p->memory[(s##size) location]; \ | |
p->R[RT].s##write(value); \ | |
}) \ | |
LOAD(ldb, 8, i); | |
LOAD(ldbu, 8, u); | |
LOAD(ldh, 16, i); | |
LOAD(ldhu, 16, u); | |
LOAD(ldw, 32, i); | |
#define STORE(mnemonic, size, s) \ | |
INSTRUCTION(mnemonic) { \ | |
const u32 RS = p->read_code_uword(); \ | |
const i16 d = p->read_code_iword(); \ | |
const u32 RA = p->read_code_uword(); \ | |
const u32 location = p->R[RA].uread() + d; \ | |
if (p->debug) std::cout << "Writing " << p->R[RS].s##read() << " into location " << location << std::endl; \ | |
p->memory[(s##size) location] = p->R[RS].s##read(); \ | |
}) \ | |
STORE(stbu, 8, u); | |
STORE(sth, 16, i); | |
STORE(sthu, 16, u); | |
STORE(stw, 32, i); | |
INSTRUCTION(_not) { | |
const u32 RT = p->read_code_uword(); | |
const u32 RA = p->read_code_uword(); | |
p->R[RT].uwrite(~p->R[RA].uread()); | |
}); | |
INSTRUCTION(b) { | |
p->pc.uwrite(p->read_code_uword()); | |
}); | |
INSTRUCTION(li) { | |
const u32 RT = p->read_code_uword(); | |
const i32 I = p->read_code_iword(); | |
p->R[RT].uwrite(I); | |
}); | |
INSTRUCTION(mov) { | |
const u32 RT = p->read_code_uword(); | |
const u32 RA = p->read_code_uword(); | |
p->R[RT].iwrite(p->R[RA].iread()); | |
}); | |
INSTRUCTION(neg) { | |
const u32 RT = p->read_code_uword(); | |
const u32 RA = p->read_code_uword(); | |
p->R[RT].iwrite(-p->R[RA].iread()); | |
}); | |
IMMEDIATE(subi, -, i); | |
} | |
#undef INSTRUCTION | |
} // namespace DASM | |
using namespace DASM; | |
/* | |
Todo: | |
- Timing | |
- More instructions | |
- IO | |
*/ | |
static u32 fibonacci[] = { | |
/* Writes the fibonacci numbers into memory */ | |
li, R1, 1, | |
li, R2, 1, | |
li, R3, 0xa0, // Keeps track of the memory location | |
stw, R1, 0, R3, | |
add, R1, R1, R2, | |
stw, R2, 4, R3, | |
add, R2, R1, R2, | |
addi, R3, R3, 8, | |
b, 0x8 | |
}; | |
// The program compares two values and does some different | |
// arithmetic based on the result | |
static u32 cmp_test[] = { | |
/* Initialise some registers */ | |
li, R1, 12, | |
li, R2, 13, | |
li, R8, 12, | |
li, R9, 6, | |
cmpeq, R3, R1, R2, // test if R1 and R2 are equal, write the result to R3 | |
bne, R3, 0x17, // branch if they were the same (R3 doesn't equal zero) | |
sub, R7, R8, R9, // subtract R8 and R9 into R7 | |
/* this is the location 0x17 */ | |
add, R7, R8, R9, // sum R8 and R9 into R7 | |
}; | |
i32 main() { | |
Processor proc; | |
proc.memory.write_data((u8*) fibonacci, 0, sizeof(fibonacci)); | |
i64 run_for_cycles = 64; | |
while (run_for_cycles--) { | |
proc.fetch_and_exec(); | |
} | |
// This will dump all memory and register values on the | |
// screen so we can see what happened | |
proc.repr_state(); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment