유로 청년 2024. 7. 30. 16:28

1. 개발 목적

  • 이전에 배웠던 "일반 시계"와 "Stop Watch", "Cooking Watch" 지식을 기반으로 통합형 시계를 만들고자 한다.
  • 이를 통해 일반 시계와 Stop Watch, Cooking Watch에 대해서 복습하고, 여러 시계 모듈을 통합하는 과정에서 디지털 회로도의 이해하는 능력과 Verilog의 프로그래밍 실력을 향상 시킬 수 있다.

 

 

 

 

2. 시계 동작 설명

  • 통합 시계는 총 3가지의 모드를 갖는다.
    - 첫 번째 모드) 일반 시계 모드
    - 두 번째 모드)  Stop Watch 모드
    - 세 번째 모드)  Cooking Watch 모드

  • 각각의 모드들은 다음과 같은 버튼 갯수와 역활을 갖는다.
    ▶ 일반 시계 모드는 버튼 3개를 갖는다.
       - watch / set 버튼 : 시계 모드와 설정 모드를 변경하는 버튼
       - 분 컨트롤 버튼 : 설정 모드에서 분 값을 1분씩 증가시켜 조절하는 버튼
       - 초 컨트롤 버튼 : 설정 모드에서 초 값을 1초씩 증가시켜 조절하는 버튼  

    ▶ Stop Watch 모드는 버튼 3개를 갖는다.
       - Start 버튼 : 해당 버튼을 누를 시, Stop watch가 시작된다.
       - Lap 버튼 : 해당 버튼을 누를 시, 누른 시점에 시간을 기록한다.
       - Clear 버튼 : 해당 버튼을 누를 시, Stop watch 뿐만 아니라 기록된 시간도 초기화 된다.

    ▶ Cooking 모드는 버튼 4개를 갖는다.
       - Start 버튼 : 분과 초를 설정 한 뒤, 해당 버튼을 누르면 점차적으로 Down Counting 시작된다.
       - 분 컨트롤 버튼 : 해당 버튼은 시작 전, 분 값을 세팅하는 버튼
       - 초 컨트롤 버튼 : 해당 버튼은 시작 전, 초 값을 세팅하는 버튼
       - 알람 끄는 버튼 : Cooking Timer가 0분 0초가 되면 Alarm이 울리게 되며, 해당 버튼을 누르면 알람이 꺼진다.

  • Basys3에 내장된 스위치와 LED이 이외에도 부족한 1개의 버튼과 외부의 알람을 연결하기 위해 
    다음과 같이 Port와 Pin을 설정하여 연결하였다.
    - Button 4 ( Turn off alarm, btn4) : JC Port의 1핀
    - Buzz ( alarm ) : JB Port의 4핀


  • 통합형 시계을 구현하는 과정에서 몇 가지 특수한 기능을 추가하였다.
    - 알람을 끄는 버튼인 btn4는 Cooking Timer 모드 뿐만 아니라 다른 모드에서도 알람이 울린다면 알람을
      끌 수 있어야 한다. 
    - 각각의 Clear 버튼을 누를 시, 해당 모드에서만 Clear가 적용되어야 한다. 
    - 각각의 시간들은 독립적으로 동작한다.

 

 

 


3. 코드 설명

  • 코드와 함께 각각에 대한 설명을 하도록 하겠다.

 

< Source, Input / Output 변수 선언 >

module Integrated_Watch_Project(
    input clk, reset_p,
    input [3:0] btn,
    input btn4,
    output [3:0] com,
    output [7:0] seg_7, 
    output [2:0] led_debug, 
    output buzz
);
  • basys3의 버튼은 총 4개이지만, 1개의 버튼은 모드 전환 용도로 사용해야 하기 때문에 실제로 사용할 수 있는 버튼은 3개이다.
  • 하지만, Cooking Timer 모드에서는 4개의 버튼이 필요하기 때문에 나머지 하나의 버튼은 외부에서 회로를 구성한 뒤, PORT를 통해 연결하게 된다. 이렇게 외부 회로에서 구성된 스위치를 btn4라고 변수명을 정한다.
  • led_debug 변수는 현재 어떤 모드에 있는지를 나타낸다.
  • buzz는 Cooking Timer 모드에서 0분 0초가 되었을 때, 울리는 알람을 나타낸다.

 

 

