Harman 세미콘 아카데미/Verilog

2024.7.1 [Verilog]10 - 주방 타이머 만들기

U_Pong 2024. 7. 1. 19:07

2024.7.1 수업날

오늘은 지난주에 배운 타이머를 응용하여 주방 타이머를 만들어본다.

(다운카운터, 0분 0초가 되면 led가 켜지면서 부저도 울리는 기능)


먼저 지난주 금요일에 마지막쯤에 만들었던 상위 2비트에 초, 하위 2비트에 1/100초를 출력하는 스탑워치 소스코드의 회로도를 그려보자.

stop_watch_top_ss 소스코드
///////////////////// 시작, 스톱, 랩 기능이 있는 상위 2비트에는 초, 하위 2비트에는 0.01초가  출력되는 스톱워치
module stop_watch_top_ss(
      input clk, reset_p,
      input [1:0] btn,
      output [3:0] com,
      output [7:0] seg_7,
      output [1:0] led);
      
      wire btn0_pedge, btn1_pedge, start_stop, lap;        //start가 1, stop이 0
      button_cntr_reg_n_p btn_start(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[0]), .btn_pedge(btn0_pedge));
      
      T_flip_flop_p t_start(
            .clk(clk), .reset_p(reset_p),
            .t(btn0_pedge), .q(start_stop));
      
       button_cntr_reg_n_p btn_lap(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[1]), .btn_pedge(btn1_pedge));
      
      T_flip_flop_p t_lap(
            .clk(clk), .reset_p(reset_p),
            .t(btn1_pedge), .q(lap));
      
      
      wire clk_start;
      assign clk_start = start_stop ? clk : 0;
       
      wire clk_usec, clk_msec, clk_sec, clk_ss;
      clock_div_100 usec_clk(  
            .clk(clk_start), .reset_p(reset_p), 
            .cp_div_100(clk_usec));
      
      clock_div_1000 msec_clk(     
            .clk(clk_start), .reset_p(reset_p),
            .clk_source(clk_usec),
            .cp_div_1000_nedge(clk_msec)); 
            
      clock_div_10 ss_clk(     
            .clk(clk_start), .reset_p(reset_p),
            .clk_source(clk_msec),
            .cp_div_10_nedge(clk_ss)); 
      
      wire [3:0] sec1, sec10,  ss1, ss10;
      clock_div_1000 sec_clk(   
            .clk(clk_start), .reset_p(reset_p),
            .clk_source(clk_msec),
            .cp_div_1000_nedge(clk_sec));
      
      counter_bcd_99 counter_ss(
            .clk(clk), .reset_p(reset_p),
            .clk_time(clk_ss),            // 1000분주기에서 이미 clk_sec로 start_stop으로 멈춘 값을 입력으로 받았기 때문에 clk로 입력을 받음
            .bcd_s1(ss1), .bcd_s10(ss10));
            
      counter_bcd_60 counter_sec(
            .clk(clk), .reset_p(reset_p),
            .clk_time(clk_sec),            // 1000분주기에서 이미 clk_sec로 start_stop으로 멈춘 값을 입력으로 받았기 때문에 clk로 입력을 받음
            .bcd1(sec1), .bcd10(sec10));
      
      // 랩 기능 구현 추가부분
      wire [15:0]cur_time ;
      assign cur_time = {sec10, sec1, ss10, ss1};
      
      reg [15:0] lap_time;
      always @(posedge clk or posedge reset_p) begin
            if(reset_p)lap_time = 0;
            else if(btn1_pedge) lap_time = cur_time;
      end
       
      wire [15:0] value;
      assign value = lap ? lap_time : cur_time;
      fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);
      
      assign led[0]= ~start_stop; 
      assign led[1] = lap;
      
 
endmodule

 

여기서는 입력값을 2비트 btn, clk, reset으로 설정했고, 출력값을 2비트 led, 4비트 com, 8비트 seg_7으로 설정하였다.

