Notice
Recent Posts
Recent Comments
Link
관리 메뉴

거북이처럼 천천히

Verilog RTL 설계(7월 22일 - 2, FSM 형식으로 Keypad 재구현) 본문

RTL Design/Verilog RTL 설계

Verilog RTL 설계(7월 22일 - 2, FSM 형식으로 Keypad 재구현)

유로 청년 2024. 7. 23. 21:37

1. FSM 기법이란?

  • Finite State Machine
  • 시스템의 동작을 상태, 이벤트, 전이로 모델링하는 수학적 모델을 의미.
  • 시스템은 한 번에 단 하나의 상태만 가질 수 있다.

 

 

1.1. FSM의 장점

  • 복잡한 시스템 동작에 대해서 유한 상태로 나눈 뒤, 각각의 상태에 대한 동작과 다른 상태로 전이를 위한 조건을 정의함으로서 복잡한 시스템을 단순화할 수 있다.
  • 시스템의 동작을 여러 상태로 나누었기 때문에 디버깅 과정에서 문제가 발생했을 때, 어느 부분에서 문제가 발생했는지를 쉽게 파악하여 빠르게 고칠 수 있다.

 

 

1.2. FSM와 상태도(State diagram)과의 관계

  • FSM은 시스템의 동작을 여러 상태로 나누어 현재 상태의 작업을 수행한 뒤, 발생한 이벤트에 따라 다른 이벤트로 전이된다.
  • FSM의 시스템의 여러 상태를 쉽게 파악하기 위해서 시각적인 자료가 필요하며, 이러한 시각적인 자료의 역활을 수행 할 수 있는 것이 State diagram이다.
  • State diagram을 통해 각각의 상태에서의 동작과 다른 상태로 전이되기 위한 조건을 한 눈에 확인할 수 있어. FSM으로 설계할 때, 밀접하게 사용된다.

 

 

 

 

 

2. 4X4 Matrix KeyPad을 FSM (Finite State Machine)으로 재구현 해보자.

  • FSM (Finite State Machine)을 공부하기 위해 4X4 Matrix KeyPad를 다시 구현해보도록 하겠다.

 

 

2.1. FSM 설계 전, 상태도(State diagram)에 대해서 공부해보자.

  • 이전에 설계했던 4X4 Matrix keypad에 대한 상태도를 그리면 다음과 같다.

State diagram of 4X4 Matrix Keypad

 

 

4X4 Matrix Keypad에 대한 설명을 다시하자면 다음과 같다.

  • Matrix Keypad의 Row 단자에 Ring Counter를 이용하여 순차적으로 1 값의 신호를 준다.
    (단, 여러개의 Row 단자 중 단 하나의 Row 단자에만 1값을 주고, 나머지 Row 단자에는 0 값을 준다.)
  • Ring Counter는 10ms Positive edge마다 순차적으로 각각의 Row단자에 1 값을 준다.
  • Row 단자의 입력된 값이 1인 행에 속해 있는 단자들 중에서 현재 눌려진 버튼이 있는지 여부를 확인한다.
  • 만약, 현재 눌러진 버튼이 있다면 Row 값(R1, R2, R3, R4)과 Col 값(C1, C2, C3, C4)을 교차하여 어떤 버튼이 눌렸는지를 확인하게 된다.

 

 

