임베디드 스터디 - Verilog 설계
임베디드 스터디 - Verilog 설계
조합논리회로 설계
assign vs always @(*)
- assign : 단순 논리 연산, 입출력 배선에 사용
- always :
if-else,case조건 분기에 사용
반가산기 설계
논리연산 문법 : AND(&), OR( ), NOT(~), XOR(^)
1
2
3
4
5
6
7
module HA(
input wire a, b,
output wire sum, carry
);
assign sum = a ^ b;
assign carry = a & b;
endmodule
다중비트 데이터
- Verilog는 다중비트 입출력을
[MSB:LSB]의 형태로 데이터를 선언한다.
1
2
input wire [1:0] in
output reg [7:0] out
숫자
- Verilog는 다음과 같이 숫자를 선언할 수 있다.
1 2 3 4
10 // Decimal 3'b000 // 3비트 이진수 000 8'd10 // 8비트 십진수 10 6'hc // 6비트 16진수 C(12)
MUX 설계
- 4X1 MUX는 Verilog의
case문을 사용하여 설계할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module mux4to1 (
input wire [1:0] sel,
input wire d0, d1, d2, d3,
output reg y
);
always @(*) begin
case (sel)
2'b00: y = d0;
2'b01: y = d1;
2'b10: y = d2;
2'b11: y = d3;
default: y = d0;
endcase
end
endmodule
- Verilog는 0, 1외에 X(Unknown), Z(High Impedance)라는 값이 존재한다. 이에 위의 MUX 설계에서
sel신호에 해당하는 비트를 모두case문에 넣어도, X, Z 데이터가 생기면 y는 미정의 상태가 된다.- 이를 해결하기 위해 상항
default를 넣는 습관을 들이자.
- 이를 해결하기 위해 상항
디코더 설계
- 3X8 디코더는 Verilog에서 두 가지 방법으로 설계할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module decoder3to8 (
input wire [2:0] in,
output reg [7:0] out
);
// case 문 사용
always@(*) begin
case (in)
3'b000 : out = 8'd1; //8'b0000_0001
3'b001 : out = 8'd2; //8'b0000_0010
3'b010 : out = 8'd4; //8'b0000_0100
3'b011 : out = 8'd8; //8'b0000_1000
3'b100 : out = 8'd16; //8'b0001_0000
3'b101 : out = 8'd32; //8'b0010_0000
3'b110 : out = 8'd64; //8'b0100_0000
3'b111 : out = 8'd128; //8'b1000_0000
default : out = 8'd1; //8'b0000_0001
endcase
end
// bit shift 문법 사용
assign out = 8'd1 << in;
endmodule
순서논리회로 설계
변수 할당
- Verilog는 변수 할당에
=,<=두 가지 방식이 존재한다.=(Blocking assignment) : 선언 순서대로 즉시 대입<=(Non-blocking assignment) : always 블록 실행이 끝날 때 우변을 일괄 대입. 순서논리회로에서 사용
D-FF 설계
- Verilog로 심플하게 설계할 수 있다.
1
2
3
4
5
6
7
8
module DFF(
input wire clk, d,
output reg q
);
always @(posedge clk) begin
q <= d;
end
endmodule
리셋
- Verilog를 통해 비동기 리셋, 동기 리셋을 구현할 수 있다.
- 동기 리셋 :
reset신호를 입력받아 조건문으로 변수 초기화. 클럭에 종속되어 데이터가 변한다. - 비동기 리셋 :
reset신호를 sensitivity list에 추가. 이후 조건문으로 변수 초기화.reset자체가 sensitivity list에 들어가있으므로 모듈이reset의 변화에 반응한다.
- 동기 리셋 :
트리거
- Verilog는 크게 3가지 형태의 트리거를 지원한다.
- posedge : 상승 엣지 트리거
- negedge : 하강 엣지 트리거
- 일반 변수 : 레벨 트리거
- Verilog는 양방향 트리거를 기본적으로 지원하지 않는다. 또, 표준 디지털 로직에 양방향 트리거 FF 프리미티브 자체가 존재하지 않아, FPGA 상에서 실제 하드웨어로 구현되기 어렵다.
4비트 동기 카운터 설계
- Verilog로 4비트 동기 카운터를 설계한다.
1 2 3 4 5 6 7 8 9 10 11 12
module counter( input wire clk, rst, output reg [3:0] count ); always@(posedge clk, posedge rst) begin if (rst == 1) count <= 4'b0000; else count <= count + 4'b0001; end endmodule
파라미터 설정
- Verilog는 파라미터를 정의하는 방법이 두 가지 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 방법 1: `define (C의 #define과 동일)
`define S0 2'b00
`define S1 2'b01
`define S2 2'b10
// 사용 시
state <= `S0; // 백틱(`) 필요
// 방법 2: parameter (Verilog 권장 방식)
parameter S0 = 2'b00;
parameter S1 = 2'b01;
parameter S2 = 2'b10;
// 사용 시
state <= S0; // 백틱 불필요
FSM 설계
- Verilog를 통해 2비트 시퀀스 검출기를 설계한다.
- FSM을 Verilog로 구현할 때, 보통 2-블록 구조를 자주 사용한다.
- 한 블록에서는 상태의 업데이트를, 다른 블록에서는 상태에 따른 동작을 설계한다. ```verilog module state_machine( input wire rst, in, clk, output reg out ); reg [1:0] state, next_state; parameter S0 = 2’b00; parameter S1 = 2’b01; parameter S2 = 2’b10;
always@(posedge clk, posedge rst) begin if (rst == 1) state <= S0; else state <= next_state; end
always@(*) begin case (state) S0 : begin if (in == 1) begin next_state = S1; end else begin next_state = S0; end out = 0; end S1 : begin if (in == 1) begin next_state = S2; end else begin next_state = S0; end out = 0; end S2 : begin out = 1; next_state = S0; end default : begin out = 0; next_state = S0; end
endcase end endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## 계층설계 (Hierarchical Design)
* 인스턴스화 : 하나의 모듈에서 다른 모듈을 불러와 설계함.
* 소프트웨어의 **함수 호출**과 다른 점 : Verilog는 모듈의 배치 후 계속 하드웨어에서 실행, 소프트웨어는 함수를 호출할 때만 실행된다.
```verilog
module top (
input wire a, b,
output wire sum, carry
);
// HA 모듈을 인스턴스화
half_adder u0 (
.a(a),
.b(b),
.sum(sum),
.carry(carry)
);
endmodule
전가산기 설계
- 전가산기는 반가산기 2개를 사용하여 회로를 구성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module half_adder(
input wire a, b,
output wire sum, carry
);
assign sum = a ^ b;
assign carry = a & b;
endmodule
module full_adder(
input wire a, b, carry_in,
output wire sum_out, carry_out
);
wire sum0, carry0, carry1;
half_adder u0 (
.a(a),
.b(b),
.sum(sum0),
.carry(carry0)
);
half_adder u1 (
.a(sum0),
.b(carry_in),
.sum(sum_out),
.carry(carry1)
);
assign carry_out = carry0 | carry1;
endmodule
재사용성 중심 모듈 설계
parameter변수를 사용해 동일 기능의 모듈을 다양한 데이터 크기로 만들 수 있다.
1
2
3
4
5
6
7
8
9
module counter #(parameter WIDTH = 4) (
input wire clk, rst,
output reg [WIDTH-1:0] count
);
always @(posedge clk, posedge rst) begin
if (rst) count <= 0;
else count <= count + 1;
end
endmodule
반복회로 생성
generate블록을 사용해 동일한 모듈의 인스턴스를 여러 개 생성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module ripple_adder #(parameter N = 4) (
input wire [N-1:0] a, b,
input wire cin,
output wire [N-1:0] sum,
output wire cout
);
wire [N:0] carry;
assign carry[0] = cin;
genvar i;
generate
for (i = 0; i < N; i = i+1) begin : FA
full_adder u (
.a(a[i]), .b(b[i]),
.carry_in(carry[i]),
.sum_out(sum[i]),
.carry_out(carry[i+1])
);
end
endgenerate
assign cout = carry[N];
endmodule
- 여기서
generate블록의 이름의FA이고,FA안의full_adder는u라는 이름으로 생성된다. - 즉, 각 모듈은 아래와 같은 형태로 접근할 수 있다.
- 시뮬레이션 및 테스트 검증 시 각 신호를 확인해볼 수 있다.
1
2
ripple_adder.FA[0].u.sum_out // 0번째 FA 인스턴스의 sum_out 신호
ripple_adder.FA[1].u.sum_out // 1번째 FA 인스턴스의 sum_out 신호
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.