본문 바로가기

RTL Design/Verilog RTL 설계

Verilog RTL 설계(7월 22일 - 1, 4X4 Matrix Keyboard - 1)

1. 4X4 Matrix Keyboard

  • 4X4 Matrix Keyboard는 다음과 같은 회로도를 같는다.
  • Row와 Column간에 회로 연결은 16개의 Switch간에 연결되어 있다.

4X4 Matrix Keyboard

 

 

 

2. 어떻게  버튼이 눌렀는지를 확인하는가?

  • Row 값(R1, R2, R3, R4)들이 High-level인 상태에서 특정 버튼을 누르게 된다면 해당하는 열(C1, C2, C3, C4)의 값이 Low-Level에서 High-Level 로 변한다.
  • ex) R1, R2, R3, R4 = 0001인 상태에서 버튼 0을 누르면 C1, C2, C3, C4 = 0100이 출력된다.
  • ex) R1, R2, R3, R4 = 0100인 상태에서 버튼 6을 누르면 C1, C2, C3, C4 = 0010이 출력된다.

 

 

 

 

3. 만약, C1값이 High-Level이라면 버튼 *, 1, 4, 7 중에서 어떤 버튼이 눌렸는지 어떻게 하는가?

  • 4개의 FND에 서로 다른 숫자들을 연속 출력하는 방식을 생각하면 쉽다.
  • Ring Counter를 통해  ROW 값 (R1, R2, R3, R4)을 0001, 0010, 0100, 1000 형식으로 순차적으로 신호를 보낸다.
  • 열 값인 c1, c2, c3, c4 값이 1000이라면 첫 번째 열(c1)에 위치한 버튼, *, 1, 4, 7 버튼 중 하나를 눌렀음을 알 수 있다.
  • Ring Counter의 현재 행 상태와 열의 값을 조합하여 정확한 버튼을 식별할 수 있다.
  • 이를 예시를 통해 이해하고자 하면 보다 쉽게 이해 할 수 있다.

    - R1가 활성화 된 상태에서 열 값(c1, c2, c3, c4)이 0010이라면 눌러진 버튼은 3번 버튼이다.
    - R2가 활성화 된 상태에서 열 값(c1, c2, c3, c4)이 0100이라면 눌러진 버튼은 5번 버튼이다.
    - R3가 활성화 된 상태에서 열 값(c1, c2, c3, c4)이 0001이라면 눌러진 버튼은 C번 버튼이다.
    - R4가 활성화 된 상태에서 열 값(c1, c2, c3, c4)이 1000이라면 눌러진 버튼은 *번 버튼이다.

  • 이 방식으로 Ring Counter의 행의 상태와 열의 값을 확인하여 정확히 어느 버튼이 눌렸는지 판단할 수 있습니다.

 

 

 

4. 4X4 Matrix Keyboard의 Row 신호 값들을 Ring Counter를 통해 빠르게 신호를 준다면 단시간
    안에
4X4 Matrix Keyboard의 버튼 중에서 어느 버튼이 눌렸는지를 확인 할 수 있다.

 

 

 

5. 4X4 Matrix Keyboard 구현

  • 외부 모듈인 4X4 Matrix Keyboard와 basys3을 연결하여 4X4 Matrix Keyboard 눌러진 버튼 값을 basys3의 FND에 출력되도록 설계 및 구현 해보도록 하겠다.

 

 

5.1. Q) 4X4 Matrix Keyboard의 Row 값을 Ring Counter를 통해 각 행에 순차적으로 신호를 입력해야 하는데,
            얼마의
주기를 갖고, 각 행에 순차적으로 신호를 줘야 하는가?

 

  • 이전 4개의 FND에서 각각의 FND에 서로 다른 숫자들을 출력시키기 위해 FND의 com 값을 1ms을 주기로 Left Shifting하였다.
  • 하지만, 이번 4X4 Matrix Keyboard는 외부 모듈이기 때문에 내부 회로에서 동작하던 FND와 비교하였을 때, PDT가 좀 더 걸릴 수 있다.
  • 이를 고려하여 여유롭게 PDT 이후에 안정된 상태에서 동작시키기 위해 delay time을 1ms이 아닌 10ms를 갖고, 4X4 Matrix Keyboard의 Row 값에 신호를 주도록 하겠다.

 

 

 