각 플립플롭에는 reset이 있지만 회로도에서는 생략하였다.

그리고 입력값인 clk는 시스템 시간(10ns)이고, clk_start를 usec_clk, msec_clk, sec_clk, min_clk에 다같이 입력값으로 받는다.

그리고 회로선에 /16 라고 되어있는 것은 선이 16개(16비트)인 bus로 묶여있는 것을 의미한다.

검은 글씨의 이름은 교수님이 만드신 모듈 이름 기준이고, 파란 글씨는 수업시간에 개인적으로 만든 모듈 이름이다.

이름이 다른 것은 차이가 없지만, 교수님이 만드신 모듈 이름이 더 구별하기 편하다.

 

 

 

이제 주방 타이머를 만들어보자.

주방 타이머는 스탑워치와 다르게 다운카운터로 시간이 점점 줄어드는 카운터이다.

그리고 설정한 시간으로부터 다운카운터가 되며 0분 0초가 되면 알람 소리가 들린다.

또한, 알람 소리를 끄기 위해 set 버튼을 누르면 초기에 설정했던 시간으로 되돌아간다.

이번에 만들 주방 타이머는 led도 들어오게하고, 부저를 통해 소리도 울리는 주방 타이머이다.

 

먼저 다운카운터를 만들기 위해 bcd 다운 카운터 소스코드를 만들어보자.

