Language/Verilog

Vivado : Basys3 FND / 디지털 시계, 분주

짱도르딘 2024. 7. 17. 19:00
728x90

□ 본 게시글은 수업 내용을 바탕으로 주관적으로 작성한 것이기 때문에, 틀리거나 오류가 있을 수 있습니다!!!

 

 

FND Control

 

FND 출력을 링카운터를 활용해 시프트 동작을 시켜 1110 -> 1101 -> 1011 -> 0111과 같은 동작을 반복하려고 한다.

 

따라서 다음과 같은 코드를 작성하였다.

 

코드를 살펴보면, reset이 발생하였을 때 1110으로 초기화된 후, clk_div_nedge가 발생할 때마다 1을 1비트씩 옆으로 이동하도록 하였다. 옆으로 이동하는 동작은 "else com = {com [2:0], 1'b1};" 문장에서 실행이 된다.

 

 

module ring_counter_fnd2(
    input clk, reset_p,
    output reg [3:0] com);
    
    reg [20:0] clk_div = 0;
    always @(posedge clk)clk_div = clk_div + 1;
    
    wire clk_div_nedge;
    edge_detector_p ed(.clk(clk), .reset_p(reset_p), .cp(clk_div[16]), .n_edge(clk_div_nedge));    
    
    always @(posedge clk or posedge reset_p)begin
    if(reset_p)com = 4'b1110;
    else if(clk_div_nedge)begin
        if(com == 4'b0111) com = 4'b1110;
        else com = {com[2:0], 1'b1};
        end
    end
endmodule

 

 

 

0이 왼쪽으로 시프트 되는 것을 확인할 수 있다.


위 코드를 Basys3로 직접 확인하기 위하여 아래와 같은 코드를 작성하였다.

 

위에서 만든 ring_counter_fnd2를 불러온 후, case문을 활용하여 작성하였다.

아래에 언급된 decoder_7 seg는 7-Segment 숫자를 표현하기 위해서 언급되었다.

 

case문을 살펴보면, switch [3:0] 은 첫 번째 비트의 숫자를 표현하는 데에, switch [7:4]는 두 번째, 그 뒤로는 세 번째, 네 번째 숫자를 표현하기 위해서 작성한 것을 확인할 수 있다.

 

작성된 코드는 아래의 코드와 같다.

module fnd_test_top(
    input clk, reset_p,
    input [15:0] switch,
    output [3:0] com,
    output [7:0] seg_7);
    
    ring_counter_fnd2(clk, reset_p,com); 
    
    reg [3:0] hex_value; 
    
    always @(posedge clk)begin
        case(com)
            4'b1110: hex_value = switch[3:0];
            4'b1101: hex_value = switch[7:4];
            4'b1011: hex_value = switch[11:8];
            4'b0111: hex_value = switch[15:12]; 
        endcase
    end   
    
    decoder_7seg(.hex_value(hex_value), .seg_7(seg_7));
    
endmodule

 

코드를 실행시키면 다음과 같이 출력되는 것을 확인할 수 있다.


디지털시계

이번에는 분과 초를 나타내는 디지털시계를 구현해보려 한다.

 

분과 초를 나타내는 시계를 구현하기 위해서는 기본적으로 분과 초를 구성하는 모듈을 생성해야 한다.

 

1ns를 1us로, 1us를 1ms로, 최종적으로는 1ms를 1 sec로 만들기 위해 분주를 사용한다.


시간제어

 

우선 1ns를 1us로 만들어보기 위해 아래와 같은 코드를 작성하였다.

 

코드를 살펴보면, 100 분주 동작을 실행시키기 위한 코드로 system clock이 99 이상일 때 0으로 초기화를 하며, 다시 clock을 증가시키는 동작을 계속 반복한다. 만일 clock이 50 이상일 때에는 1을 주게 되어 결국 주기는 100으로 된다. 즉, 10ns에 100을 곱하게 되면 1us가 되는 원리이다.

 

"cnt_sysclk"을 7bit로 준 이유는, 10진수 100을 2진수로 변환하면 최고자리의 1의 위치가 7번째이기 때문에 7bit로 준 것이다.

module clock_div_100(
    input clk, reset_p,
    output clk_div_100,
    output clk_div_100_nedge);
    
    reg [6:0] cnt_sysclk;
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)cnt_sysclk = 0;
        else begin
            if(cnt_sysclk >= 99) cnt_sysclk = 0;
            else cnt_sysclk = cnt_sysclk + 1;
        end    
    end
    
    assign clk_div_100 = (cnt_sysclk < 50) ? 0 : 1;
    
    edge_detector_n ed(
    .clk(clk), .reset_p(reset_p), .cp(clk_div_100),
    .n_edge(clk_div_100_nedge));
    
endmodule

다음은 1us를 1ms로 만들기 위해 1000 분주를 하는 코드를 구현해 보았다.

 

"cnt_clksource"에 10bit를 주는 이유는, 1000을 2진수로 변환하였을 때  "0011 1110 1000"이 나오기 때문에 1이 끝나는 부분이 10번째이기 때문에 10bit를 준 것이다.

 

1 sec 또한 밑의 코드를 사용하여 1ms로 1 sec을 구현하였다. 

module clock_div_1000(
    input clk, reset_p,
    input clk_source,
    output clk_div_1000,
    output clk_div_1000_nedge);
    
    reg [9:0] cnt_clksource;
    wire clk_source_nedge;
    
    edge_detector_n ed_source(
    .clk(clk), .reset_p(reset_p), .cp(clk_source),
    .n_edge(clk_source_nedge));    
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)cnt_clksource = 0;
        else if(clk_source_nedge)begin
            if(cnt_clksource >= 999) cnt_clksource = 0;
            else cnt_clksource = cnt_clksource + 1;
        end    
    end
    
    assign clk_div_1000 = (cnt_clksource < 500) ? 0 : 1;
    
    edge_detector_n ed(
    .clk(clk), .reset_p(reset_p), .cp(clk_div_1000),
    .n_edge(clk_div_1000_nedge));
    