5.2. 기본 틀 생성

module Matrix_Keyboard_4X4(
    input clk, reset_p,
    input [3:0] col,
    output [3:0] row,
    output [3:0] key_value,
    output key_valid );
    
    // 10ms를 주기를 갖는 One Cycle Pulse 얻기
    reg[19:0] clk_div;
    always @(posedge clk) clk_div = clk_div + 1;
    
    wire clk_10ms_n, clk_10ms_p;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_div[19]), .n_edge(clk_10ms_n), .p_edge(clk_10ms_p));
endmodule

// Edge Detector
module edge_detector (
    input clk, reset_p,
    input cp,
    output n_edge, p_edge );
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            flip_flop_current <= 0;
            flip_flop_old <= 0;
        end else begin
            flip_flop_current <= cp;
            flip_flop_old <= flip_flop_current;
        end 
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10)? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01)? 1 : 0;
    
endmodule

 

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

Q) 왜 Row 값이 Output이며, Col 값이 Input인가? 분명 위에서는 Row 값에 신호를 준다고 했고, 버튼이 눌린 것을 신호로 받기 때문에 Row는 Input, Col은 Ouput으로 설정해야 하는 거 아닌가?

 

A) 이 부분은 내가 굉장히 어렵게 이해한 부분이다. 이를 Matrix Keyboard 관점에서 이해했기 때문에 그렇다. 우리는 basys3 관점에서 설계하기 때문에 basys3 관점에서 바라봐야 한다. 

현재 basys3와 외부 모듈인 4X4 Matrix Keyboard이 연결된 상태이다. Keyboard 관점에서 Keyboard에 신호를 줘서 버튼 값을 읽을 려고 하는 것은 누구인가? 바로 basys3이다. 따라서 Keyboard의 행에서 신호 값을 입력 받지만, 그 신호는 바로 basys에서 출력되는 신호이다. 

 

즉, 다시 말해 Keyboard 입장에서의 입력은 basys3 입장에서는 출력이며, Keyboard 입장에서 출력은 basys3 입장에서는 입력이다, 

 

이를 쉽게 비유하자면 Switch와 FND이다. Switch로 부터 데이터를 받을 때, Input으로 설정하는가? Output으로 설정하는가? Switch 입장에서 출력 값은 Basys 입장에서는 입력 값으로 작용하여 Input으로 설정한다. FND도 마찬가지이다. 따라서 이를 Basys3 입장에서 바라보고, 설계해야 한다.

 

 

 

 

5.3. Ring Counter를 통해 Row 단자들에 순차적으로 1값을 주자.

// Row 값을 10ms 주기로 Ring Counter이용하여 각 행에 신호를 주자.
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) row = 4'b0001;
        else if(clk_10ms_n && !key_valid) begin
            case(row) 
                4'b0001 : row = 4'b0010;
                4'b0010 : row = 4'b0100;
                4'b0100 : row = 4'b1000;
                4'b1000 : row = 4'b0001;
                default : row = 4'b0001;
            endcase
        end
    end
  • 4번째 행(ROW4 , 0001)을 시작으로 10ms를 주기로 3번째 행, 2번째 행, 1번째 행, 다시 4번째 행 순서로 1값을 준다.
  • 여기서 행에 1값을 준다는 의미는 "해당 값에 버튼 값을 읽는다."는 것을 의미한다.
  • key_valid 값은 현재 버튼이 눌려진 상태를 나타내는 변수이며, key_valid == 1이면 현재 1값을 주고 있는 행에 속해있는 4개의 버튼 중 하나를 눌렸음을 의미하고,  key_valid == 0이면 버튼이 눌러지지 않은 상태를 의미한다.
  • 만약 key_valid 값이 1이라면 현재 버튼이 눌러진 상태이기 때문에 이를 기다릴 필요가 있기 때문에 key_valid == 0 인 동시에 10ms가 지나면 다음 행으로 넘어가 1값을 주게 된다.


    ★ ★ ★ ★ ★
  • Q) if문내에 있는 key_valid는 어떤 역활을 하며, 왜 해당 조건문에 추가하였는가?
    A)
    크게 다음과 같은 2가지 이유 때문이다.
    -
    버튼을 누르고 있는 동안에 Row를 Shifting하는 것을 막기 위해
    -
    만약 key_valid 변수를 통해 버튼이 누르고 있는 동안 Row가 Shifting하는 것을 막지 않는다면, 한 싸이클을
      돌아 다시 해당 열로 들어온다면 "다시 새로운 버튼 입력이 들어왔구나." 라고 생각하여, 다시 key_value
      변수에 버튼의 키 값을 다시 대입하게 된다.
      따라서 한 번의 버튼의 입력으로 key_value 변수에 여러 번의 값이 대입되는 현상을 막기 위해서 key_valid
      값을 통해 제어 하게 된다.

 

 

