수업/Computer Architecture

[Computer Architecture] Arithmetic for Computers(1)

hw-ani 2022. 10. 13. 11:41

앞서 Chapter 2까지는 interger에 대해서만 다뤘다. 이번 챕터에선 연산을 좀 더 깊이있게 다룬다.

 

Addition and Subtraction

컴퓨터에서 덧셈은 그냥 사람이 계산하는 것처럼 계산한다. carry가 생기면 올려버리고,, 대신 2진수로 한다는 점이 좀 다르다.

음수는 2의 보수로 표현해서 덧셈으로 진행된다.

그리고 비트 수가 제한돼있어서 올라가는 carry가 비트수를 넘어가면 그냥 버려진다.

이렇게 연산결과가 hardware로 표현할 수 없는 경우를 overflow라고 한다.

 

2의 보수 인 경우 Overflow Detection

1. 덧셈에서 서로 다른 부호를 연산하면 절대 overflow가 발생하지 않는다.

2. 뺄셈에서 서로 같은 부호를 연산하면 절대 overlfow가 발생하지 않는다.

        > `c-a`는 사실상 `c+(-a)` 이므로 1이랑 같은 말이다.

 

일단 위 진리들을 머릿속에 박아두고, 그럼 나머지 경우에서 어떻게 overflow를 감지하는지 아래 표를 보자.

overflow 발생 조건

 

Unsigned Number인 경우 Overflow Detection

덧셈에서 결과가 operands보다 작거나, 뺄셈에서 결과가 minuend(a-b에서 a)보다 크면 overflow가 발생한 것이다.

(부호가 다른 경우는 없다.)

Unsigned number는 보통 memory address에서 주로 쓰이는데, 메모리 주소 연산에선 overflow 감지를 원하지 않으므로 대부분 무시한다.

 

위 두가지처럼 overflow가 발생했단걸 hardware가 알려주긴하지만 이를 어떻게 처리할지, 혹은 처리할지 무시할지 등의 결정은 programmer나 programming environment의 몫이다.

 

 

Multiplication

우선 간단하게 양의 정수 곱셈이 일어나는 과정을 보자.

위는 우리가 실제로 곱셈을 하는 과정이다. 위 과정이 사실상 그대로 hardware로 옮겨진다.

보면 """multiplicand 를 왼쪽으로 자릿수를 계속 키우면서, multiplier의 각 비트가 더할지 말지를 결정한다."""

즉,

1. 1000 더할래 말래? -> 1(승인)

2. 10000 더할래 말래? -> 0(거부)

3. 100000 더할래 말래? -> 0(거부)

4. 1000000 더할래 말래? -> 1(승인)

 

Arithmetic Logic Unit이란 덧셈,뺄셈, logical operation(AND/OR 등)을 수행하는 hardware이다.

(자세한 작동 과정은 강의자료를 참고하는 것으로 하고 여기선 전체적으로 설명)

 

네모 박스로 된 놈들, Multiplicand, Multiplier, Product, 는 register이다. (앞에서 배운 범용 register와 혼동하지 말자. 사실 값만 저장하면 register이다.)

그리고 그림엔 안나와있는데, 각 register에는 하나의 clock 신호가 연결돼있다.(동기화)

연산을 하는 ALU라는 회로도 있다. 이 친구는 보면 알겠지만 Multiplicand의 128bits와 Product의 128bits를 받아서 덧셈 연산을 수행해 다시 Product로 결과를 보낸다.(그리고 그 Product에 들어가있는 결과는 곧바로 다시 ALU로 들어간다.)

 

control test 는 각 register나 ALU에 0/1의 1bit 신호를 줌으로써 해당 연산(각 회로 옆에 적힌 write나 shift left, shift right, ALU 덧셈 연산)을 수행할지 말지 결정한다.

참고로 오해하면 안되는게, ALU는 1이든 0이든 무조건 연산을 수행한다. 어떤 값을 저장하거나 하지않고 사실상 흐르기때문에 그렇다. ALU에게 주는 control 신호는 1이면 더하기, 0이면 빼기라고 여기선 가정한다.

 

곱셈 연산에서 product(결과값)의 최대 자리수는 multiplicand의 자리수 + multiplier의 자리수이다.

그래서 우리가 지금 볼 것이 64bits x 64bits이므로 Product를 128bits로 둔 것이다.

(Multiplicand도 128bits인데? -> shift 공간때문에 저렇게 잡아둔 것이다. 연산 시작할때는 하위 64bits만 사용가능)

 

 

일단 각 단계는 clock이 튈때마다 (clock 신호가 올때마다) 진행된다는 점을 알아두자. 논리회로에서도 배웠지만, clock 신호가 들어올때 register의 값이 변경(문앞에 대기중인 값 저장 + 저장된 값 출력)된다.

이제 연산이 수행되는 전체적인 흐름을 알아보자.

제일 처음으로 Multiplicand의 하위 64bits(좌측 64는 0으로 채워짐)와 Multiplier에 각 숫자를 넣어준다.

clock이 한번 튈때마다 (1)Write, (2)Multiplicand Shift left, (3)Multiplier Shift right을 순서대로 하나씩 진행 반복한다.

(잘 생각해보면 이 세개+ 중간에 ALU로 덧셈을 반복하는 과정이 우리가 손으로 했던 과정과 같은 원리임을 알 수 있다.)

순차적으로 진행되도록 하는 방법은 간단하다. control test에서 각 register로 주는 신호를 조절하면 된다. Write, Multiplicand Shift left, Multiplier Shift right에 들어가는 control 중 하나에만 1, 나머지는 0을 주고 clock이 튈때까지 기다렸다가 clock 신호가 들어와서 연산이 실행되고나면 다음 단계를 위해 control 신호를 바꾼다. 즉, 다음 단계에선 다른 연산에만 1을 주고 나머지엔 0을 주고 clock 신호를 기다리는 것이다. 참고로 곱셈에선 덧셈만 발생하므로 ALU로 가는 control은 항상 1이다.

 

주의할 것이 있는데, 진행 중 Write를 진행할 단계가 왔을때는 무조건 control을 1로 주지는 않는다.

나머지 연산(ALU 제외)에는 0을 주는게 맞지만 Write는 Multiplier의 LSB 신호를 받아서 그 신호를 Write에 준다.(잘보면 multiplier에서 control test로 신호를 받는데 그게 multiplier의 LSB이다.)

왜냐하면 multiplier 중 자릿수가 0인 경우는 곱했을때 0이므로 덧셈에 반영되지 않기 때문이다.

 

곱셈을 위처럼 정석대로 안하고 그냥 left shift 연산으로 대체 할 수도 있다. ex. 10xA은 2xA + 8xA로 대체될 수 있다.
이 방식이 더 빠르기 때문에, 컴파일러들이 2의 지수승을 곱하면 left shift로 대체 하기도 한다.

 

 

 

> 개선1

위 HW를 좀 더 개선할 수 있다. 곱셈 하나도 아니고 한 단계를 진행하는데 clock이 총 세번이나 필요하다.

(여기서 말하는 단계는 손으로 할때 곱하기 후 "더하는 과정"을 말한다.)

 

우선 잘보면 Multiplier는 우측으로 shift하며 하나씩 줄어들고, Product는 처음엔 사실 공간의 절반만 쓰다가 필요 공간이 한칸씩 늘어난다.
그래서 Product를 Multiplier 좌측에 붙이고 둘을 한번에 우측으로 shift 해나간다면 공간 절약이 될 것이다.

그렇게하면 Multiplicand도 좌측으로 shift할 필요가 없기 때문에(product가 우측으로 shift해주니까..), Multiplicand의 공간도 줄어든다.

보면 ALU도 크기가 작아졌다. ALU가 작으면 carry 연산 등도 작아지니 당연히 속도도 더 빨라진다.

Q.왜 product는 128bit가 아니라 129bit일까? A. 좌측끝에 덧셈연산에서의 carry를 보존하기위한 1 bit 때문이다.

(원리는 똑같은데 multiplicand가 안움직이고 product가 움직인다는 점과, product의 꽁무니에 어차피 사라질 Mplier를 붙여둔 점이 다르다.)

Multiplier가 사라지고, Product와 합쳐졌다. 처음에 우측에 빨간 박스로 표시된 부분에 Multiplier가 들어간다.

그리고 control은 위에서처럼 ALU에는 항상 1이고, product에 shift right와 write를 줄 수 있다.

참고로 두 연산은 병렬로 진행된다. 회로 내부에서 사실 둘의 순서가 정해지도록 구현됐다고 보면 된다. shift right와 write를 동시에 한다는 것도 애초에 말이 안된다. 여기서 병렬로 진행된단 말은 clock 한번에 두 연산이 진행이 되긴 하나 순서가 있긴하다는 말이다.

 

이번에도 어떻게 진행되는지 자세히 보자.

처음엔 일단 값을 넣어준다. Multiplicand 64bits, Product의 우측 공간에 Multiplier 64bits.

위에선 Write 연산이 "진행될 차례일때" LSB를 보고 판단해서 값을 줬지만,

여기선 Write로 가는 control 신호가 Multiplier의 LSB와 곧 바로 연결돼있다. 즉, LSB에 따라 계속 해당 신호를 준다는 것이다.

Shift right는 계속 1로 준다. 위 HW에선 Clock이 들어올때마다 한 단계씩 진행되기 때문이다. Product register의 전체 bit가 우측으로 한칸 이동한다.

즉, 바뀌는 값은 Multiplier의 LSB에 따른 Write밖에 없다.

 

그리고 좀 헷갈릴 수 있는데, Product 공간에서 ALU와 연결돼있는 놈은 Product의 좌측 half 64bits(상위 64bits) 공간이다.

덧셈의 결과는 계속해서 좌측 half에 적히고, ALU로 다시 들어가는 product의 영역도 product 전체가 아닌 product의 좌측 half이다.

Product가 우로 shift되며 더 높은 자리수에서의 덧셈을 만들어 주니, Product만 움직이는 것이다. 그 자리에 그대로 더함으로써 높은 자리수 덧셈이 되는 것.

 

이제 알거는 다 알았다. Multiplicand와 Multiplier에 값을 넣어두고 clock이 한번 튈때마다 (1)Write->(2)Shift right 순으로 clock하나에 병렬적으로 진행된다.

clock 하나에 곱셈의 한 단계를 진행하는 셈이다.

 

 

 

> 개선2

 

CISC라면 몰라도 RISC면 instruction 하나당 cycle을 너무 많이 쓰면 안된다.(ins 하나당 cycle 하나 정도라고 교수님께서 말씀하심) 즉, 더 개선해봐야한단 소리다.

그럼 "개선1"에서와 달리 이번엔 add 횟수를 줄여보자.

각 단계 진행할때마다 더해야하니, 총 63번의 덧셈 연산을 해야했던 이전 HW와 달리 위처럼 ALU를 구성하면 tree형태로 이전의 값을 곧바로 활용할 수 있으므로 더하기 연산 횟수가 log_2(64), 즉 6까지 줄어든다.

 

자세히 살펴보자면, 우선 제일 위의 ALU에 Mcand와 Mplier의 대응하는 bit 가 AND 연산이 돼서 들어간다.

원래는 저런 "각 단계의 결과" 결과를 순차적으로 쭉 진행하면서 ALU 하나로 더 하기를 해나갔는데, 이젠 ALU를 여러개 써서 두개씩 결과를 묶어낸다.

그리고 매 단계마다 각 결과의 MSB(or LSB)를 한비트씩 뽑아내서 종합해 Product를 만들어내는 것이다.

(사실 저렇게 바로 한비트씩 빼면 안되고 예외 처리가 필요하다. 왜냐하면 높은 자리수들은 그 아래 자리수들의 덧셈이 다 끝나고 carry까지 다 반영돼야 진짜 값이 나온다. 좀 헷갈리는데 잘 생각해보면 맞음. 그러니 사실 MSB도 저렇게 바로 빼면 안되고 어떤 예외처리를 해줘야함. 여기선 그게 중요한게 아니므로 생략한 것.)

 

즉, 하드웨어를 더 투입해도 괜찮다고 한다면, 하드웨어를 더 투입해서 더 빠르게 할 수 있다는 말이다.

(위 방법 말고도 carry save adder을 사용하거나 pipeline 기법을 쓰면 더 빨라짐...)

 

Signed Multiplication : 각 숫자를 양수로 바꾼 후 63비트 연산을 해준다. sign은 양수로 바꾸기 전에 따로 빼두고 sign끼리 연산해서 product의 sign을 알아낸다. 음수라면 계산 결과를 음수로 바꿔주고, 양수면 그냥 sign을 갖다붙인다.

 

 

Multiplication Instruction

`mul rdl, rs1, rs2` -> signed/unsigned 연산 결과의 low half만 rdl에 저장한다. (부호가 있다고 보든 없다고 보든 연산 결과의 low는 동일(2비트 예시)하므로 이 mul 연산자 하나로 해결한다. high버전만 signed 여부에 따라 다르므로 아래로 두개로 나뉜다.)

`mulh rdh, rs1, rs2` -> signed 연산 결과의 high half만 rdh에 저장한다. (애초에 한번에는 저장을 못함. register 용량 초과)

(위 rdl과 rdh를 조합해야 진짜 multiply 연산 결과를 얻을 수 있음.)

`mulhu` -> unsigned 연산 결과의 high half를 저장한다. unsigned 연산 결과의 low half는 mul 연산으로 얻을 수 있다.

`mulhsu rdh, rs1, rs2` -> rs1에는 signed, rs2에는 unsigned를 넣어서 둘을 곱한 것의 high half를 rdh에 얻을 수 있다.

 

high를 check해서 32bit(or 64bit) 연산에서 overflow를 판단할 수 있다. 예를들어 unsigned 연산에서 mulhu의 값이 0이라면 해당 register 크기 기준으론 overflow가 발생하지 않은 것이고, signed multiplication에서 mulh가 mul의 sign bit가 복사돼서 쭉 들어갔다면 overflow가 발생하지 않은 것이다.

 

 

 

Division

이번엔 양의 정수 나눗셈을 해보자. 여기서도 우선 용어를 확실히 해야하고... 곱셈처럼 실제로 우리가 나눗셈 하는 과정을 그대로 카피한다.

그런데 위 과정만봐선 생략된게 많다. 우리는 몫의 0이 들어가는 자리에 빼기를 하면 안될 거라는걸 빠르게 캐치하고 0을 적는 것이다.

하지만 컴퓨터는 하나하나 다 빼보고 음수가 나오면 다시 복구하고 0을 적는 방식으로 진행해야한다.

컴퓨터의 나눗셈은 좀 특이한 점이 있는데, (1) 0으로 나누는 것도 계산 된다는 것과  (2) overflow를 detect하지 않는다는 점이다. overflow는 사용자가 알아서 Quotient값 보고 판단해야한다. 0으로 나누는 것도 divisor을 보고 판단해야한다.(0으로 나누는건 OS단이나 에서 구분 해주기도 함)

 

 

실제 하드웨어 설계를 보자. Remainder register는 최종 나머지만 들어가는게 아닌 중간중간 나눗셈이 진행되며 나오는 나머지들도 들어간다. 연산을 시작하기 전 Remainder는 Dividend 그 자체이다.

하나 주목할 점이 있다면 곱셈에선 Mplier에서 LSB 정보를 받아왔지만, 여기선 왼쪽 부분만 메인으로 돌고 오른쪽 Mplier가 있던 자리엔 Quotient와서 중간중간 몫을 저장하는 역할을 한다는 것이다.

 

<고찰>

우리가 지금 하는건 64-bit 나누기 64-bit 연산인데 Remainder(Dividend)가 128-bit인 것도 생각해보자. 일단은 RISC-V에선 64bit 나누기 64bit를 지원하는게 맞다 왜냐하면 지금 register 크기를 64비트로 가정하고 있기 때문이다.

자 그럼 64/64를 지원하기위해 연산시 사용되는 각 register의 크기를 정해야한다. 일단 divisor 공간이 64비트니 몫도 최대 크기가 64bit일 것이다. divisor가 작은 크기면 몫의 크기가 늘어나고 둘은 서로 한놈의 크기가 늘면 한놈의 크기가 줄어드는 관계이다.

그런데 몫의 공간을 64-bit로 잡았다면 Dividend는 128-bit 크기를 지원해줘야한다. 왜냐하면 컴퓨터가 나누기 연산을 할때, 위에서 말했듯이 모든 경우의 수를 봐야하기 때문이다. 즉 몫이 1비트 숫자일지 64비트 숫자일지 모르니까 일단 64비트 숫자라고 가정하고 계산을 시작한다. 여기서 말하는 계산이란, 몫을 64비트 숫자로 생각하고 빼기 연산을 한다는 것이다.

그러면 위에서 봤듯이 두 숫자 곱의 최대 자리수는 두 숫자 자리수의 합과 같으므로, dividend의 크기는 64비트(몫의 최대 자리수)x64비트(divisor의 최대 자리수)의 max 자리수인 128비트까지 될 수 있단 소리이. 그래서 가장 아래 Remainder(Dividend)의 공간을 128비트로 한 것이다.

즉 모든 경우의 수를 다 포괄해서 생각해야하니 dividend를 128bit로 잡은 것이다.

그럼 이제 dividend를 128비트로 잡았으니 divisor로 dividend의 128비트 MSB 부분 끝자리부터 빼봐야한다, 그래서 보면 divisor register도 128비트(divisor 자체는 64비트임. 128 공간안에서 좌->우로 이동하는거임)이고, 그걸 받아서 dividend와 계산하고 내려주는 ALU도 128-bit 이다.

사실 Divisor,ALU,Remainder는 127이어도 되긴 한다. 왜냐하면 dividend가 128비트여야한다는 근거가 몫xdivisor 을 했을때 최대 128비트가 나올 수 있으니 이를 수용하기 위해 한다는 것이었는데, 몫 공간 크기가 64비트일때 100...00(64-bit)이 우리가 고려할 수 있는 최대 값이다.(실제 나누기 연산할때 제일 앞에 1만 적은 경우를 말하는 것)
즉, 100...00(64비트)(몫) x 11...11(64비트)(divisor) 이 나올 수 있는 최대 숫자인데 이는 127비트이다. 128비트로 잡고 시작하는건 사실 7/6을 하는데 몫이 10인 경우부터 해보는 꼴인 것이다...
무슨 말이냐면, 아래 참고 자료의 4비트 나누기 4비트 예시를 보자. 왼쪽 그림을 보면 컴퓨터가 나눗셈을 하는 가장 첫 단계를 표현한다. 컴퓨터는 xxxx가 0001 일수도 있고 어떤 숫자인지 모르니 일단 Q의 최상위 비트에 1을 적고 -> Q와 divisor을 곱한 후 -> diviend(remainder)와 빼보고 다음 단계를 결정한다. 즉, 왼쪽 그림처럼 divisor와 제일 위에 적힌 1(000)이 곱해져서 만들어진게 divisor register에 표현된다. 그러면 divisor register나 dividend나 7비트여도 상관이 없다. 왼쪽 그림 초록색을 보면 divisor register의 최대 크기는 7이다.
8비트로 잡고 한다는 것은 사실상 오른쪽 그림처럼 몫의 범위인 4비트에 들어가지도 않는 몫을 한번 계산해보는 꼴이다. 즉 4비트 나누기 4비트에서 어차피 빼면 음수가 나올 의미없는 계산을 제일 처음 해본다는 것이다.
근데 뭐 크기를 8의 배수로 맞춰야 하는 것도 있을 것이고, 그냥 어차피 제일 처음 iteration은 버린다는걸 알고서도 한번 더 해도 득보다 실이 크니까 이렇게 하는거라고 생각한다.
아니면 x86 같은 경우 실제로 register 2개를 dividend로 이용해서 64비트 / 32비트를 진행하기도 하는데, 그 구조를 그대로 카피해온 것일 수도 있고.. RISC-V는 딱 64/64 아니면 32/32만 하니까(정확한진 모르겠네) 저게 의미없어 보일 수 있는데, dividend에 divisor의 2배 크기를 줄 수 있다면 이렇게 하는게 맞지.

참고 자료

 

 

 

자 이제 과정을 살펴보자. 얘도 마찬가지로 3 clock에 1 단계(한단계에 몫 한자리를 구함)를 진행한다.

Mplier에 의해 write가 통제됐던 곱셈과 달리 일단 무조건 write된다. 대신 잘못 write되면 다시 되돌리는 과정이 있다.
초기화: 우선 Divisor register에 divisor을 high half에 넣고, Remainder register에 dividend를 low half에 넣는다.

ALU에는 0과 1 신호를 주며 빼기와 더하기를 할 수 있다.

 

마찬가지로 clock 하나에 아래 과정이 하나씩 진행된다.

(2번 과정 진행을 위해 write 결과의 MSB(== Remainder의 MSB)는 control로 입력된다.)

1. Remainder에 Remainder 빼기 Divisor 연산 결과를 Write한다.

        : Divisor엔 뺄셈을 위해 0을 주고, Remainder의 control에는 1을 준다. 나머지 register의 control엔 0을 준다.

2a. 만약 write 결과가 음수라면,, 다시 Divisor을 더하고, Quotient를 좌측으로 한칸 shift 한 후 0을 넣는다.

        : Divisor엔 덧셈을 위해 1을 주고, Quotient의 control에도 1을 준다.(그리고 Q 뒤에 0을 넣음)

        : 나머지 register의 control엔 0을 준다.

2b. 만약 write 결과가 양수라면,, Quotient를 좌측 한칸 shift 한 후 1을 넣는다.

3. Divisor을 우측 한칸 shift한다.

 

곱셈 연산에서는 (1)Write -> (2)Shift left -> (3)Shift right를 순서대로 하나씩 진행시키기 위해 각 (1),(2),(3) 과정마다 세 registers 중 하나에만 control 신호를 1로 주고 나머지엔 모두 0으로 줬다.

나눗셈 연산도 (1) Write(Remainder) -> (2) Shift left(Q) -> (3) Shift Right(Divisor) 순으로 하나씩 control을 주면서 진행된다는건 같다. 하지만 중간에 2번 과정에서 "ALU로 주는 신호가 바뀔 수도 있다는 것""ALU로 다시 덧셈하면 2번에서 Write 신호도 1로 해둘 수 있다는 것"만 추가된다.

 

 

 

> 개선

곱셈에서처럼 얘도 개선할 여지가 있다. Quotient는 늘어나는데, Remainder는 줄어든다. 둘을 합치자.

Remainder가 좌측으로 shift되며 그 오른쪽 공간에 Quotient가 들어간다.(늘어난다.)

마찬가지로 주의할 점: ALU는 Remainder register의 위쪽 64비트에 작성한다. 그리고 그 위쪽 64비트가 그대로 올라가서 divisor와 연산을 한다.

연산 종류가 세가지가 있다. shift right는 거의 안쓰고, 우선 shift left랑 write만 쓴다.

 

이번에도 각 과정을 살펴보자. 곱셈과는 반대로 Remainder가 좌측으로 이동한다. 오른쪽에 새로 생기는 칸에 Q가 들어간다. 아래 1번과 2번이 병렬로 계속 반복 수행된다. 3번은 제일 마지막에 한번 실행된다.

1. AUL에 0을 주고, Write를 실행한다. (좌측 half에만 write됨)

2. MSB를 보고 1/0을 판단해,

        2a. 음수라면, ALU에 1을 주고 다시 Write한 후, Remainder 전체를 좌로 1칸 shift시켜서 끝에 0을 넣는다.

        2b. 양수라면, Remainder 전체를 좌로 1칸 shift시켜서 끝에 1을 넣는다.

3. 좌측 half만 우로 1칸 shift 시킨다.

 

> 3번 과정하는 이유?

revised 버전에서 연산 결과를 얻기 위해

곱셈은 Write -> Shift right를 Operands의 bit 수만큼 반복하고

나눗셈은 write -> shift left를 Operands의 bit 수 + 1 만큼 반복한다.

(revised 버전이 아니어도 반복횟수는, 곱셈은 operands 비트 수만큼 하면되지만, 나눗셈은 거기에 한번 더 해야한다.)

일단 왜 나눗셈은 한번 더 반복해야하는지는 위에서 고찰했던 것을 보고 오자. divisor을 128비트부터 잡고 하는 것은 사실 나눗셈을 한 자릿수 더 오버해서 시작하는 것이다. 위에서 봤듯이 나올 수 있는 경우 중 최대값은 127비트자리 수인데 128부터 빼기를 시작해봤자 어차피 음수이다. 그렇게 의미없는 단계로 시작하는 것을 고려해서, 한번 더 반복 해줘야 하는 것이다.

제일 처음 Q에 추가되는 비트 0은 Quotient 공간 범위를 벗어나는 놈이다. 위 <고찰>의 참고자료를 보면 우측 그림에서 제일 처음 적힌 1(빼보고 0으로 바뀔 놈)은 Q 범위인 4비트에서 벗어난다. 하지만 우리는 이놈도 Q에 밀어넣고 있는 것이다.

제일 처음 본 나눗셈 HW에서는 제일 처음 추가된 그 0Quotient가 좌측으로 shift되며 알아서 밀려 없어졌지만,

revised 버전에선 사라지지 않고 옆 remainder 공간으로 밀려간다.

이 의미없는 놈을 처리하기(없애기) 위해 우리는 마지막에 다시 high half에 대한 right shift 연산을 해주는 것이다.

 

> Signed Division

sign을 어디 저장해두고 sign과 magnitude는 따로 연산을 한 뒤 몫에 sign 결과를 반영시켜주면 된다.

 

> 추가

위 나눗셈 연산 하드웨어는 곱셈 연산 하드웨어와 거의 동일하다.
차이는 (1)shift left가 추가된 것과 (2)shift right가 곱셈에서와는 달리 high half만 shift right 시키는 연산이라는 것이다.
즉, 개선된 DIV 하드웨어에서 전체 shfit right 시키는 기능을 추가해준다면 아마 한 HW로 곱셈 나눗셈 둘 다 지원할 수 있을 것이다.(교수님 의견)

 

Mul에서처럼 adder를 붙여서 속도를 더 빠르게 하진 못한다. 매번 빼기를해서 판단해야하기 때문이다.

그래서 SRT division 기술 등을 통해 predict를 해서 좀 더 빠르게 진행할 수도 있다.(predict를 하면 look up table에 적당한 값이 있어야 빠르다.)

 

< 곱셈 나눗셈 연산 in HW >
머릿속으로 이미지화 해보면 위에서 본 두가지 방법 모두 그게 그거다.
일단 곱셈은 잘 생각해보면 계속 Mcand의 낮은 자리수쪽에 0을 밀어넣으며, 해당 숫자를 더할지 말지를 Mplier의 각 bit가 결정한다. Mul HW의 두가지 버전 모두 이 원리이다.
나눗셈은 Mul처럼 특정 값(Mplier)의 비트를 기준으로 연산을 할지 말지 판단하는게 아니라, 계속 같은 과정이 반복된다. Divisor가 가장 높은 자리(128-bit)부터 시작해서 낮은 자리로(우측으로) 쉬프트되며 빼기연산을 계속한다. 하다가 음수가 나오면 다시 더하기를 하고 Quotient에 0을 적고, 양수가 나오면 그대로 두고 Quotient에 1을 적는 것이다. Div HW의 두가지 버전 모두 이 원리이다.
이 기본 원리를 잊지 말자.

 

 

Division Instructions

`div` -> divide

`divu` -> divide unsigned

`rem` -> remainder

`remu` -> remainder unsigned

 

 

 

 

 

'수업 > Computer Architecture' 카테고리의 다른 글

[Computer Architecture] Processor - 1  (0) 2022.11.08
[Computer Architecture] Memory Hierarchy  (0) 2022.10.20
[Computer Architecture] Arithmetic for Computers(2)  (2) 2022.10.18
Language of the Computer  (5) 2022.09.14
Introduction  (3) 2022.09.13