module ring_counter_fnd(
input clk, reset_p,
output reg [3:0] com);
reg [20:0] clk_div = 0; //레지스터에 0주는건 시뮬레이션에서만 가능, 회로에서는 레지스터에 0을 줄수없기때문, 리셋으로 0줄수있음
always @(posedge clk)clk_div = clk_div + 1;
wire clk_div_nedge; //와이어에 0주는건 접지시키는것, 거기에 1들어가면 쇼트
edge_detector_p ed(.clk(clk), .reset_p(reset_p), .cp(clk_div[16]), .n_edge(clk_div_nedge));
always @(posedge clk or posedge reset_p)begin
if(reset_p)com=4'b1110;
else if(clk_div_nedge)begin
if(com == 4'b0111)com=4'b1110;
else com = {com[2:0], 1'b1};
end
end
endmodule
대략 1ms의 주기를 위해 21개비트 크기의 레지스터중 17번째비트를 사용하요 분주(2^17*10ns=1.3ms), FND의 com에 0인곳이 출력된다
시뮬레이션 결과

com의 0이 차례로 쉬프트(1110->1101->1011->0111->1110), clk마다 clk_div가 카운트하고 17번째 마다 펄스디텍터가 펄스발생
4개의 7세그먼트중 한번에 하나씩만 키면서 지나가서 스위치에 따른 정해진 수를 표현
module fnd_test_top(
input clk, reset_p,
input [15:0] switch,
output [3:0] com,
output [7:0] seg_7);
ring_counter_fnd(clk, reset_p, com);
reg [3:0] hex_value;
always @(posedge clk)begin
case(com)
4'b1110 : hex_value = switch[3:0];
4'b1101 : hex_value = switch[7:4];
4'b1011 : hex_value = switch[11:8];
4'b0111 : hex_value = switch[15:12];
endcase
end
decoder_7seg(.hex_value(hex_value), .seg_7(seg_7));
endmodule
인스턴스화하여 저장
module fnd_test_top( //보드에 연결되는 것들(xdc포함)
input clk, reset_p,
input [15:0] switch,
output [3:0] com,
output [7:0] seg_7);
fnd_cntr(.clk(clk), .reset_p(reset_p), .value(switch), .com(com), .seg_7(seg_7)); //fnd_cntr에 연결
endmodule
module fnd_cntr(
input clk, reset_p,
input [15:0] value,
output [3:0] com,
output [7:0] seg_7);
ring_counter_fnd(clk, reset_p, com);
reg [3:0] hex_value;
always @(posedge clk)begin
case(com)
4'b1110 : hex_value = value[3:0];
4'b1101 : hex_value = value[7:4];
4'b1011 : hex_value = value[11:8];
4'b0111 : hex_value = value[15:12];
endcase
end
decoder_7seg(.hex_value(hex_value), .seg_7(seg_7));
endmodule
//60분주기
module clock_div_60(
input clk, reset_p,
input clk_source,
output clk_div_60,
output clk_div_60_nedge);
reg [5:0]/*integer*/ cnt_clksource; //32비트, 남는건 상관없음
wire clk_source_nedge;
edge_detector_n ed_source(
.clk(clk), .reset_p(reset_p), .cp(clk_source),
.n_edge(clk_source_nedge));
always @(negedge clk or posedge reset_p)begin
if(reset_p)cnt_clksource = 0;
else if(clk_source_nedge)begin
if(cnt_clksource >= 59) cnt_clksource = 0;
else cnt_clksource = cnt_clksource + 1;
end
end
assign clk_div_60 = (cnt_clksource < 30) ? 0 : 1;
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(clk_div_60),
.n_edge(clk_div_60_nedge));
endmodule
module counter_bcd_60(
input clk, reset_p,
input clk_time,
output reg [3:0] bcd1, bcd10);
wire clk_time_nedge;
edge_detector_n ed_clk(
.clk(clk), .reset_p(reset_p), .cp(clk_time),
.n_edge(clk_time_nedge));
always @(posedge clk or posedge reset_p)begin
if(reset_p)begin
bcd1 = 0;
bcd10 = 0;
end
else if(clk_time_nedge)begin
if(bcd1 >= 9)begin
bcd1 = 0;
if(bcd10 >= 5)bcd10 = 0;
else bcd10 = bcd10 + 1;
end
else bcd1 = bcd1 + 1;
end
end
endmodule
module watch_top(
input clk, reset_p,
output [3:0] com,
output [7:0] seg_7);
wire clk_usec, clk_msec, clk_sec, clk_min; //분주기 인스턴스 사용
clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_usec)); //100분주기 1us로 만듬
clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_usec), .clk_div_1000(clk_msec)); //1000분주기 1ms로 만듬
clock_div_1000 sec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_msec), .clk_div_1000(clk_sec)); //1000분주기 1s로 만듬
clock_div_60 min_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_sec), .clk_div_60(clk_min)); //60분주기 1분 만듬
wire [3:0] sec1, sec10, min1, min10; //BCD 60진 카운터 인스턴스 사용
counter_bcd_60 counter_sec(.clk(clk), .reset_p(reset_p), .clk_time(clk_sec), .bcd1(sec1), .bcd10(sec10)); //1의자리 초, 10의자리 초
counter_bcd_60 counter_min(.clk(clk), .reset_p(reset_p), .clk_time(clk_min), .bcd1(min1), .bcd10(min10)); //1의자리 분, 10의자리 분
wire [15:0] value;
assign value = {min10, min1, sec10, sec1}; //결합연산자 사용하여 FND에 표시
fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(value), .com(com), .seg_7(seg_7));
endmodule
버튼 추가
.clk_source(clk_sec)를 .clk_source(inc_sec)로 변경
모드버튼을 눌러 멈춰도 내부적으로 시간을 계속 카운터 되서 모드버튼을 다시 눌러 시간이 진행되면 60초가 되지 않았는데도 분 자리에 1분 올라감
0.5초, 30초동안 활성화되는 클락을 펄스로 바꿔서 10ns동안만 활성화 이때 버튼이 같이 눌려 같은 오류가 생길확률 극히 낮음
(다음 장에서 클락으로 신호를 보내는 모드와, 버튼으로 신호를 보내는 모드 분리)
module watch_top(
input clk, reset_p,
input [2:0] btn,
output [3:0] com,
output [7:0] seg_7);
wire btn_mode; //버튼 입력 펄스를 알기위함
wire btn_sec; //버튼 입력 펄스를 알기위함
wire btn_min; //버튼 입력 펄스를 알기위함
wire set_watch; //모드 변경 토글
wire inc_sec, inc_min;
wire clk_usec, clk_msec, clk_sec, clk_min; //분주기 인스턴스 사용
wire [3:0] sec1, sec10, min1, min10; //BCD 60진 카운터 인스턴스 사용
wire [15:0] value;
edge_detector_n ed_btn0(
.clk(clk), .reset_p(reset_p), .cp(btn[0]),
.p_edge(btn_mode));
edge_detector_n ed_btn1(
.clk(clk), .reset_p(reset_p), .cp(btn[1]),
.p_edge(btn_sec));
edge_detector_n ed_btn2(
.clk(clk), .reset_p(reset_p), .cp(btn[2]),
.p_edge(btn_min));
T_flip_flop_p t_mode(.clk(clk), .reset_p(reset_p), .t(btn_mode), .q(set_watch)); //T플립플롭의 파워 리셋으로 q=0
assign inc_sec = set_watch ? btn_sec : clk_sec; //mux사용
assign inc_min = set_watch ? btn_min : clk_min;
clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_usec)); //100분주기 1us로 만듬
clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_usec), .clk_div_1000(clk_msec)); //1000분주기 1ms로 만듬
clock_div_1000 sec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_msec), ./*clk_div_1000*/clk_div_1000_nedge(clk_sec)); //1000분주기 1s로 만듬
clock_div_60 min_clk(.clk(clk), .reset_p(reset_p), .clk_source(inc_sec), ./*clk_div_60*/clk_div_60_nedge(clk_min)); //60분주기 1분 만듬
//30초 이후, 0.5초이후 에 모드버튼을 눌러도 분, 초가 올라가는 현상 해결
//clk_time 변경
counter_bcd_60 counter_sec(.clk(clk), .reset_p(reset_p), .clk_time(inc_sec), .bcd1(sec1), .bcd10(sec10)); //1의자리 초, 10의자리 초
counter_bcd_60 counter_min(.clk(clk), .reset_p(reset_p), .clk_time(inc_min), .bcd1(min1), .bcd10(min10)); //1의자리 분, 10의자리 분
assign value = {min10, min1, sec10, sec1}; //결합연산자 사용하여 FND에 표시
fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(value), .com(com), .seg_7(seg_7));
endmodule
xdc변경
##Buttons
set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports reset_p]
set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports btn[0]]
set_property -dict { PACKAGE_PIN W19 IOSTANDARD LVCMOS33 } [get_ports btn[1]]
set_property -dict { PACKAGE_PIN T17 IOSTANDARD LVCMOS33 } [get_ports btn[2]]
#set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports btnD]
채터링은 보통 1ms안에 끝난다, 1ms클럭의 D플립플롭을 쓰면 상승엣지에서 한번만 읽기 때문에 채터링 구간을 넘길 수 있다.




