beginning ppu
This commit is contained in:
parent
5206c3871e
commit
f4cbfd09c8
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"lcd.h": "c",
|
||||
"ppu_sm.h": "c",
|
||||
"string.h": "c",
|
||||
"common.h": "c",
|
||||
"ppu.h": "c"
|
||||
}
|
||||
}
|
@ -15,5 +15,6 @@ typedef uint64_t u64;
|
||||
#define BETWEEN(a, b, c) ((a >= b) && (a <= c))
|
||||
|
||||
void delay(u32 ms);
|
||||
u32 get_ticks();
|
||||
|
||||
#define NO_IMPL { fprintf(stderr, "NOT YET IMPLEMENTED\n"); exit(-5); }
|
8
include/dma.h
Normal file
8
include/dma.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
void dma_start(u8 start);
|
||||
void dma_tick();
|
||||
|
||||
bool dma_transferring();
|
60
include/lcd.h
Normal file
60
include/lcd.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
typedef struct {
|
||||
//registers
|
||||
u8 lcdc;
|
||||
u8 lcds;
|
||||
u8 scroll_y;
|
||||
u8 scroll_x;
|
||||
u8 ly;
|
||||
u8 ly_compare;
|
||||
u8 dma;
|
||||
u8 bg_palette;
|
||||
u8 obj_palette[2];
|
||||
u8 win_y;
|
||||
u8 win_x;
|
||||
|
||||
//other data...
|
||||
u32 bg_colors[4];
|
||||
u32 sp1_colors[4];
|
||||
u32 sp2_colors[4];
|
||||
} lcd_context;
|
||||
|
||||
typedef enum {
|
||||
MODE_HBLANK,
|
||||
MODE_VBLANK,
|
||||
MODE_OAM,
|
||||
MODE_XFER
|
||||
} lcd_mode;
|
||||
|
||||
lcd_context *lcd_get_context();
|
||||
|
||||
#define LCDC_BGW_ENABLE (BIT(lcd_get_context()->lcdc, 0))
|
||||
#define LCDC_OBJ_ENABLE (BIT(lcd_get_context()->lcdc, 1))
|
||||
#define LCDC_OBJ_HEIGHT (BIT(lcd_get_context()->lcdc, 2) ? 16 : 8)
|
||||
#define LCDC_BG_MAP_AREA (BIT(lcd_get_context()->lcdc, 3) ? 0x9C00 : 0x9800)
|
||||
#define LCDC_BGW_DATA_AREA (BIT(lcd_get_context()->lcdc, 4) ? 0x8000 : 0x8800)
|
||||
#define LCDC_WIN_ENABLE (BIT(lcd_get_context()->lcdc, 5))
|
||||
#define LCDC_WIN_MAP_AREA (BIT(lcd_get_context()->lcdc, 6) ? 0x9C00 : 0x9800)
|
||||
#define LCDC_LCD_ENABLE (BIT(lcd_get_context()->lcdc, 7))
|
||||
|
||||
#define LCDS_MODE ((lcd_mode)(lcd_get_context()->lcds & 0b11))
|
||||
#define LCDS_MODE_SET(mode) { lcd_get_context()->lcds &= ~0b11; lcd_get_context()->lcdc |= mode; }
|
||||
#define LCDS_LYC (BIT(lcd_get_context()->lcds, 2))
|
||||
#define LCDS_LYC_SET(b) BIT_SET(lcd_get_context()->lcds, 2, b)
|
||||
|
||||
typedef enum {
|
||||
SS_HBLANK = (1 << 3),
|
||||
SS_VBLANK = (1 << 4),
|
||||
SS_OAM = (1 << 5),
|
||||
SS_LYC = (1 << 6)
|
||||
} stat_src;
|
||||
|
||||
#define LCDS_STAT_INT(src) (lcd_get_context()->lcds & src)
|
||||
|
||||
void lcd_init();
|
||||
|
||||
u8 lcd_read(u16 address);
|
||||
void lcd_write(u16 address, u8 value);
|
@ -2,5 +2,40 @@
|
||||
|
||||
#include <common.h>
|
||||
|
||||
static const LINES_PER_FRAME = 154;
|
||||
static const TICKS_PER_LINE = 456;
|
||||
static const YRES = 144;
|
||||
static const XRES = 160;
|
||||
|
||||
typedef struct {
|
||||
u8 y;
|
||||
u8 x;
|
||||
u8 tile;
|
||||
|
||||
unsigned f_cgb_pn : 3;
|
||||
unsigned f_cgb_vram_bank : 1;
|
||||
unsigned f_pn : 1;
|
||||
unsigned f_x_flip : 1;
|
||||
unsigned f_y_flip : 1;
|
||||
unsigned f_bgp : 1;
|
||||
} oam_entry;
|
||||
|
||||
typedef struct {
|
||||
oam_entry oam_ram[40];
|
||||
u8 vram[0x2000];
|
||||
|
||||
u32 current_frame;
|
||||
u32 line_ticks;
|
||||
u32 *video_buffer;
|
||||
} ppu_context;
|
||||
|
||||
void ppu_init();
|
||||
void ppu_tick();
|
||||
void ppu_tick();
|
||||
|
||||
void ppu_oam_write(u16 address, u8 value);
|
||||
u8 ppu_oam_read(u16 address);
|
||||
|
||||
void ppu_vram_write(u16 address, u8 value);
|
||||
u8 ppu_vram_read(u16 address);
|
||||
|
||||
ppu_context *ppu_get_context();
|
8
include/ppu_sm.h
Normal file
8
include/ppu_sm.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
|
||||
void ppu_mode_oam();
|
||||
void ppu_mode_xfer();
|
||||
void ppu_mode_vblank();
|
||||
void ppu_mode_hblank();
|
@ -2,5 +2,17 @@
|
||||
|
||||
#include <common.h>
|
||||
|
||||
typedef struct {
|
||||
u16 div;
|
||||
u8 tima;
|
||||
u8 tma;
|
||||
u8 tac;
|
||||
} timer_context;
|
||||
|
||||
void timer_init();
|
||||
void timer_tick();
|
||||
void timer_tick();
|
||||
|
||||
void timer_write(u16 address, u8 value);
|
||||
u8 timer_read(u16 address);
|
||||
|
||||
timer_context *timer_get_context();
|
@ -6,4 +6,5 @@ static const int SCREEN_WIDTH = 1024;
|
||||
static const int SCREEN_HEIGHT = 768;
|
||||
|
||||
void ui_init();
|
||||
void ui_handle_events();
|
||||
void ui_handle_events();
|
||||
void ui_update();
|
23
lib/bus.c
23
lib/bus.c
@ -4,6 +4,8 @@
|
||||
#include <cpu.h>
|
||||
#include <ram.h>
|
||||
#include <io.h>
|
||||
#include <ppu.h>
|
||||
#include <dma.h>
|
||||
|
||||
// 0x0000 - 0x3FFF : ROM Bank 0
|
||||
// 0x4000 - 0x7FFF : ROM Bank 1 - Switchable
|
||||
@ -24,10 +26,7 @@ u8 bus_read(u16 address) {
|
||||
return cart_read(address);
|
||||
} else if (address < 0xA000) {
|
||||
//Char/Map Data
|
||||
//TODO
|
||||
printf("UNSUPPORTED bus_read(%04X)\n", address);
|
||||
//NO_IMPL
|
||||
return 0;
|
||||
return ppu_vram_read(address);
|
||||
} else if (address < 0xC000) {
|
||||
//Cartridge RAM
|
||||
return cart_read(address);
|
||||
@ -39,10 +38,9 @@ u8 bus_read(u16 address) {
|
||||
return 0;
|
||||
} else if (address < 0xFEA0) {
|
||||
//OAM
|
||||
//TODO
|
||||
printf("UNSUPPORTED bus_read(%04X)\n", address);
|
||||
//NO_IMPL
|
||||
return 0;
|
||||
if(dma_transferring())
|
||||
return 0xFF;
|
||||
return ppu_oam_read(address);
|
||||
} else if (address < 0xFF00) {
|
||||
//reserved unusable
|
||||
return 0;
|
||||
@ -66,10 +64,8 @@ void bus_write(u16 address, u8 value) {
|
||||
return;
|
||||
} else if (address < 0xA000) {
|
||||
//Char/Map Data
|
||||
//TODO
|
||||
printf("UNSUPPORTED bus_write(%04X)\n", address);
|
||||
ppu_vram_write(address, value);
|
||||
return;
|
||||
//NO_IMPL
|
||||
} else if (address < 0xC000) {
|
||||
//Cartridge RAM
|
||||
cart_write(address, value);
|
||||
@ -83,10 +79,9 @@ void bus_write(u16 address, u8 value) {
|
||||
return;
|
||||
} else if (address < 0xFEA0) {
|
||||
//OAM
|
||||
//TODO
|
||||
printf("UNSUPPORTED bus_write(%04X)\n", address);
|
||||
if(!dma_transferring())
|
||||
ppu_oam_write(address, value);
|
||||
return;
|
||||
//NO_IMPL
|
||||
} else if (address < 0xFF00) {
|
||||
//reserved unusable
|
||||
return;
|
||||
|
38
lib/cpu.c
38
lib/cpu.c
@ -4,11 +4,36 @@
|
||||
#include <emu.h>
|
||||
#include <interrupts.h>
|
||||
#include <dbg.h>
|
||||
#include <timer.h>
|
||||
|
||||
cpu_context ctx = {0};
|
||||
#define CPU_DEBUG 0
|
||||
#define FILE_LOG 0
|
||||
|
||||
#if FILE_LOG == 1
|
||||
FILE *log;
|
||||
#endif
|
||||
|
||||
void cpu_init() {
|
||||
ctx.regs.a = 0x01;
|
||||
ctx.regs.f = 0xB0;
|
||||
ctx.regs.b = 0x00;
|
||||
ctx.regs.c = 0x13;
|
||||
ctx.regs.d = 0x00;
|
||||
ctx.regs.e = 0xD8;
|
||||
ctx.regs.h = 0x01;
|
||||
ctx.regs.l = 0x4D;
|
||||
ctx.regs.sp = 0xFFFE;
|
||||
ctx.regs.pc = 0x100;
|
||||
ctx.ie_register = 0;
|
||||
ctx.int_flags = 0;
|
||||
ctx.int_master_enabled = false;
|
||||
ctx.enabling_ime = false;
|
||||
|
||||
timer_get_context()->div = 0xABCC;
|
||||
#if FILE_LOG == 1
|
||||
log = fopen("log.txt", "w");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void fetch_instruction() {
|
||||
@ -32,7 +57,15 @@ bool cpu_step() {
|
||||
if(!ctx.halted) {
|
||||
u16 pc = ctx.regs.pc;
|
||||
fetch_instruction();
|
||||
emu_cycles(1);
|
||||
#if FILE_LOG == 1
|
||||
fprintf(log, "A:%02X F:%02X B:%02X C:%02X D:%02X E:%02X H:%02X L:%02X SP:%04X PC:%04X PCMEM:%02X,%02X,%02X,%02X\n",
|
||||
ctx.regs.a, ctx.regs.f, ctx.regs.b, ctx.regs.c, ctx.regs.d, ctx.regs.e, ctx.regs.h, ctx.regs.l, ctx.regs.sp, pc,
|
||||
bus_read(pc), bus_read(pc+1), bus_read(pc+2), bus_read(pc+3)
|
||||
);
|
||||
#endif
|
||||
fetch_data();
|
||||
#if CPU_DEBUG == 1
|
||||
char flags[16];
|
||||
sprintf(flags, "%c%c%c%c",
|
||||
ctx.regs.f & (1 << 7) ? 'Z' : '-',
|
||||
@ -52,6 +85,7 @@ bool cpu_step() {
|
||||
|
||||
dbg_update();
|
||||
dbg_print();
|
||||
#endif
|
||||
|
||||
execute();
|
||||
} else {
|
||||
@ -81,4 +115,8 @@ u8 cpu_get_ie_register(){
|
||||
|
||||
void cpu_set_ie_register(u8 ie){
|
||||
ctx.ie_register = ie;
|
||||
}
|
||||
|
||||
void cpu_request_interrupt(interrupt_type t) {
|
||||
ctx.int_flags |= t;
|
||||
}
|
@ -44,7 +44,7 @@ void fetch_data() {
|
||||
return;
|
||||
case AM_R_MR: {
|
||||
u16 addr = cpu_read_reg(ctx.cur_inst->reg_2);
|
||||
if(ctx.cur_inst->reg_1 == RT_C) {
|
||||
if(ctx.cur_inst->reg_2 == RT_C) {
|
||||
addr |= 0xFF00;
|
||||
}
|
||||
ctx.fetched_data = bus_read(addr);
|
||||
@ -74,7 +74,7 @@ void fetch_data() {
|
||||
cpu_set_reg(RT_HL, cpu_read_reg(RT_HL)-1);
|
||||
return;
|
||||
case AM_R_A8:
|
||||
ctx.fetched_data = bus_read(bus_read(ctx.regs.pc));
|
||||
ctx.fetched_data = bus_read(ctx.regs.pc);
|
||||
emu_cycles(2);
|
||||
ctx.regs.pc++;
|
||||
return;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
//process CPU instructions...
|
||||
|
||||
void cpu_set_flags(cpu_context *ctx, u8 z, u8 n, u8 h, u8 c){
|
||||
void cpu_set_flags(cpu_context *ctx, int8_t z, int8_t n, int8_t h, int8_t c){
|
||||
if (z != -1){
|
||||
BIT_SET(ctx->regs.f, 7, z)
|
||||
}
|
||||
@ -52,7 +52,7 @@ static void proc_daa(cpu_context *ctx) {
|
||||
}
|
||||
|
||||
static void proc_cpl(cpu_context *ctx) {
|
||||
ctx->regs.a = -ctx->regs.a;
|
||||
ctx->regs.a = ~ctx->regs.a;
|
||||
cpu_set_flags(ctx, -1, 1, 1, -1);
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ static void proc_rra(cpu_context *ctx) {
|
||||
static void proc_cb(cpu_context *ctx) {
|
||||
u8 op = ctx->fetched_data;
|
||||
reg_type reg = decode_reg(op & 0b111);
|
||||
u8 bit = (op >> 3) & 0xb111;
|
||||
u8 bit = (op >> 3) & 0b111;
|
||||
u8 bit_op = (op >> 6) & 0b11;
|
||||
u8 reg_val = cpu_read_reg8(reg);
|
||||
emu_cycles(1);
|
||||
@ -138,6 +138,7 @@ static void proc_cb(cpu_context *ctx) {
|
||||
reg_val |= (1 << bit);
|
||||
cpu_set_reg8(reg, reg_val);
|
||||
return;
|
||||
default: break;
|
||||
}
|
||||
|
||||
bool flagC = CPU_FLAG_C;
|
||||
@ -148,7 +149,7 @@ static void proc_cb(cpu_context *ctx) {
|
||||
bool setC = false;
|
||||
u8 result = (reg_val << 1) & 0xFF;
|
||||
|
||||
if(reg_val & (1 << 7) != 0) {
|
||||
if((reg_val & (1 << 7)) != 0) {
|
||||
result |= 1;
|
||||
setC = true;
|
||||
}
|
||||
@ -322,7 +323,7 @@ static void proc_add(cpu_context *ctx) {
|
||||
if(ctx->cur_inst->reg_1 == RT_SP) {
|
||||
z = 0;
|
||||
h = (cpu_read_reg(ctx->cur_inst->reg_1) & 0xF) + (ctx->fetched_data & 0xF) >= 0x10;
|
||||
c = (int)(cpu_read_reg(ctx->cur_inst->reg_1) & 0xFF) + (int)(ctx->fetched_data & 0xFF) > 0x100;
|
||||
c = (int)(cpu_read_reg(ctx->cur_inst->reg_1) & 0xFF) + (int)(ctx->fetched_data & 0xFF) >= 0x100;
|
||||
}
|
||||
|
||||
cpu_set_reg(ctx->cur_inst->reg_1, val & 0xFFFF);
|
||||
@ -349,7 +350,7 @@ static void proc_inc(cpu_context *ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
cpu_set_flags(ctx, val == 0, 0, (val & 0x0F), -1);
|
||||
cpu_set_flags(ctx, val == 0, 0, (val & 0x0F) == 0, -1);
|
||||
}
|
||||
|
||||
static void proc_dec(cpu_context *ctx) {
|
||||
@ -432,8 +433,8 @@ static void proc_ld(cpu_context *ctx) {
|
||||
u8 cflag = (cpu_read_reg(ctx->cur_inst->reg_2) & 0xFF) + (ctx->fetched_data & 0xFF) >= 0x100;
|
||||
cpu_set_flags(ctx, 0, 0, hflag, cflag);
|
||||
cpu_set_reg(ctx->cur_inst->reg_1, cpu_read_reg(ctx->cur_inst->reg_2) + (char)ctx->fetched_data);
|
||||
return;
|
||||
}
|
||||
|
||||
cpu_set_reg(ctx->cur_inst->reg_1, ctx->fetched_data);
|
||||
}
|
||||
|
||||
|
@ -73,14 +73,14 @@ u8 cpu_read_reg8(reg_type rt) {
|
||||
|
||||
void cpu_set_reg8(reg_type rt, u8 val) {
|
||||
switch(rt) {
|
||||
case RT_A: ctx.regs.a = val & 0XFF; break;
|
||||
case RT_F: ctx.regs.f = val & 0XFF; break;
|
||||
case RT_B: ctx.regs.b = val & 0XFF; break;
|
||||
case RT_C: ctx.regs.c = val & 0XFF; break;
|
||||
case RT_D: ctx.regs.d = val & 0XFF; break;
|
||||
case RT_E: ctx.regs.e = val & 0XFF; break;
|
||||
case RT_H: ctx.regs.h = val & 0XFF; break;
|
||||
case RT_L: ctx.regs.l = val & 0XFF; break;
|
||||
case RT_A: ctx.regs.a = val & 0xFF; break;
|
||||
case RT_F: ctx.regs.f = val & 0xFF; break;
|
||||
case RT_B: ctx.regs.b = val & 0xFF; break;
|
||||
case RT_C: ctx.regs.c = val & 0xFF; break;
|
||||
case RT_D: ctx.regs.d = val & 0xFF; break;
|
||||
case RT_E: ctx.regs.e = val & 0xFF; break;
|
||||
case RT_H: ctx.regs.h = val & 0xFF; break;
|
||||
case RT_L: ctx.regs.l = val & 0xFF; break;
|
||||
|
||||
case RT_HL: bus_write(cpu_read_reg(RT_HL), val); break;
|
||||
|
||||
|
40
lib/dma.c
Normal file
40
lib/dma.c
Normal file
@ -0,0 +1,40 @@
|
||||
#include <dma.h>
|
||||
#include <ppu.h>
|
||||
#include <bus.h>
|
||||
|
||||
typedef struct {
|
||||
bool active;
|
||||
u8 byte;
|
||||
u8 value;
|
||||
u8 start_delay;
|
||||
} dma_context;
|
||||
|
||||
static dma_context ctx;
|
||||
|
||||
void dma_start(u8 start) {
|
||||
ctx.active = true;
|
||||
ctx.byte = 0;
|
||||
ctx.start_delay = 2;
|
||||
ctx.value = start;
|
||||
}
|
||||
|
||||
void dma_tick() {
|
||||
if (!ctx.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.start_delay) {
|
||||
ctx.start_delay--;
|
||||
return;
|
||||
}
|
||||
|
||||
ppu_oam_write(ctx.byte, bus_read((ctx.value * 0x100) + ctx.byte));
|
||||
|
||||
ctx.byte++;
|
||||
|
||||
ctx.active = ctx.byte < 0xA0;
|
||||
}
|
||||
|
||||
bool dma_transferring() {
|
||||
return ctx.active;
|
||||
}
|
21
lib/emu.c
21
lib/emu.c
@ -3,6 +3,9 @@
|
||||
#include <cart.h>
|
||||
#include <cpu.h>
|
||||
#include <ui.h>
|
||||
#include <timer.h>
|
||||
#include <dma.h>
|
||||
#include <ppu.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
@ -19,6 +22,8 @@ emu_context *emu_get_context() {
|
||||
}
|
||||
|
||||
DWORD WINAPI cpu_run(void *p) {
|
||||
ppu_init();
|
||||
timer_init();
|
||||
cpu_init();
|
||||
|
||||
ctx.running = true;
|
||||
@ -36,7 +41,6 @@ DWORD WINAPI cpu_run(void *p) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
ctx.ticks++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -80,9 +84,15 @@ int emu_run(int argc, char **argv) {
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 prev_frame = 0;
|
||||
|
||||
while(!ctx.die) {
|
||||
sleep_ms(1);
|
||||
ui_handle_events();
|
||||
if (prev_frame != ppu_get_context()->current_frame) {
|
||||
ui_update();
|
||||
}
|
||||
prev_frame = ppu_get_context()->current_frame;
|
||||
}
|
||||
|
||||
|
||||
@ -90,5 +100,12 @@ int emu_run(int argc, char **argv) {
|
||||
}
|
||||
|
||||
void emu_cycles(int cpu_cycles) {
|
||||
//TODO
|
||||
for (int i = 0; i < cpu_cycles; i++){
|
||||
for(int n = 0; n < 4; n++){
|
||||
ctx.ticks++;
|
||||
timer_tick();
|
||||
ppu_tick();
|
||||
}
|
||||
dma_tick();
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ instruction instructions[0x100] = {
|
||||
[0x0C] = {IN_INC, AM_R, RT_C},
|
||||
[0x0D] = {IN_DEC, AM_R, RT_C},
|
||||
[0x0E] = {IN_LD, AM_R_D8, RT_C},
|
||||
[0x0F] = {IN_RRA},
|
||||
[0x0F] = {IN_RRCA},
|
||||
//0x1X
|
||||
[0x10] = {IN_STOP},
|
||||
[0x11] = {IN_LD, AM_R_D16, RT_DE},
|
||||
@ -258,7 +258,7 @@ instruction instructions[0x100] = {
|
||||
[0xF6] = {IN_OR, AM_R_D8, RT_A},
|
||||
[0xF7] = {IN_RST, AM_IMP, RT_NONE, RT_NONE, CT_NONE, 0x30},
|
||||
[0xF8] = {IN_LD, AM_HL_SPR, RT_HL, RT_SP},
|
||||
[0xF9] = {IN_LD, AM_R_R, RT_HL, RT_SP},
|
||||
[0xF9] = {IN_LD, AM_R_R, RT_SP, RT_HL},
|
||||
[0xFA] = {IN_LD, AM_R_A16, RT_A},
|
||||
[0xFB] = {IN_EI},
|
||||
[0xFE] = {IN_CP, AM_R_D8, RT_A},
|
||||
|
@ -9,7 +9,7 @@ void int_handle(cpu_context *ctx, u16 address) {
|
||||
bool int_check(cpu_context *ctx, u16 address, interrupt_type t){
|
||||
if(ctx->int_flags & t && ctx->ie_register & t) {
|
||||
int_handle(ctx, address);
|
||||
ctx->int_flags &= -t;
|
||||
ctx->int_flags &= ~t;
|
||||
ctx->halted = false;
|
||||
ctx->int_master_enabled = false;
|
||||
return true;
|
||||
|
31
lib/io.c
31
lib/io.c
@ -1,4 +1,8 @@
|
||||
#include <io.h>
|
||||
#include <timer.h>
|
||||
#include <cpu.h>
|
||||
#include <dma.h>
|
||||
#include <lcd.h>
|
||||
|
||||
static char serial_data[2];
|
||||
|
||||
@ -11,6 +15,18 @@ u8 io_read(u16 address){
|
||||
return serial_data[1];
|
||||
}
|
||||
|
||||
if(BETWEEN(address, 0xFF04, 0xFF07)){
|
||||
return timer_read(address);
|
||||
}
|
||||
|
||||
if(BETWEEN(address, 0xFF40, 0xFF4B)){
|
||||
return lcd_read(address);
|
||||
}
|
||||
|
||||
if(address == 0xFF0F) {
|
||||
return cpu_get_int_flags();
|
||||
}
|
||||
|
||||
printf("UNSUPPORTED io_read(%04X)\n", address);
|
||||
return 0;
|
||||
}
|
||||
@ -27,6 +43,21 @@ void io_write(u16 address, u8 value){
|
||||
return;
|
||||
}
|
||||
|
||||
if(BETWEEN(address, 0xFF04, 0xFF07)){
|
||||
timer_write(address, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if(address == 0xFF0F) {
|
||||
cpu_set_int_flags(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if(BETWEEN(address, 0xFF40, 0xFF4B)){
|
||||
lcd_write(address, value);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("UNSUPPORTED io_write(%04X)\n", address);
|
||||
}
|
||||
|
||||
|
73
lib/lcd.c
Normal file
73
lib/lcd.c
Normal file
@ -0,0 +1,73 @@
|
||||
#include <lcd.h>
|
||||
#include <ppu.h>
|
||||
#include <dma.h>
|
||||
|
||||
static lcd_context ctx;
|
||||
|
||||
static unsigned long colors_default[4] = {0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000};
|
||||
|
||||
lcd_context *lcd_get_context() {
|
||||
return &ctx;
|
||||
}
|
||||
|
||||
void lcd_init() {
|
||||
ctx.lcdc = 0x91;
|
||||
ctx.scroll_x = 0;
|
||||
ctx.scroll_y = 0;
|
||||
ctx.ly = 0;
|
||||
ctx.ly_compare = 0;
|
||||
ctx.bg_palette = 0xFC;
|
||||
ctx.obj_palette[0] = 0xFF;
|
||||
ctx.obj_palette[1] = 0xFF;
|
||||
ctx.win_x = 0;
|
||||
ctx.win_y = 0;
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
ctx.bg_colors[i] = colors_default[i];
|
||||
ctx.sp1_colors[i] = colors_default[i];
|
||||
ctx.sp2_colors[i] = colors_default[i];
|
||||
}
|
||||
}
|
||||
|
||||
u8 lcd_read(u16 address) {
|
||||
u8 offset = (address - 0xFF40);
|
||||
u8 *p = (u8 *)&ctx;
|
||||
return p[offset];
|
||||
}
|
||||
|
||||
void update_palette(u8 palette_data, u8 pal) {
|
||||
u32 *p_colors = ctx.bg_colors;
|
||||
|
||||
switch(pal) {
|
||||
case 1:
|
||||
p_colors = ctx.sp1_colors;
|
||||
break;
|
||||
case 2:
|
||||
p_colors = ctx.sp2_colors;
|
||||
break;
|
||||
}
|
||||
|
||||
p_colors[0] = colors_default[palette_data & 0b11];
|
||||
p_colors[1] = colors_default[(palette_data >> 2) & 0b11];
|
||||
p_colors[2] = colors_default[(palette_data >> 4) & 0b11];
|
||||
p_colors[3] = colors_default[(palette_data >> 6) & 0b11];
|
||||
}
|
||||
|
||||
void lcd_write(u16 address, u8 value) {
|
||||
u8 offset = (address - 0xFF40);
|
||||
u8 *p = (u8 *)&ctx;
|
||||
p[offset] = value;
|
||||
|
||||
if(offset == 6) {
|
||||
//offset FF64
|
||||
dma_start(value);
|
||||
}
|
||||
|
||||
if(offset == 7) {
|
||||
update_palette(value, 0);
|
||||
} else if(offset == 8) {
|
||||
update_palette(value & 0b11111100, 1);
|
||||
} else if(offset == 9) {
|
||||
update_palette(value & 0b11111100, 2);
|
||||
}
|
||||
}
|
60
lib/ppu.c
60
lib/ppu.c
@ -1,9 +1,67 @@
|
||||
#include <ppu.h>
|
||||
#include <lcd.h>
|
||||
#include <string.h>
|
||||
#include <ppu_sm.h>
|
||||
|
||||
static ppu_context ctx;
|
||||
|
||||
ppu_context *ppu_get_context() {
|
||||
return &ctx;
|
||||
}
|
||||
|
||||
void ppu_init() {
|
||||
ctx.current_frame = 0;
|
||||
ctx.line_ticks = 0;
|
||||
ctx.video_buffer = malloc(YRES * XRES * sizeof(u32));
|
||||
|
||||
lcd_init();
|
||||
LCDS_MODE_SET(MODE_OAM);
|
||||
|
||||
memset(ctx.oam_ram, 0, sizeof(ctx.oam_ram));
|
||||
memset(ctx.video_buffer, 0, YRES*XRES*sizeof(u32));
|
||||
}
|
||||
|
||||
void ppu_tick() {
|
||||
|
||||
ctx.line_ticks++;
|
||||
|
||||
switch(LCDS_MODE) {
|
||||
case MODE_OAM:
|
||||
ppu_mode_oam();
|
||||
break;
|
||||
case MODE_XFER:
|
||||
ppu_mode_xfer();
|
||||
break;
|
||||
case MODE_VBLANK:
|
||||
ppu_mode_vblank();
|
||||
break;
|
||||
case MODE_HBLANK:
|
||||
ppu_mode_hblank();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ppu_oam_write(u16 address, u8 value) {
|
||||
if (address >= 0xFE00) {
|
||||
address -= 0xFE00;
|
||||
}
|
||||
|
||||
u8 *p = (u8 *)ctx.oam_ram;
|
||||
p[address] = value;
|
||||
}
|
||||
|
||||
u8 ppu_oam_read(u16 address){
|
||||
if (address > 0xFE00) {
|
||||
address -= 0xFE00;
|
||||
}
|
||||
|
||||
u8 *p = (u8 *)ctx.oam_ram;
|
||||
return p[address];
|
||||
}
|
||||
|
||||
void ppu_vram_write(u16 address, u8 value){
|
||||
ctx.vram[address - 0x8000] = value;
|
||||
}
|
||||
|
||||
u8 ppu_vram_read(u16 address){
|
||||
return ctx.vram[address - 0x8000];
|
||||
}
|
90
lib/ppu_sm.c
Normal file
90
lib/ppu_sm.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include <ppu.h>
|
||||
#include <lcd.h>
|
||||
#include <cpu.h>
|
||||
#include <interrupts.h>
|
||||
|
||||
void increment_ly() {
|
||||
lcd_get_context()->ly++;
|
||||
|
||||
if(lcd_get_context()->ly == lcd_get_context()->ly_compare) {
|
||||
LCDS_LYC_SET(1);
|
||||
|
||||
if(LCDS_STAT_INT(SS_LYC)) {
|
||||
cpu_request_interrupt(IT_LCD_STAT);
|
||||
}
|
||||
} else {
|
||||
LCDS_LYC_SET(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ppu_mode_oam() {
|
||||
if(ppu_get_context()->line_ticks >= 80) {
|
||||
LCDS_MODE_SET(MODE_XFER);
|
||||
}
|
||||
}
|
||||
|
||||
void ppu_mode_xfer() {
|
||||
if(ppu_get_context()->line_ticks >= 80 + 172) {
|
||||
LCDS_MODE_SET(MODE_HBLANK);
|
||||
}
|
||||
}
|
||||
|
||||
void ppu_mode_vblank() {
|
||||
if(ppu_get_context()->line_ticks >= TICKS_PER_LINE) {
|
||||
increment_ly();
|
||||
|
||||
if(lcd_get_context()->ly >= LINES_PER_FRAME) {
|
||||
LCDS_MODE_SET(MODE_OAM);
|
||||
lcd_get_context()->ly = 0;
|
||||
}
|
||||
|
||||
ppu_get_context()->line_ticks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 target_frame_time = 1000/60;
|
||||
static long prev_frame_time = 0;
|
||||
static long start_timer = 0;
|
||||
static long frame_count = 0;
|
||||
|
||||
void ppu_mode_hblank() {
|
||||
if (ppu_get_context()->line_ticks >= TICKS_PER_LINE) {
|
||||
increment_ly();
|
||||
|
||||
if(lcd_get_context()->ly >= YRES) {
|
||||
LCDS_MODE_SET(MODE_VBLANK);
|
||||
|
||||
cpu_request_interrupt(IT_VBLANK);
|
||||
|
||||
if(LCDS_STAT_INT(SS_VBLANK)) {
|
||||
cpu_request_interrupt(IT_LCD_STAT);
|
||||
}
|
||||
|
||||
ppu_get_context()->current_frame++;
|
||||
|
||||
//calc FPS...
|
||||
u32 end = get_ticks();
|
||||
u32 frame_time = end - prev_frame_time;
|
||||
|
||||
if(frame_time < target_frame_time) {
|
||||
delay((target_frame_time - frame_time));
|
||||
}
|
||||
|
||||
if (end - start_timer >= 1000) {
|
||||
u32 fps = frame_count;
|
||||
start_timer = end;
|
||||
frame_count = 0;
|
||||
printf("FPS: %ld\n", fps);
|
||||
}
|
||||
|
||||
frame_count++;
|
||||
prev_frame_time = get_ticks();
|
||||
|
||||
|
||||
} else {
|
||||
LCDS_MODE_SET(MODE_OAM);
|
||||
}
|
||||
|
||||
ppu_get_context()->line_ticks = 0;
|
||||
}
|
||||
}
|
82
lib/timer.c
82
lib/timer.c
@ -1,10 +1,88 @@
|
||||
|
||||
#include <timer.h>
|
||||
#include <interrupts.h>
|
||||
|
||||
static timer_context ctx = {0};
|
||||
|
||||
void timer_init() {
|
||||
|
||||
ctx.div = 0XAC00;
|
||||
}
|
||||
|
||||
void timer_tick() {
|
||||
|
||||
u16 prev_div = ctx.div;
|
||||
ctx.div++;
|
||||
bool timer_update = false;
|
||||
switch(ctx.tac & 0b11) {
|
||||
case 0b00:
|
||||
timer_update = (prev_div & (1 << 9)) && (!(ctx.div & (1 << 9)));
|
||||
break;
|
||||
|
||||
case 0b01:
|
||||
timer_update = (prev_div & (1 << 3)) && (!(ctx.div & (1 << 3)));
|
||||
break;
|
||||
|
||||
case 0b10:
|
||||
timer_update = (prev_div & (1 << 5)) && (!(ctx.div & (1 << 5)));
|
||||
break;
|
||||
|
||||
case 0b11:
|
||||
timer_update = (prev_div & (1 << 7)) && (!(ctx.div & (1 << 7)));
|
||||
break;
|
||||
}
|
||||
|
||||
if(timer_update && ctx.tac & (1 << 2)) {
|
||||
ctx.tima++;
|
||||
if(ctx.tima == 0xFF){
|
||||
ctx.tima = ctx.tma;
|
||||
cpu_request_interrupt(IT_TIMER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void timer_write(u16 address, u8 value){
|
||||
switch(address) {
|
||||
case 0xFF04:
|
||||
//DIV
|
||||
ctx.div = 0;
|
||||
break;
|
||||
|
||||
case 0xFF05:
|
||||
//TIMA
|
||||
ctx.tima = value;
|
||||
break;
|
||||
|
||||
case 0xFF06:
|
||||
//TMA
|
||||
ctx.tma = value;
|
||||
break;
|
||||
|
||||
case 0xFF07:
|
||||
//TAC
|
||||
ctx.tac = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u8 timer_read(u16 address) {
|
||||
switch(address) {
|
||||
case 0xFF04:
|
||||
//DIV
|
||||
return ctx.div >> 8;
|
||||
|
||||
case 0xFF05:
|
||||
//TIMA
|
||||
return ctx.tima;
|
||||
|
||||
case 0xFF06:
|
||||
//TMA
|
||||
return ctx.tma;
|
||||
|
||||
case 0xFF07:
|
||||
//TAC
|
||||
return ctx.tac;
|
||||
}
|
||||
}
|
||||
|
||||
timer_context *timer_get_context() {
|
||||
return &ctx;
|
||||
}
|
84
lib/ui.c
84
lib/ui.c
@ -9,6 +9,13 @@ SDL_Renderer *sdlRenderer;
|
||||
SDL_Texture *sdlTexture;
|
||||
SDL_Surface *screen;
|
||||
|
||||
SDL_Window *sdlDebugWindow;
|
||||
SDL_Renderer *sdlDebugRenderer;
|
||||
SDL_Texture *sdlDebugTexture;
|
||||
SDL_Surface *debugScreen;
|
||||
|
||||
static int scale = 4;
|
||||
|
||||
void ui_init(){
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
printf("SDL INIT\n");
|
||||
@ -16,6 +23,79 @@ void ui_init(){
|
||||
printf("TTF INIT\n");
|
||||
|
||||
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, 0, &sdlWindow, &sdlRenderer);
|
||||
SDL_CreateWindowAndRenderer(16 * 8 * scale, 32 * 8 * scale, 0, &sdlDebugWindow, &sdlDebugRenderer);
|
||||
debugScreen = SDL_CreateRGBSurface(0, (16 * 8 * scale) + (16 * scale),
|
||||
(32 * 8 * scale) + (64 * scale), 32,
|
||||
0x00FF0000,
|
||||
0x0000FF00,
|
||||
0x000000FF,
|
||||
0xFF000000);
|
||||
sdlDebugTexture = SDL_CreateTexture(sdlDebugRenderer,
|
||||
SDL_PIXELFORMAT_ARGB8888,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
(16 * 8 * scale) + (16 * scale),
|
||||
(32 * 8 * scale) + (64 * scale));
|
||||
int x, y;
|
||||
SDL_GetWindowPosition(sdlWindow, &x, &y);
|
||||
SDL_SetWindowPosition(sdlDebugWindow, x+SCREEN_WIDTH, y);
|
||||
}
|
||||
|
||||
static unsigned long tile_colors[4] = {0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000};
|
||||
|
||||
void display_tile(SDL_Surface *surface, u16 startLocation, u16 tileNum, int x, int y) {
|
||||
SDL_Rect rc;
|
||||
for(int tileY = 0; tileY < 16; tileY += 2) {
|
||||
u8 b1 = bus_read(startLocation+(tileNum * 16) + tileY);
|
||||
u8 b2 = bus_read(startLocation+(tileNum * 16) + tileY + 1);
|
||||
|
||||
for (int bit=7; bit >= 0; bit--) {
|
||||
u8 hi = !!(b1 & (1 << bit)) << 1;
|
||||
u8 lo = !!(b2 & (1 << bit));
|
||||
|
||||
u8 color = hi | lo;
|
||||
rc.x = x + ((7 - bit) * scale);
|
||||
rc.y = y + (tileY/2 * scale);
|
||||
rc.w = scale;
|
||||
rc.h = scale;
|
||||
SDL_FillRect(surface, &rc, tile_colors[color]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_debug_window() {
|
||||
int xDraw = 0;
|
||||
int yDraw = 0;
|
||||
int tileNum = 0;
|
||||
|
||||
SDL_Rect rc;
|
||||
rc.x = 0;
|
||||
rc.y = 0;
|
||||
rc.w = debugScreen->w;
|
||||
rc.h = debugScreen->h;
|
||||
SDL_FillRect(debugScreen, &rc, 0xFF111111);
|
||||
|
||||
u16 addr = 0x8000;
|
||||
|
||||
//384 tiles, 24 x 16
|
||||
|
||||
for(int y = 0; y < 24; y++) {
|
||||
for(int x = 0; x < 16; x++) {
|
||||
display_tile(debugScreen, addr, tileNum, xDraw + (x * scale), yDraw + (y * scale));
|
||||
xDraw += (8 * scale);
|
||||
tileNum++;
|
||||
}
|
||||
yDraw += (8 * scale);
|
||||
xDraw = 0;
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(sdlDebugTexture, NULL, debugScreen->pixels, debugScreen->pitch);
|
||||
SDL_RenderClear(sdlDebugRenderer);
|
||||
SDL_RenderCopy(sdlDebugRenderer, sdlDebugTexture, NULL, NULL);
|
||||
SDL_RenderPresent(sdlDebugRenderer);
|
||||
}
|
||||
|
||||
void ui_update() {
|
||||
update_debug_window();
|
||||
}
|
||||
|
||||
void ui_handle_events(){
|
||||
@ -29,4 +109,8 @@ void ui_handle_events(){
|
||||
|
||||
void delay(u32 ms) {
|
||||
SDL_Delay(ms);
|
||||
}
|
||||
|
||||
u32 get_ticks() {
|
||||
return SDL_GetTicks();
|
||||
}
|
@ -7,5 +7,6 @@ db "My Test ROM ", 0 ; title
|
||||
ds $150 - @, 0
|
||||
|
||||
EntryPoint:
|
||||
xor a
|
||||
ld a,0x80
|
||||
rlc a
|
||||
halt
|
BIN
roms/test.gb
BIN
roms/test.gb
Binary file not shown.
Loading…
Reference in New Issue
Block a user