├── .gitignore ├── Makefile ├── README.md ├── TODO ├── jamulator ├── addressmodes.go ├── asm6502.nex ├── asm6502.y ├── asm6502_test.go ├── assembly.go ├── astprinter.go ├── codegen.go ├── compile.go ├── disassembly.go ├── interpret.go ├── nes.go ├── recompile.go ├── rom.go └── test │ ├── dasm_example.asm │ ├── dasm_example.bin.ref │ ├── hello.asm │ ├── hello.bin.ref │ ├── jumptable.asm │ ├── jumptable.bin.ref │ ├── suite6502.asm │ ├── suite6502.bin.ref │ ├── test2.asm │ ├── zelda.asm │ └── zelda.bin.ref ├── main.go └── runtime ├── main.c ├── nametable.c ├── nametable.h ├── ppu.c ├── ppu.h └── rom.h /.gitignore: -------------------------------------------------------------------------------- 1 | /jamulate 2 | /roms 3 | /jamulator/y.go 4 | /jamulator/asm6502.nn.go 5 | /runtime/runtime.a 6 | *.test 7 | *.o 8 | *.bc 9 | *.so 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: jamulator/y.go jamulator/asm6502.nn.go runtime/runtime.a 2 | go build -o jamulate main.go 3 | 4 | jamulator/y.go: jamulator/asm6502.y 5 | go tool yacc -o jamulator/y.go -v /dev/null jamulator/asm6502.y 6 | 7 | jamulator/asm6502.nn.go: jamulator/asm6502.nex 8 | ${GOPATH}/bin/nex -e jamulator/asm6502.nex 9 | 10 | clean: 11 | rm -f jamulator/asm6502.nn.go 12 | rm -f jamulator/y.go 13 | rm -f jam 14 | rm -f runtime/runtime.a 15 | rm -f runtime/main.o 16 | rm -f runtime/ppu.o 17 | rm -f runtime/nametable.o 18 | 19 | test: 20 | go test jamulator/*.go 21 | go test 22 | 23 | runtime/runtime.a: runtime/main.o runtime/ppu.o runtime/nametable.o 24 | ar rcs runtime/runtime.a runtime/main.o runtime/ppu.o runtime/nametable.o 25 | 26 | runtime/main.o: runtime/main.c 27 | clang -o runtime/main.o -c runtime/main.c 28 | 29 | runtime/ppu.o: runtime/ppu.c 30 | clang -o runtime/ppu.o -c runtime/ppu.c 31 | 32 | runtime/nametable.o: runtime/nametable.c 33 | clang -o runtime/nametable.o -c runtime/nametable.c 34 | 35 | .PHONY: build clean dev test 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jamulator 2 | 3 | See the writeup for this project: 4 | [Statically Recompiling NES Games into Native Executables with LLVM and Go](http://andrewkelley.me/post/jamulator.html) 5 | 6 | Note: This project is discontinued. See the above article for 7 | the explanation. 8 | 9 | ## Features 10 | 11 | * Recompile NES games into native executables 12 | - Only known supported game is Super Mario Brothers 1 13 | * 6502 assembler / disassembler 14 | * Unpack & disassemble NES roms and then put them back together 15 | * Adds a small custom ABI which gives you `putchar` and `exit` 16 | for making test roms. 17 | 18 | ## Getting Started Developing 19 | 20 | 1. Set up your `$GOPATH`. Make sure the `bin` folder from your go path 21 | is in `$PATH`. 22 | 2. Install the lexer: 23 | 24 | ``` 25 | go get github.com/blynn/nex 26 | ``` 27 | 28 | 3. Install llvm-3.1 or llvm-3.2 from your package manager. 29 | 4. Install gollvm (replace 3.2 with 3.1 if you want): 30 | 31 | ``` 32 | export CGO_CFLAGS=$(llvm-config-3.2 --cflags) 33 | export CGO_LDFLAGS="$(llvm-config-3.2 --ldflags) -Wl,-L$(llvm-config-3.2 --libdir) -lLLVM-$(llvm-config-3.2 --version)" 34 | go get github.com/axw/gollvm/llvm 35 | ``` 36 | 37 | 5. Install the rest of the go dependencies: 38 | 39 | ``` 40 | go install 41 | ``` 42 | 43 | 6. Install the C dependencies: 44 | 45 | ``` 46 | sudo apt-get install libsdl1.2-dev libsdl-gfx1.2-dev libsdl-image1.2-dev libglew1.6-dev libxrandr-dev 47 | ``` 48 | 49 | 7. Compile, run the tests, and then try it! 50 | 51 | ``` 52 | make 53 | make test 54 | ./jamulator -recompile game.nes 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * disassembler: detect the pacman-style jump table. (another example in nestest.nes) 2 | * make SMB1 work 3 | * get rid of jamfile; use .ines* declarations 4 | * update assembler to be able to handle smb1 disassembly 5 | * namespace everything in ppu.h 6 | * update CLI to use commands instead of flags for commands 7 | 8 | * use llvm memcpy intrinsic instead of libc 9 | * optimize dynStore 10 | * optimize dynLoad 11 | * figure out why optimized llvm code does dead loads 12 | -------------------------------------------------------------------------------- /jamulator/addressmodes.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | type AddrMode int 4 | 5 | const ( 6 | nilAddr AddrMode = iota 7 | absAddr 8 | absXAddr 9 | absYAddr 10 | immedAddr 11 | impliedAddr 12 | indirectAddr 13 | xIndexIndirectAddr 14 | indirectYIndexAddr 15 | relativeAddr 16 | zeroPageAddr 17 | zeroXIndexAddr 18 | zeroYIndexAddr 19 | 20 | addrModeCount 21 | ) 22 | 23 | type opCodeData struct { 24 | opName string 25 | addrMode AddrMode 26 | } 27 | 28 | var opNameToOpCode [addrModeCount]map[string]byte 29 | 30 | var opCodeDataMap = []opCodeData{ 31 | // 0x00 32 | {"brk", impliedAddr}, 33 | {"ora", xIndexIndirectAddr}, 34 | {"", nilAddr}, 35 | {"", nilAddr}, 36 | {"", nilAddr}, 37 | {"ora", zeroPageAddr}, 38 | {"asl", zeroPageAddr}, 39 | {"", nilAddr}, 40 | {"php", impliedAddr}, 41 | {"ora", immedAddr}, 42 | {"asl", impliedAddr}, 43 | {"", nilAddr}, 44 | {"", nilAddr}, 45 | {"ora", absAddr}, 46 | {"asl", absAddr}, 47 | {"", nilAddr}, 48 | 49 | // 0x10 50 | {"bpl", relativeAddr}, 51 | {"ora", indirectYIndexAddr}, 52 | {"", nilAddr}, 53 | {"", nilAddr}, 54 | {"", nilAddr}, 55 | {"ora", zeroXIndexAddr}, 56 | {"asl", zeroXIndexAddr}, 57 | {"", nilAddr}, 58 | {"clc", impliedAddr}, 59 | {"ora", absYAddr}, 60 | {"", nilAddr}, 61 | {"", nilAddr}, 62 | {"", nilAddr}, 63 | {"ora", absXAddr}, 64 | {"asl", absXAddr}, 65 | {"", nilAddr}, 66 | 67 | // 0x20 68 | {"jsr", absAddr}, 69 | {"and", xIndexIndirectAddr}, 70 | {"", nilAddr}, 71 | {"", nilAddr}, 72 | {"bit", zeroPageAddr}, 73 | {"and", zeroPageAddr}, 74 | {"rol", zeroPageAddr}, 75 | {"", nilAddr}, 76 | {"plp", impliedAddr}, 77 | {"and", immedAddr}, 78 | {"rol", impliedAddr}, 79 | {"", nilAddr}, 80 | {"bit", absAddr}, 81 | {"and", absAddr}, 82 | {"rol", absAddr}, 83 | {"", nilAddr}, 84 | 85 | // 0x30 86 | {"bmi", relativeAddr}, 87 | {"and", indirectYIndexAddr}, 88 | {"", nilAddr}, 89 | {"", nilAddr}, 90 | {"", nilAddr}, 91 | {"and", zeroXIndexAddr}, 92 | {"rol", zeroXIndexAddr}, 93 | {"", nilAddr}, 94 | {"sec", impliedAddr}, 95 | {"and", absYAddr}, 96 | {"", nilAddr}, 97 | {"", nilAddr}, 98 | {"", nilAddr}, 99 | {"and", absXAddr}, 100 | {"rol", absXAddr}, 101 | {"", nilAddr}, 102 | 103 | // 0x40 104 | {"rti", impliedAddr}, 105 | {"eor", xIndexIndirectAddr}, 106 | {"", nilAddr}, 107 | {"", nilAddr}, 108 | {"", nilAddr}, 109 | {"eor", zeroPageAddr}, 110 | {"lsr", zeroPageAddr}, 111 | {"", nilAddr}, 112 | {"pha", impliedAddr}, 113 | {"eor", immedAddr}, 114 | {"lsr", impliedAddr}, 115 | {"", nilAddr}, 116 | {"jmp", absAddr}, 117 | {"eor", absAddr}, 118 | {"lsr", absAddr}, 119 | {"", nilAddr}, 120 | 121 | // 0x50 122 | {"bvc", relativeAddr}, 123 | {"eor", indirectYIndexAddr}, 124 | {"", nilAddr}, 125 | {"", nilAddr}, 126 | {"", nilAddr}, 127 | {"eor", zeroXIndexAddr}, 128 | {"lsr", zeroXIndexAddr}, 129 | {"", nilAddr}, 130 | {"cli", impliedAddr}, 131 | {"eor", absYAddr}, 132 | {"", nilAddr}, 133 | {"", nilAddr}, 134 | {"", nilAddr}, 135 | {"eor", absXAddr}, 136 | {"lsr", absXAddr}, 137 | {"", nilAddr}, 138 | 139 | // 0x60 140 | {"rts", impliedAddr}, 141 | {"adc", xIndexIndirectAddr}, 142 | {"", nilAddr}, 143 | {"", nilAddr}, 144 | {"", nilAddr}, 145 | {"adc", zeroPageAddr}, 146 | {"ror", zeroPageAddr}, 147 | {"", nilAddr}, 148 | {"pla", impliedAddr}, 149 | {"adc", immedAddr}, 150 | {"ror", impliedAddr}, 151 | {"", nilAddr}, 152 | {"jmp", indirectAddr}, 153 | {"adc", absAddr}, 154 | {"ror", absAddr}, 155 | {"", nilAddr}, 156 | 157 | // 0x70 158 | {"bvs", relativeAddr}, 159 | {"adc", indirectYIndexAddr}, 160 | {"", nilAddr}, 161 | {"", nilAddr}, 162 | {"", nilAddr}, 163 | {"adc", zeroXIndexAddr}, 164 | {"ror", zeroXIndexAddr}, 165 | {"", nilAddr}, 166 | {"sei", impliedAddr}, 167 | {"adc", absYAddr}, 168 | {"", nilAddr}, 169 | {"", nilAddr}, 170 | {"", nilAddr}, 171 | {"adc", absXAddr}, 172 | {"ror", absXAddr}, 173 | {"", nilAddr}, 174 | 175 | // 0x80 176 | {"", nilAddr}, 177 | {"sta", xIndexIndirectAddr}, 178 | {"", nilAddr}, 179 | {"", nilAddr}, 180 | {"sty", zeroPageAddr}, 181 | {"sta", zeroPageAddr}, 182 | {"stx", zeroPageAddr}, 183 | {"", nilAddr}, 184 | {"dey", impliedAddr}, 185 | {"", nilAddr}, 186 | {"txa", impliedAddr}, 187 | {"", nilAddr}, 188 | {"sty", absAddr}, 189 | {"sta", absAddr}, 190 | {"stx", absAddr}, 191 | {"", nilAddr}, 192 | 193 | // 0x90 194 | {"bcc", relativeAddr}, 195 | {"sta", indirectYIndexAddr}, 196 | {"", nilAddr}, 197 | {"", nilAddr}, 198 | {"sty", zeroXIndexAddr}, 199 | {"sta", zeroXIndexAddr}, 200 | {"stx", zeroYIndexAddr}, 201 | {"", nilAddr}, 202 | {"tya", impliedAddr}, 203 | {"sta", absYAddr}, 204 | {"txs", impliedAddr}, 205 | {"", nilAddr}, 206 | {"", nilAddr}, 207 | {"sta", absXAddr}, 208 | {"", nilAddr}, 209 | {"", nilAddr}, 210 | 211 | // 0xa0 212 | {"ldy", immedAddr}, 213 | {"lda", xIndexIndirectAddr}, 214 | {"ldx", immedAddr}, 215 | {"", nilAddr}, 216 | {"ldy", zeroPageAddr}, 217 | {"lda", zeroPageAddr}, 218 | {"ldx", zeroPageAddr}, 219 | {"", nilAddr}, 220 | {"tay", impliedAddr}, 221 | {"lda", immedAddr}, 222 | {"tax", impliedAddr}, 223 | {"", nilAddr}, 224 | {"ldy", absAddr}, 225 | {"lda", absAddr}, 226 | {"ldx", absAddr}, 227 | {"", nilAddr}, 228 | 229 | // 0xb0 230 | {"bcs", relativeAddr}, 231 | {"lda", indirectYIndexAddr}, 232 | {"", nilAddr}, 233 | {"", nilAddr}, 234 | {"ldy", zeroXIndexAddr}, 235 | {"lda", zeroXIndexAddr}, 236 | {"ldx", zeroYIndexAddr}, 237 | {"", nilAddr}, 238 | {"clv", impliedAddr}, 239 | {"lda", absYAddr}, 240 | {"tsx", impliedAddr}, 241 | {"", nilAddr}, 242 | {"ldy", absXAddr}, 243 | {"lda", absXAddr}, 244 | {"ldx", absYAddr}, 245 | {"", nilAddr}, 246 | 247 | // 0xc0 248 | {"cpy", immedAddr}, 249 | {"cmp", xIndexIndirectAddr}, 250 | {"", nilAddr}, 251 | {"", nilAddr}, 252 | {"cpy", zeroPageAddr}, 253 | {"cmp", zeroPageAddr}, 254 | {"dec", zeroPageAddr}, 255 | {"", nilAddr}, 256 | {"iny", impliedAddr}, 257 | {"cmp", immedAddr}, 258 | {"dex", impliedAddr}, 259 | {"", nilAddr}, 260 | {"cpy", absAddr}, 261 | {"cmp", absAddr}, 262 | {"dec", absAddr}, 263 | {"", nilAddr}, 264 | 265 | // 0xd0 266 | {"bne", relativeAddr}, 267 | {"cmp", indirectYIndexAddr}, 268 | {"", nilAddr}, 269 | {"", nilAddr}, 270 | {"", nilAddr}, 271 | {"cmp", zeroXIndexAddr}, 272 | {"dec", zeroXIndexAddr}, 273 | {"", nilAddr}, 274 | {"cld", impliedAddr}, 275 | {"cmp", absYAddr}, 276 | {"", nilAddr}, 277 | {"", nilAddr}, 278 | {"", nilAddr}, 279 | {"cmp", absXAddr}, 280 | {"dec", absXAddr}, 281 | {"", nilAddr}, 282 | 283 | // 0xe0 284 | {"cpx", immedAddr}, 285 | {"sbc", xIndexIndirectAddr}, 286 | {"", nilAddr}, 287 | {"", nilAddr}, 288 | {"cpx", zeroPageAddr}, 289 | {"sbc", zeroPageAddr}, 290 | {"inc", zeroPageAddr}, 291 | {"", nilAddr}, 292 | {"inx", impliedAddr}, 293 | {"sbc", immedAddr}, 294 | {"nop", impliedAddr}, 295 | {"", nilAddr}, 296 | {"cpx", absAddr}, 297 | {"sbc", absAddr}, 298 | {"inc", absAddr}, 299 | {"", nilAddr}, 300 | 301 | // 0xf0 302 | {"beq", relativeAddr}, 303 | {"sbc", indirectYIndexAddr}, 304 | {"", nilAddr}, 305 | {"", nilAddr}, 306 | {"", nilAddr}, 307 | {"sbc", zeroXIndexAddr}, 308 | {"inc", zeroXIndexAddr}, 309 | {"", nilAddr}, 310 | {"sed", impliedAddr}, 311 | {"sbc", absYAddr}, 312 | {"", nilAddr}, 313 | {"", nilAddr}, 314 | {"", nilAddr}, 315 | {"sbc", absXAddr}, 316 | {"inc", absXAddr}, 317 | {"", nilAddr}, 318 | } 319 | 320 | func init() { 321 | for i := 0; i < int(addrModeCount); i++ { 322 | opNameToOpCode[i] = make(map[string]byte) 323 | } 324 | for opCode := 0; opCode < 256; opCode++ { 325 | info := opCodeDataMap[opCode] 326 | opNameToOpCode[info.addrMode][info.opName] = byte(opCode) 327 | } 328 | } 329 | 330 | -------------------------------------------------------------------------------- /jamulator/asm6502.nex: -------------------------------------------------------------------------------- 1 | /[aA][dD][cC]|[aA][nN][dD]|[aA][sS][lL]|[bB][cC][cC]|[bB][cC][sS]|[bB][eE][qQ]|[bB][iI][tT]|[bB][mM][iI]|[bB][nN][eE]|[bB][pP][lL]|[bB][rR][kK]|[bB][vV][cC]|[bB][vV][sS]|[cC][lL][cC]|[cC][lL][dD]|[cC][lL][iI]|[cC][lL][vV]|[cC][mM][pP]|[cC][pP][xX]|[cC][pP][yY]|[dD][eE][cC]|[dD][eE][xX]|[dD][eE][yY]|[eE][oO][rR]|[iI][nN][cC]|[iI][nN][xX]|[iI][nN][yY]|[jJ][mM][pP]|[jJ][sS][rR]|[lL][dD][aA]|[lL][dD][xX]|[lL][dD][yY]|[lL][sS][rR]|[nN][oO][pP]|[oO][rR][aA]|[pP][hH][aA]|[pP][hH][pP]|[pP][lL][aA]|[pP][lL][pP]|[rR][oO][lL]|[rR][oO][rR]|[rR][tT][iI]|[rR][tT][sS]|[sS][bB][cC]|[sS][eE][cC]|[sS][eE][dD]|[sS][eE][iI]|[sS][tT][aA]|[sS][tT][xX]|[sS][tT][yY]|[tT][aA][xX]|[tT][aA][yY]|[tT][sS][xX]|[tT][xX][aA]|[tT][xX][sS]|[tT][yY][aA]/ { 2 | lval.str = yylex.Text() 3 | return tokInstruction 4 | } 5 | /[xX]|[yY]|[aA]/ { 6 | lval.str = yylex.Text() 7 | return tokRegister 8 | } 9 | /[pP][rR][oO][cC][eE][sS][sS][oO][rR]/ { 10 | return tokProcessor 11 | } 12 | /\.[dD][aA][tT][aA]|[dD][cC]\.[bB]|\.[dD][bB]/ { 13 | return tokData 14 | } 15 | /[dD][cC]\.[wW]|\.[dD][wW]/ { 16 | return tokDataWord 17 | } 18 | /\.?[oO][rR][gG]/ { 19 | return tokOrg 20 | } 21 | /[sS][uU][bB][rR][oO][uU][tT][iI][nN][eE]/ { 22 | return tokSubroutine 23 | } 24 | /"[^"\n]*"/ { 25 | t := yylex.Text() 26 | lval.str = t[1:len(t)-1] 27 | return tokQuotedString 28 | } 29 | /[a-zA-Z][a-zA-Z_.0-9]*/ { 30 | lval.str = yylex.Text() 31 | return tokIdentifier 32 | } 33 | /%[01]+/ { 34 | binPart := yylex.Text()[1:] 35 | n, err := strconv.ParseUint(binPart, 2, 16) 36 | if err != nil { 37 | yylex.Error("Invalid binary integer: " + binPart) 38 | } 39 | lval.integer = int(n) 40 | return tokInteger 41 | } 42 | /\$[0-9a-fA-F]+/ { 43 | hexPart := yylex.Text()[1:] 44 | n, err := strconv.ParseUint(hexPart, 16, 16) 45 | if err != nil { 46 | yylex.Error("Invalid hexademical integer: " + hexPart) 47 | } 48 | lval.integer = int(n) 49 | return tokInteger 50 | } 51 | /[0-9]+/ { 52 | n, err := strconv.ParseUint(yylex.Text(), 10, 16) 53 | if err != nil { 54 | yylex.Error("Invalid decimal integer: " + yylex.Text()) 55 | } 56 | lval.integer = int(n) 57 | return tokInteger 58 | } 59 | /=/ { 60 | return tokEqual 61 | } 62 | /:/ { 63 | return tokColon 64 | } 65 | /#/ { 66 | return tokPound 67 | } 68 | /\./ { 69 | return tokDot 70 | } 71 | /,/ { 72 | return tokComma 73 | } 74 | /\(/ { 75 | return tokLParen 76 | } 77 | /\)/ { 78 | return tokRParen 79 | } 80 | /[ \t\r]/ { 81 | // ignore whitespace 82 | } 83 | /;[^\n]*\n/ { 84 | // ignore comments 85 | parseLineNumber += 1 86 | return tokNewline 87 | } 88 | /\n+/ { 89 | parseLineNumber += len(yylex.Text()) 90 | return tokNewline 91 | } 92 | /./ { 93 | yylex.Error(fmt.Sprintf("Unexpected character: %q", yylex.Text())) 94 | } 95 | 96 | // 97 | 98 | package jamulator 99 | 100 | import ( 101 | "strconv" 102 | "os" 103 | "fmt" 104 | ) 105 | 106 | var parseLineNumber int 107 | var parseFilename string 108 | var parseErrors ParseErrors 109 | 110 | type ParseErrors []string 111 | 112 | func (errs ParseErrors) Error() string { 113 | return strings.Join(errs, "\n") 114 | } 115 | 116 | func Parse(reader io.Reader) (ProgramAst, error) { 117 | parseLineNumber = 1 118 | 119 | lexer := NewLexer(reader) 120 | yyParse(lexer) 121 | if len(parseErrors) > 0 { 122 | return ProgramAst{}, parseErrors 123 | } 124 | return programAst, nil 125 | } 126 | 127 | func ParseFile(filename string) (ProgramAst, error) { 128 | parseFilename = filename 129 | 130 | fd, err := os.Open(filename) 131 | if err != nil { return ProgramAst{}, err } 132 | programAst, err := Parse(fd) 133 | err2 := fd.Close() 134 | if err != nil { return ProgramAst{}, err } 135 | if err2 != nil { return ProgramAst{}, err2 } 136 | return programAst, nil 137 | } 138 | 139 | func (yylex Lexer) Error(e string) { 140 | s := fmt.Sprintf("%s line %d %s", parseFilename, parseLineNumber, e) 141 | parseErrors = append(parseErrors, s) 142 | } 143 | -------------------------------------------------------------------------------- /jamulator/asm6502.y: -------------------------------------------------------------------------------- 1 | %{ 2 | package jamulator 3 | 4 | import ( 5 | "fmt" 6 | "strconv" 7 | "container/list" 8 | ) 9 | 10 | type AssignStatement struct { 11 | VarName string 12 | Value int 13 | } 14 | 15 | type LabelStatement struct { 16 | LabelName string 17 | Line int 18 | } 19 | 20 | type LabeledStatement struct { 21 | Label *LabelStatement 22 | Stmt interface{} 23 | } 24 | 25 | type OrgPseudoOp struct { 26 | Value int 27 | Fill byte 28 | Line int 29 | } 30 | 31 | type InstructionType int 32 | const ( 33 | ImmediateInstruction InstructionType = iota 34 | ImpliedInstruction 35 | DirectWithLabelIndexedInstruction 36 | DirectIndexedInstruction 37 | DirectWithLabelInstruction 38 | DirectInstruction 39 | IndirectXInstruction 40 | IndirectYInstruction 41 | IndirectInstruction 42 | ) 43 | 44 | type Instruction struct { 45 | Type InstructionType 46 | OpName string 47 | Line int 48 | 49 | // not all fields are used by all instruction types. 50 | Value int 51 | LabelName string 52 | RegisterName string 53 | 54 | // filled in later 55 | OpCode byte 56 | Offset int 57 | Payload []byte 58 | } 59 | 60 | type DataStmtType int 61 | const ( 62 | ByteDataStmt DataStmtType = iota 63 | WordDataStmt 64 | ) 65 | 66 | type DataStatement struct { 67 | Type DataStmtType 68 | dataList *list.List 69 | Line int 70 | 71 | // filled in later 72 | Offset int 73 | Payload []byte 74 | } 75 | 76 | 77 | type IntegerDataItem int 78 | type StringDataItem string 79 | type LabelCall struct { 80 | LabelName string 81 | } 82 | type ProgramAst struct { 83 | List *list.List 84 | } 85 | 86 | var programAst ProgramAst 87 | %} 88 | 89 | %union { 90 | integer int 91 | str string 92 | list *list.List 93 | assignStatement *AssignStatement 94 | orgPsuedoOp *OrgPseudoOp 95 | node interface{} 96 | } 97 | 98 | %type statementList 99 | %type assignStatement 100 | %type statement 101 | %type instructionStatement 102 | %type dataStatement 103 | %type dataList 104 | %type wordList 105 | %type dataItem 106 | %type processorDecl 107 | %type labelName 108 | %type orgPsuedoOp 109 | %type subroutineDecl 110 | %type numberExpr 111 | %type numberExprOptionalPound 112 | 113 | %token tokIdentifier 114 | %token tokRegister 115 | %token tokInteger 116 | %token tokQuotedString 117 | %token tokInstruction 118 | %token tokEqual 119 | %token tokPound 120 | %token tokDot 121 | %token tokComma 122 | %token tokNewline 123 | %token tokData 124 | %token tokDataWord 125 | %token tokProcessor 126 | %token tokLParen 127 | %token tokRParen 128 | %token tokDot 129 | %token tokColon 130 | %token tokOrg 131 | %token tokSubroutine 132 | 133 | %% 134 | 135 | programAst : statementList { 136 | programAst = ProgramAst{$1} 137 | } 138 | 139 | statementList : statementList tokNewline statement { 140 | if $3 == nil { 141 | $$ = $1 142 | } else { 143 | $$ = $1 144 | $$.PushBack($3) 145 | } 146 | } | statement { 147 | if $1 == nil { 148 | $$ = list.New() 149 | } else { 150 | $$ = list.New() 151 | $$.PushBack($1) 152 | } 153 | } 154 | 155 | statement : tokDot tokIdentifier instructionStatement { 156 | $$ = &LabeledStatement{ 157 | &LabelStatement{"." + $2, parseLineNumber}, 158 | $3, 159 | } 160 | } | tokIdentifier tokColon instructionStatement { 161 | $$ = &LabeledStatement{ 162 | &LabelStatement{$1, parseLineNumber}, 163 | $3, 164 | } 165 | } | orgPsuedoOp { 166 | $$ = $1 167 | } | subroutineDecl { 168 | $$ = $1 169 | } | instructionStatement { 170 | $$ = $1 171 | } | tokDot tokIdentifier dataStatement { 172 | $$ = &LabeledStatement{ 173 | &LabelStatement{"." + $2, parseLineNumber}, 174 | $3, 175 | } 176 | } | tokIdentifier tokColon dataStatement { 177 | $$ = &LabeledStatement{ 178 | &LabelStatement{$1, parseLineNumber}, 179 | $3, 180 | } 181 | } | dataStatement { 182 | $$ = $1 183 | } | assignStatement { 184 | $$ = $1 185 | } | tokIdentifier { 186 | $$ = &LabelStatement{$1, parseLineNumber} 187 | } | tokIdentifier tokColon { 188 | $$ = &LabelStatement{$1, parseLineNumber} 189 | } | processorDecl { 190 | if $1 != "6502" { 191 | yylex.Error("Unsupported processor: " + $1 + " - Only 6502 is supported.") 192 | } 193 | // empty statement 194 | $$ = nil 195 | } | { 196 | // empty statement 197 | $$ = nil 198 | } 199 | 200 | dataStatement : tokData dataList { 201 | $$ = &DataStatement{ 202 | Type: ByteDataStmt, 203 | dataList: $2, 204 | Line: parseLineNumber, 205 | } 206 | } | tokDataWord wordList { 207 | $$ = &DataStatement{ 208 | Type: WordDataStmt, 209 | dataList: $2, 210 | Line: parseLineNumber, 211 | } 212 | } 213 | 214 | processorDecl : tokProcessor tokInteger { 215 | $$ = strconv.FormatInt(int64($2), 10) 216 | } | tokProcessor tokIdentifier { 217 | $$ = $2 218 | } 219 | 220 | wordList : wordList tokComma numberExprOptionalPound { 221 | $$ = $1 222 | $$.PushBack($3) 223 | } | numberExprOptionalPound { 224 | $$ = list.New() 225 | $$.PushBack($1) 226 | } 227 | 228 | dataList : dataList tokComma dataItem { 229 | $$ = $1 230 | $$.PushBack($3) 231 | } | dataItem { 232 | $$ = list.New() 233 | $$.PushBack($1) 234 | } 235 | 236 | numberExpr : tokPound tokInteger { 237 | tmp := IntegerDataItem($2) 238 | $$ = &tmp 239 | } | labelName { 240 | $$ = &LabelCall{$1} 241 | } 242 | 243 | numberExprOptionalPound : numberExpr { 244 | $$ = $1 245 | } | tokInteger { 246 | tmp := IntegerDataItem($1) 247 | $$ = &tmp 248 | } 249 | 250 | dataItem : tokQuotedString { 251 | tmp := StringDataItem($1) 252 | $$ = &tmp 253 | } | numberExprOptionalPound { 254 | $$ = $1 255 | } 256 | 257 | assignStatement : tokIdentifier tokEqual tokInteger { 258 | $$ = &AssignStatement{$1, $3} 259 | } 260 | 261 | orgPsuedoOp : tokOrg tokInteger { 262 | $$ = &OrgPseudoOp{$2, 0xff, parseLineNumber} 263 | } | tokOrg tokInteger tokComma tokInteger { 264 | if $4 > 0xff { 265 | yylex.Error("ORG directive fill parameter must be a single byte.") 266 | } 267 | $$ = &OrgPseudoOp{$2, byte($4), parseLineNumber} 268 | } 269 | 270 | subroutineDecl : tokIdentifier tokSubroutine { 271 | $$ = &LabelStatement{$1, parseLineNumber} 272 | } 273 | 274 | instructionStatement : tokInstruction tokPound tokInteger { 275 | $$ = &Instruction{ 276 | Type: ImmediateInstruction, 277 | OpName: $1, 278 | Value: $3, 279 | Line: parseLineNumber, 280 | } 281 | } | tokInstruction { 282 | $$ = &Instruction{ 283 | Type: ImpliedInstruction, 284 | OpName: $1, 285 | Line: parseLineNumber, 286 | } 287 | } | tokInstruction labelName tokComma tokRegister { 288 | $$ = &Instruction{ 289 | Type: DirectWithLabelIndexedInstruction, 290 | OpName: $1, 291 | LabelName: $2, 292 | RegisterName: $4, 293 | Line: parseLineNumber, 294 | } 295 | } | tokInstruction tokInteger tokComma tokRegister { 296 | $$ = &Instruction{ 297 | Type: DirectIndexedInstruction, 298 | OpName: $1, 299 | Value: $2, 300 | RegisterName: $4, 301 | Line: parseLineNumber, 302 | } 303 | } | tokInstruction labelName { 304 | $$ = &Instruction{ 305 | Type: DirectWithLabelInstruction, 306 | OpName: $1, 307 | LabelName: $2, 308 | Line: parseLineNumber, 309 | } 310 | } | tokInstruction tokInteger { 311 | $$ = &Instruction{ 312 | Type: DirectInstruction, 313 | OpName: $1, 314 | Value: $2, 315 | Line: parseLineNumber, 316 | } 317 | } | tokInstruction tokLParen tokInteger tokComma tokRegister tokRParen { 318 | if $5 != "x" && $5 != "X" { 319 | yylex.Error("Register argument must be X.") 320 | } 321 | $$ = &Instruction{ 322 | Type: IndirectXInstruction, 323 | OpName: $1, 324 | Value: $3, 325 | Line: parseLineNumber, 326 | } 327 | } | tokInstruction tokLParen tokInteger tokRParen tokComma tokRegister { 328 | if $6 != "y" && $6 != "Y" { 329 | yylex.Error("Register argument must be Y.") 330 | } 331 | $$ = &Instruction{ 332 | Type: IndirectYInstruction, 333 | OpName: $1, 334 | Value: $3, 335 | Line: parseLineNumber, 336 | } 337 | } | tokInstruction tokLParen tokInteger tokRParen { 338 | $$ = &Instruction{ 339 | Type: IndirectInstruction, 340 | OpName: $1, 341 | Value: $3, 342 | Line: parseLineNumber, 343 | } 344 | } 345 | 346 | labelName : tokDot { 347 | $$ = "." 348 | } | tokIdentifier { 349 | $$ = $1 350 | } | tokDot tokIdentifier { 351 | $$ = "." + $2 352 | } 353 | 354 | %% 355 | 356 | -------------------------------------------------------------------------------- /jamulator/asm6502_test.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "testing" 8 | ) 9 | 10 | type testAsm struct { 11 | inFile string 12 | expectedOutFile string 13 | } 14 | 15 | var testAsmList = []testAsm{ 16 | { 17 | "test/suite6502.asm", 18 | "test/suite6502.bin.ref", 19 | }, 20 | { 21 | "test/zelda.asm", 22 | "test/zelda.bin.ref", 23 | }, 24 | { 25 | "test/hello.asm", 26 | "test/hello.bin.ref", 27 | }, 28 | } 29 | 30 | var testDisAsmList = []string{ 31 | "test/suite6502.bin.ref", 32 | "test/zelda.bin.ref", 33 | "test/hello.bin.ref", 34 | } 35 | 36 | func TestAsm(t *testing.T) { 37 | for _, ta := range testAsmList { 38 | expected, err := ioutil.ReadFile(ta.expectedOutFile) 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | programAst, err := ParseFile(ta.inFile) 43 | if err != nil { 44 | t.Error(err) 45 | } 46 | program := programAst.ToProgram() 47 | if len(program.Errors) > 0 { 48 | t.Error(fmt.Sprintf("%s: unexpected errors", ta.inFile)) 49 | } 50 | buf := new(bytes.Buffer) 51 | err = program.Assemble(buf) 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | if bytes.Compare(buf.Bytes(), expected) != 0 { 56 | t.Error(fmt.Sprintf("%s: does not match expected output", ta.inFile)) 57 | } 58 | } 59 | } 60 | 61 | func TestDisassembly(t *testing.T) { 62 | // try disassembling the ref and reassembling it, it should match byte for byte 63 | for _, binfile := range testDisAsmList { 64 | expected, err := ioutil.ReadFile(binfile) 65 | if err != nil { 66 | t.Error(err) 67 | } 68 | 69 | // disassemble binary file into a program 70 | expectedBuf := bytes.NewBuffer(expected) 71 | program, err := Disassemble(expectedBuf) 72 | if err != nil { 73 | t.Error(err) 74 | } 75 | 76 | // write the source code into a buffer 77 | sourceBuf := new(bytes.Buffer) 78 | err = program.WriteSource(sourceBuf) 79 | if err != nil { 80 | t.Error(err) 81 | } 82 | 83 | // load the source code into a program 84 | programAst, err := Parse(sourceBuf) 85 | if err != nil { 86 | t.Error(err) 87 | } 88 | program = programAst.ToProgram() 89 | if len(program.Errors) > 0 { 90 | t.Error(fmt.Sprintf("%s: unexpected errors", binfile)) 91 | } 92 | 93 | // assemble the source code into a binary buffer 94 | binBuf := new(bytes.Buffer) 95 | err = program.Assemble(binBuf) 96 | if err != nil { 97 | t.Error(err) 98 | } 99 | if bytes.Compare(binBuf.Bytes(), expected) != 0 { 100 | t.Error(fmt.Sprintf("%s: does not match expected output", binfile)) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /jamulator/assembly.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "container/list" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | type Program struct { 15 | List *list.List 16 | Labels map[string]int 17 | Errors []string 18 | ChrRom [][]byte 19 | PrgRom [][]byte 20 | Mirroring Mirroring 21 | // maps memory offset to element in Ast 22 | Offsets map[int]*list.Element 23 | Variables map[string]int 24 | } 25 | 26 | type Assembler interface { 27 | Resolve() error 28 | Assemble(symbolGetter) error 29 | GetPayload() []byte 30 | GetLine() int 31 | SetOffset(int) 32 | GetOffset() int 33 | } 34 | 35 | type symbolGetter interface { 36 | getSymbol(string, int) (int, bool) 37 | } 38 | 39 | func (i *Instruction) GetPayload() []byte { 40 | return i.Payload 41 | } 42 | 43 | func (i *Instruction) GetLine() int { 44 | return i.Line 45 | } 46 | 47 | func (i *Instruction) GetOffset() int { 48 | return i.Offset 49 | } 50 | 51 | func (i *Instruction) SetOffset(offset int) { 52 | i.Offset = offset 53 | } 54 | 55 | func (s *DataStatement) GetPayload() []byte { 56 | return s.Payload 57 | } 58 | 59 | func (s *DataStatement) GetLine() int { 60 | return s.Line 61 | } 62 | 63 | func (s *DataStatement) GetOffset() int { 64 | return s.Offset 65 | } 66 | 67 | func (s *DataStatement) SetOffset(offset int) { 68 | s.Offset = offset 69 | } 70 | 71 | func (p *Program) getSymbol(name string, offset int) (int, bool) { 72 | if name == "." { 73 | return offset, true 74 | } 75 | // look up as variable 76 | value, ok := p.Variables[name] 77 | if !ok { 78 | // look up as label 79 | value, ok = p.Labels[name] 80 | } 81 | return value, ok 82 | } 83 | 84 | // computes OpCode, Payload, and Size 85 | func (i *Instruction) Resolve() error { 86 | var ok bool 87 | lowerOpName := strings.ToLower(i.OpName) 88 | switch i.Type { 89 | default: panic("unexpected instruction type") 90 | case ImmediateInstruction: 91 | i.OpCode, ok = opNameToOpCode[immedAddr][lowerOpName] 92 | if !ok { 93 | return errors.New(fmt.Sprintf("Line %d: Unrecognized immediate instruction: %s", i.Line, i.OpName)) 94 | } 95 | if i.Value > 0xff { 96 | return errors.New(fmt.Sprintf("Line %d: Immediate instruction argument must be a 1 byte integer.", i.Line)) 97 | } 98 | i.Payload = []byte{i.OpCode, byte(i.Value)} 99 | case ImpliedInstruction: 100 | i.OpCode, ok = opNameToOpCode[impliedAddr][lowerOpName] 101 | if !ok { 102 | return errors.New(fmt.Sprintf("Line %d: Unrecognized implied instruction: %s", i.Line, i.OpName)) 103 | } 104 | i.Payload = []byte{i.OpCode} 105 | case DirectInstruction: 106 | // try indirect 107 | i.OpCode, ok = opNameToOpCode[indirectAddr][lowerOpName] 108 | if ok { 109 | if i.Value > 0xff { 110 | return errors.New(fmt.Sprintf("Line %d: Relative memory address is limited to 1 byte.", i.Line)) 111 | } 112 | i.Payload = []byte{i.OpCode, byte(i.Value)} 113 | return nil 114 | } 115 | // try zero page 116 | if i.Value <= 0xff { 117 | i.OpCode, ok = opNameToOpCode[zeroPageAddr][lowerOpName] 118 | if ok { 119 | i.Payload = []byte{i.OpCode, byte(i.Value)} 120 | return nil 121 | } 122 | } 123 | // must be absolute 124 | i.OpCode, ok = opNameToOpCode[absAddr][lowerOpName] 125 | if ok { 126 | if i.Value > 0xffff { 127 | return errors.New(fmt.Sprintf("Line %d: Absolute memory address is limited to 2 bytes.", i.Line)) 128 | } 129 | i.Payload = []byte{i.OpCode, 0, 0} 130 | binary.LittleEndian.PutUint16(i.Payload[1:], uint16(i.Value)) 131 | return nil 132 | } 133 | return errors.New(fmt.Sprintf("Line %d: Unrecognized direct instruction: %s", i.Line, i.OpName)) 134 | case DirectWithLabelInstruction: 135 | i.OpCode, ok = opNameToOpCode[absAddr][lowerOpName] 136 | if ok { 137 | // 0s are placeholder for when we resolve the label 138 | i.Payload = []byte{i.OpCode, 0, 0} 139 | return nil 140 | } 141 | i.OpCode, ok = opNameToOpCode[relativeAddr][lowerOpName] 142 | if ok { 143 | // 0 is placeholder for when we resolve the label 144 | i.Payload = []byte{i.OpCode, 0} 145 | return nil 146 | } 147 | return errors.New(fmt.Sprintf("Line %d: Unrecognized direct instruction: %s", i.Line, i.OpName)) 148 | case DirectIndexedInstruction: 149 | lowerRegName := strings.ToLower(i.RegisterName) 150 | if lowerRegName == "x" { 151 | if i.Value <= 0xff { 152 | i.OpCode, ok = opNameToOpCode[zeroXIndexAddr][lowerOpName] 153 | if ok { 154 | i.Payload = []byte{i.OpCode, byte(i.Value)} 155 | return nil 156 | } 157 | } else if i.Value > 0xffff { 158 | return errors.New(fmt.Sprintf("Line %d: Absolute memory address is limited to 2 bytes.", i.Line)) 159 | } 160 | i.OpCode, ok = opNameToOpCode[absXAddr][lowerOpName] 161 | if ok { 162 | i.Payload = []byte{i.OpCode, 0, 0} 163 | binary.LittleEndian.PutUint16(i.Payload[1:], uint16(i.Value)) 164 | return nil 165 | } 166 | return errors.New(fmt.Sprintf("Line %d: Unrecognized absolute, X instruction: %s", i.Line, i.OpName)) 167 | } else if lowerRegName == "y" { 168 | if i.Value <= 0xff { 169 | i.OpCode, ok = opNameToOpCode[zeroYIndexAddr][lowerOpName] 170 | if ok { 171 | i.Payload = []byte{i.OpCode, byte(i.Value)} 172 | return nil 173 | } 174 | } else if i.Value > 0xffff { 175 | return errors.New(fmt.Sprintf("Line %d: Absolute memory address is limited to 2 bytes.", i.Line)) 176 | } 177 | i.OpCode, ok = opNameToOpCode[absYAddr][lowerOpName] 178 | if !ok { 179 | i.Payload = []byte{i.OpCode, 0, 0} 180 | binary.LittleEndian.PutUint16(i.Payload[1:], uint16(i.Value)) 181 | return nil 182 | } 183 | return errors.New(fmt.Sprintf("Line %d: Unrecognized absolute, Y instruction: %s", i.Line, i.OpName)) 184 | } 185 | return errors.New(fmt.Sprintf("Line %d: Register argument must be X or Y", i.Line)) 186 | case DirectWithLabelIndexedInstruction: 187 | lowerRegName := strings.ToLower(i.RegisterName) 188 | if lowerRegName == "x" { 189 | i.OpCode, ok = opNameToOpCode[absXAddr][lowerOpName] 190 | if ok { 191 | // 0s are placeholder until we resolve labels 192 | i.Payload = []byte{i.OpCode, 0, 0} 193 | return nil 194 | } 195 | return errors.New(fmt.Sprintf("Line %d: Unrecognized direct, X instruction: %s", i.Line, i.OpName)) 196 | } else if lowerRegName == "y" { 197 | i.OpCode, ok = opNameToOpCode[absYAddr][lowerOpName] 198 | if !ok { 199 | // 0s are placeholder until we resolve labels 200 | i.Payload = []byte{i.OpCode, 0, 0} 201 | return nil 202 | } 203 | return errors.New(fmt.Sprintf("Line %d: Unrecognized direct, Y instruction: %s", i.Line, i.OpName)) 204 | } 205 | return errors.New(fmt.Sprintf("Line %d: Register argument must be X or Y", i.Line)) 206 | case IndirectXInstruction: 207 | i.OpCode, ok = opNameToOpCode[xIndexIndirectAddr][lowerOpName] 208 | if !ok { 209 | return errors.New(fmt.Sprintf("Line %d: Unrecognized indirect x indexed instruction: %s", i.Line, i.OpName)) 210 | } 211 | if i.Value > 0xff { 212 | return errors.New(fmt.Sprintf("Line %d: Indirect X memory address is limited to 1 byte.", i.Line)) 213 | } 214 | i.Payload = []byte{i.OpCode, byte(i.Value)} 215 | case IndirectYInstruction: 216 | i.OpCode, ok = opNameToOpCode[indirectYIndexAddr][lowerOpName] 217 | if !ok { 218 | return errors.New(fmt.Sprintf("Line %d: Unrecognized indirect y indexed instruction: %s", i.Line, i.OpName)) 219 | } 220 | if i.Value > 0xff { 221 | return errors.New(fmt.Sprintf("Line %d: Indirect Y memory address is limited to 1 byte.", i.Line)) 222 | } 223 | i.Payload = []byte{i.OpCode, byte(i.Value)} 224 | case IndirectInstruction: 225 | if lowerOpName != "jmp" { 226 | return errors.New(fmt.Sprintf("Line %d: Unrecognized indirect instruction: %s", i.Line, i.OpName)) 227 | } 228 | i.Payload = []byte{0x6c, 0, 0} 229 | if i.Value > 0xffff { 230 | return errors.New(fmt.Sprintf("Line %d: Memory address is limited to 2 bytes.", i.Line)) 231 | } 232 | binary.LittleEndian.PutUint16(i.Payload[1:], uint16(i.Value)) 233 | } 234 | return nil 235 | } 236 | 237 | func (i *Instruction) Assemble(sg symbolGetter) error { 238 | // fill in the rest of the payload 239 | var ok bool 240 | switch i.Type { 241 | default: panic("unexpected instruction type") 242 | case ImmediateInstruction, ImpliedInstruction, DirectInstruction, 243 | DirectIndexedInstruction, IndirectXInstruction, IndirectYInstruction, 244 | IndirectInstruction: 245 | // nothing to do 246 | case DirectWithLabelInstruction: 247 | i.Value, ok = sg.getSymbol(i.LabelName, i.Offset) 248 | if !ok { 249 | return errors.New(fmt.Sprintf("Line %d: Undefined label: %s", i.Line, i.LabelName)) 250 | } 251 | if i.Value > 0xffff { 252 | return errors.New(fmt.Sprintf("Line %d: Symbol must fit into 2 bytes: %s", i.Line, i.LabelName)) 253 | } 254 | if len(i.Payload) == 2 { 255 | // relative address 256 | delta := i.Value - (i.Offset + len(i.Payload)) 257 | if delta > 127 || delta < -128 { 258 | return errors.New(fmt.Sprintf("Line %d: Label address must be within 127 bytes of instruction address.", i.Line)) 259 | } 260 | i.Payload[1] = byte(delta) 261 | return nil 262 | } 263 | // absolute address 264 | binary.LittleEndian.PutUint16(i.Payload[1:], uint16(i.Value)) 265 | case DirectWithLabelIndexedInstruction: 266 | i.Value, ok = sg.getSymbol(i.LabelName, i.Offset) 267 | if !ok { 268 | return errors.New(fmt.Sprintf("Line %d: Undefined symbol: %s", i.Line, i.LabelName)) 269 | } 270 | if i.Value > 0xffff { 271 | return errors.New(fmt.Sprintf("Line %d: Symbol must fit into 2 bytes: %s", i.Line, i.LabelName)) 272 | } 273 | binary.LittleEndian.PutUint16(i.Payload[1:], uint16(i.Value)) 274 | } 275 | return nil 276 | } 277 | 278 | func (s *DataStatement) Resolve() error { 279 | size := 0 280 | for e := s.dataList.Front(); e != nil; e = e.Next() { 281 | switch t := e.Value.(type) { 282 | case *StringDataItem: 283 | switch s.Type { 284 | default: panic("unknown DataStatement Type") 285 | case ByteDataStmt: 286 | size += len(*t) 287 | case WordDataStmt: 288 | return errors.New(fmt.Sprintf("Line %d: string invalid in data word statement.", s.Line)) 289 | } 290 | case *IntegerDataItem: 291 | switch s.Type { 292 | default: panic("unknown DataStatement Type") 293 | case ByteDataStmt: 294 | if *t > 0xff { 295 | return errors.New(fmt.Sprintf("Line %d: Integer byte data item limited to 1 byte.", s.Line)) 296 | } 297 | size += 1 298 | case WordDataStmt: 299 | if *t > 0xffff { 300 | return errors.New(fmt.Sprintf("Line %d: Integer word data item limited to 2 bytes.", s.Line)) 301 | } 302 | size += 2 303 | } 304 | case *LabelCall: 305 | switch s.Type { 306 | default: panic("unknown DataStatement Type") 307 | case ByteDataStmt: 308 | return errors.New(fmt.Sprintf("Line %d: label invalid in byte data statement.", s.Line)) 309 | case WordDataStmt: 310 | size += 2 311 | } 312 | default: 313 | panic("unknown data item type") 314 | } 315 | } 316 | s.Payload = make([]byte, size) 317 | return nil 318 | } 319 | 320 | func (s *DataStatement) Assemble(sg symbolGetter) error { 321 | offset := 0 322 | for e := s.dataList.Front(); e != nil; e = e.Next() { 323 | switch t := e.Value.(type) { 324 | case *StringDataItem: 325 | if s.Type != ByteDataStmt { 326 | panic("expected ByteDataStmt") 327 | } 328 | for _, c := range string(*t) { 329 | s.Payload[offset] = byte(c) 330 | offset += 1 331 | } 332 | case *IntegerDataItem: 333 | switch s.Type { 334 | default: panic("unknown DataStatement Type") 335 | case ByteDataStmt: 336 | s.Payload[offset] = byte(*t) 337 | offset += 1 338 | case WordDataStmt: 339 | binary.LittleEndian.PutUint16(s.Payload[offset:], uint16(*t)) 340 | offset += 2 341 | } 342 | case *LabelCall: 343 | if s.Type != WordDataStmt { 344 | panic("expected WordDataStmt") 345 | } 346 | symbolValue, ok := sg.getSymbol(t.LabelName, offset) 347 | if !ok { 348 | return errors.New(fmt.Sprintf("Line %d: Undefined symbol: %s", s.Line, t.LabelName)) 349 | } 350 | if symbolValue > 0xffff { 351 | return errors.New(fmt.Sprintf("Line %d: Symbol must fit into 2 bytes: %s", s.Line, t.LabelName)) 352 | } 353 | binary.LittleEndian.PutUint16(s.Payload[offset:], uint16(symbolValue)) 354 | offset += 2 355 | default: 356 | panic("unknown data item type") 357 | } 358 | } 359 | return nil 360 | } 361 | 362 | func (p *Program) Assemble(w io.Writer) error { 363 | writer := bufio.NewWriter(w) 364 | 365 | offset := 0 366 | expectedOffset := 0 367 | firstOrg := true 368 | orgFillValue := byte(0) 369 | 370 | for e := p.List.Front(); e != nil; e = e.Next() { 371 | switch t := e.Value.(type) { 372 | default: panic("unexpected node") 373 | case *LabelStatement: 374 | // nothing to do 375 | case *OrgPseudoOp: 376 | offset = t.Value 377 | orgFillValue = t.Fill 378 | if firstOrg { 379 | firstOrg = false 380 | expectedOffset = offset 381 | } 382 | case Assembler: 383 | for t.GetOffset() > expectedOffset { 384 | // org fill 385 | err := writer.WriteByte(orgFillValue) 386 | if err != nil { 387 | return err 388 | } 389 | expectedOffset += 1 390 | } 391 | err := t.Assemble(p) 392 | if err != nil { 393 | return err 394 | } 395 | _, err = writer.Write(t.GetPayload()) 396 | if err != nil { 397 | return err 398 | } 399 | offset += len(t.GetPayload()) 400 | expectedOffset = offset 401 | } 402 | } 403 | 404 | writer.Flush() 405 | return nil 406 | } 407 | 408 | func (p *Program) AssembleToFile(filename string) error { 409 | fd, err := os.Create(filename) 410 | if err != nil { 411 | return err 412 | } 413 | 414 | err = p.Assemble(fd) 415 | err2 := fd.Close() 416 | if err != nil { 417 | return err 418 | } 419 | if err2 != nil { 420 | return err2 421 | } 422 | 423 | return nil 424 | } 425 | 426 | func (ast ProgramAst) ExpandLabeledStatements() { 427 | for e := ast.List.Front(); e != nil; e = e.Next() { 428 | s, ok := e.Value.(*LabeledStatement) 429 | if ok { 430 | elemToDel := e 431 | e = ast.List.InsertAfter(s.Label, e) 432 | e = ast.List.InsertAfter(s.Stmt, e) 433 | ast.List.Remove(elemToDel) 434 | } 435 | } 436 | } 437 | 438 | func (p *Program) Resolve() { 439 | offset := 0 440 | for e := p.List.Front(); e != nil; e = e.Next() { 441 | switch t := e.Value.(type) { 442 | default: panic("unexpected node") 443 | case *AssignStatement: 444 | p.Variables[t.VarName] = t.Value 445 | case *OrgPseudoOp: 446 | offset = t.Value 447 | case *LabelStatement: 448 | if offset >= 0xffff { 449 | err := fmt.Sprintf("Line %d: Label memory address must fit in 2 bytes.", t.Line) 450 | p.Errors = append(p.Errors, err) 451 | return 452 | } 453 | _, exists := p.Labels[t.LabelName] 454 | if exists { 455 | err := fmt.Sprintf("Line %d: Label %s already defined.", t.Line, t.LabelName) 456 | p.Errors = append(p.Errors, err) 457 | return 458 | } 459 | p.Labels[t.LabelName] = offset 460 | case Assembler: 461 | if offset >= 0xffff { 462 | err := fmt.Sprintf("Line %d: Instruction is at offset $%04x which is greater than 2 bytes.", t.GetLine(), offset) 463 | p.Errors = append(p.Errors, err) 464 | return 465 | } 466 | p.Offsets[offset] = e 467 | t.SetOffset(offset) 468 | err := t.Resolve() 469 | if err != nil { 470 | p.Errors = append(p.Errors, err.Error()) 471 | return 472 | } 473 | offset += len(t.GetPayload()) 474 | } 475 | } 476 | } 477 | 478 | func (ast ProgramAst) ToProgram() (p *Program) { 479 | ast.ExpandLabeledStatements() 480 | p = &Program{ 481 | List: ast.List, 482 | Labels: make(map[string]int), 483 | Offsets: make(map[int]*list.Element), 484 | Variables: make(map[string]int), 485 | } 486 | p.Resolve() 487 | return 488 | } 489 | -------------------------------------------------------------------------------- /jamulator/astprinter.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | func astPrint(indent int, n interface{}) { 8 | for i := 0; i < indent; i++ { 9 | fmt.Print(" ") 10 | } 11 | fmt.Println(reflect.TypeOf(n)) 12 | } 13 | 14 | func (ast ProgramAst) Print() { 15 | for e := ast.List.Front(); e != nil; e = e.Next() { 16 | astPrint(0, e.Value) 17 | switch t := e.Value.(type) { 18 | case *LabeledStatement: 19 | astPrint(2, t.Label) 20 | astPrint(2, t.Stmt) 21 | case *DataStatement: 22 | for de := t.dataList.Front(); de != nil; de = de.Next() { 23 | astPrint(2, de.Value) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jamulator/codegen.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "fmt" 5 | "github.com/axw/gollvm/llvm" 6 | ) 7 | 8 | func (i *Instruction) ResolveRender() string { 9 | switch i.Type { 10 | case DirectWithLabelInstruction: 11 | i.Type = DirectInstruction 12 | v := i.Render() 13 | i.Type = DirectWithLabelInstruction 14 | return v 15 | case DirectWithLabelIndexedInstruction: 16 | i.Type = DirectIndexedInstruction 17 | v := i.Render() 18 | i.Type = DirectWithLabelInstruction 19 | return v 20 | } 21 | return i.Render() 22 | } 23 | 24 | func (i *Instruction) Compile(c *Compilation) { 25 | c.debugPrint(fmt.Sprintf("%s\n", i.ResolveRender())) 26 | 27 | var labelAddr int 28 | var ok bool 29 | if i.LabelName != "" { 30 | labelAddr, ok = c.program.Labels[i.LabelName] 31 | if !ok { 32 | panic(fmt.Sprintf("label %s addr not defined: %s", i.LabelName, i.Render())) 33 | } 34 | } 35 | var immedValue llvm.Value 36 | if i.Type == ImmediateInstruction { 37 | immedValue = llvm.ConstInt(llvm.Int8Type(), uint64(i.Value), false) 38 | } 39 | 40 | var addrNext = i.Offset+len(i.Payload) 41 | 42 | switch i.OpCode { 43 | default: 44 | c.Errors = append(c.Errors, fmt.Sprintf("unrecognized instruction: %s", i.Render())) 45 | case 0xa2: // ldx immediate 46 | c.builder.CreateStore(immedValue, c.rX) 47 | c.testAndSetZero(i.Value) 48 | c.testAndSetNeg(i.Value) 49 | c.cycle(2, addrNext) 50 | case 0xa0: // ldy immediate 51 | c.performLdy(immedValue) 52 | c.cycle(2, addrNext) 53 | case 0xa9: // lda immediate 54 | c.builder.CreateStore(immedValue, c.rA) 55 | c.testAndSetZero(i.Value) 56 | c.testAndSetNeg(i.Value) 57 | c.cycle(2, addrNext) 58 | case 0x69: // adc immediate 59 | c.performAdc(immedValue) 60 | c.cycle(2, addrNext) 61 | case 0xe9: // sbc immediate 62 | c.performSbc(immedValue) 63 | c.cycle(2, addrNext) 64 | case 0x29: // and immediate 65 | c.performAnd(immedValue) 66 | c.cycle(2, addrNext) 67 | case 0xc9: // cmp immediate 68 | reg := c.builder.CreateLoad(c.rA, "") 69 | c.performCmp(reg, immedValue) 70 | c.cycle(2, addrNext) 71 | case 0xe0: // cpx immediate 72 | reg := c.builder.CreateLoad(c.rX, "") 73 | c.performCmp(reg, immedValue) 74 | c.cycle(2, addrNext) 75 | case 0xc0: // cpy immediate 76 | reg := c.builder.CreateLoad(c.rY, "") 77 | c.performCmp(reg, immedValue) 78 | c.cycle(2, addrNext) 79 | case 0x49: // eor immediate 80 | c.performEor(immedValue) 81 | c.cycle(2, addrNext) 82 | case 0x09: // ora immediate 83 | c.performOra(immedValue) 84 | c.cycle(2, addrNext) 85 | case 0x0a: // asl implied 86 | a := c.builder.CreateLoad(c.rA, "") 87 | c.builder.CreateStore(c.performAsl(a), c.rA) 88 | c.cycle(2, addrNext) 89 | case 0x00: // brk implied 90 | c.pushWordToStack(llvm.ConstInt(llvm.Int16Type(), uint64(i.Offset + 2), false)) 91 | c.pushToStack(c.getStatusByte()) 92 | c.setInt() 93 | c.cycle(7, -1) 94 | c.currentBlock = nil 95 | c.builder.CreateBr(*c.resetBlock) 96 | case 0x18: // clc implied 97 | c.clearCarry() 98 | c.cycle(2, addrNext) 99 | case 0x38: // sec implied 100 | c.setCarry() 101 | c.cycle(2, addrNext) 102 | case 0xd8: // cld implied 103 | c.clearDec() 104 | c.cycle(2, addrNext) 105 | case 0x58: // cli implied 106 | c.clearInt() 107 | c.cycle(2, addrNext) 108 | case 0xb8: // clv implied 109 | c.clearOverflow() 110 | c.cycle(2, addrNext) 111 | case 0xca: // dex implied 112 | c.increment(c.rX, -1) 113 | c.cycle(2, addrNext) 114 | case 0x88: // dey implied 115 | c.increment(c.rY, -1) 116 | c.cycle(2, addrNext) 117 | case 0xe8: // inx implied 118 | c.increment(c.rX, 1) 119 | c.cycle(2, addrNext) 120 | case 0xc8: // iny implied 121 | c.increment(c.rY, 1) 122 | c.cycle(2, addrNext) 123 | case 0x4a: // lsr implied 124 | oldValue := c.builder.CreateLoad(c.rA, "") 125 | newValue := c.performLsr(oldValue) 126 | c.builder.CreateStore(newValue, c.rA) 127 | c.cycle(2, addrNext) 128 | case 0xea: // nop implied 129 | c.cycle(2, addrNext) 130 | case 0x48: // pha implied 131 | a := c.builder.CreateLoad(c.rA, "") 132 | c.pushToStack(a) 133 | c.cycle(3, addrNext) 134 | case 0x68: // pla implied 135 | v := c.pullFromStack() 136 | c.builder.CreateStore(v, c.rA) 137 | c.dynTestAndSetZero(v) 138 | c.dynTestAndSetNeg(v) 139 | c.cycle(4, addrNext) 140 | //case 0x08: // php implied 141 | case 0x28: // plp implied 142 | c.pullStatusReg() 143 | c.cycle(4, addrNext) 144 | case 0x2a: // rol implied 145 | a := c.builder.CreateLoad(c.rA, "") 146 | c.builder.CreateStore(c.performRol(a), c.rA) 147 | c.cycle(2, addrNext) 148 | case 0x6a: // ror implied 149 | a := c.builder.CreateLoad(c.rA, "") 150 | c.builder.CreateStore(c.performRor(a), c.rA) 151 | c.cycle(2, addrNext) 152 | case 0x40: // rti implied 153 | c.pullStatusReg() 154 | pc := c.pullWordFromStack() 155 | c.builder.CreateStore(pc, c.rPC) 156 | c.cycle(6, -1) // -1 because we already stored the PC 157 | c.builder.CreateRetVoid() 158 | c.currentBlock = nil 159 | case 0x60: // rts implied 160 | pc := c.pullWordFromStack() 161 | pc = c.builder.CreateAdd(pc, llvm.ConstInt(pc.Type(), 1, false), "") 162 | c.debugPrintf("rts: new pc $%04x\n", []llvm.Value{pc}) 163 | c.builder.CreateStore(pc, c.rPC) 164 | c.cycle(6, -1) 165 | c.builder.CreateBr(c.dynJumpBlock) 166 | c.currentBlock = nil 167 | case 0xf8: // sed implied 168 | c.setDec() 169 | c.cycle(2, addrNext) 170 | case 0x78: // sei implied 171 | c.setInt() 172 | c.cycle(2, addrNext) 173 | case 0xaa: // tax implied 174 | c.transfer(c.rA, c.rX) 175 | c.cycle(2, addrNext) 176 | case 0xa8: // tay implied 177 | c.transfer(c.rA, c.rY) 178 | c.cycle(2, addrNext) 179 | case 0xba: // tsx implied 180 | c.transfer(c.rSP, c.rX) 181 | c.cycle(2, addrNext) 182 | case 0x8a: // txa implied 183 | c.transfer(c.rX, c.rA) 184 | c.cycle(2, addrNext) 185 | case 0x9a: // txs implied 186 | // TXS does not set flags 187 | v := c.builder.CreateLoad(c.rX, "") 188 | c.builder.CreateStore(v, c.rSP) 189 | c.cycle(2, addrNext) 190 | case 0x98: // tya implied 191 | c.transfer(c.rY, c.rA) 192 | c.cycle(2, addrNext) 193 | 194 | case 0x79: // adc abs y 195 | v := c.dynLoadIndexed(i.Value, c.rY) 196 | c.performAdc(v) 197 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rY, addrNext) 198 | case 0xf9: // sbc abs y 199 | v := c.dynLoadIndexed(i.Value, c.rY) 200 | c.performSbc(v) 201 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rY, addrNext) 202 | case 0xd9: // cmp abs y 203 | reg := c.builder.CreateLoad(c.rA, "") 204 | mem := c.dynLoadIndexed(i.Value, c.rY) 205 | c.performCmp(reg, mem) 206 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 207 | case 0xdd: // cmp abs x 208 | reg := c.builder.CreateLoad(c.rA, "") 209 | mem := c.dynLoadIndexed(i.Value, c.rX) 210 | c.performCmp(reg, mem) 211 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 212 | case 0xd5: // cmp zpg x 213 | reg := c.builder.CreateLoad(c.rA, "") 214 | mem := c.dynLoadZpgIndexed(i.Value, c.rX) 215 | c.performCmp(reg, mem) 216 | c.cycle(4, addrNext) 217 | case 0xb9: // lda abs y 218 | c.absoluteIndexedLoad(c.rA, i.Value, c.rY, addrNext) 219 | case 0xbe: // ldx abs y 220 | c.absoluteIndexedLoad(c.rX, i.Value, c.rY, addrNext) 221 | case 0xbd: // lda abs x 222 | c.absoluteIndexedLoad(c.rA, i.Value, c.rX, addrNext) 223 | case 0xbc: // ldy abs x 224 | c.absoluteIndexedLoad(c.rY, i.Value, c.rX, addrNext) 225 | case 0x99: // sta abs y 226 | c.absoluteIndexedStore(c.rA, i.Value, c.rY, addrNext) 227 | case 0x9d: // sta abs x 228 | c.absoluteIndexedStore(c.rA, i.Value, c.rX, addrNext) 229 | case 0x96: // stx zpg y 230 | v := c.builder.CreateLoad(c.rX, "") 231 | c.dynStoreZpgIndexed(i.Value, c.rY, v) 232 | c.cycle(4, addrNext) 233 | case 0x95: // sta zpg x 234 | v := c.builder.CreateLoad(c.rA, "") 235 | c.dynStoreZpgIndexed(i.Value, c.rX, v) 236 | c.cycle(4, addrNext) 237 | case 0x94: // sty zpg x 238 | v := c.builder.CreateLoad(c.rY, "") 239 | c.dynStoreZpgIndexed(i.Value, c.rX, v) 240 | c.cycle(4, addrNext) 241 | case 0xb6: // ldx zpg y 242 | v := c.dynLoadZpgIndexed(i.Value, c.rY) 243 | c.builder.CreateStore(v, c.rX) 244 | c.dynTestAndSetZero(v) 245 | c.dynTestAndSetNeg(v) 246 | c.cycle(4, addrNext) 247 | case 0xb4: // ldy zpg x 248 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 249 | c.performLdy(v) 250 | c.cycle(4, addrNext) 251 | case 0xb5: // lda zpg x 252 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 253 | c.performLda(v) 254 | c.cycle(4, addrNext) 255 | case 0x7d: // adc abs x 256 | v := c.dynLoadIndexed(i.Value, c.rX) 257 | c.performAdc(v) 258 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 259 | case 0xfd: // sbc abs x 260 | v := c.dynLoadIndexed(i.Value, c.rX) 261 | c.performSbc(v) 262 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 263 | case 0x75: // adc zpg x 264 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 265 | c.performAdc(v) 266 | c.cycle(4, addrNext) 267 | case 0xf5: // sbc zpg x 268 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 269 | c.performSbc(v) 270 | c.cycle(4, addrNext) 271 | case 0x1e: // asl abs x 272 | oldValue := c.dynLoadIndexed(i.Value, c.rX) 273 | newValue := c.performAsl(oldValue) 274 | c.dynStoreIndexed(i.Value, c.rX, newValue) 275 | c.cycle(7, addrNext) 276 | case 0x16: // asl zpg x 277 | oldValue := c.dynLoadZpgIndexed(i.Value, c.rX) 278 | newValue := c.performAsl(oldValue) 279 | c.dynStoreZpgIndexed(i.Value, c.rX, newValue) 280 | c.cycle(6, addrNext) 281 | case 0xde: // dec abs x 282 | oldValue := c.dynLoadIndexed(i.Value, c.rX) 283 | newValue := c.incrementVal(oldValue, -1) 284 | c.dynStoreIndexed(i.Value, c.rX, newValue) 285 | c.dynTestAndSetZero(newValue) 286 | c.dynTestAndSetNeg(newValue) 287 | c.cycle(7, addrNext) 288 | case 0xfe: // inc abs x 289 | oldValue := c.dynLoadIndexed(i.Value, c.rX) 290 | newValue := c.incrementVal(oldValue, 1) 291 | c.dynStoreIndexed(i.Value, c.rX, newValue) 292 | c.dynTestAndSetZero(newValue) 293 | c.dynTestAndSetNeg(newValue) 294 | c.cycle(7, addrNext) 295 | case 0xd6: // dec zpg x 296 | oldValue := c.dynLoadZpgIndexed(i.Value, c.rX) 297 | newValue := c.incrementVal(oldValue, -1) 298 | c.dynStoreZpgIndexed(i.Value, c.rX, newValue) 299 | c.dynTestAndSetZero(newValue) 300 | c.dynTestAndSetNeg(newValue) 301 | c.cycle(6, addrNext) 302 | case 0xf6: // inc zpg x 303 | oldValue := c.dynLoadZpgIndexed(i.Value, c.rX) 304 | newValue := c.incrementVal(oldValue, 1) 305 | c.dynStoreZpgIndexed(i.Value, c.rX, newValue) 306 | c.dynTestAndSetZero(newValue) 307 | c.dynTestAndSetNeg(newValue) 308 | c.cycle(6, addrNext) 309 | case 0x3e: // rol abs x 310 | oldValue := c.dynLoadIndexed(i.Value, c.rX) 311 | newValue := c.performRol(oldValue) 312 | c.dynStoreIndexed(i.Value, c.rX, newValue) 313 | c.cycle(7, addrNext) 314 | case 0x7e: // ror abs x 315 | oldValue := c.dynLoadIndexed(i.Value, c.rX) 316 | newValue := c.performRor(oldValue) 317 | c.dynStoreIndexed(i.Value, c.rX, newValue) 318 | c.cycle(7, addrNext) 319 | case 0x39: // and abs y 320 | v := c.dynLoadIndexed(i.Value, c.rY) 321 | c.performAnd(v) 322 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rY, addrNext) 323 | case 0x3d: // and abs x 324 | v := c.dynLoadIndexed(i.Value, c.rX) 325 | c.performAnd(v) 326 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 327 | case 0x35: // and zpg x 328 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 329 | c.performAnd(v) 330 | c.cycle(4, addrNext) 331 | case 0x5d: // eor abs x 332 | v := c.dynLoadIndexed(i.Value, c.rX) 333 | c.performEor(v) 334 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 335 | case 0x55: // eor zpg x 336 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 337 | c.performEor(v) 338 | c.cycle(4, addrNext) 339 | case 0x59: // eor abs y 340 | v := c.dynLoadIndexed(i.Value, c.rY) 341 | c.performEor(v) 342 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rY, addrNext) 343 | case 0x19: // ora abs y 344 | v := c.dynLoadIndexed(i.Value, c.rY) 345 | c.performOra(v) 346 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rY, addrNext) 347 | case 0x1d: // ora abs x 348 | v := c.dynLoadIndexed(i.Value, c.rX) 349 | c.performOra(v) 350 | c.cyclesForAbsoluteIndexedPtr(i.Value, c.rX, addrNext) 351 | case 0x15: // ora zpg x 352 | v := c.dynLoadZpgIndexed(i.Value, c.rX) 353 | c.performOra(v) 354 | c.cycle(4, addrNext) 355 | //case 0x5e: // lsr abs x 356 | //case 0x56: // lsr zpg x 357 | //case 0x36: // rol zpg x 358 | //case 0x76: // ror zpg x 359 | 360 | case 0x6c: // jmp indirect 361 | newPc := c.loadWord(i.Value) 362 | c.builder.CreateStore(newPc, c.rPC) 363 | c.cycle(5, -1) 364 | c.builder.CreateBr(c.dynJumpBlock) 365 | c.currentBlock = nil 366 | case 0x4c: // jmp 367 | // branch instruction - cycle before execution 368 | c.cycle(3, labelAddr) 369 | destBlock, ok := c.labeledBlocks[i.LabelName] 370 | if ok { 371 | // cool, we're jumping into statically compiled code 372 | c.builder.CreateBr(destBlock) 373 | } else { 374 | // damn, we'll have to interpret the next instruction 375 | c.builder.CreateBr(c.interpretBlock) 376 | } 377 | c.currentBlock = nil 378 | case 0x20: // jsr 379 | pc := llvm.ConstInt(llvm.Int16Type(), uint64(i.Offset+2), false) 380 | 381 | c.debugPrintf("jsr: saving $%04x\n", []llvm.Value{pc}) 382 | 383 | c.pushWordToStack(pc) 384 | c.cycle(6, i.Value) 385 | destBlock, ok := c.labeledBlocks[i.LabelName] 386 | if ok { 387 | // cool, we're jumping into statically compiled code 388 | c.builder.CreateBr(destBlock) 389 | } else { 390 | // damn, we'll have to interpret the next instruction 391 | c.builder.CreateBr(c.interpretBlock) 392 | 393 | } 394 | c.currentBlock = nil 395 | case 0xf0: // beq 396 | isZero := c.builder.CreateLoad(c.rSZero, "") 397 | c.createBranch(isZero, i.LabelName, i.Offset) 398 | case 0x90: // bcc 399 | isCarry := c.builder.CreateLoad(c.rSCarry, "") 400 | notCarry := c.builder.CreateNot(isCarry, "") 401 | c.createBranch(notCarry, i.LabelName, i.Offset) 402 | case 0xb0: // bcs 403 | isCarry := c.builder.CreateLoad(c.rSCarry, "") 404 | c.createBranch(isCarry, i.LabelName, i.Offset) 405 | case 0x30: // bmi 406 | isNeg := c.builder.CreateLoad(c.rSNeg, "") 407 | c.createBranch(isNeg, i.LabelName, i.Offset) 408 | case 0xd0: // bne 409 | isZero := c.builder.CreateLoad(c.rSZero, "") 410 | notZero := c.builder.CreateNot(isZero, "") 411 | c.createBranch(notZero, i.LabelName, i.Offset) 412 | case 0x10: // bpl 413 | isNeg := c.builder.CreateLoad(c.rSNeg, "") 414 | notNeg := c.builder.CreateNot(isNeg, "") 415 | c.createBranch(notNeg, i.LabelName, i.Offset) 416 | //case 0x50: // bvc 417 | //case 0x70: // bvs 418 | 419 | case 0xa5: 420 | c.performLda(c.load(i.Value)) 421 | c.cycle(3, addrNext) 422 | case 0xad: 423 | c.performLda(c.load(i.Value)) 424 | c.cycle(4, addrNext) 425 | case 0xa4: // ldy zpg 426 | c.performLdy(c.load(i.Value)) 427 | c.cycle(3, addrNext) 428 | case 0xac: // ldy abs 429 | c.performLdy(c.load(i.Value)) 430 | c.cycle(4, addrNext) 431 | case 0xa6, 0xae: // ldx (zpg, abs) 432 | v := c.load(i.Value) 433 | c.builder.CreateStore(v, c.rX) 434 | c.dynTestAndSetZero(v) 435 | c.dynTestAndSetNeg(v) 436 | if i.OpCode == 0xa6 { 437 | c.cycle(3, addrNext) 438 | } else { 439 | c.cycle(4, addrNext) 440 | } 441 | case 0xc6: // dec zpg 442 | c.incrementMem(i.Value, -1) 443 | c.cycle(5, addrNext) 444 | case 0xce: // dec abs 445 | c.incrementMem(i.Value, -1) 446 | c.cycle(6, addrNext) 447 | case 0xe6: // inc zpg 448 | c.incrementMem(i.Value, 1) 449 | c.cycle(5, addrNext) 450 | case 0xee: // inc abs 451 | c.incrementMem(i.Value, 1) 452 | c.cycle(6, addrNext) 453 | case 0x46: // lsr zpg 454 | newValue := c.performLsr(c.load(i.Value)) 455 | c.store(i.Value, newValue) 456 | c.cycle(5, addrNext) 457 | case 0x4e: // lsr abs 458 | newValue := c.performLsr(c.load(i.Value)) 459 | c.store(i.Value, newValue) 460 | c.cycle(6, addrNext) 461 | case 0x45: // eor zpg 462 | c.performEor(c.load(i.Value)) 463 | c.cycle(3, addrNext) 464 | case 0x4d: // eor abs 465 | c.performEor(c.load(i.Value)) 466 | c.cycle(4, addrNext) 467 | case 0xc5: // cmp zpg 468 | reg := c.builder.CreateLoad(c.rA, "") 469 | c.performCmp(reg, c.load(i.Value)) 470 | c.cycle(3, addrNext) 471 | case 0xcd: // cmp abs 472 | reg := c.builder.CreateLoad(c.rA, "") 473 | c.performCmp(reg, c.load(i.Value)) 474 | c.cycle(4, addrNext) 475 | case 0xe4: // cpx zpg 476 | reg := c.builder.CreateLoad(c.rX, "") 477 | c.performCmp(reg, c.load(i.Value)) 478 | c.cycle(3, addrNext) 479 | case 0xc4: // cpy zpg 480 | reg := c.builder.CreateLoad(c.rY, "") 481 | c.performCmp(reg, c.load(i.Value)) 482 | c.cycle(3, addrNext) 483 | case 0xec: // cpx abs 484 | reg := c.builder.CreateLoad(c.rX, "") 485 | c.performCmp(reg, c.load(i.Value)) 486 | c.cycle(4, addrNext) 487 | case 0xcc: // cpy abs 488 | reg := c.builder.CreateLoad(c.rY, "") 489 | c.performCmp(reg, c.load(i.Value)) 490 | c.cycle(4, addrNext) 491 | case 0x65: // adc zpg 492 | c.performAdc(c.load(i.Value)) 493 | c.cycle(3, addrNext) 494 | case 0x6d: // adc abs 495 | c.performAdc(c.load(i.Value)) 496 | c.cycle(4, addrNext) 497 | case 0xe5: // sbc zpg 498 | c.performSbc(c.load(i.Value)) 499 | c.cycle(3, addrNext) 500 | case 0xed: // sbc abs 501 | c.performSbc(c.load(i.Value)) 502 | c.cycle(4, addrNext) 503 | case 0x05: // ora zpg 504 | c.performOra(c.load(i.Value)) 505 | c.cycle(3, addrNext) 506 | case 0x0d: // ora abs 507 | c.performOra(c.load(i.Value)) 508 | c.cycle(4, addrNext) 509 | case 0x25: // and zpg 510 | c.performAnd(c.load(i.Value)) 511 | c.cycle(3, addrNext) 512 | case 0x2d: // and abs 513 | c.performAnd(c.load(i.Value)) 514 | c.cycle(4, addrNext) 515 | case 0x24: // bit zpg 516 | c.performBit(c.load(i.Value)) 517 | c.cycle(3, addrNext) 518 | case 0x2c: // bit abs 519 | c.performBit(c.load(i.Value)) 520 | c.cycle(4, addrNext) 521 | case 0x06: // asl zpg 522 | oldValue := c.load(i.Value) 523 | newValue := c.performAsl(oldValue) 524 | c.store(i.Value, newValue) 525 | c.cycle(5, addrNext) 526 | case 0x0e: // asl abs 527 | oldValue := c.load(i.Value) 528 | newValue := c.performAsl(oldValue) 529 | c.store(i.Value, newValue) 530 | c.cycle(6, addrNext) 531 | case 0x26: // rol zpg 532 | oldValue := c.load(i.Value) 533 | newValue := c.performRol(oldValue) 534 | c.store(i.Value, newValue) 535 | c.cycle(5, addrNext) 536 | case 0x66: // ror zpg 537 | oldValue := c.load(i.Value) 538 | newValue := c.performRor(oldValue) 539 | c.store(i.Value, newValue) 540 | c.cycle(5, addrNext) 541 | case 0x2e: // rol abs 542 | oldValue := c.load(i.Value) 543 | newValue := c.performRol(oldValue) 544 | c.store(i.Value, newValue) 545 | c.cycle(6, addrNext) 546 | case 0x6e: // ror abs 547 | oldValue := c.load(i.Value) 548 | newValue := c.performRor(oldValue) 549 | c.store(i.Value, newValue) 550 | c.cycle(6, addrNext) 551 | case 0x85: // sta zpg 552 | c.store(i.Value, c.builder.CreateLoad(c.rA, "")) 553 | c.cycle(3, addrNext) 554 | case 0x8d: // sta abs 555 | c.store(i.Value, c.builder.CreateLoad(c.rA, "")) 556 | c.cycle(4, addrNext) 557 | case 0x86: // stx zpg 558 | c.store(i.Value, c.builder.CreateLoad(c.rX, "")) 559 | c.cycle(3, addrNext) 560 | case 0x8e: // stx abs 561 | c.store(i.Value, c.builder.CreateLoad(c.rX, "")) 562 | c.cycle(4, addrNext) 563 | case 0x84: // sty zpg 564 | c.store(i.Value, c.builder.CreateLoad(c.rY, "")) 565 | c.cycle(3, addrNext) 566 | case 0x8c: // sty abs 567 | c.store(i.Value, c.builder.CreateLoad(c.rY, "")) 568 | c.cycle(4, addrNext) 569 | 570 | case 0xa1: // lda indirect x 571 | index := c.builder.CreateLoad(c.rX, "") 572 | base := llvm.ConstInt(llvm.Int8Type(), uint64(i.Value), false) 573 | addr := c.builder.CreateAdd(base, index, "") 574 | v := c.dynLoad(addr, 0, 0xff) 575 | c.performLda(v) 576 | c.cycle(6, addrNext) 577 | //case 0x61: // adc indirect x 578 | //case 0x21: // and indirect x 579 | //case 0xc1: // cmp indirect x 580 | //case 0x41: // eor indirect x 581 | //case 0x01: // ora indirect x 582 | //case 0xe1: // sbc indirect x 583 | //case 0x81: // sta indirect x 584 | 585 | 586 | //case 0x71: // adc indirect y 587 | //case 0x31: // and indirect y 588 | //case 0xd1: // cmp indirect y 589 | //case 0x51: // eor indirect y 590 | case 0xb1: // lda indirect y 591 | baseAddr := c.loadWord(i.Value) 592 | rY := c.builder.CreateLoad(c.rY, "") 593 | rYw := c.builder.CreateZExt(rY, llvm.Int16Type(), "") 594 | addr := c.builder.CreateAdd(baseAddr, rYw, "") 595 | val := c.dynLoad(addr, 0, 0xffff) 596 | c.performLda(val) 597 | c.cyclesForIndirectY(baseAddr, addr, addrNext) 598 | //case 0x11: // ora indirect y 599 | //case 0xf1: // sbc indirect y 600 | case 0x91: // sta indirect y 601 | baseAddr := c.loadWord(i.Value) 602 | rY := c.builder.CreateLoad(c.rY, "") 603 | rYw := c.builder.CreateZExt(rY, llvm.Int16Type(), "") 604 | addr := c.builder.CreateAdd(baseAddr, rYw, "") 605 | rA := c.builder.CreateLoad(c.rA, "") 606 | c.dynStore(addr, 0, 0xffff, rA) 607 | c.cycle(6, addrNext) 608 | } 609 | } 610 | -------------------------------------------------------------------------------- /jamulator/disassembly.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "container/list" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | ) 14 | 15 | type Renderer interface { 16 | Render() string 17 | } 18 | 19 | type Disassembly struct { 20 | prog *Program 21 | offset int 22 | dynJumps []int 23 | jumpTables map[int]bool 24 | } 25 | 26 | func (d *Disassembly) elemAsByte(elem *list.Element) (byte, error) { 27 | if elem == nil { 28 | return 0, errors.New("not enough bytes for byte") 29 | } 30 | stmt, ok := elem.Value.(*DataStatement) 31 | if !ok { 32 | return 0, errors.New("already marked as instruction") 33 | } 34 | if stmt.dataList.Len() != 1 { 35 | return 0, errors.New("expected DataStatement of size 1") 36 | } 37 | intDataItem, ok := stmt.dataList.Front().Value.(*IntegerDataItem) 38 | if !ok { 39 | return 0, errors.New("expected integer data item") 40 | } 41 | b := byte(*intDataItem) 42 | return b, nil 43 | } 44 | 45 | func (d *Disassembly) elemAsWord(elem *list.Element) (uint16, error) { 46 | if elem == nil { 47 | return 0, errors.New("not enough bytes for word") 48 | } 49 | next := elem.Next() 50 | if next == nil { 51 | return 0, errors.New("not enough bytes for word") 52 | } 53 | 54 | b1, err := d.elemAsByte(elem) 55 | if err != nil { 56 | return 0, err 57 | } 58 | b2, err := d.elemAsByte(next) 59 | if err != nil { 60 | return 0, err 61 | } 62 | 63 | return binary.LittleEndian.Uint16([]byte{b1, b2}), nil 64 | } 65 | 66 | func (p *Program) getLabelAt(addr int, name string) (string, error) { 67 | elem := p.elemAtAddr(addr) 68 | if elem == nil { 69 | // cannot get/make label; there is already code there 70 | return "", errors.New("cannot insert a label mid-instruction") 71 | } 72 | stmt := p.elemLabelStmt(elem) 73 | if stmt != nil { 74 | return stmt.LabelName, nil 75 | } 76 | // put one there 77 | i := new(LabelStatement) 78 | i.LabelName = name 79 | if len(i.LabelName) == 0 { 80 | i.LabelName = fmt.Sprintf("Label_%04x", addr) 81 | } 82 | p.List.InsertBefore(i, elem) 83 | 84 | // save in the label map 85 | p.Labels[i.LabelName] = addr 86 | 87 | return i.LabelName, nil 88 | } 89 | 90 | func (d *Disassembly) removeElemAt(addr int) { 91 | elem := d.prog.elemAtAddr(addr) 92 | d.prog.List.Remove(elem) 93 | delete(d.prog.Offsets, addr) 94 | } 95 | 96 | func (d *Disassembly) isJumpTable(addr int) bool { 97 | isJmpTable, ok := d.jumpTables[addr] 98 | if ok { 99 | return isJmpTable 100 | } 101 | isJmpTable = d.detectJumpTable(addr) 102 | d.jumpTables[addr] = isJmpTable 103 | return isJmpTable 104 | } 105 | 106 | func (d *Disassembly) detectJumpTable(addr int) bool { 107 | const ( 108 | expectAsl = iota 109 | expectTay 110 | expectPlaA 111 | expectStaA 112 | expectPlaB 113 | expectStaB 114 | expectInyC 115 | expectLdaC 116 | expectStaC 117 | expectInYD 118 | expectLdaD 119 | expectStaD 120 | expectJmp 121 | ) 122 | state := expectAsl 123 | var memA, memC int 124 | for elem := d.prog.elemAtAddr(addr); elem != nil; elem = elem.Next() { 125 | switch state { 126 | case expectAsl: 127 | i, ok := elem.Value.(*Instruction) 128 | if !ok { 129 | return false 130 | } 131 | if i.OpCode != 0x0a { 132 | return false 133 | } 134 | state = expectTay 135 | case expectTay: 136 | i, ok := elem.Value.(*Instruction) 137 | if !ok { 138 | return false 139 | } 140 | if i.OpCode != 0xa8 { 141 | return false 142 | } 143 | state = expectPlaA 144 | case expectPlaA: 145 | i, ok := elem.Value.(*Instruction) 146 | if !ok { 147 | return false 148 | } 149 | if i.OpCode != 0x68 { 150 | return false 151 | } 152 | state = expectStaA 153 | case expectStaA: 154 | i, ok := elem.Value.(*Instruction) 155 | if !ok { 156 | return false 157 | } 158 | if i.OpCode != 0x85 && i.OpCode != 0x8d { 159 | return false 160 | } 161 | memA = i.Value 162 | state = expectPlaB 163 | case expectPlaB: 164 | i, ok := elem.Value.(*Instruction) 165 | if !ok { 166 | return false 167 | } 168 | if i.OpCode != 0x68 { 169 | return false 170 | } 171 | state = expectStaB 172 | case expectStaB: 173 | i, ok := elem.Value.(*Instruction) 174 | if !ok { 175 | return false 176 | } 177 | if i.OpCode != 0x85 && i.OpCode != 0x8d { 178 | return false 179 | } 180 | if i.Value != memA+1 { 181 | return false 182 | } 183 | state = expectInyC 184 | case expectInyC: 185 | i, ok := elem.Value.(*Instruction) 186 | if !ok { 187 | return false 188 | } 189 | if i.OpCode != 0xc8 { 190 | return false 191 | } 192 | state = expectLdaC 193 | case expectLdaC: 194 | i, ok := elem.Value.(*Instruction) 195 | if !ok { 196 | return false 197 | } 198 | if i.OpCode != 0xb1 { 199 | return false 200 | } 201 | if i.Value != memA { 202 | return false 203 | } 204 | state = expectStaC 205 | case expectStaC: 206 | i, ok := elem.Value.(*Instruction) 207 | if !ok { 208 | return false 209 | } 210 | if i.OpCode != 0x85 && i.OpCode != 0x8d { 211 | return false 212 | } 213 | memC = i.Value 214 | state = expectInYD 215 | case expectInYD: 216 | i, ok := elem.Value.(*Instruction) 217 | if !ok { 218 | return false 219 | } 220 | if i.OpCode != 0xc8 { 221 | return false 222 | } 223 | state = expectLdaD 224 | case expectLdaD: 225 | i, ok := elem.Value.(*Instruction) 226 | if !ok { 227 | return false 228 | } 229 | if i.OpCode != 0xb1 { 230 | return false 231 | } 232 | if i.Value != memA { 233 | return false 234 | } 235 | state = expectStaD 236 | case expectStaD: 237 | i, ok := elem.Value.(*Instruction) 238 | if !ok { 239 | return false 240 | } 241 | if i.OpCode != 0x85 && i.OpCode != 0x8d { 242 | return false 243 | } 244 | if i.Value != memC+1 { 245 | return false 246 | } 247 | state = expectJmp 248 | case expectJmp: 249 | i, ok := elem.Value.(*Instruction) 250 | if !ok { 251 | return false 252 | } 253 | if i.OpCode != 0x6c { 254 | return false 255 | } 256 | if i.Value != memC { 257 | return false 258 | } 259 | return true 260 | } 261 | } 262 | return false 263 | } 264 | 265 | func (d *Disassembly) markAsInstruction(addr int) error { 266 | if addr < 0x8000 { 267 | // non-ROM address. nothing we can do 268 | return nil 269 | } 270 | elem := d.prog.elemAtAddr(addr) 271 | opCode, err := d.elemAsByte(elem) 272 | if err != nil { 273 | // already decoded as instruction 274 | return nil 275 | } 276 | i := new(Instruction) 277 | opCodeInfo := opCodeDataMap[opCode] 278 | i.OpName = opCodeInfo.opName 279 | i.OpCode = opCode 280 | i.Offset = addr 281 | switch opCodeInfo.addrMode { 282 | case nilAddr: 283 | return errors.New("cannot disassemble as instruction: bad op code") 284 | case absAddr: 285 | // convert data statements into instruction statement 286 | w, err := d.elemAsWord(elem.Next()) 287 | if err != nil { 288 | return err 289 | } 290 | i.Value = int(w) 291 | i.Payload = []byte{opCode, 0, 0} 292 | binary.LittleEndian.PutUint16(i.Payload[1:], w) 293 | i.LabelName, err = d.prog.getLabelAt(i.Value, "") 294 | if err == nil { 295 | i.Type = DirectWithLabelInstruction 296 | } else { 297 | i.Type = DirectInstruction 298 | } 299 | elem.Value = i 300 | 301 | d.removeElemAt(addr + 1) 302 | d.removeElemAt(addr + 2) 303 | 304 | switch opCode { 305 | case 0x4c: // jmp 306 | d.markAsInstruction(i.Value) 307 | case 0x20: // jsr 308 | d.markAsInstruction(i.Value) 309 | if d.isJumpTable(i.Value) { 310 | // mark this and remember to come back later 311 | d.dynJumps = append(d.dynJumps, addr+3) 312 | } else { 313 | d.markAsInstruction(addr + 3) 314 | } 315 | default: 316 | d.markAsInstruction(addr + 3) 317 | } 318 | case absXAddr, absYAddr: 319 | w, err := d.elemAsWord(elem.Next()) 320 | if err != nil { 321 | return err 322 | } 323 | if opCodeInfo.addrMode == absYAddr { 324 | i.RegisterName = "Y" 325 | } else { 326 | i.RegisterName = "X" 327 | } 328 | i.Value = int(w) 329 | i.Payload = []byte{opCode, 0, 0} 330 | binary.LittleEndian.PutUint16(i.Payload[1:], w) 331 | i.LabelName, err = d.prog.getLabelAt(i.Value, "") 332 | if err == nil { 333 | i.Type = DirectWithLabelIndexedInstruction 334 | } else { 335 | i.Type = DirectIndexedInstruction 336 | } 337 | elem.Value = i 338 | 339 | d.removeElemAt(addr + 1) 340 | d.removeElemAt(addr + 2) 341 | 342 | // next thing is definitely an instruction 343 | d.markAsInstruction(addr + 3) 344 | case immedAddr: 345 | v, err := d.elemAsByte(elem.Next()) 346 | if err != nil { 347 | return err 348 | } 349 | i.Type = ImmediateInstruction 350 | i.Value = int(v) 351 | i.Payload = []byte{opCode, v} 352 | elem.Value = i 353 | 354 | d.removeElemAt(addr + 1) 355 | 356 | // next thing is definitely an instruction 357 | d.markAsInstruction(addr + 2) 358 | case impliedAddr: 359 | i.Type = ImpliedInstruction 360 | i.Payload = []byte{opCode} 361 | elem.Value = i 362 | 363 | switch opCode { 364 | case 0x40: // RTI 365 | case 0x60: // RTS 366 | case 0x00: // BRK 367 | default: 368 | // next thing is definitely an instruction 369 | d.markAsInstruction(addr + 1) 370 | } 371 | case indirectAddr: 372 | // note: only JMP uses this 373 | w, err := d.elemAsWord(elem.Next()) 374 | if err != nil { 375 | return err 376 | } 377 | i.Type = IndirectInstruction 378 | i.Payload = []byte{opCode, 0, 0} 379 | i.Value = int(w) 380 | binary.LittleEndian.PutUint16(i.Payload[1:], w) 381 | elem.Value = i 382 | 383 | d.removeElemAt(addr + 1) 384 | d.removeElemAt(addr + 2) 385 | 386 | if opCode == 0x6c { 387 | // JMP 388 | } else { 389 | // next thing is definitely an instruction 390 | d.markAsInstruction(addr + 3) 391 | } 392 | case xIndexIndirectAddr: 393 | v, err := d.elemAsByte(elem.Next()) 394 | if err != nil { 395 | return err 396 | } 397 | i.Type = IndirectXInstruction 398 | i.Payload = []byte{opCode, v} 399 | i.Value = int(v) 400 | elem.Value = i 401 | 402 | d.removeElemAt(addr + 1) 403 | 404 | // next thing is definitely an instruction 405 | d.markAsInstruction(addr + 2) 406 | case indirectYIndexAddr: 407 | v, err := d.elemAsByte(elem.Next()) 408 | if err != nil { 409 | return err 410 | } 411 | i.Type = IndirectYInstruction 412 | i.Value = int(v) 413 | i.Payload = []byte{opCode, v} 414 | elem.Value = i 415 | 416 | d.removeElemAt(addr + 1) 417 | 418 | // next thing is definitely an instruction 419 | d.markAsInstruction(addr + 2) 420 | case relativeAddr: 421 | v, err := d.elemAsByte(elem.Next()) 422 | if err != nil { 423 | return err 424 | } 425 | i.Type = DirectWithLabelInstruction 426 | i.Value = addr + 2 + int(int8(v)) 427 | i.Payload = []byte{opCode, v} 428 | i.LabelName, err = d.prog.getLabelAt(i.Value, "") 429 | if err != nil { 430 | panic(err) 431 | } 432 | elem.Value = i 433 | 434 | d.removeElemAt(addr + 1) 435 | 436 | // mark both targets of the branch as instructions 437 | d.markAsInstruction(addr + 2) 438 | d.markAsInstruction(i.Value) 439 | case zeroPageAddr: 440 | v, err := d.elemAsByte(elem.Next()) 441 | if err != nil { 442 | return err 443 | } 444 | i.Type = DirectInstruction 445 | i.Payload = []byte{opCode, v} 446 | i.Value = int(v) 447 | elem.Value = i 448 | 449 | d.removeElemAt(addr + 1) 450 | 451 | // next thing is definitely an instruction 452 | d.markAsInstruction(addr + 2) 453 | case zeroXIndexAddr, zeroYIndexAddr: 454 | if opCodeInfo.addrMode == zeroYIndexAddr { 455 | i.RegisterName = "Y" 456 | } else { 457 | i.RegisterName = "X" 458 | } 459 | v, err := d.elemAsByte(elem.Next()) 460 | if err != nil { 461 | return err 462 | } 463 | i.Type = DirectIndexedInstruction 464 | i.Payload = []byte{opCode, v} 465 | i.Value = int(v) 466 | i.RegisterName = i.RegisterName 467 | elem.Value = i 468 | 469 | d.removeElemAt(addr + 1) 470 | 471 | // next thing is definitely an instruction 472 | d.markAsInstruction(addr + 2) 473 | } 474 | return nil 475 | } 476 | 477 | func (d *Disassembly) ToProgram() *Program { 478 | // add the org statement 479 | orgStatement := new(OrgPseudoOp) 480 | orgStatement.Fill = 0xff // this is the default; causes it to be left off when rendered 481 | orgStatement.Value = d.offset 482 | d.prog.List.PushFront(orgStatement) 483 | 484 | return d.prog 485 | } 486 | 487 | func (d *Disassembly) readAllAsData() { 488 | d.offset = 0x10000 - 0x4000*len(d.prog.PrgRom) 489 | offset := d.offset 490 | for _, bank := range d.prog.PrgRom { 491 | for _, b := range bank { 492 | stmt := new(DataStatement) 493 | stmt.Type = ByteDataStmt 494 | stmt.dataList = list.New() 495 | item := IntegerDataItem(b) 496 | stmt.dataList.PushBack(&item) 497 | stmt.Offset = offset 498 | stmt.Payload = []byte{b} 499 | d.prog.Offsets[stmt.Offset] = d.prog.List.PushBack(stmt) 500 | offset += 1 501 | } 502 | } 503 | } 504 | 505 | func (p *Program) elemAtAddr(addr int) *list.Element { 506 | elem, ok := p.Offsets[addr] 507 | if ok { 508 | return elem 509 | } 510 | // if there is only 1 prg rom bank, it is at 0x8000 and mirrored at 0xc000 511 | if len(p.PrgRom) == 1 && addr < 0xc000 { 512 | return p.Offsets[addr+0x4000] 513 | } 514 | return nil 515 | } 516 | 517 | func (d *Disassembly) markAsDataWordLabel(addr int, suggestedName string) error { 518 | elem1 := d.prog.elemAtAddr(addr) 519 | elem2 := elem1.Next() 520 | s1 := elem1.Value.(*DataStatement) 521 | s2 := elem2.Value.(*DataStatement) 522 | if s1.dataList.Len() != 1 { 523 | return errors.New("expected DataList len 1") 524 | } 525 | if s2.dataList.Len() != 1 { 526 | return errors.New("expected DataList len 1") 527 | } 528 | n1 := s1.dataList.Front().Value.(*IntegerDataItem) 529 | n2 := s2.dataList.Front().Value.(*IntegerDataItem) 530 | 531 | newStmt := &DataStatement{ 532 | Type: WordDataStmt, 533 | Offset: addr, 534 | Payload: []byte{byte(*n1), byte(*n2)}, 535 | dataList: list.New(), 536 | } 537 | targetAddr := int(binary.LittleEndian.Uint16(newStmt.Payload)) 538 | 539 | 540 | elem1.Value = newStmt 541 | d.removeElemAt(addr + 1) 542 | 543 | if targetAddr < 0x8000 { 544 | // target not in PRG ROM 545 | tmp := IntegerDataItem(targetAddr) 546 | newStmt.dataList.PushBack(&tmp) 547 | return nil 548 | } 549 | 550 | // target in PRG ROM 551 | 552 | err := d.markAsInstruction(targetAddr) 553 | if err != nil { 554 | tmp := IntegerDataItem(targetAddr) 555 | newStmt.dataList.PushBack(&tmp) 556 | return nil 557 | } 558 | 559 | labelName, err := d.prog.getLabelAt(targetAddr, suggestedName) 560 | if err != nil { 561 | tmp := IntegerDataItem(targetAddr) 562 | newStmt.dataList.PushBack(&tmp) 563 | return nil 564 | } 565 | newStmt.dataList.PushBack(&LabelCall{labelName}) 566 | 567 | return nil 568 | } 569 | 570 | func (d *Disassembly) collapseDataStatements() { 571 | if d.prog.List.Len() < 2 { 572 | return 573 | } 574 | const MAX_DATA_LIST_LEN = 8 575 | for e := d.prog.List.Front().Next(); e != nil; e = e.Next() { 576 | dataStmt, ok := e.Value.(*DataStatement) 577 | if !ok || dataStmt.Type != ByteDataStmt { 578 | continue 579 | } 580 | prev, ok := e.Prev().Value.(*DataStatement) 581 | if !ok { 582 | continue 583 | } 584 | if prev.dataList.Len()+dataStmt.dataList.Len() > MAX_DATA_LIST_LEN { 585 | continue 586 | } 587 | for de := dataStmt.dataList.Front(); de != nil; de = de.Next() { 588 | prev.dataList.PushBack(de.Value) 589 | } 590 | elToDel := e 591 | e = e.Prev() 592 | d.prog.List.Remove(elToDel) 593 | 594 | } 595 | } 596 | 597 | func allAscii(dl *list.List) bool { 598 | for e := dl.Front(); e != nil; e = e.Next() { 599 | switch t := e.Value.(type) { 600 | case *IntegerDataItem: 601 | if *t < 32 || *t > 126 || *t == '"' { 602 | return false 603 | } 604 | case *StringDataItem: 605 | // nothing to do 606 | default: 607 | panic(fmt.Sprintf("unrecognized data list item: %T", e.Value)) 608 | } 609 | } 610 | return true 611 | } 612 | 613 | func dataListToStr(dl *list.List) string { 614 | str := "" 615 | for e := dl.Front(); e != nil; e = e.Next() { 616 | switch t := e.Value.(type) { 617 | case *IntegerDataItem: 618 | str += string(*t) 619 | case *StringDataItem: 620 | str += string(*t) 621 | default: 622 | panic("unknown data item type") 623 | } 624 | } 625 | return str 626 | } 627 | 628 | const orgMinRepeatAmt = 64 629 | 630 | type orgIdentifier struct { 631 | repeatingByte byte 632 | firstElem *list.Element 633 | repeatCount int 634 | dis *Disassembly 635 | } 636 | 637 | func (oi *orgIdentifier) stop(e *list.Element) { 638 | if oi.repeatCount > orgMinRepeatAmt { 639 | firstOffset := oi.firstElem.Value.(*DataStatement).Offset 640 | for i := 0; i < oi.repeatCount; i++ { 641 | delItem := oi.firstElem 642 | oi.firstElem = oi.firstElem.Next() 643 | oi.dis.prog.List.Remove(delItem) 644 | } 645 | orgStmt := new(OrgPseudoOp) 646 | orgStmt.Value = firstOffset + oi.repeatCount 647 | orgStmt.Fill = oi.repeatingByte 648 | oi.dis.prog.List.InsertBefore(orgStmt, e) 649 | } 650 | oi.repeatCount = 0 651 | } 652 | 653 | func (oi *orgIdentifier) start(e *list.Element, b byte) { 654 | oi.firstElem = e 655 | oi.repeatingByte = b 656 | oi.repeatCount = 1 657 | } 658 | 659 | func (oi *orgIdentifier) gotByte(e *list.Element, b byte) { 660 | if oi.repeatCount == 0 { 661 | oi.start(e, b) 662 | } else if b == oi.repeatingByte { 663 | oi.repeatCount += 1 664 | } else { 665 | oi.stop(e) 666 | oi.start(e, b) 667 | } 668 | } 669 | 670 | func (d *Disassembly) identifyOrgs() { 671 | // if a byte repeats enough, use an org statement 672 | if d.prog.List.Len() < orgMinRepeatAmt { 673 | return 674 | } 675 | orgIdent := new(orgIdentifier) 676 | orgIdent.dis = d 677 | for e := d.prog.List.Front().Next(); e != nil; e = e.Next() { 678 | dataStmt, ok := e.Value.(*DataStatement) 679 | if !ok || dataStmt.dataList.Len() != 1 { 680 | orgIdent.stop(e) 681 | continue 682 | } 683 | v, ok := dataStmt.dataList.Front().Value.(*IntegerDataItem) 684 | if !ok { 685 | orgIdent.stop(e) 686 | continue 687 | } 688 | orgIdent.gotByte(e, byte(*v)) 689 | } 690 | } 691 | 692 | func (d *Disassembly) groupAsciiStrings() { 693 | const threshold = 5 694 | if d.prog.List.Len() < threshold { 695 | return 696 | } 697 | e := d.prog.List.Front() 698 | first := e 699 | buf := new(bytes.Buffer) 700 | for e != nil { 701 | dataStmt, ok := e.Value.(*DataStatement) 702 | if !ok || dataStmt.Type != ByteDataStmt || !allAscii(dataStmt.dataList) { 703 | if buf.Len() >= threshold { 704 | firstStmt := first.Value.(*DataStatement) 705 | firstStmt.dataList = list.New() 706 | tmp := StringDataItem(buf.String()) 707 | firstStmt.dataList.PushBack(&tmp) 708 | for { 709 | elToDel := first.Next() 710 | if elToDel == e { 711 | break 712 | } 713 | d.prog.List.Remove(elToDel) 714 | } 715 | } 716 | buf = new(bytes.Buffer) 717 | e = e.Next() 718 | first = e 719 | continue 720 | } 721 | buf.WriteString(dataListToStr(dataStmt.dataList)) 722 | e = e.Next() 723 | } 724 | } 725 | 726 | func (p *Program) elemLabelStmt(elem *list.Element) *LabelStatement { 727 | if elem == nil { 728 | return nil 729 | } 730 | stmt, ok := elem.Value.(*LabelStatement) 731 | if ok { 732 | return stmt 733 | } 734 | prev := elem.Prev() 735 | if prev != nil { 736 | stmt, ok = prev.Value.(*LabelStatement) 737 | if ok { 738 | return stmt 739 | } 740 | } 741 | return nil 742 | } 743 | 744 | func (d *Disassembly) resolveDynJumpCases() { 745 | // this function is recursive, since calling markAsDataWordLabel can 746 | // append more dynJumps 747 | if len(d.dynJumps) == 0 { 748 | return 749 | } 750 | // use the last item in the dynJumps list, and check a single address 751 | dynJumpAddr := d.dynJumps[len(d.dynJumps)-1] 752 | elem := d.prog.elemAtAddr(dynJumpAddr) 753 | if d.prog.elemLabelStmt(elem) != nil { 754 | // this dynJump has been exhausted. remove it from the list 755 | d.dynJumps = d.dynJumps[0 : len(d.dynJumps)-1] 756 | d.resolveDynJumpCases() 757 | return 758 | } 759 | _, err := d.elemAsWord(elem) 760 | if err != nil { 761 | // this dynJump has been exhausted. remove it from the list 762 | d.dynJumps = d.dynJumps[0 : len(d.dynJumps)-1] 763 | d.resolveDynJumpCases() 764 | return 765 | } 766 | stmt := elem.Value.(*DataStatement) 767 | // update the address to the next possible jump point 768 | d.dynJumps[len(d.dynJumps)-1] = dynJumpAddr + 2 769 | d.markAsDataWordLabel(stmt.Offset, "") 770 | d.resolveDynJumpCases() 771 | } 772 | 773 | func (r *Rom) Disassemble() (*Program, error) { 774 | if len(r.PrgRom) != 1 && len(r.PrgRom) != 2 { 775 | return nil, errors.New("only 1 or 2 prg rom banks supported") 776 | } 777 | 778 | dis := new(Disassembly) 779 | dis.jumpTables = make(map[int]bool) 780 | dis.prog = new(Program) 781 | dis.prog.List = list.New() 782 | dis.prog.Offsets = make(map[int]*list.Element) 783 | dis.prog.Labels = make(map[string]int) 784 | dis.prog.ChrRom = r.ChrRom 785 | dis.prog.PrgRom = r.PrgRom 786 | 787 | dis.readAllAsData() 788 | 789 | // use the known entry points to recursively disassemble data statements 790 | dis.markAsDataWordLabel(0xfffa, "NMI_Routine") 791 | dis.markAsDataWordLabel(0xfffc, "Reset_Routine") 792 | dis.markAsDataWordLabel(0xfffe, "IRQ_Routine") 793 | 794 | // go over the dynamic jumps that we found and mark the options as labels 795 | dis.resolveDynJumpCases() 796 | 797 | dis.identifyOrgs() 798 | dis.groupAsciiStrings() 799 | dis.collapseDataStatements() 800 | 801 | p := dis.ToProgram() 802 | p.ChrRom = r.ChrRom 803 | p.PrgRom = r.PrgRom 804 | p.Mirroring = r.Mirroring 805 | 806 | return p, nil 807 | } 808 | 809 | func Disassemble(reader io.Reader) (*Program, error) { 810 | r := new(Rom) 811 | bank, err := ioutil.ReadAll(reader) 812 | if err != nil { 813 | return nil, err 814 | } 815 | r.PrgRom = append(r.PrgRom, bank) 816 | return r.Disassemble() 817 | } 818 | 819 | func DisassembleFile(filename string) (*Program, error) { 820 | fd, err := os.Open(filename) 821 | if err != nil { 822 | return nil, err 823 | } 824 | 825 | p, err := Disassemble(fd) 826 | err2 := fd.Close() 827 | if err != nil { 828 | return nil, err 829 | } 830 | if err2 != nil { 831 | return nil, err2 832 | } 833 | 834 | return p, nil 835 | } 836 | 837 | func (i *Instruction) Render() string { 838 | switch i.Type { 839 | case ImmediateInstruction: 840 | return fmt.Sprintf("%s #$%02x", i.OpName, i.Value) 841 | case ImpliedInstruction: 842 | return i.OpName 843 | case DirectInstruction: 844 | if opCodeDataMap[i.OpCode].addrMode == zeroPageAddr { 845 | return fmt.Sprintf("%s $%02x", i.OpName, i.Value) 846 | } 847 | return fmt.Sprintf("%s $%04x", i.OpName, i.Value) 848 | case DirectWithLabelInstruction: 849 | return fmt.Sprintf("%s %s", i.OpName, i.LabelName) 850 | case DirectIndexedInstruction: 851 | addrMode := opCodeDataMap[i.OpCode].addrMode 852 | if addrMode == zeroXIndexAddr || addrMode == zeroYIndexAddr { 853 | return fmt.Sprintf("%s $%02x, %s", i.OpName, i.Value, i.RegisterName) 854 | } 855 | return fmt.Sprintf("%s $%04x, %s", i.OpName, i.Value, i.RegisterName) 856 | case DirectWithLabelIndexedInstruction: 857 | return fmt.Sprintf("%s %s, %s", i.OpName, i.LabelName, i.RegisterName) 858 | case IndirectInstruction: 859 | return fmt.Sprintf("%s ($%04x)", i.OpName, i.Value) 860 | case IndirectXInstruction: 861 | return fmt.Sprintf("%s ($%02x, X)", i.OpName, i.Value) 862 | case IndirectYInstruction: 863 | return fmt.Sprintf("%s ($%02x), Y", i.OpName, i.Value) 864 | } 865 | panic("unexpected Instruction Type") 866 | } 867 | 868 | func (i *OrgPseudoOp) Render() string { 869 | if i.Fill == 0xff { 870 | return fmt.Sprintf(".org $%04x", i.Value) 871 | } 872 | return fmt.Sprintf(".org $%04x, $%02x", i.Value, i.Fill) 873 | } 874 | 875 | func (s *DataStatement) Render() string { 876 | buf := new(bytes.Buffer) 877 | switch s.Type { 878 | default: panic("unexpected DataStatement Type") 879 | case ByteDataStmt: 880 | buf.WriteString(".db ") 881 | case WordDataStmt: 882 | buf.WriteString(".dw ") 883 | } 884 | for e := s.dataList.Front(); e != nil; e = e.Next() { 885 | switch t := e.Value.(type) { 886 | case *LabelCall: 887 | buf.WriteString(t.LabelName) 888 | case *StringDataItem: 889 | buf.WriteString("\"") 890 | buf.WriteString(string(*t)) 891 | buf.WriteString("\"") 892 | case *IntegerDataItem: 893 | switch s.Type { 894 | default: panic("unexpected DataStatement Type") 895 | case ByteDataStmt: 896 | buf.WriteString(fmt.Sprintf("$%02x", int(*t))) 897 | case WordDataStmt: 898 | buf.WriteString(fmt.Sprintf("$%04x", int(*t))) 899 | } 900 | } 901 | if e != s.dataList.Back() { 902 | buf.WriteString(", ") 903 | } 904 | } 905 | return buf.String() 906 | } 907 | 908 | func (s *LabelStatement) Render() string { 909 | return fmt.Sprintf("%s:", s.LabelName) 910 | } 911 | 912 | func (p *Program) WriteSource(writer io.Writer) (err error) { 913 | w := bufio.NewWriter(writer) 914 | 915 | for e := p.List.Front(); e != nil; e = e.Next() { 916 | switch t := e.Value.(type) { 917 | default: 918 | panic(fmt.Sprintf("unrecognized node: %T", e.Value)) 919 | case *Instruction: 920 | _, err = w.WriteString(" ") 921 | _, err = w.WriteString(t.Render()) 922 | _, err = w.WriteString("\n") 923 | case *LabelStatement: 924 | _, err = w.WriteString(t.Render()) 925 | _, err = w.WriteString("\n") 926 | case *DataStatement: 927 | _, err = w.WriteString(" ") 928 | _, err = w.WriteString(t.Render()) 929 | _, err = w.WriteString("\n") 930 | case *OrgPseudoOp: 931 | _, err = w.WriteString(t.Render()) 932 | _, err = w.WriteString("\n") 933 | } 934 | } 935 | 936 | w.Flush() 937 | return 938 | } 939 | 940 | func (p *Program) WriteSourceFile(filename string) error { 941 | fd, err := os.Create(filename) 942 | if err != nil { 943 | return err 944 | } 945 | err = p.WriteSource(fd) 946 | err2 := fd.Close() 947 | if err != nil { 948 | return err 949 | } 950 | if err2 != nil { 951 | return err2 952 | } 953 | return nil 954 | } 955 | -------------------------------------------------------------------------------- /jamulator/interpret.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "github.com/axw/gollvm/llvm" 5 | "fmt" 6 | ) 7 | 8 | var interpretOps = [256]func(*Compilation) { 9 | // 0x00 10 | nil, 11 | nil, 12 | nil, 13 | nil, 14 | nil, 15 | nil, 16 | nil, 17 | nil, 18 | nil, 19 | nil, 20 | func (c *Compilation) { 21 | // 0x0a asl implied 22 | c.debugPrintf("asl\n", []llvm.Value{}) 23 | a := c.builder.CreateLoad(c.rA, "") 24 | c.builder.CreateStore(c.performAsl(a), c.rA) 25 | c.cycle(2, -1) 26 | }, 27 | nil, 28 | nil, 29 | nil, 30 | nil, 31 | nil, 32 | // 0x10 33 | nil, 34 | nil, 35 | nil, 36 | nil, 37 | nil, 38 | nil, 39 | nil, 40 | nil, 41 | nil, 42 | nil, 43 | nil, 44 | nil, 45 | nil, 46 | nil, 47 | nil, 48 | nil, 49 | // 0x20 50 | func (c *Compilation) { 51 | // 0x20 jsr abs 52 | newPc := c.interpAbsAddr() 53 | c.debugPrintf("jsr $%04x\n", []llvm.Value{newPc}) 54 | 55 | pc := c.builder.CreateLoad(c.rPC, "") 56 | pcMinusOne := c.builder.CreateSub(pc, llvm.ConstInt(pc.Type(), 1, false), "") 57 | 58 | c.debugPrintf("jsr: saving $%04x\n", []llvm.Value{pcMinusOne}) 59 | 60 | c.pushWordToStack(pcMinusOne) 61 | c.builder.CreateStore(newPc, c.rPC) 62 | c.cycle(6, -1) 63 | }, 64 | nil, 65 | nil, 66 | nil, 67 | nil, 68 | nil, 69 | nil, 70 | nil, 71 | nil, 72 | nil, 73 | nil, 74 | nil, 75 | func (c *Compilation) { 76 | // 0x2c bit abs 77 | addr := c.interpAbsAddr() 78 | c.debugPrintf("bit $%04x\n", []llvm.Value{addr}) 79 | c.performBit(c.dynLoad(addr, 0, 0xffff)) 80 | c.cycle(4, -1) 81 | }, 82 | nil, 83 | nil, 84 | nil, 85 | // 0x30 86 | nil, 87 | nil, 88 | nil, 89 | nil, 90 | nil, 91 | nil, 92 | nil, 93 | nil, 94 | nil, 95 | nil, 96 | nil, 97 | nil, 98 | nil, 99 | nil, 100 | nil, 101 | nil, 102 | // 0x40 103 | nil, 104 | nil, 105 | nil, 106 | nil, 107 | nil, 108 | nil, 109 | nil, 110 | nil, 111 | func (c *Compilation) { 112 | // 0x48 pha implied 113 | c.debugPrintf("pha\n", []llvm.Value{}) 114 | a := c.builder.CreateLoad(c.rA, "") 115 | c.pushToStack(a) 116 | c.cycle(3, -1) 117 | }, 118 | nil, 119 | nil, 120 | nil, 121 | nil, 122 | nil, 123 | nil, 124 | nil, 125 | // 0x50 126 | nil, 127 | nil, 128 | nil, 129 | nil, 130 | nil, 131 | nil, 132 | nil, 133 | nil, 134 | nil, 135 | nil, 136 | nil, 137 | nil, 138 | nil, 139 | nil, 140 | nil, 141 | nil, 142 | // 0x60 143 | nil, 144 | nil, 145 | nil, 146 | nil, 147 | nil, 148 | nil, 149 | nil, 150 | nil, 151 | nil, 152 | nil, 153 | nil, 154 | nil, 155 | nil, 156 | nil, 157 | nil, 158 | nil, 159 | // 0x70 160 | nil, 161 | nil, 162 | nil, 163 | nil, 164 | nil, 165 | nil, 166 | nil, 167 | nil, 168 | nil, 169 | nil, 170 | nil, 171 | nil, 172 | nil, 173 | nil, 174 | nil, 175 | nil, 176 | // 0x80 177 | nil, 178 | nil, 179 | nil, 180 | nil, 181 | nil, 182 | func (c *Compilation) { 183 | // 0x85 sta zpg 184 | addr := c.interpZpgAddr() 185 | c.debugPrintf("sta $%02x\n", []llvm.Value{addr}) 186 | c.dynStore(addr, 0, 0xff, c.builder.CreateLoad(c.rA, "")) 187 | c.cycle(3, -1) 188 | }, 189 | nil, 190 | nil, 191 | nil, 192 | nil, 193 | nil, 194 | nil, 195 | nil, 196 | func (c *Compilation) { 197 | // 0x8d sta abs 198 | v := c.interpAbsAddr() 199 | c.debugPrintf("sta $%04x\n", []llvm.Value{v}) 200 | c.dynStore(v, 0, 0xffff, c.builder.CreateLoad(c.rA, "")) 201 | c.cycle(4, -1) 202 | }, 203 | nil, 204 | nil, 205 | // 0x90 206 | nil, 207 | nil, 208 | nil, 209 | nil, 210 | nil, 211 | nil, 212 | nil, 213 | nil, 214 | nil, 215 | nil, 216 | nil, 217 | nil, 218 | nil, 219 | nil, 220 | nil, 221 | nil, 222 | // 0xa0 223 | nil, 224 | nil, 225 | func (c *Compilation) { 226 | // 0xa2 ldx immediate 227 | v := c.interpImmedAddr() 228 | c.debugPrintf("ldx #$%02x\n", []llvm.Value{v}) 229 | c.performLdx(v) 230 | c.cycle(2, -1) 231 | }, 232 | nil, 233 | nil, 234 | nil, 235 | nil, 236 | nil, 237 | nil, 238 | func (c *Compilation) { 239 | // 0xa9 lda immediate 240 | v := c.interpImmedAddr() 241 | c.debugPrintf("lda #$%02x\n", []llvm.Value{v}) 242 | c.performLda(v) 243 | c.cycle(2, -1) 244 | }, 245 | nil, 246 | nil, 247 | nil, 248 | nil, 249 | nil, 250 | nil, 251 | // 0xb0 252 | nil, 253 | nil, 254 | nil, 255 | nil, 256 | func (c *Compilation) { 257 | // 0xb4 ldy zpg x 258 | addr := c.interpZpgIndexAddr(c.rX) 259 | c.debugPrintf("ldy $%02x, X\n", []llvm.Value{addr}) 260 | v := c.dynLoad(addr, 0, 0xff) 261 | c.performLdy(v) 262 | c.cycle(4, -1) 263 | }, 264 | nil, 265 | nil, 266 | nil, 267 | nil, 268 | nil, 269 | nil, 270 | nil, 271 | nil, 272 | nil, 273 | nil, 274 | nil, 275 | // 0xc0 276 | nil, 277 | nil, 278 | nil, 279 | nil, 280 | nil, 281 | nil, 282 | nil, 283 | nil, 284 | nil, 285 | nil, 286 | nil, 287 | nil, 288 | nil, 289 | nil, 290 | nil, 291 | nil, 292 | // 0xd0 293 | func (c *Compilation) { 294 | // 0xd0 bne relative 295 | // TODO: optimize by not loading destAddr if we're not in debug mode 296 | destAddr := c.interpRelAddr() 297 | c.debugPrintf("bne $%04x\n", []llvm.Value{destAddr}) 298 | 299 | pc := c.builder.CreateLoad(c.rPC, "") 300 | c1 := llvm.ConstInt(pc.Type(), 1, false) 301 | xff00 := llvm.ConstInt(llvm.Int16Type(), uint64(0xff00), false) 302 | 303 | doneBlock := c.createBlock("done") 304 | isZero := c.builder.CreateLoad(c.rSZero, "") 305 | notZero := c.builder.CreateNot(isZero, "") 306 | // if ! zero 307 | notBranchingBlock := c.createIf(notZero) 308 | c.builder.CreateStore(destAddr, c.rPC) 309 | // if instrAddr&0xff00 == addr&0xff00 { 310 | instrAddr := c.builder.CreateSub(pc, c1, "") 311 | maskedInstrAddr := c.builder.CreateAnd(instrAddr, xff00, "") 312 | maskedDestAddr := c.builder.CreateAnd(destAddr, xff00, "") 313 | eq := c.builder.CreateICmp(llvm.IntEQ, maskedInstrAddr, maskedDestAddr, "") 314 | // if same page page 315 | crossedPageBlock := c.createIf(eq) 316 | c.cycle(3, -1) 317 | c.builder.CreateBr(doneBlock) 318 | // else if crossed page 319 | c.selectBlock(crossedPageBlock) 320 | c.cycle(4, -1) 321 | c.builder.CreateBr(doneBlock) 322 | // else if not branching 323 | c.selectBlock(notBranchingBlock) 324 | // pc++ 325 | newPc := c.builder.CreateAdd(pc, c1, "") 326 | c.builder.CreateStore(newPc, c.rPC) 327 | c.cycle(2, -1) 328 | c.builder.CreateBr(doneBlock) 329 | // done 330 | c.selectBlock(doneBlock) 331 | }, 332 | nil, 333 | nil, 334 | nil, 335 | nil, 336 | nil, 337 | nil, 338 | nil, 339 | nil, 340 | nil, 341 | nil, 342 | nil, 343 | nil, 344 | nil, 345 | nil, 346 | nil, 347 | // 0xe0 348 | nil, 349 | nil, 350 | nil, 351 | nil, 352 | nil, 353 | nil, 354 | nil, 355 | nil, 356 | nil, 357 | nil, 358 | nil, 359 | nil, 360 | nil, 361 | nil, 362 | nil, 363 | nil, 364 | // 0xf0 365 | nil, 366 | nil, 367 | nil, 368 | nil, 369 | nil, 370 | nil, 371 | nil, 372 | nil, 373 | nil, 374 | nil, 375 | nil, 376 | nil, 377 | nil, 378 | nil, 379 | nil, 380 | nil, 381 | } 382 | 383 | var interpretOpCount = 0 384 | 385 | func init() { 386 | // count the non-nil ones 387 | for _, fn := range interpretOps { 388 | if fn != nil { 389 | interpretOpCount += 1 390 | } 391 | } 392 | 393 | } 394 | 395 | func (c *Compilation) interpImmedAddr() llvm.Value { 396 | oldPc := c.builder.CreateLoad(c.rPC, "") 397 | newPc := c.builder.CreateAdd(oldPc, llvm.ConstInt(oldPc.Type(), 1, false), "") 398 | c.builder.CreateStore(newPc, c.rPC) 399 | return c.dynLoad(oldPc, 0, 0xffff) 400 | } 401 | 402 | func (c *Compilation) interpAbsAddr() llvm.Value { 403 | oldPc := c.builder.CreateLoad(c.rPC, "") 404 | addr := c.dynLoadWord(oldPc) 405 | newPc := c.builder.CreateAdd(oldPc, llvm.ConstInt(oldPc.Type(), 2, false), "") 406 | c.builder.CreateStore(newPc, c.rPC) 407 | return addr 408 | } 409 | 410 | func (c *Compilation) interpZpgAddr() llvm.Value { 411 | oldPc := c.builder.CreateLoad(c.rPC, "") 412 | addr := c.dynLoad(oldPc, 0, 0xffff) 413 | newPc := c.builder.CreateAdd(oldPc, llvm.ConstInt(oldPc.Type(), 1, false), "") 414 | c.builder.CreateStore(newPc, c.rPC) 415 | return addr 416 | } 417 | 418 | func (c *Compilation) interpRelAddr() llvm.Value { 419 | pc := c.builder.CreateLoad(c.rPC, "") 420 | offset8 := c.dynLoad(pc, 0, 0xffff) 421 | offset16 := c.builder.CreateSExt(offset8, llvm.Int16Type(), "") 422 | addr := c.builder.CreateAdd(pc, offset16, "") 423 | c1 := llvm.ConstInt(llvm.Int16Type(), 1, false) 424 | return c.builder.CreateAdd(addr, c1, "") 425 | } 426 | 427 | func (c *Compilation) interpZpgIndexAddr(indexPtr llvm.Value) llvm.Value { 428 | pc := c.builder.CreateLoad(c.rPC, "") 429 | base := c.dynLoad(pc, 0, 0xffff) 430 | index := c.builder.CreateLoad(indexPtr, "") 431 | return c.builder.CreateAdd(base, index, "") 432 | } 433 | 434 | func (c *Compilation) addInterpretBlock() { 435 | // here we create a basic block that we jump to when we need to 436 | // fall back on interpreting 437 | c.selectBlock(c.interpretBlock) 438 | // load the pc 439 | pc := c.builder.CreateLoad(c.rPC, "") 440 | // get the opcode at pc 441 | opCode := c.dynLoad(pc, 0, 0xffff) 442 | // increment pc 443 | pc = c.builder.CreateAdd(pc, llvm.ConstInt(pc.Type(), 1, false), "") 444 | c.builder.CreateStore(pc, c.rPC) 445 | // switch on the opcode 446 | badOpCodeBlock := c.createBlock("BadOpCode") 447 | sw := c.builder.CreateSwitch(opCode, badOpCodeBlock, interpretOpCount) 448 | c.selectBlock(badOpCodeBlock) 449 | c.createPanic("invalid op code: $%02x\n", []llvm.Value{opCode}) 450 | 451 | i8Type := llvm.Int8Type() 452 | for op, fn := range interpretOps { 453 | if fn == nil { 454 | continue 455 | } 456 | bb := c.createBlock(fmt.Sprintf("x%02x", op)) 457 | sw.AddCase(llvm.ConstInt(i8Type, uint64(op), false), bb) 458 | c.selectBlock(bb) 459 | fn(c) 460 | // jump back to dynJumpBlock. maybe we're back in 461 | // statically compiled happy land. 462 | c.builder.CreateBr(c.dynJumpBlock) 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /jamulator/nes.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "path" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | func (r *Rom) disassembleToDirWithJam(dest string, jamFd io.Writer) error { 17 | jam := bufio.NewWriter(jamFd) 18 | 19 | jam.WriteString("# output file name when this rom is assembled\n") 20 | jam.WriteString(fmt.Sprintf("filename=%s\n", r.Filename)) 21 | jam.WriteString("# see http://wiki.nesdev.com/w/index.php/Mapper\n") 22 | jam.WriteString(fmt.Sprintf("mapper=%d\n", r.Mapper)) 23 | jam.WriteString("# 'Horizontal', 'Vertical', or 'FourScreenVRAM'\n") 24 | jam.WriteString("# see http://wiki.nesdev.com/w/index.php/Mirroring\n") 25 | jam.WriteString(fmt.Sprintf("mirroring=%s\n", r.Mirroring.String())) 26 | jam.WriteString("# whether SRAM in CPU $6000-$7FFF is present\n") 27 | jam.WriteString(fmt.Sprintf("sram=%t\n", r.SRamPresent)) 28 | jam.WriteString("# whether the SRAM in CPU $6000-$7FFF, if present, is battery backed\n") 29 | jam.WriteString(fmt.Sprintf("battery=%t\n", r.BatteryBacked)) 30 | jam.WriteString("# 'NTSC', 'PAL', or 'DualCompatible'\n") 31 | jam.WriteString(fmt.Sprintf("tvsystem=%s\n", r.TvSystem.String())) 32 | 33 | // save the prg rom 34 | jam.WriteString("# assembly code\n") 35 | program, err := r.Disassemble() 36 | if err != nil { 37 | return err 38 | } 39 | outpath := "prg.asm" 40 | err = program.WriteSourceFile(path.Join(dest, outpath)) 41 | if err != nil { 42 | return err 43 | } 44 | _, err = jam.WriteString(fmt.Sprintf("prg=%s\n", outpath)) 45 | if err != nil { 46 | return err 47 | } 48 | // save the chr banks 49 | jam.WriteString("# video data\n") 50 | for i, bank := range r.ChrRom { 51 | buf := bytes.NewBuffer(bank) 52 | outpath := fmt.Sprintf("chr%d.chr", i) 53 | chrFd, err := os.Create(path.Join(dest, outpath)) 54 | if err != nil { 55 | return err 56 | } 57 | chr := bufio.NewWriter(chrFd) 58 | _, err = io.Copy(chr, buf) 59 | if err != nil { 60 | chrFd.Close() 61 | return err 62 | } 63 | _, err = jam.WriteString(fmt.Sprintf("chr=%s\n", outpath)) 64 | if err != nil { 65 | chrFd.Close() 66 | return err 67 | } 68 | chr.Flush() 69 | err = chrFd.Close() 70 | if err != nil { 71 | return err 72 | } 73 | } 74 | 75 | jam.Flush() 76 | return nil 77 | } 78 | 79 | func (r *Rom) DisassembleToDir(dest string) error { 80 | // create the folder 81 | err := os.Mkdir(dest, 0770) 82 | if err != nil { 83 | return err 84 | } 85 | // put a .jam file which describes how to reassemble 86 | baseJamFilename := removeExtension(r.Filename) 87 | if len(baseJamFilename) == 0 { 88 | baseJamFilename = "rom" 89 | } 90 | jamFilename := path.Join(dest, baseJamFilename+".jam") 91 | jamFd, err := os.Create(jamFilename) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | err = r.disassembleToDirWithJam(dest, jamFd) 97 | err2 := jamFd.Close() 98 | if err != nil { 99 | return err 100 | } 101 | if err2 != nil { 102 | return err2 103 | } 104 | return nil 105 | } 106 | 107 | func removeExtension(filename string) string { 108 | return filename[0 : len(filename)-len(path.Ext(filename))] 109 | } 110 | 111 | func AssembleRom(dir string, ioreader io.Reader) (*Rom, error) { 112 | reader := bufio.NewReader(ioreader) 113 | r := new(Rom) 114 | r.PrgRom = make([][]byte, 0) 115 | r.ChrRom = make([][]byte, 0) 116 | 117 | lineCount := 0 118 | for { 119 | lineCount += 1 120 | rawLine, err := reader.ReadString('\n') 121 | if err == io.EOF { 122 | break 123 | } 124 | if err != nil { 125 | return nil, err 126 | } 127 | line := strings.TrimSpace(rawLine) 128 | if line[0] == '#' { 129 | continue 130 | } 131 | parts := strings.SplitN(line, "=", 2) 132 | if parts == nil { 133 | return nil, errors.New(fmt.Sprintf("Line %d: syntax error", lineCount)) 134 | } 135 | switch parts[0] { 136 | case "filename": 137 | r.Filename = parts[1] 138 | case "mapper": 139 | m64, err := strconv.ParseUint(parts[1], 10, 8) 140 | if err != nil { 141 | return nil, errors.New(fmt.Sprintf("Line %d: invalid mapper number: %d", lineCount, parts[1])) 142 | } 143 | r.Mapper = byte(m64) 144 | case "mirroring": 145 | switch parts[1] { 146 | case "Horizontal": 147 | r.Mirroring = HorizontalMirroring 148 | case "Vertical": 149 | r.Mirroring = VerticalMirroring 150 | case "FourScreenVRAM": 151 | r.Mirroring = FourScreenVRamMirroring 152 | default: 153 | return nil, errors.New(fmt.Sprintf("Line %d: unrecognized mirroring value: %s", lineCount, parts[1])) 154 | } 155 | case "tvsystem": 156 | switch parts[1] { 157 | case "NTSC": 158 | r.TvSystem = NtscTv 159 | case "PAL": 160 | r.TvSystem = PalTv 161 | case "DualCompatible": 162 | r.TvSystem = DualCompatTv 163 | default: 164 | return nil, errors.New(fmt.Sprintf("Line %d: unrecognized tvsystem value: %s", lineCount, parts[1])) 165 | } 166 | case "sram": 167 | switch parts[1] { 168 | case "true": 169 | r.SRamPresent = true 170 | case "false": 171 | r.SRamPresent = false 172 | default: 173 | return nil, errors.New(fmt.Sprintf("Line %d: unrecognized sram value: %s", lineCount, parts[1])) 174 | } 175 | case "battery": 176 | switch parts[1] { 177 | case "true": 178 | r.BatteryBacked = true 179 | case "false": 180 | r.BatteryBacked = false 181 | default: 182 | return nil, errors.New(fmt.Sprintf("Line %d: unrecognized battery value: %s", lineCount, parts[1])) 183 | } 184 | case "prg": 185 | prgfile := path.Join(dir, parts[1]) 186 | programAst, err := ParseFile(prgfile) 187 | if err != nil { 188 | return nil, err 189 | } 190 | program := programAst.ToProgram() 191 | if len(program.Errors) > 0 { 192 | return nil, errors.New(strings.Join(program.Errors, "\n")) 193 | } 194 | bank := make([]byte, 0, 0x4000) 195 | buf := bytes.NewBuffer(bank) 196 | err = program.Assemble(buf) 197 | if err != nil { 198 | return nil, err 199 | } 200 | if buf.Len() != 0x4000 { 201 | return nil, errors.New(fmt.Sprintf("%s: PRG ROM should be 0x4000 bytes; instead it is 0x%x", prgfile, buf.Len())) 202 | } 203 | r.PrgRom = append(r.PrgRom, buf.Bytes()) 204 | case "chr": 205 | chrfile := path.Join(dir, parts[1]) 206 | chrFd, err := os.Open(chrfile) 207 | if err != nil { 208 | return nil, err 209 | } 210 | bank, err := ioutil.ReadAll(chrFd) 211 | err2 := chrFd.Close() 212 | if err != nil { 213 | return nil, err 214 | } 215 | if err2 != nil { 216 | return nil, err2 217 | } 218 | r.ChrRom = append(r.ChrRom, bank) 219 | default: 220 | return nil, errors.New(fmt.Sprintf("Line %d: unrecognized property: %s", lineCount, parts[0])) 221 | } 222 | } 223 | 224 | return r, nil 225 | } 226 | 227 | func AssembleRomFile(filename string) (*Rom, error) { 228 | fd, err := os.Open(filename) 229 | if err != nil { 230 | return nil, err 231 | } 232 | 233 | r, err := AssembleRom(path.Dir(filename), fd) 234 | err2 := fd.Close() 235 | if err != nil { 236 | return nil, err 237 | } 238 | if err2 != nil { 239 | return nil, err2 240 | } 241 | 242 | return r, nil 243 | } 244 | -------------------------------------------------------------------------------- /jamulator/recompile.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "strings" 11 | ) 12 | 13 | func (rom *Rom) RecompileToBinary(filename string, flags CompileFlags) error { 14 | if len(rom.PrgRom) != 1 && len(rom.PrgRom) != 2 { 15 | return errors.New("only roms with 1-2 prg rom banks are supported") 16 | } 17 | fmt.Fprintf(os.Stderr, "Disassembling...\n") 18 | program, err := rom.Disassemble() 19 | if err != nil { 20 | return err 21 | } 22 | if len(program.Errors) > 0 { 23 | return errors.New(strings.Join(program.Errors, "\n")) 24 | } 25 | 26 | tmpDir, err := ioutil.TempDir("", "") 27 | if err != nil { 28 | return err 29 | } 30 | defer func() { 31 | os.RemoveAll(tmpDir) 32 | }() 33 | runtimeArchive := "runtime/runtime.a" 34 | tmpPrgBitcode := path.Join(tmpDir, "prg.bc") 35 | tmpPrgObject := path.Join(tmpDir, "prg.o") 36 | 37 | fmt.Fprintf(os.Stderr, "Decompiling...\n") 38 | c, err := program.CompileToFilename(tmpPrgBitcode, flags) 39 | if err != nil { 40 | return err 41 | } 42 | if len(c.Errors) != 0 { 43 | return errors.New(strings.Join(c.Errors, "\n")) 44 | } 45 | if len(c.Warnings) != 0 { 46 | fmt.Fprintf(os.Stderr, "Warnings:\n%s\n", strings.Join(c.Warnings, "\n")) 47 | } 48 | fmt.Fprintf(os.Stderr, "Compiling...\n") 49 | out, err := exec.Command("llc", "-o", tmpPrgObject, "-filetype=obj", "-relocation-model=pic", tmpPrgBitcode).CombinedOutput() 50 | fmt.Fprint(os.Stderr, string(out)) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | fmt.Fprintf(os.Stderr, "Linking...\n") 56 | out, err = exec.Command("gcc", tmpPrgObject, runtimeArchive, "-lGLEW", "-lGL", "-lSDL", "-lSDL_gfx", "-o", filename).CombinedOutput() 57 | fmt.Fprint(os.Stderr, string(out)) 58 | if err != nil { 59 | return err 60 | } 61 | fmt.Fprintf(os.Stderr, "Done: %s\n", filename) 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /jamulator/rom.go: -------------------------------------------------------------------------------- 1 | package jamulator 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | "os" 8 | "path" 9 | ) 10 | 11 | const ( 12 | NtscTv = iota 13 | PalTv 14 | DualCompatTv 15 | ) 16 | 17 | type TvSystem int 18 | 19 | func (tvs TvSystem) String() string { 20 | if tvs == NtscTv { 21 | return "NTSC" 22 | } else if tvs == PalTv { 23 | return "PAL" 24 | } 25 | return "DualCompatible" 26 | } 27 | 28 | const ( 29 | HorizontalMirroring = iota 30 | VerticalMirroring 31 | FourScreenVRamMirroring 32 | ) 33 | 34 | type Mirroring int 35 | 36 | func (m Mirroring) String() string { 37 | if m == HorizontalMirroring { 38 | return "Horizontal" 39 | } else if m == VerticalMirroring { 40 | return "Vertical" 41 | } 42 | return "FourScreenVRAM" 43 | } 44 | 45 | type Rom struct { 46 | Filename string 47 | PrgRom [][]byte 48 | ChrRom [][]byte 49 | 50 | Mapper byte 51 | Mirroring Mirroring 52 | BatteryBacked bool 53 | TvSystem TvSystem 54 | SRamPresent bool 55 | } 56 | 57 | func Load(ioreader io.Reader) (*Rom, error) { 58 | reader := bufio.NewReader(ioreader) 59 | r := new(Rom) 60 | 61 | // read the header 62 | buf := make([]byte, 16) 63 | _, err := io.ReadAtLeast(reader, buf, 16) 64 | if err != nil { 65 | return nil, err 66 | } 67 | if string(buf[0:4]) != "NES\x1a" { 68 | return nil, errors.New("Invalid ROM file") 69 | } 70 | prgBankCount := int(buf[4]) 71 | chrBankCount := int(buf[5]) 72 | flags6 := buf[6] 73 | flags7 := buf[7] 74 | if buf[8] != 0 && buf[8] != 1 { 75 | return nil, errors.New("Only 8KB program RAM supported") 76 | } 77 | flags9 := buf[9] 78 | flags10 := buf[10] 79 | 80 | r.Mapper = (flags6 >> 4) | (flags7 & 0xf0) 81 | if flags6&0x8 != 0 { 82 | r.Mirroring = FourScreenVRamMirroring 83 | } else if flags6&0x1 != 0 { 84 | r.Mirroring = HorizontalMirroring 85 | } else { 86 | r.Mirroring = VerticalMirroring 87 | } 88 | if flags6&0x2 != 0 { 89 | r.BatteryBacked = true 90 | } 91 | if flags6&0x4 != 0 { 92 | return nil, errors.New("Trainer unsupported") 93 | } 94 | if flags7&0x1 != 0 { 95 | return nil, errors.New("VS Unisystem unsupported") 96 | } 97 | if flags7&0x2 != 0 { 98 | return nil, errors.New("PlayChoice-10 unsupported") 99 | } 100 | if (flags7>>2)&0x2 != 0 { 101 | return nil, errors.New("NES 2.0 format unsupported") 102 | } 103 | if flags9&0x1 != 0 { 104 | return nil, errors.New("PAL unsupported") 105 | } 106 | switch flags10 & 0x2 { 107 | case 0: 108 | r.TvSystem = NtscTv 109 | case 2: 110 | r.TvSystem = PalTv 111 | default: 112 | r.TvSystem = DualCompatTv 113 | } 114 | r.SRamPresent = flags10&0x10 == 0 115 | if flags10&0x20 != 0 { 116 | return nil, errors.New("bus conflicts unsupported") 117 | } 118 | 119 | r.PrgRom = make([][]byte, prgBankCount) 120 | for i := 0; i < prgBankCount; i++ { 121 | bank := make([]byte, 0x4000) 122 | _, err := io.ReadAtLeast(reader, bank, len(bank)) 123 | if err != nil { 124 | return nil, err 125 | } 126 | r.PrgRom[i] = bank 127 | } 128 | 129 | r.ChrRom = make([][]byte, chrBankCount) 130 | for i := 0; i < chrBankCount; i++ { 131 | bank := make([]byte, 0x2000) 132 | _, err := io.ReadAtLeast(reader, bank, len(bank)) 133 | if err != nil { 134 | return nil, err 135 | } 136 | r.ChrRom[i] = bank 137 | } 138 | 139 | return r, nil 140 | } 141 | 142 | func LoadFile(filename string) (*Rom, error) { 143 | fd, err := os.Open(filename) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | r, err := Load(fd) 149 | err2 := fd.Close() 150 | if err != nil { 151 | return nil, err 152 | } 153 | if err2 != nil { 154 | return nil, err2 155 | } 156 | r.Filename = path.Base(filename) 157 | 158 | return r, nil 159 | } 160 | 161 | func (r *Rom) Save(writer io.Writer) error { 162 | w := bufio.NewWriter(writer) 163 | flags6 := byte(0) 164 | flags7 := byte(0) 165 | flags9 := byte(0) 166 | flags10 := byte(0) 167 | 168 | // mapper number 169 | flags6 |= (r.Mapper & 0x0f) << 4 170 | flags7 |= r.Mapper & 0xf0 171 | 172 | // mirroring 173 | switch r.Mirroring { 174 | case HorizontalMirroring: 175 | flags6 |= 0x1 176 | case VerticalMirroring: // nothing to do 177 | case FourScreenVRamMirroring: 178 | flags6 |= 0x8 179 | default: 180 | panic("Unknown mirroring") 181 | } 182 | 183 | if r.BatteryBacked { 184 | flags6 |= 0x2 185 | } 186 | 187 | switch r.TvSystem { 188 | case PalTv: 189 | flags9 |= 0x1 190 | flags10 |= 0x2 191 | case NtscTv: // nothing to do 192 | case DualCompatTv: 193 | flags10 |= 0x3 194 | default: 195 | panic("unknown tv system") 196 | } 197 | 198 | if !r.SRamPresent { 199 | flags10 |= 0x10 200 | } 201 | 202 | header := []byte{ 203 | 'N', 'E', 'S', 0x1a, 204 | byte(len(r.PrgRom)), 205 | byte(len(r.ChrRom)), 206 | flags6, 207 | flags7, 208 | 0, 209 | flags9, 210 | flags10, 211 | 0, 0, 0, 0, 0, 212 | } 213 | _, err := w.Write(header) 214 | if err != nil { 215 | return err 216 | } 217 | 218 | for _, bank := range r.PrgRom { 219 | _, err := w.Write(bank) 220 | if err != nil { 221 | return err 222 | } 223 | } 224 | 225 | for _, bank := range r.ChrRom { 226 | _, err := w.Write(bank) 227 | if err != nil { 228 | return err 229 | } 230 | } 231 | 232 | w.Flush() 233 | return nil 234 | } 235 | 236 | func (r *Rom) SaveFile(dir string) error { 237 | fd, err := os.Create(path.Join(dir, r.Filename)) 238 | if err != nil { 239 | return err 240 | } 241 | err = r.Save(fd) 242 | err2 := fd.Close() 243 | if err != nil { 244 | return err 245 | } 246 | if err2 != nil { 247 | return err2 248 | } 249 | return nil 250 | } 251 | -------------------------------------------------------------------------------- /jamulator/test/dasm_example.asm: -------------------------------------------------------------------------------- 1 | 2 | ; EXAMPLE.ASM (6502 Microprocessor) 3 | ; 4 | 5 | processor 6502 6 | 7 | mac ldax 8 | lda [{1}] 9 | ldx [{1}]+1 10 | endm 11 | mac ldaxi 12 | lda #<[{1}] 13 | ldx #>[{1}] 14 | endm 15 | mac stax 16 | sta [{1}] 17 | stx [{1}]+1 18 | endm 19 | mac pushxy 20 | txa 21 | pha 22 | tya 23 | pha 24 | endm 25 | mac popxy 26 | pla 27 | tay 28 | pla 29 | tax 30 | endm 31 | mac inc16 32 | inc {1} 33 | bne .1 34 | inc {1}+1 35 | .1 36 | endm 37 | 38 | STOP1 equ %00000000 ;CxCTL 1 Stop bit 39 | STOP2 equ %10000000 ;CxCTL 2 Stop bits (WL5:1.5, WL8&par:1) 40 | WL5 equ %01100000 ;CxCTL Wordlength 41 | WL6 equ %01000000 42 | WL7 equ %00100000 43 | WL8 equ %00000000 44 | RCS equ %00010000 ;CxCTL 1=Select baud, 0=ext. receiver clk 45 | 46 | B76800 equ %0000 ;CxCTL Baud rates (1.2288 Mhz clock) 47 | B75 equ %0001 48 | B100 equ %0010 49 | B150 equ %0011 50 | B200 equ %0100 51 | B300 equ %0101 52 | B400 equ %0110 53 | B600 equ %0111 54 | B800 equ %1000 55 | B1200 equ %1001 56 | B1600 equ %1010 57 | B2400 equ %1011 58 | B3200 equ %1100 59 | B4800 equ %1101 60 | B6400 equ %1110 61 | B12800 equ %1111 62 | 63 | PARODD equ %00100000 ;CxCMD Select Parity 64 | PAREVEN equ %01100000 65 | PARMARK equ %10100000 66 | PARSPACE equ %11100000 67 | PAROFF equ %00000000 68 | 69 | RECECHO equ %00010000 ;CxCMD Receiver Echo mode 70 | TMASK equ %00001100 71 | TDISABLE equ %00000000 ;CxCMD Transmitter modes 72 | TDISABLER equ %00001000 ;RTS stays asserted 73 | TENABLE equ %00000100 74 | TBREAK equ %00001100 ;send break 75 | 76 | UA_IRQDSBL equ %00000010 77 | DTRRDY equ %00000001 ;~DTR output is inverted (low) 78 | 79 | SR_PE equ %00000001 ;CxSTAT Status 80 | SR_FE equ %00000010 ;NOTE: writing dummy data causes RESET 81 | SR_OVRUN equ %00000100 82 | SR_RDRFULL equ %00001000 83 | SR_TDREMPTY equ %00010000 84 | SR_DCD equ %00100000 85 | SR_DSR equ %01000000 86 | SR_INTPEND equ %10000000 87 | 88 | 89 | T1_OEPB7 equ %10000000 ;x_ACR 90 | T1_FREERUN equ %01000000 ;T1 free running mode 91 | T1_ONESHOT equ %00000000 92 | T2_ICPB6 equ %00100000 ;T2 counts pulses on PB6 93 | T2_ONESHOT equ %00000000 ;T2 counts phase2 transitions 94 | SRC_OFF equ %00000000 ;shift register control 95 | SRC_INT2 equ %00000100 96 | SRC_INPH2 equ %00001000 97 | SRC_INEXT equ %00001100 98 | SRC_OUTFR equ %00010000 ;free running output using T2 99 | SRC_OUTT2 equ %00010100 100 | SRC_OUTPH2 equ %00011000 101 | SRC_OUTEXT equ %00011100 102 | PBLE equ %00000010 ;on CB1 transition (in/out). 103 | PALE equ %00000001 ;on CA1 transition (in). data retained 104 | 105 | ;x_PCR 106 | CB2_I_NEG equ %00000000 ;interrupt on neg trans, r/w ORB clears 107 | CB2_I_NEGI equ %00100000 ; same, but r/w ORB does not clear int 108 | CB2_I_POS equ %01000000 ;interrupt on pos trans, r/w ORB clears 109 | CB2_I_POSI equ %01100000 ; same, but r/w ORB does not clear int 110 | CB2_O_HSHAK equ %10000000 ;CB2=0 on r/w ORB, CB2=1 on CB1 transition 111 | CB2_O_PULSE equ %10100000 ;CB2=0 for one clock after r/w ORB 112 | CB2_O_MANLO equ %11000000 ;CB2=0 113 | CB2_O_MANHI equ %11100000 ;CB2=1 114 | 115 | CA2_I_NEG equ %00000000 ;interrupt on neg trans, r/w ORA clears 116 | CA2_I_NEGI equ %00100000 ; same, but r/w ORA does not clear int 117 | CA2_I_POS equ %01000000 ;interrupt on pos trans, r/w ORA clears 118 | CA2_I_POSI equ %01100000 ; same, but r/w ORA does not clear int 119 | CA2_O_HSHAK equ %10000000 ;CA2=0 on r/w ORA, CA2=1 on CA1 transition 120 | CA2_O_PULSE equ %10100000 ;CA2=0 for one clock after r/w ORA 121 | CA2_O_MANLO equ %11000000 ;CA2=0 122 | CA2_O_MANHI equ %11100000 ;CA2=1 123 | 124 | 125 | CB1_THI equ %00010000 126 | CB1_TLO equ %00000000 127 | CA1_THI equ %00000001 128 | CA1_TLO equ %00000000 129 | 130 | VIRPEND equ %10000000 ;x_IFR 131 | IRENABLE equ %10000000 ;x_IER 1's enable ints 0=no change 132 | IRDISABLE equ %00000000 ;x_IER 1's disable ints 0=no change 133 | 134 | IRT1 equ %01000000 135 | IRT2 equ %00100000 136 | IRCB1 equ %00010000 137 | IRCB2 equ %00001000 138 | IRSR equ %00000100 139 | IRCA1 equ %00000010 140 | IRCA2 equ %00000001 141 | 142 | seg.u bss 143 | org $0000 ;RAM (see below) 144 | org $2000 ;unused 145 | org $4000 ;unused 146 | 147 | org $6000 ;6551 CHANNEL #1 148 | C1DATA ds 1 149 | C1STAT ds 1 150 | C1CMD ds 1 151 | C1CTL ds 1 152 | 153 | org $8000 ;6551 CHANNEL #2 154 | C2DATA ds 1 155 | C2STAT ds 1 156 | C2CMD ds 1 157 | C2CTL ds 1 158 | 159 | org $A000 ;6522 (HOST COMM) 160 | H_ORB ds 1 161 | H_ORAHS ds 1 ;with CA2 handshake 162 | H_DDRB ds 1 163 | H_DDRA ds 1 164 | H_T1CL ds 1 ;read clears interrupt flag 165 | H_T1CH ds 1 ;write clears interrupt flag 166 | H_T1CLL ds 1 167 | H_T1CHL ds 1 ;write clears interrupt flag 168 | H_T2CL ds 1 ;read clears interrupt flag 169 | H_T2CH ds 1 ;write clears interrupt flag 170 | H_SR ds 1 171 | H_ACR ds 1 172 | H_PCR ds 1 173 | H_IFR ds 1 174 | H_IER ds 1 175 | H_ORA ds 1 ;no CA2 handshake 176 | 177 | org $C000 ;6522 (IO COMM) 178 | I_ORB ds 1 179 | I_ORAHS ds 1 ; (same comments apply) 180 | I_DDRB ds 1 181 | I_DDRA ds 1 182 | I_T1CL ds 1 183 | I_T1CH ds 1 184 | I_T1CLL ds 1 185 | I_T1CHL ds 1 186 | I_T2CL ds 1 187 | I_T2CH ds 1 188 | I_SR ds 1 189 | I_ACR ds 1 190 | I_PCR ds 1 191 | I_IFR ds 1 192 | I_IER ds 1 193 | I_ORA ds 1 194 | 195 | 196 | 197 | ; -------------------------- ZERO PAGE ------------------- 198 | seg.u data 199 | org $00 200 | 201 | ; -------------------------- NORMAL RAM ------------------- 202 | org $0100 203 | 204 | RAMEND equ $2000 205 | 206 | ; -------------------------- CODE ------------------- 207 | 208 | seg code 209 | org $F000 210 | PROMBEG equ . 211 | 212 | RESET subroutine 213 | sei ;disable interrupts 214 | ldx #$FF ;reset stack 215 | txs 216 | 217 | lda #$FF 218 | sta H_DDRA 219 | sta C1STAT ;reset 6551#1 (garbage data) 220 | sta C2STAT ;reset 6551#2 221 | lda #$7F ;disable all 6522 interrupts 222 | sta H_IER 223 | sta I_IER 224 | 225 | lda #%00010000 ;76.8 baud, 8 bits, 1 stop 226 | sta C1CTL 227 | lda #%00000101 ;no parity, enable transmitter & int 228 | sta C1CMD 229 | lda #$AA ;begin transmision 230 | sta C1DATA 231 | 232 | lda #%00011111 ;9600 baud, 8 bits, 1 stop 233 | sta C2CTL 234 | lda #%00000101 235 | sta C2CMD 236 | lda #$41 237 | sta C2DATA 238 | 239 | cli ;enable interrupts 240 | 241 | .1 jsr LOAD 242 | jsr SAVE 243 | jmp .1 244 | 245 | LOAD subroutine 246 | 247 | ldx #0 248 | .1 txa 249 | sta $0500,x 250 | inx 251 | bne .1 252 | rts 253 | 254 | SAVE subroutine 255 | 256 | ldx #0 257 | .2 lda $0500,x 258 | sta H_ORA 259 | inx 260 | bne .2 261 | rts 262 | 263 | NMI rti 264 | 265 | subroutine 266 | IRQ bit C1STAT 267 | bpl .1 268 | pha 269 | lda #$AA 270 | sta C1DATA 271 | lda C1DATA 272 | pla 273 | rti 274 | .1 bit C2STAT 275 | bpl .2 276 | pha 277 | lda #$41 278 | sta C2DATA 279 | lda C2DATA 280 | pla 281 | .2 rti 282 | 283 | ; VECTOR ------------------------------------------------ 284 | 285 | seg vector 286 | org $FFFA 287 | dc.w NMI 288 | dc.w RESET 289 | dc.w IRQ 290 | 291 | PROMEND equ . 292 | 293 | -------------------------------------------------------------------------------- /jamulator/test/dasm_example.bin.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewrk/jamulator/f871b77585af6bd913d6ac1c2e5054d1fe29ad77/jamulator/test/dasm_example.bin.ref -------------------------------------------------------------------------------- /jamulator/test/hello.asm: -------------------------------------------------------------------------------- 1 | org $C000 2 | 3 | msg: .data "Hello, world!", 10, 0 4 | 5 | Reset_Routine: 6 | 7 | LDX #$00 ; starting index in X register 8 | 9 | loop: LDA msg, X ; read 1 char 10 | BEQ loopend ; end loop if we hit the \0 11 | STA $2008 ; putchar (custom ABI I made for testing) 12 | INX 13 | JMP loop ; repeat 14 | 15 | loopend: 16 | LDA #$00 ; return code 0 17 | STA $2009 ; exit (custom ABI I made for testing) 18 | 19 | IRQ_Routine: rti ; do nothing 20 | NMI_Routine: rti ; do nothing 21 | 22 | org $FFFA 23 | dc.w NMI_Routine 24 | dc.w Reset_Routine 25 | dc.w IRQ_Routine 26 | -------------------------------------------------------------------------------- /jamulator/test/hello.bin.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewrk/jamulator/f871b77585af6bd913d6ac1c2e5054d1fe29ad77/jamulator/test/hello.bin.ref -------------------------------------------------------------------------------- /jamulator/test/jumptable.asm: -------------------------------------------------------------------------------- 1 | .org $c000 2 | Start: 3 | jmp NonMaskableInterrupt 4 | NonMaskableInterrupt: 5 | lda $00 ;this is the heart of the entire program, 6 | jsr JumpEngine ;most of what goes on starts here 7 | 8 | .dw TitleScreenMode 9 | .dw GameMode 10 | .dw VictoryMode 11 | .dw GameOverMode 12 | 13 | ;------------------------------------------------------------------------------------- 14 | ;$04 - address low to jump address 15 | ;$05 - address high to jump address 16 | ;$06 - jump address low 17 | ;$07 - jump address high 18 | 19 | JumpEngine: 20 | asl ;shift bit from contents of A 21 | tay 22 | pla ;pull saved return address from stack 23 | sta $04 ;save to indirect 24 | pla 25 | sta $05 26 | iny 27 | lda ($04),y ;load pointer from indirect 28 | sta $06 ;note that if an RTS is performed in next routine 29 | iny ;it will return to the execution before the sub 30 | lda ($04),y ;that called this routine 31 | sta $07 32 | jmp ($06) ;jump to the address we loaded 33 | 34 | TitleScreenMode: 35 | sta $07 36 | rts 37 | 38 | GameMode: 39 | sta $08 40 | rts 41 | 42 | VictoryMode: 43 | sta $09 44 | rts 45 | 46 | GameOverMode: 47 | sta $10 48 | rts 49 | 50 | .org $fffa 51 | .dw NonMaskableInterrupt 52 | .dw Start 53 | .dw Start 54 | -------------------------------------------------------------------------------- /jamulator/test/jumptable.bin.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewrk/jamulator/f871b77585af6bd913d6ac1c2e5054d1fe29ad77/jamulator/test/jumptable.bin.ref -------------------------------------------------------------------------------- /jamulator/test/suite6502.asm: -------------------------------------------------------------------------------- 1 | 2 | ; TEST ADDRESSING MODES 3 | 4 | processor 6502 5 | 6 | org $c000 7 | 8 | Reset_Routine 9 | 10 | adc #1 11 | adc 1 12 | adc 1,x 13 | adc 1,y ;absolute 14 | adc 1000 15 | adc 1000,x 16 | adc 1000,y 17 | adc (1,x) 18 | adc (1),y 19 | 20 | and #1 21 | and 1 22 | and 1,x 23 | and 1,y ;absolute 24 | and 1000 25 | and 1000,x 26 | and 1000,y 27 | and (1,x) 28 | and (1),y 29 | 30 | asl 31 | asl 1 32 | asl 1,x 33 | asl 1000 34 | asl 1000,x 35 | 36 | bcc . 37 | bcs . 38 | beq . 39 | bit 1 40 | bit 1000 41 | bmi . 42 | bne . 43 | bpl . 44 | brk 45 | NMI_Routine 46 | bvc . 47 | bvs . 48 | clc 49 | cld 50 | cli 51 | clv 52 | 53 | cmp #1 54 | cmp 1 55 | cmp 1,x 56 | cmp 1,y ;absolute 57 | cmp 1000 58 | cmp 1000,x 59 | cmp 1000,y 60 | cmp (1,x) 61 | cmp (1),y 62 | 63 | cpx #1 64 | cpx 1 65 | cpx 1000 66 | 67 | cpy #1 68 | cpy 1 69 | cpy 1000 70 | 71 | dec 1 72 | dec 1,x 73 | dec 1000 74 | dec 1000,x 75 | 76 | dex 77 | dey 78 | 79 | eor #1 80 | eor 1 81 | eor 1,x 82 | eor 1,y ;absolute 83 | eor 1000 84 | eor 1000,x 85 | eor 1000,y 86 | eor (1,x) 87 | eor (1),y 88 | 89 | inc 1 90 | inc 1,x 91 | inc 1000 92 | inc 1000,x 93 | 94 | inx 95 | iny 96 | 97 | bne jump_2 98 | jmp 1 ;absolute 99 | jump_2 100 | bne jump_3 101 | jmp 1000 102 | jump_3 103 | bne jump_4 104 | jmp (1) ;absolute 105 | jump_4 106 | bne jump_5 107 | jmp (1000) 108 | 109 | jump_5 110 | bne jump_6 111 | jsr 1 ;absolute 112 | jump_6 113 | bne jump_7 114 | jsr 1000 115 | 116 | jump_7 117 | lda #1 118 | lda 1 119 | lda 1,x 120 | lda 1,y ;absolute 121 | lda 1000 122 | lda 1000,x 123 | lda 1000,y 124 | lda (1,x) 125 | lda (1),y 126 | 127 | ldx #1 128 | ldx 1 129 | ldx 1,y 130 | ldx 1000 131 | ldx 1000,y 132 | 133 | ldy #1 134 | ldy 1 135 | ldy 1,x 136 | ldy 1000 137 | ldy 1000,x 138 | 139 | lsr 140 | lsr 1 141 | lsr 1,x 142 | lsr 1000 143 | lsr 1000,x 144 | 145 | nop 146 | 147 | ora #1 148 | ora 1 149 | ora 1,x 150 | ora 1,y ;absolute 151 | ora 1000 152 | ora 1000,x 153 | ora 1000,y 154 | ora (1,x) 155 | ora (1),y 156 | 157 | pha 158 | php 159 | pla 160 | plp 161 | 162 | rol 163 | rol 1 164 | rol 1,x 165 | rol 1000 166 | rol 1000,x 167 | 168 | ror 169 | ror 1 170 | ror 1,x 171 | ror 1000 172 | ror 1000,x 173 | 174 | rti 175 | IRQ_Routine 176 | bne after_rts 177 | rts 178 | 179 | after_rts 180 | sbc #1 181 | sbc 1 182 | sbc 1,x 183 | sbc 1,y ;absolute 184 | sbc 1000 185 | sbc 1000,x 186 | sbc 1000,y 187 | sbc (1,x) 188 | sbc (1),y 189 | 190 | sec 191 | sed 192 | sei 193 | 194 | sta 1 195 | sta 1,x 196 | sta 1,y ;absolute 197 | sta 1000 198 | sta 1000,x 199 | sta 1000,y 200 | sta (1,x) 201 | sta (1),y 202 | 203 | stx 1 204 | stx 1,y 205 | stx 1000 206 | 207 | sty 1 208 | sty 1,x 209 | sty 1000 210 | 211 | tax 212 | tay 213 | tsx 214 | txa 215 | txs 216 | tya 217 | 218 | 219 | ORG $FFFA 220 | dc.w NMI_Routine 221 | dc.w Reset_Routine 222 | dc.w IRQ_Routine ;Not used, just points to RTI 223 | -------------------------------------------------------------------------------- /jamulator/test/suite6502.bin.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewrk/jamulator/f871b77585af6bd913d6ac1c2e5054d1fe29ad77/jamulator/test/suite6502.bin.ref -------------------------------------------------------------------------------- /jamulator/test/test2.asm: -------------------------------------------------------------------------------- 1 | .org $c000 2 | Reset_Routine: 3 | lda #$28 4 | sta $86 5 | 6 | lda #$ff 7 | ldx #$00 8 | sec 9 | sbc $86, X 10 | NMI_Routine: 11 | rti 12 | .org $fffa 13 | .dw NMI_Routine 14 | .dw Reset_Routine 15 | .dw NMI_Routine 16 | -------------------------------------------------------------------------------- /jamulator/test/zelda.asm: -------------------------------------------------------------------------------- 1 | ;Zelda Title Screen Program 2 | ;------------------- 3 | ;Binary created using DAsm 2.12 running on an Amiga. 4 | 5 | PROCESSOR 6502 6 | 7 | ORG $C000 ;16Kb PRG-ROM, 8Kb CHR-ROM 8 | 9 | dc.b "Zelda Simulator, c1998 Chris Covell (ccovell@direct.ca)" 10 | 11 | Reset_Routine SUBROUTINE 12 | cld ;Clear decimal flag 13 | sei ;Disable interrupts 14 | .WaitV lda $2002 15 | bpl .WaitV ;Wait for vertical blanking interval 16 | ldx #$00 17 | stx $2000 18 | stx $2001 ;Screen display off, amongst other things 19 | dex 20 | txs ;Top of stack at $1FF 21 | 22 | ;Clear (most of) the NES' WRAM. This routine is ripped from "Duck Hunt" - I should probably clear all 23 | ;$800 bytes. 24 | ldy #$06 ;To clear 7 x $100 bytes, from $000 to $6FF? 25 | sty $01 ;Store count value in $01 26 | ldy #$00 27 | sty $00 28 | lda #$00 29 | 30 | .Clear sta ($00),y ;Clear $100 bytes 31 | dey 32 | bne .Clear 33 | 34 | dec $01 ;Decrement "banks" left counter 35 | bpl .Clear ;Do next if >= 0 36 | 37 | ;********* Initialize Palette to specified colour ******** 38 | 39 | ldx #$3F 40 | stx $2006 41 | ldx #$00 42 | stx $2006 43 | 44 | ldx #$27 ;Colour Value (Peach) 45 | ldy #$20 ;Clear BG & Sprite palettes. 46 | .InitPal stx $2007 47 | dey 48 | bne .InitPal 49 | ;********************************************************* 50 | 51 | ldx #$3F 52 | stx $2006 53 | ldx #$00 54 | stx $2006 55 | 56 | ldx #$0 ;Beginning of Palette 57 | ldy #$20 ;# of colours 58 | 59 | .SetPal lda .CMAP,X 60 | sta $2007 61 | inx 62 | dey 63 | bne .SetPal 64 | 65 | ;************************************************ 66 | 67 | ;********** Set up Attribute Table 68 | 69 | ldx #$23 70 | stx $2006 71 | ldx #$C0 72 | stx $2006 73 | 74 | ldx #$0 ;Beginning of Attribute Map 75 | ldy #$40 ;Fill out Table 76 | 77 | .SetAtt lda .AttrMap,X 78 | sta $2007 79 | inx 80 | dey 81 | bne .SetAtt 82 | 83 | ;********** Set up Name Table 84 | 85 | ldx #$20 86 | stx $2006 87 | ldx #$00 88 | stx $2006 89 | 90 | ldx #$0 ;Beginning of Map 91 | ldy #$0 ;256 Tiles 92 | 93 | .SetMap1 lda .TitleMap1,X 94 | sta $2007 95 | inx 96 | dey 97 | bne .SetMap1 98 | 99 | ldx #$0 ;Beginning of Map 100 | ldy #$0 ;256 Tiles 101 | 102 | .SetMap2 lda .TitleMap2,X 103 | sta $2007 104 | inx 105 | dey 106 | bne .SetMap2 107 | 108 | ldx #$0 ;Beginning of Map 109 | ldy #$0 ;256 Tiles 110 | 111 | .SetMap3 lda .TitleMap3,X 112 | sta $2007 113 | inx 114 | dey 115 | bne .SetMap3 116 | 117 | ldx #$0 ;Beginning of Map 118 | ldy #$C0 ;192 Tiles 119 | 120 | .SetMap4 lda .TitleMap4,X 121 | sta $2007 122 | inx 123 | dey 124 | bne .SetMap4 125 | 126 | ;************************************* 127 | 128 | ;***** Set up Sprites ************************** 129 | 130 | ldx #$0 131 | stx $2003 132 | 133 | ldx #$0 ;First Sprite 134 | ldy #$0 ;# of Sprites * 4 135 | 136 | .SetSpr lda .SprMap,X 137 | sta $2004 138 | inx 139 | dey 140 | bne .SetSpr 141 | 142 | ;*********************************************** 143 | 144 | ;Enable vblank interrupts, etc. 145 | lda #%10010000 146 | sta $2000 147 | lda #%00011110 ;Screen on, sprites on, show leftmost 8 pixels, colour 148 | sta $2001 149 | ; cli ;Enable interrupts(?) 150 | 151 | ;Now just loop forever? 152 | .Loop jmp .Loop 153 | 154 | .CMAP dc.b #$36,#$0D,#$00,#$10,#$36,#$17,#$27,#$0D 155 | dc.b #$36,#$08,#$1A,#$28,#$36,#$30,#$31,#$22 156 | dc.b #$36,#$30,#$31,#$11,#$36,#$15,#$15,#$15 157 | dc.b #$36,#$08,#$1A,#$28,#$36,#$30,#$31,#$22 158 | 159 | .AttrMap dc.b #$05,#$05,#$05,#$05,#$05,#$05,#$05,#$05 160 | dc.b #$08,#$6A,#$5A,#$5A,#$5A,#$5A,#$9A,#$22 161 | dc.b #$00,#$66,#$55,#$55,#$55,#$55,#$99,#$00 162 | dc.b #$00,#$6E,#$5F,#$55,#$5D,#$DF,#$BB,#$00 163 | dc.b #$00,#$0A,#$0A,#$0A,#$0A,#$0A,#$0A,#$00 164 | dc.b #$00,#$00,#$C0,#$30,#$00,#$00,#$00,#$00 165 | dc.b #$00,#$00,#$CC,#$33,#$00,#$00,#$00,#$00 166 | dc.b #$00,#$20,#$FC,#$F3,#$00,#$00,#$F0,#$F0 167 | 168 | .SprMap dc.b #$27,#$CA,#$02,#$28,#$2F,#$CB,#$02,#$28 ;Sprites 1 & 2 169 | dc.b #$27,#$CC,#$02,#$30,#$2F,#$CD,#$02,#$30 ;Sprites 3 & 4 170 | dc.b #$27,#$D6,#$02,#$50,#$2C,#$D6,#$02,#$A0 ;...etc... 171 | dc.b #$27,#$CC,#$42,#$C8,#$2F,#$CD,#$42,#$C8 172 | dc.b #$27,#$CA,#$42,#$D0,#$2F,#$CB,#$42,#$D0 173 | dc.b #$31,#$D2,#$02,#$57,#$31,#$D4,#$02,#$5F 174 | dc.b #$3F,#$D4,#$02,#$24,#$41,#$D4,#$02,#$63 175 | dc.b #$4F,#$D6,#$02,#$2C,#$4F,#$D2,#$02,#$CB ;A few Leaves 176 | dc.b #$57,#$CE,#$02,#$73,#$5F,#$CF,#$02,#$73 ;Middle Leaves... 177 | dc.b #$57,#$D0,#$02,#$7B,#$5F,#$D1,#$02,#$7B 178 | dc.b #$67,#$D2,#$02,#$7A,#$7B,#$D4,#$02,#$90 179 | dc.b #$7B,#$D6,#$02,#$BC,#$82,#$D2,#$02,#$50 ;More Leaves 180 | dc.b #$77,#$CB,#$82,#$28,#$7F,#$CA,#$82,#$28 181 | dc.b #$77,#$CD,#$82,#$30,#$7F,#$CC,#$82,#$30 182 | dc.b #$77,#$CD,#$C2,#$C8,#$7F,#$CC,#$C2,#$C8 183 | dc.b #$77,#$CB,#$C2,#$D0,#$7F,#$CA,#$C2,#$D0 184 | dc.b #$AF,#$A3,#$00,#$50,#$AF,#$A5,#$00,#$58 ;Waterfall 185 | dc.b #$AF,#$A7,#$00,#$60,#$AF,#$A9,#$00,#$68 186 | dc.b #$B7,#$B2,#$00,#$50,#$B7,#$B4,#$00,#$58 187 | dc.b #$B7,#$B6,#$00,#$60,#$B7,#$B8,#$00,#$68 188 | dc.b #$C9,#$C2,#$00,#$50,#$D1,#$C3,#$00,#$50 189 | dc.b #$C9,#$C4,#$00,#$58,#$D1,#$C5,#$00,#$58 190 | dc.b #$C9,#$C6,#$00,#$60,#$D1,#$C7,#$00,#$60 191 | dc.b #$C9,#$C8,#$00,#$68,#$D1,#$C9,#$00,#$68 192 | dc.b #$D9,#$C2,#$00,#$50,#$E1,#$C3,#$00,#$50 193 | dc.b #$D9,#$C4,#$00,#$58,#$E1,#$C5,#$00,#$58 194 | dc.b #$D9,#$C6,#$00,#$60,#$E1,#$C7,#$00,#$60 195 | dc.b #$D9,#$C8,#$00,#$68,#$E1,#$C9,#$00,#$68 196 | dc.b #$67,#$A0,#$03,#$58,#$67,#$A0,#$03,#$60 ;Sword 197 | dc.b #$67,#$A0,#$03,#$68,#$67,#$A0,#$03,#$70 198 | dc.b #$67,#$A0,#$03,#$78,#$67,#$A0,#$03,#$80 199 | dc.b #$67,#$A0,#$03,#$88,#$00,#$B0,#$00,#$00 ;Dummy Sprite 200 | 201 | .TitleMap1 dc.b "Begin " 202 | dc.b " " 203 | dc.b " " 204 | dc.b " " 205 | .TitleMap2 dc.b " " 206 | dc.b " " 207 | dc.b " " 208 | dc.b " " 209 | .TitleMap3 dc.b " " 210 | dc.b " " 211 | dc.b " " 212 | dc.b " " 213 | .TitleMap4 dc.b " " 214 | dc.b " " 215 | dc.b " End" 216 | 217 | NMI_Routine SUBROUTINE 218 | 219 | ldx #$0 220 | stx $2005 221 | stx $2005 222 | 223 | rti 224 | 225 | IRQ_Routine ;Dummy label 226 | rti 227 | 228 | ;That's all the code. Now we just need to set the vector table approriately. 229 | 230 | ORG $FFFA,0 231 | dc.w NMI_Routine 232 | dc.w Reset_Routine 233 | dc.w IRQ_Routine ;Not used, just points to RTI 234 | 235 | 236 | ;The end. 237 | -------------------------------------------------------------------------------- /jamulator/test/zelda.bin.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewrk/jamulator/f871b77585af6bd913d6ac1c2e5054d1fe29ad77/jamulator/test/zelda.bin.ref -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "./jamulator" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "path" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | astFlag bool 14 | assembleFlag bool 15 | disassembleFlag bool 16 | unRomFlag bool 17 | compileFlag bool 18 | romFlag bool 19 | disableOptFlag bool 20 | dumpFlag bool 21 | dumpPreFlag bool 22 | debugFlag bool 23 | recompileFlag bool 24 | ) 25 | 26 | // TODO: change this to use commands 27 | func init() { 28 | flag.BoolVar(&astFlag, "ast", false, "Print the abstract syntax tree and quit") 29 | flag.BoolVar(&assembleFlag, "asm", false, "Assemble into 6502 machine code") 30 | flag.BoolVar(&disassembleFlag, "dis", false, "Disassemble 6502 machine code") 31 | flag.BoolVar(&romFlag, "rom", false, "Assemble a jam package into an NES ROM") 32 | flag.BoolVar(&unRomFlag, "unrom", false, "Disassemble an NES ROM into a jam package") 33 | flag.BoolVar(&compileFlag, "c", false, "Compile into a native executable") 34 | flag.BoolVar(&disableOptFlag, "O0", false, "Disable optimizations") 35 | flag.BoolVar(&dumpFlag, "d", false, "Dump LLVM IR code for generated code") 36 | flag.BoolVar(&dumpPreFlag, "dd", false, "Dump LLVM IR code for generated code before verifying module") 37 | flag.BoolVar(&debugFlag, "g", false, "Include debug print statements in generated code") 38 | flag.BoolVar(&recompileFlag, "recompile", false, "Recompile an NES ROM into a native binary") 39 | } 40 | 41 | func usageAndQuit() { 42 | fmt.Fprintf(os.Stderr, "Usage: %s [options] inputfile [outputfile]\n", os.Args[0]) 43 | flag.PrintDefaults() 44 | os.Exit(1) 45 | } 46 | 47 | func removeExtension(filename string) string { 48 | return filename[0 : len(filename)-len(path.Ext(filename))] 49 | } 50 | 51 | func compileFlags() (flags jamulator.CompileFlags) { 52 | if disableOptFlag { 53 | flags |= jamulator.DisableOptFlag 54 | } 55 | if dumpFlag { 56 | flags |= jamulator.DumpModuleFlag 57 | } 58 | if dumpPreFlag { 59 | flags |= jamulator.DumpModulePreFlag 60 | } 61 | if debugFlag { 62 | flags |= jamulator.IncludeDebugFlag 63 | } 64 | return 65 | } 66 | 67 | func compile(filename string, program *jamulator.Program) { 68 | outfile := removeExtension(filename) + ".bc" 69 | if flag.NArg() == 2 { 70 | outfile = flag.Arg(1) 71 | } 72 | fmt.Fprintf(os.Stderr, "Compiling to %s\n", outfile) 73 | c, err := program.CompileToFilename(outfile, compileFlags()) 74 | if err != nil { 75 | panic(err) 76 | } 77 | if len(c.Errors) != 0 { 78 | fmt.Fprintf(os.Stderr, "Errors:\n%s\n", strings.Join(c.Errors, "\n")) 79 | os.Exit(1) 80 | } 81 | if len(c.Warnings) != 0 { 82 | fmt.Fprintf(os.Stderr, "Warnings:\n%s\n", strings.Join(c.Warnings, "\n")) 83 | } 84 | } 85 | 86 | func main() { 87 | flag.Parse() 88 | if flag.NArg() != 1 && flag.NArg() != 2 { 89 | usageAndQuit() 90 | } 91 | filename := flag.Arg(0) 92 | if astFlag || assembleFlag { 93 | fmt.Fprintf(os.Stderr, "Parsing %s\n", filename) 94 | programAst, err := jamulator.ParseFile(filename) 95 | if err != nil { 96 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 97 | os.Exit(1) 98 | } 99 | if astFlag { 100 | programAst.Print() 101 | } 102 | if !assembleFlag && !compileFlag { 103 | return 104 | } 105 | fmt.Fprintf(os.Stderr, "Assembling %s\n", filename) 106 | program := programAst.ToProgram() 107 | if len(program.Errors) > 0 { 108 | for _, err := range program.Errors { 109 | fmt.Fprintln(os.Stderr, err) 110 | } 111 | os.Exit(1) 112 | } 113 | if compileFlag { 114 | compile(filename, program) 115 | return 116 | } 117 | if assembleFlag { 118 | outfile := removeExtension(filename) + ".bin" 119 | if flag.NArg() == 2 { 120 | outfile = flag.Arg(1) 121 | } 122 | fmt.Fprintf(os.Stderr, "Writing to %s\n", outfile) 123 | err = program.AssembleToFile(outfile) 124 | if err != nil { 125 | panic(err) 126 | } 127 | } 128 | return 129 | } else if unRomFlag || recompileFlag { 130 | fmt.Fprintf(os.Stderr, "loading %s\n", filename) 131 | rom, err := jamulator.LoadFile(filename) 132 | if err != nil { 133 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 134 | os.Exit(1) 135 | } 136 | if unRomFlag { 137 | outdir := removeExtension(filename) 138 | if flag.NArg() == 2 { 139 | outdir = flag.Arg(1) 140 | } 141 | fmt.Fprintf(os.Stderr, "disassembling to %s\n", outdir) 142 | err = rom.DisassembleToDir(outdir) 143 | if err != nil { 144 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 145 | os.Exit(1) 146 | } 147 | return 148 | } 149 | // recompile to native binary 150 | outfile := removeExtension(filename) 151 | if flag.NArg() == 2 { 152 | outfile = flag.Arg(1) 153 | } 154 | err = rom.RecompileToBinary(outfile, compileFlags()) 155 | if err != nil { 156 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 157 | os.Exit(1) 158 | } 159 | return 160 | } else if disassembleFlag { 161 | fmt.Fprintf(os.Stderr, "disassembling %s\n", filename) 162 | p, err := jamulator.DisassembleFile(filename) 163 | if err != nil { 164 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 165 | os.Exit(1) 166 | } 167 | if compileFlag { 168 | compile(filename, p) 169 | return 170 | } 171 | if disassembleFlag { 172 | outfile := removeExtension(filename) + ".asm" 173 | if flag.NArg() == 2 { 174 | outfile = flag.Arg(1) 175 | } 176 | fmt.Fprintf(os.Stderr, "writing source %s\n", outfile) 177 | err = p.WriteSourceFile(outfile) 178 | if err != nil { 179 | panic(err) 180 | } 181 | } 182 | return 183 | } else if romFlag { 184 | fmt.Fprintf(os.Stderr, "building rom from %s\n", filename) 185 | r, err := jamulator.AssembleRomFile(filename) 186 | if err != nil { 187 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 188 | os.Exit(1) 189 | } 190 | fmt.Fprintf(os.Stderr, "saving rom %s\n", r.Filename) 191 | err = r.SaveFile(path.Dir(filename)) 192 | if err != nil { 193 | panic(err) 194 | } 195 | return 196 | } 197 | usageAndQuit() 198 | } 199 | -------------------------------------------------------------------------------- /runtime/main.c: -------------------------------------------------------------------------------- 1 | #include "rom.h" 2 | #include "assert.h" 3 | #include "ppu.h" 4 | #include "stdio.h" 5 | #include "SDL/SDL.h" 6 | #include "GL/glew.h" 7 | 8 | typedef struct { 9 | SDL_Surface* screen; 10 | GLuint tex; 11 | bool pendingResize; 12 | int pendingResizeWidth; 13 | int pendingResizeHeight; 14 | } Video; 15 | 16 | static Video v; 17 | static Ppu* p; 18 | static int interruptRequested = ROM_INTERRUPT_NONE; 19 | bool fast = false; 20 | 21 | uint8_t *framebufferSlice = NULL; 22 | int framebufferSize = 0; 23 | 24 | typedef struct { 25 | uint64_t cycle; 26 | uint8_t padIndex; 27 | uint8_t btnIndex; 28 | uint8_t btnState; 29 | } MovieFrame; 30 | static char * movieFilename = NULL; 31 | static MovieFrame* movie = NULL; 32 | static uint64_t movieFrameCount; 33 | static uint64_t frameIndex = 0; 34 | static uint64_t cycleIndex = 0; 35 | 36 | void loadMovie() { 37 | if (movieFilename == NULL) return; 38 | FILE *fd = fopen(movieFilename, "rb"); 39 | if (fd == NULL) { 40 | perror("Error opening movie file"); 41 | exit(1); 42 | } 43 | size_t n = fread(&movieFrameCount, 8, 1, fd); 44 | movie = malloc(sizeof(MovieFrame) * movieFrameCount); 45 | for (size_t i = 0; i < movieFrameCount; ++i) { 46 | n = fread(&movie[i].cycle, 8, 1, fd); 47 | n = fread(&movie[i].padIndex, 1, 1, fd); 48 | n = fread(&movie[i].btnIndex, 1, 1, fd); 49 | n = fread(&movie[i].btnState, 1, 1, fd); 50 | } 51 | if (ferror(fd) != 0) { 52 | perror("Error reading movie"); 53 | exit(1); 54 | } 55 | fclose(fd); 56 | } 57 | 58 | void setPadState(SDLKey key, uint8_t value) { 59 | switch (key) { 60 | default: break; // to make warning go away 61 | case SDLK_2: 62 | rom_set_button_state(0, ROM_BUTTON_A, value); 63 | break; 64 | case SDLK_1: 65 | rom_set_button_state(0, ROM_BUTTON_B, value); 66 | break; 67 | case SDLK_RSHIFT: 68 | rom_set_button_state(0, ROM_BUTTON_SELECT, value); 69 | break; 70 | case SDLK_RETURN: 71 | rom_set_button_state(0, ROM_BUTTON_START, value); 72 | break; 73 | case SDLK_UP: 74 | rom_set_button_state(0, ROM_BUTTON_UP, value); 75 | break; 76 | case SDLK_DOWN: 77 | rom_set_button_state(0, ROM_BUTTON_DOWN, value); 78 | break; 79 | case SDLK_LEFT: 80 | rom_set_button_state(0, ROM_BUTTON_LEFT, value); 81 | break; 82 | case SDLK_RIGHT: 83 | rom_set_button_state(0, ROM_BUTTON_RIGHT, value); 84 | break; 85 | } 86 | } 87 | 88 | void setPadStateFromMovie() { 89 | if (movie == NULL) return; 90 | while (frameIndex < movieFrameCount && cycleIndex >= movie[frameIndex].cycle) { 91 | rom_set_button_state( 92 | movie[frameIndex].padIndex, 93 | movie[frameIndex].btnIndex, 94 | movie[frameIndex].btnState); 95 | frameIndex += 1; 96 | } 97 | if (frameIndex >= movieFrameCount) exit(0); 98 | } 99 | 100 | void flush_events() { 101 | SDL_Event event; 102 | 103 | while (SDL_PollEvent(&event)) { 104 | switch (event.type) { 105 | case SDL_VIDEORESIZE: 106 | v.pendingResize = true; 107 | v.pendingResizeWidth = event.resize.w; 108 | v.pendingResizeHeight = event.resize.h; 109 | break; 110 | case SDL_QUIT: 111 | exit(0); 112 | case SDL_KEYDOWN: 113 | setPadState(event.key.keysym.sym, ROM_PAD_STATE_ON); 114 | break; 115 | case SDL_KEYUP: 116 | setPadState(event.key.keysym.sym, ROM_PAD_STATE_OFF); 117 | break; 118 | } 119 | } 120 | } 121 | 122 | void step(uint8_t cycles) { 123 | cycleIndex += cycles; 124 | for (int i = 0; i < 3 * cycles; ++i) { 125 | Ppu_step(p); 126 | } 127 | } 128 | 129 | void rom_cycle(uint8_t cycles) { 130 | flush_events(); 131 | setPadStateFromMovie(); 132 | step(cycles); 133 | int req = interruptRequested; 134 | if (req != ROM_INTERRUPT_NONE) { 135 | interruptRequested = ROM_INTERRUPT_NONE; 136 | rom_start(req); 137 | } 138 | } 139 | 140 | void reshape_video(int width, int height) { 141 | int x_offset = 0; 142 | int y_offset = 0; 143 | 144 | double dWidth = width; 145 | double dHeight = height; 146 | double r = dHeight / dWidth; 147 | 148 | if (r > 0.9375) { // Height taller than ratio 149 | int h = 0.9375 * dWidth; 150 | y_offset = (height - h) / 2; 151 | height = h; 152 | } else if (r < 0.9375) { // Width wider 153 | double scrW, scrH; 154 | if (p->overscanEnabled) { 155 | scrW = 240.0; 156 | scrH = 224.0; 157 | } else { 158 | scrW = 256.0; 159 | scrH = 240.0; 160 | } 161 | 162 | int w = (scrH / scrW) * dHeight; 163 | x_offset = (width - w) / 2; 164 | width = w; 165 | } 166 | 167 | glViewport(x_offset, y_offset, width, height); 168 | glMatrixMode(GL_PROJECTION); 169 | glLoadIdentity(); 170 | glOrtho(-1, 1, -1, 1, -1, 1); 171 | glMatrixMode(GL_MODELVIEW); 172 | glLoadIdentity(); 173 | glDisable(GL_DEPTH_TEST); 174 | } 175 | 176 | void init_video() { 177 | if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) { 178 | fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError()); 179 | exit(1); 180 | } 181 | 182 | SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, !fast); // vsync 183 | v.screen = SDL_SetVideoMode(512, 480, 32, SDL_OPENGL|SDL_RESIZABLE); 184 | 185 | if (v.screen == NULL) { 186 | fprintf(stderr, "Unable to set SDL video mode: %s\n", SDL_GetError()); 187 | exit(1); 188 | } 189 | 190 | SDL_WM_SetCaption("jamulator", NULL); 191 | 192 | if (glewInit() != 0) { 193 | fprintf(stderr, "Unable to init glew\n"); 194 | exit(1); 195 | } 196 | 197 | glEnable(GL_TEXTURE_2D); 198 | reshape_video(v.screen->w, v.screen->h); 199 | v.pendingResize = false; 200 | 201 | glGenTextures(1, &v.tex); 202 | } 203 | 204 | void vblankInterrupt() { 205 | interruptRequested = ROM_INTERRUPT_NMI; 206 | } 207 | 208 | void render() { 209 | if (v.pendingResize) { 210 | reshape_video(v.pendingResizeWidth, v.pendingResizeHeight); 211 | v.pendingResize = false; 212 | } 213 | if (framebufferSlice == NULL || framebufferSize != p->framebufferSize) { 214 | if (framebufferSlice != NULL) free(framebufferSlice); 215 | framebufferSlice = malloc(p->framebufferSize * 3); 216 | framebufferSize = p->framebufferSize; 217 | } 218 | for (int i = 0; i < p->framebufferSize; ++i) { 219 | framebufferSlice[i*3+0] = (p->framebuffer[i] >> 16) & 0xff; 220 | framebufferSlice[i*3+1] = (p->framebuffer[i] >> 8) & 0xff; 221 | framebufferSlice[i*3+2] = p->framebuffer[i] & 0xff; 222 | } 223 | 224 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 225 | 226 | glBindTexture(GL_TEXTURE_2D, v.tex); 227 | 228 | int w = p->overscanEnabled ? 240 : 256; 229 | int h = p->overscanEnabled ? 224 : 240; 230 | glTexImage2D(GL_TEXTURE_2D, 0, 3, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, framebufferSlice); 231 | 232 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 233 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 234 | 235 | glBegin(GL_QUADS); 236 | glTexCoord2f(0.0, 1.0); 237 | glVertex3f(-1.0, -1.0, 0.0); 238 | glTexCoord2f(1.0, 1.0); 239 | glVertex3f(1.0, -1.0, 0.0); 240 | glTexCoord2f(1.0, 0.0); 241 | glVertex3f(1.0, 1.0, 0.0); 242 | glTexCoord2f(0.0, 0.0); 243 | glVertex3f(-1.0, 1.0, 0.0); 244 | glEnd(); 245 | 246 | if (v.screen != NULL) { 247 | SDL_GL_SwapBuffers(); 248 | SDL_Delay(0); 249 | } 250 | } 251 | 252 | void printUsage(char * command) { 253 | fprintf(stderr, "Usage:\n%s [-movie file] [-fast]\n", command); 254 | exit(1); 255 | } 256 | 257 | void parseFlags(int argc, char* argv[]) { 258 | for (int i = 1; i < argc; ++i) { 259 | char * arg = argv[i]; 260 | if (arg[0] == '-') { 261 | if (strcmp(arg, "-movie") == 0 && i < argc - 1) { 262 | movieFilename = argv[i + 1]; 263 | i += 1; 264 | } else if (strcmp(arg, "-fast") == 0) { 265 | fast = true; 266 | } else { 267 | printUsage(argv[0]); 268 | } 269 | } else { 270 | printUsage(argv[0]); 271 | } 272 | } 273 | } 274 | 275 | int main(int argc, char* argv[]) { 276 | parseFlags(argc, argv); 277 | loadMovie(); 278 | p = Ppu_new(); 279 | p->render = &render; 280 | p->vblankInterrupt = &vblankInterrupt; 281 | p->readRam = &rom_ram_read; 282 | Nametable_setMirroring(&p->nametables, rom_mirroring); 283 | assert(rom_chr_bank_count == 1); 284 | rom_read_chr(p->vram); 285 | init_video(); 286 | rom_start(ROM_INTERRUPT_RESET); 287 | Ppu_dispose(p); 288 | } 289 | 290 | uint8_t rom_ppu_read_status() { 291 | return Ppu_readStatus(p); 292 | } 293 | 294 | uint8_t rom_ppu_read_oamdata(){ 295 | return Ppu_readOamData(p); 296 | } 297 | uint8_t rom_ppu_read_data(){ 298 | return Ppu_readData(p); 299 | } 300 | 301 | void rom_ppu_write_control(uint8_t b) { 302 | Ppu_writeControl(p, b); 303 | } 304 | 305 | void rom_ppu_write_mask(uint8_t b) { 306 | Ppu_writeMask(p, b); 307 | } 308 | 309 | void rom_ppu_write_oamaddress(uint8_t b) { 310 | Ppu_writeOamAddress(p, b); 311 | } 312 | 313 | void rom_ppu_write_address(uint8_t b) { 314 | Ppu_writeAddress(p, b); 315 | } 316 | 317 | void rom_ppu_write_data(uint8_t b) { 318 | Ppu_writeData(p, b); 319 | } 320 | 321 | void rom_ppu_write_oamdata(uint8_t b) { 322 | Ppu_writeOamData(p, b); 323 | } 324 | 325 | void rom_ppu_write_scroll(uint8_t b) { 326 | Ppu_writeScroll(p, b); 327 | } 328 | void rom_ppu_write_dma(uint8_t b) { 329 | Ppu_writeDma(p, b); 330 | 331 | // Halt the CPU for 512 cycles 332 | step(255); 333 | step(255); 334 | step(2); 335 | } 336 | 337 | uint8_t rom_apu_read_status() { 338 | return 0; 339 | } 340 | void rom_apu_write_square1control(uint8_t b){} 341 | void rom_apu_write_square1sweeps(uint8_t b){} 342 | void rom_apu_write_square1low(uint8_t b){} 343 | void rom_apu_write_square1high(uint8_t b){} 344 | void rom_apu_write_square2control(uint8_t b){} 345 | void rom_apu_write_square2sweeps(uint8_t b){} 346 | void rom_apu_write_square2low(uint8_t b){} 347 | void rom_apu_write_square2high(uint8_t b){} 348 | void rom_apu_write_trianglecontrol(uint8_t b){} 349 | void rom_apu_write_trianglelow(uint8_t b){} 350 | void rom_apu_write_trianglehigh(uint8_t b){} 351 | void rom_apu_write_noisebase(uint8_t b){} 352 | void rom_apu_write_noiseperiod(uint8_t b){} 353 | void rom_apu_write_noiselength(uint8_t b){} 354 | void rom_apu_write_dmcflags(uint8_t b){} 355 | void rom_apu_write_dmcdirectload(uint8_t b){} 356 | void rom_apu_write_dmcsampleaddress(uint8_t b){} 357 | void rom_apu_write_dmcsamplelength(uint8_t b){} 358 | void rom_apu_write_controlflags1(uint8_t b){} 359 | void rom_apu_write_controlflags2(uint8_t b){} 360 | -------------------------------------------------------------------------------- /runtime/nametable.c: -------------------------------------------------------------------------------- 1 | #include "nametable.h" 2 | #include "rom.h" 3 | 4 | void Nametable_setMirroring(Nametable* n, int m) { 5 | n->mirroring = m; 6 | 7 | switch (m) { 8 | case ROM_MIRRORING_HORIZONTAL: 9 | n->logicalTables[0] = n->nametable0; 10 | n->logicalTables[1] = n->nametable0; 11 | n->logicalTables[2] = n->nametable1; 12 | n->logicalTables[3] = n->nametable1; 13 | break; 14 | case ROM_MIRRORING_VERTICAL: 15 | n->logicalTables[0] = n->nametable0; 16 | n->logicalTables[1] = n->nametable1; 17 | n->logicalTables[2] = n->nametable0; 18 | n->logicalTables[3] = n->nametable1; 19 | break; 20 | case ROM_MIRRORING_SINGLE_UPPER: 21 | n->logicalTables[0] = n->nametable0; 22 | n->logicalTables[1] = n->nametable0; 23 | n->logicalTables[2] = n->nametable0; 24 | n->logicalTables[3] = n->nametable0; 25 | break; 26 | case ROM_MIRRORING_SINGLE_LOWER: 27 | n->logicalTables[0] = n->nametable1; 28 | n->logicalTables[1] = n->nametable1; 29 | n->logicalTables[2] = n->nametable1; 30 | n->logicalTables[3] = n->nametable1; 31 | break; 32 | } 33 | } 34 | 35 | void Nametable_writeNametableData(Nametable* n, int a, uint8_t v) { 36 | n->logicalTables[(a&0xC00)>>10][a&0x3FF] = v; 37 | } 38 | 39 | uint8_t Nametable_readNametableData(Nametable* n, int a) { 40 | return n->logicalTables[(a&0xC00)>>10][a&0x3FF]; 41 | } 42 | -------------------------------------------------------------------------------- /runtime/nametable.h: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | 3 | typedef struct { 4 | int mirroring; 5 | uint8_t* logicalTables[4]; 6 | uint8_t nametable0[0x400]; 7 | uint8_t nametable1[0x400]; 8 | } Nametable; 9 | 10 | void Nametable_writeNametableData(Nametable* n, int a, uint8_t v); 11 | uint8_t Nametable_readNametableData(Nametable* n, int a); 12 | void Nametable_setMirroring(Nametable* n, int m); 13 | -------------------------------------------------------------------------------- /runtime/ppu.c: -------------------------------------------------------------------------------- 1 | #include "ppu.h" 2 | #include "stdlib.h" 3 | #include "string.h" 4 | #include "rom.h" 5 | 6 | uint32_t PPU_PALETTE_RGB[] = { 7 | 0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E, 8 | 0x6E0040, 0x6C0600, 0x561D00, 0x333500, 0x0B4800, 9 | 0x005200, 0x004F08, 0x00404D, 0x000000, 0x000000, 10 | 0x000000, 0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE, 11 | 0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00, 0x6B6D00, 12 | 0x388700, 0x0C9300, 0x008F32, 0x007C8D, 0x000000, 13 | 0x000000, 0x000000, 0xFFFEFF, 0x64B0FF, 0x9290FF, 14 | 0xC676FF, 0xF36AFF, 0xFE6ECC, 0xFE8170, 0xEA9E22, 15 | 0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE, 16 | 0x4F4F4F, 0x000000, 0x000000, 0xFFFEFF, 0xC0DFFF, 17 | 0xD3D2FF, 0xE8C8FF, 0xFBC2FF, 0xFEC4EA, 0xFECCC5, 18 | 0xF7D8A5, 0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC, 19 | 0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000, 20 | }; 21 | 22 | Ppu* Ppu_new() { 23 | Ppu* p = (Ppu*) malloc(sizeof(Ppu)); 24 | memset(p, 0, sizeof(Ppu)); 25 | 26 | p->registers.writeLatch = true; 27 | p->overscanEnabled = true; 28 | p->spriteLimitEnabled = true; 29 | p->scanline = 241; 30 | 31 | for (unsigned int i = 0; i < 0x400; ++i) { 32 | p->attributeShift[i] = ((i >> 4) & 0x04) | (i & 0x02); 33 | p->attributeLocation[i] = ((i >> 2) & 0x07) | (((i >> 4) & 0x38) | 0x3C0); 34 | } 35 | 36 | p->palettebufferSize = 0xf000; 37 | p->palettebuffer = malloc(sizeof(Pixel) * p->palettebufferSize); 38 | 39 | p->framebufferSize = 0xefe0; 40 | p->framebuffer = malloc(sizeof(uint32_t) * p->framebufferSize); 41 | 42 | return p; 43 | } 44 | 45 | void Ppu_dispose(Ppu* p) { 46 | free(p->palettebuffer); 47 | free(p->framebuffer); 48 | free(p); 49 | } 50 | 51 | // Writes to mirrored regions of VRAM 52 | void Ppu_writeMirroredVram(Ppu* p, int a, uint8_t v) { 53 | if (a >= 0x3F00) { 54 | if ((a & 0xF) == 0) { 55 | a = 0; 56 | } 57 | p->paletteRam[a&0x1F] = v; 58 | } else { 59 | Nametable_writeNametableData(&p->nametables, a-0x1000, v); 60 | } 61 | } 62 | 63 | // $2000 64 | void Ppu_writeControl(Ppu* p, uint8_t v) { 65 | p->registers.control = v; 66 | 67 | // Control flag 68 | // 7654 3210 69 | // |||| |||| 70 | // |||| ||++- Base nametable address 71 | // |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00) 72 | // |||| |+--- VRAM address increment per CPU read/write of PPUDATA 73 | // |||| | (0: increment by 1, going across; 1: increment by 32, going down) 74 | // |||| +---- Sprite pattern table address for 8x8 sprites 75 | // |||| (0: $0000; 1: $1000; ignored in 8x16 mode) 76 | // |||+------ Background pattern table address (0: $0000; 1: $1000) 77 | // ||+------- Sprite size (0: 8x8; 1: 8x16) 78 | // |+-------- PPU master/slave select (has no effect on the NES) 79 | // +--------- Generate an NMI at the start of the 80 | // vertical blanking interval (0: off; 1: on) 81 | p->flags.baseNametableAddress = v & 0x03; 82 | p->flags.vramAddressInc = (v >> 2) & 0x01; 83 | p->flags.spritePatternAddress = (v >> 3) & 0x01; 84 | p->flags.backgroundPatternAddress = (v >> 4) & 0x01; 85 | p->flags.spriteSize = (v >> 5) & 0x01; 86 | p->flags.nmiOnVblank = (v >> 7) & 0x01; 87 | 88 | int intBaseNametableAddr = p->flags.baseNametableAddress; 89 | p->registers.vramLatch = (p->registers.vramLatch & 0xF3FF) | (intBaseNametableAddr << 10); 90 | } 91 | 92 | // $2001 93 | void Ppu_writeMask(Ppu* p, uint8_t v) { 94 | p->registers.mask = v; 95 | 96 | // 76543210 97 | // |||||||| 98 | // |||||||+- Grayscale (0: normal color; 1: produce a monochrome display) 99 | // ||||||+-- 1: Show background in leftmost 8 pixels of screen; 0: Hide 100 | // |||||+--- 1: Show sprites in leftmost 8 pixels of screen; 0: Hide 101 | // ||||+---- 1: Show background 102 | // |||+----- 1: Show sprites 103 | // ||+------ Intensify reds (and darken other colors) 104 | // |+------- Intensify greens (and darken other colors) 105 | // +-------- Intensify blues (and darken other colors) 106 | p->masks.grayscale = ((v&0x01) == 0x01); 107 | p->masks.showBackgroundOnLeft = (((v >> 1) & 0x01) == 0x01); 108 | p->masks.showSpritesOnLeft = (((v >> 2) & 0x01) == 0x01); 109 | p->masks.showBackground = (((v >> 3) & 0x01) == 0x01); 110 | p->masks.showSprites = (((v >> 4) & 0x01) == 0x01); 111 | p->masks.intensifyReds = (((v >> 5) & 0x01) == 0x01); 112 | p->masks.intensifyGreens = (((v >> 6) & 0x01) == 0x01); 113 | p->masks.intensifyBlues = (((v >> 7) & 0x01) == 0x01); 114 | } 115 | 116 | // $4014 117 | void Ppu_writeDma(Ppu* p, uint8_t v) { 118 | // Fill sprite RAM 119 | int intV = v; 120 | int addr = intV * 0x100; 121 | for (int i = 0; i < 0x100; ++i) { 122 | uint8_t d = p->readRam(addr + i); 123 | p->spriteRam[i] = d; 124 | Ppu_updateBufferedSpriteMem(p, i, d); 125 | } 126 | } 127 | 128 | void Ppu_raster(Ppu* p) { 129 | int length = p->palettebufferSize; 130 | for (int i = length - 1; i >= 0; --i) { 131 | int y = i / 256; 132 | int x = i - (y * 256); 133 | 134 | uint32_t color = p->palettebuffer[i].color; 135 | 136 | int width = 256; 137 | 138 | if (p->overscanEnabled) { 139 | if (y < 8 || y > 231 || x < 8 || x > 247) { 140 | continue; 141 | } else { 142 | y -= 8; 143 | x -= 8; 144 | } 145 | 146 | width = 240; 147 | 148 | if (p->framebufferSize == 0xf000) { 149 | free(p->framebuffer); 150 | p->framebufferSize = 0xefe0; 151 | p->framebuffer = malloc(sizeof(uint32_t) * p->framebufferSize); 152 | } 153 | } else { 154 | if (p->framebufferSize == 0xefe0) { 155 | free(p->framebuffer); 156 | p->framebufferSize = 0xf000; 157 | p->framebuffer = malloc(sizeof(uint32_t) * p->framebufferSize); 158 | } 159 | } 160 | 161 | p->framebuffer[(y*width)+x] = color; 162 | p->palettebuffer[i].value = 0; 163 | p->palettebuffer[i].pindex = -1; 164 | } 165 | 166 | if (p->render != NULL) { 167 | p->render(); 168 | } 169 | } 170 | 171 | 172 | void Ppu_step(Ppu* p) { 173 | if (p->scanline == 240) { 174 | if (p->cycle == 1) { 175 | if (!p->suppressVbl) { 176 | // We're in VBlank 177 | Ppu_setStatus(p, STATUS_VBLANK_STARTED); 178 | p->cycleCount = 0; 179 | } 180 | if (p->flags.nmiOnVblank == 0x1 && !p->suppressNmi) { 181 | // Request NMI 182 | p->vblankInterrupt(); 183 | } 184 | Ppu_raster(p); 185 | } 186 | } else if (p->scanline == 260) { 187 | // End of vblank 188 | if (p->cycle == 1) { 189 | // Clear VBlank flag 190 | Ppu_clearStatus(p, STATUS_VBLANK_STARTED); 191 | p->cycleCount = 0; 192 | } else if(p->cycle == 341) { 193 | p->scanline = -1; 194 | p->cycle = 1; 195 | p->frameCount++; 196 | return; 197 | } 198 | } else if (p->scanline < 240 && p->scanline > -1) { 199 | if (p->cycle == 254) { 200 | if (p->masks.showBackground) { 201 | Ppu_renderTileRow(p); 202 | } 203 | 204 | if (p->masks.showSprites) { 205 | Ppu_evaluateScanlineSprites(p, p->scanline); 206 | } 207 | } else if (p->cycle == 256) { 208 | if (p->masks.showBackground) { 209 | Ppu_updateEndScanlineRegisters(p); 210 | } 211 | } 212 | } else if (p->scanline == -1) { 213 | if (p->cycle == 1) { 214 | Ppu_clearStatus(p, STATUS_SPRITE0HIT); 215 | Ppu_clearStatus(p, STATUS_SPRITE_OVERFLOW); 216 | } else if (p->cycle == 304) { 217 | // Copy scroll latch into VRAMADDR register 218 | if (p->masks.showBackground || p->masks.showSprites) { 219 | p->registers.vramAddress = p->registers.vramLatch; 220 | } 221 | } 222 | } 223 | 224 | if (p->cycle == 341) { 225 | p->cycle = 0; 226 | p->scanline++; 227 | } 228 | 229 | p->cycle++; 230 | p->cycleCount++; 231 | } 232 | 233 | 234 | void Ppu_updateEndScanlineRegisters(Ppu* p) { 235 | // ******************************************************* 236 | // TODO: Some documentation implies that the X increment 237 | // should occur 34 times per scanline. These may not be 238 | // necessary. 239 | // ******************************************************* 240 | 241 | // Flip bit 10 on wraparound 242 | if ((p->registers.vramAddress&0x1F) == 0x1F) { 243 | // If rendering is enabled, at the end of a scanline 244 | // copy bits 10 and 4-0 from VRAM latch into VRAMADDR 245 | p->registers.vramAddress ^= 0x41F; 246 | } else { 247 | p->registers.vramAddress++; 248 | } 249 | 250 | // Flip bit 10 on wraparound 251 | if ((p->registers.vramAddress&0x1F) == 0x1F) { 252 | // If rendering is enabled, at the end of a scanline 253 | // copy bits 10 and 4-0 from VRAM latch into VRAMADDR 254 | p->registers.vramAddress ^= 0x41F; 255 | } else { 256 | p->registers.vramAddress++; 257 | } 258 | 259 | if (p->masks.showBackground || p->masks.showSprites) { 260 | // Scanline has ended 261 | if ((p->registers.vramAddress&0x7000) == 0x7000) { 262 | int tmp = p->registers.vramAddress & 0x3E0; 263 | p->registers.vramAddress &= 0xFFF; 264 | 265 | if (tmp == 0x3A0) { 266 | p->registers.vramAddress ^= 0xBA0; 267 | } else if (tmp == 0x3e0) { 268 | p->registers.vramAddress ^= 0x3E0; 269 | } else { 270 | p->registers.vramAddress += 0x20; 271 | } 272 | } else { 273 | // Increment the fine-Y 274 | p->registers.vramAddress += 0x1000; 275 | } 276 | 277 | p->registers.vramAddress = (p->registers.vramAddress & 0x7BE0) | (p->registers.vramLatch & 0x41F); 278 | } 279 | } 280 | 281 | void Ppu_clearStatus(Ppu* p, uint8_t s) { 282 | uint8_t current = p->registers.status; 283 | 284 | switch (s) { 285 | case STATUS_SPRITE_OVERFLOW: 286 | current = current & 0xDF; 287 | break; 288 | case STATUS_SPRITE0HIT: 289 | current = current & 0xBF; 290 | break; 291 | case STATUS_VBLANK_STARTED: 292 | current = current & 0x7F; 293 | break; 294 | } 295 | 296 | p->registers.status = current; 297 | } 298 | 299 | void Ppu_setStatus(Ppu* p, uint8_t s) { 300 | int current = p->registers.status; 301 | 302 | switch (s) { 303 | case STATUS_SPRITE_OVERFLOW: 304 | current = current | 0x20; 305 | break; 306 | case STATUS_SPRITE0HIT: 307 | current = current | 0x40; 308 | break; 309 | case STATUS_VBLANK_STARTED: 310 | current = current | 0x80; 311 | break; 312 | } 313 | 314 | p->registers.status = current; 315 | } 316 | 317 | // $2002 318 | uint8_t Ppu_readStatus(Ppu* p) { 319 | p->registers.writeLatch = true; 320 | uint8_t s = p->registers.status; 321 | 322 | if (p->cycle == 1 && p->scanline == 240) { 323 | s &= 0x7F; 324 | p->suppressNmi = true; 325 | p->suppressVbl = true; 326 | } else { 327 | p->suppressNmi = false; 328 | p->suppressVbl = false; 329 | // Clear VBlank flag 330 | Ppu_clearStatus(p, STATUS_VBLANK_STARTED); 331 | } 332 | 333 | return s; 334 | } 335 | 336 | // $2003 337 | void Ppu_writeOamAddress(Ppu* p, uint8_t v) { 338 | p->registers.spriteRamAddress = v; 339 | } 340 | 341 | // $2004 342 | void Ppu_writeOamData(Ppu* p, uint8_t v) { 343 | p->spriteRam[p->registers.spriteRamAddress] = v; 344 | 345 | Ppu_updateBufferedSpriteMem(p, p->registers.spriteRamAddress, v); 346 | 347 | p->registers.spriteRamAddress++; 348 | p->registers.spriteRamAddress %= 0x100; 349 | } 350 | 351 | void Ppu_updateBufferedSpriteMem(Ppu* p, int a, uint8_t v) { 352 | int i = a / 4; 353 | 354 | switch (a % 4) { 355 | case 0x0: 356 | p->spriteData.yCoordinates[i] = v; 357 | break; 358 | case 0x1: 359 | p->spriteData.tiles[i] = v; 360 | break; 361 | case 0x2: 362 | // Attribute 363 | p->spriteData.attributes[i] = v; 364 | break; 365 | case 0x3: 366 | p->spriteData.xCoordinates[i] = v; 367 | break; 368 | } 369 | } 370 | 371 | 372 | // $2004 373 | uint8_t Ppu_readOamData(Ppu* p) { 374 | return p->spriteRam[p->registers.spriteRamAddress]; 375 | } 376 | 377 | 378 | // $2005 379 | void Ppu_writeScroll(Ppu* p, uint8_t v) { 380 | int intValue = v; 381 | if (p->registers.writeLatch) { 382 | p->registers.vramLatch = p->registers.vramLatch & 0x7FE0; 383 | p->registers.vramLatch = p->registers.vramLatch | ((intValue & 0xF8) >> 3); 384 | p->registers.fineX = v & 0x07; 385 | } else { 386 | p->registers.vramLatch = p->registers.vramLatch & 0xC1F; 387 | p->registers.vramLatch = p->registers.vramLatch | (((intValue & 0xF8) << 2) | ((intValue & 0x07) << 12)); 388 | } 389 | 390 | p->registers.writeLatch = !p->registers.writeLatch; 391 | } 392 | 393 | 394 | // $2006 395 | void Ppu_writeAddress(Ppu* p, uint8_t v) { 396 | int intValue = v; 397 | if (p->registers.writeLatch) { 398 | p->registers.vramLatch = p->registers.vramLatch & 0xFF; 399 | p->registers.vramLatch = p->registers.vramLatch | ((intValue & 0x3F) << 8); 400 | } else { 401 | p->registers.vramLatch = p->registers.vramLatch & 0x7F00; 402 | p->registers.vramLatch = p->registers.vramLatch | intValue; 403 | p->registers.vramAddress = p->registers.vramLatch; 404 | } 405 | 406 | p->registers.writeLatch = !p->registers.writeLatch; 407 | } 408 | 409 | 410 | // $2007 411 | void Ppu_writeData(Ppu* p, uint8_t v) { 412 | if (p->registers.vramAddress > 0x3000) { 413 | Ppu_writeMirroredVram(p, p->registers.vramAddress, v); 414 | } else if (p->registers.vramAddress >= 0x2000 && p->registers.vramAddress < 0x3000) { 415 | // Nametable mirroring 416 | Nametable_writeNametableData(&p->nametables, p->registers.vramAddress, v); 417 | } else { 418 | p->vram[p->registers.vramAddress&0x3FFF] = v; 419 | } 420 | 421 | Ppu_incrementVramAddress(p); 422 | } 423 | 424 | // $2007 425 | uint8_t Ppu_readData(Ppu* p) { 426 | uint8_t r; 427 | // Reads from $2007 are buffered with a 428 | // 1-byte delay 429 | if (p->registers.vramAddress >= 0x2000 && p->registers.vramAddress < 0x3000) { 430 | r = p->registers.vramDataBuffer; 431 | p->registers.vramDataBuffer = Nametable_readNametableData(&p->nametables, p->registers.vramAddress); 432 | } else if (p->registers.vramAddress < 0x3F00) { 433 | r = p->registers.vramDataBuffer; 434 | p->registers.vramDataBuffer = p->vram[p->registers.vramAddress]; 435 | } else { 436 | int bufferAddress = p->registers.vramAddress - 0x1000; 437 | if (bufferAddress >= 0x2000 && bufferAddress < 0x3000) { 438 | p->registers.vramDataBuffer = Nametable_readNametableData(&p->nametables, bufferAddress); 439 | } else { 440 | p->registers.vramDataBuffer = p->vram[bufferAddress]; 441 | } 442 | 443 | int a = p->registers.vramAddress; 444 | if ((a&0xF) == 0) { 445 | a = 0; 446 | } 447 | 448 | r = p->paletteRam[a&0x1F]; 449 | } 450 | 451 | Ppu_incrementVramAddress(p); 452 | 453 | return r; 454 | } 455 | 456 | 457 | void Ppu_incrementVramAddress(Ppu* p) { 458 | if (p->flags.vramAddressInc == 0x01) { 459 | p->registers.vramAddress = p->registers.vramAddress + 0x20; 460 | } else { 461 | p->registers.vramAddress = p->registers.vramAddress + 0x01; 462 | } 463 | } 464 | 465 | int Ppu_sprPatternTableAddress(Ppu* p, int i) { 466 | if ((p->flags.spriteSize&0x01) != 0x0) { 467 | // 8x16 Sprites 468 | if ((i & 0x01) != 0) { 469 | return 0x1000 | ((i >> 1) * 0x20); 470 | } else { 471 | return ((i >> 1) * 0x20); 472 | } 473 | 474 | } 475 | 476 | // 8x8 Sprites 477 | int a = p->flags.spritePatternAddress == 0x01 ? 0x1000 : 0x0; 478 | 479 | return i*0x10 + a; 480 | } 481 | 482 | int Ppu_bgPatternTableAddress(Ppu* p, uint8_t i) { 483 | int a = p->flags.backgroundPatternAddress == 0x01 ? 0x1000 : 0x0; 484 | return (i << 4) | (p->registers.vramAddress >> 12) | a; 485 | } 486 | 487 | void Ppu_fetchTileAttributes(Ppu* p, PpuTileAttributes* attrs) { 488 | // Load first two tiles into shift registers at start, then load 489 | // one per loop and shift the other back out 490 | 491 | int intAttrLoc = p->attributeLocation[p->registers.vramAddress&0x3FF]; 492 | int attrAddr = 0x23C0 | (p->registers.vramAddress & 0xC00) | intAttrLoc; 493 | unsigned int shift = p->attributeShift[p->registers.vramAddress&0x3FF]; 494 | uint8_t attr = ((Nametable_readNametableData(&p->nametables, attrAddr) >> shift) & 0x03) << 2; 495 | 496 | uint8_t index = Nametable_readNametableData(&p->nametables, p->registers.vramAddress); 497 | int t = Ppu_bgPatternTableAddress(p, index); 498 | 499 | // Flip bit 10 on wraparound 500 | if ((p->registers.vramAddress&0x1F) == 0x1F) { 501 | // If rendering is enabled, at the end of a scanline 502 | // copy bits 10 and 4-0 from VRAM latch into VRAMADDR 503 | p->registers.vramAddress ^= 0x41F; 504 | } else { 505 | p->registers.vramAddress++; 506 | } 507 | 508 | attrs->low = p->vram[t]; 509 | attrs->high = p->vram[t+8]; 510 | attrs->attr = attr; 511 | } 512 | 513 | void Ppu_renderTileRow(Ppu* p) { 514 | // Generates each tile, one scanline at a time 515 | // and applies the palette 516 | 517 | // Move first tile into shift registers 518 | PpuTileAttributes tileAttrs; 519 | Ppu_fetchTileAttributes(p, &tileAttrs); 520 | p->registers.lowBitShift = tileAttrs.low; 521 | p->registers.highBitShift = tileAttrs.high; 522 | uint8_t attr = tileAttrs.attr; 523 | 524 | Ppu_fetchTileAttributes(p, &tileAttrs); 525 | // Get second tile, move the pixels into the right side of 526 | // shift registers 527 | p->registers.lowBitShift = (p->registers.lowBitShift << 8) | tileAttrs.low; 528 | p->registers.highBitShift = (p->registers.highBitShift << 8) | tileAttrs.high; 529 | // Current tile to render is attrBuf 530 | uint8_t attrBuf = tileAttrs.attr; 531 | 532 | for (int x = 0; x < 32; x++) { 533 | int palette = 0; 534 | 535 | for (unsigned int b = 0; b < 8; b++) { 536 | int intB = b; 537 | int fbRow = p->scanline*256 + ((x * 8) + intB); 538 | 539 | unsigned int uintFineX = p->registers.fineX; 540 | uint16_t pixel = (p->registers.lowBitShift >> (15 - b - uintFineX)) & 0x01; 541 | pixel += ((p->registers.highBitShift >> (15 - b - uintFineX) & 0x01) << 1); 542 | 543 | // If we're grabbing the pixel from the high 544 | // part of the shift register, use the buffered 545 | // palette, not the current one 546 | if ((15 - b - uintFineX) < 8) { 547 | palette = Ppu_bgPaletteEntry(p, attrBuf, pixel); 548 | } else { 549 | palette = Ppu_bgPaletteEntry(p, attr, pixel); 550 | } 551 | 552 | if (p->palettebuffer[fbRow].value != 0) { 553 | // Pixel is already rendered and priority 554 | // 1 means show behind background 555 | continue; 556 | } 557 | 558 | p->palettebuffer[fbRow].color = PPU_PALETTE_RGB[palette%64]; 559 | p->palettebuffer[fbRow].value = pixel; 560 | p->palettebuffer[fbRow].pindex = -1; 561 | } 562 | 563 | // xcoord = p->registers.vramAddress & 0x1F 564 | attr = attrBuf; 565 | 566 | // Shift the first tile out, bring the new tile in 567 | Ppu_fetchTileAttributes(p, &tileAttrs); 568 | p->registers.lowBitShift = (p->registers.lowBitShift << 8) | tileAttrs.low; 569 | p->registers.highBitShift = (p->registers.highBitShift << 8) | tileAttrs.high; 570 | attrBuf = tileAttrs.attr; 571 | } 572 | } 573 | 574 | void Ppu_evaluateScanlineSprites(Ppu* p, int line) { 575 | int spriteCount = 0; 576 | 577 | for (int i = 0; i < 256; ++i) { 578 | uint8_t y = p->spriteData.yCoordinates[i]; 579 | int spriteHeight = 8; 580 | if ((p->flags.spriteSize&0x1) == 0x1) { 581 | spriteHeight = 16; 582 | } 583 | 584 | int intY = y; 585 | if (intY > (line-1)-spriteHeight && intY+(spriteHeight-1) < (line-1)+spriteHeight) { 586 | uint8_t attrValue = p->spriteData.attributes[i] & 0x3; 587 | uint8_t t = p->spriteData.tiles[i]; 588 | 589 | int c = (line - 1) - intY; 590 | 591 | int ycoord; 592 | bool yflip = ((p->spriteData.attributes[i]>>7)&0x1) == 0x1; 593 | int intYCoord = p->spriteData.yCoordinates[i]; 594 | if (yflip) { 595 | ycoord = intYCoord + ((spriteHeight - 1) - c); 596 | } else { 597 | ycoord = intYCoord + c + 1; 598 | } 599 | 600 | uint8_t entry[4]; 601 | Ppu_sprPaletteEntry(p, attrValue, entry); 602 | int s = Ppu_sprPatternTableAddress(p, t); 603 | bool sprite0 = i == 0; 604 | int t0Index; 605 | int t1Index; 606 | if ((p->flags.spriteSize&0x01) != 0x0) { 607 | // 8x16 Sprite 608 | int topStartIndex = s; 609 | int bottomStartIndex = s + 16; 610 | int tileStartIndex; 611 | if (c > 7 && yflip) { 612 | tileStartIndex = topStartIndex; 613 | ycoord += 8; 614 | } else if (c < 8 && yflip) { 615 | tileStartIndex = bottomStartIndex; 616 | ycoord -= 8; 617 | } else if (c > 7) { 618 | tileStartIndex = bottomStartIndex; 619 | } else { 620 | tileStartIndex = topStartIndex; 621 | } 622 | 623 | t0Index = tileStartIndex + c % 8; 624 | t1Index = tileStartIndex + (c % 8) + 8; 625 | } else { 626 | // 8x8 Sprite 627 | t0Index = s + c; 628 | t1Index = s + c + 8; 629 | } 630 | Ppu_decodePatternTile(p, p->vram[t0Index], p->vram[t1Index], 631 | p->spriteData.xCoordinates[i], 632 | ycoord, 633 | entry, 634 | &p->spriteData.attributes[i], sprite0, i); 635 | 636 | spriteCount++; 637 | 638 | if (spriteCount == 9) { 639 | if (p->spriteLimitEnabled) { 640 | Ppu_setStatus(p, STATUS_SPRITE_OVERFLOW); 641 | break; 642 | } 643 | } 644 | } 645 | } 646 | } 647 | 648 | void Ppu_decodePatternTile(Ppu* p, uint8_t t0, uint8_t t1, int x, int y, 649 | uint8_t* pal, uint8_t* attr, bool spZero, int index) 650 | { 651 | for (unsigned int b = 0; b < 8; ++b) { 652 | int xcoord = 0; 653 | int intB = b; 654 | if (((*attr>>6)&0x1) != 0) { 655 | xcoord = x + intB; 656 | } else { 657 | int reversedB = 7 - b; 658 | xcoord = x + reversedB; 659 | } 660 | 661 | // Don't wrap around if we're past the edge of the 662 | // screen 663 | if (xcoord > 255) { 664 | continue; 665 | } 666 | 667 | int fbRow = y*256 + xcoord; 668 | 669 | // Store the bit 0/1 670 | uint8_t pixel = (t0 >> b) & 0x01; 671 | pixel += ((t1 >> b & 0x01) << 1); 672 | 673 | bool trans = false; 674 | if (attr != NULL && pixel == 0) { 675 | trans = true; 676 | } 677 | 678 | // Set the color of the pixel in the buffer 679 | if (fbRow < 0xF000 && !trans) { 680 | uint8_t priority = (*attr >> 5) & 0x1; 681 | 682 | bool hit = ((p->registers.status&0x40) == 0x40); 683 | if (p->palettebuffer[fbRow].value != 0 && spZero && !hit) { 684 | // Since we render background first, if we're placing an opaque 685 | // pixel here and the existing pixel is opaque, we've hit 686 | // Sprite 0 687 | Ppu_setStatus(p, STATUS_SPRITE0HIT); 688 | } 689 | 690 | if (p->palettebuffer[fbRow].pindex > -1 && p->palettebuffer[fbRow].pindex < index) { 691 | // Pixel with a higher sprite priority (lower index) 692 | // is already here, so don't render this pixel 693 | continue; 694 | } else if (p->palettebuffer[fbRow].value != 0 && priority == 1) { 695 | // Pixel is already rendered and priority 696 | // 1 means show behind background 697 | // unless background pixel is not transparent 698 | continue; 699 | } 700 | 701 | int intPalPixel = pal[pixel]; 702 | p->palettebuffer[fbRow].color = PPU_PALETTE_RGB[intPalPixel%64]; 703 | p->palettebuffer[fbRow].value = pixel; 704 | p->palettebuffer[fbRow].pindex = index; 705 | } 706 | } 707 | } 708 | 709 | int Ppu_bgPaletteEntry(Ppu* p, uint8_t a, uint16_t pix) { 710 | if (pix == 0x0) { 711 | return p->paletteRam[0x00]; 712 | } 713 | 714 | switch (a) { 715 | case 0x0: 716 | return p->paletteRam[0x00+pix]; 717 | case 0x4: 718 | return p->paletteRam[0x04+pix]; 719 | case 0x8: 720 | return p->paletteRam[0x08+pix]; 721 | case 0xC: 722 | return p->paletteRam[0x0C+pix]; 723 | } 724 | 725 | return 0; 726 | } 727 | 728 | 729 | void Ppu_sprPaletteEntry(Ppu* p, unsigned int a, uint8_t* dest) { 730 | switch (a) { 731 | case 0x0: 732 | dest[0] = p->paletteRam[0x10]; 733 | dest[1] = p->paletteRam[0x11]; 734 | dest[2] = p->paletteRam[0x12]; 735 | dest[3] = p->paletteRam[0x13]; 736 | break; 737 | case 0x1: 738 | dest[0] = p->paletteRam[0x10]; 739 | dest[1] = p->paletteRam[0x15]; 740 | dest[2] = p->paletteRam[0x16]; 741 | dest[3] = p->paletteRam[0x17]; 742 | break; 743 | case 0x2: 744 | dest[0] = p->paletteRam[0x10]; 745 | dest[1] = p->paletteRam[0x19]; 746 | dest[2] = p->paletteRam[0x1A]; 747 | dest[3] = p->paletteRam[0x1B]; 748 | break; 749 | case 0x3: 750 | dest[0] = p->paletteRam[0x10]; 751 | dest[1] = p->paletteRam[0x1D]; 752 | dest[2] = p->paletteRam[0x1E]; 753 | dest[3] = p->paletteRam[0x1F]; 754 | break; 755 | } 756 | } 757 | -------------------------------------------------------------------------------- /runtime/ppu.h: -------------------------------------------------------------------------------- 1 | #include "stdbool.h" 2 | #include "stdint.h" 3 | #include "nametable.h" 4 | 5 | // TODO: namespace everything 6 | 7 | enum { 8 | STATUS_SPRITE_OVERFLOW, 9 | STATUS_SPRITE0HIT, 10 | STATUS_VBLANK_STARTED, 11 | }; 12 | 13 | typedef struct { 14 | uint8_t tiles[256]; 15 | uint8_t yCoordinates[256]; 16 | uint8_t attributes[256]; 17 | uint8_t xCoordinates[256]; 18 | } SpriteData; 19 | 20 | typedef struct { 21 | uint8_t baseNametableAddress; 22 | uint8_t vramAddressInc; 23 | uint8_t spritePatternAddress; 24 | uint8_t backgroundPatternAddress; 25 | uint8_t spriteSize; 26 | uint8_t masterSlaveSel; 27 | uint8_t nmiOnVblank; 28 | } Flags; 29 | 30 | typedef struct { 31 | uint32_t color; 32 | int value; 33 | int pindex; 34 | } Pixel; 35 | 36 | typedef struct { 37 | bool grayscale; 38 | bool showBackgroundOnLeft; 39 | bool showSpritesOnLeft; 40 | bool showBackground; 41 | bool showSprites; 42 | bool intensifyReds; 43 | bool intensifyGreens; 44 | bool intensifyBlues; 45 | } Masks; 46 | 47 | typedef struct { 48 | uint8_t control; 49 | uint8_t mask; 50 | uint8_t status; 51 | uint8_t vramDataBuffer; 52 | int vramAddress; 53 | int vramLatch; 54 | int spriteRamAddress; 55 | uint8_t fineX; 56 | uint8_t data; 57 | bool writeLatch; 58 | uint16_t highBitShift; 59 | uint16_t lowBitShift; 60 | } Registers; 61 | 62 | typedef struct { 63 | Registers registers; 64 | Flags flags; 65 | Masks masks; 66 | SpriteData spriteData; 67 | uint8_t vram[0xffff]; 68 | uint8_t spriteRam[0x100]; 69 | Nametable nametables; 70 | uint8_t paletteRam[0x20]; 71 | unsigned int attributeLocation[0x400]; 72 | unsigned int attributeShift[0x400]; 73 | bool a12High; 74 | 75 | Pixel *palettebuffer; 76 | int palettebufferSize; 77 | uint32_t *framebuffer; 78 | int framebufferSize; 79 | 80 | void (*render)(); 81 | void (*vblankInterrupt)(); 82 | uint8_t (*readRam)(uint16_t addr); 83 | 84 | int cycle; 85 | int scanline; 86 | int timestamp; 87 | int frameCount; 88 | int frameCycles; 89 | 90 | bool suppressNmi; 91 | bool suppressVbl; 92 | bool overscanEnabled; 93 | bool spriteLimitEnabled; 94 | 95 | int cycleCount; 96 | } Ppu; 97 | 98 | typedef struct { 99 | uint16_t low; 100 | uint16_t high; 101 | uint8_t attr; 102 | } PpuTileAttributes; 103 | 104 | // don't forget to call Ppu_dispose 105 | Ppu* Ppu_new(); 106 | void Ppu_dispose(Ppu* p); 107 | 108 | void Ppu_writeControl(Ppu* p, uint8_t v); 109 | void Ppu_writeMask(Ppu* p, uint8_t v); 110 | void Ppu_writeOamAddress(Ppu* p, uint8_t v); 111 | void Ppu_writeOamData(Ppu* p, uint8_t v); 112 | void Ppu_writeScroll(Ppu* p, uint8_t v); 113 | void Ppu_writeAddress(Ppu* p, uint8_t v); 114 | void Ppu_writeData(Ppu* p, uint8_t v); 115 | void Ppu_writeDma(Ppu* p, uint8_t v); 116 | 117 | uint8_t Ppu_readStatus(Ppu* p); 118 | uint8_t Ppu_readOamData(Ppu* p); 119 | uint8_t Ppu_readData(Ppu* p); 120 | 121 | void Ppu_raster(Ppu* p); 122 | void Ppu_step(Ppu* p); 123 | 124 | void Ppu_writeMirroredVram(Ppu* p, int a, uint8_t v); 125 | void Ppu_updateEndScanlineRegisters(Ppu* p); 126 | void Ppu_clearStatus(Ppu* p, uint8_t s); 127 | void Ppu_setStatus(Ppu* p, uint8_t s); 128 | void Ppu_updateBufferedSpriteMem(Ppu* p, int a, uint8_t v); 129 | void Ppu_incrementVramAddress(Ppu* p); 130 | int Ppu_sprPatternTableAddress(Ppu* p, int i); 131 | int Ppu_bgPatternTableAddress(Ppu* p, uint8_t i); 132 | int Ppu_bgPaletteEntry(Ppu* p, uint8_t a, uint16_t pix); 133 | void Ppu_renderTileRow(Ppu* p); 134 | void Ppu_fetchTileAttributes(Ppu* p, PpuTileAttributes* attrs); 135 | void Ppu_sprPaletteEntry(Ppu* p, unsigned int a, uint8_t* dest); 136 | void Ppu_evaluateScanlineSprites(Ppu* p, int line); 137 | void Ppu_decodePatternTile(Ppu* p, uint8_t t0, uint8_t t1, int x, int y, uint8_t* pal, uint8_t* attr, bool spZero, int index); 138 | -------------------------------------------------------------------------------- /runtime/rom.h: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | 3 | enum { 4 | ROM_MIRRORING_VERTICAL, 5 | ROM_MIRRORING_HORIZONTAL, 6 | ROM_MIRRORING_SINGLE_UPPER, 7 | ROM_MIRRORING_SINGLE_LOWER, 8 | }; 9 | 10 | enum { 11 | ROM_INTERRUPT_NONE, 12 | ROM_INTERRUPT_NMI, 13 | ROM_INTERRUPT_RESET, 14 | ROM_INTERRUPT_IRQ, 15 | }; 16 | 17 | enum { 18 | ROM_BUTTON_A, 19 | ROM_BUTTON_B, 20 | ROM_BUTTON_SELECT, 21 | ROM_BUTTON_START, 22 | ROM_BUTTON_UP, 23 | ROM_BUTTON_DOWN, 24 | ROM_BUTTON_LEFT, 25 | ROM_BUTTON_RIGHT, 26 | }; 27 | 28 | enum { 29 | ROM_PAD_STATE_OFF = 0x40, 30 | ROM_PAD_STATE_ON = 0x41, 31 | }; 32 | 33 | uint8_t rom_mirroring; 34 | uint8_t rom_chr_bank_count; 35 | 36 | // write the chr rom into dest 37 | void rom_read_chr(uint8_t* dest); 38 | 39 | // starts executing the PRG ROM. 40 | // this function returns when the RTI instruction is executed, 41 | // or the program exits. 42 | // when an interrupt occurs, call rom_start with the interrupt 43 | // index. 44 | void rom_start(uint8_t interrupt); 45 | 46 | // called after every instruction with the number of 47 | // cpu cycles that have passed. 48 | void rom_cycle(uint8_t); 49 | 50 | // PPU hooks 51 | uint8_t rom_ppu_read_status(); 52 | uint8_t rom_ppu_read_oamdata(); 53 | uint8_t rom_ppu_read_data(); 54 | void rom_ppu_write_control(uint8_t); 55 | void rom_ppu_write_mask(uint8_t); 56 | void rom_ppu_write_oamaddress(uint8_t); 57 | void rom_ppu_write_oamdata(uint8_t); 58 | void rom_ppu_write_scroll(uint8_t); 59 | void rom_ppu_write_address(uint8_t); 60 | void rom_ppu_write_data(uint8_t); 61 | void rom_ppu_write_dma(uint8_t); 62 | 63 | // APU hooks 64 | uint8_t rom_apu_read_status(); 65 | void rom_apu_write_square1control(uint8_t); 66 | void rom_apu_write_square1sweeps(uint8_t); 67 | void rom_apu_write_square1low(uint8_t); 68 | void rom_apu_write_square1high(uint8_t); 69 | void rom_apu_write_square2control(uint8_t); 70 | void rom_apu_write_square2sweeps(uint8_t); 71 | void rom_apu_write_square2low(uint8_t); 72 | void rom_apu_write_square2high(uint8_t); 73 | void rom_apu_write_trianglecontrol(uint8_t); 74 | void rom_apu_write_trianglelow(uint8_t); 75 | void rom_apu_write_trianglehigh(uint8_t); 76 | void rom_apu_write_noisebase(uint8_t); 77 | void rom_apu_write_noiseperiod(uint8_t); 78 | void rom_apu_write_noiselength(uint8_t); 79 | void rom_apu_write_dmcflags(uint8_t); 80 | void rom_apu_write_dmcdirectload(uint8_t); 81 | void rom_apu_write_dmcsampleaddress(uint8_t); 82 | void rom_apu_write_dmcsamplelength(uint8_t); 83 | void rom_apu_write_controlflags1(uint8_t); 84 | void rom_apu_write_controlflags2(uint8_t); 85 | 86 | // controller 87 | void rom_set_button_state(uint8_t padIndex, uint8_t buttonIndex, uint8_t value); 88 | 89 | // RAM 90 | uint8_t rom_ram_read(uint16_t addr); 91 | --------------------------------------------------------------------------------