从最高位或高位开始提取寄存器中的位。

3

编辑:

我没有预料到这个问题会如此迅速地引起关注。根据我已经收到的答案,似乎我漏掉了一个重要的信息。该模式没有固定数量的位。有些字母可能具有更多或更少的位。例如,B有5位,但C可能使用多达6位,但没有任何一个超过一个字节。我在我的问题中包含了“A”位模式的示例,每行使用7位。请参见问题底部的编辑。

我是组合语言的新手。我有一个位模式,对应于字母的文本表示。每个1代表$(或任何符号),每个0代表空格。例如:

    $$$$            11110
    $   $           10001
    $   $           10001
    $$$$            11110
    $   $           10001
    $   $           10001
    $$$$            11110

   $            0001000  
  $ $           0010100
 $$$$$          0111110  
$     $         1000001

我编写了一个汇编程序,它可以读取每个模式并根据读取到的1或0打印正确的符号。为了确定它是1还是0,我将寄存器与1进行AND运算,然后将位向右移,位移量等于每行中的位数,然后比较结果:

请注意,每行的位都存储在单独的2字节单词的底部,我将其加载到8位寄存器中。

patternb:   dw 011110b,010001b,010001b,011110b,010001b,010001b,011110b

rowloop:
    mov bl,[patternb+si]    ;iterate through each element in binary array

    patternloop:    
        mov bh,bl   ;move bit pattern into register so that we can change it
        and bh,1    ;AND register to find set bits and store back in register
        shr bl,1    ;SHIFT original bit pettern right
        cmp bh,1    ;check if bit is set or not (1=set, else 0)
        je writesym ;if set, write symbol
        jne writeblank  ;if not set, write space

问题在于AND操作的方式。很明显,它从最低有效位(least significant bit)开始,并且随着右移而打印比特位,但这会导致字母“反向”打印的问题。例如:

 ####
#   #
#   #
 ####
#   #
#   #
 ####

我已经尝试了一些位操作,但都没有成功。我也尝试将位模式移动和旋转以对应正确的打印方式,但由于不是每行都需要这样操作,所以这种方法不适用于每行。例如,第二行将在无需先进行操作的情况下正确打印。我对字母A-E采用了相同的位模式技术。
理想情况下,我希望能从最高有效位开始进行比较,这样应该会按正确顺序打印,但我不确定如何操纵位以实现此目的。
编辑: 根据Peter Duniho的答案,我想分享一些我尝试过的内容: 我尝试将模式与10000b进行AND运算,然后ROL结果以得到00001b的答案,然后向左移动位。然后比较结果以查看应打印哪个符号。但由于位模式并不总是固定的,所以这也不是一个解决方案。
    mov bh,bl   ;move bit pattern into register so that we can change it
    and bh,10000b ;AND register to find set bits and store back in register
    rol bh,1    ;rol result to obtain 00001b
    shl bl,1    ;SHIFT original bit pettern right
    cmp bh,1    ;check if bit is set or not (1=set, else 0)
    je writesym ;if set, write symbol
    jne writeblank  ;if not set, write space

目前最接近解决这个问题的方法(在 Peter Duniho 的答案帮助下),是将我的位数组存储为完整的 8 位形式(即使用 011110000b 等,而不是 011110b,否则汇编器会隐式地将它存储为 00011101,正如 Martin Rosenau 所述,我们不想要这样),并用完整的 10000000b 进行 AND 操作(因为我们最多使用 8 位,这样可以检查 MSB),而不是像我之前尝试的那样用 1(000000001b)进行 AND 操作,然后使用上面的 ROL 和比较方法(或者只将其与 10000000b 进行比较)。循环总共运行了 7 次(因为每个字母都有 7 条线路/位模式,除了 A 只有 4 条,所以 A 打印不正确,但这是我可以通过一些条件来解决的问题)。该程序现在能够正确工作和打印。这是我使用的代码:

        mov bh,bl   ;move bit pattern into register so that we can change it
        and bh,10000000b   ;AND register to find set bits and store back in register
        rol bh,1    ;shift MSB to LSB to compare (or could just compare with 10000000b instead)
        shl bl,1    ;SHIFT original bit pettern left
        cmp bh,1    ;check if bit is set or not (or use cmp bh,10000000b and omit rol above)
        je writesym ;if set, write symbol
        jne writeblank  ;if not set, write space

