Skip to content

Instantly share code, notes, and snippets.

@dlandahl
Last active January 16, 2021 18:05
Show Gist options
  • Save dlandahl/55b15c24424eeb05390fbb1d8e2fdaf8 to your computer and use it in GitHub Desktop.
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"
#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