Conway의 Game of Life에서 테트리스 작업 게임 제작


993

여기에 이론적 인 질문이 있습니다. 어떤 경우에도 쉬운 대답을 제공하지 않으며 사소한 질문조차 없습니다.

Conway의 Game of Life에는 다른 Game-of-Life 규칙 시스템을 시뮬레이션 할 수있는 메타 픽셀 과 같은 구성이 있습니다 . 또한, Game of Life는 Turing-complete 인 것으로 알려져 있습니다.

당신의 임무는 테트리스 게임을 할 수 있도록 Conway의 삶의 게임 규칙을 사용하여 셀룰러 오토 마톤을 만드는 것입니다.

프로그램은 인터럽트를 나타 내기 위해 특정 세대에서 오토 마톤 상태를 수동으로 변경하여 (예 : 피스를 왼쪽 또는 오른쪽으로 이동, 떨어 뜨리거나 회전 시키거나, 그리드에 배치 할 새 피스를 임의로 생성) 카운팅을 통해 입력을받습니다. 대기 시간으로 특정 세대 수를 표시하고 결과를 오토 마톤 어딘가에 표시합니다. 표시된 결과는 실제 테트리스 그리드와 비슷해야합니다.

프로그램은 다음과 같은 순서로 점수가 매겨집니다 (낮은 기준은 높은 기준의 순위 결정 역할을합니다).

  • 경계 상자 크기-주어진 솔루션이 완전히 포함 된 가장 작은 영역을 가진 사각형 상자가 승리합니다.

  • 입력에 대한 작은 변경-인터럽트 승리를 위해 수동으로 조정해야하는 가장 적은 셀 (오토 마톤에서 최악의 경우).

  • 가장 빠른 실행 — 시뮬레이션에서 한 번의 진드기를 진전시키는 가장 적은 세대.

  • 초기 생세포 수-적은 수의 승리.

  • 먼저 게시-이전 게시물이 이깁니다.


95
"명백하게 작동하는 예"는 몇 시간 안에 실행되는 것이거나 우주의 열사병이 재생되기까지 걸릴 수 있지만 올바른 것으로 입증 될 수있는 것을 의미합니까?
피터 테일러

34
나는 이와 같은 것이 가능하고 재생할 수 있다고 확신합니다. 세계에서 가장 난해한 "조립 언어"중 하나를 프로그래밍 할 수있는 전문 지식을 가진 사람은 거의 없습니다.
Justin L.

58
이 과제는 현재 진행 중입니다! 대화방 | 진행 | 블로그
mbomb007

49
오늘 오전 5:10 (오후 9시 10 분) 현재이 질문은 PPCG 기록에서 답변을 얻지 않고 100 표에 도달 한 첫 번째 질문입니다! 잘 했어.
Joe Z.

76
이 문제를 해결하려고 노력하고 있습니다 ... 이제, 잠자리에들 때, 글라이더가 사방으로 튀어 나와 큰 혼란에 빠지게됩니다. 나의 수면은 맥동하는 pentadecathlons가 나의 길을 막고있는 Herschels가 나를 흡수하기 위해 진화하고있는 악몽으로 가득하다. John Conway, 저를 위해 기도해주세요.
dim

답변:


937

이것은 퀘스트로 시작되었지만 오디세이로 끝났습니다.

테트리스 프로세서 퀘스트, 2,940,928 x 10,295,296

패턴 파일은 모든 영광에서 여기 에서 볼 수 있으며 브라우저 에서 볼 수 있습니다 .

이 프로젝트는 지난 1 년과 1/2 년 동안 많은 사용자들의 노력의 결과입니다. 팀 구성은 시간이 지남에 따라 다양하지만 글을 쓰는 참가자는 다음과 같습니다.

또한 7H3_H4CK3R, Conor O'Brien 및이 과제를 해결하기 위해 노력한 많은 다른 사용자들 에게도 감사의 말씀을 전합니다 .

이 협업의 전례없는 범위로 인해이 답변은이 팀 구성원이 작성한 여러 답변으로 나누어 져 있습니다. 각 회원은 특정 하위 주제에 대해 글을 쓰고, 가장 관련성이 높은 프로젝트 영역에 해당합니다.

팀의 모든 구성원에게 투표 또는 바운티를 배포하십시오.

목차

  1. 개요
  2. 메타 픽셀 및 VarLife
  3. 하드웨어
  4. QFTASM과 코골
  5. 조립, 번역 및 미래
  6. 새로운 언어와 컴파일러

또한 솔루션의 일부로 작성한 모든 코드를 넣은 GitHub 조직을 확인 하십시오. 질문은 우리의 개발 대화방에 직접 문의하십시오 .


1 부 : 개요

이 프로젝트의 기본 아이디어는 추상화 입니다. Life에서 Tetris 게임을 직접 개발하는 대신 일련의 단계로 추상화를 천천히 래칫했습니다. 각 계층에서 우리는 인생의 어려움에서 멀어지고 다른 컴퓨터처럼 프로그래밍하기 쉬운 컴퓨터 구성에 더 가까워집니다.

먼저, 우리는 컴퓨터의 기초로 OTCA 메타 픽셀 을 사용 했습니다 . 이 메타 픽셀은 "실제와 같은"규칙을 모방 할 수 있습니다. WireworldWireworld 컴퓨터 는이 프로젝트에서 중요한 영감의 원천이되었으므로 메타 픽셀과 유사한 구성을 만들려고했습니다. OTCA 메타 픽셀로 Wireworld를 에뮬레이트 할 수는 없지만 다른 메타 픽셀에 다른 규칙을 할당하고 와이어와 유사한 기능을하는 메타 픽셀 배열을 구축 할 수 있습니다.

다음 단계는 컴퓨터의 기초가되는 다양한 기본 로직 게이트를 구성하는 것이 었습니다. 이 단계에서는 이미 실제 프로세서 설계와 유사한 개념을 다루고 있습니다. 다음은 OR 게이트의 예입니다.이 이미지의 각 셀은 실제로 전체 OTCA 메타 픽셀입니다. "전자"(각각 단일 비트의 데이터를 나타냄)가 게이트로 들어오고 나가는 것을 볼 수 있습니다. 또한 컴퓨터에서 우리가 사용했던 다양한 메타 픽셀 유형을 모두 볼 수 있습니다 : B / S를 검정색 배경으로, B1 / S를 파란색으로, B2 / S를 녹색으로, B12 / S1을 빨간색으로.

영상

여기에서 프로세서 아키텍처를 개발했습니다. 비전 역적이고 구현하기 쉬운 아키텍처를 설계하는 데 많은 노력을 기울였습니다. Wireworld 컴퓨터는 초보적인 전송 트리거 아키텍처를 사용했지만이 프로젝트는 여러 opcode 및 주소 지정 모드가 포함 된 훨씬 유연한 RISC 아키텍처를 사용합니다. 우리는 QFTASM (Quest for Tetris Assembly)으로 알려진 어셈블리 언어를 만들었으며, 이는 프로세서 구성을 안내합니다.

우리의 컴퓨터도 비동기식이므로 컴퓨터를 제어하는 ​​글로벌 시계가 없습니다. 오히려, 데이터는 컴퓨터를 따라 흐를 때 클럭 신호를 동반하므로 컴퓨터의 로컬 타이밍은 아니지만 로컬 타이밍에만 초점을 맞출 필요가 있습니다.

다음은 프로세서 아키텍처를 보여줍니다.

영상

여기서는 컴퓨터에서 Tetris를 구현하는 것입니다. 이를 달성하기 위해 고급 언어를 QFTASM으로 컴파일하는 여러 가지 방법을 연구했습니다. 우리는 개발중인 두 번째 고급 언어 인 Cogol이라는 기본 언어를 가지고 있으며, 최종적으로 GCC 백엔드가 있습니다. 현재 테트리스 프로그램은 Cogol에서 작성 / 컴파일되었습니다.

최종 Tetris QFTASM 코드가 생성되면 마지막 단계는이 코드에서 해당 ROM으로 조립 한 다음 메타 픽셀에서 기본 Game of Life로 조립하여 구성을 완료하는 것입니다.

테트리스 실행

컴퓨터를 어지럽히 지 않고 Tetris를 즐기려 는 경우 QFTASM 인터프리터 에서 Tetris 소스 코드 를 실행할 수 있습니다 . 전체 게임을 보려면 RAM 표시 주소를 3-32로 설정하십시오. 다음은 편의를위한 영구 링크입니다. Tetris in QFTASM .

게임 특징 :

  • 총 7 개의 테트로 미노
  • 운동, 회전, 부드러운 방울
  • 라인 클리어 및 스코어링
  • 미리보기 조각
  • 플레이어 입력은 무작위성을 주입합니다

디스플레이

우리의 컴퓨터는 테트리스 보드를 메모리 내의 그리드로 나타냅니다. 주소 10-31은 보드를 표시하고 주소 5-8은 미리보기 조각을 표시하며 주소 3은 점수를 포함합니다.

입력

RAM 주소 1의 내용을 수동으로 편집하여 게임에 입력합니다. QFTASM 인터프리터를 사용하면 이는 주소 1에 직접 쓰기를 수행하는 것을 의미합니다. 인터프리터 페이지에서 "RAM에 직접 쓰기"를 찾으십시오. 각 이동은 단일 비트의 RAM 만 편집하면되며이 입력 레지스터는 입력 이벤트를 읽은 후에 자동으로 지워집니다.

value     motion
   1      counterclockwise rotation
   2      left
   4      down (soft drop)
   8      right
  16      clockwise rotation

채점 시스템

한 번에 여러 줄을 클리어하면 보너스를받습니다.

1 row    =  1 point
2 rows   =  2 points
3 rows   =  4 points
4 rows   =  8 points

14
@ Christopher2EZ4RTZ이 개요 포스트는 많은 프로젝트 멤버들이 수행 한 작업에 대해 자세히 설명합니다 (개요 포스트의 실제 쓰기 포함). 따라서 CW 인 것이 적절합니다. 우리는 또한 한 사람이 두 개의 게시물을 갖는 것을 피하려고 노력했습니다. 왜냐하면 우리는 그 담당자를 유지하려고 노력하기 때문에 불공정 한 양의 담당자를 받게 될 것이기 때문입니다.
Mego

28
우선 +1은 놀라운 업적이므로 (특히 테트리스가 아닌 인생의 게임에 컴퓨터를 구축 한 이후). 둘째, 컴퓨터는 얼마나 빠르며 테트리스 게임은 얼마나 빠릅니까? 심지어 원격으로 재생할 수 있습니까? (다시 : 이것은 굉장하다)
Socratic Phoenix

18
이건 .. 이건 완전 미쳤어 모든 답변에 +1합니다.
scottinet

28
답변에 대해 작은 바운티를 분배하고자하는 사람에게 경고 : 500 회가 될 때까지 바운티 금액을 매번 두 배로 늘려야합니다. 따라서 500 명이 아닌 한 사람은 모든 답변에 동일한 금액을 줄 수 없습니다.
Martin Ender

