1. 스톱워치를 만들기 위한 기본 Flow 및 코드 작성
개괄적인 구상은 다음과 같다. 버튼0(start), 버튼1(lap), 버튼2(clear) 총 세 개를 이용해서 스톱워치를 제어할 것이다. FND 상위 2개는 분(minute), 하위 2개는 초(second)를 나타낸다.
버튼 세 개는 D플립플롭을 통해 Start/Stop과 lap 버튼을 입력으로 주고, 딜레이를 통해 버튼의 바운싱을 제거해서 안정적으로 출력된 Q를 이용한다. 그리고 Q로부터 나온 출력 값을 다시 T플립플롭의 클락으로 입력하여, T플립플롭의 특성을 이용해 On/Off를 구현한다. 한 번 누를 때마다 Q값이 반전될 것이다.
다음 T플립플롭으로부터 나온 각 버튼의 Q값을 always문의 sensitive list로 작동하여 하강엣지일 때, 즉, 버튼을 누른상태에서 떼면 if문을 타고 어떤 버튼을 눌렀는지 선택하여 lap을 출력할지, 현재 시각을 출력할지 정한다.
클락이 들어가는 모든 기능에는 reset을 구현하여 넣어준다.
먼저 코드를 보자.
`timescale 1ns / 1ps
module stop_watch_top(
input clk,
input [2:0] btn,
output [3:0] com_an,
output [6:0] seg_7
);
wire btn_start, btn_lap, btn_clear;
wire reset_n, start, clk_start;
reg [3:0] sec [1:0]; // 배열 : 4비트 sec 변수가 2개 선언됨
reg [3:0] min [1:0]; // 배열 : 4비트 min 변수가 2개 선언됨
reg [3:0] lap [3:0]; //lap값 저장 4비트 lap 4개
reg [3:0] out_value [3:0]; //lap상태에서 lap을 위해 저장할 변수
always @(lap_e) begin //switcher에게 out_value를 줘서 lap의 경우와 시간출력의 경우 두가지를 선택하게 해줌
//lap버튼 누를 때 동작
if(lap_e) begin //t플립플롭으로부터 1이 나오면 lap을 출력해주어야함
out_value[0] = lap[0];
out_value[1] = lap[1];
out_value[2] = lap[2];
out_value[3] = lap[3];
end
else begin //t플립플롭으로부터 0이 나오면 원래 sec와 min을 출력해주어야함
out_value[0] = sec[0];
out_value[1] = sec[1];
out_value[2] = min[0];
out_value[3] = min[1];
end
end
always @(posedge btn_lap or negedge reset_n) begin //lap 구간 스위치 떼면
if(!reset_n) begin
lap[0] = 0;
lap[1] = 0;
lap[2] = 0;
lap[3] = 0;
end
else begin
lap[0] = sec[0];
lap[1] = sec[1];
lap[2] = min[0];
lap[3] = min[1];
end
end
not(reset_n, btn[2]); // 버튼 기본상태가 0이라 이대로 두면 계속 reset이라서 반전시켜서 넣음
// prset은 0일 때 동작하는데 사용안할거라 1을 줌
T_flip_flop_posedge tff_start (.T(1), .clk(btn_start), .reset_n(reset_n), .preset_n(1), .Q(start));
T_flip_flop_posedge tff_lap (.T(1), .clk(btn_lap), .reset_n(reset_n), .preset_n(1), .Q(lap_e));
and (clk_start, start, clk_msec);
always@(negedge clk_sec or negedge reset_n) // 초단위 counter
begin
if(!reset_n) // reset이 0이면 clear
begin
sec[0] = 0;
sec[1] = 0;
end
else if (sec[0] >= 9) // 초의 1의 자리가 9이상이면
begin
sec[0] = 0; // 초의 1의 자리 초기화
if(sec[1] >= 5) // 초의 10의 자리가 5이상이면
begin
sec[1] = 0; // 초의 10의 자리 초기화
end
else sec[1] = sec[1] + 1; // 초의 10의 자리 1올림
end
else sec[0] = sec[0] + 1;
end
always@(negedge clk_min or negedge reset_n) // 분단위 counter
begin
if(!reset_n) // reset이 0이면 clear
begin
min[0] = 0;
min[1] = 0;
end
else if(min[0] >= 9) // 분의 1의 자리가 9이상이면
begin
min[0] = 0; // 분의 1의 자리 초기화
if(min[1] >= 5) // 분의 10의 자리가 5이상이면
begin
min[1] = 0; // 분의 10의 자리 초기화
end
else min[1] = min[1] + 1; // 분의 10의 자리 1올림
end
else min[0] = min[0] + 1;
end
D_flip_flop_posedge dff_start (.D(btn[0]), .E(clk_msec), .Q(btn_start));
D_flip_flop_posedge dff_lap (.D(btn[1]), .E(clk_msec), .Q(btn_lap));
clock_usec uCLK(.clk(clk), .reset_n(reset_n), .clk_usec(clk_usec));
clock_msec mCLK(.clk_usec(clk_usec), .reset_n(reset_n), .clk_msec(clk_msec));
clock_sec sCLK(.clk_msec(clk_start), .reset_n(reset_n), .clk_sec(clk_sec));
clock_min MCLK(.clk_sec(clk_sec), .reset_n(reset_n), .clk_min(clk_min));
FND_4digit_switcher FND_4digit (
.value_1(out_value[0]),
.value_10(out_value[1]),
.value_100(out_value[2]),
.value_1000(out_value[3]),
.clk(clk),
.com_an(com_an),
.seg_7(seg_7)
);
endmodule
TOP 모듈을 코드로 구현하였다. TOP모듈 내에서 쓰이는 시간 계산 모듈(clock_usec, clock_msec, clock_sec, clock_min)과 FND_4digit_switcher 코드는 아래에 있다.
`timescale 1ns / 1ps
module clock_usec(
input clk,
input reset_n,
output reg clk_usec
);
reg [6:0] cnt_usec = 0;
wire clk_100MHz;
assign clk_100MHz = clk;
always @(negedge clk_100MHz or negedge reset_n)
begin
if(!reset_n)
begin
cnt_usec = 0;
clk_usec = 0;
end
else
begin
if(cnt_usec >= 99) cnt_usec = 0; // cnt_usec는 0부터 99까지 100개 count함
else cnt_usec = cnt_usec + 1;
if(cnt_usec < 99) clk_usec = 0;
else clk_usec = 1;
end
end
endmodule
module clock_msec(
input clk_usec,
input reset_n,
output reg clk_msec
);
reg [9:0] cnt_msec = 0;
always @(negedge clk_usec or negedge reset_n)
begin
if(!reset_n)
begin
cnt_msec = 0;
clk_msec = 0;
end
else
begin
if(cnt_msec >= 999) cnt_msec = 0; // cnt_usec는 0부터 99까지 100개 count함
else cnt_msec = cnt_msec + 1;
if(cnt_msec < 999) clk_msec = 0;
else clk_msec = 1;
end
end
endmodule
module clock_sec(
input clk_msec,
input reset_n,
output reg clk_sec
);
reg [9:0] cnt_sec = 0;
always @(negedge clk_msec or negedge reset_n)
begin
if(!reset_n)
begin
cnt_sec = 0;
clk_sec = 0;
end
else
begin
if(cnt_sec >= 999) cnt_sec = 0; // cnt_usec는 0부터 99까지 100개 count함
else cnt_sec = cnt_sec + 1;
if(cnt_sec < 999) clk_sec = 0;
else clk_sec = 1;
end
end
endmodule
module clock_min(
input clk_sec,
input reset_n,
output reg clk_min
);
reg [5:0] cnt_min = 0;
always @(negedge clk_sec or negedge reset_n)
begin
if(!reset_n)
begin
cnt_min = 0;
clk_min = 0;
end
else
begin
if(cnt_min >= 59) cnt_min = 0; // cnt_min은 0부터 59까지 60개 count함
else cnt_min = cnt_min + 1;
if(cnt_min < 59) clk_min = 0;
else clk_min = 1;
end
end
endmodule
///////////////////////FND_4digit_switcher.v
`timescale 1ns / 1ps
module FND_4digit_switcher(
input [3:0] value_1, // fnd 1의 자리
input [3:0] value_10, // fnd 10의 자리
input [3:0] value_100, // fnd 100의 자리
input [3:0] value_1000, // fnd 1000의 자리
input clk,
output reg [3:0] com_an = 4'b1110,
output [6:0] seg_7
);
wire clk_usec, clk_msec;
reg [3:0] hex_value;
clock_usec UCLK(
.clk(clk),
.reset_n(1),
.clk_usec(clk_usec)
);
clock_msec MCLK(
.clk_usec(clk_usec),
.reset_n(1),
.clk_msec(clk_msec)
);
decoder_7_seg dec7seg (
.hex_value(hex_value),
.seg_7(seg_7)
);
always @(negedge clk_msec) // 1msec clk 마다 always문 진입
begin
case(com_an)
4'b1110 : begin
com_an = 4'b1101;
hex_value = value_10;
end
4'b1101 : begin
com_an = 4'b1011;
hex_value = value_100;
end
4'b1011 : begin
com_an = 4'b0111;
hex_value = value_1000;
end
4'b0111 : begin
com_an = 4'b1110;
hex_value = value_1;
end
default : begin // 이외의 경우 선언
com_an = 4'b1110;
hex_value = value_1;
end
endcase
end
endmodule
코드의 전체적인 Flow는 다음과 같다.
크게 watch_top 모듈에서는 버튼 3개(Start / Clear / lap)를 입력 받는다. 먼저 스톱워치의 시작을 위해서는 Start버튼을 눌러야 한다.
Start 버튼을 누르면 reset버튼을 누르거나 Start버튼을 다시 누르기 전까지는 계속해서 시간 계산이 시작된다.(시스템 클락을 사용하고 있기 때문에 어떤 동작을 해도 시간은 계속 계산이 됨)
각각 받은 버튼 값은 플립플롭을 거쳐(바운싱 제거 및 ON/OFF를 위해 플립플롭을 사용) Q로 나온 출력 값은 always문의 클럭으로 작동하여 해당 버튼 기능에 맞게 출력을 해야하는 값을 저장한다.
그리고 저장된 값은 FND_4digit_switcher 모듈로 전송되어 FND의 출력 위치와 값을 결과 값으로 FND에 전송한다.
이 과정을 통해서 FND에 시간이 출력된다.
2. basys3 xdc파일 설정
시스템 클럭, FND, 버튼을 활성화하기 위해 코드에서 작성했던 변수를 각각 알맞게 매핑해준다. 여기서 시스템 클럭은 10ns동안 0ns~5ns까지는 low, 10ns까지는 high로 출력하여 듀티비 50%의 파형을 생성해준다.
3. 결과
오른쪽 버튼은 Start/Stop, 가운데 버튼은 Clear, 왼쪽버튼은 Lab이다. 잘 작동되는 것을 볼 수 있다.
.
'FPGA' 카테고리의 다른 글
[FPGA] 13. Basys3에서 HC-SR04(초음파 센서) 모듈 동작 (TRIG OUTPUT/ECHO INPUT 제어) (0) | 2023.06.04 |
---|---|
[FPGA] 12. 디지털 논리 회로 레지스터 (0) | 2023.05.02 |
[FPGA] 10. Basys3의 LED와 FND를 이용한 10진 카운터 실습 (0) | 2023.04.28 |
[FPGA] 9. 래치(Latch)와 플립플롭(Flip-Flop) (0) | 2023.04.28 |
[FPGA] 8. 2진수를 BCD코드로 변환 프로그램을 Basys3에서 구현하기 (0) | 2023.04.20 |