MCU를 이용해 덧셈, 뺄셈, 곱셈, 나눗셈 등 사칙연산을 수행하는 방법에 대해 ST마이크로일렉트로닉스의 유지 카와노 매니저가 설명한다.
MCU, 논리 반전·쉬프팅으로 사칙연산
뺄셈 직접 수행불가, 보수 이용해 간접적 계산
쉬프터·가산기로 곱셈, 쉬프터·감산기로 나눗셈
[편집자주]일반적으로 반도체라 하면 컴퓨터의 CPU와 메모리처럼 일반인에게 익숙한 반도체를 떠올리기 마련이다. 반면에 전자제품 구동을 위해서 핵심 반도체로 쓰이는 MCU(Micro Controller Unit)의 경우 일반적으로 우리가 쉽게 접하는 모든 전자제품에서 사용되고 있으면서도 일반인에게는 아직 낯선 반도체다. 이런 MCU가 최근 반도체 부족 사태로 인해 언론에 오르내리며, 일반인들에게 주목받기 시작했다. 이에 본지는 MCU 반도체 전문기업인 ST마이크로일렉트로닉스의 유지 카와노 매니저의 연재기고를 통해 MCU에 대해 전문적으로 알아보는 자리를 마련했다.
이미 잘 알려져 있듯이, MCU는 다양한 연산을 수행할 수 있으나 현실적으로 산술 연산 중 덧셈만 수행할 수 있다. MCU는 다른 산술연산(가령, 뺄셈, 곱셈, 나눗셈)을 수행하기 위해서 덧셈과 논리 반전(inversion) 및 쉬프팅(shifting) 연산을 결합한다.
우선, 가장 단순한 산술 연산인 덧셈을 살펴보겠다.
▲그림 1. 이진수 덧셈
이진수 덧셈은 십진수 덧셈과 크게 다르지 않다. 특정 열(column)의 수를 더해서 2가 될 경우, 왼쪽 열로 1을 ‘자리올림(carried)’ 한다. 그림 1은 두 이진수 [0, 1, 1, 0]와 [0, 0, 1, 1]를 더한 예를 보여준다.
참고로 왼쪽 열로 1을 더하는 것을 ‘자리올림(carrying)’이라 하고, 왼쪽 열에서 1을 제하는 것을 ‘자리내림(borrowing)’으로 표현한다.
다음으로 뺄셈을 살펴보겠다. 앞에서 언급했듯이, MCU는 뺄셈을 직접 수행할 수 없기 때문에 보수(complement)를 이용해서 간접적으로 계산한다.
숫자 y의 보수 x란 x + y의 결과가 자리올림 되는 최소의 x값을 말한다. 즉, x와 y가 한자리의 십진수인 경우, 보수는 x + y = 10이 되는 x 값을 말한다. 이진수의 경우, x + y = 2가 될 x값을 뜻한다(예, [1, 0]).
십진수에서 6의 보수는 4이며, 3의 보수는 7이다. 이진수의 경우 [1]의 보수는 [1]이 되는데, 이는 [1] + [1] = [1, 0] (첫 번째 열에서 두 번째 열로 1 자리올림) 이기 때문이다.
이와 같이 [1, 1, 0, 1]의 보수는 [0, 0, 1, 1]인데 이는 [1, 1, 0, 1] + [0, 0, 1, 1] = [1, 0, 0, 0, 0] (두 수의 합이 2가 될 때마다 왼쪽 열로 1 자리올림) 이기 때문이다.
그렇다면 뺄셈에서 보수가 어떻게 사용될까. 우선 보수에 임의의 수를 더해보겠다. 쉬운 이해를 위해 십진수를 사용하겠다.
숫자 9(피감수)에서 숫자 6(감수)를 빼기 위해 보수를 사용하는 방법을 살펴보겠다. 감수의 보수는 4이고 이를 피감수에 더하면 다음과 같다.
9+4=13
여기서 올림수를 제하면 3이 남는데 이는 9에서 6을 뺐을 때의 결과이다.
9-6=3
이는 곧 감수의 보수와 피감수를 더한 후 높은 자리 수를 제하면 사실상 뺄셈을 수행하게 된다는 뜻이다.
다음으로 이진수를 이용하겠다. [1, 1, 1, 1](15)에서 [1, 1, 0, 1](13)을 빼기 위해서는 [1, 1, 0, 1]의 보수가 필요한데 이는 [0, 0, 1, 1]이다. 보수를 피감수에 더한 결과는 다음과 같다.
[1,1,1,1] (15)+[0,0,1,1] (3) = [1,0,0,1,0] (18)
그 후, 여기서 올림수를 제하고 마지막 4자리를 보면 [0, 0, 1, 0](2)다.
[1,1,1,1] (15) [0,0,1,1] (13)=[0,0,1,0] (2)
이는 곧, 이진수의 경우에도 피감수를 감수의 보수에 더하면 사실상 뺄셈을 수행하게 된다는 뜻이다.
이와 같이 보수와 덧셈을 이용해 뺄셈을 수행할 수 있으나 보수를 구하기 위한 계산이 먼저 선행되어야 한다.
▲그림 2. 이진수 보수 산출
MCU는 이진수 보수를 쉽게 산출할 수 있다. 이는 각 자릿수를 반전 시키고(즉, [0] → [1] 또는 [1] → [0]) 마지막에 [1]을 더함으로써 가능하다. 그 예는 그림 2와 같다. [1, 1, 0, 1]의 보수를 구하기 위해 각 자릿수를 [0, 0, 1, 0]으로 반전시킨 후, 끝에 [1]을 더하면 [0, 0, 1, 1]이 된다. MCU는 역(inverse)의 논리 연산을 통해 이진수의 각 자릿수를 쉽게 반전시킬 수 있다. [1]을 더하기 위한 회로는 증가기(incrementer)로 불리며, 간단하게 회로를 추가하여 구현할 수 있다.
다른 방법으로도 보수를 산출할 수 있다. 가장 높은 자리부터 최우측 [1] 앞까지의 모든 [1]과 [0]을 반전시키고, 나머지(최우측 [1] 포함)는 그대로 두는 것이다. 예를 들어, [1, 1, 0, 1]의 보수를 구하려면 최우측 [1] 앞의 높은 자리([1, 1, 0])를 반전시키는데 그 결과는 [0, 0, 1, 1]이 된다. 이진수 [1, 0, 1, 0]의 경우 높은 자리의 [1, 0] 반전 후 [1, 0](최우측 [1] 및 그 다음 자릿수)은 그대로 둔다. 그 결과 보수는 [0, 1, 1, 0]이 된다. 그러나 MCU에서 최우측 [1]을 판단하기 어렵기 때문에 일반적으로 모든 자릿수(즉, 모든 비트) 반전 후 [1]을 더하는 첫번째 보수 산출 방법을 사용한다.
그렇다면 곱셈은 어떻게 해야 할까? 기본 원리에 따라 가장 단순하면서 쉬운 곱셈법은 피승수(즉, 다른 수로 곱해지는 수)에 피승수를 승수(곱하는 수) 횟수만큼 더하는 것이다. 이 방법은 가산기(adder) 및 계수기(counter) 로 구현할 수 있다. 1-2 자릿수의 간단한 승수 및 피승수를 곱하는 경우엔 이 방법을 쉽게 사용할 수 있으나 32비트(즉, 32자리) 수를 곱할 경우 2의 32제곱(4,294,967,296)회까지 덧셈을 수행해야 할 수도 있다. MCU의 계산속도가 빠르긴 하지만 그 정도 규모의 수 계산은 비현실적이다.
▲그림 3. 이진수 곱셈의 예
긴 곱셈(long multiplication)을 이용해 이진수를 곱셈했던 때를 생각해 보자. 그림 3은 그와 같은 방식을 이용한 [1, 0, 1, 0] x [1, 0] 산출법을 보여준다. 여기서 (1)은 피승수 x 승수 1열의 결과이며 (2)는 피승수 x 승수 2열의 결과이다. (1)에서 승수 1열의 수는 [0]이기 때문에 곱은 [0, 0, 0, 0]이 된다. (2)에서는 승수 2열의 숫자가 [1]이기 때문에 그 곱은 피승수를 왼쪽으로 한자리 옮긴 것이 된다(즉, 승수의 2열과 맞춤). 이진수 계산은 [1]과 [0]만을 사용하기 때문에 상당히 단순하다. 기본적으로 승수의 [1]마다 피승수를 왼쪽으로 옮겨 최우측 수가 승수의 관련 [1]과 맞도록 한 후 모든 열을 더한다.
이와 같이 자리를 옮기는 것을 ‘쉬프팅(shifting)’이라고 부르며 이를 수행하기 위한 연산 유닛을 ‘쉬프터(shifter)’라고 한다. 인버터(inverter)와 마찬가지로 쉬프터도 쉽게 만들 수 있다. 따라서 쉬프터와 가산기만 있다면 MCU로 곱셈을 수행할 수 있다.
가장 단순한 곱셈 회로(또는 최소필요회로)는 1비트(한자릿수) 쉬프터와 가산기로 구성된다. 32비트(32자리) 이진수 곱셈 또한 쉬프트 32회, 덧셈 최대 32회를 수행함으로써 가능하다. 덧셈을 4,294,967,296회까지 할 필요가 없는 것이다.
기본 원리에 따라 가장 단순하면서 쉬운 나눗셈법은 몫이 [1] 미만이 될 때까지 피제수(즉, 나누어지는 수)에서 제수(즉, 나누는 수)를 계속 뺀 후 뺀 횟수를 세는 것이다. 그러나 곱셈과 같이 32비트 피제수의 경우 뺄셈을 최대 4,294,967,296회 수행해야 하므로 비현실적이다. 더욱이, 곱셈과정에서는 덧셈을 일부 줄일 수 있지만 나눗셈의 경우 뺄셈을 줄일 수 없기 때문에 나눗셈이 더 어려울 수 있다.
▲그림 4. 이진수 나눗셈 예
긴 나눗셈(long division)을 이용해 계산해보겠다. 그림 4는 긴 나눗셈을 이용해 [0, 1, 1, 0]을 [0, 1, 1]로 나누는 것을 보여준다. 제수가 세자리 수이므로 우선 피제수의 첫 세 자리(즉, [0, 1, 1])을 제수(즉, [0, 1, 1])로 나눌 수 있다. 십진수와 달리, 이진수는 [1]과 [0]으로만 구성되기 때문에 제수를 피제수의 첫 세 자리에서 빼면 된다. 그 후, 그 차의 다음 세 자리 수에서 제수를 빼고 피제수의 마지막 자리까지 이를 반복한다. 그 결과 몫과 나머지를 얻게 된다. 그림 4에서 피제수는 4자리이고 제수는 3자리이다. 그러므로 뺄셈을 두 번만 해도 충분하다. 제수와 피제수의 자릿수가 크게 다를 경우, 쉬프트 및 뺄셈 횟수가 증가하게 되고 그 결과, 계산 횟수도 증가하게 된다. 그러나 제수 및 피제수의 자릿수가 같다면 뺄셈 한번으로 나눗셈을 할 수 있기 때문에 계산 속도가 빨라진다. 이를 고려할 때, MCU는 쉬프터와 감산기(subtractor)만 있으면 이와 같은 알고리즘을 이용해 나눗셈을 수행할 수 있다.
ARM의 Cortex-M3 CPU는 감산기 하드웨어를 내장하고 있다. 나눗셈 계산 시간은 2-12 사이클에 이르며(1 사이클 = 계산 1회 수행 시간), 나눗셈에 필요한 사이클 수는 제수 및 피제수의 자릿수에 따라 달라진다. 제수와 피제수의 자릿수가 같다면 2 사이클로 계산을 완료할 수 있으나 자릿수가 다른 경우 최대 12 사이클이 필요할 수 있다.
위의 설명을 통해 MCU의 뺄셈, 곱셈 및 나눗셈 메커니즘을 이해하는데 도움이 되었기를 바란다.