loadable_down_counter_bcd_60 소스코드
/////////////////////////////////////////////////////////////////////// 2024.7.1
//////////////// 60진 bcd load 다운 카운터, set모드, watch 모드에 서로 덮어씌움
module loadable_down_counter_bcd_60(
   // 입출력 정의
  input clk, reset_p;      // 시스템 클럭, 리셋 신호
  input clk_time;          // 카운터 동작 클럭
  input load_enable;       // 로드 신호 (값 변경 활성화)
  input [3:0] load_bcd1, load_bcd10;  // 로드할 4비트 BCD 값 (1의 자리, 10의 자리)
  output reg[3:0] bcd1, bcd10;       // 현재 카운터 값 (출력용 4비트 BCD, 1의 자리, 10의 자리)
  output reg dec_clk;        // 카운터 감소 신호 (출력용)

  // always 블록: 에지 감지 (posedge clk or posedge reset_p)
  always @(posedge clk or posedge reset_p) begin
    if (reset_p) begin		// 리셋 시 초기화
      bcd1 <= 0;
      bcd10 <= 0;
      dec_clk <= 0;
    end else begin  			// 리셋 상태가 아닌 경우
      if (load_enable) begin	// 로드 신호 활성화 시 로드 값으로 변경
        bcd1 <= load_bcd1;
        bcd10 <= load_bcd10;
      end else if (clk_time) begin	// 카운터 동작 클럭이 발생하는 경우
        if (bcd1 == 0) begin
          bcd1 <= 9;			// 1의 자리가 0이면 9로 변경 (BCD 코드 특성상)
          if (bcd10 == 0) begin
            dec_clk <= 1;	// 10의 자리도 0이면 감소 신호 발생 (60초 도달)
            bcd10 <= 5;  	// 10의 자리를 5로 설정 (다음 60초 시작)
          end else begin
            bcd10 <= bcd10 - 1;	  // 10의 자리가 0이 아니면 10의 자리 감소
          end
        end else begin
          bcd1 <= bcd1 - 1;	// 1의 자리가 0이 아니면 1의 자리 감소
        end
      end else begin
        dec_clk <= 0;		// 카운터 동작 클럭이 발생하지 않으면 감소 신호 비활성화
      end
    end
  end

endmodule

다운 카운터에는 dec_clk(감소 카운터)를 출력값에 추가하여 

00초가 되면 십의 자리는 5가되고, 일의자리는 9가되도록 소스코드를 만들었다.

 

이제 60진 다운카운터 소스코드를 이용하여 다운터가 계속 진행하고 set버튼을 누르면 초기 설정시간으로 돌아가는 소스코드를 만들어본다. 

다운카운트는 계속 진행되고, set을 누르면 설정한 시간으로 돌아오는 주방 타이머
///////////////////////////////////////////////////////////////// 2024.7.1
////////////// 주방 스톱워치, 설정한 시간에서 set 버튼을 누르면 다운카운트가 계속 진행하다가 다시 set을 누르면 설정한 시간으로 되돌아감
module cook_timer(
      input clk, reset_p,
      input [3:0] btn,
      output [3:0] com,
      output [7:0] seg_7);
      
      wire [3:0] btn_pedge;
      button_cntr_reg_n_p start(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[0]), .btn_pedge(btn_pedge[0]));
      
      T_flip_flop_p t_start(
            .clk(clk), .reset_p(reset_p),
            .t(btn_pedge[0]), .q(start_stop));
      
      wire load_enable;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p),
            .cp(start_stop),
            .p_edge(load_enable));   
      
      
      button_cntr_reg_n_p btn_inc_sec(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[1]), .btn_pedge(btn_pedge[1]));   
      button_cntr_reg_n_p btn_inc_min(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[2]), .btn_pedge(btn_pedge[2]));     
      button_cntr_reg_n_p btn_alarm_stop(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[3]), .btn_pedge(btn_pedge[3]));
      
      
      wire clk_usec, clk_msec, clk_sec;
      clock_div_100 usec_clk(  
            .clk(clk), .reset_p(reset_p), 
            .cp_div_100(clk_usec));
      
      clock_div_1000 msec_clk(     
            .clk(clk), .reset_p(reset_p),
            .clk_source(clk_usec),
            .cp_div_1000_nedge(clk_msec)); 
            
      clock_div_1000 sec_clk(     
            .clk(clk), .reset_p(reset_p),
            .clk_source(clk_msec),
            .cp_div_1000_nedge(clk_sec));
      
      
      wire alarm_off, inc_min, inc_sec, btn_start;
      assign {alarm_off, inc_min, inc_sec, btn_start} = btn_pedge;      //btn_pedge의 0비트가 btn_start, 1비트가 inc_sec... 이런 방식으로
      
      wire [3:0] set_sec1, set_sec10, set_min1, set_min10;      
      counter_bcd_60 counter_sec(
            .clk(clk), .reset_p(reset_p),
            .clk_time(inc_sec),
            .bcd1(set_sec1), .bcd10(set_sec10));
            
      counter_bcd_60 counter_min(
            .clk(clk), .reset_p(reset_p),
            .clk_time(inc_min),
            .bcd1(set_min1), .bcd10(set_min10));
      
      
      wire [3:0] cur_sec1, cur_sec10, cur_min1, cur_min10;
      wire dec_clk;
      loadable_down_counter_bcd_60 cur_sec(
            .clk(clk), .reset_p(reset_p),
            .clk_time(clk_sec),
            .load_enable(load_enable),
            .load_bcd1(set_sec1), .load_bcd10(set_sec10),
            .bcd1(cur_sec1), .bcd10(cur_sec10),
            .dec_clk(dec_clk));
      
      loadable_down_counter_bcd_60 cur_min(
            .clk(clk), .reset_p(reset_p),
            .clk_time(dec_clk),
            .load_enable(load_enable),
            .load_bcd1(set_min1), .load_bcd10(set_min10),
            .bcd1(cur_min1), .bcd10(cur_min10));
      
      
      wire [15:0] value, set_time, cur_time;
      assign set_time = {set_min10, set_min1, set_sec10, set_sec1};
      assign cur_time = {cur_min10, cur_min1, cur_sec10, cur_sec1};
      assign value = start_stop ? cur_time : set_time;
      fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);
      
      

endmodule

이번 주방타이머는 스탑워치에서 만들었던 loadable_watch_top_btn와 비슷하게 set과 cur를 서로 덮어쓰는 방식을 사용하여 로드 기능과 cur_sec, cur_min에는 다운 카운터가 설정되도록 소스코드를 만들었다.