我已将Peter的解决方案标记为答案,因为它指引了我解决这个问题的正确方向。但正如他所提到的,有许多种方法可以解决这个问题(正如发布的不同解决方案所示),而他的解决方案恰好是我实现自己代码中最容易的方法,这也是他的目的。
Martin Rosenau的回答也很有见地,尤其是优化方面。我会在有更多时间时尝试实现这些优化,并更新上述解决方案。

左移将位从顶部移出,进入CF。例如:add bl,bl / jc writesym,否则继续执行或跳转到writeblank。 - Peter Cordes
1
回复:编辑:如果位模式实际上是可变宽度的,那么您的代码如何知道位模式从哪里开始?看起来将每个块视为始终为8x7会更容易,即使前几列都是零。然后,您可以按照我在第一条评论中建议的方式轻松解码它,逐位将其移入CF。 - Peter Cordes
3个回答

3
理想情况下,我希望它从最高有效位开始比较,这样应该就能按正确的顺序打印出来。
听起来对我来说是个好主意。你尝试过类似的东西吗?如果是的话,具体尝试了什么?你遇到了什么具体困难?
在此期间...
是什么决定了要检查的位数(即循环计数)?它是固定的吗?如果是,为什么不直接与高端的位进行与操作(例如10000b,也称为16),然后左移而不是右移?
例如:
mov bh,bl     ;move bit pattern into register so that we can change it
and bh,10000b ;AND register to find set bits and store back in register
shl bl,1      ;SHIFT original bit pattern left
cmp bh,10000b ;check if bit is set or not (1=set, else 0)
je writesym   ;if set, write symbol
jne writeblank  ;if not set, write space

如果您在运行时不知道循环次数,可以每次迭代都进行移位操作:
mov bh,bl     ;move bit pattern into register so that we can change it
shr bh,cl     ;the assumption being that cl has the width of your bit pattern
dec cl        ;next bit
and bh,1      ;AND register to find set bits and store back in register
cmp bh,1      ;check if bit is set or not (1=set, else 0)
je writesym   ;if set, write symbol
jne writeblank  ;if not set, write space

如果您已经在使用CX进行循环,显然需要对上述内容进行一些修改。但是希望你能基本理解。
另一种变化是将AND位模式存储在另一个寄存器(例如al)中,通过存储1和左移适当的计数(例如shl al,cl),然后使用al作为操作数,而不是像上面第一个示例中使用10000b
这些远非您的唯一选择。如果想获得更具体的答案,您需要明确问题的限制。但是,假设这是一个学习ASM的练习,这是一个了解可用于您的位操作的绝佳机会。 :)

我只会写一个高效的例子,就像马丁的答案一样。没有必要列举所有的方法,甚至不需要尝试,但是只使用几个指令可以使示例更容易理解,更好地复制。我认为,能够遵循程序逻辑的初学者可以相当快地看到他们的版本在简单的方式下被过度复杂化。因此,我认为消除低效并不是理解的障碍。此外,那只是一个读者;其他人可能没有陷入他们的复制和销毁策略中。 - Peter Cordes
Stack Overflow的答案通常应该是良好的示例,以便未来的读者可以从中学习或复制/粘贴而不会自食其果。当容易且高效版本更短、更简单且更少时,我强烈反对最小化更改OP低效的做事方式的做法。 - Peter Cordes
@PeterCordes:你的意见我很欢迎。:) 我有个建议:你可以发表一个回答,详细地展示并解释你所说的优化方法,我会删除我的回答并点赞你的回答。这似乎比评论我的回答更有生产力。 - Peter Duniho
Martin已经发布了一个足够接近我所写的答案。他解释了哪些操作是必要的,并解释了test让您可以非破坏性地检查位,或者shladd same,same将位移入CF,您可以使用jcjnc测试它。优化自然而然地遵循这一点。 - Peter Cordes
感谢你们两位的建议。看到不同的方法很有趣。我已经更新了我的问题,并提供了更多的信息。Peter Duniho的答案恰好是解决我的问题比较容易理解的方法,但Peter Cordes在优化方面也提出了一个很好的观点,我也会尝试实现。请查看我的更新后的问题。再次感谢你们两位。 - user931018
显示剩余2条评论