< Source, State parameter 선언 >

// declare state parameter
parameter S_CLOCK         = 3'b001;
parameter S_STOP_WATCH    = 3'b010;
parameter S_COOKING_TIMER = 3'b100;

 

  • 통합형 시계에서는 총 3가지 시계 기능을 수행하기 때문에 위와 같이 3개의 state로 구분한 뒤, 각각의 state에 대해서 parameter를 선언하였다.

 

 

< Source, 현재 어떤 모드에 있는지 LED로 표시한다. >

 // LED for state         
 assign led_debug = state;

 

 

 

< Source, Get one cycle pulse of button 0, button 4 >

// Get One Cycle pulse of button0, 4
// Button0 : changing mode
// Button4 : Turn off alarm
wire clk_btn0;
button_cntr btn_cntr_btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(clk_btn0));
  • button 0 는 시계 모드를 전환하는 버튼이다.
  • button 1 ~ 3 는 각각의 시계 모드에서 통합적으로 사용하는 버튼이다.
  • button 4는 Cooking Timer 에서만 동작하는 버튼이며, 해당 버튼을 통해 울리는 알람을 끌 수 있다.
  • clk_btn0는 Button 0 버튼이 눌렀을 때마다 발생하는 One Cycle Pulse이다.
  • Q) 왜 나머지 버튼 (button 1 ~ 4)에 대해서는 One Cycle Pulse를 생성하지 않는가?
    A)
     Button 1 ~ 3번 버튼은 모든 모드에서 사용하는 공통의 버튼이며, Button 4번 버튼은 Cooking Timer 내부에서 알람을 끄는 목적으로 사용하는 버튼이다. 

 

 

< Source, state와 next_state 변수 선언 >

// declare state, next_state variable
reg [2:0] state, next_state;

 

 

 

< Source, 각 모드의 com, seg_7 변수 선언 >

// declare com, seg_7 variables.
wire [3:0] com_clock, com_stop, com_cook;
wire [7:0] seg_7_clock, seg_7_stop, seg_7_cook;
  • 각각의 모듈의 출력 값인 com, seg_7 변수 선언
  • 각각의 모드의 모듈로부터 com, seg_7 값을 받은 뒤, 현재의 모드(state)에 따라 현재의 모드의 com, seg_7 값을 선택한 뒤, 7-segment를 통해 해당 데이터 출력한다.
  • State diagram와 함께 생각해보는 것을 추천한다.

 

 

< Source, 각 모드의 button 변수 선언 >

// Declare Button
reg [2:0] clock_button, stop_button;
reg [3:0] cook_button;
  • 각각의 모듈의 instance의 argument 값으로 전달을 위한 버튼 데이터 변수
  • 현재 state의 버튼 변수는 버튼 값, btn[1], btn[2], btn[3] 값을 받지만, 나머지 state의 버튼 변수들은 0 값을 받는다.
  • 단, 알람 끄는 버튼인 경우, 모든 모드에서 알람이 울렸을 때, 알람을 꺼야하기 때문에 알람 끄는 버튼인 btn4는 모든 모드에서 값이 들어 갈 수 있도록 설계할 필요가 있다.

 

 

< Source, State 와 버튼 값 초기화 >

// State transition and button processing logic
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            state = S_CLOCK;
            clock_button = 3'b000;
            stop_button = 3'b000;
            cook_button = 4'b0000;
        end else begin
               ''' (생략) '''           
        end
    end
  • state 값은 S_IDLE, 각 모드의 버튼 값은 0으로 초기화 한다.

 

 

< Source, Button 0의 One Cycle Pulse인 clk_btn0가 활성화 될 때마다 모드 전환 >

// State transition and button processing logic
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            state = S_CLOCK;
            clock_button = 3'b000;
            stop_button = 3'b000;
            cook_button = 4'b0000;
        end else begin
            if(clk_btn0) begin // if pressed button 0
                case(state)
                    S_CLOCK: state = S_STOP_WATCH;
                    S_STOP_WATCH: state = S_COOKING_TIMER;
                    S_COOKING_TIMER: state = S_CLOCK;
                endcase
            end
            else begin
                ''' 생략 '''
            end          
        end
    end
  • clk_btn0 변수는 모드 전환 버튼인 button0의 One Cycle Pulse이다.
  • clk_btn0가 활성화 될 때마다 다음 state로 전이된다.
  • 단, next_state 변수 사용 없이 바로 state 변수에 대입한다.

 

 