1ms클럭을 넣으면 기존 10ns클럭으로 사용하던 것들과 맞지않아 비동기, enable포트로 엣지검출기로 1ms마다 펄스를 넣어 enable시키면, 버튼이 눌려지면 1ms마다 버튼 읽음
버튼 컨트롤 모듈
module button_cntr(
input clk, reset_p,
input btn,
output btn_pedge, btn_nedge);
reg [20:0] clk_div = 0;
always @(posedge clk)clk_div = clk_div + 1;
wire clk_div_nedge;
edge_detector_p ed_clk(.clk(clk), .reset_p(reset_p), .cp(clk_div[16]), .n_edge(clk_div_nedge)); //대략1.3ms주기를 주기위해 16번 비트 사용
reg debounced_btn;
always @(posedge clk or posedge reset_p)begin
if(reset_p)debounced_btn = 0;
else if(clk_div_nedge)debounced_btn = btn;
end
edge_detector_p ed_btn(.clk(clk), .reset_p(reset_p),
.cp(debounced_btn), .n_edge(btn_nedge), .p_edge(btn_pedge));
endmodule
버튼 컨트롤 모듈 인스턴스
module watch_top(
input clk, reset_p,
input [2:0] btn,
output [3:0] com,
output [7:0] seg_7);
wire btn_mode; //버튼 입력 펄스를 알기위함
wire btn_sec; //버튼 입력 펄스를 알기위함
wire btn_min; //버튼 입력 펄스를 알기위함
wire set_watch; //모드 변경 토글
wire inc_sec, inc_min;
wire clk_usec, clk_msec, clk_sec, clk_min; //분주기 인스턴스 사용
wire [3:0] sec1, sec10, min1, min10; //BCD 60진 카운터 인스턴스 사용
wire [15:0] value;
//채터링 보완
button_cntr btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_mode));
button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_sec));
button_cntr btn2(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_min));
/* //채터링 생김
edge_detector_n ed_btn0(
.clk(clk), .reset_p(reset_p), .cp(btn[0]),
.p_edge(btn_mode));
edge_detector_n ed_btn1(
.clk(clk), .reset_p(reset_p), .cp(btn[1]),
.p_edge(btn_sec));
edge_detector_n ed_btn2(
.clk(clk), .reset_p(reset_p), .cp(btn[2]),
.p_edge(btn_min));
*/
T_flip_flop_p t_mode(.clk(clk), .reset_p(reset_p), .t(btn_mode), .q(set_watch)); //T플립플롭의 파워 리셋으로 q=0
assign inc_sec = set_watch ? btn_sec : clk_sec; //mux사용
assign inc_min = set_watch ? btn_min : clk_min;
clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_usec)); //100분주기 1us로 만듬
clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_usec), .clk_div_1000(clk_msec)); //1000분주기 1ms로 만듬
clock_div_1000 sec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_msec), ./*clk_div_1000*/clk_div_1000_nedge(clk_sec)); //1000분주기 1s로 만듬
clock_div_60 min_clk(.clk(clk), .reset_p(reset_p), .clk_source(inc_sec), ./*clk_div_60*/clk_div_60_nedge(clk_min)); //60분주기 1분 만듬
//30초 이후, 0.5초이후 에 모드버튼을 눌러도 분, 초가 올라가는 현상 해결
//clk_time 변경
counter_bcd_60 counter_sec(.clk(clk), .reset_p(reset_p), .clk_time(inc_sec), .bcd1(sec1), .bcd10(sec10)); //1의자리 초, 10의자리 초
counter_bcd_60 counter_min(.clk(clk), .reset_p(reset_p), .clk_time(inc_min), .bcd1(min1), .bcd10(min10)); //1의자리 분, 10의자리 분
assign value = {min10, min1, sec10, sec1}; //결합연산자 사용하여 FND에 표시
fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(value), .com(com), .seg_7(seg_7));
endmodule
클락 리셋선은 그리면 그림이 복잡해지므로 생략, 있다는 표시만 함



app.diagrams.net