Skip to content

retrocomputing

I miei computer: cronologia hardware e audio (1988–2012)

Ho sistemato dei vecchi appunti, ed ho raccolto la successione delle macchine che mi hanno accompagnato nel tempo, tra floppy disk, megahertz ed audio digitale, dalle prime Sound Blaster fino alle interfacce da studio.

1988 — IBM PS/2 Modello 30

  • CPU: Intel 8086 (8 MHz)
  • RAM: 640 KB
  • Disco: Hard Disk 20 MB
  • Supporti: Floppy drive 3.5" da 720 KB (DD)
  • Grafica: MCGA (Multi-Color Graphics Array: il precursore del VGA, capace di 320x200 a 256 colori)
  • Monitor: IBM monocromatico da 12" (B/N)
  • Audio: PC Speaker interno (cicalino a 1 bit)
  • Software: MS-DOS 3.30, Windows 2.11 e Word 1.0

1992 — Il clone AMD 386/40

  • CPU: AMD 386DX 40 MHz
  • RAM: 4 MB
  • Disco: HD 120 MB
  • Supporti: Floppy 1.44 MB
  • Grafica: OAK VGA
  • Software: DOS 5.0, Windows 3.1
  • Audio: DAC R-2R artigianale su porta parallela (il leggendario "Covox" autocostruito con le resistenze: 8 bit mono per far suonare i MOD a 4 canali con ModPlay, ModEdit + ModRes ed infine con FastTracker II)

1996 — L'era Pentium e l'audio a 16-bit

  • CPU: Intel Pentium 150 MHz
  • RAM: 32 MB, portata a 64 MB a fine 1998 (upgrade da Jolly Computer, per gestire i banchi SoundFont con Vienna e le tracce audio su Cakewalk Pro Audio senza dropout)
  • Ottica: CD-ROM Asus 40x (aprile 1999, da Silicon Valley a Padova)
  • OS: Windows 95 OSR2
  • Audio: Creative Sound Blaster AWE64
  • Software Audio & MIDI:
  • Sequencing & DAW: Cakewalk Pro Audio e il bundle Creative (Voyetra MIDI Orchestrator Plus)
  • SoundFont: Creative Vienna SoundFont Studio
  • Tracker: FastTracker II in ambiente DOS

2000 — Il cambio di millennio con AMD

  • Data acquisto: 13 dicembre 2000
  • CPU: AMD Athlon "Thunderbird" 800 MHz (Socket A)
  • RAM: 128 MB SDRAM PC133 (o 256 MB, da verificare)
  • Monitor: CRT 17"
  • Audio: Creative Sound Blaster Live! (chip EMU10K1, l'era dell'EAX e del riverbero ambientale hardware)
  • OS: Windows 98 SE / Windows 2000
  • Note: Insieme al PC arrivò la scatola di Fuga da Monkey Island (Monkey Island 4), fresco di uscita a novembre 2000: il primo capitolo in 3D, che sul vecchio Pentium 150 sarebbe stato impensabile far girare!

