initial cpu structure :thonk:

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-08-27 21:40:55 +05:30
parent ad64b1e8d1
commit 84c68a4e00
20 changed files with 449 additions and 42 deletions

View File

@@ -1,2 +1,7 @@
BasedOnStyle: LLVM
BasedOnStyle: Mozilla
IndentWidth: 4
BreakBeforeBraces: Attach
AlwaysBreakTemplateDeclarations: Yes
AlignConsecutiveAssignments: Consecutive
BreakAfterAttributes: Always
AllowShortEnumsOnASingleLine: False

View File

@@ -1,10 +1,17 @@
#include "matar.hh"
#include "emulator.hh"
#include <iostream>
int main() {
std::cout << "Hello" << std::endl;
if (run() > 0) {
std::cerr << "Crashed" << std::endl;
int
main(int argc, const char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <file>" << std::endl;
return 1;
}
try {
emulator::run(argv[1]);
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}

View File

@@ -10,6 +10,6 @@ executable(
meson.project_name(),
target_sources,
link_with: target_deps,
include_directories: includes,
install : true
include_directories: inc,
install : true,
)

View File

@@ -16,12 +16,12 @@
eachSystem (system:
let
pkgs = import nixpkgs { inherit system; };
llvm = pkgs.llvmPackages;
llvm = pkgs.llvmPackages_16;
stdenv = llvm.libcxxStdenv;
nativeBuildInputs = with pkgs; [ meson ninja ];
in
{
rec {
packages = rec {
matar = stdenv.mkDerivation rec {
name = "matar";
@@ -44,7 +44,13 @@
matar = pkgs.mkShell.override { inherit stdenv; } {
name = "matar";
packages = nativeBuildInputs ++ (with pkgs; [
clang-tools
# dev tools
clang-tools_16
# other tools
valgrind
llvm.lldb
]);
};
default = matar;

16
include/emulator.hh Normal file
View File

@@ -0,0 +1,16 @@
#ifndef EMULATOR_HH
#define EMULATOR_HH
// Why do I have a public API? We will know that in the future
#include <string>
namespace emulator {
void
run(std::string filepath);
void
run(std::ifstream& ifile);
}
#endif /* EMULATOR_HH */

View File

@@ -1,6 +0,0 @@
#ifndef MATAR_HH
#define MATAR_HH
int run();
#endif /* MATAR_HH */

5
include/meson.build Normal file
View File

@@ -0,0 +1,5 @@
headers = files(
'emulator.hh'
)
install_headers(headers, subdir: meson.project_name(), preserve_path: true)

View File

@@ -6,10 +6,7 @@ project('matar', 'cpp',
'optimization=3',
'cpp_std=c++20'])
includes = include_directories('include')
inc = include_directories('include')
subdir('include')
subdir('src')
subdir('apps')
import('pkgconfig').generate(lib)

39
src/bits.hh Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <concepts>
#include <limits>
using std::size_t;
template<std::integral Int>
inline bool
get_nth_bit(Int num, size_t n) {
return (1 && (num >> n));
}
template<std::integral Int>
inline void
set_nth_bit(Int& num, size_t n) {
num |= (1 << n);
}
template<std::integral Int>
inline void
rst_nth_bit(Int& num, size_t n) {
num &= ~(1 << n);
}
template<std::integral Int>
inline void
chg_nth_bit(Int& num, size_t n, bool x) {
num ^= (num ^ -x) & 1 << n;
}
/// read range of bits from start to end inclusive
template<std::unsigned_integral Int>
inline Int
get_bit_range(Int& num, size_t start, size_t end) {
// NOTE: we do not require -1 if it is a signed integral (which it is not)
Int left = std::numeric_limits<Int>::digits - 1 - end;
return num << left >> (left + start);
}

14
src/bus.hh Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <fstream>
#include <vector>
class Bus {
std::vector<uint8_t> data;
public:
Bus() = default;
Bus(std::istream& ifile)
: data(std::istreambuf_iterator<char>(ifile),
std::istreambuf_iterator<char>()) {}
};

61
src/cpu/arm/insruction.hh Normal file
View File

@@ -0,0 +1,61 @@
enum class ArmInstructionFormat {
DataProcessingAndFsrTransfer,
Multiply,
MultiplyLong,
SingleDataSwap,
BranchAndExchange,
HalfwordDataTransferRegisterOffset,
HalfwordDataTransferImmediateOffset,
SingleDataTransfer,
Undefined,
BlockDataTransfer,
Branch,
CoprocessorDataTransfer,
CoprocessorDataOperation,
CoprocessorRegisterTransfer,
SoftwareInterrupt
};
enum class Condition {
EQ = 0b0000,
NE = 0b0001,
CS = 0b0010,
CC = 0b0011,
MI = 0b0100,
PL = 0b0101,
VS = 0b0110,
VC = 0b0111,
HI = 0b1000,
LS = 0b1001,
GE = 0b1010,
LT = 0b1011,
GT = 0b1100,
LE = 0b1101,
AL = 0b1110
};
enum class OpCode {
AND = 0b0000,
EOR = 0b0001,
SUB = 0b0010,
RSB = 0b0011,
ADD = 0b0100,
ADC = 0b0101,
SBC = 0b0110,
RSC = 0b0111,
TST = 0b1000,
TEQ = 0b1001,
CMP = 0b1010,
CMN = 0b1011,
ORR = 0b1100,
MOV = 0b1101,
BIC = 0b1110,
MVN = 0b1111
};
enum class Shift {
LSL = 0b00,
LSR = 0b01,
ASR = 0b10,
ROR = 0b11
};

2
src/cpu/arm/meson.build Normal file
View File

@@ -0,0 +1,2 @@
lib_sources += files(
)

View File

@@ -1,6 +1,104 @@
#include "cpu.hh"
#include <iostream>
#include "cpu/utility.hh"
#include <algorithm>
#include <cstdio>
namespace cpu {
void run() { std::cout << "Hello from inside the CPU" << std::endl; }
} // namespace cpu
/* change modes */
void
Cpu::chg_mode(Mode from, Mode to) {
if (from == to)
return;
/* TODO: replace visible registers with view once I understand how to
* concatenate views */
#define STORE_BANKED(mode, MODE) \
std::copy(gpr + GPR_##MODE##_BANKED_FIRST, \
gpr + GPR_##MODE##_BANKED_FIRST + GPR_##MODE##_BANKED_COUNT, \
gpr_banked.mode)
switch (from) {
case Mode::Fiq:
STORE_BANKED(fiq, FIQ);
spsr_banked.fiq = spsr;
break;
case Mode::Supervisor:
STORE_BANKED(svc, SVC);
spsr_banked.svc = spsr;
break;
case Mode::Abort:
STORE_BANKED(abt, ABT);
spsr_banked.abt = spsr;
break;
case Mode::Irq:
STORE_BANKED(irq, IRQ);
spsr_banked.irq = spsr;
break;
case Mode::Undefined:
STORE_BANKED(und, UND);
spsr_banked.und = spsr;
break;
case Mode::User:
case Mode::System:
STORE_BANKED(old, SYS_USR);
break;
}
#define RESTORE_BANKED(mode, MODE) \
std::copy(gpr_banked.mode, \
gpr_banked.mode + GPR_##MODE##_BANKED_COUNT, \
gpr + GPR_##MODE##_BANKED_FIRST)
switch (to) {
case Mode::Fiq:
RESTORE_BANKED(fiq, FIQ);
spsr = spsr_banked.fiq;
break;
case Mode::Supervisor:
RESTORE_BANKED(svc, SVC);
spsr = spsr_banked.svc;
break;
case Mode::Abort:
RESTORE_BANKED(abt, ABT);
spsr = spsr_banked.abt;
break;
case Mode::Irq:
RESTORE_BANKED(irq, IRQ);
spsr = spsr_banked.irq;
break;
case Mode::Undefined:
RESTORE_BANKED(und, UND);
spsr = spsr_banked.und;
break;
case Mode::User:
case Mode::System:
STORE_BANKED(old, SYS_USR);
break;
}
#undef RESTORE_BANKED
cpsr.set_mode(to);
}
// set register
inline uint32_t&
Cpu::operator[](size_t idx) {
// avoid unneeded complexity like index checks
return gpr[idx];
}
// get register
inline const uint32_t&
Cpu::operator[](size_t idx) const {
return gpr[idx];
}

View File

@@ -1,7 +1,77 @@
#pragma once
#define ABSOLUTE "MADarchod"
#include "bus.hh"
#include "psr.hh"
namespace cpu {
void run();
}
#include <cstdint>
using std::size_t;
static constexpr size_t GPR_VISIBLE_COUNT = 16;
static constexpr size_t GPR_FIQ_BANKED_FIRST = 8;
static constexpr size_t GPR_FIQ_BANKED_COUNT = 7;
static constexpr size_t GPR_SVC_BANKED_FIRST = 13;
static constexpr size_t GPR_SVC_BANKED_COUNT = 2;
static constexpr size_t GPR_ABT_BANKED_FIRST = 13;
static constexpr size_t GPR_ABT_BANKED_COUNT = 2;
static constexpr size_t GPR_IRQ_BANKED_FIRST = 13;
static constexpr size_t GPR_IRQ_BANKED_COUNT = 2;
static constexpr size_t GPR_UND_BANKED_FIRST = 13;
static constexpr size_t GPR_UND_BANKED_COUNT = 2;
static constexpr size_t GPR_SYS_USR_BANKED_FIRST = 8;
static constexpr size_t GPR_SYS_USR_BANKED_COUNT = 7;
struct _GprBanked {
uint32_t fiq[GPR_FIQ_BANKED_COUNT];
uint32_t svc[GPR_SVC_BANKED_COUNT];
uint32_t abt[GPR_ABT_BANKED_COUNT];
uint32_t irq[GPR_IRQ_BANKED_COUNT];
uint32_t und[GPR_UND_BANKED_COUNT];
/* visible registers before the mode switch */
uint32_t old[GPR_SYS_USR_BANKED_COUNT];
};
typedef struct _GprBanked GprBanked;
struct _SpsrBanked {
Psr fiq;
Psr svc;
Psr abt;
Psr irq;
Psr und;
};
typedef struct _SpsrBanked SpsrBanked;
class Cpu {
uint32_t gpr[GPR_VISIBLE_COUNT]; // general purpose registers
GprBanked gpr_banked; // banked general purpose registers
SpsrBanked spsr_banked; // banked saved program status registers
Psr cpsr; // current program status register
Psr spsr; // status program status register
Bus bus;
void chg_mode(Mode from, Mode to);
uint32_t& operator[](size_t idx);
const uint32_t& operator[](size_t idx) const;
public:
Cpu(Bus bus)
: gpr(0)
, gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } })
, spsr_banked({ 0, 0, 0, 0, 0 })
, cpsr(0)
, spsr(0)
, bus(bus) {
cpsr.set_mode(Mode::System);
cpsr.set_irq_disabled(true);
cpsr.set_fiq_disabled(true);
cpsr.set_state(State::Arm);
}
};

View File

@@ -1,3 +1,5 @@
lib_sources += files(
'cpu.cc'
)
subdir('arm')

55
src/cpu/psr.hh Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include "bits.hh"
#include "utility.hh"
#include <cstdint>
static constexpr uint32_t PSR_CLEAR_RESERVED = 0xf00000ff;
static constexpr uint32_t PSR_CLEAR_MODE = 0x0b00000;
class Psr {
uint32_t psr;
public:
// clear the reserved bits i.e, [8:27]
Psr(uint32_t raw) { psr = raw & PSR_CLEAR_RESERVED; }
// Mode : [4:0]
Mode mode() const { return static_cast<Mode>(psr & ~PSR_CLEAR_MODE); }
void set_mode(Mode mode) {
psr &= PSR_CLEAR_MODE;
psr |= static_cast<uint32_t>(mode);
}
// State : [5]
bool state() const { return get_nth_bit(psr, 5); }
void set_state(State state) {
chg_nth_bit(psr, 5, static_cast<bool>(state));
}
#define GET_SET_NTH_BIT_FUNCTIONS(name, n) \
bool name() const { return get_nth_bit(psr, n); } \
void set_##name(bool val) { chg_nth_bit(psr, n, val); }
// FIQ disable : [6]
GET_SET_NTH_BIT_FUNCTIONS(fiq_disabled, 6)
// IRQ disable : [7]
GET_SET_NTH_BIT_FUNCTIONS(irq_disabled, 7)
// Reserved bits : [27:8]
// Overflow flag : [28]
GET_SET_NTH_BIT_FUNCTIONS(v, 28);
// Carry flag : [29]
GET_SET_NTH_BIT_FUNCTIONS(c, 29);
// Zero flag : [30]
GET_SET_NTH_BIT_FUNCTIONS(z, 30);
// Negative flag : [30]
GET_SET_NTH_BIT_FUNCTIONS(n, 31);
#undef GET_SET_NTH_BIT_FUNCTIONS
};

17
src/cpu/utility.hh Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
enum class Mode {
/* M[4:0] in PSR */
User = 0b10000,
Fiq = 0b10001,
Irq = 0b10010,
Supervisor = 0b10011,
Abort = 0b10111,
Undefined = 0b11011,
System = 0b11111,
};
enum class State {
Arm = 0,
Thumb = 1
};

24
src/emulator.cc Normal file
View File

@@ -0,0 +1,24 @@
#include "emulator.hh"
#include "bus.hh"
#include "cpu/cpu.hh"
#include <fstream>
namespace emulator {
void
run(std::ifstream& ifile) {
Bus bus(ifile);
Cpu cpu(bus);
}
void
run(std::string filepath) {
std::ifstream ifile(filepath, std::ios::in | std::ios::binary);
if (!ifile.is_open()) {
throw std::ios::failure("No such file exists", std::error_code());
}
run(ifile);
ifile.close();
}
}

View File

@@ -1,7 +0,0 @@
#include "matar.hh"
#include "cpu/cpu.hh"
int run() {
cpu::run();
return 0;
}

View File

@@ -1,5 +1,5 @@
lib_sources = files(
'matar.cc'
'emulator.cc'
)
subdir('cpu')
@@ -7,6 +7,8 @@ subdir('cpu')
lib = library(
meson.project_name(),
lib_sources,
include_directories: includes,
install : true
include_directories: inc,
install: true
)
import('pkgconfig').generate(lib)