본문 바로가기

FPGA

[FPGA] 11. 플립플롭과 카운터를 이용하여 Basys3 스톱워치 만들기

1. 스톱워치를 만들기 위한 기본 Flow 및 코드 작성


개괄적인 구상은 다음과 같다. 버튼0(start), 버튼1(lap), 버튼2(clear) 총 세 개를 이용해서 스톱워치를 제어할 것이다.  FND 상위 2개는 분(minute), 하위 2개는 초(second)를 나타낸다.

 

버튼 세 개는 D플립플롭을 통해 Start/Stop과 lap 버튼을 입력으로 주고, 딜레이를 통해 버튼의 바운싱을 제거해서 안정적으로 출력된 Q를 이용한다. 그리고 Q로부터 나온 출력 값을 다시 T플립플롭의 클락으로 입력하여, T플립플롭의 특성을 이용해 On/Off를 구현한다. 한 번 누를 때마다 Q값이 반전될 것이다. 

 

다음 T플립플롭으로부터 나온 각 버튼의 Q값을 always문의 sensitive list로 작동하여 하강엣지일 때, 즉, 버튼을 누른상태에서 떼면 if문을 타고 어떤 버튼을 눌렀는지 선택하여 lap을 출력할지, 현재 시각을 출력할지 정한다.

 

클락이 들어가는 모든 기능에는 reset을 구현하여 넣어준다.

 

먼저 코드를 보자.

 

`timescale 1ns / 1ps 

module stop_watch_top(
    input clk,
    input [2:0] btn,
    output [3:0] com_an,
    output [6:0] seg_7
    );
    
    wire btn_start, btn_lap, btn_clear;
    wire reset_n, start, clk_start;
    
    reg [3:0] sec [1:0]; // 배열 : 4비트 sec 변수가 2개 선언됨
    reg [3:0] min [1:0]; // 배열 : 4비트 min 변수가 2개 선언됨
    reg [3:0] lap [3:0];    //lap값 저장 4비트 lap 4개 
    reg [3:0] out_value [3:0];  //lap상태에서 lap을 위해 저장할 변수 
    
    always @(lap_e) begin           //switcher에게 out_value를 줘서 lap의 경우와 시간출력의 경우 두가지를 선택하게 해줌
                                    //lap버튼 누를 때 동작
        if(lap_e) begin             //t플립플롭으로부터 1이 나오면 lap을 출력해주어야함
            out_value[0] = lap[0];
            out_value[1] = lap[1];
            out_value[2] = lap[2];
            out_value[3] = lap[3];
        end
        else begin                  //t플립플롭으로부터 0이 나오면 원래 sec와 min을 출력해주어야함
            out_value[0] = sec[0];
            out_value[1] = sec[1];
            out_value[2] = min[0];
            out_value[3] = min[1];
        end
    end
    
    always @(posedge btn_lap or negedge reset_n) begin      //lap 구간 스위치 떼면 
        if(!reset_n) begin
            lap[0] = 0;
            lap[1] = 0;
            lap[2] = 0;
            lap[3] = 0;
        end
    
        else begin
            lap[0] = sec[0];
            lap[1] = sec[1];
            lap[2] = min[0];
            lap[3] = min[1];
        end     
    end 
    
    not(reset_n, btn[2]); // 버튼 기본상태가 0이라 이대로 두면 계속 reset이라서 반전시켜서 넣음
    
                                                                             // prset은 0일 때 동작하는데 사용안할거라 1을 줌
    T_flip_flop_posedge tff_start (.T(1), .clk(btn_start), .reset_n(reset_n), .preset_n(1), .Q(start));
    T_flip_flop_posedge tff_lap (.T(1), .clk(btn_lap), .reset_n(reset_n), .preset_n(1), .Q(lap_e));
    
    
    and (clk_start, start, clk_msec);
    
    always@(negedge clk_sec or negedge reset_n) // 초단위 counter
    begin 
        if(!reset_n) // reset이 0이면 clear
        begin
            sec[0] = 0;
            sec[1] = 0;
        end
        else if (sec[0] >= 9) // 초의 1의 자리가 9이상이면
        begin 
            sec[0] = 0; // 초의 1의 자리 초기화
            if(sec[1] >= 5) // 초의 10의 자리가 5이상이면
            begin
                sec[1] = 0; // 초의 10의 자리 초기화
            end
            else sec[1] = sec[1] + 1; // 초의 10의 자리 1올림
        end
        else sec[0] = sec[0] + 1;
    end
    
    always@(negedge clk_min or negedge reset_n) // 분단위 counter
    begin
        if(!reset_n) // reset이 0이면 clear
        begin
            min[0] = 0;
            min[1] = 0;
        end
        else if(min[0] >= 9) // 분의 1의 자리가 9이상이면
        begin 
            min[0] = 0; // 분의 1의 자리 초기화
            if(min[1] >= 5) // 분의 10의 자리가 5이상이면
            begin
                min[1] = 0; // 분의 10의 자리 초기화
            end
            else min[1] = min[1] + 1; // 분의 10의 자리 1올림
        end
        else min[0] = min[0] + 1;
    end

    D_flip_flop_posedge dff_start (.D(btn[0]), .E(clk_msec), .Q(btn_start));
    D_flip_flop_posedge dff_lap (.D(btn[1]), .E(clk_msec), .Q(btn_lap));

    clock_usec uCLK(.clk(clk), .reset_n(reset_n), .clk_usec(clk_usec));
    clock_msec mCLK(.clk_usec(clk_usec), .reset_n(reset_n), .clk_msec(clk_msec));
    clock_sec sCLK(.clk_msec(clk_start), .reset_n(reset_n), .clk_sec(clk_sec));
    clock_min MCLK(.clk_sec(clk_sec), .reset_n(reset_n), .clk_min(clk_min));
    
    FND_4digit_switcher FND_4digit (
    .value_1(out_value[0]),
    .value_10(out_value[1]),
    .value_100(out_value[2]), 
    .value_1000(out_value[3]), 
    .clk(clk),
    .com_an(com_an),
    .seg_7(seg_7)
    );
    
endmodule

TOP 모듈을 코드로 구현하였다. TOP모듈 내에서 쓰이는 시간 계산 모듈(clock_usec, clock_msec, clock_sec, clock_min)과 FND_4digit_switcher 코드는 아래에 있다.

 

`timescale 1ns / 1ps

