Applying the Concepts - From Pseudocode to Program

A common technique for assembly programming is to devise some sort of pseudocode for whatever you're trying to accomplish. Pseudocode doesn't really have to be a language - it can be as simple as a group of coherent english words. However, some people find it easier to organize the harder routines (like the various sorting algorithms) in a higher language, such as C/C++. I myself use BASIC as a form of pseudocode from time to time. :o Basically, pseudocode attempts to outline your thoughts on the program into a purposeful and easy-to-understand piece of code.

For this application, let's say you were a BASIC programmer and you have some code:

For(A,1,8:For(B,1,12:[A](A,B):End:End

Other than being a waste of space without much practical use, the above code simply transverses through the 8x12 matrix [A] and gets the value to be stored in Ans. Yes, as I said, it is a do-nothing piece of code, but that's not the point of this exercise. The point is, how can we translate this pseudocode into assembly code? Before we continue, it is important to note that there is more than one way to do this: I'm just presenting what I think is logical to a beginner in assembly, not what may be the most uber-optimized form. With that said, let's break the pseudocode into the core component parts - primarily two nested For( loops and a matrix being addressed. See them? Ok, let's think on this, since that's what you're going to need to do also when you make your own programs.

Analyzing and Translating the Pseudocode

As we already determined, there are two For loops and one matrix. Let's now take everything step-by-step and discuss For loops, since there are two of them and they are on the outside of the matrix code, meaning it will most likely come first in the source code.

A For Loop

The parameters of a For loop in TI-BASIC consists of Var,Start,End,[Step], with [Step] being optional. That's pretty obvious if you've programmed some BASIC before. However, we could not write such a For loop in that format For(B,1,12,1) in assembly because that's a high-level way of thinking. We need to enter the thought-processes of the low-level CPU and think how a microprocessor thinks. The B,1,12,1 in the For are variable parameters, but assembly doesn't have that exactly; it has registers and memory. So how then do you translate your pseudocode into Z80 with these registers?

By breaking the pseudocode into pieces, you have some For loops. "Is there a way to emulate a For loop in assembly?" As it is, there is one such instruction, djnz (decrement and jump if not zero), which I found in Day 7 of 83pa28d. If you look up djnz in an instruction table (such as 83pa28d's) you'll see that this instruction will decrement register b and jump if the Zero flag was reset. In other words, this code:

someLabel:
	;code here
	djnz someLabel

is the same as the following:

someLabel:
	;code here
	dec b
	jr nz,someLabel

They both mean decrement register b and if b is not zero, then (relative) jump to someLabel. However, if you think carefully, you really don't want the loop to count down by decrementing register b (well you could, but just play along with me here ... ;)). That is, you don't want to start at 8 and count down 7, 6, 5, all the way to 1. You want to increment from 1, 2, all the way up until 8. If you check the instruction sets, you'll soon see that there is no one sole instruction to do this (nope, there is no 'ijc' instruction). What next? Well, here's where you use your brain and get a little creative. :) Here's one way to count up instead of down:

	ld c,0		;register c is zero
someLabel:
	;code here

	inc c		;increment c by one
	ld a,7		;register a is 7
	cp c		;compare register a with register c
	jr nz,someLabel	;if the "virtual subtraction" did not cause the z flag to be set, jump to someLabel

This code effectively represents a For(A,0,7,1):End. If you'll notice, I loop from 0 to 7, not from 1 to 8. This becomes important when you're dealing with memory, as everything is zero-base indexed. More on that when we get to the matrix part.

Nested For Loops

Now we have one loop, but what about two loops? If you'll notice, the pseudocode we're attempting to translate has nested loops. How do you nest one loop inside the other? "Well that's easy! Just do it like it is in BASIC and write one inside another, duh!". To illustrate what you just suggested:

	ld c,0		;outer loop
someLabel:

	ld c,0		;inner loop
someLabel:
	;code here

	inc c
	ld a,11		;changed to an 11
	cp c
	jr nz,someLabel

	inc c
	ld a,7
	cp c
	jr nz,someLabel

It looks like it works, right? "Hey, there's one For loop in another, and you did say that that was the code for a For loop..." No. If you still think that this will work as we intended, you have not digested the fact that you are still subconsciously bound by the "BASIC way" and not "adapting" to the CPU. For one, a register is not a variable ("variable" being an automatic variable in C terminology) - it is static until it is changed. Jumping to another piece of code will not magically preserve the values of the register. Everything and anything that you do with your code must be done explicitly - it is this aspect of assembly that explains why code written in pure Z80 code is almost always faster than any compiler or interpreter could ever hope to achieve ("C to Z80" or "BASIC", respectively).

Even if the above code doesn't work, it shouldn't be discarded - you should "figure out what's wrong and tackle it." What's going wacko here is that the registers aren't being preserved, so logically, it looks like we need some way to preserve registers! "Wow, thank you Mr. Obvious!" The best way would be to use something called a stack. I won't go too much in depth as it's been thoroughly discussed elsewhere (see your 83pa28d reference :)), but this analogy should get you started on stacks: Imagine that your two hands (a register pair) can only hold one really heavy lead bar (a 16-bit value/number). There is also one huge pile (a stack) of really heavy lead bars (values/numbers) that have been placed there previously by somebody else. You don't want to mess up the pile, since you need to leave things as they are when you're done. So therefore, if you want to save the lead bar that you're holding, you place it on top of the pile (push the register pair onto the top of the stack). When you're ready to lead-poison yourself again, you remove the top bar into your hands (pop the top value into a register pair). With this in mind, see if you can understand the changes I made to the nested loop code:

	ld c,0
outerLoop:		;For(A,1,8
	push bc		;save bc onto the stack (check opcode list - "push c" is not valid)

	ld c,0
innerLoop:		;For(B,1,12
	push bc		;notice how the push is after the label, not before
	;code here

	pop bc		;also notice that for every push, there is a pop
	inc c
	ld a,$0B	;just illustrating that $0B is the same as 11d
	cp c
	jr nz,innerLoop

	pop bc		;the value popped here is the outerLoop counter
	inc c
	ld a,$07	;seven
	cp c
	jr nz,outerLoop

This code should execute a 0...11 loop 8 times. Look at the strategic placement of the pushes and pops. Also note that you don't have to pop the same variable as whatever you pushed. In other words, "push af \ pop hl" is just as legal as "push af \ pop af". So there we have it - we just translated some nested For loop pseudocode into Z80 assembly by using a combination of references and brainpower. :)

Addressing a matrix

"We just finished the For loop piece of the pseudocode, now what about the matrix part?"

[A](A,B)

Ah, now that we finished one piece, we are ready to move on to another. Before I continue, you must realize that while we write this code, you must keep in mind that everything is in relation to everyting else - that is, this "matrix addressing" code must fit harmoniously with the For loop code.

If you have ever used xLIB (essentially a Codex-type program on an OD of steroids :P), you might have noticed that the pseudocode loop above is something that you could use for a tilemap in a game. In xLIB, the screen is represented by an 8x12 matrix of sprites. In BASIC, if we wanted to store 5 into the tilemap, we would execute 5->[A](A,B). Looks easy right? Well, that's only for BASIC. The point of all this is that in assembly, there is no hardware to distinguish memory into "rows" and "columns" - all data is essentially just a stream of bits and bytes, sort of like a 65,536 element list in BASIC (note that I say "sort of"). "But, but, then how do we make a matrix in assembly?" The answer is, you make one. ;) For this pseudocode example, you can (and will), set aside a total of 96 bytes (8*12=96... simple math) and force our program to "virtually" set every 12th byte a new row. "How do we do that?" Look at each of the four examples below and then realize that they are all identical when assembled:

TileMap_1:           ;8 rows of 12 zero bytes
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
TileMap_1:           ;96 bytes
	.db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
TileMap_1:           ;96 bytes
	.fill 96,0
TileMap_1:
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop	;12 bytes
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop

"So what if there are four ways to declare 96 zero bytes?"

Well, this means that you can use any and either one for 96 zero bytes. However, hopefully, you'll choose one that works and makes sense for what you're doing, and not something overly-superfluous. ;) I'll be using the the first one for the rest of the page as I found it more useful to visualize an 8x12 matrix rather than 96 linear bytes.

Here's the pseudocode we want to translate into Z80: [A](A,B). If we let [A] be TileMap_1, we have TileMap_1[A,B]. If we wanted to address TileMap_1[2,1], could we, therefore, do?:

	ld a,2
	ld b,1
	TileMap_1[a,b]

;... later on in the code...
TileMap_1:           ;8 rows of 12 zero bytes
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0

No, this is still too BASIC-y. We need to work on the byte level and not the BASIC level. "If we can't use the [row,column] format, then what can we use?" We use a "row" and "column" format. :P Notice how we "defined" a row to be 12 bytes. There wasn't necessarily anything to stop us from defining a 100 byte row or a 1 byte row. What makes a "row" then? Well, here enters the concept of the memory pointer. If we wanted to point to the "second row", we could do:

	ld hl,TileMap_1+12

Register hl would then point to the "first byte in the second row" (row 2, column 1) and we could use an indirection instruction like ld a,(hl) to get that value. If we wanted the third row, what would we add in place of 12? The answer is:

	ld hl,TileMap_1 + (2*12)

"Huh? Shouldn't that be a 3*12 and not a 2*12 if we wanted the third row? I mean, come on, we are talking about the third row, so that implies the number 3..."

In another programming language, it might, but here you must take into account zero-based indexing. Assembly is largely zero-based, rather than one-based, because if we wanted the first element in a list, all we would add is zero; second element, add one; and so on and so forth. It is much more efficient than subtracting one and adding the number, as is the case with one-based (go on and try one-based indexing if you don't believe/understand me).

"Ok, I know how to point to 'rows'; now how about pointing to 'columns'?"

Well, think it through. If we wanted the first byte of the second row (row 2, column 1), we would do ld hl,TileMap_1 + (1*12) , as we found above. But if we wanted the second byte of the first row (row 1, column 2), it would follow that we add one, that is:

	ld hl,TileMap_1 + ((0*12)+1)

With enough trials, I hope that you'll come to realize that there is a generic formula (zero-based indexing only) for calculating the memory pointer, namely:

	Base_Pointer+((Y*12)+X)

Now that we have a general formula for [A](A,B), it is time to actually implement it! Those still stuck in the "BASIC style" might be tempted to write this:

	ld a,2
	ld b,1
	ld hl,TileMap_1+((a*12)+b)

;... later on in the code...
TileMap_1:           ;8 rows of 12 zero bytes
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0

No. That's not how things roll in assembly. ;) If you were to assemble the above, TASM would interpret the above as a constant not a variable (in addition, TASM will complain about a and b being undefined). You don't want your registers to masquerade as your variables as much, since this is assembly, not BASIC. Therefore, we need to break apart the formula itself into even smaller pieces, sort of like subpieces. If you were to use the formula with pencil and paper, it would make sense that you would:

  1. Multiply the row by a constant 12
  2. Add the column to the (row*12) to get an offset
  3. Add that offset to address TileMap_1

"Hey now, I just looked in all the opcode references and there is no instruction to multiply! Nothing that looks even remotely close to a 'mult' instruction! Where do I look to find out how to multiply?"

Your references, obviously. :) The fastest way to multiply by a constant is detailed in Day 5 of 83pa28d. I will definitely be alluding to this page, so it would be in your best interest to read that day as you read the following code, while also keeping in mind how everything "relates" to everything else. This will probably be the hardest single piece of code that you will follow in this guide, because I am intentionally just "shoving it in your face" rather than explaining it to you step-by-step. Don't be afraid however! Be aware and be diligent and apply your newfound knowledge. :)

row .equ appBackUpScreen	;This is our temporary RAM variable to store our "row" value
col .equ row+1			;"column" value

;TileMap_1 points to our pre-defined tilemap
;we need to get the number in the tilemap:
;col = column X (should be from 0-11, zero-based indexed)
;row = row Y (should be from 0-7)

;TileMap_1+((Y*12)+(X))

	;*** 1.  Multiply the row by a constant 12

	ld hl,row
	B_CALL(_LdHLind)	;hl = value pointed to by row

	;hl * 12 = (hl * 4) + (hl * 8)  <-- read Day 5 on multiplying for why this works

	add hl,hl	;hl = hl * 2 (bit shift = multiply by power of two [again, read day 5])
	add hl,hl	;hl = hl * 4
	ld d,h
	ld e,l		;save hl * 4 result into register de for later
	add hl,hl	;hl = hl * 8
	add hl,de	;hl = (hl * 8) + (hl * 4)

	;*** 2.  Add the column to the (row*12) to get an offset

	;now we know Y*12, we need to add X (column)
	;hl has Y (row)

	ex de,hl		;swap de and hl to save row*12 from destruction
	ld hl,col
	B_CALL(_LdHLind)	;hl = (col)
	add hl,de		;(row*12)+col
	;now hl has the offset

	;*** 3.  Add that offset to address TileMap_1

	;offset means add/sub from something, in this case, we add to TileMap_1
	ld de,TileMap_1		;de has the ADDRESS of the byte at TileMap_1, NOT the number
	add hl,de		;now, hl points to the byte at [Y,X]

;... later on in the code...
TileMap_1:           ;8 rows of 12 zero bytes
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0

If you actually understood all that code, then you deserve a pat on the back! :) If you didn't, don't worry! Go back and read this code over and over until you do. Hopefully, there are enough comments so you understand why each line of code is necessary, because I won't try to explain it step-by-step for you any further. Read it until you understand, and when you do, give yourself a pat on the back. :) In case you really don't get it, keep in mind that the point of all that code above was simply to find TileMap_1+((Y*12)+(X)), our generic formula for [A](A,B), which is part of our pseudocode we are translating to Z80 assembly.

Piecing it together

Now, all that remains in order to translate our pseudocode to a program is to combine all the pieces! Here is a copy of the pseudocode in case you forgot it already:

For(A,1,8:For(B,1,12:[A](A,B):End:End

In this final product, note how there are parts (such as the For loop code) that need to "relate" and change in order for everything to work bug-free and harmoniously:

.nolist
#include "ti83plus.inc"
.list

row .equ appBackUpScreen	;This is our temporary RAM variable to store our "row" value
col .equ row+1			;"column" value

.org userMem-2
	.db t2ByteTok,tasmCmp

	;Start of program

	ld a,$00
	ld (row),a
outerLoop:			;For(A,1,8

	xor a			;an optimization to set a = 0
	ld (col),a
innerLoop:			;For(B,1,12

	call getThePointer	;calling a procedure/function/subprogram for [A](A,B)
	;now hl has the pointer to (row),(col) of TileMap_1

	ld a,(col)		;a = value pointed to by col
	inc a			;add one to a
	ld (col),a		;and save back into col
	ld b,$0B
	cp b			;compare a with b
	jr nz,innerLoop

	;Using indirection here to show how memory pointers are useful
	ld hl,row
	inc (hl)		;increment the value pointed to by row by one
	ld a,7
	cp (hl)			;compare 7 to the value pointed to by row
	jr nz,outerLoop

	ret			;quit the program

getThePointer:
;TileMap_1 points to our pre-defined tilemap
;we need to get the number in the tilemap:
;col = column X (should be from 0-11, zero-based indexed)
;row = row Y (should be from 0-7)

;TileMap_1+((Y*12)+(X))

	;*** 1.  Multiply the row by a constant 12

	ld hl,row
	B_CALL(_LdHLind)	;hl = value pointed to by row

	;hl * 12 = (hl * 4) + (hl * 8)  <-- read Day 5 on multiplying for why this works

	add hl,hl	;hl = hl * 2 (bit shift = multiply by power of two [again, read day 5])
	add hl,hl	;hl = hl * 4
	ld d,h
	ld e,l		;save hl * 4 result into register de for later
	add hl,hl	;hl = hl * 8
	add hl,de	;hl = (hl * 8) + (hl * 4)

	;*** 2.  Add the column to the (row*12) to get an offset

	;now we know Y*12, we need to add X (column)
	;hl has Y (row)

	ex de,hl		;swap de and hl to save row*12 from destruction
	ld hl,col
	B_CALL(_LdHLind)	;hl = (col)
	add hl,de		;(row*12)+col
	;now hl has the offset

	;*** 3.  Add that offset to address TileMap_1

	;offset means add/sub from something, in this case, we add to TileMap_1
	ld de,TileMap_1		;de has the ADDRESS of the byte at TileMap_1, NOT the number
	add hl,de		;now, hl points to the byte at [Y,X]
	ret			;and return back to the caller

TileMap_1:           ;8 rows of 12 zero bytes
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,0,0,0,0,0
.end

There's no need to test this program, because if you can follow the code and trace through it step-by-step, it should be fairly safe to say that the code could work without even running it on a calculator first to verify! At least, I hope it works - I haven't tested it at all. ;) This is also how programmers (e.g. on forums and during chat) can find out why a piece of code doesn't work in a few minutes of analysis without touching a(n emulated) calculator. If you've ever wondered how these people are able to do this, well, now you know! :)

Tieing the loose ends together

What we just went through - this process - is something that you'll find very handy when you're analyzing a piece of code, especially disassembled code where there are essentially no comments available at all. You need to be able to apply your prior knowledge of what you learned into the present happenings: "What's going on? What's the point of doing this? Is it working to do it this way? Are we all satisfied with the results?" et cetera, et cetera. I do hope that this way of thinking has helped you in some way or another, and that you'll also go on to make cool, fun, and/or useful programs (in assembly of course!). =D

Continue...

Up to Table of Contents