23
이것은 내가 거의 이해하지 못하면서 스크롤 한 가장 위대한 것입니다.
엔지니어 토스트

678

2 부 : OTCA 메타 픽셀 및 VarLife

OTCA 메타 픽셀

OTCA 메타 픽셀
( 소스 )

OTCA Metapixel은 모든 생명과 같은 셀룰러 오토마타를 시뮬레이션하는 데 사용할 수있는 생명의 콘웨이의 게임의 구조입니다. LifeWiki (위의 링크)가 말했듯이

OTCA 메타 픽셀은 Brice Due에 의해 구성된 2048 × 2048 기간 35328 단위 셀입니다. 실제와 같은 셀룰러 오토 마톤을 에뮬레이트하는 기능과 축소했을 때 ON이라는 사실을 포함하여 많은 장점이 있습니다. OFF 셀은 구별하기 쉽습니다 ...

무엇 실제와 같은 셀룰러 오토마타는 여기에서 의미하는 세포가 태어나는 세포가 자신의 팔 인접 셀의 대부분은 살아 방식에 따라 살아남은 본질적이다. 이러한 규칙의 구문은 다음과 같습니다. B 다음에 출생을 유발하는 라이브 이웃 수, 슬래시, S 다음에 셀을 유지하는 라이브 이웃 수가 이어집니다. 조금 말이 많으므로 예제가 도움이 될 것이라고 생각합니다. 정식 생명 게임은 B3 / S23 규칙에 의해 표현 될 수 있는데, 이는 3 개의 살아있는 이웃을 가진 모든 죽은 세포가 살아남을 것이고 2 또는 3 개의 살아있는 이웃을 가진 모든 살아있는 세포는 살아남을 것이라고 말합니다. 그렇지 않으면 세포가 죽습니다.

2048 x 2048 셀 임에도 불구하고, OTCA 메타 픽셀은 실제로 2058 x 2058 셀의 경계 상자를 가지며, 그 이유는 대각선 이웃 과 모든 방향으로 5 개의 셀로 겹치기 때문 입니다. 겹치는 셀은 글라이더를 차단하는 역할을합니다. 글라이더는 켜져있는 메타 셀을 알리기 위해 방출되어 다른 메타 픽셀을 방해하거나 무기한으로 비행하지 않습니다. 출생 및 생존 규칙은 두 열을 따라 특정 위치에 먹는 사람의 존재 여부에 따라 메타 픽셀의 왼쪽에있는 세포의 특수 섹션에 인코딩됩니다 (하나는 출생, 다른 하나는 생존). 인접 셀의 상태를 감지하는 방법은 다음과 같습니다.

그런 다음 9-LWSS 스트림이 셀 주위로 시계 방향으로 이동하여 허니 비트 반응을 일으킨 인접한 각 '온'셀에 대해 LWSS가 손실됩니다. 누락 된 LWSSe의 수는 다른 LWSS를 반대 방향에서 충돌시켜 전면 LWSS의 위치를 ​​감지하여 계산됩니다. 이 충돌은 글라이더를 방출하며, 이는 출생 / 생존 상태가 없음을 나타내는 먹는 사람이없는 경우 다른 하나 또는 두 개의 벌집 반응을 유발합니다.

OTCA 메타 픽셀의 각 측면에 대한 자세한 다이어그램은 원래 웹 사이트에서 찾을 수 있습니다. 작동 방식 .

VarLife