< Source, state 변수 값에 따라 각 모드의 버튼 값 설정 >

case(state) 
     S_CLOCK: begin
         clock_button = {btn[1], btn[2], btn[3]};
         stop_button = 3'b000;
         cook_button = {btn4, 3'b000};
     end 
                      
     S_STOP_WATCH: begin
         clock_button = 3'b000;
         stop_button = {btn[3], btn[2], btn[1]};
         cook_button = {btn4, 3'b000};
     end
                     
     S_COOKING_TIMER: begin
         clock_button = 3'b000;
         stop_button = 3'b000;
         cook_button = {btn4, btn[3], btn[2], btn[1]};
     end
endcase
  • state 변수 값을 통해 현재 모드의 버튼 값에 button의 신호 값을 대입한다.
    - ex) state = S_STOP_WATCH 일 경우.
    - 현재 Stop Watch 모드이기 때문에 Stop watch 버튼 변수에 버튼 신호 값인 btn[1] ~ btn[3] 을 대입한다.
    - 나머지 모드의 버튼 값은 0을 대입한다.
  • Q) 왜 Cooking Button의 btn4 값은 모드와 상관 없이 값을 주는 건가?
  • A) btn4는 Alarm off 버튼이다. Alarm이 울리게 되면 모드에 상관 없이 Alarm을 끄기 위해서는 모든 모드에서 btn4 값이 cook_button 변수에 대입할 수 있도록 설계해야 한다. 따라서 btn4 값을 모든 모드에서 값을 주는 이유는 "Alarm이 울리면 모드와 상관 없이 Alarm을 끄기 위해서"이다.

 

 

 

< Source, 각 모드의 모듈의 Instance의 argument 값으로 버튼 값을 준 뒤, com값과 seg_7 값을 출력 값으로 받는다.  >

// Module instantiations with direct button connections
    watch_top watch_mode (
        .clk(clk),
        .reset_p(reset_p),
        .btn(clock_button),
        .com(com_clock),
        .seg_7(seg_7_clock)
    );
    
    stop_watch_top stop_watch_mode (
        .clk(clk),
        .reset_p(reset_p),
        .btn(stop_button),
        .com(com_stop),
        .seg_7(seg_7_stop)
    );
    
    cook_timer_top cook_timer_mode (
        .clk(clk),
        .reset_p(reset_p),
        .btn(cook_button),
        .com(com_cook),
        .seg_7(seg_7_cook),
        .buzz(buzz)
    );
  • 각 모드의 모듈의 argument 값으로 각 모드의 버튼 값을 전달한다.
  • 각 모드의 모듈 내에서 계산된 시간 데이터를 com 값과 seg_7 값을 통해 받게 된다.
  • Q) 각 모드에서 출력 값으로 com 값과 seg_7 값을 주지만, 모듈 내부에서도 시간을 FND로 출력하지 않는가?
    A) 
    각각의 모듈은 com 값과 seg_7 값을 출력으로 top module에 데이터를 전달할 뿐, 최종적으로 Basys3의 FND에 출력으로 내보내는 것은 top module에서 current state 값에 따라 출력할 com 값과 seg_7 값을 결정한다.

    만약, 각각의 모듈이 Top module이였다면, 각 모듈 내에서 com 값와 seg_7 값을 출력하지 않고, 모듈내에서 시간 데이터를 FND로 출력했을 것이다. 하지만, Top module은 Integrated_Watch_Project 이기 때문에 최종적인 출력은 Integrated_Watch_Project 모듈에서 결정하고, 이루어 진다.

 

 

 

< Source, 현재의 state 변수 값에 따라 com 값과 seg_7 값을 선택하여 출력한다.  >

// Output selection based on current state
assign com = (state == S_CLOCK) ? com_clock :
             (state == S_STOP_WATCH) ? com_stop : com_cook;
assign seg_7 = (state == S_CLOCK) ? seg_7_clock :
               (state == S_STOP_WATCH) ? seg_7_stop : seg_7_cook;
  • 현재 state 값을 통해 현재 모드의 com 값과 seg_7 값을 선택한 뒤, FND를 통해 해당 모드의 시간 데이터를 출력한다.

 

 

 

