running PokemonEmerald causes a crash in the Scheduler's event.proc.call

This commit is contained in:
Matthew Berry 2021-02-05 19:41:02 -08:00
parent bc35157dca
commit ee80ede79f
22 changed files with 265 additions and 177 deletions

View file

@ -1,5 +1,6 @@
require "./arm/*" require "./arm/*"
require "./thumb/*" require "./thumb/*"
require "./thumb.cr"
require "./pipeline" require "./pipeline"
class CPU class CPU
@ -41,12 +42,12 @@ class CPU
num mode, 5 num mode, 5
end end
getter r = Slice(Word).new 16 getter r : Slice(Word) = Slice(Word).new 16
@cpsr : PSR = PSR.new CPU::Mode::SYS.value getter cpsr : PSR = PSR.new CPU::Mode::SYS.value
@spsr : PSR = PSR.new CPU::Mode::SYS.value getter spsr : PSR = PSR.new CPU::Mode::SYS.value
getter pipeline = Pipeline.new getter pipeline = Pipeline.new
getter lut : Slice(Proc(Word, Nil)) { fill_lut } getter lut : Slice(Proc(Word, Nil)) { fill_lut }
getter thumb_lut : Slice(Proc(Word, Nil)) { fill_thumb_lut } # getter thumb_lut : Slice(Proc(Word, Nil)) { fill_thumb_lut }
@reg_banks = Array(Array(Word)).new 6 { Array(Word).new 7, 0 } @reg_banks = Array(Array(Word)).new 6 { Array(Word).new 7, 0 }
@spsr_banks = Array(Word).new 6, CPU::Mode::SYS.value # logically independent of typical register banks @spsr_banks = Array(Word).new 6, CPU::Mode::SYS.value # logically independent of typical register banks
property halted = false property halted = false
@ -126,7 +127,7 @@ class CPU
instr = @pipeline.shift instr = @pipeline.shift
{% if flag? :trace %} print_state instr {% end %} {% if flag? :trace %} print_state instr {% end %}
if @cpsr.thumb if @cpsr.thumb
thumb_execute instr thumb_execute @gba, instr
else else
arm_execute instr arm_execute instr
end end

105
src/crab/thumb.cr Normal file
View file

