Easy RISC-V

1 week ago 10

  • RISC-V 어셈블리 프로그래밍을 웹 기반 인터랙티브 에뮬레이터로 학습할 수 있도록 만든 오픈 튜토리얼로, 저수준 컴퓨터 구조 개념을 실습 중심으로 설명
  • RV32I_Zicsr 명령어 집합을 중심으로, 산술·비트 연산·분기·메모리 접근·예외 처리 등 RISC-V의 핵심 구조를 단계별로 시각화
  • 에뮬레이터 조작, 레지스터 상태 확인, 메모리 접근, 시스템 콜 구현 등을 통해 실제 CPU 동작을 체험할 수 있도록 구성
  • 후반부에서는 Machine/User 모드 전환, 예외 처리 루틴, 간단한 운영체제 커널 구현까지 다루며, RISC-V 특유의 오픈 표준과 확장성을 강조
  • RISC-V의 구조적 단순성과 공개 표준이 교육·연구·임베디드 시스템 개발에 적합함을 보여주는 실습형 자료

개요

  • 이 튜토리얼은 Nick Morgan의 Easy 6502에서 영감을 받아 제작된 RISC-V 어셈블리 입문서로, 저수준 프로그래밍 개념을 알고 있지만 RISC-V에 익숙하지 않은 사용자를 대상으로 함
    • RISC-V는 UC Berkeley에서 시작된 오픈 RISC 아키텍처로, 전 세계 연구자·엔지니어·학생들이 참여하는 활발한 커뮤니티를 형성
  • 주요 특징은 간결한 설계, 공개 표준, 커뮤니티 중심 개발로, 누구나 자유롭게 코어와 칩을 설계 가능
  • 본 튜토리얼은 32비트 RV32I_Zicsr 명령어 집합을 다루며, Rust의 riscv32i-unknown-none-elf 타깃과 호환
  • 다루는 명령어는 총 45개로, 산술·논리·분기·메모리·CSR 조작 등 CPU의 기본 기능을 모두 포함

첫 번째 RISC-V 프로그램

  • 에뮬레이터를 통해 addi와 ebreak 명령어를 실행하며, 레지스터 a0 값이 0x00000123으로 변하는 과정을 시각적으로 확인
  • Start, Run, Step, Dump 버튼으로 코드 실행·단계별 추적·어셈블 결과 확인 가능
  • Dump는 심볼 테이블과 명령어 인코딩(리틀엔디언 형식)을 표시해, 실제 메모리 주소와 기계어의 관계를 보여줌

프로세서 상태

  • 프로그램 카운터(pc) 는 현재 실행 중인 명령어의 주소를 가리킴
  • 일반 레지스터(x1~x31) 은 32비트 데이터를 저장하며, x0은 항상 0을 반환하는 제로 레지스터로 쓰기 무시
  • RV32I에는 플래그 레지스터가 없으며, 오버플로·캐리 처리는 프로그래머가 직접 관리

명령어 문법

  • 명령어는 명령어명, 피연산자1, 피연산자2, ... 형태로 구성
    • 예: addi x10, x0, 0x123 → x10 = x0 + 0x123
  • 즉시값(immediate)은 12비트 2의 보수 범위 [-2048, 2047] 내에서 사용
  • li, mv 등 의사명령어(pseudoinstruction) 는 실제 명령어 조합으로 확장되어 편의 제공

연산 명령어

  • 산술 연산: add, addi, sub 등으로 덧셈·뺄셈 수행
    • addi는 즉시값을 더하고, sub는 두 레지스터의 차를 계산
  • 비트 연산: and, or, xor 및 즉시값 버전(andi, ori, xori) 지원
    • xori rd, rs1, -1은 비트 반전(not) 효과
  • 비교 연산: slt, sltu, slti, sltiu로 부호·무부호 비교 수행
    • a < b는 slt, a >= b는 slt 후 xori 1로 구현
  • 시프트 연산: sll, srl, sra 및 즉시값 버전 존재
    • 산술 시프트(sra)는 부호 비트를 유지, 논리 시프트(srl)은 0으로 채움
  • RV32I에는 곱셈·나눗셈이 없으며, 확장 명령어(M 확장)로 제공

큰 수 다루기

  • lui 명령어는 상위 20비트를 로드하고, addi로 하위 12비트를 채워 32비트 상수를 구성
  • %hi(), %lo() 매크로로 상·하위 비트를 자동 계산 가능

분기와 점프

  • 분기(branch) : 조건에 따라 점프 (beq, bne, blt, bge, bltu, bgeu)
  • 점프(jump) : 무조건 점프 (jal, jalr)
    • jal은 다음 명령어 주소를 rd에 저장해 함수 호출에 사용
    • ret은 jalr x0, 0(x1)의 의사명령어로 복귀 수행
  • 라벨(label) 은 코드 내 주소 식별자로, 루프·조건문 구현에 활용

