강의일: 4월 10일 FL (4주차)
학습목표: 디지털 회로에서 알고리듬을 표현하려면 알고리듬에서 사용한 연산을 회로로 표현할 수 있어야 한다. 따라서 디지털 하드웨어에서 구현 가능한 연산이 무엇이 있는지 익히는 건 중요하다.
C 랭귀지로 구현한 건 CPU에 의해 동작한다.
하드웨어 프로그래밍으로는 어떻게 각 연산이 구현될까?
-
Verilog의 연산자들
-
power 연산, 몫(/)과 나머지(%) 연산 같은 건 이에 상응하는 하드웨어가 존재하지 않는다. 그래서 하드웨어로 이게 어떻게 구현될지 고민해야 한다. 빼기의 반복으로 나누기를 만드는 식으로... 직접 구현해주어야 한다.
-
modulo는 조건부로 합성이 될 때도 있다.
-
이 표에는 없지만 수업자료에는 ===, !==라는 case 등가, case부등 연산자도 있는데 이것 역시 합성용은 아니다. 테스트용 문법 중 하나임.
-
-
실수 형에 쓸 수 있는 연산자
- 일단 실수는 실제 회로에는 합성하지 못한다는 걸 꼭 기억하자.
- C언어와 파이썬에서 쓰는 연산자들은 거의 다 됨. 다만 shift 연산자, 비트 연산자는 불가.
-
연산자 우선순위
대충 이런 식이다. 참고로 **은 단항 산술연산자 바로 밑 2순위다.
근데... 굳이 외우지 말고 그냥 괄호 꼬박꼬박 잘 쳐주면 된다.
-
수식에서의 정수형 수의 저리
integer IntA, IntB, IntC, IntD; // 32-bit signed integer IntA = -12/4; // -3 IntB = -'d12 / 4; // 1073741821 // unsigned에 음수 저장은 불가하나 일단 앞에-를 붙였으니 2의 보수 연산을 해주긴 함. // 그러나 저장은 unsigned로서 저장했기 때문에 나누기 연산을 음수로서 해주지 않음 // signed/unsigned가 저장할 땐 동일하나 연산할 때는 서로 다른 것이 나타나는 예제임 IntC = -'sd12 / 4; // -3 IntD = -4'sd12 / 4; // 1 // IntD 해설: // -4'sd12는 1100의 보수, 즉 0100 = 4 이므로 4로 나눈 결과가 1이 됨.
흔히 말하는 사칙 연산을 수행하는 연산자들.
-
산술 연산자 관련 유의할 점 몇가지
-
결과가 x(unknown)로 나오는 경우 :
- 피연산자의 비트에 x나 z가 포함됨
- N / 0 또는 N % 0 연산
-
% 연산의 결과의 부호는 첫번째 피연산자의 부호를 따른다.
-
** 연산에서 결과가 정의되지 않는 경우 :
- 0 ** <음수>
- <음수> ** <실수>
-
그 외 합성 조건들 (물론 상수 끼리 연산한 결과는 얼마든 합성 가능함) :
- /(몫)과 %(나머지) 연산의 경우 조건부1 합성가능
- ** (거듭제곱)은 피연산자 중 하나라도 변수가 있으면 합성 불가능.
-
-
산술 연산자에서의 자료형 해석
- signed : signed net, signed reg, integer, real, realtime
- unsigned : unsigned net, unsigned reg, time
"크냐?" "작냐?"를 비교하는 연산자이다. (>, <, >= ,<=)
- 산술연산자보다 우선순위 떨어진다. 예를 들어 b - (1<a) 와 b-1<a는 다르다.
- 결과가 x가 나오는 경우 :
- 피연산자의 비트에 x나 z가 포함됨
- 두 피연산자의 비트 수가 다른 경우 :
- 비트수가 큰 쪽에 맞춰 작은 애가 MSB 쪽에 0을 채운다. 그 후 관계 판단.
- signed인 경우, MSB를 확대해서 부호가 안 바뀌도록 한다. (signed extention)
- 피연산자 중 하나가 실수형 : 다른 피연산자를 실수형으로 변환한 후 비교
"같은가?" 를 판단하는 연산자
-
관계연산자보다 우선순위 떨어진다.
-
결과가 x가 나오는 경우 :
- Case Equality 연산자가 아니고, 피연산자의 비트에 x나 z가 포함됨
-
두 피연산자의 비트 수가 다른 경우:
- 비트수가 큰 쪽으로 맞춰서 작은 쪽을 MSB 방향으로 0을 채워줌. 그 후 등가 판단.
-
Case Equality 연산자(===, !==)
- 논리합성불가. test하는 경우에만 써야 한다.
- x와 z가 포함된 일치 불일치 판단.
- 테스트 상황에서 unknown이나 high-z가 나오는지, 에러 상황 등을 체크하고 싶을 때 쓰는 편이다.
다음은 등가 연산자 사용지 주의할 만한 상황이다.
// D = 4'b1001, F = 4'b1xxz라고 하자.
/* 일반 등가 연산자 */
if(D!=F)
#10 A = 0;
else
#10 A = 1;
// 이 때는 D!=F 연산의 결과가 x이기 때문에, else 실행문으로 빠져서 결과는 A=1
/* Case 등가 연산자 */
if(D!==F)
#10 A = 0;
else
#10 A = 1;
// 이 경우엔 x와z도 비교 대상으로서 끼워주므로, D!==F가 True라서 결과는 A=0"참이냐?" "거짓이냐?"를 판단함.
- 0이면 거짓, 그 외의 수는 다 참이다.
- 결과가 x가 되는 경우:
- 참 / 거짓 판단이 모호함 - 논리 연산자의 피연산자에 x가 섞인 수
대충 이런 식으로 3항을 엮어도 된다는 것 같다.
(a < size-1) && (b != c) && (index != last_one)-
피연산자의 해당 비트들에 대한 연산 수행
- 피연산자의 비트 수만큼 결과 출력함
-
두 피연산자의 비트 수가 같지 않으면:
- 비트 수가 작은 피연산자의 MSB위치에 0 채워준 후 연산함
-
~ (NOT) ^(XOR) &(AND) |(OR)
-
(a ~^b ) _NOR_는 되는데 ~& (NAND), ~| (NOR)같은 축약은 안 됨. ~(a&b)나 ~(a|b)로 표현해야 한다고 한다.
-
비트 AND 연산자
& 0 1 x z 0 0 0 0 0 1 0 1 x x x 0 x x x z 0 x x x 0의 AND는 무조건 0. 나머지는 1&1 제외 전부 x
-
비트 OR 연산자
| 0 1 x z 0 0 1 x x 1 1 1 1 1 x x 1 x x z x 1 x x 1과의 OR은 전부 1. 나머지는 0|0 제외 x
-
비트 XOR 연산자
^ 0 1 x z 0 0 1 x x 1 1 0 x x x x x x x z x x x x x와 z가 섞이면 죄다 결과는 x
-
비트 NOT
~0 = 1, ~1 = 0, ~x = x, ~z = x
-
단항 연산자이다.
-
한 피연산자가 가진 비트 끼리끼리 연산을 함.
reg [7:0] cnt; assign parity = ^cnt; assign parity = cnt[7]^cnt[7]^cnt[5]^cnt[4]^cnt[3]^cnt[2]^cnt[1]^cnt[0] // 위 두 구문은 같은 의미다.
또한 이 경우엔 피연산자가 하나 뿐이라 ~&, ~| 같은 표현도 가능.
-
어떨 때 이런 연산자를 쓸까?
- 모든 비트가 0인지(~|) / 모든 비트가 1인지(&) / 1의 개수가 짝수 개인지 (~^) / 1의 개수가 홀수 개인지(^) 확인할 때 유용하다.
-
논리 시프트 연산자
- <<: 우측 피연산자 값만큼 좌측 피연산자를 왼쪽으로 시프트 후, 비어있는 비트에 0을 채움
- >>: 위에서 왼쪽 -> 오른쪽
-
산술 시프트 연산자(arithmetic shift) : 논리 시프트 연산자의 signed 고려 버전. 비트가 아닌 숫자로서 인식한다.
- <<< : <<와 비슷하나 MSB는 시프트되지 않음.
- >>> : 위에서 왼쪽 -> 오른쪽
-
우측 피연산자 (얼만큼 이동시키는지 나타내는 지표)
- 합성 가능하려면 상수로 써야 함. 변수를 넣어도 되긴 하는데 합성은 안 된다.
- x 또는 z가 포함된 경우, 시프트 연산의 결과값은 x
- 항상 unsigned 수가 들어감.
-
예문
module shift_op (): wire signed [7:0] data1 = 8'b1010_1100; wire [7:0] data2 = 8'b1000_1111; // unsigned reg [7:0] out1, out2; // unsigned always @(data1, data2) begin out1 = data1 >>> 2; // out1 : 1110_1011 out2 = data2 >>> 2; // out2 : 0010_0011 #100 $stop; end endmodule // out1은 us임에도 불구하고 data1>>>2는 s연산을 함. // 피연산자의 자료형이 결정한다는 사실!
- C에서의 삼항 연산자와 동일하다. 신호처리 쪽으로는 multiplexing 개념으로 이해할 수도 있다.
- 만약 ( )? 안의 값이 참/ 거짓이 아니라 x또는 z라면 ( ) : ( ) 양쪽의 결과를 합쳐서 내놓는다. 즉, 잘못된 결과가 나온다.
- 합치는 방식: 양쪽 결과항을 서로 비교하며, 같은 자리 수의 비트가 둘다 0이면 0, 둘다 1이면 1, 나머지 모든 상황에선 x로 결과가 나온다.
wire [15:0] buss, data;
// ...
assign buss = drive_buss ? data : 16'bz
// 요게 대강 버스를 위한 삼상회로의 구현방법이다.내가 a라는 3비트 데이터와 b라는 4비트 데이터를 7비트 데이터로서 묶어서 쓰고 싶다고 생각해보자. 그럴 때 중괄호 { } 로 묶어주면 된다.
-
조건
- 결합되는 피연산자들의 비트가 결정되어있어야 함 (unsized 상수는 결합 연산자 사용 불가)
-
대입문의 좌측 또는 우측에 사용 가능
-
비트 폭이 일치하지 않는 변수의 연산 / 대입
- 우변의 비트 폭이 작다면, 우변의 MSB에 0을 붙여 연산(signed의 경우 MSB 부호 유지)
- 좌변의 비트 폭이 작다면, 일단은 합쳐 놓고, 좌변에 맞춰 MSB 누락.
-
사용예
-
대입문의 우측
wire [15:0] addr_bus; wire [7:0] addr_h, addr_l; //... assign addr_bus = {addr_h, addr_l}
-
대입문의 좌측
wire [3:0] a, b, sum; wire carry; //... assign {carry, sum} = a+b;
-
굉장히 유용하고, 많이 애용해도 되는 합성 가능 연산자이다.
- 형태: {a{b}} b를 a회 반복해준다. (반복 횟수는 0, x, z가 아닌 상수)
{4{w}} // {w, w, w, w}와 동일한 표현이다
{b, {3, {a, b}}} // {b, a, b, a, b, a, b}
a[31:0] = {32{1'b1}};
// 이렇게 하면 많은 비트 공간에 전부다 1을 채워넣는 뭐 이런 게 가능함.Footnotes
-
분모가 2의 거듭제곱. 비트(이진수) 단위로 잘라냄으로써 하드웨어에 구현할 수 있기 때문이다. 즉 a/2^n 은 a>>n 과 동일하고, a%2^n은 하위 n비트를 선택하는 것과 같다. ↩
