Using it
A first program
Five complete programs, one per front door. CI assembles every sample on this
page with the real asm198x binary, so they assemble as written.
Commodore 64, ACME syntax
; Fill the screen with a character, then colour it.
* = $c000
screen = $0400
colour = $d800
lda #$51 ; a filled circle in the C64 character set
ldx #$00
loop sta screen,x
sta screen + $100,x
inx
bne loop
lda #$01 ; white
ldx #$00
colours sta colour,x
sta colour + $100,x
inx
bne colours
rts
asm198x --dialect acme fill.a -o fill.bin
Two loops rather than one, because a bne counts to 256 and the screen is
1000 bytes: each pass covers a page, and two passes cover most of it. Making
that exact is the reader’s exercise, not the assembler’s problem.
--prg wraps the output with the two-byte load address a C64 expects:
asm198x --dialect acme --prg fill.a -o fill.prg
NES, ca65 syntax
; Set the background colour, then stop.
.segment "HEADER"
.byte "NES", $1a
.byte 2 ; 32K of program
.byte 1 ; 8K of character data
.byte $00, $00
.segment "CODE"
reset:
sei ; no interrupts while we set up
cld
ldx #$ff
txs ; the stack lives at $01ff downwards
wait1: bit $2002 ; two frames, for the PPU to warm up
bpl wait1
wait2: bit $2002
bpl wait2
bit $2002 ; reset the address latch
lda #$3f ; palette memory starts at $3f00
sta $2006
lda #$00
sta $2006
lda #$21 ; a mid blue
sta $2007
forever:
jmp forever
nmi: rti
irq: rti
.segment "VECTORS"
.word nmi
.word reset
.word irq
asm198x --dialect ca65 game.s -o game.nes
That one command assembles and links: ca65 normally hands object files to
ld65, and what comes out here is the finished 40,976-byte ROM. The segments are
the interface to the layout — CODE and VECTORS land where the NROM mapping
puts them. When assembling is not the last step has the detail.
The two bit $2002 loops are not decoration. The PPU is not ready to be written
to for the first couple of frames after power-on, and code that skips the wait
works on some emulators and not on hardware.
ZX Spectrum, pasmo syntax
; Cycle the border through all eight colours.
org 32768
ld b, 8 ; eight colours
next: ld a, b
dec a
out ($fe), a ; the border takes the low three bits
ld hl, 0
wait: dec hl
ld a, h
or l
jr nz, wait
djnz next
ret
asm198x --dialect pasmo border.asm -o border.bin
--sna writes a 48K snapshot an emulator will load directly, which needs an
end directive naming the entry point:
asm198x --dialect pasmonext --sna border.asm -o border.sna
Amiga, vasm syntax
; Write the background-colour register eight times, with a pause between.
CUSTOM equ $dff000
COLOR00 equ $180
section code,code
start:
lea CUSTOM,a5 ; the custom chips, as a base register
moveq #7,d1 ; eight times round
next: move.w d1,d0
lsl.w #4,d0 ; shift it up into the colour bits
move.w d0,COLOR00(a5) ; background colour
move.l #$20000,d2 ; a pause long enough to notice
wait: subq.l #1,d2
bne.s wait
dbra d1,next
moveq #0,d0 ; return code: nothing went wrong
rts
asm198x --dialect vasm --exe demo.s -o demo
--exe writes a hunk executable, which is what AmigaDOS loads and runs. Note
what this program does not do: it never takes the machine over. With the
operating system still running, anything it writes to a display register is
liable to be written back over by the OS on the next redraw. A demo that wants
the screen to itself has to take it first; the register writes are the part
that comes after.
rts returns to AmigaDOS with d0 as the return code, which is why the program
ends rather than looping forever like the other four.
Tandy CoCo, lwasm syntax
; Write one byte across the first row of the text screen.
SCREEN equ $0400 ; the text screen is memory, 32 by 16
org $0e00
start:
ldx #SCREEN ; where to write
ldb #32 ; one row of cells
lda #$2a ; the character code to write
loop: sta ,x+ ; store, then step X on one
decb
bne loop
rts
asm198x --dialect lwasm screen.asm -o screen.bin
sta ,x+ is the 6809 idiom the 6502 samples spell with an index register and a
counter: store through X, then advance X. It is the same loop as the C64 one
with the bookkeeping folded into the addressing mode.
The origin is where you intend the program to be loaded, and on a real CoCo you would choose it to sit clear of BASIC — which depends on how much memory the machine has. It is a decision about the target, not about the assembler.
When it does not assemble
lda takes one byte. Give it two and the assembler says so, naming the line
and the column the value starts at:
* = $c000
lda #$1234
rts
asm198x: fill.a:2:13: error: value 4660 does not fit in a byte
The address is $c000, so lda #$1234 is not ambiguous — it is a byte-sized
instruction given a word. Dropping the # makes it lda $1234, which is an
absolute load from address $1234.
What else these are good for
Any of these can go straight back through the other two operations. Tidying the C64 one, for instance:
asm198x fmt --dialect acme fill.a -o fill.tmp && mv fill.tmp fill.a
Formatting is idempotent and does not change the bytes the file assembles to, so it is safe on source you have not read — Keeping source tidy. And a binary reads back in the dialect’s own syntax, which is Reading a binary back.
Which dialect?
--dialect names the assembler, not the machine — see
Dialects for the full table and the conventional choice per
machine. If you have existing source, the answer is whichever assembler it was
written for.