가장 중요한 차이점은 Dynamic Register Renaming(동적 레지스터 이름 변경)을 하드웨어 레벨에서 구현했다는 점입니다.
Eliminates WAR & WAW hazards without stalling: 스코어보딩에서는 앞사람이 다 읽을 때까지, 혹은 다 쓸 때까지 파이프라인 전체를 멈추고 기다려야(Stall) 했습니다. 하지만 Tomasulo는 레지스터 이름을 내부적으로 바꿔치기(Renaming)하여, 진짜 데이터 의존성(True Data Dependence, RAW)이 아닌 가짜 의존성(Name Dependence)인 WAR와 WAW를 완벽하게 제거합니다.
명령어들은 멈추지 않고 계속 Issue 단계로 진입할 수 있습니다.

역할: Execution unit(연산기) 바로 앞에 달려 있는 대기실(Buffer)입니다.
특징: 명령어가 Issue 되면 Register File이 아니라 이 RS로 들어갑니다. 만약 재료(Operand)가 준비되어 있으면 바로 RS에 값을 복사해 두고, 준비되어 있지 않다면 "누가 이 재료를 만들어줄 것인가(Tag)"를 적어놓고 기다립니다.
Renaming의 실체: 이 RS 자체가 임시 레지스터 역할을 합니다. 즉, 목적지 레지스터(Destination Register)로 향해야 할 값들이 RS의 이름(Tag)으로 매핑되면서 Dynamic Register Renaming이 완성됩니다.
다이어그램의 굵은 빨간색 선입니다.
역할: 연산이 끝난 결과를 Register File뿐만 아니라, 그 값을 애타게 기다리고 있는 모든 Reservation Stations로 직접 쏴줍니다 (Broadcast).
특징: 스코어보딩에서는 반드시 Register File에 값을 쓰고 나서 다음 명령어가 그걸 읽어가야 했습니다. 하지만 CDB를 통하면 Direct forwarding (Bypass)이 가능해져서, RAW 해저드로 인한 지연(Penalty)이 획기적으로 줄어듭니다.
스코어보드는 하나의 거대한 중앙 통제소(Issue Logic)가 모든 장부를 관리했습니다.
Tomasulo는 Distributed(분산된) 방식을 씁니다. 각 RS가 CDB를 계속 쳐다보고 있다가(Snooping), 자기가 기다리던 Tag가 적힌 데이터가 지나가면 알아서 낚아채서 연산을 시작합니다. 각 연산기(Functional Unit)가 스스로 해저드를 관리하는 독립적인 형태입니다.

