move timing functions to their own file, LY wait on registers too

This commit is contained in:
Matthew Laux
2026-01-25 22:16:22 -06:00
parent 31d6caed10
commit 23f752af23
8 changed files with 477 additions and 151 deletions
+33 -96
View File
@@ -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;
}
}
}
+16
View File
@@ -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)
{
+2
View File
@@ -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
+207 -54
View File
@@ -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);
}
+183
View File
@@ -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);
}
+33
View File
@@ -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
+1
View File
@@ -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
View File
@@ -745,6 +745,7 @@ int main(int argc, char *argv[])
}
}
}
audio_mac_shutdown();
StopEmulation();
return 0;
}