Mandelbrot yet again, but this time in assembler

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Post Reply
Martin_H
Posts: 837
Joined: 08 Jan 2014

Mandelbrot yet again, but this time in assembler

Post by Martin_H »

I decided to revisit the Mandelbrot set, but in assembler to see how much faster I could make it than my Forth version. While I'm not an assembler wizard, I'm competent enough, especially when I reuse code libraries to do the hard part of multiplication.

The net result is that assembler is about 4 times faster than Forth, and about 20 times faster than ehBasic. This makes sense when you consider that much of my code uses a Forth like approach, but with primitives like push or drop in-lined rather than called via a subroutine.

Video: https://youtu.be/W24UmuEV0Uc

Github repo: https://github.com/Martin-H1/6502/blob/ ... elbrot.asm

The code depends upon a 16 bit math library and page zero stack macros, but the basic flow is understandable without them. So here's the code with the unused 4.12 fixed point removed:

Code: Select all

; -----------------------------------------------------------------------------
; Mandelbrot in ASCI art. It uses 8.8 fixed point mathematics in a 16 bit cell
; instead of floating point. It depends upon the stack, math, and I/O functions
; defined in the other modules.
; Martin Heermance <mheermance@gmail.com>
; -----------------------------------------------------------------------------

; establish module level scope to hide module locals.
.scope

;
; Aliases
;

; Setup constants to remove magic numbers to allow
; for greater zoom with different scale factors.
.alias MAXITER	20
.alias IMAXVAL	$0170
.alias IMINVAL	$FE90
.alias ISTEP	$0010
.alias RMINVAL	$FE00
.alias RMAXVAL	$0110
.alias RSTEP	$000A
.alias ESCAPE	$0400

;
; Data segments
;
.data BSS
.space CREAL 2			; These variables hold values during the
.space CIMAG 2			; escape calculation.
.space ZREAL 2
.space ZIMAG 2
.space COUNT 1			; current escape count

.text

;
; Macros
;
.macro rescale
	lda NOS_MSB,x
	sta NOS_LSB,x
	lda TOS_LSB,x
	sta NOS_MSB,x
	`drop
.macend

;
; Functions
;

; Main entry point
mandelbrot:
.scope
	`printcr
	`pushi IMAXVAL		; Start iteration at max imaginary
_do:	jsr doRow		; For each row in the set.
	`printcr
	`pushi ISTEP
	jsr sub16
	`dup
	`pushi IMINVAL
	jsr compare16
	bmi _do
	`drop
	rts
.scend

; For each cell in a row.
doRow:
.scope
	`pushi RMINVAL		; Start iteration at max imaginary
_do:	jsr doCell		; For each row in the set.
	`pushi RSTEP
	jsr add16
	`dup
	`pushi RMAXVAL
	jsr compare16
	bpl _do
	`drop
	rts
.scend

; Iterates on a single cell to compute its escape factor.
; input - row and column on stack.
; output - none
doCell:
	jsr initVars
	`pushZero		; set the loop up for repeating
_while:
	`drop			; drop status from last iteration.
	jsr doEscape
	`tosZero?
	beq _while
	`drop
	lda COUNT
	jsr toChar
	rts

; stores the row column values from the stack for the escape calculation.
; input - two cells on stack
; output - none
initVars:
	`over
	`over
	`pop CREAL
	`pop CIMAG
	`pushzero
	`peek ZREAL
	`pop ZIMAG
	lda #$01
	sta COUNT
	rts

; Performs a single iteration of the escape calculation.
; input - implicit from memory.
; ouput - true or false.
doEscape:
.scope
	jsr zrSq
	jsr ziSq
	`over
	`over
	jsr add16
	`pushi ESCAPE		; Numbers >= 4 will always escape
	jsr compare16
	beq +
	bpl _else
*	`drop			; cleanup the stack and return.
	`drop
	`pushTrue
	rts
_else:
	jsr sub16		; Squared i yeilds negative, so subtract
	`push CREAL
	jsr add16		; add real components and leave result on stack
	`push ZREAL		; 2 * ZREAL * ZIMAG
	`push ZIMAG
	jsr mstar
	`rescale
	`pushi 1
	jsr lshift16
	`push CIMAG
	jsr add16
	`pop ZIMAG		; Store stack item into ZIMAG and ZREAL
	`pop ZREAL
	jmp countAndTest
.scend

; Compute squares, but rescale to remove extra scaling factor.
; input - implicit from memory
; output - value on stack
zrSq:
	`push ZREAL
	`dup
	jsr mstar		; rescale by shifting one byte
	`rescale
	rts

; Compute squares, but rescale to remove extra scaling factor.
; input - implicit from memory
; output - value on stack
ziSq:
	`push ZIMAG
	`dup
	jsr mstar		; rescale by shifting one byte
	`rescale
	rts

; Translate escape count to ascii greyscale.
; input - value in accumulator
; output - character to console.
toChar:
	phy
	tay
	lda _charVals,y
	jsr putch
	ply
	rts
_charVals:
	.byte " ..,'~!^:;[/<&?oxOX#  "

; Increment count and compare to max iterations.
; input - implicit from memory
; output - true or false on stack
countAndTest:
.scope
	inc COUNT
	lda COUNT
	`pushA
	`pushi MAXITER
	`if_greater16
	`pushTrue
	rts
_else:	`pushZero
	rts
.scend

.scend
Post Reply