RTL Design/Verilog 프로젝트
통합 시계
유로 청년
2024. 7. 30. 16:28
1. 개발 목적
- 이전에 배웠던 "일반 시계"와 "Stop Watch", "Cooking Watch" 지식을 기반으로 통합형 시계를 만들고자 한다.
- 이를 통해 일반 시계와 Stop Watch, Cooking Watch에 대해서 복습하고, 여러 시계 모듈을 통합하는 과정에서 디지털 회로도의 이해하는 능력과 Verilog의 프로그래밍 실력을 향상 시킬 수 있다.
2. 시계 동작 설명
- 통합 시계는 총 3가지의 모드를 갖는다.
- 첫 번째 모드) 일반 시계 모드
- 두 번째 모드) Stop Watch 모드
- 세 번째 모드) Cooking Watch 모드 - 각각의 모드들은 다음과 같은 버튼 갯수와 역활을 갖는다.
▶ 일반 시계 모드는 버튼 3개를 갖는다.
- watch / set 버튼 : 시계 모드와 설정 모드를 변경하는 버튼
- 분 컨트롤 버튼 : 설정 모드에서 분 값을 1분씩 증가시켜 조절하는 버튼
- 초 컨트롤 버튼 : 설정 모드에서 초 값을 1초씩 증가시켜 조절하는 버튼
▶ Stop Watch 모드는 버튼 3개를 갖는다.
- Start 버튼 : 해당 버튼을 누를 시, Stop watch가 시작된다.
- Lap 버튼 : 해당 버튼을 누를 시, 누른 시점에 시간을 기록한다.
- Clear 버튼 : 해당 버튼을 누를 시, Stop watch 뿐만 아니라 기록된 시간도 초기화 된다.
▶ Cooking 모드는 버튼 4개를 갖는다.
- Start 버튼 : 분과 초를 설정 한 뒤, 해당 버튼을 누르면 점차적으로 Down Counting 시작된다.
- 분 컨트롤 버튼 : 해당 버튼은 시작 전, 분 값을 세팅하는 버튼
- 초 컨트롤 버튼 : 해당 버튼은 시작 전, 초 값을 세팅하는 버튼
- 알람 끄는 버튼 : Cooking Timer가 0분 0초가 되면 Alarm이 울리게 되며, 해당 버튼을 누르면 알람이 꺼진다. - Basys3에 내장된 스위치와 LED이 이외에도 부족한 1개의 버튼과 외부의 알람을 연결하기 위해
다음과 같이 Port와 Pin을 설정하여 연결하였다.
- Button 4 ( Turn off alarm, btn4) : JC Port의 1핀
- Buzz ( alarm ) : JB Port의 4핀 - 통합형 시계을 구현하는 과정에서 몇 가지 특수한 기능을 추가하였다.
- 알람을 끄는 버튼인 btn4는 Cooking Timer 모드 뿐만 아니라 다른 모드에서도 알람이 울린다면 알람을
끌 수 있어야 한다.
- 각각의 Clear 버튼을 누를 시, 해당 모드에서만 Clear가 적용되어야 한다.
- 각각의 시간들은 독립적으로 동작한다.
3. 코드 설명
- 코드와 함께 각각에 대한 설명을 하도록 하겠다.
< Source, Input / Output 변수 선언 >
module Integrated_Watch_Project(
input clk, reset_p,
input [3:0] btn,
input btn4,
output [3:0] com,
output [7:0] seg_7,
output [2:0] led_debug,
output buzz
);
- basys3의 버튼은 총 4개이지만, 1개의 버튼은 모드 전환 용도로 사용해야 하기 때문에 실제로 사용할 수 있는 버튼은 3개이다.
- 하지만, Cooking Timer 모드에서는 4개의 버튼이 필요하기 때문에 나머지 하나의 버튼은 외부에서 회로를 구성한 뒤, PORT를 통해 연결하게 된다. 이렇게 외부 회로에서 구성된 스위치를 btn4라고 변수명을 정한다.
- led_debug 변수는 현재 어떤 모드에 있는지를 나타낸다.
- buzz는 Cooking Timer 모드에서 0분 0초가 되었을 때, 울리는 알람을 나타낸다.
< Source, State parameter 선언 >
// declare state parameter
parameter S_CLOCK = 3'b001;
parameter S_STOP_WATCH = 3'b010;
parameter S_COOKING_TIMER = 3'b100;
- 통합형 시계에서는 총 3가지 시계 기능을 수행하기 때문에 위와 같이 3개의 state로 구분한 뒤, 각각의 state에 대해서 parameter를 선언하였다.
< Source, 현재 어떤 모드에 있는지 LED로 표시한다. >
// LED for state
assign led_debug = state;
< Source, Get one cycle pulse of button 0, button 4 >
// Get One Cycle pulse of button0, 4
// Button0 : changing mode
// Button4 : Turn off alarm
wire clk_btn0;
button_cntr btn_cntr_btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(clk_btn0));
- button 0 는 시계 모드를 전환하는 버튼이다.
- button 1 ~ 3 는 각각의 시계 모드에서 통합적으로 사용하는 버튼이다.
- button 4는 Cooking Timer 에서만 동작하는 버튼이며, 해당 버튼을 통해 울리는 알람을 끌 수 있다.
- clk_btn0는 Button 0 버튼이 눌렀을 때마다 발생하는 One Cycle Pulse이다.
- Q) 왜 나머지 버튼 (button 1 ~ 4)에 대해서는 One Cycle Pulse를 생성하지 않는가?
A) Button 1 ~ 3번 버튼은 모든 모드에서 사용하는 공통의 버튼이며, Button 4번 버튼은 Cooking Timer 내부에서 알람을 끄는 목적으로 사용하는 버튼이다.
< Source, state와 next_state 변수 선언 >
// declare state, next_state variable
reg [2:0] state, next_state;
< Source, 각 모드의 com, seg_7 변수 선언 >
// declare com, seg_7 variables.
wire [3:0] com_clock, com_stop, com_cook;
wire [7:0] seg_7_clock, seg_7_stop, seg_7_cook;
- 각각의 모듈의 출력 값인 com, seg_7 변수 선언
- 각각의 모드의 모듈로부터 com, seg_7 값을 받은 뒤, 현재의 모드(state)에 따라 현재의 모드의 com, seg_7 값을 선택한 뒤, 7-segment를 통해 해당 데이터 출력한다.
- State diagram와 함께 생각해보는 것을 추천한다.
< Source, 각 모드의 button 변수 선언 >
// Declare Button
reg [2:0] clock_button, stop_button;
reg [3:0] cook_button;
- 각각의 모듈의 instance의 argument 값으로 전달을 위한 버튼 데이터 변수
- 현재 state의 버튼 변수는 버튼 값, btn[1], btn[2], btn[3] 값을 받지만, 나머지 state의 버튼 변수들은 0 값을 받는다.
- 단, 알람 끄는 버튼인 경우, 모든 모드에서 알람이 울렸을 때, 알람을 꺼야하기 때문에 알람 끄는 버튼인 btn4는 모든 모드에서 값이 들어 갈 수 있도록 설계할 필요가 있다.
< Source, State 와 버튼 값 초기화 >
// State transition and button processing logic
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
state = S_CLOCK;
clock_button = 3'b000;
stop_button = 3'b000;
cook_button = 4'b0000;
end else begin
''' (생략) '''
end
end
- state 값은 S_IDLE, 각 모드의 버튼 값은 0으로 초기화 한다.
< Source, Button 0의 One Cycle Pulse인 clk_btn0가 활성화 될 때마다 모드 전환 >
// State transition and button processing logic
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
state = S_CLOCK;
clock_button = 3'b000;
stop_button = 3'b000;
cook_button = 4'b0000;
end else begin
if(clk_btn0) begin // if pressed button 0
case(state)
S_CLOCK: state = S_STOP_WATCH;
S_STOP_WATCH: state = S_COOKING_TIMER;
S_COOKING_TIMER: state = S_CLOCK;
endcase
end
else begin
''' 생략 '''
end
end
end
- clk_btn0 변수는 모드 전환 버튼인 button0의 One Cycle Pulse이다.
- clk_btn0가 활성화 될 때마다 다음 state로 전이된다.
- 단, next_state 변수 사용 없이 바로 state 변수에 대입한다.
< Source, state 변수 값에 따라 각 모드의 버튼 값 설정 >
case(state)
S_CLOCK: begin
clock_button = {btn[1], btn[2], btn[3]};
stop_button = 3'b000;
cook_button = {btn4, 3'b000};
end
S_STOP_WATCH: begin
clock_button = 3'b000;
stop_button = {btn[3], btn[2], btn[1]};
cook_button = {btn4, 3'b000};
end
S_COOKING_TIMER: begin
clock_button = 3'b000;
stop_button = 3'b000;
cook_button = {btn4, btn[3], btn[2], btn[1]};
end
endcase
- state 변수 값을 통해 현재 모드의 버튼 값에 button의 신호 값을 대입한다.
- ex) state = S_STOP_WATCH 일 경우.
- 현재 Stop Watch 모드이기 때문에 Stop watch 버튼 변수에 버튼 신호 값인 btn[1] ~ btn[3] 을 대입한다.
- 나머지 모드의 버튼 값은 0을 대입한다. - Q) 왜 Cooking Button의 btn4 값은 모드와 상관 없이 값을 주는 건가?
- A) btn4는 Alarm off 버튼이다. Alarm이 울리게 되면 모드에 상관 없이 Alarm을 끄기 위해서는 모든 모드에서 btn4 값이 cook_button 변수에 대입할 수 있도록 설계해야 한다. 따라서 btn4 값을 모든 모드에서 값을 주는 이유는 "Alarm이 울리면 모드와 상관 없이 Alarm을 끄기 위해서"이다.
< Source, 각 모드의 모듈의 Instance의 argument 값으로 버튼 값을 준 뒤, com값과 seg_7 값을 출력 값으로 받는다. >
// Module instantiations with direct button connections
watch_top watch_mode (
.clk(clk),
.reset_p(reset_p),
.btn(clock_button),
.com(com_clock),
.seg_7(seg_7_clock)
);
stop_watch_top stop_watch_mode (
.clk(clk),
.reset_p(reset_p),
.btn(stop_button),
.com(com_stop),
.seg_7(seg_7_stop)
);
cook_timer_top cook_timer_mode (
.clk(clk),
.reset_p(reset_p),
.btn(cook_button),
.com(com_cook),
.seg_7(seg_7_cook),
.buzz(buzz)
);
- 각 모드의 모듈의 argument 값으로 각 모드의 버튼 값을 전달한다.
- 각 모드의 모듈 내에서 계산된 시간 데이터를 com 값과 seg_7 값을 통해 받게 된다.
- Q) 각 모드에서 출력 값으로 com 값과 seg_7 값을 주지만, 모듈 내부에서도 시간을 FND로 출력하지 않는가?
A) 각각의 모듈은 com 값과 seg_7 값을 출력으로 top module에 데이터를 전달할 뿐, 최종적으로 Basys3의 FND에 출력으로 내보내는 것은 top module에서 current state 값에 따라 출력할 com 값과 seg_7 값을 결정한다.
만약, 각각의 모듈이 Top module이였다면, 각 모듈 내에서 com 값와 seg_7 값을 출력하지 않고, 모듈내에서 시간 데이터를 FND로 출력했을 것이다. 하지만, Top module은 Integrated_Watch_Project 이기 때문에 최종적인 출력은 Integrated_Watch_Project 모듈에서 결정하고, 이루어 진다.
< Source, 현재의 state 변수 값에 따라 com 값과 seg_7 값을 선택하여 출력한다. >
// Output selection based on current state
assign com = (state == S_CLOCK) ? com_clock :
(state == S_STOP_WATCH) ? com_stop : com_cook;
assign seg_7 = (state == S_CLOCK) ? seg_7_clock :
(state == S_STOP_WATCH) ? seg_7_stop : seg_7_cook;
- 현재 state 값을 통해 현재 모드의 com 값과 seg_7 값을 선택한 뒤, FND를 통해 해당 모드의 시간 데이터를 출력한다.
4. Block diagram
- 위 소스 코드를 Block diagram으로 표현하면 다음과 같이 표현할 수 있다.
5. 구현 영상
6. 전체 소스
module Integrated_Watch_Project(
input clk, reset_p,
input [3:0] btn,
input btn4,
output [3:0] com,
output [7:0] seg_7,
output [2:0] led_debug,
output buzz
);
// declare state parameter
parameter S_CLOCK = 3'b001;
parameter S_STOP_WATCH = 3'b010;
parameter S_COOKING_TIMER = 3'b100;
// LED for state
assign led_debug = state;
// Get One Cycle pulse of button0
// Button0 : changing mode
wire clk_btn0;
button_cntr btn_cntr_btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(clk_btn0));
// declare state, next_state variable
reg [2:0] state;
// declare com, seg_7 variables.
wire [3:0] com_clock, com_stop, com_cook;
wire [7:0] seg_7_clock, seg_7_stop, seg_7_cook;
// Declare Button
// Clock Button : Down : set mode, Left : minute 설정, Right : second 설정
// Stop Watch : Down : clear, Right : start, Left : Lap
// Cooking Watch : Down : minute, Right : start, Left : second.
reg [2:0] clock_button, stop_button;
reg [3:0] cook_button;
// State transition and button processing logic
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
state = S_CLOCK;
clock_button = 3'b000;
stop_button = 3'b000;
cook_button = 4'b0000;
end else begin
if(clk_btn0) begin // if pressed button 0
case(state)
S_CLOCK: state = S_STOP_WATCH;
S_STOP_WATCH: state = S_COOKING_TIMER;
S_COOKING_TIMER: state = S_CLOCK;
endcase
end
else begin
case(state)
S_CLOCK: begin
clock_button = {btn[1], btn[2], btn[3]};
stop_button = 3'b000;
cook_button = {btn4, 3'b000};
end
S_STOP_WATCH: begin
clock_button = 3'b000;
stop_button = {btn[3], btn[2], btn[1]};
cook_button = {btn4, 3'b000};
end
S_COOKING_TIMER: begin
clock_button = 3'b000;
stop_button = 3'b000;
cook_button = {btn4, btn[3], btn[2], btn[1]};
end
endcase
end
end
end
// Module instantiations with direct button connections
watch_top watch_mode (
.clk(clk),
.reset_p(reset_p),
.btn(clock_button),
.com(com_clock),
.seg_7(seg_7_clock)
);
stop_watch_top stop_watch_mode (
.clk(clk),
.reset_p(reset_p),
.btn(stop_button),
.com(com_stop),
.seg_7(seg_7_stop)
);
cook_timer_top cook_timer_mode (
.clk(clk),
.reset_p(reset_p),
.btn(cook_button),
.com(com_cook),
.seg_7(seg_7_cook),
.buzz(buzz)
);
// Output selection based on current state
assign com = (state == S_CLOCK) ? com_clock :
(state == S_STOP_WATCH) ? com_stop : com_cook;
assign seg_7 = (state == S_CLOCK) ? seg_7_clock :
(state == S_STOP_WATCH) ? seg_7_stop : seg_7_cook;
endmodule
7. 기타.
- WNS (Worst Negative Slack)이 2.138로 나타나, Positive slack임을 확인하였다,
- 즉, Required time - Arrival time 값이 양수이기 때문에 신호나 데이터가 목적지까지 가야하는 요구 시간보다 2.138ns 먼저 도착하여 2.138ns 만큼 여유 시간을 갖음을 의미하며, 이는 타이밍 요구사항을 만족함을 의미한다.
- 이번 구현에서 FPGA 내에서 LUT은 273개, Flip Flop은 298개를 사용하였음을 확인할 수 있었다.