1
0
mirror of https://github.com/ksherlock/x65.git synced 2024-11-05 13:05:29 +00:00
6502 Macro Assembler in a single c++ file using the struse single file text parsing library. Supports most syntaxes.
Go to file
2015-10-11 15:10:58 -07:00
sublime Adding ENUM and assembler directives from LISA and MERLIN assemblers. 2015-10-06 22:42:22 -07:00
asm6502.cpp Error fix II, debug symbol table rewrite 2015-10-11 14:55:55 -07:00
LICENSE
README.md Some more notes on Merlin syntax support 2015-10-11 15:10:58 -07:00

Asm6502

6502 Macro Assembler in a single c++ file using the struse single file text parsing library. Supports most syntaxes.

Every assembler seems to add or change its own quirks to the 6502 syntax. This implementation aims to support all of them at once as long as there is no contradiction.

To keep up with this trend Asm6502 is adding the following features to the mix:

  • Full expression evaluation everywhere values are used: Expressions
  • Basic relative sections and linking.
  • C style scoping within '{' and '}': Scopes
  • Reassignment of labels. This means there is no error if you declare the same label twice, but on the other hand you can do things like label = label + 2.
  • Local labels can be defined in a number of ways, such as leading period (.label) or leading at-sign (@label) or terminating dollar sign (label$).
  • Directives support both with and without leading period.
  • Labels don't need to end with colon, but they can.
  • No indentation required for instructions, meaning that labels can't be mnemonics, macros or directives.
  • Conditional assembly with #if/#ifdef/#else etc.
  • As far as achievable, support the syntax of other 6502 assemblers (Merlin syntax now requires command line argument).

In summary, if you are familiar with any 6502 assembler syntax you should feel at home with Asm6502. If you're familiar with C programming expressions you should be familiar with '{', '}' scoping and complex expressions.

There are no hard limits on binary size so if the address exceeds $ffff it will just wrap around to $0000. I'm not sure about the best way to handle that or if it really is a problem.

There is a sublime package for coding/building in Sublime Text 3 in the sublime subfolder.

Prerequisite

Asm6502.cpp requires struse.h which is a single file text parsing library that can be retrieved from https://github.com/Sakrac/struse.

References

Features

  • Code
  • Comments
  • Labels
  • Directives
  • Macros
  • Expressions

Code

Code is any valid mnemonic/opcode and addressing mode. At the moment only one opcode per line is assembled.

Comments

Comments are currently line based and both ';' and '//' are accepted as delimiters.

Expressions

Anywhere a number can be entered it can also be interpreted as a full expression, for example:

Get123:
    bytes Get1-*, Get2-*, Get3-*
Get1:
	lda #1
	rts
Get2:
	lda #2
	rts
Get3:
	lda #3
	rts

Would yield 3 bytes where the address of a label can be calculated by taking the address of the byte plus the value of the byte.

Labels

Labels come in two flavors: Addresses (PC based) or Values (Evaluated from an expression). An address label is simply placed somewhere in code and a value label is follwed by '=' and an expression. All labels are rewritable so it is fine to do things like NumInstance = NumInstance+1. Value assignments can be prefixed with '.const' or '.label' but is not required to be prefixed by anything.

Local labels exist inbetween global labels and gets discarded whenever a new global label is added. The syntax for local labels are one of: prefix with period, at-sign, exclamation mark or suffix with $, as in: .local or !local or @local or local$. Both value labels and address labels can be local labels.

Function:			; global label
	ldx #32
.local_label		; local label
	dex
	bpl .local_label
	rts

Next_Function:		; next global label, the local label above is now erased.
	rts

Directives

