Now Reading
Unit testing on an 8-bit CPU – The Boston Diaries

Unit testing on an 8-bit CPU – The Boston Diaries

2023-11-28 01:38:18

Unit testing on an 8-bit CPU

I have been utilizing my assembler to jot down foolish little packages for the Color Computer
(simulated—simpler than establishing my Shade Laptop, the primary pc I owned as a child).
It took me completely too lengthy to find a bug in a maze-drawing program I have been writing,
and I need to do a deep dive on this.

This system is easy,
it simply attracts a maze,
following a number of easy guidelines:

  1. choose a random path (up, proper, left or down);
  2. try to attract a maze phase (utilizing coloration 1) within the given path;
  3. if we’re boxed in (no path to go in)—
    1. if we’re on the beginning location, we’re achieved;
    2. backtrack alongside a phase of the trail we have drawn (utilizing coloration 2);
    3. if we’re now not boxed in, return to step 1;
    4. in any other case, return to step 3.

Nothing laborious about it,
but this system stored getting caught.

[Image of a partially drawn maze]

It begins within the higher left,
meanders a bit
(in blue),
backtracks a bit
(in pink)
till it will get caught.
I might then stare on the code till blood shaped on my forehead,
simplify the code if I might,
and take a look at once more solely to look at it get caught,
once more.

The difficulty is that I’ve no debugger on this method.
I haven’t got two screens upon which to indicate debugging output.
I’ve no method of single-stepping although the code.
I do not also have a log to undergo.
Debugging consists of operating the code,
then pondering actual laborious.
And sure,
it was a silly mistake that took all of two traces of code to repair.

[Image of a fully drawn maze]

Now,
the query I need to ask is—would I’ve saved time if I did “unit testing?”
Not simply testing,
which I used to be doing all alongside,
however the typical type of “unit testing” the place you check a “unit” of code
(getting back to that question again).
Trying over the code
(and that model has the bug—see if you could find it;
you will additionally want these
two files),
probably the most remoted “unit” is random
(the final subroutine within the code).

;***********************************************************************
;	RANDOM		Generate a random quantity
;Entry:	none
;Exit:	B - random quantity (1 - 255)
;***********************************************************************

random		ldb	lfsr
		andb	#1
		negb
		andb	#$B4
		stb	,-s		; lsb = -(lfsr & 1) & faucets
		ldb	lfsr
		lsrb			; lfsr >>= 1
		eorb	,s+		; lfsr ^=  lsb
		stb	lfsr
		rts

It implements a linear-feedback shift register
with a interval of 255
(in that it’s going to return every worth within the vary of 1‥255 as soon as in some randomesque order earlier than repeating,
which is Good Sufficient™ for this code).
So what if we need to check that the code is right?

And it is right here I got here to a realization—“unit testing” actually relies upon upon the language and tooling round it.
Trendy orthodoxy holds “unit testing über alles” and subsequently,
trendy languages and tooling is created to assist “unit testing über alles”
(and woe betide those that query it).
I believe my battle with “unit testing” is that the environments I discover myself in do not lend themselves very properly to “unit testing.”
Even once I labored at The Enterprise,
we had been utilizing C (C99 at greatest) and C++ (C++98, perhaps C++03?) which take loads of work upfront to assist “unit testing” properly,
and there wasn’t loads of work upfront to assist “unit testing” properly,
and there was a decidely lack of a “unit testing” framework.
And right here,
there’s positively not a “unit testing” framework.
Any “unit testing” I’ve to do entails writing but extra code.
So let’s write but extra code and check this sucker.

CHROUT		equ	$A002		; BASIC character output routine
lfsr		equ	$F6		; unused location in direct web page

		org	$4000

begin		ldx	#result_array	; level to reminiscence
		clra			; storing 0 to reminiscence
		clrb			; 256 bytes to clear

.clrarray	sta	,x+		; clear reminiscence
		decb			; decrement rely
		bne	.clrarray	; maintain going till rely = 0

		ldx	#result_array	; level to array
		lda	#255		; cycle size