2004 — Il salto ai 64 bit

  • Data acquisto: 12 novembre 2004
  • CPU: AMD Athlon 64 3200+ (Socket 754, core ClawHammer)
  • RAM: 1 GB
  • Monitor: CRT 17" riciclato, successivamente un LCD
  • Disco: HD 160 GB
  • Audio: Creative Sound Blaster Audigy2 Platinum (con il pannello frontale da 5.25" pieno di ingressi jack, ottici e MIDI)

2012 — Dell XPS 8300 e l'home studio USB

  • CPU: Intel Core i7-2600 (Sandy Bridge, 3.40 GHz, 8 MB cache)
  • RAM: 8 GB DDR3 (4x2 GB) a 1333 MHz
  • Disco: HD 1 TB SATA 7200 rpm, successivamente SSD512GB
  • Monitor: LCD 17" riciclato, successivamente LCD 27"
  • Grafica: NVIDIA GeForce GT 530 (1 GB)
  • OS: Windows 7 Home Premium SP1 (64 bit)
  • Audio: Roland Quad-Capture (il salto di qualità: stop alle schede audio PCI interne, ma senza gestione SF2 nativa)

MODRES: reverse engineering di un TSR audio negli anni '90

MODRES era la libreria audio di MODEDIT, un tracker per MOD molto diffuso in ambiente DOS. Non aveva documentazione pubblica, l'ho quindi ricostruita a suo tempo con il Turbo Debugger ed inviata a Ralf Brown, ed oggi nel 2026 tali voci sono ancora presenti nella Interrupt List.

Cos'è MODRES

MODRES era un programma per DOS di tipo TSR (Terminate and Stay Resident) che permetteva di riprodurre in background i moduli musicali in formato MOD quattro tracce dei tracker Amiga.

Il suo uso principale era all'interno di MODEDIT: MODRES era la libreria audio che permetteva al tracker di ascoltare i moduli ed i campioni mentre li si editava, e di continuare a suonarli anche quando il tracker era in background.

Supportava:

  • PC speaker
  • D/A converter su porte parallele LPT1-LPT4
  • Sound Blaster (porta 02x0h)
  • Disney Sound Source
  • Configurazioni stereo su due porte parallele
  • Stereo-on-1

Esponeva le sue funzioni attraverso l'interrupt INT 2F, con AX=8220h come base.

Il reverse engineering

MODRES non aveva documentazione pubblica. Tramite il Turbo Debugger della Borland ne ho seguito l'esecuzione istruzione per istruzione, mettendo breakpoint ed esaminando registri e memoria. Con molta pazienza, ho ricostruito le sue logiche interne di funzionamento.

Le funzioni che avevo documentato sono sette. Le strutture dati due: MODPARM e SAMPARM, più una tabella di output device e una dei pitch.

La documentazione

MODRES - PLAY MODULE

AX = 8220h
DX:CX -> MODPARM structure (see #2646)

Return:
AX = status
  5722h successful
  2000h parameters out of range
  other MODRES not installed

See Also: AX=8221h - AX=8223h - AX=8225h - AX=8227h - AX=8200h"RESPLAY"

Format of MODPARM Structure (Table 2646)

Offset  Size    Description
00h     WORD    signature 504Dh ("MP" = Modparm)
02h     BYTE    output device (see #2648 at INT 2F/AX=8221h)
03h     WORD    segment of start of main module (pattern) data
05h  31 WORDs   segment of start of sample numbers 1-31
43h     BYTE    pattern at which to start playing (00h to 7Fh)
44h     BYTE    function
                00h play from pattern [offset 43h] until end of the song
                01h play indicated pattern [offset 43h] only
45h     BYTE    Machine speed
                00h 10-12Mhz
                01h 12-25Mhz (default)
                02h 25Mhz+
                03h mix speed 10kHz (fast 8Mhz machines)
                04h mix speed 12kHz (10Mhz machines)
                05h mix speed 13kHz
                06h mix speed 8kHz (test for 8Mhz machines)
46h     BYTE    allow >64k sample playing
                80h MOD has samples >64k in it
                else all samples in MOD are <64k

Notes: Main module data and all samples must start on segment
boundaries. In version 2.00 (ONLY) this function carries on
playing (works in the background).

See Also: #2647

MODRES - INSTALLATION CHECK

AX = 8221h

Return:
AX = status
  5722h successful
  other MODRES not installed
BX = BCD version number (BH = major, BL = minor)
DX:CX -> Output Device structure (read-only) (see #2647)

See Also: AX=8220h - AX=8222h - AX=8225h - AX=8227h

Format of Output Device structure [array] (Table 2647)

Offset  Size    Description
00h 20 BYTEs   ASCIZ name of the output device
               (end of list if first char is FFh)
14h    WORD    apparently always FFFFh
16h    WORD    0000h if output device not available
               else first I/O port for the output device
18h    WORD    second I/O port for the output device (for example
               if it is stereo)
               0000h if only one port used or device is not available
1Ah  7 BYTEs   ???

See Also: #2646 - #2648

Values for MODRES v1.52 output device index (Table 2648)

00h    PC speaker
01h    D/A Converter on LPT1
02h    D/A Converter on LPT2
03h    D/A Converter on LPT3
04h    D/A Converter on LPT4
05h    D/A Converter on LPT1&LPT2 (stereo)
06h    D/A Converter on LPT1&LPT2 (mono)
07h    Sound Blaster (port 02x0h)
08h    User Defined D/A (mono)
09h    User Defined D/A (stereo)
0Ah    Stereo-on-1
0Bh    Disney SS su LPT1
0Ch    Disney SS su LPT2
0Dh    Disney SS su LPT3
0Eh    Disney SS su LPT4

Note: This list may vary between versions of MODRES

MODRES - UNINSTALL

AX = 8222h

Return:
AX = code segment of the program

Note: This function does not release the TSRs memory; the caller
must do so

See Also: AX=8220h - AX=8221h - AX=8223h

MODRES - PLAY SAMPLE

AX = 8223h
DX:CX -> SAMPARM structure (see #2649)

Return:
AX = status
  5722h successful
  2000h parameters out of range
  other MODRES not installed

See Also: AX=8221h - AX=8224h - AX=8225h - AX=8226h

Format of SAMPARM Structure (Table 2649)

Offset  Size    Description
00h     WORD    signature 5053h ("SP" = SAMPARM)
02h     WORD    segment of start of sample to play
04h     WORD    length of sample (IN WORD)
06h     BYTE    output device (see #2648 at INT 2F/AX=8221h)
07h     WORD    pitch to play (see #2650)
09h     BYTE    volume (from 00h to 40h)
0Ah     WORD    loop start
0Ch     WORD    loop length
0Eh     BYTE    machine speed (see INT 2F/AX=8220h)

See Also: #2646

Values for Pitch to play (Table 2650)

C 0 is 06B0h
C#0 is 06B0h / 2^(1/12)
D 0 is (06B0h / 2^(1/12)) / 2^(1/12)
...

Note: C 1 is 06B0h / 2. C 2 is 06B0h / 4. Etc.

See Also: #2649

MODRES - ???

AX = 8224h
DX:CX -> ???

Return:
???

See Also: AX=8221h - AX=8223h - AX=8224h

MODRES v2.00+ - GET LOCATION IN MOD

AX = 8225h

Return:
AL = status
  00h playing
  01h reached end or stopped
AH = speed of MOD
BX = position within pattern 0000h-0400h
CL = position within the song (track number)

See Also: AX=8220h - AX=8221h - AX=8223h - AX=8226h

MODRES v2.00+ - STOP PLAYING

AX = 8226h

Return:
AX = status
  5722h successful
  other MODRES not installed

Desc: Stops playing the MOD file before performing critical
operations such as disk accesses

See Also: AX=8220h - AX=8221h - AX=8223h - AX=8225h - AX=8227h

MODRES - CONFIGURE

AX = 8227h
BX = function
  0001h set default playing speed (06h)
  0002h select output device
    CL = output device (see #2648 at INT 2F/AX=8221h)

Return:
AX = status
  5722h successful
  2000h parameters out of range
  other MODRES not installed

Note: Function 0001h should be called every time a new module
is loaded

See Also: AX=8220h - AX=8221h - AX=8222h - AX=8223h

Note sulla documentazione

Alcune cose che vale la pena notare, rileggendola oggi:

  • La MODPARM inizia con una signature (504Dh = "MP"), come quasi tutte le strutture dati di quei tempi. Serviva a verificare che il puntatore passato alla funzione fosse davvero una struttura valida.
  • Il campo machine speed non è un valore in MHz, ma un indice che il TSR usa per scegliere la frequenza di mixaggio. La voce 06h è "test for 8MHz machines": il programma si adattava alla macchina su cui girava.
  • La tabella dei pitch parte da C 0 = 06B0h e calcola le note successive come divisioni per 2^(1/12). È il temperamento equabile applicato ai registri del timer. Quasi certamente però il programma utilizzava una tabella precompilata per evitare di calcolare i valori a runtime, che però non sono riuscito a recuperare.
  • Non sono riuscito a capire il significato della funzione AX=8224h che è rimasta non documentata.

Dove è finita

La documentazione è nella Ralf Brown's Interrupt List, alla voce INT 2F/AX=8220h e seguenti. È ancora consultabile su ctyme.com/rbrown.htm.

Il riconoscimento

Nella sezione CREDITS della Release 55 (28 settembre 1997) della Ralf Brown's Interrupt List, c'è questa riga:

12/96 A Federico Thiella fthiella@stud32.math.unipd.it  MODRES

Dicembre 1996. Non ho più quella casella di posta ovviamente!

Fun Facts about the 8088

Note: A classic compilation of x86 low-level optimization tricks originally attributed to Microsoft engineer Chris Peters in the 1980s. Preserved here as a historical document on the craft of shaving off single bytes and clock cycles on the Intel 8088 prefetch queue and bus architecture.

1. Comparing a register

The fastest and smallest way to compare a 16 bit register to zero is to OR it with itself, e.g.

OR      BX,BX           ; 2 bytes, 3 clocks
JGE     BXisPositive

this is much better than comparing it with zero, e.g.

CMP     BX,0    ; 3 bytes, 4 clocks (bush league)

For the ultimate in comparing with zero, try to use the CX register. The 8088 contains the single instruction:

JCXZ    CXisZero        ; jump if CX is zero

This instruction makes a short jump if CX is zero.

To destructivly test for 1 or -1, use the DEC or INC instructions:

DEC     DX              ; 1 byte, 3 clocks
JZ      DXisOne         ; if zero, DX was 1

or

INC     DX              ; 1 byte, 3 clocks
JZ      DXisMinusOne    ; if zero, DX was -1

The LOOP instruction is just a fancy way of writing:

DEC     CX
JNZ     CXisNotZero

The difference is that LOOP is 1 byte smaller and 2 clocks faster. The LOOP instruction can be used to compare CX with multiple values as follows:

LOOP    CXisNotOne       ; If CX = 1 then...
...                      ; ...do this code, else...

CXisNotOne: LOOP CXisNotTwo ; if CX = 2 then... ... ; ...do this code, else...

CXisNotTwo: LOOP CXisNotThree ; if CX = 3 then... ... ; ...do this code, else etc.

Its possible to check if a signed number is in the range 0-n with a single comparison to n:

CMP     DX,639          ; if <0 or >639...
JA      OutOfRange      ; ...its out of range

This is smaller and faster than:

OR      DX,DX           ; Never CMP DX,0!
JL      OutOfRange      ; If negative, its out of range
CMP     DX,639
JG      OutOfRange      ; if greater, its out of range

This can be generalized to any signed compare within a specific range. If you wanted to make sure the AX register contained a number from -5 to 17:

SUB     AX,-5           ; Subtract off lower bound
CMP     AX,17-(-5)      ; Compare upper - lower bound
JA      OutOfRange

You cannot compare a segment register. To do so copy it to a register or memory location, then compare it.

2. Setting a register to zero

To set a register to zero, the smallest, fastest way is to XOR it with itself, e.g.

    XOR     BX,BX           ; 2 bytes, 2 clocks

is smaller and faster than:

    MOV     DX,0            ; 3 bytes, 4 clocks

There is one side affect: MOVing does not affect the flags, but XORing does. The 8088 aficianado will only move a zero into a register in the rare cases where the flags must be preserved.

3. Incrementing, Decrementing

It is smaller to increment or decrement a 16 bit register then an 8 bit register, so if it doesnt matter, use a 16 bit register, e.g.:

    INC     DX              ; 1 byte, 3 clocks

is smaller than:

    INC     DL              ; 2 bytes, 3 clocks

Same thing goes for decrementing, its smaller to use a 16 bit register.

Its smaller (but not faster) to increment a register twice then to add 2 to it, e.g.

INC     DX              ; 1 byte,  3 clocks (total)
INC     DX              ; 2 bytes, 6 clocks

is smaller and slower than:

ADD     DX,2            ; 3 bytes, 4 clocks

One side affect: INC and DEC do not affect the carry flag, ADD does.

4. If Then Else

When confronted with an If, Then, Else problem the assembly language programmer will often write it as Else, If, Then. For example, a sample problem might be to return 123H in the DX register if AL<5, otherwise return zero in DX. Using If, Then, Else produces:

 CMP     AL,5                ; Is AL less than 5?
 JB      ALisBelow5          ; yes...
 XOR     DX,DX               ; Set DX to zero if AL>=5
 JMP     SHORT Continue      ; proceed

ALisBelow5: MOV DX,123H ; Set DX to 80H if AL<5 Continue:

Using Else, If, Then:

 MOV     DX,123H             ; Set DX to 80H if AL<5
 CMP     AL,5                ; Is AL less than 5?
 JB      ALisBelow5          ; yes...
 XOR     DX,DX               ; Set DX to zero if AL>=5

ALisBelow5:

The idea is to do the work for the most likely case, then do the comparision. If you were right you're all done, if not then do the other case.

5. Copying strings

One of the simplest ways to copy a null terminated string is as follows:

; ; DS:SI points at source ; ES:DI points at destination ; CopyString: LODSB ; read character into AL, inc SI STOSB ; store charcter, increment DI OR AL,AL ; was it the null JNZ CopyString ; no, repeat

A fast way to copy a string where the count is known is

SHR     CX,1    ; divide count by two (2 bytes = 1 word)
REP     MOVSW   ; move the first part fast
JNC     Even    ; No carry if count was even
MOVSB           ; move the odd byte

Even:

Slightly faster but slightly bigger is:

SHR     CX,1    ; Get the word count
REP     MOVSW   ; move the words, leaving CX = 0
RCL     CX,1    ; if count was odd make CX = 1 
REP     MOVSB   ; (possibly) move last byte

This is slightly faster because there is no jump to empty the prefetch queue.

The REP instruction checks to see if the loop count in CX is zero before starting, there is no need to check it beforehand.

6. Testing long pointer for null

Sometimes its necessary to check if a long pointer is null before using it. A sequence of code that works well is:

 LES     DI,[LongPointer]        ; get the long pointer
 MOV     CX,ES                   ; copy ES to CX
 JCXZ    PointerIsNull           ; if zero, dont use it

This assumes segment zero is an invalid pointer.

7. Exchanging

To move a register to or from AX takes 2 bytes and 2 clocks

MOV     AX,DX           ; 2 bytes, 2 clocks

However, to exchange a register with the AX register takes 1 byte and 3 clocks:

XCHG    AX,DX           ; 1 byte, 3 clocks

A clear case where its possible to optimize for size or speed. This optimization is only available with the AX register. A full understanding is actually more complex. Although XCHG takes 3 clocks it is often faster because it only uses one byte of the prefetch queue.

8. Testing bits

The 8088 contains an instruction that allows you to test various bits. It works by doing a non destructive AND operation.

TEST    AX,0800H        ; 4 bytes, 5 clocks
JZ      BitIsOff

If one of the bytes is zero (a common occurence) this can be optimized to:

TEST    AH,08H          ; 3 bytes, 4 clocks
JZ      BitIsOff

The best way to test the hi bit is:

OR      AX,AX           ; 2 bytes, 2 clocks
JNS     HiBitIsOff      ; jump not signed (hi bit zero)

A destructive way to test the low order bit is to shift it to the right into the carry flag:

SHR     AX,1            ; 2 bytes, 2 clocks
JNC     LoBitIsOff

You can test multiple bits with the TEST instruction:

TEST    BL,11000000b    ; check 2 highest bits
JZ      BothAreZero     ; if zero, both are zero

Sometimes you want to jump if either are zero instead of both zero, this can usually be accomplished by the NOT instruction and reversing the sense of the jump instruction:

NOT     BL              ; reverse the bits   
TEST    BL,11000000b    ; check 2 highest bits
JNZ     EitherAreZero   ; if either were zero, then this 
                        ; is non zero

9. Absolute value

A fascinating way to get the absolute value of the AX register was discovered by Marlin Eller:

CWD             ; replicate hi order bit of AX into DX
XOR     AX,DX   ; do a 1's complement or do nothing
SUB     AX,DX   ; add 1 to get a 2's complement

The boring method does not affect the DX register and can be used on any register:

OR      BX,BX   ; never CMP BX,0!
JGE     NotNeg  ; if negative...
NEG     BX      ; ...make it positive

NotNeg:

The boring method empties the prefetch queue with the JGE instruction, making it much slower.

10. Length of null terminated string

To get the length of a null terminated string, scan for the null at the end with a starting count of -1

; ; ES:DI points at null terminated string ; XOR AL,AL ; look for null 2 bytes (total) MOV CX,-1 ; CX = -1 5 bytes REPNE SCASB ; CX = -len-2 7 bytes NOT CX ; CX = len+1 9 bytes DEC CX ; CX = len 10 bytes

This count does not include the null at the end. If you want it to include the null, just delete the final DEC CX. The use of the NOT instruction is quite interesting here.

11. Returning flags

The 8088 contains instructions for setting (STC) and clearing (CLC) the carry flag. To set the zero flag, simply compare some register with itself:

CMP     DX,DX            ; set zero flag

To clear the zero flag, OR the stack pointer with itself:

OR      SP,SP           ; clear zero flag

This is making the safe assumption that the stack pointer is not zero.

12. Shifting

Variable count shifting is slow on the 8088. Its faster to shift twice then to set a count of 2:

SHR     AX,1    ; 2 bytes, 2 clocks  (total)
SHR     AX,1    ; 4 bytes, 4 clocks

is much faster than:

MOV     CL,2    ; 2 bytes,  4 clocks  (total)
SHR     AX,CL   ; 4 bytes, 20 clocks!

Variable count shifting is slower when shifting less than 5 bits, after that the prefetch queue makes variable shift counts faster.

13. Multiply and Divide

The multiply and divide instruction are some of the slowest instructions on the 8088. To give some perspective, a register to register MOV instruction takes 2 clocks, while a signed divide (IDIV) using registers can take 184 clocks.

If your goal is to write fast 8088 code, multiplying by constants can usually be done as a series of shifts and adds:

;
;  Multiply the AX register by 10
;
SHL     AX,1            ; AX = AX *  2    ( 2 clocks)
MOV     BX,AX           ; BX = AX *  2    ( 4 clocks)
SHL     AX,1            ; AX = AX *  4    ( 6 clocks)
SHL     AX,1            ; AX = AX *  8    ( 8 clocks)
ADD     AX,BX           ; AX = AX * 10    (11 clocks)

A multiply would be more than 10 times slower, but would take fewer bytes. Multiply is useful when neither argument is constant or you need to save bytes.

Mark Zbikowski uses this method to divide the AX register by 512:

SHR     AX,1         ; divide AX by 2
XCHG    AL,AH        ; divide AX by 512 (AX is unsigned)
CBW                  ; AL < 128, so this sets AH to 0

14. Converting bytes to segments

To convert a byte count to a paragraph count try:

;
;  DX contains a byte count
;
ADD     DX,15       ; round up to next paragraph
MOV     CL,4        ; 2^4 = 16 bytes per paragraph
SHR     DX,CL       ; divide by 16 by shifting 4 times

DX now contains a paragraph count. This assumes the value in DX is less than 0FFF1H. To cover the extended case:

ADD  DX,15        ; round up to next paragraph
RCR  DX,1         ; divide by 2, including carry 
MOV  CL,3         ; 2^3 = 8
SHR  DX,CL        ; divide by a total of 16

15. Call, Return, Jump

A near call followed by a near return can always be replaced with a near jump:

JMP NearProc  ; 3 bytes, 15 clocks

is smaller and much faster than:

CALL  NearProc  ; 3 bytes, 19 clocks (total)
RET     ; 4 bytes, 35 clocks

Its often possible to eliminate the JMP entirely by moving the subroutines adjacent to each other.

Conditional jumps on the 8088 are always short, i.e. the destination must be within -128 to 127 of the instruction pointer. It seems every time a single line of new code is added some conditional jump becomes out of range. One technique to get around this is to find a similiar conditional jump to jump to:

JC      OutOfRange     ; I want to jump to disk error...
...                    ; ...but its too far away, so...

OutOfRange:

JC      DiskError      ; I jump to this test for carry

Although this is not in the scope of this document, out of range jumps are usually the 8088 telling you that your subroutines have grown too large and should be broken up.

16. Multiple Entry Points

An old 8080 trick involving multiple entry points can be adapted to the 8088. Instead of doing this:

Entry1:
    MOV     AL,1                    ; 2 bytes (total)
    JMP     SHORT EntryCommon       ; 4 bytes
Entry2: 
    MOV     AL,2                    ; 6 bytes
    JMP     SHORT EntryCommon       ; 8 bytes
Entry3:
    MOV     AL,3                    ;10 bytes 
EntryCommon:

The hearty and brave will do this:

Entry1:
    MOV     AL,1                    ; 2 bytes (total)
    DB      03DH                    ; 3 bytes 
Entry2:
    MOV     AL,2                    ; 5 bytes
    DB      03DH                    ; 6 bytes
Entry3:
    MOV     AL,3                    ; 8 bytes
EntryCommon:                        ; flags are modified

The DB 03DH is the opcode for a CMP AX,xx. In this case the bogus CMP AX's are used to swallow up the MOV AL,x that follow.

A special case of this discovered by Pat Tharp optimizes for dual entry points:

TrueEntry:      ; come here to set AX = TRUE
  DB  0B8H      ; (opcode for MOV AX,...)
FalseEntry:     ; come here to set AX = FALSE
  XOR     AX,AX   ; AX = 0 = FALSE

Whats happening here is that the 0B8H is the opcode for MOV AX,### which will put the opcode for the XOR AX,AX (nonzero C031H) in AX. This is a clear win of 3 bytes over the boring method:

TrueEntry:
  MOV     AL,1    ; set AX nonzero
  JMP     SHORT EntryCommon
FalseEntry:
  XOR     AX,AX   ; set AX zero
EntryCommon:

17. Assertion Macros

When using these advanced techniques its important not to expose yourself to bugs caused by changing constants in your program. For instance, in the Multiply and Divide section of this document there is a code to quickly multiply by ten. If the constant should later change from ten to twelve this code would no longer work. An assertion macro would flag this code as being in error, saving many hours of needless debugging. In cannot be stressed to strongly that advanced 8088 programming requires liberal use of assertion macros and extra documentation.