2024.6.28 수업날
오늘은 어제 만들었던 스톱워치에 기능을 추가하고, 응용하는 법에 대해 알아본다.
기능을 추가한 스톱워치를 만들기 전에 어제 만들었던 loadalbe_watch_top_btn 소스코드를 살펴보고 회로도를 그려보자.
소스코드를 만들어도 어떤 모듈의 출력값이 다른 모듈의 입력값으로 받는지, 어느 플립플롭과 mux를 쓰는지 알아야한다.
회로도를 먼저 그려서 각 모듈의 입력값과 출력값이 어떤지 이해하고 소스코드를 만드는 것이 일반적인 순서이다.
loadable_watch_top_btn 소스코드
//////////////////////////// 카운터 시계 만들기--> 보드에서 테스트 할 때 쓰는 모듈
// 하강에지 에서는 set을 watch 에 로드, 상승에지에는 watch를 set에 로드, 오류가 없는 완벽하게 동작하는 시계
module loadable_watch_top_btn(
input clk, reset_p,
input [2:0] btn,
output [7:0] seg_7,
output [3:0] com,
output mode_led);
wire mode, sec_btn, min_btn;
wire set_watch;
wire inc_sec, inc_min;
wire clk_usec, clk_msec, clk_sec, clk_min; // 생략해도 1비트 짜리라서 실행은 되지만, wire를 다 쓰도록 습관을 들여라
button_cntr_reg_n_p btn_mode(
.clk(clk), .reset_p(reset_p),
.btn(btn[0]), .btn_pedge(mode));
button_cntr_reg_n_p btn_sec(
.clk(clk), .reset_p(reset_p),
.btn(btn[1]), .btn_pedge(sec_btn));
button_cntr_reg_n_p btn_min(
.clk(clk), .reset_p(reset_p),
.btn(btn[2]), .btn_pedge(min_btn));
T_flip_flop_p t_mode(
.clk(clk), .reset_p(reset_p),
.t(mode),
.q(set_watch));
//추가한 부분
wire watch_time_load_en, set_time_load_en;
edge_detector_n ed(
.clk(clk), .reset_p(reset_p),
.cp(set_watch),
.n_edge(watch_time_load_en), .p_edge(set_time_load_en));
assign inc_sec = set_watch ? sec_btn : clk_sec; //set 모드에는 sec_btn, watch에는 clk_sec
assign inc_min = set_watch ? min_btn : clk_min; // inc_min 먹스의 출력으로 받는 입력값이 없어서 주석처리함
assign mode_led = set_watch; // led가 켜지면 set
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));
clock_div_60 min_clk(
.clk(clk), .reset_p(reset_p),
.clk_source(inc_sec),
.cp_div_60_nedge(clk_min));
//추가 +수정한 부분
wire [3:0] watch_sec1, watch_sec10, watch_min1, watch_min10; //초의 1의자리, 초의 10의 자리, 분의 1의 자리, 분의 10의 자리
wire [3:0] set_sec1, set_sec10, set_min1, set_min10;
loadable_counter_bcd_60 sec_watch(
.clk(clk), .reset_p(reset_p),
.clk_time(clk_sec),
.load_enable(watch_time_load_en),
.load_bcd1(set_sec1), .load_bcd10(set_sec10),
.bcd1(watch_sec1), .bcd10(watch_sec10));
loadable_counter_bcd_60 min_watch(
.clk(clk), .reset_p(reset_p),
.clk_time(clk_min),
.load_enable(watch_time_load_en),
.load_bcd1(set_min1), .load_bcd10(set_min10),
.bcd1(watch_min1), .bcd10(watch_min10));
loadable_counter_bcd_60 sec_set(
.clk(clk), .reset_p(reset_p),
.clk_time(sec_btn),
.load_enable(set_time_load_en),
.load_bcd1(watch_sec1), .load_bcd10(watch_sec10),
.bcd1(set_sec1), .bcd10(set_sec10));
loadable_counter_bcd_60 min_set(
.clk(clk), .reset_p(reset_p),
.clk_time(min_btn),
.load_enable(set_time_load_en),
.load_bcd1(watch_min1), .load_bcd10(watch_min10),
.bcd1(set_min1), .bcd10(set_min10));
wire [15:0] value, set_value, watch_value;
assign set_value = {set_min10, set_min1, set_sec10, set_sec1};
assign watch_value = {watch_min10, watch_min1, watch_sec10, watch_sec1};
assign value = set_watch ? set_value : watch_value;
fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);
endmodule
여기서는 입력값을 btn, clk, reset으로 설정했고, 출력값을 mode_led, com, seg_7으로 설정하였다.
각 플립플롭에는 reset이 있지만 회로도에서는 생략하였다.
그리고 입력값인 clk는 시스템 시간(10ns)이기 때문에 usec_clk, msec_clk, sec_clk, min_clk에 다같이 입력값으로 받는
동기 카운터여서 그리는 것을 생략하였다.
그리고 회로선에 /4 라고 되어있는 것은 선이 4개(4비트)인 bus로 묶여있는 것을 의미한다.
이제 기능을 추가하는 스톱워치 소스코드를 만들어보자.
먼저 모바일 스톱워치를 예로 들어보자.
모바일 스톱워치를 실행하면, 실행하자마자 카운터가 올라가는 것이 아니라 시작(start)버튼을 눌러야 카운트가 올라간다.
또한, 모바일 스톱워치에는 구간기록(lap)이 있다.
카운터가 계속 올라갈 때 구간기록을 누른 상태의 시간을 저장하며, 카운터는 계속 증가한다.
이러한 start, stop, lap 기능이 있는 스톱워치의 소스코드를 작성해보자.
.xdc 파일에서 사용할 것은 button, 7 Segment Display이므로 수정하지 않아도 된다.
stop_watch_top 소스코드
/////////////////////////////////////////////////// 2024.6.28
///////////////////// 시작, 스톱, 랩 기능이 있는 스톱워치
module stop_watch_top(
input clk, reset_p,
input [1:0] btn,
output [3:0] com,
output [7:0] seg_7);
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_min;
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_1000 sec_clk(
.clk(clk_start), .reset_p(reset_p),
.clk_source(clk_msec),
.cp_div_1000_nedge(clk_sec));
wire [3:0] sec1, sec10, min1, min10;
clock_div_60 min_clk(
.clk(clk_start), .reset_p(reset_p),
.clk_source(clk_sec),
.cp_div_60_nedge(clk_min));
counter_bcd_60 counter_sec(
.clk(clk), .reset_p(reset_p),
.clk_time(clk_sec),
.bcd1(sec1), .bcd10(sec10));
counter_bcd_60 counter_min(
.clk(clk), .reset_p(reset_p),
.clk_time(clk_min),
.bcd1(min1), .bcd10(min10));
// 랩 기능 구현 추가부분
wire [15:0]cur_time ;
assign cur_time = {min10, min1, sec10, sec1};
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);
endmodule
소스코드를 실행하면 바로 카운트가 올라가는 것이 아니라 start 버튼(BTNU)을 눌러야 카운트가 올라가는 것을 확인할 수 있다.
또한, lap 버튼(BTNL)을 클릭한 순간부터 lap기능으로 인해 시간이 저장되고, 카운트는 계속 증가한다. 다시 BTNL을 클릭하면 계속 카운트 되던 현재시간으로 변환한다.
기능은 추가했지만, 버튼을 눌렀을 때 어떤 모드인지 가늠하기는 어렵다.
때문에 led 기능을 추가하여 LED0에 불이 들어오면 stop, LED1에 불이 들어오면 lap인 상태를 나타내는 led 기능도 추가해보자.
stop_watch_top_led 소스코드
///////////////////// led0번에 스톱시작, led1번에 랩 기능을 표시하는 있는 스톱워치
module stop_watch_top_led(
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;
wire 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_min;
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_1000 sec_clk(
.clk(clk_start), .reset_p(reset_p),
.clk_source(clk_msec),
.cp_div_1000_nedge(clk_sec));
wire [3:0] sec1, sec10, min1, min10;
clock_div_60 min_clk(
.clk(clk_start), .reset_p(reset_p),
.clk_source(clk_sec),
.cp_div_60_nedge(clk_min));
counter_bcd_60 counter_sec(
.clk(clk), .reset_p(reset_p),
.clk_time(clk_sec),
.bcd1(sec1), .bcd10(sec10));
counter_bcd_60 counter_min(
.clk(clk), .reset_p(reset_p),
.clk_time(clk_min),
.bcd1(min1), .bcd10(min10));
// 랩 기능 구현 추가부분
wire [15:0]cur_time ;
assign cur_time = {min10, min1, sec10, sec1};
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비트 led로 설정하고, assign 으로 각 led에 불이 켜졌을 때 어떤 기능을 나타내는 것인지 추가하면 된다.
.xdc 파일에는 led를 두개만 쓸 것이기 때문에 상위 2줄만 주석을 풀고 설정한 led로 이름을 바꾼다.
start 버튼(BTNU)을 누르면 LED0의 불이 꺼지고, lap 버튼(BTNL)을 누르면 LED1의 불이 켜지는 것을 확인할 수 있다.
그리고 둘 다 동시에 불이 켜질 수도 있다.
지금까지는 상위 2비트에 분, 하위 2비트에 초를 출력하는 소스코드를 만들었다.
이제는 상위 2비트에 초, 하위 2비트에 1/100초 를 출력하는 소스코드를 만들어보자.
또한 LED0에 불이 들어오면 stop, LED1에 불이 들어오면 lap인 상태를 나타내는 led 기능도 같이 추가한다.
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비트에 1/100초를 출력하는 원리는 다음과 같다. 우리의 목표는 0.01s를 만드는 것이다.
먼저 시스템 클록 시간(clk)으로 10ns을 받는다.
이를 100분주하면 10n x 100 = 1000ns가 된다. 1000ns는 1us이다.
다음으로 1us를 1000분주하면 1us x 1000 = 1000us 가 된다. 1000us는 1ms이다.
1ms를 1000분주하면 1ms x 1000 = 1000ms 가 된다. 1000ms는 1s이다.
여기까지가 상위 2비트에 분, 하위 2비트에 초를 출력하는 스톱워치의 분주기 계산법이다.
하지만 이번에는 상위 2비트에 초, 하위 2비트에 0.01초를 출력하는 것이기 때문에
1ms까지 만든 후, 1ms를 10분주하면 10ms, 즉 0.01s가 되도록 소스코드를 만들어야한다.
그리고 0.01s는 99까지 카운트 되고 상위 2비트인 초로 넘어가기 때문에 99진 bcd 카운터 소스코드도 만들어야한다.
그러므로 clock_library 파일에 10분주기, 99진 bcd 카운터 소스코드를 추가로 만들었다.
10분주기, 99진 bcd 카운터 소스코드
///////////////////////////////////////////////////////// 2024.6.28
////////////////////////// 10분주기
module clock_div_10( //외부에서 들어오는 클럭을 가지고 카운터?--> 1초 카운터 만듦
input clk, reset_p,
input clk_source, //동기 카운터로 만들기 위해 추가함
output cp_div_10_nedge ); // 클락 펄스 마이크로세크
wire nedge_source, cp_div_10;
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(clk_source),
.n_edge(nedge_source));
reg [3:0] cnt_clk_source; //10분주기, 시스템 클락 카운터, 10개-> 최소 4비트(2^4=16)
always @(posedge clk or posedge reset_p) begin // clk는 시스템 클락, always문에는 clk, reset_p만 씀
if(reset_p) cnt_clk_source = 0;
else if(nedge_source)begin
if(cnt_clk_source >= 9) cnt_clk_source = 0; // 10분주기, 9보다 크거나 같으면 카운터를 0으로 리셋, 그렇지 않으면 1증가
else cnt_clk_source = cnt_clk_source + 1; // 1씩 증가하는 카운터
end
end
assign cp_div_10 = (cnt_clk_source < 9) ? 0 : 1; // 10분주기
edge_detector_n ed10(
.clk(clk), .reset_p(reset_p), .cp(cp_div_10),
.n_edge(cp_div_10_nedge));
endmodule
//////////////////////////////// 99진 bcd 카운터
module counter_bcd_99(
input clk, reset_p,
input clk_time,
output reg[3:0] bcd_s1, bcd_s10); //bcd_s1은 1초(1의자리), bcd_s10은 10초(10의자리)
wire nedge_source;
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(clk_time),
.n_edge(nedge_source));
always @ (posedge clk or posedge reset_p) begin
if(reset_p)begin
bcd_s1 = 0;
bcd_s10 = 0;
end
else if(nedge_source) begin
if(bcd_s1 >= 9) begin
bcd_s1 = 0;
if(bcd_s10 >= 9) bcd_s10 = 0;
else bcd_s10 = bcd_s10 + 1; //10의 자리를 1 증가
end
else bcd_s1 = bcd_s1 + 1;
end
end
endmodule
그러면 아래와 같이 결과가 나타나는 것을 확인할 수 있다.
마찬가지로 stop, lap 기능이 실행중이면 led에 불이 켜지고, 둘 다 동시에 불이 켜질 수도 있다.
스톱워치 만들기 기능추가+응용 끝!
'Harman 세미콘 아카데미 > Verilog' 카테고리의 다른 글
2024.7.2 [Verilog]11 - 키패드, 습도센서 (0) | 2024.07.02 |
---|---|
2024.7.1 [Verilog]10 - 주방 타이머 만들기 (0) | 2024.07.01 |
2024.6.27 [Verilog]⑧ - 스톱워치 만들기 (0) | 2024.06.27 |
2024.6.26 [Verilog]⑦ - 병렬입력-병렬출력 레지스터, 메모리, 스톱워치 만들기 진행단계 (0) | 2024.06.26 |
2024.6.21 [Verilog]⑥ - 링카운터 활용 + basys3 보드 연결, 레지스터 (0) | 2024.06.21 |