@ -0,0 +1,105 @@
module THUMB
def thumb_execute(gba : GBA, instr : Word) : Nil
THUMB_LUT[instr >> 8].call gba, instr
end
macro meme
THUMB_LUT = Array(Proc(GBA, Word, Nil)).new 256, thumb_unimplemented
{% for idx in (0...256) %}
{% if idx & 0b11110000 == 0b11110000 %}
THUMB_LUT[{{idx}}] = thumb_long_branch_link
{% elsif idx & 0b11111000 == 0b11100000 %}
THUMB_LUT[{{idx}}] = thumb_unconditional_branch
{% elsif idx & 0b11111111 == 0b11011111 %}
THUMB_LUT[{{idx}}] = thumb_software_interrupt
{% elsif idx & 0b11110000 == 0b11010000 %}
THUMB_LUT[{{idx}}] = thumb_conditional_branch
{% elsif idx & 0b11110000 == 0b11000000 %}
THUMB_LUT[{{idx}}] = thumb_multiple_load_store
{% elsif idx & 0b11110110 == 0b10110100 %}
THUMB_LUT[{{idx}}] = thumb_push_pop_registers
{% elsif idx & 0b11111111 == 0b10110000 %}
THUMB_LUT[{{idx}}] = thumb_add_offset_to_stack_pointer
{% elsif idx & 0b11110000 == 0b10100000 %}
THUMB_LUT[{{idx}}] = thumb_load_address
{% elsif idx & 0b11110000 == 0b10010000 %}
THUMB_LUT[{{idx}}] = thumb_sp_relative_load_store
{% elsif idx & 0b11110000 == 0b10000000 %}
THUMB_LUT[{{idx}}] = thumb_load_store_halfword
{% elsif idx & 0b11100000 == 0b01100000 %}
THUMB_LUT[{{idx}}] = thumb_load_store_immediate_offset
{% elsif idx & 0b11110010 == 0b01010010 %}
THUMB_LUT[{{idx}}] = thumb_load_store_sign_extended
{% elsif idx & 0b11110010 == 0b01010000 %}
THUMB_LUT[{{idx}}] = thumb_load_store_register_offset
{% elsif idx & 0b11111000 == 0b01001000 %}
THUMB_LUT[{{idx}}] = thumb_pc_relative_load
{% elsif idx & 0b11111100 == 0b01000100 %}
THUMB_LUT[{{idx}}] = thumb_high_reg_branch_exchange
{% elsif idx & 0b11111100 == 0b01000000 %}
THUMB_LUT[{{idx}}] = thumb_alu_operations
{% elsif idx & 0b11100000 == 0b00100000 %}
THUMB_LUT[{{idx}}] = thumb_move_compare_add_subtract
{% elsif idx & 0b11111000 == 0b00011000 %}
THUMB_LUT[{{idx}}] = thumb_add_subtract
{% elsif idx & 0b11100000 == 0b00000000 %}
THUMB_LUT[{{idx}}] = thumb_move_shifted_register
{% end %}
{% end %}
end
meme
def fill_thumb_lut
lut = Slice(Proc(Word, Nil)).new 256, ->thumb_unimplemented(Word)
256.times do |idx|
if idx & 0b11110000 == 0b11110000
lut[idx] = ->thumb_long_branch_link(Word)
elsif idx & 0b11111000 == 0b11100000
lut[idx] = ->thumb_unconditional_branch(Word)
elsif idx & 0b11111111 == 0b11011111
lut[idx] = ->thumb_software_interrupt(Word)
elsif idx & 0b11110000 == 0b11010000
lut[idx] = ->thumb_conditional_branch(Word)
elsif idx & 0b11110000 == 0b11000000
lut[idx] = ->thumb_multiple_load_store(Word)
elsif idx & 0b11110110 == 0b10110100
lut[idx] = ->thumb_push_pop_registers(Word)
elsif idx & 0b11111111 == 0b10110000
lut[idx] = ->thumb_add_offset_to_stack_pointer(Word)
elsif idx & 0b11110000 == 0b10100000
lut[idx] = ->thumb_load_address(Word)
elsif idx & 0b11110000 == 0b10010000
lut[idx] = ->thumb_sp_relative_load_store(Word)
elsif idx & 0b11110000 == 0b10000000
lut[idx] = ->thumb_load_store_halfword(Word)
elsif idx & 0b11100000 == 0b01100000
lut[idx] = ->thumb_load_store_immediate_offset(Word)
elsif idx & 0b11110010 == 0b01010010
lut[idx] = ->thumb_load_store_sign_extended(Word)
elsif idx & 0b11110010 == 0b01010000
lut[idx] = ->thumb_load_store_register_offset(Word)
elsif idx & 0b11111000 == 0b01001000
lut[idx] = ->thumb_pc_relative_load(Word)
elsif idx & 0b11111100 == 0b01000100
lut[idx] = ->thumb_high_reg_branch_exchange(Word)
elsif idx & 0b11111100 == 0b01000000
lut[idx] = ->thumb_alu_operations(Word)
elsif idx & 0b11100000 == 0b00100000
lut[idx] = ->thumb_move_compare_add_subtract(Word)
elsif idx & 0b11111000 == 0b00011000
lut[idx] = ->thumb_add_subtract(Word)
elsif idx & 0b11100000 == 0b00000000
lut[idx] = ->thumb_move_shifted_register(Word)
end
end
lut
end
macro thumb_unimplemented
->(gba : GBA, instr : Word) {
puts "Unimplemented instruction: #{hex_str instr.to_u16}"
exit 1
}
end
end

View file

@ -1,11 +1,13 @@
module THUMB module THUMB
def thumb_add_offset_to_stack_pointer(instr : Word) : Nil macro thumb_add_offset_to_stack_pointer
->(gba : GBA, instr : Word) {
sign = bit?(instr, 7) sign = bit?(instr, 7)
offset = bits(instr, 0..6) offset = bits(instr, 0..6)
if sign # negative if sign # negative
set_reg(13, @r[13] &- (offset << 2)) gba.cpu.set_reg(13, gba.cpu.r[13] &- (offset << 2))
else # positive else # positive
set_reg(13, @r[13] &+ (offset << 2)) gba.cpu.set_reg(13, gba.cpu.r[13] &+ (offset << 2))
end end
}
end end
end end

View file

@ -1,5 +1,6 @@
module THUMB module THUMB
def thumb_add_subtract(instr : Word) : Nil macro thumb_add_subtract
->(gba : GBA, instr : Word) {
imm_flag = bit?(instr, 10) imm_flag = bit?(instr, 10)
sub = bit?(instr, 9) sub = bit?(instr, 9)
imm = bits(instr, 6..8) imm = bits(instr, 6..8)
@ -8,13 +9,14 @@ module THUMB
operand = if imm_flag operand = if imm_flag
imm imm
else else
@r[imm] gba.cpu.r[imm]
end end
if sub if sub
set_reg(rd, sub(@r[rs], operand, true)) gba.cpu.set_reg(rd, gba.cpu.sub(gba.cpu.r[rs], operand, true))
else else
set_reg(rd, add(@r[rs], operand, true)) gba.cpu.set_reg(rd, gba.cpu.add(gba.cpu.r[rs], operand, true))
end end
set_neg_and_zero_flags(@r[rd]) gba.cpu.set_neg_and_zero_flags(gba.cpu.r[rd])
}
end end
end end

