split out compile_dmg_*, fix alignment bug for dmg_write, add more instructions, hacky lcd update for jit

This commit is contained in:
Matthew Laux
2025-12-19 17:05:12 -06:00
parent 1e9a822765
commit 3d0ef51bb2
16 changed files with 578 additions and 79 deletions
+186
View File
@@ -0,0 +1,186 @@
#include <stdint.h>
#include "cb_prefix.h"
#include "compiler.h"
#include "emitters.h"
#include "flags.h"
#include "interop.h"
// GB register encoding in CB-prefix instructions:
// 0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A
//
// 68k register mapping:
// D4 = A
// D5 = BC in split format: 0x00BB00CC (B in bits 16-23, C in bits 0-7)
// D6 = DE in split format: 0x00DD00EE (D in bits 16-23, E in bits 0-7)
// A2 = HL (contiguous 16-bit value)
// Set GB flags for BIT instruction: Z from 68k Z flag, N=0, H=1, C unchanged
static void compile_bit_flags(struct code_block *block)
{
// After btst, 68k Z flag is set if bit was 0
// GB Z flag is bit 7, H flag is bit 5
// We need: Z set if tested bit was 0, N=0, H=1, C unchanged (bit 4)
// Capture 68k Z flag FIRST before other instructions clobber it
// scc stores 0xff if condition true, 0x00 if false
emit_scc(block, 0x07, REG_68K_D_SCRATCH_1); // seq: set if Z=1
emit_andi_b_dn(block, REG_68K_D_SCRATCH_1, 0x80); // mask to just Z position
// Keep only C flag (bit 4)
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0x10);
// Set H flag (bit 5)
emit_ori_b_dn(block, REG_68K_D_FLAGS, 0x20);
// OR in the Z flag
emit_or_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_FLAGS);
}
// BIT n, reg - test bit n of register
static void compile_bit_reg(struct code_block *block, int bit, int gb_reg)
{
switch (gb_reg) {
case 0: // B - high byte of D5 (bits 16-23)
emit_btst_imm_dn(block, bit + 16, REG_68K_D_BC);
break;
case 1: // C - low byte of D5 (bits 0-7)
emit_btst_imm_dn(block, bit, REG_68K_D_BC);
break;
case 2: // D - high byte of D6 (bits 16-23)
emit_btst_imm_dn(block, bit + 16, REG_68K_D_DE);
break;
case 3: // E - low byte of D6 (bits 0-7)
emit_btst_imm_dn(block, bit, REG_68K_D_DE);
break;
case 4: // H - high byte of HL
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_btst_imm_dn(block, bit + 8, REG_68K_D_SCRATCH_1);
break;
case 5: // L - low byte of HL
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_btst_imm_dn(block, bit, REG_68K_D_SCRATCH_1);
break;
case 6: // (HL) - memory indirect
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_read_to_d0(block); // result in D0
emit_btst_imm_dn(block, bit, REG_68K_D_NEXT_PC);
break;
case 7: // A
emit_btst_imm_dn(block, bit, REG_68K_D_A);
break;
}
compile_bit_flags(block);
}
// RES n, reg - clear bit n of register
static void compile_res_reg(struct code_block *block, int bit, int gb_reg)
{
switch (gb_reg) {
case 0: // B
emit_bclr_imm_dn(block, bit + 16, REG_68K_D_BC);
break;
case 1: // C
emit_bclr_imm_dn(block, bit, REG_68K_D_BC);
break;
case 2: // D
emit_bclr_imm_dn(block, bit + 16, REG_68K_D_DE);
break;
case 3: // E
emit_bclr_imm_dn(block, bit, REG_68K_D_DE);
break;
case 4: // H - need to move from A2, modify, move back
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_bclr_imm_dn(block, bit + 8, REG_68K_D_SCRATCH_1);
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
break;
case 5: // L
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_bclr_imm_dn(block, bit, REG_68K_D_SCRATCH_1);
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
break;
case 6: // (HL) - read, modify, write
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_read_to_d0(block); // D0 = memory[HL]
emit_bclr_imm_dn(block, bit, REG_68K_D_NEXT_PC); // clear bit in D0
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1); // D1 = address
compile_call_dmg_write_d0(block); // write D2 to address D1
break;
case 7: // A
emit_bclr_imm_dn(block, bit, REG_68K_D_A);
break;
}
}
// SET n, reg - set bit n of register
static void compile_set_reg(struct code_block *block, int bit, int gb_reg)
{
switch (gb_reg) {
case 0: // B
emit_bset_imm_dn(block, bit + 16, REG_68K_D_BC);
break;
case 1: // C
emit_bset_imm_dn(block, bit, REG_68K_D_BC);
break;
case 2: // D
emit_bset_imm_dn(block, bit + 16, REG_68K_D_DE);
break;
case 3: // E
emit_bset_imm_dn(block, bit, REG_68K_D_DE);
break;
case 4: // H
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_bset_imm_dn(block, bit + 8, REG_68K_D_SCRATCH_1);
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
break;
case 5: // L
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_bset_imm_dn(block, bit, REG_68K_D_SCRATCH_1);
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
break;
case 6: // (HL) - read, modify, write
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_read_to_d0(block); // D0 = memory[HL]
emit_bset_imm_dn(block, bit, REG_68K_D_NEXT_PC); // set bit in D0
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1); // D1 = address
compile_call_dmg_write_d0(block); // write D0 to address D1
break;
case 7: // A
emit_bset_imm_dn(block, bit, REG_68K_D_A);
break;
}
}
int compile_cb_insn(struct code_block *block, uint8_t cb_op)
{
int op = cb_op >> 6;
int bit = (cb_op >> 3) & 0x7;
int reg = cb_op & 0x7;
switch (op) {
case 0: // rotates/shifts - only swap is implemented for now
if (cb_op == 0x37) {
// swap a
emit_ror_b_imm(block, 4, REG_68K_D_A);
emit_cmp_b_imm_dn(block, REG_68K_D_A, 0);
compile_set_z_flag(block);
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0x80);
return 1;
}
return 0; // other rotates/shifts not yet implemented
case 1: // BIT
compile_bit_reg(block, bit, reg);
return 1;
case 2: // RES
compile_res_reg(block, bit, reg);
return 1;
case 3: // SET
compile_set_reg(block, bit, reg);
return 1;
}
return 0;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef CB_PREFIX_H
#define CB_PREFIX_H
#include <stdint.h>
#include "compiler.h"
// Compile a CB-prefixed instruction (BIT, RES, SET, and rotates/shifts)
// Returns 1 if successfully compiled, 0 if unknown opcode
int compile_cb_insn(struct code_block *block, uint8_t cb_op);
#endif
+103 -10
View File
@@ -6,6 +6,7 @@
#include "branches.h"
#include "flags.h"
#include "interop.h"
#include "cb_prefix.h"
// helper for reading GB memory during compilation
#define READ_BYTE(off) (ctx->read(ctx->dmg, src_address + (off)))
@@ -191,6 +192,14 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
emit_move_b_dn(block, REG_68K_D_BC, READ_BYTE(src_ptr++));
emit_swap(block, REG_68K_D_BC);
break;
case 0x07: // rlca - rotate A left, old bit 7 to carry and bit 0
emit_rol_b_imm(block, 1, REG_68K_D_A);
// Capture 68k carry into scratch, mask to GB C position
emit_scc(block, 0x05, REG_68K_D_SCRATCH_1); // scs: set if carry
emit_andi_b_dn(block, REG_68K_D_SCRATCH_1, 0x10); // mask to bit 4
emit_move_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_FLAGS); // Z=N=H=0, C=result
break;
case 0x0b: // dec bc
emit_ext_w_dn(block, REG_68K_D_BC);
@@ -214,6 +223,18 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
emit_move_b_dn(block, REG_68K_D_BC, READ_BYTE(src_ptr++));
break;
case 0x0f: // rrca - rotate A right, old bit 0 to carry and bit 7
emit_ror_b_imm(block, 1, REG_68K_D_A);
emit_scc(block, 0x05, REG_68K_D_SCRATCH_1); // scs: set if carry
emit_andi_b_dn(block, REG_68K_D_SCRATCH_1, 0x10);
emit_move_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_FLAGS);
break;
case 0x13: // inc de
emit_ext_w_dn(block, REG_68K_D_DE);
emit_addq_l_dn(block, REG_68K_D_DE, 1);
break;
case 0x16: // ld d, imm8
emit_swap(block, REG_68K_D_DE);
emit_move_b_dn(block, REG_68K_D_DE, READ_BYTE(src_ptr++));
@@ -311,16 +332,63 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
emit_move_b_dn_dn(block, REG_68K_D_A, REG_68K_D_DE);
break;
case 0x67: // ld h, a
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_rol_w_8(block, REG_68K_D_SCRATCH_1);
emit_move_b_dn_dn(block, REG_68K_D_A, REG_68K_D_SCRATCH_1);
emit_ror_w_8(block, REG_68K_D_SCRATCH_1);
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
break;
case 0x6f: // ld l, a
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_move_b_dn_dn(block, REG_68K_D_A, REG_68K_D_SCRATCH_1);
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
break;
case 0x78: // ld a, b
emit_swap(block, REG_68K_D_BC);
emit_move_b_dn_dn(block, REG_68K_D_BC, REG_68K_D_A);
emit_swap(block, REG_68K_D_BC);
break;
case 0x77: // ld (hl), a
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_write(block);
break;
case 0x22: // ld (hl+), a
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_write(block);
emit_addq_w_an(block, REG_68K_A_HL, 1);
break;
case 0x35: // dec (hl)
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_read_to_d0(block);
emit_subq_b_dn(block, 0, 1);
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_write_d0(block);
break;
case 0x79: // ld a, c
emit_move_b_dn_dn(block, REG_68K_D_BC, REG_68K_D_A);
break;
case 0x7c: // ld a, h
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_rol_w_8(block, REG_68K_D_SCRATCH_1);
emit_move_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_A);
break;
case 0x7d: // ld a, l
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
emit_move_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_A);
break;
case 0x7e: // ld a, (hl)
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
compile_call_dmg_read(block);
break;
case 0x87: // add a, a
emit_add_b_dn_dn(block, REG_68K_D_A, REG_68K_D_A);
compile_set_z_flag(block);
@@ -333,6 +401,13 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
compile_set_znc_flags(block);
break;
case 0xb8: // cp a, b
emit_swap(block, REG_68K_D_BC);
emit_cmp_b_dn_dn(block, REG_68K_D_BC, REG_68K_D_A);
compile_set_znc_flags(block);
emit_swap(block, REG_68K_D_BC);
break;
case 0xaf: // xor a, a - always results in 0, Z=1
emit_moveq_dn(block, REG_68K_D_A, 0);
emit_move_b_dn(block, REG_68K_D_FLAGS, 0x80); // Z=1, N=0, H=0, C=0
@@ -381,22 +456,13 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
case 0xcb: // CB prefix
{
uint8_t cb_op = READ_BYTE(src_ptr++);
switch (cb_op) {
case 0x37: // swap a
emit_ror_b_imm(block, 4, REG_68K_D_A);
// set Z if result is 0, clear N, H, C
emit_cmp_b_imm_dn(block, REG_68K_D_A, 0);
compile_set_z_flag(block);
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0x80);
break;
default:
if (!compile_cb_insn(block, cb_op)) {
block->error = 1;
block->failed_opcode = 0xcb00 | cb_op;
block->failed_address = src_address + src_ptr - 2;
emit_move_l_dn(block, REG_68K_D_NEXT_PC, 0xffffffff);
emit_rts(block);
done = 1;
break;
}
}
break;
@@ -420,6 +486,13 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
}
break;
case 0xc6: // add a, #imm
emit_addi_b_dn(block, REG_68K_D_A, READ_BYTE(src_ptr++));
compile_set_z_flag(block);
// TODO: proper H and C flags
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0x80); // keep Z, clear N, H, C
break;
case 0xca: // jp z, imm16
compile_jp_cond(block, ctx, &src_ptr, src_address, 7, 1);
break;
@@ -459,6 +532,11 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
emit_move_b_dn_disp_an(block, REG_68K_D_SCRATCH_1, 1, REG_68K_A_SP);
break;
case 0xd6: // sub a, #imm
emit_subi_b_dn(block, REG_68K_D_A, READ_BYTE(src_ptr++));
compile_set_znc_flags(block);
break;
case 0xd8: // ret c
compile_ret_cond(block, 4, 1);
break;
@@ -544,6 +622,21 @@ struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
}
break;
case 0xee: // xor a, u8
emit_eor_b_imm_dn(block, READ_BYTE(src_ptr++), REG_68K_D_A);
compile_set_z_flag(block);
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0x80); // clear N, H, C
break;
case 0xfa: // ld a, (u16)
{
uint16_t addr = READ_BYTE(src_ptr) | (READ_BYTE(src_ptr + 1) << 8);
src_ptr += 2;
emit_move_w_dn(block, REG_68K_D_SCRATCH_1, addr);
compile_call_dmg_read(block);
}
break;
default:
// unknown opcode - set error info and halt
block->error = 1;
+2 -2
View File
@@ -45,8 +45,8 @@
struct code_block {
uint8_t code[256];
uint16_t m68k_offsets[256];
uint8_t code[2048];
uint16_t m68k_offsets[2048];
size_t length;
uint16_t gb_cycles; // for timing
uint16_t src_address;
+90
View File
@@ -156,6 +156,19 @@ void emit_addq_b_dn(struct code_block *block, uint8_t dreg, uint8_t val)
emit_word(block, 0x5000 | ddd << 9 | dreg);
}
void emit_addq_l_dn(struct code_block *block, uint8_t dreg, uint8_t val)
{
uint16_t ddd;
// 0101 ddd 0 10 000 rrr
if (val == 0 || val > 8) {
printf("can only addq values between 1 and 8\n");
exit(1);
}
ddd = val == 8 ? 0 : val;
emit_word(block, 0x5080 | ddd << 9 | dreg);
}
// addq.w #val, An
void emit_addq_w_an(struct code_block *block, uint8_t areg, uint8_t val)
{
@@ -230,6 +243,13 @@ void emit_cmp_b_imm_dn(struct code_block *block, uint8_t dreg, uint8_t imm)
emit_word(block, imm);
}
// cmp.b Ds, Dd - compare data registers (Dd - Ds, set flags)
void emit_cmp_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
{
// 1011 ddd 000 000 sss
emit_word(block, 0xb000 | (dest << 9) | src);
}
// scc Dn - set byte based on condition
// cond: 0x7 = seq (Z=1), 0x6 = sne (Z=0), 0x5 = scs (C=1), 0x4 = scc (C=0)
void emit_scc(struct code_block *block, uint8_t cond, uint8_t dreg)
@@ -254,6 +274,22 @@ void emit_ori_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm)
emit_word(block, imm);
}
// subi.b #imm, Dn
void emit_subi_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm)
{
// 0000 0100 00 000 rrr
emit_word(block, 0x0400 | dreg);
emit_word(block, imm);
}
// addi.b #imm, Dn
void emit_addi_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm)
{
// 0000 0110 00 000 rrr
emit_word(block, 0x0600 | dreg);
emit_word(block, imm);
}
// or.b Ds, Dd (result to Dd)
void emit_or_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
{
@@ -296,6 +332,22 @@ void emit_btst_imm_dn(struct code_block *block, uint8_t bit, uint8_t dreg)
emit_word(block, bit);
}
// bclr #bit, Dn - clear bit, sets Z based on previous bit value
void emit_bclr_imm_dn(struct code_block *block, uint8_t bit, uint8_t dreg)
{
// 0000 1000 10 000 rrr, followed by bit number
emit_word(block, 0x0880 | dreg);
emit_word(block, bit);
}
// bset #bit, Dn - set bit, sets Z based on previous bit value
void emit_bset_imm_dn(struct code_block *block, uint8_t bit, uint8_t dreg)
{
// 0000 1000 11 000 rrr, followed by bit number
emit_word(block, 0x08c0 | dreg);
emit_word(block, bit);
}
// move.l Ds, Dd - copy data register to data register (long)
void emit_move_l_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
{
@@ -324,6 +376,14 @@ void emit_lsl_w_imm_dn(struct code_block *block, uint8_t count, uint8_t dreg)
emit_word(block, 0xe148 | (ccc << 9) | dreg);
}
// move.b #imm, -(A7) - push immediate word
void emit_push_b_imm(struct code_block *block, uint16_t val)
{
// 00 01 111 100 111 100 (dest = -(A7), src = immediate word)
emit_word(block, 0x1f3c);
emit_word(block, val);
}
// move.w #imm, -(A7) - push immediate word
void emit_push_w_imm(struct code_block *block, uint16_t val)
{
@@ -332,6 +392,14 @@ void emit_push_w_imm(struct code_block *block, uint16_t val)
emit_word(block, val);
}
// move.b Dn, -(A7) - push byte from data register
// this actually decreases SP by 2
void emit_push_b_dn(struct code_block *block, uint8_t dreg)
{
// 00 01 111 100 000 ddd (dest = -(A7), src = Dn)
emit_word(block, 0x1f00 | dreg);
}
// move.w Dn, -(A7) - push word from data register
void emit_push_w_dn(struct code_block *block, uint8_t dreg)
{
@@ -418,6 +486,14 @@ void emit_eor_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
emit_word(block, 0xb100 | (src << 9) | dest);
}
// eori.b #imm, Dn - XOR immediate with data register
void emit_eor_b_imm_dn(struct code_block *block, uint8_t imm, uint8_t dreg)
{
// 0000 1010 00 000 rrr
emit_word(block, 0x0a00 | dreg);
emit_word(block, imm);
}
void emit_ext_w_dn(struct code_block *block, uint8_t dreg)
{
// 0100 100 010 000 ddd
@@ -452,6 +528,20 @@ void emit_ror_b_imm(struct code_block *block, uint8_t count, uint8_t dreg)
emit_word(block, 0xe018 | (ccc << 9) | dreg);
}
// rol.b #count, Dn - rotate left byte by immediate (1-8)
void emit_rol_b_imm(struct code_block *block, uint8_t count, uint8_t dreg)
{
uint16_t ccc;
// 1110 ccc 1 00 0 11 rrr (d=1 for left, size=00 for byte, i=0 for imm)
if (count == 0 || count > 8) {
printf("can only rol by 1-8\n");
exit(1);
}
ccc = count == 8 ? 0 : count;
emit_word(block, 0xe118 | (ccc << 9) | dreg);
}
// add.b Ds, Dd - ADD data registers (result to Dd)
void emit_add_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest)
{
+11 -1
View File
@@ -24,7 +24,8 @@ void emit_movea_w_imm16(struct code_block *block, uint8_t areg, uint16_t val);
void emit_subq_b_dn(struct code_block *block, uint8_t dreg, uint8_t val);
void emit_subq_l_dn(struct code_block *block, uint8_t dreg, uint8_t val);
void emit_subq_w_an(struct code_block *block, uint8_t areg, uint8_t val);
void emit_addq_b_dn(struct code_block *block, uint8_t areg, uint8_t val);
void emit_addq_b_dn(struct code_block *block, uint8_t dreg, uint8_t val);
void emit_addq_l_dn(struct code_block *block, uint8_t dreg, uint8_t val);
void emit_addq_w_an(struct code_block *block, uint8_t areg, uint8_t val);
void emit_move_w_dn_ind_an(struct code_block *block, uint8_t dreg, uint8_t areg);
void emit_move_w_ind_an_dn(struct code_block *block, uint8_t areg, uint8_t dreg);
@@ -34,9 +35,12 @@ void emit_move_b_ind_an_dn(struct code_block *block, uint8_t areg, uint8_t dreg)
void emit_move_b_disp_an_dn(struct code_block *block, int16_t disp, uint8_t areg, uint8_t dreg);
void emit_andi_l_dn(struct code_block *block, uint8_t dreg, uint32_t imm);
void emit_cmp_b_imm_dn(struct code_block *block, uint8_t dreg, uint8_t imm);
void emit_cmp_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_scc(struct code_block *block, uint8_t cond, uint8_t dreg);
void emit_andi_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm);
void emit_ori_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm);
void emit_subi_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm);
void emit_addi_b_dn(struct code_block *block, uint8_t dreg, uint8_t imm);
void emit_or_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_rts(struct code_block *block);
@@ -44,11 +48,15 @@ void emit_bra_w(struct code_block *block, int16_t disp);
void emit_beq_w(struct code_block *block, int16_t disp);
void emit_bne_w(struct code_block *block, int16_t disp);
void emit_btst_imm_dn(struct code_block *block, uint8_t bit, uint8_t dreg);
void emit_bclr_imm_dn(struct code_block *block, uint8_t bit, uint8_t dreg);
void emit_bset_imm_dn(struct code_block *block, uint8_t bit, uint8_t dreg);
void emit_move_l_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_move_w_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_lsl_w_imm_dn(struct code_block *block, uint8_t count, uint8_t dreg);
void emit_push_b_imm(struct code_block *block, uint16_t val);
void emit_push_w_imm(struct code_block *block, uint16_t val);
void emit_push_b_dn(struct code_block *block, uint8_t dreg);
void emit_push_w_dn(struct code_block *block, uint8_t dreg);
void emit_push_l_disp_an(struct code_block *block, int16_t disp, uint8_t areg);
void emit_movea_l_disp_an_an(struct code_block *block, int16_t disp, uint8_t src_areg, uint8_t dest_areg);
@@ -59,11 +67,13 @@ void emit_movem_l_from_postinc(struct code_block *block, uint16_t mask);
void emit_move_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_movea_l_imm32(struct code_block *block, uint8_t areg, uint32_t val);
void emit_eor_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_eor_b_imm_dn(struct code_block *block, uint8_t imm, uint8_t dreg);
void emit_ext_w_dn(struct code_block *block, uint8_t dreg);
void emit_not_b_dn(struct code_block *block, uint8_t dreg);
void emit_and_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_ror_b_imm(struct code_block *block, uint8_t count, uint8_t dreg);
void emit_rol_b_imm(struct code_block *block, uint8_t count, uint8_t dreg);
void emit_add_b_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_add_w_dn_dn(struct code_block *block, uint8_t src, uint8_t dest);
void emit_adda_w_dn_an(struct code_block *block, uint8_t dreg, uint8_t areg);
+14 -2
View File
@@ -6,7 +6,7 @@
void compile_call_dmg_write(struct code_block *block)
{
// Push args right-to-left: val, addr, dmg
emit_push_w_dn(block, REG_68K_D_A); // push value (A register = D4)
emit_push_b_dn(block, REG_68K_D_A); // push value (A register = D4)
emit_push_w_dn(block, REG_68K_D_SCRATCH_1); // push address (D1)
emit_push_l_disp_an(block, JIT_CTX_DMG, REG_68K_A_CTX); // push dmg pointer
emit_movea_l_disp_an_an(block, JIT_CTX_WRITE, REG_68K_A_CTX, REG_68K_A_SCRATCH_1); // A0 = write func
@@ -17,7 +17,7 @@ void compile_call_dmg_write(struct code_block *block)
// Call dmg_write(dmg, addr, val) - addr in D1
void compile_call_dmg_write_imm(struct code_block *block, uint8_t val)
{
emit_push_w_imm(block, val);
emit_push_b_imm(block, val);
emit_push_w_dn(block, REG_68K_D_SCRATCH_1);
emit_push_l_disp_an(block, JIT_CTX_DMG, REG_68K_A_CTX);
emit_movea_l_disp_an_an(block, JIT_CTX_WRITE, REG_68K_A_CTX, REG_68K_A_SCRATCH_1);
@@ -25,6 +25,17 @@ void compile_call_dmg_write_imm(struct code_block *block, uint8_t val)
emit_addq_l_an(block, 7, 8);
}
// Call dmg_write(dmg, addr, val) - addr in D1, val in D0
void compile_call_dmg_write_d0(struct code_block *block)
{
emit_push_b_dn(block, REG_68K_D_NEXT_PC); // push value (D0)
emit_push_w_dn(block, REG_68K_D_SCRATCH_1); // push address (D1)
emit_push_l_disp_an(block, JIT_CTX_DMG, REG_68K_A_CTX); // push dmg pointer
emit_movea_l_disp_an_an(block, JIT_CTX_WRITE, REG_68K_A_CTX, REG_68K_A_SCRATCH_1); // A0 = write func
emit_jsr_ind_an(block, REG_68K_A_SCRATCH_1); // call dmg_write
emit_addq_l_an(block, 7, 8); // clean up stack (4 + 2 + 2 = 8 bytes)
}
// Call dmg_read(dmg, addr) - addr in D1, result stays in D0 (scratch)
void compile_call_dmg_read_to_d0(struct code_block *block)
{
@@ -48,6 +59,7 @@ void compile_call_ei_di(struct code_block *block, int enabled)
{
// push enabled
emit_moveq_dn(block, REG_68K_D_SCRATCH_1, (int8_t) enabled);
// i actually have this as a 16-bit int for some reason
emit_push_w_dn(block, REG_68K_D_SCRATCH_1);
// push dmg pointer
emit_push_l_disp_an(block, JIT_CTX_DMG, REG_68K_A_CTX);
+1
View File
@@ -5,6 +5,7 @@
void compile_call_dmg_write(struct code_block *block);
void compile_call_dmg_write_imm(struct code_block *block, uint8_t val);
void compile_call_dmg_write_d0(struct code_block *block); // value in D0, address in D1
void compile_call_dmg_read(struct code_block *block);
void compile_call_dmg_read_to_d0(struct code_block *block);
void compile_call_ei_di(struct code_block *block, int enabled);
+5 -6
View File
@@ -97,18 +97,17 @@ void m68k_write_memory_32(unsigned int address, unsigned int value)
static void setup_runtime_stubs(void)
{
// stub_write: writes value byte to address
// Stack layout after jsr: ret(4), dmg(4), addr(2), val(2)
// Note: must zero-extend address since movea.w sign-extends
// Stack layout after jsr: ret(4), dmg(4), addr(2), val(1), empty(1)
// moveq #0, d0 ; clear d0
// move.w 8(sp), d0 ; d0 = address (zero-extended)
// movea.l d0, a0 ; a0 = address
// move.b 11(sp), (a0) ; write value to memory
// move.b 10(sp), (a0) ; write value to memory
// rts
static const uint8_t stub_write[] = {
0x70, 0x00, // moveq #0, d0
0x30, 0x2f, 0x00, 0x08, // move.w 8(sp), d0
0x20, 0x40, // movea.l d0, a0
0x10, 0xaf, 0x00, 0x0b, // move.b 11(sp), (a0)
0x10, 0xaf, 0x00, 0x0a, // move.b 10(sp), (a0)
0x4e, 0x75 // rts
};
@@ -129,7 +128,7 @@ static void setup_runtime_stubs(void)
};
static const uint8_t stub_ei_di[] = {
0x11, 0xef, 0x00, 0x09, 0x40, 0x00, // move.b 9(sp), (U8_INTERRUPTS_ENABLED)
0x31, 0xef, 0x00, 0x08, 0x40, 0x00, // move.w 8(sp), (U16_INTERRUPTS_ENABLED)
0x4e, 0x75 // rts
};
@@ -242,7 +241,7 @@ void run_program(uint8_t *gb_rom, uint16_t start_pc)
m68k_set_reg(M68K_REG_SP, STACK_BASE - 4);
m68k_set_reg(M68K_REG_PC, CODE_BASE);
m68k_execute(1000);
int cycles = m68k_execute(5000);
// Check D0 for next PC or halt
pc = get_dreg(REG_68K_D_NEXT_PC);
+44 -2
View File
@@ -780,7 +780,8 @@ TEST(test_ei)
0x10 // 0x0001: stop
};
run_program(rom, 0);
ASSERT_EQ(get_mem_byte(U8_INTERRUPTS_ENABLED), 1);
// big endian...
ASSERT_EQ(get_mem_byte(U16_INTERRUPTS_ENABLED + 1), 1);
}
TEST(test_di)
@@ -793,7 +794,7 @@ TEST(test_di)
0x10 // 0x0001: stop
};
run_program(rom, 0);
ASSERT_EQ(get_mem_byte(U8_INTERRUPTS_ENABLED), 0);
ASSERT_EQ(get_mem_byte(U16_INTERRUPTS_ENABLED + 1), 0);
}
TEST(test_push_pop_de_hl)
@@ -868,6 +869,43 @@ TEST(test_rst_28)
ASSERT_EQ((get_dreg(REG_68K_D_BC) >> 16) & 0xff, 0x22);
}
TEST(test_ldh_dec_ldh_loop)
{
// Test the pattern: ldh a, (n); dec a; ldh (n), a; jr nz, loop
// This is similar to the sprite counter decrement loop
uint8_t rom[] = {
0x3e, 0x03, // 0x0000: ld a, 3
0xe0, 0x90, // 0x0002: ldh ($ff90), a ; counter = 3
// loop:
0xf0, 0x90, // 0x0004: ldh a, ($ff90) ; read counter
0x3d, // 0x0006: dec a
0xe0, 0x90, // 0x0007: ldh ($ff90), a ; write counter
0x20, 0xf9, // 0x0009: jr nz, -7 (back to 0x0004)
0x10 // 0x000b: stop
};
run_program(rom, 0);
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 0x00); // A should be 0
ASSERT_EQ(get_mem_byte(0xff90), 0x00); // counter should be 0
}
TEST(test_ldh_dec_preserves_value)
{
// Verify that ldh read -> dec -> ldh write preserves the correct byte value
// even if other registers have garbage in high bytes
uint8_t rom[] = {
0x3e, 0x05, // 0x0000: ld a, 5
0xe0, 0x91, // 0x0002: ldh ($ff91), a ; mem = 5
0x3e, 0xff, // 0x0004: ld a, 0xff ; pollute A with 0xff
0xf0, 0x91, // 0x0006: ldh a, ($ff91) ; read back (should be 5)
0x3d, // 0x0008: dec a ; a = 4
0xe0, 0x91, // 0x0009: ldh ($ff91), a ; write 4
0x10 // 0x000b: stop
};
run_program(rom, 0);
ASSERT_EQ(get_dreg(REG_68K_D_A) & 0xff, 0x04);
ASSERT_EQ(get_mem_byte(0xff91), 0x04);
}
void register_exec_tests(void)
{
printf("\nExecution tests:\n");
@@ -992,4 +1030,8 @@ void register_exec_tests(void)
RUN_TEST(test_ei);
RUN_TEST(test_di);
RUN_TEST(test_exec_jr_nz_then_call);
printf("\nLDH counter tests:\n");
RUN_TEST(test_ldh_dec_ldh_loop);
RUN_TEST(test_ldh_dec_preserves_value);
}
+3 -3
View File
@@ -51,7 +51,7 @@ static uint8_t test_read(void *dmg, uint16_t address)
/*
* Assembly stubs that match the JIT calling convention.
* JIT code pushes: value(2), addr(2), dmg(4), then JSR
* So stack after jsr: ret(4), dmg(4), addr(2), value(2)
* So stack after jsr: ret(4), dmg(4), addr(2), value(1), empty(1)
*
* These access the mem[] array using indexed addressing.
*/
@@ -63,7 +63,7 @@ asm(
" moveq #0, %d0\n" /* clear d0 */
" move.w 8(%sp), %d0\n" /* d0 = gb addr (zero-extended) */
" lea mem, %a0\n" /* a0 = mem base */
" move.b 11(%sp), (%a0,%d0.l)\n" /* write value */
" move.b 10(%sp), (%a0,%d0.l)\n" /* write value */
" rts\n"
".globl stub_read_asm\n"
@@ -80,7 +80,7 @@ asm(
".globl stub_ei_di\n"
"stub_ei_di:\n"
" lea mem, %a0\n" /* a0 = mem base */
" move.b 9(%sp), 0x4000(%a0)\n" /* write to mem[U8_INTERRUPTS_ENABLED] */
" move.b 8(%sp), 0x4000(%a0)\n" /* write to mem[U8_INTERRUPTS_ENABLED] */
" rts\n"
);
+1 -1
View File
@@ -84,6 +84,6 @@ void register_unit_tests(void);
void register_exec_tests(void);
#define GLOBALS_BASE 0x4000 // random variables
#define U8_INTERRUPTS_ENABLED 0x4000
#define U16_INTERRUPTS_ENABLED 0x4000
#endif
+23 -46
View File
@@ -124,48 +124,13 @@ static u8 get_button_state(struct dmg *dmg)
return ret;
}
// Advance LCD state based on elapsed CPU cycles (for JIT mode)
static void sync_lcd(struct dmg *dmg)
{
int cycle_diff = dmg->cpu->cycle_count - dmg->last_lcd_update;
while (cycle_diff >= 456) {
int next_ly = lcd_step(dmg->lcd);
dmg->last_lcd_update += 456;
cycle_diff -= 456;
// render at vblank
if (next_ly == 144) {
int lcdc = lcd_read(dmg->lcd, REG_LCDC);
if (lcdc & LCDC_ENABLE_BG) {
int window_enabled = lcdc & LCDC_ENABLE_WINDOW;
lcd_render_background(dmg, lcdc, window_enabled);
}
if (lcdc & LCDC_ENABLE_OBJ) {
lcd_render_objs(dmg);
}
lcd_draw(dmg->lcd);
}
}
}
static u8 dmg_read_slow(struct dmg *dmg, u16 address)
{
// Hacky: advance LCD every time LY is read (for JIT mode)
if (address == REG_LY) {
int next_ly = lcd_step(dmg->lcd);
if (next_ly == 144) {
int lcdc = lcd_read(dmg->lcd, REG_LCDC);
if (lcdc & LCDC_ENABLE_BG) {
int window_enabled = lcdc & LCDC_ENABLE_WINDOW;
lcd_render_background(dmg, lcdc, window_enabled);
}
if (lcdc & LCDC_ENABLE_OBJ) {
lcd_render_objs(dmg);
}
lcd_draw(dmg->lcd);
}
return next_ly;
dmg->cpu->cycle_count += 456;
dmg_sync_hw(dmg);
return lcd_read(dmg->lcd, REG_LY);
}
// OAM and LCD registers
@@ -215,19 +180,27 @@ static u8 dmg_read_slow(struct dmg *dmg, u16 address)
return 0xff;
}
extern void debug_log_string(const char *str);
u8 dmg_read(void *_dmg, u16 address)
{
// char buf[128];
// u8 val;
struct dmg *dmg = (struct dmg *) _dmg;
u8 *page = dmg->read_page[address >> 8];
if (page) {
return page[address & 0xff];
}
// sprintf(buf, "dmg_read pc=%04x addr=%04x value=%02x\n", dmg->cpu->pc, address, val);
// debug_log_string(buf);
return dmg_read_slow(dmg, address);
}
static void dmg_write_slow(struct dmg *dmg, u16 address, u8 data)
{
// char buf[128];
// sprintf(buf, "dmg_write pc=%04x addr=%04x value=%02x\n", dmg->cpu->pc, address, data);
// debug_log_string(buf);
// ROM region writes go to MBC for bank switching
if (address < 0x8000) {
mbc_write(dmg->rom->mbc, dmg, address, data);
@@ -338,13 +311,9 @@ static void timer_step(struct dmg *dmg)
dmg->last_timer_update = dmg->cpu->cycle_count;
}
void dmg_step(void *_dmg)
// Sync hardware state without running CPU - for JIT mode
void dmg_sync_hw(struct dmg *dmg)
{
struct dmg *dmg = (struct dmg *) _dmg;
// order of dependencies? i think cpu needs to step first then update
// all other hw
cpu_step(dmg->cpu);
timer_step(dmg);
// each line takes 456 cycles
@@ -405,7 +374,15 @@ void dmg_step(void *_dmg)
}
}
void dmg_ei_di(void *dmg, u16 enabled)
void dmg_step(void *_dmg)
{
((struct dmg *) dmg)->interrupt_enabled = enabled;
struct dmg *dmg = (struct dmg *) _dmg;
cpu_step(dmg->cpu);
dmg_sync_hw(dmg);
}
void dmg_ei_di(void *_dmg, u16 enabled)
{
struct dmg *dmg = (struct dmg *) _dmg;
dmg->cpu->interrupt_enable = enabled ? 1 : 0;
}
+1
View File
@@ -64,6 +64,7 @@ u8 dmg_read(void *dmg, u16 address);
void dmg_write(void *dmg, u16 address, u8 data);
void dmg_step(void *dmg);
void dmg_sync_hw(struct dmg *dmg);
void dmg_request_interrupt(struct dmg *dmg, int nr);
// page table management
+3 -2
View File
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.9)
include_directories(../src ../compiler)
set(CMAKE_C_FLAGS "-O3")
set(CMAKE_C_FLAGS "-O0")
add_application(Emulator
../src/bootstrap.c
@@ -18,6 +18,7 @@ add_application(Emulator
../compiler/branches.c
../compiler/interop.c
../compiler/flags.c
../compiler/cb_prefix.c
emulator.c
resources.r
)
)
+80 -4
View File
@@ -34,6 +34,29 @@ static struct code_block *block_cache[MAX_CACHED_BLOCKS];
// Debug logging - open/close each time to avoid losing data on crash
static int debug_enabled = 1;
void debug_log_string(const char *str)
{
short fref;
char buf[128];
long len;
OSErr err;
err = FSOpen("\pjit_log.txt", 0, &fref);
if (err == fnfErr) {
Create("\pjit_log.txt", 0, 'ttxt', 'TEXT');
err = FSOpen("\pjit_log.txt", 0, &fref);
}
if (err != noErr) return;
// Seek to end
SetFPos(fref, fsFromLEOF, 0);
len = strlen(str);
FSWrite(fref, &len, str);
buf[0] = '\n';
len = 1;
FSWrite(fref, &len, buf);
FSClose(fref);
}
static void debug_log_block(struct code_block *block)
{
short fref;
@@ -322,6 +345,8 @@ static int jit_step(void)
if (!block) {
sprintf(buf, "JIT: alloc fail pc=%04x", cpu.pc);
set_status_bar(buf);
lcd_draw(dmg.lcd);
jit_halted = 1;
return 0;
}
@@ -330,13 +355,13 @@ static int jit_step(void)
if (block->error) {
sprintf(buf, "pc=%04x op=%02x", block->failed_address, block->failed_opcode);
set_status_bar(buf);
lcd_draw(dmg.lcd);
jit_halted = 1;
block_free(block);
return 0;
}
// Log the compiled block
debug_log_block(block);
// Cache the block
// if (cpu.pc < MAX_CACHED_BLOCKS) {
@@ -344,6 +369,8 @@ static int jit_step(void)
// }
}
// Log the compiled block
debug_log_block(block);
// Execute the block
execute_block(block->code);
@@ -356,6 +383,54 @@ static int jit_step(void)
return 0;
}
// Debug: show interrupt state every ~1000 blocks
{
static int dbg_cnt = 0;
static u16 min_pc = 0xffff, max_pc = 0;
if (cpu.pc < min_pc) min_pc = cpu.pc;
if (cpu.pc > max_pc) max_pc = cpu.pc;
if (++dbg_cnt >= 1000) {
dbg_cnt = 0;
sprintf(buf, "PC=%04x-%04x IE=%02x", min_pc, max_pc,
dmg.interrupt_enabled);
set_status_bar(buf);
lcd_draw(dmg.lcd);
min_pc = 0xffff;
max_pc = 0;
}
}
// Check for pending interrupts
if (cpu.interrupt_enable) {
u8 pending = dmg.interrupt_enabled & dmg.interrupt_requested & 0x1f;
if (pending) {
static const u16 handlers[] = { 0x40, 0x48, 0x50, 0x58, 0x60 };
int k;
for (k = 0; k < 5; k++) {
if (pending & (1 << k)) {
sprintf(buf, "INT %d -> %04x", k, handlers[k]);
set_status_bar(buf);
lcd_draw(dmg.lcd);
// Clear IF bit and disable IME
dmg.interrupt_requested &= ~(1 << k);
cpu.interrupt_enable = 0;
// Push PC to stack
u16 sp = (u16) jit_aregs[REG_68K_A_SP];
sp -= 2;
dmg_write(&dmg, sp + 1, (next_pc >> 8) & 0xff);
dmg_write(&dmg, sp, next_pc & 0xff);
jit_aregs[REG_68K_A_SP] = sp;
// Jump to handler
next_pc = handlers[k];
break;
}
}
}
}
// Update CPU state
cpu.pc = (u16) next_pc;
@@ -363,6 +438,9 @@ static int jit_step(void)
// for now
cpu.cycle_count += block->gb_cycles ? block->gb_cycles : 16;
// Sync LCD/timer state
dmg_sync_hw(&dmg);
return 1;
}
@@ -714,8 +792,6 @@ int main(int argc, char *argv[])
while ((long) (frame_end - cpu.cycle_count) > 0 && !jit_halted) {
jit_step();
}
// since dmg_step is bypassed...
lcd_draw(&lcd);
} else {
// Interpreter mode
while ((long) (frame_end - cpu.cycle_count) > 0) {