├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── dub.json ├── fib.asm ├── hello.asm ├── mul.asm └── source ├── Makefile └── app.d /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /a80 6 | a80.so 7 | a80.dylib 8 | a80.dll 9 | a80.a 10 | a80.lib 11 | a80-test-* 12 | *.exe 13 | *.o 14 | *.obj 15 | *.lst 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Brian Callahan 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # a80 Makefile 2 | 3 | all: 4 | ${MAKE} -C source 5 | 6 | test: 7 | ./a80 fib.asm 8 | ./a80 hello.asm 9 | ./a80 mul.asm 10 | 11 | clean: 12 | ${MAKE} -C source clean 13 | rm -f fib.com hello.com mul.com 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | a80 2 | === 3 | `a80` is an assembler written in [D](https://dlang.org/) for the 4 | Intel 8080 (and, by extension, the Zilog Z80) CPU. 5 | 6 | `a80` is developed on [OpenBSD](https://www.openbsd.org/) but 7 | should work on any system that D targets. 8 | 9 | `a80` is not an exact clone of any pre-existing CP/M assember, nor 10 | does it want to be. The differences will be explained in this 11 | document. 12 | 13 | `a80` also is quite conscious about its design practice and 14 | implementation. It is written to be the subject of a series of 15 | [blog posts](https://briancallahan.net/blog/20210408.html) in 16 | which we attempt to demystify the building of programming tools, 17 | and as such very intentionally does not use some very obvious data 18 | structures. And it may make some seemingly peculiar design choices. 19 | My goal is to have written a real assembler for a real CPU that you 20 | can still purchase today (in the form of the Z80) that true 21 | beginners can come to understand. 22 | 23 | After the blog series, if we want to turn this into a clone of an 24 | existing CP/M assembler, I'm all for it. 25 | 26 | Bug reports are welcome at any time. 27 | 28 | Usage 29 | ----- 30 | ``` 31 | a80 file.asm 32 | ``` 33 | The output will be `file.com`. 34 | 35 | Syntax 36 | ------ 37 | A line of assembly takes the following form: 38 | ``` 39 | [label:] [op [arg1[, arg2]]] [; comment] 40 | ``` 41 | Example assembly programs can be found in `hello.asm`, `fib.asm`, 42 | and `mul.asm`. 43 | 44 | `a80` only understands Intel 8080 opcodes. 45 | 46 | The CP/M `EQU` directive is supported however you cannot use other 47 | labels as a value nor can you use expressions. 48 | 49 | All **op** and **arg** must be lowercase, though labels may include 50 | capital letters. 51 | 52 | Numbers 53 | ------- 54 | Numbers may be in decimal or hex. 55 | 56 | Hex numbers must end with an `h`. If a hex number begins with 57 | `a-f`, it must be prefixed with `0`. This is not too dissimilar 58 | compared to other CP/M assemblers. 59 | 60 | Strings 61 | ------- 62 | The `DB` pseudo-op is available. Strings can be written within 63 | single quotes. The multi-comma syntax is not available. 64 | 65 | Expression parser 66 | ----------------- 67 | There is none. That is to keep things simple. 68 | 69 | The `equ` pseudo-op does allow for `$` and very simple `$+number` 70 | expressions only. No spaces in expressions. Valid expression 71 | operands are `+`, `-`, `*`, `/`, and `%`. 72 | 73 | License 74 | ------- 75 | ISC license. See `LICENSE` for more information. 76 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Brian Callahan" 4 | ], 5 | "copyright": "Copyright © 2021, Brian Callahan", 6 | "description": "Intel 8080/Zilog Z80 assembler.", 7 | "license": "ISC", 8 | "name": "a80" 9 | } -------------------------------------------------------------------------------- /fib.asm: -------------------------------------------------------------------------------- 1 | ; Fibonacci in Intel 8080 assembler. 2 | ; Results in b 3 | org 100h ; We are CP/M 4 | 5 | label equ 108h ; Just to show it works 6 | 7 | start: 8 | nop 9 | xra a ; zero out a 10 | mov b, a ; b = a 11 | mov c, a ; c = a 12 | adi 01h ; a = a + 1 13 | mov c, a ; c = a 14 | xra a ; zero out a 15 | loop: add c ; a = a + c 16 | comp: cmp c 17 | jc start ; jump if carry 18 | mov b, a 19 | mov a, c 20 | mov c, b 21 | jmp loop ; jump to loop 22 | 23 | ; Everything below here is garbage just to test the assembler. 24 | rar: rar 25 | jmp msg 26 | more: lxi sp, 0efdch 27 | shld 7fffh 28 | mvi c, 80h 29 | ldax d 30 | msg: db 'Hello, world$' ; a string! 31 | -------------------------------------------------------------------------------- /hello.asm: -------------------------------------------------------------------------------- 1 | org 100h 2 | bdos equ 0005h ; BDOS entry point 3 | start: mvi c,9h ; BDOS function: output string 4 | lxi d,msg$ ; address of msg 5 | call bdos 6 | ret 7 | msg$: db 'Hello, world from Assembler!$' 8 | end 9 | -------------------------------------------------------------------------------- /mul.asm: -------------------------------------------------------------------------------- 1 | ; Multiplication: d * e 2 | ; Result in bc 3 | org 100h 4 | 5 | start: mvi d, 05h 6 | mvi e, 05h 7 | call mp88 8 | hlt 9 | 10 | mp88: lxi b, 0000h 11 | mvi l, 08h 12 | nxtbit: mov a, d 13 | rar 14 | mov d, a 15 | jnc noadd 16 | mov a, b 17 | add e 18 | mov b, a 19 | noadd: mov a, b 20 | rar 21 | mov b, a 22 | mov a, c 23 | rar 24 | mov c, a 25 | dcr l 26 | jnz nxtbit 27 | ret 28 | -------------------------------------------------------------------------------- /source/Makefile: -------------------------------------------------------------------------------- 1 | # a80 Makefile 2 | 3 | PROG = a80 4 | OBJS = app.o 5 | 6 | DFLAGS = -O2 -pipe -frelease -finline 7 | 8 | all: ${OBJS} 9 | ${DC} ${LDFLAGS} -o ../${PROG} ${OBJS} 10 | 11 | clean: 12 | rm -f ../${PROG} ${OBJS} 13 | -------------------------------------------------------------------------------- /source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.file; 3 | import std.algorithm; 4 | import std.string; 5 | import std.conv; 6 | import std.exception; 7 | import std.ascii; 8 | 9 | /** 10 | * Line number. 11 | */ 12 | private size_t lineno; 13 | 14 | /** 15 | * Pass. 16 | */ 17 | private int pass; 18 | 19 | /* 20 | * Did we encounter any errors? 21 | */ 22 | private bool errors; 23 | 24 | /** 25 | * Output stored in memory until we're finished. 26 | */ 27 | private ubyte[] output; 28 | 29 | /** 30 | * Address for labels. 31 | */ 32 | private ushort addr; 33 | 34 | /** 35 | * 8 and 16 bit immediates 36 | */ 37 | enum IMM8 = 8; 38 | enum IMM16 = 16; 39 | 40 | /** 41 | * Fancy pass constants 42 | */ 43 | enum PASS1 = 1; 44 | enum PASS2 = 2; 45 | 46 | /** 47 | * Intel 8080 assembler instruction. 48 | */ 49 | private string lab; /// Label 50 | private string op; /// Instruction mnemonic 51 | private string a1; /// First argument 52 | private string a2; /// Second argument 53 | private string comm; /// Comment 54 | 55 | /** 56 | * Individual symbol table entry. 57 | */ 58 | struct symtab 59 | { 60 | string lab; /// Symbol name 61 | ushort value; /// Symbol value 62 | }; 63 | 64 | /** 65 | * Symbol table is an array of entries. 66 | */ 67 | private symtab[] stab; 68 | 69 | /** 70 | * Array of valid opcodes. 71 | * Does not include out and in, since they break the mixin. 72 | * Both out and in are handled separately in the matching function. 73 | */ 74 | enum opcodes = [ "nop", "lxi", "stax", "inx", "inr", "dcr", "mvi", "rlc", 75 | "dad", "ldax", "dcx", "rrc", "ral", "rar", "shld", "daa", 76 | "lhld", "cma", "sta", "stc", "lda", "cmc", "mov", "hlt", 77 | "add", "adc", "sub", "sbb", "ana", "xra", "ora", "cmp", 78 | "rnz", "pop", "jnz", "jmp", "cnz", "push", "adi", "rst", 79 | "rz", "ret", "jz", "cz", "call", "aci", "rnc", "jnc", 80 | "cnc", "sui", "rc", "jc", "cc", "sbi", "rpo", "jpo", 81 | "xthl", "cpo", "ani", "rpe", "pchl", "jpe", "xchg", 82 | "cpe", "xri", "rp", "jp", "di", "cp", "ori", "rm", 83 | "sphl", "jm", "ei", "cm", "cpi", "equ", "db", "dw", "ds", 84 | "org", "name", "title", "end" ]; 85 | 86 | /** 87 | * Top-level assembly function. 88 | * Everything cascades downward from here. 89 | * Repeat the parsing twice. 90 | * Pass 1 gathers symbols and their addresses/values. 91 | * Pass 2 emits code. 92 | */ 93 | private int assemble(string[] lines, string outfile) 94 | { 95 | pass = PASS1; 96 | for (lineno = 0; lineno < lines.length; lineno++) { 97 | parse(lines[lineno]); 98 | process(); 99 | } 100 | 101 | pass = PASS2; 102 | for (lineno = 0; lineno < lines.length; lineno++) { 103 | parse(lines[lineno]); 104 | process(); 105 | } 106 | 107 | /* Only output final executable if there are no errors. */ 108 | if (!errors) 109 | fileWrite(outfile); 110 | 111 | return errors ? 1 : 0; 112 | } 113 | 114 | /** 115 | * After all code is emitted, write it out to a file. 116 | */ 117 | private void fileWrite(string outfile) { 118 | import std.file : write; 119 | 120 | write(outfile, output); 121 | } 122 | 123 | /** 124 | * Parse each line into (up to) five tokens. 125 | */ 126 | private void parse(string line) { 127 | size_t i = 0; 128 | 129 | /* Is this the end of the token? */ 130 | bool endoftoken() 131 | { 132 | if (line[i] == ' ' || line[i] == '\t' || line[i] == ';' || 133 | line[i] == '\n' || line[i] == '\0') 134 | return true; 135 | 136 | return false; 137 | } 138 | 139 | /* Reset all our variables. */ 140 | lab = null; 141 | op = null; 142 | a1 = null; 143 | a2 = null; 144 | comm = null; 145 | 146 | if (line.length == 0) 147 | return; 148 | 149 | /* Get label. */ 150 | if (line[i] != ' ' && line[i] != '\t') { 151 | size_t labend; 152 | for (; i < line.length; i++) { 153 | if (endoftoken() || line[i] == ':') { 154 | labend = i; 155 | 156 | lab = line[0..labend]; 157 | 158 | if (line[i] == ':') 159 | i++; 160 | 161 | break; 162 | } 163 | } 164 | } 165 | 166 | /* Whitespace check. */ 167 | while (i < line.length && (line[i] == ' ' || line[i] == '\t' || 168 | line[i] == ';')) { 169 | if (line[i] == ';') { 170 | comm = line[i..$]; 171 | return; 172 | } 173 | 174 | i++; 175 | } 176 | 177 | if (i == line.length) 178 | return; 179 | 180 | /* Get op. */ 181 | auto opstart = i; 182 | for (; i < line.length; i++) { 183 | if (endoftoken()) { 184 | auto opend = i; 185 | op = line[opstart..opend]; 186 | break; 187 | } 188 | 189 | } 190 | 191 | if (i == line.length) { 192 | op = line[opstart..$]; 193 | return; 194 | } 195 | 196 | /* Whitespace check. */ 197 | while (i < line.length && (line[i] == ' ' || line[i] == '\t' || 198 | line[i] == ';')) { 199 | if (line[i] == ';') { 200 | comm = line[i..$]; 201 | return; 202 | } 203 | 204 | i++; 205 | } 206 | 207 | if (i == line.length) 208 | return; 209 | 210 | /* Get first arg. */ 211 | auto a1start = i; 212 | if (line[i] == '\'') { 213 | i++; 214 | for (; i < line.length; i++) { 215 | if (line[i] == '\'') { 216 | if (i != line.length - 1 && line[i + 1] == '\'') { 217 | a1 ~= '\''; 218 | i++; 219 | continue; 220 | } else { 221 | break; 222 | } 223 | } 224 | 225 | a1 ~= line[i]; 226 | } 227 | 228 | if (line[i] != '\'') 229 | err("unterminated string", PASS1); 230 | 231 | i++; 232 | } else { 233 | for (; i < line.length; i++) { 234 | if (endoftoken() || line[i] == ',') { 235 | auto a1end = i; 236 | a1 = line[a1start..a1end]; 237 | if (line[i] == ',') 238 | i++; 239 | break; 240 | } 241 | } 242 | 243 | if (i == line.length) { 244 | a1 = line[a1start..$]; 245 | return; 246 | } 247 | } 248 | 249 | /* Whitespace check. */ 250 | while (i < line.length && (line[i] == ' ' || line[i] == '\t' || 251 | line[i] == ';')) { 252 | if (line[i] == ';') { 253 | comm = line[i..$]; 254 | return; 255 | } 256 | 257 | i++; 258 | } 259 | 260 | if (i == line.length) 261 | return; 262 | 263 | /* Get second arg. */ 264 | auto a2start = i; 265 | for (; i < line.length; i++) { 266 | if (endoftoken()) { 267 | auto a2end = i; 268 | a2 = line[a2start..a2end]; 269 | break; 270 | } 271 | } 272 | 273 | if (i == line.length) { 274 | a2 = line[a2start..$]; 275 | return; 276 | } 277 | 278 | /* Whitespace check. */ 279 | while (i < line.length && (line[i] == ' ' || line[i] == '\t' || 280 | line[i] == ';')) { 281 | if (line[i] == ';') { 282 | comm = line[i..$]; 283 | return; 284 | } 285 | 286 | i++; 287 | } 288 | } 289 | 290 | /** 291 | * Figure out which op we have. 292 | */ 293 | private void process() 294 | { 295 | /** 296 | * Special case for if you put a label by itself on a line. 297 | * Or have a totally blank line. 298 | */ 299 | if (op.empty && a1.empty && a2.empty) { 300 | if (pass == PASS1) { 301 | if (!lab.empty) 302 | addsym(); 303 | } 304 | return; 305 | } 306 | 307 | /** 308 | * Match opcode to helper function. 309 | */ 310 | match: switch (op) { 311 | static foreach(opstr; opcodes) { 312 | case opstr: 313 | mixin(opstr)(); 314 | break match; 315 | } 316 | 317 | default: 318 | if (op == "out") 319 | i80_out(); 320 | else if (op == "in") 321 | i80_in(); 322 | else 323 | err("unknown opcode: " ~ op, 1); 324 | } 325 | } 326 | 327 | /** 328 | * Take action depending on which pass this is. 329 | */ 330 | private void passAct(ushort size, int outbyte) 331 | { 332 | if (pass == PASS1) { 333 | /* Add new symbol if we have a label. */ 334 | if (!lab.empty) 335 | addsym(); 336 | 337 | /* Increment address counter by size of instruction. */ 338 | addr += size; 339 | } else { 340 | /** 341 | * Output the byte representing the opcode. 342 | * If the opcode carries additional information 343 | * (e.g., immediate or address), we will output that 344 | * in a separate helper function. 345 | */ 346 | output ~= cast(ubyte)outbyte; 347 | } 348 | } 349 | 350 | /** 351 | * Add a symbol to the symbol table. 352 | */ 353 | private void addsym() 354 | { 355 | foreach (st; stab) { 356 | if (lab == st.lab) 357 | err("duplicate label: " ~ lab, PASS1); 358 | } 359 | 360 | symtab newsym = { lab, addr }; 361 | stab ~= newsym; 362 | } 363 | 364 | /** 365 | * nop (0x00) 366 | */ 367 | private void nop() 368 | { 369 | argcheck(a1.empty && a2.empty); 370 | passAct(1, 0x00); 371 | } 372 | 373 | /** 374 | * lxi (0x01 + 16 bit register offset) 375 | */ 376 | private void lxi() 377 | { 378 | argcheck(!a1.empty && !a2.empty); 379 | passAct(3, 0x01 + regMod16()); 380 | imm(IMM16); 381 | } 382 | 383 | /** 384 | * stax (0x02 + 16 bit register offset) 385 | */ 386 | private void stax() 387 | { 388 | argcheck(!a1.empty && a2.empty); 389 | if (a1 == "b") 390 | passAct(1, 0x02); 391 | else if (a1 == "d") 392 | passAct(1, 0x12); 393 | else 394 | err("stax only takes b or d", PASS1); 395 | } 396 | 397 | /** 398 | * inx (0x03 + 16 bit register offset) 399 | */ 400 | private void inx() 401 | { 402 | argcheck(!a1.empty && a2.empty); 403 | passAct(1, 0x03 + regMod16()); 404 | } 405 | 406 | /** 407 | * inr (0x04 + (8 bit register offset << 3)) 408 | */ 409 | private void inr() 410 | { 411 | argcheck(!a1.empty && a2.empty); 412 | passAct(1, 0x04 + (regMod8(a1) << 3)); 413 | } 414 | 415 | /** 416 | * dcr (0x05 + (8 bit register offset << 3)) 417 | */ 418 | private void dcr() 419 | { 420 | argcheck(!a1.empty && a2.empty); 421 | passAct(1, 0x05 + (regMod8(a1) << 3)); 422 | } 423 | 424 | /** 425 | * mvi (0x06 + (8 bit register offset << 3)) 426 | */ 427 | private void mvi() 428 | { 429 | argcheck(!a1.empty && !a2.empty); 430 | passAct(2, 0x06 + (regMod8(a1) << 3)); 431 | imm(IMM8); 432 | } 433 | 434 | /** 435 | * rlc (0x07) 436 | */ 437 | private void rlc() 438 | { 439 | argcheck(a1.empty && a2.empty); 440 | passAct(1, 0x07); 441 | } 442 | 443 | /** 444 | * dad (0x09 + 16 bit register offset) 445 | */ 446 | private void dad() 447 | { 448 | argcheck(!a1.empty && a2.empty); 449 | passAct(1, 0x09 + regMod16()); 450 | } 451 | 452 | /** 453 | * ldax (0x0a + 16 bit register offset) 454 | */ 455 | private void ldax() 456 | { 457 | argcheck(!a1.empty && a2.empty); 458 | if (a1 == "b") 459 | passAct(1, 0x0a); 460 | else if (a1 == "d") 461 | passAct(1, 0x1a); 462 | else 463 | err("ldax only takes b or d", PASS1); 464 | } 465 | 466 | /** 467 | * dcx (0x0b + 16 bit register offset) 468 | */ 469 | private void dcx() 470 | { 471 | argcheck(!a1.empty && a2.empty); 472 | passAct(1, 0x0b + regMod16()); 473 | } 474 | 475 | /** 476 | * rrc (0x0f) 477 | */ 478 | private void rrc() 479 | { 480 | argcheck(a1.empty && a2.empty); 481 | passAct(1, 0x0f); 482 | } 483 | 484 | /** 485 | * ral (0x17) 486 | */ 487 | private void ral() 488 | { 489 | argcheck(a1.empty && a2.empty); 490 | passAct(1, 0x17); 491 | } 492 | 493 | /** 494 | * rar (0x1f) 495 | */ 496 | private void rar() 497 | { 498 | argcheck(a1.empty && a2.empty); 499 | passAct(1, 0x1f); 500 | } 501 | 502 | /** 503 | * shld (0x22) 504 | */ 505 | private void shld() 506 | { 507 | argcheck(!a1.empty && a2.empty); 508 | passAct(3, 0x22); 509 | a16(); 510 | } 511 | 512 | /** 513 | * daa (0x27) 514 | */ 515 | private void daa() 516 | { 517 | argcheck(a1.empty && a2.empty); 518 | passAct(1, 0x27); 519 | } 520 | 521 | /** 522 | * lhld (0x2a) 523 | */ 524 | private void lhld() 525 | { 526 | argcheck(!a1.empty && a2.empty); 527 | passAct(3, 0x2a); 528 | a16(); 529 | } 530 | 531 | /** 532 | * cma (0x2f) 533 | */ 534 | private void cma() 535 | { 536 | argcheck(a1.empty && a2.empty); 537 | passAct(1, 0x2f); 538 | } 539 | 540 | /** 541 | * sta (0x32) 542 | */ 543 | private void sta() 544 | { 545 | argcheck(!a1.empty && a2.empty); 546 | passAct(3, 0x32); 547 | a16(); 548 | } 549 | 550 | /** 551 | * stc (0x37) 552 | */ 553 | private void stc() 554 | { 555 | argcheck(a1.empty && a2.empty); 556 | passAct(1, 0x37); 557 | } 558 | 559 | /** 560 | * lda (0x3a) 561 | */ 562 | private void lda() 563 | { 564 | argcheck(!a1.empty && a2.empty); 565 | passAct(3, 0x3a); 566 | a16(); 567 | } 568 | 569 | /** 570 | * cmc (0x3f) 571 | */ 572 | private void cmc() 573 | { 574 | argcheck(a1.empty && a2.empty); 575 | passAct(1, 0x3f); 576 | } 577 | 578 | /** 579 | * mov (0x40 + (8-bit register offset << 3) + 8-bit register offset 580 | * We allow mov m, m (0x76) 581 | * But that will result in HLT. 582 | */ 583 | private void mov() 584 | { 585 | argcheck(!a1.empty && !a2.empty); 586 | passAct(1, 0x40 + (regMod8(a1) << 3) + regMod8(a2)); 587 | } 588 | 589 | /** 590 | * hlt (0x76) 591 | */ 592 | private void hlt() 593 | { 594 | argcheck(a1.empty && a2.empty); 595 | passAct(1, 0x76); 596 | } 597 | 598 | /** 599 | * add (0x80 + 8-bit register offset) 600 | */ 601 | private void add() 602 | { 603 | argcheck(!a1.empty && a2.empty); 604 | passAct(1, 0x80 + regMod8(a1)); 605 | } 606 | 607 | /** 608 | * adc (0x88 + 8-bit register offset) 609 | */ 610 | private void adc() 611 | { 612 | argcheck(!a1.empty && a2.empty); 613 | passAct(1, 0x88 + regMod8(a1)); 614 | } 615 | 616 | /** 617 | * sub (0x90 + 8-bit register offset) 618 | */ 619 | private void sub() 620 | { 621 | argcheck(!a1.empty && a2.empty); 622 | passAct(1, 0x90 + regMod8(a1)); 623 | } 624 | 625 | /** 626 | * sbb (0x98 + 8-bit register offset) 627 | */ 628 | private void sbb() 629 | { 630 | argcheck(!a1.empty && a2.empty); 631 | passAct(1, 0x98 + regMod8(a1)); 632 | } 633 | 634 | /** 635 | * ana (0xa0 + 8-bit register offset) 636 | */ 637 | private void ana() 638 | { 639 | argcheck(!a1.empty && a2.empty); 640 | passAct(1, 0xa0 + regMod8(a1)); 641 | } 642 | 643 | /** 644 | * xra (0xa8 + 8-bit register offset) 645 | */ 646 | private void xra() 647 | { 648 | argcheck(!a1.empty && a2.empty); 649 | passAct(1, 0xa8 + regMod8(a1)); 650 | } 651 | 652 | /** 653 | * ora (0xb0 + 8-bit register offset) 654 | */ 655 | private void ora() 656 | { 657 | argcheck(!a1.empty && a2.empty); 658 | passAct(1, 0xb0 + regMod8(a1)); 659 | } 660 | 661 | /** 662 | * cmp (0xb8 + 8-bit register offset) 663 | */ 664 | private void cmp() 665 | { 666 | argcheck(!a1.empty && a2.empty); 667 | passAct(1, 0xb8 + regMod8(a1)); 668 | } 669 | 670 | /** 671 | * rnz (0xc0) 672 | */ 673 | private void rnz() 674 | { 675 | argcheck(a1.empty && a2.empty); 676 | passAct(1, 0xc0); 677 | } 678 | 679 | /** 680 | * pop (0xc1 + 16-bit register offset) 681 | */ 682 | private void pop() 683 | { 684 | argcheck(!a1.empty && a2.empty); 685 | passAct(1, 0xc1 + regMod16()); 686 | } 687 | 688 | /** 689 | * jnz (0xc2) 690 | */ 691 | private void jnz() 692 | { 693 | argcheck(!a1.empty && a2.empty); 694 | passAct(3, 0xc2); 695 | a16(); 696 | } 697 | 698 | /** 699 | * jmp (0xc3) 700 | */ 701 | private void jmp() 702 | { 703 | argcheck(!a1.empty && a2.empty); 704 | passAct(3, 0xc3); 705 | a16(); 706 | } 707 | 708 | /** 709 | * cnz (0xc4) 710 | */ 711 | private void cnz() 712 | { 713 | argcheck(!a1.empty && a2.empty); 714 | passAct(3, 0xc4); 715 | a16(); 716 | } 717 | 718 | /** 719 | * push (0xc5 + 16-bit register offset) 720 | */ 721 | private void push() 722 | { 723 | argcheck(!a1.empty && a2.empty); 724 | passAct(1, 0xc5 + regMod16()); 725 | } 726 | 727 | /** 728 | * adi (0xc6) 729 | */ 730 | private void adi() 731 | { 732 | argcheck(!a1.empty && a2.empty); 733 | passAct(2, 0xc6); 734 | imm(IMM8); 735 | } 736 | 737 | /** 738 | * rst (0xc7 + offset) 739 | */ 740 | private void rst() 741 | { 742 | argcheck(!a1.empty && a2.empty); 743 | auto offset = to!int(a1, 10); 744 | if (offset >= 0 && offset <= 7) 745 | passAct(1, 0xc7 + (offset << 3)); 746 | else 747 | err("invalid reset vector: " ~ to!string(offset), PASS1); 748 | } 749 | 750 | /** 751 | * rz (0xc8) 752 | */ 753 | private void rz() 754 | { 755 | argcheck(a1.empty && a2.empty); 756 | passAct(1, 0xc8); 757 | } 758 | 759 | /** 760 | * ret (0xc9) 761 | */ 762 | private void ret() 763 | { 764 | argcheck(a1.empty && a2.empty); 765 | passAct(1, 0xc9); 766 | } 767 | 768 | /** 769 | * jz (0xca) 770 | */ 771 | private void jz() 772 | { 773 | argcheck(!a1.empty && a2.empty); 774 | passAct(3, 0xca); 775 | a16(); 776 | } 777 | 778 | /** 779 | * cz (0xcc) 780 | */ 781 | private void cz() 782 | { 783 | argcheck(!a1.empty && a2.empty); 784 | passAct(3, 0xcc); 785 | a16(); 786 | } 787 | 788 | /** 789 | * call (0xcd) 790 | */ 791 | private void call() 792 | { 793 | argcheck(!a1.empty && a2.empty); 794 | passAct(3, 0xcd); 795 | a16(); 796 | } 797 | 798 | /** 799 | * aci (0xce) 800 | */ 801 | private void aci() 802 | { 803 | argcheck(!a1.empty && a2.empty); 804 | passAct(2, 0xce); 805 | imm(IMM8); 806 | } 807 | 808 | /** 809 | * rnc (0xd0) 810 | */ 811 | private void rnc() 812 | { 813 | argcheck(a1.empty && a2.empty); 814 | passAct(1, 0xd0); 815 | } 816 | 817 | /** 818 | * jnc (0xd2) 819 | */ 820 | private void jnc() 821 | { 822 | argcheck(!a1.empty && a2.empty); 823 | passAct(3, 0xd2); 824 | a16(); 825 | } 826 | 827 | /** 828 | * out (0xd3) 829 | */ 830 | private void i80_out() 831 | { 832 | argcheck(!a1.empty && a2.empty); 833 | passAct(2, 0xd3); 834 | imm(IMM8); 835 | } 836 | 837 | /** 838 | * cnc (0xd4) 839 | */ 840 | private void cnc() 841 | { 842 | argcheck(!a1.empty && a2.empty); 843 | passAct(3, 0xd4); 844 | a16(); 845 | } 846 | 847 | /** 848 | * sui (0xd6) 849 | */ 850 | private void sui() 851 | { 852 | argcheck(!a1.empty && a2.empty); 853 | passAct(2, 0xd6); 854 | imm(IMM8); 855 | } 856 | 857 | /** 858 | * rc (0xd8) 859 | */ 860 | private void rc() 861 | { 862 | argcheck(a1.empty && a2.empty); 863 | passAct(1, 0xd8); 864 | } 865 | 866 | /** 867 | * jc (0xda) 868 | */ 869 | private void jc() 870 | { 871 | argcheck(!a1.empty && a2.empty); 872 | passAct(3, 0xda); 873 | a16(); 874 | } 875 | 876 | /** 877 | * in (0xdb) 878 | */ 879 | private void i80_in() 880 | { 881 | argcheck(!a1.empty && a2.empty); 882 | passAct(2, 0xdb); 883 | imm(IMM8); 884 | } 885 | 886 | /** 887 | * cc (0xdc) 888 | */ 889 | private void cc() 890 | { 891 | argcheck(!a1.empty && a2.empty); 892 | passAct(3, 0xdc); 893 | a16(); 894 | } 895 | 896 | /** 897 | * sbi (0xde) 898 | */ 899 | private void sbi() 900 | { 901 | argcheck(!a1.empty && a2.empty); 902 | passAct(2, 0xde); 903 | imm(IMM8); 904 | } 905 | 906 | /** 907 | * rpo (0xe0) 908 | */ 909 | private void rpo() 910 | { 911 | argcheck(a1.empty && a2.empty); 912 | passAct(1, 0xe0); 913 | } 914 | 915 | /** 916 | * jpo (0xe2) 917 | */ 918 | private void jpo() 919 | { 920 | argcheck(!a1.empty && a2.empty); 921 | passAct(3, 0xe2); 922 | a16(); 923 | } 924 | 925 | /** 926 | * xthl (0xe3) 927 | */ 928 | private void xthl() 929 | { 930 | argcheck(a1.empty && a2.empty); 931 | passAct(1, 0xe3); 932 | } 933 | 934 | /** 935 | * cpo (0xe4) 936 | */ 937 | private void cpo() 938 | { 939 | argcheck(!a1.empty && a2.empty); 940 | passAct(3, 0xe4); 941 | a16(); 942 | } 943 | 944 | /** 945 | * ani (0xe6) 946 | */ 947 | private void ani() 948 | { 949 | argcheck(!a1.empty && a2.empty); 950 | passAct(2, 0xe6); 951 | imm(IMM8); 952 | } 953 | 954 | /** 955 | * rpe (0xe8) 956 | */ 957 | private void rpe() 958 | { 959 | argcheck(a1.empty && a2.empty); 960 | passAct(1, 0xe8); 961 | } 962 | 963 | /** 964 | * pchl (0xe9) 965 | */ 966 | private void pchl() 967 | { 968 | argcheck(a1.empty && a2.empty); 969 | passAct(1, 0xe9); 970 | } 971 | 972 | /** 973 | * jpe (0xea) 974 | */ 975 | private void jpe() 976 | { 977 | argcheck(!a1.empty && a2.empty); 978 | passAct(3, 0xea); 979 | a16(); 980 | } 981 | 982 | /** 983 | * xchg (0xeb) 984 | */ 985 | private void xchg() 986 | { 987 | argcheck(a1.empty && a2.empty); 988 | passAct(1, 0xeb); 989 | } 990 | 991 | /** 992 | * cpe (0xec) 993 | */ 994 | private void cpe() 995 | { 996 | argcheck(!a1.empty && a2.empty); 997 | passAct(3, 0xec); 998 | a16(); 999 | } 1000 | 1001 | /** 1002 | * xri (0xee) 1003 | */ 1004 | private void xri() 1005 | { 1006 | argcheck(!a1.empty && a2.empty); 1007 | passAct(2, 0xee); 1008 | imm(IMM8); 1009 | } 1010 | 1011 | /** 1012 | * rp (0xf0) 1013 | */ 1014 | private void rp() 1015 | { 1016 | argcheck(a1.empty && a2.empty); 1017 | passAct(1, 0xf0); 1018 | } 1019 | 1020 | /** 1021 | * jp (0xf2) 1022 | */ 1023 | private void jp() 1024 | { 1025 | argcheck(!a1.empty && a2.empty); 1026 | passAct(3, 0xf2); 1027 | a16(); 1028 | } 1029 | 1030 | /** 1031 | * di (0xf3) 1032 | */ 1033 | private void di() 1034 | { 1035 | argcheck(a1.empty && a2.empty); 1036 | passAct(1, 0xf3); 1037 | } 1038 | 1039 | /** 1040 | * cp (0xf4) 1041 | */ 1042 | private void cp() 1043 | { 1044 | argcheck(!a1.empty && a2.empty); 1045 | passAct(3, 0xf4); 1046 | a16(); 1047 | } 1048 | 1049 | /** 1050 | * ori (0xf6) 1051 | */ 1052 | private void ori() 1053 | { 1054 | argcheck(!a1.empty && a2.empty); 1055 | passAct(2, 0xf6); 1056 | imm(IMM8); 1057 | } 1058 | 1059 | /** 1060 | * rm (0xf8) 1061 | */ 1062 | private void rm() 1063 | { 1064 | argcheck(a1.empty && a2.empty); 1065 | passAct(1, 0xf8); 1066 | } 1067 | 1068 | /** 1069 | * sphl (0xf9) 1070 | */ 1071 | private void sphl() 1072 | { 1073 | argcheck(a1.empty && a2.empty); 1074 | passAct(1, 0xf9); 1075 | } 1076 | 1077 | /** 1078 | * jm (0xfa) 1079 | */ 1080 | private void jm() 1081 | { 1082 | argcheck(!a1.empty && a2.empty); 1083 | passAct(3, 0xfa); 1084 | a16(); 1085 | } 1086 | 1087 | /** 1088 | * ei (0xfb) 1089 | */ 1090 | private void ei() 1091 | { 1092 | argcheck(a1.empty && a2.empty); 1093 | passAct(1, 0xfb); 1094 | } 1095 | 1096 | /** 1097 | * cm (0xfc) 1098 | */ 1099 | private void cm() 1100 | { 1101 | argcheck(!a1.empty && a2.empty); 1102 | passAct(3, 0xfc); 1103 | a16(); 1104 | } 1105 | 1106 | /** 1107 | * cpi (0xfe) 1108 | */ 1109 | private void cpi() 1110 | { 1111 | argcheck(!a1.empty && a2.empty); 1112 | passAct(2, 0xfe); 1113 | imm(IMM8); 1114 | } 1115 | 1116 | /** 1117 | * Define a constant. 1118 | */ 1119 | private void equ() 1120 | { 1121 | ushort value; 1122 | 1123 | if (lab.empty) 1124 | err("must have a label in equ statement", PASS1); 1125 | 1126 | if (a1[0] == '$') 1127 | value = dollar(); 1128 | else 1129 | value = numcheck(a1); 1130 | 1131 | if (pass == PASS1) { 1132 | auto temp = addr; 1133 | addr = value; 1134 | addsym(); 1135 | addr = temp; 1136 | } 1137 | } 1138 | 1139 | /** 1140 | * Place a byte. 1141 | */ 1142 | private void db() 1143 | { 1144 | argcheck(!a1.empty && a2.empty); 1145 | 1146 | if (isDigit(a1[0])) { 1147 | auto num = numcheck(a1); 1148 | passAct(1, num); 1149 | } else { 1150 | if (pass == PASS1) { 1151 | if (!lab.empty) 1152 | addsym(); 1153 | addr += a1.length; 1154 | } else { 1155 | foreach (c; a1) 1156 | output ~= cast(ubyte)c; 1157 | addr += a1.length; 1158 | } 1159 | } 1160 | } 1161 | 1162 | /** 1163 | * Place a word. 1164 | */ 1165 | private void dw() 1166 | { 1167 | argcheck(!a1.empty && a2.empty); 1168 | 1169 | if (pass == PASS1) { 1170 | if (!lab.empty) 1171 | addsym(); 1172 | } 1173 | a16(); 1174 | 1175 | addr += 2; 1176 | } 1177 | 1178 | /** 1179 | * Reserve an area of uninitialized memory. 1180 | */ 1181 | private void ds() 1182 | { 1183 | argcheck(!a1.empty && a2.empty); 1184 | 1185 | if (pass == PASS1) { 1186 | if (!lab.empty) 1187 | addsym(); 1188 | } else { 1189 | auto num = numcheck(a1); 1190 | foreach (i; 0..num) 1191 | output ~= cast(ubyte)0; 1192 | } 1193 | 1194 | addr += numcheck(a1); 1195 | } 1196 | 1197 | /** 1198 | * Force updated the address counter. 1199 | */ 1200 | private void org() 1201 | { 1202 | argcheck(lab.empty && !a1.empty && a2.empty); 1203 | 1204 | if (isDigit(a1[0])) { 1205 | if (pass == PASS1) 1206 | addr = numcheck(a1); 1207 | } else { 1208 | err("org must take a number", PASS1); 1209 | } 1210 | } 1211 | 1212 | /** 1213 | * Set module name. 1214 | * Not useful for us, since we don't generate a listing file. 1215 | * Check and ignore. 1216 | */ 1217 | private void name() 1218 | { 1219 | argcheck(lab.empty && !a1.empty && a2.empty); 1220 | } 1221 | 1222 | /** 1223 | * Set module title. 1224 | * Not useful for us, since we don't generate a listing file. 1225 | * Check and ignore. 1226 | */ 1227 | private void title() 1228 | { 1229 | argcheck(lab.empty && !a1.empty && a2.empty); 1230 | } 1231 | 1232 | /** 1233 | * End of assembly, even if there is more after. 1234 | */ 1235 | private void end() 1236 | { 1237 | argcheck(lab.empty && a1.empty && a2.empty); 1238 | lineno = lineno.max - 1; 1239 | } 1240 | 1241 | /** 1242 | * Get an 8-bit or 16-bit immediate. 1243 | */ 1244 | private void imm(int type) 1245 | { 1246 | ushort num; 1247 | string arg; 1248 | bool found = false; 1249 | 1250 | if (op == "lxi" || op == "mvi") 1251 | arg = a2; 1252 | else 1253 | arg = a1; 1254 | 1255 | if (isDigit(arg[0])) { 1256 | num = numcheck(arg); 1257 | } else { 1258 | if (pass == PASS2) { 1259 | foreach (st; stab) { 1260 | if (arg == st.lab) { 1261 | num = st.value; 1262 | found = true; 1263 | break; 1264 | } 1265 | } 1266 | 1267 | if (!found) 1268 | err("label " ~ arg ~ " not defined", PASS2); 1269 | } 1270 | } 1271 | 1272 | if (pass == PASS2) { 1273 | output ~= cast(ubyte)(num & 0xff); 1274 | if (type == IMM16) 1275 | output ~= cast(ubyte)((num >> 8) & 0xff); 1276 | } 1277 | } 1278 | 1279 | /** 1280 | * Get a 16-bit address. 1281 | */ 1282 | private void a16() 1283 | { 1284 | ushort num; 1285 | bool found = false; 1286 | 1287 | if (isDigit(a1[0])) { 1288 | num = numcheck(a1); 1289 | } else { 1290 | if (pass == PASS2) { 1291 | foreach (st; stab) { 1292 | if (a1 == st.lab) { 1293 | num = st.value; 1294 | found = true; 1295 | break; 1296 | } 1297 | } 1298 | 1299 | if (!found) 1300 | err("label " ~ a1 ~ " not defined", PASS2); 1301 | } 1302 | } 1303 | 1304 | if (pass == PASS2) { 1305 | output ~= cast(ubyte)(num & 0xff); 1306 | output ~= cast(ubyte)((num >> 8) & 0xff); 1307 | } 1308 | } 1309 | 1310 | /** 1311 | * Return the 16 bit register offset. 1312 | */ 1313 | private int regMod16() 1314 | { 1315 | if (a1 == "b") { 1316 | return 0x00; 1317 | } else if (a1 == "d") { 1318 | return 0x10; 1319 | } else if (a1 == "h") { 1320 | return 0x20; 1321 | } else if (a1 == "psw") { 1322 | if (op == "pop" || op == "push") 1323 | return 0x30; 1324 | else 1325 | err("psw may not be used with " ~ op, PASS1); 1326 | } else if (a1 == "sp") { 1327 | if (op != "pop" && op != "push") 1328 | return 0x30; 1329 | else 1330 | err("sp may not be used with " ~ op, PASS1); 1331 | } else { 1332 | err("invalid register for " ~ op, PASS1); 1333 | } 1334 | 1335 | /* This will never be reached, but quiets gdc. */ 1336 | return 0; 1337 | } 1338 | 1339 | /** 1340 | * Return the 8-bit register offset. 1341 | */ 1342 | private int regMod8(string reg) 1343 | { 1344 | if (reg == "b") 1345 | return 0x00; 1346 | else if (reg == "c") 1347 | return 0x01; 1348 | else if (reg == "d") 1349 | return 0x02; 1350 | else if (reg == "e") 1351 | return 0x03; 1352 | else if (reg == "h") 1353 | return 0x04; 1354 | else if (reg == "l") 1355 | return 0x05; 1356 | else if (reg == "m") 1357 | return 0x06; 1358 | else if (reg == "a") 1359 | return 0x07; 1360 | else 1361 | err("invalid register " ~ reg, PASS1); 1362 | 1363 | /* This will never be reached, but quiets gdc. */ 1364 | return 0; 1365 | } 1366 | 1367 | /** 1368 | * Check arguments. 1369 | */ 1370 | private void argcheck(bool passed) 1371 | { 1372 | if (passed == false) 1373 | err("arguments not correct for mnemonic: " ~ op, PASS1); 1374 | } 1375 | 1376 | /** 1377 | * Check if a number is decimal or hex. 1378 | */ 1379 | private ushort numcheck(string input) 1380 | { 1381 | ushort num; 1382 | 1383 | if (input[input.length - 1] == 'h') 1384 | num = to!ushort(chop(input), 16); 1385 | else 1386 | num = to!ushort(input, 10); 1387 | 1388 | return num; 1389 | } 1390 | 1391 | /** 1392 | * If the argument to EQU begins with $, we need to parse that. 1393 | * Our syntax differs a little from the CP/M assembler. 1394 | * And it only deals with simple expressions. 1395 | */ 1396 | private ushort dollar() 1397 | { 1398 | ushort num = addr; 1399 | 1400 | if (a1.length > 1) { 1401 | if (a1[1] == '+') 1402 | num += numcheck(a1[2..$]); 1403 | else if (a1[1] == '-') 1404 | num -= numcheck(a1[2..$]); 1405 | else if (a1[1] == '*') 1406 | num *= numcheck(a1[2..$]); 1407 | else if (a1[1] == '/') 1408 | num /= numcheck(a1[2..$]); 1409 | else if (a1[1] == '%') 1410 | num %= numcheck(a1[2..$]); 1411 | else 1412 | err("invalid operator in equ", PASS1); 1413 | } 1414 | 1415 | return num; 1416 | } 1417 | 1418 | /** 1419 | * Nice error messages. 1420 | */ 1421 | private void err(string msg, int passprint) 1422 | { 1423 | if (passprint == pass) 1424 | stderr.writeln("a80: " ~ to!string(lineno + 1) ~ ": " ~ msg); 1425 | 1426 | errors = true; 1427 | } 1428 | 1429 | /** 1430 | * All good things start with a single function. 1431 | */ 1432 | int main(string[] args) 1433 | { 1434 | /** 1435 | * Make sure the user provides only one input file. 1436 | */ 1437 | if (args.length != 2) { 1438 | stderr.writeln("usage: a80 file.asm"); 1439 | return 1; 1440 | } 1441 | 1442 | /** 1443 | * Create an array of lines from the input file. 1444 | */ 1445 | string[] lines = splitLines(cast(string)read(args[1])); 1446 | 1447 | /** 1448 | * Name output file the same as the input but with .com ending. 1449 | */ 1450 | auto split = args[1].findSplit(".asm"); 1451 | auto outfile = split[0] ~ ".com"; 1452 | 1453 | /** 1454 | * Do the work. 1455 | */ 1456 | return assemble(lines, outfile); 1457 | } 1458 | --------------------------------------------------------------------------------