# 2020q3 Gameboy-emu
contributed by < `Rsysz` >
###### tags: `sysprog`
> [Gameboy 模擬器](https://hackmd.io/@sysprog/BJvOWzFqv)
> [GitHub](https://github.com/Rsysz/gameboy-emu)
## Intro
首先根據老師給予的 [jitboy](https://github.com/sysprog21/jitboy) 內的描述,了解到當前模擬器有兩種較常見的實作方向。
* 一是模擬 `Game boy` 的環境,就能讓舊有的 `ROM` 順利執行,但缺點就是因 `CPU instruction` 不兼容,因此須將指令逐行翻譯,採用靜態編譯,因此 `branch miss` 將無法避免
* 第二種便是透過 `JITcompiler` 達成動態編譯,也就是在執行期才對下段指令編譯,因更好的效能而為多數模擬器所採用,相對於前者更消耗資源
根據老師制定的目標,我所實作的 [gameboy-emu](https://github.com/Rsysz/gameboy-emu) 採用第一種方式,目標在達成 **portable** 與優化模擬器效能。

## Gameboy
根據 [Gameboy Overview](https://thomas.spurden.name/gameboy/) 內的解說與 [Gameboy CPU (LR35902) instruction set](https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html) 的 `CPU instruction`
- [x] TODO: 研讀 [git-commit](https://chris.beams.io/posts/git-commit/) 使用 `git rebase` 合併多餘 `commit` 並優化 `commit message`,並依檔案增減修改數量拆分為多個 `commit` 分次提交 `pull request`
```cpp
gameboy.h __gb_execute_cb >> cpu.h __cpu_execute_cb
gameboy.h __gb_step_cpu >> cpu.h cpu_step
gameboy.h __gb_draw_line >> cpu.h __gb_draw_line
gameboy.h __gb_write >> mmu.h mmu_write
gameboy.h __gb_read >> mmu.h mmu_read
```
[github log](https://github.com/Rsysz/gameboy-emu/commits/master)
不知道這樣的改動是否OK?
> 這樣的修改還是難以 code review,先從 CPU 執行狀態開始搬動,一開始大概兩個 commits。注意,你提交的 pull request 可能會有相依性,你應該等待 review 並做出對應的變動,然後 rebase 再提交。
> :notes: jserv
## Refactor
因目前 `pull request` 的分支與 `master` 分支部分有所不同,在這裡單獨介紹 `master` 部分的改動
從 `gameboy.h` 中將
```cpp
__gb_step_cpu()
__gb_execute_cb()
```
拆離至 `cpu` 部分
```cpp
__gb_read()
__gb_write()
```
拆離至 `mmu` 部分
參考 [mGBA](https://github.com/mgba-emu/mgba/tree/master/src/sm83) 將 `cpu instruction` 以 `macro` 展開型式表達
以 `computed-goto` 建構 `dispatch_table` 修改 `__gb_step_cpu` 用以快速跳躍至指定 `instruction`
整合 `gbit` 指令除錯器進 `bench.c`
整合 `bios` 檔案於開機時顯示
## Experiment
目前打算參考 `jitboy` `instr_table` 部分對 `cpu.c` 程式碼重構,並接著加入 `BIOS` 顯示功能,因此先做了一個小實驗,驗證想法可行性
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct cpu_instr_s {
void *execute;
enum {
NONE,
REG_A,
REG_B,
REG_C,
REG_D,
REG_E,
REG_H,
REG_L /* = 7 */,
REG_AF,
REG_BC,
REG_DE,
REG_HL,
REG_SP,
IMM8,
IMM16 /* = 14 */,
MEM_BC,
MEM_DE,
MEM_HL,
MEM_16,
MEM_8,
MEM_C,
MEM_INC_DE,
MEM_INC_HL,
MEM_DEC_HL /*=22*/,
CC_Z,
CC_C,
CC_NZ,
CC_NC,
MEM_0x00,
MEM_0x08,
MEM_0x10,
MEM_0x18,
MEM_0x20,
MEM_0x28,
MEM_0x30,
MEM_0x38,
BIT_0,
BIT_1,
BIT_2,
BIT_3,
BIT_4,
BIT_5,
BIT_6,
BIT_7,
TARGET_1,
TARGET_2,
WAIT_LY,
WAIT_STAT3
} op1,
op2;
uint8_t *args;
uint16_t address;
uint8_t bytes;
uint8_t cycles, alt_cycles;
enum {
INST_FLAG_NONE = 0x00,
INST_FLAG_PRESERVE_CC = 0x01,
INST_FLAG_PERS_WRITE = 0x02,
INST_FLAG_USES_CC = 0x04,
INST_FLAG_AFFECTS_CC = 0x08,
INST_FLAG_ENDS_BLOCK = 0x10,
INST_FLAG_SAVE_CC = 0x20,
INST_FLAG_RESTORE_CC = 0x40
} flags;
} cpu_instr;
void NOP() {
printf("__func__");
}
static const cpu_instr inst_table[1] = {
[0] = {NOP, NONE, NONE, 0, 0, 1, 1, 1, 0}
};
int main()
{
struct cpu_instr_s test = inst_table[0];
void(*opcode_function)() = test.execute;
opcode_function();
getchar();
return 0;
}
```
:::warning
TODO:
1. 使用 `clang-format -i *.[ch]` 對原始程式碼進行縮排調整,強制設定縮排為 4 個空白,如此才能確保原始程式碼和 HackMD 展現的效果一致。
2. 避免使用 **不雅** 詞彙
3. 研讀 [Threaded Code](http://www.complang.tuwien.ac.at/forth/threaded-code.html) 和 [Fast VMs without assembly](http://www.emulators.com/docs/nx25_nostradamus.htm),以理解上述手法的原理
4. 使用 computed-goto 時,要確定已在 `Makefile` 加入 `CFLAGS += -fno-gcse -fno-crossjumping`,如此才能讓 gcc 產生更好的程式碼
>不好意思,老師我想請問應該要在哪個部分使用 `computed-goto` 呢? 目前是想說在 `gb_step_cpu` 內並將每個迴圈前面確認 `interrupt flags` 部分跟後方確認 `reg.lcy` 狀態每次 `run` 完指令都重複做一次 不知道這樣的想法對不對呢,另外我把指令拆離成獨立的 `function` 那內部也不用 `switch-case` 了
>這樣還需要使用`computed-goto` 嗎
:notes: jserv
:::
前面提的大概像這樣,另外 `Tail_handler` 外部是 `while(!gb->gb_frame)`
所以就像是沒有使用 `_gb_read` 或 `_gb_write` 的指令可以不用檢查直接繼續執行嗎?
```cpp
static const void *dispatch_table[] = {
&&NOP, &&...
}
#define DISPATCH() \
goto* dispatch_table[(gb->gb_halt ? 0x00 : __gb_read(gb, gb->cpu_reg.pc++))]
__gb_step_cpu(struct gb_s *gb)
{
...
NOP:
...
DISPATCH();
EI:
gb->gb_ime = 1;
goto Tail_handler
Tail_handler:
...
}
```
可是現在我把這些cpu指令都寫成function的話,這種改寫還有必要嗎?
另外如果要改的話我是直接把function塞在 `label` 裡面嗎?
目前已改用 `computed-goto` 的 `dispatch_table` 做跳躍與 `macro` 展開的 `instruction function`,並能正常運行,但我的寫法是把 `table` 當成 `switch-case` 的用法在作跳躍,也就是每個 `cycle` 還是要到 `while` 的邊界判斷一次,想請問是否有更好的改法呢?
> 這是為何你需要讀書,讀書可以讓我們有充分的視野和思緒來做判斷。上面的參考文獻讀過後,你應該知道取捨。快去讀書!
> :notes: jserv

引入 `gbit` 程式碼並修改 `bench.c` 對全指令偵錯並將偵錯版本上傳為新分支 `Debug`

已將 `Debug` 分支與原始程式碼合併至 `master`,並修改 `bench.c` 為測試全指令並輸出測試時間