5.4. 각 행의 버튼 값을 읽자. (아직 완벽한 구현을 하지 않고, 큰 틀을 세우자.)

// 각 행에 신호를 주었을 때, 해당 행에 속해있는 버튼 값을 읽자.
// 버튼 값은 key_value이다.
// 버튼이 눌렸는지 여부를 알려주는 변수는 key_valid이다.
always @(posedge clk or posedge reset_p) begin
    if(reset_p) begin
        key_value = 4'b0000;
        key_valid = 0;
    end else if(clk_10ms_p) begin
        if(col) begin
           key_valid = 1;
        end else key_valid = 0;
    end     
 end
  • 각각의 변수들은 다음과 같은 의미를 갖는다.
    - key_value는 4X4 Matrix Keyboard에서 눌려진 입력값을 의미한다.
    - key_valid는 버튼이 눌려졌는지 여부를 나타내는 변수이다.
    - key_valid = 1 : 버튼이 눌려졌다. , 
  • 4X4 matrix keyboard의 각 행들에 10ms를 주기로 1값을 주기 때문에 col 값에 대해서 10ms를 주기로 입력값을 확인한다.
  • 만약, 1값이 들어가고 있는 행에 있는 4개의 버튼 중 단 하나의 버튼이라도 눌리게 된다면 col 값은 0000이 아닌 값을 갖게된다. 즉, 버튼이 하나라도 눌리면 col값은 0보다 큰 값을 갖는다.
  • 따라서 col 값이 0이 아니라는 뜻은 "현재 1값이 들어가고 있는 행에 있는 버튼이 눌려졌다."라는 것을 의미하기 때문에 key_valid = 1로 만들어주는 동시에 어떤 버튼이 눌렸는지를 확인할 필요가 있다.
  • 만약 col 값이 0이라면 1값이 들어가고 있는 행에 있는 버튼이 눌러지지 않았음을 의미하기 때문에 key_valid = 0이며, 10ms 동안 기다린 후, 다음 행을 확인하게 된다.


    ★ ★ ★ ★ ★
  • Q) 왜 Row값은 10ms 주기를 갖고 Shifting할 때, clk_10ms_n 에서 Shifting하지만, 키 값을 Row, Col 값을
        통해 찾은 후, key_value에 대입하는 과정은 clk_10ms_p에서 일어나는가?
    A)
    만약, 같은 edge에서 동작했다면 Row 값은 바로 변화하지 않고, PDT를 갖고, 변화하기 때문에 Row 값이
         안정화 되는 시간이 필요하지만, 갖은 edge에서 동작하기 때문에 key_value 값은  변화 된 Row 값을 통해
         키 값을 찾는 것이 아닌 이전 Row 값을 통해 키 값을 찾게 된다.
         따라서 변화된 Row값이 안정화를 되고, 변화된 Row값과 Col 값을 기준으로 키 값을 읽기 위해서는 5ms
         시간을 확보함으로서 확실한 Row값을 통해 확실한 Key_value 값을 읽을 수 있는 것이다.

 

 

 

