Ce este necesar pentru a începe programarea în asamblatorul RISC-V

Ultima actualizare: 08/10/2025
Autorul: Isaac
  • Include RV32I: registre, ABI și control al fluxului cu ecall.
  • Exersează cu Jupiter și exerciții: negativ, factori, lanțuri, recursivitate.
  • Lanț de instrumente Master Cross, scenariu legarea și depanarea cu objdump.

Asamblor RISC-V: Cerințe și Noțiuni introductive

Dacă ești curios în legătură cu asamblatorul și simți că RISC-V este calea de urmat, ai ajuns la locul potrivit. Începerea cu ASM pe RISC-V este mai accesibilă decât pare Dacă înțelegi instrumentele, modelul de programare și câteva reguli cheie ale arhitecturii.

În rândurile următoare am combinat ce e mai bun din mai multe surse: practici cu simulatoare de tip Jupiter, convențiile repertoriului de bază RV32I, exemple de bucle și recursivitate, apeluri de sistem și chiar o privire asupra designului procesorului RISC-V în VHDL (cu ALU, control al memoriei și mașină de stări), plus o trecere în revistă a scripturilor de tip cross-toolchain și linking.

Ce este asamblatorul RISC-V și cum diferă de limbajul mașină?

Deși ambele sunt atașate de hardware, Limbajul mașină este pur binar (unui și zerouri) pe care procesorul o interpretează direct, în timp ce asamblatorul folosește mnemonice și simboluri mai lizibil decât un asamblator, apoi se traduce în binar.

RISC-V definește un ISA deschis cu un repertoriu de bază foarte curat. Profilul RV32I (32 biți) include 39 de instrucțiuni pentru utilizator cu ortogonalitate remarcabilă, separând accesul la memorie de calculul pur și cu suport excelent în GCC/LLVM.

Înregistrări, acorduri și punct de intrare

În RV32I aveți 32 de registre de uz general (x0–x31) 32 de biți; x0 este întotdeauna citit ca 0 și nu se poate scrie în el. Aliasuri precum a0–a7 (argumente), t0–t6 (temporare) sau s0–s11 ​​​​(salvate) sunt, de asemenea, utile pentru urmărirea ABI.

În funcție de mediu sau simulator, programul poate începe de la o anumită etichetă. În Jupiter, programele încep la eticheta globală __start., pe care trebuie să îl declarați ca vizibil (de exemplu, cu .globl) pentru a marca punctul de intrare.

Las etichetele se termină prin două puncte, poți pune o singură instrucțiune pe linie, iar comentariile pot fi începute cu # sau ; astfel încât asamblatorul le ignoră.

Instrumente și simulatoare: Jupiter și fluxul de lucru de bază

Pentru a exersa fără complicații, aveți la dispoziție Simulator/asamblor Jupiter, un instrument grafic inspirat de SPIM/MARS/VENUS care facilitează editarea, asamblarea și execuția într-un singur mediu.

În Jupiter puteți crea, edita și șterge fișiere în fila Editor. După salvare, asamblați cu F3 și rulați pentru a depana fluxul instrucțiune cu instrucțiune, folosind vizualizări de registru și memorie pentru a înțelege starea mașinii.

Programele trebuie să se termine cu un apel către mediu: setarea apelului de ieșire a0 cu codul 10 (ieșire). În RISC-V, apelurile electronice sunt echivalente cu apelurile de sistem sau capcanele către mediu/sistem.

Structura minimă a unui program și apeluri de sistem

Structura tipică din exemplele academice definește un punct de plecare, efectuează lucrarea și se termină cu un ecall. Argumentele ecall se deplasează de obicei în a0–a2 și selectorul de servicii din a7, în funcție de mediu.

Într-un singur Linux De exemplu, în RISC-V, puteți imprima cu apelul de sistem write și ieși cu codul corespunzător. Pentru scriere se utilizează a0 (fd), a1 (buffer), a2 (lungime) și a7 cu numărul de serviciuÎn final, a0 este setat la codul de retur, iar a7 la numărul de ieșire.

# 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"

Dacă lucrați în afara Linuxului, cum ar fi în simulatoare educaționale cu propriul serviciu IoT, modificați numărul ecall și registrele conform documentației de mediu.

  Obțineți fișiere ISO pentru Windows 8.1 [Configurare USB și DVD]

Exerciții inițiale pentru a te familiariza cu condiționalitățile, buclele și memoria

O încălzire tipică constă în detectarea dacă un număr întreg este negativ. Puteți returna 0 dacă este pozitiv și 1 dacă este negativ.; cu RV32I, o comparație cu 0 și o setare la o valoare mai mică decât rezolvă problema într-o singură instrucțiune bine gândită.