endmodule

이제 1 sec를 1 min로 만들어보자. 1 min을 만들기 위해서는 60 sec이 필요하므로 아래와 같은 코드로 구현하였다.

 

위의 과정과 동일하게 10진수 60을 2진수로 변환하면 "0011 1100"이기 때문에 6bit로 선언하였다.

module clock_div_60(
    input clk, reset_p,
    input clk_source,
    output clk_div_60,
    output clk_div_60_nedge);
    
    reg [5:0] cnt_clksource;
    wire clk_source_nedge;
    
    edge_detector_n ed_source(
    .clk(clk), .reset_p(reset_p), .cp(clk_source),
    .n_edge(clk_source_nedge));    
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)cnt_clksource = 0;
        else if(clk_source_nedge)begin
            if(cnt_clksource >= 59) cnt_clksource = 0;
            else cnt_clksource = cnt_clksource + 1;
        end    
    end
    
    assign clk_div_60 = (cnt_clksource < 30) ? 0 : 1;
    
    edge_detector_n ed(
    .clk(clk), .reset_p(reset_p), .cp(clk_div_60),
    .n_edge(clk_div_60_nedge));
    
endmodule

 


버튼제어

 

이제 시간 관련 작업은 끝났으므로, 시간을 제어할 버튼에 관하여 알아볼 것이다.

 

버튼은 총 3개를 사용할 것이며, 각각의 역할은 mode, sec, min을 제어하는 것이다.

 

mode를 선택할 시, 시간이 흐르는 동작이 멈추게 되며, min과 sec은 분과 초를 1씩 올릴 수 있도록 설정하였다.

 

버튼 제어를 구현할 코드는 아래와 같다.

 

우선, negative edge에 반응할 수 있도록 " edge detector" 모듈을 선언하여 각 핀들을 제어하였다.