카운터 분주기는 그대로이고, 버튼에 각각 초 증가, 분 증가, 알람끄기, 리셋 기능을 설정하였다.

 

그리고 버튼을 모두 다 사용하기 때문에 .xdc 파일에 아래와 같이 수정해야한다.

 

BTNR를 누르면 분이 증가하고, BTNL을 누르면 초가 증가한다.

BTNU를 누르면 value로 인해 cur_time이 되어 다운카운터가 진행되고, 다시 한 번 누르면 set_time이 되어 초기 설정했던 시간으로 돌아가는 것을 확인할 수 있다.

cook_timer 결과

 

 

다음으로 다운카운터가 진행되다가 0분 0초가 되면 초기 설정 시간으로 돌아감과 동시에

led에 불이 켜지고 연결된 부저에 소리가 울리는 주방 타이머를 만들어본다.

 

먼저 부저에 대해 알아보자.

부저는 능동 부저와 피동 부저가 있다.

능동 부저는 바닥 부분이 검은색이고, 피동 부저는 바닥 부분이 초록색이다.

 

능동 부저는 내부 발진 소스가 있으며, 전원이 공급되는 즉시 부저가 울린다.

피동 부저는 내부 발진 소스가 없으며 변화하는 입력 신호에 따라 사운드를 생성한다.

둘을 구별하는 간단한 방법은 양극 및 음극 단자를 배터리에 연결했을 때, 부저가 울리면 능동 부저이고 소리가 나지 않으면 수동 부저이다.

둘 다 극성 부분이 있으며 능동 부저의 경우는 다리가 짧은 쪽이 -, 긴 쪽이 +이다.

수동 부저의 경우는 초록색 바닥 부분에 +, - 가 적혀있으므로 이를 참고하여 연결하면 된다.

 

아래는 Basys3 보드에 점퍼선을 연결할 수 있는 부분을 표시한 것이다.

이번에 쓸 부분은 JA 부분으로 왼쪽 상단의 부분이다.

핀 번호는 기판을 아래로 향하게 하여 오른쪽부터 왼쪽 순서대로 번호를 지정한다.

만약 능동 부저를 JA1번에 연결하고 싶다면 능동부저의 짧은 다리는 GND, 긴 다리는 pin1번에 꽂으면 된다.

 

 

