signed 비교: 뺄셈은 맞았는데 작은 수가 더 크다고 나온다
두 정수를 비교하려고 a−b를 계산한 뒤 최상위 비트를 읽었다. 음수면 a가 더 작다는 직관이다. 대부분의 값에서는 맞지만, signed 범위의 끝에서 정반대 답이 나온다. signed 비교에서 중요한 것은 뺄셈의 최상위 비트만이 아니라 피연산자의 부호와 overflow가 그 비트의 해석을 바꾸는 조건이다.
여기서는 8비트 2의 보수 signed 값, 즉 −128부터 127까지를 사용한다. 같은 비트열을 unsigned로 해석하는 비교와는 다른 함수다.
−128이 1보다 크다고 나오는 순간
a=−128, b=1이면 a가 더 작다. 수학적인 차이는 −129다. 하지만 −129는 8비트 signed 범위를 벗어난다. 낮은 8비트만 남기면 결과는 01111111, 즉 +127이고 최상위 비트는 0이다.
a−b의 MSB=1이면 a<b라는 단순한 판정은 이 입력에서 거짓을 반환한다. 반대 방향도 가능하다. a=126, b=−3이면 실제로 a가 더 큰데 차이 129가 8비트 10000001, signed −127로 나타난다. 결과 비트가 음수처럼 보인다는 이유로 a가 작다고 판정하면 틀린다.
문제는 뺄셈기가 덧셈을 잘못했다는 것이 아니다. modulo 256의 낮은 결과 비트는 맞다. 그 비트를 범위 밖의 signed 차이 부호로 해석한 비교 논리가 잘못된 것이다.
부호가 다를 때는 빼지 않아도 답이 있다
signed 비교에서 a는 음수이고 b는 0 이상이면 a<b다. a가 0 이상이고 b가 음수이면 a<b는 거짓이다. 이 조건에서는 두 피연산자의 부호만으로 답을 정할 수 있다.
부호가 같으면 차이의 범위가 달라진다. 두 입력이 0–127이면 차이는 −127부터 127까지다. 두 입력이 −128–−1인 경우에도 차이는 같은 범위에 있다. 따라서 같은 부호의 두 8비트 값의 차이는 signed 범위에 들어가고, 뺄셈 결과의 MSB로 작은 쪽을 판단할 수 있다.
설명용 논리는 다음과 같다.
logic [7:0] a_bits, b_bits;
logic [7:0] difference;
logic less_signed;
assign difference = a_bits - b_bits;
assign less_signed = (a_bits[7] != b_bits[7])
? a_bits[7]
: difference[7];
입력은 signed 숫자를 나타내는 8비트 패턴이라는 계약이다. 코드의 비트 비교는 그 표현을 직접 사용한다. 변수 타입이 unsigned라고 해서 결과가 unsigned 비교인 것은 아니지만, 호출자가 입력을 어떤 숫자로 읽는지는 명확히 해야 한다.
overflow 비트를 사용하는 같은 의미의 해법
뺄셈에서 signed overflow는 피연산자 부호가 다르고 결과 부호가 a와 다를 때 발생한다. V = (sign_a XOR sign_b) AND (sign_result XOR sign_a)로 표현할 수 있다.
정상적인 차이 부호를 얻는 less-than 조건은 sign_result XOR V다. −128−1에서는 result 부호 0, V=1이므로 less=1이다. 126−(−3)에서는 result 부호 1, V=1이므로 less=0이다. overflow가 감긴 결과 부호를 바로잡는다.
두 방식은 같은 signed 비교를 표현한다. 어떤 방식으로 구현할지는 이미 있는 ALU 신호, 셀 매핑과 경로에 달려 있다. overflow 비트가 다른 연산에도 필요하면 재사용할 수 있고, 부호 분기 방식은 비교에 필요한 조건을 직접 드러낸다.
Ibex ALU의 비교 경로는 피연산자의 최상위 비트가 같을 때 adder 결과의 부호를 사용하고, 다를 때 signed 여부와 입력 부호로 판정한다. signed와 unsigned의 greater-or-equal 관계를 나누어 설명하는 주석도 있다. 전체 코어의 모든 비교가 이 단순 예제와 같은 폭이라는 뜻은 아니다.
unsigned 비교와 섞으면 더 조용한 오류가 된다
8비트 11111111은 signed −1이고 unsigned 255다. 00000001과 비교하면 signed에서는 더 작고 unsigned에서는 더 크다. RTL 비교식의 타입 변환이나 한 피연산자의 signed 선언이 예상과 다르면 같은 파형에서 다른 판정이 나올 수 있다.
signed 비교를 뺄셈 carry-out 하나로 대신하는 것도 조건을 확인해야 한다. carry와 borrow는 unsigned 차이의 가중치 관계에 연결되어 있고, signed 순서의 끝점은 부호 표현에 달려 있다. 플래그 이름만 보고 두 의미를 같게 다루면 안 된다.
equal은 별도의 관계다. signed 해석이 달라도 두 비트열이 같다는 조건 자체는 같다. less-than, equal과 greater-or-equal을 결합할 때도 경계값과 같은 값에서 서로 모순되지 않는지 확인할 수 있다.
작은 폭의 모델에서는 모든 입력 쌍을 검사할 수 있다. 8비트라면 256×256=65536쌍이다. 부호가 같은 영역과 다른 영역을 나누어 비교하면 틀린 입력이 어느 조건에 모이는지 이해하기 쉽다. 큰 폭에서도 최소·최대값과 서로 다른 부호의 경계는 직접 넣는 것이 좋다.
signed 비교의 답을 결정하는 것은 감긴 차이의 겉모습만이 아니다. 부호가 다른 입력은 부호로, 같은 입력 부호는 범위 안의 차이로 판단하거나 overflow로 보정해야 한다. 이 관계를 확인하면 뺄셈 결과는 맞는데 비교가 틀리는 조용한 오류를 잡을 수 있다.