module button_cntr(
    input clk, reset_p,
    input btn,
    output btn_pedge, btn_nedge);
    
    reg [20:0] clk_div = 0;
    always @(posedge clk)clk_div = clk_div + 1;
    
    wire clk_div_nedge;
    edge_detector_p ed(.clk(clk), .reset_p(reset_p), .cp(clk_div[16]), .n_edge(clk_div_nedge));
    
    reg debounced_btn;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)debounced_btn = 0;
        else if(clk_div_nedge)debounced_btn = btn;
    end 
    
    edge_detector_p ed_btn(.clk(clk), .reset_p(reset_p), .cp(debounced_btn), .n_edge(btn_nedge), .p_edge(btn_pedge));
    
endmodule

설정 제어

이번에 구현할 시계에서는 설정 모드와 일반적인 동작모드가 존재한다. 이러한 설정모드를 제어하기 위해서 T F/F 모듈을 선언하였다. T F/F 모듈의 코드는 아래와 같다.

 

T F/F의 용도는 Toggle이 주 목적이기 때문에 T가 입력될 때마다 플립플롭의 값이 토글 된다.

module T_flip_flop_p(
    input clk, reset_p,
    input t,
    output reg q);
    
    always @(posedge clk or posedge reset_p)begin
        if(reset_p)q = 0;
        else begin
            if(t) q = ~q    ;
            else q = q;
        end
    end                    
endmodule

 

아래의 코드는 시계를 구현할 코드에서의 T F/F의 역할을 나타낸다.

 

플립플롭에 입력이 있을 때마다 "set_watch"는 활성화되며, "set_watch"의 값에 따라 코드에 나온 바와 같이 sec과 min이 제어된다. "inc(increment)_sec/min"은 "set_watch"이 1이 될 때마다 sec/min 에 clock을 인가하여 1씩 증가하게 한다.

    T_flip_flop_p t_mode(.clk(clk), .reset_p(reset_p), .t(btn_mode), .q(set_watch));
    assign inc_sec = set_watch ? btn_sec : clk_sec; 
    assign inc_min = set_watch ? btn_min : clk_min;

디지털시계 구현 코드

 

이제 위에서 정리된 코드들을 종합하여 코드를 구현하면 아래와 같이 구현이 되는 것을 확인할 수 있다.

 

module watch_top(
    input clk, reset_p,
    input [2:0] btn,
    output [3:0] com,
    output [7:0] seg_7);
    
    // wire
    wire btn_mode;
    wire btn_sec;
    wire btn_min;
    wire set_watch;
    wire inc_sec, inc_min; // inc = increment
    wire clk_usec, clk_msec, clk_sec, clk_min;
    wire [3:0] sec1, sec10, min1, min10;
    wire [15:0] value;
    
    // button Mode    
    button_cntr btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_mode));
    button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_sec));
    button_cntr btn2(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_min));
    
    // button control
    T_flip_flop_p t_mode(.clk(clk), .reset_p(reset_p), .t(btn_mode), .q(set_watch));
    assign inc_sec = set_watch ? btn_sec : clk_sec; 
    assign inc_min = set_watch ? btn_min : clk_min;
    
    // Time Setting
    clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_usec));
    clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_usec), .clk_div_1000(clk_msec));
    clock_div_1000 sec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_msec), .clk_div_1000_nedge(clk_sec));
    clock_div_60 min_clk(.clk(clk), .reset_p(reset_p), .clk_source(inc_sec), .clk_div_60_nedge(clk_min));
      
    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));
    
    assign value = {min10, min1, sec10, sec1};
    
    fnd_cntr fnd(.clk(clk), .reset_p(reset_p), .value(value), .com(com), .seg_7(seg_7));
    
endmodule

 

 

코드를 바탕으로 회로도를 그려보면 아래와 같은 회로도를 그릴 수 있다.


동작 영상

 

코드를 실행시키면 다음과 같이 동작되는 것을 확인할 수 있다.

 

 

 

 

 

 

728x90

'Language > Verilog' 카테고리의 다른 글

Vivado : 초음파 센서  (0) 2024.07.25
Vivado : Basys3 Stop watch  (0) 2024.07.18
Vivado : Basys3 7segment  (0) 2024.07.16
Vivado : Edge Detector, Shift Register(SISO, SIPO, PISO, PIPO)  (2) 2024.07.16
Vivado : 동기식 카운터  (0) 2024.07.12