494 lines
14 KiB
D
494 lines
14 KiB
D
module riscd.cpu;
|
|
|
|
import riscd.memory;
|
|
import std.traits;
|
|
|
|
|
|
private enum OPCODE
|
|
{
|
|
// Referenced from:
|
|
// The RISC-V Instruction Set Manual Volume I: Unprivileged Architecture
|
|
// Chapter 34. RV32/64G Instruction Set Listings
|
|
|
|
// 00
|
|
LOAD = 0b0000011,
|
|
LOAD_FP = 0b0000111,
|
|
CUSTOM0 = 0b0001011,
|
|
MISCMEM = 0b0001111,
|
|
OPIMM = 0b0010011,
|
|
AUIPC = 0b0010111,
|
|
OPIMM32 = 0b0011011,
|
|
|
|
// 01
|
|
STORE = 0b0100011,
|
|
STORE_FP = 0b0100111,
|
|
CUSTOM1 = 0b0101011,
|
|
AMO = 0b0101111,
|
|
OP = 0b0110011,
|
|
LUI = 0b0110111,
|
|
OP32 = 0b0111011,
|
|
|
|
// 10
|
|
MADD = 0b1000011,
|
|
MSUB = 0b1000111,
|
|
NMSUB = 0b1001011,
|
|
NMADD = 0b1001111,
|
|
OP_FP = 0b1010011,
|
|
OP_V = 0b1010111,
|
|
CUSTOM2 = 0b1011011,
|
|
|
|
// 11
|
|
BRANCH = 0b1100011,
|
|
JALR = 0b1100111,
|
|
_RESERVED = 0b1101011,
|
|
JAL = 0b1101111,
|
|
SYSTEM = 0b1110011,
|
|
OP_VE = 0b1110111,
|
|
CUSTOM3 = 0b1111011,
|
|
}
|
|
|
|
|
|
enum Cause
|
|
{
|
|
MISALIGNED_FETCH = 0x00,
|
|
FETCH_ACCESS = 0x01,
|
|
ILLEGAL_INSTRUCTION = 0x02,
|
|
BREAKPOINT = 0x03,
|
|
MISALIGNED_LOAD = 0x04,
|
|
LOAD_ACCESS = 0x05,
|
|
MISALIGNED_STORE = 0x06,
|
|
STORE_ACCESS = 0x07,
|
|
USER_ECALL = 0x08,
|
|
SUPERVISOR_ECALL = 0x09,
|
|
VIRTUAL_SUPERVISOR_ECALL = 0x0A,
|
|
MACHINE_ECALL = 0x0B,
|
|
FETCH_PAGE_FAULT = 0x0C,
|
|
LOAD_PAGE_FAULT = 0x0D,
|
|
STORE_PAGE_FAULT = 0x0F,
|
|
DOUBLE_TRAP = 0x10,
|
|
SOFTWARE_CHECK_FAULT = 0x12,
|
|
HARDWARE_ERROR_FAULT = 0x13,
|
|
FETCH_GUEST_PAGE_FAULT = 0x14,
|
|
LOAD_GUEST_PAGE_FAULT = 0x15,
|
|
VIRTUAL_INSTRUCTION = 0x16,
|
|
STORE_GUEST_PAGE_FAULT = 0x17,
|
|
}
|
|
|
|
|
|
private T signExtend(T)(T num, uint bits) @safe @nogc pure nothrow
|
|
{
|
|
alias U = Unsigned!T;
|
|
U one = 1;
|
|
bool ngt = (num & (one << (bits-1))) != 0;
|
|
if (ngt)
|
|
return (U.max >> bits << bits) | num;
|
|
else
|
|
return (U.max >> (U.sizeof*8 - bits)) & num;
|
|
}
|
|
static assert(signExtend!int(0b0110, 4) == 6);
|
|
static assert(signExtend!int(0b1010, 4) == -6);
|
|
|
|
|
|
class CPU
|
|
{
|
|
@safe:
|
|
public:
|
|
alias WORD = uint;
|
|
enum XLEN = WORD.sizeof;
|
|
|
|
this(Memory memory)
|
|
{
|
|
this.memory = memory;
|
|
}
|
|
|
|
void tick()
|
|
{
|
|
static assert(WORD.sizeof == 4, "Only 32 bit architecture is supported");
|
|
|
|
// Zero register
|
|
x[0] = 0;
|
|
|
|
// Jumped internal flag
|
|
jumped = false;
|
|
|
|
// Common variables
|
|
bool mems; // Memory read/write status
|
|
|
|
// Fetch instruction
|
|
WORD inst = fetch(pc);
|
|
|
|
// Ensure instruction is 32bit
|
|
if (!((inst & 0b11) == 0b11 && (inst & 0b11111) != 0b11111))
|
|
{
|
|
// It is not!
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
}
|
|
if (pc & 3)
|
|
exception(Cause.MISALIGNED_FETCH);
|
|
|
|
// Decode instruction
|
|
ubyte opcode = inst & 0x7F;
|
|
ubyte rd = (inst >> 7) & 0x1F;
|
|
ubyte funct3 = (inst >> 12) & 0x07;
|
|
ubyte funct7 = (inst >> 25) & 0x7F;
|
|
ubyte rs1 = (inst >> 15) & 0x1F;
|
|
ubyte rs2 = (inst >> 20) & 0x1F;
|
|
WORD i_imm = (inst >> 20) & 0xFFF;
|
|
WORD u_imm = inst & 0xFFFFF000;
|
|
|
|
WORD s_imm;
|
|
s_imm |= (inst >> 7) & 0x0001F; // [4:0]
|
|
s_imm |= (inst >> 20) & 0x00FE0; // [11:5]
|
|
|
|
WORD b_imm;
|
|
b_imm |= (inst >> 7) & 0x0001E; // [4:1]
|
|
b_imm |= (inst >> 20) & 0x007E0; // [10:5]
|
|
b_imm |= (inst << 4) & 0x00800; // [11]
|
|
b_imm |= (inst >> 19) & 0x01000; // [12]
|
|
|
|
WORD j_imm;
|
|
j_imm |= (inst >> 20) & 0x0007FE; // [10:1]
|
|
j_imm |= (inst >> 9) & 0x000800; // [11]
|
|
j_imm |= inst & 0x0FF000; // [19:12]
|
|
j_imm |= (inst >> 11) & 0x100000; // [20]
|
|
|
|
WORD i_immex = signExtend!WORD(i_imm, 12);
|
|
WORD s_immex = signExtend!WORD(s_imm, 12);
|
|
WORD b_immex = signExtend!WORD(b_imm, 12);
|
|
WORD u_immex = signExtend!WORD(u_imm, 20);
|
|
WORD j_immex = signExtend!WORD(j_imm, 20);
|
|
|
|
// Debug logging
|
|
debug
|
|
{
|
|
import std.stdio;
|
|
writeln();
|
|
writefln(
|
|
"\x1B[35m%06X\x1B[0m: \x1B[96m%s\x1B[0m, funct7: \x1B[33m%d\x1B[0m, funct3: \x1B[33m%d\x1B[0m " ~
|
|
"rs2: \x1B[36m%s\x1B[0m, rs1: \x1B[36m%s\x1B[0m, rd: \x1B[91m%d\x1B[0m",
|
|
pc,
|
|
cast(OPCODE)opcode,
|
|
funct7,
|
|
funct3,
|
|
rs2,
|
|
rs1,
|
|
rd,
|
|
);
|
|
auto binstr = (WORD num, int bits){
|
|
import std.format;
|
|
string img = "";
|
|
foreach (c; format("%0*b", bits, num))
|
|
if (c=='1')
|
|
img ~= "\x1B[32m1";
|
|
else
|
|
img ~= "\x1B[31m0";
|
|
return img~"\x1B[0m";
|
|
};
|
|
writefln("I-IMM : %s | \x1B[94m%d\x1B[0m", binstr(i_imm&0xFFF, 12), cast(int)i_immex);
|
|
writefln("S-IMM : %s | \x1B[94m%d\x1B[0m", binstr(s_imm&0xFFF, 12), cast(int)s_immex);
|
|
writefln("B-IMM : %s | \x1B[94m%d\x1B[0m", binstr(b_imm&0xFFF, 12), cast(int)b_immex);
|
|
writefln("U-IMM : %s | \x1B[94m%d\x1B[0m >> %d", binstr(u_imm&0xFFFFF, 20), cast(int)u_immex, cast(int)u_immex >> 12);
|
|
writefln("J-IMM : %s | \x1B[94m%d\x1B[0m", binstr(j_imm&0xFFFFF, 20), cast(int)j_immex);
|
|
}
|
|
|
|
// Do instruction
|
|
with (OPCODE) switch (opcode)
|
|
{
|
|
case OPIMM:
|
|
switch (funct3)
|
|
{
|
|
case 0: // ADDI
|
|
x[rd] = x[rs1] + i_immex;
|
|
break;
|
|
case 1: // SLLI
|
|
x[rd] = x[rs1] << (i_imm & 0x1F);
|
|
break;
|
|
case 2: // SLTI
|
|
x[rd] = cast(Signed!WORD)x[rs1] < cast(Signed!WORD)i_immex;
|
|
break;
|
|
case 3: // SLTU
|
|
x[rd] = x[rs1] < i_immex;
|
|
break;
|
|
case 4: // XORI
|
|
x[rd] = x[rs1] ^ i_immex;
|
|
break;
|
|
case 5: // SRLI/SRAI
|
|
if ((i_imm & 0x400) == 0) // SRLI
|
|
x[rd] = x[rs1] >> (i_imm & 0x1F);
|
|
else // SRAI
|
|
x[rd] = cast(Signed!WORD)x[rs1] >> (i_imm & 0x1F);
|
|
break;
|
|
case 6: // ORI
|
|
x[rd] = x[rs1] | i_immex;
|
|
break;
|
|
case 7: // ANDI
|
|
x[rd] = x[rs1] & i_immex;
|
|
break;
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
}
|
|
break;
|
|
|
|
case OP:
|
|
switch (funct3)
|
|
{
|
|
case 0: // ADD
|
|
if (funct7 == 0)
|
|
x[rd] = x[rs1] + x[rs2];
|
|
else if (funct7 == 32)
|
|
x[rd] = x[rs1] - x[rs2];
|
|
else
|
|
goto default;
|
|
break;
|
|
case 1: // SLL
|
|
if (funct7 == 0)
|
|
x[rd] = x[rs1] << (x[rs2] & 0x1F);
|
|
else
|
|
goto default;
|
|
break;
|
|
case 2: // SLT
|
|
if (funct7 == 0)
|
|
x[rd] = cast(Signed!WORD)x[rs1] < cast(Signed!WORD)x[rs2];
|
|
else
|
|
goto default;
|
|
break;
|
|
case 3: // SLTU
|
|
if (funct7 == 0)
|
|
x[rd] = x[rs1] < x[rs2];
|
|
else
|
|
goto default;
|
|
break;
|
|
case 4: // XOR
|
|
if (funct7 == 0)
|
|
x[rd] = x[rs1] ^ x[rs2];
|
|
else
|
|
goto default;
|
|
break;
|
|
case 5: // SRL/SRA
|
|
if (funct7 == 0) // SRL
|
|
x[rd] = x[rs1] >> (x[rs2] & 0x1F);
|
|
else if (funct7 == 32) // SRA
|
|
x[rd] = cast(Signed!WORD)x[rs1] >> (x[rs2] & 0x1F);
|
|
else
|
|
goto default;
|
|
break;
|
|
case 6: // OR
|
|
if (funct7 == 0)
|
|
x[rd] = x[rs1] | x[rs2];
|
|
else
|
|
goto default;
|
|
break;
|
|
case 7: // AND
|
|
if (funct7 == 0)
|
|
x[rd] = x[rs1] & x[rs2];
|
|
else
|
|
goto default;
|
|
break;
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
}
|
|
break;
|
|
|
|
case LUI:
|
|
x[rd] = u_imm >> 12;
|
|
break;
|
|
|
|
case AUIPC:
|
|
pc += u_imm >> 12;
|
|
x[rd] = pc;
|
|
break;
|
|
|
|
case JAL:
|
|
x[rd] = pc + 4;
|
|
|
|
jump(pc + cast(Signed!WORD)j_immex);
|
|
break;
|
|
|
|
case JALR:
|
|
if (funct3)
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
x[rd] = pc + 4;
|
|
jump((x[rs1] + cast(Signed!WORD)i_immex) << 1 >> 1);
|
|
break;
|
|
|
|
case BRANCH:
|
|
switch (funct3)
|
|
{
|
|
case 0: // BEQ
|
|
if (x[rs1] == x[rs2])
|
|
jump(pc + cast(Signed!WORD)b_immex);
|
|
break;
|
|
case 1: // BNE
|
|
if (x[rs1] != x[rs2])
|
|
jump(pc + cast(Signed!WORD)b_immex);
|
|
break;
|
|
case 4: // BLT
|
|
if (cast(Signed!WORD)x[rs1] < cast(Signed!WORD)x[rs2])
|
|
jump(pc + cast(Signed!WORD)b_immex);
|
|
break;
|
|
case 5: // BGE
|
|
if (cast(Signed!WORD)x[rs1] > cast(Signed!WORD)x[rs2])
|
|
jump(pc + cast(Signed!WORD)b_immex);
|
|
break;
|
|
case 6: // BLTU
|
|
if (x[rs1] < x[rs2])
|
|
jump(pc + cast(Signed!WORD)b_immex);
|
|
break;
|
|
case 7: // BGEU
|
|
if (x[rs1] > x[rs2])
|
|
jump(pc + cast(Signed!WORD)b_immex);
|
|
break;
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LOAD:
|
|
switch (funct3)
|
|
{
|
|
case 0: // LB
|
|
x[rd] = load!byte(x[rs1] + cast(Signed!WORD)i_immex);
|
|
break;
|
|
case 1: // LH
|
|
x[rd] = load!short(x[rs1] + cast(Signed!WORD)i_immex);
|
|
break;
|
|
case 2: // LW
|
|
x[rd] = load!int(x[rs1] + cast(Signed!WORD)i_immex);
|
|
break;
|
|
case 4: // LBU
|
|
x[rd] = load!ubyte(x[rs1] + cast(Signed!WORD)i_immex);
|
|
break;
|
|
case 5: // LHU
|
|
x[rd] = load!ushort(x[rs1] + cast(Signed!WORD)i_immex);
|
|
break;
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STORE:
|
|
switch (funct3)
|
|
{
|
|
case 0: // SB
|
|
store!ubyte(x[rs1] + s_immex, cast(ubyte)x[rs2]);
|
|
break;
|
|
case 1: // SH
|
|
store!ushort(x[rs1] + s_immex, cast(ushort)x[rs2]);
|
|
break;
|
|
case 2: // SW
|
|
store!uint(x[rs1] + s_immex, cast(uint)x[rs2]);
|
|
break;
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MISCMEM:
|
|
switch (funct3)
|
|
{
|
|
case 0: // FENCE
|
|
if (rs1 != 0 || rd != 0)
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
if (i_imm ^ 0x0FF) // && i_imm ^ 0x8FF)
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
fence();
|
|
break;
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
}
|
|
break;
|
|
|
|
case SYSTEM:
|
|
switch (funct3)
|
|
{
|
|
case 0: // PRIV
|
|
if (rs1 != 0 || rd != 0)
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
switch (i_imm)
|
|
{
|
|
case 0: // ECALL
|
|
exception(Cause.MACHINE_ECALL);
|
|
break;
|
|
|
|
case 1: // EBREAK
|
|
exception(Cause.BREAKPOINT);
|
|
break;
|
|
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
exception(Cause.ILLEGAL_INSTRUCTION);
|
|
break;
|
|
}
|
|
|
|
// Increment program counter
|
|
if (!jumped)
|
|
pc += XLEN;
|
|
}
|
|
|
|
void exception(Cause cause)
|
|
{
|
|
import std.conv;
|
|
throw new Exception(cause.to!string);
|
|
}
|
|
|
|
void interrupt(Cause cause)
|
|
{
|
|
import std.conv;
|
|
throw new Exception(cause.to!string);
|
|
}
|
|
|
|
void fence()
|
|
{
|
|
}
|
|
|
|
WORD[32] x;
|
|
WORD pc;
|
|
|
|
Memory memory;
|
|
|
|
private:
|
|
void jump(WORD addr)
|
|
{
|
|
pc = addr;
|
|
jumped = true;
|
|
}
|
|
|
|
WORD fetch(WORD addr)
|
|
{
|
|
WORD value;
|
|
if (!memory.get!WORD(addr, value))
|
|
exception(Cause.FETCH_ACCESS);
|
|
return value;
|
|
}
|
|
|
|
T load(T)(WORD addr)
|
|
{
|
|
T value;
|
|
if (!memory.get!T(addr, value))
|
|
exception(Cause.LOAD_ACCESS);
|
|
return value;
|
|
}
|
|
|
|
T store(T)(WORD addr, T value)
|
|
{
|
|
if (!memory.set!T(addr, value))
|
|
exception(Cause.STORE_ACCESS);
|
|
return value;
|
|
}
|
|
|
|
bool jumped = false;
|
|
}
|