본문 바로가기
# Programming/- FPGA Project

[FPGA Project] Multi Function Clock(다기능 시계 구현 프로젝트)

by Graffitio 2023. 10. 2.
[FPGA Project] Multi Function Clock(다기능 시계 구현 프로젝트)
728x90
반응형
[Project Summary]

 

📌 Project name

      : Multi Function Clock(다기능 시계) 구현

 

📌 Mission

      i) 시간, 분을 출력하는 시계 기능

      ii) 10ms 단위의 StopWatch 기능

      iii) Cook Timer 기능(99분 59초까지, 알람 기능 추가)

 

📌 프로젝트 기간 및 팀원

      - 기간 : 2 Days 

      - 팀원 : 3명

 


 

[Result]

 

📌 Operation 

 

 


 

📌 Mode 설명

 

 

Mode는 총 3단계로 구성되어 있고, 0번 버튼 입력에 따라 2진 카운터에 의해 Mode가 Select된다.

 

Mode 1은 Watch 모드로 23시59분까지 구현 가능하고 run state/set state의 두 가지 state가 존재한다.

run state에서는 minute clock에 따라 시간이 흐르게 되고 set state에서는 hour와 min을 각각 조정이 가능하다.

 

Mode2는 Stop watch 모드이다.

10ms 단위로 동작하며, start/stop 기능과 lap 기능을 가지고 있다.

 

Mode3는 Cook Timer 모드이다.

99분 59초까지 타이머를 셋팅할 수 있고, 셋팅된 시간이 다 지나 Timeout이 되면 알람이 발생하도록 설계하였다.

 


 

📌 Block Diagram & Code Description

 

Multi Clock Top module

 

Multi Clock Top

각 Mode를 담당하는 Watch, Stop Watch, Cook Timer의 모듈을 인스턴스하여 구성한

Multi Clock Top module의 블록 다이어그램이다.

Mode Selector의 출력값에 따라 Mode가 정해지며,

그 신호에 따라 Demux에서는 버튼 입력을 선택하여 해당 모듈에 전달하고

각 모듈의 출력값들은 Mux에 전달되어 해당 Mode에 맞는 출력값이 FND Controller에 전달된다.

 

<Code>

module Multi_function_clock_top(
  input clk, reset_p, all_rst,
  input [3:0] btn,
  output [3:0] com,
  output [7:0] seg_7,
  output reg [7:0] LED_bar,
  output Alarm_LED
);
  
  wire [1:0] mode;
  button_cntr btn_cntr_mode(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pe(mode_btn));
  counter_dec_mode mode_select(.clk(clk), .btn(mode_btn), .dec1(mode));
  
  reg [3:0] btn_watch_buf, btn_stp_buf, btn_cook_buf;
  reg reset_watch_buf, reset_stp_buf, reset_cook_buf;


  wire [15:0] value;  
  wire [15:0] value_watch, value_stp_watch, value_cook;
  assign value = (mode==0) ? value_watch : (mode == 1) ? value_stp_watch : (mode == 2) ? value_cook : 16'b1111_1111_1111_1111;
  
  always @(posedge clk) begin
    case(mode)
      0 : begin // Watch
		btn_watch_buf[1] = btn[1]; // setmode
		btn_watch_buf[2] = btn[2]; // hour
		btn_watch_buf[3] = btn[3]; // min
		reset_watch_buf = reset_p;
		LED_bar = 8'b0000_0001;
      end
      
      1 : begin // Stopwatch
		btn_stp_buf[1] = btn[1]; // start/stop
		btn_stp_buf[2] = btn[2]; // lap
		btn_stp_buf[3] = btn[3];
		reset_stp_buf = reset_p;
		LED_bar = 8'b0000_0010;
      end
      
      2 : begin // Cooktimer  
		btn_cook_buf[1] = btn[1]; // start/stop
		btn_cook_buf[2] = btn[2]; // min+
		btn_cook_buf[3] = btn[3]; // sec+
		reset_cook_buf = reset_p;
        LED_bar = 8'b0000_0100;
      end
      
      default : begin // 에러 검증용 default
        LED_bar = 8'b1110_0000;
      end
    endcase
  end
      // 각 기능의 모듈 인스턴스
      Watch_Top_proj watch(.clk(clk), .reset_p(reset_watch_buf | all_rst), .btn(btn_watch), .value_watch(value_watch));
      Stop_Watch_10ms_proj stp_wt(.clk(clk), .reset_p(reset_stp_buf | all_rst), .btn(btn_stp), .value_stp_watch(value_stp_watch));
  	  cook_timer_proj cook_tim(.clk(clk), .reset_p(reset_cook_buf | all_rst), .btn(btn_cook), .value_cook(value_cook), .Alarm_LED(Alarm_LED));
      
      FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(value), .com(com), .seg_7(seg_7));