checkrnd	bsr	random		; get random quantity in B
		tst	b,x		; have we seen this quantity?
		bne	.failed		; in that case, we've got failed
		inc	b,x		; set flag for this quantity
		deca			; are we achieved with the cycle
		bne	checkrnd	; if not, maintain going

		ldx	#msg.success	; SUCCESS!
		bsr	places
		rts

	;---------------------------------------------------
	; Retailer rely (register A) and random # (register B) 
	; so we will use PEEK in BASIC to see the place we failed
	;---------------------------------------------------

.failed		std	lfsr+1
		ldx	#msg.failed	; failed message
		bsr	places		; show it
		rts			; return to BASIC

places.10		jsr	[CHROUT]	; print character
places		lda	,x+		; get character
		bne	places.10		; if not NUL, print it
		rts			; return to BASIC

;******************************************************************

random		ldb	lfsr
		andb	#1
		negb
		andb	#$B4
		stb	,-s		; lsb = -(lfsr & 1) & faucets
		ldb	lfsr
		lsrb			; lfsr >>= 1
		eorb	,s+		; lfsr ^=  lsb
		stb	lfsr
		rts

;*******************************************************************

msg.success	asciiz	'SUCCESS!r'
msg.failed	asciiz	'FAILED!r'
result_array	equ	*
		finish	begin

The tooling right here would not assist linking 6809 code,
and I would fairly not must maintain every routine in its personal file for the reason that program is so easy and makes modifying it simpler if every thing is in a single file
(no IDE right here—and sure,
I’ve ideas about testing and IDEs however that is past the scope for this submit).
So I’ve to repeat the routine to the check program.

This was one thing I stored making an attempt to inform my supervisor at The Enterprise—the check program itself may be buggy
(he personally handled the output as gospel—sigh).
And the “unit testing” proponents appear to hem and haw about testing the testing code,
implying that one doesn’t merely check the testing code.
But when programmers aren’t trusted to jot down code and should check,
then why are they trusted to jot down testing code with out testing?

It might sound I digress,
however I am not.
There are 4 bugs within the above check.
The code I am testing, random?
It was advantageous.
And I wasn’t intending to jot down a buggy check harness,
it simply occurred as I used to be writing it.
Bug one—I forgot to check that random did not return 0
(that is a degenerate case with LFSRs).
Second bug—I forgot to ininitialize the LFSR state with a non-zero worth,
so random would return nothing however zero.
The third bug was pondering I had used the fallacious situation when branching to the failure case,
however no,
I had it proper the primary time
(the truth that I modified it, after which modified it again, is the bug).
The precise bug that induced this was the fourth bug,
however I’ve to digress a bit to elucidate it.

The 6809 has an in depth indexing addressing mode for an 8-bit CPU.
One of many modes enable one to make use of an accumulator register (A, B or D)
as an offset to the index register.
I used the B register,
which incorporates the random quantity,
as an offset right into a 256-element array to trace the return values,
thus using b,x within the code above.
What I forgot about within the second of writing the code is that the “accumulator,index-register” indexing mode signal extends the accumulator.
And the primary worth from random is,
as a result of LFSR I am utilizing,
if handled as signed,
a destructive worth—it might fail on the very first try.

Sigh.

Because of this I panicked and thought I botched the conditional department.

Now,
all of that simply to check probably the most remoted of subroutines in this system.

However had I continued,
would any type of “unit testing” been useful?
There’s the subroutine point_addr—which converts an X,Y place right into a byte tackle within the body buffer,
and the pixel in mentioned byte.
I might have achieved an exhaustive check of all 4,096 factors,
once more,
that is code I might have write
(in 6809 Meeting code)
and sadly, check,
to have any confidence in it.
And dealing up the chain,
there’s getpixel and setpixel.
Testing these would require a little bit of thought—let’s examine … getpixel returns the colour of the pixel on the given X,Y location on the display screen.
and assuming point_addr is working,
it might solely take 4 checks
(one per pixel within the byte)
however at this level,
would I even belief myself to jot down the check code?

In truth,
would “unit testing” have saved me any time?

Provided that I must write the testing framework,
no,
I do not assume I might have saved time.
Maybe if I assumed the problem by means of earlier than diving into altering the code,
I might have solved this earlier.