module clock_usec(
    input clk,
    input reset_n,
    output reg clk_usec
    );
    reg [6:0] cnt_usec = 0;
    wire clk_100MHz;
    assign clk_100MHz = clk;
    
    always @(negedge clk_100MHz or negedge reset_n)
    begin
        if(!reset_n)
        begin
            cnt_usec = 0;
            clk_usec = 0;
        end
        
        else
        begin
            if(cnt_usec >= 99) cnt_usec = 0; // cnt_usec는 0부터 99까지 100개 count함
            else cnt_usec = cnt_usec + 1; 
            if(cnt_usec < 99) clk_usec = 0;
            else clk_usec = 1;
        end
    end
    
endmodule

module clock_msec(
    input clk_usec,
    input reset_n,
    output reg clk_msec
    );
    reg [9:0] cnt_msec = 0;

   always @(negedge clk_usec or negedge reset_n)
    begin
        if(!reset_n)
        begin
            cnt_msec = 0;
            clk_msec = 0;
        end
        
        else
        begin
            if(cnt_msec >= 999) cnt_msec = 0; // cnt_usec는 0부터 99까지 100개 count함
            else cnt_msec = cnt_msec + 1;
            
            if(cnt_msec < 999) clk_msec = 0;
            else clk_msec = 1;
        end
    end
    
endmodule

module clock_sec(
    input clk_msec,
    input reset_n,
    output reg clk_sec
    );
    
    reg [9:0] cnt_sec = 0;

   always @(negedge clk_msec or negedge reset_n)
    begin
        if(!reset_n)
        begin
            cnt_sec = 0;
            clk_sec = 0;
        end
        
        else
        begin
            if(cnt_sec >= 999) cnt_sec = 0; // cnt_usec는 0부터 99까지 100개 count함
            else cnt_sec = cnt_sec + 1;
            if(cnt_sec < 999) clk_sec = 0;
            else clk_sec = 1;
        end
    end
    