endmodule

 


 

Watch Module

 

 

Watch module

Watch module은 시간과 분 단위로 업카운트하며, Start/Stop 기능과 run/set state로 구성되어 있다.

먼저 각 clock 분주기로 단위별 clock를 만들어 필요한 단위를 사용할 수 있도록 하였고

loadable counter 모듈로 run state에서는 clock 신호를 받아 시계의 기능을 하며

set state에서는 버튼의 입력을 받아 시간을 설정할 수 있다.

 

<Code>

module Watch_Top_proj(
    input clk, reset_p,
    input [3:0] btn,
    output [15:0] value_watch
    );
    // 분주기 인스턴스
    wire clk_usec, clk_msec, clk_sec, clk_min;
    wire upcount_sec;
    clock_usec_proj usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
    clock_div_1000_proj msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
    clock_div_1000_proj sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
    clock_min_proj min_clk(.clk(clk), .clk_sec(clk_sec), .reset_p(reset_p), .clk_min(clk_min));
    clock_hour_proj hour_clk(.clk(clk), .clk_min(upcount_min), .reset_p(reset_p), .clk_hour(clk_hour)); 
    
    // set mode select
    wire set_mode;
    assign upcount_min = set_mode ? incmin : clk_min;
    
    // btn
    button_cntr btn_cntr_setmode(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pe(btn_set_pe)); // 버튼눌러도 시간 안 바뀌게 고정
    t_flip_flop_p tff_setmode(.clk(clk), .rst(reset_p), .t(btn_set_pe), .q(set_mode)); 
    button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(btn[3]), .btn_pe(incmin)); // sec+
    button_cntr btn_inchour(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pe(inchour)); // min+

    // Time set
    wire [3:0] min1, min10, hour1, hour10;
    wire [3:0] min1_set, min10_set, hour1_set, hour10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
    
    // time count 
    loadable_up_counter_dec_60_proj cnter_min(.clk(clk), .reset_p(reset_p), .clk_time(clk_min), .load_enable(btn_set_pe), 
                                         .set_value1(min1_set), .set_value10(min10_set), .dec1(min1), .dec10(min10)); // sec up_counter
    loadable_up_counter_dec_24_proj cnter_hour(.clk(clk), .reset_p(reset_p), .clk_time(clk_hour), .load_enable(btn_set_pe), 
                                          .set_value1(hour1_set), .set_value10(hour10_set), .dec1(hour1), .dec10(hour10)); // min up_counter
	
    // time set
    loadable_up_counter_dec_60_proj set_min(.clk(clk), .reset_p(reset_p), .clk_time(incmin), .load_enable(btn_set_pe),
                                       .set_value1(min1), .set_value10(min10), .dec1(min1_set), .dec10(min10_set)); // sec up_set
    loadable_up_counter_dec_24_proj set_hour(.clk(clk), .reset_p(reset_p), .clk_time(inchour), .load_enable(btn_set_pe),
                                        .set_value1(hour1), .set_value10(hour10), .dec1(hour1_set), .dec10(hour10_set)); // min up_set

    // cur_time / set_time 
    wire [15:0] cur_time, set_time;
    assign cur_time = {hour10, hour1, min10, min1};
    assign set_time = {hour10_set, hour1_set, min10_set, min1_set};
    
    assign value_watch = set_mode ? set_time : cur_time;
endmodule

 


 

Stop Watch Module

 

Stop Watch Module

Stop watch 모듈은 10ms 단위로 업카운트하며, Start/Stop 기능과 Lap 기능으로 구성되어 있다.

Start/Stop 신호를 MUX를 통해 msec clock 모듈의 입력에 연결하여,

Start 시에는 clock을 받고 Stop 시에는 0을 입력받도록 설계하였고

Lap 기능이 비활성화 상태일 경우에는 업카운터에서 받은 값을 value_stopwatch로 받고

Lap 기능이 활성화되면 현재 값을 레지스터에 저장한 뒤 그 값을 출력하며,

Background에서는 카운트가 계속 진행되도록 하여 정상 상태로 돌아왔을 때

Stopwatch의 Lap 기능을 똑같이 구현할 수 있도록 설계하였다.

 

 

<Code>