View file

@ -1,36 +1,38 @@
module THUMB module THUMB
def thumb_alu_operations(instr : Word) : Nil macro thumb_alu_operations
->(gba : GBA, instr : Word) {
op = bits(instr, 6..9) op = bits(instr, 6..9)
rs = bits(instr, 3..5) rs = bits(instr, 3..5)
rd = bits(instr, 0..2) rd = bits(instr, 0..2)
barrel_shifter_carry_out = @cpsr.carry barrel_shifter_carry_out = gba.cpu.cpsr.carry
case op case op
when 0b0000 then res = set_reg(rd, @r[rd] & @r[rs]) when 0b0000 then res = gba.cpu.set_reg(rd, gba.cpu.r[rd] & gba.cpu.r[rs])
when 0b0001 then res = set_reg(rd, @r[rd] ^ @r[rs]) when 0b0001 then res = gba.cpu.set_reg(rd, gba.cpu.r[rd] ^ gba.cpu.r[rs])
when 0b0010 when 0b0010
res = set_reg(rd, lsl(@r[rd], @r[rs], pointerof(barrel_shifter_carry_out))) res = gba.cpu.set_reg(rd, gba.cpu.lsl(gba.cpu.r[rd], gba.cpu.r[rs], pointerof(barrel_shifter_carry_out)))
@cpsr.carry = barrel_shifter_carry_out gba.cpu.cpsr.carry = barrel_shifter_carry_out
when 0b0011 when 0b0011
res = set_reg(rd, lsr(@r[rd], @r[rs], false, pointerof(barrel_shifter_carry_out))) res = gba.cpu.set_reg(rd, gba.cpu.lsr(gba.cpu.r[rd], gba.cpu.r[rs], false, pointerof(barrel_shifter_carry_out)))
@cpsr.carry = barrel_shifter_carry_out gba.cpu.cpsr.carry = barrel_shifter_carry_out
when 0b0100 when 0b0100
res = set_reg(rd, asr(@r[rd], @r[rs], false, pointerof(barrel_shifter_carry_out))) res = gba.cpu.set_reg(rd, gba.cpu.asr(gba.cpu.r[rd], gba.cpu.r[rs], false, pointerof(barrel_shifter_carry_out)))
@cpsr.carry = barrel_shifter_carry_out gba.cpu.cpsr.carry = barrel_shifter_carry_out
when 0b0101 then res = set_reg(rd, adc(@r[rd], @r[rs], set_conditions: true)) when 0b0101 then res = gba.cpu.set_reg(rd, gba.cpu.adc(gba.cpu.r[rd], gba.cpu.r[rs], set_conditions: true))
when 0b0110 then res = set_reg(rd, sbc(@r[rd], @r[rs], set_conditions: true)) when 0b0110 then res = gba.cpu.set_reg(rd, gba.cpu.sbc(gba.cpu.r[rd], gba.cpu.r[rs], set_conditions: true))
when 0b0111 when 0b0111
res = set_reg(rd, ror(@r[rd], @r[rs], false, pointerof(barrel_shifter_carry_out))) res = gba.cpu.set_reg(rd, gba.cpu.ror(gba.cpu.r[rd], gba.cpu.r[rs], false, pointerof(barrel_shifter_carry_out)))
@cpsr.carry = barrel_shifter_carry_out gba.cpu.cpsr.carry = barrel_shifter_carry_out
when 0b1000 then res = @r[rd] & @r[rs] when 0b1000 then res = gba.cpu.r[rd] & gba.cpu.r[rs]
when 0b1001 then res = set_reg(rd, sub(0, @r[rs], set_conditions: true)) when 0b1001 then res = gba.cpu.set_reg(rd, gba.cpu.sub(0, gba.cpu.r[rs], set_conditions: true))
when 0b1010 then res = sub(@r[rd], @r[rs], set_conditions: true) when 0b1010 then res = gba.cpu.sub(gba.cpu.r[rd], gba.cpu.r[rs], set_conditions: true)
when 0b1011 then res = add(@r[rd], @r[rs], set_conditions: true) when 0b1011 then res = gba.cpu.add(gba.cpu.r[rd], gba.cpu.r[rs], set_conditions: true)
when 0b1100 then res = set_reg(rd, @r[rd] | @r[rs]) when 0b1100 then res = gba.cpu.set_reg(rd, gba.cpu.r[rd] | gba.cpu.r[rs])
when 0b1101 then res = set_reg(rd, @r[rs] &* @r[rd]) when 0b1101 then res = gba.cpu.set_reg(rd, gba.cpu.r[rs] &* gba.cpu.r[rd])
when 0b1110 then res = set_reg(rd, @r[rd] & ~@r[rs]) when 0b1110 then res = gba.cpu.set_reg(rd, gba.cpu.r[rd] & ~gba.cpu.r[rs])
when 0b1111 then res = set_reg(rd, ~@r[rs]) when 0b1111 then res = gba.cpu.set_reg(rd, ~gba.cpu.r[rs])
else raise "Invalid alu op: #{op}" else raise "Invalid alu op: #{op}"
end end
set_neg_and_zero_flags(res) gba.cpu.set_neg_and_zero_flags(res)
}
end end
end end

