View

300x250

이 글은 insight 출판사의 [밑바닥부터 만드는 컴퓨팅 시스템 / The Elements of Computing System]이라는 책에 있는 프로젝트(과제) 를 수행하는 글입니다.
과제 수행을 위한 언어로 C#을 선택했지만, Java와 거의 유사하여 Java를 알면 쉽게 이해할만한 코드들이예요.

 


Chapter 3. Sequential Logic - Project

Ch.1, 2에서는 입력 값에 따라 출력값이 변하는 Combination Chip(이하 조합 칩)을 다루었다. 그러나 이번 Chapter에서는 시간의 요소를 추가로 넣은 Sequential Chip(이하 순차 칩)을 다룰 예정이다. 조합 칩과 다르게, 순차 칩은 상태를 유지하는 기능을 갖고있다. 따라서 값을 저장하고 불러오는 기능을 순차 칩을 이용해 메모리 소자를 만들어 사용하게 된다. 

 

메모리 소자의 구현은 동기화, 클로킹, 피드백 루프 같은 복잡한 개념이 들어가는 기술이지만, Flip-Flop이라는 저수준의 순차게이트가 이 개념을 모두 수행한다. 플립플롭을 이용하면 현대에서 사용되는 메모리장치를 만들어볼 수 있다. 메모리 장치를 만들면, 컴퓨터를 구성하는데 필요한 모든 칩을 만들 수 있다.

 

단순히 입력값에 대한 결과를 내놓았던 조합 칩과는 다르게, 정보를 유지해야하는 순차 칩을 구현하기 위해 순차 칩들은 모두 클래스로 구현해주었다. 사용할 때는 instance를 만들어서 칩 처럼 사용해주면 된다.

 

클록 CLOCK

컴퓨터에서 연속적으로 0-1의 신호를 발생시키는 역할을 하는 클록은 시간 진행을 표현한다. 이 신호는 HW를 통해 모든 순차 칩에 (최대한) 동시에 전달된다. VHDL과 같은 설계용 프로그램, 언어을 활용하면 쉽게 구현이 가능했겠지만, C#을 이용해 만들려고 시도하다보니 어떻게 클록 신호를 순차칩에 전달할지를 꽤 고민을 했다. 그리고 나는 C#의 EventHandler, Delegate를 활용하기로 했다. 

 

이벤트 핸들러는 C# 공부를 할 때 잠깐 봤던 내용이라 잘 기억이 나지 않아서, 아래 블로그의 글을 참고하여 만들었다.

http://blog.xenotech.net/1019

public static class CLK {
    public delegate void CLKEventHandler();
    public static event CLKEventHandler CLKEvent;

    public static void CLKChange() {
        CLKValue = !(CLKValue);
        if(CLKEvent != null)
        {
            // Console.WriteLine("Event 발생");
            CLKEvent();
        }
    }  

    static bool CLKValue = false;
}

CLK value를 만들어주고, CLKEventHandler로 CLKEvent를 만들어주었다. 뒤의 모든 순차칩에서 CLKEvent에 구독을 해놓으면, CLKChange( )가 실행될 때 (Clock의 값이 바뀔 때) CLKEvent를 실행하여 모든 순차칩에게 CLK 값이 바뀌었음을 알려줄 수 있다. 이후, CLK이 바뀌면 할 Action을 각 순차칩에서 정해두면 CLK에 따른 다양한 순차칩의 액션을 취해줄 수 있다.

 

데이터 플립플롭 Data Flip-Flop

입력 : in
출력 : out
기능 : out(t) = in(t-1)

사실상 순차 칩의 가장 핵심이다. DFF는 1Bit을 입력받고, 1Bit을 출력하는 단순한 게이트이자, 모든 메모리 소자의 기본 부품이다. HW적으로는 모든 DFF가 마스터클록에 연결되어 동시에 상태변화를 하며 시간의존성(동기화)를 구현한다. 책에서는 DFF를 기본 소자로 보고 구현을 하지는 않지만(DFF 역시 NAND 게이트만으로 구현이 가능하다), 코드로 구현하였다.

public class DFF {
    private bool state;
    private bool inputState;

    public bool value {
        get {
            return state;
        }
        set {
            inputState = value;
        }
    }

    public DFF() {
        state = false;
        CLK.CLKEvent += new CLK.CLKEventHandler(StateChange);
    }

    private void StateChange() {
        state = inputState;
    }
}

 

state는 현재 DFF가 담고있는 value, inputState는 입력으로 들어와 다음 CLK까지 대기중인 value이다. 이 값들은 private으로 설정해두고, 외부에서는 value 라는 변수에서 값을 입력하고, 가져가도록 get-set을 구현해두었다. 

생성자(constructor)에서는 CLKEvent에 이 클래스가 갖고있는 메서드 StateChange( )를 추가한다. 이는 Clock과 DFF 칩의 연결을 구현하기 위해 이렇게 만들어주었다. 이를 통해 CLK 값이 바뀌면 특별한 과정 없이 바로 StateChange( ) 메서드가 호출된다.

StateChange( ) 메서드는 inputState (대기중인 값)이 state로 들어가도록 한다.

 

Bit - 1bit 레지스터 

입력 : in, load
출력 : out
기능 : load가 1이라면 out(t)=in(t-1), 아니면 out(t) = out(t-1)

DFF는 단순히 입력값이 CLK에 따라 출력으로 넘어가는 칩이였다면, Bit은 원래 값을 유지/새 값을 저장을 선택할 수 있다.

 

원래대로라면 load bit을 Mux에 연결하여 내부 값으로 in을 넣을지, 아니면 원래 있던 값을 넣을지를 결정하는 selector로 써야 하지만, in에 값을 넣는 것 자체가 대입을 위한 것이라고 보고 load bit은 따로 구현해주지는 않았다. (추후에 load bit이 필요하면 넣어줘야겠지)