4. Block diagram

  • 위 소스 코드를 Block diagram으로 표현하면 다음과 같이 표현할 수 있다.

 

 

 

5. 구현 영상

 

 

 

 

6. 전체 소스

module Integrated_Watch_Project(
    input clk, reset_p,
    input [3:0] btn,
    input btn4,
    output [3:0] com,
    output [7:0] seg_7, 
    output [2:0] led_debug, 
    output buzz
);
    
    // declare state parameter
    parameter S_CLOCK         = 3'b001;
    parameter S_STOP_WATCH    = 3'b010;
    parameter S_COOKING_TIMER = 3'b100;
    
    // LED for state
    assign led_debug = state;
    
    // Get One Cycle pulse of button0
    // Button0 : changing mode
    wire clk_btn0;
    button_cntr btn_cntr_btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(clk_btn0));
    
    // declare state, next_state variable
    reg [2:0] state;
    
    // declare com, seg_7 variables.
    wire [3:0] com_clock, com_stop, com_cook;
    wire [7:0] seg_7_clock, seg_7_stop, seg_7_cook;
    
    // Declare Button
    // Clock Button : Down : set mode, Left : minute 설정, Right : second 설정
    // Stop Watch : Down : clear, Right : start, Left : Lap
    // Cooking Watch : Down : minute, Right : start, Left : second.
    reg [2:0] clock_button, stop_button;
    reg [3:0] cook_button;
    
    // State transition and button processing logic
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            state = S_CLOCK;
            clock_button = 3'b000;
            stop_button = 3'b000;
            cook_button = 4'b0000;
        end else begin
            if(clk_btn0) begin // if pressed button 0
                case(state)
                    S_CLOCK: state = S_STOP_WATCH;
                    S_STOP_WATCH: state = S_COOKING_TIMER;
                    S_COOKING_TIMER: state = S_CLOCK;
                endcase
            end
            else begin
                case(state) 
                       S_CLOCK: begin
                            clock_button = {btn[1], btn[2], btn[3]};
                            stop_button = 3'b000;
                            cook_button = {btn4, 3'b000};
                       end 
                       S_STOP_WATCH: begin
                            clock_button = 3'b000;
                            stop_button = {btn[3], btn[2], btn[1]};
                            cook_button = {btn4, 3'b000};
                      end
                      S_COOKING_TIMER: begin
                           clock_button = 3'b000;
                           stop_button = 3'b000;
                           cook_button = {btn4, btn[3], btn[2], btn[1]};
                      end
                endcase       
            end          
        end
    end
    
    
    // Module instantiations with direct button connections
    watch_top watch_mode (
        .clk(clk),
        .reset_p(reset_p),
        .btn(clock_button),
        .com(com_clock),
        .seg_7(seg_7_clock)
    );
    
    stop_watch_top stop_watch_mode (
        .clk(clk),
        .reset_p(reset_p),
        .btn(stop_button),
        .com(com_stop),
        .seg_7(seg_7_stop)
    );
    
    cook_timer_top cook_timer_mode (
        .clk(clk),
        .reset_p(reset_p),
        .btn(cook_button),
        .com(com_cook),
        .seg_7(seg_7_cook),
        .buzz(buzz)
    );
    
    // Output selection based on current state
    assign com = (state == S_CLOCK) ? com_clock :
                 (state == S_STOP_WATCH) ? com_stop : com_cook;
    assign seg_7 = (state == S_CLOCK) ? seg_7_clock :
                (state == S_STOP_WATCH) ? seg_7_stop : seg_7_cook;
endmodule

 

 

 

 

7. 기타.

  • WNS (Worst Negative Slack)이 2.138로 나타나, Positive slack임을 확인하였다,
  • 즉, Required time - Arrival time 값이 양수이기 때문에 신호나 데이터가 목적지까지 가야하는 요구 시간보다 2.138ns 먼저 도착하여 2.138ns 만큼 여유 시간을 갖음을 의미하며, 이는 타이밍 요구사항을 만족함을 의미한다.

 

 

  • 이번 구현에서 FPGA 내에서 LUT은 273개, Flip Flop은 298개를 사용하였음을 확인할 수 있었다.