Compare commits

...

3 Commits

Author SHA1 Message Date
Matthew Laux bc3f717285 add vram tile viewer thing 2022-07-14 02:11:20 -05:00
Matthew Laux b1dca59463 run rom image, more instructions 2022-07-14 01:26:59 -05:00
Matthew Laux 261bb40563 first output: nintendo logo 2022-07-14 00:48:18 -05:00
5 changed files with 248 additions and 116 deletions

View File

@ -11,7 +11,7 @@ execute_process(COMMAND sdl2-config --libs
OUTPUT_VARIABLE SDL_LIBS
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CMAKE_CXX_FLAGS "-std=c++14 ${SDL_CFLAGS}")
set(CMAKE_CXX_FLAGS "-std=c++14 -g -DGB6_DEBUG ${SDL_CFLAGS}")
add_executable(gb6
../src/bootstrap.c

View File

@ -9,6 +9,7 @@
#include "imgui/imgui_memory_editor.h"
#include <stdio.h>
#include <SDL.h>
#include <SDL_timer.h>
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <SDL_opengles2.h>
#else
@ -49,6 +50,7 @@ GLuint make_output_texture() {
}
unsigned char output_image[256 * 256 * 4];
unsigned char vram_tiles[256 * 96 * 4];
void convert_output(struct lcd *lcd) {
int x, y;
@ -57,14 +59,41 @@ void convert_output(struct lcd *lcd) {
for (x = 0; x < 256; x++) {
int val = lcd->buf[y * 256 + x];
int fill = val ? 255 : 0;
output_image[out_index++] = val;
output_image[out_index++] = val;
output_image[out_index++] = val;
output_image[out_index++] = fill;
output_image[out_index++] = fill;
output_image[out_index++] = fill;
output_image[out_index++] = 255;
}
}
}
void convert_vram(struct dmg *dmg) {
int tile_y, tile_x;
int off, in;
for (tile_y = 0; tile_y < 12; tile_y++) {
for (tile_x = 0; tile_x < 32; tile_x++) {
off = 256 * 8 * tile_y + 8 * tile_x;
in = 16 * (tile_y * 32 + tile_x);
int b, i;
for (b = 0; b < 16; b += 2) {
int data1 = dmg->video_ram[in + b];
int data2 = dmg->video_ram[in + b + 1];
for (i = 7; i >= 0; i--) {
// monochrome for now
int fill = (data1 & (1 << i)) ? 255 : 0;
vram_tiles[4 * off + 0] = fill;
vram_tiles[4 * off + 1] = fill;
vram_tiles[4 * off + 2] = fill;
vram_tiles[4 * off + 3] = 255;
//dmg->lcd->buf[off] |= (data2 & (1 << i)) ? 1 : 0;
off++;
}
off += 248;
}
}
}
}
char full_address_space[0x10000];
void fill_memory_editor(struct dmg *dmg)
{
@ -164,6 +193,7 @@ int main(int argc, char *argv[])
// setup output
GLuint texture = make_output_texture();
GLuint vram_texture = make_output_texture();
// Our state
bool z_flag = false;
@ -173,88 +203,104 @@ int main(int argc, char *argv[])
// Main loop
bool done = false;
unsigned int lastDrawTime = 0, currentTime;
while (!done)
{
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT)
done = true;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
done = true;
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
dmg_step(&dmg);
z_flag = flag_isset(dmg.cpu, FLAG_ZERO);
n_flag = flag_isset(dmg.cpu, FLAG_SIGN);
h_flag = flag_isset(dmg.cpu, FLAG_HALF_CARRY);
c_flag = flag_isset(dmg.cpu, FLAG_CARRY);
currentTime = SDL_GetTicks();
if (currentTime >= lastDrawTime + 16) {
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT)
done = true;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
done = true;
}
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
{
ImGui::Begin("State"); // Create a window called "Hello, world!" and append into it.
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
ImGui::Text(A_FORMAT, dmg.cpu->a);
ImGui::Text(B_FORMAT, dmg.cpu->b);
ImGui::SameLine();
ImGui::Text(C_FORMAT, dmg.cpu->c);
ImGui::Text(D_FORMAT, dmg.cpu->d);
ImGui::SameLine();
ImGui::Text(E_FORMAT, dmg.cpu->e);
ImGui::Text(H_FORMAT, dmg.cpu->h);
ImGui::SameLine();
ImGui::Text(L_FORMAT, dmg.cpu->l);
ImGui::Text(SP_FORMAT, dmg.cpu->sp);
ImGui::SameLine();
ImGui::Text(PC_FORMAT, dmg.cpu->pc);
z_flag = flag_isset(dmg.cpu, FLAG_ZERO);
n_flag = flag_isset(dmg.cpu, FLAG_SIGN);
h_flag = flag_isset(dmg.cpu, FLAG_HALF_CARRY);
c_flag = flag_isset(dmg.cpu, FLAG_CARRY);
ImGui::Checkbox("Z", &z_flag);
ImGui::SameLine();
ImGui::Checkbox("N", &n_flag);
ImGui::SameLine();
ImGui::Checkbox("H", &h_flag);
ImGui::SameLine();
ImGui::Checkbox("C", &c_flag);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
{
ImGui::Begin("State"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
ImGui::Text(A_FORMAT, dmg.cpu->a);
ImGui::Text(B_FORMAT, dmg.cpu->b);
ImGui::SameLine();
ImGui::Text(C_FORMAT, dmg.cpu->c);
ImGui::Text(D_FORMAT, dmg.cpu->d);
ImGui::SameLine();
ImGui::Text(E_FORMAT, dmg.cpu->e);
ImGui::Text(H_FORMAT, dmg.cpu->h);
ImGui::SameLine();
ImGui::Text(L_FORMAT, dmg.cpu->l);
ImGui::Text(SP_FORMAT, dmg.cpu->sp);
ImGui::SameLine();
ImGui::Text(PC_FORMAT, dmg.cpu->pc);
ImGui::Checkbox("Z", &z_flag);
ImGui::SameLine();
ImGui::Checkbox("N", &n_flag);
ImGui::SameLine();
ImGui::Checkbox("H", &h_flag);
ImGui::SameLine();
ImGui::Checkbox("C", &c_flag);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
{
ImGui::Begin("Output");
convert_output(dmg.lcd);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, output_image);
ImGui::Image((void*)(intptr_t) texture, ImVec2(256, 256));
ImGui::End();
}
{
ImGui::Begin("VRAM");
convert_vram(&dmg);
glBindTexture(GL_TEXTURE_2D, vram_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 96, 0, GL_RGBA, GL_UNSIGNED_BYTE, vram_tiles);
ImGui::Image((void*)(intptr_t) vram_texture, ImVec2(256, 96));
ImGui::End();
}
fill_memory_editor(&dmg);
editor.DrawWindow("Memory", full_address_space, 0x10000, 0x0000);
// Rendering
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
lastDrawTime = currentTime;
}
{
ImGui::Begin("Output");
convert_output(dmg.lcd);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, output_image);
ImGui::Image((void*)(intptr_t) texture, ImVec2(256, 256));
ImGui::End();
}
fill_memory_editor(&dmg);
editor.DrawWindow("Memory", full_address_space, 0x10000, 0x0000);
editor.DrawWindow("LCD", dmg.lcd->buf, 0x2000, 0);
// Rendering
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
}
rom_free(&rom);