View file

@ -1,9 +1,11 @@
module THUMB module THUMB
def thumb_conditional_branch(instr : Word) : Nil macro thumb_conditional_branch
->(gba : GBA, instr : Word) {
cond = bits(instr, 8..11) cond = bits(instr, 8..11)
offset = bits(instr, 0..7).to_i8!.to_i32 offset = bits(instr, 0..7).to_i8!.to_i32
if check_cond cond if gba.cpu.check_cond cond
set_reg(15, @r[15] &+ (offset * 2)) gba.cpu.set_reg(15, gba.cpu.r[15] &+ (offset * 2))
end end
}
end end
end end

View file

@ -1,5 +1,6 @@
module THUMB module THUMB
def thumb_high_reg_branch_exchange(instr : Word) : Nil macro thumb_high_reg_branch_exchange
->(gba : GBA, instr : Word) {
op = bits(instr, 8..9) op = bits(instr, 8..9)
h1 = bit?(instr, 7) h1 = bit?(instr, 7)
h2 = bit?(instr, 6) h2 = bit?(instr, 6)
@ -11,16 +12,17 @@ module THUMB
# In this group only CMP (Op = 01) sets the CPSR condition codes. # In this group only CMP (Op = 01) sets the CPSR condition codes.
case op case op
when 0b00 then set_reg(rd, add(@r[rd], @r[rs], false)) when 0b00 then gba.cpu.set_reg(rd, gba.cpu.add(gba.cpu.r[rd], gba.cpu.r[rs], false))
when 0b01 then sub(@r[rd], @r[rs], true) when 0b01 then gba.cpu.sub(gba.cpu.r[rd], gba.cpu.r[rs], true)
when 0b10 then set_reg(rd, @r[rs]) when 0b10 then gba.cpu.set_reg(rd, gba.cpu.r[rs])
when 0b11 when 0b11
if bit?(@r[rs], 0) if bit?(gba.cpu.r[rs], 0)
set_reg(15, @r[rs]) gba.cpu.set_reg(15, gba.cpu.r[rs])
else else
@cpsr.thumb = false gba.cpu.cpsr.thumb = false
set_reg(15, @r[rs]) gba.cpu.set_reg(15, gba.cpu.r[rs])
end end
end end
}
end end
end end

View file

@ -1,10 +1,12 @@
module THUMB module THUMB
def thumb_load_address(instr : Word) : Nil macro thumb_load_address
->(gba : GBA, instr : Word) {
source = bit?(instr, 11) source = bit?(instr, 11)
rd = bits(instr, 8..10) rd = bits(instr, 8..10)
word = bits(instr, 0..7) word = bits(instr, 0..7)
imm = word << 2 imm = word << 2
# Where the PC is used as the source register (SP = 0), bit 1 of the PC is always read as 0. # Where the PC is used as the source register (SP = 0), bit 1 of the PC is always read as 0.
set_reg(rd, (source ? @r[13] : @r[15] & ~2) &+ imm) gba.cpu.set_reg(rd, (source ? gba.cpu.r[13] : gba.cpu.r[15] & ~2) &+ imm)
}
end end
end end

View file

