commit 069a53608564e674df90d93b02ee13abef956923 Author: Matthew Berry Date: Sat Aug 22 00:15:30 2020 -0700 initial commit: load cartridge, parse instr types diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..163eb75 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bb75ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..765f0e9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: crystal + +# Uncomment the following if you'd like Travis to run specs and check code formatting +# script: +# - crystal spec +# - crystal tool format --check diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b771843 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 your-name-here + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0226476 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# crab + +TODO: Write a description here + +## Installation + +TODO: Write installation instructions here + +## Usage + +TODO: Write usage instructions here + +## Development + +TODO: Write development instructions here + +## Contributing + +1. Fork it () +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Contributors + +- [your-name-here](https://github.com/your-github-user) - creator and maintainer diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..efd73fa --- /dev/null +++ b/shard.yml @@ -0,0 +1,13 @@ +name: crab +version: 0.1.0 + +authors: + - Matthew Berry + +targets: + crab: + main: src/crab.cr + +crystal: 0.35.1 + +license: MIT diff --git a/spec/crab_spec.cr b/spec/crab_spec.cr new file mode 100644 index 0000000..a1c81e3 --- /dev/null +++ b/spec/crab_spec.cr @@ -0,0 +1,9 @@ +require "./spec_helper" + +describe Crab do + # TODO: Write tests + + it "works" do + false.should eq(true) + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..34a50bd --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/crab" diff --git a/src/crab.cr b/src/crab.cr new file mode 100644 index 0000000..436c87e --- /dev/null +++ b/src/crab.cr @@ -0,0 +1,16 @@ +require "./crab/gba" + +module Crab + VERSION = "0.1.0" + + extend self + + def run + gba = GBA.new ARGV[0] + gba.run + end +end + +unless PROGRAM_NAME.includes?("crystal-run-spec") + Crab.run +end diff --git a/src/crab/bus.cr b/src/crab/bus.cr new file mode 100644 index 0000000..834c92d --- /dev/null +++ b/src/crab/bus.cr @@ -0,0 +1,40 @@ +class Bus + CARTRIDGE = 0x08000000..0x0FFFFFFF + UNUSED = 0x10000000..0xFFFFFFFF + + def initialize(@gba : GBA) + end + + def [](index : Int) : Byte + # puts "read #{hex_str index.to_u32}" + case index + when CARTRIDGE then @gba.cartridge[index - CARTRIDGE.begin] + when UNUSED then 0xFF + else 0xFF + end.to_u8 + end + + def read_word(index : Int) : Word + self[index].to_u32 | + (self[index + 1].to_u32 << 8) | + (self[index + 2].to_u32 << 16) | + (self[index + 3].to_u32 << 24) + end + + def []=(index : Int, value : Byte) : Nil + # puts "write #{hex_str index.to_u32} -> #{hex_str value}" + case index + when CARTRIDGE then @gba.cartridge[index] + when UNUSED then nil + else @todo[index] = value + end + end + + def []=(index : Int, value : Word) : Nil + # puts "write #{hex_str index.to_u32} -> #{hex_str value}" + self[index] = 0xFF_u8 & (value >> 24) + self[index] = 0xFF_u8 & (value >> 16) + self[index] = 0xFF_u8 & (value >> 8) + self[index] = 0xFF_u8 & value + end +end diff --git a/src/crab/cartridge.cr b/src/crab/cartridge.cr new file mode 100644 index 0000000..4471807 --- /dev/null +++ b/src/crab/cartridge.cr @@ -0,0 +1,24 @@ +class Cartridge + @rom : Bytes + + getter title : String { + io = IO::Memory.new + io.write @rom[0x0A0...0x0AC] + io.to_s + } + + def initialize(rom_path : String) + @rom = File.open rom_path do |file| + bytes = Bytes.new file.size + file.read bytes + bytes + end + end + + def [](index : Int) : Byte + @rom[index] + end + + def []=(index : Int, value : Byte) : Nil + end +end diff --git a/src/crab/cpu.cr b/src/crab/cpu.cr new file mode 100644 index 0000000..cacf28f --- /dev/null +++ b/src/crab/cpu.cr @@ -0,0 +1,27 @@ +class CPU + @registers = Slice(UInt32).new 16 + + def initialize(@gba : GBA) + self.pc = 0x08000000 + end + + def pc : Word + @registers[15] + end + + def pc=(pc : Word) : Nil + @registers[15] = pc + end + + def tick : Nil + puts "PC: #{hex_str pc}, INSTRUCTION: #{hex_str @gba.bus.read_word pc}, TYPE: #{Instr.from_hash hash_instr @gba.bus.read_word pc}" + # puts hex_str @gba.bus.read_word pc + # puts hex_str hash_instr @gba.bus.read_word pc + # puts Instr.from_hash hash_instr @gba.bus.read_word pc + self.pc += 4 + end + + def hash_instr(instr : Word) : Word + ((instr >> 16) & 0x0FF0) | ((instr >> 4) & 0xF) + end +end diff --git a/src/crab/gba.cr b/src/crab/gba.cr new file mode 100644 index 0000000..643d1de --- /dev/null +++ b/src/crab/gba.cr @@ -0,0 +1,22 @@ +require "./types" +require "./util" +require "./cartridge" +require "./bus" +require "./cpu" + +class GBA + getter cartridge : Cartridge + getter bus : Bus { Bus.new self } + getter cpu : CPU { CPU.new self } + + def initialize(rom_path : String) + @cartridge = Cartridge.new rom_path + end + + def run : Nil + # puts @cartridge.title + loop do + cpu.tick + end + end +end diff --git a/src/crab/types.cr b/src/crab/types.cr new file mode 100644 index 0000000..79fb38e --- /dev/null +++ b/src/crab/types.cr @@ -0,0 +1,57 @@ +alias Byte = UInt8 +alias Word = UInt32 +alias Words = Slice(UInt32) + +enum Instr + DATA_PROCESSING_PSR_TRANSFER + MULTIPLY + MULTIPLY_LONG + SINGLE_DATA_SWAP + BRANCH_EXCHANGE + HALFWORD_DATA_TRANSFER_REGISTER_OFFSET + HALFWORD_DATA_TRANSFER_IMMEDIATE_OFFSET + SINGLE_DATA_TRANSFER + UNDEFINED + BLOCK_DATA_TRANSFER + BRANCH + COPROCESSOR_DATA_TRANSFER + COPROCESSOR_DATA_OPERATION + COPROCESSOR_REGISTER_TRANSFER + SOFTWARE_INTERRUPT + + def self.from_hash(hash : Word) : Instr + if hash & 0b111100000000 == 0b111100000000 + SOFTWARE_INTERRUPT + elsif hash & 0b111100000001 == 0b111000000001 + COPROCESSOR_REGISTER_TRANSFER + elsif hash & 0b111100000001 == 0b111000000001 + COPROCESSOR_DATA_OPERATION + elsif hash & 0b111000000000 == 0b110000000000 + COPROCESSOR_DATA_TRANSFER + elsif hash & 0b111000000000 == 0b101000000000 + BRANCH + elsif hash & 0b111000000000 == 0b100000000000 + BLOCK_DATA_TRANSFER + elsif hash & 0b111000000001 == 0b011000000001 + UNDEFINED + elsif hash & 0b110000000000 == 0b010000000000 + SINGLE_DATA_TRANSFER + elsif hash & 0b111001001001 == 0b000001001001 + HALFWORD_DATA_TRANSFER_IMMEDIATE_OFFSET + elsif hash & 0b111001001001 == 0b000000001001 + HALFWORD_DATA_TRANSFER_REGISTER_OFFSET + elsif hash & 0b111111111111 == 0b000100100001 + BRANCH_EXCHANGE + elsif hash & 0b111110111111 == 0b000100001001 + SINGLE_DATA_SWAP + elsif hash & 0b111110001111 == 0b000010001001 + MULTIPLY_LONG + elsif hash & 0b111111001111 == 0b000000001001 + MULTIPLY + elsif hash & 0b110000000000 == 0b000000000000 + DATA_PROCESSING_PSR_TRANSFER + else + raise "Unimplemented from_hash #{hex_str hash}" + end + end +end diff --git a/src/crab/util.cr b/src/crab/util.cr new file mode 100644 index 0000000..9351bd4 --- /dev/null +++ b/src/crab/util.cr @@ -0,0 +1,15 @@ +def hex_str(n : UInt8 | UInt16 | UInt32 | UInt64) : String + "0x#{n.to_s(16).rjust(sizeof(typeof(n)) * 2, '0').upcase}" +end + +def bit?(value : Int, bit : Int) : Bool + (value >> bit) & 1 > 0 +end + +def set_bit(value : Int, bit : Int) : Nil + value | 1 << bit +end + +def clear_bit(value : Int, bit : Int) : Nil + value & ~(1 << bit) +end