所以我正在研究nand2tetris项目,我想在软件层面实现shift right逻辑,因为硬件不支持它。 我知道右移逻辑是两个除法。因此,我实现它的第一个镜头将计算在值变为0或负值之前我能够从初始值中减去2的次数。类似的,如果数字是负数。 但是,我发现了一个不起作用的场景。我想换右-27139。那么移位后的二进制值是19199.它应该是19198.因此我正在寻找一种新的方法来实现这种转变。 我可以和价值观或价值观相加,减去,这就是我所拥有的一切。
感谢您的帮助。
OSFTW
以下是我在Hack实现的汇编语言中的代码:
//============================================================
// SLR: Shift Logical Right (by 1)
//============================================================
(SLR)
@SLR.1 // Load value
D=M
@SLR.2
M=0 // Clear variables
@SLR_POSITIVE_LOOP // If value is positive, go to the positive loop
D;JGT
(SLR_NEGATIVE_LOOP)
@SLR.1 // Add 2 to value, since it's negative
M=M+1
M=M+1
@SLR.1 // If initial value was negative and current value is positive or zero, jump out of loop
D=M
@SLR_NEG
D; JGE
@SLR.2 // If value is negative, add 1 to SLR.2 (we're dividing)
M=M+1
@SLR.1 // If value is less than 0, restart the loop
D=M
@SLR_NEGATIVE_LOOP
D; JLT
(SLR_NEG)
@SLR.2
D=M
D=!D // Invert the result
D=D+1 // Add 1 to finish converting
@32767 // And it with 0111111111111111 to clear the sign bit
D=D&A
@SLR.2
M=D // Set value
@SLR_END // Jump to end of loop
0;JMP
(SLR_POSITIVE_LOOP)
@SLR.1 // Subtract 2 from value
M=M-1
M=M-1
@SLR.1 // If initial value was positive and current value is negative or zero, jump out of loop
D=M
@SLR_END
D; JLE
@SLR.2 // If value is still positive, add 1 to SLR.2 (we're dividing)
M=M+1
@SLR.1 // If value is greater than 0, restart the loop
D=M
@SLR_POSITIVE_LOOP
D; JGT
(SLR_END) // Loop is over. Set value of SLR.1 to that of SLR.2, for return purposes
@SLR.2 // Place result in correct place
D=M
@SLR.1
M=D
@SLR.0 // Return to calling function
A = M
0; JMP
答案 0 :(得分:1)
左数或右数的逻辑移位等于将N-n位从N位的一个字复制到另一个字。因此:
unsigned int a = 0x1321;
unsigned int b = 0;
unsigned int mask1 = 1;
unsigned int mask2 = 1 << n; // use repeated addition for left shift...
int i;
for (i = 0; i < N-n; i++) {
if (a & mask2)
b|= mask1;
mask1 += mask1;
mask2 += mask2;
}
交换mask1和mask2将实现左移(仅使用按位操作)。
答案 1 :(得分:1)
为了与Nand2Tetris课程的性质保持一致,我试图在这个答案中走一条路,给出了Hack汇编编码技术和通用算法的例子,但最后的代码仍然是一个练习。
Hack ALU没有任何将位N与位N-1连接的数据路径。这意味着必须使用左旋转来实现右移和旋转。 (注意:左=最高有效位,右=最低有效位)
左移是很容易的,因为它只是乘以2,这本身就是自我添加。例如:
// left-shift variable someVar 1 bit
@someVar // A = address of someVar
D = M // D = Memory[A]
M = M + D // Memory[A] = Memory[A] * 2
左旋有点困难。您需要保留最左边的位的副本,并在执行乘法后将其移动到最右边的位。但请注意,您在D寄存器中有一个“someVar”原始值的副本,您可以根据其值进行测试和跳转 - 如果D的最左边的位为1,则D将小于零。此外,请注意,在将“someVar”乘以2后,它的最右边位始终为0,这样可以轻松设置而不更改任何其他位。
左旋后右转是直截了当的;如果你想左旋N位,你改为右旋16-N位。请注意,这假设N在0-15范围内。
右移是最复杂的操作。在这种情况下,您需要先进行右旋,然后生成一个掩码,其中N位设置为零。你和右边的结果与面具一起旋转。
生成掩码的基本方法是从-1开始(设置所有位)并将其添加到自身N次;这使掩码0的最右边N位。然后左旋这16-N次,将所有0位移到最左边的N位。
然而,这是很多周期,当用汇编语言编程时,保存周期就是它的全部。您可以使用几种技术。
第一种是使用地址算法来实现case语句的等价物。对于16个可能的旋转值中的每一个,您需要将16位掩码值加载到D寄存器中,然后跳转到大小写的末尾。你必须要小心,因为你只能使用@instruction加载15位常数,但你可以在6条指令中进行加载和无条件跳转(4加载完整的16位常数,2加载跳转)。
因此,如果您从位置(CASE)开始有16个,则只需要将N乘以6,将其添加到@CASE,然后跳转到该位置。在考虑如何乘以6时,请记住HACK指令集的一个非常可爱的特性;您可以同时将ALU操作的结果存储在多个寄存器中。
然而,最有效的解决方案是预先计算掩码表。在程序初始化期间,您生成16位掩码并将它们存储在内存中的某个固定位置,然后您只需将N添加到表的起始地址并读取掩码。
由于HACK CPU无法访问程序ROM而不是获取指令,因此无法将表存储在ROM中,每个表项必须使用多个指令将值加载到D寄存器中然后保存它进入RAM。我最终写了一个简单的python脚本,生成初始化表的代码。
答案 2 :(得分:0)
如果将值转换为无符号,则会更容易,因为逻辑右移不会保留符号。然后你只需重复减去2直到结果小于2,此时减法的数量就是你的商(即右移值)。
C中的示例实现:
int lsr(int valueToShift)
{
int shifted = 0;
uint16_t u = valueToShift;
while (u >= 2) {
u -= 2;
shifted++;
}
return shifted;
}
答案 3 :(得分:0)
你应该使用二进制或十六进制,因为使用decimal会很难想象数字表示。
如果你有算术移位而不是逻辑移位,最明显的解决方案是清除顶部位,如果它是负的
int LogicalRightShift(int x, int shift)
{
return (x >> shift) & ((1U << (CHAR_BIT*sizeof(x) - shift)) - 1);
// or
return (x >> shift) & (~((~0) << (CHAR_BIT*sizeof(x) - shift)));
}
如果您没有算术右移,您可以逐位复制
int LogicalRightShift(int x, int shift)
{
// assuming int size is 32
int bits[] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000,
0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000,
0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000
}
int res = 0;
for (int i = 31; i >= shift; i++)
{
if (x & bits[i])
res |= bits[i - shift];
}
return res;
}
另一种方法是重复除以2.或者您可以将2的幂存储在查找表中并除以该幂。这样,如果你没有硬件分频器,它可能比上面的位复制方法慢,但仍然比你的方法减去数千次要快得多。要将-27139(38397)右移1位,您需要从数字9599次减去2,如果数字较大或需要移位不同的位数,则需要更多
答案 4 :(得分:0)
更快的方法可能是使用添加。举一个粗略的例子:
uin32_t LSR(uint32_t value, int count) {
uint32_t result = 0;
uint32_t temp;
while(count < 32) {
temp = value + value;
if(temp < value) { // Did the addition overflow?
result = result + result + 1;
} else {
result = result + result;
}
value = temp;
count++;
}
return result;
}
基本思路是将64位无符号整数移位“32 - count”次,然后返回最高32位。
在汇编中,上面的大部分代码(分支等)有望成为add value, value
然后add_with_carry result, result
。