파트 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
, OR
및 XOR
명령어가 있습니다. NOT
우리는 지시를받는 대신 "and-not"( ANT
) 지시를 선택했습니다. 이 NOT
명령 의 어려움 은 다시 신호 부족으로 신호를 생성해야한다는 것인데, 이는 셀룰러 오토마타로는 어렵다. ANT
첫번째 인수 비트가 1이고, 두 번째 인수 비트 따라서, 0 인 경우에만 지시 1을 반환 NOT x
동등하다 ANT -1 x
(뿐만 아니라 XOR -1 x
). 또한, ANT
다목적이며 마스킹에서 주요 이점이 있습니다. Tetris 프로그램의 경우이를 사용하여 테트로 미노를 지 웁니다.
비트 시프 팅
비트 시프 팅 작업은 ALU에서 처리하는 가장 복잡한 작업입니다. 그들은 두 개의 데이터 입력, 즉 값을 이동시키고 양을 이동시키는 양을 취합니다. (변동의 양이 다양하기 때문에) 복잡성에도 불구하고 이러한 작업은 테트리스와 관련된 많은 "그래픽"작업을 포함하여 많은 중요한 작업에 중요합니다. 비트 시프트는 효율적인 곱셈 / 나눗셈 알고리즘의 기초가됩니다.
프로세서에는 "왼쪽 시프트"( SL
), "오른쪽 시프트 논리"( SRL
) 및 "오른쪽 시프트 연산"( SRA
)의 3 가지 비트 시프트 연산이 있습니다. 처음 두 비트 이동 ( SL
및 SRL
)은 새 비트를 모두 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
진술
유일한 차이 while
와 do-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 소스 자체보다 약간 길다.