From 4901241fefd5a8a5c0b1a13d189d46b2d062e804 Mon Sep 17 00:00:00 2001 From: Yaroslav Veremenko Date: Tue, 12 Feb 2019 11:48:09 -0700 Subject: [PATCH] Initial commit --- .gitignore | 8 + .vscode/keybindings.json | 10 + .vscode/settings.json | 15 ++ .vscode/tasks.json | 49 ++++ LICENSE | 339 +++++++++++++++++++++++++++ Makefile | 22 ++ README.md | 3 + assets/palette.pal | 1 + assets/tiles.chr | Bin 0 -> 8192 bytes nes.cfg | 21 ++ src/bf.asm | 61 +++++ src/bf.inc | 72 ++++++ src/boot.asm | 104 +++++++++ src/header.asm | 13 ++ src/lib.asm | 114 +++++++++ src/lib.inc | 47 ++++ src/lib_macroses.inc | 278 ++++++++++++++++++++++ src/main.asm | 41 ++++ src/nes.inc | 148 ++++++++++++ src/ppu.asm | 490 +++++++++++++++++++++++++++++++++++++++ src/ppu.inc | 106 +++++++++ 21 files changed, 1942 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/keybindings.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 assets/palette.pal create mode 100644 assets/tiles.chr create mode 100644 nes.cfg create mode 100644 src/bf.asm create mode 100644 src/bf.inc create mode 100644 src/boot.asm create mode 100644 src/header.asm create mode 100644 src/lib.asm create mode 100644 src/lib.inc create mode 100644 src/lib_macroses.inc create mode 100644 src/main.asm create mode 100644 src/nes.inc create mode 100644 src/ppu.asm create mode 100644 src/ppu.inc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ee8e77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.dbg +*.nes +*.o +game.map +src/dialogues/*_msg.inc +src/levels/*_data.inc +src/levels/levels_*.inc +src/levels/*.nam diff --git a/.vscode/keybindings.json b/.vscode/keybindings.json new file mode 100644 index 0000000..057212e --- /dev/null +++ b/.vscode/keybindings.json @@ -0,0 +1,10 @@ + +[{ + "key": "ctrl+r", + "command": "workbench.action.tasks.runTask", + "when": "run build" +},{ + "key": "ctrl+b", + "command": "workbench.action.tasks.runTask", + "when": "run debug" +}] \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ff95cf2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "editor.detectIndentation": false, + "editor.tabSize": 4, + "AllAutocomplete.languageSpecialCharacters": { + "inc": "^[\\.#]", + "asm": "^[\\.#]" + }, + "AllAutocomplete.languageWhitespace": { + "ca65": "[^\\w_\\u0080-\\uFFFF]+" + }, + "AllAutocomplete.wordListFiles": [ + "nes.inc" + ], + "editor.acceptSuggestionOnEnter": "smart" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f9e97fe --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,49 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "debug", + "type": "shell", + "command": "make run" + }, + { + "label": "build", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [{ + "owner": "cc65", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^(.*)\\((\\d+)\\):\\s+(Warning|Error):\\s+(.*)$", + "file": 1, + "line": 2, + "severity": 3, + "message": 4 + } + }, { + "owner": "cc65", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^(ld65.exe):\\s+(Warning|Error):\\s+(.*)$", + "file": 1, + "line": 1, + "severity": 2, + "message": 3 + } + }], + "presentation": { + "echo": true, + "reveal": "never", + "focus": false, + "panel": "shared", + "showReuseMessage": true + } + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..09cc2dc --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CX65 = ../cc65/bin +CC65 = $(CX65)/cc65.exe +CA65 = $(CX65)/ca65.exe +LD65 = $(CX65)/ld65.exe +EMU = ../tools/mesen/mesen.exe +LP = lprun + +all: game.nes + +clean: + del *.nes *.dbg + del src\*.o + +run: + $(EMU) game.nes + +%.o: src/%.asm + @ $(CA65) -g $< + +game.nes: header.o boot.o main.o ppu.o lib.o bf.o + @ $(LD65) --dbgfile game.dbg -m game.map -C nes.cfg -o game.nes src/boot.o src/main.o src/header.o src/ppu.o src/lib.o src/bf.o + diff --git a/README.md b/README.md new file mode 100644 index 0000000..554b276 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# NES Brainfuck + +Brainfuck VM for NES. Uses ca65 macroses to generate binary for supplied Brainfuck code. Read operator is not supported. \ No newline at end of file diff --git a/assets/palette.pal b/assets/palette.pal new file mode 100644 index 0000000..939190e --- /dev/null +++ b/assets/palette.pal @@ -0,0 +1 @@ +*'8881'  ) \ No newline at end of file diff --git a/assets/tiles.chr b/assets/tiles.chr new file mode 100644 index 0000000000000000000000000000000000000000..daeceb26712686fda28315328b46bd5b6b95d1bf GIT binary patch literal 8192 zcmeI1OKT%Z636qTlWF#-L$e1V22lv(Q)3um7Ev^XDC#qa9wUYw)6A(ci!h52iztLY zL?MPE5&jIqh(ZXVymKNyhnwk@pW-)^^4upJt>thCZK)ePNPiun)Zwb|+fD@h7qGjSt>; zsD5VbJ?zEKi1b{`Gm(yu^~@yCr#aex%1GtNh06}U%?no{zP$^L%T7CtAeU^1ZO$)@ zmYt;C{_Kp&B5sr37%rn&>zGM>g^Q$<%Nj*d3yl=m56(d^v27Z z%TK?oJ~?BzJL4*i)r)Sodw~j-_`8a~oe}k(cT)JTM0}f_6#gmM*aZsECw%6=oikFp zB!QJPSfBm12cX=hd2TO~j6^^dvl zU;cq)<aRh{wh=I!N=7=OlFcJ$U6H|erL@i*R5_f?@+?njFz z#s8F%$`PhUmFI@hA+o*Rw@4FqyM4%g^smq%a^3C~^ukKz3i>N&q;hwx7rsF-(G+Jy zIb^T2j4(!qVPEKI?Lq#M6_~DIz$v@k57*!KDZh~2M1G@lpMJoH_8G~153;cZ5!JJQ zM5On--4}BED%w8qg%=mkn9gs}k3vV0WBW&}Qy)WSeuw70gIx06 z+jrlQtnBrgv;ZQ%5G&0E<}|H1;4TQT{>LRZL4b81@mE5Rb)WUjeWQW(AMrmS;?FYn zKWwhLe+s9QQ1^eK_if$(m076!KNIUdwsPM8nOOJRy8kn=|6{49^Mw$ZV3+rQChDgj zwZr&c_kUxe`#%%L=tt{66NS(OF3&_F{9rv3`+ltZ{}Flrhs^%}Igs+g>EyaD=O3Z> zAL^Jts7IMy*Eqirm42*=|4jJ*f%HcBeIxdT@E>;Beaior}~-j5B8=MeHYkL)5&}?S-ZK>`^QNRxnwraIKOb={W{AYopGH{A4zYV`+Oz# zg~$y1&?{k=eq`TRh<$Hdqlu(v!WjLW5$UL!%QKM13$$U+Dcn=f5%=%KQ{Y-rwu|XCm$c(i;)~vDgm|p-`Ea;*8)KS24srg6UOAW#IKOb4rt1OD|Hl1j{78D^ zCLOKBzHmPnz&`X!*qv+r#zO3;#&wGGKk_kmX$pHgXGHo!%QKMN_wXF zJy!q6ln%c+KU4f?J^UZ5pZCAvNaYdDe|)@u3hmx+EbsmH4)A*a|2^IR5&xir`C4*1 zsfU>V*#Cv)z2Dve{=fS_*8is17h?T~-3{&kOw?P~`i<2?&os;6DE1HO?qSK z8?o3ICRq*kY5xo19Hi1Q{()?5zb|`TFn{nasDH{aftJu*caa8+*hwduY_G(UATVZ7qRb+`ydFY ze&!hGz948iBht58o{4n$!+PdD&VLQG|C9}t8(2S=0rh{$@w`s)-x$+njp7fz^07|w z*`&g4x;&@&8~3ACjr7J%x@d}h;ePN4`_L<4x32XYzY+UO<2l72`Ix(O341Zte<9Kv zTAqn?_``bU>Am0H0bcI^p--0{?JYSS&gJ~4^eN82bpJ69aQ>zI!pNso*o+KQ*r9{FgFgpTZuEZz0Mrv^*2(@Q3xxBpM{)_l4_l+jre-U5hCTQTkPWgqH zEU*u~5_a+K%k>-a?#r?f@4kpX>=QBgVGr?DBK`IL?`b^BJ3x%VBJmeu-H%`I|NQ@t vqjvzl|GeJ+^Vj=7=1nT=r}FjwFZX_X2Y9{zzux~x_c(vi`v>v&b^rf2G(Vr= literal 0 HcmV?d00001 diff --git a/nes.cfg b/nes.cfg new file mode 100644 index 0000000..d3d240d --- /dev/null +++ b/nes.cfg @@ -0,0 +1,21 @@ +SYMBOLS { + __STACKSIZE__: type = weak, value = $0300; # 3 pages stack +} +MEMORY { + ZP: file = "", start = $0000, size = $00FF, type = rw, define = yes; + HEADER: file = %O, start = $0000, size = $0010, fill = yes; + PRG: file = %O, start = $8000, size = $8000, fill = yes, define = yes; + CHR: file = %O, start = $0000, size = $2000, fill = yes, define = yes; + SPRITE_RAM: file = "", start = $0200, size = $0100, define = yes; + RAM: file = "", start = $0300, size = $0500, define = yes; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + HEADER: load = HEADER, type = ro; + CODE: load = PRG, type = ro, define = yes; + RODATA: load = PRG, type = ro, define = yes; + VECTORS: load = PRG, type = rw, start = $fffa; + OAM: load = SPRITE_RAM type = rw; + CHR: load = CHR type = ro; + BSS: load = RAM, type = bss, define = yes; +} \ No newline at end of file diff --git a/src/bf.asm b/src/bf.asm new file mode 100644 index 0000000..b7d3d87 --- /dev/null +++ b/src/bf.asm @@ -0,0 +1,61 @@ +.include "lib.inc" +.include "ppu.inc" +.include "nes.inc" +.include "bf.inc" + +.segment "BSS" + BF_ram: .res 1024 +.segment "ZEROPAGE" + BF_ptr: .res 2 + BF_line: .res 1 + +.segment "CODE" +.proc BF_incp + inc BF_ptr+0 + bne :+ + inc BF_ptr+1 + : + rts +.endproc +.proc BF_decp + dec BF_ptr+0 + bne :+ + dec BF_ptr+1 + : + rts +.endproc +.proc BF_inc + ldy #0 + lda (BF_ptr),y + clc + adc #1 + sta (BF_ptr),y + rts +.endproc +.proc BF_dec + ldy #0 + lda (BF_ptr),y + sec + sbc #1 + sta (BF_ptr),y + rts +.endproc +.proc BF_print + ldy #0 + lda (BF_ptr),y + cmp #$0A + bne :+ + lda BF_line + add #1 + sta BF_line + call ppu_SetAddr, #>PPU_ADDR_NAMETABLE1, #1, BF_line + jmp :++ + : + sta PPU_DATA + : + rts +.endproc +.proc BF_read + rts +.endproc + diff --git a/src/bf.inc b/src/bf.inc new file mode 100644 index 0000000..131b63d --- /dev/null +++ b/src/bf.inc @@ -0,0 +1,72 @@ +.ifndef BF_INC +.define BF_INC + +.globalzp BF_ptr, BF_line +.global BF_ram +.global BF_incp +.global BF_decp +.global BF_inc +.global BF_dec +.global BF_print +.global BF_read + +BF_MAX_SOURCE_LENGTH = 1024 + +bflabel .set 0 +bfstack .set 0 +.macro BF_compile src + lda #BF_ram + sta BF_ptr+1 + lda #3 + sta BF_line + m_ppu_BeginWrite + call ppu_FillNameTable, #>PPU_ADDR_NAMETABLE1, #$20, #0 + call ppu_SetAddr, #>PPU_ADDR_NAMETABLE1, #1, BF_line + .if .strlen(src) > BF_MAX_SOURCE_LENGTH + .error .sprintf("Brainfuck source is too long. Max ", BF_MAX_SOURCE_LENGTH) + .endif + .repeat BF_MAX_SOURCE_LENGTH, i + .if i < .strlen(src) + .if .strat(src, i) = '>' + jsr BF_incp + .endif + .if .strat(src, i) = '<' + jsr BF_decp + .endif + .if .strat(src, i) = '+' + jsr BF_inc + .endif + .if .strat(src, i) = '-' + jsr BF_dec + .endif + .if .strat(src, i) = '.' + jsr BF_print + .endif + .if .strat(src, i) = ',' + jsr BF_read + .endif + .if .strat(src, i) = '[' + .ident(.concat("BF_LABEL", .sprintf("%d", bflabel))): + ldy #0 + lda (BF_ptr),y + bne :+ + jmp .ident(.concat("BF_LABEL_END", .sprintf("%d", bflabel))) + : + ; put label onto stack + bfstack .set bfstack + 1 + .ident(.concat("bfstack", .sprintf("%d", bfstack))) .set bflabel + bflabel .set bflabel + 1 + .endif + .if .strat(src, i) = ']' + jmp .ident(.concat("BF_LABEL", .sprintf("%d", .ident(.concat("bfstack", .sprintf("%d", bfstack)))))) + .ident(.concat("BF_LABEL_END", .sprintf("%d", .ident(.concat("bfstack", .sprintf("%d", bfstack)))))): + bfstack .set bfstack - 1 + .endif + .endif + .endrep + rts +.endmacro + +.endif \ No newline at end of file diff --git a/src/boot.asm b/src/boot.asm new file mode 100644 index 0000000..3e5fa09 --- /dev/null +++ b/src/boot.asm @@ -0,0 +1,104 @@ +.include "nes.inc" +.include "ppu.inc" + +.import main + +.segment "CODE" +reset: + sei ; ignore IRQs + cld ; disable decimal mode + ldx #APU_FRAMECT_IRQ_DISABLE + stx APU_FRAMECT ; disable APU frame IRQ + ldx #$ff + txs ; Set up stack + inx ; now X = 0 + stx PPU_CTRL ; disable NMI + stx PPU_MASK ; disable rendering + stx APU_MODCTRL ; disable DMC IRQs + + ; Optional (omitted): + ; Set up mapper and jmp to further init code here. + + ; If the user presses Reset during vblank, the PPU may reset + ; with the vblank flag still true. This has about a 1 in 13 + ; chance of happening on NTSC or 2 in 9 on PAL. Clear the + ; flag now so the @vblankwait1 loop sees an actual vblank. + bit PPU_STATUS + + ; First of two waits for vertical blank to make sure that the + ; PPU has stabilized + : + bit PPU_STATUS + bpl :- + + ; Clear ram + txa + : + sta $000,x + sta $100,x + sta $300,x + sta $400,x + sta $500,x + sta $600,x + sta $700,x + inx + bne :- + ; Clear OAM region + lda #$ff + : + sta $200,x + inx + bne :- + + : + bit PPU_STATUS + bpl :- + + ; Enable NMI in order for ppu_On and ppu_Off to work + lda PPU_STATUS ; Prevent immediate NMIs + lda #PPU_CTRL_NMI_ON + sta PPU_CTRL + + lda #0 + jmp main + +; ----------------------------------------------------------------------------- +; V-Blank interupt handler +nmi: + pushseg + ; Write to OAM DMA + lda ppu_needOam + beq @oamEnd + mova ppu_needOam, #0 ; reset OAM flag + lda #ppu_oam + sta APU_SPR_DMA + @oamEnd: + + lda ppu_hasBuffer + beq @bufferEnd + jsr ppu_OutputBuffer + @bufferEnd: + ; Fix scroll + mova PPU_SCROLL, ppu_scrollx + mova PPU_SCROLL, ppu_scrolly + + @end: + m_ppu_SetNmiDone + popseg + rti + +irq: + rti + +.proc lib_SwitchBank + and #$0F + sta $8000 + rts +.endproc + +.segment "VECTORS" + .word nmi + .word reset + .word irq diff --git a/src/header.asm b/src/header.asm new file mode 100644 index 0000000..b6ec13c --- /dev/null +++ b/src/header.asm @@ -0,0 +1,13 @@ +.segment "HEADER" + ; iNES header + ; see http://wiki.nesdev.com/w/index.php/INES + .byte $4e, $45, $53, $1a ; "NES" followed by MS-DOS EOF + .byte $02 ; size of PRG ROM in 16 KiB units + .byte $01 ; size of CHR ROM in 8 KiB units + .byte $30 ; horizontal mirroring, mapper 003 (CNROM) + .byte $00 ; mapper 003 (CNROM) + + .byte $00 ; size of PRG RAM in 8 KiB units + .byte $00 ; NTSC + .byte $00 ; unused + .res 5, $00 \ No newline at end of file diff --git a/src/lib.asm b/src/lib.asm new file mode 100644 index 0000000..8923315 --- /dev/null +++ b/src/lib.asm @@ -0,0 +1,114 @@ +.include "lib.inc" +.include "nes.inc" + +.SEGMENT "ZEROPAGE" + lib_seed: .res 2 ; initialize 16-bit seed to any value except 0 + controls_pad1: .res 1 + controls_pad1_pressed: .res 1 + ; P/Pointer pseudo 16-bit register + p: + pl: .res 1 + ph: .res 1 + + r1: .res 1 + r2: .res 1 + + r3: .res 1 + r4: .res 1 + + ptr1: + r5: .res 1 + r6: .res 1 + + ptr2: + r7: .res 1 + r8: .res 1 + + ptr3: + r9: .res 1 + r10: .res 1 + + ptr4: + r11: .res 1 + r12: .res 1 + + ptr5: + r13: .res 1 + r14: .res 1 + + ptr6: + r15: .res 1 + r16: .res 1 + +.SEGMENT "CODE" + +; ----------------------------------------------------------------------------- +; Initialize random generator SEED +; USE: A +.proc lib_Init + ; Init random generator + lda #$2F + sta lib_seed + lda #$C0 + sta lib_seed+1 + rts +.endproc + +; ----------------------------------------------------------------------------- +; Returns a random 8-bit number in A (0-255), clobbers X (0). +; OUT: A - random number +; USE: X +; +; Requires a 2-byte value on the zero page called "seed". +; Initialize seed to any value except 0 before the first call to prng. +; (A seed value of 0 will cause prng to always return 0.) +; +; This is a 16-bit Galois linear feedback shift register with polynomial $002D. +; The sequence of numbers it generates will repeat after 65535 calls. +; +; Execution time is an average of 125 cycles (excluding jsr and rts) +.proc lib_Rand + ldx #8 ; iteration count (generates 8 bits) + lda lib_seed+0 +@loop: + asl ; shift the register + rol lib_seed+1 + bcc @noFeedback + eor #$2D ; apply XOR feedback whenever a 1 bit is shifted out +@noFeedback: + dex + bne @loop + sta lib_seed+0 + cmp #0 ; reload flags + rts +.endproc + +; ----------------------------------------------------------------------------- +; Read Joystick 1 +; OUT: .zp controls_pad1 +; USE: A +.proc controls_ReadPad1 + ; save previous frame buttons + mova controls_pad1_pressed, controls_pad1 + lda #$01 + ; While the strobe bit is set, buttons will be continuously reloaded. + ; This means that reading from JOYPAD1 will only return the state of the + ; first button: button A. + sta APU_PAD1 + sta controls_pad1 + lsr a ; now A is 0 + ; By storing 0 into APU_PAD1, the strobe bit is cleared and the reloading + ; stops. + ; This allows all 8 buttons (newly reloaded) to be read from APU_PAD1. + sta APU_PAD1 + @loop: + lda APU_PAD1 + lsr a ; bit0 -> Carry + rol controls_pad1 ; Carry -> bit0; bit 7 -> Carry + bcc @loop + lda controls_pad1_pressed + eor #$FF + and controls_pad1 + sta controls_pad1_pressed + rts +.endproc diff --git a/src/lib.inc b/src/lib.inc new file mode 100644 index 0000000..9ec45b2 --- /dev/null +++ b/src/lib.inc @@ -0,0 +1,47 @@ +.ifndef LIB_INC +LIB_INC .set 1 + +.include "lib_macroses.inc" + +; ----------------------------------------------------------------------------- +; Exports + +.global lib_Rand +.global lib_Init +.global controls_ReadPad1, controls_ReadPad2 + +.globalzp lib_seed +.globalzp controls_pad1, controls_pad1_pressed +.globalzp p, pl, ph + +.globalzp r1 +.globalzp r2 + +.globalzp r3 +.globalzp r4 + +.globalzp ptr1 +.globalzp r5 +.globalzp r6 + +.globalzp ptr2 +.globalzp r7 +.globalzp r8 + +.globalzp ptr3 +.globalzp r9 +.globalzp r10 + +.globalzp ptr4 +.globalzp r11 +.globalzp r12 + +.globalzp ptr5 +.globalzp r13 +.globalzp r14 + +.globalzp ptr6 +.globalzp r15 +.globalzp r16 + +.endif \ No newline at end of file diff --git a/src/lib_macroses.inc b/src/lib_macroses.inc new file mode 100644 index 0000000..514b710 --- /dev/null +++ b/src/lib_macroses.inc @@ -0,0 +1,278 @@ +.ifndef LIB_MACROSES_INC +LIB_MACROSES_INC .set 1 + +; Enable negative constants +.feature force_range +.macpack longbranch + +; ----------------------------------------------------------------------------- +; Macroses + +.macro pushseg + pusha a, x, y +.endmacro + +.macro popseg + popa a, x, y +.endmacro + +.macro stackalloc ident, size + .ifdef _stackParamPos + .error "stackalloc must be called before stackparam" + .endif + .if .blank ({size}) + stackalloc {ident}, 1 + .exitmac + .endif + + .ifdef _stackVarPos + _stackVarPos .set _stackVarPos + size + _stackVarCount .set _stackVarCount + size + .else + _stackVarPos .set $101 + _stackVarCount .set size + .endif + ident := _stackVarPos + pha +.endmacro + +.macro stackfree + .ifndef _stackVarCount + .error "stackalloc was not used" + .endif + .repeat _stackVarCount + pla + .endrepeat +.endmacro + +.macro stackparam ident + .ifdef _stackParamPos + _stackParamPos .set (_stackParamPos) + 1 + .else + .ifdef _stackVarPos + _stackParamPos .set (_stackVarPos) + 3 ; to preserve return address + .else + _stackParamPos .set $103 + .endif + .endif + ident := _stackParamPos +.endmacro + +.macro decl type, ident + .if (.not .match({type}, "stack")) .and (.not .match({type}, "reg")) + .error "Incorrect call type" + .endif + ;calltype_{ident} .set {type} + .if (.strat(type, 0) = 's') + .ident(.concat("calltype_stack", .string(ident))) := 0 + .else + .ident(.concat("calltype_reg", .string(ident))) := 0 + .endif + .global ident +.endmacro + +.macro call ident, p1, p2, p3, p4, p5, p6 + .ifdef .ident (.concat("calltype_stack", .string(ident))) + stackcall {ident}, {p1}, {p2}, {p3}, {p4}, {p5}, {p6} + .elseif .defined(.ident (.concat("calltype_reg", .string(ident)))) + regcall {ident}, {p1}, {p2}, {p3}, {p4}, {p5}, {p6} + .else + .error "calltype is not defined" + .endif +.endmacro + +.macro stackcall ident, p1, p2, p3, p4, p5, p6 + ; I don't know how to make .repeat with decreasing numbers + .ifnblank p6 + pusha {p6} + .endif + .ifnblank p5 + pusha {p5} + .endif + .ifnblank p4 + pusha {p4} + .endif + .ifnblank p3 + pusha {p3} + .endif + .ifnblank p2 + pusha {p2} + .endif + .ifnblank p1 + pusha {p1} + .endif + jsr ident + .ifnblank p6 + pla + .endif + .ifnblank p5 + pla + .endif + .ifnblank p4 + pla + .endif + .ifnblank p3 + pla + .endif + .ifnblank p2 + pla + .endif + .ifnblank p1 + pla + .endif +.endmacro + +.macro regcall ident, ar, xr, yr, p1, p2, p3 + .ifnblank p1 + lda p1 + sta r1 + .endif + .ifnblank p2 + lda p2 + sta r1 + .endif + .ifnblank p3 + lda p3 + sta r1 + .endif + .ifnblank ar + lda ar + .endif + .ifnblank xr + ldx xr + .endif + .ifnblank yr + ldy yr + .endif + jsr ident +.endmacro + +; ----------------------------------------------------------------------------- +; mov and push macroses +; SOURCE: https://forums.nesdev.com/viewtopic.php?f=2&t=11112&start=15#p127422 + +.macro mova dest, src + lda src + sta dest +.endmacro + +.macro pusha a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 + .ifnblank a0 + .if .xmatch({a0},p) .or .xmatch({a0},P) + php + .else + .if .match({a0},x) + txa + .elseif .match({a0},y) + tya + .elseif .match(.left(1,{a0}),=) + lda #>(.right(.tcount({a0})-1,{a0})) + pha + lda #<(.right(.tcount({a0})-1,{a0})) + .elseif !(.match({a0},a)) + lda a0 + .endif + pha + .endif + pusha a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 + .endif +.endmacro + +.macro popa a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 + .ifnblank a0 + popa a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 + .if .xmatch({a0},p) .or .xmatch({a0},P) + plp + .else + pla + .if .match({a0},x) + tax + .elseif .match({a0},y) + tay + .elseif !(.match({a0},a)) + sta a0 + .endif + .endif + .endif +.endmacro + +; ----------------------------------------------------------------------------- + +.macro movwa dest, src + ;.local sepd, seps + sepd .set 0 + seps .set 0 + + .if .match({.right(2,{dest})},{,x}) .or .match({.right(2,{dest})},{,y}) + sepd .set 2 + .endif + .if .match({.right(2,{src})},{,x}) .or .match({.right(2,{src})},{,y}) + seps .set 2 + .endif + + .if .match(.left(1, {src}),#) + mova {.left(.tcount({dest})-sepd,dest)+0 .right(sepd,dest)}, #<(.right(.tcount({src})-1,{src})) + mova {.left(.tcount({dest})-sepd,dest)+1 .right(sepd,dest)}, #>(.right(.tcount({src})-1,{src})) + .else + mova {.left(.tcount({dest})-sepd,dest)+0 .right(sepd,dest)}, {.left(.tcount({src})-seps,src)+0 .right(seps,src)} + mova {.left(.tcount({dest})-sepd,dest)+1 .right(sepd,dest)}, {.left(.tcount({src})-seps,src)+1 .right(seps,src)} + .endif +.endmacro + +.macro add Arg ; add without carry + clc + adc Arg +.endmacro + +.macro sub Arg ; subtract without borrow + sec + sbc Arg +.endmacro + +.macro bge Arg ; branch on greater-than or equal + bcs Arg +.endmacro + +.macro jge Arg ; branch on greater-than or equal + jcs Arg +.endmacro + +.macro blt Arg ; branch on less-than + bcc Arg +.endmacro + +.macro jlt Arg ; branch on less-than + jcc Arg +.endmacro + +.macro bgt Arg ; branch on greater-than + beq :+ + bcs Arg +: +.endmacro + +.macro jgt Arg ; branch on greater-than + beq :+ + jcs Arg +: +.endmacro + +.macro ble Arg ; branch on less-than or equal + beq Arg + bcc Arg +.endmacro + +.macro jle Arg ; branch on less-than or equal + jeq Arg + jcc Arg +.endmacro + +.macro bnz Arg ; branch on not zero + bne Arg +.endmacro + +.macro bze Arg ; branch on zero + beq Arg +.endmacro + +.endif \ No newline at end of file diff --git a/src/main.asm b/src/main.asm new file mode 100644 index 0000000..d5c4e36 --- /dev/null +++ b/src/main.asm @@ -0,0 +1,41 @@ +.include "nes.inc" +.include "ppu.inc" +.include "lib.inc" +.include "bf.inc" + +.global main + +BF_run: +; Hello World +; BF_compile "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." + +; Factorial +BF_compile "+++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++>+++++++>>+<<[>++++++++++++++++++++++++++++++++++++++++++++++++.------------------------------------------------<<<<.-.>.<.+>>>>>>>++++++++++<<[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>[<+>-]>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>+>>]<<<<<]>[-]>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<.>>+>[>>+<<-]>>[<<<[>+>+<<-]>>[<<+>>-]>-]<<<<-]" + +.proc main + jsr ppu_Off + jsr lib_Init + lda #(PPU_CTRL_NMI_ON | PPU_CTRL_BG_ADDR_0 | PPU_CTRL_SPR_ADDR_1) + sta ppu_ctrl + lda #(PPU_MASK_SPR_ON | PPU_MASK_SPR_LEFT_ON | PPU_MASK_BG_ON | PPU_MASK_BG_LEFT_ON) + sta ppu_mask + + call ppu_LoadPallete, #Palette + + jsr BF_run + jsr ppu_ClearSprites + jsr ppu_On +@loop: + jmp @loop +.endproc + +.segment "RODATA" + ; Build info + ; started this project on Mon 2019-02-12 + BUILDDAY = (.TIME / 86400) - 17939 + .out .sprintf("Build(0): Info: %d days since beginning of the project", BUILDDAY) +Palette: + .incbin "assets/palette.pal" + .incbin "assets/palette.pal" +.segment "CHR" + .incbin "assets/tiles.chr" \ No newline at end of file diff --git a/src/nes.inc b/src/nes.inc new file mode 100644 index 0000000..5b4bc27 --- /dev/null +++ b/src/nes.inc @@ -0,0 +1,148 @@ +; +; NES definitions. By Groepaz/Hitmem & Yaroslav Veremenko (c) 2018 +; + +;; FIXME: optimize zeropage usage + +SCREEN_PTR = $62 ;2 +CRAM_PTR = $64 ;2 +CHARCOLOR = $66 +BGCOLOR = $67 +RVS = $68 +CURS_X = $69 +CURS_Y = $6a + +tickcount = $6b ;2 + +VBLANK_FLAG = $70 + +ringbuff = $0200 +ringwrite = $71 +ringread = $72 +ringcount = $73 + +ppuhi = $74 +ppulo = $75 +ppuval = $76 + +screenrows = (30-1) +charsperline = 32 +xsize = charsperline + +;; PPU defines + +PPU_CTRL = $2000 +; |||| |||| +; |||| ||++- Base nametable address +; |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00) +; |||| |+--- VRAM address increment per CPU read/write of PPUDATA +; |||| | (0: add 1, going across; 1: add 32, going down) +; |||| +---- Sprite pattern table address for 8x8 sprites +; |||| (0: $0000; 1: $1000; ignored in 8x16 mode) +; |||+------ Background pattern table address (0: $0000; 1: $1000) +; ||+------- Sprite size (0: 8x8; 1: 8x16) +; |+-------- PPU master/slave select +; | (0: read backdrop from EXT pins; 1: output color on EXT pins) +; +--------- Generate an NMI at the start of the +; vertical blanking interval (0: off; 1: on) + PPU_CTRL_INREMENT_V = $04 + PPU_CTRL_INREMENT_H = $00 + PPU_CTRL_SPR_ADDR_0 = $00 + PPU_CTRL_SPR_ADDR_1 = $08 + PPU_CTRL_BG_ADDR_0 = $00 + PPU_CTRL_BG_ADDR_1 = $10 + PPU_CTRL_SPR_8x8 = $00 + PPU_CTRL_SPR_8x16 = $40 + PPU_CTRL_NMI_ON = $80 +PPU_MASK = $2001 + PPU_MASK_GREYSCALE = $01 ;Greyscale (0: normal color, 1: produce a greyscale display) + PPU_MASK_BG_LEFT_ON = $02 ;Show background in leftmost 8 pixels of screen, 0: Hide + PPU_MASK_SPR_LEFT_ON = $04 ;Show sprites in leftmost 8 pixels of screen, 0: Hide + PPU_MASK_BG_ON = $08 ;Show background + PPU_MASK_SPR_ON = $10 ;Show sprites + PPU_MASK_EMPH_RED = $20 ;Emphasize red* + PPU_MASK_EMPH_GREEN = $40 ;Emphasize green* + PPU_MASK_EMPH_BLUE = $80 ;Emphasize blue* + +PPU_STATUS = $2002 + PPU_STATUS_VBLANK = %1000000 +PPU_SPR_ADDR = $2003 +PPU_SPR_DATA = $2004 +PPU_SCROLL = $2005 +PPU_ADDR = $2006 + PPU_ADDR_NAMETABLE1 = $2000 + PPU_ADDR_NAMETABLE2 = $2400 + PPU_ADDR_NAMETABLE3 = $2800 + PPU_ADDR_NAMETABLE4 = $2C00 + PPU_ADDR_ATTRIBUTE = $C0 + PPU_ADDR_ATTRIBUTE1 = (PPU_ADDR_NAMETABLE1 + $300) | PPU_ADDR_ATTRIBUTE ; Attribute tables addresses + PPU_ADDR_ATTRIBUTE2 = (PPU_ADDR_NAMETABLE2 + $300) | PPU_ADDR_ATTRIBUTE + PPU_ADDR_ATTRIBUTE3 = (PPU_ADDR_NAMETABLE3 + $300) | PPU_ADDR_ATTRIBUTE + PPU_ADDR_ATTRIBUTE4 = (PPU_ADDR_NAMETABLE4 + $300) | PPU_ADDR_ATTRIBUTE + PPU_ADDR_PALETTE = $3F00 ;Pallete address in PPU +PPU_DATA = $2007 +PPU_SPR_DMA = $2014 +PPU_SPR_FLIP_H = $40 + +;; APU defines + +APU_PULSE1CTRL = $4000 ; Pulse #1 Control Register (W) +APU_PULSE1RAMP = $4001 ; Pulse #1 Ramp Control Register (W) +APU_PULSE1FTUNE = $4002 ; Pulse #1 Fine Tune (FT) Register (W) +APU_PULSE1CTUNE = $4003 ; Pulse #1 Coarse Tune (CT) Register (W) +APU_PULSE2CTRL = $4004 ; Pulse #2 Control Register (W) +APU_PULSE2RAMP = $4005 ; Pulse #2 Ramp Control Register (W) +APU_PULSE2FTUNE = $4006 ; Pulse #2 Fine Tune Register (W) +APU_PULSE2STUNE = $4007 ; Pulse #2 Coarse Tune Register (W) +APU_TRICTRL1 = $4008 ; Triangle Control Register #1 (W) +APU_TRICTRL2 = $4009 ; Triangle Control Register #2 (?) +APU_TRIFREQ1 = $400A ; Triangle Frequency Register #1 (W) +APU_TRIFREQ2 = $400B ; Triangle Frequency Register #2 (W) +APU_NOISECTRL = $400C ; Noise Control Register #1 (W) +APU_RESERVER = $400D ; Unused (???) +APU_NOISEFREQ1 = $400E ; Noise Frequency Register #1 (W) +APU_NOISEFREQ2 = $400F ; Noise Frequency Register #2 (W) +APU_MODCTRL = $4010 ; Delta Modulation Control Register (W) +APU_MODDA = $4011 ; Delta Modulation D/A Register (W) +APU_MODADDR = $4012 ; Delta Modulation Address Register (W) +APU_MODLEN = $4013 ; Delta Modulation Data Length Register (W) +APU_SPR_DMA = $4014 ; Sprite DMA Register (W) +APU_CHANCTRL = $4015 ; Sound/Vertical Clock Signal Register (R) +APU_PAD1 = $4016 ; Joypad #1 (RW) +APU_PAD2 = $4017 ; Joypad #2/SOFTCLK (RW) + PAD_A = $80 + PAD_B = $40 + PAD_SELECT = $20 + PAD_START = $10 + PAD_UP = $08 + PAD_DOWN = $04 + PAD_LEFT = $02 + PAD_RIGHT = $01 +APU_FRAMECT = $4017 ; APU Frame Counter (W) + APU_FRAMECT_IRQ_DISABLE = $40 ; Disable IRQ Interrupt + APU_FRAMECT_MODE4 = $00 ; Sequencer mode - 4 step sequence + APU_FRAMECT_MODE5 = $80 ; Sequencer mode - 5 step sequence + + +CH_HLINE = 11 +CH_VLINE = 14 +CH_ULCORNER = 176 +CH_URCORNER = 174 +CH_LLCORNER = 173 +CH_LRCORNER = 189 +CH_TTEE = 178 +CH_RTEE = 179 +CH_BTEE = 177 +CH_LTEE = 171 +CH_CROSS = 123 +CH_CURS_UP = 145 +CH_CURS_DOWN = 17 +CH_CURS_LEFT = 157 +CH_CURS_RIGHT = 29 +CH_PI = 126 +CH_DEL = 20 +CH_INS = 148 +CH_ENTER = 10 +CH_STOP = 3 +CH_ESC = 27 + diff --git a/src/ppu.asm b/src/ppu.asm new file mode 100644 index 0000000..b3b5452 --- /dev/null +++ b/src/ppu.asm @@ -0,0 +1,490 @@ +.include "lib.inc" +.include "nes.inc" +.include "ppu.inc" + +.segment "ZEROPAGE" + ; Public + ppu_nmi_done: .res 1 ; set 1 by NMI and reset by m_ppu_WaitForVblank + ppu_spriteId: .res 1 + ppu_addr: .res 2 + ppu_hasBuffer: .res 1 ; set to 1 whenever buffered data for nametable is present + + ; Private + _ppu_tmp1: .res 1 + _ppu_tmp2: .res 1 + _ppu_tmp3: .res 1 +.segment "BSS" + ; Public + ppu_scrollx: .res 1 + ppu_scrolly: .res 1 + ppu_needOam: .res 1 + ppu_need_draw: .res 1 + ppu_ctrl: .res 1 + ppu_mask: .res 1 + ppu_attributes: .res 64 ; internal buffer for the attributes + + ppu_bufferCount := $101 + ppu_buffer := $104 + ; Private + _ppu_bufferDirection := $100 + _ppu_bufferAddrLo := $102 + _ppu_bufferAddrHi := $103 + +.segment "OAM" + ppu_oam: + +.segment "CODE" + +.proc ppu_WaitForNmiDone +@forever: + lda ppu_nmi_done ; Execute main loop once per frame + beq @forever + lda #0 + sta ppu_nmi_done ; Reset the flag + rts +.endproc + +; ----------------------------------------------------------------------------- +; Fills nametable with specified value. Does not change the attributes +; IN: [stackcall] +; nameTable - high byte of nametable +; fillValue - value to be put into the nametable +; OUT: none +; USE: a,x,y,ppu_tmp1 +.proc ppu_FillNameTable + stackparam nameTable + stackparam fillValue + stackparam fillAttribute + tsx + + lda nameTable,x ; set address in PPU + sta PPU_ADDR + lda #0 + sta PPU_ADDR + + lda fillValue,x ; transfer fill value to A + + ldy #0 + ldx #4 ; repeat 4 times + @loop: + sta PPU_DATA + iny + + cpx #1 ; if X is 0 and Y is C0-1 (pallete start) + bne :+ ; then exit + cpy #PPU_ADDR_ATTRIBUTE + bne :+ + stx _ppu_tmp1 + tsx + lda fillAttribute,x + ldx _ppu_tmp1 + : + cpy #0 + bne @loop + dex ; decrease X + bne @loop + @end: + rts +.endproc + +; ----------------------------------------------------------------------------- +; Fills nametable with specified value. Does not change the attributes +; IN: [regcall] +; addr1 - low byte of palette +; addr2 - high byte of palette +; OUT: none +; USE: a,y,p +.proc ppu_LoadPallete + sta pl + stx ph + m_ppu_SetAddr PPU_ADDR_PALETTE + + ldy #$00 + @loop: + lda (p),y + sta PPU_DATA + iny + cpy #$20 ; Pallete length + bne @loop + rts +.endproc + +; ----------------------------------------------------------------------------- +; Enable NMI, enable Sprites +; IN: none +; OUT: none +; USE: A +.proc ppu_On + jsr ppu_WaitForNmiDone + mova PPU_CTRL, ppu_ctrl + mova PPU_MASK, ppu_mask + rts +.endproc + +; ----------------------------------------------------------------------------- +; Disable NMI, disable Sprites +; IN: none +; OUT: none +; USE: A +.proc ppu_Off + mova ppu_nmi_done, #0 + jsr ppu_WaitForNmiDone + lda #0 + sta ppu_needOam + sta ppu_need_draw + lda #0 + sta PPU_MASK + rts +.endproc + +; USE: A, X +.proc ppu_ClearSprites + mova ppu_needOam, #1 + mova ppu_spriteId, #0 + jmp ppu_FinishSprites +.endproc + +ppu_BeginSprites: + mova ppu_needOam, #1 + mova ppu_spriteId, #0 + rts +; USE: A, X +ppu_FinishSprites: + lda ppu_spriteId + asl a + asl a + tax +__ppu_sprites_loop: + lda #$FF + sta ppu_oam+OAM_YPOS,x + inx + inx + inx + inx + bne __ppu_sprites_loop + rts + +; ----------------------------------------------------------------------------- +; Add new entry to OAM +; IN: [stachcall] +; xParam - x coordinate +; yParam - y coordin +; attribute - attribute +; title - tile +; OUT: +; A - next available sprite ID +; USE: A, X, Y +.proc ppu_AddSprite + stackparam xParam + stackparam yParam + stackparam tile + stackparam attribute + tsx ; SP => X + + lda ppu_spriteId + asl ; a * 4 to get address of the Sprite + asl + tay + lda yParam,x + sta ppu_oam+OAM_YPOS,y + lda xParam,x + sta ppu_oam+OAM_XPOS,y + lda attribute,x + sta ppu_oam+OAM_ATTR,y + lda tile,x + sta ppu_oam+OAM_TILE,y + + lda ppu_spriteId ; save sprite ID to X + add #1 + sta ppu_spriteId + rts +.endproc + +; ----------------------------------------------------------------------------- +; Resets scroll to 0 +; PARAMS: +; None +; RETURN: +; None +.export ppu_ResetScroll +.proc ppu_ResetScroll + mova PPU_SCROLL, ppu_scrollx + mova PPU_SCROLL, ppu_scrolly + rts +.endproc + +; ----------------------------------------------------------------------------- +; Loads nametable from the address +; PARAMS: +; Stack+0 - Data hi address +; Stack+1 - Data lo address +.proc ppu_LoadNameTable + ; Declaring subroutines parameters + stackparam addrLo + stackparam addrHi + tsx + lda addrLo,x + sta _ppu_tmp1 + lda addrHi,x + sta _ppu_tmp2 + ldx #0 ; high loop counter + ldy #0 ; low loop counter + @while: ; X < 4 + lda (_ppu_tmp1),y + sta PPU_DATA + iny + cpy #0 + bne @while + lda _ppu_tmp2 + add #1 + sta _ppu_tmp2 + inx + cpx #4 + bne @while + rts +.endproc + +; ----------------------------------------------------------------------------- +; Loads attribtues +; PARAMS: +; Stack+0 - Data hi address +; Stack+1 - Data lo address +.proc ppu_LoadAttributes + ; Declaring subroutines parameters + stackparam addrLo + stackparam addrHi + tsx + lda addrLo,x + sta _ppu_tmp1 + lda addrHi,x + sta _ppu_tmp2 + ldy #0 ; low loop counter + @while: ; X < 4 + lda (_ppu_tmp1),y + sta PPU_DATA + sta ppu_attributes,y + iny + cpy #$20 + bne @while + rts +.endproc + +; ----------------------------------------------------------------------------- +; Sets address in nametable +; IN: +; A - nametable high address (eg $20, $24 etc) +; X - X coordinate +; Y - Y coordinate +; OUT: +; none +; USE: +; ppu_tmp1, ppu_tmp2 +.proc ppu_SetAddr + ; high then low + sta _ppu_tmp1+0 ; move nametable high address to high byte + stx _ppu_tmp1+1 ; store low x address in low byte + ; local variables on stack + stackalloc yShiftHi + stackalloc yShiftLo + tsx ; transfer stack pointer on x + lda #0 ; initialize yShiftHi with 0 + sta yShiftHi,x + tya ; now we shift y << 5 and add it to both bytes + + ; do y << 3 as maximum value of Y is 30 and it will not overflow + asl + asl + asl + clc + asl + bcc :+ + pha + lda #(1 << 1) ; put bit 1 if carry + sta yShiftHi,x + pla + : + asl + bcc :+ + pha + lda #(1 << 0) ; put bit 0 if carry + sta yShiftHi,x + pla + : + ; Not we finally have y<<5 + sta yShiftLo,x + ; not we need to add these two numbers + add _ppu_tmp1+1 + sta _ppu_tmp1+1 + lda yShiftHi,x + adc _ppu_tmp1+0 + sta PPU_ADDR + sta ppu_addr+0 + lda _ppu_tmp1+1 + sta PPU_ADDR + sta ppu_addr+1 + + stackfree + rts +.endproc + +; ----------------------------------------------------------------------------- +; Initialized buffered write on stack +; PARAMS: +; A: 0 - horizontal write, 1 - vertical write +; X: X coordinate +; Y: Y coordinate +.proc ppu_BeginBufferWrite + sta _ppu_bufferDirection + stx _ppu_bufferAddrLo + mova _ppu_bufferAddrHi, #0 ; reset High address + mova ppu_hasBuffer, #0 ; reset flag, in case our buffer write take too long + mova ppu_bufferCount, #0 + + tya + ; do y << 3 as maximum value of Y is 30 and it will not overflow + asl + asl + asl + clc + asl + bcc :+ + pha + lda #(1 << 1) ; put bit 1 if carry + sta _ppu_bufferAddrHi + pla + : + clc + asl + bcc :+ + pha + lda _ppu_bufferAddrHi + ora #(1 << 0) ; put bit 1 if carry + sta _ppu_bufferAddrHi + pla + : + ; in the end we finally have y<<5 + ; now we need to add low part of Y<<5 and X + add _ppu_bufferAddrLo + sta _ppu_bufferAddrLo + ; now we get high part of y<<5 and add it to high part of nametable address + ; make sure we use carry flag we might got from the addition above + lda _ppu_bufferAddrHi + adc #>PPU_ADDR_NAMETABLE1 + sta _ppu_bufferAddrHi + rts +.endproc + +; ----------------------------------------------------------------------------- +; Puts byte in the buffer +; PARAM: +; A - next byte in buffer +; USE: A +.proc ppu_WriteBuffer + stx _ppu_tmp2 + + ldx ppu_bufferCount + sta ppu_buffer,x + inx + stx ppu_bufferCount + + ldx _ppu_tmp1 ; restore registers + rts +.endproc + +; ----------------------------------------------------------------------------- +; Outputs the buffer. To be used in NMI +; Test code for this functionality below +; call ppu_BeginBufferWrite, #0, #29, #29 +; call ppu_WriteBuffer, #01 +; call ppu_WriteBuffer, #02 +; call ppu_WriteBuffer, #03 +; jsr ppu_FinishBufferWrite +.proc ppu_OutputBuffer + lda _ppu_bufferDirection + beq :+ + lda ppu_ctrl ; use current ppu_ctrl settings to set vertical increment + ora #PPU_CTRL_INREMENT_V + sta PPU_CTRL + : + m_ppu_BeginWrite ; set address + lda _ppu_bufferAddrHi + sta PPU_ADDR + lda _ppu_bufferAddrLo + sta PPU_ADDR + + ldx #0 + @loop: + lda ppu_buffer,x + sta PPU_DATA + inx + cpx ppu_bufferCount + bne @loop + + lda ppu_ctrl ; restore PPU_CTRL + sta PPU_CTRL + mova ppu_hasBuffer, #0 + rts +.endproc + +; ----------------------------------------------------------------------------- +; Finishes the buffer write +; USE: A +.proc ppu_FinishBufferWrite + mova ppu_hasBuffer, #1 + rts +.endproc + +; ----------------------------------------------------------------------------- +; Loads RLE packed nametable from the address +; PARAMS: +; Stack+0 - Data hi address +; Stack+1 - Data lo address +; .proc ppu_LoadNameTableRle +; ; Declaring subroutines parameters +; stackparam addrLo +; stackparam addrHi +; rleTag = _ppu_tmp1 +; rleByte = _ppu_tmp2 +; tsx +; lda addrLo,x +; sta ppu_addr+0 +; lda addrHi,x +; sta ppu_addr+1 + +; ldy #0 +; jsr @readByte +; sta rleTag +; @checkTag: +; jsr @readByte +; cmp rleTag +; beq @checkEnd +; sta PPU_DATA +; sta rleByte +; bne @checkTag +; @checkEnd: +; jsr @readByte +; cmp #0 +; beq @end +; tax +; lda rleByte +; @writeRun: +; sta PPU_DATA +; dex +; bne @writeRun +; beq @checkTag +; @end: +; rts + +; @readByte: +; lda (ppu_addr),y +; inc ppu_addr+0 +; bne :+ +; inc ppu_addr+1 +; : +; rts +; .endproc + +; .macro m_ppu_SetXy nameTable, xCoord, yCoord +; ;POSITION = +; m_ppu_SetAddr {(nameTable | ((yCoord << 5) | xCoord))} +; .endmacro diff --git a/src/ppu.inc b/src/ppu.inc new file mode 100644 index 0000000..42a8aad --- /dev/null +++ b/src/ppu.inc @@ -0,0 +1,106 @@ +.ifndef PPU_INC +.define PPU_INC + +.include "lib_macroses.inc" + +.segment "ZEROPAGE" + ; set to 1 when NMI has been finalized + .globalzp ppu_nmi_done + ; ID of the last sprite added to oam + .globalzp ppu_spriteId + .globalzp ppu_addr + .globalzp ppu_hasBuffer + ;.globalzp ppu_tmp1 + +.segment "BSS" + .global ppu_scrollx + .global ppu_scrolly + .global ppu_needOam + .global ppu_need_draw + .global ppu_ctrl + .global ppu_mask + .global ppu_buffer + .global ppu_bufferCount + +.segment "OAM" + .global ppu_oam + +.segment "CODE" + decl "stack", ppu_AddSprite + decl "stack", ppu_FillNameTable + decl "stack", ppu_LoadNameTable + decl "stack", ppu_LoadAttributes + ;decl "stack", ppu_LoadNameTableRle + decl "reg", ppu_SetAddr + decl "reg", ppu_LoadPallete + .global ppu_BeginSprites + .global ppu_FinishSprites + .global ppu_ClearSprites + .global ppu_ResetScroll + .global ppu_Off + .global ppu_On + .global ppu_WaitForNmiDone + + decl "reg", ppu_BeginBufferWrite + decl "reg", ppu_WriteBuffer + .global ppu_OutputBuffer + .global ppu_FinishBufferWrite + +; ----------------------------------------------------------------------------- +; Macroses + +OAM_YPOS = 0 +OAM_TILE = 1 +OAM_ATTR = 2 +OAM_XPOS = 3 + +.macro m_ppu_SetNmiDone + mova ppu_nmi_done, #1 ; Execute main loop once per frame +.endmacro + +.macro m_ppu_BeginWrite + lda PPU_STATUS +.endmacro + +.macro m_ppu_Write + sta PPU_DATA + pha + lda #1 + add ppu_addr+1 + sta ppu_addr+1 + lda #0 + adc ppu_addr+0 + sta ppu_addr+0 + pla +.endmacro + +.macro m_ppu_Read + lda PPU_DATA + pha + lda #1 + add ppu_addr+1 + sta ppu_addr+1 + lda #0 + adc ppu_addr+0 + sta ppu_addr+0 + pla +.endmacro + +.macro m_ppu_SetAddr addr + .if .paramcount <> 1 + .error "Too few parameters for macro m_ppu_SetAddr" + .endif + lda #>addr + sta PPU_ADDR + sta ppu_addr+0 + lda #