교수님이 처음 시작할 때 "Left part(왼쪽 부분)는 보지 마라"라고 하셨죠? 다이어그램 왼쪽의 Address unit과 Store/Load buffers 부분은 메모리 최적화를 위한 큐(Queue)인데, 원리 이해가 목표인 이 수업에서는 복잡하니까 과감히 생략(just leave it there)하겠다고 하신 겁니다. 우리는 철저히 오른쪽의 Reservation Stations와 Execution Units만 보면 됩니다.
Tomasulo에서 가장 복잡하고 중요한 단계입니다. 교수님이 "Issue in-order"라고 강조하셨듯, 이 단계는 무조건 줄을 서서 순서대로 들어갑니다.
동작 1: 빈자리 찾기 (Structural Hazard 체크)
명령어가 들어오면, 자기가 필요한 연산기(예: FP Adder) 앞에 있는 대기실인 Reservation Station (RS)에 빈자리(Slot)가 있는지 확인합니다. 빈자리가 없으면 들어가지 못하고 멈춥니다(Stall).
동작 2: Register Renaming (핵심!)
빈자리가 있다면, 그 명령어의 '결과를 저장할 목적지 레지스터(Destination Register)'의 이름을 아예 '방금 배정받은 RS의 이름'으로 바꿔버립니다. (예: F4 레지스터 대신 Add1이라는 RS 이름을 씁니다.) 이렇게 하면 WAW, WAR 해저드가 완벽하게 사라집니다.
동작 3: 재료 챙기기 (Read Operands 통합)
스코어보드에는 Read operands라는 단계가 따로 있었죠? Tomasulo는 그게 없습니다! * Issue 단계에서 만약 재료(Source Operand)가 이미 완성되어 Register File에 있다면, 그 값(Value) 자체를 RS의 Vj, Vk 칸에 바로 복사해 옵니다.
만약 재료가 아직 누군가 만들고 있는 중이라면, 그 재료를 만들 RS의 이름(Tag)을 Qj, Qk 칸에 적어두고 기다립니다.
이 단계부터는 순서에 상관없이(Out-of-order) 각자 알아서 뜁니다.
교수님 멘트 요약: "Execute out-of-order. Wait until operands are ready (RAW hazard)." (순서 상관없이 실행한다. 재료가 다 모일 때까지 기다린다.)
연산기가 요리를 끝냈을 때, 결과를 어떻게 배달하는지가 스코어보딩과 완전히 다릅니다.
동작 1: Common Data Bus (CDB) 방송
연산이 끝나면 그 결과값을 CDB (Common Data Bus)라는 빨간색 고속도로에 쏩니다.
동작 2: Register File 업데이트
해당 결과값을 기다리고 있던 원래 목적지 레지스터(Register File)에 값을 씁니다.
동작 3: RS로 직접 배달 (Direct Forwarding)
이게 제일 중요합니다! 아까 Issue 단계에서 "앞사람이 요리 끝내길 기다리며 RS에 이름표(Tag)만 적어두고 대기하던 명령어들" 기억나시나요? CDB를 통해 결과값이 날아가면, 이 대기하던 RS들이 "어? 내가 찾던 값이다!" 하고 낚아채서 자기의 빈칸(Vj 또는 Vk)을 채웁니다. Register File을 거치지 않고 바로 쏴주기 때문에 엄청나게 빠릅니다.
교수님 멘트 요약: "Write the result to the register file AND the corresponding reservation station slot who is waiting for you via Common Data Bus." (CDB를 통해 레지스터 파일과 널 기다리던 RS에 동시에 결과를 써라.)

현재 파이프라인에 들어온 명령어들이 3단계(Issue, Execute, Write result) 중 어디에 머물러 있는지를 추적하는 장부입니다. 스코어보딩과 달리 Read operands 단계가 없다는 점을 다시 한번 상기하시기 바랍니다.
연산기 앞 대기실(RS)의 한 칸(Slot)이 어떤 정보들을 들고 있는지 보여줍니다. 여기가 Tomasulo 알고리즘의 심장부입니다.
Busy:
해당 RS 슬롯이 현재 사용 중인지(Occupied) 나타냅니다 (Yes/No). 명령어가 Issue 될 때 Yes가 되고, Write result를 마치고 퇴근할 때 No가 됩니다.
Op (Operation):
수행해야 할 연산의 종류입니다 (예: ADD.D, MUL.D).
Vj, Vk (Values of S1 & S2):
연산에 필요한 첫 번째, 두 번째 재료의 실제 값(Value)입니다.
명령어가 Issue 될 때 이미 Register File에 값이 완성되어 있거나, 대기하던 중 CDB를 통해 값을 전달받으면 이 칸에 그 값을 적습니다.
Qj, Qk (Tags of the RS slots that will produce S1, S2):
A: A(Address) 필드는 Issue 단계에서는 명령어의 Offset을 보관하고, Execute 단계에서는 계산이 완료된 Effective Address를 담아두는 전용 버퍼 역할을 합니다.
Issue 단계 (현재 사진):
해당 명령어의 Immediate 값 (여기서는 메모리 오프셋인 34)이 먼저 A 필드에 기록됩니다. 동시에 Base Register인 R2의 값은 Vk 필드(Regs[R2])로 읽어옵니다.
Execute 단계 (이후 동작):
실제 데이터를 가져오려면 정확한 메모리 주소를 계산해야 합니다. 이후 실행 단계의 첫 번째 스텝(Address Calculation)에서, 하드웨어는 Vk에 들어있는 R2의 실제 값과 A에 임시로 저장해둔 34를 더합니다. 연산이 완료되면 그 결과인 최종 Effective Address(유효 주소)를 다시 A 필드에 덮어쓰게 됩니다.
이 장부는 Register Renaming (동적 레지스터 이름 변경)이 물리적으로 기록되는 곳입니다.
Qi:
현재 각 레지스터(예: F0, F2, F4...)에 "가장 마지막으로 값을 덮어쓸 예정인 RS의 이름표"를 적어두는 필드입니다.
예를 들어 ADD.D F2, F4, F6 명령어가 Add1이라는 RS 슬롯에 Issue 되었다면, Register Result Status 테이블의 F2 레지스터 밑에 Add1이라고 덮어씁니다.
이후에 들어오는 명령어들이 F2를 재료로 쓰려고 하면, Register File을 보는 게 아니라 이 장부를 보고 "아, 내가 필요한 값은 Add1이 만들어줄 예정이구나" 하고 자기 RS의 Qj나 Qk 칸에 Add1이라고 적고 기다리게 됩니다

