Intel 8080 Assembler + Debugger (Minimal)This repository contains a VS Code extension with key features: a two-pass Intel 8080 assembler, a tiny Vector 06c emulator, and a debugger, along with quality of life VS Code functionality to improve the Vector 06c development process. Table of Contents
Quick start
Test Suites1. Assembler directive tests
The command recompiles the TypeScript sources and executes every test case under 2. Emulator testsRun the i8080 CPU emulator test suite at any time:
The command recompiles the TypeScript sources and executes all emulator test cases stored in
Current coverage includes:
Add more test
How to assemble and run
Project ConfigurationYou can configure your project using a Example
|
| Format | Example | Description |
|---|---|---|
| Decimal | 42, -5 |
Standard decimal numbers |
Hex $ |
$FF, $1234 |
Hexadecimal with $ prefix |
Hex 0x |
0xFF, 0x1234 |
Hexadecimal with 0x prefix |
Binary % |
%1010, %11_00 |
Binary with % prefix (underscores allowed) |
Binary 0b |
0b1010, 0b11_00 |
Binary with 0b prefix (underscores allowed) |
Binary b |
b1010, b11_00 |
Binary with b prefix (underscores allowed) |
| Character | 'A', '\n' |
ASCII character (supports escapes) |
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
+ |
Addition | Value + 10 |
- |
Subtraction | EndAddr - StartAddr |
* |
Multiplication | Count * 2 |
/ |
Division | Total / 4 |
% |
Modulo (remainder) | Offset % 256 |
Comparison Operators
| Operator | Description | Example |
|---|---|---|
== |
Equal | Value == 0 |
!= |
Not equal | Flag != FALSE |
< |
Less than | Count < 10 |
<= |
Less than or equal | Index <= Max |
> |
Greater than | Size > 0 |
>= |
Greater than or equal | Addr >= $100 |
Bitwise Operators
| Operator | Description | Example |
|---|---|---|
& |
Bitwise AND | Value & $0F |
\| |
Bitwise OR | Flags \| $80 |
^ |
Bitwise XOR | Data ^ $FF |
~ |
Bitwise NOT | ~Mask |
<< |
Left shift | 1 << 4 |
>> |
Right shift | Value >> 8 |
Logical Operators
| Operator | Description | Example |
|---|---|---|
! |
Logical NOT | !Enabled |
&& |
Logical AND | (A > 0) && (B < 10) |
\|\| |
Logical OR | (X == 0) \|\| (Y == 0) |
Unary Prefix Operators
| Operator | Description | Example |
|---|---|---|
+ |
Unary plus (identity) | +Value |
- |
Unary minus (negation) | -Offset |
< |
Low byte (bits 0-7) | <$1234 → $34 |
> |
High byte (bits 8-15) | >$1234 → $12 |
The < (low byte) and > (high byte) unary operators extract 8-bit portions from 16-bit values. This is useful for splitting addresses or constants when working with 8-bit instructions:
ADDR = $1234
mvi l, <ADDR ; Load low byte ($34) into L
mvi h, >ADDR ; Load high byte ($12) into H
db <$ABCD ; Emits $CD
db >$ABCD ; Emits $AB
Symbols
Expressions can reference:
- Labels (e.g.,
StartAddr,Loop) - Constants defined with
=orEQU(e.g.,MAX_VALUE) - Local labels prefixed with
@(e.g.,@loop) - Boolean literals
TRUE(1) andFALSE(0)
Operator Precedence (highest to lowest)
- Parentheses
() - Unary operators:
+,-,!,~,<,> - Multiplicative:
*,/,% - Additive:
+,- - Shift:
<<,>> - Relational:
<,<=,>,>= - Equality:
==,!= - Bitwise AND:
& - Bitwise XOR:
^ - Bitwise OR:
| - Logical AND:
&& - Logical OR:
||
.orgdirective: supported (decimal,0x.., or$..). Example:.org 0x100..includedirective: include another file inline using.include "file.asm"or.include 'file.asm'. Includes are resolved relative to the including file and support recursive expansion up to 16 levels.Origins mapping: when using
.include, the assembler records the original file and line for each expanded line. Errors and warnings reference the original filename and line number and print the offending source line plus afile:///link.Tokens file: the assembler writes a debug JSON alongside the ROM (e.g.,
myproject.debug.json) containinglabelswith addresses (hex), and the originalsrcbasename andlinewhere each label was defined. This is useful for setting breakpoints by name in the emulator/debugger.- Note: When compiling through the VS Code extension
devector.compileProjectcommand, the extension also appends abreakpointssection to the tokens JSON that records per-file breakpoints (line numbers, enabled status, and label/addr where available) discovered in the editor across the main file and recursive includes. - Breakpoints can be toggled only on meaningful lines (labels or instructions). Empty lines, pure comment lines, or lines containing compiler commands
.<cmd>are ignored when you click the gutter or pressF9.
- Note: When compiling through the VS Code extension
Warnings for immediates/addresses: if an immediate or address exceeds the instruction width (8-bit or 16-bit), the assembler emits a warning and truncates the value to the appropriate width. These are currently non-fatal warnings.
Diagnostics: invalid mnemonics or bad operands are reported as errors with file/line and the offending source text. The assembler rejects invalid operations such as
MOV M,M..macro/.endmacro: build parameterized macros (with defaults, nested calls, and per-invocation label namespaces) that expand inline before assembly..if/.endif: wrap any sequence of source lines in a conditional block that assembles only when its expression evaluates to non-zero. You can nest.ifdirectives freely, and the parser short-circuits inactive branches so forward references inside skipped blocks do not trigger errors. The argument may be a single numeric/boolean literal or any full expression evaluated with the rules below. Expressions support decimal/hex/binary literals, character constants, symbol names (labels, constants,@locallabels), arithmetic (+ - * / % << >>), comparisons (== != < <= > >=), bitwise logic (& | ^ ~), and boolean operators (! && ||). Example:
Value = 3
.if (Value >= 2) && (SomeFlag == TRUE)
mvi a, #$00
sta $d020
.endif
.loop/.endloop: repeat a block of source linesLoopCounttimes (maximum per loop: 100,000). Loop counts are evaluated with the same expression engine as.if, so you can reference previously defined constants or simple expressions. Loop bodies can nest other.loopor.ifblocks, and any constant assignments inside the block execute on each iteration because the assembler expands the body inline:
Value = 0
Step = 4
.loop (Step / 2)
db Value
Value = Value + 1
.endloop
The example above emits Value three times (0, 1, 2) and leaves Value set to 3 for subsequent code.
.print: emit compile-time diagnostics to the console during the second pass. Arguments are comma-separated and can mix string literals ("PC="), numeric literals, labels, or arbitrary expressions. Each argument is evaluated with the same expression engine as.if, so you can dump intermediate values or addresses while assembling:
.print "Copying from", SourceAddr, "to", DestAddr
.print "Loop count:", (EndAddr - StartAddr) / 16
Strings honor standard escapes (\n, \t, \", etc.). Non-string arguments are printed in decimal.
.error: immediately halt the second pass with a fatal diagnostic that uses the same argument rules as.print. Use it for compile-time validation inside conditionals or macros. Arguments can be strings, numbers, labels, or expressions, separated by commas. When the directive executes the assembler stops and surfaces the concatenated message to the user. Because inactive.ifblocks are skipped during expansion,.errorcalls inside false branches never trigger:
MAX_SIZE = 100
.if (BUFFER_SIZE > MAX_SIZE)
.error "Buffer size", BUFFER_SIZE, "exceeds", MAX_SIZE
.endif
Typical use cases include guardrails for configuration constants, macro argument validation, and short-circuiting builds when a derived value falls outside a legal range.
.var Name value: declares a mutable variable whose value can be reassigned later in the file (or inside macros). Unlike=orEQU,.varestablishes an initial value but can be updated with either direct assignments (Counter = Counter - 1) or a subsequentEQU. The symbol participates in all expression contexts just like any other constant.
ImmutableConst = 1 ; Initialize constant
; Emits: 0x01
Counter .var 10 ; Initialize variable
db Counter ; Emits: 0x0A
Counter = Counter - 1 ; Update with expression
db Counter ; Emits: 0x09
Counter equ 5 ; Update with EQU
db Counter ; Emits: 0x05
.align value: pad the output with zero bytes until the program counter reaches the next multiple ofvalue, then resume emitting instructions. The argument can be any expression understood by the.ifevaluator, must be positive, and has to be a power of two (1, 2, 4, 8, ...). If the current address is already aligned no padding is emitted. Example:
.org $100
Start:
db 0, 1, 2
.align 16 ; next instructions start at $110
AlignedLabel:
mvi a, 0
AlignedLabel is assigned the aligned address ($110) and the gap between $103 and $10F is filled with zeros.
.word value[, values...]: emit one or more 16-bit words at the current address. Values may be decimal, hexadecimal (0x/$), or binary (b/%) literals. Negative decimal literals are allowed down to -0x7FFF (15-bit magnitude) and are encoded using two's complement. Each value is written little-endian (low byte first). Example:
.word $1234, 42, b0000_1111, -5
The snippet above outputs 34 12 2A 00 0F 00 FB FF.
Alternative: DW
.byte value[, values...]: emit one or more bytes at the current address. Accepts decimal, hex (0x/$), or binary (b/%). Example:
.byte 255, $10, 0xFF, b1111_0000, %11_11_00_00
The snippet above emits FF 10 FF F0 F0.
Alternative: DB
.macro Name(param, otherParam, optionalParam=$10): defines reusable code blocks. A macro's body is copied inline wherever you invokeName(...), and all parameters are substituted as plain text before the normal two-pass assembly runs. Each parameter ultimately resolves to the same numeric/boolean values accepted by others durectives such as.if,.loop, etc. inside a macro. Parameters that are omitted during a call fall back to their default value.
Each macro call receives its own namespace for "normal" (Label:) and local (@loop) labels, so you can safely reuse throwaway labels inside macros or even call a macro recursively. Normal labels defined inside the macro are exported as MacroName_<call-index>.Label, letting you jump back into generated code for debugging tricks:
.macro SetColors(Background=$06, Border=$0e, Addr)
lda #Background
sta $d021
lda #Border
sta $d020
AddrPtr ldx Addr
stx $3300
.endmacro
SetColors($0b, $0f, PalettePtr)
SetColors(, MyColor+1, $0000) ; Background uses the default $06
Nested macros are supported (up to 32 levels deep), but you cannot open another .macro inside a macro body. All macro lines keep their original file/line metadata, so assembler errors still point back to the macro definition.
.encoding "Type", "Case": selects how upcoming.textliterals convert characters to bytes. Supported types are"ascii"(default) and"screencodecommodore". The optional case argument accepts"mixed"(default),"lower", or"upper". Example:
.encoding "ascii", "upper"
.text "hello", 'w' ; emits: 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x57
.encoding "screencodecommodore"
.text "@AB" ; emits: 0x00, 0x01, 0x02
.text value[, values...]: emits bytes from comma-separated string or character literals using the current.encodingsettings. Strings honor standard escapes like\n,\t,\", etc. Example:
.encoding "ascii"
.text " address: 1", '\n', '\0'
; emits: 20 20 20 61 64 64 72 65 73 73 3A 20 20 20 31 0A 00
Tools
FDD utility CLI
The FDD utility tool is a command-line tool that reads and writes FDD images, and adds files to the image. It is useful for creating custom FDD images for the Vector 06c emulator.
npm run compile # make sure out/tools/fddutil.js exists
node .\out\tools\fddutil.js -h
node .\out\tools\fddutil.js -r .\res\fdd\rds308.fdd -i file1.com -i file2.dat -o mydisk.fdd
Key switches:
-t <file>optional template disk image (Commonly FDD image with a boot sector and the OS of your choice).-i <file>adds a host file into the image; repeat the flag for each additional file.-o <file>writes the resulting.fddimage.-hprints the usage summary.