ㅎㅇ
고3 대입 시기에 팩토리오 800시간을 찍고 CPU를 버전 7까지 만들었다가
컴퓨터공학과에서 2년을 나름 열심히 수련하게 되고 팩토리오에서 점점 멀어짐.
그러다 군입대를 결정하고 휴학을 때려서 지금은 밥을 똥으로 바꾸는 기계가 되어버렸는데
이참에 다시 팩토리오에서 컴퓨터나 만들어볼까 하고 글을 쓰게 됨.
팩토리오에서 어떻게 하면 컴퓨터를 잘 설계할 수 있을 거 같은지에 대한 내 생각을 정리한 글임.
[팩토리오 회로 설계의 특이사항]
1. 60틱의 제약
회로는 1초에 60번보다 빠르게 돌 수 없음. 현재 CPU를 대충 3GHz라고 가정하면 5천만 배 느린 속도임.
따라서 이 틱을 정말 최대한 아껴써서 속도를 뽑아내야 함.
2. 10만개의 제약
컴마다 다를 수는 있는데 조합기 10만개정도 박으면 렉걸림. 이 안에서 써야 함.
모든 조합기를 하나의 연산에 투자한다면 초당 6백만개의 연산이 가능한데, 따라서 어떤 연산이든 6M operation/s 가 성능의 이론상 한계가 될 것임.
실제로는 연산을 위한 부가 장치가 필요하므로 GPU같이 매우 병렬화된 하드웨어를 만든다고 해도 1Mops를 넘기 힘들 것이라고 생각함.
이 성능이 이론상 최대라고 생각하고 목표 성능을 결정해야 함.
예를 들어 1024*1024의 이미지를 처리한다고 해도 어떤 구조에서든 아주 이상적으로 봐도 1초에 한 장 이하의 속도로 처리할 수 있을 것임.
3. 컨트롤은 비싸고 연산은 싸다
덧셈/곱셈도 1틱, 나눗셈에 거듭제곱까지 전부 1틱이지만 조건이 참일 때 1을 출력하는 것도 1틱임.
따라서 현실보다 연산 장치는 저렴해지고 제어 장치는 비싸짐.
이를 고려해서 최대한 단순한 제어를 해야 속도를 높일 수 있음.
4. 모든 것이 무조건 1틱, 겹치면 더해짐
팩토리오는 모든 회로가 정확히 1틱에 한 번 동작함.
따라서 여러 신호 중에 누가 더 먼저 도달하는지 경쟁해서 결과가 달라지는 경우가 없음.
이것을 잘 활용해야 함.
특히 저장장치는 되먹임 회로가 사용되는데 여기서 이득을 많이 볼 수 있음.
또한 신호 여러 개가 겹치면 더해진다는 성질도 굉장히 범용적이고 효과적으로 사용할 수 있음.
덧셈 연산을 몇 번이고 거의 공짜처럼 사용 가능한 사기 기능임.
5. 여러 유형의 신호를 병렬로 처리 가능
조합기엔 각각 기능이 있어서 여러 유형 신호를 병렬로 처리 가능함.
이를 잘 이용하면 조합기 개수를 매우 많이, 최대 256배 줄일 수 있음.
특히 저장 장치와 신호 전송(버스)에서 그럼.
[생각하고 있는 구조]
아래는 생각하고 있는 컴퓨터 시스템의 구조임.
1. 단일 프로세서에 단일 스레드, 운영체제 없음
아무리 생각해 봐도 팩토리오에서 여러 스레드나 프로세스를 굴릴 일은 일어날 거 같지 않음.
그래서 스레드/프로세스 1개만 돌릴 수 있는 시스템으로 제한해서 만드려고 하고
자원을 나눠줄 놈도 하나니 운영체제도 없는 시스템으로 가려고 함.
대신에 프로그램마다 시그널 핸들러를 넣어서 인터럽트 시 호출되도록 하는 기능 정도는 있으면 좋을 거 같음.
2. 프로그램이 별도의 공간에 존재하는 하버드 아키텍쳐
아래서도 설명하겠지만 이 시스템이 실행하는 프로그램은 현실의 x86같은 명령어 집합 구조보다 훨씬 저수준의 명령들을 실행할 것임.
따라서 그 명령어를 직접 하나하나 넣어주는 것은 굉장히 굉장히 비효율적인 일이 됨. (전에 실제로 했는데 CPU 버전 하나 설계하는 것만큼 걸림...)
그래서 컴파일러가 필수적이고, 외부의 진짜 CPU에서 도는 진짜 SW인 컴파일러에서 컴파일을 마친 후 블루프린트를 뽑아 인게임에 넣어 주는 방식이 가장 효율적임.
그랬을 때 굳이 프로그램을 데이터와 합칠 이유가 없어짐.
따라서 프로그램은 주로 일정신호조합기로 구성된 별도의 ROM을 게임 외부에서 생성된 블루프린트로 넣어주는 방식으로 동작함.
3. CPU, GPU 존재
CPU의 코어는 1개지만 병렬화가 가능한 연산, 예를 들면 그래픽 연산 등을 처리하기 위한 별도의 GPU가 있으면 좋을 거 같다는 생각을 해봄.
GPU프로그램은 현실처럼 CPU에서 넣어주는 것이 아닌, 위처럼 GPU 내부의 ROM을 블루프린트로 찍는 방식으로 입력됨.
4. 보다 저수준의 명령어 집합 구조
위 시스템에서 실행하는 명령어 집합 구조는 x86이나 ARM등의 현실 사례보다 훨씬 저수준임. (심지어 micro-op보다도!)
왜냐하면 위에서 언급한 '컨트롤은 비싸고 연산은 싸다'는 특성으로 인해 컨트롤 유닛의 역할을 최대한 줄여야 하기 때문임.
그래서 'A에서 B로 신호가 가는 길을 열어라' 라던지 'X번째 틱에 Y에 신호를 X만큼 쏴라' 처럼 상당히 저수준의 명령어를 실행함.
이로 인해 기계어로 직접 코딩하기가 현실보다 훨씬 복잡해짐. 디버깅은 토 나옴.
그래서 컴파일러를 굉장히 잘 설계해야 하며 게임 외부에서 가상 머신과 디버거를 만들어 보는 것도 고려해 볼 수 있음.
5. 파이프라인화 적음
현실의 CPU가 수십, GPU는 수백 수천(아마?) 개의 파이프라인을 가질 수 있는 것에 비해
이건 파이프라인화가 적음.
왜냐면 애초에 메모리 병목이랑 연산 병목이 없음. 모조리 1틱임.
그래서 파이프라인도 적고, 따라서 분기예측이나 겹겹이 쌓인 캐시. 캐시 prefetch 등도 필요없음.
6. fused연산의 적극적인 사용
컨트롤이 비싸므로 최대한 적은 컨트롤로 많은 연산을 해야 함.
그래서 연산 여러 개가 결합된 연산을 기본 기능으로 채택하면 좋음.
현실에서는 곱셈과 덧셈이 합쳐진 FMA(fused multipliy accumulate) 연산이 있는 것처럼
곱셈 후 덧셈 / minmax / 병렬로 비교 후 and와 or 등의 연산을 만들면 성능이 좋아짐.
7. 레지스터 - L1캐시 - 통합 메모리 - 스토리지의 4단 메모리 구성
팩토리오에서는 모든 소자가 조합기로 통일되어 있음. (물론 기차나 벨트같은 걸 쓸 수도 있는데 성능은 매우 느리고 별 장점이 없다 생각함)
그래서 현실에서처럼 서로 다른 특성을 가진 소자별로 계층적 메모리를 구성하는 전략이 덜 유효할 수 있음.
대신에 컨트롤이 비싸기 때문에 신호를 여러 군데로 바로 보낼 수 있도록 이곳저곳에 연결된 레지스터를 두면 상당히 유용함.
어차피 주로 쓰는 변수는 16개나 32개를 넘는 경우가 적으니까.
레지스터는 메모리의 0번지부터 시작하는 자리에 고정적으로 매핑됨.
그리고 그 아래에 일반적인 메모리 역할을 하는 L1캐시를 둬서 한 번에 한두개씩 데이터를 뽑아올 수 있게 함.
캐시 히트를 판정하는 컨트롤은 비싸므로 이건 명시적으로 컨트롤하도록 했음.
또 CPU의 L1캐시와 GPU의 SM들이 서로 통신할 필요가 있으므로 통합 메모리를 둬서 통신함.
통합 메모리와, L1캐시와 SM의 메모리 사이는 한 번에 데이터를 덩어리째로 옮길 수 있도록 설계할 수 있음.
그리고 마지막으로 각각 연산을 활용해서 한 개의 셀에 하나가 아닌 여러 개의 데이터를 담을 수 있도록 했지만 속도가 떨어진 스토리지가 있으면 좋을 것임.
그래서 메모리는 이렇게 4계층으로 구성됨.
8. MMU에 관해
솔직히 프로세스 하나만 돌아가고 해서 MMU가 그렇게 필요해 보이진 않음. 그냥 다 수동으로 조작하면 되지 않나라는 생각.
그래도 IO라던지에 유용할 수도 있고 나름 또 쓸만할 수도 있으니 그냥 그림에 넣어봄.
9. I/O에 관해
I/O는 잘 모르긴 한데 memory mapped I/O처럼 만들면 빠를 거 같음. 아니면 아예 고정적인 주소 매핑을 박아놓던가.
10. GPU 구조에 관해
GPU는 상당히 유용할 수 있고 현실과 비슷하게 설계할 수 있음. 제어보단 연산이 중심이므로 더 현실 구조와 가까워짐.
대신에 레지스터 블럭의 크기와 개수를 고정해 놓았는데, 이건 충분히 가변적으로 할 수 있어보임.
버전이 올라가면 바꿀 거 같지만 그럼 구조가 꽤 복잡해질 거 같아 최초 버전은 일단 고정하는 걸로. 어차피 성능은 크게 차이 안 날 거 같으니까.
다른 구조는 다 현실과 똑같아보이지만, Sync.(Synchronized) Mem이라고 다른 걸 하나 박아놨음.
저게 뭐냐면, 팩토리오는 신호 여러 개가 동시에 들어오면 현실처럼 망하는 게 아니라 더해지므로
덧셈 reducing을 굉장히 빠르고 간단하게 할 수 있다는 사기성을 지님.
그래서 동시에 같은 메모리를 읽거나, 같은 곳에 덧셈 reducing 할 때 쓸 수 있는 메모리를 하나 두고 동시에 쓴다는 의미에서 이름을 저렇게 붙임.
현실에서는 const 메모리에서 읽기밖에 안 되지만 여기선 쓰기도 가능하지.
이상 끝임.
팩토리오 커뮤니틴데 컴퓨터 공학 내용을 올려서 미안함. 문제되면 글 내림.
다른 의견 주면 감사하겠음.
근데 이걸 언제 다 구현할 수 있을지 모르겠네
개쩐당
아...오랜만에 올리셨네. 정말 구현 가능하다면 유튜브로 올려보세요. 외국인들 까무러칠듯 - dc App
이러다가 팩토리오로 DOOM도 돌릴 수 있겠다...? 이미 있나
이미 있을 걸
Verilog로 일반 파이프라인 cpu 만드는거 조교했었는데 이게 3학점 전공 한학기짜리 프로젝트임 일단 그걸 목표로 ㄱㄱ
위의 구조도를 게이트로 열거해도 한참 걸릴 것 같은데..
문자열 하나와 시냅스단위 처리 한개의 회로 사이즈에 따라 규모가 갈릴듯 힘내!
시체콘
https://m.dcinside.com/board/factorio/55732
예전에 님이 벨트물류측정기 아이디어 준거 응용해서 메모리를 만든적이 있는데 조합기3개당 1틱당 데이터1개 읽거나 쓰는 동작이 가능하지만 쓰는레이턴시가7틱 읽는레이턴시가5틱정도 되기 때문에 RAM으로는 좀 아쉽고 보조기억장치로 적절하지 않을까란 생각을 한적이 있었음
무엇보다 결정적으로 32비트중 31비트만 사용할 수 있기때문에 시스템을 32비트 프로세서가아니라 31비트 프로세서로 만들어야하나 하는 고민도 있었음
바닐라 신호가 정확히 256개는 아니고 여유가 더 있기 때문에 이거 이용하면 못저장한 1비트들을 다른 신호에 모아 저장하는식으로 해서 32비트 다 저장하는것이 가능해보이지만 메모리컨트롤이 좀더 복잡해지고, 아니면 아싸리 16비트 2개로 쪼게버려서 각각 저장해버리고 읽을때는 다시 합치는 식으로 동작시키면 컨트롤은 좀더 편할것 같지만 조합기를 2배로 써버려야한다는 문제점이 있음 아무튼 그래서 31비트 프로세서로 가느냐 아니면 좀더 리소스 써서 32비트 프로새서로 가느냐에 대한 고민을 한 적이 있는데, 님이 생각하는 메모리의 스펙은 레이턴시나 쓰루풋 측면에서 어떤 스펙인지가 궁금함
https://m.dcinside.com/board/factorio/59720
팩토2.0나오고 회로2.0까지 나오면 비교조합기가 너무 강력하져서 위 문제가 해결이 될 것 같긴함
비교조합기2.0은 기차스케쥴식으로 동작하니 복잡한 조건문 판별에 2틱필요한걸 1틱으로 줄일수도 있고, 무엇보다 조건 판별에 녹색선,빨간선이 합선된 값이 아니라 원하는 선 하나만 고를수 있다는점이 너무 큼
그래서 시스템 설계를 장기프로젝트로 잡는다면 만드는 도중에 회로2.0 업데이트 되면 갈아엎고 싶어지는거 존나많아져서 현타 올 것 같음
지금 생각하고 있는 건 일단 CPU L1캐시랑 GPU 공유 메모리는 비교기 3개 써서 읽기/쓰기 스루풋 1/틱, 레이턴시 1틱, 저장 32비트 되는 전에도 썼던 평범한 구조로 하려고 하고 디스크는 위와 같은 구조를 쓰되 256채널을 써서 1kb를 저장하고 입출력 포트도 그냥 조합기 256n개 써서 단순하고 빠르게 내보내려고 함 그래서 디스크 <-> 메인메모리 <-> CPU L1, GPU shared 간에 MMU를 낀 상태로 1kb 블럭 단위로 한꺼번에 이동할 수 있도록 회로를 짜면 어떨까 싶음 근데 말해준 대로 2.0나오면 현타오겠네 그냥 그거 나오면 그때 생각해야겠다
31비트밖에 못 쓰는 문제가 출력 때문에 그런 거라면 출력만 16비트 두개로 쪼개서 하나씩 뽑은다음에 합치는 방식으로 하면 안되나?
41분댓글이랑 비슷한 얘기같은데 16비트 따로저장하는게 편해보이긴 함
I/O 중에서 출력장치(모니터)의 경우
https://m.dcinside.com/board/factorio/55878
나는
저 한글출력기의 궁극적인 형태를 터미널처럼 만드는게 목표였는데 잘 만들어지면 이 글 본문에서처럼 특별한 어드레스를 할당하고 버스에 연결한다음 그 어드레스에 어떤 값을 쓰는 명령이 들어오는 것만 필터링해서 터미널을 업데이트 할 수 있도록 하고자 했음
입력장치(키보드)의 경우 조금 애매한데 바닐라에서 pushbutton모드처럼 1틱 신호 편하게 방출할 수 있으면 참 편할 것 같은데 지금 상황에서는 캐릭터 벽에 껴두고 방향키 누르는걸 출입구로 감지하거나, 아니면 상자에 아이템을 z등으로 넣으면 그걸 감지히거나 하는 식으로 인터페이스를 만들어야 할듯 한데 만들기가 좀 귀찮긴 할 것같음. 2.0에서 원격상호작용이 개선된다면 좀 나아질지도
오 그렇구만
전에 글자 입력기까지는 만들었었는데
https://gall.dcinside.com/mgallery/board/view?id=factorio&no=33644
이때는
조합기 껐다키는걸로 입력했었고
이게 아마 매 틱마다 입력이 가능했던 걸로 기억하는데
그래서 전에 만든 CPU에서 메모리랑 상관없이 내부에 I/O전용 포트 만들어서 그걸로 입출력했었음
그게 제일 빠르긴 한데 큐잉이 불가능해서 CPU가 입력받을 땐 I/O장치가 CPU와 동기화돼야 된다는 단점이 있었음
이 방식 I/O도 같이 넣으면 어떨까 고민중
CPU에 입력기 연결한건 여기
https://gall.dcinside.com/mgallery/board/view?id=factorio&no=33965
여러
줄 다차원으로 합쳐서 사용함
조합기 열어서 키고 끄고하는게 너무 불편할것 같음 ㅋㅋ 저거 별찍기 하는건 *에 대한 폰트를 따로 넣어준거임? *말고 다른기호로도 지원되나?
그러게 입력 수단이 개선된다면 확실히 좋겠는데
표시는
https://gall.dcinside.com/mgallery/board/view?id=factorio&no=33617
여기있는거 다됨
직접 그려서 인코딩된 블프 뽑는 프로그램 만들어서 씀
https://m.dcinside.com/board/factorio/59241
https://m.dcinside.com/board/factorio/59256
지금은 상수조합기가 20개밖에 출력못하는데 2.0나오면 물류그룹 기능 사용해서 제한없이 사용하기 때문에 폰트나 코드 저장등에 쓰는 ROM용 상수조합기 크기도 엄청 줄일 수 있을 것으로 보임
와 미쳤네...
프로그램 코드 작성은 C코드정도 수준도 아니고 아예 어셈블리어로 작성해야하는건가? 예전에 C코드로 작성하면 그걸 내가원하는 ISA에 맞게 변환하는게 가능할까 싶어서 LLVM이니 뭐니하는것도 좀 뒤적거려봤는데 봐야할게 너무 많아서 gg침
그러게 LLVM 백엔드 만들어서 ㅁㄴㅇㄹ하면 멋질듯 근데 할 수 있을지가..
기계어로 작성하려면 어셈보다 낮은 수준임 어셈에서 add $3 $4 명령어가 저 구조에서는 레지스터 3번에서 ALU1번으로 가는 신호를 1틱째에 보내고.. 레지스터 4번에서 ALU2번으로 가는 신호를 2틱째에 보내고.. ALU에 add해서 3번 레지스터에 쓰는 동작을 나타내는 신호를 나타내는 id값을 3틱째에 보내고.. 4틱째에 PC를 1 증가시키고.. 이런걸 다 짜넣어야함
고건 머가리 존나 굴려서 datapath 잘 구성해봐야지 ㅋㅋㅋ
하드웨어만 띡 만드는게 목표가 아니라면 전용 컴파일러를 결국 만들어야 할 듯