用 8085 微处理器汇编语言求一个数的绝对值
Finding the absolute value of a number in 8085 microprocessor assembly language
我的任务是用 8085 汇编语言求任意给定数字的绝对值。
算法如下(网上找的):
mask = n >> 7(数字本身是8位)
(掩码 + n) 异或掩码
我的问题是如何用汇编语言实现它。
似乎我应该使用 "RRC" 命令,但它对数字执行循环移位并且算法似乎不起作用。
如有任何想法,我们将不胜感激。
干杯。
abs
算法中的 n>>7
是一个 算术右移 ,它在符号位的副本中移动,所以你得到 -1
为负 n,0
为非负。 (在 2 的补码中,-1
的位模式已设置所有位)。
然后你用它来做任何事情 (n+0) ^ 0
或者做 2 的补码取反 "manually" 作为 -n = (n + (-1)) ^ -1 = ~(n-1)
.
有关 2 的补码恒等式,请参阅 How to prove that the C statement -x, ~x+1, and ~(x-1) yield the same results?。与全 1 的 XOR 是按位非。添加mask = -1
当然是n-1
分支很便宜,创建和使用 0
或 -1
(根据数字的符号)所涉及的寄存器复制加起来。 (虽然我确实想出了一个只用 6 字节代码实现它的方法,代码大小与分支版本相同。)
在8085上,简单实现一下:if(n<0) n=-n;
(将结果视为无符号;请注意 8 位中的 -0x80 = 0x80
。如果您假设它在 abs 之后是有符号正数,那么对于最负数输入来说您将是错误的。)
这应该是微不足道的,条件分支条件分支否定; 8085 确实有依赖于符号位的分支。 (虽然一般不进行签名比较,除非您使用未记录的 k
标志 = 签名溢出)。 根据 A
设置标志,然后 JP
取反 。 ("Plus" 条件测试 Sign flag = 0,所以它实际上是在测试非负而不是严格的正)
我在 https://www.daenotes.com/electronics/digital-electronics/instruction-set-intel-8085 so you you could zero another register and sub
, or you could negate the accumulator in place with a 2's complement identity like CMA
(NOT A) 中没有看到 neg
指令; inr a
(累加器 += 1) 而不是移动到另一个 reg 并从 A=0 中减去。
8085 有便宜的分支,不像现代流水线 CPU,后者的分支预测错误可能代价高昂。 mask = n >> 31
或无分支 abs
的等价物在那里很有用,整个事情通常只有 3 或 4 条指令。 (8085 只有移位 1 指令;包括现代 x86 在内的后来的 ISA 具有快速立即移位,可以在一条指令中完成 n >> 31
,通常具有良好的延迟,如 1 个周期。)
未测试 A = abs(A)
; 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 个周期。
- 对于 non_negative 输入(采用):周期成本为 4 (ANA) + 10 (JP) = 14 个周期
- 对于负输入(未采用):4(ANA) + 7(JP) + 4 + 4 = 19 个周期。
(时序计算似乎微不足道;每条指令只有一个固定成本,这与现代流水线超标量乱序 CPUs 不同,其中吞吐量和延迟是分开的,并非每条指令都可以 运行 在每个执行端口...)
在 8085 上实现 branchless bithack
SBB A
根据CF
设置A=0或-1
这是一个有点知名的汇编技巧,用于将比较条件转换为 0 / -1 掩码。您只需要将您的价值的 MSB 放入进位标志中,例如与 A+A 或旋转。这为您提供了 n >> 7
0 : -1 所需的值 xor/add.
为了好玩,我尝试用这个技巧无分支地实现 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 技巧在这里也适用之前就已经输入了它。)
我的任务是用 8085 汇编语言求任意给定数字的绝对值。
算法如下(网上找的):
mask = n >> 7(数字本身是8位)
(掩码 + n) 异或掩码
我的问题是如何用汇编语言实现它。 似乎我应该使用 "RRC" 命令,但它对数字执行循环移位并且算法似乎不起作用。
如有任何想法,我们将不胜感激。 干杯。
abs
算法中的 n>>7
是一个 算术右移 ,它在符号位的副本中移动,所以你得到 -1
为负 n,0
为非负。 (在 2 的补码中,-1
的位模式已设置所有位)。
然后你用它来做任何事情 (n+0) ^ 0
或者做 2 的补码取反 "manually" 作为 -n = (n + (-1)) ^ -1 = ~(n-1)
.
有关 2 的补码恒等式,请参阅 How to prove that the C statement -x, ~x+1, and ~(x-1) yield the same results?。与全 1 的 XOR 是按位非。添加mask = -1
当然是n-1
分支很便宜,创建和使用 0
或 -1
(根据数字的符号)所涉及的寄存器复制加起来。 (虽然我确实想出了一个只用 6 字节代码实现它的方法,代码大小与分支版本相同。)
在8085上,简单实现一下:if(n<0) n=-n;
(将结果视为无符号;请注意 8 位中的 -0x80 = 0x80
。如果您假设它在 abs 之后是有符号正数,那么对于最负数输入来说您将是错误的。)
这应该是微不足道的,条件分支条件分支否定; 8085 确实有依赖于符号位的分支。 (虽然一般不进行签名比较,除非您使用未记录的 k
标志 = 签名溢出)。 根据 A
设置标志,然后 JP
取反 。 ("Plus" 条件测试 Sign flag = 0,所以它实际上是在测试非负而不是严格的正)
我在 https://www.daenotes.com/electronics/digital-electronics/instruction-set-intel-8085 so you you could zero another register and sub
, or you could negate the accumulator in place with a 2's complement identity like CMA
(NOT A) 中没有看到 neg
指令; inr a
(累加器 += 1) 而不是移动到另一个 reg 并从 A=0 中减去。
8085 有便宜的分支,不像现代流水线 CPU,后者的分支预测错误可能代价高昂。 mask = n >> 31
或无分支 abs
的等价物在那里很有用,整个事情通常只有 3 或 4 条指令。 (8085 只有移位 1 指令;包括现代 x86 在内的后来的 ISA 具有快速立即移位,可以在一条指令中完成 n >> 31
,通常具有良好的延迟,如 1 个周期。)
未测试 A = abs(A)
; 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 个周期。
- 对于 non_negative 输入(采用):周期成本为 4 (ANA) + 10 (JP) = 14 个周期
- 对于负输入(未采用):4(ANA) + 7(JP) + 4 + 4 = 19 个周期。
(时序计算似乎微不足道;每条指令只有一个固定成本,这与现代流水线超标量乱序 CPUs 不同,其中吞吐量和延迟是分开的,并非每条指令都可以 运行 在每个执行端口...)
在 8085 上实现 branchless bithack
SBB A
根据CF
设置A=0或-1
这是一个有点知名的汇编技巧,用于将比较条件转换为 0 / -1 掩码。您只需要将您的价值的 MSB 放入进位标志中,例如与 A+A 或旋转。这为您提供了 n >> 7
0 : -1 所需的值 xor/add.
为了好玩,我尝试用这个技巧无分支地实现 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 技巧在这里也适用之前就已经输入了它。)