메모리 접근

  • 메모리 영역: 0x40000000~0x400FFFFF (1MiB)
  • 로드/스토어 명령어:
    • lw / sw : 32비트 단위
    • lb, lbu, lh, lhu : 부호 확장/제로 확장 지원
    • sb, sh : 바이트·하프워드 저장
  • 엔디언: 리틀엔디언 사용 (.byte 0x1,0x2,0x3,0x4 → .word 0x04030201)
  • 메모리 정렬: 워드는 4바이트, 하프워드는 2바이트 정렬 필요

메모리 매핑 I/O

  • 주소 0x10000000에 32비트 쓰기(sw) 시 하위 8비트가 출력창에 문자로 표시
  • 이를 이용해 Hello World를 출력 가능

함수와 호출 규약

  • 레지스터 별칭:
    • a0~a7: 인자/반환값
    • t0~t6: 임시
    • s0~s11: 호출 보존
    • ra: 복귀 주소, sp: 스택 포인터
  • 함수 호출 시 인자는 a0~a7, 반환값은 a0 사용
  • 호출된 함수는 s 레지스터와 sp를 보존해야 함

스택 구조

  • 스택은 낮은 주소 방향으로 성장, 16바이트 정렬 유지
  • 함수 진입 시 sp 감소로 공간 확보, 종료 시 복원
  • ra, s 레지스터를 스택에 저장·복원하여 중첩 호출 지원
  • 예시: 재귀 피보나치 함수 구현

숫자 라벨

  • 1:과 같은 숫자 라벨을 사용해 1b(backward), 1f(forward)로 간단히 참조 가능

위치 독립 코드 (PIC)

  • auipc 명령어는 pc + (imm << 12)를 계산해 PC 상대 주소 지정 지원
  • %pcrel_hi(), %pcrel_lo() 매크로로 위치 독립 주소 계산
  • la, call 의사명령어는 auipc와 jalr 조합으로 구현

특권 아키텍처 기초

  • Privilege Level: Machine(3), User(0) 두 단계
    • 시스템 초기화는 Machine 모드에서 시작
  • CSR(Control and Status Register) : 시스템 제어용 특수 레지스터
    • 조작 명령어: csrrw, csrrs, csrrc 및 즉시값 버전
    • 읽기 전용 접근은 csrr 의사명령어 사용

카운터와 상태

  • cycle, instret은 각각 클록 사이클명령어 실행 수를 카운트
    • RV32에서는 cycleh, instreth로 상위 32비트 제공
  • 현재 특권 레벨은 CSR로 직접 읽을 수 없으며, 설계상 가상화 조건을 충족하기 위함

예외 처리

  • 예외 발생 시 mcause, mepc, mtval, mstatus에 정보 저장 후 mtvec으로 점프
  • mret 명령어는 예외 복귀 시 사용, mepc로 복귀 주소 설정
  • 예외 코드 예시:
    • 2: Illegal instruction
    • 3: Breakpoint
    • 8: Environment call from User mode

User 모드 전환과 시스템 콜

  • mret으로 Machine → User 모드 전환
  • ecall은 시스템 콜 트리거, ebreak은 디버그용 브레이크포인트
  • mscratch CSR은 커널 스택 포인터 저장소로 활용, User 모드에서 접근 불가

최소 운영체제 구현

  • 기능:
    • a7=1: putchar (문자 출력)
    • a7=2: exit (종료)
  • 예외 처리 루틴은 U모드 예외 시 레지스터 상태를 스택에 저장하고 trap_main 호출
  • trap_main은 mcause를 확인해 시스템 콜 분기, a0에 반환값 저장 후 mret으로 복귀

의사 코드 개요

  • trap_main은 mcause==8일 때 do_syscall 호출
  • do_syscall은 a7 값에 따라 sys_putchar 또는 sys_exit 실행
  • sys_putchar는 0x10000000 주소로 문자 출력
  • sys_exit은 ebreak으로 종료

생략 및 단순화된 부분

  • 실제 RISC-V 어셈블리와 완전히 동일하지 않으며, 일부 의사명령어·CSR·확장 기능은 생략
  • 다루지 않은 주제:
    • 64비트 아키텍처
    • 압축 명령어
    • 인터럽트, 메모리 보호, 가상 메모리 등 고급 OS 기능

참고 자료 및 라이선스

  • 튜토리얼 및 코드 모두 CC0 퍼블릭 도메인 또는 0-clause BSD 라이선스
  • 원문 및 코드 저장소: https://github.com/dramforever/easyriscv
  • RISC-V 학습용으로 교육·연구·시뮬레이션 환경 구축에 적합한 자료

Read Entire Article