I’ve written previously about the importance of a style guide for written content. The same is true for code. Arguably, it’s more important because code is much harder to understand. When it comes to code style, you’ll never find two developers who agree on everything. So my advice would be to get the input of everyone who touches the code and come to a consensus on what the style should be. And remember that it’s a guide, not a rigid set of rules.
Because I spent nearly 15 years working as a technical writer, most of the coding I’ve done in my life has been for my own amusement. And that means I’ve been free to do what I like. But in that time I’ve seen some bad habits that I’ve tried to avoid. In particular, I’ve dealt a lot with Z80 assembly language source code. Most of it is horrible. My gripes include:
- Single monolithic assembly files.
- Written in CAPITALS.
- Needless decoration on comments.
- Too few comments.
- Meaningless labels
- Full of magic numbers
- Pointless gaps between instructions and parameters
- Enough whitespace to fill the Albert Hall
- No API docs.
- Numbers written as hex that should be written as decimal.
- Bitwise instructions written as hex that should be written as binary.
- Undocumented pseudo-op codes.
My own code style is essentially a reaction against this. I was also influenced by a document published by Nikos Drakos and Ken Clowes at the Computer Based Learning Unit of the University of Leeds, and the fact that I used to write SDK documentation for C++ developers. I’ll now attempt to formally document it. At some point in the future, I should extend it. Where information is missing, I defer to the C++ Core Guidelines.
This document describes coding standards for Z80 assembly language programs. The following plug-ins are recommended for Visual Studio Code:
Assuming you’re using something more modern than Z80, I also recommend these plug-ins:
API calls should use a vector table so that relocation of the target code won’t break existing applications. For example:
org $04c4; ;; ; open a file for appending if it exists ; @param IX - pointer to ASCIIZ file path ; @throws sets carry flag on error ;; SEFileAppend: jp v_open_w_append; // $00
When comparing ASCII characters, use the character, not the code point. For example:
cp '('; // opening parenthesis?
In Z80, the semicolon ( ; ) denotes that a comment follows. However, it is most commonly used as a line terminator. Modern integrated development environments are set up to expect this. Therefore, every line should end with a semicolon. Comments should be preceded with a double slash ( // ) for visibility. Standalone comments should be written as:
; // this is a standalone comment
Inline comments should start at the tenth tab stop (column 40), for example:
di; // interrupts off
Comments should be as short as possible to convey what the code is doing. Where an instruction can only have one meaning, it’s not necessary to comment the code.
Use capitals for register names in comments. For example:
ld d, e; // E to D
So far as possible, data should be stored separately from code.
Use a definitions file to declare:
- index register offsets
If you’re using a large range of index registers, for example as a set of variables, define the offset with the name of the variable and a leading underscore. For example:
Lines should be no more than 80 characters in length for display on a standard terminal. In-line comments should occur at the 40 character point so that they will appear on the next line on a 40-column display.
Use macros sparingly. Different assemblers use different syntax, and converting them can be time-consuming. They work best for pseudo op codes.
Define meaningful names for constants and variables. Avoid magic numbers.
Use decimal wherever possible.
Always use binary for bitwise operations (
XOR). For example,
Write hexadecimal with lower case letters and a leading dollar sign. For example,
Pseudo op codes
Never use pseudo op codes. Support is inconsistent between assemblers. If you must, define a pseudo-op code as a macro.
Use the asmdoc Perl script to generate API docs for public routines.
;; ; Short description. ; @author Name of contributing author. ; @deprecated Version deprecated in and replacement module. ; @param HL - Register contents. ; @return Result in register, such as <code>HL</code>. ; @see <a href="www.example.com">External reference</a>. ; @since Version in which module was introduced. ; @throws Error number and description handled by RST 8 routine. ; @version Version in which the module was last udpated. ;;
A routine is somewhat analogous to a function in C. A public routine is preceded by an asmdoc definition. Routine and subroutine names are terminated by a colon. Instructions in the routine or subroutine are indented by one tab. For example:
joystick: in a, (stick); // read joystick ld (jstate), a; // store in system variable ret; // end of subroutine
Self-modifying code should be avoided with two exceptions:
- When it is critical for speed of operation.
- When it is critical for code compactness.
Break source code into sets of related modules and include those modules in the main assembly file. Modules should start with a special comment. For example:
;; ; // --- ARITHMETIC ROUTINES ------------------------------------------------- ;; :
User lowercase wherever possible. Use camelCase in preference to underscores ( _ ). Reserve CAPITALS for substitutions in comments. For example:
xor a; // LD A, 0
Uses tabs (one tab is four characters). This speeds up search and compile times.
Image: Detail from Rodnay Zaks’ “Programming the Z80” (Sybex)