5.5. 행(row)과 열(col) 신호 값에 따른 버튼 입력값 설정

// Read Key Value
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            key_value = 0;
            key_valid = 0;
        end 
        else if(clk_10ms_pedge) begin
            if(col) begin
                key_valid = 1;
                case({row, col})
                       8'b0001_0001 : key_value = 4'h0;   
                       8'b0001_0010 : key_value = 4'h1;
                       8'b0001_0100 : key_value = 4'h2;
                       8'b0001_1000 : key_value = 4'h3;
                        
                       8'b0010_0001 : key_value = 4'h4;
                       8'b0010_0010 : key_value = 4'h5;
                       8'b0010_0100 : key_value = 4'h6;
                       8'b0010_1000 : key_value = 4'h7;
                        
                       8'b0100_0001 : key_value = 4'h8;
                       8'b0100_0010 : key_value = 4'h9;
                       8'b0100_0100 : key_value = 4'ha;
                       8'b0100_1000 : key_value = 4'hb;
                        
                       8'b1000_0001 : key_value = 4'hc;
                       8'b1000_0010 : key_value = 4'hd;
                       8'b1000_0100 : key_value = 4'he;
                       8'b1000_1000 : key_value = 4'hf;
                endcase
            end
            else key_valid = 0;
        end
    end
  • reset_p에 의해 key_value와 key_valid 값을 0으로 초기화 한다.
  • 10ms 주기로 ring counter를 통해 각 행에 1 값을 입력하기 때문에 열 값(col)이 0이 아닌 1이라면 해당 행에 속해 있는 버튼들 중 하나의 버튼이 눌러졌음을 의미하기 때문에 key_valid 값을 1로 설정하고, 어떤 버튼이 눌러졌는지는 case문과 row값과 col값을 통해 얻어 key_value변수에 저장한다.
  • 만약 열 값(col)이 0이라는 것은 해당 행에 속해 있는 버튼은 눌러지지 않았음을 의미하기 때문에 key_valid 변수에 0을 대입한 후, 다음 행에 1값을 들어가기를 기다린다.

 

 

 

 

5.6. 구현 영상

 

 

 

 

5.7. 전체 소스 코드

