레지스터 접근 규칙
110분 안팎
학습 목표
일반 RW와 W1C 레지스터의 쓰기를 별도 함수로 구현합니다.
개념
계산 수식만 맞으면 끝나지 않습니다
일반 숫자 3에서 bit0을 지우는 수식은 3 & ~1입니다. 하지만 상태 레지스터가 W1C라면 그 수식의 결과 2를 레지스터에 쓰는 순간 bit1이 지워질 수 있습니다. 필요한 연산을 고르려면 레지스터 이름뿐 아니라 접근 속성을 읽어야 합니다. 이번 실습은 쓰기 요청과 그 결과 상태를 서로 다른 값으로 두어 이 오류를 재현합니다. 값의 비트 연산이 맞아도 장치 쓰기 의미에 맞지 않으면 다른 이벤트를 잃습니다.
RW는 일반 read/write 저장 규칙입니다. 모형에서는 현재 old에 mask를 OR한 값을 저장하면 선택 비트가 켜집니다. 반대로 old와 반전 mask를 AND한 값을 저장하면 선택 비트가 꺼집니다. rw_set과 rw_clear는 새 저장값을 반환하는 순수 함수입니다. 실제 장치의 모든 RW 필드가 임의 값을 허용한다는 뜻은 아닙니다. 접근 폭과 예약 비트 규칙까지 해당 데이터시트에서 확인해야 합니다.
W1C는 write one to clear입니다. 해당 자리에 1을 쓰면 그 플래그가 지워지고 0을 쓰면 변화가 없습니다. 쓰는 값은 새 상태 전체가 아니라 지울 위치를 지정하는 요청입니다. Microchip 공식 Register Bit Types 표에서도 이 구분을 확인할 수 있습니다. 실습은 이 규칙만을 모델링하며 특정 칩의 주소나 인터럽트 우선순위를 주장하지 않습니다. 일반 RW와 W1C를 구분한 뒤 장치마다 섞여 있는 필드의 세부 규칙을 추가로 읽습니다.
버스에 쓴 값과 남은 상태를 분리합니다
flags가 0x03이면 bit0과 bit1 이벤트가 모두 남아 있습니다. bit0만 처리했다면 버스에는 0x01을 보내야 합니다. 장치가 요청을 적용한 뒤 남는 상태는 0x02입니다. 0x02를 직접 쓰면 bit1이 지워지고 bit0은 남습니다. 숫자 0x02가 원하는 최종 상태라는 이유로 그대로 쓰지 않습니다. 출력 표에는 old flags, bus value, remaining flags를 각각 적어 어느 단계의 값인지 드러냅니다.
w1c_apply(flags, bus_value)는 RAM으로 만든 장치 모델입니다. 내부에서 flags & ~bus_value를 계산해 1을 받은 자리만 지웁니다. acknowledge(flags, mask)는 드라이버 역할의 함수로 mask만 w1c_apply에 전달합니다. 실제 W1C 레지스터에서는 해당 장치 문서에 맞는 접근 폭으로 mask를 주소에 직접 쓰는 동작이 필요합니다. RAM 모델의 내부 AND-NOT을 물리 W1C 주소의 read-modify-write 코드로 옮기면 역할이 뒤바뀝니다.
잘못된 예로 flags | mask를 버스 값으로 보내 봅니다. flags 0x03과 mask 0x01의 OR는 0x03입니다. 장치가 두 1을 받아 두 플래그를 모두 지웁니다. 이미 관측한 다른 플래그까지 다시 1로 쓰는 것이 손실 원인입니다. 일반 RW에서 보존하려고 넣은 OR가 W1C에서는 다른 플래그 삭제 요청으로 해석됩니다. demo는 잘못된 RMW 결과 00과 올바른 결과 02를 나란히 보여 줍니다.
아직 0인 플래그에 1을 쓰면 모형에서는 계속 0입니다. mask 0을 쓰면 모든 플래그를 유지하고 mask 255를 쓰면 모든 자리를 지웁니다. 반복 acknowledge는 해당 플래그를 다시 켜지 않습니다. 그러나 실제 장치에서 같은 자리의 새 이벤트가 발생하는 시점과 삭제 요청이 겹칠 때 우선순위는 이 순수 함수가 정하지 않습니다. 시험의 범위는 고정된 old 상태에 대한 단일 쓰기의 결과입니다.
volatile과 원자성을 따로 봅니다
하드웨어가 코드 밖에서 바꿀 수 있는 메모리 맵 레지스터에는 대상 컴파일러·장치 규칙에 맞는 volatile 접근이 사용됩니다. volatile은 그 접근을 일반 RAM 변수의 최적화와 구분하는 데 필요할 수 있지만 여러 연산을 한 덩어리로 만들지는 않습니다. 읽고 계산하고 쓰는 사이에 다른 실행 흐름이 값을 바꾸면 갱신 손실이 가능합니다. volatile을 붙였다는 이유로 ISR와 태스크 공유가 안전하다고 설명하지 않습니다.
현재 실습의 flags와 old는 함수 인자로 복사된 값이고 외부 장치가 동시에 갱신하지 않습니다. 이 RAM 모형에 volatile을 추가해도 실제 버스 트랜잭션이나 인터럽트가 생기지 않습니다. 단일 실행의 계산 정확성부터 검증하는 목적입니다. 뒤의 동시성 모듈에서 경쟁 조건을 다룰 때는 별도 동기화 정책을 도입합니다. 보드가 전용 SET·CLEAR 레지스터를 제공한다면 그 규칙을 확인해 쓰며 모든 칩에 있다고 가정하지 않습니다.
예약 비트는 쓰기 정책을 문서에서 읽습니다. 어떤 필드는 0으로 써야 할 수 있고 어떤 필드는 보존이 요구될 수 있습니다. 전체 상태를 읽어 그대로 되쓰는 방식이 항상 안전하지 않은 이유입니다. 특히 읽기 자체가 상태를 지우는 RC, 쓰면 동작을 시작하는 비트, RW와 W1C가 혼합된 필드는 서로 다른 주의가 필요합니다. 이번 함수가 8자리 전체를 같은 속성으로 정의했다는 사실을 README에 명시합니다.
실패 이름에서 원인을 좁힙니다
starter의 rw_set은 mask만 반환하고 rw_clear는 old를 그대로 반환합니다. acknowledge는 flags까지 OR해서 전달합니다. 모두 컴파일은 되지만 기능 검사가 실패하도록 구성합니다. 먼저 일반 RW 두 함수의 수식을 수정하고 마지막으로 acknowledge의 버스 요청을 바꿉니다. w1c_apply는 이미 장치 규칙을 구현한 모델이므로 테스트 통과를 위해 바꾸지 않습니다. 드라이버 오류를 장치 모형 변경으로 숨기지 않는 훈련입니다.
테스트는 old 0~255와 mask 0~255를 전수 조합하고 세 함수를 비교합니다. 256 × 256 × 3으로 196608번이며 경계 0과 255 및 모든 복수 비트 선택이 포함됩니다. 이 출력의 comparisons는 서로 다른 하드웨어 시나리오 수가 아니라 순수 계산 비교 횟수입니다. 실패 개수를 보고 충분하다고 끝내지 말고 demo의 버스 값과 결과를 손으로 설명합니다. 계산 영역의 전수 검증과 장치 타이밍 검증은 다릅니다.
unused parameter 경고가 자신의 수정 뒤 나타나면 함수가 필요한 인자를 사용하지 않는지 확인합니다. old를 쓰지 않는 설정 함수는 다른 상태를 보존할 수 없습니다. -Werror가 붙어 경고도 빌드 실패이므로 오래된 실행 파일 결과로 통과를 판단하지 않습니다. make test가 Error 1이라고 끝나면 위쪽 프로그램의 failure 숫자와 컴파일 진단을 먼저 구분합니다. 테스트 실패와 빌드 실패는 수정 위치가 다를 수 있습니다.
접근 계약을 코드 리뷰에 사용합니다
제출물은 registers.c 수정과 세 줄의 설명입니다. 일반 RW 설정은 다른 비트를 왜 보존하는지, W1C 확인은 왜 mask만 보내는지, RAM 모형과 실제 주소 쓰기가 왜 다른지를 적습니다. flags 3에서 mask 1을 처리할 때 올바른 버스 값 1과 남은 상태 2를 포함합니다. 실제 주소를 임의로 만들어 실행하지 않습니다. 테스트 결과가 성공해도 실물 레지스터 접근 폭과 예약 비트 규칙은 추가 확인 대상입니다.
사람이 보는 기준은 정답 숫자보다 책임 분리입니다. 모델 함수는 장치가 쓰기를 어떻게 해석하는지 설명하고 드라이버 함수는 어떤 요청을 보내는지 설명합니다. 모든 레지스터에 같은 비트 해제 도우미를 적용하겠다는 설계는 거부합니다. 다음 미션에서는 이 관점을 GPIO의 일반 RW 출력 보존에 적용하고 애플리케이션에서 레지스터 표현을 감춥니다. 세부 메모리 맵 설명은 서재 장으로 이어 읽습니다.
따라하기
starter 비교 실패 확인
starter에서 실행합니다. 컴파일 뒤 순수 함수 비교가 일부 실패하는지 확인합니다.
make test접근 종류별 구현 수정
registers.c에서 rw_set은 OR, rw_clear는 AND-NOT, acknowledge는 mask만 전달하도록 고칩니다.
make -s demo장치 쓰기 효과 비교
solution에서 직접 실행한 결과입니다. W1C의 bus와 result가 다름을 확인합니다.
./demo실행 결과
RW set=A1 clear=A0 W1C flags=03 bus=01 result=02 wrong RMW bus=03 result=00
전수 계산 비교
수정한 구현에서 세 연산의 모든 8비트 값·마스크 조합을 확인합니다.
make -s test실행 결과
registers: 196608 comparisons, 0 failures
확인 문제
실습
registers.c의 rw_set·rw_clear·acknowledge를 수정합니다. w1c_apply는 장치 모형이므로 유지합니다. 일반 RW의 비트 보존과 W1C mask 직접 전달을 구현하고 make test를 통과합니다. flags 3, mask 1의 버스 요청과 남은 상태를 구분해 README에 설명을 추가합니다.
실행 명령
make test
기대 결과
registers: 196608 comparisons, 0 failures
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 특정 레지스터 비트만 바꾸는 코드를 설명해 주시면 됩니다.