위 동작 과정을 기반으로 State diagram을 그리면 위와 같이 그릴 수 있으며, State diagram에 대해서 설명하자면 다음과 같다.

  • 현재 Row 단자는 총 4줄이기 때문에 각각의 Row 단자에 1이 들어갔을 경우는 총 4가지로 나타나며, 이를 경우의 수로 구분하자면 총 4가지 케이스가 나올 수 있어 이를 scan1, scan2, scan3, scan4로 명명하였다.
  • Ring Counter는 10ms Positive edge 을 주기로 순차적으로 Row 단자에 1값을 주기 때문에 상태의 변화 조건으로 10ms Positive edge라 작성하였다.
  • 만약, 현재 1값이 들어가고 있는 Row에 속해있는 버튼이 눌려졌다면 이를 알려주기 위해 Key_valid 변수 값을 1로 설정하며, 이를 처리하기 위해 key_process 상태로 전이된다.
    (즉, 다시 말해 scan1 ~ scan4 상태에서 속해 있는 버튼 중 버튼 눌림이 감지 된다면 key_valid 변수에 1값을 넣고, 이를 처리하기 위해 key_process 상태로 전이 된다.)
  • key_process 상태에 입력값을 출력한 뒤, 더 이상의 버튼 입력이 사라지면 다시 scan0로 이동하여 다시 처음부터 순차적으로 각 행에 1값을 주게 된다.

 

 

 

 

2.2. State diagram을 기반으로 코드를 작성하면 다음과 같이 작성할 수 있다.

 

< 시스템의 동작의 단계를 나누어 상태로 표현 >

// 상태 상수 정의
parameter SCAN0 = 5'b00001;
parameter SCAN1 = 5'b00010;
parameter SCAN2 = 5'b00100;
parameter SCAN3 = 5'b01000;
parameter KEY_PROCESS = 5'b10000;
  • 상태도를 보면 알 수 있듯이 첫 번쩨 행부터 네 번째 행까지 1값의 신호를 넣는 경우와 1값이 들어온 행에 속해 있는 버튼이 눌려 졌을 때, 이에 대한 처리를 하는 경우로, 총 5단계로 나누어 상태로 표현했다.

 

 

< 현재 상태와 다음 상태의 변수 선언 >

// define State, next_State Value 
reg [4:0] state, next_state;

 

  • state는 "현재 있는 상태"를 의미하며, next_state는 "다음 전이될 상태"를 의미한다.

 

 

 

 

< 언제 다음 상태로 전이되는가? >

// state machine start.
// 어떤 경우에 상태가 변화하는가?
// clk_10ms_pedge가 활성화 될 때마다 전이 된다.
always @(posedge clk or posedge reset_p) begin
    if(reset_p) state = SCAN0;
    else if(clk_10ms_pedge) state = next_state;
end

 

  • 시스템 시작과 함께 시행되는 초기화 작업에서 현재 상태를 SCAN0으로 초기화한다.
    즉, 첫 번째 행부터 1 값의 신호를 주게 된다.
  • 주기가 10ms인 Pulse에서 Positive edge일 때, 현재 상태 변수인 state에 다음 상태 변수인 next_state를 대입함으로서 다음 상태로 전이되게 된다.

 

 

 

 

< 다음 상태로 전이되기 위한 조건은 무엇인가? >