endmodule

module clock_min(
    input clk_sec,
    input reset_n,
    output reg clk_min
    );
    
    reg [5:0] cnt_min = 0;

   always @(negedge clk_sec or negedge reset_n)
    begin
        if(!reset_n)
        begin
            cnt_min = 0;
            clk_min = 0;
        end
        
        else 
        begin 
            if(cnt_min >= 59) cnt_min = 0; // cnt_min은 0부터 59까지 60개 count함
            else cnt_min = cnt_min + 1;
            if(cnt_min < 59) clk_min = 0;
            else clk_min = 1;
        end
    end
    
endmodule




///////////////////////FND_4digit_switcher.v

`timescale 1ns / 1ps

module FND_4digit_switcher(
    input [3:0] value_1, // fnd 1의 자리
    input [3:0] value_10, // fnd 10의 자리
    input [3:0] value_100, // fnd 100의 자리
    input [3:0] value_1000, // fnd 1000의 자리
    input clk,
    output reg [3:0] com_an = 4'b1110,
    output [6:0] seg_7
    );
    
    wire clk_usec, clk_msec;
    reg [3:0] hex_value;
    
    clock_usec UCLK(
    .clk(clk),
    .reset_n(1),
    .clk_usec(clk_usec)
    );
    
    clock_msec MCLK(
    .clk_usec(clk_usec),
    .reset_n(1),
    .clk_msec(clk_msec)
    );
    
    decoder_7_seg dec7seg (
    .hex_value(hex_value),
    .seg_7(seg_7)
    );
    
    always @(negedge clk_msec) // 1msec clk 마다 always문 진입
    begin
        case(com_an)
            4'b1110 : begin
                com_an = 4'b1101;
                hex_value = value_10;
            end
            4'b1101 : begin
                com_an = 4'b1011;
                hex_value = value_100;
            end
            4'b1011 : begin
                com_an = 4'b0111;
                hex_value = value_1000;
            end
            4'b0111 : begin
                com_an = 4'b1110;
                hex_value = value_1;
            end
            default : begin // 이외의 경우 선언 
                 com_an = 4'b1110;
                hex_value = value_1;   
            end
        endcase
    end
    
endmodule

코드의 전체적인 Flow는 다음과 같다.

 

크게 watch_top 모듈에서는 버튼 3개(Start / Clear / lap)를 입력 받는다. 먼저 스톱워치의 시작을 위해서는 Start버튼을 눌러야 한다.

Start 버튼을 누르면 reset버튼을 누르거나 Start버튼을 다시 누르기 전까지는 계속해서 시간 계산이 시작된다.(시스템 클락을 사용하고 있기 때문에 어떤 동작을 해도 시간은 계속 계산이 됨)

각각 받은 버튼 값은 플립플롭을 거쳐(바운싱 제거 및 ON/OFF를 위해 플립플롭을 사용)  Q로 나온 출력 값은 always문의 클럭으로 작동하여 해당 버튼 기능에 맞게 출력을 해야하는 값을 저장한다.  

그리고 저장된 값은 FND_4digit_switcher 모듈로 전송되어 FND의 출력 위치와 값을 결과 값으로 FND에 전송한다.

이 과정을 통해서 FND에 시간이 출력된다.

 

 

 

2. basys3 xdc파일 설정

시스템 클럭, FND, 버튼을 활성화하기 위해 코드에서 작성했던 변수를 각각 알맞게 매핑해준다. 여기서 시스템 클럭은 10ns동안 0ns~5ns까지는 low, 10ns까지는 high로 출력하여 듀티비 50%의 파형을 생성해준다.

 

 

3. 결과


오른쪽 버튼은 Start/Stop, 가운데 버튼은 Clear, 왼쪽버튼은 Lab이다. 잘 작동되는 것을 볼 수 있다.

 

.