- RV32I 포함: 레지스터, ABI, ecall을 통한 흐름 제어.
- 목성과 연습문제를 다루세요: 부정, 인수, 사슬, 재귀.
- 마스터 크로스 툴체인, 스크립트 objdump를 이용한 링크 및 디버깅.
어셈블러에 관심이 있고 RISC-V가 나에게 맞는 방법이라고 생각하신다면, 당신은 올바른 곳에 왔습니다. RISC-V에서 ASM을 시작하는 것은 생각보다 저렴합니다. 도구를 이해한다면, 모델을 이해한다면 프로그래밍 그리고 건축의 몇 가지 주요 규칙.
다음 줄에서는 Jupiter 유형 시뮬레이터를 사용한 연습 등 여러 소스 중 가장 좋은 것을 결합했습니다. RV32I 기본 레퍼토리의 규칙루프와 재귀 예제, 시스템 호출, 심지어 VHDL(ALU, 메모리 제어, 상태 머신 포함)로 작성된 RISC-V CPU 설계에 대한 살펴보기와 크로스 툴체인과 연결 스크립트에 대한 검토도 포함됩니다.
RISC-V 어셈블러란 무엇이고 기계어와 어떻게 다른가요?
둘 다 붙어 있지만 하드웨어, 기계어는 순수한 이진법(1과 0)입니다. CPU가 직접 해석하는 반면 어셈블러는 니모닉을 사용합니다. 기호 어셈블러로 바이너리로 변환하는 것보다 더 읽기 쉽습니다.
RISC-V는 매우 깔끔한 기본 레퍼토리를 갖춘 개방형 ISA를 정의합니다. RV32I(32비트) 프로파일에는 39개의 사용자 지침이 포함되어 있습니다. 뛰어난 직교성을 갖추고 있으며, 메모리 접근을 순수 계산에서 분리하고, GCC/LLVM에서 뛰어난 지원을 제공합니다.
기록, 계약 및 진입점
RV32I에는 다음이 있습니다. 32개의 일반 용도 레지스터(x0–x31) 32비트입니다. x0은 항상 0으로 읽히며 쓸 수 없습니다. a0–a7(인수), t0–t6(임시 변수), s0–s11(저장됨)과 같은 별칭도 ABI를 따르는 데 유용합니다.
환경이나 시뮬레이터에 따라 프로그램은 특정 레이블에서 시작될 수 있습니다. Jupiter에서는 프로그램이 글로벌 __start 태그에서 시작됩니다.진입점을 표시하려면 표시 가능으로 선언해야 합니다(예: .globl).
라스 태그는 콜론으로 끝납니다한 줄에 하나의 명령어만 넣을 수 있으며 주석은 # 또는 ;로 시작할 수 있으므로 어셈블러가 이를 무시합니다.
도구 및 시뮬레이터: Jupiter 및 기본 워크플로
합병증 없이 연습하려면 다음이 필요합니다. 목성 시뮬레이터/어셈블러SPIM/MARS/VENUS에서 영감을 받은 그래픽 도구로, 단일 환경에서 편집, 조립, 실행을 용이하게 해줍니다.
Jupiter에서는 편집기 탭에서 파일을 만들고, 편집하고, 삭제할 수 있습니다. 저장 후 F3으로 조립하고 실행하세요. 레지스터와 메모리 뷰를 사용하여 명령어별로 흐름 명령어를 디버깅하여 머신의 상태를 파악합니다.
프로그램은 환경에 대한 호소로 끝나야 합니다. 코드 10으로 a0 설정으로 통화 종료 (종료). RISC-V에서 ecall은 환경/시스템에 대한 시스템 호출이나 트랩과 동일합니다.
프로그램의 최소 구조와 시스템 호출
학술적 사례의 전형적인 구조는 시작점을 정의하고, 작업을 수행하고, ecall로 끝납니다. ecall 인수는 일반적으로 a0–a2로 이동합니다. 그리고 환경에 따라 a7의 서비스 선택기가 달라집니다.
한 곳에서 Linux 예를 들어 RISC-V에서는 write 시스템 호출로 인쇄하고 적절한 코드로 종료할 수 있습니다. 쓰기의 경우 a0(fd), a1(버퍼), a2(길이) 및 a7이 서비스 번호와 함께 사용됩니다.마지막으로 a0은 반환 코드로 설정되고 a7은 종료 번호로 설정됩니다.
# Ejemplo mínimo (Linux RISC-V) para escribir y salir
.global _start
_start:
addi a0, x0, 1 # fd = 1 (stdout)
la a1, msg # a1 = &msg
addi a2, x0, 12 # a2 = longitud
addi a7, x0, 64 # write
ecall
addi a0, x0, 0 # return code
addi a7, x0, 93 # exit
ecall
.data
msg: .ascii "Hola mundo\n"
Linux 외부에서 작업하는 경우(예: 자체 IoT 서비스를 갖춘 교육용 시뮬레이터, 환경 문서에 따라 ecall 번호와 레지스터를 변경합니다.
조건문, 루프 및 메모리에 익숙해지는 데 도움이 되는 초기 연습
일반적인 워밍업은 정수가 음수인지 감지하는 것입니다. 양수이면 0을 반환하고, 음수이면 1을 반환합니다.; RV32I를 사용하면 0과 set-on-less-than을 비교해서 잘 고안된 단일 명령어로 문제를 해결할 수 있습니다.
또 다른 매우 유용한 연습은 숫자의 인수를 나열하는 것입니다. 1에서 n까지 순회하며 약수를 출력하고 약수의 개수를 반환합니다.조건 분기, 나눗셈(또는 반복 뺄셈), 덧셈과 비교를 포함하는 루프를 연습하게 됩니다.
문자열을 사용하면 메모리를 관리해야 합니다. 메모리에 있는 문자열의 각 문자를 방문하여 소문자를 대문자로 변환합니다. ASCII 범위 내에 있으면 반환합니다. 완료되면 문자열의 원래 주소를 반환합니다.
루프, 함수 및 재귀: 팩토리얼, 피보나치 및 하노이의 탑
루프를 디자인할 때는 조건, 본문, 단계라는 세 가지 블록을 생각해 보세요. beq/bne/bge 및 무조건 점프 jal/j while/for가 구축됩니다. 신비함이 없이, 추가와 비교에 의지합니다.
.text
.globl __start
__start:
li t0, 0 # i
li t1, 10 # max
cond:
bge t0, t1, end # si i >= max, salta
# cuerpo: usar a0/a1 segun servicio IO del entorno
addi t0, t0, 1 # i++
j cond
end:
li a0, 10
ecall
함수 호출에서는 ABI를 준수하세요. 더 많은 통화를 연결하려면 저장하세요, s0–s11을 수정하더라도 보존하고, sp가 단어의 배수로 이동하는 스택을 사용합니다.
팩토리얼은 고전적인 재귀입니다. 기본 케이스 n==0은 1을 반환합니다.; 그렇지 않으면 factorial(n-1)을 호출하고 n을 곱합니다. 호출 전에 스택에 저장된 ra와 레지스터를 보호하고 반환 시 복원합니다.
factorial:
beq a0, x0, base
addi sp, sp, -8
sw ra, 4(sp)
sw s0, 0(sp)
mv s0, a0
addi a0, a0, -1
jal factorial
mul a0, a0, s0
lw s0, 0(sp)
lw ra, 4(sp)
addi sp, sp, 8
jr ra
base:
li a0, 1
jr ra
피보나치는 두 가지 모두 연습하는 데 유용합니다. 두 번의 호출을 통한 재귀 어큐뮬레이터 변수를 사용하는 효율적인 반복 버전으로 구현할 수 있습니다. 흐름 및 매개변수 제어 과제를 해결하려면 솔루션을 어셈블러로 변환해야 합니다. 하노이의 탑 디스크, 소스, 대상, 보조 타워라는 4개의 인수를 사용합니다. 호출 순서를 존중하고 각 이동을 표시합니다.
메모리 접근, 배열 및 문자열 조작
RISC-V에서는 메모리 접근이 로드/스토어를 통해 수행됩니다. lw/sw는 단어, lh/sh는 반단어, lb/sb는 바이트입니다., 요금에 부호가 있거나 없는 변형이 있습니다(lb 대 lbu, lh 대 lhu).
정수 배열을 탐색하려면 인덱스당 4바이트 오프셋을 사용합니다. 텍스트 문자열의 경우 종료자를 찾을 때까지 바이트 단위로 진행합니다. 규칙에 따라 필요한 경우(예: \0) 기본 주소를 저장하고 addi/auipc/la를 사용하여 포인터를 적절히 처리해야 합니다.
RV32I CPU를 처음부터 설계하기: 개요
실리콘에 대해 자세히 알고 싶다면 교육 프로젝트를 통해 VHDL로 구현된 RV32I CPU, FPGA로 합성 가능 중저가형. 프로그램 ROM, 데이터 RAM, 그리고 LED 점등을 위한 간단한 GPIO가 포함되어 있습니다.
커널은 기본 레퍼토리(M/A/C 확장 또는 CSR 없음)를 구현합니다. 32비트 주소 버스를 사용합니다 적절한 경우 8/16/32비트 부호 확장 메모리 액세스를 허용합니다. 이 설계는 레지스터, ALU, 메모리 컨트롤러, 상태 머신을 명확하게 구분합니다.
ALU, 교대근무, 그리고 "지연 로딩"이라는 개념
ALU는 다음과 같은 연산과 조합하여 설명됩니다. 덧셈, 뺄셈, XOR, OR, AND, 비교(부호 있는 비교와 없는 비교) 논리/산술적 변화.
FPGA에서 LUT를 저장하기 위해 다중 비트 이동이 구현됩니다. 상태 머신에 의해 제어되는 1비트 이동 반복: 여러 사이클을 소모하지만 논리적 리소스가 줄어듭니다.
동기 회로에서는 클록 에지에서 변화가 관찰됩니다. "지연된 로드" 개념은 멀티플렉서에서 선택한 내용이 다음 사이클의 레지스터에 영향을 미친다는 것을 상기시킵니다., fetch-decode-execute 상태 머신을 설계할 때 중요한 측면입니다.
메모리 컨트롤러 및 맵: ROM, RAM 및 GPIO
메모리 블록은 ROM과 RAM을 연속된 공간으로 통합합니다. 프로세서 인터페이스 단순화컨트롤러는 AddressIn(32비트), DataIn, 너비(바이트/반/워드), 부호 확장 신호, WE(읽기/쓰기), Start를 수신하여 트랜잭션을 시작합니다.
작업이 끝나면, ReadyOut은 1로 설정되고 읽혀졌다면, DataOut에는 데이터가 들어 있습니다(요청 시 부호 확장됨). 데이터가 기록된 경우, 데이터는 RAM에 남아 있습니다.
실용적인 지도의 예: 0x0000부터 0x0FFF까지의 ROM, 0x1000(핀에 대한 비트 0)의 GPIO 바이트 및 0x1001부터 0x1FFF까지의 RAM이를 통해 출력 비트를 쓰고 토글하여 방향 지시등을 만들 수 있습니다.
레지스터, 멀티플렉서 및 상태 머신
CPU는 VHDL 배열로 인스턴스화된 32개의 일반 용도 레지스터를 정의합니다. 디코더 ALU에서 쓰기 대상을 선택하고 나머지는 그대로 둡니다.
멀티플렉서는 ALU 입력(피연산자 및 연산)을 제어합니다. 메모리 컨트롤러에 대한 신호 (너비, 주소, 시작 제어 및 읽기/쓰기) 및 특수 레지스터: PC, IR 및 반복적 이동을 위한 보조 카운터.
상태 머신은 다음으로 시작합니다. 재설정, PC가 가리키는 명령어를 가져옵니다. (4바이트 읽기) 준비가 되면 IR에 로드되어 실행 노드로 전달됩니다. 실행 노드에는 ALU(1사이클에 1개 명령어, 쉬프트 제외), 로드/저장, 분기 및 점프, ebreak와 같은 특수 명령어가 있습니다.
크로스 툴체인, 연결 및 디버깅
RV32I 바이너리를 생성하려면 다음을 사용하세요. 크로스 GCC(대상 riscv32-none-elf)C/C++/ASM 소스를 컴파일하고, 메모리 맵을 정의하는 스크립트와 연결하고, 출력을 ROM/FPGA가 기대하는 형식으로 변환합니다.
간단한 후크 스크립트를 배치할 수 있습니다 ROM의 .text는 0x0000부터입니다. 그리고 0x1004부터 RAM의 .data(0x1000–0x1003이 GPIO 레지스터에 의해 점유된 경우). 시작 루틴은 "naked"로 배치될 수 있습니다. RAM 끝의 스택 포인터(예: 0x1FFC) main을 호출하기 전에.
/* Mapa simple
* ROM: 0x00000000 - 0x00000FFF
* GPIO: 0x00001000 - 0x00001003
* RAM: 0x00001004 - 0x00001FFF
*/
SECTIONS {
. = 0x00000000;
.text : { *(.startup) *(.text) *(.text.*) *(.rodata*) }
. = 0x00001004;
.data : { *(.data) *(.data.*) }
}
riscv32-none-elf-objdump를 사용하면 다음을 수행할 수 있습니다. ELF를 분해하고 주소를 확인하세요예를 들어, 다음을 볼 수 있습니다. 부팅 lui/addi/jal과 같은 명령어와 메인으로의 전환을 통해 0x00000000에서 실행됩니다. VHDL 시뮬레이션의 경우, GHDL은 GtkWave로 열 수 있는 트레이스를 생성합니다.
시뮬레이션에서 검증한 후, 설계를 FPGA(Quartus 또는 다른 툴체인)로 옮깁니다. RAM이 내부 블록으로 추론되고 코드가 명확한 RTL인 경우베테랑 기기에서도 놀라지 않고 합성할 수 있어야 합니다.
시작할 때의 실용적인 알림과 일반적인 실수
잊지 마라 x0은 항상 0입니다; 여기에 쓰면 아무런 효과가 없고, 읽으면 0이 반환됩니다. 이 점을 추가, 비교, 레지스트리 정리에 활용하세요.
기능을 구현할 때, 수정한 ra 및 sN 레코드를 저장합니다., 그리고 sp에 단어별로 정렬된 덧셈/뺄셈을 통해 스택을 관리합니다. 복귀 시 역순으로 복원하고 jr ra로 점프합니다.
Jupiter와 같은 시뮬레이터에서는 다음을 확인하세요. __start는 전역이고 ecall로 끝냅니다. (종료하려면 a0=10). 시작되지 않으면 레이블과 전역성을 확인하고 다시 컴파일하세요(F3).
IO를 사용한 연습에서는 환경 프로토콜을 존중하다: 어떤 레지스터가 매개변수를 전달하는지, 서비스 번호, 그리고 주소 또는 즉시 값을 필요로 하는지 여부를 확인합니다. 시뮬레이터 또는 운영 체제 설명서를 참조하세요.
명확한 ISA 기반(RV32I, 레지스터 및 ABI), Jupiter와 같은 편리한 시뮬레이터, 점점 늘어나는 예제(음수, 인수, 대문자, 루프, 팩토리얼, 피보나치 및 하노이)를 통해 RISC-V 어셈블러는 더 이상 벽이 아니며 CPU가 생각하는 방식을 이해할 수 있는 흥미로운 영역이 됩니다. 그리고 VHDL로 내려가 보면 ALU, 메모리, 제어가 어떻게 조화를 이루는지 알 수 있을 것입니다.: 명령어 페칭과 지연 로딩부터 메모리 인터페이스, 그리고 ROM, RAM, GPIO를 갖춘 맵까지, 이를 통해 사용자 프로세서로 LED를 깜빡이게 할 수 있습니다.
바이트와 기술 전반에 관한 세계에 대한 열정적인 작가입니다. 나는 글쓰기를 통해 내 지식을 공유하는 것을 좋아하며 이것이 바로 이 블로그에서 할 일이며 가젯, 소프트웨어, 하드웨어, 기술 동향 등에 관한 가장 흥미로운 모든 것을 보여 드리겠습니다. 제 목표는 여러분이 간단하고 재미있는 방식으로 디지털 세계를 탐색할 수 있도록 돕는 것입니다.