Part 2 of 11 -- Simple Assembly for Atari BASIC - Learn 82.7% of Assembly Language in About Three Pages
Atari 8-bit 6502 Assembly Atari BASIC Mac/65
Learn 82.7% of Assembly Language in About Three Pages
Part 1 - Introduction
Part 2 - Learn 82.7% of Assembly Language in About Three Pages
Part 3 - The World Inside a USR() Routine
Part 4 - Implement DPEEK()
Part 5 - Implement DPOKE
Part 6 - Various Bit Manipulations
Part 7 - Convert Integer to Hex String
Part 8 - Convert Integer to Bit String
Part 9 - Memory Copy
Part 10 - Binary File I/O Part 1 (XIO is Broken)
Part 11 - Binary File I/O Part 2 (XIO is Broken)
(The printed PDF version of this section really is just 3 pages long.)
The hardest part of problem solving is overcoming the perception of difficulty. 6502 Assembly language is not difficult. It is only a different way of thinking about programs. In fact, because the 6502 is a simple processor, the world of 6502 Assembly language is simple. The hard part of Assembly programming is breaking a complex problem down in a way that can be solved by the simplicity of Assembly.
BASIC presents a program as text and program execution means interpreting each BASIC instruction which takes considerable time. The 6502 world is distinctly different. The text representation of 6502 instructions that humans can read and write is called “Assembly Language”. An “Assembler” is a program to convert the human-readable Assembly Language text into the 6502 “Machine Language” instructions for execution. The final program is only the 6502 machine language instructions without the Assembly Language text.
The 6502 works on data one, 8-bit byte at a time. The bytes of data come from memory which the 6502 describes with addresses 16-bits (two bytes) long. A two-byte address identifies one specific byte of memory at a location ranging from 0 to 65,535. This range is also referred to as 64K. The program the 6502 executes and the data it uses reside in the 64K of addressable memory. Additionally, Atari's custom hardware and devices also occupy specific addresses in memory.
The 6502 has a 256 byte structure called the stack occupying a specific block of memory. The CPU accesses only the top of the stack. It may add (called pushing) bytes only at the top of the stack and can remove (called pulling) them only from the top of the stack. The 6502 uses the stack to save return addresses when calling subroutines. Programs may use it for temporary information storage.
The 6502 has three dedicated registers called, “A” (for Accumulator), “X”, and “Y” that can each contain one byte of data. Most work occurs on data contained in these registers, though some operations can be done directly on memory. The majority of work takes place in the A register. The X and Y registers can't perform the same math and other data manipulations as the A register, but they can hold temporary values and facilitate looping behavior. Data may be exchanged one byte at a time between the A register and the X or Y registers, between the A register and the stack, and between any of the 3 registers and memory.
The 6502 machine language program is a sequence of instructions in memory. The instructions direct work such as reading data from memory into a register, writing data from a register into memory, pushing and pulling values to and from the stack, performing addition or subtraction, comparing values, merging data values, manipulating individual bits in a byte, evaluating various kinds of true/false conditions, and changing program flow based on those evaluations. 6502 machine language instructions may be one, two, or three bytes long. In general, longer instructions take more time to execute than shorter instructions, though there are exceptions.
At the completion of most instructions the CPU evaluates the results and sets flags identifying conditions for testing by subsequent instructions. The conditions include whether or not the result is zero, whether or not the result is negative, and whether or not a math operation results in a value overflow (or carry).
The 6502 has special treatment for the first 256 bytes (aka a “page”) in memory referred to as Page Zero. The 6502 has specific instructions referencing Page Zero addresses that are shorter than other instructions, so Page Zero use can reduce the size of a program and in some cases makes a program faster. Page Zero addresses also facilitate special methods of accessing memory not possible with addresses outside of Page Zero.
6502 assembly language describes each instruction using three-character abbreviations followed by either an explicit byte value or an address. The “#” sign (that is the “pound” or “number” pre-internet, “hashtag” in contemporary terms) precedes explicit values to differentiate them from addresses. Values and memory addresses are expressed as decimal numbers (e.g. 32) or preceded with the dollar sign to express hexadecimal. e.g. the value #$20 equals #32 decimal, and the address $52 equals 82 decimal.
Reading a value into a register is called “Loading”, thus the instruction to “Load” a value into the Accumulator is, “LDA”. For the X or Y register replace the “A” with “X” or “Y” resulting in “LDX” or “LDY”. Writing the value from a register into memory is called “Storing”, so the instructions are “STA”, “STX”, and “STY”. There are different methods to determine the target memory location. These methods, called addressing modes, are not equally available to all registers. The X and Y registers usually allow fewer options.
Here are a few example instructions, how they acquire values, and how they do (or do not) resemble BASIC:
LDA #32, loads the byte value 32 into the Accumulator. In BASIC, A=32 assigns value 32 to variable “A”.
LDA 710, loads the byte value held at address 710 (decimal) into the Accumulator. In BASIC, A=PEEK(710) assigns the byte value held at address 710 to variable “A” (and converts it to the Atari's six-byte floating point format). Alternatively, imagining that memory is like a numeric array the BASIC expression A=MEMORY would be conceptually similar.
The prior two examples work exactly the same for the X and Y registers: e.g. LDX #32 or LDY 710.
LDA $2C0,X, determines the value of $2C0 (hex) plus the value held in the X register, then loads the byte value held in that resulting address into the Accumulator. In BASIC, A=PEEK(704+X) determines the value of 704 plus the value of variable “X” and assigns the byte value held in that resulting address to variable “A”. Or, using the memory model again, this would be similar to A=MEMORY[704+X].
LDA ($D4),Y determines the address held in the Zero Page location $D4 and $D5 plus the value held in the Y register, then loads the byte value held in that resulting address into the Accumulator. Using an address to point to another address is a powerful feature of the 6502 called, “indirection” which is the basis of making reusable code that can operate on any possible memory. Change the contents of the Page Zero value, and the instruction acts on a different location. The closest parallel in BASIC: A=PEEK(V+Y) where the variable “V” defines the base location, and Y is an index added to the location.
The instructions INX or INY adds 1 to the X or Y register and DEX or DEY subtracts 1 from the X or Y register. Note there is no address or option. These are one-byte instructions. In BASIC, obviously, this is X=X+1 and X=X-1, etc. In the examples above we saw the X and Y registers add an offset to a target address in different ways. This is a basis for loop control and iterating across a range of bytes held in sequential memory locations.
Each of the Load instructions above has a corresponding Store instruction. These store the Accumulator value into a specified address using the same methods of determining the target address seen earlier: STA 710, STA $2C0,X. The BASIC equivalents would use POKE to store in memory: POKE 710,A, POKE 704+X,A. The third form is STA ($D4),Y and imitated in BASIC: POKE V+Y,A where V is a variable containing a base address.
There is no STA #32. Storing a constant value in memory requires first loading a register with the byte value and then using a store instruction to place it into a target memory address.
CMP #32, Compare the contents of the Accumulator to the byte value 32, setting flags in the CPU for evaluation by subsequent statements. A BASIC example is only vaguely similar: IF A=32 THEN ZFLAG=1. This performs the comparison and sets variable “ZFLAG” in preparation for later examination. However, the 6502 comparison evaluates several different criteria at the same time, not just this one flag.
BEQ $9C40 or BNE $9C40. These cause the program execution to jump to (or branch) to the destination address based on the current state of flags set or cleared in the CPU. The CPU evaluates and sets the state of flags by a CMP instruction or any instruction that changes register contents. In this case when the Zero Flag is set due to a previous comparison then the compared values are considered equal, thus the instruction is “Branch when EQual”, BEQ. When this evaluation is true then the program branches to the target address. BNE is the opposite evaluation for when the Zero flag is not set, or “Branch when Not Equal”. The 6502 has branch instructions for each of the flags to “Branch when” the flag is set or “Branch when” the flag is clear. One special note: the branch target address must be within +/-127 bytes of the current program address. The BASIC equivalents that are roughly similar: IF ZFLAG=1 THEN GOTO 1200 or IF ZFLAG=0 THEN GOTO 1200.
PLA pulls the top value off the stack and places it in the Accumulator. The stack is a hardware feature inherent to the 6502, so there is no direct parallel in BASIC. For the sake of illustration consider the stack an array and a variable called “SP” (for stack pointer) identifies the top element. A BASIC equivalent would then be: A=STACK[SP]:SP=SP-1.
PHA pushes the value in the Accumulator to the top of the stack. Again, this requires an imaginary expression in BASIC: SP=SP+1:STACK[SP]=A
TAX and TAY transfer the contents of the Accumulator to the X or Y register respectively. In BASIC, it is conceptually similar to X=A or Y=A. Likewise, TXA and TYA transfer the contents of the X or Y register to the Accumulator which is like A=X or A=Y in BASIC.
JMP $9C40 is like GOTO 1200 in BASIC and JSR $9C40 is like GOSUB 1200 in BASIC. These instructions change the program counter to the specified 16-bit address. JSR also pushes the current program counter address on the stack allowing the subroutine to return to this location.
RTS is how a subroutine exits and returns to the point where it was called. This instruction updates the program counter with a 16-bit address pulled from the stack. That address is usually pushed on the stack by a prior JSR instruction. This is similar to RETURN in BASIC.
The heart of man plans his way, but the Lord establishes his steps.