// next_state를 정하자.
    // row 값이 0이 아니면 현재 행에 속해있는 4개의 버튼 중 입력이 들어왔음을 의미
    // 따라서 입력된 버튼의 값을 읽기 위해서 KEY_PROCESS 상태로 전이
    // row 값이 0이면 눌러진 버튼이 없기 때문에 다음 행으로 이동.
    always @(*) begin
        case(state)
            SCAN0 : begin
                if(col == 0) next_state = SCAN1;
                else next_state = KEY_PROCESS;
            end
            
            SCAN1 : begin
                if(col == 0) next_state = SCAN2;
                else next_state = KEY_PROCESS;
            end
            
            SCAN2 : begin
                if(col == 0) next_state = SCAN3;
                else next_state = KEY_PROCESS;
            end
            
            SCAN3 : begin
                if(col == 0) next_state = SCAN0;
                else next_state = KEY_PROCESS;
            end
            
            // KEY_PROCESS 상태에서 계속 버튼이 눌러진 상태라면
            // 버튼을 뗄때까지 계속 처리하기 위해 KEY_PROCESS 상태를 유지
            KEY_PROCESS : begin
                if(col == 0) next_state = SCAN0;
                else next_state = KEY_PROCESS;
            end
            
            default : next_state = SCAN0;
        endcase
    end
  • 이전 블록에서 언제 다음 상태로 전이되는가에 대해서 정의를 했다면, 현재 블록에서는 어떤 이벤트가 발생했을 때, 어느 상태로 넘어갈지를 정해주게 된다. 즉, 다음 상태를 발생한 이벤트에 따라 선택하게 된다.
  • "col == 0" 이라는 의미는 "현재 신호 값을 1 주고 있는 행에 속해 있는 버튼이 눌러지지 않았음"을 의미한다.
  • 따라서, 눌러진 버튼이 없기 때문에 "다음 행에 1값을 주는 경우 및 상태"를 next_state로 설정하고, 다음 10ms positive edge를 기다린다.
  • 반면에 "col == 1" 이라는 의미는 "현재 신호 값을 1 주고 있는 행에 속해 있는 버튼 중 하나의 버튼이 눌렀음"을 의미한다. 
  • 따라서, 어떤 버튼이 눌렀으며, 눌러진 버튼을 출력으로 내보내기 위한 작업을 수행하기 위해 KEY_PROCESS 으로 next_state를 설정하고, 다음 10ms positive edge를 기다린다.
  • 만약, 현재 KEY_PROCESS이라면 com == 0일 경우에 다시 첫 번째 행(SCAN0)으로 이동해 확인하게 되며, com == 1일 경우 눌러진 버튼이 끝날 때까지 계속해서 값을 읽게 된다.

 

 

 

< 각 상태에서의 수행되는 작업들을 정의 >

// 각 상태에 따른 처리 과정
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            key_value = 0;
            key_valid = 0;
            row = 0;
        end 
        else begin
            case (state) 
                SCAN0 : begin row = 4'b0001; key_valid = 0; end
                SCAN1 : begin row = 4'b0010; key_valid = 0; end
                SCAN2 : begin row = 4'b0100; key_valid = 0; end
                SCAN3 : begin row = 4'b1000; key_valid = 0; end
                KEY_PROCESS : 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;
                        
                        default : key_value = key_value;
                    endcase 
                end 
            endcase
        end
    end
  • 이전 단계에서 "언제 다음 상태로 전이되는가?"와 "어떤 이벤트가 발생했을 때, 어느 상태로 전이 될건가?"에 대해서 정의하였다.
  • 이번 블록에서는 "각 상태에서 수행해야 할 작업들" 에 대해서 정의한다.
  • 현재 상태가 SCAN0 ~ SCAN3 일 경우, 아직 버튼이 눌러지지 않은  상태이기 때문에 key_valid = 0로 정의하게 된다.
  • 반면에 현재 상태가 KEY_PROCESS 이면 버튼이 눌러졌기 때문에 key_valid = 1로 정의하고, case문와 row 값과 col 값을 대조하여 현재 어떤 버튼이 눌러졌음을 확인하고, 눌러진 값을 key_value에 저장하고, 출력으로 내보내게 된다.

 

 

 

 

3. 구현 영상

 

 

 

 

 

4. 전체적인 소스 코드