module keyboard_test_top (
    input clk, reset_p,
    input [3:0] col,
    output [3:0] row,
    output [3:0] com,
    output [7:0] seg_7);
    
    wire key_valid;
    wire [3:0] key_value;
    Matrix_KeyBoard_4x4 keypad(.clk(clk), .reset_p(reset_p), .row(row), .col(col),  .key_value(key_value), .key_valid(key_valid)); 
    
    fnd_cntr fnd(.clk(clk), .reset_p(reset_p), .hex_value({12'b0, key_value}), .com(com), .seg_7(seg_7)); 
endmodule    


module Matrix_KeyBoard_4x4(
    input clk, reset_p,
    input [3:0] col,
    output reg [3:0] row,
    output reg [3:0] key_value,
    output reg key_valid );
    
    //  10ms 주기를 갖는 One Cycle Pulse 얻기
    wire clk_10ms_nedge, clk_10ms_pedge;
    reg[19:0] count;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) count = 0;
        else count = count + 1;
    end
    
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(count[19]), .n_edge(clk_10ms_nedge),  .p_edge(clk_10ms_pedge));
    
    // Ring Counter
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) row = 4'b0001;
        else if(clk_10ms_pedge && !key_valid) begin
            if(row == 4'b1000) row = 4'b0001;
            else row = {row[2:0], 1'b0};
        end
    end
    
    // Read Key Value
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            key_value = 0;
            key_valid = 0;
        end 
        else if(clk_10ms_pedge) begin
            if(col) begin
                key_valid = 1;
                case({row, col})
                       8'b0001_0001 : key_value = 4'h0;   
                       8'b0001_0010 : key_value = 4'h1;
                       8'b0001_0100 : key_value = 4'h2;
                       8'b0001_1000 : key_value = 4'h3;
                        
                       8'b0010_0001 : key_value = 4'h4;
                       8'b0010_0010 : key_value = 4'h5;
                       8'b0010_0100 : key_value = 4'h6;
                       8'b0010_1000 : key_value = 4'h7;
                        
                       8'b0100_0001 : key_value = 4'h8;
                       8'b0100_0010 : key_value = 4'h9;
                       8'b0100_0100 : key_value = 4'ha;
                       8'b0100_1000 : key_value = 4'hb;
                        
                       8'b1000_0001 : key_value = 4'hc;
                       8'b1000_0010 : key_value = 4'hd;
                       8'b1000_0100 : key_value = 4'he;
                       8'b1000_1000 : key_value = 4'hf;
                endcase
            end
            else key_valid = 0;
        end
    end
    
endmodule

// Control FND
module fnd_cntr (
    input clk, reset_p,
    input [15:0] hex_value,
    output reg [3:0] com,
    output [7:0] seg_7 );
    
    wire clk_1ms;
    clk_div_nbit clk_div(.clk(clk), .reset_p(reset_p), .clk_source(clk), .prescale(100000), .clk_div_n_nedge(clk_1ms));
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p)  com = 4'b1110;
        else if(clk_1ms)  begin
            if(com == 4'b0111) com = 4'b1110;
            else com = {com[2:0], 1'b1};
        end 
    end
    
    reg [3:0] value;
    always @(com) begin
        case (com) 
            4'b1110 : value = hex_value[3:0];
            4'b1101 : value = hex_value[7:4];
            4'b1011 : value = hex_value[11:8];
            4'b0111 : value = hex_value[15:12];
            default : value = value;
        endcase
    end
    
    decoder_7seg decoder_seg (.hex_value(value), .seg_7(seg_7));
endmodule

// clk_div_n
module clk_div_nbit (
    input clk, reset_p,
    input clk_source,
    input [31:0] prescale,
    output clk_div_n,
    output clk_div_n_nedge );
    
    wire clk_source_nedge;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_source), .n_edge(clk_source_nedge));
    
    integer count;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) count = 0;
        else if(clk_source_nedge) begin
            if(count >= prescale - 1) count = 0;
            else count  = count + 1;
        end
    end
    
    assign clk_div_n = (count < prescale/2) ? 0 : 1;
    edge_detector edge_detector_1 (.clk(clk), .reset_p(reset_p), .cp(clk_div_n), .n_edge(clk_div_n_nedge));
    
endmodule

// Decoder of 7-segment.
module decoder_7seg (
    input [3:0]  hex_value,
    output reg [7:0] seg_7);
    
    always @(hex_value) begin
        case(hex_value)
            0  : seg_7 = 8'b0000_0011; //common anode, 0이 켜지는거임 
            1  : seg_7 = 8'b1001_1111;  // 1
            2  : seg_7 = 8'b0010_0101;  // 2
            3  : seg_7 = 8'b0000_1101;  // 3
            4  : seg_7 = 8'b1001_1001;  // 4
            5  : seg_7 = 8'b0100_1001;  // 5
            6  : seg_7 = 8'b0100_0001;  // 6
            7  : seg_7 = 8'b0001_1111;  // 7
            8  : seg_7 = 8'b0000_0001;  // 8
            9  : seg_7 = 8'b0000_1001;  // 9
            10 : seg_7 = 8'b0001_0001;  // A
            11 : seg_7 = 8'b1100_0001;  // b
            12 : seg_7 = 8'b0110_0011;  // C
            13 : seg_7 = 8'b1000_0101;  // d
            14 : seg_7 = 8'b0110_0001;  // E
            15 : seg_7 = 8'b0111_0001;  // F
        endcase
    end
    
endmodule

// Edge detector.
module edge_detector (
    input clk, reset_p,
    input cp,
    output n_edge, p_edge );
    
    reg flip_flop_current, flip_flop_old;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            flip_flop_current <= 0;
            flip_flop_old <= 0;
        end else begin
            flip_flop_current <= cp;
            flip_flop_old <= flip_flop_current;
        end       
    end
    
    assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10) ? 1 : 0;
    assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01) ? 1 : 0;
    
 endmodule