Un alt exercițiu foarte util este enumerarea factorilor unui număr: parcurge de la 1 la n, afișează divizorii și returnează câți au fostVei exersa ramificații condiționate, împărțirea (sau scăderea repetată) și bucle cu adunare și comparare.

Lucrul cu șiruri de caractere te obligă să gestionezi memoria: Vizitați fiecare caracter al unui șir de caractere din memorie și convertiți in situ literele mici în majuscule dacă se încadrează în intervalul ASCII. La finalizare, returnează adresa originală a șirului de caractere.

Bucle, funcții și recursivitate: factorial, Fibonacci și Turnul Hanoi

Când proiectați bucle, gândiți-vă la trei blocuri: condiție, corp și pas. Cu beq/bne/bge și salturi necondiționate, se construiesc jal/j while/for fără mister, bazându-se pe adiții și comparații.

.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

În apelurile de funcții, respectați ABI-ul: salvați dacă veți înlănțui mai multe apeluri, păstrează s0–s11 ​​​​dacă le modificați și folosește stiva cu sp care se deplasează în multipli de cuvânt.

Factorialul este recursivitatea clasică: cazul de bază n==0 returnează 1; altfel, apelați factorial(n-1) și înmulțiți cu n. Protejați ra și registrele salvate în stivă înainte de apel și restaurați-le la returnare.

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

Fibonacci este util pentru exersarea ambelor recursiune cu două apeluri ca o versiune iterativă eficientă cu variabile acumulatoare. Și dacă doriți o provocare de control al fluxului și parametrilor, traduceți o soluție în asamblator Turnurile din Hanoi cu patru argumente: discuri, sursă, destinație și turn auxiliar; respectă ordinea apelurilor și afișează fiecare mișcare.

Acces la memorie, matrice și manipulare de șiruri de caractere

În RISC-V, accesul la memorie se face cu operațiunile load/store: lw/sw pentru cuvinte, lh/sh pentru semicuvinte și lb/sb pentru octeți, cu variante cu semn sau fără semn în acuzații (lb vs lbu, lh vs lhu).

  Project Zomboid pe Android: îl poți juca pe mobil?

Pentru a traversa tablouri de numere întregi, utilizați offset-uri de 4 octeți per index; pentru șiruri de text, avansează octet cu octet până când găsește terminatorul dacă convenția o impune (de exemplu, \0). Nu uitați să salvați adresele de bază și să gestionați pointerii cu addi/auipc/la, după caz.

Proiectarea unui procesor RV32I de la zero: o prezentare generală la nivel înalt

Dacă simți că vrei să cobori până la siliciu, un proiect educațional construiește o CPU RV32I în VHDL, sintetizabil în FPGA Gamă medie-inferioară. Include memorie ROM pentru program, memorie RAM pentru date și un GPIO simplu pentru aprinderea unui LED.

Nucleul implementează repertoriul de bază (fără extensii M/A/C sau CSR-uri), folosește o magistrală de adrese pe 32 de biți și permite acces la memorie extinsă cu semn pe 8/16/32 de biți, acolo unde este cazul. Designul separă clar registrele, ALU-ul, controlerul de memorie și mașina de stare.

ALU, schimburi și ideea de „încărcare întârziată”

ALU este descrisă combinațional cu operații precum adunare, scădere, XOR, SAU, ȘI, comparații (cu semn și fără semn) și schimbări logice/aritmetice.

Pentru a salva LUT-urile în FPGA, sunt implementate deplasări pe mai mulți biți iterarea deplasărilor de 1 bit controlate de mașina de stare: consumi mai multe cicluri, dar reduci resursele logice.

În circuitele sincrone, se observă modificări pe fronturile de tact. Conceptul de „încărcare întârziată” amintește că ceea ce este selectat de un multiplexor are impact asupra registrului în ciclul următor., un aspect cheie la proiectarea mașinii de stări fetch-decode-execute.

Controler și hartă de memorie: ROM, RAM și GPIO

Un bloc de memorie integrează memoria ROM și memoria RAM într-un spațiu contiguu, simplificarea interfeței procesoruluiControlerul primește AddressIn (32 biți), DataIn, lățimea (octet/jumătate/cuvânt), semnalul de extensie a semnului, WE (citire/scriere) și Start pentru a iniția tranzacțiile.

Când operațiunea se termină, ReadyOut este setat la 1 și, dacă a fost citit, DataOut conține datele (cu semn extins la cerere). Dacă au fost scrise, datele rămân în RAM.