Directives are assembler commands that control the code generation but that does not generate code by itself. Some assemblers prefix directives with a period (.org instead of org) so a leading period is accepted but not required for directives.

  • ORG (same as PC): Set the current compiling address.
  • LOAD Set the load address for binary formats that support it.
  • SECTION Start a relative section
  • LINK Link a relative section at this address
  • ALIGN Align the address to a multiple by filling with 0s
  • MACRO Declare a macro
  • EVAL Log an expression during assembly.
  • BYTES Insert comma separated bytes at this address (same as BYTE or DC.B)
  • WORDS Insert comma separated 16 bit values at this address (same as WORD or DC.W)
  • TEXT Insert text at this address
  • INCLUDE Include another source file and assemble at this address
  • INCBIN Include a binary file at this address
  • CONST Assign a value to a label and make it constant (error if reassigned with other value)
  • LABEL Decorative directive to assign an expression to a label
  • INCSYM Include a symbol file with an optional set of wanted symbols.
  • POOL Add a label pool for temporary address labels
  • #IF / #ELSE / #IFDEF / #ELIF / #ENDIF Conditional assembly
  • STRUCT Hierarchical data structures (dot separated sub structures)
  • REPT Repeat a scoped block of code a number of times.
  • INCDIR Add a directory to look for binary and text include files in.
  • MERLIN A variety of directives and label rules to support Merlin assembler sources

ORG


org $2000
(or pc $2000)

Sets the current assembler address to this address

SECTION

    section Code
Start:
	lda #<Data
	sta $fe
	lda #>Data
	sta $ff
	rts

	section BSS
Data:
	byte 1,2,3,4

Starts a relative section. Relative sections require a name and sections that share the same name will be linked sequentially. The labels will be evaluated at link time.

LINK

Link a set of relative sections (sharing the same name) at this address

The following lines will place all sections named Code sequentially at location $1000, followed by all sections named BSS:

	org $1000
    link Code
	link BSS

Currently there are no object files so there is not a great use of the link directive yet. When object files exist this feature will make working in big projects easier.

LOAD

load $2000

For c64 .prg files this prefixes the binary file with this address.

ALIGN

align $100

Add bytes of 0 up to the next address divisible by the alignment

MACRO

See the 'Macro' section below

EVAL

Example:

eval Current PC: *

Might yield the following in stdout:

Eval (15): Current PC : "*" = $2010

When eval is encountered on a line print out "EVAL (<line#>) <message>: <expression> = <evaluated expression>" to stdout. This can be useful to see the size of things or debugging expressions.

BYTES

Adds the comma separated values on the current line to the assembled output, for example

RandomBytes:
	bytes NumRandomBytes
	{
	    bytes 13,1,7,19,32
		NumRandomBytes = * - !
	}

byte or dc.b are also recognized.

WORDS

Adds comma separated 16 bit values similar to how BYTES work. word or dc.w are also recognized.

TEXT

Copies the string in quotes on the same line. The plan is to do a petscii conversion step. Use the modifier 'petscii' or 'petscii_shifted' to convert alphabetic characters to range.

Example:

text petscii_shifted "This might work"

INCLUDE

Include another source file. This should also work with .sym files to import labels from another build. The plan is for Asm6502 to export .sym files as well.

Example:

include "wizfx.s"

INCBIN

Include binary data from a file, this inserts the binary data at the current address.

Example:

incbin "wizfx.gfx"

CONST

Prefix a label assignment with 'const' or '.const' to cause an error if the label gets reassigned.

const zpData = $fe

LABEL

Decorative directive to assign an expression to a label, label assignments are followed by '=' and an expression.

These two assignments do the same thing (with different values):

label zpDest = $fc
zpDest = $fa

INCSYM

Include a symbol file with an optional set of wanted symbols.

Open a symbol file and extract a set of symbols, or all symbols if no set was specified. Local labels will be discarded if possible.

incsym Part1_Init, Part1_Update, Part1_Exit "part1.sym"

POOL

Add a label pool for temporary address labels. This is similar to how stack frame variables are assigned in C.

A label pool is a mini stack of addresses that can be assigned as temporary labels with a scope ('{' and '}'). This can be handy for large functions trying to minimize use of zero page addresses, the function can declare a range (or set of ranges) of available zero page addresses and labels can be assigned within a scope and be deleted on scope closure. The format of a label pool is: "pool start-end, start-end" and labels can then be allocated from that range by '

Example:

Function_Name: {
	pool zpWork $f6-$100		; these zero page addresses are available for temporary labels
	zpWork zpTrg.w				; zpTrg will be $fe
	zpWork zpSrc.w				; zpSrc will be $fc

	lda #>Src
	sta zpSrc
	lda #<Src
	sta zpSrc+1
	lda #>Dest
	sta zpDst
	lda #<Dest
	sta zpDst+1

	{
		zpWork zpLen			; zpLen will be $fb
		lda #Length
		sta zpLen
	}
	nop
	{
		zpWork zpOff			; zpOff will be $fb (shared with previous scope zpLen)
	}
	rts

#IF / #ELSE / #IFDEF / #ELIF / #ENDIF

Conditional code parsing is very similar to C directive conditional compilation.

Example:

DEBUG = 1

#if DEBUG
    lda #2
#else
    lda #0
#endif

STRUCT

Hierarchical data structures (dot separated sub structures)

Structs helps define complex data types, there are two basic types to define struct members, and as long as a struct is declared it can be used as a member type of another struct.

The result of a struct is that each member is an offset from the start of the data block in memory. Each substruct is referenced by separating the struct names with dots.

Example:

struct MyStruct {
	byte	count
	word	pointer
}

struct TwoThings {
	MyStruct thing_one
	MyStruct thing_two
}

struct Mixed {
	word banana
	TwoThings things
}

Eval Mixed.things
Eval Mixed.things.thing_two
Eval Mixed.things.thing_two.pointer
Eval Mixed.things.thing_one.count

results in the output:

EVAL(16): "Mixed.things" = $2
EVAL(27): "Mixed.things.thing_two" = $5
EVAL(28): "Mixed.things.thing_two.pointer" = $6
EVAL(29): "Mixed.things.thing_one.count" = $2

REPT

Repeat a scoped block of code a number of times. The syntax is rept <count> { <code> }.

Example:

columns = 40
rows = 25
screen_col = $400
height_buf = $1000
rept columns {
	screen_addr = screen_col
	ldx height_buf
	dest = screen_addr
	remainder = 3
	rept (rows+remainder)/4 {
		stx dest
		dest = dest + 4*40
	}
	rept 3 {
		inx
		remainder = remainder-1
		screen_addr = screen_addr + 40
		dest = screen_addr
		rept (rows+remainder)/4 {
			stx dest
			dest = dest + 4*40
		}
	}
	screen_col = screen_col+1
	height_buf = height_buf+1
}

INCDIR

Adds a folder to search for INCLUDE, INCBIN, etc. files in

###MERLIN

A variety of directives and label rules to support Merlin assembler sources. Merlin syntax is supported in Asm6502 since there is historic relevance and readily available publicly release source.

To enable Merlin 8.16 syntax use the '-merlin' command line argument. Where it causes no harm, Merlin directives are supported for non-merlin mode.

LABELS

]label means mutable address label, also does not seem to invalidate local labels.

:label is perfectly valid, currently treating as a local variable

labels can include '?'

Merlin labels are not allowed to include '.' as period means logical or in merlin, which also means that enums and structs are not supported when assembling with merlin syntax.

Expressions

Merlin may not process expressions (probably left to right, parenthesis not allowed) the same as Asm6502 but given that it wouldn't be intuitive to read the code that way, there are probably very few cases where this would be an issue.

EJECT

An old assembler directive that does not affect the assembler but if printed would insert a page break at that point.

DS

Define section, followed by a number of bytes. If number is positive insert this amount of 0 bytes, if negative, reduce the current PC.

DUM, DEND

Dummy section, this will not write any opcodes or data to the binary output but all code and data will increment the PC addres up to the point of DEND.

PUT

A variation of INCLUDE that applies an oddball set of filename rules. These rules apply to INCLUDE as well just in case they make sense.

USR

In Merlin USR calls a function at a fixed address in memory, Asm6502 safely avoids this. If there is a requirement for a user defined macro you've got the source code to do it in.

Command Line Options

Typical command line:

Asm6502 [-DLabel] [-iIncDir] <source.s> <dest.prg> [-sym dest.sym] [-vice dest.vs] [-c64]/[-bin]/[-a2b] [-merlin]

Usage

