├── .github └── workflows │ └── main.yml ├── .gitignore ├── COPYING ├── README.md ├── pyz80.py └── testing ├── bootable.dsk ├── bootable.z80s ├── dummydos ├── golden.bin ├── golden.dsk.gz ├── golden.z80s ├── include.z80s ├── test_invalid.py ├── test_valid.py ├── unbootable.dsk ├── unbootable.z80s ├── utf8-bom.z80s ├── valid.bin └── valid.z80s /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, dev ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ubuntu-latest, windows-latest] 17 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v4 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | 31 | - name: Functional tests 32 | run: | 33 | python ./testing/test_valid.py 34 | python ./testing/test_invalid.py 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.github 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyz80 - a Z80 cross assembler 2 | Unofficial branch of https://sourceforge.net/projects/pyz80/ for fixes and enhancements. 3 | 4 | **pyz80 - a Z80 cross assembler** 5 | 6 | Version 1.21, released 11 July 2013 7 | 8 | By Andrew Collier 9 | 10 | [https://www.intensity.org.uk/contact.html](https://www.intensity.org.uk/contact.html) 11 | 12 | **Introduction** 13 | 14 | pyz80 is an assembler for the assembly language of the Z80 micro-processor. 15 | 16 | It is designed in particular for producing code to run on the Sam Coupé 17 | computer, and outputs disk images which can be loaded by the SimCoupe emulator, 18 | or copied onto floppy disk for use with genuine hardware Sams. Nonetheless, it 19 | may be useful for users of other Z80-based platforms as well. 20 | 21 | pyz80 is distributed under the terms of the [GNU General Public 22 | License](https://www.gnu.org/licenses/gpl.html). You can download and use it at 23 | no cost. See the file COPYING for more information. 24 | 25 | **Installation** 26 | 27 | pyz80 is written in python, and should work on MacOS X, Linux, Windows and other 28 | platforms if the appropriate interpreter is installed. 29 | 30 | If you do not have python already, installers can be downloaded from: 31 | 32 | [https://www.python.org/downloads/](https://www.python.org/downloads/) 33 | 34 | **Usage** 35 | 36 | pyz80 is a command-line tool. To use it, type pyz80 on your command line 37 | followed by the path to your input z80 source file: 38 | 39 | e.g. `pyz80 testing/test.z80s` 40 | 41 | This will assemble the source file, and generate a disk image which contains the 42 | object code. You can supply more than one z80 source file, in which case each 43 | object file is saved as a separate file in the resulting disk image. 44 | 45 | You can also add options (before the source files) to change details about the 46 | operation of pyz80: 47 | 48 | `-o outputfile` 49 | 50 | This option allows you to specify the name of the generated disk image. If this 51 | option is not given, the name is chosen based on the filename of the input 52 | source file (or the first input source file, if you have specified more than 53 | one). 54 | 55 | `--nozip` 56 | 57 | If this option is given, then the generated disk image will not be compressed. 58 | Otherwise, it will be compressed using gzip. 59 | 60 | `-I filepath` 61 | 62 | This option allows you to add an extra file to the disk image. It will be added 63 | to the directory before any assembled files, so for example you could add the 64 | "SAMDOS" file to the image to make it bootable. 65 | 66 | You can add multiple files by using the -I option more than once: 67 | 68 | e.g. `pyz80 -I SAMDOS -I file.txt textReader.z80s` 69 | 70 | Only code files can be added in this manner, they start at address 32768 and do 71 | not auto-execute. If you wish to change the start or execute address of the 72 | file, you will need to create a small Z80 source input file containing an MDAT 73 | directive. See the Syntax section for further details. 74 | 75 | `--obj=outputfile` 76 | 77 | This option allows you to save the generated code as a raw binary file, instead 78 | of part of a disk image. If you specify this option, no disk image will be 79 | generated (unless you also explicitly specify an output disk image file name). 80 | 81 | You may only assemble a single input Z80 source file if you use this option. 82 | 83 | ``` 84 | -D symbol 85 | -D symbol=value 86 | ``` 87 | 88 | This option defines a symbol before beginning to assemble the source file. The 89 | value of the symbol will be available to all expressions, both within 90 | instructions and also in assembler directives. 91 | 92 | Since every symbol must have an integer value, if nothing is specified then 1 93 | will be assumed. 94 | 95 | `--exportfile=file` 96 | 97 | This option allows you to save the whole symbol table to a file, following 98 | assembly. 99 | 100 | Only one file can be exported in this fashion. The file format is likely to be 101 | specific to the version of python in use. 102 | 103 | `--importfile=file` 104 | 105 | This option allows you to import a symbol table which had previously been saved 106 | with the corresponding --exportfile option. All the symbols which had been 107 | defined during the previous assembly run, will be available to any part of your 108 | input Z80 source. 109 | 110 | `--case` 111 | 112 | If this option is given, then all symbols in the input Z80 source file will be 113 | treated as case sensitive (this is the behaviour of the original COMET 114 | assembler). Otherwise, any combination of lower case or upper case will be 115 | considered equivalent. 116 | 117 | Please note this applies only to symbols. Instruction op-codes are always 118 | accepted in any combination of upper and lower case. 119 | 120 | `--nobodmas` 121 | 122 | If this option is given, then all expressions will be parsed from left to right 123 | instead of using the usual operator ordering rules, although brackets can still 124 | be used to specify precedence. For example the result of 10+10/10 in this mode 125 | will be two and not eleven (this is the behaviour of the original COMET 126 | assembler). 127 | 128 | `--intdiv` 129 | 130 | If this option is given, then all arithmetic divisions will give integer 131 | results, instead of floating point results. For example the result of 3/2 will 132 | be 1 and not 1.5 (this is the behaviour of the original COMET assembler). This 133 | will mostly affect calculations where an intermediate result is used for further 134 | calculations. The final value will always be truncated to an integer for 135 | register assignments. 136 | 137 | `-s regexp` 138 | 139 | This option allows you to print out the final value of one or more symbols at 140 | the end of assembling a source file. If you specify the text name of a 141 | particular symbol, pyz80 will print out the value of that symbol, and any others 142 | whose name completely contains what you have asked for: 143 | 144 | e.g. `pyz80 -s LDI3 testing/test.z80s` 145 | 146 | prints `{'LDI31': 33802, 'LDI3': 32850, 'LDI30': 33741, 'LDI32': 33865}` 147 | 148 | Another common use is to print the value of every symbol in the whole assembly, 149 | by using the wild-card character (full stop): 150 | 151 | e.g. `pyz80 -s . testing/test.z80s` 152 | 153 | Most precisely, regexp is a regular expression in python's format; if any part 154 | of a symbol matches the expression, then the symbol will be printed. 155 | 156 | `-e` 157 | 158 | If there are errors in the source code, pyz80 will attempt to diagnose the 159 | problem and tell you which line of the input Z80 source file is causing the 160 | problem. Occasionally, however, it can be difficult to see what mistake in that 161 | line is causing the confusion. If this option was specified on the command line, 162 | pyz80 will allow the python interpreter to print out its own messages about the 163 | problems it found, which may provide more detailed information. 164 | 165 | **Syntax** 166 | 167 | The input Z80 source text files have a format similar to that used by COMET, one 168 | of the Sam's most popular native assemblers. 169 | 170 | Each line of the source file may contain one symbol definition, and one Z80 171 | instruction or assembler directive. 172 | 173 | Comments start with semicolon and continue to the end of the line. 174 | 175 | **Instructions** 176 | 177 | Z80 instructions and assembler directives may be formatted in upper or lower 178 | case. 179 | 180 | e.g. 181 | ``` 182 | ld A,(DE) 183 | INC e 184 | ``` 185 | 186 | pyz80 recognises all valid Z80 instructions, included those traditionally 187 | undocumented. In instructions where the low byte or high byte of the index 188 | registers can be addressed individually, they are referred to in pyz80 syntax as 189 | IXh, IXl, IYh and IYl. 190 | 191 | The undocumented compound instructions (shift and rotate instructions which 192 | operate through the index registers and also copy the resulting value into a 193 | main register) have the following form of syntax in pyz80: 194 | 195 | e.g. 196 | ``` 197 | ROT r, (IX+c) 198 | ``` 199 | 200 | Undocumented instruction SLL is also included, but because it doesn't operate as 201 | its name implies, pyz80 emits a warning when it is used. To assemble this 202 | instruction without a warning, use the opcode SL1 instead. 203 | 204 | **Symbols** 205 | 206 | Symbols are defined by the symbol name at the start of a line, followed by 207 | colon. They are generally given the address of the following instruction, but 208 | can be assigned explicitly using the EQU directive. 209 | 210 | Symbols can contain any alphanumeric character, underscore, and full stop. 211 | Symbols formatted in upper or lower case may be used interchangeably, unless 212 | --case was specified on the command line. 213 | 214 | General symbols have global scope. A symbol defined anywhere in the source is 215 | valid at all other points in the source, and must be defined only once. 216 | 217 | A symbol starting with the @ sign (or consisting of the @ sign alone) is a local 218 | symbol. You may define a local symbol with the same name multiple times within 219 | the source, and this is not an error. The value of a local symbol is always 220 | taken from the a definition in the same file, the nearest definition to the 221 | point where the symbol is being used. 222 | 223 | When using a local symbol, you can add + (or -) immediately following the @ 224 | sign, which forces pyz80 to use the next (or previous) definition respectively, 225 | always ignoring matches in the opposite direction. 226 | 227 | e.g. 228 | ``` 229 | @loop: call work 230 | dec b 231 | jr nz, @-loop ; jump up two lines 232 | 233 | ld b,7 234 | @loop: call office 235 | dec b 236 | jr nz, @loop 237 | ``` 238 | 239 | Where a pair of braces is used as part of a symbol name, the value of the 240 | expression inside will be substituted into the name of the symbol being defined 241 | or used. 242 | 243 | e.g. 244 | ``` 245 | nine: equ 9 246 | k{nine}: equ $ ; defines a symbol called k9 247 | ; is equivalent to k9: equ $ 248 | ``` 249 | 250 | Note that the symbol being substituted must be available in the first pass of 251 | the assembler (i.e. it must be defined earlier in the file than it is used). 252 | 253 | The expression defined (symbol) tests whether a symbol is defined anywhere in 254 | the source, without causing an error if it is not found. The value of this 255 | expression is always either 0, if the symbol does not exist, or 1, if it is 256 | defined no matter what its value. It can be particularly useful in conjunction 257 | with the command line option -D, which allows you to build variant code files 258 | without changing the source. 259 | 260 | e.g. 261 | ``` 262 | ld a, defined(DEBUG) 263 | ``` 264 | 265 | **Assembler directives** 266 | 267 | `ORG address` 268 | 269 | Specify the origin of the code, i.e. the address from which it is intended to be 270 | run. This can be anywhere in the address range of the Z80, that is, 0 to 65535. 271 | 272 | ``` 273 | DUMP address 274 | DUMP page, offset 275 | ``` 276 | 277 | Specify the destination address of the code output. This directive is available 278 | in two forms. The first takes an address from 16384 to 65535, these are 279 | addresses in pages 0 to 2 as addressed in BASIC. The second form takes a page 280 | number from 0 to 31, and an offset within that page of 0 to 16383. 281 | 282 | `AUTOEXEC` 283 | 284 | The current DUMP address will be marked as the execute address in the directory 285 | entry of the output disk image. When the code file is loaded from the disk image 286 | by SamDOS, it will automatically call the execute address. Note that when a 287 | machine code routine is called from BASIC it will usually be paged into section 288 | C of the address space. 289 | 290 | This directive may not be used more than once during assembly. 291 | 292 | ``` 293 | DEFB n [,n ...] 294 | DB n [,n ...] 295 | ``` 296 | 297 | Define bytes at the current address. Either DEFB or DB are allowed and are 298 | equivalent in meaning. n can be a literal number or any expression, in the range 299 | 0 to 255 (or -128 to +127). 300 | 301 | ``` 302 | DEFW n [,n ...] 303 | DW n [,n ...] 304 | ``` 305 | 306 | Define words at the current address. Either DEFW or DW are allowed and are 307 | equivalent in meaning. n can be a literal number or any expression, in the range 308 | 0 to 65535 (or -32768 to +32767). 309 | 310 | ``` 311 | DEFM "string" 312 | DM "string" 313 | ``` 314 | 315 | Define a message at the current address. The string is always delimited by 316 | double quotes. To include a double quote in the string itself, place two double 317 | quote characters adjacent to each other. 318 | 319 | ``` 320 | DEFS n 321 | DS n 322 | ``` 323 | 324 | Define storage space at the current address. The current address increases by n 325 | bytes, thus leaving them available for your program's own use. 326 | 327 | `DS ALIGN n` 328 | 329 | This ensures that the following instruction will be aligned on the boundary 330 | specified, by leaving a space if necessary. 331 | 332 | `MDAT "filename"` 333 | 334 | This directive merges a data file from the host filesystem into the object code 335 | at the current address. filename is enclosed in double-quotes and is expressed 336 | relatively to the file currently being assembled. 337 | 338 | ``` 339 | INC "filename" 340 | INCLUDE "filename" 341 | ``` 342 | 343 | This directive assembles the specified source file starting at the current 344 | address, as though the whole file were literally included in the current one. 345 | All symbols in the included file will be available globally, and must therefore 346 | be unique (unless they are local symbols). 347 | 348 | (Both forms of this directive are equivalent, but INCLUDE is available to avoid 349 | confusion with the Z80's INC instruction) 350 | 351 | `FOR range, instruction` 352 | 353 | Repeats a single instruction (or directive) range times. 354 | 355 | During assembly of this instruction, a symbol FOR is valid and holds the 356 | iteration number from 0 to range-1. 357 | 358 | ``` 359 | symbol: EQU FOR range 360 | ... 361 | NEXT symbol 362 | ``` 363 | 364 | Repeats several lines of assembly, range times. When the lines between EQU FOR 365 | and NEXT are being assembled, the symbol symbol is valid, and holds the 366 | iteration number from 0 to range-1. 367 | 368 | FOR...NEXT blocks can be nested, with each layer using a different symbol name. 369 | 370 | Where a local symbol is defined within a FOR...NEXT block, it can only be used 371 | within the same block. All references to it will target the same iteration. 372 | 373 | ``` 374 | IF expression 375 | ... 376 | [ELSE IF expression] 377 | ... 378 | [ELSE] 379 | ... 380 | ENDIF 381 | ``` 382 | 383 | An IF block allows conditional assembly of your source based on some condition. 384 | For example, you may want to introduce some features during development of an 385 | application but to leave them out of release builds; in this case, IF blocks can 386 | be effectively used in conjunction with the -D command line option. 387 | 388 | e.g. 389 | 390 | ``` 391 | IF defined (DEBUG) 392 | XOR A 393 | OUT (CLUT),a 394 | ENDIF 395 | ``` 396 | 397 | Note that any symbols used in the expression must be available in the first pass 398 | of the assembler (i.e. they must be defined earlier in the file than they are 399 | used). 400 | 401 | `ASSERT condition` 402 | 403 | This directive evaluates whether condition is true, and aborts assembly if not. 404 | For example, this can be used to ensure that the size of your code has not 405 | overstepped any limits you may be imposing. 406 | 407 | `PRINT expression1[, expression2 ...]` 408 | 409 | Prints the result of the expressions as soon as they are evaluated during 410 | assembly. Can be useful for logging and debugging, and showing the value of 411 | symbols (as an alternative to the -s command line option) 412 | 413 | **Expressions and special characters** 414 | 415 | Wherever an instruction or directive requires a number, a mathematical 416 | expression can be used instead. These are allowed to contain any symbol names 417 | defined in the file. If the result of an expression is negative, it will appear 418 | in the instruction in two's complement. 419 | 420 | `$` a symbol representing the address of the current instruction 421 | `&` prefixes a hexadecimal number 422 | `0x` prefixes a hexadecimal number 423 | `%` prefixes a binary number 424 | `0b` prefixes a binary number 425 | `"` a single character enclosed in double-quotes can be used to represent the ascii value of that character 426 | `""` strings are delimited by the double-quote character. To encode a literal double-quote within a string, use the double-quote character twice. 427 | `\` mod 428 | 429 | Several mathematical functions are available within expressions. You may use any 430 | method of the random or math python modules; a few examples are listed below: 431 | 432 | `random()` 433 | 434 | Returns a random floating point number between 0 and 1. It should be noted that 435 | while intermediate stages of the calculation are allowed to have non-integer 436 | values, the final value of an expression is always rounded to the nearest 437 | integer. Thus it can be useful to multiply float numbers by a constant to keep 438 | them in a useful range: 439 | 440 | e.g. 441 | ``` 442 | DEFB random()\*256 443 | ``` 444 | 445 | `randrange(start,stop [,step])` 446 | 447 | Returns a random integer in the range start ≤ x < stop 448 | 449 | ``` 450 | sin(angle) 451 | cos(angle) 452 | ``` 453 | 454 | angle is specified in radians. 455 | 456 | `pi` 457 | 458 | **Thanks** 459 | 460 | Thanks to Edwin Blink for writing the original COMET assembler for the Sam Coupé. 461 | 462 | Thanks to Simon Owen and other users for their feedback during development. 463 | 464 | **Links** 465 | 466 | pyz80 web page: 467 | 468 | [https://www.intensity.org.uk/samcoupe/pyz80.html](https://www.intensity.org.uk/samcoupe/pyz80.html) 469 | 470 | SimCoupe Homepage: 471 | 472 | [https://simonowen.com/simcoupe/](https://simonowen.com/simcoupe/) 473 | 474 | World of Sam archive: 475 | 476 | [https://www.worldofsam.org/](https://www.worldofsam.org/) 477 | 478 | Wikipedia entry for the SAM Coupé (and for more links): 479 | 480 | [https://wikipedia.org/wiki/Sam\_Coupe](https://wikipedia.org/wiki/SAM_Coupé) 481 | 482 | **Disclaimer** 483 | 484 | THIS PROGRAM AND DOCUMENTATION ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 485 | KIND, NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A 486 | PARTICULAR PURPOSE. BY USING THE PROGRAM, YOU AGREE TO BEAR ALL RISKS AND 487 | LIABILITIES ARISING FROM THE USE OF THE PROGRAM AND DOCUMENTATION AND THE 488 | INFORMATION PROVIDED BY THE PROGRAM AND THE DOCUMENTATION. 489 | 490 | **Release History** 491 | 492 | Version 1.21, 11 July 2013 493 | 494 | - A symbol name could include tab characters, which should be treated as a source error (reported by Chris Pile) 495 | 496 | Version 1.2, 2 February 2009 497 | 498 | - Add PRINT directive 499 | - Local symbols used in an included file will search the parent file to find a definition 500 | - Chooses more appropriate names for files on the Sam disk image, based on the input source filename (reported by Thomas Harte) 501 | - Fixed options --obj and -o were incorrectly conflicting (reported by Thomas Harte) 502 | - Fixed symbol names containing strings 0b and 0x were misparsed (fixed by Simon Owen) 503 | - Fixed a misparseing of incorrect instructions of the form LD H,(23) (reported by Thomas Harte) 504 | 505 | Version 1.1, 13 April 2007 506 | 507 | - Fix a problem when the comma character is included in quoted expressions 508 | - Documentation fix for INCLUDE directive 509 | 510 | Version 1.0, 10 April 2007 511 | 512 | - option to include other files on the resulting disk image, or multiple object files 513 | - IXh, IXl, IYh and IYl can be used in operands where this forms a valid (undocumented) instruction 514 | - fixed ex (sp),ix 515 | - fixed parse bug if a symbol name starts with IX or IY 516 | - support for "compound" instructions such as RLC r,(IX+c) 517 | - option to not gzip the disk image 518 | - option to save raw binaries and not just into disk images 519 | - option to treat labels as case sensitive (as COMET itself does) 520 | - option to work without bodmas operator precedence (as COMET itself does) 521 | - better package with documentation and test sources 522 | - local" symbols starting with the @ character do not need to be unique; use them for the beginning of loops, for example. Any references to them will go to the nearest in the same file (or use @+ and @- for always the next or previous respectively) 523 | - IF, ELSE (IF), ENDIF pseudo-opcodes 524 | - defined(symbol) tests whether a symbol exists 525 | - multi-line FOR constructs for repeating sequences of instructions 526 | - curly braces text-substitute the value of one symbol into the name of another, 527 | - option to predefine symbols from the pyz80 command line 528 | - option to save and load whole symbol tables 529 | - saved files include only used space, and not the rest of the page 530 | 531 | Version 0.6, 10 February 2006 532 | 533 | - implemented SLL (with a warning) 534 | - use directive AUTOEXEC (usually straight after a DUMP) to set automatic execution of code from the current target address 535 | - simple test to stop output file overwriting source file 536 | - instructions may now be tab separated, not just space 537 | - add warning when operands are generated out of range of the instruction 538 | - better default output file name 539 | - cope better with errors in z80s source (symbol names used twice) 540 | - added -e option (lets you see python's error messages directly) 541 | - allow . in symbol names 542 | - file doesn't have to start at page boundary (helpful for writing boot sectors and stuff) 543 | 544 | Version 0.5, 15 January 2005 545 | 546 | - new pseudo-opcode: FOR limit, codeseq 547 | - allow % as a prefix to binary numbers 548 | - fix LD (IX+n),n 549 | - slightly more flexibility if EQU definition depends on a symbol has not been defined until later in the source file (this is not allowed to affect code size) 550 | - allow leading zeros in decimal literals (passing these directly to the python expression parser as previously would cause them to be treated as octal) 551 | - allow DB, DW ... as shorthand forms of DEFB, DEFW etc 552 | - allow character constants expressions (e.g. DB "A") 553 | - save file correctly if unused part of the last sector falls out of memory 554 | - more robust expression parser doesn't replace substrings of symbols 555 | - DEFM can cope with strings containing semicolons, colons 556 | - import python math and random modules to make some of its functions available to the expression parser 557 | - removed nesting of functions because the scoping rules don't seem to work the way FOR needs them 558 | - fixed confusion between INC opcode and INC (include) directive 559 | - changed file loading slightly because fileinput doesn't cut the recursive mustard. 560 | - allow underscore character in symbol names, and sanity check for syntax errors 561 | - print a subset of the symbol table after assembly is complete 562 | - files included by files included along a relative path will search for their files on that path 563 | 564 | Version 0.1, 19 November 2004 565 | 566 | - initial release 567 | -------------------------------------------------------------------------------- /pyz80.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import math 4 | 5 | # TODO: define and assemble macro blocks 6 | # added FILESIZE("filename") 7 | # defs doesn't cause bytes to be written to output unless real data follows 8 | 9 | def printusage(): 10 | print("pyz80 by Andrew Collier, modified by Simon Owen") 11 | print(" https://github.com/simonowen/pyz80/") 12 | print("Usage:") 13 | print(" pyz80 (options) inputfile(s)") 14 | print("Options:") 15 | print("-o outputfile") 16 | print(" save the resulting disk image at the given path") 17 | print("--nozip") 18 | print(" do not compress the resulting disk image") 19 | print("-B filepath") 20 | print(" Bootable (usually DOS) file added first to disk image, but") 21 | print(" only if the first assembled file has no BOOT signature.") 22 | print("-I filepath") 23 | print(" Add this file to the disk image before assembling") 24 | print(" May be used multiple times to add multiple files") 25 | print("--obj=outputfile") 26 | print(" save the output code as a raw binary file at the given path") 27 | print("-D symbol") 28 | print("-D symbol=value") 29 | print(" Define a symbol before parsing the source") 30 | print(" (value is integer; if omitted, assume 1)") 31 | print("--exportfile=filename") 32 | print(" Save all symbol information into the given file") 33 | print("--importfile=filename") 34 | print(" Define symbols before assembly, from a file previously exported") 35 | print("--mapfile=filename") 36 | print(" Save address-to-symbol map into the given file") 37 | print("--lstfile=filename") 38 | print(" Produce assembly listing into given file") 39 | print("--case") 40 | print(" treat source labels as case sensitive (as COMET itself did)") 41 | print("--nobodmas") 42 | print(" treat arithmetic operators without precedence (as COMET itself did)") 43 | print("--intdiv") 44 | print(" force all division to give an integer result (as COMET itself did)") 45 | print("-s regexp") 46 | print(" print the value of any symbols matching the given regular expression") 47 | print(" This may be used multiple times to output more than one subset") 48 | print("-x") 49 | print(" display values from the -s option and PRINT directives in hex") 50 | print("-e") 51 | print(" use python's own error handling instead of trying to catch parse errors") 52 | 53 | 54 | def printlicense(): 55 | print("This program is free software; you can redistribute it and/or modify") 56 | print("it under the terms of the GNU General Public License as published by") 57 | print("the Free Software Foundation; either version 2 of the License, or") 58 | print("(at your option) any later version.") 59 | print(" ") 60 | print("This program is distributed in the hope that it will be useful,") 61 | print("but WITHOUT ANY WARRANTY; without even the implied warranty of") 62 | print("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the") 63 | print("GNU General Public License for more details.") 64 | print(" ") 65 | print("You should have received a copy of the GNU General Public License") 66 | print("along with this program; if not, write to the Free Software") 67 | print("Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA") 68 | 69 | import getopt 70 | import sys, os, datetime 71 | import array 72 | import re 73 | import gzip 74 | import pickle 75 | import math # for use by expressions in source files 76 | import random # note: in use via eval strings 77 | 78 | def new_disk_image(sectors_per_track): 79 | global SPT 80 | SPT = sectors_per_track 81 | targetsize = 80*SPT*2*512 82 | # disk image is arranged as: tr 0 s 1-10, tr 128 s 1-10, tr 1 s 1-10, tr 129 s 1-10 etc 83 | return array.array('B', [0] * targetsize) 84 | 85 | def dsk_at(track,side,sector): 86 | return (track*SPT*2+side*SPT+(sector-1))*512 87 | # uses numbering 1-10 for sectors, because SAMDOS internal format also does 88 | 89 | 90 | def add_file_to_disk_image(image, filename, codestartpage, codestartoffset, execpage=0, execoffset=0, filelength=None, fromfile=None ): 91 | global firstpageoffset, global_currentfile, global_currentline 92 | 93 | global_currentfile = 'add file' 94 | global_currentline = '' 95 | 96 | if fromfile != None: 97 | modified = datetime.datetime.fromtimestamp(os.path.getmtime(fromfile)) 98 | fromfilefile = open(fromfile,'rb') 99 | fromfilefile.seek(0,2) 100 | filelength = fromfilefile.tell() 101 | 102 | fromfilefile.seek(0) 103 | fromfile = array.array('B') 104 | fromfile.fromfile(fromfilefile, filelength) 105 | else: 106 | modified = datetime.datetime.now() 107 | 108 | sectors_already_used = 0 109 | # we're writing the whole image, so we can take a bit of a shortcut 110 | # instead of reading the entire sector map to find unused space, we can assume all files are contiguous 111 | # and place new files just at the end of the used space 112 | 113 | #find an unused directory entry 114 | for direntry in range(4*SPT*2): 115 | dirpos = dsk_at(direntry//(SPT*2),0,1+(direntry%(SPT*2))//2) + 256*(direntry%2) 116 | if image[dirpos] == 0: 117 | break 118 | else: 119 | sectors_already_used += image[dirpos+11]*256 +image[dirpos+12] 120 | 121 | else: 122 | fatal ("Too many files for dsk format") 123 | 124 | image[dirpos] = 19 # code file 125 | 126 | for i in range(10): 127 | image[dirpos+1+i] = ord((filename+" ")[i]) 128 | 129 | nsectors = math.ceil(( filelength + 9 ) / 510) 130 | image[dirpos+11] = nsectors // 256 # MSB number of sectors used 131 | image[dirpos+12] = nsectors % 256 # LSB number of sectors used 132 | 133 | starting_side = (4 + sectors_already_used//SPT)//80 134 | starting_track = (4 + sectors_already_used//SPT)%80 135 | starting_sector = sectors_already_used%SPT + 1 136 | 137 | image[dirpos+13] = starting_track + 128*starting_side # starting track 138 | image[dirpos+14] = starting_sector # starting sector 139 | 140 | # 15 - 209 sector address map 141 | 142 | # write table of used sectors (can precalculate from number of used bits) 143 | while nsectors > 0: 144 | image[dirpos+15 + sectors_already_used//8] |= (1 << (sectors_already_used & 7)) 145 | sectors_already_used += 1 146 | nsectors -= 1 147 | 148 | # 210-219 MGT future and past (reserved) 149 | 150 | image[dirpos+220] = 0 # flags (reserved) 151 | 152 | # 221-231 File type information (n/a for code files) 153 | # 232-235 reserved 154 | 155 | image[dirpos+236] = codestartpage # start page number 156 | image[dirpos+237] = (codestartoffset%256) # page offset (in section C, 0x8000 - 0xbfff) 157 | image[dirpos+238] = 128 + (codestartoffset // 256) 158 | 159 | image[dirpos+239] = filelength//16384 # pages in length 160 | image[dirpos+240] = filelength%256 # file length % 16384 161 | image[dirpos+241] = (filelength%16384)//256 162 | 163 | if (execpage>0) : 164 | image[dirpos+242] = execpage # execution address or 255 255 255 (basicpage, L, H - offset in page C) 165 | image[dirpos+243] = execoffset % 256 166 | image[dirpos+244] = (execoffset%16384)//256 + 128 167 | else: 168 | image[dirpos+242] = 255 # execution address or 255 255 255 (basicpage, L, H - offset in page C) 169 | image[dirpos+243] = 255 170 | image[dirpos+244] = 255 171 | 172 | image[dirpos+245] = modified.day 173 | image[dirpos+246] = modified.month 174 | image[dirpos+247] = modified.year % 100 + 100 175 | image[dirpos+248] = modified.hour 176 | image[dirpos+249] = modified.minute 177 | 178 | side = starting_side 179 | track = starting_track 180 | sector = starting_sector 181 | fpos = 0 182 | 183 | # write file's 9 byte header and file 184 | imagepos = dsk_at(track,side,sector) 185 | 186 | # 0 File type 19 for a code file 187 | image[imagepos + 0] = 19 188 | # 1-2 Modulo length Length of file % 16384 189 | image[imagepos + 1] = filelength%256 190 | image[imagepos + 2] = (filelength%16384)//256 191 | # 3-4 Offset start Start address 192 | image[imagepos + 3] = (codestartoffset%256) 193 | image[imagepos + 4] = 128 + (codestartoffset // 256) 194 | # 5-6 Unused 195 | # 7 Number of pages 196 | image[imagepos + 7] = filelength//16384 197 | # 8 Starting page number 198 | image[imagepos + 8] = codestartpage 199 | 200 | start_of_file = True 201 | while fpos < filelength: 202 | imagepos = dsk_at(track,side,sector) 203 | unadjustedimagepos = imagepos 204 | if start_of_file: 205 | if filelength > 500: 206 | copylen = 501 207 | else: 208 | copylen = filelength 209 | imagepos += 9 210 | start_of_file = False 211 | else: 212 | if (filelength-fpos) > 509: 213 | copylen = 510 214 | else: 215 | copylen = (filelength-fpos) 216 | 217 | if fromfile != None: 218 | image[imagepos:imagepos+copylen] = fromfile[fpos:fpos+copylen] 219 | else: 220 | if ((fpos+firstpageoffset)//16384) == (((fpos+codestartoffset)+copylen-1)//16384): 221 | if memory[codestartpage+(fpos+codestartoffset)//16384] != '': 222 | image[imagepos:imagepos+copylen] = memory[codestartpage+(fpos+firstpageoffset)//16384][(fpos+codestartoffset)%16384 : (fpos+codestartoffset)%16384+copylen] 223 | else: 224 | copylen1 = 16384 - ((fpos+codestartoffset)%16384) 225 | page1 = (codestartpage+(fpos+codestartoffset)//16384) 226 | if memory[page1] != '': 227 | image[imagepos:imagepos+copylen1] = memory[page1][(fpos+codestartoffset)%16384 : ((fpos+codestartoffset)%16384)+copylen1] 228 | if (page1 < 31) and memory[page1+1] != '': 229 | image[imagepos+copylen1:imagepos+copylen] = memory[page1+1][0 : ((fpos+codestartoffset)+copylen)%16384] 230 | 231 | fpos += copylen 232 | 233 | sector += 1 234 | if sector == (SPT+1): 235 | sector = 1 236 | track += 1 237 | if track == 80: 238 | track = 0 239 | side += 1 240 | if side==2: 241 | fatal("Disk full writing "+filename) 242 | 243 | # pointers to next sector and track 244 | if (fpos < filelength): 245 | image[unadjustedimagepos+510] = track + 128*side 246 | image[unadjustedimagepos+511] = sector 247 | 248 | def array_bytes(arr): 249 | return arr.tobytes() if hasattr(arr, "tobytes") else arr.tostring() 250 | 251 | def save_disk_image(image, pathname): 252 | imagestr = array_bytes(image) 253 | if ZIP: 254 | dskfile = gzip.open(pathname, 'wb') 255 | else: 256 | dskfile = open(pathname, 'wb') 257 | dskfile.write(imagestr) 258 | dskfile.close() 259 | 260 | 261 | def save_memory(memory, image=None, filename=None): 262 | global firstpage,firstpageoffset 263 | 264 | if firstpage==32: 265 | # code was assembled without using a DUMP directive 266 | firstpage = 1 267 | firstpageoffset = 0 268 | 269 | if memory[firstpage] != '': 270 | # check that something has been assembled at all 271 | 272 | filelength = (lastpage - firstpage + 1) * 16384 273 | 274 | filelength -= firstpageoffset 275 | filelength -= 16384-lastpageoffset 276 | 277 | if (autoexecpage>0) : 278 | savefilename = ("AUTO" + filename + " ")[:8]+".O" 279 | else: 280 | savefilename = (filename + " ")[:8]+".O" 281 | 282 | if image: 283 | add_file_to_disk_image(image,savefilename,firstpage, firstpageoffset, autoexecpage, autoexecorigin, filelength) 284 | else: 285 | save_memory_to_file(filename, firstpage, firstpageoffset, filelength) 286 | 287 | def save_file_to_image(image, pathname): 288 | sam_filename = os.path.basename(pathname) 289 | 290 | if len(sam_filename)>10: 291 | if sam_filename.count("."): 292 | extpos = sam_filename.rindex(".") 293 | extlen = len(sam_filename)-extpos 294 | sam_filename = sam_filename[:10-extlen] + sam_filename[extpos:] 295 | else: 296 | sam_filename = sam_filename[:10] 297 | 298 | add_file_to_disk_image(image,sam_filename, 1, 0, fromfile=pathname) 299 | 300 | def save_memory_to_file(filename, firstusedpage, firstpageoffset, filelength): 301 | objfile = open(filename, 'wb') 302 | 303 | flen = filelength 304 | page = firstusedpage 305 | offset = firstpageoffset 306 | 307 | while flen: 308 | wlen = min(16384-offset, flen) 309 | 310 | if memory[page] != "": 311 | pagestr = array_bytes(memory[page]) 312 | objfile.write(pagestr[offset:offset+wlen]) 313 | 314 | 315 | else: 316 | # write wlen nothings into the file 317 | objfile.seek(wlen,1) 318 | 319 | flen -= wlen 320 | page += 1 321 | offset=0 322 | 323 | objfile.close() 324 | 325 | def warning(message): 326 | print(global_currentfile, 'warning:', message) 327 | print('\t', global_currentline.strip()) 328 | 329 | def fatal(message): 330 | print(global_currentfile, 'error:', message, file=sys.stderr) 331 | print ('\t', global_currentline.strip(), file=sys.stderr) 332 | sys.exit(1) 333 | 334 | def expand_symbol(sym): 335 | while 1: 336 | match = re.search(r'\{([^\{\}]*)\}', sym) 337 | if match: 338 | value = parse_expression(match.group(1)) 339 | sym = sym.replace(match.group(0),str(value)) 340 | else: 341 | break 342 | return sym 343 | 344 | def file_and_stack(explicit_currentfile=None): 345 | 346 | if explicit_currentfile==None: 347 | explicit_currentfile = global_currentfile 348 | 349 | f,l = explicit_currentfile.rsplit(':', 1) 350 | s='' 351 | for i in forstack: 352 | s=s+"^"+str(i[2]) 353 | return f+s+':'+l 354 | 355 | def set_symbol(sym, value, explicit_currentfile=None, is_label=False): 356 | symorig = expand_symbol(sym) 357 | sym = symorig if CASE else symorig.upper() 358 | 359 | if sym[0]=='@': 360 | sym = sym + '@' + file_and_stack(explicit_currentfile=explicit_currentfile) 361 | 362 | symboltable[sym] = value 363 | if sym != symorig: 364 | symbolcase[sym] = symorig 365 | 366 | if is_label: 367 | labeltable[sym] = value 368 | 369 | def get_symbol(sym): 370 | symorig = expand_symbol(sym) 371 | sym = symorig if CASE else symorig.upper() 372 | 373 | if sym[0]=='@': 374 | if (sym + '@' + file_and_stack()) in symboltable: 375 | return symboltable[sym + '@' + file_and_stack()] 376 | else: 377 | if len(sym) > 1 and (sym[1]=='-' or sym[1]=='+'): 378 | directive = sym[1] 379 | sym = sym[0]+sym[2:] 380 | else: 381 | directive='' 382 | 383 | reqfile,reqline = file_and_stack().rsplit(':', 1) 384 | reqline = int(reqline) 385 | 386 | closestKey = None 387 | for key in symboltable: 388 | if (sym+'@'+reqfile+":").startswith(key.rsplit(":",1)[0]+":") or (sym+'@'+reqfile+":").startswith(key.rsplit(":",1)[0]+"^"): 389 | # key is allowed fewer layers of FOR stack, but any layers it has must match 390 | # ensure a whole number (ie 1 doesn't match 11) by forceing a colon or hat 391 | symfile,symline = key.rsplit(':', 1) 392 | symline=int(symline) 393 | 394 | difference = reqline - symline 395 | 396 | if (difference < 0 or directive != '+') and (difference > 0 or directive != '-') and ((closestKey == None) or (abs(difference) < closest)): 397 | closest = abs(difference) 398 | closestKey = key 399 | 400 | if (not closestKey) and (directive == '-'): 401 | global include_stack 402 | use_include_stack = include_stack 403 | use_include_stack.reverse() 404 | # try searching up the include stack 405 | for include_item in use_include_stack: 406 | include_file, include_line = include_item[1].rsplit(":",1) 407 | if not closestKey: 408 | for key in symboltable: 409 | if (sym+'@'+include_file+":").startswith(key.rsplit(":",1)[0]+":") or (sym+'@'+include_file+":").startswith(key.rsplit(":",1)[0]+"^"): 410 | # key is allowed fewer layers of FOR stack, but any layers it has must match 411 | # ensure a whole number (ie 1 doesn't match 11) by forceing a colon or hat 412 | symfile,symline = key.rsplit(':', 1) 413 | symline=int(symline) 414 | 415 | difference = int(include_line) - symline 416 | 417 | if (difference < 0 or directive != '+') and (difference > 0 or directive != '-') and ((closestKey == None) or (abs(difference) < closest)): 418 | closest = abs(difference) 419 | closestKey = key 420 | 421 | if closestKey != None: 422 | sym = closestKey 423 | 424 | if sym in symboltable: 425 | symusetable[sym] = symusetable.get(sym,0)+1 426 | return symboltable[sym] 427 | 428 | return None 429 | 430 | def parse_expression(arg, signed=0, byte=0, word=0, silenterror=0): 431 | if ',' in arg: 432 | if silenterror: 433 | return '' 434 | fatal("Erroneous comma in expression"+arg) 435 | 436 | while 1: 437 | match = re.search('"(.)"', arg) 438 | if match: 439 | arg = arg.replace('"'+match.group(1)+'"',str(ord(match.group(1)))) 440 | else: 441 | break 442 | 443 | while 1: 444 | match = re.search(r'defined\s*\(\s*(.*?)\s*\)', arg, re.IGNORECASE) 445 | if match: 446 | result = (get_symbol(match.group(1)) != None) 447 | arg = arg.replace(match.group(0),str(int(result))) 448 | 449 | else: 450 | break 451 | arg = arg.replace('$','('+str(origin)+')') 452 | arg = arg.replace('%','0b') # COMET syntax for binary literals (parsed later, change to save confusion with modulus) 453 | arg = arg.replace('\\','%') # COMET syntax for modulus 454 | arg = re.sub(r'&([0-9a-fA-F]+\b)', r'0x\g<1>', arg) # COMET syntax for hex numbers 455 | 456 | if INTDIV: 457 | arg = re.sub(r'(?2 and testsymbol[1]=='b': 523 | # binary literal 524 | literal = 0 525 | for digit in testsymbol[2:]: 526 | literal *= 2 527 | if digit == '1': 528 | literal += 1 529 | elif digit != '0': 530 | fatal("Invalid binary digit '"+digit+"'") 531 | testsymbol = str(literal) 532 | 533 | elif testsymbol[0]=='0' and len(testsymbol)>1 and testsymbol[1]!='x': 534 | # literals with leading zero would be treated as octal, 535 | # COMET source files do not expect this 536 | decimal = testsymbol 537 | while decimal[0] == '0' and len(decimal)>1: 538 | decimal = decimal[1:] 539 | testsymbol = decimal 540 | 541 | argcopy += testsymbol 542 | testsymbol = '' 543 | argcopy += c 544 | 545 | if NOBODMAS: 546 | # add bracket pairs at interesting locations to simulate left-to-right evaluation 547 | 548 | aslist = list(argcopy) # turn it into a list so that we can add characters without affecting indexes 549 | bracketstack=[0] 550 | symvalid = False 551 | 552 | for c in range (len(aslist)): 553 | if aslist[c] == "(": 554 | bracketstack = [c]+bracketstack 555 | elif aslist[c] == ")": 556 | bracketstack = bracketstack[1:] 557 | elif (not aslist[c].isalnum()) and (not aslist[c]=='.') and (not aslist[c].isspace()) and symvalid: 558 | aslist[c] = ")"+aslist[c] 559 | aslist[bracketstack[0]] = '('+aslist[bracketstack[0]] 560 | symvalid = False 561 | elif aslist[c].isalnum(): 562 | symvalid = True 563 | 564 | argcopy2="" 565 | for entry in aslist: 566 | argcopy2 += entry 567 | # print(argcopy,"->",argcopy2) 568 | argcopy = argcopy2 569 | 570 | narg = int(eval(argcopy)) 571 | # print(arg, " -> ",argcopy," == ",narg) 572 | 573 | if not signed: 574 | if byte: 575 | if narg < -128 or narg > 255: 576 | warning ("Unsigned byte value truncated from "+str(narg)) 577 | narg %= 256 578 | elif word: 579 | if narg < -32768 or narg > 65535: 580 | warning ("Unsigned word value truncated from "+str(narg)) 581 | narg %= 65536 582 | 583 | return narg 584 | 585 | def double(arg, allow_af_instead_of_sp=0, allow_af_alt=0, allow_index=1): 586 | # decode double register [bc, de, hl, sp][ix,iy] --special: af af' 587 | double_mapping = {'BC':([],0), 'DE':([],1), 'HL':([],2), 'SP':([],3), 'IX':([0xdd],2), 'IY':([0xfd],2), 'AF':([],5), "AF'":([],4) } 588 | rr = double_mapping.get(arg.strip().upper(),([],-1)) 589 | if (rr[1]==3) and allow_af_instead_of_sp: 590 | rr = ([],-1) 591 | if rr[1]==5: 592 | if allow_af_instead_of_sp: 593 | rr = ([],3) 594 | else: 595 | rr = ([],-1) 596 | if (rr[1]==4) and not allow_af_alt: 597 | rr = ([],-1) 598 | 599 | if (rr[0] != []) and not allow_index: 600 | rr = ([],-1) 601 | 602 | return rr 603 | 604 | def single(arg, allow_i=0, allow_r=0, allow_index=1, allow_offset=1, allow_half=1): 605 | #decode single register [b,c,d,e,h,l,(hl),a][(ix {+c}),(iy {+c})] 606 | single_mapping = {'B':0, 'C':1, 'D':2, 'E':3, 'H':4, 'L':5, 'A':7, 'I':8, 'R':9, 'IXH':10, 'IXL':11, 'IYH':12, 'IYL':13 } 607 | m = single_mapping.get(arg.strip().upper(),-1) 608 | prefix=[] 609 | postfix=[] 610 | 611 | if m==8 and not allow_i: 612 | m = -1 613 | if m==9 and not allow_r: 614 | m = -1 615 | 616 | if allow_half: 617 | if m==10: 618 | prefix = [0xdd] 619 | m = 4 620 | if m==11: 621 | prefix = [0xdd] 622 | m = 5 623 | if m==12: 624 | prefix = [0xfd] 625 | m = 4 626 | if m==13: 627 | prefix = [0xfd] 628 | m = 5 629 | else: 630 | if m >= 10 and m <= 13: 631 | m = -1 632 | 633 | if m==-1 and re.search(r"\A\s*\(\s*HL\s*\)\s*\Z", arg, re.IGNORECASE): 634 | m = 6 635 | 636 | if m==-1 and allow_index: 637 | match = re.search(r"\A\s*\(\s*(I[XY])\s*\)\s*\Z", arg, re.IGNORECASE) 638 | if match: 639 | m = 6 640 | prefix = [0xdd] if match.group(1).lower() == 'ix' else [0xfd] 641 | postfix = [0] 642 | 643 | elif allow_offset: 644 | match = re.search(r"\A\s*\(\s*(I[XY])\s*([+-].*)\s*\)\s*\Z", arg, re.IGNORECASE) 645 | if match: 646 | m = 6 647 | prefix = [0xdd] if match.group(1).lower() == 'ix' else [0xfd] 648 | if p==2: 649 | offset = parse_expression(match.group(2), byte=1, signed=1) 650 | if offset < -128 or offset > 127: 651 | fatal ("invalid index offset: "+str(offset)) 652 | postfix = [(offset + 256) % 256] 653 | else: 654 | postfix = [0] 655 | 656 | return prefix,m,postfix 657 | 658 | def condition(arg): 659 | # decode condition [nz, z, nc, c, po, pe, p, m] 660 | condition_mapping = {'NZ':0, 'Z':1, 'NC':2, 'C':3, 'PO':4, 'PE':5, 'P':6, 'M':7 } 661 | return condition_mapping.get(arg.upper(),-1) 662 | 663 | 664 | def dump(bytes): 665 | def initpage(page): 666 | memory[page] = array.array('B') 667 | 668 | memory[page].append(0) 669 | while len(memory[page]) < 16384: 670 | memory[page].extend(memory[page]) 671 | 672 | global dumppage, dumporigin, dumpspace_pending, lstcode, listingfile 673 | 674 | if (p==2): 675 | if dumpspace_pending > 0: 676 | if memory[dumppage]=='': 677 | initpage(dumppage) 678 | dumporigin += dumpspace_pending 679 | dumppage += dumporigin // 16384 680 | dumporigin %= 16384 681 | dumpspace_pending = 0 682 | 683 | if memory[dumppage]=='': 684 | initpage(dumppage) 685 | lstcode = "" 686 | for b in bytes: 687 | # if b<0 or b>255: 688 | # warning("Dump byte out of range") 689 | memory[dumppage][dumporigin] = b 690 | if listingfile != None: 691 | lstcode=lstcode+"%02X "%(b) 692 | dumporigin += 1 693 | if dumporigin == 16384: 694 | dumporigin = 0 695 | dumppage += 1 696 | 697 | if memory[dumppage]=='': 698 | initpage(dumppage) 699 | 700 | def check_args(args,expected): 701 | if args=='': 702 | received = 0 703 | else: 704 | received = len(args.split(',')) 705 | if expected!=received: 706 | fatal("Opcode wrong number of arguments, expected "+str(expected)+" received "+str(args)) 707 | 708 | def op_ORG(p,opargs): 709 | global origin 710 | check_args(opargs,1) 711 | origin = parse_expression(opargs, word=1) 712 | return 0 713 | 714 | def op_DUMP(p,opargs): 715 | global dumppage, dumporigin, dumpused, firstpage, firstpageoffset, dumpspace_pending 716 | 717 | if dumpused: 718 | check_lastpage() 719 | dumpused = True 720 | 721 | dumpspace_pending = 0 722 | if ',' in opargs: 723 | page,offset = opargs.split(',',1) 724 | offset = parse_expression(offset, word=1) 725 | dumppage = parse_expression(page) + (offset//16384) 726 | dumporigin = offset % 16384 727 | else: 728 | offset = parse_expression(opargs) 729 | if (offset<16384): 730 | fatal("DUMP value out of range") 731 | dumppage = (offset//16384) - 1 732 | dumporigin = offset % 16384 733 | 734 | if ((dumppage*16384 + dumporigin) < (firstpage*16384 + firstpageoffset)): 735 | firstpage = dumppage 736 | firstpageoffset = dumporigin 737 | 738 | return 0 739 | 740 | def op_PRINT(p, opargs): 741 | text = [] 742 | for expr in opargs.split(","): 743 | if expr.strip().startswith('"'): 744 | text.append(expr.strip().rstrip()[1:-1]) 745 | else: 746 | a = parse_expression(expr, silenterror=True) 747 | if a: 748 | text.append(f"{a:x}" if HEX else str(a)) 749 | else: 750 | text.append("?") 751 | print(global_currentfile, "PRINT: ", ",".join(text)) 752 | return 0 753 | 754 | def check_lastpage(): 755 | global lastpage, lastpageoffset 756 | if dumppage > lastpage: 757 | lastpage = dumppage 758 | lastpageoffset = dumporigin 759 | elif (dumppage == lastpage) and (dumporigin > lastpageoffset): 760 | lastpageoffset = dumporigin 761 | 762 | def op_AUTOEXEC(p,opargs): 763 | global autoexecpage, autoexecorigin 764 | check_args(opargs,0) 765 | if (p==2): 766 | if (autoexecpage>0 or autoexecorigin>0): 767 | fatal("AUTOEXEC may only be used once.") 768 | autoexecpage = dumppage + 1 # basic type page numbering 769 | autoexecorigin = dumporigin 770 | 771 | 772 | return 0 773 | 774 | def op_EQU(p,opargs): 775 | global symboltable 776 | check_args(opargs,1) 777 | if (symbol): 778 | if opargs.upper().startswith("FOR") and (opargs[3].isspace() or opargs[3]=='('): 779 | set_symbol(symbol, 0) 780 | 781 | limit = parse_expression(opargs[4:].strip()) 782 | if limit < 1: 783 | fatal("FOR range < 1 not allowed") 784 | forstack.append( [symbol,global_currentfile,0,limit] ) 785 | 786 | else: 787 | if p==1: 788 | set_symbol(symbol, parse_expression(opargs, signed=1, silenterror=1)) 789 | else: 790 | expr_result = parse_expression(opargs, signed=1) 791 | 792 | existing = get_symbol(symbol) 793 | 794 | if existing == '': 795 | set_symbol(symbol, expr_result) 796 | elif existing != expr_result: 797 | fatal("Symbol "+expand_symbol(symbol)+": expected "+str(existing)+" but calculated "+str(expr_result)+", has this symbol been used twice?") 798 | else: 799 | warning("EQU without symbol name") 800 | return 0 801 | 802 | def op_NEXT(p,opargs): 803 | global global_currentfile 804 | 805 | 806 | check_args(opargs,1) 807 | foritem = forstack.pop() 808 | if opargs != foritem[0]: 809 | fatal("NEXT symbol "+opargs+" doesn't match FOR: expected "+foritem[0]) 810 | foritem[2] += 1 811 | 812 | set_symbol(foritem[0], foritem[2], explicit_currentfile=foritem[1]) 813 | 814 | if foritem[2] < foritem[3]: 815 | global_currentfile = foritem[1] 816 | forstack.append(foritem) 817 | 818 | return 0 819 | 820 | def op_ALIGN(p,opargs): 821 | global dumpspace_pending 822 | check_args(opargs,1) 823 | 824 | align = parse_expression(opargs) 825 | if align<1: 826 | fatal("Invalid alignment") 827 | elif (align & (-align)) != align: 828 | fatal("Alignment is not a power of 2") 829 | 830 | s = (align - origin%align)%align 831 | dumpspace_pending += s 832 | return s 833 | 834 | def op_DS(p,opargs): 835 | return op_DEFS(p,opargs) 836 | def op_DEFS(p,opargs): 837 | global dumppage, dumporigin, dumpspace_pending 838 | check_args(opargs,1) 839 | 840 | if opargs.upper().startswith("ALIGN") and (opargs[5].isspace() or opargs[5]=='('): 841 | return op_ALIGN(p,opargs[5:].strip()) 842 | 843 | s = parse_expression(opargs) 844 | if s<0: 845 | fatal("Allocated invalid space < 0 bytes ("+str(s)+")") 846 | dumpspace_pending += s 847 | return s 848 | 849 | def op_DB(p,opargs): 850 | return op_DEFB(p,opargs) 851 | def op_DEFB(p,opargs): 852 | s = opargs.split(',') 853 | if (p==2): 854 | for b in s: 855 | byte=(parse_expression(b, byte=1, silenterror=1)) 856 | if byte=='': 857 | fatal("Didn't understand DB or character constant "+b) 858 | else: 859 | dump([byte]) 860 | return len(s) 861 | 862 | def op_DW(p,opargs): 863 | return op_DEFW(p,opargs) 864 | def op_DEFW(p,opargs): 865 | s = opargs.split(',') 866 | if (p==2): 867 | for b in s: 868 | b=(parse_expression(b, word=1)) 869 | dump([b%256, b//256]) 870 | return 2*len(s) 871 | 872 | def op_DM(p,opargs): 873 | return op_DEFM(p,opargs) 874 | def op_DEFM(p,opargs): 875 | messagelen = 0 876 | if opargs.strip()=="44" or opargs=="(44)": 877 | dump ([44]) 878 | messagelen = 1 879 | else: 880 | matchstr = opargs 881 | while matchstr.strip(): 882 | match = re.match(r'\s*\"(.*)\"(\s*,)?(.*)', matchstr) 883 | if not match: 884 | match = re.match(r'\s*([^,]*)(\s*,)?(.*)', matchstr) 885 | byte=(parse_expression(match.group(1), byte=1, silenterror=1)) 886 | if byte=='': 887 | fatal("Didn't understand DM character constant "+match.group(1)) 888 | elif p==2: 889 | dump([byte]) 890 | 891 | messagelen += 1 892 | else: 893 | message = list(match.group(1)) 894 | 895 | if p==2: 896 | for i in message: 897 | dump ([ord(i)]) 898 | messagelen += len(message) 899 | 900 | matchstr = match.group(3) 901 | 902 | if match.group(3) and not match.group(2): 903 | matchstr = '""' + matchstr 904 | # For cases such as DEFM "message with a "" in it" 905 | # I can only apologise for this, this is an artefact of my parsing quotes 906 | # badly at the top level but it's too much for me to go back and refactor it all. 907 | # Of course, it would have helped if Comet had had sane quoting rules in the first place. 908 | return messagelen 909 | 910 | def op_MDAT(p,opargs): 911 | global dumppage, dumporigin 912 | match = re.search(r'\A\s*\"(.*)\"\s*\Z', opargs) 913 | filename = os.path.join(global_path, match.group(1)) 914 | 915 | try: 916 | mdatfile = open(filename,'rb') 917 | except: 918 | fatal("Unable to open file for reading: "+filename) 919 | 920 | mdatfile.seek(0,2) 921 | filelength = mdatfile.tell() 922 | if p==1: 923 | dumporigin += filelength 924 | dumppage += dumporigin // 16384 925 | dumporigin %= 16384 926 | elif p==2: 927 | mdatfile.seek(0) 928 | mdatafilearray = array.array('B') 929 | mdatafilearray.fromfile(mdatfile, filelength) 930 | dump(mdatafilearray) 931 | 932 | mdatfile.close() 933 | return filelength 934 | 935 | def op_INCLUDE(p,opargs): 936 | global global_path, global_currentfile 937 | global include_stack 938 | 939 | match = re.search(r'\A\s*\"(.*)\"\s*\Z', opargs) 940 | filename = match.group(1) 941 | 942 | include_stack.append((global_path, global_currentfile)) 943 | assembler_pass(p, filename) 944 | global_path, global_currentfile = include_stack.pop() 945 | 946 | return 0 947 | # global origin has already been updated 948 | 949 | 950 | def op_FOR(p,opargs): 951 | args = opargs.split(',',1) 952 | limit = parse_expression(args[0]) 953 | bytes = 0 954 | for iterate in range(limit): 955 | symboltable['FOR'] = iterate 956 | if CASE: 957 | symboltable['for'] = iterate 958 | bytes += assemble_instruction(p,args[1].strip()) 959 | 960 | if limit != 0: 961 | del symboltable['FOR'] 962 | if CASE: 963 | del symboltable['for'] 964 | 965 | return bytes 966 | 967 | def op_noargs_type(p,opargs,instr): 968 | check_args(opargs,0) 969 | if (p==2): 970 | dump(instr) 971 | return len(instr) 972 | 973 | def op_ASSERT(p,opargs): 974 | check_args(opargs,1) 975 | if (p==2): 976 | value = parse_expression(opargs) 977 | if value == 0: 978 | fatal("Assertion failed ("+opargs+")") 979 | return 0 980 | 981 | 982 | def op_NOP(p,opargs): 983 | return op_noargs_type(p,opargs,[0x00]) 984 | def op_RLCA(p,opargs): 985 | return op_noargs_type(p,opargs,[0x07]) 986 | def op_RRCA(p,opargs): 987 | return op_noargs_type(p,opargs,[0x0F]) 988 | def op_RLA(p,opargs): 989 | return op_noargs_type(p,opargs,[0x17]) 990 | def op_RRA(p,opargs): 991 | return op_noargs_type(p,opargs,[0x1F]) 992 | def op_DAA(p,opargs): 993 | return op_noargs_type(p,opargs,[0x27]) 994 | def op_CPL(p,opargs): 995 | return op_noargs_type(p,opargs,[0x2F]) 996 | def op_SCF(p,opargs): 997 | return op_noargs_type(p,opargs,[0x37]) 998 | def op_CCF(p,opargs): 999 | return op_noargs_type(p,opargs,[0x3F]) 1000 | def op_HALT(p,opargs): 1001 | return op_noargs_type(p,opargs,[0x76]) 1002 | def op_DI(p,opargs): 1003 | return op_noargs_type(p,opargs,[0xf3]) 1004 | def op_EI(p,opargs): 1005 | return op_noargs_type(p,opargs,[0xfb]) 1006 | def op_EXX(p,opargs): 1007 | return op_noargs_type(p,opargs,[0xd9]) 1008 | def op_NEG(p,opargs): 1009 | return op_noargs_type(p,opargs,[0xed,0x44]) 1010 | def op_RETN(p,opargs): 1011 | return op_noargs_type(p,opargs,[0xed,0x45]) 1012 | def op_RETI(p,opargs): 1013 | return op_noargs_type(p,opargs,[0xed,0x4d]) 1014 | def op_RRD(p,opargs): 1015 | return op_noargs_type(p,opargs,[0xed,0x67]) 1016 | def op_RLD(p,opargs): 1017 | return op_noargs_type(p,opargs,[0xed,0x6F]) 1018 | def op_LDI(p,opargs): 1019 | return op_noargs_type(p,opargs,[0xed,0xa0]) 1020 | def op_CPI(p,opargs): 1021 | return op_noargs_type(p,opargs,[0xed,0xa1]) 1022 | def op_INI(p,opargs): 1023 | return op_noargs_type(p,opargs,[0xed,0xa2]) 1024 | def op_OUTI(p,opargs): 1025 | return op_noargs_type(p,opargs,[0xed,0xa3]) 1026 | def op_LDD(p,opargs): 1027 | return op_noargs_type(p,opargs,[0xed,0xa8]) 1028 | def op_CPD(p,opargs): 1029 | return op_noargs_type(p,opargs,[0xed,0xa9]) 1030 | def op_IND(p,opargs): 1031 | return op_noargs_type(p,opargs,[0xed,0xaa]) 1032 | def op_OUTD(p,opargs): 1033 | return op_noargs_type(p,opargs,[0xed,0xab]) 1034 | def op_LDIR(p,opargs): 1035 | return op_noargs_type(p,opargs,[0xed,0xb0]) 1036 | def op_CPIR(p,opargs): 1037 | return op_noargs_type(p,opargs,[0xed,0xb1]) 1038 | def op_INIR(p,opargs): 1039 | return op_noargs_type(p,opargs,[0xed,0xb2]) 1040 | def op_OTIR(p,opargs): 1041 | return op_noargs_type(p,opargs,[0xed,0xb3]) 1042 | def op_LDDR(p,opargs): 1043 | return op_noargs_type(p,opargs,[0xed,0xb8]) 1044 | def op_CPDR(p,opargs): 1045 | return op_noargs_type(p,opargs,[0xed,0xb9]) 1046 | def op_INDR(p,opargs): 1047 | return op_noargs_type(p,opargs,[0xed,0xba]) 1048 | def op_OTDR(p,opargs): 1049 | return op_noargs_type(p,opargs,[0xed,0xbb]) 1050 | 1051 | def op_cbshifts_type(p,opargs,offset,step_per_register=1): 1052 | args = opargs.split(',',1) 1053 | if len(args) == 2: 1054 | # compound instruction of the form RLC B,(IX+c) 1055 | pre1,r1,post1 = single(args[0], allow_half=0, allow_index=0) 1056 | pre2,r2,post2 = single(args[1], allow_half=0, allow_index=1) 1057 | if r1==-1 or r2==-1: 1058 | fatal("Registers not recognized for compound instruction") 1059 | if r1==6: 1060 | fatal("(HL) not allowed as target of compound instruction") 1061 | if len(pre2)==0: 1062 | fatal("Must use index register as operand of compound instruction") 1063 | 1064 | instr=pre2 1065 | instr.extend([0xcb]) 1066 | instr.extend(post2) 1067 | instr.append(offset + step_per_register*r1) 1068 | 1069 | else: 1070 | check_args(opargs,1) 1071 | pre,r,post = single(opargs,allow_half=0) 1072 | instr = pre 1073 | instr.extend([0xcb]) 1074 | instr.extend(post) 1075 | if r==-1: 1076 | fatal ("Invalid argument") 1077 | else: 1078 | instr.append(offset + step_per_register*r) 1079 | 1080 | if (p==2): 1081 | dump(instr) 1082 | return len(instr) 1083 | 1084 | def op_RLC(p,opargs): 1085 | return op_cbshifts_type(p,opargs,0x00) 1086 | def op_RRC(p,opargs): 1087 | return op_cbshifts_type(p,opargs,0x08) 1088 | def op_RL(p,opargs): 1089 | return op_cbshifts_type(p,opargs,0x10) 1090 | def op_RR(p,opargs): 1091 | return op_cbshifts_type(p,opargs,0x18) 1092 | def op_SLA(p,opargs): 1093 | return op_cbshifts_type(p,opargs,0x20) 1094 | def op_SRA(p,opargs): 1095 | return op_cbshifts_type(p,opargs,0x28) 1096 | def op_SLL(p,opargs): 1097 | if (p==1): 1098 | warning("SLL doesn't do what you probably expect on z80b! Use SL1 if you know what you're doing.") 1099 | return op_cbshifts_type(p,opargs,0x30) 1100 | def op_SL1(p,opargs): 1101 | return op_cbshifts_type(p,opargs,0x30) 1102 | def op_SRL(p,opargs): 1103 | return op_cbshifts_type(p,opargs,0x38) 1104 | 1105 | 1106 | 1107 | def op_register_arg_type(p,opargs,offset,ninstr,step_per_register=1): 1108 | check_args(opargs,1) 1109 | pre,r,post = single(opargs,allow_half=1) 1110 | instr = pre 1111 | if r==-1: 1112 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", opargs) 1113 | if match: 1114 | fatal ("Illegal indirection") 1115 | 1116 | instr.extend(ninstr) 1117 | if (p==2): 1118 | n = parse_expression(opargs, byte=1) 1119 | else: 1120 | n = 0 1121 | instr.append(n) 1122 | else: 1123 | instr.append(offset + step_per_register*r) 1124 | instr.extend(post) 1125 | if (p==2): 1126 | dump(instr) 1127 | return len(instr) 1128 | 1129 | def op_SUB(p,opargs): 1130 | return op_register_arg_type(p,opargs, 0x90, [0xd6]) 1131 | def op_AND(p,opargs): 1132 | return op_register_arg_type(p,opargs, 0xa0, [0xe6]) 1133 | def op_XOR(p,opargs): 1134 | return op_register_arg_type(p,opargs, 0xa8, [0xee]) 1135 | def op_OR(p,opargs): 1136 | return op_register_arg_type(p,opargs, 0xb0, [0xf6]) 1137 | def op_CP(p,opargs): 1138 | return op_register_arg_type(p,opargs, 0xb8, [0xfe]) 1139 | 1140 | def op_registerorpair_arg_type(p,opargs,rinstr,rrinstr,step_per_register=8,step_per_pair=16): 1141 | check_args(opargs,1) 1142 | pre,r,post = single(opargs) 1143 | 1144 | if r==-1: 1145 | pre,rr = double(opargs) 1146 | if rr==-1: 1147 | fatal ("Invalid argument") 1148 | 1149 | instr = pre 1150 | instr.append(rrinstr + step_per_pair*rr) 1151 | else: 1152 | instr = pre 1153 | instr.append(rinstr + step_per_register*r) 1154 | instr.extend(post) 1155 | if (p==2): 1156 | dump(instr) 1157 | return len(instr) 1158 | 1159 | def op_INC(p,opargs): 1160 | # Oh dear - COMET also used "INC" for INClude source file 1161 | if '"' in opargs: 1162 | return op_INCLUDE(p,opargs) 1163 | 1164 | return op_registerorpair_arg_type(p,opargs, 0x04, 0x03) 1165 | def op_DEC(p,opargs): 1166 | return op_registerorpair_arg_type(p,opargs, 0x05, 0x0b) 1167 | 1168 | def op_add_type(p,opargs,rinstr,ninstr,rrinstr,step_per_register=1,step_per_pair=16): 1169 | args = opargs.split(',',1) 1170 | r=-1 1171 | 1172 | if len(args) == 2: 1173 | pre,r,post = single(args[0]) 1174 | 1175 | if (len(args) == 1) or r==7: 1176 | pre,r,post = single(args[-1]) 1177 | instr = pre 1178 | if r==-1: 1179 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", args[-1]) 1180 | if match: 1181 | fatal ("Illegal indirection") 1182 | 1183 | instr.extend(ninstr) 1184 | if (p==2): 1185 | n = parse_expression(args[-1], byte=1) 1186 | else: 1187 | n = 0 1188 | instr.append(n) 1189 | else: 1190 | instr.extend(rinstr) 1191 | instr[-1] += step_per_register*r 1192 | 1193 | instr.extend(post) 1194 | else: 1195 | pre,rr1 = double(args[0]) 1196 | dummy,rr2 = double(args[1]) 1197 | 1198 | if (rr1 == rr2) and (pre != dummy): 1199 | fatal ("Can't mix index registers and HL") 1200 | if (len(rrinstr) > 1) and pre: 1201 | fatal ("Can't use index registers in this instruction") 1202 | 1203 | if (len(args) != 2) or (rr1 != 2) or (rr2 == -1): 1204 | fatal("Invalid argument") 1205 | 1206 | instr = pre 1207 | instr.extend(rrinstr) 1208 | instr[-1] += step_per_pair*rr2 1209 | 1210 | if (p==2): 1211 | dump(instr) 1212 | return len(instr) 1213 | 1214 | def op_ADD(p,opargs): 1215 | return op_add_type(p,opargs,[0x80], [0xc6],[0x09]) 1216 | def op_ADC(p,opargs): 1217 | return op_add_type(p,opargs,[0x88], [0xce],[0xed,0x4a]) 1218 | def op_SBC(p,opargs): 1219 | return op_add_type(p,opargs,[0x98], [0xde],[0xed,0x42]) 1220 | 1221 | def op_bit_type(p,opargs,offset): 1222 | check_args(opargs,2) 1223 | arg1,arg2 = opargs.split(',',1) 1224 | b = parse_expression(arg1) 1225 | if b>7 or b<0: 1226 | fatal ("argument out of range") 1227 | pre,r,post = single(arg2,allow_half=0) 1228 | if r==-1: 1229 | fatal ("Invalid argument") 1230 | instr = pre 1231 | instr.append(0xcb) 1232 | instr.extend(post) 1233 | instr.append(offset + r + 8*b) 1234 | if (p==2): 1235 | dump(instr) 1236 | return len(instr) 1237 | 1238 | def op_BIT(p,opargs): 1239 | return op_bit_type(p,opargs, 0x40) 1240 | def op_RES(p,opargs): 1241 | return op_bit_type(p,opargs, 0x80) 1242 | def op_SET(p,opargs): 1243 | return op_bit_type(p,opargs, 0xc0) 1244 | 1245 | def op_pushpop_type(p,opargs,offset): 1246 | 1247 | check_args(opargs,1) 1248 | prefix, rr = double(opargs, allow_af_instead_of_sp=1) 1249 | instr = prefix 1250 | if rr==-1: 1251 | fatal ("Invalid argument") 1252 | else: 1253 | instr.append(offset + 16 * rr) 1254 | if (p==2): 1255 | dump(instr) 1256 | return len(instr) 1257 | 1258 | def op_POP(p,opargs): 1259 | return op_pushpop_type(p,opargs, 0xc1) 1260 | def op_PUSH(p,opargs): 1261 | return op_pushpop_type(p,opargs, 0xc5) 1262 | 1263 | def op_jumpcall_type(p,opargs,offset, condoffset): 1264 | args = opargs.split(',',1) 1265 | if len(args) == 1: 1266 | instr = [offset] 1267 | else: 1268 | cond = condition(args[0]) 1269 | if cond == -1: 1270 | fatal ("Expected condition, received "+opargs) 1271 | instr = [condoffset + 8*cond] 1272 | 1273 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", args[-1]) 1274 | if match: 1275 | fatal ("Illegal indirection") 1276 | 1277 | if (p==2): 1278 | nn = parse_expression(args[-1],word=1) 1279 | instr.extend([nn%256, nn//256]) 1280 | dump(instr) 1281 | 1282 | return 3 1283 | 1284 | def op_JP(p,opargs): 1285 | if (len(opargs.split(',',1)) == 1): 1286 | prefix, r, postfix = single(opargs, allow_offset=0,allow_half=0) 1287 | if r==6: 1288 | instr = prefix 1289 | instr.append(0xe9) 1290 | if (p==2): 1291 | dump(instr) 1292 | return len(instr) 1293 | return op_jumpcall_type(p,opargs, 0xc3, 0xc2) 1294 | 1295 | def op_CALL(p,opargs): 1296 | return op_jumpcall_type(p,opargs, 0xcd, 0xc4) 1297 | 1298 | def op_DJNZ(p,opargs): 1299 | check_args(opargs,1) 1300 | if (p==2): 1301 | target = parse_expression(opargs,word=1) 1302 | displacement = target - (origin + 2) 1303 | if displacement > 127 or displacement < -128: 1304 | fatal ("Displacement from "+str(origin)+" to "+str(target)+" is out of range") 1305 | dump([0x10,(displacement+256)%256]) 1306 | 1307 | return 2 1308 | 1309 | def op_JR(p,opargs): 1310 | args = opargs.split(',',1) 1311 | if len(args) == 1: 1312 | instr = 0x18 1313 | else: 1314 | cond = condition(args[0]) 1315 | if cond == -1: 1316 | fatal ("Expected condition, received "+opargs) 1317 | elif cond >= 4: 1318 | fatal ("Invalid condition for JR") 1319 | instr = 0x20 + 8*cond 1320 | if (p==2): 1321 | target = parse_expression(args[-1],word=1) 1322 | displacement = target - (origin + 2) 1323 | if displacement > 127 or displacement < -128: 1324 | fatal ("Displacement from "+str(origin)+" to "+str(target)+" is out of range") 1325 | dump([instr,(displacement+256)%256]) 1326 | 1327 | return 2 1328 | 1329 | def op_RET(p,opargs): 1330 | if opargs=='': 1331 | if (p==2): 1332 | dump([0xc9]) 1333 | else: 1334 | check_args(opargs,1) 1335 | cond = condition(opargs) 1336 | if cond == -1: 1337 | fatal ("Expected condition, received "+opargs) 1338 | if (p==2): 1339 | dump([0xc0 + 8*cond]) 1340 | return 1 1341 | 1342 | def op_IM(p,opargs): 1343 | check_args(opargs,1) 1344 | if (p==2): 1345 | mode = parse_expression(opargs) 1346 | if (mode>2) or (mode<0): 1347 | fatal ("argument out of range") 1348 | if mode > 0: 1349 | mode += 1 1350 | 1351 | dump([0xed, 0x46 + 8*mode]) 1352 | return 2 1353 | 1354 | def op_RST(p,opargs): 1355 | check_args(opargs,1) 1356 | if (p==2): 1357 | vector = parse_expression(opargs) 1358 | if (vector>0x38) or (vector<0) or ((vector%8) != 0): 1359 | fatal ("argument out of range or doesn't divide by 8") 1360 | 1361 | dump([0xc7 + vector]) 1362 | return 1 1363 | 1364 | def op_EX(p,opargs): 1365 | check_args(opargs,2) 1366 | args = opargs.split(',',1) 1367 | 1368 | if re.search(r"\A\s*\(\s*SP\s*\)\s*\Z", args[0], re.IGNORECASE): 1369 | pre2,rr2 = double(args[1],allow_af_instead_of_sp=1, allow_af_alt=1, allow_index=1) 1370 | 1371 | if rr2==2: 1372 | instr = pre2 1373 | instr.append(0xe3) 1374 | else: 1375 | fatal("Can't exchange "+args[0]+" with "+args[1]) 1376 | else: 1377 | pre1,rr1 = double(args[0],allow_af_instead_of_sp=1, allow_index=0) 1378 | pre2,rr2 = double(args[1],allow_af_instead_of_sp=1, allow_af_alt=1, allow_index=0) 1379 | 1380 | if rr1==1 and rr2==2: 1381 | # EX DE,HL 1382 | instr = pre1 1383 | instr.extend(pre2) 1384 | instr.append(0xeb) 1385 | elif (rr1==3 and rr2==4): 1386 | instr=[0x08] 1387 | else: 1388 | fatal("Can't exchange "+args[0]+" with "+args[1]) 1389 | 1390 | if (p==2): 1391 | dump(instr) 1392 | 1393 | return len(instr) 1394 | 1395 | def op_IN(p,opargs): 1396 | check_args(opargs,2) 1397 | args = opargs.split(',',1) 1398 | if (p==2): 1399 | pre,r,post = single(args[0],allow_index=0,allow_half=0) 1400 | if r!=-1 and r!=6 and re.search(r"\A\s*\(\s*C\s*\)\s*\Z", args[1], re.IGNORECASE): 1401 | dump([0xed, 0x40+8*r]) 1402 | elif r==7: 1403 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", args[1]) 1404 | if match==None: 1405 | fatal("No expression in "+args[1]) 1406 | 1407 | n = parse_expression(match.group(1)) 1408 | dump([0xdb, n]) 1409 | else: 1410 | fatal("Invalid argument") 1411 | return 2 1412 | 1413 | def op_OUT(p,opargs): 1414 | check_args(opargs,2) 1415 | args = opargs.split(',',1) 1416 | if (p==2): 1417 | pre,r,post = single(args[1],allow_index=0,allow_half=0) 1418 | if r!=-1 and r!=6 and re.search(r"\A\s*\(\s*C\s*\)\s*\Z", args[0], re.IGNORECASE): 1419 | dump([0xed, 0x41+8*r]) 1420 | elif r==7: 1421 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", args[0]) 1422 | n = parse_expression(match.group(1)) 1423 | dump([0xd3, n]) 1424 | else: 1425 | fatal("Invalid argument") 1426 | return 2 1427 | 1428 | def op_LD(p,opargs): 1429 | check_args(opargs,2) 1430 | arg1,arg2 = opargs.split(',',1) 1431 | 1432 | prefix, rr1 = double(arg1) 1433 | if rr1 != -1: 1434 | prefix2, rr2 = double(arg2) 1435 | if rr1==3 and rr2==2: 1436 | instr = prefix2 1437 | instr.append(0xf9) 1438 | dump(instr) 1439 | return len(instr) 1440 | 1441 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", arg2) 1442 | if match: 1443 | # ld rr, (nn) 1444 | if p==2: 1445 | nn = parse_expression(match.group(1),word=1) 1446 | else: 1447 | nn = 0 1448 | instr = prefix 1449 | if rr1==2: 1450 | instr.extend([0x2a, nn%256, nn//256]) 1451 | else: 1452 | instr.extend([0xed, 0x4b + 16*rr1, nn%256, nn//256]) 1453 | dump(instr) 1454 | return len (instr) 1455 | else: 1456 | #ld rr, nn 1457 | if p==2: 1458 | nn = parse_expression(arg2,word=1) 1459 | else: 1460 | nn = 0 1461 | instr = prefix 1462 | instr.extend([0x01 + 16*rr1, nn%256, nn//256]) 1463 | dump(instr) 1464 | return len (instr) 1465 | 1466 | prefix, rr2 = double(arg2) 1467 | if rr2 != -1: 1468 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", arg1) 1469 | if match: 1470 | # ld (nn), rr 1471 | if p==2: 1472 | nn = parse_expression(match.group(1)) 1473 | else: 1474 | nn = 0 1475 | instr = prefix 1476 | if rr2==2: 1477 | instr.extend([0x22, nn%256, nn//256]) 1478 | else: 1479 | instr.extend([0xed, 0x43 + 16*rr2, nn%256, nn//256]) 1480 | dump(instr) 1481 | return len (instr) 1482 | 1483 | prefix1,r1,postfix1 = single(arg1, allow_i=1, allow_r=1) 1484 | prefix2,r2,postfix2 = single(arg2, allow_i=1, allow_r=1) 1485 | if r1 != -1 : 1486 | if r2 != -1: 1487 | if (r1 > 7) or (r2 > 7): 1488 | if r1==7: 1489 | if r2==8: 1490 | dump([0xed,0x57]) 1491 | return 2 1492 | elif r2==9: 1493 | dump([0xed,0x5f]) 1494 | return 2 1495 | if r2==7: 1496 | if r1==8: 1497 | dump([0xed,0x47]) 1498 | return 2 1499 | elif r1==9: 1500 | dump([0xed,0x4f]) 1501 | return 2 1502 | fatal("Invalid argument") 1503 | 1504 | if r1==6 and r2==6: 1505 | fatal("Ha - nice try. That's a HALT.") 1506 | 1507 | if (r1==4 or r1==5) and (r2==4 or r2==5) and prefix1 != prefix2: 1508 | fatal("Illegal combination of operands") 1509 | 1510 | if r1==6 and (r2==4 or r2==5) and len(prefix2) != 0: 1511 | fatal("Illegal combination of operands") 1512 | 1513 | if r2==6 and (r1==4 or r1==5) and len(prefix1) != 0: 1514 | fatal("Illegal combination of operands") 1515 | 1516 | instr = prefix1 1517 | if len(prefix1) == 0: 1518 | instr.extend(prefix2) 1519 | instr.append(0x40 + 8*r1 + r2) 1520 | instr.extend(postfix1) 1521 | instr.extend(postfix2) 1522 | dump(instr) 1523 | return len(instr) 1524 | 1525 | else: 1526 | if r1 > 7: 1527 | fatal("Invalid argument") 1528 | 1529 | if r1==7 and re.search(r"\A\s*\(\s*BC\s*\)\s*\Z", arg2, re.IGNORECASE): 1530 | dump([0x0a]) 1531 | return 1 1532 | if r1==7 and re.search(r"\A\s*\(\s*DE\s*\)\s*\Z", arg2, re.IGNORECASE): 1533 | dump([0x1a]) 1534 | return 1 1535 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", arg2) 1536 | if match: 1537 | if r1 != 7: 1538 | fatal("Illegal indirection") 1539 | if p==2: 1540 | nn = parse_expression(match.group(1), word=1) 1541 | dump([0x3a, nn%256, nn//256]) 1542 | return 3 1543 | 1544 | instr = prefix1 1545 | instr.append(0x06 + 8*r1) 1546 | instr.extend(postfix1) 1547 | if (p==2): 1548 | n = parse_expression(arg2, byte=1) 1549 | else: 1550 | n = 0 1551 | instr.append(n) 1552 | dump(instr) 1553 | return len(instr) 1554 | 1555 | elif r2==7: 1556 | # ld (bc/de/nn),a 1557 | if re.search(r"\A\s*\(\s*BC\s*\)\s*\Z", arg1, re.IGNORECASE): 1558 | dump([0x02]) 1559 | return 1 1560 | if re.search(r"\A\s*\(\s*DE\s*\)\s*\Z", arg1, re.IGNORECASE): 1561 | dump([0x12]) 1562 | return 1 1563 | match = re.search(r"\A\s*\(\s*(.*)\s*\)\s*\Z", arg1) 1564 | if match: 1565 | if p==2: 1566 | nn = parse_expression(match.group(1), word=1) 1567 | dump([0x32, nn%256, nn//256]) 1568 | return 3 1569 | fatal("LD args not understood - "+arg1+", "+arg2) 1570 | 1571 | return 1 1572 | 1573 | #ifstate=0: parse all code 1574 | #ifstate=1: parse this code, but stop at ELSE 1575 | #ifstate=2: do not parse this code, but might start at ELSE 1576 | #ifstate=3: do not parse any code until ENDIF 1577 | 1578 | def op_IF(p,opargs): 1579 | global ifstate, ifstack 1580 | check_args(opargs,1) 1581 | 1582 | ifstack.append( (global_currentfile,ifstate) ) 1583 | if ifstate < 2: 1584 | cond = parse_expression(opargs) 1585 | if cond: 1586 | ifstate = 1 1587 | else: 1588 | ifstate = 2 1589 | else: 1590 | ifstate = 3 1591 | return 0 1592 | 1593 | def op_ELSE(p,opargs): 1594 | global ifstate, ifstack 1595 | if ifstate==1 or ifstate==3: 1596 | ifstate = 3 1597 | elif ifstate==2: 1598 | if opargs.upper().startswith("IF"): 1599 | cond = parse_expression(opargs[2:].strip()) 1600 | if cond: 1601 | ifstate = 1 1602 | else: 1603 | ifstate = 2 1604 | else: 1605 | ifstate = 1 1606 | else: 1607 | fatal("Mismatched ELSE") 1608 | 1609 | return 0 1610 | 1611 | def op_ENDIF(p,opargs): 1612 | global ifstate, ifstack 1613 | check_args(opargs,0) 1614 | 1615 | if len(ifstack) == 0: 1616 | fatal("Mismatched ENDIF") 1617 | 1618 | ifline,state = ifstack.pop() 1619 | ifstate = state 1620 | 1621 | return 0 1622 | 1623 | def assemble_instruction(p, line): 1624 | match = re.match(r'^(\w+)(.*)', line) 1625 | if not match: 1626 | fatal("Expected opcode or directive") 1627 | 1628 | inst = match.group(1).upper() 1629 | args = match.group(2).strip() 1630 | 1631 | if (ifstate < 2) or inst in ('IF', 'ELSE', 'ENDIF'): 1632 | functioncall = 'op_'+inst+'(p,args)' 1633 | if PYTHONERRORS: 1634 | return eval(functioncall) 1635 | else: 1636 | try: 1637 | return eval(functioncall) 1638 | except SystemExit as e: 1639 | sys.exit(e) 1640 | except: 1641 | fatal("Opcode not recognised") 1642 | else: 1643 | return 0 1644 | 1645 | def assembler_pass(p, inputfile): 1646 | global memory, symboltable, symusetable, labeltable, origin, dumppage, dumporigin, symbol 1647 | global global_currentfile, global_currentline, lstcode, listingfile 1648 | # file references are local, so assembler_pass can be called recursively (for op_INC) 1649 | # but copied to a global identifier for warning printouts 1650 | global global_path 1651 | 1652 | global_currentfile="command line" 1653 | global_currentline="0" 1654 | 1655 | # just read the whole file into memory, it's not going to be huge (probably) 1656 | # I'd prefer not to, but assembler_pass can be called recursively 1657 | # (by op_INCLUDE for example) and fileinput does not support two files simultaneously 1658 | 1659 | this_currentfilename = os.path.join(global_path, inputfile) 1660 | if os.sep in this_currentfilename: 1661 | global_path = os.path.dirname(this_currentfilename) 1662 | 1663 | try: 1664 | currentfile = open(this_currentfilename,'r',encoding='utf-8-sig') 1665 | wholefile=currentfile.readlines() 1666 | wholefile.insert(0, '') # prepend blank so line numbers are 1-based 1667 | currentfile.close() 1668 | except: 1669 | fatal("Couldn't open file "+this_currentfilename+" for reading") 1670 | 1671 | 1672 | consider_linenumber=0 1673 | while consider_linenumber < len(wholefile): 1674 | 1675 | currentline = wholefile[consider_linenumber] 1676 | 1677 | global_currentline = currentline 1678 | global_currentfile = this_currentfilename+":"+str(consider_linenumber) 1679 | # write these every instruction because an INCLUDE may have overwritten them 1680 | 1681 | symbol = '' 1682 | opcode = '' 1683 | inquotes = '' 1684 | inquoteliteral = False 1685 | i = "" 1686 | for nexti in currentline+" ": 1687 | if (i==';' or i=='#') and not inquotes: 1688 | break 1689 | if i==':' and not inquotes: 1690 | symbol = opcode 1691 | opcode='' 1692 | i = '' 1693 | 1694 | if i == '"': 1695 | if not inquotes: 1696 | inquotes = i 1697 | else: 1698 | if (not inquoteliteral) and nexti=='"': 1699 | inquoteliteral = True 1700 | 1701 | elif inquoteliteral: 1702 | inquoteliteral = False 1703 | inquotes += i 1704 | 1705 | else: 1706 | inquotes += i 1707 | 1708 | if inquotes == '""': 1709 | inquotes = '"""' 1710 | elif inquotes == '","': 1711 | inquotes = " 44 " 1712 | i = "" 1713 | 1714 | opcode += inquotes 1715 | inquotes = "" 1716 | elif inquotes: 1717 | inquotes += i 1718 | else: 1719 | opcode += i 1720 | 1721 | i = nexti 1722 | 1723 | symbol = symbol.strip() 1724 | opcode = opcode.strip() 1725 | 1726 | if inquotes: 1727 | fatal("Mismatched quotes") 1728 | 1729 | if len( symbol.split()) > 1: 1730 | fatal("Whitespace not allowed in symbol name") 1731 | 1732 | if (symbol and (opcode[0:3].upper() !="EQU") and (ifstate < 2)): 1733 | if p==1: 1734 | set_symbol(symbol, origin, is_label=True) 1735 | elif get_symbol(symbol) != origin: 1736 | fatal("Symbol "+symbol+": expected "+str(get_symbol(symbol))+" but calculated "+str(origin)+", has this symbol been used twice?") 1737 | 1738 | if (opcode): 1739 | bytes = assemble_instruction(p,opcode) 1740 | if p>1 and listingfile != None: 1741 | lstout="%04X %-13s\t%s"%(origin,lstcode,wholefile[consider_linenumber].rstrip()) 1742 | lstcode="" 1743 | writelisting(lstout) 1744 | origin = (origin + bytes) % 65536 1745 | else: 1746 | if p>1 and listingfile != None: 1747 | lstout=" %-13s\t%s"%("",wholefile[consider_linenumber].rstrip()) 1748 | lstcode="" 1749 | writelisting(lstout) 1750 | 1751 | if global_currentfile.startswith(this_currentfilename+":") and int(global_currentfile.rsplit(':',1)[1]) != consider_linenumber: 1752 | consider_linenumber = int(global_currentfile.rsplit(':', 1)[1]) 1753 | 1754 | consider_linenumber += 1 1755 | 1756 | ########################################################################### 1757 | 1758 | try: 1759 | option_args, file_args = getopt.getopt(sys.argv[1:], 'ho:s:eD:B:I:x', ['version','help','nozip','obj=','case','nobodmas','intdiv','exportfile=','importfile=','mapfile=','lstfile=']) 1760 | file_args = [os.path.normpath(x) for x in file_args] 1761 | except getopt.GetoptError: 1762 | printusage() 1763 | sys.exit(2) 1764 | 1765 | inputfile = '' 1766 | outputfile = '' 1767 | objectfile = '' 1768 | 1769 | PYTHONERRORS = False 1770 | ZIP = True 1771 | CASE = False 1772 | NOBODMAS = False 1773 | INTDIV = False 1774 | HEX = False 1775 | SPT = None 1776 | 1777 | lstcode="" 1778 | listsymbols=[] 1779 | predefsymbols=[] 1780 | includefiles=[] 1781 | importfiles=[] 1782 | bootfile = None 1783 | exportfile = None 1784 | mapfile = None 1785 | listingfile = None 1786 | 1787 | def writelisting(line): 1788 | if listingfile != None: 1789 | listingfile.write(line+"\n") 1790 | 1791 | for option,value in option_args: 1792 | if option in ['--version']: 1793 | printusage() 1794 | print("") 1795 | printlicense() 1796 | sys.exit(0) 1797 | if option in ['--help','-h']: 1798 | printusage() 1799 | sys.exit(0) 1800 | 1801 | if option in ['-o']: 1802 | outputfile=value 1803 | 1804 | if option in ['--obj']: 1805 | objectfile=value 1806 | 1807 | if option in ['-s']: 1808 | listsymbols.append(value) 1809 | 1810 | if option in ['-e']: 1811 | PYTHONERRORS = True # let python do its own error handling 1812 | 1813 | if option in ['-x']: 1814 | HEX = True 1815 | 1816 | if option in ['--nozip']: 1817 | ZIP = False # save the disk image without compression 1818 | 1819 | if option in ['--nobodmas']: 1820 | NOBODMAS = True # use no operator precedence 1821 | 1822 | if option in ['--case']: 1823 | CASE = True 1824 | 1825 | if option in ['--intdiv']: 1826 | INTDIV = True 1827 | 1828 | if option in ['--exportfile']: 1829 | if exportfile == None: 1830 | exportfile = value 1831 | else: 1832 | print("Export file specified twice") 1833 | printusage() 1834 | sys.exit(2) 1835 | 1836 | if option in ['--importfile']: 1837 | importfiles.append(value) 1838 | 1839 | if option in ['--mapfile']: 1840 | if mapfile == None: 1841 | mapfile = value 1842 | else: 1843 | print("Map file specified twice") 1844 | printusage() 1845 | sys.exit(2) 1846 | 1847 | if option in ['--lstfile']: 1848 | if listingfile == None: 1849 | listingfile=open(value,"wt") 1850 | else: 1851 | print("List file specified twice") 1852 | printusage() 1853 | sys.exit(2) 1854 | 1855 | if option in ['-D']: 1856 | predefsymbols.append(value) 1857 | 1858 | if option in ['-B']: 1859 | if bootfile == None: 1860 | bootfile = value 1861 | else: 1862 | print("Boot file specified twice") 1863 | printusage() 1864 | sys.exit(2) 1865 | 1866 | if option in ['-I']: 1867 | includefiles.append(value) 1868 | 1869 | if len(file_args) == 0 and len(includefiles) == 0: 1870 | print("No input file specified") 1871 | printusage() 1872 | sys.exit(2) 1873 | 1874 | if (objectfile != '') and (len(file_args) != 1): 1875 | print("Object file output supports only a single source file") 1876 | printusage() 1877 | sys.exit(2) 1878 | 1879 | if (outputfile == '') and (objectfile == ''): 1880 | outputfile = os.path.splitext(file_args[0])[0] + ".dsk" 1881 | 1882 | image = new_disk_image(10) 1883 | 1884 | for inputfile in file_args: 1885 | 1886 | if (inputfile == outputfile) or (inputfile == objectfile): 1887 | print("Output file and input file are the same!") 1888 | printusage() 1889 | sys.exit(2) 1890 | 1891 | symboltable = {} 1892 | symbolcase = {} 1893 | symusetable = {} 1894 | labeltable = {} 1895 | memory = [] 1896 | forstack=[] 1897 | ifstack = [] 1898 | ifstate = 0 1899 | 1900 | for value in predefsymbols: 1901 | sym=value.split('=',1) 1902 | if len(sym)==1: 1903 | sym.append("1") 1904 | if not CASE: 1905 | sym[0]=sym[0].upper() 1906 | if PYTHONERRORS: 1907 | val = int(sym[1]) 1908 | else: 1909 | try: 1910 | val = int(sym[1]) 1911 | except: 1912 | print("Error: Invalid value for symbol predefined on command line, "+value) 1913 | sys.exit(1) 1914 | set_symbol(sym[0], int(sym[1])) 1915 | 1916 | for picklefilename in importfiles: 1917 | with open(picklefilename, "rb") as f: 1918 | ImportSymbols = pickle.load(f) 1919 | for sym,val in list(ImportSymbols.items()): 1920 | symkey = sym if CASE else sym.upper() 1921 | symboltable[symkey]=val 1922 | if symkey != sym: 1923 | symbolcase[symkey] = sym 1924 | 1925 | firstpage=32 1926 | firstpageoffset=16384 1927 | lastpage=-1 1928 | lastpageoffset=0 1929 | 1930 | # always 32 memory pages, each a 16k array allocate-on-write 1931 | for initmemorypage in range(32): 1932 | memory.append('') 1933 | 1934 | for p in 1,2: 1935 | print("pass ",p,"...") 1936 | 1937 | global_path='' 1938 | include_stack=[] 1939 | 1940 | origin = 32768 1941 | dumppage = 1 1942 | dumporigin = 0 1943 | dumpspace_pending = 0 1944 | dumpused = False 1945 | autoexecpage = 0 1946 | autoexecorigin = 0 1947 | 1948 | assembler_pass(p, inputfile) 1949 | 1950 | check_lastpage() 1951 | 1952 | if len(ifstack) > 0: 1953 | print("Error: Mismatched IF and ENDIF statements, too many IF") 1954 | for item in ifstack: 1955 | print(item[0]) 1956 | sys.exit(1) 1957 | if len(forstack) > 0: 1958 | print("Error: Mismatched EQU FOR and NEXT statements, too many EQU FOR") 1959 | for item in forstack: 1960 | print(item[1]) 1961 | sys.exit(1) 1962 | 1963 | printsymbols = {} 1964 | for symreg in listsymbols: 1965 | # add to printsymbols any pair from symboltable whose key matches symreg 1966 | for sym in symboltable: 1967 | if re.search(symreg, sym, 0 if CASE else re.IGNORECASE): 1968 | value = symboltable[sym] 1969 | printsymbols[symbolcase.get(sym, sym)] = f"{value:x}" if HEX else value 1970 | 1971 | if printsymbols != {}: 1972 | print(printsymbols) 1973 | 1974 | if exportfile: 1975 | with open(exportfile, 'wb') as f: 1976 | pickle.dump({ symbolcase.get(k, k):v for k, v in symboltable.items() }, f, protocol=0) 1977 | 1978 | if mapfile: 1979 | addrmap = {} 1980 | for sym,count in sorted(list(symusetable.items()), key=lambda x: x[1]): 1981 | if sym in labeltable: 1982 | symkey = sym if CASE else sym.upper() 1983 | symorig = symbolcase.get(sym, sym) 1984 | 1985 | if symorig[0] == '@': 1986 | symorig += ':' + sym.rsplit(':', 1)[1] 1987 | 1988 | addrmap[labeltable[sym]] = symorig 1989 | 1990 | with open(mapfile,'w') as f: 1991 | for addr,sym in sorted(addrmap.items()): 1992 | f.write("%04X=%s\n" % (addr,sym)) 1993 | 1994 | boot_page, boot_offset = (1,0) if firstpage == 32 else (firstpage,firstpageoffset) 1995 | boot_sig = memory[boot_page][boot_offset+0xf7:boot_offset+0xf7+4] 1996 | bootable = bytes(b & 0x5F for b in boot_sig) == b'BOOT' 1997 | 1998 | if bootfile and not bootable: 1999 | if os.path.basename(bootfile.lower()) == 'samdos9': 2000 | image = new_disk_image(9) 2001 | save_file_to_image(image, bootfile) 2002 | bootfile = None 2003 | 2004 | for pathname in includefiles: 2005 | save_file_to_image(image, pathname) 2006 | includefiles = [] 2007 | 2008 | save_memory(memory, image=image, filename=os.path.splitext(os.path.basename(inputfile))[0]) 2009 | if objectfile != "": 2010 | save_memory(memory, filename=objectfile) 2011 | 2012 | if outputfile != '': 2013 | save_disk_image(image, outputfile) 2014 | 2015 | print("Finished") 2016 | -------------------------------------------------------------------------------- /testing/bootable.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/pyz80/83a2b008bf99d765544100b3ca8edfbdb639c5e5/testing/bootable.dsk -------------------------------------------------------------------------------- /testing/bootable.z80s: -------------------------------------------------------------------------------- 1 | ; File with "bootable" signature. 2 | 3 | org &8000 4 | dump $ 5 | nop 6 | 7 | org &8009 8 | dump $ 9 | ret 10 | 11 | org &80f7 12 | dump $ 13 | defm "BOOT" 14 | -------------------------------------------------------------------------------- /testing/dummydos: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/pyz80/83a2b008bf99d765544100b3ca8edfbdb639c5e5/testing/dummydos -------------------------------------------------------------------------------- /testing/golden.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/pyz80/83a2b008bf99d765544100b3ca8edfbdb639c5e5/testing/golden.bin -------------------------------------------------------------------------------- /testing/golden.dsk.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/pyz80/83a2b008bf99d765544100b3ca8edfbdb639c5e5/testing/golden.dsk.gz -------------------------------------------------------------------------------- /testing/golden.z80s: -------------------------------------------------------------------------------- 1 | ORG 32768 2 | dump 32768 3 | 4 | defw 0xffee 5 | 6 | withoutspace: 7 | jp includeend 8 | 9 | 10 | ld (0),de 11 | ld (0), de 12 | ld (0), DE 13 | 14 | xor a 15 | 16 | sra a,(IY+3) 17 | sl1 b,(ix+4) 18 | rlc c,(iy-17) 19 | rrc d,(ix+65) 20 | 21 | n: equ FOR 5 22 | m: equ FOR 6 23 | inc a 24 | NEXT m 25 | inc b 26 | xor a 27 | next n 28 | 29 | i: equ for 32 30 | ldi{i+1}: for i+1,ldi 31 | ret 32 | next i 33 | 34 | for 32, dw ldi{for+1} 35 | 36 | 37 | db -1 38 | db 0 39 | dw -1 40 | dw 0 41 | 42 | includestart: 43 | include "include.z80s" 44 | includeend: 45 | includelength: equ includeend - includestart 46 | 47 | ldi 48 | db 3 49 | 50 | for 8, db 1 51 | for 8, db for 52 | 53 | for 4, ldi 54 | for 256, db floor(127.5*sin((FOR/256.0)*pi*2)) 55 | 56 | 57 | ld (iy+0),45 58 | ld (ix+1),2 59 | 60 | ex de,hl 61 | ex (sp),hl 62 | ex (sp),ix 63 | 64 | 65 | defb 0 66 | DB 1 67 | DB 8 68 | DEFB 08, 01, 00, 0, 1, 0xab, 0b01110100 69 | defb "A" 70 | defm "," 71 | 72 | ; defb "b","C","de" 73 | ; DEFB 43,23,",",17,56 74 | 75 | 76 | testoffset: 77 | 78 | # ds -5 79 | 80 | testoffset2: 81 | 82 | 83 | 84 | ds 65520 85 | 86 | testoffset3: 87 | 88 | ds align 256 89 | assert ($\256) == 0 90 | 91 | before: 92 | ds align 256 93 | after: 94 | assert(before == after) 95 | 96 | 97 | 98 | testoffset4: 99 | 100 | if defined(DEBUG) 101 | sll a 102 | else 103 | sl1 a 104 | endif 105 | 106 | testoffset5: 107 | 108 | ld ixl,4 109 | 110 | 111 | ; caseinsensitive: 112 | ; dw CASEINSENSITIVE 113 | ; CASEINSENSITIVE: 114 | ; dw caseinsensitive 115 | 116 | ld c,4 117 | @2: ld b,30 118 | @: outi 119 | djnz @ 120 | dec c 121 | jr nz,@2 122 | 123 | @b: jr @+a 124 | 125 | @a: jr @-b 126 | 127 | @: jr @+c 128 | 129 | @d: jr @+d 130 | 131 | @c: jr @-d 132 | 133 | @d: jr @c 134 | 135 | if defined (nobodmas) 136 | assert (10+10/10) == 2 137 | endif 138 | 139 | nine: equ 9 140 | forty9: equ 49 141 | 142 | symbol{forty{nine}}: equ 1337 143 | 144 | assert defined(symbol49) 145 | 146 | quotestesting: 147 | dm "message with a , and a "" in it" 148 | 149 | dm "0" 150 | dm " "" " 151 | dm "1" 152 | dm """" 153 | dm "2" 154 | dm " " 155 | dm "3" 156 | dm "" 157 | dm "4" 158 | dm "," 159 | dm "5" 160 | dm """""" 161 | dm "6" 162 | dm ",""""""" 163 | dm "7" 164 | 165 | db 34 166 | dm "8" 167 | dm (44) 168 | dm "9" 169 | 170 | db "" 171 | dm "A" 172 | db """" 173 | dm "B" 174 | dm "," 175 | dm "C" 176 | db "," 177 | dm "D" 178 | db "a","b",",","d" 179 | dm "E" 180 | db "e","","f","""" 181 | dm "F" 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /testing/include.z80s: -------------------------------------------------------------------------------- 1 | startwithininclude: 2 | NOP 3 | RLCA 4 | RRCA 5 | RLA 6 | RRA 7 | DAA 8 | CPL 9 | SCF 10 | CCF 11 | DAA 12 | HALT 13 | DI 14 | EI 15 | EXX 16 | ex af,af' 17 | ex de,hl 18 | ex (sp),hl 19 | NEG 20 | RETN 21 | RETI 22 | RRD 23 | RLD 24 | LDI 25 | CPI 26 | INI 27 | OUTI 28 | LDD 29 | CPD 30 | IND 31 | OUTD 32 | LDIR 33 | CPIR 34 | INIR 35 | OTIR 36 | LDDR 37 | CPDR 38 | INDR 39 | OTDR 40 | RLC b 41 | rrc c 42 | rl e 43 | rr a 44 | rr b 45 | rr c 46 | rr h 47 | SLA a 48 | SRA b 49 | ADD a,c 50 | add d 51 | add hl,de 52 | ADc a,c 53 | adc d 54 | adc hl,de 55 | sbc a,c 56 | sbc d 57 | sbc hl,de 58 | bit 7,e 59 | res 6,d 60 | set 5,c 61 | inc a 62 | dec b 63 | and l 64 | and 17 65 | xor a 66 | xor 31 67 | or e 68 | or 93 69 | cp e 70 | cp 3 71 | sub c 72 | sub 31 73 | pop hl 74 | push de 75 | startjumps: 76 | jp label1 77 | jp (hl) 78 | call label1 79 | label1: 80 | jr label1 81 | djnz label1 82 | 83 | ret 84 | im 0 85 | im 1 86 | im 2 87 | rst 16 88 | in e,(c) 89 | in a,(77) 90 | out (c),d 91 | out (47),a 92 | 93 | ld a,i 94 | ld i,a 95 | ld a,r 96 | ld r,a 97 | 98 | 99 | rl (iy+4) 100 | rr (ix+89) 101 | SLA (ix) 102 | SRA (ix+1) 103 | 104 | ADD a,(iy+8) 105 | add iy,de 106 | 107 | bit 7,(iy+4) 108 | res 6,(ix+2) 109 | set 5,(ix+9) 110 | 111 | inc (ix+8) 112 | dec (iy+3) 113 | inc ix 114 | and (iy+12) 115 | xor (ix+12) 116 | pop ix 117 | 118 | jp (ix) 119 | 120 | 121 | ld (12345),ix 122 | 123 | add ix,ix 124 | add ix,de 125 | 126 | ld (hl),a 127 | ld (ix+1),a 128 | ld (ix-127),3 129 | 130 | endwithininclude: 131 | lengthwithininclude: equ endwithininclude - startwithininclude 132 | -------------------------------------------------------------------------------- /testing/test_invalid.py: -------------------------------------------------------------------------------- 1 | # Test invalid instructions do not assemble 2 | 3 | import os 4 | import sys 5 | import tempfile 6 | import subprocess 7 | 8 | curr_dir = os.path.split(__file__)[0] 9 | pyz80_path = os.path.join(curr_dir, '../pyz80.py') 10 | 11 | equs = '\n'.join( [ "nn: equ &1122", "n: equ &33", "o: equ &44" ] ) + '\n' 12 | 13 | test_sets = [ 14 | ( "and ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 15 | ( "or ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 16 | ( "xor ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 17 | ( "cp ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 18 | ( "add a", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 19 | ( "adc a", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 20 | ( "sbc a,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 21 | ( "sub ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 22 | ( "add hl", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","c","d","e","h","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r"] ), 23 | ( "adc hl,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","c","d","e","h","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r"] ), 24 | ( "sbc hl,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","c","d","e","h","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r"] ), 25 | ( "rl ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 26 | ( "rlc ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 27 | ( "rr ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 28 | ( "rrc ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 29 | ( "sla ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 30 | ( "sll ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 31 | ( "sra ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 32 | ( "srl ", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 33 | ( "bit 0,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 34 | ( "set 0,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 35 | ( "res 0,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 36 | ( "ex (sp),", ["(bc)","(de)","(hl)","(ix)","(ix+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","i","ixh","ixl","iyh","iyl","l","n","r","sp"] ), 37 | ( "ex af,", ["(bc)","(de)","(hl)","(ix)","(iy+o)","(nn)","a","af","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 38 | ( "ex de,", ["(bc)","(de)","(hl)","(ix)","(iy+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 39 | ( "ex hl,", ["de"] ), 40 | ( "inc ", ["(bc)","(de)","(nn)","af","af'","i","n","r"] ), 41 | ( "dec ", ["(bc)","(de)","(nn)","af","af'","i","n","r"] ), 42 | ( "push ", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af'","b","c","d","e","h","i","ixh","ixl","iyh","iyl","l","n","r","sp"] ), 43 | ( "pop ", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af'","b","c","d","e","h","i","ixh","ixl","iyh","iyl","l","n","r","sp"] ), 44 | ( "in a,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 45 | ( "in b,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 46 | ( "out (c),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","n","r","sp"] ), 47 | ( "out (n),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 48 | ( "jp ", ["(bc)","(de)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 49 | ( "jr ", ["m,$","p,$","pe,$","po,$"] ), 50 | ( "ld a,", ["af","af'","bc","de","hl","ix","iy","sp"] ), 51 | ( "ld b,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 52 | ( "ld c,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 53 | ( "ld d,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 54 | ( "ld e,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","iy","r","sp"] ), 55 | ( "ld h,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","r","sp"] ), 56 | ( "ld l,", ["(bc)","(de)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","r","sp"] ), 57 | ( "ld af,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 58 | ( "ld af',", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 59 | ( "ld hl',", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 60 | ( "ld bc,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 61 | ( "ld de,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 62 | ( "ld hl,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 63 | ( "ld ix,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 64 | ( "ld iy,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","r","sp"] ), 65 | ( "ld ixh,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","h","hl","i","ix","iy","l","r","sp"] ), 66 | ( "ld ixl,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","h","hl","i","ix","iy","l","r","sp"] ), 67 | ( "ld iyh,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","h","hl","i","ix","iy","l","r","sp"] ), 68 | ( "ld iyl,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","h","hl","i","ix","iy","l","r","sp"] ), 69 | ( "ld sp,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","a","af","af'","b","bc","c","d","de","e","h","i","ixh","ixl","iyh","iyl","l","r","sp"] ), 70 | ( "ld i,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 71 | ( "ld r,", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 72 | ( "ld (bc),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 73 | ( "ld (de),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","b","bc","c","d","de","e","h","hl","i","ix","ixh","ixl","iy","iyh","iyl","l","n","r","sp"] ), 74 | ( "ld (nn),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","b","c","d","e","h","i","ixh","ixl","iyh","iyl","l","n","r"] ), 75 | ( "ld (hl),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","r","sp"] ), 76 | ( "ld (ix+o),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","r","sp"] ), 77 | ( "ld (iy+o),", ["(bc)","(de)","(hl)","(ix+o)","(iy+o)","(nn)","af","af'","bc","de","hl","i","ix","ixh","ixl","iy","iyh","iyl","r","sp"] ), 78 | ( "ld ", ["(ix-129),n", "(ix+128),n", "(iy-129),n", "(iy+128),n"] ), 79 | ( "im ", ["-1","3" ] ), 80 | ( "rst ", ["-1","&01","&100","&37","&40","&f0","&ff"] ), 81 | ] 82 | 83 | 84 | def assemble_instr( line ): 85 | exitcode = 0 86 | 87 | with tempfile.NamedTemporaryFile(suffix=".asm", delete=False, mode='w+t') as asm_file: 88 | asm_file.write( equs ) 89 | asm_file.write( line ) 90 | asm_path = asm_file.name 91 | 92 | with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as bin_file: 93 | bin_path = bin_file.name 94 | 95 | try: 96 | result = subprocess.run( 97 | [ "python", pyz80_path, '--obj', bin_path, '-o', os.devnull, asm_path ], 98 | capture_output=True, 99 | text=True ) 100 | 101 | if result.returncode == 0: 102 | with open(bin_path, 'rb') as f: 103 | content = f.read() 104 | print( f"BUG: '{line.strip().upper()}' assembles to: {content.hex().upper()}", file=sys.stderr ) 105 | exitcode = 1 106 | 107 | finally: 108 | if os.path.isfile( asm_path ): 109 | os.remove( asm_path ) 110 | if os.path.isfile( bin_path ): 111 | os.remove( bin_path ) 112 | 113 | return exitcode 114 | 115 | 116 | if __name__ == "__main__": 117 | exitcode = 0 118 | for prefix, operands in test_sets: 119 | for op in operands: 120 | instr = f" {prefix}{op}" 121 | exitcode |= assemble_instr( instr ) 122 | 123 | sys.exit( exitcode ) 124 | -------------------------------------------------------------------------------- /testing/test_valid.py: -------------------------------------------------------------------------------- 1 | # Test valid code assembles correctly 2 | 3 | import os 4 | import sys 5 | import gzip 6 | import tempfile 7 | import subprocess 8 | 9 | curr_dir = os.path.split(__file__)[0] 10 | pyz80_path = os.path.join(curr_dir, '../pyz80.py') 11 | dummydos_path = os.path.join(curr_dir, 'dummydos') 12 | 13 | test_files = [ 14 | ("golden.z80s", "golden.dsk.gz", "golden.bin", ["--nozip"]), 15 | ("golden.z80s", "golden.dsk.gz", "golden.bin", []), 16 | ("valid.z80s", None, "valid.bin", []), 17 | ("bootable.z80s", "bootable.dsk", None, ["-B", dummydos_path]), 18 | ("unbootable.z80s", "unbootable.dsk", None, ["-B", dummydos_path]), 19 | ("utf8-bom.z80s", None, None, []), 20 | ] 21 | 22 | 23 | def compare_files( output_path, expected_path ): 24 | with open(output_path, 'rb') as f1, open(expected_path, 'rb') as f2: 25 | output = bytearray(f1.read()) 26 | expected = f2.read() 27 | 28 | if output.startswith(b'\x1f\x8b'): 29 | output = bytearray(gzip.decompress( output )) 30 | 31 | if expected.startswith(b'\x1f\x8b'): 32 | expected = gzip.decompress( expected ) 33 | 34 | if '.dsk' in expected_path: 35 | for slot in range(18): 36 | offset = (slot * 256) + 245 # file date 37 | output[offset:offset+5] = expected[offset:offset+5] # ignore dates 38 | 39 | return output == expected 40 | 41 | 42 | def assemble_file( asm_file, dsk_file, bin_file, extra_opts ): 43 | exitcode = 0 44 | 45 | asm_path = os.path.join( curr_dir, asm_file ) 46 | dsk_path = os.path.join( curr_dir, dsk_file ) if dsk_file else None 47 | bin_path = os.path.join( curr_dir, bin_file ) if bin_file else None 48 | 49 | with tempfile.NamedTemporaryFile(suffix=".dsk", delete=False) as dskfile: 50 | dsk_out_path = dskfile.name 51 | 52 | with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as binfile: 53 | bin_out_path = binfile.name 54 | 55 | try: 56 | result = subprocess.run( 57 | [ "python", pyz80_path, *extra_opts, '-o', dsk_out_path, '--obj', bin_out_path, asm_path ], 58 | capture_output=True, 59 | text=True ) 60 | 61 | if result.returncode != 0: 62 | print( f"ERROR: '{asm_file}' failed to assemble: {result.stderr}", file=sys.stderr ) 63 | else: 64 | if dsk_path and not compare_files( dsk_out_path, dsk_path ): 65 | print( f"ERROR: '{asm_file}' disk image mismatch against '{dsk_file}'", file=sys.stderr ) 66 | exitcode = 1 67 | 68 | if bin_path and not compare_files( bin_out_path, bin_path ): 69 | print( f"ERROR: '{asm_file}' binary output mismatch against '{bin_file}'", file=sys.stderr ) 70 | exitcode = 1 71 | finally: 72 | if os.path.isfile( dsk_out_path ): 73 | os.remove( dsk_out_path ) 74 | if os.path.isfile( bin_out_path ): 75 | os.remove( bin_out_path ) 76 | 77 | return exitcode 78 | 79 | 80 | if __name__ == "__main__": 81 | exitcode = 0 82 | for asm_file, dsk_file, bin_file, extra_opts in test_files: 83 | exitcode |= assemble_file( asm_file, dsk_file, bin_file, extra_opts ) 84 | 85 | sys.exit( exitcode ) 86 | -------------------------------------------------------------------------------- /testing/unbootable.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/pyz80/83a2b008bf99d765544100b3ca8edfbdb639c5e5/testing/unbootable.dsk -------------------------------------------------------------------------------- /testing/unbootable.z80s: -------------------------------------------------------------------------------- 1 | ; File without "bootable" signature. 2 | 3 | org &8000 4 | dump $ 5 | nop 6 | 7 | org &8009 8 | dump $ 9 | ret 10 | 11 | org &80f7 12 | dump $ 13 | defm "BXXT" ; not BOOT 14 | -------------------------------------------------------------------------------- /testing/utf8-bom.z80s: -------------------------------------------------------------------------------- 1 | ; This file was saved with a UTF-8 BOM. 2 | 3 | org &8000 4 | dump $ 5 | nop 6 | -------------------------------------------------------------------------------- /testing/valid.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/pyz80/83a2b008bf99d765544100b3ca8edfbdb639c5e5/testing/valid.bin -------------------------------------------------------------------------------- /testing/valid.z80s: -------------------------------------------------------------------------------- 1 | ; List of all valid Z80 instructions (including common undocumented) 2 | 3 | nn: equ &1122 4 | n: equ &33 5 | o: equ &44 6 | 7 | ; Base set 8 | nop 9 | ld bc,nn 10 | ld (bc),a 11 | inc bc 12 | inc b 13 | dec b 14 | ld b,n 15 | rlca 16 | ex af,af' 17 | add hl,bc 18 | ld a,(bc) 19 | dec bc 20 | inc c 21 | dec c 22 | ld c,n 23 | rrca 24 | djnz $ 25 | ld de,nn 26 | ld (de),a 27 | inc de 28 | inc d 29 | dec d 30 | ld d,n 31 | rla 32 | jr $ 33 | add hl,de 34 | ld a,(de) 35 | dec de 36 | inc e 37 | dec e 38 | ld e,n 39 | rra 40 | jr nz,$ 41 | ld hl,nn 42 | ld (nn),hl 43 | inc hl 44 | inc h 45 | dec h 46 | ld h,n 47 | daa 48 | jr z,$ 49 | add hl,hl 50 | ld hl,(nn) 51 | dec hl 52 | inc l 53 | dec l 54 | ld l,n 55 | cpl 56 | jr nc,$ 57 | ld sp,nn 58 | ld (nn),a 59 | inc sp 60 | inc (hl) 61 | dec (hl) 62 | ld (hl),n 63 | scf 64 | jr c,$ 65 | add hl,sp 66 | ld a,(nn) 67 | dec sp 68 | inc a 69 | dec a 70 | ld a,n 71 | ccf 72 | ld b,b 73 | ld b,c 74 | ld b,d 75 | ld b,e 76 | ld b,h 77 | ld b,l 78 | ld b,(hl) 79 | ld b,a 80 | ld c,b 81 | ld c,c 82 | ld c,d 83 | ld c,e 84 | ld c,h 85 | ld c,l 86 | ld c,(hl) 87 | ld c,a 88 | ld d,b 89 | ld d,c 90 | ld d,d 91 | ld d,e 92 | ld d,h 93 | ld d,l 94 | ld d,(hl) 95 | ld d,a 96 | ld e,b 97 | ld e,c 98 | ld e,d 99 | ld e,e 100 | ld e,h 101 | ld e,l 102 | ld e,(hl) 103 | ld e,a 104 | ld h,b 105 | ld h,c 106 | ld h,d 107 | ld h,e 108 | ld h,h 109 | ld h,l 110 | ld h,(hl) 111 | ld h,a 112 | ld l,b 113 | ld l,c 114 | ld l,d 115 | ld l,e 116 | ld l,h 117 | ld l,l 118 | ld l,(hl) 119 | ld l,a 120 | ld (hl),b 121 | ld (hl),c 122 | ld (hl),d 123 | ld (hl),e 124 | ld (hl),h 125 | ld (hl),l 126 | halt 127 | ld (hl),a 128 | ld a,b 129 | ld a,c 130 | ld a,d 131 | ld a,e 132 | ld a,h 133 | ld a,l 134 | ld a,(hl) 135 | ld a,a 136 | add a,b 137 | add a,c 138 | add a,d 139 | add a,e 140 | add a,h 141 | add a,l 142 | add a,(hl) 143 | add a,a 144 | adc a,b 145 | adc a,c 146 | adc a,d 147 | adc a,e 148 | adc a,h 149 | adc a,l 150 | adc a,(hl) 151 | adc a,a 152 | sub b 153 | sub c 154 | sub d 155 | sub e 156 | sub h 157 | sub l 158 | sub (hl) 159 | sub a 160 | sbc a,b 161 | sbc a,c 162 | sbc a,d 163 | sbc a,e 164 | sbc a,h 165 | sbc a,l 166 | sbc a,(hl) 167 | sbc a,a 168 | and b 169 | and c 170 | and d 171 | and e 172 | and h 173 | and l 174 | and (hl) 175 | and a 176 | xor b 177 | xor c 178 | xor d 179 | xor e 180 | xor h 181 | xor l 182 | xor (hl) 183 | xor a 184 | or b 185 | or c 186 | or d 187 | or e 188 | or h 189 | or l 190 | or (hl) 191 | or a 192 | cp b 193 | cp c 194 | cp d 195 | cp e 196 | cp h 197 | cp l 198 | cp (hl) 199 | cp a 200 | ret nz 201 | pop bc 202 | jp nz,nn 203 | jp nn 204 | call nz,nn 205 | push bc 206 | add a,n 207 | rst &00 208 | ret z 209 | ret 210 | jp z,nn 211 | call z,nn 212 | call nn 213 | adc a,n 214 | rst &08 215 | ret nc 216 | pop de 217 | jp nc,nn 218 | out (n),a 219 | call nc,nn 220 | push de 221 | sub n 222 | rst &10 223 | ret c 224 | exx 225 | jp c,nn 226 | in a,(n) 227 | call c,nn 228 | sbc a,n 229 | rst &18 230 | ret po 231 | pop hl 232 | jp po,nn 233 | ex (sp),hl 234 | call po,nn 235 | push hl 236 | and n 237 | rst &20 238 | ret pe 239 | jp (hl) 240 | jp pe,nn 241 | ex de,hl 242 | call pe,nn 243 | xor n 244 | rst &28 245 | ret p 246 | pop af 247 | jp p,nn 248 | di 249 | call p,nn 250 | push af 251 | or n 252 | rst &30 253 | ret m 254 | ld sp,hl 255 | jp m,nn 256 | ei 257 | call m,nn 258 | cp n 259 | rst &38 260 | 261 | ; DD set 262 | add ix,bc 263 | add ix,de 264 | ld ix,nn 265 | ld (nn),ix 266 | inc ix 267 | inc ixh 268 | dec ixh 269 | ld ixh,n 270 | add ix,ix 271 | ld ix,(nn) 272 | dec ix 273 | inc ixl 274 | dec ixl 275 | ld ixl,n 276 | inc (ix+o) 277 | dec (ix+o) 278 | ld (ix+o),n 279 | add ix,sp 280 | ld b,ixh 281 | ld b,ixl 282 | ld b,(ix+o) 283 | ld c,ixh 284 | ld c,ixl 285 | ld c,(ix+o) 286 | ld d,ixh 287 | ld d,ixl 288 | ld d,(ix+o) 289 | ld e,ixh 290 | ld e,ixl 291 | ld e,(ix+o) 292 | ld ixh,b 293 | ld ixh,c 294 | ld ixh,d 295 | ld ixh,e 296 | ld ixh,ixh 297 | ld ixh,ixl 298 | ld h,(ix+o) 299 | ld ixh,a 300 | ld ixl,b 301 | ld ixl,c 302 | ld ixl,d 303 | ld ixl,e 304 | ld ixl,ixh 305 | ld ixl,ixl 306 | ld l,(ix+o) 307 | ld ixl,a 308 | ld (ix+o),b 309 | ld (ix+o),c 310 | ld (ix+o),d 311 | ld (ix+o),e 312 | ld (ix+o),h 313 | ld (ix+o),l 314 | ld (ix+o),a 315 | ld a,ixh 316 | ld a,ixl 317 | ld a,(ix+o) 318 | add a,ixh 319 | add a,ixl 320 | add a,(ix+o) 321 | adc a,ixh 322 | adc a,ixl 323 | adc a,(ix+o) 324 | sub ixh 325 | sub ixl 326 | sub (ix+o) 327 | sbc a,ixh 328 | sbc a,ixl 329 | sbc a,(ix+o) 330 | and ixh 331 | and ixl 332 | and (ix+o) 333 | xor ixh 334 | xor ixl 335 | xor (ix+o) 336 | or ixh 337 | or ixl 338 | or (ix+o) 339 | cp ixh 340 | cp ixl 341 | cp (ix+o) 342 | pop ix 343 | ex (sp),ix 344 | push ix 345 | jp (ix) 346 | ld sp,ix 347 | 348 | ; FD set 349 | add iy,bc 350 | add iy,de 351 | ld iy,nn 352 | ld (nn),iy 353 | inc iy 354 | inc iyh 355 | dec iyh 356 | ld iyh,n 357 | add iy,iy 358 | ld iy,(nn) 359 | dec iy 360 | inc iyl 361 | dec iyl 362 | ld iyl,n 363 | inc (iy+o) 364 | dec (iy+o) 365 | ld (iy+o),n 366 | add iy,sp 367 | ld b,iyh 368 | ld b,iyl 369 | ld b,(iy+o) 370 | ld c,iyh 371 | ld c,iyl 372 | ld c,(iy+o) 373 | ld d,iyh 374 | ld d,iyl 375 | ld d,(iy+o) 376 | ld e,iyh 377 | ld e,iyl 378 | ld e,(iy+o) 379 | ld iyh,b 380 | ld iyh,c 381 | ld iyh,d 382 | ld iyh,e 383 | ld iyh,iyh 384 | ld iyh,iyl 385 | ld h,(iy+o) 386 | ld iyh,a 387 | ld iyl,b 388 | ld iyl,c 389 | ld iyl,d 390 | ld iyl,e 391 | ld iyl,iyh 392 | ld iyl,iyl 393 | ld l,(iy+o) 394 | ld iyl,a 395 | ld (iy+o),b 396 | ld (iy+o),c 397 | ld (iy+o),d 398 | ld (iy+o),e 399 | ld (iy+o),h 400 | ld (iy+o),l 401 | ld (iy+o),a 402 | ld a,iyh 403 | ld a,iyl 404 | ld a,(iy+o) 405 | add a,iyh 406 | add a,iyl 407 | add a,(iy+o) 408 | adc a,iyh 409 | adc a,iyl 410 | adc a,(iy+o) 411 | sub iyh 412 | sub iyl 413 | sub (iy+o) 414 | sbc a,iyh 415 | sbc a,iyl 416 | sbc a,(iy+o) 417 | and iyh 418 | and iyl 419 | and (iy+o) 420 | xor iyh 421 | xor iyl 422 | xor (iy+o) 423 | or iyh 424 | or iyl 425 | or (iy+o) 426 | cp iyh 427 | cp iyl 428 | cp (iy+o) 429 | pop iy 430 | ex (sp),iy 431 | push iy 432 | jp (iy) 433 | ld sp,iy 434 | 435 | ; CB set 436 | rlc b 437 | rlc c 438 | rlc d 439 | rlc e 440 | rlc h 441 | rlc l 442 | rlc (hl) 443 | rlc a 444 | rrc b 445 | rrc c 446 | rrc d 447 | rrc e 448 | rrc h 449 | rrc l 450 | rrc (hl) 451 | rrc a 452 | rl b 453 | rl c 454 | rl d 455 | rl e 456 | rl h 457 | rl l 458 | rl (hl) 459 | rl a 460 | rr b 461 | rr c 462 | rr d 463 | rr e 464 | rr h 465 | rr l 466 | rr (hl) 467 | rr a 468 | sla b 469 | sla c 470 | sla d 471 | sla e 472 | sla h 473 | sla l 474 | sla (hl) 475 | sla a 476 | sra b 477 | sra c 478 | sra d 479 | sra e 480 | sra h 481 | sra l 482 | sra (hl) 483 | sra a 484 | sll b 485 | sll c 486 | sll d 487 | sll e 488 | sll h 489 | sll l 490 | sll (hl) 491 | sll a 492 | srl b 493 | srl c 494 | srl d 495 | srl e 496 | srl h 497 | srl l 498 | srl (hl) 499 | srl a 500 | bit 0,b 501 | bit 0,c 502 | bit 0,d 503 | bit 0,e 504 | bit 0,h 505 | bit 0,l 506 | bit 0,(hl) 507 | bit 0,a 508 | bit 1,b 509 | bit 1,c 510 | bit 1,d 511 | bit 1,e 512 | bit 1,h 513 | bit 1,l 514 | bit 1,(hl) 515 | bit 1,a 516 | bit 2,b 517 | bit 2,c 518 | bit 2,d 519 | bit 2,e 520 | bit 2,h 521 | bit 2,l 522 | bit 2,(hl) 523 | bit 2,a 524 | bit 3,b 525 | bit 3,c 526 | bit 3,d 527 | bit 3,e 528 | bit 3,h 529 | bit 3,l 530 | bit 3,(hl) 531 | bit 3,a 532 | bit 4,b 533 | bit 4,c 534 | bit 4,d 535 | bit 4,e 536 | bit 4,h 537 | bit 4,l 538 | bit 4,(hl) 539 | bit 4,a 540 | bit 5,b 541 | bit 5,c 542 | bit 5,d 543 | bit 5,e 544 | bit 5,h 545 | bit 5,l 546 | bit 5,(hl) 547 | bit 5,a 548 | bit 6,b 549 | bit 6,c 550 | bit 6,d 551 | bit 6,e 552 | bit 6,h 553 | bit 6,l 554 | bit 6,(hl) 555 | bit 6,a 556 | bit 7,b 557 | bit 7,c 558 | bit 7,d 559 | bit 7,e 560 | bit 7,h 561 | bit 7,l 562 | bit 7,(hl) 563 | bit 7,a 564 | res 0,b 565 | res 0,c 566 | res 0,d 567 | res 0,e 568 | res 0,h 569 | res 0,l 570 | res 0,(hl) 571 | res 0,a 572 | res 1,b 573 | res 1,c 574 | res 1,d 575 | res 1,e 576 | res 1,h 577 | res 1,l 578 | res 1,(hl) 579 | res 1,a 580 | res 2,b 581 | res 2,c 582 | res 2,d 583 | res 2,e 584 | res 2,h 585 | res 2,l 586 | res 2,(hl) 587 | res 2,a 588 | res 3,b 589 | res 3,c 590 | res 3,d 591 | res 3,e 592 | res 3,h 593 | res 3,l 594 | res 3,(hl) 595 | res 3,a 596 | res 4,b 597 | res 4,c 598 | res 4,d 599 | res 4,e 600 | res 4,h 601 | res 4,l 602 | res 4,(hl) 603 | res 4,a 604 | res 5,b 605 | res 5,c 606 | res 5,d 607 | res 5,e 608 | res 5,h 609 | res 5,l 610 | res 5,(hl) 611 | res 5,a 612 | res 6,b 613 | res 6,c 614 | res 6,d 615 | res 6,e 616 | res 6,h 617 | res 6,l 618 | res 6,(hl) 619 | res 6,a 620 | res 7,b 621 | res 7,c 622 | res 7,d 623 | res 7,e 624 | res 7,h 625 | res 7,l 626 | res 7,(hl) 627 | res 7,a 628 | set 0,b 629 | set 0,c 630 | set 0,d 631 | set 0,e 632 | set 0,h 633 | set 0,l 634 | set 0,(hl) 635 | set 0,a 636 | set 1,b 637 | set 1,c 638 | set 1,d 639 | set 1,e 640 | set 1,h 641 | set 1,l 642 | set 1,(hl) 643 | set 1,a 644 | set 2,b 645 | set 2,c 646 | set 2,d 647 | set 2,e 648 | set 2,h 649 | set 2,l 650 | set 2,(hl) 651 | set 2,a 652 | set 3,b 653 | set 3,c 654 | set 3,d 655 | set 3,e 656 | set 3,h 657 | set 3,l 658 | set 3,(hl) 659 | set 3,a 660 | set 4,b 661 | set 4,c 662 | set 4,d 663 | set 4,e 664 | set 4,h 665 | set 4,l 666 | set 4,(hl) 667 | set 4,a 668 | set 5,b 669 | set 5,c 670 | set 5,d 671 | set 5,e 672 | set 5,h 673 | set 5,l 674 | set 5,(hl) 675 | set 5,a 676 | set 6,b 677 | set 6,c 678 | set 6,d 679 | set 6,e 680 | set 6,h 681 | set 6,l 682 | set 6,(hl) 683 | set 6,a 684 | set 7,b 685 | set 7,c 686 | set 7,d 687 | set 7,e 688 | set 7,h 689 | set 7,l 690 | set 7,(hl) 691 | set 7,a 692 | 693 | ; ED set 694 | in b,(c) 695 | out (c),b 696 | sbc hl,bc 697 | ld (nn),bc 698 | neg 699 | retn 700 | im 0 701 | ld i,a 702 | in c,(c) 703 | out (c),c 704 | adc hl,bc 705 | ld bc,(nn) 706 | reti 707 | ld r,a 708 | in d,(c) 709 | out (c),d 710 | sbc hl,de 711 | ld (nn),de 712 | im 1 713 | ld a,i 714 | in e,(c) 715 | out (c),e 716 | adc hl,de 717 | ld de,(nn) 718 | im 2 719 | ld a,r 720 | in h,(c) 721 | out (c),h 722 | sbc hl,hl 723 | ld (nn),hl 724 | rrd 725 | in l,(c) 726 | out (c),l 727 | adc hl,hl 728 | ld hl,(nn) 729 | rld 730 | sbc hl,sp 731 | ld (nn),sp 732 | in a,(c) 733 | out (c),a 734 | adc hl,sp 735 | ld sp,(nn) 736 | ldi 737 | cpi 738 | ini 739 | outi 740 | ldd 741 | cpd 742 | ind 743 | outd 744 | ldir 745 | cpir 746 | inir 747 | otir 748 | lddr 749 | cpdr 750 | indr 751 | otdr 752 | 753 | ; Relative offsets 754 | ld (ix-128),a 755 | ld (ix+0),a 756 | ld (ix),a 757 | ld (ix+127),a 758 | 759 | ; Value truncation 760 | ld a,-1 761 | ld a,-2 762 | ld a,-128 763 | ld a,-129 764 | ld a,-130 765 | ld a,256 766 | ld a,257 767 | ld hl,-1 768 | ld hl,-2 769 | ld hl,-32767 770 | ld hl,-32768 771 | ld hl,-32769 772 | ld hl,65536 773 | ld hl,65537 774 | --------------------------------------------------------------------------------