Un exemplu de hartă practică: ROM de la 0x0000 la 0x0FFF, un octet GPIO la 0x1000 (bitul 0 la un pin) și Memoria RAM de la 0x1001 la 0x1FFFCu aceasta puteți crea o semnalizare intermitentă prin scrierea și comutarea bitului de ieșire.

Registre, multiplexoare și mașini de stare

CPU-ul definește 32 de registre de uz general instanțiate cu matrici în VHDL, cu un decodor pentru a selecta destinația de scriere din ALU și a păstra restul.

Multiplexoarele guvernează intrările ALU (operanzi și operații), semnalele către controlerul de memorie (lățimi, adresă, control la pornire și citire/scriere) și registrele speciale: PC, IR și un contor auxiliar pentru deplasări iterative.

Mașina de stări începe cu reseta, preia instrucțiunea indicată de PC (citire pe 4 octeți), este încărcată în IR când este gata și transmite nodurilor de execuție: ALU (o instrucțiune într-un ciclu, cu excepția deplasărilor), încărcare/stocare, ramificări și salturi, pe lângă instrucțiuni speciale, cum ar fi ebreak.

  Cum se configurează Bluetooth în Windows 10

Lanț de instrumente încrucișat, conectare și depanare

Pentru a genera fișiere binare RV32I, utilizați un Cross GCC (țintă riscv32-none-elf)Compilezi surse C/C++/ASM, creezi legătura cu un script care definește harta memoriei și converti ieșirea în forma așteptată de memoria ROM/FPGA.

Un script simplu de tip hook poate plasa .text în ROM de la 0x0000 și .data în RAM de la 0x1004 (dacă 0x1000–0x1003 este ocupat de registre GPIO). Rutina de pornire poate fi „goală” și poate plasa indicator de stivă la sfârșitul memoriei RAM (de exemplu, 0x1FFC) înainte de a apela main.

/* Mapa simple
 * ROM: 0x00000000 - 0x00000FFF
 * GPIO: 0x00001000 - 0x00001003
 * RAM: 0x00001004 - 0x00001FFF
 */
SECTIONS {
  . = 0x00000000;
  .text : { *(.startup) *(.text) *(.text.*) *(.rodata*) }
  . = 0x00001004;
  .data : { *(.data) *(.data.*) }
}

Cu riscv32-none-elf-objdump poți dezasamblați ELF-ul și verificați adresele; de exemplu, veți vedea cizmă la 0x00000000 cu instrucțiuni precum lui/addi/jal și tranziția către fișierul principal. Pentru simularea VHDL, GHDL generează urme pe care le puteți deschide cu GtkWave.

După verificarea în simulare, transferați proiectul într-un FPGA (Quartus sau alt lanț de instrumente). Dacă memoria RAM este dedusă ca blocuri interne și codul este RTL clar, ar trebui să sintetizezi fără surprize, chiar și pe dispozitive veterane.

Mementouri practice și greșeli tipice la început

Nu uita asta x0 este întotdeauna zero; scrierea în acesta nu are niciun efect, iar citirea returnează 0. Folosește acest lucru în avantajul tău în adunări, comparații și curățări de registry.

Când implementați funcții, salvați înregistrările ra și sN pe care le modificațiși gestionează stiva cu adunări/scăderi aliniate la cuvinte către sp. La revenire, restaurează în ordine inversă și sare cu jr ra.

În simulatoare precum Jupiter, verificați dacă __start este global și îl închei cu ecall corect (a0=10 pentru ieșire). Dacă ceva nu pornește, verificați eticheta, globalitatea și recompilați (F3).

În exercițiile cu IO, respectați protocolul mediului: ce registre poartă parametri, numărul de serviciu și dacă se așteaptă o adresă sau o valoare imediată. Folosiți documentația simulatorului sau a sistemului de operare.

Cu o bază ISA clară (RV32I, registre și ABI), un simulator confortabil precum Jupiter și exemple crescânde (negative, factori, majuscule, bucle, factoriale, Fibonacci și Hanoi), asamblatorul RISC-V încetează să mai fie un zid și devine un teren suculent pentru a înțelege cum gândește un procesor. Și dacă îndrăznești să treci la VHDL, vei vedea cum ALU, memoria și controlul se potrivesc.de la preluarea instrucțiunilor și încărcarea lentă la interfețe de memorie și o hartă cu ROM, RAM și GPIO care vă permite să clipiți un LED cu propriul procesor.

Cele mai bune programe de programat
Articol asociat:
Cele 7 cele mai bune programe de programat