나는 당신이 어떤 세포가 어떤 생생한 규칙에 따라 행동 할 수있게하는 생명과 같은 규칙의 온라인 시뮬레이터를 만들었고 그것을 "생명의 변이"라고 불렀습니다. 이 이름은 "VarLife"로 더 간결 해졌습니다. 여기 스크린 샷이 있습니다 ( http://play.starmaninnovations.com/varlife/BeeHkfCpNR 링크 ) :

VarLife 스크린 샷

주목할만한 기능 :

  • 라이브 / 데드 사이에서 셀을 전환하고 다른 규칙으로 보드를 페인트합니다.
  • 시뮬레이션을 시작 및 중지하고 한 번에 한 단계 씩 수행하는 기능. 초당 틱 및 초당 밀리 초 상자에 설정된 속도로 가능한 빨리 또는 더 느리게 주어진 단계 수를 수행 할 수도 있습니다.
  • 모든 라이브 셀을 지우거나 보드를 공백 상태로 완전히 재설정하십시오.
  • 셀 및 보드 크기를 변경하고 가로 및 / 또는 세로로 토 로이드 랩을 사용할 수 있습니다.
  • 영구 링크 (URL의 모든 정보를 인코딩)와 짧은 URL (때로는 너무 많은 정보가 있기 때문에 어쨌든 좋습니다).
  • B / S 사양, 색상 및 선택적 임의성을 갖는 규칙 세트.
  • 그리고 마지막으로 확실히 GIF를 렌더링하십시오!

render-to-gif 기능은 구현하는 데 엄청난 양의 작업이 필요했기 때문에 제가 가장 좋아하는 것이므로 아침 7시에 마침내 크랙 할 때 만족스럽고 VarLife 구조를 다른 사람들과 공유하기가 매우 쉽기 때문에 실제로 만족했습니다. .

기본 VarLife 회로

대체로 VarLife 컴퓨터에는 4 가지 셀 유형 만 있으면됩니다! 죽은 / 생존 상태를 모두 계산하는 8 개의 상태입니다. 그들은:

  • B / S 셀은 결코 살아남을 수 없기 때문에 모든 구성 요소 사이의 버퍼 역할을하는 B / S (흑백).
  • 신호 전파에 사용되는 기본 셀 유형 인 B1 / S (청색 / 청록색).
  • B2 / S (녹색 / 노란색)는 주로 신호 ​​제어에 사용되며 역 전파되지 않도록합니다.
  • B12 / S1 (빨간색 / 주황색) : 신호 교차 및 비트 데이터 저장과 같은 특수한 상황에서 사용됩니다.

이 짧은 URL을 사용하여 이미 인코딩 된 규칙 ( http://play.starmaninnovations.com/varlife/BeeHkfCpNR)으로 VarLife를여십시오 .

전선

다양한 특성을 가진 몇 가지 와이어 설계가 있습니다.

이것은 녹색 스트립으로 둘러싸인 파란색 스트립 인 VarLife에서 가장 쉽고 가장 기본적인 와이어입니다.

기본 와이어
짧은 URL : http://play.starmaninnovations.com/varlife/WcsGmjLiBF

이 와이어는 단방향입니다. 즉, 반대 방향으로 이동하려고 시도하는 모든 신호를 제거합니다. 또한 기본 와이어보다 좁은 하나의 셀입니다.

단방향 와이어
짧은 URL : http://play.starmaninnovations.com/varlife/ARWgUgPTEJ

대각선 와이어도 존재하지만 많이 사용되지는 않습니다.

대각선
짧은 URL : http://play.starmaninnovations.com/varlife/kJotsdSXIj

게이츠

실제로 각 개별 게이트를 구성하는 많은 방법이 있으므로 각 종류의 예를 하나만 보여 드리겠습니다. 이 첫 번째 GIF는 각각 AND, XOR 및 OR 게이트를 보여줍니다. 여기서 기본 아이디어는 녹색 셀은 AND와 같은 역할을하고, 파란색 셀은 XOR과 같은 역할을하며, 빨간색 셀은 OR과 같은 역할을하며, 주변의 다른 모든 셀은 흐름을 올바르게 제어하기위한 것입니다.

AND, XOR 또는 OR 논리 게이트
짧은 URL : http://play.starmaninnovations.com/varlife/EGTlKktmeI

"ANT gate"로 약칭 된 AND-NOT 게이트는 중요한 구성 요소로 판명되었습니다. B의 신호가없는 경우에만 A의 신호를 전달하는 게이트입니다. 따라서 "A AND NOT B"입니다.

AND-NOT 게이트
짧은 URL : http://play.starmaninnovations.com/varlife/RsZBiNqIUy

정확하게 게이트 는 아니지만 와이어 교차 타일은 여전히 ​​매우 중요하고 유용합니다.

와이어 크로싱
짧은 URL : http://play.starmaninnovations.com/varlife/OXMsPyaNTC

또한 여기에는 게이트가 없습니다. 들어오는 신호가 없으면 일정한 출력이 생성되어야하므로 현재 컴퓨터 하드웨어에 필요한 다양한 타이밍에서 제대로 작동하지 않기 때문입니다. 우리는 어쨌든 그것을 잘하지 못했습니다.

또한 많은 구성 요소가 11 x 11 바운딩 박스 ( 타일 ) 내에 들어가 도록 의도적으로 설계되어 타일에서 나가기 위해 11 틱 신호가 타일에 들어가는 것을 방지합니다. 따라서 간격이나 타이밍에 맞게 와이어를 조정할 필요없이 구성 요소를보다 모듈화 할 수 있으며 필요에 따라 함께 쉽게 밟을 수 있습니다.

회로 구성 요소를 탐색하는 과정에서 발견 / 구축 된 게이트를 더 보려면 PhiNotPi : Building Blocks : Logic Gates의 블로그 게시물을 확인하십시오 .

구성 요소 지연

컴퓨터 하드웨어를 설계하는 과정에서 KZhang은 아래와 같이 여러 지연 구성 요소를 고안했습니다.

4 틱 지연 : 짧은 URL : http://play.starmaninnovations.com/varlife/gebOMIXxdh
4 틱 지연

5 틱 지연 : 짧은 URL : http://play.starmaninnovations.com/varlife/JItNjJvnUB
5 틱 지연

8 틱 지연 (세 가지 진입 점) : 짧은 URL : http://play.starmaninnovations.com/varlife/nSTRaVEDvA
8 틱 지연

11 틱 지연 : 짧은 URL : http://play.starmaninnovations.com/varlife/kfoADussXA
11 틱 지연

12 틱 지연 : 짧은 URL : http://play.starmaninnovations.com/varlife/bkamAfUfud
12 틱 지연

14 틱 지연 : 짧은 URL : http://play.starmaninnovations.com/varlife/TkwzYIBWln
14 틱 지연

15 틱 지연 ( 이와 비교하여 확인 ) : 짧은 URL : http://play.starmaninnovations.com/varlife/jmgpehYlpT
15 틱 지연

바로 이것이 바로 VarLife의 기본 회로 구성 요소입니다. 컴퓨터의 주요 회로는 KZhang의 하드웨어 포스트 를 참조하십시오 !


4
VarLife는이 프로젝트에서 가장 인상적인 부분 중 하나입니다. 예를 들어 Wireworld놀랍습니다 . OTCA 메타 픽셀은 필요한 것보다 훨씬 더 큰 것 같습니다. 골프를 시도한 적이 있습니까?
primo

@primo : Dave Greene은 다소 노력하고 있습니다. chat.stackexchange.com/transcript/message/40106098#40106098
El'endia Starman

6
네, 이번 주말에 512x512 HashLife 친화적 인 메타 셀 ( conwaylife.com/forums/viewtopic.php?f=&p=51287#p51287 ) 의 중심에서 상당한 발전을 이루었습니다 . 축소 될 때 "픽셀"영역이 셀의 상태를 나타내는 데 얼마나 큰지에 따라 메타 셀이 다소 작아 질 수 있습니다. 그러나 Golly의 HashLife 알고리즘이 컴퓨터를 훨씬 빠르게 실행할 수 있기 때문에 정확한 2 ^ N 크기 타일에서 멈추는 것이 좋습니다.
Dave Greene

2
와이어와 게이트를 덜 "폐기적인"방식으로 구현할 수 없습니까? 전자는 글라이더 또는 우주선 (방향에 따라 다름)으로 표현됩니다. 나는 그것들을 리디렉션하고 (필요한 경우 다른 것으로 변경하는) 배열과 글라이더로 작동하는 일부 게이트를 보았습니다. 예, 더 많은 공간이 필요하고 디자인이 더 복잡하고 타이밍이 정확해야합니다. 그러나 일단 기본 구성 요소가 있으면 쉽게 구성 할 수 있어야하며 OTCA를 사용하여 VarLife가 구현 한 것보다 훨씬 적은 공간을 차지하게됩니다. 더 빨리 실행될 것입니다.
Heimdall

@Heimdall 잘 작동하지만 Tetris를 재생하는 동안 잘 표시되지 않습니다.
MilkyWay90

649

3 부 : 하드웨어

논리 게이트에 대한 지식과 프로세서의 일반적인 구조를 통해 컴퓨터의 모든 구성 요소 설계를 시작할 수 있습니다.

디멀티플렉서

디멀티플렉서 또는 demux는 ROM, RAM 및 ALU의 중요한 구성 요소입니다. 주어진 선택기 데이터에 따라 입력 신호를 많은 출력 신호 중 하나로 라우팅합니다. 직렬-병렬 변환기, 신호 검사기 및 클럭 신호 분배기의 세 가지 주요 부분으로 구성됩니다.

직렬 선택기 데이터를 "병렬"로 변환하는 것으로 시작합니다. 이것은 가장 왼쪽의 데이터 비트가 가장 왼쪽의 11x11 정사각형의 클록 신호와 교차하고, 다음 데이터의 비트가 다음 11x11 정사각형의 클록 신호와 교차하도록 데이터를 전략적으로 분할하고 지연시킴으로써 수행됩니다. 모든 데이터 비트가 11x11 정사각형마다 출력되지만 모든 데이터 비트는 클럭 신호와 한 번만 교차합니다.

직렬-병렬 변환기

다음으로, 병렬 데이터가 사전 설정 주소와 일치하는지 확인합니다. 클록 및 병렬 데이터에서 AND 및 ANT 게이트를 사용하여이를 수행합니다. 그러나 병렬 데이터도 다시 비교할 수 있도록 출력해야합니다. 이것들은 내가 생각해 낸 문입니다.

신호 점검 게이트

마지막으로, 우리는 클럭 신호를 분리하고, 다수의 신호 검사기 (각 주소 / 출력마다 하나씩)를 쌓아서 멀티플렉서를 가지고 있습니다!

멀티플렉서

ROM

ROM은 주소를 입력으로 받아서 해당 주소의 명령을 출력으로 보냅니다. 우리는 멀티플렉서를 사용하여 클럭 신호를 명령 중 하나로 지정합니다. 다음으로 와이어 교차점과 OR 게이트를 사용하여 신호를 생성해야합니다. 와이어 크로싱은 클럭 신호가 명령의 모든 58 비트 아래로 이동하고 생성 된 신호 (현재 병렬)가 ROM을 통해 아래로 이동하여 출력되도록합니다.

ROM 비트

다음으로 병렬 신호를 직렬 데이터로 변환하면 ROM이 완성됩니다.

병렬-직렬 변환기

ROM

ROM은 현재 Golly에서 클립 보드의 어셈블리 코드를 ROM으로 변환하는 스크립트를 실행하여 생성됩니다.

SRL, SL, SRA

이 세 가지 로직 게이트는 비트 시프트에 사용되며 일반적인 AND, OR, XOR 등보다 복잡합니다. 이러한 게이트를 작동시키기 위해 먼저 클럭 신호를 적절한 시간 지연시켜 "시프트"를 발생시킵니다. 데이터에서. 이 게이트들에 주어진 두 번째 주장은 얼마나 많은 비트를 이동 시킬지를 지시합니다.

SL과 SRL의 경우

  1. 12 개의 최상위 비트가 켜져 있지 않아야합니다 (그렇지 않으면 출력이 단순히 0 임).
  2. 4 개의 최하위 비트를 기준으로 올바른 양의 데이터를 지연시킵니다.

이것은 많은 AND / ANT 게이트와 멀티플렉서로 가능합니다.

SRL

시프트 중에 부호 비트를 복사해야하기 때문에 SRA는 약간 다릅니다. 우리는 클럭 비트와 부호 비트를 AND 한 다음 와이어 스플리터와 OR 게이트를 사용하여 출력을 여러 번 복사하여이 작업을 수행합니다.

SRA

세트 리셋 (SR) 래치

프로세서 기능의 많은 부분은 데이터를 저장하는 기능에 의존합니다. 2 개의 빨간색 B12 / S1 셀을 사용하면 그렇게 할 수 있습니다. 두 셀은 서로를 유지할 수 있고 함께 떨어져있을 수도 있습니다. 여분의 세트, 리셋 및 읽기 회로를 사용하여 간단한 SR 래치를 만들 수 있습니다.

SR 래치

동기 장치

직렬 데이터를 병렬 데이터로 변환 한 다음 많은 SR 래치를 설정하여 전체 데이터 워드를 저장할 수 있습니다. 그런 다음 데이터를 다시 가져 오려면 모든 래치를 읽고 재설정 한 후 그에 따라 데이터를 지연하면됩니다. 이를 통해 다른 단어를 기다리는 동안 하나 이상의 데이터 단어를 저장할 수 있으므로 다른 시간에 도착한 두 단어의 데이터가 동기화 될 수 있습니다.

동기 장치

카운터 읽기

이 장치는 RAM에서 몇 번이나 더 많은 주소를 지정해야하는지 추적합니다. SR 래치와 유사한 장치 인 T 플립 플롭을 사용하여이 작업을 수행합니다. T 플립 플롭이 입력을 수신 할 때마다 상태가 변경됩니다. 켜져있는 경우 꺼지고 그 반대도 마찬가지입니다. T 플립 플롭이 온에서 오프로 플립되면 출력 펄스를 보내 다른 T 플립 플롭에 공급되어 2 비트 카운터를 형성 할 수 있습니다.

2 비트 카운터

읽기 카운터를 만들려면 카운터를 두 개의 ANT 게이트가있는 적절한 주소 지정 모드로 설정하고 카운터의 출력 신호를 사용하여 클럭 신호를 ALU 또는 RAM으로 보낼 위치를 결정해야합니다.

카운터 읽기

읽기 대기열

읽기 대기열은 RAM의 출력을 올바른 위치로 보낼 수 있도록 RAM에 입력을 보낸 읽기 카운터를 추적해야합니다. 이를 위해 일부 SR 래치를 사용합니다. 각 입력에 대해 하나의 래치입니다. 판독 카운터에서 신호가 RAM으로 전송되면 클럭 신호가 분리되어 카운터의 SR 래치를 설정합니다. RAM의 출력은 SR 래치와 함께 AND되며 RAM의 클럭 신호는 SR 래치를 재설정합니다.

읽기 대기열

알루

ALU는 SR 래치를 사용하여 신호를 보낼 위치를 추적한다는 점에서 읽기 큐와 유사하게 작동합니다. 먼저, 명령의 오피 코드에 대응하는 논리 회로의 SR 래치는 멀티플렉서를 사용하여 설정된다. 다음으로, 첫 번째 및 두 번째 인수의 값은 SR 래치와 함께 AND 된 다음 논리 회로로 전달됩니다. 클럭 신호는 래치가 지나갈 때 래치를 재설정하여 ALU를 다시 사용할 수 있도록합니다. (대부분의 회로는 골프를 치고 많은 지연 관리 기능이 도입되어 약간 혼란스러워 보입니다)

알루

RAM은이 프로젝트에서 가장 복잡한 부분이었습니다. 데이터를 저장 한 각 SR 래치를 매우 구체적으로 제어해야했습니다. 읽기 위해, 주소는 멀티플렉서로 전송되고 RAM 장치로 전송됩니다. RAM 장치는 저장된 데이터를 병렬로 출력하며 직렬로 변환되어 출력됩니다. 기록을 위해, 어드레스는 다른 멀티플렉서로 전송되고, 기록 될 데이터는 직렬에서 병렬로 변환되고, RAM 유닛은 RAM을 통해 신호를 전파한다.

각 22x22 메타 픽셀 RAM 장치는 다음과 같은 기본 구조를 갖습니다.

RAM 유닛

전체 RAM을 합하면 다음과 같은 결과가 나타납니다.

램

모든 것을 하나로 모으기

이러한 모든 구성 요소와 개요에 설명 된 일반 컴퓨터 아키텍처를 사용 하여 작동중인 컴퓨터를 구성 할 수 있습니다!

다운로드 : - 완료 테트리스 컴퓨터 - ROM 생성 스크립트, 빈 컴퓨터, 프라임 발견 컴퓨터

컴퓨터


49
나는이 게시물의 이미지가 어떤 이유로 든 내 의견으로는 매우 아름답다고 말하고 싶습니다. : P +1
HyperNeutrino

7
이것은 내가 본 것 중 가장 놀라운 것입니다 .... 가능하다면 +20을 할 것입니다
FantaC

3
@tfbninja 현상금이라고하며 200 평판을 줄 수 있습니다.
Fabian Röling

10
이 프로세서는 스펙터 및 멜트 다운 공격에 취약합니까? :)
Ferrybig

5
@Ferrybig 분기 예측이 없으므로 의심합니다.
JAD

621

파트 4 : QFTASM 및 Cogol

아키텍처 개요

요컨대, 우리 컴퓨터에는 16 비트 비동기 RISC 하버드 아키텍처가 있습니다. 수작업으로 프로세서를 구축 할 때는 RISC ( 축소 명령어 세트 컴퓨터 ) 아키텍처가 실질적으로 필요합니다. 우리의 경우, 이것은 opcode의 수가 적고, 더 중요한 것은 모든 명령이 매우 유사한 방식으로 처리된다는 것을 의미합니다.

참고로 Wireworld 컴퓨터는 전송 트리거 아키텍처 를 사용했으며,이 명령은 유일한 레지스터였으며 MOV특수 레지스터를 쓰거나 읽음으로써 계산이 수행되었습니다. 이 패러다임은 구현하기 매우 쉬운 아키텍처로 이어지지 만 결과는 사용할 수 없습니다. 모든 산술 / 논리 / 조건부 연산에는 세 가지 명령이 필요 합니다. 우리는 훨씬 덜 난해한 아키텍처를 만들고 싶었습니다.

유용성을 높이면서 프로세서를 단순하게 유지하기 위해 몇 가지 중요한 디자인 결정을 내 렸습니다.

  • 레지스터가 없습니다. RAM의 모든 주소는 동일하게 취급되며 모든 작업에 대한 인수로 사용할 수 있습니다. 어떤 의미에서 이것은 모든 RAM이 레지스터처럼 취급 될 수 있음을 의미합니다. 이는 특별한로드 / 저장 명령어가 없음을 의미합니다.
  • 비슷한 맥락에서, 메모리 매핑. 쓰거나 읽을 수있는 모든 것은 통합 된 주소 지정 체계를 공유합니다. 이것은 프로그램 카운터 (PC)가 주소 0이며, 정규 명령과 제어 흐름 명령의 유일한 차이점은 제어 흐름 명령이 주소 0을 사용한다는 것입니다.
  • 데이터는 전송시 직렬로 저장되며 병렬로 저장됩니다. 컴퓨터의 "전자"기반 특성으로 인해 데이터가 직렬 리틀 엔디안 (최하위 비트 우선) 형식으로 전송 될 때 더하기 및 빼기가 구현하기가 훨씬 쉽습니다. 더욱이 시리얼 데이터는 번거롭고 번거로운 번거로운 데이터 버스의 필요성을 제거합니다 (데이터를 함께 유지하려면 버스의 모든 "레인"이 동일한 이동 지연을 경험해야합니다).
  • 하버드 아키텍처는 프로그램 메모리 (ROM)와 데이터 메모리 (RAM)를 구분하는 것을 의미합니다. 이렇게하면 프로세서의 유연성이 떨어지지 만 크기 최적화에 도움이됩니다. 프로그램의 길이는 필요한 RAM의 양보다 훨씬 크기 때문에 프로그램을 ROM으로 분리 한 다음 ROM 압축에 집중할 수 있습니다 읽기 전용 인 경우 훨씬 쉽습니다.
  • 16 비트 데이터 너비 이것은 표준 테트리스 보드 (10 블록)보다 넓은 2의 가장 작은 전력입니다. 이를 통해 데이터 범위는 -32768 ~ +32767이며 최대 프로그램 길이는 65536입니다. (2 ^ 8 = 256 명령은 장난감 프로세서가 원하는 대부분의 간단한 작업에는 충분하지만 테트리스에는 충분하지 않습니다.)
  • 비동기식 디자인. 컴퓨터의 타이밍을 지시하는 중앙 시계 (또는 동등하게 몇 개의 시계)를 갖기보다는, 모든 데이터는 컴퓨터 주위를 흐를 때 데이터와 병행하여 이동하는 "시계 신호"를 동반한다. 특정 경로는 다른 경로보다 짧을 수 있으며, 이는 중앙 시계 설계에 어려움이 있지만 비동기 설계는 가변 시간 작업을 쉽게 처리 할 수 ​​있습니다.
  • 모든 지침은 크기가 동일합니다. 각 명령어에 피연산자 3 개 (값 값 대상)가있는 1 개의 opcode가있는 아키텍처가 가장 유연한 옵션이라고 생각했습니다. 여기에는 이진 데이터 작업과 조건부 이동이 포함됩니다.
  • 간단한 주소 지정 모드 시스템. 다양한 주소 지정 모드를 갖는 것은 배열이나 재귀와 같은 것을 지원하는 데 매우 유용합니다. 비교적 간단한 시스템으로 몇 가지 중요한 주소 지정 모드를 구현했습니다.

우리 아키텍처의 그림은 개요 게시물에 포함되어 있습니다.

기능 및 ALU 작업

여기서부터는 프로세서에 어떤 기능이 있어야하는지 결정해야했습니다. 각 명령의 다양성뿐만 아니라 구현의 용이성에 특별한주의를 기울였습니다.

조건부 동작

조건부 이동은 매우 중요하며 소규모 및 대규모 제어 흐름의 역할을합니다. "소규모"는 특정 데이터 이동의 실행을 제어하는 ​​능력을 의미하는 반면 "대규모"는 제어 흐름을 임의의 코드 조각으로 전송하기위한 조건부 점프 동작으로 사용됩니다. 메모리 매핑으로 인해 조건부 이동으로 데이터를 일반 RAM으로 복사하고 대상 주소를 PC로 복사 할 수 있기 때문에 전용 점프 작업이 없습니다. 또한 비슷한 이유로 무조건 이동과 무조건 점프를 모두 포기하기로 결정했습니다. 둘 다 TRUE로 하드 코드 된 조건으로 조건부 이동으로 구현할 수 있습니다.

"0이 아닌 경우 이동"( MNZ)과 "0보다 작은 경우 이동 "( ) 의 두 가지 유형의 조건부 이동을 선택했습니다 MLZ. 기능적으로 MNZ데이터의 MLZ비트가 1인지 확인 하는 반면 부호 비트가 1인지 확인하는 것이 중요합니다. 이들은 각각 등식과 비교에 유용합니다. 그 이유는 우리는 "제로 경우 이동"으로 다른 사람을 통해이 두 가지를 선택 ( MEZ( "제로보다 큰 경우 이동") 또는 MGZ그했다) MEZ동안 빈 신호로부터 TRUE 신호를 만들 필요 MGZ하여을 요구, 더 복잡한 검사입니다 부호 비트는 0이고 다른 비트는 1입니다.

산수

프로세서 설계 안내 측면에서 가장 중요한 다음 명령어는 기본 산술 연산입니다. 앞서 언급했듯이, 우리는 리틀 엔디안 시리얼 데이터를 사용하고 있습니다. 더하기 / 빼기 연산의 용이성에 의해 결정되는 엔디안 선택. 가장 중요하지 않은 비트가 먼저 도착하면, 산술 단위는 캐리 비트를 쉽게 추적 할 수 있습니다.

우리는 음수에 2의 보수 표현을 사용하기로 선택했습니다. 왜냐하면 더하기와 빼기가 더 일관되게하기 때문입니다. Wireworld 컴퓨터가 1의 보수를 사용했음을 주목할 가치가 있습니다.

덧셈과 뺄셈은 컴퓨터의 기본 산술 지원 범위 (나중에 설명 할 비트 시프트 외에)입니다. 곱셈과 같은 다른 연산은 아키텍처에서 처리하기에는 너무 복잡하므로 소프트웨어로 구현해야합니다.

비트 단위 연산

우리의 프로세서에는 AND, ORXOR명령어가 있습니다. NOT우리는 지시를받는 대신 "and-not"( ANT) 지시를 선택했습니다. 이 NOT명령 의 어려움 은 다시 신호 부족으로 신호를 생성해야한다는 것인데, 이는 셀룰러 오토마타로는 어렵다. ANT첫번째 인수 비트가 1이고, 두 번째 인수 비트 따라서, 0 인 경우에만 지시 1을 반환 NOT x동등하다 ANT -1 x(뿐만 아니라 XOR -1 x). 또한, ANT다목적이며 마스킹에서 주요 이점이 있습니다. Tetris 프로그램의 경우이를 사용하여 테트로 미노를 지 웁니다.

비트 시프 팅

비트 시프 팅 작업은 ALU에서 처리하는 가장 복잡한 작업입니다. 그들은 두 개의 데이터 입력, 즉 값을 이동시키고 양을 이동시키는 양을 취합니다. (변동의 양이 다양하기 때문에) 복잡성에도 불구하고 이러한 작업은 테트리스와 관련된 많은 "그래픽"작업을 포함하여 많은 중요한 작업에 중요합니다. 비트 시프트는 효율적인 곱셈 / 나눗셈 알고리즘의 기초가됩니다.

프로세서에는 "왼쪽 시프트"( SL), "오른쪽 시프트 논리"( SRL) 및 "오른쪽 시프트 연산"( SRA)의 3 가지 비트 시프트 연산이 있습니다. 처음 두 비트 이동 ( SLSRL)은 새 비트를 모두 0으로 채 웁니다 (오른쪽으로 이동 한 음수는 더 이상 음수가 아님). 시프트의 두 번째 인수가 0에서 15의 ​​범위를 벗어나면 예상 한대로 결과가 모두 0입니다. 마지막 비트 시프트의 SRA경우 비트 시프트는 입력 부호를 유지하므로 2의 실제 나누기 역할을합니다.

지시 파이프 라이닝

이제 아키텍처의 세부 사항에 대해 이야기 할 차례입니다. 각 CPU주기는 다음 5 단계로 구성됩니다.

1. ROM에서 현재 명령을 가져옵니다

PC의 현재 값은 ROM에서 해당 명령을 가져 오는 데 사용됩니다. 각 명령어에는 하나의 opcode와 세 개의 피연산자가 있습니다. 각 피연산자는 하나의 데이터 단어와 하나의 주소 지정 모드로 구성됩니다. 이 부분들은 ROM에서 읽을 때 서로 분리되어 있습니다.

opcode는 16 개의 고유 한 opcode를 지원하기 위해 4 비트이며 11 개가 할당됩니다.

0000  MNZ    Move if Not Zero
0001  MLZ    Move if Less than Zero
0010  ADD    ADDition
0011  SUB    SUBtraction
0100  AND    bitwise AND
0101  OR     bitwise OR
0110  XOR    bitwise eXclusive OR
0111  ANT    bitwise And-NoT
1000  SL     Shift Left
1001  SRL    Shift Right Logical
1010  SRA    Shift Right Arithmetic
1011  unassigned
1100  unassigned
1101  unassigned
1110  unassigned
1111  unassigned

2. 이전 명령어 의 결과 (필요한 경우) 를 RAM에 씁니다.

이전 명령의 조건 (예 : 조건부 이동의 첫 번째 인수 값)에 따라 쓰기가 수행됩니다. 쓰기의 주소는 이전 명령어의 세 번째 피연산자에 의해 결정됩니다.

명령 페칭 후에 쓰기가 발생한다는 점에 유의해야합니다. 이것은 브랜치 타겟에서 제 1 명령 대신에 브랜치 명령 직후의 명령 (PC에 기록하는 임의의 동작)이 실행 되는 브랜치 지연 슬롯 을 생성한다.

무조건 점프와 같은 특정 경우 분기 지연 슬롯을 최적화 할 수 있습니다. 다른 경우에는 불가능하며 분기 이후의 명령은 비워 두어야합니다. 또한이 유형의 지연 슬롯은 발생하는 PC 증분을 설명하기 위해 지점이 실제 대상 명령보다 1 주소 적은 지점 대상을 사용해야 함을 의미합니다.

즉, 다음 명령어를 가져온 후 이전 명령어의 출력이 RAM에 기록되므로 조건부 점프에는 빈 명령어가 있어야합니다. 그렇지 않으면 점프를 위해 PC가 올바르게 업데이트되지 않습니다.

3. RAM에서 현재 명령어의 인수에 대한 데이터를 읽습니다.

앞에서 언급했듯이 세 피연산자 각각은 데이터 단어와 주소 지정 모드로 구성됩니다. 데이터 워드는 16 비트이며 RAM과 같은 폭입니다. 주소 지정 모드는 2 비트입니다.

많은 실제 주소 지정 모드는 오프셋 추가와 같은 다단계 계산을 포함하므로 주소 지정 모드는 이와 같은 프로세서에있어 상당히 복잡한 소스가 될 수 있습니다. 동시에 다목적 어드레싱 모드는 프로세서의 유용성에 중요한 역할을합니다.

하드 코딩 된 숫자를 피연산자로 사용하고 데이터 주소를 피연산자로 사용하는 개념을 통일하려고했습니다. 이로 인해 카운터 기반 주소 지정 모드가 만들어졌습니다. 피연산자의 주소 지정 모드는 RAM 읽기 루프 주위로 데이터를 몇 번이나 보내야 하는지를 나타내는 숫자입니다. 여기에는 즉각적, 직접, 간접 및 이중 간접 주소 지정이 포함됩니다.

00  Immediate:  A hard-coded value. (no RAM reads)
01  Direct:  Read data from this RAM address. (one RAM read)
10  Indirect:  Read data from the address given at this address. (two RAM reads)
11  Double-indirect: Read data from the address given at the address given by this address. (three RAM reads)

이 역 참조가 수행 된 후 명령의 세 피연산자는 다른 역할을합니다. 첫 번째 피연산자는 일반적으로 이항 연산자의 첫 번째 인수이지만 현재 명령이 조건부 이동 인 경우 조건으로도 사용됩니다. 두 번째 피연산자는 이항 연산자의 두 번째 인수로 사용됩니다. 세 번째 피연산자는 명령어 결과의 대상 주소로 사용됩니다.

첫 번째 두 명령어는 데이터로 제공되고 세 번째 명령어는 주소로 사용되므로 주소 지정 모드는 사용되는 위치에 따라 약간 다르게 해석됩니다. 예를 들어 직접 모드는 고정 RAM 주소에서 데이터를 읽는 데 사용됩니다. 하나의 RAM 읽기가 필요하지만 즉시 읽기 모드는 고정 RAM 주소에 데이터를 쓰는 데 사용됩니다 (RAM 읽기가 필요하지 않기 때문에).

4. 결과 계산

이진 연산을 수행하기 위해 opcode와 처음 두 피연산자가 ALU로 전송됩니다. 산술, 비트 및 시프트 연산의 경우 관련 연산을 수행하는 것을 의미합니다. 조건부 이동의 경우 이는 단순히 두 번째 피연산자를 반환한다는 의미입니다.

opcode와 첫 번째 피연산자는 결과를 메모리에 쓸지 여부를 결정하는 조건을 계산하는 데 사용됩니다. 조건부 이동의 경우, 이는 피연산자의 MNZ비트가 1인지 (for )인지 또는 부호 비트가 1인지 (for )인지를 의미합니다 MLZ. opcode가 조건부 이동이 아닌 경우 쓰기가 항상 수행됩니다 (조건은 항상 참).

5. 프로그램 카운터 증가

마지막으로, 프로그램 카운터를 읽고, 증가시키고, 기록합니다.

인스트럭션 읽기와 인스트럭션 쓰기 사이의 PC 증분 위치로 인해 PC를 1 씩 증가시키는 인스트럭션이 작동하지 않음을 의미합니다. PC를 자체적으로 복사하는 명령어는 다음 명령어가 두 번 연속으로 실행되도록합니다. 그러나 명령 파이프 라인에주의를 기울이지 않으면 여러 PC 명령이 연속으로 무한 반복을 포함하여 복잡한 효과가 발생할 수 있습니다.

테트리스 총회 퀘스트

프로세서를 위해 QFTASM이라는 새로운 어셈블리 언어를 만들었습니다. 이 어셈블리 언어는 컴퓨터의 ROM에있는 기계어 코드와 일대일로 대응됩니다.

모든 QFTASM 프로그램은 한 줄에 하나씩 일련의 명령으로 작성됩니다. 각 줄의 형식은 다음과 같습니다.

[line numbering] [opcode] [arg1] [arg2] [arg3]; [optional comment]

오피 코드 목록

앞에서 설명한 바와 같이 컴퓨터가 지원하는 11 개의 opcode가 있으며 각각 3 개의 피연산자가 있습니다.

MNZ [test] [value] [dest]  – Move if Not Zero; sets [dest] to [value] if [test] is not zero.
MLZ [test] [value] [dest]  – Move if Less than Zero; sets [dest] to [value] if [test] is less than zero.
ADD [val1] [val2] [dest]   – ADDition; store [val1] + [val2] in [dest].
SUB [val1] [val2] [dest]   – SUBtraction; store [val1] - [val2] in [dest].
AND [val1] [val2] [dest]   – bitwise AND; store [val1] & [val2] in [dest].
OR [val1] [val2] [dest]    – bitwise OR; store [val1] | [val2] in [dest].
XOR [val1] [val2] [dest]   – bitwise XOR; store [val1] ^ [val2] in [dest].
ANT [val1] [val2] [dest]   – bitwise And-NoT; store [val1] & (![val2]) in [dest].
SL [val1] [val2] [dest]    – Shift Left; store [val1] << [val2] in [dest].
SRL [val1] [val2] [dest]   – Shift Right Logical; store [val1] >>> [val2] in [dest]. Doesn't preserve sign.
SRA [val1] [val2] [dest]   – Shift Right Arithmetic; store [val1] >> [val2] in [dest], while preserving sign.

주소 지정 모드

각 피연산자에는 데이터 값과 주소 지정 이동이 모두 포함됩니다. 데이터 값은 -32768에서 32767 사이의 10 진수로 표시됩니다. 주소 지정 모드는 데이터 값의 한 문자 접두어로 표시됩니다.

mode    name               prefix
0       immediate          (none)
1       direct             A
2       indirect           B
3       double-indirect    C 

예제 코드

다섯 줄의 피보나치 수열 :

0. MLZ -1 1 1;    initial value
1. MLZ -1 A2 3;   start loop, shift data
2. MLZ -1 A1 2;   shift data
3. MLZ -1 0 0;    end loop
4. ADD A2 A3 1;   branch delay slot, compute next term

이 코드는 현재 용어를 포함하는 RAM 주소 1을 사용하여 피보나치 시퀀스를 계산합니다. 28657 후에 빠르게 오버플로됩니다.

회색 코드 :

0. MLZ -1 5 1;      initial value for RAM address to write to
1. SUB A1 5 2;      start loop, determine what binary number to covert to Gray code
2. SRL A2 1 3;      shift right by 1
3. XOR A2 A3 A1;    XOR and store Gray code in destination address
4. SUB B1 42 4;     take the Gray code and subtract 42 (101010)
5. MNZ A4 0 0;      if the result is not zero (Gray code != 101010) repeat loop
6. ADD A1 1 1;      branch delay slot, increment destination address

이 프로그램은 그레이 코드를 계산하여 주소 5부터 시작하여 성공적인 주소에 코드를 저장합니다.이 프로그램은 간접 주소 지정 및 조건부 점프와 같은 몇 가지 중요한 기능을 사용합니다. 결과 그레이 코드가 101010이면 중지 되고 주소 56의 입력 51에 대해 발생합니다.

온라인 통역

El'endia Starman은 여기서 매우 유용한 온라인 통역사를 만들었습니다 . 코드를 단계별로 실행하고 중단 점을 설정하며 RAM에 대한 수동 쓰기를 수행하고 RAM을 디스플레이로 시각화 할 수 있습니다.

코골

일단 아키텍처와 어셈블리 언어가 정의되면 프로젝트의 "소프트웨어"측면에서 다음 단계는 테트리스에 적합한 고급 언어를 만드는 것입니다. 그래서 나는 Cogol을 만들었 습니다 . 이름은 "COBOL"과 "C of Game of Life"의 약어입니다. 비록 Cogol이 우리 컴퓨터가 실제 컴퓨터 인 C라는 것은 주목할 가치가 있습니다.

Cogol은 어셈블리 언어 바로 위에 있습니다. 일반적으로 Cogol 프로그램의 대부분의 라인은 단일 어셈블리 라인에 해당하지만 언어의 몇 가지 중요한 기능이 있습니다.

  • 기본 기능에는 더 읽기 쉬운 구문을 가진 할당 및 연산자가있는 명명 된 변수가 포함됩니다. 예를 들어, ADD A1 A2 3해진다 z = x + y;주소 상 컴파일러 맵핑 변수.
  • 같은 반복 구조 if(){}, while(){}do{}while();따라서 컴파일러는 분기 처리한다.
  • Tetris 보드에 사용되는 1 차원 배열 (포인터 산술 포함).
  • 서브 루틴 및 호출 스택 큰 코드 덩어리의 복제를 방지하고 재귀를 지원하는 데 유용합니다.

컴파일러 (처음부터 작성)는 매우 기본적 / 순진하지만 짧은 컴파일 된 프로그램 길이를 달성하기 위해 여러 언어 구문을 수동으로 최적화하려고했습니다.

다음은 다양한 언어 기능의 작동 방식에 대한 간략한 개요입니다.

토큰 화

소스 코드는 토큰 내에서 어떤 문자가 인접 할 수 있는지에 대한 간단한 규칙을 사용하여 선형으로 토큰 화됩니다 (단일 패스). 현재 토큰의 마지막 문자와 인접 할 수없는 문자가 발견되면 현재 토큰이 완료된 것으로 간주하고 새 문자가 새 토큰을 시작합니다. 일부 문자 (예 : {또는 ,) 다른 문자에 인접하기 때문에 자신의 토큰입니다 수 없습니다. (같은 기타 >또는 =) 그들의 클래스 내에서 다른 문자에 인접 할 수 있으며, 따라서 같은 토큰을 형성 할 수있다 >>>, ==또는 >=,하지만 좋아하지을 =2. 공백 문자는 토큰 간의 경계를 강제하지만 결과에 포함되지는 않습니다. 토큰 화하기 가장 어려운 캐릭터는- 뺄셈과 단항 부정을 모두 나타낼 수 있기 때문에 특별한 경우가 필요하기 때문입니다.

파싱

구문 분석은 단일 패스 방식으로 수행됩니다. 컴파일러에는 각기 다른 언어 구성을 처리하기위한 메소드가 있으며, 다양한 컴파일러 메소드에서 사용되는 토큰이 글로벌 토큰 목록에서 튀어 나옵니다. 컴파일러가 예상하지 않은 토큰을 발견하면 구문 오류가 발생합니다.

글로벌 메모리 할당

컴파일러는 각 전역 변수 (워드 또는 배열)에 고유 한 지정된 RAM 주소를 할당합니다. my컴파일러가 공간을 할당 할 수 있도록 키워드를 사용하여 모든 변수를 선언해야 합니다. 스크래치 주소 메모리 관리는 명명 된 전역 변수보다 훨씬 시원합니다. 많은 명령어 (특히 조건부 및 많은 어레이 액세스)에는 중간 계산을 저장하기 위해 임시 "스크래치"주소가 필요합니다. 컴파일 과정에서 컴파일러는 필요에 따라 스크래치 주소를 할당하고 할당 해제합니다. 컴파일러에 더 많은 스크래치 주소가 필요한 경우 더 많은 RAM을 스크래치 주소로 사용합니다. 각 스크래치 주소가 여러 번 사용되지만 프로그램이 스크래치 주소를 몇 개만 요구하는 것이 일반적이라고 생각합니다.

IF-ELSE 진술

의 구문 if-else문은 표준 C의 형태이다 :

other code
if (cond) {
  first body
} else {
  second body
}
other code

QFTASM으로 변환 될 때 코드는 다음과 같이 배열됩니다.

other code
condition test
conditional jump
first body
unconditional jump
second body (conditional jump target)
other code (unconditional jump target)

첫 번째 본문이 실행되면 두 번째 본문을 건너 뜁니다. 첫 번째 본문을 건너 뛰면 두 번째 본문이 실행됩니다.

어셈블리에서 조건 테스트는 일반적으로 뺄셈 일 뿐이며 결과의 부호에 따라 점프를 수행할지 신체를 실행할지가 결정됩니다. MLZ명령과 같은 부등식을 처리하는 데 사용 >하거나 <=. MNZ명령 처리에 사용되는 ==(인수가 동일하지 않은 경우에 따라서)의 차이가 0이 아닌 경우는 몸 점프 이후. 다중 표현 조건은 현재 지원되지 않습니다.

는 IF else문을 생략, 무조건 점프는 생략하고, QFTASM 코드는 다음과 같습니다 :

other code
condition test
conditional jump
body
other code (conditional jump target)

WHILE 진술

while명령문 의 구문 도 표준 C 형식입니다.

other code
while (cond) {
  body
}
other code

QFTASM으로 변환 될 때 코드는 다음과 같이 배열됩니다.

other code
unconditional jump
body (conditional jump target)
condition test (unconditional jump target)
conditional jump
other code

조건 테스트와 조건부 점프는 블록의 끝에 있습니다. 즉, 블록을 실행할 때마다 다시 실행됩니다. 조건이 false를 반환하면 본문이 반복되지 않고 루프가 종료됩니다. 루프 실행이 시작되는 동안 제어 흐름은 루프 본문을 조건 코드로 건너 뛰므로 조건이 처음 false 인 경우 본문이 실행되지 않습니다.

MLZ명령과 같은 부등식을 처리하는 데 사용 >하거나 <=. while if문과 달리 , 차이가 0이 아닌 경우 (그리고 인수가 같지 않은 경우) 본문으로 점프하기 때문에 MNZ명령을 처리하는 데 사용됩니다 !=.

DO-WHILE 진술

유일한 차이 whiledo-while는 A이다 do-while가 항상 적어도 한 번 실행되도록 루프 본체 초기 스킵되지 않는다. do-while루프를 완전히 건너 뛸 필요가 없다는 것을 알 때 일반적으로 명령문을 사용하여 몇 줄의 어셈블리 코드를 저장합니다.

배열

1 차원 배열은 연속적인 메모리 블록으로 구현됩니다. 모든 배열은 선언에 따라 고정 길이입니다. 배열은 다음과 같이 선언됩니다 :

my alpha[3];               # empty array
my beta[11] = {3,2,7,8};   # first four elements are pre-loaded with those values

어레이의 경우 이는 가능한 RAM 매핑으로, 주소 15-18이 어레이에 예약되는 방법을 보여줍니다.

15: alpha
16: alpha[0]
17: alpha[1]
18: alpha[2]

레이블 alpha이 지정된 주소 는의 위치에 대한 포인터로 채워 alpha[0]지므로이 경우 주소 15에는 값 16이 포함됩니다. alpha변수를이 배열을 스택으로 사용하려면 Cogol 코드 내에서 스택 포인터로 사용할 수 있습니다. .

배열의 요소에 액세스하는 것은 표준 array[index]표기법으로 수행 됩니다. 의 값이 index상수 인 경우이 참조는 해당 요소의 절대 주소로 자동 채워집니다. 그렇지 않으면 원하는 절대 주소를 찾기 위해 포인터 산술을 수행합니다. 와 같은 인덱싱을 중첩 할 수도 있습니다 alpha[beta[1]].

서브 루틴 및 호출

서브 루틴은 여러 컨텍스트에서 호출 할 수있는 코드 블록으로, 코드 중복을 방지하고 재귀 프로그램을 작성할 수 있습니다. 다음은 피보나치 수 (기본적으로 가장 느린 알고리즘)를 생성하는 재귀 서브 루틴이있는 프로그램입니다.

# recursively calculate the 10th Fibonacci number
call display = fib(10).sum;
sub fib(cur,sum) {
  if (cur <= 2) {
    sum = 1;
    return;
  }
  cur--;
  call sum = fib(cur).sum;
  cur--;
  call sum += fib(cur).sum;
}

서브 루틴은 키워드로 선언되며 서브 루틴은 sub프로그램 내부 어디에나 배치 될 수 있습니다. 각 서브 루틴에는 여러 로컬 변수가있을 수 있으며 인수 목록의 일부로 선언됩니다. 이러한 인수에는 기본값이 제공 될 수도 있습니다.

재귀 호출을 처리하기 위해 서브 루틴의 로컬 변수가 스택에 저장됩니다. RAM의 마지막 정적 변수는 호출 스택 포인터이며 그 이후의 모든 메모리는 호출 스택으로 사용됩니다. 서브 루틴이 호출되면 호출 스택에 새 프레임을 작성했으며 여기에는 모든 로컬 변수와 리턴 (ROM) 주소가 포함됩니다. 프로그램의 각 서브 루틴에는 포인터로 사용할 단일 정적 RAM 주소가 제공됩니다. 이 포인터는 호출 스택에서 서브 루틴의 "현재"호출 위치를 제공합니다. 지역 변수 참조는이 정적 포인터의 값과 해당 지역 변수의 주소를 제공하는 오프셋을 사용하여 수행됩니다. 또한 호출 스택에는 정적 포인터의 이전 값이 포함됩니다. 이리'

RAM map:
0: pc
1: display
2: scratch0
3: fib
4: scratch1
5: scratch2
6: scratch3
7: call

fib map:
0: return
1: previous_call
2: cur
3: sum

서브 루틴에서 흥미로운 점은 특정 값을 반환하지 않는다는 것입니다. 오히려 서브 루틴이 수행 된 후 서브 루틴의 모든 로컬 변수를 읽을 수 있으므로 서브 루틴 호출에서 다양한 데이터를 추출 할 수 있습니다. 이는 서브 루틴의 해당 특정 호출에 대한 포인터를 저장함으로써 달성되며, 그런 다음 (최근 할당 해제 된) 스택 프레임 내에서 로컬 변수를 복구하는 데 사용할 수 있습니다.

call키워드를 사용하여 서브 루틴을 호출하는 방법에는 여러 가지가 있습니다 .

call fib(10);   # subroutine is executed, no return vaue is stored

call pointer = fib(10);   # execute subroutine and return a pointer
display = pointer.sum;    # access a local variable and assign it to a global variable

call display = fib(10).sum;   # immediately store a return value

call display += fib(10).sum;   # other types of assignment operators can also be used with a return value

서브 루틴 호출에 대한 인수로 여러 값을 제공 할 수 있습니다. 제공되지 않은 인수는 기본값으로 채워집니다 (있는 경우). 제공되지 않고 기본값이없는 인수는 지워지지 않으므로 (명령 / 시간을 절약하기 위해) 서브 루틴 시작시 임의의 값을 취할 수 있습니다.

포인터는 서브 루틴의 여러 로컬 변수에 액세스하는 방법이지만 포인터는 일시적인 것입니다. 즉, 다른 서브 루틴 호출이 수행 될 때 포인터가 가리키는 데이터는 소멸됩니다.

라벨 디버깅

{...}Cogol 프로그램의 모든 코드 블록 앞에는 여러 단어로 된 설명 레이블이있을 수 있습니다. 이 레이블은 컴파일 된 어셈블리 코드에서 주석으로 첨부되며 특정 코드를 쉽게 찾을 수 있으므로 디버깅에 매우 유용합니다.

분기 지연 슬롯 최적화

컴파일 된 코드의 속도를 향상시키기 위해 Cogol 컴파일러는 실제로 기본적인 지연 슬롯 최적화를 QFTASM 코드에 대한 최종 패스로 수행합니다. 비어있는 분기 지연 슬롯이있는 무조건 점프의 경우 지연 슬롯은 점프 대상의 첫 번째 명령으로 채워질 수 있으며 점프 대상은 다음 명령을 가리 키도록 1 씩 증가합니다. 이것은 일반적으로 무조건 점프가 수행 될 때마다 한 사이클을 절약합니다.

Cogol에서 테트리스 코드 작성

최종 테트리스 프로그램은 Cogol로 작성되었으며 소스 코드는 여기에 있습니다 . 컴파일 된 QFTASM 코드는 여기에 있습니다 . 편의를 위해 여기 에 Permalink 가 제공됩니다. Tetris in QFTASM . Cogol 코드가 아닌 어셈블리 코드를 골프화하는 것이 목표 였으므로 결과 Cogol 코드는 다루기 힘들다. 프로그램의 많은 부분은 일반적으로 서브 루틴에 위치하지만 해당 서브 루틴은 실제로 코드가 저장된 명령을 복제 할 정도로 짧습니다.call진술. 최종 코드에는 기본 코드 외에 하나의 서브 루틴 만 있습니다. 또한 많은 배열이 제거되어 동등하게 긴 개별 변수 목록 또는 프로그램에서 많은 하드 코딩 된 숫자로 대체되었습니다. 최종 컴파일 된 QFTASM 코드는 300 개 미만의 명령어이지만 Cogol 소스 자체보다 약간 길다.


22
어셈블리 언어 명령어의 선택은 인쇄물 하드웨어에 의해 정의되는 것을 좋아합니다 (2 개의 거짓에서 참을 조립하는 것이 어렵 기 때문에 MEZ는 없습니다). 환상적인 읽기.
AlexC

1
당신은 그 =옆에 서있을 수 있다고 말 했지만 !=.
Fabian Röling

@Fabian and a+=
Oliphaunt

@Oliphaunt 그래 내 설명은 정확하지 않았다. 특정 클래스의 문자가 서로 인접 할 수있는 문자 클래스에 가깝다.
PhiNotPi

606

5 부 : 조립, 번역 및 미래

컴파일러의 어셈블리 프로그램을 사용하여 Varlife 컴퓨터의 ROM을 조립하고 모든 것을 큰 GoL 패턴으로 변환해야합니다!

어셈블리

어셈블리 프로그램을 ROM으로 어셈블하는 것은 기존 프로그래밍과 거의 같은 방식으로 수행됩니다. 각 명령은 이진으로 변환되고 명령은 큰 이진 블롭으로 연결되어 실행 파일이라고 부릅니다. 우리에게 유일한 차이점은 바이너리 BLOB을 Varlife 회로로 변환하고 컴퓨터에 연결해야한다는 것입니다.

K Zhang은 어셈블리 및 번역을 수행하는 Golly 용 Python 스크립트 인 CreateROM.py를 작성 했습니다 . 그것은 매우 간단합니다 : 클립 보드에서 어셈블리 프로그램을 가져 와서 바이너리로 조립하고 바이너리를 회로로 변환합니다. 다음은 스크립트에 포함 된 간단한 우선 순위 테스터가있는 예입니다.

#0. MLZ -1 3 3;
#1. MLZ -1 7 6; preloadCallStack
#2. MLZ -1 2 1; beginDoWhile0_infinite_loop
#3. MLZ -1 1 4; beginDoWhile1_trials
#4. ADD A4 2 4;
#5. MLZ -1 A3 5; beginDoWhile2_repeated_subtraction
#6. SUB A5 A4 5;
#7. SUB 0 A5 2;
#8. MLZ A2 5 0;
#9. MLZ 0 0 0; endDoWhile2_repeated_subtraction
#10. MLZ A5 3 0;
#11. MNZ 0 0 0; endDoWhile1_trials
#12. SUB A4 A3 2;
#13. MNZ A2 15 0; beginIf3_prime_found
#14. MNZ 0 0 0;
#15. MLZ -1 A3 1; endIf3_prime_found
#16. ADD A3 2 3;
#17. MLZ -1 3 0;
#18. MLZ -1 1 4; endDoWhile0_infinite_loop

다음과 같은 이진이 생성됩니다.

0000000000000001000000000000000000010011111111111111110001
0000000000000000000000000000000000110011111111111111110001
0000000000000000110000000000000000100100000000000000110010
0000000000000000010100000000000000110011111111111111110001
0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000011110100000000000000100000
0000000000000000100100000000000000110100000000000001000011
0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000110100000000000001010001
0000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000001010100000000000000100001
0000000000000000100100000000000001010000000000000000000011
0000000000000001010100000000000001000100000000000001010011
0000000000000001010100000000000000110011111111111111110001
0000000000000001000000000000000000100100000000000001000010
0000000000000001000000000000000000010011111111111111110001
0000000000000000010000000000000000100011111111111111110001
0000000000000001100000000000000001110011111111111111110001
0000000000000000110000000000000000110011111111111111110001

Varlife 회로로 변환하면 다음과 같습니다.

ROM

근접 촬영 ROM

ROM은 컴퓨터와 연결되어 Varlife에서 완벽하게 작동하는 프로그램을 형성합니다. 하지만 아직 끝나지 않았습니다 ...

인생의 게임으로의 번역

지금까지 우리는 Game of Life의 기초 위에서 다양한 추상화 계층으로 작업 해 왔습니다. 그러나 이제 추상화의 막을 철회하고 우리의 작업을 Game of Life 패턴으로 변환 할 때입니다. 앞에서 언급했듯이 OTCA 메타 픽셀을 Varlife 의 기반으로 사용하고 있습니다. 마지막 단계는 Varlife의 각 셀을 Game of Life의 메타 픽셀로 변환하는 것입니다.

다행히도 Golly에는 OTCA 메타 픽셀을 통해 다른 규칙 세트의 패턴을 Game of Life 패턴으로 변환 할 수 있는 스크립트 ( metafier.py )가 제공됩니다. 불행히도 단일 글로벌 규칙 세트로 패턴을 변환하도록 설계 되었기 때문에 Varlife에서는 작동하지 않습니다. 각 메타 픽셀에 대한 규칙이 Varlife에 대해 셀별로 생성되도록 해당 문제를 해결 하는 수정 된 버전 을 작성했습니다 .

따라서 Tetris ROM이 장착 된 컴퓨터에는 1,436 x 5,082의 경계 상자가 있습니다. 이 상자에있는 7,297,752 개의 셀 중 6,075,811은 빈 공간으로 실제 인구 수는 1,221,941입니다. 이러한 각 셀은 2048x2048의 경계 상자와 64,691 (ON 메타 픽셀) 또는 23,920 (OFF 메타 픽셀)의 모집단을 갖는 OTCA 메타 픽셀로 변환되어야합니다. 즉, 최종 제품에는 29,228,828,720에서 79,048,585,231 사이의 인구를 가진 2,940,928 x 10,407,936의 경계 상자 (메타 픽셀의 경계에 대해 수천 개의 추가)가 있습니다. 라이브 셀당 1 비트를 사용하면 전체 컴퓨터와 ROM을 나타내는 데 27 ~ 74GiB가 필요합니다.

스크립트를 시작하기 전에 실행을 무시하고 컴퓨터의 메모리가 매우 빨리 소모되어 계산에 포함 시켰습니다. 당황한 kill명령 후 , 나는 메타 파이어 스크립트를 수정했다. 메타 라인의 10 행마다 패턴이 디스크에 (지퍼 링 된 RLE 파일로) 저장되고 그리드가 플러시됩니다. 이렇게하면 변환에 추가 런타임이 추가되고 더 많은 디스크 공간이 사용되지만 메모리 사용량은 허용 가능한 한도 내로 유지됩니다. Golly는 패턴의 위치를 ​​포함하는 확장 된 RLE 형식을 사용하기 때문에 패턴로드에 더 많은 복잡성을 추가하지 않고 동일한 레이어에서 모든 패턴 파일을 열면됩니다.

K Zhang은이 작업을 시작으로 MacroCell 파일 형식을 활용 하는 보다 효율적인 메타 파이어 스크립트 를 만들었습니다 .이 패턴은 큰 패턴에 대해 RLE보다 더 효율적으로로드됩니다. 이 스크립트는 원래 메타 파이어 스크립트의 경우 여러 시간에 비해 몇 초만에 훨씬 더 빠르게 실행되며 훨씬 더 작은 출력 (121KB 대 1.7GB)을 생성하며 대량으로 사용하지 않고 전체 컴퓨터와 ROM을 한 번에 쓸어 넘길 수 있습니다. 기억의. MacroCell 파일은 패턴을 설명하는 트리를 인코딩한다는 사실을 이용합니다. 사용자 정의 템플릿 파일을 사용하면 메타 픽셀이 트리에 사전로드되고 인접 감지를위한 일부 계산 및 수정 후 Varlife 패턴을 간단히 추가 할 수 있습니다.

Game of Life의 전체 컴퓨터 및 ROM의 패턴 파일은 여기 에서 찾을 수 있습니다 .


프로젝트의 미래

이제 테트리스를 만들었으니 다 했어요? 근처에도 안. 우리는이 프로젝트를 위해 몇 가지 목표를 가지고 있습니다 :

  • muddyfish와 Kritixi Lithos는 QFTASM으로 컴파일되는 고급 언어를 계속 연구하고 있습니다.
  • El'endia Starman은 온라인 QFTASM 인터프리터 업그레이드를 위해 노력하고 있습니다.
  • quartata는 GCC 백엔드를 개발 중이며,이를 통해 독립형 C 및 C ++ 코드 (및 잠재적으로 다른 언어 (예 : Fortran, D 또는 Objective-C))를 GCC를 통해 QFTASM으로 컴파일 할 수 있습니다. 이를 통해 표준 라이브러리가 없어도보다 복잡한 프로그램을보다 친숙한 언어로 작성할 수 있습니다.
  • 더 많은 진전을 이루기 전에 극복해야 할 가장 큰 장애물 중 하나는 툴이 위치 독립적 인 코드를 생성 할 수 없다는 것입니다 (예 : 상대 점프). PIC가 없으면 링크를 수행 할 수 없으므로 기존 라이브러리에 링크 할 수있는 이점을 놓치게됩니다. PIC를 올바르게 수행하는 방법을 찾으려고 노력하고 있습니다.
  • 우리는 QFT 컴퓨터를 위해 작성하고자하는 다음 프로그램에 대해 논의하고 있습니다. 지금, 탁구는 좋은 목표처럼 보입니다.

2
미래의 하위 섹션을 보면 상대 점프가 ADD PC offset PC아닌가? 이것이 정확하지 않으면 내 순진을 실례합니다. 어셈블리 프로그래밍은 결코 나의 장점이 아닙니다.
MBraedley

3
@Timmmm 예, 매우 느립니다. (HashLife도 사용해야합니다).
스파게티

75
다음에 작성하는 프로그램은 Conway의 게임 게임이어야합니다.
ACK_stoverflow

13
@ACK_stoverflow 언젠가는 끝날 것입니다.
Mego

13
비디오가 실행되고 있습니까?
PyRulez

583

6 부 : QFTASM의 최신 컴파일러

Cogol이 기초적인 테트리스 구현에는 충분하지만 일반 프로그래밍이 읽기 쉬운 수준으로 구현하기에는 너무 단순하고 수준이 낮습니다. 우리는 2016 년 9 월에 새로운 언어로 작업하기 시작했습니다. 실제 생활뿐만 아니라 벌레를 이해하기 어렵 기 때문에 언어 진행이 느 렸습니다.

간단한 유형 시스템, 재귀 및 인라인 연산자를 지원하는 서브 루틴을 포함하여 Python과 유사한 구문으로 저수준 언어를 구축했습니다. 텍스트에서 QFTASM까지의 컴파일러는 토큰 화기, 문법 트리, 상위 수준 컴파일러 및 하위 수준 컴파일러의 4 단계로 만들어졌습니다.

토큰 화기

내장형 tokeniser 라이브러리를 사용하여 Python을 사용하여 개발을 시작했습니다.이 단계는 매우 간단했습니다. 주석 제거 ( #includes는 아님)를 포함하여 기본 출력에 대한 약간의 변경 만 필요했습니다 .

문법 트리

문법 트리는 소스 코드를 수정하지 않고도 쉽게 확장 할 수 있도록 만들어졌습니다.

트리 구조는 트리를 구성 할 수있는 노드의 구조와 다른 노드 및 토큰으로 구성되는 방법을 포함하는 XML 파일에 저장됩니다.

문법은 선택적 노드뿐만 아니라 반복 된 노드를 지원해야했습니다. 이것은 토큰을 읽는 방법을 설명하기 위해 메타 태그를 도입함으로써 달성되었습니다.

그런 다음 생성 된 토큰은 문법 규칙을 통해 구문 분석되어 출력이 subs 및와 같은 문법 요소의 트리를 형성하고 generic_variables다른 문법 요소와 토큰이 포함됩니다.

고급 코드로 컴파일

언어의 각 기능은 고급 구조로 컴파일 할 수 있어야합니다. 다음을 포함 assign(a, 12) 하고 call_subroutine(is_prime, call_variable=12, return_variable=temp_var). 요소의 인라이닝과 같은 기능이이 세그먼트에서 실행됩니다. 이것들은로 정의되고 operator연산자가 +또는 연산자와 같이 %사용될 때마다 인라인된다는 점에서 특별 합니다. 이로 인해 일반 코드보다 더 제한적입니다. 자신의 연산자 나 정의 된 연산자를 사용하는 연산자를 사용할 수 없습니다.

인라이닝 프로세스 동안 내부 변수는 호출되는 변수로 대체됩니다. 이것은 사실상 회전합니다

operator(int a + int b) -> int c
    return __ADD__(a, b)
int i = 3+3

으로

int i = __ADD__(3, 3)

그러나 입력 변수와 출력 변수가 메모리의 동일한 위치를 가리키는 경우이 동작은 해가되고 버그가 발생하기 쉽습니다. 'safer'동작을 사용하기 위해 unsafe키워드는 컴파일 프로세스를 조정하여 필요에 따라 추가 변수가 작성되어 인라인에서 복사되도록합니다.

스크래치 변수 및 복잡한 연산

a += (b + c) * 4추가 메모리 셀을 사용하지 않으면 수학 연산을 계산할 수 없습니다. 고급 컴파일러는 연산을 다른 섹션으로 분리하여이를 처리합니다.

scratch_1 = b + c
scratch_1 = scratch_1 * 4
a = a + scratch_1

이것은 계산의 중간 정보를 저장하는 데 사용되는 스크래치 변수의 개념을 소개합니다. 필요에 따라 할당되고 일단 완료되면 일반 풀에 할당 해제됩니다. 사용에 필요한 스크래치 메모리 위치 수가 줄어 듭니다. 스크래치 변수는 전역으로 간주됩니다.

각 서브 루틴에는 서브 루틴이 사용하는 모든 변수와 유형에 대한 참조를 유지하기 위해 자체 VariableStore가 있습니다. 컴파일이 끝나면 상점 시작부터 상대 오프셋으로 변환 된 다음 RAM에 실제 주소가 지정됩니다.

RAM 구조

Program counter
Subroutine locals
Operator locals (reused throughout)
Scratch variables
Result variable
Stack pointer
Stack
...

저수준 컴파일

낮은 수준의 컴파일러가 가지고있는 유일한 물건은 취급하는 sub, call_sub, return, assign, ifwhile. QFTASM 명령어로보다 쉽게 ​​번역 할 수있는 작업 목록이 훨씬 줄었습니다.

sub

이름 지정된 서브 루틴의 시작과 끝을 찾습니다. 저수준 컴파일러는 레이블을 추가하고 main서브 루틴 의 경우 종료 명령 (ROM 끝으로 점프)을 추가합니다.

ifwhile

모두 whileif낮은 수준의 통역은 매우 간단하다 : 그들은 그들의 조건과 그들에 따라 점프에 대한 포인터를 얻는다. while루프는 다음과 같이 컴파일된다는 점에서 약간 다릅니다.

...
condition
jump to check
code
condition
if condtion: jump to code
...

call_subreturn

대부분의 아키텍처와 달리, 우리가 컴파일하는 컴퓨터는 스택에서 밀고 터지는 하드웨어 지원이 없습니다. 이것은 스택에서 밀고 터지는 데 두 가지 지침이 필요하다는 것을 의미합니다. 터지는 경우 스택 포인터를 줄이고 값을 주소에 복사합니다. 푸시하는 경우 현재 스택 포인터의 주소에서 주소로 값을 복사 한 다음 증가시킵니다.

서브 루틴의 모든 로컬은 컴파일 타임에 결정된 RAM의 고정 위치에 저장됩니다. 재귀 작업을 수행하기 위해 호출 시작시 함수의 모든 로컬이 스택에 배치됩니다. 그런 다음 서브 루틴에 대한 인수가 로컬 저장소의 해당 위치로 복사됩니다. 리턴 주소의 값이 스택에 저장되고 서브 루틴이 실행됩니다.

return문이 발생, 스택의 상단 오프 팝되고 프로그램 카운터는 그 값으로 설정됩니다. 호출 서브 루틴의 로컬 값은 스택에서 튀어 나와 이전 위치로 튀어 나옵니다.

assign

변수 할당은 컴파일하기 가장 쉬운 것입니다. 변수와 값을 가져 와서 한 줄로 컴파일합니다. MLZ -1 VALUE VARIABLE

점프 대상 할당

마지막으로 컴파일러는 명령어에 첨부 된 레이블의 점프 대상을 해결합니다. 레이블의 절대 위치가 결정되고 해당 레이블에 대한 참조가 해당 값으로 바뀝니다. 레이블 자체는 코드에서 제거되고 마지막으로 명령어 번호가 컴파일 된 코드에 추가됩니다.

단계별 컴파일 예

이제 모든 단계를 밟았으므로 실제 프로그램의 실제 컴파일 프로세스를 단계별로 살펴 보겠습니다.

#include stdint

sub main
    int a = 8
    int b = 12
    int c = a * b

좋아, 충분히 간단하다. 그것은 그 프로그램의 끝에서 명백해야한다 a = 8, b = 12, c = 96. 먼저 다음의 관련 부분을 포함시킵니다 stdint.txt.

operator (int a + int b) -> int
    return __ADD__(a, b)

operator (int a - int b) -> int
    return __SUB__(a, b)

operator (int a < int b) -> bool
    bool rtn = 0
    rtn = __MLZ__(a-b, 1)
    return rtn

unsafe operator (int a * int b) -> int
    int rtn = 0
    for (int i = 0; i < b; i+=1)
        rtn += a
    return rtn

sub main
    int a = 8
    int b = 12
    int c = a * b

좋아, 조금 더 복잡해. 토크 나이저로 가서 무엇이 나오는지 봅시다. 이 단계에서, 우리는 어떤 형태의 구조 없이도 토큰의 선형 흐름 만 가질 것입니다

NAME NAME operator
LPAR OP (
NAME NAME int
NAME NAME a
PLUS OP +
NAME NAME int
NAME NAME b
RPAR OP )
OP OP ->
NAME NAME int
NEWLINE NEWLINE
INDENT INDENT     
NAME NAME return
NAME NAME __ADD__
LPAR OP (
NAME NAME a
COMMA OP ,
NAME NAME b
RPAR OP )
...

이제 모든 토큰이 문법 파서를 통과하여 각 섹션의 이름을 가진 트리를 출력합니다. 이것은 코드에서 읽은 높은 수준의 구조를 보여줍니다.

GrammarTree file
 'stmts': [GrammarTree stmts_0
  '_block_name': 'inline'
  'inline': GrammarTree inline
   '_block_name': 'two_op'
   'type_var': GrammarTree type_var
    '_block_name': 'type'
    'type': 'int'
    'name': 'a'
    '_global': False

   'operator': GrammarTree operator
    '_block_name': '+'

   'type_var_2': GrammarTree type_var
    '_block_name': 'type'
    'type': 'int'
    'name': 'b'
    '_global': False
   'rtn_type': 'int'
   'stmts': GrammarTree stmts
    ...

이 문법 트리는 고급 컴파일러가 구문 분석 할 정보를 설정합니다. 변수의 구조 유형 및 속성과 같은 정보가 포함됩니다. 문법 트리는이 정보를 가져 와서 서브 루틴에 필요한 변수를 할당합니다. 트리는 또한 모든 인라인을 삽입합니다.

('sub', 'start', 'main')
('assign', int main_a, 8)
('assign', int main_b, 12)
('assign', int op(*:rtn), 0)
('assign', int op(*:i), 0)
('assign', global bool scratch_2, 0)
('call_sub', '__SUB__', [int op(*:i), int main_b], global int scratch_3)
('call_sub', '__MLZ__', [global int scratch_3, 1], global bool scratch_2)
('while', 'start', 1, 'for')
('call_sub', '__ADD__', [int op(*:rtn), int main_a], int op(*:rtn))
('call_sub', '__ADD__', [int op(*:i), 1], int op(*:i))
('assign', global bool scratch_2, 0)
('call_sub', '__SUB__', [int op(*:i), int main_b], global int scratch_3)
('call_sub', '__MLZ__', [global int scratch_3, 1], global bool scratch_2)
('while', 'end', 1, global bool scratch_2)
('assign', int main_c, int op(*:rtn))
('sub', 'end', 'main')

다음으로, 저수준 컴파일러는이 고수준 표현을 QFTASM 코드로 변환해야합니다. 변수는 다음과 같이 RAM의 위치에 할당됩니다.

int program_counter
int op(*:i)
int main_a
int op(*:rtn)
int main_c
int main_b
global int scratch_1
global bool scratch_2
global int scratch_3
global int scratch_4
global int <result>
global int <stack>

그런 다음 간단한 지침이 컴파일됩니다. 마지막으로 명령어 번호가 추가되어 실행 가능한 QFTASM 코드가 생성됩니다.

0. MLZ 0 0 0;
1. MLZ -1 12 11;
2. MLZ -1 8 2;
3. MLZ -1 12 5;
4. MLZ -1 0 3;
5. MLZ -1 0 1;
6. MLZ -1 0 7;
7. SUB A1 A5 8;
8. MLZ A8 1 7;
9. MLZ -1 15 0;
10. MLZ 0 0 0;
11. ADD A3 A2 3;
12. ADD A1 1 1;
13. MLZ -1 0 7;
14. SUB A1 A5 8;
15. MLZ A8 1 7;
16. MNZ A7 10 0;
17. MLZ 0 0 0;
18. MLZ -1 A3 4;
19. MLZ -1 -2 0;
20. MLZ 0 0 0;

구문

이제 우리는 기본 언어를 가지고 있으므로 실제로 작은 프로그램을 작성해야합니다. 우리는 파이썬처럼 들여 쓰기를 사용하고 논리적 블록과 제어 흐름을 나누고 있습니다. 이것은 프로그램에 공백이 중요하다는 것을 의미합니다. 모든 전체 프로그램에는 C와 같은 언어 mainmain()기능 과 같은 기능을 하는 서브 루틴이 있습니다. 이 기능은 프로그램 시작시 실행됩니다.

변수와 타입

변수가 처음 정의 될 때 변수와 연관된 유형이 있어야합니다. 현재 정의 된 유형은 다음 intbool정의 된 배열에 대한 구문이 아니라 컴파일러.

라이브러리 및 연산자

stdint.txt기본 연산자를 정의 하는 라이브러리 를 사용할 수 있습니다. 이것이 포함되지 않으면 간단한 연산자조차 정의되지 않습니다. 이 라이브러리를와 함께 사용할 수 있습니다 #include stdint. stdint같은 운영자 정의 +, >>심지어 *%직접 QFTASM의 옵 코드입니다 어느 것도을.

이 언어를 사용하면 QFTASM opcode를로 직접 호출 할 수도 있습니다 __OPCODENAME__.

추가 stdint는 다음과 같이 정의됩니다.

operator (int a + int b) -> int
    return __ADD__(a, b)

+두 개가 주어 졌을 때 연산자가 수행 하는 작업을 정의합니다 int.


1
그것은 삶의 콘웨이의 게임에서 wireworld 같은 CA를 생성하고이 회로 오히려 재사용보다는를 사용하여 새 프로세서를 만들기로 결정 이유를 물어 수 / 보편적 인 컴퓨터 cgol 기존과 같은 개조 이 하나 ?
eaglgenes101 오전

4
@ eaglgenes101 우선, 우리 대부분은 다른 유용한 범용 컴퓨터의 존재를 알고 있다고 생각하지 않습니다. 다중 혼합 규칙을 가진 와이어 월드와 같은 CA에 대한 아이디어는 메타 셀을 가지고 놀았 던 결과로 나왔습니다 (Phi는이 아이디어를 생각 해낸 사람이었습니다). 거기에서 우리가 만든 것을 논리적으로 발전 시켰습니다.
Mego
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.