2023. 3. 8. 16:58ㆍVerilog
반 가산기 설계
반가산기는 이전 자리수에서 올라온 올림수(carry)를 고려하지 않고 입력 두개만 고려하는 가산기를 반 가산기라 한다.
진리표 :
x | y | co | s |
0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 |
1 | 0 | 0 | 1 |
1 | 1 | 1 | 0 |
논리식
s = x ^ y; // sum
co = x & y; //carry out
모듈 정의
//1
module HA(
input wire x, y,
output wire co, s,
reg co, s,
);
//2
module HA(x,y,co,s);
intput x, y;
output co, s;
모듈은 c언어의 함수 선언과 유사하게 선언한다. 1번처럼 모듈을 선언할 때 내부 변수를 넣어도 되고 2번처럼 먼저 선언한 뒤 나중에 wire나 reg등을 따로 정의해도 된다.
모델링 방법
구조적 모델링
module HA(
input wire x, y,
output wire co, s,
reg co, s
);
and U1 (co,x,y);
xor U2 (s,x,y);
endmodule
구조적 모델링은 이미 설계해놓은 모델을 호출하는 모델링 방식이다.
데이터플로우 모델링
module HA(
input wire x, y,
output wire co, s,
reg co, s
);
assign s = x ^ y;
assign co = x & y;
endmodule
assign은 wire끼리 동작을 수행하게 저장한다. s = x^y를보면, x와 y라는 외부 입력을 연결한 전선을 XOR의 입력에 연결하고, s라는 모듈 외부로 나가는 전선을 XOR의 출력 부분에 연결했다는 뜻이다.
동작적 모델링
module HA(
input wire x, y,
output wire co, s,
reg co, s
);
always @(x or y) begin
s = x ^ y;
co = x & y;
end
endmodule
입력은 x, y의 외부 입력 2개를 받고, 출력으로는 co, s를 두개를 출력한다.
always 구문은 ()안의 신호가 변경될 때 내부 구문이 실행되는 함수와 유사한 구문이다. begin end는 c언어에서의 중괄호와 유사하다. x,y중 하나의 값이 바뀔 때마다 레지스터에 input으로 들어온 값을 저장한다.
진리표를 사용한 동작적 모델링
module HA (x, y, co, s);
input x;
input y;
output co, s;
reg co, s;
// 절차형 블록문, 진리표의 동작적 모델링
always @ (x or y)
case ({x,y})
2'b00 : {co,s} = 2'b00;
2'b01 : {co,s} = 2'b01;
2'b10 : {co,s} = 2'b01;
2'b11 : {co,s} = 2'b10;
default : {co,s} = 2'b00;
endcase
endmodule
위 코드는 진리표를 직접 코드로 구현한 코드이다. 위의 코드들을 분석하면 모두 같은 결과를 나타낸다
RTL ANALYSIS를 한 결과는 다음과 같다
반 가산기가 잘 나온 모습을 볼 수 있다.
시뮬레이션
Stimulus generator는 입력을 생성해주는 부분이고, Response Monitor는 테스트하는 모듈에서 나온 출력을 모니터하기위한 부분이다. 이 3개가 모여 하나의 테스트벤치를 구성한다.
`timescale 1 ns/1 ps // 시뮬레이션 시간 단위
module TB_HA; // 입력,출력 포트가 없다, 실제 입력 출력을 사용하지 않기 때문
reg x, y;
wire cout, sum;
// DUT instance
HA U1 (.x(x), .y(y), .co(cout), .s(sum) ); //테스트할 모듈을 불러옴, 객체지향 언어에서 객체를 호출할때와 유사하게 사용
// input stimulus
initial begin
x=0; y=0; #200; // 200 ns 지연
x=0; y=1; #200; // 200 ns 지연
x=1; y=0; #200;
x=1; y=1; #200;
$finish; // 시뮬레이션 종료
//$stop은 시뮬레이션 일시정지
end
endmodule
위의 코드는 HA를 불러와 200ns간격으로 입력을 바꿔 출력을 보는 방법이다.
run simulator를 누르면 다음과같이 리스폰스 모니터가 나온다. 결과가 잘 나오는 것을 볼 수 있다.
베릴로그 문법
1.기본 특성
1. 대소문자 구별
2. 여백은 어휘 토큰을 분리하기 위해 사용되는 경우 제외하고는 무시하고, 문자열에서는 무시하지 않는다.
3. 주석은 c언어와 동일하다.
4 식별자도 대부분의 프로그래밍 언어와 동일한데, 대소문자 구별, 밑줄(_)사용 가능하다. 첫번째 문자로는 _나 영문자 대소문자만 가능하다.
2.논리값과 숫자
베릴로그에서는 0, 1, Z, X 4개의 논리값을 사용한다. 이중 Z, X는 시뮬레이터에서만 사용한다.
논리값 | 설명 |
0 | low, false |
1 | high, true |
z | high impedance, open |
x | unknown |
내부 시뮬레이션에서는 좀더 다양한 값들을 사용할 수 있다.
정수 표현
숫자는 [부호][비트수]['진수][값]의 형식으로 표시한다. 예를들어 2비트의 이진수 10을 표시하려면 다음과 같이 표현한다.
'는 작은따옴표이다.
2'b10
1250 //부호 있는 10진수
'o765 //부호 없는 8진수
'sHab74 //부호 있는 16진수
'b1100_111 // 부호 있는 2진수
'bz // z...z 32비트 unsigned값은 크기를 지정하지 않으면 32비트로 동작
실수 표현
0~9자리만 사용가능하다.
0.5
689.00123
3e4
5.6E-2
문자열
""사이에 있는 문자들을 문자열이라 한다. 문자열은 단일 라인에 사용되어야 하며, 여러 라인에 걸치면 사용 불가능한다.
내부적으로 ASCII 값으로 표현되는 unsigned 상수로 취급된다.
문자열은 reg형의 변수이고, 문자 수 * 8비트의 크기를 가진다.
3. 모듈
모듈은 설계의 기본 단위로 다음과 같은 구조를 가진다.
module HA( //모듈 정의
input x, //모델 내부로 들어가는 wire
input y,
output reg co, s //모델 외부로 나가는 reg
); //세미콜론 주의
//모듈 item이 들어가는 자리
always @(x or y) begin
s = x ^ y;
co = x & y;
end
endmodule //세미콜론을 붙이지 않는다.
모듈 정의내부에 포트목록을 추가할 수도 있고 정의 다음줄에 포트를 선언할 수도 있다.
포트를 선언할 때는 [포트의 방향] [포트신호의 타입][포트신호의 이름] 으로 선언한다.
포트의 방향은
종류 | 방향 |
input | 외부->내부 |
output | 내부->외부 |
inout | 두 방향 모두 가능 |
포트의 신호는 다음 표와 같고 생략시 wire로 선언된다. c언어처럼 같은 타입의 포트는 ,로 연결해 동시에 선언 가능하다.
타입 | 역할 |
reg | 1비트 값을 저장하는 신호(변수와 유사) |
wire | 1비트 값을 전달하는 신호선 |
reg[msb : lsb] | 벡터 레지스터 신호 |
wire[msb : lsb] | 벡터 레지스터 wire |
reg signed[msb : lsb] | 부호가 포함된 벡터 레지스터 신호 |
wire signed[msb : lsb] | 부호가 포함된 벡터 레지스터 신호선 |
아래와 같이 벡터를 선언할 때는 상수 표현식을 사용 가능하고, 상수 함수도 사용 가능하다.
모듈의 몸체에는 Task, function 등이 모듈의 몸체를 구성된다.
모듈의 모든 구성요소는 선언 순서가 중요하지 않다.
4. 데이터 형식
데이터 타입에는 크게 2가지가 있는데, net 형식과 variable이 있다.
net 형식은 continuous assign문이나 하위 모듈들의 구성 요소들의 포트를 연결하는 역할을 한다. 쉽게 생각해서 하드웨어의 전선 역할을 하는 변수 타입이라 생각하면 된다.
키워드 | 정의 |
wire, tri | 일반적인 net 형식 |
wor, trior | or연산을 하는 wire |
wand, triand | and연산을 하는 wire |
trireg | 값을 저장하는 net |
tri1 | net의 기본값이 1 |
tri0 | net의 기본값이 0 |
supply1 | Vdd |
supply0 | Gnd |
Variable형식은 always나 initial 블록 내에서 assign문에 의해 할당받은 값을 기억할 수 있는 데이터 타입이다.
기본적으로 모두 unsigned 타입이다.
키워드 | 정의 |
reg | 1비트 값을 저장 |
integer | 여러배수 크기의 정수값 |
real | 부동소수점값 |
time | 시뮬레이션 시간값 |
논리 합성이 가능한 회로에서 주로 사용되는 net 과 variable은 wire과 reg이다.
wire은 다음과 같이 continuous assignment를 사용해야 한다.
wire sum;
wire v1, v2, v3;
assign sum = v1|v2|v3;
wire d, e, f; // 3개의 1 비트 스칼라 net
tri1 [7:0] dbus; // 3 상태일 때 pull-up인 8 비트 net
wire [1:0] sel = 2'b00; // 초기값을 가지는 와이어, 초기값은 시뮬레이션용
wire signed [1:12] res; // 12 비트 signed net
wire [7:0] A[0:15][0:255]; // 8 비트 와이어의 2차원 배열 -- 동영상 설명에 행열 순서 오류
wire #(2.4,1.8) ripple; // rise 및 fall delay를 가지는 net
wire [0:15] (strong1, pull0) sum = a + b; //1이 될때는 strong형식으로, 0이될때는 pulldown형식으로
// strength를 가진 16 비트 net 선언과 연속 할당으로 값을 전달
trireg (small) #(0,0,35) ram_bit; // decay 타임이 35가 지나면 tri상태가 된다.
아래 표는 하드웨어 합성시에는 사용되지 않지만 시뮬레이터에는 사용될 수 있다.
reg는 다음과 블록 내의 assignment를 사용한다.
reg carry;
always @ (a,b,c)
carry = a | b | c;
reg d, e, f; // 3개의 스칼라(1 비트) 변수
reg signed [12:0] a1, a2; // 2개의 13 비트 signed 변수
reg [7:0] A[0:3][0:15]; // 8비트의 2차원 배열 변수 동영상의 배열 행렬 설명 오류
integer i, j; // 2개의 signed 정수 변수
real r1, r2; // 2개의 배정도(double-precision) 실수 변수
reg clk = 0, rst = 1; // 초기값을 가지는 2개의 reg 변수, 시뮬레이션을 위한 초기값 설정
realtime rtime; // 실수값을 가지는 time 변수
time save_stime; // time 변수
initial
save_stime = $time; // $time은 현재 시뮬레이션 시간을 반환하는 시스템 함수
reg [7:0] A[0:3][0:15]는 한 셀당 8비트 값을 가지는 2차원 배열 형식이다.
reg변수가 항상 하드웨어적인 저장장소를 의미하는것은 아니다. always에 클럭이 들어가지 않으면 조합논리회로로 동작되고, 클럭이 들어가면 저장장소로 동작한다. 아래 코드는 저장장치가 합성된다.
module mydff (clk, d, q);
input d, clk;
output q;
reg q;
always @(posedge clk)
q <= d;
endmodule
아래 코드는 저장장치가 합성되지 않고 단순 조합회로로 동작한다.
module mux2x1(a, b, sel, out);
input [1:0] a, b;
input sel;
output [1:0] out;
wire [1:0] a, b;
reg [1:0] out;
always @(a or b or sel)
if (sel == 1'b0)
out = a;
else
out = b;
endmodule
기타 형식의 선언 방식은 다음과 같다.
#delay는 시뮬레이터에서 사용하며, net형에서만 사용한다.
#decay_time은 trireg net이 off로 바뀐 후 논리 LOW로 방전되기 전 정보를 저장하는 시간이다. (rise_delay, fall_delay, decay_time)으로 사용한다.
[size]는 [msb : lsb] 형식으로 사용하며, 벡터 형식이며, reg[7:0]과 같이 사용하면 크기를 설정할 수 있다.
[lsb : msb]로 사용하는 것은 배열(array)라 하며, 메모리의 크기를 표현하는데 사용한다.
reg mem1bit[0:1023]; // 1K bit memory
reg [7:0] membyte [0:1023]; //1KB memory, 1024 * 8 bit
reg [63:0] word64mem [0:511]; // 64 bits * 512 words
reg [7:0] data_reg; // single byte register
reg status_bit;
initial begin
data_reg = membyte[211]; // 211번지의 한 바이트를 읽어서 data_reg에 할당
status_bit = data_reg[3]; // data_reg의 4번째 비트를 읽어서 status_bit에 할당
end
net과 variable 말고 다른 데이터 형들은 아래 표와 같다.
키워드 | 기능 |
parameter | 정수, 실수, time, 지연시간 등을 저장하는 실행 시간 상수이고, 파라미터 값은 모듈마다 달라질 수 있다 |
localparam | 정수, 실수, time, 지연시간 등을 저장하는 지역 상수 |
specparam | 정수, 실수, time, 지연시간 등을 저장하는 특정 블록 상수 |
genvar | generate loop에서 사용되는 임시 변수로 다른 장소에서는 사용 불가능하다. |
event | 논리값이나 데이터 저장소를 가지지 않는 순간적인 flag이다. |
아래에 파라미터와 다른 데이터 형식들의 선언 예이다.
parameter [2:0] s1 = 3'b001, // 3 개의 3 비트 상수 선언
s2 = 3'b010,
s3 = 3'b100;
parameter integer period = 50; // 정수 상수
localparam signed val = -8; // unsized signed 상수, 초기값으로 크기 결정
event hs_rdy, hs_sent; // 2개의 event 데이터형
parameter msb = 15; // msb를 상수 15로 정의
parameter a = 15, b = 180; // 2개의 상수 정의
parameter r1 = 25.7; // 실수 파라미터 정의
parameter size = 8, mask = size - 1;
parameter p_delay = (r + f) / 2; // 상수 수식 정의
parameter signed [3:0] sel = 0; // 벡터 상수 정의와 초기값 할당
parameter real r2 = 3.41e8;
parameter p1 = 16'h7a5;
parameter [31:0] f_const = 1'b1; // 32 비트로 변환
parameter s_const = 3'h4; // [2:0]의 암시적 사이즈
파라미터 정의, 사용 예
module myxnor (y_out, a, b);
parameter size=8, delay=15;
output [size-1:0] y_out;
input [size-1:0] a, b;
wire [size-1:0] #delay y_out = a ~^ b; // bit-wise xnor with delay
endmodule
module my_param;
wire [7:0] y1_out;
wire [3:0] y2_out;
reg [7:0] b1, c1;
reg [3:0] b2, c2;
myxnor G1 (y1_out, b1, c1); // use default parameters
myxnor #(4, 5) G2 (y2_out, b2, c2); // overrides default parameters
endmodule
//파라미터를 사용해서 같은 모듈을 각각 다른 특성을 가지게 할 수 있다.
5. 벡터, 배열
벡터는 reg나 wire형의 다중 바트 선언에 사용된다. 다음과 같이 사용된다.
wire [7:0] bus;
wire [31:0] busA, busB; // 32 비트 벡터, MSB=31
reg [0:40] addr; // 41 비트 벡터, MSB=0
busA[7]; // 벡터 부분 사용 예, 7번 비트만 사용
busB[2:0]; // 벡터 slice, 0~2번 비트만 사용
addr[0:1];
배열은 다른 언어에서의 배열과 동일하게, 데이터의 1차원 또는 2차원으로 정의한다.
배열은 배열 전체 또는 일부는 단일 할당문에 의해 값을 할당받을 수 없다. 배열을 구성하는 요소에만 단일 할당문으로 값을 할당받을 수 있다. 쉽게 말하면 배열의 전체나 어떤 구간을 정해서 값을 지정할 수 없고 한번에 하나의 요소만 콕 찝어서 할당해야 한다.
reg [7:0] mema[0:255]; // 256개의 8비트 메모리 레지스터, mema
reg memb[7:0][0:255]; // 1비트 레지스터의 2차원 배열
wire w_array[3:0][7:0]; // 1 비트 와이어의 2차원 배열
integer id[1:64]; // 64개 정수의 1차원 배열
time t_hist[1:1000]; // 1000개 time의 1차원 배열
mema = 0; // 오류 구문 - 전체 배열에 쓰기 불가
memb[1] = 0; // 오류 구문 - 배열 다중요소, [1][0]..[1][255] 에 쓰기 불가
memb[1][12:31] = 0; // 오류 구문 – 배열 다중요소, [1][12], [1][13], .. , [1][31]쓰기 불가
mema[1] = 0; // mema의 두번째 원소에 8비트 값, 0 할당
memb[1][0] = 0; // memb[1][0] 원소에 1비트 값, 0 할당
id[4] = 33559; // 정수배열 원소에 10진수 할당
t_hist[t_index] = $time; // 정수인덱스, t_index로 접근된 원소에 현재 시뮬레이션 시간 할당
6. 연산자
대부분은 c언어와 동일하지만, 약간의 차이는 존재한다.
산술 우측 연산 연산은 unsigned일때는 논리 쉬프트와 동일하지만, signed일때는 빈칸을 부호피트로 채우게 된다.
8비트 레지스터의 값이 11011001일때, 우측으로 3번 산술 이동하면 11111011이된다.
비트 단위 &, | ^는 비트 단위로 논리 연산을 하는 연산자이고, y=&a같은 형태로 사용하게 되면 a의 비트들을 모두 and하여y로 보낸다는 뜻이다.
결합 연산자는 각각의 비트를 묶는 연산자이다. y = {1,0}은 y = 2'b10으로 사용하겠다는 뜻이다.
결합 연산자를 반복하여 계속 결합할 수 있는데, 다음과 같은 예제에서 사용한다.
reg[WD-1:0] qout;
if (!rst) begin
qout <= {WD{1'b0}}; //0을 WD만큼 만든다 WD = 4이면 0000으로 만든다
end
조건 연산자는
assign y = (aaa) ? da : db;
c언어와 사용 방법이 동일하다. aaa가 1이 아니면 da, 0이면 db값을 내보낸다.