module Stop_Watch_10ms_proj(
    input clk,
    input reset_p,
    input [3:0] btn,
    output [15:0] value_stp_watch
);
    
    wire start_stop_input, start_stop, start_stop_btn;
    
    // Start_stop 기능 구현 - usec과 연결
  	button_cntr btn_cntr_start_stop(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pe(start_stop_btn));
    t_flip_flop_p tff_start_stop(.clk(clk), .rst(reset_p), .t(start_stop_btn), .q(start_stop)); // start_stop 버튼 토글 기능 
  
    // Lap 기능 구현
    wire lap_input, lap_btn, lap;
    button_cntr btn_cntr_lap(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pe(lap_btn));
    t_flip_flop_p tff_lap(.clk(clk), .rst(reset_p), .t(lap_btn), .q(lap)); // 토글을 위한 TFF
    
    // 분주기 인스턴스
    wire clk_usec, clk_msec, clk_sec;
    wire clk_start;
    assign clk_start = start_stop ? clk_usec : 0;
    clock_usec_proj usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
    clock_div_1000_proj msec_clk(.clk(clk), .clk_source(clk_start), .reset_p(reset_p), .clk_div_1000(clk_msec));
    clock_div_1000_proj sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
    clock_min_proj min_clk(.clk(clk), .clk_sec(clk_sec), .reset_p(reset_p), .clk_min(clk_min));
    
    reg [9:0] cnt_clk_msec;
    always @(posedge clk, posedge reset_p) begin
        if(reset_p) cnt_clk_msec = 0; // reset이 들어오면 cnt = 0
        else if(clk_msec) begin
            if (cnt_clk_msec >= 999) begin
                cnt_clk_msec = 0;
            end
            else cnt_clk_msec = cnt_clk_msec + 1;
        end
    end
    
    wire [15:0] msec_b2d;
    bin_to_dec_stopwatch_proj msec_clk_b2d(.bin({2'b00, cnt_clk_msec}), .bcd(msec_b2d));
    
    // 카운터 인스턴스
    wire [3:0] sec1, sec10; // sec1 : 1의자리, sec10 : 10의 자리
    counter_dec_60_stopwatch_proj cnt_60_sec(.clk(clk), .reset_p(reset_p), .clk_time(clk_sec), .dec1(sec1), .dec10(sec10)); // sec count

    // Lap Value / value Select
    reg [15:0] lap_value;
    always @(posedge clk, posedge reset_p) begin
        if(reset_p) lap_value = 0;
        else if(lap_btn) // lap_btn 나올 때 저장
            lap_value = {sec10, sec1, msec_b2d[11:4]}; // 10ms 단위만 쓸 것이기 때문에 [11:4]만 비트 슬라이싱해서 쓰자
    end
    
    assign value_stp_watch = lap ? lap_value : {sec10, sec1, msec_b2d[11:4]};
endmodule

 


 

Cook Timer Module

 

Cook Timer Module

Timer는 시간을 설정할 수 있는 Set part, Timer count 기능이 있는 Timer part,

시간이 다 지나 Timeout이 발생했을 때 알람이 출력되는 Alarm part, 총 3가지 part로 구성되어 있다.

설정된 시간은 Set timer register에 저장되고 start 버튼을 누르면,

분주된 sec clock에 맞춰서 설정된 시간부터 카운트가 진행되며 현재 카운트가 FND에 출력된다.

설정된 시간이 종료되어 Timeout 상태가 되면, Alarm이 발생하고,

이때 발생한 알람은 어떤 버튼을 눌러도 정지가 되도록 설계하였다.

 

<Code>

module cook_timer_proj(
    input clk, reset_p,
    input [3:0] btn,
    output [15:0] value_cook,
    output Alarm_LED
    );
    
    // button 
    wire start, incsec, incmin;
    wire timeout, alarm_start;
    wire t_start_stop;
    assign t_start_stop = start ? 1 : (alarm_start ? 1 : 0); // alarm이 발생하거나 start가 1일 때, t_start_stop = 1
    button_cntr btn_start_stop(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pe(start)); // start/stop
    t_flip_flop_p tff_start_stop(.clk(clk), .rst(reset_p), .t(t_start_stop), .q(start_stop)); // 버튼눌렀을 때, alarm 발생 시, start_stop 변수 토글
    button_cntr btn_incsec(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pe(incsec)); // sec+
    button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(btn[3]), .btn_pe(incmin)); // min+
    
    // Time Prescaler(clock Library)
    wire clk_usec, clk_msec, clk_sec;
    clock_usec_proj usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
    clock_div_1000_proj msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
    clock_div_1000_proj sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
    clock_min_proj min_clk(.clk(clk), .clk_sec(clk_sec), .reset_p(reset_p), .clk_min(clk_min));
    
    // Time set
    wire [3:0] sec1_set, sec10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
    counter_dec_60_proj up_sec(.clk(clk), .reset_p(reset_p), .clk_time(incsec), .dec1(sec1_set), .dec10(sec10_set)); // sec up_counter
    wire [3:0] min1_set, min10_set;
    counter_dec_100_proj up_min(.clk(clk), .reset_p(reset_p), .clk_time(incmin), .dec1(min1_set), .dec10(min10_set)); // min up_counter
       
    // load_enable을 start_stop에 연결하면, start 시(start_stop = 1)계속해서 값을 불러와버린다.
    // 따라서 start_stop = 0일때만 1이 들어가도록 설계(start 변수의 참거짓에 따라 load_enable 변화하도록)
    wire clk_start, load_enable;
    wire dec_clk; // 다음 자리(예 : minute)에 신호(펄스)를 주기 위한 변수
    wire [3:0] sec1, sec10, min1, min10;
    assign clk_start = start_stop ? clk_sec : 0; // 대표적인 MUX 생성 방법
    assign load_enable = ~start_stop ? start : 0; // start_stop = 0 이면, start 변수 사용
    // load_enable에는 edge 한 번만 주어야 하므로 start 사용
    
    loadable_down_counter_dec_60_proj dc_sec(.clk(clk), .reset_p(reset_p), .clk_time(clk_start), .load_enable(load_enable), 
                                        .set_value1(sec1_set), .set_value10(sec10_set),
                                        .dec1(sec1), .dec10(sec10), .dec_clk(dec_clk));
    loadable_down_counter_dec_100_proj dc_min(.clk(clk), .reset_p(reset_p), .clk_time(dec_clk), .load_enable(load_enable), 
                                        .set_value1(min1_set), .set_value10(min10_set),
                                        .dec1(min1), .dec10(min10));                                    
    
    // Setting Time for FND
    reg [15:0] set_time;
    always @(posedge clk, posedge reset_p) begin
        if(reset_p) set_time = 0;
        else set_time = {min10_set, min1_set, sec10_set, sec1_set};
    end
    
    // Count Time for FND
    reg [15:0] count_time;
    always @(posedge clk, posedge reset_p) begin
        if(reset_p) count_time = 0;
        else count_time = {min10, min1, sec10, sec1};
    end
    
    // time = 0 도달 시,
    assign timeout = |count_time; // 뒤의 모든 비트를 OR 연산한 결과(베릴로그에서만 사용할 수 있는 문법)
    edge_detector_n edg_timeout(.clk(clk), .cp_in(timeout), .rst(reset_p), .n_edge(alarm_start)); // timeout 발생 시, alarm edge 발생
    
    // Alarm On/Off를 위한 기능
    wire alarm, alarm_off;
    assign alarm_off = |{btn, reset_p}; // 모든 버튼을 다 묶어버려서 넣어주면, 어떤 버튼을 눌러도 alarm이 다 리셋된다.
    t_flip_flop_p tff_alarm_on_off(.clk(clk), .rst(alarm_off), .t(alarm_start), .q(alarm));
    assign Alarm_LED = alarm;
    
    assign value_cook = start_stop ? count_time : set_time;
    
endmodule

 


 

📌 Wrap-up

 

프로젝트를 진행하면서,

각 기능별로 버튼을 분배하는 단계에서 시간이 많이 소요되었다.

1bit의 값들은 자동적으로 wire가 생성되어 연결되지만, 그 이상의 값들은

1bit만 짤려서 연결되어 값들이 제대로 전달되지 않는 문제가 자주 발생하였다.

 

이래서 코딩 습관이 중요하다는 것 같다.

시스템이 알아서 연결해주니까~ 하는 안일한 생각을 갖지 말고

1bit라도 꼭 wire로 선언해주는 습관을 들이도록 해야겠다.

또한 사소하다고 넘어갔던 부분들을 다시 한 번 생각해보고

따져보는 태도로 가져가야겠다는 생각이 들었다.

 


 

[Full Code]

 

프로젝트에 관련된 코드는 아래 링크 참조

 

GitHub - Graffitio/Harman_Project_MultiClock

Contribute to Graffitio/Harman_Project_MultiClock development by creating an account on GitHub.

github.com

 


 

Fin

 


 

728x90
반응형