Last active
September 30, 2015 12:10
-
-
Save SplittyDev/e6a0c0865df5f6aa259b to your computer and use it in GitHub Desktop.
WIP F# CHIP8 emulator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The virtual screen width | |
let SCREEN_WIDTH = 64 | |
// The virtual screen height | |
let SCREEN_HEIGHT = 32 | |
// The CHIP8 font map | |
let font : byte array = | |
[| 0xF0uy; 0x90uy; 0x90uy; 0x90uy; 0xF0uy; | |
0x20uy; 0x60uy; 0x20uy; 0x20uy; 0x70uy; | |
0xF0uy; 0x10uy; 0xF0uy; 0x80uy; 0xF0uy; | |
0xF0uy; 0x10uy; 0xF0uy; 0x10uy; 0xF0uy; | |
0x90uy; 0x90uy; 0xF0uy; 0x10uy; 0x10uy; | |
0xF0uy; 0x80uy; 0xF0uy; 0x10uy; 0xF0uy; | |
0xF0uy; 0x80uy; 0xF0uy; 0x90uy; 0xF0uy; | |
0xF0uy; 0x10uy; 0x20uy; 0x40uy; 0x40uy; | |
0xF0uy; 0x90uy; 0xF0uy; 0x90uy; 0xF0uy; | |
0xF0uy; 0x90uy; 0xF0uy; 0x10uy; 0xF0uy; | |
0xF0uy; 0x90uy; 0xF0uy; 0x90uy; 0x90uy; | |
0xE0uy; 0x90uy; 0xE0uy; 0x90uy; 0xE0uy; | |
0xF0uy; 0x80uy; 0x80uy; 0x80uy; 0xF0uy; | |
0xE0uy; 0x90uy; 0x90uy; 0x90uy; 0xE0uy; | |
0xF0uy; 0x80uy; 0xF0uy; 0x80uy; 0xF0uy; | |
0xF0uy; 0x80uy; 0xF0uy; 0x80uy; 0x80uy; |] | |
// The CHIP8 keypad map | |
let keymap : char array = | |
[| 'x'; '1'; '2'; '3'; | |
'q'; 'w'; 'e'; 'a'; | |
's'; 'd'; 'z'; 'c'; | |
'4'; 'r'; 'f'; 'v'; |] | |
// Debugging bit flags | |
[<System.FlagsAttribute>] | |
type debug_option = None=0 | Pause=2 | Halt=4 | Restart=8 | Step=16 | Verbose=32 | Slow=64 | |
// The CHIP8 keypad state | |
let keystate : byte array = Array.zeroCreate 16 | |
// The CHIP8 video memory | |
let vmem : byte array = SCREEN_WIDTH * SCREEN_HEIGHT |> Array.zeroCreate | |
// Random number generator for your daily portion of side effects | |
let rng = new System.Random () | |
// Initialize memory | |
let mem_init size : byte array = | |
Array.append font <| Array.zeroCreate (size - font.Length) | |
// Clear memory | |
let mem_clear (mem : byte array) = | |
mem_init mem.Length | |
// Load a CHIP8 ROM into the memory | |
let mem_load_rom (mem : byte array) (rom : byte array) = Array.blit rom 0 mem 0x200 <| if rom.Length > 3584 then 3584 else rom.Length | |
// Read 8-bit integer from memory | |
let mem_read8 (mem : byte array) addr = mem.[addr] | |
// Read 16-bit integer from memory | |
let mem_read16 (mem : byte array) addr : int16 = int16 mem.[addr] <<< 8 ||| int16 mem.[addr + 1] | |
// Write 8-bit integer to memory | |
let mem_write8 (mem : byte array) addr (value : byte) = | |
Array.mapi (fun i x -> | |
if i = addr then value | |
else mem.[addr]) mem | |
// Write 16-bit integer to memory | |
let mem_write16 (mem : byte array) addr (value : int16) = | |
mem |> Array.mapi (fun i x -> | |
if i = addr then byte ((value >>> 8) &&& 0xFFs) | |
elif i = (addr + 1) then byte (value &&& 0xFFs) | |
else mem.[addr]) | |
// Mark a key as pressed in the keystate | |
let keypad_set_key (key : char) = keystate.[Array.findIndex (fun x -> x = key) keymap] <- 1uy | |
// Mark a key as not pressed in the keystate | |
let keypad_unset_key (key : char) = keystate.[Array.findIndex (fun x -> x = key) keymap] <- 0uy | |
// Check if a key is pressed | |
let keypad_is_pressed keypos = keystate.[keypos] = 1uy | |
// Check if a key is not pressed | |
let keypad_is_released keypos = keystate.[keypos] = 0uy | |
// Read a key from stdin | |
let keypad_read_key () = System.Console.Read () | |
// Clear the video memory | |
let vmem_clear = | |
let rec recfun i = | |
if i < vmem.Length then vmem.[i] <- 0uy | |
recfun 0 | |
// Check if a pixel is set in the video memory | |
let vmem_is_set pos = vmem.[pos] = 1uy | |
// Set a pixel in the video memory | |
let vmem_set_pixel pos attr = vmem.[pos] <- attr | |
// Clear a pixel in the video memory | |
let vmem_unset_pixel pos = vmem.[pos] <- 0uy | |
// Dump the ROM without the font map | |
let rom_dump (mem) = Seq.skip 0x200 mem |> Seq.toArray | |
// Initialize the V register | |
let cpu_reg_init_V () : int array = Array.zeroCreate 16 | |
// Set a 32-bit value in the V register | |
let cpu_reg_set_V (V : int array) i value = | |
V.[i] <- value | |
V | |
let rec cpu_cycle cycle (flags : debug_option) mem PC (SB : int16) SP DT ST I (V : int array) = | |
if PC + 0x200s >= 4096s || PC < 0x200s then | |
printfn "Error: Invalid memory region!" | |
if flags.HasFlag debug_option.Slow then Thread.Sleep 100 | |
let instr : uint16 = uint16 (mem_read16 mem (int PC)) | |
let nnn : int16 = int16 instr &&& 0x0FFFs | |
let nn : byte = byte (instr &&& 0x00FFus) | |
let op : byte = byte ((instr &&& 0xF000us) >>> 12) | |
let x : byte = byte ((instr &&& 0x0F00us) >>> 8) | |
let y : byte = byte ((instr &&& 0x00F0us) >>> 4) | |
let n : byte = byte (instr &&& 0x000Fus) | |
if flags.HasFlag debug_option.Verbose then | |
printfn "Iter: %d" cycle | |
(* Program counter, Stack base, Stack pointer *) | |
printfn "PC : 0x%04X SB : 0x%04X SP : 0x%04X" PC SB SP | |
(* Delay timer, Sound timer, Index register *) | |
printfn "DT : 0x%04X ST : 0x%04X I : 0x%04X" DT ST I | |
(* Full instruction and instruction parts *) | |
printfn "ins : 0x%04x" instr | |
(* | |
printfn "ins : 0x%04X nnn : 0x%04X" instr nnn | |
printfn "nn : 0x%02X op : 0x%02X" nn op | |
printfn "x : 0x%02X y : 0x%02X n : 0x%02X" x y n | |
*) | |
(* V registers *) | |
let rec dump_v i = | |
if i <> 0 && i % 3 = 0 then printf "\n" | |
printf "V%01X : 0x%04X " i V.[i] | |
if i < 15 then dump_v (i + 1) | |
dump_v 0 | |
(* Newline *) | |
printf "\n\n" | |
(* This should be done at 60hz *) | |
let dt = if DT > 0uy then DT - 1uy else DT | |
let st = if ST > 0uy then Task.Factory.StartNew (fun () -> System.Console.Beep (300, 100)) |> ignore; ST - 1uy; else ST | |
match op with | |
| 0x0uy -> | |
match nn with | |
| 0xEEuy -> cpu_cycle (cycle + 1UL) flags mem (mem_read16 mem (int SB + int SP)) SB SP dt st I V | |
| _ -> printfn "Error: Invalid opcode 0x%04x" op | |
| 0x1uy -> cpu_cycle (cycle + 1UL) flags mem nnn SB SP dt st I V | |
| 0x2uy -> cpu_cycle (cycle + 1UL) flags (mem_write16 mem (int SB + int (SP + 2uy)) PC) PC SB (SP + 2uy) dt st I V | |
| 0x3uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s + if V.[int x] = int nn then 2s else 0s) SB SP dt st I V | |
| 0x4uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s + if V.[int x] <> int nn then 2s else 0s) SB SP dt st I V | |
| 0x5uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s + if V.[int x] = V.[int y] then 2s else 0s) SB SP dt st I V | |
| 0x6uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) (int nn)) | |
| 0x7uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) (V.[int x] + (int nn))) | |
| 0x8uy -> | |
match n with | |
| 0x0uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) V.[int y]) | |
| 0x1uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) (V.[int x] ||| V.[int y])) | |
| 0x2uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) (V.[int x] &&& V.[int y])) | |
| 0x3uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) (V.[int x] ^^^ V.[int y])) | |
| 0x4uy -> let v = cpu_reg_set_V V 0xF <| if (V.[int x] + V.[int y]) > 0xFF then 1 else 0 | |
cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V v (int x) ((V.[int x] + V.[int y]) &&& 0xFF)) | |
| 0x5uy -> let v = cpu_reg_set_V V 0xF <| if V.[int x] > V.[int y] then 1 else 0 | |
cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V v (int x) ((V.[int x] - V.[int y]) &&& 0xFF)) | |
| 0x6uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V (cpu_reg_set_V V 0xF (V.[int x] &&& 1)) (int x) (V.[int x] >>> 1)) | |
| 0x7uy -> let v = cpu_reg_set_V V 0xF <| if V.[int y] > V.[int x] then 1 else 0 | |
cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V v (int x) ((V.[int y] - V.[int x]) &&& 0xFF)) | |
| 0xEuy -> let v = cpu_reg_set_V V 0xF ((V.[int x] &&& 128) >>> 7) | |
cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V v (int x) (V.[int x] <<< 1)) | |
| _ -> printfn "Error: Invalid opcode 0x%04x" op | |
| 0x9uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s + if V.[int x] <> V.[int y] then 2s else 0s) SB SP dt st I V | |
| 0xAuy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st nnn V | |
| 0xBuy -> cpu_cycle (cycle + 1UL) flags mem (nnn + int16 V.[0x0]) SB SP dt st I V | |
| 0xCuy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) (int (rng.NextDouble () * 255.0) &&& int nn)) | |
| 0xDuy -> | |
let v = cpu_reg_set_V V 0xF 0 | |
let rec screen_iter_y px py yy = | |
if yy >= int n then () | |
else | |
let _py = (V.[int y] + yy) % SCREEN_HEIGHT | |
if _py < 0 || _py > SCREEN_HEIGHT then screen_iter_y px _py (yy + 1) | |
let rec screen_iter_x px py yy xx = | |
if xx >= 8 then () | |
else | |
let _px = (V.[int x] + xx) % SCREEN_WIDTH | |
if _px < 0 || _px > SCREEN_WIDTH then screen_iter_x _px _py yy (xx + 1) | |
let _attr = (mem.[int I + yy] >>> (7 - xx)) &&& 1uy | |
let col = vmem.[_px + _py * 64] | |
if _attr > 0uy && col > 0uy then V.[0xF] <- 1 | |
let attr = _attr ^^^ col | |
vmem_set_pixel <| _px + _py * 64 <| attr | |
screen_iter_x _px _py yy (xx + 1) | |
screen_iter_x px _py yy 0 | |
screen_iter_y px _py (yy + 1) | |
screen_iter_y 0 0 0 | |
cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I V | |
| 0xEuy -> | |
match nn with | |
| 0x9Euy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s + if keypad_is_pressed V.[int x] then 2s else 0s) SB SP dt st I V | |
| 0xA1uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s + if keypad_is_released V.[int x] then 2s else 0s) SB SP dt st I V | |
| _ -> printfn "Error: Invalid opcode 0x%04x" op | |
| 0xFuy -> | |
match nn with | |
| 0x07uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V <| int x <| int dt) | |
| 0x0Auy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (cpu_reg_set_V V (int x) <| keypad_read_key ()) | |
| 0x15uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP (byte V.[int x]) st I V | |
| 0x18uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt (byte V.[int x]) I V | |
| 0x1Euy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st (I + int16 V.[int x]) V | |
| 0x29uy -> cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st (int16 V.[int x] * 5s) V | |
| 0x33uy -> let mem_s1 = mem_write8 mem <| int I <| byte V.[int x] / 100uy | |
let mem_s2 = mem_write8 mem_s1 <| int (I + 1s) <| (byte V.[int x] / 10uy) % 10uy | |
let mem_s3 = mem_write8 mem_s2 <| int (I + 2s) <| (byte V.[int x] / 100uy) % 10uy | |
cpu_cycle (cycle + 1UL) flags mem_s3 (PC + 2s) SB SP dt st I V | |
| 0x55uy -> | |
let rec iterfun (i : int) _mem = | |
if i < int x then iterfun (i + 1) (mem_write8 _mem <| int I + i <| byte V.[i]) | |
else _mem | |
cpu_cycle (cycle + 1UL) flags (iterfun 0 mem) (PC + 2s) SB SP dt st I V | |
| 0x65uy -> | |
let rec iterfun (i : int) (v : int array) = | |
if i < int x then iterfun (i + 1) (cpu_reg_set_V v i <| int mem.[int I + i]) | |
else v | |
cpu_cycle (cycle + 1UL) flags mem (PC + 2s) SB SP dt st I (iterfun 0 V) | |
| _ -> printfn "Error: Invalid opcode 0x%04x" op | |
| _ -> printfn "Error: Invalid opcode 0x%04x" op |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment