베릴로그로 4비트 덧셈과 뺄셈을 구현해봤다.
그러기 위해서는 1비트 단위의 덧셈부터 구현해야 하는데, 이는 위키피디아에 잘 정리되어 있다.
https://en.wikipedia.org/wiki/Adder_(electronics)
Adder (electronics) - Wikipedia
From Wikipedia, the free encyclopedia Jump to navigation Jump to search Digital circuit that produces sums from inputs An adder, or summer,[1] is a digital circuit that performs addition of numbers. In many computers and other kinds of processors adders ar
en.wikipedia.org
이를 토대로 Full Adder를 아래와 같이 설계했다.
module FA1(
output cout,
output sum,
input a, b, cin
);
wire a_xor_b = a ^ b;
wire abc = a_xor_b & cin;
wire a_and_b = a & b;
assign cout = a_and_b | abc;
assign sum = a_xor_b ^ cin;
endmodule
module FA2(
output cout,
output sum,
input a, b, cin
);
wire a_xor_b, a_and_b, abc;
xor xor0(a_xor_b, a, b);
xor xor1(sum, a_xor_b, cin);
and and0(a_and_b, a, b);
and and1(abc, a_xor_b, cin);
or or0(cout, abc, a_and_b);
endmodule
두 모듈은 완전히 똑같은 모듈이다.
4비트 덧셈은 앞의 링크에서도 볼 수 있는 사진처럼 구현이 가능하다.
이를 코드로 구현하면 아래와 같다.
module adder_4b_RCA( // Ripple Carry Adder
output cout, // carry out
output [3:0] sum,
input [3:0] a, b,
input cin // carry in
);
wire [2:0] carry;
FA1 fa0(carry[0], sum[0], a[0], b[0], cin);
FA1 fa1(carry[1], sum[1], a[1], b[1], carry[0]);
FA1 fa2(carry[2], sum[2], a[2], b[2], carry[1]);
FA1 fa3(cout, sum[3], a[3], b[3], carry[2]);
endmodule
이렇게 구현하는 것을 Ripple Carry Adder라고 부른다.
RCA의 장점은 Full Adder를 계속 연결해주면 몇비트건 간에 연산이 가능하기 때문이다. 단점은 carry propagation이 느리기 때문에 다른 더 나은 가산기 설계보다 시간이 오래 걸리게 된다.
이제 뺄셈을 구현해보자.
4비트 뺄셈 구현에 앞서, 뺄셈이 어떻게 연산되는지 먼저 아는게 이해에 도움이 된다.
2진수에서 뺄셈은 2의 보수를 더하는 것이다. 2의 보수는 2진수의 모든 자릿수를 반전(not 연산)한 후 1을 더해주면 된다. 즉 a와 b에 대해 a-b는
a + ~b + 1
이다.
이것을 앞에서 본 가산기로 구현하기 위해서는 b의 반전 및 1을 더해주는게 필요하다.
단순히 1을 더하는 것은 cin에 1을 넣는것과 같기 때문에 뺄셈을 할 경우 cin을 1로 만들어주면 된다.
그래서 아래와 같이 구현했다.
module add_sub_4b( // use MODE instead of cin
output [3:0] sum,
input [3:0] a, b, // a+b / a-b
input MODE // 0:add, 1:sub
);
wire [3:0] b_in;
wire [2:0] carry;
wire cout;
assign b_in = MODE ? ~b : b;
FA2 fa0(carry[0], sum[0], a[0], b_in[0], MODE);
FA2 fa1(carry[1], sum[1], a[1], b_in[1], carry[0]);
FA2 fa2(carry[2], sum[2], a[2], b_in[2], carry[1]);
FA2 fa3(cout, sum[3], a[3], b_in[3], carry[2]);
endmodule
cin 대신 MODE라는 입력으로 2의 보수를 구현했다.
해당 모듈에서는 cout을 출력으로 정의하지 않았는데, 이유는 4비트끼리 뺄셈에서 5비트 결과를 내놓기 위해서는 sign extension이 필요하기 때문이다.
sign extension은 비트수를 늘릴때 MSB(Most Significant Bit)를 늘린 비트수만큼 MSB 앞에 채워놓는 것이다.
예를 들어 7 - 7을 4비트로 연산한다 치면, 0111 + 1001 = (1)0000인데, 괄호친 1까지 출력으로 내놓으면 오버플로우가 생기기 때문이다.
하지만 5비트 sign extension을 하면 00111 + 11001 = 00000 이 된다.
베릴로그에서는 덧셈결과가 입력 비트수보다 클 경우 자동적으로 sign extension을 하게 된다. sign extension은 결과 비트수만큼 채운다. 이때 자료형이 signed일 경우 MSB대로, unsigned일 경우 0을 채워넣는다.
이를 보여주기 위해 아래와 같이 테스트벤치를 작성하였다.
module tb_addsub();
reg [3:0] a, b;
reg signed [3:0] as, bs;
reg MODE;
//
reg [3:0] refsum;
reg signed [3:0] refsum_s;
reg [4:0] extended_sum;
reg signed [4:0] extended_sum_s;
wire [3:0] sum, sum_s;
wire [3:0] sub, sub_s;
wire cout0, cout1;
adder_4b_RCA UNSIGNED0(cout0, sum, a, b, 1'b0);
adder_4b_RCA SIGNED0(cout1, sum_s, as, bs, 1'b0);
add_sub_4b UNSIGNED1(sub, a, b, MODE);
add_sub_4b SIGNED1(sub_s, as, bs, MODE);
initial begin
a = 4'd5; as = 4'd5; // 4'b0101
b = 4'd9; bs = -4'd7; // 4'b1001
MODE = 1'b0;
refsum = a + b;
extended_sum = a + b; // 00101 + 01001
refsum_s = as + bs;
extended_sum_s = as + bs; // 00101 + 11001
#10
MODE = 1'b1;
refsum = a - b;
extended_sum = a - b; // 00101 - 01001
refsum_s = as - bs;
extended_sum_s = as - bs; // 00101 - 11001
#10
a = 4'd11; as = -4'd5; // 4'b1011 both
b = 4'd7; bs = -4'd9; // 4'b1100 both
MODE = 1'b0;
refsum = a + b;
extended_sum = a + b; // 01011 + 01100
refsum_s = as + bs;
extended_sum_s = as + bs; // 11011 + 11100
#10
MODE = 1'b1;
refsum = a - b;
extended_sum = a - b; // 01011 - 01100
refsum_s = as - bs;
extended_sum_s = as - bs; // 11011 - 11100
#10
$finish;
end
endmodule
해당 테스트벤치를 시뮬레이션 해보면 아래와 같은 파형을 볼 수 있다.
다른건 다 똑같은데 extended_sum와 sum_extended만 다른 것을 볼 수 있다.
extended_sum은 테스트벤치에 있는 주석대로 sign extension이 되기 때문이고, sum_extended는 4비트만 더해주는 모듈을 사용했기 때문이다.
wire나 reg는 기본적으로 unsigned이기 때문에 비트를 확장할 경우 0으로 채운 채 더하게 된다.
sign extension은 곱셈에서도 똑같이 적용된다. 결과 비트수를 바탕으로 입력들을 extension한 후, 곱셈을 수행한 다음, 결과 비트수만큼만 값을 가져온다.
그래서 산술 연산자로 연산을 수행할 경우 결과 비트수에 맞춰서 결과를 생각해봐야 한다.
'Verilog HDL 설계' 카테고리의 다른 글
Deinterleaver 심화 - mod 1536 (0) | 2022.07.07 |
---|---|
Discrete Cosine Transform 구현 (2) | 2022.06.30 |
include의 필요성 및 사용법 (0) | 2022.04.12 |
파라미터를 이용한 모듈 선언 (0) | 2022.04.07 |
Simple Finite State Machine implementation (0) | 2021.04.29 |