RTL, Synthesis, P&R

4-digit Counter with FPGA

MiddleJo 2024. 8. 30. 10:18

진행년월: 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. 실습결과

 

 

 

촬영 환경이 좋지 않은 점 양해 부탁드립니다.