Clock: 순차 회로의 상태 변화를 동기화하는 주기 신호
클럭(Clock)은 디지털 회로 내 모든 플립플롭과 레지스터가 같은 순간에 상태를 갱신하도록 만드는 주기적인 전기 신호다. CPU 성능, 파이프라이닝, 클럭 도메인 교차 문제를 이해하려면 클럭 신호가 왜 필요하고 어떤 물리적 제약을 받는지 알아야 한다. 이 글에서는 클럭의 정의부터 클럭 스큐, 메타스테이블리티 같은 실무에서 마주치는 문제까지 다룬다.
1. 개념
클럭(Clock)은 디지털 회로에서 일정한 주기로 0과 1(또는 Low와 High)을 반복하는 전기 신호다. 파형은 보통 구형파(square wave)에 가깝고, 이 신호가 상승하는 순간(rising edge)이나 하강하는 순간(falling edge)을 기준으로 회로 내 레지스터와 플립플롭(flip-flop)이 값을 갱신한다.
클럭을 수치로 표현할 때 두 가지 값을 쓴다.
- 클럭 주파수(clock frequency): 1초에 몇 번 진동하는지, 단위는 Hz. 예를 들어 3GHz는 초당 30억 번 진동한다.
- 클럭 주기(clock period): 주파수의 역수. 3GHz면 주기는 1/3,000,000,000초 ≈ 0.333ns.
CPU가 "3.5GHz로 동작한다"는 말은 초당 35억 번의 클럭 사이클(clock cycle)이 발생한다는 뜻이지, 초당 35억 개의 명령어를 처리한다는 뜻은 아니다. 이 둘을 구분하는 게 이 글 전체에서 중요한 포인트다.
2. 왜 사용하는가
조합 논리 회로(combinational logic circuit)만으로 시스템을 구성하면 문제가 생긴다. 각 논리 게이트를 신호가 통과하는 데 걸리는 시간인 전파 지연(propagation delay)이 경로마다 다르기 때문이다. 입력이 바뀌었을 때 어떤 신호는 먼저 도착하고 어떤 신호는 늦게 도착하면, 회로가 일시적으로 잘못된 중간 값을 출력하는 순간이 생긴다. 이를 글리치(glitch) 또는 레이스 컨디션(race condition)이라 부른다.
클럭은 "모든 레지스터가 정확히 이 순간에만 새 값을 받아들여라"라는 기준선을 만들어서 이 문제를 해결한다. 클럭 에지 사이의 충분한 시간 동안 조합 논리의 출력이 안정될 시간을 주고, 그 다음 에지에서 한꺼번에 상태를 갱신하는 방식이다. 이게 동기식 순차 회로(synchronous sequential circuit) 설계의 기본 전제이며, CPU의 파이프라이닝(pipelining)도 이 원리 위에서 동작한다.
3. 동작 원리
플립플롭은 클럭의 에지에서만 입력값을 내부 상태로 래치(latch)한다. 상승 에지 트리거(positive edge-triggered) 방식이 일반적이며, 이 순간 외에는 입력이 아무리 바뀌어도 출력에 영향을 주지 않는다.
클럭 주기는 임의로 짧게 만들 수 없다. 다음 조건을 만족해야 한다.
클럭 주기 ≥ 클럭-투-큐 지연(clock-to-Q delay)
+ 조합 논리 최대 지연(critical path delay)
+ 셋업 타임(setup time)여기서 조합 논리 최대 지연이 가장 큰 변수다. 두 플립플롭 사이에 있는 조합 논리 경로 중 가장 느린 경로를 임계 경로(critical path)라 부르고, 이 값이 클럭 주기의 하한선을 결정한다. 클럭을 무리하게 빠르게 돌리면 신호가 다음 플립플롭에 도착하기 전에 클럭 에지가 와버려서 셋업 타임 위반(setup time violation)이 발생하고, 이는 잘못된 값이 래치되는 원인이 된다.
CPU 내부에서는 클럭 신호가 클럭 트리(clock tree)라는 분배 네트워크를 통해 칩 전체의 수백만 개 플립플롭에 전달된다. 물리적 배선 길이 차이 때문에 같은 클럭 에지라도 칩의 위치마다 도착 시각이 미세하게 다를 수 있는데 이를 클럭 스큐(clock skew)라 하며, 설계 시 이 값을 임계 경로 계산에 반영해야 한다.
4. 코드 예제
하드웨어 클럭 자체는 실행 가능한 코드로 재현할 수 없지만, 클럭 사이클 단위로 상태가 갱신되는 순차 회로의 동작 방식은 시뮬레이션으로 표현할 수 있다. 아래는 클럭 에지마다 값을 갱신하는 4비트 카운터를 파이썬으로 흉내 낸 예제다.
class Register:
"""클럭 상승 에지에서만 값을 갱신하는 레지스터를 흉내 낸다."""
def __init__(self, width_bits: int = 4):
self.width_bits = width_bits
self.value = 0
def clock_tick(self, next_value: int) -> None:
# 실제 하드웨어에서는 이 대입이 상승 에지 순간에만 일어난다.
mask = (1 << self.width_bits) - 1
self.value = next_value & mask
def combinational_logic(current: int) -> int:
# 조합 논리: 다음 상태를 계산만 할 뿐, 클럭 없이는 레지스터에 반영되지 않는다.
return current + 1
def simulate_clock(cycles: int, freq_hz: float) -> None:
period_ns = (1 / freq_hz) * 1e9
reg = Register(width_bits=4)
print(f"클럭 주파수: {freq_hz:.0f}Hz, 클럭 주기: {period_ns:.2f}ns")
for cycle in range(cycles):
next_val = combinational_logic(reg.value)
reg.clock_tick(next_val) # 클럭 에지 발생 시점
print(f"cycle {cycle}: register = {reg.value}")
simulate_clock(cycles=5, freq_hz=1_000_000_000) # 1GHz 가정클럭 주파수: 1000000000Hz, 클럭 주기: 1.00ns
cycle 0: register = 1
cycle 1: register = 2
cycle 2: register = 3
cycle 3: register = 4
cycle 4: register = 5핵심은 combinational_logic이 다음 값을 아무리 미리 계산해도 clock_tick이 호출되는 순간, 즉 클럭 에지가 발생하는 순간에만 실제 상태가 바뀐다는 점이다.
5. 시간 복잡도 또는 성능 특성
CPU 성능을 클럭 주파수만으로 판단할 수 없다. 실제 처리 시간은 다음 식으로 근사한다.
실행 시간(초) = 명령어 수 × CPI × 클럭 주기
= 명령어 수 × (CPI / 클럭 주파수)CPI(Cycles Per Instruction)는 명령어 하나를 처리하는 데 평균 몇 클럭 사이클이 걸리는지를 나타낸다. 클럭 주파수가 같아도 CPI가 낮은(파이프라이닝, 슈퍼스칼라 구조가 잘 된) CPU가 더 빠르다. 이 때문에 2000년대 중반 이후 제조사들은 클럭 주파수 경쟁 대신 IPC(Instructions Per Cycle) 향상과 멀티코어 방향으로 전환했다.
클럭 주파수를 무한정 올릴 수 없는 이유는 전력 소비 때문이다. 동적 전력 소비는 다음 식을 따른다.
P(동적 전력) ∝ C × V² × fC는 커패시턴스, V는 전압, f는 클럭 주파수다. 주파수를 올리면 전력 소비가 선형으로 증가하고, 동작 전압을 유지한 채 주파수만 올리면 발열이 급증한다. 이것이 흔히 파워 월(power wall)이라 부르는 물리적 한계이며, 2000년대 중반 이후 단일 코어 클럭이 4~5GHz대에서 정체된 근본 원인이다.
6. 실무 사용 사례
- 성능 프로파일링:
perf같은 도구는 클럭 사이클 수와 명령어 수를 함께 측정해서 CPI를 계산하고, 코드의 어느 부분이 파이프라인 스톨(stall)을 일으키는지 분석한다. - 오버클러킹(overclocking): 제조사가 설정한 클럭 주파수보다 높게 설정하는 작업. 임계 경로의 지연시간 여유(margin)를 소비하는 행위이므로 발열 관리와 전압 조정이 함께 필요하다.
- 임베디드 시스템 클럭 설정: 마이크로컨트롤러(MCU)는 내부 오실레이터나 외부 크리스탈을 클럭 소스로 선택하고, PLL(Phase-Locked Loop)로 주파수를 배수 증폭해서 원하는 클럭을 만든다. 저전력 모드에서는 클럭 주파수를 낮추거나 특정 주변장치의 클럭을 꺼서 전력을 아낀다.
- DVFS(Dynamic Voltage and Frequency Scaling): 모바일 CPU와 서버 CPU 모두 부하에 따라 실시간으로 클럭 주파수와 전압을 조절해서 전력 효율을 관리한다.
7. 주의할 점
- 클럭 스큐(clock skew): 클럭 신호가 칩의 각 지점에 도달하는 시각 차이. 스큐가 크면 실제 사용 가능한 클럭 주기가 줄어들어 최대 동작 주파수가 낮아진다.
- 클럭 지터(clock jitter): 클럭 주기가 이상적인 값에서 미세하게 흔들리는 현상. 고속 인터페이스(고속 직렬 통신 등)에서는 지터가 데이터 오류율에 직접 영향을 준다.
- 메타스테이블리티(metastability): 플립플롭의 셋업/홀드 타임을 어긴 상태에서 클럭 에지가 들어오면 출력이 0도 1도 아닌 불안정한 상태에 머무를 수 있다. 특히 비동기 입력 신호를 그대로 클럭 도메인에 연결하면 발생 확률이 높아진다.
- 클럭 도메인 교차(CDC, Clock Domain Crossing): 서로 다른 클럭으로 동작하는 두 회로 블록 간에 신호를 주고받을 때는 동기화 플립플롭(synchronizer)을 여러 단 거쳐야 메타스테이블리티 전파를 막을 수 있다. 이를 무시하면 간헐적이고 재현하기 어려운 버그가 생긴다.
8. 핵심 정리
클럭은 순차 회로의 모든 상태 변화 시점을 하나의 기준으로 통일하는 주기 신호이며, 이 덕분에 조합 논리의 지연시간 차이로 인한 레이스 컨디션을 피할 수 있다. 클럭 주기는 회로 내 가장 느린 임계 경로의 지연시간으로 하한선이 정해지고, CPU 성능은 클럭 주파수 하나만으로 결정되지 않고 CPI와 곱해서 봐야 한다. 클럭 주파수를 올리는 데는 전력 소비와 발열이라는 물리적 한계가 있고, 실무에서는 클럭 스큐와 클럭 도메인 교차로 인한 메타스테이블리티 문제를 항상 염두에 두어야 한다.