진행년월: 24.04
목차
1. 배경
2. 과제 정의 및 개요
3. 소스코드
4. 실습결과
1. 배경
Counter는 굉장히 기본적인 예제이지만, 보통의 예제에서는 보드 상에 FND가 최소 2개 이상, 제가 사용한 Xilinx(현 AMD) 사의 ZYNQ-7000의 경우에는 4개가 있는데, 이를 다 활용하는 것이 아니라 일의 자릿수만 하거나 복수의 경우도 단순히 복제해서 작동시키는 경우가 대부분이었습니다.
저는 FPGA 구조를 이해하고 4자리수로 동작시킬 방법에 대해 생각해보려고 합니다.
7-Segment나 Counter 등의 자료는 다른 곳에서도 많이 찾아볼 수 있기 때문에 해당 내용은 생략하도록 하겠습니다.
2. 과제 정의 및 개요
먼저, 구성이 어떻게 되어 있는지를 알 필요가 있습니다.
보통 Pin의 개수 이득을 위해서인지 위와 같이 Digit Pin으로 어떤 FND를 활성화할 것인지를 결정하고,
그 칸에서의 dot를 포함한 8개의 Segment를 제어하도록 구성되어 있습니다.
예를들어, Digit0 (T11) 핀을 활성화하고 나머지(T10, T12, U12) 핀을 비활성화하면,
Segment들의 작동은 일의 자릿수인 첫 번째 FND에서만 보여주게 됩니다.
그러면, Digit0부터 Digit3까지 Counter의 숫자에 따라 적절하게 변화를 줄 수 있도록 설계해야 합니다.
다양한 방법이 있겠지만, 저는 다음과 같은 아이디어로 스펙을 정의하도록 하겠습니다.
스펙 정의
1. Counter는 사람이 인지할 수 있는 속도로 증가합니다.(Slow Clock)
2. Shift Registor를 활용해 Digit0부터 Digit3까지 사람 눈에 인식되지 않는 빠른 속도로 (Fast Clock) 1을 순환시킵니다. 이는 모든 칸이 계속 불이 들어오는 것으로 인지됩니다.
(ex: 0001 -> 0010 -> 0100 -> 1000 -> 0001)
3. Counter 값에서 자릿수 숫자를 분리하여 각 자릿수에 해당하는 칸이 활성화될 때, 해당 자릿수 숫자를 할당합니다.
(ex: Counter가 396일 때각 자릿수는 0396이며, Digit이 0100일 순간에는 0300을 보여주도록 함)
4. 할당된 숫자는 Segment로 디코딩되며, 디지털 숫자로 보여주도록 합니다.
3. 소스코드
// 메인 모듈
module counter(clk, rst, seg_out, mode_out);
input clk, rst; // 클록, 리셋 포트
output [7:0] seg_out; // 7-세그먼트 포트 a ~ dp
output [3:0] mode_out; // digit_0 ~ digit_3 포트
reg [3:0] thous, hunds, tens, units; // 자릿수에 사용될 십진수
wire [7:0] seg_thous, seg_hunds, seg_tens, seg_units; // 7-세그먼트로 변환된 자릿수
wire clk_wiz, clk_div; // 분주기를 통해 나온 두가지 클록 신호. clk_wiz는 clocking wizard를 통해 만들어짐.
integer tmp, cnt; // 자릿수 계산에 사용될 임시 변수, 카운터에 사용될 변수
reg [3:0] mode_tmp; // 디지털 숫자판 digit_0 ~ digit_3중 어디에 신호를 넣을지 결정하는 배열
reg [7:0] seg_tmp; // 모드가 결정되면 그에 따른 7-세그먼트를 선언해 주기 위한 변수
assign mode_out = mode_tmp; // 어떤 칸을 켤지에 대한 포트. 0010 이면 오른쪽에서 두번째 칸을 킨다.
assign seg_out = seg_tmp; // 결정된 모드에 따라 알맞은 자릿수를 전달하기 위함
// 카운터 모듈 동작부. 분주기 모듈 gen_clk에 의해 동작한다.
always @(posedge clk_div or posedge rst) begin
if (rst) begin
cnt <= 0; units <= 0; tens <= 0; hunds <= 0; thous <= 0;
end
else if (cnt == 9999) begin
cnt <= 0; units <= 0; tens <= 0; hunds <= 0; thous <= 0;
end
else begin
cnt = cnt + 1; // 자릿수 계산. 중간에 tmp 변수를 활용.
thous = (cnt - (cnt % 1000)) / 1000; tmp = cnt % 1000; // 직관적으로 이해할 수 있도록 이것과 같이 코드를 작성하였음.
hunds = (tmp - (tmp % 100)) / 100; tmp = tmp % 100;
tens = (tmp - (tmp % 10)) / 10;
units = tmp % 10;
end
end
// Shift-Resister 방식으로 mode의 값을 순차적으로 왼쪽으로 밀어줌.
// 이것은 fnd의 켜지는 칸을 계속 바꿔주는 작동을 함.
always @(posedge clk_wiz or posedge rst) begin
if (rst) begin
mode_tmp <= 4'b0001; seg_tmp <= 8'b01111111;
end
else begin
mode_tmp[0] <= mode_tmp[3];
mode_tmp[3:1] <= mode_tmp[2:0];
end
case(mode_out)
4'b0001 : seg_tmp <= seg_tens; // 위에서 한칸씩 왼쪽으로 이동되므로 하나씩 왼쪽 것을 할당한다.
4'b0010 : seg_tmp <= seg_hunds;
4'b0100 : seg_tmp <= seg_thous;
4'b1000 : seg_tmp <= seg_units;
default: seg_tmp <= 8'b11000000;
endcase
end
clk_wiz_0 wclk(clk_wiz, clk); // Clocking Wizard에 의한 클록 신호. 5 Mhz 보다 낮게 설정 불가
gen_clk gclk(clk_wiz, rst, clk_div); // 사용자의 임의대로 만들어질 새로운 클록 신호.
seven_segment thous_seg(.digit(thous), .seg(seg_thous)); // 천의 자리 세그먼트
seven_segment hunds_seg(.digit(hunds), .seg(seg_hunds)); // 백의 자리 세그먼트
seven_segment tens_seg(.digit(tens), .seg(seg_tens)); // 십의 자리 세그먼트
seven_segment units_seg(.digit(units), .seg(seg_units)); // 일의 자리 세그먼트
endmodule
제가 사용한 보드에서는 50 MHz 클럭이고, 분주 방법으로 Vivado의 IP Catalog 중 Clocking Wizard를 사용하였습니다.
이 방법으로는 5 MHz보다 느리게 만들 수 없지만,
이것은 사람이 인지보다 훨씬 빠르기 때문에 Fast Clock으로 사용하였습니다.
Counter를 위한 Slow Clock을 만들기 위해 분주기를 따로 사용한 코드는 다음과 같습니다.
// 카운터를 응용하여 분주기 모듈을 만들어봄. 더 효율적인 것이 있을 수 있음.
module gen_clk(input clk, input rst, output reg clk_div);
integer i;
always @(posedge clk) begin
if (rst) begin
i = 0; clk_div = 0;
end
else begin
i = i+1;
if (i == 249999) begin // 2,499,999 를 사용하면 1초에 한번 클락으로 가능.
i = 0; clk_div = ~clk_div; // 네자리수까지 보여줘야 하므로 0.1초 클락으로 진행.
end
end
end
endmodule
4개의 자릿수마다 segment를 따로 Decoding 해주어야 하므로 인스턴스화해서 사용하였고, 코드는 다음과 같습니다.
// 7-세그먼트 표현을 위한 모듈
// 1은 꺼진 것, 0이 켜진 것으로 설계.
// 8번째 칸은 디지털 숫자 우하단 점(dp)를 의미하는데 사용하지 않을 것이므로 모두 1로 한다.
module seven_segment(input [3:0] digit, output reg [7:0] seg);
always @(*) begin
case (digit)
4'b0000: seg = 8'b11000000; // 0
4'b0001: seg = 8'b11111001; // 1
4'b0010: seg = 8'b10100100; // 2
4'b0011: seg = 8'b10110000; // 3
4'b0100: seg = 8'b10011001; // 4
4'b0101: seg = 8'b10010010; // 5
4'b0110: seg = 8'b10000010; // 6
4'b0111: seg = 8'b11111000; // 7
4'b1000: seg = 8'b10000000; // 8
4'b1001: seg = 8'b10010000; // 9
default: seg = 8'b11111111; // 이외에 다른 숫자가 입력으로 들어올 시 모두 off
endcase
end
endmodule
4. 실습결과
촬영 환경이 좋지 않은 점 양해 부탁드립니다.
'RTL, Synthesis, P&R' 카테고리의 다른 글
UART 통신 - (2) Arm보드로 FPGA LCD 제어 (0) | 2024.09.03 |
---|---|
UART 통신 - (1) RTL (0) | 2024.09.03 |
32-bit SPI Interface - (3) Arduino로 FPGA Motor 제어 (0) | 2024.09.03 |
32-bit SPI Interface - (2) Synthesis, P&R (0) | 2024.09.03 |
32-bit SPI Interface - (1) RTL (0) | 2024.09.03 |