我的任务是在 8085 汇编语言中找到任何给定数字的绝对值。
该算法如下(在互联网上找到):
mask = n >> 7(数字本身是 8 位)
(掩码 + n) 异或掩码
我的问题是我将如何用汇编语言实现它。看来我应该使用“RRC”命令,但是对数字执行循环移位并且算法似乎不起作用。
任何想法,将不胜感激。干杯。
我的任务是在 8085 汇编语言中找到任何给定数字的绝对值。
该算法如下(在互联网上找到):
mask = n >> 7(数字本身是 8 位)
(掩码 + n) 异或掩码
我的问题是我将如何用汇编语言实现它。看来我应该使用“RRC”命令,但是对数字执行循环移位并且算法似乎不起作用。
任何想法,将不胜感激。干杯。
n>>7
在该算法abs
中是算术右移,它在符号位的副本中移动,所以你得到-1
负数 n,0
非负数。(在 2 的补码中,位模式的-1
所有位都已设置)。
然后你用它来做任何事(n+0) ^ 0
或做 2 的补码否定“手动”作为-n = (n + (-1)) ^ -1 = ~(n-1)
.
请参阅如何证明 C 语句 -x、~x+1 和 ~(x-1) 产生相同的结果?对于 2 的补码恒等式。与全一的异或是按位非。添加mask = -1
当然是n-1
分支很便宜,而且创建和使用0
或-1
(根据数字符号)所涉及的寄存器复制加起来。(虽然我确实想出了一种方法来实现这个只用 6 字节的代码,代码大小与分支版本相同。)
在 8085 上,只需简单地实现它:if(n<0) n=-n;
(将结果视为无符号;请注意,它-0x80 = 0x80
是 8 位的。如果您假设它在 abs 之后是有符号的,那么对于最负的输入,您将是错误的。)
对于否定的条件分支条件分支,这应该是微不足道的;8085 确实有依赖于符号位的分支。(一般不进行签名比较,除非您使用未记录的k
标志 = 签名溢出)。 根据 设置标志A
,然后JP
否定。(“加号”条件测试 Sign 标志 = 0,因此它实际上是在测试非负而不是严格正)
我在https://www.daenotes.com/electronics/digital-electronics/instruction-set-intel-8085中没有看到neg
说明,因此您可以将另一个寄存器归零,或者您可以使用2 的补码恒等式 ( NOT A ) ; (accumulator += 1) 而不是 mov 到另一个 reg 并从 A=0 中减去。sub
CMA
inr a
8085 具有廉价的分支,不像现代流水线 CPU,在分支错误预测时分支可能会很昂贵。无分支的mask = n >> 31
或等效的在abs
那里很有用,整个事情通常只有 3 或 4 条指令。(8085 仅具有移位 1 指令;包括现代 x86 在内的后来的 ISA 具有可以n >> 31
在单个指令中完成的快速立即移位,通常具有良好的延迟,例如 1 个周期。)
; total 6 bytes. (jumps are opcode + 16-bit absolute target address)
ana A ; set flags from A&A
jp non_negative ; jump if MSB was clear
cma
inr A ; A = ~A+1 = -A
non_negative:
; unsigned A = abs(signed A) at this point
http://pastraiser.com/cpu/i8085/i8085_opcodes.html有一个带有循环时间的操作码映射。1 字节 ALU 寄存器指令需要 4 个周期,2 字节 ALU reg 指令(带立即数)需要 7 个。条件分支需要 7 个周期未使用,10 个周期使用。
(时序计算似乎微不足道;每条指令只有一个固定成本,这与现代流水线超标量乱序 CPU 不同,后者的吞吐量和延迟是分开的,而且并非每条指令都可以在每个执行端口上运行……)
SBB A
根据 CF 设置 A = 0 或 -1这是一个众所周知的汇编技巧,用于将比较条件转换为 0 / -1 掩码。您只需要将值的 MSB 放入进位标志,例如使用 A+A 或旋转。这为您提供n >> 7
了 xor/add 所需的 0 : -1 值。
只是为了好玩,我尝试用这个技巧无分支地实现 abs()。这是我想出的最好的。 仅当您需要对时序攻击具有免疫力时才使用它,因此时钟周期成本不取决于输入数据。 (或者对于与位置无关的代码;跳转使用绝对目标地址,而不是 +- 相对偏移量。)
它的优点是将原件保存在另一个寄存器中。
;;; UNTESTED slower branchless abs
;; a = abs(b). destroys c (or pick any other tmp reg)
;; these are all 1-byte instructions (4 cycles each)
mov a, b
add a ; CF = sign bit
sbb a ; A = n-n-CF = -CF. 0 or -1
mov c, a
xra b ; n or ~n
sub a, c ; n-0 = n or ~n-(-1) = ~n+1 = -n
; uint8_t A = abs(int8_t B)
这仍然只有 6 个字节,与 branchy 相同,但它需要 6*4 = 24 个周期。
如果 XRA 不影响标志,我们可以sbi 0
执行该-1
步骤。但它确实总是清除 CF。我看不到保存 0 / -1 结果副本的方法。而且我们无法计算B
就地进行;8085是一种蓄能器机器。当您需要时,8086 的 1 字节交换与累加器在哪里?xchg a,b 会很有用。
如果您的值从 A 开始,则需要将其复制到其他地方,因此您需要销毁另外两个寄存器。
将 A 的符号位广播到所有位置的更糟糕的选择:
RLC ; low bit of accumulator = previous sign bit
CMA ; Bitwise NOT: 0 for negative, 1 for non-negative
ANI 1 ; isolate it, clearing higher bits
DCR A ; 0 or 1 -> -1 or 0
这甚至比rlc
/更糟糕sbb a
;我仅将它作为位操作的练习包含在内,以了解它为什么起作用。(而且因为我在记住我从其他 ISA 知道的 SBB 技巧也可以在这里工作之前已经输入了它。)