diff --git a/.clang-format b/.clang-format index 5bf2f83..c15ab69 100644 --- a/.clang-format +++ b/.clang-format @@ -1,2 +1,7 @@ -BasedOnStyle: LLVM -IndentWidth: 4 \ No newline at end of file +BasedOnStyle: Mozilla +IndentWidth: 4 +BreakBeforeBraces: Attach +AlwaysBreakTemplateDeclarations: Yes +AlignConsecutiveAssignments: Consecutive +BreakAfterAttributes: Always +AllowShortEnumsOnASingleLine: False \ No newline at end of file diff --git a/apps/target/main.cc b/apps/target/main.cc index 19c0e0b..22a3981 100644 --- a/apps/target/main.cc +++ b/apps/target/main.cc @@ -1,10 +1,17 @@ -#include "matar.hh" +#include "emulator.hh" #include -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] << " " << std::endl; + return 1; + } + + try { + emulator::run(argv[1]); + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; return 1; } diff --git a/apps/target/meson.build b/apps/target/meson.build index 9238d00..75f34ae 100644 --- a/apps/target/meson.build +++ b/apps/target/meson.build @@ -10,6 +10,6 @@ executable( meson.project_name(), target_sources, link_with: target_deps, - include_directories: includes, - install : true + include_directories: inc, + install : true, ) \ No newline at end of file diff --git a/flake.nix b/flake.nix index 591359f..fcbbc10 100644 --- a/flake.nix +++ b/flake.nix @@ -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; diff --git a/include/emulator.hh b/include/emulator.hh new file mode 100644 index 0000000..d6b8cb4 --- /dev/null +++ b/include/emulator.hh @@ -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 + +namespace emulator { +void +run(std::string filepath); + +void +run(std::ifstream& ifile); +} + +#endif /* EMULATOR_HH */ diff --git a/include/matar.hh b/include/matar.hh deleted file mode 100644 index 70ccdd1..0000000 --- a/include/matar.hh +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef MATAR_HH -#define MATAR_HH - -int run(); - -#endif /* MATAR_HH */ diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..f80dd7f --- /dev/null +++ b/include/meson.build @@ -0,0 +1,5 @@ +headers = files( + 'emulator.hh' +) + +install_headers(headers, subdir: meson.project_name(), preserve_path: true) \ No newline at end of file diff --git a/meson.build b/meson.build index 5815672..3f901ec 100644 --- a/meson.build +++ b/meson.build @@ -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) \ No newline at end of file diff --git a/src/bits.hh b/src/bits.hh new file mode 100644 index 0000000..6acdab1 --- /dev/null +++ b/src/bits.hh @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +using std::size_t; + +template +inline bool +get_nth_bit(Int num, size_t n) { + return (1 && (num >> n)); +} + +template +inline void +set_nth_bit(Int& num, size_t n) { + num |= (1 << n); +} + +template +inline void +rst_nth_bit(Int& num, size_t n) { + num &= ~(1 << n); +} + +template +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 +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::digits - 1 - end; + return num << left >> (left + start); +} diff --git a/src/bus.hh b/src/bus.hh new file mode 100644 index 0000000..0890f51 --- /dev/null +++ b/src/bus.hh @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class Bus { + std::vector data; + + public: + Bus() = default; + Bus(std::istream& ifile) + : data(std::istreambuf_iterator(ifile), + std::istreambuf_iterator()) {} +}; diff --git a/src/cpu/arm/insruction.hh b/src/cpu/arm/insruction.hh new file mode 100644 index 0000000..0d84f6e --- /dev/null +++ b/src/cpu/arm/insruction.hh @@ -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 +}; diff --git a/src/cpu/arm/meson.build b/src/cpu/arm/meson.build new file mode 100644 index 0000000..4673a60 --- /dev/null +++ b/src/cpu/arm/meson.build @@ -0,0 +1,2 @@ +lib_sources += files( +) \ No newline at end of file diff --git a/src/cpu/cpu.cc b/src/cpu/cpu.cc index 6f23299..b92d78a 100644 --- a/src/cpu/cpu.cc +++ b/src/cpu/cpu.cc @@ -1,6 +1,104 @@ #include "cpu.hh" -#include +#include "cpu/utility.hh" +#include +#include -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]; +} diff --git a/src/cpu/cpu.hh b/src/cpu/cpu.hh index 5aaf3bb..1d8d471 100644 --- a/src/cpu/cpu.hh +++ b/src/cpu/cpu.hh @@ -1,7 +1,77 @@ #pragma once -#define ABSOLUTE "MADarchod" +#include "bus.hh" +#include "psr.hh" -namespace cpu { -void run(); -} +#include + +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); + } +}; diff --git a/src/cpu/meson.build b/src/cpu/meson.build index ed188bb..8fcbc9f 100644 --- a/src/cpu/meson.build +++ b/src/cpu/meson.build @@ -1,3 +1,5 @@ lib_sources += files( 'cpu.cc' -) \ No newline at end of file +) + +subdir('arm') \ No newline at end of file diff --git a/src/cpu/psr.hh b/src/cpu/psr.hh new file mode 100644 index 0000000..1726f9d --- /dev/null +++ b/src/cpu/psr.hh @@ -0,0 +1,55 @@ +#pragma once + +#include "bits.hh" +#include "utility.hh" +#include + +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(psr & ~PSR_CLEAR_MODE); } + void set_mode(Mode mode) { + psr &= PSR_CLEAR_MODE; + psr |= static_cast(mode); + } + + // State : [5] + bool state() const { return get_nth_bit(psr, 5); } + void set_state(State state) { + chg_nth_bit(psr, 5, static_cast(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 +}; diff --git a/src/cpu/utility.hh b/src/cpu/utility.hh new file mode 100644 index 0000000..95e0f3c --- /dev/null +++ b/src/cpu/utility.hh @@ -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 +}; diff --git a/src/emulator.cc b/src/emulator.cc new file mode 100644 index 0000000..fa50829 --- /dev/null +++ b/src/emulator.cc @@ -0,0 +1,24 @@ +#include "emulator.hh" +#include "bus.hh" +#include "cpu/cpu.hh" +#include + +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(); +} +} diff --git a/src/matar.cc b/src/matar.cc deleted file mode 100644 index 180adef..0000000 --- a/src/matar.cc +++ /dev/null @@ -1,7 +0,0 @@ -#include "matar.hh" -#include "cpu/cpu.hh" - -int run() { - cpu::run(); - return 0; -} diff --git a/src/meson.build b/src/meson.build index 6ff591c..59981c7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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 -) \ No newline at end of file + include_directories: inc, + install: true +) + +import('pkgconfig').generate(lib) \ No newline at end of file