[MODULE] Chip8 Interpreter [ALL OS]

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
Mijikai
Beiträge: 754
Registriert: 25.09.2016 01:42

[MODULE] Chip8 Interpreter [ALL OS]

Beitrag von Mijikai »

Chip8 eignet sich besonders als Einstiegsprojekt wenn es um Emulatoren geht.

Wobei Chip8 im Prinzip nur ein Interpreter ist.
Wikipedia:https://de.wikipedia.org/wiki/CHIP-8

Angemerkt sei noch, dass sich einige Opcodes vom Original unterscheiden (u.a. SHR / SHL)
Die meisten ROMs wurden/werden jedoch für diese abgwandelte, 'moderne' Variante geschrieben.

Als Leitfaden diente Cowgods Reference:
http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#2.2

Um Input, Sound und das Rendern muss sich der Nutzer kümmern :)

Code:

Code: Alles auswählen

DeclareModule CHIP8
  
  ;CHIP8 INTERPRETER / EMULATOR
  ;AUTHOR: MIJIKAI
  ;VERSION: WIP.0.7.2
  ;COMPILER: PB 5.62 (x64)
  ;TARGET OS: ALL!
  
  Structure CHIP8_OPCODE
    StructureUnion
      dw.u
      db.a[2]
    EndStructureUnion
  EndStructure
  
  Structure CHIP8
    nfo.u
    opc.CHIP8_OPCODE  
    dec.u[6]
    adr.u[3]
    reg.a[$12]
    stk.u[$10]
    ram.a[$1000]
    vid.q[$20]
    key.a[$10]
    dat.u[$10]
  EndStructure
  
  Interface OBJECT
    Link.i();get the a pointer to (CHIP8) have access to all internals!
    State.i();get the current state: 0 = idle / 1 = running / opcode  = crash @ that opcode!
    Input.i(*Callback,Parameter.i = #Null);handle input
    Render.i(*Callback,Parameter.i = #Null);handle rendering
    Sound.i(*Callback,Parameter.i = #Null);play a sound -> nonblocking!
    Restart.i();restart (resets rom if loaded)
    Load.i(ROM.s,*Buffer = #Null,BufferSize.i = #Null);load a rom
    Step.i(Cycles.i = 16);step / execute (how many cycles at once?)
    Clock.i();clock (needed for the sound and delay timers)
    Reset.i();reset (wipes all)
    Release.i();release all resources
  EndInterface
  
  Declare.i Create()
  
EndDeclareModule

Module CHIP8
  
  EnableExplicit

  Prototype.i CALLBACK(*buffer,custom.i)
  
  Structure VM
    *vtable
    *ptr[7]
    chip8.CHIP8
    rom.a[$1000]
    siz.u
    clk_start.q
    clk_time.q
    *input.CALLBACK
    input_custom.i
    *render.CALLBACK
    render_custom.i
    *sound.CALLBACK
    sound_custom.i
  EndStructure
  
  Procedure.i vt_Link(*vm.VM)
    ProcedureReturn *vm\ptr[$0]
  EndProcedure
  
  Procedure.i vt_State(*vm.VM)
    ProcedureReturn *vm\chip8\nfo
  EndProcedure
   
  Procedure.i vt_Input(*vm.VM,*input,parameter.i = #Null)
    *vm\input = *input
    *vm\input_custom = parameter
    ProcedureReturn *vm\ptr[$4]
  EndProcedure
  
  Procedure.i vt_Render(*vm.VM,*render,parameter.i = #Null)
    *vm\render = *render
    *vm\render_custom = parameter
    ProcedureReturn *vm\ptr[$3]
  EndProcedure
  
  Procedure.i vt_Sound(*vm.VM,*sound,parameter.i = #Null)
    *vm\sound = *sound
    *vm\sound_custom = parameter
    ProcedureReturn #Null
  EndProcedure
  
  Procedure.i vt_Restart(*vm.VM)
    FillMemory(*vm\ptr[$0],SizeOf(CHIP8))
    If *vm\siz 
      *vm\chip8\nfo = $1
      *vm\chip8\adr[$0] = $200
      CopyMemory(?chip8_font,*vm\ptr[$1],?chip8_eod - ?chip8_font)
      CopyMemory(*vm\ptr[$6],*vm\ptr[$2],*vm\siz)
    EndIf
    ProcedureReturn #Null
  EndProcedure
  
  Procedure.i vt_Load(*vm.VM,file.s,*mem,siz.i)
    Protected handle.i
    Protected file_siz.i
    FillMemory(*vm\ptr[$0],SizeOf(CHIP8))
    *vm\siz = 0
    If file
      handle = ReadFile(#PB_Any,file)
      If handle
        file_siz = Lof(handle)
        If Not file_siz > $E00
          If ReadData(handle,*vm\ptr[$6],file_siz) = file_siz
            *vm\siz = file_siz
          EndIf
        EndIf
        CloseFile(handle)
      EndIf
    ElseIf *mem And siz > 0 And Not siz > $E00
      CopyMemory(*vm\ptr[$6],*mem,siz) 
      *vm\siz = siz
    EndIf
    If *vm\siz
      *vm\chip8\nfo = $1
      *vm\chip8\adr[$0] = $200
      CopyMemory(?chip8_font,*vm\ptr[$1],?chip8_eod - ?chip8_font)
      CopyMemory(*vm\ptr[$6],*vm\ptr[$2],*vm\siz)
    Else
      FillMemory(*vm\ptr[$6],$1000) 
    EndIf
    ProcedureReturn *vm\siz
  EndProcedure
  
  Procedure.i vt_Step(*vm.VM,cycles.i = 16)
    Protected px.i
    Protected py.i
    If *vm\chip8\nfo = $1
      If *vm\input
        *vm\Input(*vm\ptr[4],*vm\input_custom)
      EndIf
      With *vm\chip8
        Repeat
          \opc\db[$0] = \ram[\adr[$0] + $1]
          \opc\db[$1] = \ram[\adr[$0]]
          \adr[$0] + $2
          \dec[$0] = (\opc\dw >> $C)
          \dec[$1] = (\opc\dw & $0FFF)
          \dec[$2] = (\opc\dw & $00FF)
          \dec[$3] = (\opc\dw & $000F)
          \dec[$4] = (\opc\dw & $0F00) >> $8
          \dec[$5] = (\opc\dw & $00F0) >> $4
          Select \dec[$0]
            Case $0
              Select \dec[$2]
                Case $E0
                  FillMemory(*vm\ptr[3],$100)
                Case $EE
                  \adr[$2] - $1
                  \adr[$0] = \stk[\adr[$2]]
                Default:\nfo = \opc\dw
              EndSelect
            Case $1
              \adr[$0] = \dec[$1]
            Case $2
              \stk[\adr[$2]] = \adr[$0]
              \adr[$2] + $1
              \adr[$0] = \dec[$1]      
            Case $3
              If \reg[\dec[$4]] = \dec[$2]
                \adr[$0] + $2
              EndIf   
            Case $4
              If \reg[\dec[$4]] <> \dec[$2]
                \adr[$0] + $2
              EndIf
            Case $5
              If \reg[\dec[$4]] = \reg[\dec[$5]]
                \adr[$0] + $2
              EndIf
            Case $6
              \reg[\dec[$4]] = \dec[$2]
            Case $7
              \reg[\dec[$4]] + \dec[$2]
            Case $8
              Select \dec[$3]
                Case $0
                  \reg[\dec[$4]] = \reg[\dec[$5]]
                Case $1
                  \reg[\dec[$4]] | \reg[\dec[$5]]
                Case $2
                  \reg[\dec[$4]] & \reg[\dec[$5]]
                Case $3
                  \reg[\dec[$4]] ! \reg[\dec[$5]]
                Case $4
                  If ($FF - \reg[\dec[$4]]) < \reg[\dec[$5]]
                    \reg[$F] = $1  
                  Else
                    \reg[$F] = $0
                  EndIf
                  \reg[\dec[$4]] + \reg[\dec[$5]]       
                Case $5
                  If \reg[\dec[$4]] < \reg[\dec[$5]]
                    \reg[$F] = $0  
                  Else
                    \reg[$F] = $1
                  EndIf
                  \reg[\dec[$4]] - \reg[\dec[$5]]
                Case $6
                  \reg[$F] = (\reg[\dec[$4]] & $1)
                  \reg[\dec[$4]] >> $1 
                Case $7
                  If \reg[\dec[$4]] > \reg[\dec[$5]]
                    \reg[$F] = $0
                  Else
                    \reg[$F] = $1
                  EndIf
                  \reg[\dec[$4]] = (\reg[\dec[$5]] - \reg[\dec[$4]])
                Case $E
                  \reg[$F] = ((\reg[\dec[$4]] >> $7) & $1)
                  \reg[\dec[$4]] << $1
                Default:\nfo = \opc\dw 
              EndSelect
            Case $9
              If \reg[\dec[$4]] <> \reg[\dec[$5]]
                \adr[$0] + $2
              EndIf
            Case $A
              \adr[$1] = \dec[$1]
            Case $B
              \adr[$0] = (\reg[\dec[$0]] + \dec[$1])
            Case $C
              \reg[\dec[$4]] = (Random($FF) & \dec[$2]) 
            Case $D
              \dat[$0] = \reg[\dec[$4]]
              \dat[$1] = \reg[\dec[$5]]
              \dat[$2] = \dec[$3] 
              \dat[$3] = \adr[$1]
              \dat[$4] = $0
              \dat[$5] = $0
              \dat[$6] = \dat[$0] + 7             
              \dat[$7] = \dat[$1] + \dat[$2] - 1
              \reg[$F] = 0
              If \dat[$0] < $0
                \dat[$4] = \dat[$0] * -$1
                \dat[$0] = $0
              EndIf
              If \dat[$1] < $0
                \dat[$5] = \dat[$1] * -$1
                \dat[$1] = $0
              EndIf
              If \dat[$6] > $3F
                \dat[$6] = $3F
              EndIf
              If \dat[$7] > $1F
                \dat[$7] = $1F
              EndIf
              \dat[$8] = \dat[$4]
              \dat[$9] = \dat[$5] + \dat[$3]
              For py = \dat[$1] To \dat[$7]
                For px = \dat[$0] To \dat[$6]
                  If ((\ram[\dat[$9]] >> ($7 - \dat[$8])) & $1) 
                    If ((\vid[py] >> ($7 - px)) & $1)
                      \reg[$F] = $1
                    EndIf
                    \vid[py] ! ($1 << ($7 - px))
                  EndIf
                  \dat[$8] + $1
                Next
                \dat[$8] = \dat[$4]
                \dat[$9] + $1
              Next
            Case $E
              Select \dec[$2]
                Case $9E
                  If \key[\reg[\dec[$4]]]
                    \adr[$0] + 2
                  EndIf
                Case $A1
                  If Not \key[\reg[\dec[$4]]]
                    \adr[$0] + 2
                  EndIf
                Default:\dat = \opc\dw 
              EndSelect
            Case $F
              Select \dec[$2]
                Case $07
                  \reg[\dec[$4]] = \reg[$10]
                Case $0A
                  py = $0
                  For px = $0 To $F
                    If \key[px]
                      \reg[\dec[$4]] = px
                      py = $1
                      Break
                    EndIf
                  Next
                  If py = $0
                    \adr[$0] - $2
                  EndIf
                Case $15
                  \reg[$10] = \reg[\dec[$4]]
                Case $18
                  \reg[$11] = \reg[\dec[$4]]
                Case $1E
                  \adr[$1] + \reg[\dec[$4]]
                Case $29
                  \adr[$1] = (\reg[\dec[$4]] * $5)
                Case $33
                  \ram[\adr[$1]] = (\reg[\dec[$4]] / 100)
                  \ram[\adr[$1] + $1] = (\reg[\dec[$4]] / 10) % 10
                  \ram[\adr[$1] + $2] = (\reg[\dec[$4]] % 100) % 10
                Case $55
                  CopyMemory(@\reg[$0],@\ram[$0] + \adr[$1],\dec[$4] + $1)
                  \adr[$1] + (\dec[$4] + $2)   
                Case $65
                  CopyMemory(@\ram[$0] + \adr[$1],@\reg[$0],\dec[$4] + $1)
                  \adr[$1] + (\dec[$4] + $2) 
                Default:\nfo = \opc\dw  
              EndSelect
            Default:\nfo = \opc\dw
          EndSelect
          cycles - 1
        Until cycles < 1
        If \reg[$10] > $0
          \reg[$10] - $1
        EndIf
        If \reg[$11] > $0
          \reg[$11] - $1
          If *vm\sound
            *vm\sound(\opc\dw,*vm\sound_custom)
          EndIf
        EndIf
      EndWith
      If *vm\render
        *vm\render(*vm\ptr[3],*vm\render_custom)
      EndIf
    Else
      If *vm\render
        *vm\render(*vm\ptr[3],*vm\render_custom)
      EndIf
    EndIf
  EndProcedure
  
  Procedure.i vt_Clock(*vm.VM)
    Repeat
      *vm\clk_time = ElapsedMilliseconds() - *vm\clk_start
      If *vm\clk_time < 10
        Delay(1)
      EndIf
    Until *vm\clk_time > 16
    *vm\clk_start = (*vm\clk_start + *vm\clk_time)
    ProcedureReturn #Null
  EndProcedure
  
  Procedure.i vt_Reset(*vm.VM)
    FillMemory(*vm\ptr[$0],SizeOf(CHIP8))
    ProcedureReturn #Null
  EndProcedure
  
  Procedure.i vt_Release(*vm.VM)
    FreeMemory(*vm)
    ProcedureReturn #Null
  EndProcedure
  
  Procedure.i Create()
    Protected *vm.VM
    *vm = AllocateMemory(SizeOf(VM))
    If *vm
      *vm\vtable = ?vtable
      *vm\ptr[$0] = @*vm\chip8
      *vm\ptr[$1] = @*vm\chip8\ram[$0]
      *vm\ptr[$2] = @*vm\chip8\ram[$200]
      *vm\ptr[$3] = @*vm\chip8\vid[$0]
      *vm\ptr[$4] = @*vm\chip8\key[$0]
      *vm\ptr[$5] = @*vm\chip8\dat[$0]
      *vm\ptr[$6] = @*vm\rom[$0]
    EndIf
    ProcedureReturn *vm
  EndProcedure
  
  DataSection
    vtable:
    Data.i @vt_Link()
    Data.i @vt_State()
    Data.i @vt_Input()
    Data.i @vt_Render()
    Data.i @vt_Sound()
    Data.i @vt_Restart()
    Data.i @vt_Load()
    Data.i @vt_Step()
    Data.i @vt_Clock()
    Data.i @vt_Reset()
    Data.i @vt_Release()
    chip8_font:
    !db 0xF0,0x90,0x90,0x90,0xF0;0
    !db 0x20,0x60,0x20,0x20,0x70;1
    !db 0xF0,0x10,0xF0,0x80,0xF0;2
    !db 0xF0,0x10,0xF0,0x10,0xF0;3
    !db 0x90,0x90,0xF0,0x10,0x10;4
    !db 0xF0,0x80,0xF0,0x10,0xF0;5
    !db 0xF0,0x80,0xF0,0x90,0xF0;6
    !db 0xF0,0x10,0x20,0x40,0x40;7
    !db 0xF0,0x90,0xF0,0x90,0xF0;8
    !db 0xF0,0x90,0xF0,0x10,0xF0;9
    !db 0xF0,0x90,0xF0,0x90,0x90;A
    !db 0xE0,0x90,0xE0,0x90,0xE0;B
    !db 0xF0,0x80,0x80,0x80,0xF0;C
    !db 0xE0,0x90,0x90,0x90,0xE0;D
    !db 0xF0,0x80,0xF0,0x80,0xF0;E
    !db 0xF0,0x80,0xF0,0x80,0x80;F
    chip8_eod:
  EndDataSection
  
EndModule

Procedure.i CallbackRender(*Buffer.Quad,*Parameter);example renders to the debug window
  Protected px.i
  Protected py.i
  Protected sl.s
  For py = 0 To 31
    For px = 0 To 63
      If ((*Buffer\q >> ($7 - px)) & $1) 
        sl + "1"  
      Else
        sl + "0"
      EndIf
    Next
    *Buffer + $8
    Debug sl
    sl = #Null$
  Next  
EndProcedure

Procedure.i Dummy()
  Protected *chip8.CHIP8::OBJECT
  *chip8 = CHIP8::Create()
  If *chip8
    *chip8\Render(@CallbackRender())
    *chip8\Load("...");<- load a rom!!!!!!!!!!!!!
    Repeat
      *chip8\Step()
      *chip8\Clock()
    ForEver
    *chip8\Release()
  EndIf
  ProcedureReturn #Null
EndProcedure

Dummy()

End
Hier noch Code speziell für Windows (x64):

Bild

Code: Alles auswählen

Import "chip8dib.lib"
  chip8CreateDIB.i(hwnd.i)
EndImport

Interface CHIP8DIB
  Clear.i()
  Pixel.i(X.i,Y.i)
  Blit.i()
  Color.i(*Buffer,Flag.i = #Null);0 back- / 1 front color // upload colors (64 x 32)
  Resize.i()
  Release.i()
EndInterface

Structure CHIP8KEY
  key.a[$10]
EndStructure

Procedure.i chip8_Input(*key.CHIP8KEY,*Parameter)
  *key\key[$0] = GetAsyncKeyState_(#VK_0) & $1
  *key\key[$1] = GetAsyncKeyState_(#VK_1) & $1
  *key\key[$2] = GetAsyncKeyState_(#VK_2) & $1
  *key\key[$3] = GetAsyncKeyState_(#VK_3) & $1
  *key\key[$4] = GetAsyncKeyState_(#VK_4) & $1
  *key\key[$5] = GetAsyncKeyState_(#VK_5) & $1
  *key\key[$6] = GetAsyncKeyState_(#VK_6) & $1
  *key\key[$7] = GetAsyncKeyState_(#VK_7) & $1
  *key\key[$8] = GetAsyncKeyState_(#VK_8) & $1
  *key\key[$9] = GetAsyncKeyState_(#VK_9) & $1
  *key\key[$A] = GetAsyncKeyState_(#VK_A) & $1
  *key\key[$B] = GetAsyncKeyState_(#VK_B) & $1
  *key\key[$C] = GetAsyncKeyState_(#VK_C) & $1
  *key\key[$D] = GetAsyncKeyState_(#VK_D) & $1
  *key\key[$E] = GetAsyncKeyState_(#VK_E) & $1
  *key\key[$F] = GetAsyncKeyState_(#VK_F) & $1
  ProcedureReturn #Null
EndProcedure

Procedure.i chip8_Render(*vram.Quad,*chip8dib.CHIP8DIB)
  Protected px.i
  Protected py.i
  *chip8dib\Clear()
  For py = 0 To 31
    For px = 0 To 63
      If ((*vram\q >> ($7 - px)) & $1) 
        *chip8dib\Pixel(px,py)
      EndIf
    Next
    *vram + $8
  Next
  *chip8dib\Blit()
  ProcedureReturn #Null
EndProcedure

Procedure.i chip8_Sound(opcode.i,*Parameter)
  ;//play sound
EndProcedure

Procedure.i Chip8(Title.s = #Null$,Width.i = 800,Height.i = 600)
  Protected wnd.i
  Protected wnd_flags.i
  Protected wnd_event.i
  Protected wnd_exit.i
  Protected menu.i
  Protected *chip8.CHIP8::OBJECT
  Protected *chip8debug.CHIP8::CHIP8
  Protected *chip8dib.CHIP8DIB
  wnd_flags = #PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget
  wnd = OpenWindow(#PB_Any,#Null,#Null,Width,Height,Title,wnd_flags) 
  If wnd
    WindowBounds(wnd,Width,Height,#PB_Ignore,#PB_Ignore)
    SetWindowColor(wnd,0)
    menu = CreateMenu(#PB_Any,WindowID(wnd))
    If menu
      MenuTitle("Main")
      MenuItem(1,"Load ROM")
      MenuTitle("State")
      MenuItem(2,"Restart")
      MenuItem(3,"Reset")
      *chip8 = CHIP8::Create()
      If *chip8
        *chip8dib = chip8CreateDIB(WindowID(wnd))
        If *chip8dib
          *chip8\Input(@chip8_Input())
          *chip8\Render(@chip8_Render(),*chip8dib)
          *chip8\Sound(@chip8_Sound())
          *chip8debug = *chip8\Link()
          Repeat
            Repeat
              wnd_event = WindowEvent()
              Select wnd_event
                Case #PB_Event_Menu
                  Select EventMenu()
                    Case 1
                      *chip8dib\Clear()
                      *chip8dib\Blit()
                      *chip8\Load(OpenFileRequester("Load ROM",#Null$,#Null$,#Null))
                    Case 2
                      *chip8\Restart()
                    Case 3
                      *chip8\Reset()
                      *chip8dib\Clear()
                      *chip8dib\Blit()
                  EndSelect
                Case #PB_Event_SizeWindow
                  *chip8dib\Resize()
                Case  #PB_Event_CloseWindow
                  wnd_exit = #True
              EndSelect
            Until wnd_event = #Null
            *chip8\Step(16)
            *chip8\Clock()
          Until wnd_exit
          *chip8dib\Release()
        EndIf
        *chip8\Release()
      EndIf
    EndIf
    CloseWindow(wnd)
  EndIf
  ProcedureReturn #Null
EndProcedure

Chip8("CHIP8 Interpreter (c) 2020 by Mijikai")

End
Die Chip8 Render-Lib (chip8dib.lib) kann hier herundtergeladen werden:
https://www.dropbox.com/s/hls03os9rzt1h ... b.zip?dl=0

Viel Spaß beim Testen :D