├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd └── chip8 │ └── main.go ├── go.mod ├── go.sum ├── pkg └── emulator │ ├── device │ ├── display.go │ ├── instructions.go │ ├── keyboard.go │ ├── memory.go │ ├── processor.go │ ├── processor_test.go │ ├── registers.go │ └── stack.go │ ├── emulator.go │ ├── graphics.go │ └── sound.go └── test └── characters.ch8 /.gitignore: -------------------------------------------------------------------------------- 1 | roms/ 2 | build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Ege Emir Özkan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = chip8 2 | ifeq ($(OS),Windows_NT) 3 | BIN := $(BIN).exe 4 | endif 5 | 6 | build/$(BIN): 7 | cd cmd/chip8 &&\ 8 | go build -o ../../build/$(BIN) 9 | cd ../.. 10 | 11 | all: 12 | cd cmd/chip8 &&\ 13 | go build -o ../../build/$(BIN) 14 | cd ../.. 15 | 16 | .PHONY: all 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Chip8 Emulator in Go 2 | 3 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/ambertide/chip8) 4 | 5 | chip8.go is a simple Chip8 emulator, compliant with the technical standard laid out in the 6 | [Cowgod's Manual](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM). Graphics and sound are both 7 | powered by Faiface's [Beep](https://github.com/faiface/beep/) and [Pixel](https://github.com/faiface/pixel) libraries. 8 | 9 | ## Installation 10 | 11 | For Linux distrobutions, you will need `libasound2-dev` package. Your go version should be 17+ 12 | 13 | ``` 14 | git clone https://github.com/ambertide/chip8 15 | cd chip8 16 | make all 17 | ``` 18 | 19 | The built file can be accessed in the `build/chip8` directory. If you have added the `chip8` to the path, simply 20 | 21 | ``` 22 | chip8 -rom myrom.ch8 23 | ``` 24 | 25 | to play a rom. 26 | 27 | You can also specify the speed using `-speed` flag, by default, the speed is 500MHz 28 | -------------------------------------------------------------------------------- /cmd/chip8/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "github.com/ambertide/chip8/pkg/emulator" 8 | "github.com/faiface/pixel/pixelgl" 9 | ) 10 | 11 | func main() { 12 | clockSpeed := flag.Uint64("speed", 500, "Sets the speed of the main processor in Hz.") 13 | programPath := flag.String("rom", "", "Path to the rom file for chip8.") 14 | flag.Parse() 15 | if *programPath == "" { 16 | flag.PrintDefaults() 17 | os.Exit(1) 18 | } 19 | pixelgl.Run(func() { emulator.RunEmulator(*clockSpeed, *programPath) }) 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ambertide/chip8 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/faiface/beep v1.1.0 7 | github.com/faiface/pixel v0.10.0 8 | ) 9 | 10 | require ( 11 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 // indirect 12 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect 13 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect 14 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 // indirect 15 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 // indirect 16 | github.com/hajimehoshi/oto v0.7.1 // indirect 17 | github.com/pkg/errors v0.9.1 // indirect 18 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect 19 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff // indirect 20 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect 21 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 2 | github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c= 6 | github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4= 7 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= 8 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= 9 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= 10 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= 11 | github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0= 12 | github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A= 13 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 14 | github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= 15 | github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= 16 | github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= 17 | github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= 18 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= 19 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= 20 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= 21 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 22 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI= 23 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= 24 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 25 | github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= 26 | github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= 27 | github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= 28 | github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= 29 | github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A= 30 | github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= 31 | github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= 32 | github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= 33 | github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= 34 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 35 | github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU= 36 | github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA= 37 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 38 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 39 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 40 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 41 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 43 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 44 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 45 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= 46 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 47 | golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 48 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 49 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 50 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ= 51 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 52 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc= 53 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 54 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 55 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 57 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= 58 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 60 | -------------------------------------------------------------------------------- /pkg/emulator/device/display.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | type chip8Display struct { 4 | // A 64x32 pixel screen 5 | // Can easilly be represented 6 | // This way. 7 | screen [32]uint64 8 | // Global buffer used to 9 | // Communicate between Goroutines. 10 | screenBuffer *[32]uint64 11 | } 12 | 13 | func newDisplay(screenBuffer *[32]uint64) *chip8Display { 14 | display := new(chip8Display) 15 | display.screenBuffer = screenBuffer 16 | return display 17 | } 18 | 19 | func (d *chip8Display) SyncBuffer() { 20 | copy((*d.screenBuffer)[:], d.screen[:]) 21 | } 22 | 23 | // Clear the display. 24 | func (d *chip8Display) ClearDisplay() { 25 | for i := 0; i < 32; i++ { 26 | d.screen[i] = 0 27 | } 28 | d.SyncBuffer() 29 | } 30 | 31 | // Draw a sprite of height height into the screen 32 | // Starting from x and y, return true if any pixels 33 | // are erased. 34 | func (d *chip8Display) DrawSprite(x byte, y byte, height byte, sprite []byte) bool { 35 | collusion := false 36 | for i, spriteRow := range sprite[:height] { 37 | displayRow := d.screen[(i+int(y))%32] 38 | // Convert sprite row to have space. 39 | paddedSpriteRow := uint64(spriteRow) 40 | // Align the sprite row to its XOR location. 41 | alignedSpriteRow := paddedSpriteRow 42 | if x < 56 { 43 | alignedSpriteRow = paddedSpriteRow << (56 - x) 44 | } else { 45 | alignedSpriteRow = paddedSpriteRow >> (x - 56) 46 | } 47 | // Check for collusion 48 | collusion = collusion || (alignedSpriteRow&displayRow != 0) 49 | // And XOR the screen. 50 | d.screen[(i+int(y))%32] ^= alignedSpriteRow 51 | } 52 | d.SyncBuffer() 53 | return collusion 54 | } 55 | -------------------------------------------------------------------------------- /pkg/emulator/device/instructions.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | type LogicalInstructionType uint8 8 | 9 | const ( 10 | Load LogicalInstructionType = iota 11 | Or 12 | And 13 | Xor 14 | Add 15 | Sub 16 | Shr 17 | Subn 18 | Shl = 0xE 19 | ) 20 | 21 | // Execute system instructions RET and CLR 22 | func (p *Processor) executeSystemInstruction(instruction string) { 23 | switch instruction { 24 | case "0000": 25 | break 26 | case "00E0": 27 | // CLR: Clear Screen 28 | //log.Print("INSTRUCTION: Clear the display.\n") 29 | p.display.ClearDisplay() 30 | case "00EE": 31 | // RET: Return from subroutine. 32 | //log.Print("INSTRUCTION: Return from subroutine.\n") 33 | p.registers.SetProgramCounter(p.stack.Pop()) 34 | default: 35 | //log.Panicf("ERROR: Unknown Instruction %s.", instruction) 36 | } 37 | } 38 | 39 | // Execute skip instructions that skip a number of intsructions 40 | // Depending on a condition. SE, SNE, SE, SNE. 41 | func (p *Processor) executeSkipInstructions(instructionChar string, instruction uint16) { 42 | byteValue := byte(instruction & 0xFF) // For 3XNN and 4XNN 43 | register := getRegisterIndex(instructionChar[1]) 44 | register2 := getRegisterIndex(instructionChar[2]) // For 5XY0 45 | msn := instructionChar[0] // Most significant nibble 46 | lsn := instructionChar[3] // Least significant nibble 47 | switch { 48 | case msn == '3' && p.registers.ReadRegister(register) == byteValue: 49 | // Incrementing the program counter now will effectively 50 | // Skip this instruction. 51 | //log.Print("INSTRUCTION: Skip Variant 1.\n") 52 | p.registers.IncrementProgramCounter() 53 | case msn == '4' && p.registers.ReadRegister(register) != byteValue: 54 | //log.Print("INSTRUCTION: Skip Variant 2.\n") 55 | p.registers.IncrementProgramCounter() 56 | case msn == '5' && lsn == '0' && p.registers.CompareRegisters(register, register2): 57 | //log.Print("INSTRUCTION: Skip Variant 3.\n") 58 | p.registers.IncrementProgramCounter() 59 | case msn == '9' && lsn == '0' && !p.registers.CompareRegisters(register, register2): 60 | //log.Print("INSTRUCTION: Skip Variant 4.\n") 61 | p.registers.IncrementProgramCounter() 62 | case msn == 'E' && (instruction<<8 == 0x9E00) && p.keyboards.IsKeyPressed(p.registers.ReadRegister(register)): 63 | //log.Print("INSTRUCTION: Skip Variant 5.\n") 64 | p.registers.IncrementProgramCounter() 65 | case msn == 'E' && (instruction<<8 == 0xA100) && !p.keyboards.IsKeyPressed(p.registers.ReadRegister(register)): 66 | //log.Print("INSTRUCTION: Skip Variant 6.\n") 67 | p.registers.IncrementProgramCounter() 68 | } 69 | } 70 | 71 | // Given the indexes for two registers and the operation type execute 72 | // the operation. 73 | func (p *Processor) executeLogicalInstructions(x uint8, y uint8, operationType LogicalInstructionType, instruction uint16) { 74 | switch operationType { 75 | case Load: 76 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Load V%d, V%d.\n", x, y) 77 | p.registers.RegisterOperation(x, y, func(b1 byte, b2 byte) byte { return b2 }) 78 | case Or: 79 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Or V%d, V%d.\n", x, y) 80 | p.registers.RegisterOperation(x, y, func(b1 byte, b2 byte) byte { return b1 | b2 }) 81 | case And: 82 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit And V%d, V%d.\n", x, y) 83 | p.registers.RegisterOperation(x, y, func(b1, b2 byte) byte { return b1 & b2 }) 84 | case Xor: 85 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Xor V%d, V%d.\n", x, y) 86 | p.registers.RegisterOperation(x, y, func(b1, b2 byte) byte { return b1 ^ b2 }) 87 | case Add: 88 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Add V%d, V%d.\n", x, y) 89 | p.registers.RegisterOperationWithCarry(x, y, func(b1, b2 byte) byte { return b1 + b2 }, 90 | func(b1, b2 byte) byte { 91 | if (uint16(b1) + uint16(b2)) > 255 { 92 | return 1 93 | } else { 94 | return 0 95 | } 96 | }, 97 | ) 98 | case Sub: 99 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Sub V%d, V%d.\n", x, y) 100 | p.registers.RegisterOperationWithCarry(x, y, func(b1, b2 byte) byte { return b1 - b2 }, 101 | func(b1, b2 byte) byte { 102 | if b1 > b2 { 103 | return 1 104 | } else { 105 | return 0 106 | } 107 | }, 108 | ) 109 | case Shr: 110 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Shr V%d, V%d.\n", x, y) 111 | p.registers.RegisterOperationWithCarry(x, y, func(b1, b2 byte) byte { return b1 >> 1 }, 112 | func(b1, b2 byte) byte { 113 | return b1 & 0x1 114 | }, 115 | ) 116 | case Subn: 117 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Subn V%d, V%d.\n", x, y) 118 | p.registers.RegisterOperationWithCarry(x, y, func(b1, b2 byte) byte { return b2 - b1 }, 119 | func(b1, b2 byte) byte { 120 | if b2 > b1 { 121 | return 1 122 | } else { 123 | return 0 124 | } 125 | }, 126 | ) 127 | case Shl: 128 | //log.Printf("INSTRUCTION: Arithmatic Logic Unit Shl V%d, V%d.\n", x, y) 129 | p.registers.RegisterOperationWithCarry(x, y, func(b1, b2 byte) byte { return b1 << 1 }, 130 | func(b1, b2 byte) byte { 131 | return b1 >> 7 132 | }, 133 | ) 134 | default: 135 | //log.Panicf("ERROR: Unknown Instruction %04X.", instruction) 136 | } 137 | } 138 | 139 | // Set the value of the register to the immediate ANDed with a 140 | // Randomly generated number. 141 | func (p *Processor) executeRandomAnd(register uint8, immediate byte) { 142 | //log.Print("INSTRUCTION: Execute random number generation.\n") 143 | randomByte := byte(rand.Intn(255)) 144 | p.registers.WriteRegister(register, randomByte&immediate) 145 | } 146 | 147 | // Draw to the screen the sprite loaded from n bytes from the memory address at 148 | // Register I starting from screen coordinates (x, y) set VF to true if 149 | // there is collision. 150 | func (p *Processor) executeDrawInstruction(x uint8, y uint8, n byte) { 151 | //log.Printf("INSTRUCTION: Draw at (V%d, V%d)\n", x, y) 152 | memoryAddress := p.registers.ReadIRegister() 153 | spriteData := p.memory.BlockReadFromMemory(memoryAddress, memoryAddress+uint16(n)) 154 | startX, startY := p.registers.ReadRegister(x), p.registers.ReadRegister(y) 155 | collision := p.display.DrawSprite(startX, startY, n, spriteData) 156 | p.registers.SetCarry(collision) 157 | } 158 | 159 | func (p *Processor) executeRegisterInstructions(register uint8, instruction uint16) { 160 | // There are 9 subtypes of F operations. 161 | subtype := instruction & 0xFF 162 | switch subtype { 163 | case 0x07: 164 | // LD: Load delay timer to VX 165 | //log.Print("INSTRUCTION: Store DT.\n") 166 | p.registers.LoadDelayTimer(register) 167 | case 0x0A: 168 | // LD: Wait and load key to VX 169 | //log.Print("INSTRUCTION: Wait for Key.\n") 170 | p.registers.WriteRegister(register, p.keyboards.WaitForKeyPress()) 171 | case 0x15: 172 | // LD: Load VX to delay timer 173 | //log.Print("INSTRUCTION: Write DT\n") 174 | p.registers.SetDelayTimer(register) 175 | case 0x18: 176 | // LD: Set Sound timer to VX 177 | //log.Print("INSTRUCTION: Write ST\n") 178 | p.registers.SetSoundTimer(register) 179 | case 0x1E: 180 | // ADD: Accumulate VX to I 181 | //log.Print("INSTRUCTION: Accumulate I\n") 182 | p.registers.AccumulateIRegister(register) 183 | case 0x29: 184 | // LD: Set I to the location for the sprite 185 | // of the digit in the register. 186 | //log.Print("INSTRUCTION: Set I to sprite.\n") 187 | p.registers.SetIDigitSprite(register) 188 | case 0x33: 189 | // LD: Store BCD representation of VX in memory. 190 | //log.Print("INSTRUCTION: Load BCD\n") 191 | bcd := p.registers.ReadRegisterBCD(register) 192 | addrrStart := p.registers.ReadIRegister() 193 | p.memory.BlockWriteToMemory(addrrStart, addrrStart+3, bcd[:]) 194 | case 0x55: 195 | // LD: Store registers to memory. 196 | //log.Print("INSTRUCTION: Dump registers to memory.\n") 197 | addrStart := p.registers.ReadIRegister() 198 | registers := p.registers.BlockReadRegisters() 199 | p.memory.BlockWriteToMemory(addrStart, addrStart+uint16(register+1), registers[:register+1]) 200 | case 0x65: 201 | // LD: Load registers from memory. 202 | //log.Print("INSTRUCTION: Load registers from memory.\n") 203 | addrStart := p.registers.ReadIRegister() 204 | registers := p.memory.BlockReadFromMemory(addrStart, addrStart+uint16(register+1)) 205 | var registersCopy [16]byte 206 | copy(registersCopy[:register+1], registers[:register+1]) 207 | p.registers.BlockWriteRegisters(registersCopy, register+1) 208 | //fmt.Printf("0x%03X => %#v\n%#v\n%#v", addrStart, registers, registersCopy, p.registers.generalPurpose) 209 | default: 210 | //log.Panicf("ERROR: Unknown Instruction %04X.", instruction) 211 | } 212 | } 213 | 214 | // Execute the next instruction 215 | func (p *Processor) executeInstruction(instruction uint16) { 216 | instructionCharacter := getInstructionChar(instruction) 217 | register := getRegisterIndex(instructionCharacter[1]) // For many instructions. 218 | register2 := getRegisterIndex(instructionCharacter[2]) // For some instructions. 219 | immediate := byte(instruction & 0xFF) // For some instructions. 220 | switch instructionCharacter[0] { 221 | case '0': 222 | p.executeSystemInstruction(instructionCharacter) 223 | case '1': 224 | // JP: Jump to the location. 225 | //log.Print("INSTRUCTION: Jump to location.\n") 226 | p.registers.SetProgramCounter((instruction & 0x0FFF) - 2) 227 | case '2': 228 | // CALL: Call a subroutine. 229 | //log.Print("INSTRUCTION: Call a subroutine.\n") 230 | p.stack.Push(p.registers.GetProgramCounter()) 231 | p.registers.SetProgramCounter((instruction & 0x0FFF) - 2) 232 | case '3', '4', '5', '9', 'E': 233 | p.executeSkipInstructions(instructionCharacter, instruction) 234 | case '6': 235 | // LD, load immediate value to register. 236 | //log.Print("INSTRUCTION: Write an immediate value to a register.\n") 237 | p.registers.WriteRegister(register, immediate) 238 | case '7': 239 | // ADD, add immediate value to register. 240 | //log.Print("INSTRUCTION: Add an immediate value to a register.\n") 241 | p.registers.AddRegisterImmediate(register, immediate) 242 | case '8': 243 | p.executeLogicalInstructions(register, register2, LogicalInstructionType(instruction&0xF), instruction) 244 | case 'A': 245 | // LD: Load to I register. 246 | //log.Print("INSTRUCTION: Write to an I register.\n") 247 | p.registers.WriteIRegister(instruction & 0xFFF) 248 | case 'B': 249 | // JP: Jump to V0 + NNN. 250 | //log.Print("INSTRUCTION: Jump to a location V0 + immediate.\n") 251 | p.registers.SetProgramCounter((uint16(p.registers.ReadRegister(0)) + instruction&0xFFF) - 2) 252 | case 'C': 253 | // RND: Set VX tp Random byte AND immediate 254 | p.executeRandomAnd(register, immediate) 255 | case 'D': 256 | // DRW draw a sprite to the screen. 257 | p.executeDrawInstruction(register, register2, byte(instruction&0xF)) 258 | case 'F': 259 | // Register instructions 260 | p.executeRegisterInstructions(register, instruction) 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /pkg/emulator/device/keyboard.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | type chip8Keyboard struct { 4 | keyboardMask *uint16 5 | } 6 | 7 | // Returns true if a key is pressed. 8 | func (k *chip8Keyboard) IsKeyPressed(keyValue byte) bool { 9 | keymask := uint16(1) << keyValue // Calculate mask from value. 10 | mask := *k.keyboardMask 11 | return mask&keymask != 0 12 | } 13 | 14 | // Decode which key is pressed, emulator 15 | // Does not support multiple key presses 16 | // and the rightmost will be selected. 17 | func DecodeKey(keyboardMask uint16) byte { 18 | for i := 0; i < 16; i++ { 19 | keyboardMask := keyboardMask >> i 20 | if keyboardMask&1 == 1 { 21 | ////log.Printf("User pressed %X\n", i) 22 | return byte(i) 23 | } 24 | } 25 | return 0 26 | } 27 | 28 | // Halt program execution until key is pressed. 29 | func (k *chip8Keyboard) WaitForKeyPress() byte { 30 | ////log.Println("Waiting for key press.") 31 | for { 32 | keyboradMask := *k.keyboardMask 33 | if keyboradMask != 0 { 34 | return DecodeKey(keyboradMask) 35 | } 36 | } 37 | } 38 | 39 | // Initialise a new keyboard whose buffer is shared 40 | // Between emulator logic and device logic. 41 | func NewKeyboard(keyboardBuffer *uint16) *chip8Keyboard { 42 | keyboard := new(chip8Keyboard) 43 | keyboard.keyboardMask = keyboardBuffer 44 | return keyboard 45 | } 46 | -------------------------------------------------------------------------------- /pkg/emulator/device/memory.go: -------------------------------------------------------------------------------- 1 | // Contains structs and methods for the chip-8 memory. 2 | package device 3 | 4 | const RamStartLocation = 0x200 5 | 6 | // Givena a chip-8 adress, calculate the location 7 | // of the corresponding value in the ram array. 8 | func calculateRAMOffset(address uint16) uint16 { 9 | return address - RamStartLocation 10 | } 11 | 12 | // Check if an address is in the reserved range. 13 | func isAddressInReservedRange(address uint16) bool { 14 | return address < RamStartLocation 15 | } 16 | 17 | type chip8Memory struct { 18 | // Reserved for the Interpreter. 19 | reserved [512]byte 20 | // Program space 21 | ram [3584]byte 22 | } 23 | 24 | // Read a single cell from memory. 25 | func (m *chip8Memory) ReadMemory(address uint16) byte { 26 | if isAddressInReservedRange(address) { 27 | return m.reserved[address] 28 | } else { 29 | return m.ram[calculateRAMOffset(address)] 30 | } 31 | } 32 | 33 | // Write a single cell of memory. 34 | func (m *chip8Memory) WriteMemory(address uint16, value byte) bool { 35 | if isAddressInReservedRange(address) { 36 | return false 37 | } else { 38 | m.ram[calculateRAMOffset(address)] = value 39 | return true 40 | } 41 | } 42 | 43 | // Read from a block of memory between the start and stop addresses. 44 | func (m *chip8Memory) BlockWriteToMemory(start uint16, stop uint16, data []byte) bool { 45 | if start < 0x200 { 46 | // Reserved memory cannot be manipulated. 47 | return false 48 | } 49 | // Calculate the destination slice. 50 | dst := m.ram[calculateRAMOffset(start):calculateRAMOffset(stop)] 51 | // And copy the data. 52 | copy(dst, data) 53 | return true 54 | } 55 | 56 | // Read a block of memory between the start and stop adresses. 57 | func (m *chip8Memory) BlockReadFromMemory(start uint16, stop uint16) []byte { 58 | // Create a buffer to hold copied values 59 | // Preallocate it as well. 60 | buffer := make([]byte, stop-start) 61 | // Since the read request may cross the reserved boundry. 62 | // We must calculate the ranges we will copy from them. 63 | var reservedStart, reservedStop, ramStart, ramStop uint16 64 | if isAddressInReservedRange(start) { 65 | reservedStart = start 66 | } else { 67 | ramStart = calculateRAMOffset(start) 68 | } 69 | if isAddressInReservedRange(stop) { 70 | reservedStop = stop 71 | } else { 72 | ramStop = calculateRAMOffset(stop) 73 | } 74 | // Finally we must calculate the stop point of the reserved 75 | // Copy in the buffer. 76 | seperator := reservedStop - reservedStart 77 | // Now we can finally copy our values. 78 | copy(buffer[:seperator], m.reserved[reservedStart:reservedStop]) 79 | copy(buffer[seperator:], m.ram[ramStart:ramStop]) 80 | return buffer 81 | } 82 | 83 | // Load a program to the chip8 memory given the size and the data 84 | // of the program. isETI660 is used to determine the loading start 85 | // location. 86 | func (m *chip8Memory) loadProgram(program []byte, programSize uint16, isETI660 bool) { 87 | var startLocation uint16 = RamStartLocation // The RAM start location 88 | if isETI660 { 89 | // ETI600 programs start in another location. 90 | startLocation += 0x400 91 | } 92 | // Write the program to memory. 93 | m.BlockWriteToMemory(startLocation, startLocation+programSize, program) 94 | } 95 | 96 | // Load a traditional Chip-8 program to the memory 97 | // Given its data and program size. 98 | func (m *chip8Memory) LoadProgram(program []byte, programSize uint16) { 99 | m.loadProgram(program, programSize, false) 100 | } 101 | 102 | // Load an ETI600 Chip-8 program to the memory 103 | // Given its data and program size. 104 | func (m *chip8Memory) LoadETIProgram(program []byte, programSize uint16) { 105 | m.loadProgram(program, programSize, true) 106 | } 107 | 108 | // Load the reserved sections of the memory 109 | // With apporpirate data. 110 | func (m *chip8Memory) LoadReserved() { 111 | characterSprites := []uint8{ 112 | 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 113 | 0x20, 0x60, 0x20, 0x20, 0x70, // 1 114 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 115 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 116 | 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 117 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 118 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 119 | 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 120 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 121 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 122 | 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 123 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 124 | 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 125 | 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 126 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 127 | 0xF0, 0x80, 0xF0, 0x80, 0x80, // F 128 | } 129 | copy(m.reserved[0:81], characterSprites) 130 | } 131 | 132 | func newMemory() *chip8Memory { 133 | memory := new(chip8Memory) 134 | memory.LoadReserved() 135 | return memory 136 | } 137 | -------------------------------------------------------------------------------- /pkg/emulator/device/processor.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | // Convert an instruction to its character version. 10 | func getInstructionChar(instruction uint16) string { 11 | return fmt.Sprintf("%04X", instruction) 12 | } 13 | 14 | // Get register index from its hexadecimal representation. 15 | func getRegisterIndex(hex byte) byte { 16 | switch { 17 | case '0' <= hex && hex <= '9': 18 | return byte(hex) - 48 19 | case 'a' <= hex && hex <= 'f': 20 | return byte(hex) - 87 21 | case 'A' <= hex && hex <= 'F': 22 | return byte(hex) - 55 23 | default: 24 | return 128 25 | } 26 | 27 | } 28 | 29 | // Main processor of the Chip-8 30 | type Processor struct { 31 | memory *chip8Memory 32 | registers *chip8Registers 33 | display *chip8Display 34 | stack *chip8Stack 35 | keyboards *chip8Keyboard 36 | } 37 | 38 | func NewProcessor(screenBuffer *[32]uint64, keyboardBuffer *uint16, soundBuffer *bool) *Processor { 39 | rand.Seed(time.Now().Unix()) 40 | processor := new(Processor) 41 | processor.display = newDisplay(screenBuffer) 42 | //log.Println("Display initialised.") 43 | processor.memory = newMemory() 44 | //log.Println("Memory initialised.") 45 | processor.registers = NewRegisters(soundBuffer) 46 | //log.Println("Registers initialised.") 47 | processor.keyboards = NewKeyboard(keyboardBuffer) 48 | //log.Println("Keyboard initialised.") 49 | processor.stack = new(chip8Stack) 50 | //log.Println("Stack initialised.") 51 | go processor.registers.RegisterClockLoop() 52 | //log.Println("Register clock loop started.") 53 | return processor 54 | } 55 | 56 | // Load a standard Chip-8 Program to the memory 57 | // And set the program counter accordingly. 58 | func (p *Processor) LoadProgram(program []byte, programSize uint16) { 59 | // Load the program. 60 | p.memory.LoadProgram(program, programSize) 61 | // Set the PC to standard start location. 62 | p.registers.SetProgramCounter(0x198) 63 | } 64 | 65 | // Load an ETI program to the memory and set 66 | // the program counter accordingly. 67 | func (p *Processor) LoadETIProgram(program []byte, programSize uint16) { 68 | p.memory.LoadETIProgram(program, programSize) 69 | p.registers.SetProgramCounter(0x600) 70 | } 71 | 72 | // Fetch the current instruction. 73 | func (p *Processor) fetchInstruction() uint16 { 74 | mostSignificantByte := p.memory.ReadMemory(p.registers.GetProgramCounter()) 75 | leastSignificantByte := p.memory.ReadMemory(p.registers.GetProgramCounter() + 1) 76 | return uint16(mostSignificantByte)<<8 + uint16(leastSignificantByte) 77 | } 78 | 79 | // Returns true if the processor should halt. 80 | func (p *Processor) ShouldHalt() bool { 81 | return p.registers.GetProgramCounter() >= 0xFFD 82 | } 83 | 84 | // Run a CPU Fetch/Execute cycle. 85 | func (p *Processor) Cycle() { 86 | //Increment the PC. 87 | p.registers.IncrementProgramCounter() 88 | // Fetch the instruction. 89 | instruction := p.fetchInstruction() 90 | // Execute the instruction 91 | p.executeInstruction(instruction) 92 | } 93 | -------------------------------------------------------------------------------- /pkg/emulator/device/processor_test.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import "testing" 4 | 5 | func TestGetRegisterIndex(t *testing.T) { 6 | var testMap = map[byte]byte{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, 7 | '9': 9, 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 8 | 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15} 9 | for character, index := range testMap { 10 | if index != getRegisterIndex(character) { 11 | t.Fatalf("Register character %v returned invalid index %d.", character, index) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/emulator/device/registers.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // This type of function is used to operate on register values. 8 | type OperationFunction func(byte, byte) byte 9 | 10 | // This type of function is used to determine VF. 11 | type CarryFunction func(byte, byte) byte 12 | 13 | type chip8Registers struct { 14 | generalPurpose [16]byte 15 | iRegister uint16 16 | programCounter uint16 17 | delayTimer byte 18 | soundTimer byte 19 | soundBuffer *bool 20 | } 21 | 22 | // Write to a general purpose register. 23 | func (r *chip8Registers) WriteRegister(registerIndex uint8, value byte) { 24 | r.generalPurpose[registerIndex] = value 25 | } 26 | 27 | // Read from a general purpose register. 28 | func (r *chip8Registers) ReadRegister(registerIndex uint8) byte { 29 | return r.generalPurpose[registerIndex] 30 | } 31 | 32 | // Read register as BCD array, ie: 100 first then 10s then 1s. 33 | func (r *chip8Registers) ReadRegisterBCD(registerIndex uint8) [3]byte { 34 | value := r.ReadRegister(registerIndex) 35 | return [3]byte{value / 100, (value / 10) % 10, value % 10} 36 | } 37 | 38 | // Add to the value of the register with the given 39 | // index an immediate value and store it in that 40 | // register. 41 | func (r *chip8Registers) AddRegisterImmediate(registerIndex uint8, value byte) { 42 | r.generalPurpose[registerIndex] += value 43 | } 44 | 45 | // Given indexes of two registers, use their values in the operation function, 46 | // Store the return value in the register x. Carry function 47 | // is calculated BEFORE the operation and its result is stored in VF register. 48 | func (r *chip8Registers) RegisterOperationWithCarry(x uint8, y uint8, operation OperationFunction, carry CarryFunction) { 49 | r.generalPurpose[15] = carry(r.generalPurpose[x], r.generalPurpose[y]) 50 | r.generalPurpose[x] = operation(r.generalPurpose[x], r.generalPurpose[y]) 51 | } 52 | 53 | // Given indexes of two registers, use their values in the operation function, 54 | // Store the return value in the register x. 55 | func (r *chip8Registers) RegisterOperation(x uint8, y uint8, operation OperationFunction) { 56 | r.generalPurpose[x] = operation(r.generalPurpose[x], r.generalPurpose[y]) 57 | } 58 | 59 | // Set the program counter to a value. 60 | func (r *chip8Registers) SetProgramCounter(value uint16) { 61 | r.programCounter = value 62 | } 63 | 64 | // Increment the program counter to the location of 65 | // the next instruction. 66 | func (r *chip8Registers) IncrementProgramCounter() { 67 | r.programCounter += 2 68 | } 69 | 70 | // Return the value of the program counter. 71 | func (r *chip8Registers) GetProgramCounter() uint16 { 72 | return r.programCounter 73 | } 74 | 75 | // Compare two registers and return true if their values are equal. 76 | func (r *chip8Registers) CompareRegisters(x uint8, y uint8) bool { 77 | return r.generalPurpose[x] == r.generalPurpose[y] 78 | } 79 | 80 | // Write to the I register. 81 | func (r *chip8Registers) WriteIRegister(value uint16) { 82 | r.iRegister = value 83 | } 84 | 85 | // Read the I register. 86 | func (r *chip8Registers) ReadIRegister() uint16 { 87 | return r.iRegister 88 | } 89 | 90 | // Add the value of the source register to the I register. 91 | func (r *chip8Registers) AccumulateIRegister(sourceRegister uint8) { 92 | r.iRegister += uint16(r.ReadRegister(sourceRegister)) 93 | } 94 | 95 | // Set the value of I to the address of the sprite representing 96 | // the digit stored in the source register. 97 | func (r *chip8Registers) SetIDigitSprite(sourceRegister uint8) { 98 | characterIndex := r.ReadRegister(sourceRegister) 99 | //log.Printf("Getting character index for %X\n", characterIndex) 100 | // Since characters consist of 5 bytes in their sprites, 101 | // Just times 5 should work. 102 | //log.Printf("Writing address %03X\n to I register.\n", uint16(characterIndex)*5) 103 | r.WriteIRegister(uint16(characterIndex) * 5) 104 | } 105 | 106 | // Set the carry register VF to 1 if value is true. 107 | func (r *chip8Registers) SetCarry(value bool) { 108 | var byteValue byte = 0x0 109 | if value { 110 | byteValue = 0x1 111 | } 112 | r.WriteRegister(15, byteValue) 113 | } 114 | 115 | // Set the sound timer to the value of the source register. 116 | func (r *chip8Registers) SetSoundTimer(sourceRegister uint8) { 117 | r.soundTimer = r.ReadRegister(sourceRegister) 118 | } 119 | 120 | // Set the delay timer to the value of the source register. 121 | func (r *chip8Registers) SetDelayTimer(sourceRegister uint8) { 122 | r.delayTimer = r.ReadRegister(sourceRegister) 123 | } 124 | 125 | // Set the value of the destination register to the value of the 126 | // Sound timer. 127 | func (r *chip8Registers) LoadSoundTimer(destinationRegister uint8) { 128 | r.WriteRegister(destinationRegister, r.soundTimer) 129 | } 130 | 131 | // Set the value of the destination register to the value of the 132 | // Delay timer. 133 | func (r *chip8Registers) LoadDelayTimer(destinationRegister uint8) { 134 | r.WriteRegister(destinationRegister, r.delayTimer) 135 | } 136 | 137 | // Write an array of bytes to the registers 138 | func (r *chip8Registers) BlockWriteRegisters(registerData [16]byte, length uint8) { 139 | copy(r.generalPurpose[:length], registerData[:length]) 140 | } 141 | 142 | // Return all the registers. 143 | func (r *chip8Registers) BlockReadRegisters() [16]byte { 144 | var buffer [16]byte 145 | copy(buffer[:], r.generalPurpose[:]) 146 | return buffer 147 | } 148 | 149 | // Decrement ST and DT. 150 | func (r *chip8Registers) UpdateClockRegisters() { 151 | if r.soundTimer > 0 { 152 | r.soundTimer-- 153 | } 154 | if r.delayTimer > 0 { 155 | r.delayTimer-- 156 | } 157 | *r.soundBuffer = r.soundTimer > 0 158 | } 159 | 160 | func (r *chip8Registers) RegisterClockLoop() { 161 | for { 162 | r.UpdateClockRegisters() 163 | time.Sleep(time.Second / 60) 164 | } 165 | } 166 | 167 | // Initialise a new register with a sound buffer. 168 | func NewRegisters(soundBuffer *bool) *chip8Registers { 169 | register := new(chip8Registers) 170 | register.soundBuffer = soundBuffer 171 | return register 172 | } 173 | -------------------------------------------------------------------------------- /pkg/emulator/device/stack.go: -------------------------------------------------------------------------------- 1 | package device 2 | 3 | type chip8Stack struct { 4 | // Although technically a register 5 | // It fits here much better. 6 | stackPointer uint16 7 | // This is where the addresses are hold. 8 | addresses [16]uint16 9 | } 10 | 11 | // Pop an address from the stack. 12 | func (s *chip8Stack) Pop() uint16 { 13 | s.stackPointer-- 14 | stackValue := s.addresses[s.stackPointer] 15 | return stackValue 16 | } 17 | 18 | // Push an address to the stack. 19 | func (s *chip8Stack) Push(address uint16) { 20 | s.addresses[s.stackPointer] = address 21 | s.stackPointer++ 22 | } 23 | -------------------------------------------------------------------------------- /pkg/emulator/emulator.go: -------------------------------------------------------------------------------- 1 | package emulator 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/ambertide/chip8/pkg/emulator/device" 8 | ) 9 | 10 | type Emulator struct { 11 | screenBuffer [32]uint64 12 | processor *device.Processor 13 | keyboardBuffer uint16 14 | soundBuffer bool 15 | clockSpeed uint64 16 | programPath string 17 | } 18 | 19 | func NewEmulator(clockSpeed uint64, programPath string) *Emulator { 20 | emulator := new(Emulator) 21 | emulator.clockSpeed = clockSpeed 22 | emulator.programPath = programPath 23 | emulator.processor = device.NewProcessor(&emulator.screenBuffer, &emulator.keyboardBuffer, &emulator.soundBuffer) 24 | return emulator 25 | } 26 | 27 | func (e *Emulator) RunEmulator(program []byte, programSize uint16) { 28 | e.processor.LoadProgram(program, programSize) 29 | for !e.processor.ShouldHalt() { 30 | e.processor.Cycle() 31 | time.Sleep(time.Second / time.Duration(e.clockSpeed)) 32 | } 33 | } 34 | 35 | func (emulator *Emulator) emulatorCode() { 36 | romPath := emulator.programPath 37 | file, err := os.Open(romPath) 38 | if err != nil { 39 | panic(err) 40 | } 41 | var program [0xFFF]byte // Maximum ROM size. 42 | programSize, err := file.Read(program[:]) 43 | if err != nil { 44 | panic(err) 45 | } 46 | emulator.RunEmulator(program[:], uint16(programSize)) 47 | } 48 | 49 | // Run the emulator subroutines. 50 | func RunEmulator(clockSpeed uint64, programPath string) { 51 | e := NewEmulator(clockSpeed, programPath) 52 | //log.Println("Emulator initialised.") 53 | go e.emulatorCode() 54 | go BeepRoutine(&e.soundBuffer) 55 | //log.Println("Emulator goroutine dispatched.") 56 | RunGraphics(&e.screenBuffer, &e.keyboardBuffer) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/emulator/graphics.go: -------------------------------------------------------------------------------- 1 | package emulator 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | _ "image/png" 7 | 8 | "github.com/faiface/pixel" 9 | "github.com/faiface/pixel/pixelgl" 10 | ) 11 | 12 | type Graphics struct { 13 | screen *[32]uint64 14 | pixelSprite *pixel.Sprite 15 | window *pixelgl.Window 16 | batch *pixel.Batch 17 | keyboardBuffer *uint16 18 | } 19 | 20 | var keysToChip8 = map[pixelgl.Button]uint16{ 21 | pixelgl.Key0: 1, 22 | pixelgl.Key1: 2, 23 | pixelgl.Key2: 4, 24 | pixelgl.Key3: 8, 25 | pixelgl.Key4: 16, 26 | pixelgl.Key5: 32, 27 | pixelgl.Key6: 64, 28 | pixelgl.Key7: 128, 29 | pixelgl.Key8: 256, 30 | pixelgl.Key9: 512, 31 | pixelgl.KeyA: 1024, 32 | pixelgl.KeyB: 2048, 33 | pixelgl.KeyC: 4096, 34 | pixelgl.KeyD: 8192, 35 | pixelgl.KeyE: 16384, 36 | pixelgl.KeyF: 32768, 37 | } 38 | 39 | // Almost verbatim from the Pixel tutorial in 40 | // https://github.com/faiface/pixel/wiki/Drawing-a-Sprite 41 | func (g *Graphics) loadPixelSprite() pixel.Picture { 42 | img := image.NewRGBA(image.Rect(0, 0, 10, 10)) 43 | for i := 0; i < 10; i++ { 44 | for j := 0; j < 10; j++ { 45 | img.SetRGBA(i, j, color.RGBA{255, 255, 255, 1}) 46 | } 47 | } 48 | picture := pixel.PictureDataFromImage(img) 49 | g.pixelSprite = pixel.NewSprite(picture, picture.Bounds()) 50 | //log.Print("Hi") 51 | return picture 52 | } 53 | 54 | // Calculate the matrices to locate sprites. 55 | func (g *Graphics) calculateMatrices() []pixel.Matrix { 56 | matrices := []pixel.Matrix{} 57 | for y, row := range g.screen { 58 | x := 64 // We are coming from the reverse side. 59 | for bitmask := uint64(1); bitmask != 0; bitmask = bitmask << 1 { 60 | x -= 1 61 | if row&bitmask != 0 { // This means pixel is lit 62 | // Append the location of the pixel to the matrices as a matrix. 63 | matrices = append(matrices, pixel.IM.Moved(pixel.V(float64(x), float64(32-y)).Scaled(10))) 64 | } 65 | } 66 | } 67 | return matrices 68 | } 69 | 70 | // Draw the pixels to the screen. 71 | func (g *Graphics) drawPixels() { 72 | pixelLocations := g.calculateMatrices() 73 | for _, pixelLocation := range pixelLocations { 74 | g.pixelSprite.Draw(g.batch, pixelLocation) 75 | } 76 | 77 | } 78 | 79 | func NewGraphics(screenBuffer *[32]uint64, keyboardBuffer *uint16) *Graphics { 80 | graphics := new(Graphics) 81 | graphics.screen = screenBuffer 82 | graphics.keyboardBuffer = keyboardBuffer 83 | var err error 84 | config := pixelgl.WindowConfig{ 85 | Title: "Chip8", 86 | Bounds: pixel.R(0, 0, 640, 320), 87 | VSync: true, 88 | } 89 | graphics.window, err = pixelgl.NewWindow(config) 90 | if err != nil { 91 | panic(err) 92 | } 93 | pixelPicture := graphics.loadPixelSprite() 94 | graphics.batch = pixel.NewBatch(&pixel.TrianglesData{}, pixelPicture) 95 | return graphics 96 | } 97 | 98 | // Reset and recalculate the keyboard buffer 99 | // From a slice of pressed keys. 100 | func (g *Graphics) updateKeyboardBuffer(keyValues []uint16) { 101 | newMask := uint16(0x0) 102 | for _, keyValue := range keyValues { 103 | newMask ^= keyValue 104 | } 105 | *g.keyboardBuffer = newMask 106 | } 107 | 108 | // Handle keyboard presses by the user. 109 | func (g *Graphics) handleKeyboard() { 110 | pressedKeys := []uint16{} 111 | for key, value := range keysToChip8 { 112 | if g.window.Pressed(key) { 113 | pressedKeys = append(pressedKeys, value) 114 | } 115 | } 116 | g.updateKeyboardBuffer(pressedKeys) 117 | } 118 | 119 | // Loop through the graphics engine. 120 | func (g *Graphics) Mainloop() { 121 | for !g.window.Closed() { 122 | g.window.Clear(color.Black) 123 | g.batch.Clear() 124 | g.drawPixels() 125 | g.batch.Draw(g.window) 126 | g.window.Update() 127 | g.handleKeyboard() 128 | } 129 | } 130 | 131 | func RunGraphics(screenBuffer *[32]uint64, keyboardBuffer *uint16) { 132 | //log.Println("Graphic initialisation starting...") 133 | graphics := NewGraphics(screenBuffer, keyboardBuffer) 134 | //log.Println("Graphics initialised") 135 | graphics.Mainloop() 136 | 137 | } 138 | -------------------------------------------------------------------------------- /pkg/emulator/sound.go: -------------------------------------------------------------------------------- 1 | package emulator 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/faiface/beep" 7 | "github.com/faiface/beep/generators" 8 | "github.com/faiface/beep/speaker" 9 | ) 10 | 11 | func BeepRoutine(soundTimer *bool) { 12 | speaker.Init(44100, 735) 13 | sound, err := generators.SinTone(44100, 1190) 14 | if err != nil { 15 | return 16 | } 17 | for { 18 | if *soundTimer { 19 | speaker.Play(beep.Take(44100/60, sound)) 20 | } 21 | time.Sleep(time.Second / 60) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/characters.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ambertide/chip8/19e7a64229ff3e6aec7eb0294ed596ab61696adf/test/characters.ch8 --------------------------------------------------------------------------------