@ -1,14 +1,16 @@
module THUMB module THUMB
def thumb_load_store_halfword(instr : Word) : Nil macro thumb_load_store_halfword
->(gba : GBA, instr : Word) {
load = bit?(instr, 11) load = bit?(instr, 11)
offset = bits(instr, 6..10) offset = bits(instr, 6..10)
rb = bits(instr, 3..5) rb = bits(instr, 3..5)
rd = bits(instr, 0..2) rd = bits(instr, 0..2)
address = @r[rb] + (offset << 1) address = gba.cpu.r[rb] + (offset << 1)
if load if load
set_reg(rd, @gba.bus.read_half_rotate(address)) gba.cpu.set_reg(rd, gba.bus.read_half_rotate(address))
else else
@gba.bus[address] = @r[rd].to_u16! gba.bus[address] = gba.cpu.r[rd].to_u16!
end end
}
end end
end end

View file

@ -1,15 +1,17 @@
module THUMB module THUMB
def thumb_load_store_immediate_offset(instr : Word) : Nil macro thumb_load_store_immediate_offset
->(gba : GBA, instr : Word) {
byte_quantity_and_load = bits(instr, 11..12) byte_quantity_and_load = bits(instr, 11..12)
offset = bits(instr, 6..10) offset = bits(instr, 6..10)
rb = bits(instr, 3..5) rb = bits(instr, 3..5)
rd = bits(instr, 0..2) rd = bits(instr, 0..2)
base_address = @r[rb] base_address = gba.cpu.r[rb]
case byte_quantity_and_load case byte_quantity_and_load
when 0b00 then @gba.bus[base_address &+ (offset << 2)] = @r[rd] # str when 0b00 then gba.bus[base_address &+ (offset << 2)] = gba.cpu.r[rd] # str
when 0b01 then set_reg(rd, @gba.bus.read_word_rotate(base_address &+ (offset << 2))) # ldr when 0b01 then gba.cpu.set_reg(rd, gba.bus.read_word_rotate(base_address &+ (offset << 2))) # ldr
when 0b10 then @gba.bus[base_address &+ offset] = @r[rd].to_u8! # strb when 0b10 then gba.bus[base_address &+ offset] = gba.cpu.r[rd].to_u8! # strb
when 0b11 then set_reg(rd, @gba.bus[base_address &+ offset].to_u32) # ldrb when 0b11 then gba.cpu.set_reg(rd, gba.bus[base_address &+ offset].to_u32) # ldrb
end end
}
end end
end end

View file

@ -1,15 +1,17 @@
module THUMB module THUMB
def thumb_load_store_register_offset(instr : Word) : Nil macro thumb_load_store_register_offset
->(gba : GBA, instr : Word) {
load_and_byte_quantity = bits(instr, 10..11) load_and_byte_quantity = bits(instr, 10..11)
ro = bits(instr, 6..8) ro = bits(instr, 6..8)
rb = bits(instr, 3..5) rb = bits(instr, 3..5)
rd = bits(instr, 0..2) rd = bits(instr, 0..2)
address = @r[rb] &+ @r[ro] address = gba.cpu.r[rb] &+ gba.cpu.r[ro]
case load_and_byte_quantity case load_and_byte_quantity
when 0b00 then @gba.bus[address] = @r[rd] # str when 0b00 then gba.bus[address] = gba.cpu.r[rd] # str
when 0b01 then @gba.bus[address] = @r[rd].to_u8! # strb when 0b01 then gba.bus[address] = gba.cpu.r[rd].to_u8! # strb
when 0b10 then set_reg(rd, @gba.bus.read_word_rotate(address)) # ldr when 0b10 then gba.cpu.set_reg(rd, gba.bus.read_word_rotate(address)) # ldr
when 0b11 then set_reg(rd, @gba.bus[address].to_u32!) # ldrb when 0b11 then gba.cpu.set_reg(rd, gba.bus[address].to_u32!) # ldrb
end end
}
end end
end end

View file

@ -1,16 +1,18 @@
module THUMB module THUMB
def thumb_load_store_sign_extended(instr : Word) : Nil macro thumb_load_store_sign_extended
->(gba : GBA, instr : Word) {
hs = bits(instr, 10..11) hs = bits(instr, 10..11)
ro = bits(instr, 6..8) ro = bits(instr, 6..8)
rb = bits(instr, 3..5) rb = bits(instr, 3..5)
rd = bits(instr, 0..2) rd = bits(instr, 0..2)
address = @r[rb] &+ @r[ro] address = gba.cpu.r[rb] &+ gba.cpu.r[ro]
case hs case hs
when 0b00 then @gba.bus[address] = @r[rd].to_u16! # strh when 0b00 then gba.bus[address] = gba.cpu.r[rd].to_u16! # strh
when 0b01 then set_reg(rd, @gba.bus[address].to_i8!.to_u32!) # ldsb when 0b01 then gba.cpu.set_reg(rd, gba.bus[address].to_i8!.to_u32!) # ldsb
when 0b10 then set_reg(rd, @gba.bus.read_half_rotate(address)) # ldrh when 0b10 then gba.cpu.set_reg(rd, gba.bus.read_half_rotate(address)) # ldrh
when 0b11 then set_reg(rd, @gba.bus.read_half_signed(address)) # ldsh when 0b11 then gba.cpu.set_reg(rd, gba.bus.read_half_signed(address)) # ldsh
else raise "Invalid load/store signed extended: #{hs}" else raise "Invalid load/store signed extended: #{hs}"
end end
}
end end
end end

