2023. 3. 10. 11:58ㆍVerilog
가산기
`timescale 1ns / 1ps
module FA(
input x, y, z,
output c,s
);
wire w1,w2,w3;
xor(w1, x, y);
xor(s, w1, z);
and(w2,z,w1);
and(w3,x,y);
or(c,w2,w3);
endmodule
1비트 가산기 모듈을 상위 모듈에서 불러 사용한다.
module FA_4bit(
input [3:0] x,y,
input ci,
output co,
output [3:0] s
);
wire [3:1] tc;
FA A1 (.x(x[0]), .y(y[0]), .z(ci), .c(tc[1]), .s(s[0]));
FA A2 (.x(x[1]), .y(y[1]), .z(tc[1]), .c(tc[2]), .s(s[1]));
FA A3 (.x(x[2]), .y(y[2]), .z(tc[2]), .c(tc[3]), .s(s[2]));
FA A4 (.x(x[3]), .y(y[3]), .z(tc[3]), .c(co), .s(s[3]));
endmodule
VIVADO IDE로 RTL분석을 한 결과는 위와 같다.
2. 디코더에 대한 구조적 모델링
...
3. 래치에 대한 구조적 모델링
래치는 클록과 상관없이 clk신호가 1이면 동작하고, 0이되면 동작하지 않는 저장 소자의 일종이다.
플립플롭과 구조가 유사하며 플립플롭과 마찬가지로 D, SR, JK래치가 있다.
....
4. 파라미터
파라미터는 모듈에서 사용할 상수나 표현식에 대한 이름 정의를 말하며, 모듈 인스턴스에서 재정의 가능하다.
C언어에서의 #define혹은 변수와 비슷한 역할을 한다.
아래와 같이 벡터나 배열형식의 크기를 지정할 때 사용한다.
5. 지연시간
`timescale 1ns / 1ns
module gate_delay ;
reg a, b, c, d ;
wire y1 , y2 , y3 ;
and #20 (y1 , a, b) ;
or #(20 ,40 ) (y2 , a, b) ;
notif1 #(20 ,40 ,60 ) (y3 , c, d);
initial begin
a = 1 ; b = 1 ; c = 1 ; d = 1 ;
#100 a = 0 ; b = 0 ; c = 0 ; d = 1 ;
#100 a = 0 ; b = 1 ; c = 1 ; d = 0 ;
#100 a = 1 ; b = 0 ; c = 0 ; d = 0 ;
#100 a = 1'bx ; b = 1'bx ; c = 1'bx ; d = 1 ;
end endmodule
지연시간은 시뮬레이션의 신호의 시간을 바꿀 수 있는 함수이다. 실제 하드웨어에서 소자에 의한 지연시간은 항상 발생하기 때문에 시뮬레이션에서 이를 반영할 필요가 있다. 각 소자가 가지는 지연 시간을 실제 값에 근사하게 규정할 때 정확한 동작을 예측할 수 있다.
위 코드의 시뮬레이션 결과는 다음과 같다.
y1은 a,b가 and된 연산이 20ns만큼 딜레이되어 나타나고, y2도 딜레이 타임이 20ns이후로 나타나고, y3은 3상 버퍼를 통과한 연산의 fall 딜레이가 40ns 지연된 채로 나타난다.
6. 인스턴스 배열
8비트 레지스터의 2가지 구조적 모델링 비교
module dreg_a
#(parameter size = 8)
(output wire [size-1:0] q,
input [size-1:0] d,
input clk, clr);
// 인스턴스 배열
dff1 u[size-1:0] (clk, clr, d, q);
endmodule
module dreg8
(output wire [7:0] q,
input [7:0] d,
input clk, clr);
dff1 u7 (clk, clr, d[7], q[7]);
dff1 u6 (clk, clr, d[6], q[6]);
dff1 u5 (clk, clr, d[5], q[5]);
dff1 u4 (clk, clr, d[4], q[4]);
dff1 u3 (clk, clr, d[3], q[3]);
dff1 u2 (clk, clr, d[2], q[2]);
dff1 u1 (clk, clr, d[1], q[1]);
dff1 u0 (clk, clr, d[0], q[0]);
endmodule
인스턴스 배열을 사용하면 인스턴스의 수가 많을 때 간편하게 불러올 수 있다.
7.생성문
모듈, 프리미티브 게이트, 연속할당문, initial/always 블록 등을 하나 또는 여러개 생성
net, reg, integer 등의 자료형을 생성 영억 내에서 선언 가능
인스턴스는 고유의 식별자를 가지며 계층적 이름으로 참조가 가능
반복 생성문
module xor_for
#(parameter width = 4, delay =10)
(output wire [1:width] y,
input [1:width] in1, in2);
generate
genvar i; // 반복 인덱스 변수 선언
for (i = 1; i <= width; i=i+1) begin: xi //식별자를 반드시 선언
assign #delay y[i] = in1[i] ^ in2[i];
end
endgenerate
endmodule
generate - endgenerate 구문 내부에 for문을 사용해 특정 모듈 또는 블록을 반복적으로 인스턴스
variable, 모듈, UDP, 게이트 프리미티브, initial, always 등을 인스턴스 할 수 있음.
생성문 내에서 사용하는 변수(i)는 genvar로 선언해야함
for 문의 begin 뒤에 블록 식별자 (:식별자)를 붙여야 함
예시
module gray2bin2(bin, gray);
parameter SIZE = 4;
output [SIZE-1:0] bin;
input [SIZE-1:0] gray;
reg [SIZE-1:0] bin;
genvar i;
generate
for(i=0; i<SIZE; i=i+1) begin : gb1
always @ (gray[SIZE-1:i])
bin[i] = ^gray[SIZE-1:i];
end
endgenerate
endmodule
리플캐리 가산기
`timescale 1ns / 1ns
module rca_for (a, b, ci, co, sum );
parameter size = 4;
input [size-1:0] a, b;
input ci;
output co;
output wire [size-1:0] sum;
wire [size:0] c;
genvar i;
assign c[0] = ci;
generate
for(i=0; i<size; i=i+1) begin : ra
wire t1, t2, t3; //로컬 net 선언
xor g1 (t1, a[i], b[i]);
xor g2 (sum[i], t1, c[i]);
and g3 (t2, a[i], b[i]);
and g4 (t3, t1, c[i]);
or g5 (c[i+1], t2, t3);
end
endgenerate
assign co = c[size];
endmodule
generate for문은 for 내부 코드를 동일하게 생성해주는 코드이며, RTL 분석을해보면 FA가 여러개 생성된것을 볼 수 있다.
generate if
module mult_if (x, y, prod);
parameter x_wid = 8, y_wid = 8;
localparam prod_wid = x_wid + y_wid;
// localparam으로 선언된 파라미터는 defparam나 모듈 인스턴스 #에서 변경 불가
input [x_wid-1:0] x ;
input [y_wid-1:0] y;
output wire [prod_wid-1:0] prod;
generate
if( (x_wid < 8) || (y_wid < 8) )
CLA_mult #(x_wid, y_wid) u1 (x, y, prod); // CLA 승산기 인스턴스
else
WALLACE_mult #(x_wid, y_wid) u2 (x, y, prod); // Wallace-tree 승산기 인스턴스
endgenerate
endmodule
주로 장치 마법사를 사용하는데 많이 사용한다. 조건이 맞으면 CLA 승산기를 만들고, 다르면 wallace 승산기를 만든다.
generate case
module rca_case
#(parameter size = 4)
( input [size-1:0] a, b,
input cin,
output reg [size-1:0] sum,
output reg co, neg, ov );
reg [size-1:0] c;
generate
genvar i;
for (i = 0; i< size; i=i+1) begin: stage
case(i)
0: begin
always @(*) begin
sum[i] = a[i] ^ b[i] ^ cin;
c[i] = a[i] & b[i] | b[i] & cin | a[i] & cin;
end
end
size-1: begin
always @(*) begin
sum[i] = a[i] ^ b[i] ^ c[i-1];
co = a[i] & b[i] | b[i] & c[i-1] | a[i] & c[i-1];
neg = sum[i]; // 부호
ov = co ^ c[i-1]; // overflow
end
end
default: begin
always @(*) begin
sum[i] = a[i] ^ b[i] ^ c[i-1];
c[i] = a[i] & b[i] | b[i] & c[i-1] | a[i] & c[i-1];
end
end
endcase
end
endgenerate
endmodule
if문과 마찬가지로 조건에 맞는 회로를 생성할 때 쓰인다.
8. 할당문
continuous assignment
net형 객체에 값을 할당
회로의 실행 상태에 관계없이 항상 활성화되어 있음
assignment의 입력 신호가 변경되면 출력값이 변함
연속 할당문 예제
wire mem_we;
assign mem_we = cs & wr; // 명시적 연속할당
wire mem_rd = cs & rd; // 암시적 연속할당
//지연은 FPGA에는 동작하지 않음. 시뮬레이터에 실제 소자의 지연시간을 시뮬레이션 하는 용도로만 사용
assign #10 out = in1 & in2; // 지연 연속할당
assign #12 {c, sum} = a + b + cin; // 좌변 비트 결합
assign mux_out = (sel) ? in1 : in2; // 2 input MUX
assign tc = (count == 4'h9); // 터미널 카운팅
assign high_byte = data[15:8]; // 비트 슬라이스 우변
연속 할당문으로 만든가산기
module add_ap (a, b, res);
parameter size = 8;
input [size-1:0] a, b;
output wire [size:0] res;
assign res = a + b; // res[size]는 캐리
endmodule
연속 할당문의 지연
정규지연
wire out;
assign #10 out = in1 & in2;
우변의 연산 결과가 좌변에 할당되기까지의 시간이다.
암시적 할당
wire #10 out = in1 & in2;
net 지연
wire #10 out;
assign out = in1 & in2;
out을 사용하는 모든 구동자에 영향을 미치게 된다. 선언될때 이미 지연이 됐기 때문이다.
procedure assignment
variable형 객체에 값을 할당한다.
blocking assignment
c언어에서 프로그램을 수행할 때와 유사하게 동작한다. C = 5가 실행될때 C에 값을 바로 업데이트하므로 D는 12가된다.
none-blocking assignment
A<= B & C;
D<= A | C;
할당문의 우항인 B & C의 값 계산이 끝났으나 A에 넣지 않고 다음 문장인 A | C를 실행한다.
A의 값은 아직 업데이트되기 전이므로 B & C가 실행되기 전의 값으로 A | C를 계산한다.
none-blocking 할당문을 쓰는 이유는 blocking 구문을 사용할 때는 순서에 따라 다른 회로가 합성될 수 있기 때문이다.
always @(posedge clk)
b = a;
always @(posedge clk)
c = b;
always는 동시에 실행되지만, b = a의 평가가 먼저 이루어지게 되면
입력으로 a가 들어가고 클록이 들어갈 때마다 출력으로 b, c가 나가는 회로가 나올 수 있다. 반대로 c = b가 먼저 계산되면
이러한 쉬프트 연산기가 만들어진다.
이런 현상을 방지하기 위해 none-blocking을 사용한다.
always @(posedge clk)
b <= a;
always @(posedge clk)
c <= b;
1. clk의 라이징엣지가 발생하면 always의 각 항의 evaluation이 이루어짐
2. a의 값을 읽어서 다른 장소에 보관하고, b의 값을 읽어서 다른 장소에 잠시 보관한다.
3. 모든 구문의 evaluation이 끝나면 다른 장소에 보관하고 있던 a, b값을 가져와 업데이트를 수행한다.
이러한 동작을 수행하면 always의 실행 순서에 상관없이 항상 일관된 회로가 나오게 된다.
Intra 지연 할당문
`timescale 1ns/1ns
module nblk1;
reg a, b, c, d, e, f;
// intra 지연을 가진 블로킹 할당
initial begin
a = #15 1; // 15 ns에서 a에 1 할당
b = #3 0; // 18 ns에서 b에 0 할당
c = #6 1; // 24 ns에서 c에 1 할당
end
//intra 지연을 가진 논블로킹 할당
initial begin
d <= #15 1; // 15 ns에서 d에 1 할당
e <= #3 0; // 3 ns에서 e에 0 할당
f <= #6 1; // 6 ns에서 f에 1 할당
end
endmodule
블로킹 구문으로 작성된 a,b,c는 상대적 시간으로 값이 들어가게 된다. 하지만 논 블로킹 구문으로 작성한 d, e, f는 다른 결과가 나오게 된다. 먼저 RHS의 구문을 실행한 후 initial,내의 구문이 끝난 후 함께 실행되기 때문에 절대적 지연시간처럼 동작하게 된다. 테스트벤치에는 논 블로킹 구문을 사용하지 않는게 좋다.
Regular 지연 할당문
`timescale 1ns/1ns
module nblk2;
reg a, b, c, d, e, f;
// regular 지연을 가진 블로킹 할당
initial begin
#15 a = 1; // 15 ns에서 a에 1 할당
#3 b = 0; // 18 ns에서 b에 0 할당
#6 c = 1; // 24 ns에서 c에 1 할당
end
// regular 지연을 가진 논블로킹 할당
initial begin
#15 d <= 1; // 15 ns에서 d에 1 할당
#3 e <= 0; // 18 ns에서 e에 0 할당
#6 f <= 1; // 24 ns에서 f에 1 할당
end
endmodule
이번에는 None blocking과 blocking이 같은 결과를 보인다. 이는 절대적인 지연 시간을 정해주었기 때문이다.
할당문의 실행 순서
블로킹
module nblk3;
reg [7:0] y1, y2;
reg [3:0] a, b;
reg clk;
initial begin
a = 5; b = 9;
clk = 0;
end
always clk = #10 ~clk;
always @(posedge clk) begin
y1 = a + b; // y1=14
y2 = y1 + a; // y2=19 즉시할당
end
endmodule
논블로킹
module nblk4;
reg [7:0] y1, y2;
reg [3:0] a, b;
reg clk;
initial begin
a = 5; b = 9;
clk = 0;
end
always clk = #10 ~clk;
always @(posedge clk) begin
y1 <= a + b; // y1= 14, 14
y2 <= y1+ a; // y2= x , 19, 지연할당
end
endmodule
결과는 같지만 값이 계산되는 타이밍이 달라지는것을 확인할 수 있다