Review : Counter Design
< Design Specification >
- up/down counter 설계
- mode 0 : up counter
- mode 1 : down counter
- 0.1초 간격으로 counting 동작
- FND에 숫자값 출력 (0000~9999)
- 전원이 인가되면 mode에 맞게 counting 동작
< Block Design >
< Code : UpDownCounter >
`timescale 1ns / 1ps
module UpDownCounter (
input logic clk,
input logic reset,
input logic sw_mode,
output logic [13:0] count
);
logic tick_10hz;
clk_div_10hz U_CLK_DIV_10hz (
.clk (clk),
.reset (reset),
.tick_10hz(tick_10hz)
);
up_down_counter U_UP_DOWN_COUNTER (
.clk (clk),
.reset(reset),
.tick (tick_10hz),
.mode (sw_mode),
.count(count)
);
endmodule
module clk_div_10hz (
input logic clk,
input logic reset,
output logic tick_10hz
);
//logic [23:0] div_counter;
logic [$clog2(10_000_000)-1:0] div_counter;
always_ff @(posedge clk, posedge reset) begin
if (reset) begin
div_counter <= 0;
tick_10hz <= 1'b0;
end else begin
if (div_counter == 10_000_000 - 1) begin
div_counter <= 0;
tick_10hz <= 1'b1;
end else begin
div_counter <= div_counter + 1;
tick_10hz <= 1'b0;
end
end
end
endmodule
module up_down_counter (
input logic clk,
input logic reset,
input logic tick,
input logic mode,
output logic [13:0] count
);
always_ff @(posedge clk, posedge reset) begin
if (reset) begin
count <= 0;
end else begin
if (mode == 1'b0) begin // up counter
if (tick) begin
if (count == 9999) begin
count <= 0;
end else begin
count <= count + 1;
end
end
end else begin // down counter
if (tick) begin
if (count == 0) begin
count <= 9999;
end else begin
count <= count - 1;
end
end
end
end
end
endmodule
< Comment >
clk_div_10hz
system clock = 100_000_000Hz = 100MHz (1초에 1억 싸이클)
clk_div_10hz → 10_000_000 싸이클마다 tick_10hz를 1로 출력 (0.1초 주기)
즉, tick_10hz는 10Hz로 동작 (1초에 10번 펄스 발생)
up_down_counter
초기에 up_counter와 down_counter를 각각 따로 설계하고 top 모듈에서 sw를 통해 출력만 선택(MUX)하려 했으나, 이 경우 mode 전환 시 현재 카운터 값이 아닌 선택된 다른 카운터의 이전 값이 갑자기 출력될 우려가 있었다.
→ 결과적으로 출력 값이 불연속적으로 변할 수 있음따라서 하나의 순차회로(counter)에서 mode를 입력으로 받아, 동일한 레지스터(count)에 대해 up/down 제어를 하는 방식이 연속적인 동작을 보장하고 설계 목표에도 더 적합하다고 판단했다.
< Code : FndController >
`timescale 1ns / 1ps
module FndController (
input logic clk,
input logic reset,
input logic [13:0] number,
output logic [ 3:0] fndCom,
output logic [ 7:0] fndFont
);
logic tick_1khz;
logic [1:0] count;
logic [3:0] digit_1, digit_10, digit_100, digit_1000, digit;
clk_div_1khz U_CLK_DIV_1KHZ (
.clk (clk),
.reset (reset),
.tick_1khz(tick_1khz)
);
counter_2bit U_COUNTER_2BIT (
.clk (clk),
.reset(reset),
.tick (tick_1khz),
.count(count)
);
decoder_2x4 U_DECODER_2X4 (
.x(count),
.y(fndCom)
);
digitSplitter U_DIGITSPLITTER (
.number (number),
.digit_1 (digit_1),
.digit_10 (digit_10),
.digit_100 (digit_100),
.digit_1000(digit_1000)
);
mux_4x1 U_MUX_4X1 (
.sel(count),
.x0 (digit_1),
.x1 (digit_10),
.x2 (digit_100),
.x3 (digit_1000),
.y (digit)
);
BCDtoFND_Decoder U_BCDTOFND_DECODER (
.bcd(digit),
.fnd(fndFont)
);
endmodule
module clk_div_1khz (
input logic clk,
input logic reset,
output logic tick_1khz
);
logic [$clog2(100_000):0] div_counter;
always_ff @(posedge clk, posedge reset) begin
if (reset) begin
div_counter <= 0;
tick_1khz <= 0;
end else begin
if (div_counter == 100_000 - 1) begin
div_counter <= 0;
tick_1khz <= 1;
end else begin
div_counter <= div_counter + 1;
tick_1khz <= 0;
end
end
end
endmodule
module counter_2bit (
input logic clk,
input logic reset,
input logic tick,
output logic [1:0] count
);
always_ff @(posedge clk, posedge reset) begin
if (reset) begin
count <= 0;
end else begin
if (tick) begin
count <= count + 1;
end
end
end
endmodule
module decoder_2x4 (
input logic [1:0] x,
output logic [3:0] y
);
always_comb begin
case (x)
2'b00: y = 4'b1110;
2'b01: y = 4'b1101;
2'b10: y = 4'b1011;
2'b11: y = 4'b0111;
endcase
end
endmodule
module digitSplitter (
input logic [13:0] number,
output logic [ 3:0] digit_1,
output logic [ 3:0] digit_10,
output logic [ 3:0] digit_100,
output logic [ 3:0] digit_1000
);
assign digit_1 = number % 10;
assign digit_10 = number / 10 % 10;
assign digit_100 = number / 100 % 10;
assign digit_1000 = number / 1000 % 10;
endmodule
module mux_4x1 (
input logic [1:0] sel,
input logic [3:0] x0,
input logic [3:0] x1,
input logic [3:0] x2,
input logic [3:0] x3,
output logic [3:0] y
);
always_comb begin
y = 4'b0000;
case (sel)
2'b00: y = x0;
2'b01: y = x1;
2'b10: y = x2;
2'b11: y = x3;
endcase
end
endmodule
module BCDtoFND_Decoder (
input logic [3:0] bcd,
output logic [7:0] fnd
);
always_comb begin
case (bcd)
4'h0: fnd = 8'hc0;
4'h1: fnd = 8'hf9;
4'h2: fnd = 8'ha4;
4'h3: fnd = 8'hb0;
4'h4: fnd = 8'h99;
4'h5: fnd = 8'h92;
4'h6: fnd = 8'h82;
4'h7: fnd = 8'hf8;
4'h8: fnd = 8'h80;
4'h9: fnd = 8'h90;
4'ha: fnd = 8'h88;
4'hb: fnd = 8'h83;
4'hc: fnd = 8'hc6;
4'hd: fnd = 8'ha1;
4'he: fnd = 8'h86;
4'hf: fnd = 8'h8e;
default: fnd = 8'hff;
endcase
end
endmodule
< Comment >
clk_div_1khz
1ms 마다 tick_1khz를 생성
counter_2bit
tick 받을 때마다count
0→1→2→3 순환
decoder_2x4
count
에 따라 FND의 자릿수 선택 (fndCom 출력)
digitSplitter
14비트 입력 숫자를 천/백/십/일의 4자리로 분할
mux_4x1
count
자리에 해당하는 자릿수 값을 선택
BCDtoFND_Decoder
자릿수 값을 FND 디코딩 (fndFont 출력)
< Code : top_UpDownCounter >
`timescale 1ns / 1ps
module top_UpDownCounter (
input logic clk,
input logic reset,
input logic sw_mode,
output logic [3:0] fndCom,
output logic [7:0] fndFont
);
logic [13:0] count;
UpDownCounter U_UPDOWNCOUNTER (
.clk (clk),
.reset (reset),
.sw_mode(sw_mode),
.count (count)
);
FndController U_FNDCONTROLLER (
.clk (clk),
.reset (reset),
.number (count),
.fndCom (fndCom),
.fndFont(fndFont)
);
endmodule
< Schematic >
< 파일 >
sources (Class)
constrs (Class)
Homework
< Design Specification >
- mode 버튼을 눌렀다 뗐을 때 up_counter → down_counter
- mode 버튼을 눌렀다 뗐을 때 down_counter → up_counter
- mode 버튼 1개로 counter 동작 toggle
< 구상 >
기존 스위치 입력을 버튼 입력으로 대체하고자 했으며, 버튼 특성상 노이즈가 발생하므로 디바운싱 처리가 필요하다고 판단했다.
특히, 버튼을 눌렀다 떼는 순간(falling edge)에만 동작이 발생해야 하므로, 엣지 검출 기반 동작 설계가 이번 구현의 핵심이 되었다.
< Code : btn_debounce >
`timescale 1ns / 1ps
module btn_debounce (
input logic clk,
input logic rst,
input logic i_btn,
output logic o_btn
);
parameter F_COUNT = 10_000;
logic [$clog2(F_COUNT)-1:0] r_counter;
logic r_clk;
logic [7:0] q_reg, q_next;
logic w_debounce;
// 10,000Hz clock divider (0.1ms)
always_ff @(posedge clk, posedge rst) begin
if (rst) begin
r_counter <= 0;
r_clk <= 0;
end else begin
if (r_counter == (F_COUNT - 1)) begin
r_counter <= 0;
r_clk <= 1'b1;
end else begin
r_counter <= r_counter + 1;
r_clk <= 1'b0;
end
end
end
// shift register
always_comb begin
q_next = {i_btn, q_reg[7:1]};
end
always_ff @(posedge r_clk, posedge rst) begin
if (rst) begin
q_reg <= 0;
end else begin
q_reg <= q_next;
end
end
// 8 input and gate
assign w_debounce = &q_reg;
logic r_edge_q;
// edge detector
always_ff @(posedge clk, posedge rst) begin
if (rst) begin
r_edge_q <= 0;
end else begin
r_edge_q <= w_debounce;
end
end
// rising edge
// assign o_btn = (~r_edge_q) & w_debounce;
// falling edge
assign o_btn = r_edge_q & ~w_debounce;
endmodule
< Comment >
10,000Hz clock divider
0.1ms 마다 tick_1khz를 생성
shift register
0.1ms마다 버튼 입력 i_btn을 가장 상위 비트에 넣고, 기존 값은 한 칸씩 오른쪽으로 밀어 저장
→ 최근 0.8ms 간의 입력 이력을 저장
8 input and gate
8비트 shift register의 모든 비트가 1일 때만 w_debounce = 1
→ 버튼이 0.8ms 이상 안정적으로 눌렸는지를 판별
edge detector
w_debounce의 상태를 한 클럭 지연시킨 r_edge_q와 비교
→ 현재 상태와 이전 상태의 차이를 기반으로 버튼 변화 시점을 감지
falling edge
r_edge_q = 1, w_debounce = 0인 경우에만 o_btn = 1
→ 버튼이 떼어지는 순간(falling edge)만 한 클럭 동안 출력
< Code : UpDownCounter (up_down_counter) >
// ...
logic toggle_state;
always_ff @(posedge clk or posedge reset) begin
if (reset) begin
toggle_state <= 0;
end else begin
if (mode) begin
toggle_state <= ~toggle_state;
end
end
end
//...
// counter 변환 조건 변경 : mode → toggle_state
< Schematic >
< Video >
< 고찰 >
단순히 일정 시간 동안 버튼이 눌렸는지를 판단하는 방식이 아닌, 현재 상태와 이전 상태의 차이를 기반으로 버튼이 눌리는 순간과 떼어지는 순간을 감지하는 엣지 검출(edge detection) 방식이 새로웠다.
또한, shift register를 활용해 버튼 입력의 이력을 저장함으로써 노이즈에 강한 안정적인 디바운싱 로직을 구현할 수 있다는 점이 인상 깊었다.
< 파일 >
sources (Homework)
constrs (Homework)