M/C Part 21 PROGRAMMING STYLE!!! (Yeahhhh!) For the last 20 odd articles I've discussed various ideas you might find useful in your own coding. By now, all you beginners are probably writing your own games and stuff. Now its time for your code to be structured, readable, and generally different class. (A bit like me really) Look at SAM BASIC. Providing you don't use GOTOs and GOSUBs, you can create good bits of code with it, via procedures, functions and control structures like FOR...NEXT and DO...LOOP. Why is this a good thing? Firstly - modulisation. If you can split a program into lots of little bits which act independantly of one another, you can write each of these bits seperately. Testing is eased because you can test each module as you write it. This testing is nomally done with a "test driver". If your module isn't totally independant, but instead calls other modules, these can be replaced by "test stubs". TEST DRIVERS: This is a bit of code which pumps test data into the routine and outputs the results for you to check. TEST STUBS: These are little bits of code which, in terms of input and output, look like the routines they represent, but which don't actually do all (or any) of the processing. Other advantages of modulisation include: avoiding having to replicate code (and thus having to test it twice), and readability (only one set of documentation, and, providing you give procedures sensible names, you can tell at a glance what something is supposed to do). Secondly, readability. With these structures, understanding the program is a lot simpler - no jumping around following GOTOs. This is important when someone else needs to look at it. It also assists debugging. So, now you're convinced that good structures are important and that high-level languages like SAM BASIC, Pascal etc. are designed to make this easy. Bit of a shame about assembly then, isn't it. Or is it? Let's be honest, all of us already use "procedures" or subroutines in our code. But we can get lazy, in large programs internal documentation becomes a drag, and it's sometimes easier not to bother about conflicts between these subroutines. And as for control structures - we already use them, but it's not always clear how they work and all too often we stick conditional JPs (compare to GOTOs) in the middle of such loops. Not a good move. We know why we use assembly - because of its speed etc. And we take it as a consequence of using it that good programming habits are discarded. Well, (finally!) that's what this month's article is about - structuring your assembly. MACRO COMMANDS ============== I kinda touched upon this last month. Sometimes, there are commands which you wished existed, but don't. Occasionally, they only take a few lines, but stuck in the middle of a routine their meaning can get lost, and if you attach documentation every time, your code suddenly gets very long indeed. So what we do is create a whole host of such macros. You should bundle them all together in your assembly file, but little documentation is needed - they're all self-explanatory. The rules are simple - preserve all registers except the ones output is required from. Don't invent commands that don't already exist in one language or another. And don't make them too complicated (You would be better to specify these as actual procedures). Here are some examples: 1. Adding A with a 16-bit register. add_hl_a PUSH BC LD C,A LD B,0 ADD HL,BC POP BC RET add_de_a EX DE,HL CALL add_hl_a EX DE,HL RET add_bc_a CALL ex_bc_hl CALL add_hl_a CALL ex_bc_hl RET add_ix_a CALL ex_ix_hl CALL add_hl_a CALL ex_ix_hl RET You should be able to see a pattern. You'll need a few more routines, though. 2. Exchanging registers. ex_bc_hl PUSH BC PUSH HL POP BC POP HL RET ex_ix_hl PUSH IX PUSH HL POP IX POP HL RET ex_bc_de PUSH BC PUSH DE POP BC POP DE RET I think you get the general idea. It's important to keep the syntax of the "command" the same as that of any similar, existing command, like EX DE,HL and CALL ex_bc_hl. Flag results should be the same too, like in add_hl_a. Some of the more exotic macros substitute for high-level commands which don't exist in Z80 assembly. 3. Multiplication mult_a_b PUSH BC LD C,A SUB A INC B DEC B CALL NZ,axb_loop POP BC RET axb_loop ADD A,C DJNZ axb_loop RET mult_hl_bc PUSH AF PUSH BC PUSH DE LD E,L LD D,H CP A SBC HL,HL LD A,B OR C CALL NZ,hlxbc_loop POP DE POP BC POP AF RET hlxbc_loop ADD HL,DE DEC BC LD A,B OR C JR NZ,hlxbc_loop 4. Division div_a_b PUSH BC LD C,#FF a/b_loop INC C SUB B JR NC,a/b_loop LD A,C POP BC RET div_hl_de PUSH BC LD BC,#FFFF hl/de_loop INC BC CP A SBC HL,DE JR NC,hl/de_loop LD L,C LD H,B POP BC RET Okay. Now, although these routines might look a bit unnecessary the big improvement is that you instantly know, by looking at the code, what is happening. For example, what looks better? . . . SBC HL,BC PUSH HL PUSH BC POP HL POP BC . . . or . . . SBC HL,BC CALL ex_hl_bc . . . Exactly. In addition, you only need to test the macro once, so you can be assured calls to it work, and debugging is quickened. Finally, here are a couple of handy little macros which I use all the time. Normally, programs are run in lower ram, with screen, data, extra mem etc. paged into sections C and D. Occasionally (eg. to make a call to the ROM) you need to change the program to upper RAM, and then back again. These small routines will disable interrupts, switch pages, fix the stack and will return to the new code. Everything's preserved. Jump_up also turns off ROM1. jump_up DI EX (SP),HL SET 7,H EX (SP),HL PUSH AF PUSH HL IN A,(lmpr) AND %10111111 OUT (lmpr),A AND %00011111 OUT (hmpr),A LD HL,#8000 ADD HL,SP LD SP,HL POP HL POP AF RET jump_down DI EX (SP),HL RES 7,H EX (SP),HL PUSH AF PUSH HL IN A,(hmpr) AND %00011111 OR %00100000 OUT (lmpr),A LD HL,#8000 ADD HL,SP LD SP,HL POP HL POP AF RET Use them in your code like: . . CALL jump_up RST #28 DB ..... CALL jump_down+#8000 EI . . In this case, to use the FPC. One thing to remember - get your assembler to adjust the ORG address by #8000 each time, or follow all address references by "+#8000", or by "+up" after defining "up" as #8000. Right, what's next? CLOSED SUBROUTINES ================== How many times do you use a bit of code like this . . LD HL,table LD B,len PUSH HL CALL sort POP HL . . because the subroutine might corrupt HL? It's called LAZINESS!! For every subroutine you write, make a list of entry requirements, output requirements and corrupted registers. Stick this information together with a small note detailing what the routine does and what variables (if any) it requires in documentation at the start of the routine like this ; SORT ROUTINE ; ------------ ; Sorts a table into order specified by direction_flag. ; Entry: HL = table address ; B = length of table ; Exit: --- ; Corrupted: AF, BC, D ; Vars: direction_flag, swap_flag, sort_count, , sort_max_count If you can, make sure the routine corrupts as few registers as possible. If an inputing reg doesn't carry documented output, preserve it too. Preferably make it completely closed. I find it useful to group all the variables together at the end of the file. (In SC_Assembler you can put them in their own bank). Also, try not to share these variables between just a few routines. Either make them GLOBAL (important enough to be used by all the routines) or LOCAL (only used by one). If a routine needs data, pass it in with a register. And if it provides a result, pass it back out with a register. In this way the "Vars" line above can be expanded to: ; Global: direction_flag ; Local: swap_flag, sort_count, sort_max_count However, the above example should be improved by generalising it more, using A to pass in the direction, instead of a global var. Little things like putting comments like ; ------------------------------------------------------------- between subroutines also helps.