View file

@ -1,14 +1,16 @@
module THUMB module THUMB
def thumb_long_branch_link(instr : Word) : Nil macro thumb_long_branch_link
->(gba : GBA, instr : Word) {
second_instr = bit?(instr, 11) second_instr = bit?(instr, 11)
offset = bits(instr, 0..10) offset = bits(instr, 0..10)
if second_instr if second_instr
temp = @r[15] &- 2 temp = gba.cpu.r[15] &- 2
set_reg(15, @r[14] &+ (offset << 1)) gba.cpu.set_reg(15, gba.cpu.r[14] &+ (offset << 1))
set_reg(14, temp | 1) gba.cpu.set_reg(14, temp | 1)
else else
offset = (offset << 5).to_i16! >> 5 offset = (offset << 5).to_i16! >> 5
set_reg(14, @r[15] &+ (offset.to_u32! << 12)) gba.cpu.set_reg(14, gba.cpu.r[15] &+ (offset.to_u32! << 12))
end end
}
end end
end end

View file

@ -1,16 +1,18 @@
module THUMB module THUMB
def thumb_move_compare_add_subtract(instr : Word) : Nil macro thumb_move_compare_add_subtract
->(gba : GBA, instr : Word) {
op = bits(instr, 11..12) op = bits(instr, 11..12)
rd = bits(instr, 8..10) rd = bits(instr, 8..10)
offset = bits(instr, 0..7) offset = bits(instr, 0..7)
case op case op
when 0b00 when 0b00
set_reg(rd, offset) gba.cpu.set_reg(rd, offset)
set_neg_and_zero_flags(@r[rd]) gba.cpu.set_neg_and_zero_flags(gba.cpu.r[rd])
when 0b01 then sub(@r[rd], offset, true) when 0b01 then gba.cpu.sub(gba.cpu.r[rd], offset, true)
when 0b10 then set_reg(rd, add(@r[rd], offset, true)) when 0b10 then gba.cpu.set_reg(rd, gba.cpu.add(gba.cpu.r[rd], offset, true))
when 0b11 then set_reg(rd, sub(@r[rd], offset, true)) when 0b11 then gba.cpu.set_reg(rd, gba.cpu.sub(gba.cpu.r[rd], offset, true))
else raise "Invalid move/compare/add/subtract op: #{op}" else raise "Invalid move/compare/gba.cpu.add/gba.cpu.subtract op: #{op}"
end end
}
end end
end end

View file

@ -1,17 +1,19 @@
module THUMB module THUMB
def thumb_move_shifted_register(instr : Word) : Nil macro thumb_move_shifted_register
->(gba : GBA, instr : Word) {
op = bits(instr, 11..12) op = bits(instr, 11..12)
offset = bits(instr, 6..10) offset = bits(instr, 6..10)
rs = bits(instr, 3..5) rs = bits(instr, 3..5)
rd = bits(instr, 0..2) rd = bits(instr, 0..2)
carry_out = @cpsr.carry carry_out = gba.cpu.cpsr.carry
case op case op
when 0b00 then set_reg(rd, lsl(@r[rs], offset, pointerof(carry_out))) when 0b00 then gba.cpu.set_reg(rd, gba.cpu.lsl(gba.cpu.r[rs], offset, pointerof(carry_out)))
when 0b01 then set_reg(rd, lsr(@r[rs], offset, true, pointerof(carry_out))) when 0b01 then gba.cpu.set_reg(rd, gba.cpu.lsr(gba.cpu.r[rs], offset, true, pointerof(carry_out)))
when 0b10 then set_reg(rd, asr(@r[rs], offset, true, pointerof(carry_out))) when 0b10 then gba.cpu.set_reg(rd, gba.cpu.asr(gba.cpu.r[rs], offset, true, pointerof(carry_out)))
else raise "Invalid shifted register op: #{op}" else raise "Invalid shifted register op: #{op}"
end end
set_neg_and_zero_flags(@r[rd]) gba.cpu.set_neg_and_zero_flags(gba.cpu.r[rd])
@cpsr.carry = carry_out gba.cpu.cpsr.carry = carry_out
}
end end
end end

