mirror of
https://github.com/mlaux/gb6.git
synced 2026-04-19 17:16:48 +00:00
move timing functions to their own file, LY wait on registers too
This commit is contained in:
+33
-96
@@ -11,6 +11,7 @@
|
||||
#include "alu.h"
|
||||
#include "stack.h"
|
||||
#include "instructions.h"
|
||||
#include "timing.h"
|
||||
|
||||
// helper for reading GB memory during compilation
|
||||
#define READ_BYTE(off) (ctx->read(ctx->dmg, src_address + (off)))
|
||||
@@ -91,89 +92,6 @@ static void compile_ld_imm16_contiguous(
|
||||
emit_movea_w_imm16(block, reg, hibyte << 8 | lobyte);
|
||||
}
|
||||
|
||||
// synthesize wait for LY to reach target value
|
||||
// detects ldh a, [$44]; cp N; jr cc, back
|
||||
static void compile_ly_wait(
|
||||
struct code_block *block,
|
||||
uint8_t target_ly,
|
||||
uint8_t jr_opcode,
|
||||
uint16_t next_pc
|
||||
) {
|
||||
// jr nz (0x20): loop while LY != N, exit when LY == N -> wait for N
|
||||
// jr z (0x28): loop while LY == N, exit when LY != N -> wait for N+1
|
||||
// jr c (0x38): loop while LY < N, exit when LY >= N -> wait for N
|
||||
uint8_t wait_ly = target_ly;
|
||||
if (jr_opcode == 0x28) {
|
||||
wait_ly = (target_ly + 1) % 154;
|
||||
}
|
||||
|
||||
uint32_t target_cycles = wait_ly * 456;
|
||||
|
||||
// load frame_cycles pointer
|
||||
emit_movea_l_disp_an_an(block, JIT_CTX_FRAME_CYCLES_PTR, REG_68K_A_CTX, REG_68K_A_SCRATCH_1);
|
||||
// load frame_cycles into d0
|
||||
emit_move_l_ind_an_dn(block, REG_68K_A_SCRATCH_1, REG_68K_D_SCRATCH_0);
|
||||
|
||||
// compare frame_cycles to target
|
||||
emit_cmpi_l_imm_dn(block, target_cycles, REG_68K_D_SCRATCH_0);
|
||||
emit_bcc_s(block, 10); // if frame_cycles >= target, wait until next frame
|
||||
|
||||
// same frame: d2 = target - frame_cycles
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, target_cycles);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
emit_bra_b(block, 8);
|
||||
|
||||
// next frame: d2 = (70224 + target) - frame_cycles
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, 70224 + target_cycles);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
|
||||
// set A to the LY value we waited for
|
||||
emit_moveq_dn(block, REG_68K_D_A, wait_ly);
|
||||
|
||||
// exit to C
|
||||
emit_move_l_dn(block, REG_68K_D_NEXT_PC, next_pc);
|
||||
emit_rts(block);
|
||||
}
|
||||
|
||||
// only good for vblank interrupts for now...
|
||||
static void compile_halt(struct code_block *block, int next_pc)
|
||||
{
|
||||
// movea.l JIT_CTX_FRAME_CYCLES_PTR(a4), a0 ; 4 bytes
|
||||
// move.l (a0), d0 ; 2 bytes
|
||||
// cmpi.l #65664, d0 ; 6 bytes
|
||||
// bcc.s _frame_end ; 2 bytes
|
||||
// move.l #65664, d2 ; 6 bytes
|
||||
// sub.l d0, d2 ; 2 bytes
|
||||
// bra.s _exit ; 2 bytes
|
||||
// _frame_end
|
||||
// move.l #70224, d2 ; 6 bytes
|
||||
// sub.l d0, d2 ; 2 bytes
|
||||
// _exit
|
||||
// move.l #next_pc, d3 ; 6 bytes
|
||||
// rts ; 2 bytes
|
||||
|
||||
// load frame_cycles pointer
|
||||
emit_movea_l_disp_an_an(block, JIT_CTX_FRAME_CYCLES_PTR, REG_68K_A_CTX, REG_68K_A_SCRATCH_1);
|
||||
emit_move_l_ind_an_dn(block, REG_68K_A_SCRATCH_1, REG_68K_D_SCRATCH_0);
|
||||
|
||||
// see if already in vblank
|
||||
emit_cmpi_l_imm_dn(block, 65664, REG_68K_D_SCRATCH_0);
|
||||
emit_bcc_s(block, 10);
|
||||
|
||||
// before vblank: d2 = 65664 - frame_cycles
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, 65664);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
emit_bra_b(block, 8);
|
||||
|
||||
// in vblank: d2 = (70224 - frame_cycles) + 65664
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, 70224 + 65664);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
|
||||
// exit to C
|
||||
emit_move_l_dn(block, REG_68K_D_NEXT_PC, next_pc);
|
||||
emit_rts(block);
|
||||
}
|
||||
|
||||
struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
|
||||
{
|
||||
struct code_block *block;
|
||||
@@ -610,20 +528,39 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
|
||||
// check for LY polling loop
|
||||
if (addr == 0x44) {
|
||||
uint8_t next0 = READ_BYTE(src_ptr);
|
||||
uint8_t target_ly = READ_BYTE(src_ptr + 1);
|
||||
uint8_t jr_op = READ_BYTE(src_ptr + 2);
|
||||
int8_t offset = (int8_t) READ_BYTE(src_ptr + 3);
|
||||
|
||||
if (next0 == 0xfe && // cp imm8
|
||||
// jr z, jr nz, jr c
|
||||
(jr_op == 0x20 || jr_op == 0x28 || jr_op == 0x38) &&
|
||||
offset < 0) {
|
||||
// detected polling loop - synthesize wait
|
||||
uint16_t next_pc = src_address + src_ptr + 4;
|
||||
compile_ly_wait(block, target_ly, jr_op, next_pc);
|
||||
src_ptr += 4;
|
||||
done = 1;
|
||||
break;
|
||||
// check for cp imm8 pattern
|
||||
if (next0 == 0xfe) {
|
||||
uint8_t target_ly = READ_BYTE(src_ptr + 1);
|
||||
uint8_t jr_op = READ_BYTE(src_ptr + 2);
|
||||
int8_t offset = (int8_t) READ_BYTE(src_ptr + 3);
|
||||
|
||||
if ((jr_op == 0x20 || jr_op == 0x28 || jr_op == 0x38) &&
|
||||
offset < 0) {
|
||||
// detected polling loop - synthesize wait
|
||||
uint16_t next_pc = src_address + src_ptr + 4;
|
||||
compile_ly_wait(block, target_ly, jr_op, next_pc);
|
||||
src_ptr += 4;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for cp r pattern (0xb8-0xbe = cp B,C,D,E,H,L,(HL))
|
||||
if (next0 >= 0xb8 && next0 <= 0xbe) {
|
||||
int gb_reg = next0 - 0xb8;
|
||||
uint8_t jr_op = READ_BYTE(src_ptr + 1);
|
||||
int8_t offset = (int8_t) READ_BYTE(src_ptr + 2);
|
||||
|
||||
if ((jr_op == 0x20 || jr_op == 0x28 || jr_op == 0x38) &&
|
||||
offset < 0) {
|
||||
// detected polling loop with register compare
|
||||
uint16_t next_pc = src_address + src_ptr + 3;
|
||||
compile_ly_wait_reg(block, gb_reg, jr_op, next_pc);
|
||||
src_ptr += 3;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1154,6 +1154,22 @@ void emit_sub_l_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
|
||||
emit_word(block, 0x9080 | (dest << 9) | src);
|
||||
}
|
||||
|
||||
// mulu.w #imm, Dn
|
||||
void emit_mulu_w_imm_dn(struct code_block *block, uint16_t imm, uint8_t dreg)
|
||||
{
|
||||
// MULU <ea>,Dn: 1100 ddd 011 <ea>
|
||||
// immediate mode: <ea> = 111 100
|
||||
emit_word(block, 0xc0fc | (dreg << 9));
|
||||
emit_word(block, imm);
|
||||
}
|
||||
|
||||
// cmp.l Dn,Dn (src - dest comparison, sets flags)
|
||||
void emit_cmp_l_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
|
||||
{
|
||||
// CMP.L Dn,Dn: 1011 dest 010 000 src
|
||||
emit_word(block, 0xb080 | (dest << 9) | src);
|
||||
}
|
||||
|
||||
// emit_add_cycles - add GB cycles to context, picks optimal instruction
|
||||
void emit_add_cycles(struct code_block *block, int cycles)
|
||||
{
|
||||
|
||||
@@ -139,5 +139,7 @@ void emit_sub_l_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
|
||||
void emit_add_cycles(struct code_block *block, int cycles);
|
||||
void emit_move_sr_dn(struct code_block *block, uint8_t dreg);
|
||||
void emit_move_dn_ccr(struct code_block *block, uint8_t dreg);
|
||||
void emit_mulu_w_imm_dn(struct code_block *block, uint16_t imm, uint8_t dreg);
|
||||
void emit_cmp_l_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,89 +3,83 @@
|
||||
// ============================================================================
|
||||
// HALT instruction tests
|
||||
// HALT waits until vblank interrupt (LY 144, cycle 65664)
|
||||
// Note: HALT's own 4 cycles are added to D2 before skip calculation,
|
||||
// so true_pos = frame_cycles + 4
|
||||
// ============================================================================
|
||||
|
||||
TEST(test_halt_before_vblank)
|
||||
{
|
||||
// HALT when frame_cycles=0, true_pos=4, skip = 65664-4 = 65660
|
||||
// HALT when frame_cycles=0 should wait 65664 cycles to reach vblank
|
||||
uint8_t rom[] = {
|
||||
0x76 // halt
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_cycle_count(), 65664 - 4);
|
||||
ASSERT_EQ(get_cycle_count(), 65664);
|
||||
}
|
||||
|
||||
TEST(test_halt_mid_frame)
|
||||
{
|
||||
// HALT at frame_cycles=10000, true_pos=10004, skip = 65664-10004 = 55660
|
||||
// HALT at frame_cycles=10000 should wait 55664 cycles (65664-10000)
|
||||
uint8_t rom[] = {
|
||||
0x76 // halt
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 10000);
|
||||
ASSERT_EQ(get_cycle_count(), 65664 - 10000 - 4);
|
||||
ASSERT_EQ(get_cycle_count(), 65664 - 10000);
|
||||
}
|
||||
|
||||
TEST(test_halt_just_before_vblank)
|
||||
{
|
||||
// HALT at frame_cycles=65659, true_pos=65663, skip = 1
|
||||
// HALT at frame_cycles=65663 should wait 1 cycle
|
||||
uint8_t rom[] = {
|
||||
0x76 // halt
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 65659);
|
||||
run_block_with_frame_cycles(rom, 65663);
|
||||
ASSERT_EQ(get_cycle_count(), 1);
|
||||
}
|
||||
|
||||
TEST(test_halt_at_vblank_start)
|
||||
{
|
||||
// HALT at frame_cycles=65660, true_pos=65664 (exactly at vblank)
|
||||
// In vblank path: skip = 135888 - 65664 = 70224
|
||||
// HALT at exactly cycle 65664 (vblank start) should wait until next frame
|
||||
// cycles = (70224 + 65664) - 65664 = 70224
|
||||
uint8_t rom[] = {
|
||||
0x76 // halt
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 65660);
|
||||
run_block_with_frame_cycles(rom, 65664);
|
||||
ASSERT_EQ(get_cycle_count(), 70224);
|
||||
}
|
||||
|
||||
TEST(test_halt_during_vblank)
|
||||
{
|
||||
// HALT at frame_cycles=68000, true_pos=68004 (in vblank)
|
||||
// skip = 135888 - 68004 = 67884
|
||||
// HALT at frame_cycles=68000 (in vblank) should wait until next frame vblank
|
||||
// cycles = (70224 + 65664) - 68000 = 135888 - 68000 = 67888
|
||||
uint8_t rom[] = {
|
||||
0x76 // halt
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 68000);
|
||||
ASSERT_EQ(get_cycle_count(), 135888 - 68000 - 4);
|
||||
ASSERT_EQ(get_cycle_count(), 135888 - 68000);
|
||||
}
|
||||
|
||||
TEST(test_halt_near_frame_end)
|
||||
{
|
||||
// HALT at frame_cycles=70000, true_pos=70004 (near frame end)
|
||||
// skip = 135888 - 70004 = 65884
|
||||
// HALT at frame_cycles=70000 should wait until next frame vblank
|
||||
// cycles = (70224 + 65664) - 70000 = 65888
|
||||
uint8_t rom[] = {
|
||||
0x76 // halt
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 70000);
|
||||
ASSERT_EQ(get_cycle_count(), 135888 - 70000 - 4);
|
||||
ASSERT_EQ(get_cycle_count(), 135888 - 70000);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LY wait pattern tests
|
||||
// Pattern: ldh a, [$44]; cp N; jr cc, back
|
||||
// Compiler synthesizes a wait instead of spinning in a loop
|
||||
// Note: The initial ld's cycles are added to D2
|
||||
// before skip calculation, so true_pos = frame_cycles + 12
|
||||
// ============================================================================
|
||||
|
||||
#define LY_WAIT_CYCLES 12
|
||||
|
||||
TEST(test_ly_wait_jr_nz_ly0)
|
||||
{
|
||||
// ldh a, [$44]; cp 0; jr nz, back
|
||||
// Wait for LY=0 (frame start), from frame_cycles=0
|
||||
// true_pos = 20, target = 0, so wait until next frame
|
||||
// skip = (70224 + 0) - 20 = 70204
|
||||
// target_cycles = 0 * 456 = 0, so wait until next frame
|
||||
// D2 = (70224 + 0) - 0 = 70224, A = 0
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44) - read LY
|
||||
0xfe, 0x00, // cp 0
|
||||
@@ -94,15 +88,16 @@ TEST(test_ly_wait_jr_nz_ly0)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 0);
|
||||
ASSERT_EQ(get_cycle_count(), 70224 - LY_WAIT_CYCLES);
|
||||
// At frame_cycles=0, waiting for LY 0 means next frame
|
||||
ASSERT_EQ(get_cycle_count(), 70224);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_jr_nz_ly90)
|
||||
{
|
||||
// ldh a, [$44]; cp 90; jr nz, back
|
||||
// Wait for LY=90, from frame_cycles=0
|
||||
// true_pos = 20, target = 90*456 = 41040
|
||||
// skip = 41040 - 20 = 41020
|
||||
// target_cycles = 90 * 456 = 41040
|
||||
// D2 = 41040 - 0 = 41040, A = 90
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44) - read LY
|
||||
0xfe, 0x5a, // cp 90
|
||||
@@ -111,14 +106,13 @@ TEST(test_ly_wait_jr_nz_ly90)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 90);
|
||||
ASSERT_EQ(get_cycle_count(), 90 * 456 - LY_WAIT_CYCLES);
|
||||
ASSERT_EQ(get_cycle_count(), 90 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_jr_nz_ly144)
|
||||
{
|
||||
// Wait for LY=144 (vblank start), from frame_cycles=0
|
||||
// true_pos = 20, target = 144*456 = 65664
|
||||
// skip = 65664 - 20 = 65644
|
||||
// target_cycles = 144 * 456 = 65664
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x90, // cp 144
|
||||
@@ -127,15 +121,15 @@ TEST(test_ly_wait_jr_nz_ly144)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 144);
|
||||
ASSERT_EQ(get_cycle_count(), 144 * 456 - LY_WAIT_CYCLES);
|
||||
ASSERT_EQ(get_cycle_count(), 144 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_jr_nz_past_target)
|
||||
{
|
||||
// Wait for LY=50, but frame_cycles already past that
|
||||
// frame_cycles=30000, true_pos=30020, target=22800
|
||||
// Since true_pos >= target, wait until next frame
|
||||
// skip = (70224 + 22800) - 30020 = 63004
|
||||
// frame_cycles=30000, LY 50 is at 22800
|
||||
// Since frame_cycles >= target, wait until next frame
|
||||
// D2 = (70224 + 22800) - 30000 = 63024
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x32, // cp 50
|
||||
@@ -144,7 +138,7 @@ TEST(test_ly_wait_jr_nz_past_target)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 30000);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 50);
|
||||
ASSERT_EQ(get_cycle_count(), 70224 + (50 * 456) - 30000 - LY_WAIT_CYCLES);
|
||||
ASSERT_EQ(get_cycle_count(), 70224 + (50 * 456) - 30000);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_jr_z_ly90)
|
||||
@@ -152,8 +146,7 @@ TEST(test_ly_wait_jr_z_ly90)
|
||||
// ldh a, [$44]; cp 90; jr z, back
|
||||
// jr z: loop while LY == 90, exit when LY != 90
|
||||
// This waits for LY = (90 + 1) % 154 = 91
|
||||
// true_pos = 20, target = 91*456 = 41496
|
||||
// skip = 41496 - 20 = 41476
|
||||
// target_cycles = 91 * 456 = 41496
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x5a, // cp 90
|
||||
@@ -162,16 +155,15 @@ TEST(test_ly_wait_jr_z_ly90)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 91);
|
||||
ASSERT_EQ(get_cycle_count(), 91 * 456 - LY_WAIT_CYCLES);
|
||||
ASSERT_EQ(get_cycle_count(), 91 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_jr_z_ly153)
|
||||
{
|
||||
// ldh a, [$44]; cp 153; jr z, back
|
||||
// wait_ly = (153 + 1) % 154 = 0 (wraps to start of frame)
|
||||
// true_pos = 20, target = 0
|
||||
// Since true_pos >= target, wait for next frame
|
||||
// skip = (70224 + 0) - 20 = 70204
|
||||
// target_cycles = 0 * 456 = 0
|
||||
// From frame_cycles=0, this should wait for next frame
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x99, // cp 153
|
||||
@@ -180,15 +172,15 @@ TEST(test_ly_wait_jr_z_ly153)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 0);
|
||||
ASSERT_EQ(get_cycle_count(), 70224 - LY_WAIT_CYCLES);
|
||||
// At frame_cycles=0, target is 0, so next frame
|
||||
ASSERT_EQ(get_cycle_count(), 70224);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_jr_c_ly100)
|
||||
{
|
||||
// ldh a, [$44]; cp 100; jr c, back
|
||||
// jr c: loop while LY < 100, exit when LY >= 100
|
||||
// true_pos = 20, target = 100*456 = 45600
|
||||
// skip = 45600 - 20 = 45580
|
||||
// wait_ly = 100, target_cycles = 100 * 456 = 45600
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x64, // cp 100
|
||||
@@ -197,14 +189,14 @@ TEST(test_ly_wait_jr_c_ly100)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 100);
|
||||
ASSERT_EQ(get_cycle_count(), 100 * 456 - LY_WAIT_CYCLES);
|
||||
ASSERT_EQ(get_cycle_count(), 100 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_mid_frame)
|
||||
{
|
||||
// Wait for LY=100, starting at frame_cycles=20000
|
||||
// true_pos = 20020, target = 45600
|
||||
// skip = 45600 - 20020 = 25580
|
||||
// LY 100 is at cycle 45600
|
||||
// D2 = 45600 - 20000 = 25600
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x64, // cp 100
|
||||
@@ -213,25 +205,175 @@ TEST(test_ly_wait_mid_frame)
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 20000);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 100);
|
||||
ASSERT_EQ(get_cycle_count(), 45600 - 20000 - LY_WAIT_CYCLES);
|
||||
ASSERT_EQ(get_cycle_count(), 45600 - 20000);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_exact_target)
|
||||
{
|
||||
// Start at frame_cycles such that true_pos exactly equals target
|
||||
// target = 50*456 = 22800, so frame_cycles = 22800 - 20 = 22780
|
||||
// true_pos = 22800 >= target, so wait for next frame
|
||||
// skip = (70224 + 22800) - 22800 = 70224
|
||||
// Start exactly at the target LY cycle
|
||||
// LY 50 is at cycle 22800, start there
|
||||
// frame_cycles >= target_cycles, so wait for next frame
|
||||
uint8_t rom[] = {
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xfe, 0x32, // cp 50
|
||||
0x20, 0xfa, // jr nz, -6
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 22800 - LY_WAIT_CYCLES);
|
||||
run_block_with_frame_cycles(rom, 22800);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 50);
|
||||
// true_pos == target_cycles, uses next frame path
|
||||
ASSERT_EQ(get_cycle_count(), 70224);
|
||||
// frame_cycles == target_cycles, uses next frame path
|
||||
ASSERT_EQ(get_cycle_count(), 70224);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LY wait pattern tests with register compare
|
||||
// Pattern: ld r, N; ldh a, [$44]; cp r; jr cc, back
|
||||
// Same as immediate but target comes from a register
|
||||
// ============================================================================
|
||||
|
||||
TEST(test_ly_wait_reg_cp_b)
|
||||
{
|
||||
// ld b, 90; ldh a, [$44]; cp b; jr nz, back
|
||||
// Wait for LY=90
|
||||
uint8_t rom[] = {
|
||||
0x06, 0x5a, // ld b, 90
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xb8, // cp b
|
||||
0x20, 0xfb, // jr nz, -5 (back to ldh)
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 90);
|
||||
ASSERT_EQ(get_cycle_count(), 90 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_cp_c)
|
||||
{
|
||||
// ld c, 100; ldh a, [$44]; cp c; jr nz, back
|
||||
// Wait for LY=100
|
||||
uint8_t rom[] = {
|
||||
0x0e, 0x64, // ld c, 100
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xb9, // cp c
|
||||
0x20, 0xfb, // jr nz, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 100);
|
||||
ASSERT_EQ(get_cycle_count(), 100 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_cp_h)
|
||||
{
|
||||
// ld h, 144; ldh a, [$44]; cp h; jr nz, back
|
||||
// Wait for LY=144 (vblank)
|
||||
uint8_t rom[] = {
|
||||
0x26, 0x90, // ld h, 144
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xbc, // cp h
|
||||
0x20, 0xfb, // jr nz, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 144);
|
||||
ASSERT_EQ(get_cycle_count(), 144 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_cp_l)
|
||||
{
|
||||
// ld l, 50; ldh a, [$44]; cp l; jr nz, back
|
||||
// Wait for LY=50
|
||||
uint8_t rom[] = {
|
||||
0x2e, 0x32, // ld l, 50
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xbd, // cp l
|
||||
0x20, 0xfb, // jr nz, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 50);
|
||||
ASSERT_EQ(get_cycle_count(), 50 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_jr_z)
|
||||
{
|
||||
// ld b, 90; ldh a, [$44]; cp b; jr z, back
|
||||
// jr z: loop while LY == 90, wait for LY = 91
|
||||
uint8_t rom[] = {
|
||||
0x06, 0x5a, // ld b, 90
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xb8, // cp b
|
||||
0x28, 0xfb, // jr z, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 91);
|
||||
ASSERT_EQ(get_cycle_count(), 91 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_jr_z_wrap)
|
||||
{
|
||||
// ld c, 153; ldh a, [$44]; cp c; jr z, back
|
||||
// jr z with LY=153: wait_ly = (153+1) % 154 = 0
|
||||
uint8_t rom[] = {
|
||||
0x0e, 0x99, // ld c, 153
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xb9, // cp c
|
||||
0x28, 0xfb, // jr z, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 0);
|
||||
// target=0, true_pos > 0, so wait for next frame
|
||||
ASSERT_EQ(get_cycle_count(), 70224);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_jr_c)
|
||||
{
|
||||
// ld d, 100; ldh a, [$44]; cp d; jr c, back
|
||||
// jr c: loop while LY < 100, wait for LY >= 100
|
||||
uint8_t rom[] = {
|
||||
0x16, 0x64, // ld d, 100
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xba, // cp d
|
||||
0x38, 0xfb, // jr c, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 0);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 100);
|
||||
ASSERT_EQ(get_cycle_count(), 100 * 456);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_mid_frame)
|
||||
{
|
||||
// Wait for LY=100, starting at frame_cycles=20000
|
||||
uint8_t rom[] = {
|
||||
0x06, 0x64, // ld b, 100
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xb8, // cp b
|
||||
0x20, 0xfb, // jr nz, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 20000);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 100);
|
||||
ASSERT_EQ(get_cycle_count(), 100 * 456 - 20000);
|
||||
}
|
||||
|
||||
TEST(test_ly_wait_reg_past_target)
|
||||
{
|
||||
// Wait for LY=50, but frame_cycles already past that
|
||||
// target = 50*456 = 22800, frame_cycles = 30000
|
||||
// Wait until next frame
|
||||
uint8_t rom[] = {
|
||||
0x1e, 0x32, // ld e, 50
|
||||
0xf0, 0x44, // ldh a, ($ff44)
|
||||
0xbb, // cp e
|
||||
0x20, 0xfb, // jr nz, -5
|
||||
0x10 // stop
|
||||
};
|
||||
run_block_with_frame_cycles(rom, 30000);
|
||||
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 50);
|
||||
ASSERT_EQ(get_cycle_count(), 70224 + 50 * 456 - 30000);
|
||||
}
|
||||
|
||||
void register_timing_tests(void)
|
||||
@@ -254,4 +396,15 @@ void register_timing_tests(void)
|
||||
RUN_TEST(test_ly_wait_jr_c_ly100);
|
||||
RUN_TEST(test_ly_wait_mid_frame);
|
||||
RUN_TEST(test_ly_wait_exact_target);
|
||||
|
||||
printf("\nLY wait register pattern tests:\n");
|
||||
RUN_TEST(test_ly_wait_reg_cp_b);
|
||||
RUN_TEST(test_ly_wait_reg_cp_c);
|
||||
RUN_TEST(test_ly_wait_reg_cp_h);
|
||||
RUN_TEST(test_ly_wait_reg_cp_l);
|
||||
RUN_TEST(test_ly_wait_reg_jr_z);
|
||||
RUN_TEST(test_ly_wait_reg_jr_z_wrap);
|
||||
RUN_TEST(test_ly_wait_reg_jr_c);
|
||||
RUN_TEST(test_ly_wait_reg_mid_frame);
|
||||
RUN_TEST(test_ly_wait_reg_past_target);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "compiler.h"
|
||||
#include "emitters.h"
|
||||
#include "interop.h"
|
||||
#include "timing.h"
|
||||
|
||||
// synthesize wait for LY to reach target value
|
||||
// detects ldh a, [$44]; cp N; jr cc, back
|
||||
void compile_ly_wait(
|
||||
struct code_block *block,
|
||||
uint8_t target_ly,
|
||||
uint8_t jr_opcode,
|
||||
uint16_t next_pc
|
||||
) {
|
||||
// jr nz (0x20): loop while LY != N, exit when LY == N -> wait for N
|
||||
// jr z (0x28): loop while LY == N, exit when LY != N -> wait for N+1
|
||||
// jr c (0x38): loop while LY < N, exit when LY >= N -> wait for N
|
||||
uint8_t wait_ly = target_ly;
|
||||
if (jr_opcode == 0x28) {
|
||||
wait_ly = (target_ly + 1) % 154;
|
||||
}
|
||||
|
||||
uint32_t target_cycles = wait_ly * 456;
|
||||
|
||||
// load frame_cycles pointer
|
||||
emit_movea_l_disp_an_an(block, JIT_CTX_FRAME_CYCLES_PTR, REG_68K_A_CTX, REG_68K_A_SCRATCH_1);
|
||||
// load frame_cycles into d0
|
||||
emit_move_l_ind_an_dn(block, REG_68K_A_SCRATCH_1, REG_68K_D_SCRATCH_0);
|
||||
|
||||
// compare frame_cycles to target
|
||||
emit_cmpi_l_imm_dn(block, target_cycles, REG_68K_D_SCRATCH_0);
|
||||
emit_bcc_s(block, 10); // if frame_cycles >= target, wait until next frame
|
||||
|
||||
// same frame: d2 = target - frame_cycles
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, target_cycles);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
emit_bra_b(block, 8);
|
||||
|
||||
// next frame: d2 = (70224 + target) - frame_cycles
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, 70224 + target_cycles);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
|
||||
// set A to the LY value we waited for
|
||||
emit_moveq_dn(block, REG_68K_D_A, wait_ly);
|
||||
|
||||
// exit to C
|
||||
emit_move_l_dn(block, REG_68K_D_NEXT_PC, next_pc);
|
||||
emit_rts(block);
|
||||
}
|
||||
|
||||
// get GB register value into D0, zero-extended to word
|
||||
void compile_get_gb_reg_d0(struct code_block *block, int gb_reg)
|
||||
{
|
||||
switch (gb_reg) {
|
||||
case GB_REG_B:
|
||||
emit_move_l_dn_dn(block, REG_68K_D_BC, REG_68K_D_SCRATCH_0);
|
||||
emit_swap(block, REG_68K_D_SCRATCH_0);
|
||||
break;
|
||||
case GB_REG_C:
|
||||
emit_move_l_dn_dn(block, REG_68K_D_BC, REG_68K_D_SCRATCH_0);
|
||||
break;
|
||||
case GB_REG_D:
|
||||
emit_move_l_dn_dn(block, REG_68K_D_DE, REG_68K_D_SCRATCH_0);
|
||||
emit_swap(block, REG_68K_D_SCRATCH_0);
|
||||
break;
|
||||
case GB_REG_E:
|
||||
emit_move_l_dn_dn(block, REG_68K_D_DE, REG_68K_D_SCRATCH_0);
|
||||
break;
|
||||
case GB_REG_H:
|
||||
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_0);
|
||||
emit_lsr_w_imm_dn(block, 8, REG_68K_D_SCRATCH_0);
|
||||
break;
|
||||
case GB_REG_L:
|
||||
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_0);
|
||||
break;
|
||||
case GB_REG_HL:
|
||||
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
|
||||
compile_call_dmg_read(block);
|
||||
break;
|
||||
default:
|
||||
printf("invalid register for compile_get_gb_reg_d0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
emit_andi_w_dn(block, REG_68K_D_SCRATCH_0, 0xff);
|
||||
}
|
||||
|
||||
// synthesize wait for LY to reach target value from a register
|
||||
// detects ldh a, [$44]; cp <reg>; jr cc, back
|
||||
void compile_ly_wait_reg(
|
||||
struct code_block *block,
|
||||
int gb_reg,
|
||||
uint8_t jr_opcode,
|
||||
uint16_t next_pc
|
||||
) {
|
||||
// Get the target LY value into D0
|
||||
compile_get_gb_reg_d0(block, gb_reg);
|
||||
|
||||
// jr nz (0x20): loop while LY != N, exit when LY == N -> wait for N
|
||||
// jr z (0x28): loop while LY == N, exit when LY != N -> wait for N+1
|
||||
// jr c (0x38): loop while LY < N, exit when LY >= N -> wait for N
|
||||
if (jr_opcode == 0x28) {
|
||||
// wait_ly = (target + 1) % 154
|
||||
emit_addq_w_dn(block, REG_68K_D_SCRATCH_0, 1);
|
||||
emit_cmpi_w_imm_dn(block, 154, REG_68K_D_SCRATCH_0);
|
||||
emit_bcs_b(block, 2); // skip the clear if D0 < 154
|
||||
emit_moveq_dn(block, REG_68K_D_SCRATCH_0, 0);
|
||||
}
|
||||
|
||||
// D0 = wait_ly; save to stack for later
|
||||
emit_push_l_dn(block, REG_68K_D_SCRATCH_0);
|
||||
|
||||
// D0 = target_cycles = wait_ly * 456
|
||||
emit_mulu_w_imm_dn(block, 456, REG_68K_D_SCRATCH_0);
|
||||
|
||||
// load frame_cycles pointer into A0
|
||||
emit_movea_l_disp_an_an(block, JIT_CTX_FRAME_CYCLES_PTR, REG_68K_A_CTX, REG_68K_A_SCRATCH_1);
|
||||
// load frame_cycles into D1
|
||||
emit_move_l_ind_an_dn(block, REG_68K_A_SCRATCH_1, REG_68K_D_SCRATCH_1);
|
||||
|
||||
// compare frame_cycles (D1) to target_cycles (D0)
|
||||
emit_cmp_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_SCRATCH_1);
|
||||
emit_bcc_s(block, 6); // if frame_cycles >= target_cycles, skip to next_frame
|
||||
|
||||
// same frame: d2 = target_cycles - frame_cycles = d0 - d1
|
||||
emit_move_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_CYCLE_COUNT);
|
||||
emit_bra_b(block, 10);
|
||||
|
||||
// next frame: d2 = (70224 + target_cycles) - frame_cycles
|
||||
emit_move_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
emit_addi_l_dn(block, REG_68K_D_CYCLE_COUNT, 70224);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_CYCLE_COUNT);
|
||||
|
||||
// restore wait_ly from stack into A register
|
||||
emit_pop_l_dn(block, REG_68K_D_A);
|
||||
|
||||
// exit to C
|
||||
emit_move_l_dn(block, REG_68K_D_NEXT_PC, next_pc);
|
||||
emit_rts(block);
|
||||
}
|
||||
|
||||
// only good for vblank interrupts for now...
|
||||
void compile_halt(struct code_block *block, int next_pc)
|
||||
{
|
||||
// movea.l JIT_CTX_FRAME_CYCLES_PTR(a4), a0 ; 4 bytes
|
||||
// move.l (a0), d0 ; 2 bytes
|
||||
// cmpi.l #65664, d0 ; 6 bytes
|
||||
// bcc.s _frame_end ; 2 bytes
|
||||
// move.l #65664, d2 ; 6 bytes
|
||||
// sub.l d0, d2 ; 2 bytes
|
||||
// bra.s _exit ; 2 bytes
|
||||
// _frame_end
|
||||
// move.l #70224, d2 ; 6 bytes
|
||||
// sub.l d0, d2 ; 2 bytes
|
||||
// _exit
|
||||
// move.l #next_pc, d3 ; 6 bytes
|
||||
// rts ; 2 bytes
|
||||
|
||||
// load frame_cycles pointer
|
||||
emit_movea_l_disp_an_an(block, JIT_CTX_FRAME_CYCLES_PTR, REG_68K_A_CTX, REG_68K_A_SCRATCH_1);
|
||||
emit_move_l_ind_an_dn(block, REG_68K_A_SCRATCH_1, REG_68K_D_SCRATCH_0);
|
||||
|
||||
// see if already in vblank
|
||||
emit_cmpi_l_imm_dn(block, 65664, REG_68K_D_SCRATCH_0);
|
||||
emit_bcc_s(block, 10);
|
||||
|
||||
// before vblank: d2 = 65664 - frame_cycles
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, 65664);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
emit_bra_b(block, 8);
|
||||
|
||||
// in vblank: d2 = (70224 - frame_cycles) + 65664
|
||||
emit_move_l_dn(block, REG_68K_D_CYCLE_COUNT, 70224 + 65664);
|
||||
emit_sub_l_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_CYCLE_COUNT);
|
||||
|
||||
// exit to C
|
||||
emit_move_l_dn(block, REG_68K_D_NEXT_PC, next_pc);
|
||||
emit_rts(block);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef _TIMING_H
|
||||
#define _TIMING_H
|
||||
|
||||
// GB register indices for cp r instruction
|
||||
#define GB_REG_B 0
|
||||
#define GB_REG_C 1
|
||||
#define GB_REG_D 2
|
||||
#define GB_REG_E 3
|
||||
#define GB_REG_H 4
|
||||
#define GB_REG_L 5
|
||||
#define GB_REG_HL 6
|
||||
|
||||
// synthesize wait for LY to reach target value
|
||||
// detects ldh a, [$44]; cp N; jr cc, back
|
||||
void compile_ly_wait(
|
||||
struct code_block *block,
|
||||
uint8_t target_ly,
|
||||
uint8_t jr_opcode,
|
||||
uint16_t next_pc
|
||||
);
|
||||
|
||||
void compile_get_gb_reg_d0(struct code_block *block, int gb_reg);
|
||||
|
||||
void compile_ly_wait_reg(
|
||||
struct code_block *block,
|
||||
int gb_reg,
|
||||
uint8_t jr_opcode,
|
||||
uint16_t next_pc
|
||||
);
|
||||
|
||||
void compile_halt(struct code_block *block, int next_pc);
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@ add_application("Gray Brick"
|
||||
../compiler/alu.c
|
||||
../compiler/instructions.c
|
||||
../compiler/stack.c
|
||||
../compiler/timing.c
|
||||
arena.c
|
||||
cpu_cache.c
|
||||
dialogs.c
|
||||
|
||||
+2
-1
@@ -745,6 +745,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
}
|
||||
audio_mac_shutdown();
|
||||
|
||||
StopEmulation();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user