Asm6502 [options] filename.s code.prg

  • -i<path>: Add include path (multiple allowed)
  • -D<label>[=<value>]: Define a label with an optional value (otherwise 1, multiple allowed)
  • -bin: Raw binary
  • -c64: Include load address
  • -a2b: Apple II Dos 3.3 Binary Executable
  • -sym <file.sym>: vice/kick asm symbol file
  • -vice <file.vs>: export a vice symbol file
  • -merlin: Assembler syntax for Apple II Merlin 8.16

Expression syntax

Expressions contain values, such as labels or raw numbers and operators including +, -, *, /, & (and), | (or), ^ (eor), << (shift left), >> (shift right) similar to how expressions work in C. Parenthesis are supported for managing order of operations where C style precedence needs to be overrided. In addition there are some special characters supported:

  • *: Current address (PC). This conflicts with the use of * as multiply so multiply will be interpreted only after a value or right parenthesis
  • <: If less than is not follwed by another '<' in an expression this evaluates to the low byte of a value (and $ff)
  • : If greater than is not followed by another '>' in an expression this evaluates to the high byte of a value (>>8)

  • !: Start of scope (use like an address label in expression)
  • %: First address after scope (use like an address label in expression)
  • $: Preceeds hexadecimal value
  • %: If immediately followed by '0' or '1' this is a binary value and not scope closure address

Example:

lda #(((>SCREEN_MATRIX)&$3c)*4)+8
sta $d018

Macros

A macro can be defined by the using the directive macro and includes the line within the following scope:

Example:

macro ShiftLeftA(Source) {
    rol Source
    rol A
}

The macro will be instantiated anytime the macro name is encountered:

lda #0
ShiftLeftA($a0)

The parameter field is optional for both the macro declaration and instantiation, if there is a parameter in the declaration but not in the instantiation the parameter will be removed from the macro. If there are no parameters in the declaration the parenthesis can be omitted and will be slightly more efficient to assemble, as in:

.macro GetBit {
    asl
    bne %
    jsr GetByte
}

Currently macros with parameters use search and replace without checking if the parameter is a whole word, the plan is to fix this.

Scopes

Scopes are lines inbetween '{' and '}' including macros. The purpose of scopes is to reduce the need for local labels and the scopes nest just like C code to support function level and loops and inner loop scoping. '!' is a label that is the first address of the scope and '%' the first address after the scope.

This means you can write

{
    lda #0
    ldx #8
    {
        sta Label,x
    	dex
    	bpl !
    }
}

to construct a loop without adding a label.

##Examples

Using scoping to avoid local labels

; set zpTextPtr to a memory location with text
; return: y is the offset to the first space.
;  (y==0 means either first is space or not found.)
FindFirstSpace
  ldy #0
  {
    lda (zpTextPtr),y
    cmp #$20
    beq %             ; found, exit
    iny
    bne !             ; not found, keep searching
  }
  rts

Development Status

Currently the assembler is in an early revision and while features are tested individually it is fairly certain that untested combinations of features will indicate flaws and certain features are not in a complete state.

TODO

  • Object file format so sections can be saved for later linking
  • Export full memory of fixed sections instead of a single section
  • Macro parameters should replace only whole words instead of any substring
  • Add 'import' directive as a catch-all include/incbin/etc. alternative
  • irp (indefinite repeat)

FIXED

Revisions:

  • 2015-10-10 Relative Sections and Link support, adding -merlin command line to clean up code
  • 2015-10-06 Added ENUM and MERLIN / LISA assembler directives (EJECT, DUM, DEND, DS, DB, DFB, DDB, IF, ENDIF, etc.)
  • 2015-10-05 Added INCDIR, some command line options (-D, -i, -vice)
  • 2015-10-04 Added REPT directive
  • 2015-10-04 Added STRUCT directive, sorted functions by grouping a bit more, bug fixes
  • 2015-10-02 Cleanup hid an error (#else without #if), exit with nonzero if error was encountered
  • 2015-10-02 General cleanup, wrapping conditional assembly in functions
  • 2015-10-01 Added Label Pools and conditional assembly
  • 2015-09-29 Moved Asm6502 out of Struse Samples.
  • 2015-09-28 First commit