View file

@ -1,14 +1,15 @@
module THUMB module THUMB
def thumb_multiple_load_store(instr : Word) : Nil macro thumb_multiple_load_store
->(gba : GBA, instr : Word) {
load = bit?(instr, 11) load = bit?(instr, 11)
rb = bits(instr, 8..10) rb = bits(instr, 8..10)
list = bits(instr, 0..7) list = bits(instr, 0..7)
address = @r[rb] address = gba.cpu.r[rb]
unless list == 0 unless list == 0
if load # ldmia if load # ldmia
8.times do |idx| 8.times do |idx|
if bit?(list, idx) if bit?(list, idx)
set_reg(idx, @gba.bus.read_word(address)) gba.cpu.set_reg(idx, gba.bus.read_word(address))
address &+= 4 address &+= 4
end end
end end
@ -16,21 +17,22 @@ module THUMB
base_addr = nil base_addr = nil
8.times do |idx| 8.times do |idx|
if bit?(list, idx) if bit?(list, idx)
@gba.bus[address] = @r[idx] gba.bus[address] = gba.cpu.r[idx]
base_addr = address if rb == idx base_addr = address if rb == idx
address &+= 4 address &+= 4
end end
end end
@gba.bus[base_addr] = address if base_addr && first_set_bit(list) != rb # rb is written after first store gba.bus[base_addr] = address if base_addr && first_set_bit(list) != rb # rb is written after first store
end end
set_reg(rb, address) gba.cpu.set_reg(rb, address)
else # https://github.com/jsmolka/gba-suite/blob/0e32e15c6241e6dc20851563ba88f4656ac50936/thumb/memory.asm#L459 else # https://github.com/jsmolka/gba-suite/blob/0e32e15c6241e6dc20851563ba88f4656ac50936/thumb/memory.asm#L459
if load if load
set_reg(15, @gba.bus.read_word(address)) gba.cpu.set_reg(15, gba.bus.read_word(address))
else else
@gba.bus[address] = @r[15] &+ 2 gba.bus[address] = gba.cpu.r[15] &+ 2
end end
set_reg(rb, address &+ 0x40) gba.cpu.set_reg(rb, address &+ 0x40)
end end
}
end end
end end

View file

@ -1,7 +1,9 @@
module THUMB module THUMB
def thumb_pc_relative_load(instr : Word) : Nil macro thumb_pc_relative_load
->(gba : GBA, instr : Word) {
imm = bits(instr, 0..7) imm = bits(instr, 0..7)
rd = bits(instr, 8..10) rd = bits(instr, 8..10)
set_reg(rd, @gba.bus.read_word((@r[15] & ~2) &+ (imm << 2))) gba.cpu.set_reg(rd, gba.bus.read_word((gba.cpu.r[15] & ~2) &+ (imm << 2)))
}
end end
end end

View file

@ -1,32 +1,34 @@
module THUMB module THUMB
def thumb_push_pop_registers(instr : Word) : Nil macro thumb_push_pop_registers
->(gba : GBA, instr : Word) {
pop = bit?(instr, 11) pop = bit?(instr, 11)
pclr = bit?(instr, 8) pclr = bit?(instr, 8)
list = bits(instr, 0..7) list = bits(instr, 0..7)
address = @r[13] address = gba.cpu.r[13]
if pop if pop
8.times do |idx| 8.times do |idx|
if bit?(list, idx) if bit?(list, idx)
set_reg(idx, @gba.bus.read_word(address)) gba.cpu.set_reg(idx, gba.bus.read_word(address))
address &+= 4 address &+= 4
end end
end end
if pclr if pclr
set_reg(15, @gba.bus.read_word(address)) gba.cpu.set_reg(15, gba.bus.read_word(address))
address &+= 4 address &+= 4
end end
else else
if pclr if pclr
address &-= 4 address &-= 4
@gba.bus[address] = @r[14] gba.bus[address] = gba.cpu.r[14]
end end
7.downto(0).each do |idx| 7.downto(0).each do |idx|
if bit?(list, idx) if bit?(list, idx)
address &-= 4 address &-= 4
@gba.bus[address] = @r[idx] gba.bus[address] = gba.cpu.r[idx]
end end
end end
end end
set_reg(13, address) gba.cpu.set_reg(13, address)
}
end end
end end

View file