And the clues had been there.
I did uncover fairly early on that the bug was within the backtracking code.
The highest degree code is fairly simple to visually examine:

backtrack	lda	#BACKTRACK
		sta	coloration

.loop		ldd	xpos		; verify to see if we're again
		cmpd	xstart		; at the start line,
		beq	achieved		; and in that case, we're achieved

		ldd	xpos		; can we backtrack NORTH?
		decb
		lbsr	getpixel
		cmpb	#EXPLORE
		bne	.check_east
		lbsr	move_north.now	; in that case, transfer NORTH and see if
		bra	.probe		; we've got to maintain backtracking

.check_east	ldd	xpos		; east ...
		inca
		lbsr	getpixel
		cmpb	#EXPLORE
		bne	.check_west
		lbsr	move_east.now
		bra	.probe

.check_west	ldd	xpos		; yada yada ...
		deca
		lbsr	getpixel
		cmpb	#EXPLORE
		bne	.check_south
		lbsr	move_west.now
		bra	.probe

.check_south	ldd	xpos
		incb
		lbsr	getpixel
		cmpb	#EXPLORE
		bne	.probe
		lbsr	move_south.now

.probe		bsr	boxed_in	; can we cease backtracking?
		bne	discover		; in that case, return to exploring
		bra	.loop		; else backtrack some extra

The factor to remember right here is that the D register is a 16-bit register the place the higher 8-bits is the A register,
and the decrease 8-bits are the B register,
and that the 6809 is big-endian.
So once we do ldd xpos we’re loading the A register with the X coordinate,
and B with the Y coordinate.
And the move_*.now subroutines work,
or else we would not get the beginning maze segments in any respect.
So it is clear that setpixel works advantageous.

The code is getting caught making an attempt to backtrack alongside already drawn segments,
and it does that by calling getpixel,
subsequently,
it appears prudent to verify getpixel.
And positive sufficient,
that’s the place the bug resides.

;*************************************************************************
;	GETPIXEL	Get the colour of a given pixel
;Entry:	A - x pos
;	B - y pos
;Exit:	X - video tackle
;	A - 0
;	B - coloration
;*************************************************************************

getpixel	bsr	point_addr	; get video tackle
		comb			; reverse masks (since we're studying
		stb	,-s		; the display screen, not writing it)
		ldb	,x		; get video knowledge
		andb	,s+		; masks off the pixel
.rotate		lsrb			; shift coloration bits
		deca
		bne	.rotate
.achieved		rts			; return coloration in B

;*************************************************************************
;	POINT_ADDR		calculate the tackle of a pixel
;Entry:	A - xpos
;	B - ypos
;Exit:	X - video tackle
;	A - shift worth
;	B - masks
;*************************************************************************


point_addr.bits	fcb	%00111111,%11001111,%11110011,%11111100 ; masks
		fcb	6,4,2,0	; bit shift counts

I’ve included a little bit of point_addr to provide some context.
point_addr returns the variety of shifts required to maneuver the colour worth into place,
and a kind of shift values is 0.
However getpixel would not verify to see it is 0 earlier than decrementing it.
And thus,
getpixel will return the fallacious worth for the final pixel in any byte.
The repair is easy:

		tsta
		beq	.achieved

simply earlier than the .rotate label fixes the bug
(two directions, three bytes and this is a hyperlink to the fixed code).

Sure,
I freely admit {that a} “unit check“ of this subroutine would have proven the bug.
However how a lot time would I’ve spent writing the check code to start with?
The one cause it took me so long as it did to search out was as a result of the reference code I used to be utilizing was fairly convoluted,
and I frolicked simplifying the code as I went alongside
(which is worthy of doing anyway).

What I want the “unit testing” proponents would notice is that simple testing relies upon upon the language and tooling concerned within the venture,
and what a “unit” is really relies upon upon the language.
I think that “unit check” proponents additionally discover “unit testing” simpler to take care of than “integration testing” and even “end-to-end testing,”
thus why we get “unit checks über alles” shouted from the roof tops.


Discussions about this entry

Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top