Search code examples
cassemblydos

Is there a significant difference between outportb and the out instruction in 8086 assembly?


I am coding in old, 1994 era DOS for game programming fun, and am outputting data from a RAD file into the OPL3 FM Synthesis chip. I'm running off of DOSBox.

In a bid to increase performance when dealing with hardware registers (especially since I am updating the FM Synth chip through a timing interrupt (to keep tempo) and want to get back into main code as quickly as possible) I thought to rewrite my register outputting in assembly.

However, while this works, it seems to introduce distortion in the synth chip's output that isn't present when I use pure C's outportb, and I figured I'd ask here to see if anyone could tell me what's going on. Is there a particular, subtle difference between 'out dx,al' and 'outp(int, char)' that I'm missing?

OPLStatus status = NONE;
unsigned int primAd;
unsigned int secAd;
unsigned int backAd;

void OPLWriteImpl(void *argument, unsigned int registerNumber, unsigned char registerValue) {
    (void)argument; /* Unused variable */
    if(status == OPL3 || status == DUALOPL2) {
        if(registerNumber <= REGISTER_THRESHOLD) {
            outp(primAd, registerNumber);
            outp(primAd+1, registerValue);
        }
        else {
            outp(secAd, registerNumber);
            outp(secAd+1, registerValue);
        }
    }
    else {
        outp(backAd, registerNumber);
        outp(backAd+1, registerValue);
    }
}
EXTRN status:WORD, primAd:WORD, backAd:WORD

.CODE

PUBLIC OPLWriteImpl
OPLWriteImpl PROC FAR C argument:DWORD, regnum:WORD, regval:BYTE

cmp status,0        ; if status == 0, go to OPL3
je three
cmp status,2        ; if status == DUAL, also go to OPL3
jne two             ; otherwise, go to OPL2

three:
mov dx,primAd       ; put OPL3 primary bank in dx
cmp regnum,100h     ; check if regnum <=than threshold (256)
jle prim            ; if so, go straight to primary bank
    inc dx          ; otherwise, writing to secondary bank (primary+2)
    inc dx
prim:
    mov ax,regnum   ; put the register number in ax
    out dx,ax       ; output to register
    inc dx          ; go to data address
    mov al,regval   ; put the value in al
    out dx,al       ; output to data
    jmp done        ; finished
two:
    mov dx,backAd
    mov ax,regnum
    out dx,ax
    mov cx,6
loopOne:
    in al,dx
    loop loopOne

    inc dx
    mov al,regval
    out dx,al
    dec dx
    mov cx,36
loopTwo:
    in al,dx
    loop loopTwo
done:
ret             ; return to C
OPLWriteImpl ENDP

When using my C routine, the music outputs correctly as it was in RAD Tracker. But when I use my assembly routine, some of the instruments seem to have a strange grungy sound. I can't really blame Dosbox emulation because, like I said, it works just fine with pure C. The difference isn't super egregious, but it's more than noticeable. My only guess is that outportb's implementation does some magic that isn't present in my routine.


Solution

  • There is one main difference in the C and the assembler program:

    I don't know your C compiler's library, but outp() is either equivalent to out dx,ax or to out dx,al.

    (I would guess that it is equivalent to out dx,al.)

    However, in your assembler program you are sometimes replacing outp() by out dx,ax and sometimes by out dx,al. One of both must be wrong.

    If a real sound card has an 8-bit ISA connector and outp() is equivalent to out dx,al, the following assembler code:

    mov ax,regnum
    out dx,ax
    inc dx
    mov al,regval
    out dx,al
    

    ... taken from your assembler program equals the following C program:

    outportb(address, regnum);
    outportb(address + 1, regnum >> 8); // This may cause the noise
    outportb(address + 1, regval);
    

    Try to replace out dx,ax by out dx,al.

    (Unfortunately I don't know if DOSbox simulates a sound card with an 8-bit or a 16-bit connector.)

    Please note

    After reading what I have written before, you might might try to use only one out instruction to write register and value at the same time:

    mov al,regnum
    mov ah,regval
    out dx,ax
    

    With a high probability this would work with a real sound card with an 8-bit connector.

    However, this might not work on real sound cards with a 16-bit connector (and maybe also not on a simulator like DOSbox).