outportb 和 8086 汇编中的 out 指令之间是不是存在显着差异?

Posted

技术标签:

【中文标题】outportb 和 8086 汇编中的 out 指令之间是不是存在显着差异?【英文标题】:Is there a significant difference between outportb and the out instruction in 8086 assembly?outportb 和 8086 汇编中的 out 指令之间是否存在显着差异? 【发布时间】:2019-06-02 15:53:54 【问题描述】:

我正在使用 1994 年的旧 DOS 进行编码,以获得游戏编程的乐趣,并且正在将 RAD 文件中的数据输出到 OPL3 FM 合成芯片中。我正在使用 DOSBox。

为了在处理硬件寄存器时提高性能(特别是因为我正在通过定时中断(以保持速度)更新 FM Synth 芯片并希望尽快回到主代码中)我想重写我的寄存器在汇编中输出。

但是,虽然这可行,但它似乎会在合成器芯片的输出中引入失真,而当我使用纯 C 的 outportb 时不存在这种失真,我想我会在这里询问是否有人能告诉我发生了什么。 'out dx,al' 和 'outp(int, char)' 之间是否存在我遗漏的特殊细微差别?

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

当使用我的 C 例程时,音乐可以像在 RAD Tracker 中一样正确输出。但是当我使用我的装配程序时,一些乐器似乎有一种奇怪的邋遢的声音。我真的不能责怪 Dosbox 仿真,因为就像我说的,它与纯 C 一起工作得很好。差异不是特别大,但它非常明显。我唯一的猜测是,outportb 的实现做了一些我的例程中没有的魔法。

【问题讨论】:

汇编代码似乎做了一些额外的事情?那些循环会减慢例行程序吗?还是您破坏了调用者的cx 寄存器?将您的代码与 C 例程的反汇编进行比较可能会有所帮助。 【参考方案1】:

C 和汇编程序有一个主要区别:

我不知道你的 C 编译器的库,但 outp() 或者等效于 out dx,axout dx,al

(我猜它相当于out dx,al。)

但是,在您的汇编程序中,您有时将 outp() 替换为 out dx,ax,有时替换为 out dx,al。两者之一一定是错的。

如果一个真正的声卡有一个 8 位 ISA 连接器并且outp() 等同于out dx,al,下面的汇编代码:

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

...取自您的汇编程序等于以下 C 程序:

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

尝试将out dx,ax 替换为out dx,al

(很遗憾,我不知道 DOSbox 是模拟 8 位还是 16 位连接器的声卡。)

请注意

看完我之前写的,你可能会尝试只用一条out指令同时写寄存器和值:

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

这很可能适用于带有 8 位连接器的真实声卡。

但是,这可能不适用于具有 16 位连接器的真实声卡(也可能不适用于 DOSbox 之类的模拟器)。

【讨论】:

我尝试将 AH 设置为 0,将 AL 设置为 regval 并发送 AX,但不幸的是没有区别,但这是个好主意。此外,不能将数字作为 BYTE 发送(可能是 >BYTE)并且它们位于不同的地址。 @Razoric480 如果模拟 8 位卡,将 AH 设置为 0 并使用 out dx,ax 就像将值 0 写入声卡寄存器一样。你不想那样做!你肯定必须使用out dx,al 而不是out dx,ax 事实证明你是对的。使用 C 例程查看了编译器生成的程序集(感谢 Margaret Bloom 的建议),它仅输出带有 mov bx,regnum mov al,bl out dx,al 的寄存器号的低字节即使寄存器可能比一个字节大。现在我的音乐又好听了~

以上是关于outportb 和 8086 汇编中的 out 指令之间是不是存在显着差异?的主要内容,如果未能解决你的问题,请参考以下文章

汇编 8086 中的 SHR 命令

8086汇编 Loop 指令

8086汇编 Loop 指令

8086汇编语言学习 8086汇编开发环境搭建和Debug模式介绍

8086汇编之 CALL 和 RET指令

汇编中的移位指令(8086CPU)