3

问题在于AND的工作方式。

你的第一个问题在于右移操作的工作方式。右移操作会移除你的“图像”中最右边的“像素”,并将该像素左侧的像素移动到最右侧的位置(这样下一个被打印的像素就是该像素):

"$$$ $ $ " ->
" $$$ $ $" ->
"  $$$ $ " ->
"   $$$ $" ->
...

如果您想从左到右输出像素,就必须执行左移而不是右移。请注意,可以使用“shl bl,1”或“add bl,bl”进行左移。(注意:本文中的“bl”是一个字节寄存器名称)
由于您的“图像”仅有5个“像素”宽,但一个字节有8位,因此您必须决定是在图像的左侧还是右侧添加未使用的位。
例如:
"$$$ $" = 11101000 or 00011101 ?

假设你决定在左侧添加像素 (00011101 - 如果你将数字表示为 011101b,汇编器会默认执行此操作)。
然后,你需要执行一个与左侧像素最左边的位相对应的值进行AND操作。
Old:              New:

and bh,1          and bh, 010000b
shr bl,1          shl bl, 1

顺便说一下:您的程序有两个优化选项: 1)利用最左边的位不会丢失的事实:
patternloop:    
    shl bl,1
    test bl,0100000b
    je writesym

这种优化利用了一个事实,即字节中有3个空闲位,因此在左移时,字节的左侧位不会被"丢失":

"0 0 0<1>1 1 0 1"
  -> Left shift ->
"0 0<1>1 1 0 1 0"

"< >" = Bit you are interested in

指令test bl,xxx会影响ZF标志位(这会影响je指令),它的作用方式与两条指令 and bl,xxxcmp bl,0 的组合相同,但它不会修改bl寄存器!

2)利用右移操作把位移出到CF的事实:

patternloop:    
    shl bl,1
    jc writesym

这种优化假设 "$$$ $" 存储为 11101000 而不是 00011101。它利用了 shl 在位移之前会将最左边(最高位)的位复制到 CF 标志位上的事实(假定进行 1 位位移):
BL="<1>1 1 0 1 0 0 0", CF=?
   -> Left shift (using SHL or ADD) ->
BL="1 1 0 1 0 0 0 0", CF=<1>
jc指令将在CF标志被设置时执行跳转。

刚想发一个类似的帖子,使用 add / test。突然想到在循环之前使用 shl bl, 3 将感兴趣的位移到顶部并设置为 add bl,bl / jc 循环,从而避免了 test bl, 1<<5 的使用。 - Peter Cordes
shl将在进行移位之前将最左(最高)位复制到CF标志:这仅适用于移位1位,对于shl bl,2可能会产生困惑。我认为按照英特尔的描述方式更容易理解,即CF获取最后一个移出的位。(或者当然可以使用add bl,bl使其成为正常加法的进位,并且更有效率,因为它可以与现代CPU上的jc宏融合。) - Peter Cordes
@MartinRosenau: 我认为他指的是你写010000h(十六进制,而不是二进制)的那一行。但由于NASM支持表达式,我认为在源代码中编写test bl, 1<<5以写入位位置要容易得多,而不是让读者计算。或者在移位后写1<<(5+1) - Peter Cordes
1
@PeterDuniho 是的。这应该是一个“b”,而不是“h”。我已经更正了。 - Martin Rosenau

0

您可以使用操作码ROL,将位向MSB旋转一位,而MSB将被旋转到LSB的位置,例如:

11110000 -> ROL 1 -> 11100001

这样,您就可以做一些像这样的事情:

ROL1 -> 测试LSB -> ROL1 -> 测试LSB -> ....

在您的情况下,bl是一个8位寄存器,循环ROL,测试8次以绘制ASCII艺术。


这在给定的示例数据中不起作用,即当只有5个有效位时。 - Peter Duniho
你正在帮助一个显然对位操作和汇编编程都不太有经验的人。所以你应该确保你的回答不会遗漏任何细节,例如“忽略前三个 ROL”。这对那些无法自然推断出这一点的人来说非常重要。 - Peter Duniho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接