module keypad_test_top (
    input clk, reset_p,
    input [3:0] col,
    output [3:0] row,
    output [3:0] com,
    output [7:0] seg_7 );
    
    wire [3:0] key_value;
    wire key_valid;
    Matrix_KeyPad_4X4_FSM keypad_fsm(.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), .value({12'b0, key_value}), .com(com), .seg_7(seg_7)); 
endmodule   

module Matrix_KeyPad_4X4_FSM(
    input clk, reset_p,
    input [3:0] col,
    output reg [3:0] row,
    output reg [3:0] key_value,
    output reg key_valid);
    
    // 상태 상수 정의
    parameter SCAN0 = 5'b00001;
    parameter SCAN1 = 5'b00010;
    parameter SCAN2 = 5'b00100;
    parameter SCAN3 = 5'b01000;
    parameter KEY_PROCESS = 5'b10000;
    
    // Get 10ms One Cycle Pulse for ring Counter.
    reg [19:0] count;
    always @(posedge clk) count = count + 1;
    
    wire clk_10ms_pedge;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(count[19]), .p_edge(clk_10ms_pedge));
    
    // define State, next_State Value 
    reg [4:0] state, next_state;
    
    // state machine start.
    // 어떤 경우에 상태가 변화하는가?
    // clk_10ms_pedge가 활성화 될 때마다 전이 된다.
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) state = SCAN0;
        else if(clk_10ms_pedge) state = next_state;
    end
    
    
    // next_state를 정하자.
    // row 값이 0이 아니면 현재 행에 속해있는 4개의 버튼 중 입력이 들어왔음을 의미
    // 따라서 입력된 버튼의 값을 읽기 위해서 KEY_PROCESS 상태로 전이
    // row 값이 0이면 눌러진 버튼이 없기 때문에 다음 행으로 이동.
    always @(*) begin
        case(state)
            SCAN0 : begin
                if(col == 0) next_state = SCAN1;
                else next_state = KEY_PROCESS;
            end
            
            SCAN1 : begin
                if(col == 0) next_state = SCAN2;
                else next_state = KEY_PROCESS;
            end
            
            SCAN2 : begin
                if(col == 0) next_state = SCAN3;
                else next_state = KEY_PROCESS;
            end
            
            SCAN3 : begin
                if(col == 0) next_state = SCAN0;
                else next_state = KEY_PROCESS;
            end
            
            // KEY_PROCESS 상태에서 계속 버튼이 눌러진 상태라면
            // 버튼을 뗄때까지 계속 처리하기 위해 KEY_PROCESS 상태를 유지
            KEY_PROCESS : begin
                if(col == 0) next_state = SCAN0;
                else next_state = KEY_PROCESS;
            end
            
            default : next_state = SCAN0;
        endcase
    end
    
    // 각 상태에 따른 처리 과정
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin
            key_value = 0;
            key_valid = 0;
            row = 0;
        end 
        else begin
            case (state) 
                SCAN0 : begin row = 4'b0001; key_valid = 0; end
                SCAN1 : begin row = 4'b0010; key_valid = 0; end
                SCAN2 : begin row = 4'b0100; key_valid = 0; end
                SCAN3 : begin row = 4'b1000; key_valid = 0; end
                KEY_PROCESS : 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;
                        
                        default : key_value = key_value;
                    endcase 
                end 
            endcase
        end
    end
    
endmodule

// Control FND.
module fnd_cntr (
    input clk, reset_p,
    input[15:0] value, 
    output reg [3:0] com,
    output [7:0] seg_7);
 
    wire clk_1ms, clk_1ms_nedge;
    reg [17:0] count;
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) count = 0;
        else begin
            if(count >= 99999) count = 0;
            else count = count + 1;
        end
    end
    
    assign clk_1ms = (count < 50000) ? 0 : 1;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_1ms), .n_edge(clk_1ms_nedge));
    
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) com = 4'b1110;
        else if(clk_1ms_nedge) begin
            if(com == 4'b0111) com = 4'b1110;
            else com = {com[2:0], 1'b1};
        end
    end
    
    reg [3:0] hex_value;
    
    always @(com) begin
        case(com)
            4'b1110 : hex_value = value[3:0];
            4'b1101 : hex_value = value[7:4];
            4'b1011 : hex_value = value[11:8];
            4'b0111 : hex_value = value[15:12];
            default : hex_value = hex_value;
        endcase 
    end
    
    decoder_seg7 decoder_seg (.hex_value(hex_value), .seg_7(seg_7));
endmodule

// clk_div_n
module clk_div_n (
    input clk, reset_p,
    input clk_source,
    input [31:0] prescale,
    output clk_div_n,
    output clk_div_nedge);
    
    wire clk_source_nedge;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_sourcen), .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_nedge));
endmodule

// Decoder of 7-Segment.
module decoder_seg7 (
     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