RISC-V е архитектура с комплект процесорни инструкции (ISA), която или харесват, или ненавиждат, а в последно време започна да се проявява известно съперничество между лагерите на ARM и RISC-V.
Вероятно има някакви причини за появата на някаква нова надпревара. От друга страна, RISC-V и ARM са две съвсем различни философии на това, как трябва да бъде проектиран един RISC чип. RISC-V отдавна акцентира върху простотата на използване и не се стреми към краткосрочни печалби , които биха могли да доведат до дългосрочни проблеми. RISC-V плътно се придържа към RISC философията – всичко трябва да е съвсем логично не само от гледна точка на минималния брой процесорни команди, но и самите процесорни инструкции трябва да са максимално опростени.
ARM е по-скоро един безжалостен и прагматичен дизайн. Решенията в тази архитектура се вземат на базата на това, което има смисъл към днешен ден, както и в близкото бъдеще, в зависимост от актуалните хардуерни възможности. ARM не избягва добавянето на сложни процесорни команди, ако е преценено, че те подобряват общата производителност.
В резултат от всичко това в ARM има редица процесорни инструкции, които вършат много работа. В ARM има различни команди за сложно адресиране, както и инструкции за условно изпълняване (само при 32-битовата ARM архитектура). Тези блокове от инструкции се изпълняват само ако се изпълни някакво условие и не предизвикват разклонения и зацикляне.
Както ARM, така и RISC-V си имат своите достойнства и недостатъци. А когато става дума за тяхното разглеждане и критикуване, по-добре е да бъдат показани конкретни примери и факти, а не някакви заблуждаващи изказвания. Относно RISC-V има немалко заблуди и митове, както тук ще покажа само най-популярните, с които съм се сблъсквал в моята работа.
Първи мит: RISC-V „раздува“ размера на програмите
Взето като средно, една RISC-V процесорна команда върши по-малко работа в сравнение с ARM. Така например, в ARM е добре позната инструкцията LDR за зареждането на данни от паметта в един от регистрите на процесора. Тя е специално създадена за следния съвсем типичен C/C++ код:
// C/C++ code
int a = xs[i];
Подобен код се използва ако например искаме да заредим данни от масива xs по индекса i. Този C/C++ код в ARM архитектурата може да се реализира чрез регистъра x1, в който е поставен началния адрес на масива xs, а в регистъра x2 се намира индекса i. Необходимо е само да пресметнем изместването спрямо началния адрес. За масив от 32-битови цели числа това съответства на умножението на индекса i на 4, като по този начин се пресмята изместването в байтове. Съвсем същото, отново при ARM архитектурата, може да бъде постигнато чрез двойно логическо изместване на регистъра x2 два пъти наляво:
x1 ← mem[x1 + x2<<2]
В случая mem съответства на основната памет. Ние използваме x1, x2, както и логическо изместване, равносилно на деление на 2, 4 и 8, за да получим желания адрес. Цялата процесорна инструкция изглежда по следния начин:
; ARM 64-bit code
LDR x1, [x1, x2, lsl #2] ; x1 ← mem[x1 + x2<<2]
При RISC-V архитектурата същото се постига с помощта на цели три инструкции (# показва началото на коментар):
# RISC-V code SLLI x2, x2, 2 # x2 ← x2 << 2 ADD x1, x1, x2 # x1 ← x1 + x2 LW x1, x1, 0 # x1 ← mem[x1 + 0]
На пръв поглед изглежда, че ARM има огромно преимущество пред RISC-V, понеже кодът е три пъти по-плътен. Това означава по-малко използване на кеша и по-висока производителност на контейнера за процесорни инструкции.
Компресираните инструкции
Само че RISC-V поддържа компресирани процесорни инструкции чрез добавянето в чипа на само 400 логически вериги от типа AND, OR, NAND. По този начин, две от най-често използваните инструкции могат да се поберат в една 32-битова дума. Още по-хубаво е, че тази компресия не добавя каквато и да било латентност – това съвсем не е zip алгоритъма. Декомпресията се осъществява едновременно със стандартното декодиране на процесорните инструкции – тоест, мигновено.
# RISC-V code C.SLLI x2, 2 # x2 ← x2 << 2 C.ADD x1, x2 # x1 ← x1 + x2 C.LW x1, x1, 0 # x1 ← mem[x1 + 0]
За компресираните инструкции с пълна сила важи Законът на Парето:
20% от процесорните инструкции използват 80% от времето
Това всъщност не бива да се приема съвсем буквално. Създателите на RISC-V много внимателно са подбрали инструкциите, които са включени в комплекта с компресирани инструкции.
По този начин RISC-V с лекота превъзхожда ARM по плътност на кода.
Всъщност, съвременните ARM чипове могат да работят в два режима: или в 32-битов режим (AArch32) за обратна съвместимост с предишния софтуер, или 64-битов режим (AArch64). В 32-битовия режим ARM чиповете поддържат компресираните инструкции Thumb.
Уточнение: не бива да се обърква 64-битовия режим с дългите инструкции. И при 64-битовия режим дължината на една процесорна инструкция е 32 бита. Смисълът на 64-битовия режим при ARM е възможността да се работи с 64-битови регистри. При 64-битовия режим комплекта процесорни инструкции е друг и е съвсем различен, и е необходимо ясно да се указва в какъв точно режим се работи.
Това различие не е толкова важно при RISC-V архитектурата, където 32-битовият набор команди (RV32I) и 64-битовия са почти идентични. Причината е, че дизайнерите на RISC-V предварително са предвидили 32-битови, 64 и дори 128-битови архитектури при разработването на RISC-V ISA.
Да се върнем към компресираните RISC-V инструкции. Въпреки че по-малкото използване на памет от страна на процесорните инструкции е добре за кеша, това съвсем не решава всички проблеми. Но са налични немалък брой процесорни команди за декодиране, за запис на резултатите и други подобни. В този случай се използва макро-оп синтез (macro-op fusion).
Намаляване броя на инструкциите чрез макро-оп синтез
Макро-оп синтезът преобразува няколко инструкции в една. RISC-V специалистите набелязват характерните шаблони от код, които компилаторите трябва да заместват, за да може ефективно да се използва macro-op fusion. Едно от изискванията е тези инструкции да използват един същ регистър, в който се запазва резултатът. В нашия пример двете инструкции ADD и LW записват резултата в регистъра x1 и е възможно тяхното обединяване в една обща команда, което се извършва от хардуерния декодер на инструкциите.
Забележки относно макро-оп синтеза
Безплатен обяд няма. За осъществяването на macro-op fusion е необходимо добавянето на повече транзистори в декодерите на инструкции. Необходима е и съвместната работа с разработчиците на компилатори с цел те да забелязват и генерират необходимите шаблони код, които могат да бъдат сливани. Днес разработчиците на RISC-V се занимават именно с тази задача. Понякога няма как да се използва макро-оп синтез например, заради достигане границите на кеша (cache-line). Но тези проблеми са актуални и за x86 процесорите, които имат инструкции с произволна дължина.
Втори мит: Инструкциите с променлива дължина усложняват паралелното декодиране на инструкциите
В света на х86, една процесорна инструкция би могла да има безкрайна дължина, въпреки че от чисто практическа гледна точка тя е ограничена до 16 байта. Това усложнява разработването на суперскаларни процесори, които паралелно и едновременно декодират по няколко процесорни инструкции. Защо? Банално, след като получите например, 32 байта код, няма откъде да знаете откъде точно започва дадена инструкция. Решаването на този проблем изисква използването на твърде сложни методи, които водят да значително повече цикли при декодирането.
Според редица специалисти, използваните от Intel и AMD методи се базират на натрупаната статистика от прилагането на грубия метод на пробите и грешките, при които се правят многобройни предположения къде започва и къде свършва дадена инструкция.
При RISC-V не е така. Допълнителната сложност, идваща от компресираните инструкции е съвсем тривиална. Специално подбраните за тази цел инструкции винаги са подравнени към 16 бита. Това означава, че всеки един 16-битов блок е или началото на една 16/32-битова инструкция или края на 32-битова инструкция.
Този прост факт може да се използва за създаването на най-различни методи за паралелно декодиране на RISC-V процесорните инструкции. Въпреки, че аз не съм дизайнер на чипове, все пак мога да измисля схема за постигането на тази цел. Така например, възможно е използването на декодери за всеки 16-битов блок, както е показано по-долу.
След това всеки декодер получава своя първи код от някой свързан 16-битов блок. По този начин първата част от инструкцията за декодера D2 ще бъде взета от блока команди B2. А втората част – от B3, ако става дума за 32-битова инструкция.
При декодирането, декодерът определя дали е получил 16-байтова или 32-байтова инструкция и решава дали да използва втория входен поток от следващия блок. Важна забележка: всеки декодер отделно решава дали да използва и втория 16-битов блок от инструкции, като прави това независимо от другите декодери, Това означава, че този процес може да протича паралелно.
За сравнение, нямам никаква представа как може да се декодират х86 процесорните инструкции, които могат да бъдат с дължина от 1 до 15 байта. Това е наистина много по-сложна задача.
Самите създатели на RISC-V архитектурата заявяват, че това не е сложен проблем. Това, което аз описах, е само опит да обясня, че това не е сложно, от гледна точка на непрофесионалиста. Не се зацикляйте твърде много върху предложеното от мен решение – очевидно е, липсват редица детайли. Така например, явно е необходима някаква цифрова логика за превключване на мултиплексорите.
Трети мит: липсата на условно изпълнение е грешка
В 32-битовия ARM код (ARMv7 и по-ранни версии) има условни инструкции (conditional instructions). Вземаме една обикновена процесорна инструкция от рода на LDR (зареждане в регистъра) или ADD, след което следим дали се е получило EQ при равенство и NE за неравенство. Готово, получаваме съответните условни инструкции LDREQ и ADDNE.
; ARM 32-bit code CMP r6, #42 LDREQ r3, #33 ; r3 ← 33 if r6 = 42 LDRNE r3, #12 ; r3 ← 12 if r6 ≠ 42 CMP r3, #8 SUBS r3, r6, #42 ADDEQ r3, #33 ; r3 ← r3 + 33 if r6 - 42 = 0 ADDNE r3, #12
Инструкцията CMP вдига един от флаговете, които се проверяват при изпълнението на условните инструкции. Да подчертаем, че тези инструкции се изпълняват само ако условието е изпълнено.
При RISC-V не се използват условни инструкции. По този начин значително се затруднява реализацията на Out-of-Order Execution (OoOE), а това е много важно за създаването на високопроизводителни чипове. Всъщност нито един от съвременните 64-битови ARM процесори (ARMv8 и нагоре) нямат условно изпълнение именно поради тази причина. Там присъстват само безусловните инструкции CSEL, CSINC.
Край на първа част. Във втората част ще продължим с условните инструкции, ще се спрем върху векторната обработка, целочисленото препълване и други характерни технологии на RISC-V процесорната архитектура.
Всичко важно от света на технологиите, директно в пощата ти.
С абонирането приемате нашите Условия и Политика за поверителност. Може да се отпишете с един клик по всяко време.
Коментирайте статията в нашите Форуми. За да научите първи най-важното, харесайте страницата ни във Facebook, и ни последвайте в Google Новини, TikTok, Telegram и Viber или изтеглете приложението на Kaldata.com за Android, iPhone, Huawei, Google Chrome, Microsoft Edge и Opera!






