2024.6.27 수업날
오늘은 어제에 이어서 Basys3 모듈의 fnd에 스톱워치 기능이 나타나도록 소스코드를 작성해본다.
스톱워치를 만들기 위해 여태 배웠던 회로들 중에서 어떻게 구성해야하는지 회로도를 그려보자.
먼저 시스템 클록 입력을 10ns로 하여 100주기 입력에 넣고, 그 출력값인 1us를 1000주기 입력값에 넣고,
그 출력값인 1ms를 1000주기 입력값에 넣어 1s가 되게 만든다.
그리고 60초 카운터에 그 입력을 넣고 Fnd 컨트롤러에 입력값으로 넣으면 Basys3 보드에 출력이 될 것이다.
정말 간단하게 스톱워치 시간 카운터, fnd 컨트롤러만 가지고 회로도를 그리면 될 것 같지만, 사실 이는 비동기 카운터와 형식이 매우 유사하다.
비동기 카운터는 각각 다른 클록 입력을 받기 때문에, pdt(전파지연시간)이 계속해서 누적되어 쓸 수 없게된다.
때문에 이 회로도를 동기 카운터로 바꿔야만 한다.
때문에 아래와 같이 시스템 클록을 입력으로 받는 동기 카운터로 만들어야 한다.
이제 60분주기 카운터를 1s 입력으로 받게 추가하고,
60초 카운터를 1분, 1초로 나누어서 만든 후
16개의 비트로 FND 컨트롤러에 입력으로 넣어준다.
먼저 동기 카운터 1000분주기 소스코드를 만들어보자.
동기 카운터 1000분주기 소스코드
//////////////////////////////////////////////////////////////////2024.6.27
////////////////////////// 1000분주기
module clock_div_1000( //외부에서 들어오는 클럭을 가지고 카운터?--> 1초 카운터 만듦
input clk, reset_p,
input clk_source, //동기 카운터로 만들기 위해 추가함
output cp_div_1000_nedge ); // 클락 펄스 마이크로세크
wire nedge_source, cp_div_1000;
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(clk_source),
.n_edge(nedge_source));
reg [9:0] cnt_clk_source; //1000분주기, 시스템 클락 카운터, 1000개-> 최소 10비트(2^10=1024)
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 >= 999) cnt_clk_source = 0; // 1000분주기, 999보다 크거나 같으면 카운터를 0으로 리셋, 그렇지 않으면 1증가
else cnt_clk_source = cnt_clk_source + 1; // 1씩 증가하는 카운터
end
end
assign cp_div_1000 = (cnt_clk_source < 500) ? 0 : 1; // 1000분주기
edge_detector_n ed1000(
.clk(clk), .reset_p(reset_p), .cp(cp_div_1000),
.n_edge(cp_div_1000_nedge));
endmodule
마지막에 assign 밑에 edge_detector가 어제와 다르게 추가되었다. 이는 래치를 막기 위해 오류를 최소한으로 줄이기 위한 조치이다.
다음으로 60분주기 카운터 소스코드를 만들어보자.
60분주기 카운터 소스코드
/////////////////////////////////////////////////////////////60분주기 카운터
module clock_div_60( // 외부 클럭을 이용하여 1분 주기 클럭 생성 모듈
input clk, reset_p, // 시스템 클럭, 리셋 신호
input clk_source, // 동기 카운터로 만들기 위해 추가, 동기화 클럭 소스
output cp_div_60_nedge ); // 1분 주기 클럭 출력
// 내부 신호
wire nedge_source, cp_div_60; // 클럭 소스의 음의 에지(falling edge) 감지 신호
edge_detector_n ed(
.clk(clk), .reset_p(reset_p), .cp(clk_source),
.n_edge(nedge_source)); // 음의 에지 감지 신호 출력
// reg [5:0] cnt_clk_source; //60분주기, 시스템 클락 카운터, 60개-> 최소 10비트(2^6=64)
integer cnt_clk_source; //비트수를 정해주지 않아도 최적화로 레지스터의 갯수를 알아서 필요한 만큼 만들어줌
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 >= 59) cnt_clk_source = 0; // 60분주기, 59보다 크거나 같으면 카운터를 0으로 리셋, 그렇지 않으면 1증가
else cnt_clk_source = cnt_clk_source + 1; // 1씩 증가하는 카운터
end
end
assign cp_div_60 = (cnt_clk_source < 59) ? 0 : 1; // 래치가 생기므로 오류를 최소화 하기 위한 조치
edge_detector_n ed60(
.clk(clk), .reset_p(reset_p), .cp(cp_div_60),
.n_edge(cp_div_60_nedge)); // 59초 다음에 0초 되기 10ns전서부터 set을 누르면 mode가 바뀜
endmodule
Basys3 보드의 fnd에는 분, 초만 나타나는 스톱워치를 만들 수 있다.
때문에 59분, 59초가 되었을 때 0으로 초기화되도록 만들었고, 어제와 다르게 assign에 cnt_clk_source < 59 로 수정하였다. cnt_clk_source가 59보다 작으면 0이고, 59보다 크면 1이 된다는 의미이다. 즉, 59초에서 00초가 되기 10ns전부터 set(일시정지)기능이 있는 버튼을 누르면 분의 카운터가 올라가는 것이다. 이때, 래치가 생기는 것이다.
마찬가지로 assign 밑에 edge_detector_n이 있다.
래치를 막기 위해 오류를 최소한으로 줄이기 위한 조치이다.
60진 bcd 카운터 소스코드
//////////////////////////////// 60진 bcd load 카운터
module counter_bcd_60(
input clk, reset_p,
input clk_time,
output reg [3:0] bcd1, bcd10); //bcd1은 1의자리, bcd10은 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
bcd1 = 0;
bcd10 = 0;
end
else if(nedge_source) begin
if(bcd1 >= 9) begin
bcd1 = 0;
if(bcd10 >= 5)bcd10 = 0;
else bcd10 = bcd10 + 1;
end
else bcd1 = bcd1 + 1;
end
end
endmodule
다음으로 60진 bcd 카운터 소스코드를 만들었다.
일의 자리가 9가 되면 0으로 초기화가 되고, 십의 자리가 5가 되고 일의 자리가 9가 되면 00으로 초기화 되는 것이다.
이제 지금까지 만든 소스코드와 어제 만든 100분주기 카운터 소스코드를 이용하여
일시정지 기능, 일시정지를 했을 때 분과 초를 각각 증가시킬 수 있는 기능이 있는 스톱워치를 만들어보자.
먼저 분, 초 단위로 카운트가 되는 시계를 만들어본다.
카운터 시계 만들기
//////////////////////////////////////////////// 2024.6.27
//////////////////////////// 카운터 시계 만들기--> 보드에서 테스트 할 때 쓰는 모듈
module watch_top(
input clk, reset_p,
output [7:0] seg_7,
output [3:0] com);
wire clk_usec, clk_msec, clk_sec, clk_min; // 생략해도 1비트 짜리라서 실행은 되지만, wire를 다 쓰도록 습관을 들여라
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(clk_msec));
clock_div_1000 sec_clk(
.clk(clk), .reset_p(reset_p),
.clk_source(clk_msec),
.cp_div_1000(clk_sec));
clock_div_60 min_clk(
.clk(clk), .reset_p(reset_p),
.clk_source(clk_sec),
.cp_div_60(clk_min));
wire [3:0] sec1, sec10, min1, min10; //초의 1의자리, 초의 10의 자리, 분의 1의 자리, 분의 10의 자리
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] value;
assign value = {min10, min1, sec10, sec1};
fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);
endmodule
회로도를 참고하여 필요한 소스코드의 이름을 가져와서 watch_top과 입력, 출력값을 연결해주면 된다.
여기서 wire라는 변수가 쓰였는데, 만약 선언하려는 wire 변수가 1비트라면 그 변수는 생략해도 되지만, 그 이상의 비트를 가진 wire 변수라면 생략해서는 안된다.
그래도 베릴로그는 아직 초보수준이기 때문에, wire 변수를 생략하지 않는 습관을 들이는 것이 좋다.
그리고 .xdc 파일에서 아래와 같이 내용을 수정해준다. 오늘은 Switches를 사용하지 않으니 주석처리해준다.
소스코드를 모두 작성했으면,
어제와 마찬가지로 Basys3보드와 연결하기 위해 Bitsteam을 클릭하여 실행한다.
그러면 Basys3 보드에 분, 초가 카운트되는 결과가 나타나는 것을 확인할 수 있다.
다음으로 버튼을 눌러 일시정지 기능, 일시정지를 했을 때 분과 초를 각각 증가시킬 수 있는 기능을 추가하는 소스코드를 작성한다.
버튼을 눌러 기능이 생긴 스톱워치 만들기
//////////////////////////// 카운터 시계 만들기--> 보드에서 테스트 할 때 쓰는 모듈, 래치를 최소화 했지만, 오류가 아예 없는건 아님
module 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));
assign inc_sec = set_watch ? sec_btn : clk_sec; //set 모드에는 sec_btn, watch에는 clk_sec
assign inc_min = set_watch ? min_btn : clk_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] sec1, sec10, min1, min10; //초의 1의자리, 초의 10의 자리, 분의 1의 자리, 분의 10의 자리
counter_bcd_60 counter_sec(
.clk(clk), .reset_p(reset_p),
.clk_time(inc_sec),
.bcd1(sec1), .bcd10(sec10));
counter_bcd_60 counter_min(
.clk(clk), .reset_p(reset_p),
.clk_time(inc_min),
.bcd1(min1), .bcd10(min10));
wire [15:0] value;
assign value = {min10, min1, sec10, sec1};
fnd_4digit_cntr fnd(clk, reset_p, value, seg_7, com);
endmodule
이번에는 버튼 컨트롤러 소스코드의 입출력을 가져와서 연결해준다.
BTNU(가장 상단의 버튼)을 한 번 누르면 set(일시정지)가 되고 다시 한 번 누르면 카운트가 되는 mode가 바뀌고,
BTNL(가장 좌측의 버튼)을 set 상태일 때 누르면 초가 증가되고,
BTNR(가장 우측의 버튼)을 set 상태일 때 누르면 분이 증가하고,
BTNC(중앙의 버튼)을 누르면 reset(초기화) 기능이 있는 소스코드이다.
.xdc 파일도 아래와 같이 수정한다.
led 0번의 켜짐 유무로 set모드인지, 카운트 모드인지를 구분할 수 있다.
led 0번이 켜지면 set 모드이고, 꺼지면 카운트 모드가 된다.
버튼도 BTNU, BTNL, BTNR로 이름을 수정한다.
Bitstream을 클릭하면 소스코드에서 설정한 기능대로 나타나는 것을 확인할 수 있다.
하지만 watch_top_btn 소스코드에는 래치 현상의 오류가 있다. 오류를 최대한 줄이기 위한 조치를 취했지만, 오류를 완전히 없애는 것은 아니다.
이 오류를 없애기 위해서는 하강에지때 set 모드를 watch(카운터)모드에 로드해야하고, 상승에지때 watch(카운터)모드를 set 모드에 로드하는 소스코드를 작성하면 된다.
먼저 60진 bcd 카운터 소스코드를 로드가 되도록 수정해야한다.
loadable_counter_bcd_60 소스코드
//////////////////////////////// 60진 bcd load 카운터, set모드, watch 모드에 서로 덮어씌움
module loadable_counter_bcd_60(
input clk, reset_p,
input clk_time,
input load_enable,
input [3:0] load_bcd1, load_bcd10,
output reg[3:0] bcd1, bcd10); //bcd1은 1의자리, bcd10은 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
bcd1 = 0;
bcd10 = 0;
end
else begin
if(load_enable) begin
bcd1 = load_bcd1;
bcd10 = load_bcd10;
end
else if(nedge_source) begin
if(bcd1 >= 9) begin
bcd1 = 0;
if(bcd10 >= 5) bcd10 = 0;
else bcd10 = bcd10 + 1; //10의 자리를 1 증가
end
else bcd1 = bcd1 + 1;
end
end
end
endmodule
이제 loadable bcd load 카운터를 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;
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
즉, set_watch가 1이면 set_value 값이 출력되는 것이고, 0이면 watch_value 값이 출력되는 것이다.
사람의 속도로 래치현상을 나타내지 못해 watch_top_btn과 차이가 없어보이지만,
그런 오류가 없는 분, 초가 카운트되는 스톱워치 기능이 있는 것을 Basys3에 구현하였다.
스톱워치 만들기 끝!
'Harman 세미콘 아카데미 > Verilog' 카테고리의 다른 글
2024.7.1 [Verilog]10 - 주방 타이머 만들기 (0) | 2024.07.01 |
---|---|
2024.6.28[Verilog]⑨ 스톱워치 만들기 기능추가+응용 (0) | 2024.06.28 |
2024.6.26 [Verilog]⑦ - 병렬입력-병렬출력 레지스터, 메모리, 스톱워치 만들기 진행단계 (0) | 2024.06.26 |
2024.6.21 [Verilog]⑥ - 링카운터 활용 + basys3 보드 연결, 레지스터 (0) | 2024.06.21 |
2024.6.20 [Verilog]⑤ - 비동기 카운터, 동기카운터, BCD 카운터, 업다운 카운터, 링카운터, 주종형 D플립플롭 (0) | 2024.06.20 |