[Digital Watch 만들기]
이제까지 만들어놓은 모듈 라이브러리를 활용하여 디지털 시계를 구현해보도록 하자.
[Time Setting]
📌 Block Diagram
디지털 시계의 기능 중 하나인, 시간 설정 기능을 구현해보도록 하자.
시작은 00min : 00sec이며, 시간은 back ground에서 계속 흘러가고,
설정은 흘러가는 시간 위에 적용시킬 수 있도록 설계하였다.
📌 Code & Operation
Clock Library
// usec clock
module clock_usec(
input clk, reset_p,
input enable,
output clk_usec
);
// 125개 카운트하면 1us
reg [6:0] cnt_8nsec;
wire cp_usec; // cp : clock pulse
always @(posedge clk, posedge reset_p) begin
if(reset_p) cnt_8nsec = 0; // reset이 들어오면 cnt = 0
else if(cnt_8nsec >= 124) cnt_8nsec = 0; // 0부터 124번까지 카운트하면 다시 0으로
else cnt_8nsec = cnt_8nsec + 1;
end
assign cp_usec = cnt_8nsec < 63 ? 0 : 1; // 0~62 : 0, 63~124 : 1
// 비정규 clock이므로 동기화가 필요하다.
edge_detector_n edg(.clk(clk), .cp_in(cp_usec), .rst(reset_p), .n_edge(clk_usec)); /// 안 쓰는 출력은 빼버리면 된다.
endmodule
// min clock
module clock_min(
input clk, clk_sec, reset_p,
output clk_min
);
// 60개 카운트하면 1s
reg [5:0] cnt_sec;
reg cp_min; // cp : clock pulse
always @(posedge clk, posedge reset_p) begin
if(reset_p) cnt_sec = 0; // reset이 들어오면 cnt = 0
else if(clk_sec) begin
if (cnt_sec >= 29) begin
cnt_sec = 0; // 0부터 번까지 카운트하면 다시 0으로
cp_min = ~cp_min;
end
else cnt_sec = cnt_sec + 1;
end
end // 이렇게 카운트가 짝수일 경우, 위와 같이 코딩하여 1bit 줄일 수 있다.
// 비정규 clock이므로 동기화가 필요하다.
edge_detector_n edg_min(.clk(clk), .cp_in(cp_min), .rst(reset_p), .n_edge(clk_min)); /// 안 쓰는 출력은 빼버리면 된다.
endmodule
// 1000분주 clock
module clock_div_1000(
input clk, clk_source, reset_p,
output clk_div_1000
);
reg [8:0] cnt_clk_source;
reg cp_div_1000; // cp : clock pulse
always @(posedge clk, posedge reset_p) begin
if(reset_p) cnt_clk_source = 0; // reset이 들어오면 cnt = 0
else if(clk_source) begin
if (cnt_clk_source >= 499) begin
// if (cnt_clk_source >= 999) begin
cnt_clk_source = 0; // 0부터 499번까지 카운트하면 다시 0으로
cp_div_1000 = ~cp_div_1000;
end
else cnt_clk_source = cnt_clk_source + 1;
end
end // 이렇게 카운트가 짝수일 경우, 위와 같이 코딩하여 1bit 줄일 수 있다.
// 비정규 clock이므로 동기화가 필요하다.
edge_detector_n edg_div_1000(.clk(clk), .cp_in(cp_div_1000), .rst(reset_p), .n_edge(clk_div_1000)); /// 안 쓰는 출력은 빼버리면 된다.
endmodule
Counter
// 60bit_Counter
module counter_dec_60(
input clk, reset_p,
input clk_time,
output reg [3:0] dec1, dec10
);
always @(posedge clk, posedge reset_p) begin
if(reset_p) begin
dec1 = 0;
dec10 = 0;
end
else if(clk_time) begin
if(dec1 >= 9) begin
dec1 <= 0;
if(dec10 >= 5) dec10 = 0;
else dec10 <= dec10 + 1;
end
else dec1 <= dec1 + 1;
end
end
endmodule
Btn Controller
module button_cntr(
input clk, reset_p,
input btn,
output btn_pe, btn_ne
);
// DFF를 위한 분주기
reg [16:0] clk_div = 0; // 이렇게 = 0 해놓으면 시뮬레이션에서 자동으로 0으로 초기화된다.
// 보드에서 쓸 때는 시스템적으로 0으로 초기화
always @(posedge clk) clk_div = clk_div + 1;
// Debounce
wire debounced_btn;
D_flip_flop_n debnc(.d(btn), .clk(clk_div[16]), .rst(reset_p), .q(debounced_btn)); // 바운싱 제거용 DFF
// synchronization(Edge Detecting)
edge_detector_n edg(.clk(clk), .cp_in(debounced_btn), .rst(reset_p), .p_edge(btn_pe), .n_edge(btn_ne)); // Edge Detector
// p_edge(btn_pe) : 버튼을 눌렀을 때 동작
// n_edge(btn_ne) : 버튼을 눌렀다 뗄 때 동작
// 둘 중에 필요한 것만 골라서 쓰면 된다.
endmodule
Watch Top
module Watch_Top(
input clk, reset_p,
input [3:0] btn,
output [3:0] com,
output [7:0] seg_7
);
// Clock Library
wire clk_usec, clk_msec, clk_sec;
clock_usec usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
clock_div_1000 msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
clock_div_1000 sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
clock_min min_clk(.clk(clk), .clk_sec(clk_sec), .reset_p(reset_p), .clk_min(clk_min));
// btn
button_cntr btn_incsec(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pe(incsec)); // sec+
button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pe(incmin)); // min+
// Time set
wire incsec, incmin;
wire [3:0] sec1_set, sec10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
assign comp_sec = |{incsec, clk_sec}; // 시간과 up_sec 버튼 입력을 OR 연산(동시에 받음)하여 넣어주면,
assign comp_min = |{incmin, clk_min}; // 시간은 시간대로 버튼입력은 버튼입력대로 각각 따로 받을 수 있다.
counter_dec_60 up_sec(.clk(clk), .reset_p(reset_p), .clk_time(comp_sec), .dec1(sec1_set), .dec10(sec10_set)); // sec up_counter
wire [3:0] min1_set, min10_set;
counter_dec_60 up_min(.clk(clk), .reset_p(reset_p), .clk_time(comp_min), .dec1(min1_set), .dec10(min10_set)); // min up_counter
// Setting Time for FND
reg [15:0] watch_time;
always @(posedge clk, posedge reset_p) begin
if(reset_p) watch_time = 0;
else watch_time = {min10_set, min1_set, sec10_set, sec1_set};
end
// 출력을 위한 FND Controller
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(watch_time), .com(com), .seg_7(seg_7)); /// 컨트롤러가 분주기 가지고 있으니 그냥 clk준다.
endmodule
module Watch_Top(
input clk, reset_p,
input [3:0] btn,
output [3:0] com,
output [7:0] seg_7
);
// Clock Library
wire clk_usec, clk_msec, clk_sec;
clock_usec usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
clock_div_1000 msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
clock_div_1000 sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
clock_min min_clk(.clk(clk), .clk_sec(clk_sec), .reset_p(reset_p), .clk_min(clk_min));
// btn
wire fix_sec, fix_min;
assign fix_sec = btn_fix ? btn[1] : 0 ;
assign fix_min = btn_fix ? btn[2] : 0 ;
button_cntr btn_cntr_fix(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pe(debounced_fix)); // 버튼눌러도 시간 안 바뀌게 고정
t_flip_flop_p tff_fix(.clk(clk), .rst(reset_p), .t(debounced_fix), .q(btn_fix));
button_cntr btn_incsec(.clk(clk), .reset_p(reset_p), .btn(fix_sec), .btn_pe(incsec)); // sec+
button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(fix_min), .btn_pe(incmin)); // min+
// Time set
wire incsec, incmin;
wire [3:0] sec1_set, sec10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
assign comp_sec = |{incsec, clk_sec}; // 시간과 up_sec 버튼 입력을 OR 연산(동시에 받음)하여 넣어주면,
assign comp_min = |{incmin, clk_min}; // 시간은 시간대로 버튼입력은 버튼입력대로 각각 따로 받을 수 있다.
counter_dec_60 up_sec(.clk(clk), .reset_p(reset_p), .clk_time(comp_sec), .dec1(sec1_set), .dec10(sec10_set)); // sec up_counter
wire [3:0] min1_set, min10_set;
counter_dec_60 up_min(.clk(clk), .reset_p(reset_p), .clk_time(comp_min), .dec1(min1_set), .dec10(min10_set)); // min up_counter
// Setting Time for FND
reg [15:0] watch_time;
always @(posedge clk, posedge reset_p) begin
if(reset_p) watch_time = 0;
else watch_time = {min10_set, min1_set, sec10_set, sec1_set};
end
// 출력을 위한 FND Controller
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(watch_time), .com(com), .seg_7(seg_7)); /// 컨트롤러가 분주기 가지고 있으니 그냥 clk준다.
endmodule
[Time Fix 기능]
📌 Block Diagram
기껏 시간을 맞춰놨는데, 실수로 버튼을 눌러서 다시 셋팅해야하는 불상사가 일어나지 않도록
Time Fix 기능을 추가하여 fix 버튼을 누르면 setting 버튼이 동작하지 않도록 설계하였다.
📌 Code & Operation
module Watch_Top(
input clk, reset_p,
input [3:0] btn,
output [3:0] com,
output [7:0] seg_7
);
// Clock Library
wire clk_usec, clk_msec, clk_sec;
clock_usec usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
clock_div_1000 msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
clock_div_1000 sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
clock_min min_clk(.clk(clk), .clk_sec(clk_sec), .reset_p(reset_p), .clk_min(clk_min));
// btn
wire fix_sec, fix_min;
assign fix_sec = btn_fix ? btn[1] : 0 ; // btn_fix 누르면 btn[]가 동작하고 다시 누르면 0이 입력되도록 Muxing
assign fix_min = btn_fix ? btn[2] : 0 ; // 즉, btn_fix 입력에 따라 Setting mode, Fix mode 선택 가능
button_cntr btn_cntr_fix(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pe(debounced_fix)); // 버튼눌러도 시간 안 바뀌게 고정
t_flip_flop_p tff_fix(.clk(clk), .rst(reset_p), .t(debounced_fix), .q(btn_fix));
button_cntr btn_incsec(.clk(clk), .reset_p(reset_p), .btn(fix_sec), .btn_pe(incsec)); // sec+
button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(fix_min), .btn_pe(incmin)); // min+
// Time set
wire incsec, incmin;
wire [3:0] sec1_set, sec10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
assign comp_sec = |{incsec, clk_sec}; // 시간과 up_sec 버튼 입력을 OR 연산(동시에 받음)하여 넣어주면,
assign comp_min = |{incmin, clk_min}; // 시간은 시간대로 버튼입력은 버튼입력대로 각각 따로 받을 수 있다.
counter_dec_60 up_sec(.clk(clk), .reset_p(reset_p), .clk_time(comp_sec), .dec1(sec1_set), .dec10(sec10_set)); // sec up_counter
wire [3:0] min1_set, min10_set;
counter_dec_60 up_min(.clk(clk), .reset_p(reset_p), .clk_time(comp_min), .dec1(min1_set), .dec10(min10_set)); // min up_counter
// Setting Time for FND
reg [15:0] watch_time;
always @(posedge clk, posedge reset_p) begin
if(reset_p) watch_time = 0;
else watch_time = {min10_set, min1_set, sec10_set, sec1_set};
end
// 출력을 위한 FND Controller
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(watch_time), .com(com), .seg_7(seg_7)); /// 컨트롤러가 분주기 가지고 있으니 그냥 clk준다.
endmodule
[min과 sec가 따로 노는 오류 수정]
📌 Error
이전과 같이 코딩하면, sec가 60초가 되어도 min은 clk_min에 맞춰서 따로 동작한다.
즉, up_sec btn을 5번 눌러서 셋팅하면 65초에 min이 1 증가하는 오류가 발생한다.
위와 같은 오류가 발생한 이유는 sec와 min을 동기화시켜주지 않았기 때문이다.
따라서 sec counter에서 60까지 count하면, min_pulse를 발생시켜 min counter에 전달해 줘야 한다.
또한 min_pulse와 min set button 입력을 OR연산하여(동시에 적용되도록) min counter에 넣어주면
sec와 min이 연동되고 up_min도 같이 구현할 수 있다.
📌 Fixed Code & Result
Counter 수정
// 60bit_Counter_puler
module counter_dec_60_pul(
input clk, reset_p,
input clk_time,
output reg [3:0] dec1, dec10,
output reg dec_clk // 다음 자리(예 : minute)에 신호(펄스)를 주기 위한 변수
);
always @(posedge clk, posedge reset_p) begin
if(reset_p) begin
dec1 = 0;
dec10 = 0;
end
else if(clk_time) begin
if(dec1 >= 9) begin
dec1 <= 0;
if(dec10 >= 5) begin
dec10 = 0;
dec_clk <= 1; // dec10, dec1 = 0, 0 이 되고 이후에 5, 9가 되면서 dec_clk 상승 펄스 발생
end
else dec10 <= dec10 + 1;
end
else dec1 <= dec1 + 1;
end
else dec_clk <= 0;
end
endmodule
clk_sec으로 min clock까지 count
module Watch_Top(
input clk, reset_p,
input [3:0] btn,
output [3:0] com,
output [7:0] seg_7
);
// 분주기 인스턴스
wire clk_usec, clk_msec, clk_sec;
clock_usec usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
clock_div_1000 msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
clock_div_1000 sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
// btn
wire fix_sec, fix_min;
assign fix_sec = btn_fix ? btn[1] : 0 ; // btn_fix 누르면 btn[]가 동작하고 다시 누르면 0이 입력되도록 Muxing
assign fix_min = btn_fix ? btn[2] : 0 ; // 즉, btn_fix 입력에 따라 Setting mode, Fix mode 선택 가능
button_cntr btn_cntr_fix(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pe(debounced_fix)); // 버튼눌러도 시간 안 바뀌게 고정
t_flip_flop_p tff_fix(.clk(clk), .rst(reset_p), .t(debounced_fix), .q(btn_fix));
button_cntr btn_incsec(.clk(clk), .reset_p(reset_p), .btn(fix_sec), .btn_pe(incsec)); // sec+
button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(fix_min), .btn_pe(incmin)); // min+
// Time set
wire incsec, incmin, min_pulse;
wire [3:0] sec1_set, sec10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
assign comp_sec = |{incsec, clk_sec}; // 시간과 up_sec 버튼 입력을 OR 연산(동시에 받음)하여 넣어주면,
assign comp_min = |{incmin, min_pulse}; // 시간은 시간대로 버튼입력은 버튼입력대로 각각 따로 받을 수 있다.
counter_dec_60_pul up_sec(.clk(clk), .reset_p(reset_p), .clk_time(comp_sec), .dec1(sec1_set), .dec10(sec10_set), .dec_clk(min_pulse)); // sec up_counter
wire [3:0] min1_set, min10_set;
counter_dec_60 up_min(.clk(clk), .reset_p(reset_p), .clk_time(comp_min), .dec1(min1_set), .dec10(min10_set)); // min up_counter
// Setting Time for FND
reg [15:0] watch_time;
always @(posedge clk, posedge reset_p) begin
if(reset_p) watch_time = 0;
else watch_time = {min10_set, min1_set, sec10_set, sec1_set};
end
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(watch_time), .com(com), .seg_7(seg_7)); /// 컨트롤러가 분주기 가지고 있으니 그냥 clk준다.
endmodule
[구조적 모델링은 지양하자.]
Verilog에서는 정확성, 유연성, 확장성, 고수준 추상화, 그리고 복잡성 관리 등의 이유와
Gate를 사용하여 만들어도어짜피 MUX밖에 없어서 MUX로 구현하기 때문에
구조적 모델링은 지양하고, 동작적 모델링을 지향한다.
📌 Structural Level Modeling Code
// 구조적 모델링
// OR 연산 사용
assign comp_sec = |{incsec, clk_sec};
assign comp_min = |{incmin, min_pulse};
📌 Behavioral Level Modeling Code
// 동작적 모델링
assign comp_sec = clk_sec ? 1 : incsec ? 1 : 0 ;
assign comp_min = min_pulse ? 1 : incmin ? 1 : 0 ;
[Time Setting Mode]
📌 Block Diagram
위에서 Fix button을 구현해서 멋대로 셋팅되는 것을 방지했는데,
실제로 우리가 사용하는 디지털 시계는 Setting mode가 존재하고 해당 mode로 들어가서 시간을 setting한다.
그러므로 loadable counter를 활용하여 Setting mode를 구현해보도록 하자.
📌 Code
Counter
// 60bit_loadable_up_Counter
module loadable_up_counter_dec_60(
input clk, reset_p,
input clk_time, load_enable,
input [3:0] set_value1, set_value10,
output reg [3:0] dec1, dec10,
);
always @(posedge clk, posedge reset_p) begin
if(reset_p) begin
dec1 = 0;
dec10 = 0;
end
else if (load_enable) begin
dec1 = set_value1;
dec10 = set_value10;
end
else if(clk_time) begin
if(dec1 >= 9) begin
dec1 <= 0;
if(dec10 >= 5) dec10 = 0;
else dec10 <= dec10 + 1;
end
else dec1 <= dec1 + 1;
end
end
endmodule
Wach_top
module Watch_Top(
input clk, reset_p,
input [3:0] btn,
output [3:0] com,
output [7:0] seg_7
);
// 분주기 인스턴스
wire clk_usec, clk_msec, clk_sec;
wire upcount_sec;
clock_usec usec_clk(.clk(clk), .reset_p(reset_p), .clk_usec(clk_usec));
clock_div_1000 msec_clk(.clk(clk), .clk_source(clk_usec), .reset_p(reset_p), .clk_div_1000(clk_msec));
clock_div_1000 sec_clk(.clk(clk), .clk_source(clk_msec), .reset_p(reset_p), .clk_div_1000(clk_sec));
clock_min min_clk(.clk(clk), .clk_sec(upcount_sec), .reset_p(reset_p), .clk_min(clk_min));
// set mode select
wire set_mode;
assign upcount_sec = set_mode ? incsec : clk_sec;
// btn
button_cntr btn_cntr_setmode(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .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_incsec(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pe(incsec)); // sec+
button_cntr btn_incmin(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pe(incmin)); // min+
// Time set
wire [3:0] sec1, sec10, min1, min10;
wire [3:0] sec1_set, sec10_set, min1_set, min10_set; // 사용자 경험(UX)애 맞춰서 UI를 설계하는 것이 중요하다. -> min, sec 동작시키자.
// time count
loadable_up_counter_dec_60 cnter_sec(.clk(clk), .reset_p(reset_p), .clk_time(clk_sec), .load_enable(btn_set_pe),
.set_value1(sec1_set), .set_value10(sec10_set), .dec1(sec1), .dec10(sec10)); // sec up_counter
loadable_up_counter_dec_60 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)); // min up_counter
// time set
loadable_up_counter_dec_60 set_sec(.clk(clk), .reset_p(reset_p), .clk_time(incsec), .load_enable(btn_set_pe),
.set_value1(sec1), .set_value10(sec10), .dec1(sec1_set), .dec10(sec10_set)); // sec up_set
loadable_up_counter_dec_60 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)); // min up_set
// cur_time / set_time
wire [15:0] cur_time, set_time;
assign cur_time = {min10, min1, sec10, sec1};
assign set_time = {min10_set, min1_set, sec10_set, sec1_set};
wire [15:0] value;
assign value = set_mode ? set_time : cur_time;
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(value), .com(com), .seg_7(seg_7)); /// 컨트롤러가 분주기 가지고 있으니 그냥 clk준다.
endmodule
set mode로 전환하면 counter값을 불러와 그 것을 기준으로 time setting되고,
setting된 time을 기준으로 count를 시작하게 된다.