add browser, inputs & display

This commit is contained in:
kishimisu 2023-11-16 01:09:46 +01:00
parent 364e0a44f2
commit 553e0e6745
19 changed files with 608 additions and 263 deletions

8
.gitignore vendored
View File

@ -1,6 +1,4 @@
.vs
out/build/x64-debug/*
!out/build/x64-debug/fx9860-emulator
out/build/x64-debug/fx9860-emulator/*
!out/build/x64-debug/fx9860-emulator/SAMPLE_ADDIN.G1A
!out/build/x64-debug/fx9860-emulator/U+0020.png
.vscode
tmp/
fx9860-emulator/build

View File

@ -1,15 +0,0 @@
# CMakeList.txt : Top-level CMake project file, do global configuration
# and include sub-projects here.
#
cmake_minimum_required (VERSION 3.8)
# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
project ("fx9860-emulator")
# Include sub-projects.
add_subdirectory ("fx9860-emulator")

View File

@ -1,61 +0,0 @@
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "x64-debug",
"displayName": "x64 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-release",
"displayName": "x64 Release",
"inherits": "x64-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "x86-debug",
"displayName": "x86 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x86",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x86-release",
"displayName": "x86 Release",
"inherits": "x86-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

BIN
JJSH3.g1a Normal file

Binary file not shown.

BIN
PrintMini.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -3,6 +3,8 @@
This is a work-in-progress emulator for the Casio fx-9860 SH4 models
It will display the content of the VRAM in the console at the end of the execution.
Broswser demo : https://sh4.vercel.app/
All non-DSP instructions are implemented, except the following ones:
- 'MOVUAL', 'MOVUALP', 'ICBI', 'OCBI',
- 'OCBP', 'OCBWB', 'PREFI', 'SLEEP',
@ -10,4 +12,32 @@ All non-DSP instructions are implemented, except the following ones:
fx9860-emulator/ contains the source for the emulator
scripts/ contains the code for auto-generating the instructions code from the doc
scripts/ contains the code for auto-generating the instructions code from the doc
### Browser version
To build the browser version of the emulator, make sure you have emscripten downloaded and installed: https://emscripten.org/docs/getting_started/downloads.html
First, make sur `#define EMSCRIPT` is enabled in main.h (not as a comment).
Then:
cd fx9860-emulator
mkdir build
cd build
emcmake make ..
Then, until I find a nicer way to integrate this into the Makefile, run the following command:
emcc -O2 .\CMakeFiles\fx9860-emulator.dir\main.o .\CMakeFiles\fx9860-emulator.dir\utils.o .\CMakeFiles\fx9860-emulator.dir\memory.o .\CMakeFiles\fx9860-emulator.dir\instructions\instructions.o .\CMakeFiles\fx9860-emulator.dir\instructions\syscalls.o --embed-file ..\..\U+0020.png@U+0020.png --embed-file ..\..\PrintMini.bmp@PrintMini.bmp --embed-file ..\..\JJSH3.G1A@JJSH3.G1A -sASYNCIFY -sEXIT_RUNTIME -o index.js -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='$getBoundingClientRect' -sEXPORTED_RUNTIME_METHODS='writeArrayToMemory, setValue, getValue'
After that you can server the content of the build/ folder through a local server and open index.html
### Console version
To build the console version of the emulator (no display, only VRAM dump after 10M executions):
First, remove (comment) `#define EMSCRIPT` in main.h
cd fx9860-emulator
mkdir build
cd build
cmake ..
make

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -26,16 +26,15 @@
typedef struct {
int col; // starts at 1
int row; // starts at 1
//int flashstyle; // -1 if not flashing, else flashmode
//int graphic_mode;
} cursor_t;
struct display_t {
// Pointer to virtual memory in RAM
uint8_t vram[VRAM_SIZE];
//uint8_t* vram;
uint8_t lcd_registers; // Unused, but still emulates access to the LCD screen
// LCD screen emulation
uint8_t lcd[VRAM_SIZE];
uint8_t lcd_registers; // Use for both selector & data registers
// Current cursor position
cursor_t cursor;

View File

@ -4,6 +4,8 @@
void (*get_instruction_impl(uint16_t instruction))(cpu_t*, uint16_t);
#define R0 cpu->r[0]
void ADD(cpu_t* cpu, uint16_t instruction);
void ADDI(cpu_t* cpu, uint16_t instruction);
void ADDC(cpu_t* cpu, uint16_t instruction);

View File

@ -9,9 +9,17 @@ void syscall_Bdisp_AllClr_DD(cpu_t* cpu);
void syscall_Bdisp_AllClr_VRAM(cpu_t* cpu);
void syscall_Bdisp_AllClr_DDVRAM(cpu_t* cpu);
void syscall_GetKey(cpu_t* cpu, unsigned int keycode_address);
void syscall_Locate(cpu_t* cpu, int x, int y);
void syscall_Print(cpu_t* cpu, const unsigned char* str);
void syscall_PrintXY(cpu_t* cpu, int x, int y, const unsigned char* str, int type);
void syscall_PrintXY(cpu_t* cpu, int x, int y, const unsigned char* str, int mode);
void syscall_PrintMiniSd(cpu_t* cpu, int x, int y, const unsigned char* str, int mode);
void syscall_GetAppName(cpu_t* cpu, char* dest);
void syscall_Malloc(cpu_t* cpu, uint32_t size);
void syscall_GlibGetAddinLibInf(cpu_t* cpu, uint32_t a_ptr, uint32_t b_ptr, uint32_t c_ptr);
void syscall_GlibGetOSVersionInfo(cpu_t* cpu, uint32_t a_ptr, uint32_t b_ptr, uint32_t c_ptr, uint32_t d_ptr);
void syscall_Malloc(cpu_t* cpu, uint32_t size, uint8_t clear_data);
void syscall_Bfile_OpenFile_OS(cpu_t* cpu, const char* filename, int mode, int mode2);
void syscall_Bfile_CreateEntry_OS(cpu_t* cpu, const char* filename, int mode, int size_ptr);

View File

@ -5,6 +5,12 @@ typedef struct memory_t memory_t;
typedef struct cpu_t cpu_t;
typedef struct display_t display_t;
#define USE_EMSCRIPT
#ifdef USE_EMSCRIPT
#include <emscripten.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
@ -13,6 +19,7 @@ typedef struct display_t display_t;
#include "display.h"
#include "utils.h"
#include "instructions/instructions.h"
#include "instructions/syscalls.h"
// Initialization
#define PC_PROGRAM_START 0x00300200 // Execution starting address
@ -21,9 +28,6 @@ typedef struct display_t display_t;
#define SYSCALL_ADDRESS 0x80010070 // The calling convention to access the system calls, is to jump to 0x80010070 with the syscall number in r0
// Status Register bits
#define SR_BIT_T 0
// CPU
struct cpu_t {
// General registers R0 - R15
@ -31,7 +35,12 @@ struct cpu_t {
// Control registers
uint32_t gbr; // Global base register
uint32_t sr; // Status register (0: T Bit)
uint32_t sr; // Status register
uint32_t ssr; // Saved status register
uint32_t spc; // Saved program counter
uint32_t vbr; // Vector base register
uint32_t sgr; // Saved general register
uint32_t dbr; // Debug base register
// System registers
uint32_t pc; // Program counter
@ -39,23 +48,24 @@ struct cpu_t {
uint32_t mach; // Multiply-accumulate high
uint32_t macl; // Multiply-accumulate low
// To sort
uint32_t vbr; // Vector base register
uint32_t ssr; // Saved status register
uint32_t sgr; // Saved general register
uint32_t spc; // Saved program counter
uint32_t dbr; // Debug base register
// Custom variables
uint8_t isExecutionFinished; // Set to true when PR == PC_PROGRAM_START
// Debug
uint32_t instruction_count;
uint8_t* asciiTexture;
uint32_t instruction_per_frame;
// External Components
memory_t* mem; // Memory
memory_t* mem; // Memory
display_t* disp; // Display
// (Temporary) Character sets
uint8_t* asciiTexture;
uint8_t* printMiniTexture;
};
void run_next_instruction(cpu_t* cpu);
void run_next_instruction(cpu_t* cpu);
void main_loop_html(void* arg);
void init_loop_html(cpu_t* cpu);
void init_loop_c(cpu_t* cpu);

View File

@ -2,6 +2,7 @@
#include "main.h"
// Memory start addresses
#define ROM_START 0x00300000
#define RAM_START_MMU_ALIAS 0x08100000
#define RAM_START 0x8801c000 // 0x88048000 on 35+E II
@ -9,11 +10,21 @@
#define YRAM_START 0xe5017000
#define ILRAM_START 0xe5200000
#define ROM_SIZE (2048 * 1024)
#define RAM_SIZE (512 * 1024)
#define XRAM_SIZE (8 * 1024)
#define YRAM_SIZE (8 * 1024)
#define ILRAM_SIZE (4 * 1024)
// Memory sizes
#define ROM_SIZE (2048 * 1024)
#define RAM_SIZE (512 * 1024)
#define XRAM_SIZE (8 * 1024)
#define YRAM_SIZE (8 * 1024)
#define ILRAM_SIZE (4 * 1024)
// Keyboard register addresses (SH3-specific)
#define KB_PORTB_CTRL 0xA4000102
#define KB_PORTM_CTRL 0xA4000118
#define KB_PORTA 0xA4000120
#define KB_PORTB 0xA4000122
#define KB_PORTM 0xA4000138
#define KB_SIZE (0xA4000138 - 0xA4000102 + 1) // Max - min address + 1 (insclusive)
#define KB_START KB_PORTB_CTRL
typedef struct{
uint32_t size;
@ -27,10 +38,13 @@ struct memory_t {
uint8_t ram[RAM_SIZE]; // Random-access memory
uint8_t keyboard[10]; // Keyboard rows state
uint8_t keyboard_registers[KB_SIZE]; // Keyboard registers
uint32_t* tmp;
// uint8_t xram[XRAM_SIZE];
// uint8_t yram[YRAM_SIZE];
// uint8_t ilram[ILRAM_SIZE];
// Malloc
@ -41,5 +55,7 @@ struct memory_t {
uint8_t* get_memory_for_address(cpu_t* cpu, uint32_t address);
uint32_t mem_read(cpu_t* cpu, uint32_t address, uint8_t bytes);
void mem_write(cpu_t* cpu, uint32_t address, uint32_t data, uint8_t bytes);
void mem_write(cpu_t* cpu, uint32_t address, uint32_t data, uint8_t bytes);
uint32_t emulate_keyboard_register_read(cpu_t* cpu);
void emulate_lcd_register_write(cpu_t* cpu, uint32_t address, uint32_t data);

View File

@ -10,6 +10,8 @@ void critical_error(const char* format, ...);
// Prints the binary representation of a number
void print_binary(uint32_t x, int n);
void loadAsciiTexture(cpu_t* cpu);
// Load a character set from an image
uint8_t* load_character_set(const char* path);
void cpu_debug(cpu_t* cpu);
void cpu_debug(cpu_t* cpu);
void vram_debug(cpu_t* cpu);

View File

@ -245,13 +245,12 @@ struct SR0 {
#define PC cpu->pc
#define PR cpu->pr
#define R cpu->r
#define R0 cpu->r[0]
#define MACH cpu->mach
#define MACL cpu->macl
#define Read_Byte(addr) mem_read_nolog(cpu, addr, 1)
#define Read_Word(addr) mem_read_nolog(cpu, addr, 2)
#define Read_Long(addr) mem_read_nolog(cpu, addr, 4)
#define Read_Byte(addr) mem_read(cpu, addr, 1)
#define Read_Word(addr) mem_read(cpu, addr, 2)
#define Read_Long(addr) mem_read(cpu, addr, 4)
#define Write_Byte(addr, data) mem_write(cpu, addr, data, 1)
#define Write_Word(addr, data) mem_write(cpu, addr, data, 2)

View File

@ -2,9 +2,9 @@
// Syscall entry point
void run_syscall(cpu_t* cpu) {
uint32_t syscall_code = cpu->r[0];
uint32_t syscall_code = R0;
printf("run_syscall: 0x%03X 0x%X 0x%X 0x%X 0x%X (Return: 0x%08X)\n", syscall_code, cpu->r[4], cpu->r[5], cpu->r[6], cpu->r[7], cpu->pr);
// printf("run_syscall 0x%03X, args: 0x%X 0x%X 0x%X 0x%X\n", syscall_code, cpu->r[4], cpu->r[5], cpu->r[6], cpu->r[7]);
int arg1 = cpu->r[4];
int arg2 = cpu->r[5];
@ -15,6 +15,7 @@ void run_syscall(cpu_t* cpu) {
syscall_GetVRAMAddress(cpu);
}
else if (syscall_code == 0x144) { // Bdisp_AllClr_DDVRAM
printf("Run Syscall: Bdisp_AllClr_DDVRAM\n");
syscall_Bdisp_AllClr_DDVRAM(cpu);
}
else if (syscall_code == 0x807) { // locate
@ -23,60 +24,113 @@ void run_syscall(cpu_t* cpu) {
else if (syscall_code == 0x808) { // Print
uint8_t* mem = get_memory_for_address(cpu, arg1);
const unsigned char* str = (const unsigned char*)mem;
// printf("Run Syscall: syscall_Print (%s)\n", str);
syscall_Print(cpu, str);
}
else if (syscall_code == 0x9AD) { // PrintXY
else if (syscall_code == 0x9AD) { // PrintXY
uint8_t* mem = get_memory_for_address(cpu, arg3);
const unsigned char* str = (const unsigned char*)mem;
// printf("Run Syscall: syscall_PrintXY (%d %d %s %d)\n", arg1, arg2, str, arg4);
syscall_PrintXY(cpu, arg1, arg2, str, arg4);
}
else if (syscall_code == 0xACD) { // malloc
syscall_Malloc(cpu, arg1);
else if (syscall_code == 0xC4F) { // PrintMiniSd
uint8_t* mem = get_memory_for_address(cpu, arg3);
const unsigned char* str = (const unsigned char*)mem;
// printf("Run Syscall: PrintMiniSd (%d %d %s %d)\n", arg1, arg2, str, arg4);
syscall_PrintMiniSd(cpu, arg1, arg2, str, arg4);
}
else if (syscall_code == 0x462) { // GetAppName
syscall_GetAppName(cpu, arg1);
else if (syscall_code == 0xACD) { // malloc
syscall_Malloc(cpu, arg1, 0);
}
else if (syscall_code == 0xE6B) { // calloc
syscall_Malloc(cpu, arg1, 1);
}
else if (syscall_code == 0xE6D) { // realloc
printf("Skipped syscall: realloc\n");
}
else if (syscall_code == 0xACC) { // free
printf("Skipped syscall: free\n");
}
else if (syscall_code == 0x462) { // GetAppName
syscall_GetAppName(cpu, (char*)arg1);
}
else if (syscall_code == 0x014) { // GlibGetAddinLibInf
syscall_GlibGetAddinLibInf(cpu, arg1, arg2, arg3);
}
else if (syscall_code == 0x015) { // GlibGetOSVersionInfo
syscall_GlibGetOSVersionInfo(cpu, arg1, arg2, arg3, arg4);
}
else if (syscall_code == 0x90F) { // GetKey
printf("Syscall not implemented, skipping: GetKey\n");
cpu->isExecutionFinished = 1;
printf("Skipped syscall: GetKey\n");
// syscall_GetKey(cpu, arg1);
// printf("Run Syscall: GetKey (0x%08X: %d)\n", arg1, R0);
// cpu->isExecutionFinished = 1;
}
else if (syscall_code == 0x24C) { // Keyboard_IsSpecialKeyDown
printf("Syscall not implemented, skipping: Keyboard_IsSpecialKeyDown\n");
cpu->isExecutionFinished = 1;
printf("Skipped syscall: Keyboard_IsSpecialKeyDown\n");
}
else if (syscall_code == 0x03B) { // RTC_GetTicks
R0 = cpu->instruction_count / 7812;
// printf("Run Syscall: RTC_GetTicks %d\n", R0);
}
else if (syscall_code == 0x43B) { // Bfile_FindFirst
printf("Syscall not implemented, skipping: Bfile_FindFirst\n");
}
else if (syscall_code == 0x434) { // Bfile_CreateEntry_OS
printf("Syscall not implemented, skipping: Bfile_CreateEntry_OS\n");
uint8_t* mem = get_memory_for_address(cpu, arg1);
const char* str = (const char*)mem;
// syscall_Bfile_CreateEntry_OS(cpu, str, arg2, arg3);
printf("Skipped syscall: Bfile_CreateEntry_OS\n");
}
else if (syscall_code == 0x42C) { // Bfile_OpenFile_OS
printf("Syscall not implemented, skipping: Bfile_OpenFile_OS\n");
}
uint8_t* mem = get_memory_for_address(cpu, arg1);
const char* str = (const char*)mem;
// syscall_Bfile_OpenFile_OS(cpu, str, arg2, arg3);
printf("Skipped syscall: Bfile_OpenFile_OS\n");
}
else if (syscall_code == 0x432) { // Bfile_ReadFile_OS
printf("Skipped syscall: Bfile_ReadFile_OS\n");
}
else if (syscall_code == 0x435) { // Bfile_WriteFile_OS
printf("Skipped syscall: Bfile_WriteFile_OS\n");
}
else if (syscall_code == 0x42D) { // Bfile_CloseFile_OS
printf("Skipped syscall: Bfile_CloseFile_OS\n");
}
else if (syscall_code == 0x43B) { // Bfile_FindFirst
printf("Skipped syscall: Bfile_FindFirst\n");
}
else if (syscall_code == 0x439) { // Bfile_DeleteEntry
printf("Skipped syscall: Bfile_DeleteEntry\n");
}
else if (syscall_code == 0x82B) { // MCSPutVar2
printf("Syscall not implemented, skipping: MCSPutVar2\n");
printf("Skipped syscall: MCSPutVar2\n");
}
else if (syscall_code == 0x840) { // MCSGetDlen2
printf("Syscall not implemented, skipping: MCSGetDlen2\n");
printf("Skipped syscall: MCSGetDlen2\n");
}
else if (syscall_code == 0x420) { // OS_inner_Sleep
// https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_Sleep.HTM
printf("Syscall not implemented, skipping: OS_inner_Sleep\n");
}
else if (syscall_code == 0x014) { // GlibGetAddinLibInf
printf("Syscall not implemented, skipping: GlibGetAddinLibInf\n");
}
else if (syscall_code == 0x015) { // GlibGetOSVersionInfo
printf("Syscall not implemented, skipping: GlibGetOSVersionInfo\n");
// printf("Skipped syscall: OS_inner_Sleep\n");
}
else if (syscall_code == 0x494) { // void SetQuitHandler( void (*callback)(void) );
printf("Syscall not implemented, skipping: SetQuitHandler\n");
printf("Skipped syscall: SetQuitHandler\n");
}
else if (syscall_code == 0x3ED) { // Interrupt_SetOrClrStatusFlags
// https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_INTERRUPT.HTM
printf("Skipped syscall: Interrupt_SetOrClrStatusFlags\n");
}
// Can be ignored
else if (syscall_code == 0x013) { // GlibAddinAplExecutionCheck
printf("Ignored syscall: GlibAddinAplExecutionCheck\n");
}
else if (syscall_code == 0x3FA) { // Hmem_SetMMU
printf("Ignored syscall: Hmem_SetMMU\n");
}
// Can be skipped
else if (syscall_code == 0x013) {} // GlibAddinAplExecutionCheck
else if (syscall_code == 0x3FA) {} // Hmem_SetMMU
// else if (syscall_code == 0x1032) { // return a pointer to a matrixcode/keycode mapping table
// // https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_keyboard.htm
// printf("Skipped syscall: 0x1032\n");
// }
else {
printf("Syscall not implemented: 0x%03X\n", syscall_code);
cpu->isExecutionFinished = 1;
@ -107,15 +161,63 @@ void syscall_Bdisp_AllClr_DDVRAM(cpu_t* cpu) {
void syscall_Locate(cpu_t* cpu, int x, int y) {
cpu->disp->cursor.col = x;
cpu->disp->cursor.row = y;
printf("syscall_locate: %d %d\n", x, y);
printf("Run Syscall: locate (%d %d)\n", x, y);
}
void draw_character_mini(cpu_t* cpu, const char c, int pixel_start_x, int pixel_start_y, int mode) {
// Character x,y in ASCII texture (16x16)
int char_x = c / 16;
int char_y = c % 16;
if (char_x < 0 || char_x >= 16 || char_y < 0) return;
// Pixel start x, y in ASCII texture (128x128)
char_x = char_x * 7;
char_y = char_y * 7;
for (int y = 0; y < 7; y++) {
for (int x = 0; x < 7; x++) {
// Current pixel position in ASCII texture
int ascii_x = char_x + x;
int ascii_y = char_y + y;
int ascii_id = ascii_x + ascii_y * 128; // Pixel index
if (ascii_id < 0 || ascii_id >= 128 * 128) break;
// Current pixel position on-screen
int vram_x = pixel_start_x + x;
int vram_y = pixel_start_y + y;
int vram_id = (vram_x + vram_y * SCREEN_WIDTH) / 8; // index in VRAM
if (vram_id < 0 || vram_id >= VRAM_SIZE) break;
int ascii_bit = cpu->printMiniTexture[ascii_id] ^ mode; // Pixel value
int vram_bit = 7 - vram_x % 8; // Current bit
// Change one single bit in the VRAM
if (ascii_bit)
cpu->disp->vram[vram_id] = (cpu->disp->vram[vram_id] & ~(1 << vram_bit)) | (1 << vram_bit);
}
}
}
void syscall_PrintMiniSd(cpu_t* cpu, int x, int y, const unsigned char* str, int mode) {
int i = 0;
while (x < SCREEN_WIDTH) {
const char c = str[i++]; // Current character
if (c == 0x00) break; // Line terminator
draw_character_mini(cpu, c, x, y, mode);
x += 4; // Move the cursor the the right
}
}
// Draw a character on screen
// pixel_start_x and y represent the top left pixel of the character
void draw_character(cpu_t* cpu, const char c, int pixel_start_x, int pixel_start_y) {
void draw_character(cpu_t* cpu, const char c, int pixel_start_x, int pixel_start_y, int mode) {
int offset = c - 0x0020;
// Character x,y in ASCII texture (16x6)
// Character x,y in ASCII texture (16x16)
int char_x = offset % 16;
int char_y = offset / 16;
// Pixel start x, y in ASCII texture (112x54)
@ -128,9 +230,8 @@ void draw_character(cpu_t* cpu, const char c, int pixel_start_x, int pixel_start
int ascii_x = char_x + x;
int ascii_y = char_y + y;
int ascii_id = ascii_x + ascii_y * 112; // Pixel index
int ascii_bit = cpu->asciiTexture[ascii_id]; // Pixel value
if (ascii_id < 0 || ascii_id > 112 * 54) critical_error("ASCII Texture Access out of bounds: %d %d (%d)", ascii_x, ascii_y, ascii_id);
if (ascii_id < 0 || ascii_id >= 112 * 54) break; // critical_error("ASCII Texture Access out of bounds: %d %d (%d)", ascii_x, ascii_y, ascii_id);
// Current pixel position on-screen
int vram_x = pixel_start_x + x;
@ -138,10 +239,13 @@ void draw_character(cpu_t* cpu, const char c, int pixel_start_x, int pixel_start
int vram_id = (vram_x + vram_y * SCREEN_WIDTH) / 8; // index in VRAM
int vram_bit = 7 - vram_x % 8; // Current bit
if (vram_id < 0 || vram_id > VRAM_SIZE) critical_error("VRAM Access out of bounds: %d %d (%d)", vram_x, vram_y, vram_id);
if (vram_id < 0 || vram_id >= VRAM_SIZE) break; // critical_error("VRAM Access out of bounds: %d %d (%d)", vram_x, vram_y, vram_id);
// Change one single bit in the VRAM
cpu->disp->vram[vram_id] = (cpu->disp->vram[vram_id] & ~(1 << vram_bit)) | (ascii_bit << vram_bit);
int ascii_bit = cpu->asciiTexture[ascii_id] ^ mode; // Pixel value
if (ascii_bit)
cpu->disp->vram[vram_id] = (cpu->disp->vram[vram_id] & ~(1 << vram_bit)) | (ascii_bit << vram_bit);
}
}
}
@ -163,34 +267,32 @@ void syscall_Print(cpu_t* cpu, const unsigned char* str) {
screen_x = screen_x * CHAR_WIDTH + 1;
screen_y = screen_y * CHAR_HEIGHT;
draw_character(cpu, c, screen_x, screen_y);
draw_character(cpu, c, screen_x, screen_y, 0);
// Move the cursor the the right
cpu->disp->cursor.col++;
}
}
void syscall_PrintXY(cpu_t* cpu, int x, int y, const unsigned char* str, int type) {
printf("syscall_PrintXY: %d %d %s\n", x, y, str);
void syscall_PrintXY(cpu_t* cpu, int x, int y, const unsigned char* str, int mode) {
int i = 0;
while (x < SCREEN_WIDTH) {
const char c = str[i++]; // Current character
if (c == 0x00) break; // Line terminator
draw_character(cpu, c, x, y);
draw_character(cpu, c, x, y, mode);
x += CHAR_WIDTH; // Move the cursor the the right
}
}
#define MALLOC_MEM_LOW RAM_START_MMU_ALIAS + (32 * 1042)
#define MALLOC_MEM_LOW RAM_START_MMU_ALIAS + (32 * 1024)
#define MALLOC_MEM_HIGH RAM_START_MMU_ALIAS + RAM_SIZE - (16 * 1024)
#define MALLOC_MARGIN 0x200
// Allocate memory and return the address of the allocated memory in r0
void syscall_Malloc(cpu_t* cpu, uint32_t size) {
void syscall_Malloc(cpu_t* cpu, uint32_t size, uint8_t clear_data) {
memory_t* mem = cpu->mem;
// Initialize with the lowest address
@ -206,35 +308,146 @@ void syscall_Malloc(cpu_t* cpu, uint32_t size) {
mem->mallocCount++;
mem->mallocs = realloc(mem->mallocs, mem->mallocCount * sizeof(malloc_info_t));
if (clear_data) {
for (int i = 0; i < size; i++) {
mem_write(cpu, addr + i, 0, 4);
}
}
if (mem->mallocs == 0) {
critical_error("syscall_malloc(): Could not allocate memory");
}
if (clear_data)
printf("Run Syscall: Calloc #%d (size: %d)\n", mem->mallocCount - 1, size);
else
printf("Run Syscall: Malloc #%d (size: %d)\n", mem->mallocCount - 1, size);
mem->mallocs[mem->mallocCount - 1] = (malloc_info_t){size, addr};
printf("syscall_malloc %d : 0x%08X\n", mem->mallocCount, addr);
// Return the address of the allocated memory
cpu->r[0] = addr;
R0 = addr;
}
// https://bible.planet-casio.com/simlo/chm/v20/fx_legacy_AppName.HTM
// Copies the registered name for the running application into the character array dest.
// dest must be able to hold 9 bytes. dest is returned.
void syscall_GetAppName(cpu_t* cpu, char* dest) {
// The app names is stored in 9 bytes
dest = malloc(9 * sizeof(char));
// Copies the app name to the new buffer
memcpy(dest, &cpu->mem->rom[0x20], 9);
for (int i = 0; i < 9; i++) {
mem_write(cpu, (uint32_t)dest + i, cpu->mem->rom[0x20 + i], 1);
}
// Return the buffer
cpu->r[0] = dest;
printf("Run Syscall: GetAppName (%s)\n", (const char*)get_memory_for_address(cpu, (uint32_t)dest));
printf("syscall_GetAppName %s\n", dest);
R0 = (int32_t)dest; // Return the buffer
}
void syscall_GetVRAMAddress(cpu_t* cpu) {
cpu->r[0] = VRAM_ADDRESS; // &cpu->mem->ram[VRAM_ADDRESS];
//printf("syscall_GetVRAMAddress 0x%08X\n", cpu->r[0]);
}
R0 = VRAM_ADDRESS;
// printf("Run Syscall: GetVRAMAddress (-> 0x%08X)\n", R0);
}
void syscall_GlibGetAddinLibInf(cpu_t* cpu, uint32_t a_ptr, uint32_t b_ptr, uint32_t c_ptr) {
// Mimic Casio SDK Emulator
mem_write(cpu, a_ptr, 0x0, 4);
mem_write(cpu, b_ptr, 0x1, 4);
mem_write(cpu, c_ptr, 0x1, 4);
cpu->r[0] = 0x1;// 0x0;
cpu->r[2] = 0xA0151F28;
cpu->r[3] = 0x0;
cpu->r[4] = 0x1;
printf("Run Syscall: GlibGetAddinLibInf\n");
}
void syscall_GlibGetOSVersionInfo(cpu_t* cpu, uint32_t a_ptr, uint32_t b_ptr, uint32_t c_ptr, uint32_t d_ptr) {
// Mimic Casio SDK Emulator
mem_write(cpu, a_ptr, 0x1, 1);
mem_write(cpu, b_ptr, 0x3, 1);
mem_write(cpu, c_ptr, 0x0, 2);
mem_write(cpu, d_ptr, 0x0, 2);
cpu->r[0] = 0x1;
cpu->r[2] = 0x3;
cpu->r[3] = 0x1;
cpu->r[4] = 0x0;
// printf("Run Syscall: GlibGetOSVersionInfo\n");
}
const char* convertFileName(const char* filename) {
char name[40];
char ch;
int i;
// Convert filename from { 0x00, 0xXX, 0x00, 0xXX, ... } to { 0xXX, 0xXX, ... }
for (i = 0; i < 40; i++) {
uint16_t ch = *(uint16_t*) &filename[i * 2 + 1];
name[i] = ch;
if (ch == 0x00) break;
}
// Remove '\\fls0\'
i -= 6;
char* final_name = malloc(sizeof(char) * i);
memcpy(final_name, name + 7, i);
return (const char*)final_name;
}
// mode: 1 = read, 2 = write
void syscall_Bfile_OpenFile_OS(cpu_t* cpu, const char* filename, int mode, int mode2) {
const char* name = convertFileName(filename);
FILE* handle = fopen(name, mode == 1 ? "rb" : "wb");
printf("Run Syscall: Bfile_OpenFile_OS (%s, 0x%X, 0x%X) handle: 0x%X\n", name, mode, mode2, (uint32_t)handle);
R0 = handle == NULL ? -1 : (int32_t)handle;
}
void syscall_Bfile_CreateEntry_OS(cpu_t* cpu, const char* filename, int mode, int size_ptr) {
const char* name = convertFileName(filename);
FILE* handle;
if (mode == 0x1) { // Create file
handle = fopen(name, "wb");
fclose(handle);
}
// else if (mode == 0x5) { } // Create folder
else printf("[Warning] CreateEntry mode not implemented\n");
printf("Run Syscall: Bfile_CreateEntry_OS (%s, 0x%X) size: %d, handle: 0x%X\n", name, mode, mem_read(cpu, size_ptr, 4), (uint32_t)handle);
R0 = handle == NULL ? -1 : (int32_t)handle;
}
// #ifdef USE_EMSCRIPT
// EM_ASYNC_JS(int, async_browser_getkey, (), {
// return await new Promise((resolve, reject) => {
// const onClick = (event) => {
// const res = window.keyboardEvent(event);
// if (res != null) {
// document.getElementById("keyboard").removeEventListener("click", onClick);
// resolve(res);
// }
// };
// document.getElementById("keyboard").addEventListener("click", onClick);
// });
// })
// #else
// int async_browser_getkey() {
// return 0;
// }
// #endif
// void syscall_GetKey(cpu_t* cpu, unsigned int keycode_address) {
// int key = async_browser_getkey();
// mem_write(cpu, keycode_address, key, 4);
// R0 = key;
// }

View File

@ -38,6 +38,13 @@ memory_t* init_memory(uint8_t* g1a_content, uint32_t g1a_size) {
mem->rom = g1a_content;
mem->rom_size = g1a_size;
mem->mallocs = calloc(1, sizeof(uint8_t));
mem->tmp = calloc(1024, sizeof(uint8_t));
// Initialize keyboard rows (1 = key not pressed)
for (int i = 0; i < 10; i++)
mem->keyboard[i] = 0xFF;
return mem;
}
@ -48,7 +55,6 @@ display_t* init_display(memory_t* memory) {
if(disp == NULL) critical_error("Could not allocate memory for Display");
//disp->vram = &memory->ram[VRAM_ADDRESS];
disp->cursor.row = 1;
disp->cursor.col = 1;
@ -95,10 +101,7 @@ void run_next_instruction(cpu_t* cpu) {
}
// Extract 16bits instruction code
uint16_t instruction = (mem_read_nolog(cpu, address, 1) << 8) | mem_read_nolog(cpu, address + 1, 1);
if (cpu->instruction_count % 1000000 == 0)
cpu_debug(cpu);
uint16_t instruction = (mem_read(cpu, address, 1) << 8) | mem_read(cpu, address + 1, 1);
// Get a pointer to the function implementing the current instruction
get_instruction_impl(instruction)
@ -108,44 +111,84 @@ void run_next_instruction(cpu_t* cpu) {
int main() {
// Load G1A
uint32_t g1a_size;
uint8_t* g1a_content = load_g1a("SAMPLE_ADDIN.G1A", &g1a_size);
uint8_t* g1a_content = load_g1a("JJSH3.G1A", &g1a_size);
// Init CPU, Memory and Display
memory_t* mem = init_memory(g1a_content, g1a_size);
display_t* disp = init_display(mem);
cpu_t* cpu = init_cpu(mem, disp);
loadAsciiTexture(cpu); // (Temporary) load the ASCII font texture
// (Temporary) load the ASCII font texture
cpu->asciiTexture = load_character_set("U+0020.png");
cpu->printMiniTexture = load_character_set("PrintMini.bmp");
#ifdef USE_EMSCRIPT
init_loop_html(cpu);
#else
init_loop_c(cpu);
#endif
/// [Main Loop]
/// Execute add-in (up to 20M instructions for now)
for (int i = 0; i < 2e7; i++) {
return 0;
}
// Init the HTML version of the emulator
void init_loop_html(cpu_t* cpu) {
#ifdef USE_EMSCRIPT
cpu->instruction_per_frame = 10000;
// Pass useful pointers to javascript
EM_ASM({
window.lcdImage = $0;
window.keyboardPointer = $1;
window.instructionPerFrame = $2;
window.instructionCount = $3;
window.cpuPointer = $4;
window.initGUI();
},
&cpu->disp->vram,
&cpu->mem->keyboard,
&cpu->instruction_per_frame,
&cpu->instruction_count,
cpu
);
// Setup the main animation frame loop
emscripten_set_main_loop_arg(main_loop_html, cpu, -1, 0);
#endif
}
// Main loop for the HTML version of the emulator
void main_loop_html(void* arg) {
cpu_t* cpu = (cpu_t*)arg;
if (cpu->isExecutionFinished) return;
for (int i = 0; i < cpu->instruction_per_frame; i++) {
run_next_instruction(cpu);
cpu->instruction_count++;
if (cpu->isExecutionFinished) break;
}
}
// Run the console version of the emulator
// (no display - VRAM dump at the end)
void init_loop_c(cpu_t* cpu) {
// Run up to 10M instructions
for (int i = 0; i < 1e7; i++) {
run_next_instruction(cpu);
cpu->instruction_count++;
if (cpu->isExecutionFinished) break;
}
vram_debug(cpu);
// (Temporary) Display VRAM
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 16; x++) {
for (int b = 7; b >= 0; b--) {
int id = x + y * 16;
if (id < 0 || id >= VRAM_SIZE) critical_error("error %d", id);
int bit = (disp->vram[id] >> b) & 1;
printf(bit || y == 0 || y == 63 || (x==0 && b==7) || (x==15 && b==0) ? "X" : " ");
}
}
printf("\n");
}
printf("\n");
free(g1a_content);
free(mem);
free(cpu->mem->mallocs);
free(cpu->mem->tmp);
free(cpu->mem->rom);
free(cpu->mem);
free(cpu->asciiTexture);
free(cpu->printMiniTexture);
free(cpu);
return 0;
}

View File

@ -3,36 +3,42 @@
// Returns a pointer to the buffer (memory)
// corresponding to the `address` parameter
uint8_t* get_memory_for_address(cpu_t* cpu, uint32_t address) {
if (address >= ROM_START && address < ROM_START + cpu->mem->rom_size) {
// (ROM) - MMU
if (address >= ROM_START && address < ROM_START + cpu->mem->rom_size) { // ROM - (MMU)
return &cpu->mem->rom[address - ROM_START];
}
else if (address >= RAM_START_MMU_ALIAS && address < RAM_START_MMU_ALIAS + RAM_SIZE) {
// (RAM) - MMU
else if (address >= RAM_START_MMU_ALIAS && address < RAM_START_MMU_ALIAS + RAM_SIZE) { // RAM - (MMU)
return &cpu->mem->ram[address - RAM_START_MMU_ALIAS];
}
else if (address >= RAM_START && address < RAM_START + RAM_SIZE) {
// (RAM) - Physical
else if (address >= RAM_START && address < RAM_START + RAM_SIZE) { // RAM - (Physical)
return &cpu->mem->ram[address - RAM_START];
}
else if (address >= VRAM_ADDRESS && address < VRAM_ADDRESS + VRAM_SIZE) {
// (VRAM)
else if (address >= VRAM_ADDRESS && address < VRAM_ADDRESS + VRAM_SIZE) { // VRAM
return &cpu->disp->vram[address - VRAM_ADDRESS];
}
else if (address == LCD_SELECT_REGISTER || address == LCD_DATA_REGISTER) {
// LCD emulation
else if (address == LCD_SELECT_REGISTER || address == LCD_DATA_REGISTER) { // LCD registers
return &cpu->disp->lcd_registers;
}
else if (address >= KB_START && address < KB_START + KB_SIZE) { // Keyboard registers
return &cpu->mem->keyboard_registers[address - KB_START];
}
else if (address >= 0xFFFFFEE0 && address < 0xFFFFFFFF) { // Temporary memory
return &cpu->mem->tmp[address - 0xFFFFFEE0];
}
else {
// cpu_debug(cpu);
critical_error("Request for memory at address 0x%08X is out of bounds\n", address);
}
}
// Write to the memory
void mem_write(cpu_t* cpu, uint32_t address, uint32_t data, uint8_t bytes) {
uint8_t* mem = get_memory_for_address(cpu, address);
// LCD input registers
if (address == LCD_SELECT_REGISTER || address == LCD_DATA_REGISTER) {
emulate_lcd_register_write(cpu, address, data);
return;
}
//printf("Memory write [0x%08X]: 0x%08X\n", address, data);
uint8_t* mem = get_memory_for_address(cpu, address);
if (bytes == 1) {
*mem = data & 0xFF;
@ -50,24 +56,99 @@ void mem_write(cpu_t* cpu, uint32_t address, uint32_t data, uint8_t bytes) {
}
// Read from the memory
uint32_t mem_read_(cpu_t* cpu, uint32_t address, uint8_t bytes) {
uint32_t mem_read(cpu_t* cpu, uint32_t address, uint8_t bytes) {
// Keyboard output register
if (address == KB_PORTA) {
return emulate_keyboard_register_read(cpu);
}
// Address that stores the model of the calculator (0 for emulator)
else if (address == 0x80000300) {
return 0;
}
uint8_t* mem = get_memory_for_address(cpu, address);
uint32_t data;
if (bytes == 1) {
return *mem;
data = *mem;
}
else if (bytes == 2) {
return (*mem << 8) | (*(mem + 1));
data = (*mem << 8) | (*(mem + 1));
}
else {
return (*mem << 24) | (*(mem + 1) << 16) | (*(mem + 2) << 8) | (*(mem + 3));
data = (*mem << 24) | (*(mem + 1) << 16) | (*(mem + 2) << 8) | (*(mem + 3));
}
// printf("Memory read [0x%08X]: : 0x%X\n", address, data);
return data;
}
// Emulate keyboard register A output by reading values
// from port B and M control registers.
// Returns the state of the keyboard for the current row
// https://www.planet-casio.com/Fr/forums/topic17509-2-emulateur-fx-9860-sh4.html#194390
uint32_t emulate_keyboard_register_read(cpu_t* cpu) {
uint16_t PORTB_CTRL = mem_read(cpu, KB_PORTB_CTRL, 2);
uint8_t row = 0;
if (PORTB_CTRL != 0xAAAA) { // Rows 0-7
uint16_t smask = PORTB_CTRL ^ 0xAAAA;
while (smask >> (row*2) > 0b11) row++;
}
else { // Rows 8-9
uint16_t PORTM_CTRL = mem_read(cpu, KB_PORTM_CTRL, 2); // row 8: xxxx0001 row 9: xxx0010
row = 8 + ((PORTM_CTRL & 0b11) >> 1);
}
if (row > 9) {
printf("[Warning] Request for keyboard row %d which does not exist!\n", row);
return 0xffffffff;
}
else {
return cpu->mem->keyboard[row];
}
}
uint32_t mem_read_nolog(cpu_t* cpu, uint32_t address, uint8_t bytes) {
return mem_read_(cpu, address, bytes);
}
uint32_t mem_read(cpu_t* cpu, uint32_t address, uint8_t bytes) {
uint32_t value = mem_read_(cpu, address, bytes);
printf("Memory read [0x%08X]: : 0x%X\n", address, value);
return value;
// Keep track of current row and column set in the LCD selector register
// and write data to the LCD buffer.
// Refresh the HTML canvas when the last row is written.
// Works well for MonochromeLib's DisplayVRAM() function, but might
// not work for more specific register accesses.
void emulate_lcd_register_write(cpu_t* cpu, uint32_t address, uint32_t data) {
static int row = -1;
static int col = 0;
// Setup LCD row
if (address == LCD_SELECT_REGISTER) {
if (data == 7) { // New row
row++;
if (row >= 64) {
row = 0;
col = 0;
}
}
// printf("LCD REGISTER Write: 0x%08X, set row: %d\n", data, row);
}
// Write data to LCD row
else if (address == LCD_DATA_REGISTER) {
if (row >= 0) {
cpu->disp->lcd[row * 16 + col++] = data; // Update LCD buffer
#ifdef EMSCRIPTEN
if (row == 63 && col == 16) { // Refresh HTML canvas
EM_ASM({
window.refreshScreen();
});
}
#endif
if (col >= 16) {
col = 0;
}
}
// printf("LCD DATA Write: 0x%08X, row: %d, col: %d\n", data, row, col);
}
}

View File

@ -8,8 +8,14 @@
void critical_error(const char* format, ...) {
va_list args;
va_start(args, format);
printf("[Error] ");
vprintf(format, args);
va_end(args);
#ifdef USE_EMSCRIPT
emscripten_cancel_main_loop();
#endif
exit(EXIT_FAILURE);
}
@ -21,65 +27,79 @@ void print_binary(uint32_t x, int n) {
}
}
void loadAsciiTexture(cpu_t* cpu) {
const char* file_path = "U+0020.png";
// Load a character set from an image
uint8_t* load_character_set(const char* path) {
// Load the PNG image using stb_image
int width, height, channels;
unsigned char* image_data = stbi_load(file_path, &width, &height, &channels, STBI_rgb);
unsigned char* image_data = stbi_load(path, &width, &height, &channels, STBI_rgb);
// Check if the image loading was successful
if (image_data == NULL) {
printf("Error loading image: %s\n", stbi_failure_reason());
return -1;
critical_error("Could not load character set %s: %s\n", path, stbi_failure_reason());
}
// Check if the image dimensions match the requirements
/*if (width != 112 || height != 54 || channels != 3) {
printf("Image dimensions or bit depth do not match requirements.\n");
stbi_image_free(image_data);
return -1;
}*/
uint8_t* asciiTexture = malloc(width * height * sizeof(uint8_t));
uint8_t* packed_data = malloc(width * height * sizeof(uint8_t));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = (y * width + x);
int indexRGB = (y * width + x) * channels;
unsigned char red = image_data[indexRGB];
asciiTexture[index] = red ? 0 : 1;
//printf(asciiTexture[index] ? "X" : " ");
if (index >= width * height) critical_error("error %d", index);
packed_data[index] = red ? 0 : 1;
//printf(packed_data[index] ? "X" : " ");
}
//printf("\n");
}
cpu->asciiTexture = asciiTexture;
// Free the allocated memory for the image data
stbi_image_free(image_data);
printf("Loaded character set %s: %dx%dx%d\n", path, width, height, channels);
return packed_data;
}
// Prints the current state of the CPU
void cpu_debug(cpu_t* cpu) {
uint16_t instruction = (mem_read_nolog(cpu, cpu->pc, 1) << 8) | mem_read_nolog(cpu, cpu->pc + 1, 1);
uint8_t high = (instruction >> 12) & 0x0F;
uint8_t high8 = instruction >> 8;
uint8_t low = instruction & 0x0F;
uint8_t low8 = instruction & 0xFF;
if (cpu->pc != 0x80010070) {
uint16_t instruction = (mem_read(cpu, cpu->pc, 1) << 8) | mem_read(cpu, cpu->pc + 1, 1);
uint8_t high = (instruction >> 12) & 0x0F;
uint8_t high8 = instruction >> 8;
uint8_t low = instruction & 0x0F;
uint8_t low8 = instruction & 0xFF;
printf("[%d][0x%08X] instruction: 0x%04X -", cpu->instruction_count, cpu->pc, instruction);
print_binary(instruction, 16);
print_binary(high, 4);
print_binary(low, 4);
printf("\n");
/*
for (int i = 0; i < 16; i++) {
int r = cpu->r[i];
if (r != 0)
printf("r%d: 0x%08X, ", i, r);
printf("[%d][0x%08X] instruction: 0x%04X -", cpu->instruction_count, cpu->pc, instruction);
print_binary(instruction, 16);
print_binary(high, 4);
print_binary(low, 4);
printf("\n");
}
printf("pr: 0x%08X, cursor: %d-%d, T: %d\n", cpu->pr, cpu->disp->cursor.col, cpu->disp->cursor.row, get_status_register_bit(cpu, SR_BIT_T));
*/
//printf("mem: 0x%02X 0x%02X 0x%02X 0x%02X\n", mem_read_nolog(cpu, 0x00300670, 1), mem_read_nolog(cpu, 0x00300670 + 1, 1), mem_read_nolog(cpu, 0x00300670 + 2, 1), mem_read_nolog(cpu, 0x00300670 + 3, 1));
else printf("[%d][0x%08X] instruction: syscall\n", cpu->instruction_count, cpu->pc);
for (int i = 0; i < 16; i++) {
int r = cpu->r[i];
if (r != 0)
printf("r%d: 0x%08X, ", i, r);
}
printf("pr: 0x%08X, cursor: %d-%d, T: %d\n", cpu->pr, cpu->disp->cursor.col, cpu->disp->cursor.row, (cpu->sr >> 31) & 1);
// printf("pr: 0x%08X, macl: 0x%08X, gbr: 0x%08X, T: %d\n", cpu->pr, cpu->macl, cpu->gbr, cpu->disp->cursor.col, cpu->disp->cursor.row, (cpu->sr >> 31) & 1);
// printf("mem: 0x%02X 0x%02X 0x%02X 0x%02X\n", mem_read(cpu, 0x00300670, 1), mem_read(cpu, 0x00300670 + 1, 1), mem_read(cpu, 0x00300670 + 2, 1), mem_read(cpu, 0x00300670 + 3, 1));
}
// Prints the current state of the VRAM
void vram_debug(cpu_t* cpu) {
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 16; x++) {
for (int b = 7; b >= 0; b--) {
int id = x + y * 16;
if (id < 0 || id >= VRAM_SIZE) critical_error("error %d", id);
int bit = ((cpu->disp->vram[id] >> b) & 1) || y == 0 || y == 63 || (x == 0 && b == 7) || (x == 15 && b == 0);
printf(bit ? "_" : "X");
}
}
printf("\n");
}
printf("\n");
}