@ -1,10 +1,12 @@
module THUMB module THUMB
def thumb_software_interrupt(instr : Word) : Nil macro thumb_software_interrupt
lr = @r[15] - 2 ->(gba : GBA, instr : Word) {
switch_mode CPU::Mode::SVC lr = gba.cpu.r[15] - 2
set_reg(14, lr) gba.cpu.switch_mode CPU::Mode::SVC
@cpsr.irq_disable = true gba.cpu.set_reg(14, lr)
@cpsr.thumb = false gba.cpu.cpsr.irq_disable = true
set_reg(15, 0x08) gba.cpu.cpsr.thumb = false
gba.cpu.set_reg(15, 0x08)
}
end end
end end

View file

@ -1,13 +1,15 @@
module THUMB module THUMB
def thumb_sp_relative_load_store(instr : Word) : Nil macro thumb_sp_relative_load_store
->(gba : GBA, instr : Word) {
load = bit?(instr, 11) load = bit?(instr, 11)
rd = bits(instr, 8..10) rd = bits(instr, 8..10)
word = bits(instr, 0..7) word = bits(instr, 0..7)
address = @r[13] &+ (word << 2) address = gba.cpu.r[13] &+ (word << 2)
if load if load
set_reg(rd, @gba.bus.read_word_rotate(address)) gba.cpu.set_reg(rd, gba.bus.read_word_rotate(address))
else else
@gba.bus[address] = @r[rd] gba.bus[address] = gba.cpu.r[rd]
end end
}
end end
end end

View file

@ -1,56 +0,0 @@
module THUMB
def thumb_execute(instr : Word) : Nil
thumb_lut[instr >> 8].call instr
end
def fill_thumb_lut
lut = Slice(Proc(Word, Nil)).new 256, ->thumb_unimplemented(Word)
256.times do |idx|
if idx & 0b11110000 == 0b11110000
lut[idx] = ->thumb_long_branch_link(Word)
elsif idx & 0b11111000 == 0b11100000
lut[idx] = ->thumb_unconditional_branch(Word)
elsif idx & 0b11111111 == 0b11011111
lut[idx] = ->thumb_software_interrupt(Word)
elsif idx & 0b11110000 == 0b11010000
lut[idx] = ->thumb_conditional_branch(Word)
elsif idx & 0b11110000 == 0b11000000
lut[idx] = ->thumb_multiple_load_store(Word)
elsif idx & 0b11110110 == 0b10110100
lut[idx] = ->thumb_push_pop_registers(Word)
elsif idx & 0b11111111 == 0b10110000
lut[idx] = ->thumb_add_offset_to_stack_pointer(Word)
elsif idx & 0b11110000 == 0b10100000
lut[idx] = ->thumb_load_address(Word)
elsif idx & 0b11110000 == 0b10010000
lut[idx] = ->thumb_sp_relative_load_store(Word)
elsif idx & 0b11110000 == 0b10000000
lut[idx] = ->thumb_load_store_halfword(Word)
elsif idx & 0b11100000 == 0b01100000
lut[idx] = ->thumb_load_store_immediate_offset(Word)
elsif idx & 0b11110010 == 0b01010010
lut[idx] = ->thumb_load_store_sign_extended(Word)
elsif idx & 0b11110010 == 0b01010000
lut[idx] = ->thumb_load_store_register_offset(Word)
elsif idx & 0b11111000 == 0b01001000
lut[idx] = ->thumb_pc_relative_load(Word)
elsif idx & 0b11111100 == 0b01000100
lut[idx] = ->thumb_high_reg_branch_exchange(Word)
elsif idx & 0b11111100 == 0b01000000
lut[idx] = ->thumb_alu_operations(Word)
elsif idx & 0b11100000 == 0b00100000
lut[idx] = ->thumb_move_compare_add_subtract(Word)
elsif idx & 0b11111000 == 0b00011000
lut[idx] = ->thumb_add_subtract(Word)
elsif idx & 0b11100000 == 0b00000000
lut[idx] = ->thumb_move_shifted_register(Word)
end
end
lut
end
def thumb_unimplemented(instr : Word) : Nil
puts "Unimplemented instruction: #{hex_str instr.to_u16}"
exit 1
end
end

View file

@ -1,7 +1,9 @@
module THUMB module THUMB
def thumb_unconditional_branch(instr : Word) : Nil macro thumb_unconditional_branch
->(gba : GBA, instr : Word) {
offset = bits(instr, 0..10) offset = bits(instr, 0..10)
offset = (offset << 5).to_i16! >> 4 offset = (offset << 5).to_i16! >> 4
set_reg(15, @r[15] &+ offset) gba.cpu.set_reg(15, gba.cpu.r[15] &+ offset)
}
end end
end end