결정과 반복

결정과 반복

기본적으로 CPU는 명령을 위에서 아래로 나타나는 순서대로 실행합니다. 그 순서만으로는 선택을 하거나 작업을 반복할 수 없습니다. 분기는 프로그램이 그 직선 경로를 벗어나는 방법입니다. 조건을 테스트하고, 조건이 성립하면 CPU가 다음 줄이 아닌 다른 위치에서 계속하도록 지시합니다. 모든 결정과 모든 루프는 이 하나의 메커니즘에서 나옵니다.

if나 for는 없습니다. 레이블(이름이 붙은 위치)과 분기(조건이 성립할 때 위치로 점프)로 그것들을 구성합니다. 레이블은 이름 뒤에 :가 오는 것입니다.

분기가 어떻게 실행되는지 이해하려면 프로그램 카운터(PC)에 대해 알아야 합니다. PC는 코드에서 숨겨진 특수 레지스터로, 실행 중인 명령의 메모리 주소를 보관합니다. 분기 명령은 PC에 새 값을 쓰므로, CPU는 다음 명령을 코드의 다음 줄이 아니라 대상 레이블에서 가져옵니다.

분기 계열

모든 분기는 두 레지스터를 비교하고 테스트가 성립할 때만 레이블로 점프합니다. 테스트가 실패하면 실행은 다음 줄로 계속됩니다. 여섯 가지 테스트가 핵심을 이룹니다:

여섯 개의 실제 브랜치
beq a, b, label # a == b이면 분기
bne a, b, label # a != b이면 분기
blt a, b, label # a < b이면 분기 (부호 있음)
bge a, b, label # a >= b이면 분기 (부호 있음)
bltu a, b, label # a < b이면 분기 (부호 없음)
bgeu a, b, label # a >= b이면 분기 (부호 없음)

와 는 동등성을 테스트하고 부호를 무시합니다. 와 는 순서 쌍, 즉 보다 작음과 보다 크거나 같음을 이루며, 레지스터를 부호 있는 수로 비교합니다. 와 는 같은 비교를 수행하지만 값을 부호 없는 것으로 취급하며, 이는 결코 음수가 아닌 주소나 카운트에 적합합니다.

부호 있는과 부호 없는은 무슨 뜻일까요? 레지스터는 32비트를 보관하며, 같은 비트를 두 가지 방식으로 읽을 수 있습니다. 부호 없는 것으로 읽으면 0부터 약 40억까지의 단순한 정수를 이룹니다. 부호 있는 것으로 읽으면 최상위 비트가 마이너스 부호로 계산되어, 비트 패턴의 절반이 음수를 나타냅니다. 비트 자체는 변하지 않습니다. 변하는 것은 읽는 방식뿐입니다. 그래서 두 세트의 비교가 있습니다. 음수가 될 수 있는 일반적인 수에는 부호 있는 것(, )을 사용하고, 메모리 주소처럼 결코 음수가 아닌 값에는 부호 없는 것(, )을 사용하세요.

직접적인 "더 크면 분기"나 "작거나 같으면 분기"는 없습니다. 그것들은 두 레지스터를 교환하여 얻으며, 어셈블러는 교환을 대신 수행하는 의사 분기로 와 를 제공하므로 는 가 됩니다. 두 가지 더 많은 지름길: 와 는 하나의 레지스터를 0과 비교하고, 은 조건 없이 점프합니다.

1부터 5까지 루프로 합산
li t0, 0 # 합계 = 0
li t1, 1 # i = 1
li t2, 6 # 한계 = 6 (i < 6인 동안 반복)
loop:
bge t1, t2, done # i >= 6이면 루프 종료
add t0, t0, t1 # 합계 += i
addi t1, t1, 1 # i++
j loop # 다시 확인하러 돌아간다
done:
mv a0, t0 # 합계를 출력 (15가 되어야 함)
li a7, 1
ecall
li a7, 10
ecall

추적해 보세요. t0는 실행 중인 합계를 보관하고(0에서 시작), t1은 카운터 i를 보관하며(1에서 시작), t2는 한계 6을 보관합니다. loop를 통과할 때마다: 은 i가 6에 도달하면 루프를 빠져나갑니다. 그렇지 않으면 이 i를 합계에 더하고, 이 i를 1 증가시키며, 가 다시 테스트하기 위해 점프합니다. 루프는 1, 2, 3, 4, 5를 더하고, done은 총합 15를 출력합니다.

가지가 있는 루프
loop:
bge t1, t2, done
add t0, t0, t1
addi t1, t1, 1
j loop
i (t1)1
나 (t0)0
yesnot1 < 6?sum += t1t1 += 1완료

bge는 t1과 t2를 비교하여 루프를 계속할지 결정합니다.

아래 그림은 카운터를 갱신하는 명령과 실행 중인 합계를 갱신하는 명령을 표시하여 루프의 나머지와 구별합니다. 은 카운터를 진행시키고, 뒤따르는 은 새로운 카운터 값을 합계에 더합니다. 마지막 프레임은 루프를 끝내는 테스트를 보여줍니다. t1이 6이 되면, 은 본문을 다시 실행하게 하는 대신 제어를 done으로 옮깁니다. 그 테스트가 반복하는 패스와 멈추는 패스를 구분합니다.
💡
Run 대신 Step으로 프로그램을 진행하세요. 진행하면서 t1이 증가하고 프로그램 카운터(pc)가 loop로 점프해 돌아옵니다. pc가 loop로 돌아올 때마다 분기 조건은 거짓이었으므로 루프는 다시 실행됩니다.
⚠
종료 조건이 잘못되면 루프는 결코 끝나지 않습니다. 시뮬레이터는 폭주 루프를 멈추지만, 분기 로직은 여러분이 올바르게 해야 할 몫입니다.