Home > Final > 2025-08-06

2025-08-06
Study System Verilog

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 >

alt text

< 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 >

alt text

< 파일 >

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 >

alt text

< Video >

< 고찰 >

단순히 일정 시간 동안 버튼이 눌렸는지를 판단하는 방식이 아닌, 현재 상태와 이전 상태의 차이를 기반으로 버튼이 눌리는 순간과 떼어지는 순간을 감지하는 엣지 검출(edge detection) 방식이 새로웠다.
또한, shift register를 활용해 버튼 입력의 이력을 저장함으로써 노이즈에 강한 안정적인 디바운싱 로직을 구현할 수 있다는 점이 인상 깊었다.

< 파일 >

sources (Homework)

constrs (Homework)