109
src/cpu.c
View File

@ -110,23 +110,53 @@ static void dec_with_carry(struct cpu *regs, u8 *reg)
static u8 rotate_left(struct cpu *regs, u8 reg)
{
// copy old leftmost bit to carry flag
regs->f = (reg & 0x80) >> 3 | (regs->f & ~FLAG_CARRY);
int old_carry = flag_isset(regs, FLAG_CARRY);
// copy old leftmost bit to carry flag, clear Z, N, H
regs->f = (reg & 0x80) >> 3;
// rotate
int result = reg << 1;
// restore leftmost (now rightmost) bit
result |= (regs->f & FLAG_CARRY) >> 4;
int result = reg << 1 | old_carry;
if (!result) set_flag(regs, FLAG_ZERO);
return result;
}
static u8 rlc(struct cpu *cpu, u8 val)
{
int old_msb = (val & 0x80) >> 7;
int result = (val & 0x7f) << 1 | old_msb;
if (!result)
set_flag(cpu, FLAG_ZERO);
else clear_flag(cpu, FLAG_ZERO);
clear_flag(cpu, FLAG_SIGN);
clear_flag(cpu, FLAG_HALF_CARRY);
if (old_msb)
set_flag(cpu, FLAG_CARRY);
else clear_flag(cpu, FLAG_CARRY);
return result;
}
static u8 rotate_right(struct cpu *regs, u8 reg)
{
// copy old rightmost bit to carry flag
regs->f = (reg & 0x01) << 4 | (regs->f & ~FLAG_CARRY);
int old_carry = flag_isset(regs, FLAG_CARRY) << 7;
// copy old rightmost bit to carry flag, clear ZNH
regs->f = (reg & 0x01) << 4;
// rotate
int result = reg >> 1;
// restore rightmost bit to left
result |= (regs->f & FLAG_CARRY) << 3;
int result = old_carry | reg >> 1;
if (!result) set_flag(regs, FLAG_ZERO);
return result;
}
static u8 rrc(struct cpu *cpu, u8 val)
{
int old_lsb = (val & 1) << 7;
int result = old_lsb | (val & 0xfe) >> 1;
if (!result)
set_flag(cpu, FLAG_ZERO);
else clear_flag(cpu, FLAG_ZERO);
clear_flag(cpu, FLAG_SIGN);
clear_flag(cpu, FLAG_HALF_CARRY);
if (old_lsb)
set_flag(cpu, FLAG_CARRY);
else clear_flag(cpu, FLAG_CARRY);
return result;
}
@ -202,10 +232,18 @@ static void subtract(struct cpu *cpu, u8 value, int with_carry, int just_compare
if (with_carry && flag_isset(cpu, FLAG_CARRY)) {
sum_full--;
}
sum_trunc = (u8) sum_full;
set_flag(cpu, sum_trunc == 0 ? FLAG_ZERO : 0);
sum_trunc = (u8) (sum_full & 0xff);
if (!sum_trunc) {
set_flag(cpu, FLAG_ZERO);
} else {
clear_flag(cpu, FLAG_ZERO);
}
set_flag(cpu, FLAG_SIGN);
set_flag(cpu, sum_full < sum_trunc ? FLAG_CARRY : 0);
if (sum_full < sum_trunc) {
set_flag(cpu, FLAG_CARRY);
} else {
clear_flag(cpu, FLAG_CARRY);
}
// TODO H
if (!just_compare) {
@ -296,10 +334,10 @@ static void extended_insn(struct cpu *cpu, u8 insn)
int reg = insn & 0x7;
u8 (*funcs[8])(struct cpu *, u8) = {
rlc,
rrc,
rotate_left,
rotate_right,
rotate_left, // TODO non-carry version
rotate_right,
shift_left,
shift_right,
swap,
@ -356,7 +394,7 @@ void cpu_step(struct cpu *cpu)
cpu->pc += 2;
break;
case 0x07: // RLCA
cpu->a = rotate_left(cpu, cpu->a);
cpu->a = rlc(cpu, cpu->a);
break;
case 0x08: // LD (a16),SP
write16(cpu, read16(cpu, cpu->pc), cpu->sp);
@ -544,6 +582,7 @@ void cpu_step(struct cpu *cpu)
case 0xc0: // RET NZ
if (!flag_isset(cpu, FLAG_ZERO)) {
cpu->pc = pop(cpu);
cpu->cycle_count += instructions[opc].cycles_branch - instructions[opc].cycles;
}
break;
case 0xc9: // RET
@ -561,6 +600,7 @@ void cpu_step(struct cpu *cpu)
case 0xd2: // JP NC,a16
if (flag_isset(cpu, FLAG_CARRY)) {
cpu->pc = read16(cpu, cpu->pc);
cpu->cycle_count += instructions[opc].cycles_branch - instructions[opc].cycles;
}
break;
@ -657,6 +697,18 @@ void cpu_step(struct cpu *cpu)
case 0xc5: // PUSH BC
push(cpu, read_bc(cpu));
break;
case 0xc8: // RET Z
if (flag_isset(cpu, FLAG_ZERO)) {
cpu->pc = pop(cpu);
cpu->cycle_count += instructions[opc].cycles_branch - instructions[opc].cycles;
}
break;
case 0xca: // JP Z, u16
if (flag_isset(cpu, FLAG_ZERO)) {
cpu->pc = read16(cpu, cpu->pc);
cpu->cycle_count += instructions[opc].cycles_branch - instructions[opc].cycles;
}
break;
case 0xcb:
extended_insn(cpu, read8(cpu, cpu->pc));
cpu->pc++;
@ -665,13 +717,28 @@ void cpu_step(struct cpu *cpu)
add(cpu, read8(cpu, cpu->pc), 1);
cpu->pc++;
break;
case 0xd1: // POP DE
write_de(cpu, pop(cpu));
break;
case 0xd5: // PUSH DE
push(cpu, read_de(cpu));
break;
case 0xe0: // LD (a8),A
write8(cpu, 0xff00 + read8(cpu, cpu->pc), cpu->a);
cpu->pc++;
break;
case 0xe1: // POP HL
write_hl(cpu, pop(cpu));
break;
case 0xe2: // LD (C),A
write8(cpu, 0xff00 + cpu->c, cpu->a);
break;
case 0xe5: // PUSH HL
push(cpu, read_hl(cpu));
break;
case 0xe9: // JP HL
cpu->pc = read_hl(cpu);
break;
case 0xea: // LD (a16),A
write8(cpu, read16(cpu, cpu->pc), cpu->a);
cpu->pc += 2;
@ -680,11 +747,21 @@ void cpu_step(struct cpu *cpu)
cpu->a = read8(cpu, 0xff00 + read8(cpu, cpu->pc));
cpu->pc++;
break;
case 0xf1: // POP AF
write_af(cpu, pop(cpu));
break;
case 0xf2: // LD A,(C)
cpu->a = read8(cpu, 0xff00 + cpu->c);
break;
case 0xf3: // DI
break;
case 0xf5: // PUSH AF
push(cpu, read_af(cpu));
break;
case 0xfa: // LD A,(u16)
cpu->a = read8(cpu, read16(cpu, cpu->pc));
cpu->pc += 2;
break;
case 0xfb: // EI
break;
default:

View File

@ -18,9 +18,10 @@ void dmg_new(struct dmg *dmg, struct cpu *cpu, struct rom *rom, struct lcd *lcd)
u8 dmg_read(void *_dmg, u16 address)
{
struct dmg *dmg = (struct dmg *) _dmg;
if (address < 0x100) {
return dmg_boot_rom[address];
} else if (address < 0x4000) {
// if (address < 0x100) {
// return dmg_boot_rom[address];
// } else if (address < 0x4000) {
if (address < 0x4000) {
return dmg->rom->data[address];
} else if (address < 0x8000) {
// TODO switchable rom bank
@ -68,6 +69,7 @@ void dmg_write(void *_dmg, u16 address, u8 data)
// not sure about any of this yet
}
}
void exit(int);
void dmg_step(void *_dmg)
{
@ -78,7 +80,8 @@ void dmg_step(void *_dmg)
cpu_step(dmg->cpu);
// each line takes 456 cycles
if (dmg->cpu->cycle_count % 456 == 0) {
if (dmg->cpu->cycle_count - dmg->last_lcd_update >= 456) {
dmg->last_lcd_update = dmg->cpu->cycle_count;
int next_scanline = lcd_step(dmg->lcd);
if (next_scanline == 144) {
// vblank has started, draw all the stuff from ram into the lcd
@ -89,26 +92,31 @@ void dmg_step(void *_dmg)
int use_unsigned = lcdc & LCDC_BG_TILE_DATA;
int tilebase = use_unsigned ? 0x8000 : 0x9000;
printf("base is %04x\n", bg_base);
printf("tile map: %04x, tile data: %04x\n", bg_base, tilebase);
int k, off = 0;
for (k = 0; k < 1024; k++) {
int tile = dmg_read(dmg, bg_base + k);
int eff_addr;
if (use_unsigned) {
eff_addr = tilebase + 16 * tile;
} else {
eff_addr = tilebase + 16 * (signed char) tile;
}
int b, i;
for (b = 0; b < 16; b += 2) {
int data1 = dmg_read(dmg, eff_addr + b);
int data2 = dmg_read(dmg, eff_addr + b + 1);
for (i = 0; i < 8; i++) {
// monochrome for now
dmg->lcd->buf[off] |= (data1 & (1 << i)) ? 1 : 0;
dmg->lcd->buf[off] |= (data2 & (1 << i)) ? 1 : 0;
off++;
int k = 0, off = 0;
int tile_y = 0, tile_x = 0;
for (tile_y = 0; tile_y < 32; tile_y++) {
for (tile_x = 0; tile_x < 32; tile_x++) {
off = 256 * 8 * tile_y + 8 * tile_x;
int tile = dmg_read(dmg, bg_base + (tile_y * 32 + tile_x));
int eff_addr;
if (use_unsigned) {
eff_addr = tilebase + 16 * tile;
} else {
eff_addr = tilebase + 16 * (signed char) tile;
}
int b, i;
for (b = 0; b < 16; b += 2) {
int data1 = dmg_read(dmg, eff_addr + b);
int data2 = dmg_read(dmg, eff_addr + b + 1);
for (i = 7; i >= 0; i--) {
// monochrome for now
dmg->lcd->buf[off] = (data1 & (1 << i)) ? 1 : 0;
//dmg->lcd->buf[off] |= (data2 & (1 << i)) ? 1 : 0;
off++;
}
off += 248;
}
}
}

View File

@ -12,6 +12,7 @@ struct dmg {
u8 main_ram[0x2000];
u8 video_ram[0x2000];
u8 zero_page[0x80];
u32 last_lcd_update;
};
void dmg_new(struct dmg *dmg, struct cpu *cpu, struct rom *rom, struct lcd *lcd);