Figure 3.9의 Write result 칸을 보면, 결과를 레지스터에 쓰기 전에 if (RegisterStat[x].Qi = r) 이라는 확인 절차를 거칩니다.
교수님의 질문은 이것입니다. "계산을 다 끝냈으면 그냥 목적지 레지스터(예: F4)에 확 써버리면 되지, 왜 굳이 장부를 확인해서 내 이름표(Qi)가 아직 남아있는지 체크해야 할까?"
교수님이 드신 예시를 따라가 봅시다. 똑같이 F4 레지스터를 목적지로 하는 명령어 두 개가 연속으로 들어왔습니다.
앞선 명령어: L.D F4, ... (결괏값으로 10을 만들어낼 예정)
뒤따른 명령어: MUL.D F4, ... (결괏값으로 20을 만들어낼 예정)
[Issue 단계의 장부 덮어쓰기]
[Write Result 단계의 딜레마]
교수님이 "만약 그냥 10을 써버리면 어떻게 되느냐"고 역질문하셨죠.
여기서 분명히 의문이 드실 겁니다. "아니, 레지스터에 안 쓰고 10을 버려버리면, 곱셈 말고 진짜로 Load1의 10을 애타게 기다리던 애들은 굶어 죽나요?"
바로 여기서 Figure 3.9 코드의 진가가 드러납니다.
레지스터(Regs[x])에 값을 쓰는 코드 바로 밑에, RSx 장부를 뒤져서 값을 쏴주는 코드가 따로 두 줄 더 있죠?
"목적지 레지스터에 값을 쓸 때 장부를 확인하는 이유는, 내 뒤에 똑같은 레지스터를 쓰는 명령어(WAW)가 들어와서 내 이름표를 덮어썼는지 확인하기 위해서입니다. 덮어써졌다면 레지스터 업데이트는 포기하고 버리되, 나를 기다리는 RS 슬롯들에게는 CDB를 통해 값을 정상적으로 전달해 줍니다."

명령어가 파이프라인에 진입하여 Reservation Station (RS)을 할당받고, Register Renaming을 수행하는 단계입니다. 이 단계는 반드시 In-order로 진행됩니다.
Wait until: Station r empty
Action (Operand rs 확인): if (RegisterStat[rs].Qi != 0)
Action (Operand rt 확인):
Action (Register Renaming): RegisterStat[rd].Qi <- r
재료가 모두 준비되면 실제 연산을 수행하는 단계입니다. 이 단계부터는 Out-of-order로 진행됩니다.
Wait until: (RS[r].Qj = 0) and (RS[r].Qk = 0)
Action: Vj와 Vk의 값을 가지고 계산(Compute result)을 수행합니다.
Wait until: RS[r].Qj = 0 & r is head of load-store queue
Action: 준비된 Base Address(RS[r].Vj)와 Issue 단계에서 저장해둔 Offset(RS[r].A)을 더해서 최종 Effective Address를 계산하고, 다시 RS[r].A에 덮어씁니다.
계산된 결과를 Common Data Bus (CDB)를 통해 레지스터와 대기 중인 RS에 Broadcast하는 단계입니다.
Wait until: Execution이 완료되고, CDB를 다른 명령어가 쓰고 있지 않아야(CDB available) 합니다.
Action (Register File 업데이트): (if (RegisterStat[x].Qi = r))
Action (Direct Forwarding): (if (RS[x].Qj = r)) 및 Qk 확인 -> data broadcasting
Action (RS 비우기): 자신의 RS 사용이 끝났으므로 RS[r].Busy <- no로 변경하여 자리를 반납합니다.
B. Store