我有一项任务,我应该计算我设置的二进制数中奇数为1的数字1,然后我需要在7段显示器上显示该数字。
在代码上,我写了一个注释。
我正在使用Texas Instruments msp430。我看了另一种解决方案,但他们不是用汇编语言用C编写的,不幸的是无法弄清楚如何在汇编语言上进行处理。
bis.b #11111111b, &P1DIR
bic.b #11111111b, &P1OUT
loop_1:
; do stuff with &P1OUT
call #delay
...
delay
mov #0, R5
mov #0, R4
odd_even:
;Over here i need to count number of 1's in binary but cant figure out how to do it
jnz try
jz delay_over
...
ret
答案 0 :(得分:0)
在大多数计算机上,只有两条说明中没有硬件可以做到这一点。
您要做的是设置一组遮罩和移位:
unsigned char to_count, nbr=0, mask=0x1, m;
for (int i=0; i<8; i++) {
m = to_count&mask ; //1 if LSB=1, 0 otherwise
nbr += m;
to_count >>=1 ;
}
对于更多的位,您可以采用更智能的策略来统计地减少计算时间,但是对于8位,您将没有增益。
答案 1 :(得分:0)
有些算法对于8位以上的比特来说更好。 @rcgldr的答案是16或32位popcount的有用起点。有关某些bithack和其他算法(包括表查找)的信息,请参见How to count the number of set bits in a 32-bit integer?。
您可以考虑使用4位查找表。 MSP430的移位很慢(每位1个周期,如果没有MSP430X,则每位1条指令)。或使用较大的8位查找表。
或在设置的位上循环,用v &= v - 1;
清除低位。在采用MOV,DEC和AND的MSP430中。如果通常只设置几个位,但是它们经常分散,那就太好了。
但是最简单,最小的代码大小方法是一次一次循环所有位。
如果要一次循环一次以使其简单而紧凑,则希望通过移入进位并使用ADDC(随身携带)来利用进位标志。 / strong>
我试图编写C语言,使编译器可以使用ADDC转换为漂亮的asm,但是https://godbolt.org/z/2Ev2IC是我管理的最好的代码。 GCC和clang对于MSP430而言,它们对于x86和大多数其他体系结构都认可的tmp = a+a; carry = tmp<a;
惯用法并不是很好。
因此,无论如何,您首先要使用asm:
;; simple naive bit-count. Small code-size and not too slow for 8 bits
;; input in r12, result: r11 = popcount(r12)
mov.w #0, r11 ; retval = 0
.popcount_loop: ; do{
add.b r12,r12 ; shift a bit into carry flag
addc #0, r11 ; add that bit to r11: r11 += 0 + C
tst.b r12
jnz .popcount_loop ; } while( (uint8_t)r12 != 0);
为add
使用字节操作数大小意味着第7位进入C,而不是第15位。
我们可以改用右移将低位放入C标志,特别是如果我们希望许多输入为小数(因此非零位都朝向低位)结束)。根据{{3}}谷歌发现,普通MSP430没有右移,只有进位向右旋转。 RRC[.W]
/ RRC.B
。 MSP430X具有一些“旋转”,实际上会移位为零,因此它们实际上是移位。但是,如果在运行C之前确保C = 0,则不需要。由于不会计算总数,因此ADDC会可靠地为我们清除C。
我们可以通过让JNZ和ADDC都使用来自同一ADD的标志来优化此程序,以减少循环内 的指令(代码大小相同,但运行速度更快)。由于ADDC也写入标志,这意味着它必须在下一次迭代中。因此,我们必须倾斜循环。我们可以剥离第一个迭代,并在循环外添加它。此后我们将不会检查零,但这很好。为input = 0x80
运行一个额外的迭代不是一个正确性问题,也不值得在上面花费额外的指令。
; simple looping popcount, optimized for small numbers (right shift)
; and optimized for fewer instructions inside the loop
;; input in r12, result: r11 = popcount(r12)
xor.w r11, r11 ; r11=0, C=!Z=0. (mov doesn't set flags; this saves a CLRC)
rrc.b r12 ; C = lsb(r12); r12 >>= 1 ; prep for first iter
.popcount_loop: ; do{
addc #0, r11 ; result += C; Clears C because r11 won't wrap
rrc.b r12 ; C = lsb(r12); r12 >>= 1; Z = (r12==0)
jnz .popcount_loop ; } while( (uint8_t)r12 != 0);
addc #0, r11 ; we left the loop with the last bit still in C
如果输入值是零扩展的,则可以使用rrc.w r12
,以便循环适用于8位或16位值。但这并不慢,因为在将所有位右移后仍会退出。
使循环倾斜并剥离第一个迭代的前半部分和最后一个迭代的后半部分,只会使我们总共多花一条指令。 (而且它们仍然都是单字指令。)
您提到的是奇/偶。 您真的只想要平价吗? (人口总数是奇数还是偶数)?这与所有位的水平XOR相同。
; Needs MSP430X for rrum, otherwise you can only shift by 1 bit per instruction
;; input in r12, result: r12=parity(r12)
;; clobbers: r11
mov.b r12, r11 ; copy the low byte, zero the upper byte of R11 (not that it matters)
rrum #4, r11 ; costs 4 cycles for shift-count = 4
xor r11, r12 ; low 4 bits ^= (high 4 bits >> 4)
mov.b r12, r11
rrum #2, r11 ; costs 2 cycles for shift-count = 2
xor r11, r12 ; narrow again to 2 bits
mov.b r12, r11
rrum #1, r11 ; costs 1 cycle for shift-count = 1.
xor r11, r12 ; narrow again to 2 bits
and #1, r12 ; clear high garbage from the high bits.
; ret if this isn't inline
您可以循环执行此操作,例如使用popcount循环并在最后执行and #1, r12
。
我觉得,如果我们向左移动(先移4再乘2)并用add.b r12,r12
做最后一步(移1),则可以保存指令,因为有符号溢出(V标志)= {{ 3}}。如果两个输入的加法相同,则现有符号位将始终为0 + 0 = 00或1 + 1 = 10,因此符号位=带符号的进位。
因此,使用r12.b = XY??????
这样的位模式, add.b r12,r12
设置V = X^Y
,即输入的高两位的水平XOR 。因为Y
是MSB的输入,X是输出。
如果您想在其上分支,这会很好,但是MSP430似乎没有设置在jXX
上分支的V
。它有JL
和JGE
在(N XOR V)
上分支(即有符号比较),但是N
等于MSB,所以N ^ V
只是C,左移V设置V = N ^ C
之后。我想您必须从标志寄存器中取出标志字并对其进行移位/屏蔽!或测试该标志位和JNZ。
答案 2 :(得分:-1)
此逻辑可能比循环略短:
unsigned char popcnt(unsigned char a)
{
a = a - ((a >> 1) & 0x55); // 2 bit fields 0 -> 2
a = (a & 0x33) + ((a >> 2) & 0x33); // 4 bit fields 0 -> 4
a = (a & 0x0f) + (a >> 4); // a = bit count
return a;
}