cook_timer_00_stop_led_buzz 소스코드
///주방 스톱워치, 다운 카운트가 0분 0초가 되면 초기 설정시간으로 돌아감과 동시에 led0에 불이 들어오며 부저 소리가 울린다, BTND를 눌러야 led0이 꺼짐
module cook_timer_00_stop_led_buzz(
      input clk, reset_p,
      input [3:0] btn,
      output [3:0] com,
      output [7:0] seg_7,
      output reg timeout_led,
      output buzz);                             // 능동 부저
      
      wire [3:0] btn_pedge;
      wire load_enable;
      wire clk_usec, clk_msec, clk_sec;
      wire alarm_off, inc_min, inc_sec, btn_start;
      wire [3:0] set_sec1, set_sec10, set_min1, set_min10;  
      wire [3:0] cur_sec1, cur_sec10, cur_min1, cur_min10;  
      wire dec_clk;    
      wire [15:0] value, set_time, cur_time;
      
      reg start_stop;
       
      assign buzz = timeout_led;
      
      button_cntr_reg_n_p start(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[0]), .btn_pedge(btn_pedge[0]));
      
      
      always @ (posedge clk or posedge reset_p) begin
            if(reset_p)begin
                  start_stop = 0;
                  timeout_led = 0;
            end
            else begin
                  if(btn_start)start_stop = ~start_stop;
                  else if(cur_time == 0  && start_stop )begin     //카운트다운을 시작한 후에 cur_time 이 0이면 led가 불이 켜진다.
                        start_stop = 0;
                        timeout_led = 1;              // led는 한시간 뒤에 켜짐
                  end
                  else  if (alarm_off) timeout_led = 0;
            end      
      end
      
      // wire load_enable;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p),
            .cp(start_stop),
            .p_edge(load_enable));   
      
      
      button_cntr_reg_n_p btn_inc_sec(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[1]), .btn_pedge(btn_pedge[1]));   
      button_cntr_reg_n_p btn_inc_min(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[2]), .btn_pedge(btn_pedge[2]));     
      button_cntr_reg_n_p btn_alarm_stop(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn[3]), .btn_pedge(btn_pedge[3]));
      
      
      // wire clk_usec, clk_msec, clk_sec;
      clock_div_100 usec_clk(  
            .clk(clk), .reset_p(reset_p), 
            .cp_div_100(clk_usec));
      
      clock_div_1000 msec_clk(     
            .clk(clk), .reset_p(reset_p),
            .clk_source(clk_usec),
            .cp_div_1000_nedge(clk_msec)); 
            
      clock_div_1000 sec_clk(     
            .clk(clk), .reset_p(reset_p),
            .clk_source(clk_msec),
            .cp_div_1000_nedge(clk_sec));
      
      
      // wire alarm_off, inc_min, inc_sec, btn_start;
      assign {alarm_off, inc_min, inc_sec, btn_start} = btn_pedge;      //btn_pedge의 0비트가 btn_start, 1비트가 inc_sec... 이런 방식으로
      
      // wire [3:0] set_sec1, set_sec10, set_min1, set_min10;      
      counter_bcd_60 counter_sec(
            .clk(clk), .reset_p(reset_p),
            .clk_time(inc_sec),
            .bcd1(set_sec1), .bcd10(set_sec10));
            
      counter_bcd_60 counter_min(
            .clk(clk), .reset_p(reset_p),
            .clk_time(inc_min),
            .bcd1(set_min1), .bcd10(set_min10));
      
      
      // wire [3:0] cur_sec1, cur_sec10, cur_min1, cur_min10;
      // wire dec_clk;
      loadable_down_counter_bcd_60 cur_sec(
            .clk(clk), .reset_p(reset_p),
            .clk_time(clk_sec),
            .load_enable(load_enable),
            .load_bcd1(set_sec1), .load_bcd10(set_sec10),
            .bcd1(cur_sec1), .bcd10(cur_sec10),
            .dec_clk(dec_clk));
      
      loadable_down_counter_bcd_60 cur_min(
            .clk(clk), .reset_p(reset_p),
            .clk_time(dec_clk),
            .load_enable(load_enable),
            .load_bcd1(set_min1), .load_bcd10(set_min10),
            .bcd1(cur_min1), .bcd10(cur_min10));
      
      
      // wire [15:0] value, set_time, cur_time;
      assign set_time = {set_min10, set_min1, set_sec10, set_sec1};
      assign cur_time = {cur_min10, cur_min1, cur_sec10, cur_sec1};
      assign value = start_stop ? cur_time : set_time;
      fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);
      
      

endmodule

 

 

이번에는 JA(왼쪽 상단)을 쓰기 때문에 .xdc 파일도 아래와 같이 수정해야한다.

JA1은 능동부저, JA2는 피동부저를 사용하기 위해 아래와 같이 위치를 지정한 것이다.

 

부저를 연결하기 전의 결과를 살펴보자면,

초기 설정 시간을 정하고 set을 누르면 다운카운터가 실행된다.

0분 0초가 되자마자 초기 설정 시간으로 돌아가고, led0 이 켜지는 것을 확인할 수 있다.

만약 부저도 연결되어 있다면 BTND를 눌러 led와 부저를 모두 끌 수 있다.

cook_timer_00_stop_led_buzz 결과

 

 

 

능동 부저를 연결하였을 때의 사진

 

피동 부저를 연결하였을 때의 사진


주방 타이머 만들기 끝!