---
title: "가속기 명령을 보낸 뒤 결과를 읽어도 되는 시점"
author: "브레이브브라우저쓰자"
published_at: "2026-11-01T14:19:03.855Z"
updated_at: "2026-10-06T03:43:37.612Z"
tags: ["accelerator custom instruction"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/%EA%B0%80%EC%86%8D%EA%B8%B0-%EB%AA%85%EB%A0%B9%EC%9D%84-%EB%B3%B4%EB%82%B8-%EB%92%A4-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EC%9D%BD%EC%96%B4%EB%8F%84-%EB%90%98%EB%8A%94-%EC%8B%9C%EC%A0%90"
ai_assisted: true
---

# 가속기 명령을 보낸 뒤 결과를 읽어도 되는 시점

이 글은 AI가 활용됐습니다.

[원문](https://ariser.io/@writer_a9817d5a8de0/%EA%B0%80%EC%86%8D%EA%B8%B0-%EB%AA%85%EB%A0%B9%EC%9D%84-%EB%B3%B4%EB%82%B8-%EB%92%A4-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EC%9D%BD%EC%96%B4%EB%8F%84-%EB%90%98%EB%8A%94-%EC%8B%9C%EC%A0%90)

CPU가 가속기 명령 하나를 실행한 직후 결과 배열을 읽었다. 그런데 배열에는 이전 값이 남아 있다. accelerator custom instruction을 CPU의 일반 덧셈처럼 이해하면 생길 수 있는 상황이다. 명령을 받아 작업 큐에 넣는 시점, 연산을 끝내는 시점과 결과가 CPU에 보이는 시점은 서로 다를 수 있다. software는 그 사이의 계약을 알아야 한다.

## 발행은 어떤 일을 맡겼는지 전달하는 단계다

Gemmini는 RISC-V CPU의 RoCC 인터페이스에 연결된 accelerator이며 비표준 custom instruction을 쓴다. 명령에는 계산만 있는 것이 아니라 설정, memory와 scratchpad 사이의 이동, preload와 실행이 있다. register operand의 주소가 CPU의 main memory 주소인지 accelerator 내부의 local 주소인지도 명령마다 구분한다. [Gemmini, Architecture 및 ISA](https://github.com/ucb-bar/gemmini#architecture)

설정 하나와 계산 하나를 묶어 생각할 수도 있지만, 실제로는 여러 명령이 만든 상태를 뒤의 작업이 사용한다. 같은 opcode라도 이미 설정한 stride나 dataflow에 따라 동작이 달라질 수 있다. 생성한 hardware와 software header가 같은 기능과 encoding을 정의하는지도 확인해야 한다.

Gemmini 문서의 `matmul.preload`는 array가 그 명령을 받은 다음 cycle에 commit되지만, 뒤의 계산 명령을 기다리는 동안 array는 idle이라고 설명한다. 여기서 commit을 읽고 곧바로 행렬 곱 전체가 끝났다고 해석할 수 없다. 명령 종류와 문서가 정의한 완료 범위를 함께 읽어야 한다. [Gemmini, Preloading](https://github.com/ucb-bar/gemmini#preloading)

## 세 시점을 구분하면 오래된 값을 읽은 이유가 보인다

특정 hardware의 timing을 재현하지 않는 가상 실행을 정하자. CPU-visible output의 초기값은 9다. t=0에 계산 요청이 수락되고 CPU는 다음 명령으로 진행할 수 있다. t=5에 accelerator의 local 결과가 42가 되며, t=8에 output write가 끝나 CPU에 새 값이 보인다고 약속한 모델이다.

이 경우 CPU가 t=1에 결과를 읽으면 9다. t=6에도 local 결과는 준비됐지만 output write가 끝나지 않았으므로 아직 9다. 정한 completion 조건을 확인한 뒤 t=9에 읽으면 42다. 명령 수락, local 계산 완료와 CPU-visible 완료를 같은 사건으로 놓으면 어느 쪽에서 기다려야 하는지 알 수 없다.

completion의 의미도 인터페이스에서 정한다. 특정 명령의 계산만 끝났다는 응답인지, 관련 store까지 끝났는지, 이후 CPU load가 그 결과를 볼 수 있도록 ordering과 visibility가 보장됐는지 확인한다. local 주소의 완료 표식을 CPU memory의 완료 표식과 혼동하지 않는다.

## 설정을 언제 잡아 두는지도 계약이다

또 다른 자체 예에서 scale 설정은 2이고, t=1에 수락한 작업은 입력 3을 그 scale로 변환해야 한다고 정하자. 수락 시 설정을 붙잡아 둔다는 계약이면 결과는 6이다. t=2에 다음 작업을 위해 scale을 4로 바꿔도 이미 수락한 작업의 의미는 바뀌지 않는다.

hardware가 scale을 나중에 실행할 때 읽도록 설계됐다면 같은 sequence의 의미가 달라질 수 있다. 그 경우 software가 설정 변경을 앞선 작업의 해당 사용 시점 뒤로 정렬하는 규칙이 필요하다. 이 예는 Gemmini의 모든 설정이 수락 시 capture된다는 설명이 아니다. 상태를 공유하는 명령에서 capture 시점과 ordering을 문서화해야 한다는 반례다.

Gemmini는 load·store·execute controller를 나누어 작업을 겹치고, 서로 다른 controller의 데이터 hazard를 ROB에서 검사한다. 같은 controller로 가는 명령은 program order로 전달되고 내부 의존성은 해당 controller가 처리한다. CPU가 발행한 명령의 총순서만 보고 모든 실제 작업이 직렬로 끝난다고 가정하면 이 구조를 놓친다. [Gemmini, Decoupled Access/Execute 및 ROB](https://github.com/ucb-bar/gemmini#rob)

## software의 기다림을 실제 경로와 대응시킨다

공식 테스트의 `mvin_mvout.c`는 여러 입력 이동과 출력 이동을 발행한 뒤 `gemmini_fence()`를 호출하고, 그다음 input과 output을 비교한다. 함께 제공된 header는 이 호출을 `fence` assembly로 정의한다. 원코드에서 발행 루프와 결과 검사가 분리된 순서를 직접 확인할 수 있다. [Gemmini 공식 테스트](https://github.com/ucb-bar/gemmini-rocc-tests/blob/master/bareMetalC/mvin_mvout.c), [동기화 wrapper 정의](https://github.com/ucb-bar/gemmini-rocc-tests/blob/master/include/gemmini.h)

이 사용례를 다른 custom accelerator에 적용하려면 그 SoC와 CPU가 device의 진행 상태를 어떻게 fence와 연결하는지 알아야 한다. `fence`라는 한 단어가 임의의 device에서 모든 비동기 작업을 기다리는 보편적인 함수가 되는 것은 아니다. compiler의 memory 접근 순서와 hardware ordering, cache visibility도 각각 해당 경로의 보장을 확인한다.

입력 buffer의 수명도 같은 문제다. 요청 수락 뒤 hardware가 아직 input을 읽고 있는데 CPU가 그 공간을 다음 요청으로 덮으면, 주소는 같아도 작업이 받은 내용이 달라질 수 있다. 어떤 완료가 input 재사용을 허용하고 어떤 완료가 output 소비를 허용하는지 API가 구분해야 한다.

accelerator custom instruction은 계산을 빠르게 요청하는 방법이면서 공유 상태와 memory의 사용 순서를 정하는 인터페이스다. 명령 발행에서 결과 소비까지 수락·진행·visibility를 따라가고, 설정과 buffer를 다시 사용할 시점을 붙인다. 그때 비로소 명령이 실행됐다는 사실을 결과가 준비됐다는 판단으로 안전하게 연결할 수 있다.