public class Bit {
    private DFF dff;

    public bool value {
        get {
            return dff.value;
        }
        set {
            // 새로운 값이 들어오면 load bit이 1이라고 가정하고 값을 바꾸어준다. 
            // 그게 아니라면 값을 유지.
            dff.value = value;
        }
    }

    // Constructor
    public Bit() {
        dff = new DFF();
    }
}

생성자에서 DFF를 생성하여 Bit가 갖고있는다. DFF를 만들면서 자동으로 CLK에 연결되기 때문에, 상위 클래스인 Bit에서는 CLK과 연결할 필요가 없다. 

 

이렇게 만든 Bit을 여러 개 연결하면 w bit register가 된다.

 

레지스터 Register

입력 : in[16], load
출력 : out[16]
기능 : load가 1이라면 out(t)=in(t-1), 아니면 out(t) = out(t-1)

단순히 Bit에서 입/출력 수가 많아진 버전이다. Bit을 여러개 묶고, 기본 데이터를 배열로 사용하면서 간단하게 구현할 수 있다. 

public class Register {
    private Bit[] state;
    private bool[] inputState;

    public Register() {
        state = new Bit[16];
        for(int i = 0; i < 16; i++) {
            state[i] = new Bit();
        }
    }

    public bool[] value {
        get {
            bool[] result = new bool[16];

            for(int i = 0; i < 16; i++) {
                result[i] = state[i].value;
            }
            return result;
        }
        set {
            for(int i = 0; i < 16; i++) {
                state[i].value = value[i];
            }
        }
    }
}

 

메모리 RAM

입력 : in[16], address[k], load
출력 : out[16]
기능 : out(t)=RAM[address(t)](t), 만약 load가 1이라면 RAM[address(t-1)](t) = in(t-1)

public class RAM8 {
    private Register[] data;
    private bool[] inputData;

    public RAM8() {
        // 더 큰 RAM을 만들때는 아래 ARRAY만 바꾸어주면 된다.
        data = new Register[8];
        for(int i = 0; i < data.Length; i++) {
            data[i] = new Register();
        }
    }

    public void SetData(bool[] input, bool[] address) {
        data[BoolToInt(address)].value = input;
    }

    public bool[] GetData(bool[] address) {
        return (bool[])data[BoolToInt(address)].value.Clone();
    }

    private int BoolToInt(bool[] input) {
        int result = 0;
        for(int i = input.Length - 1; i >= 0; i--) {
            result = result << 1;
            if(input[i]) result++;
        }
        return result;
    }
}

w-bit 레지스터를 여러개 연결하여 만든 저장 장치이다. address 값에 해당하는 주소를 갖는 레지스터에 접근하여 값을 받아오거나, load 값이 true 일 때 address 값에 해당하는 주소의 레지스터에 in 값을 저장하는 역할을 한다. 

 

위 코드에서 더 큰 RAM을 만들려면 Constructor 첫 줄의 Array 크기를 바꾸거나, register의 폭 자체를 넓게 바꾸어주면 된다.

 

계수기 Counter

입력 : in[16], inc, load, reset
출력 : out[16]
기능 : reset이 1이면 out(t) = 0,
         reset이 1이 아닐 때 load가 1이면 out(t) = in(t-1),
         load가 1이 아닐 때 inc가 1이면 out(t) = out(t-1) + 1,
         inc가 1이 아니면 out(t) = out(t-1)

계수기는 단순히 CLK마다 원래 값에서 1씩 더한 값을 출력하는 장치이다. reset은 계수기의 값을 0으로 초기화하고, load는 계수기에 in 값을 넣어주고, inc는 계수기가 1씩 더하는 작업을 수행할지 말지 정해준다. 계수기는 컴퓨터가 프로그램의 몇 번째 명령어를 읽고 있는지 주소를 기록하는데에서 유용하게 사용된다. 

public class Counter16Bit {
    private bool reset;
    private bool load;
    private bool inc;

    private Register state;
    private bool[] inputState;
    
    private bool[] zero = new bool[16] {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
    private bool[] one = new bool[16] {true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};

    public bool[] value {
        get {
            return state.value;
            }
        set {
            inputState = (bool[]) value.Clone();
        }
    }

    // Constructor
    public Counter16Bit() {
        CLK.CLKEvent += new CLK.CLKEventHandler(CLKAction);
        state = new Register();
    }

    // CLK 변화 Event에 반응
    private void CLKAction() {
        if(reset) {
            state.value = zero;
            reset = false;
        }
        else if (load) {
            state.value = inputState;
            load = false;
        } 
        else if (inc) {
            state.value = CombinationalChips.BoolOperation.Add16(state.value, one);
        }
    }

    public void ResetTrue() {
        reset = true;
    }

    public void LoadTrue() {
        load = true;
    }

    public void IncTrue() {
        inc = true;
    }

    public void IncFalse() {
        inc = false;
    }
}

계수기의 inc, load, reset bit에 따른 작업을 CLK에 따라 수행해주기 위해서, 계수기에 따로 CLK을 연결하는 이벤트 핸들러를 만들어주었다. Constructor에서 CLK에 대해 option bit에 대한 이벤트 Action을 먼저 추가해준 뒤에 Register를 생성해주었기 때문에 (Register를 만들면 자동으로 DFF들이 CLK과 연결된다) option bit이 적용된 후 DFF 값 변화를 일으켜 정상적으로 1 CLK 만에 값이 바뀐다. 이 순서를 반대로 하면 정상적으로 작동하지 않는 CLK이 될 것이다.

320x100
Share Link
reply
반응형
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31