Реактивное введение в программирование Game Boy Advance (часть 6 из 8)

Прерывания, DMA…



Оглавление


Часть 1: Инструментарий, основы, кнопки, таймеры
Часть 2: Пиксельные видеорежимы
Часть 3: Тайловые видеорежимы
Часть 4: Спрайты
Часть 5: Вращающиеся фоны и прозрачность
Часть 6: Прерывания, DMA
Часть 7: Звук (Direct Sound)
Часть 8: Сохранения

8. Прерывания


Как и в практически любом компьютере в GBA существует механизм и аппаратных и программных прерываний. Через программные прерывания можно вызывать некоторые процедуры из BIOS, что мы пока не будем охватывать здесь. Но более важно то, что по наступлению какого-то внешнего события аппаратура GBA может прервать выполнение основной программы и вызвать так называемый обработчик прерываний. Более того — даже обработчик прерываний может быть в свою очередь тоже прерван другим прерыванием. Состояние процессора на момент возникновения прерывания запоминается и после отработки обработчика ход выполнения программы восстанавливается ровно на той операции где он был прерван. Генерировать прерывания может множество событий: нажатие кнопки, начало интервала VBlank видеочипа, переполнение таймера и т.п. Вообще одноядерные и однопроцессорные системы (как GBA) чаще всего реализуют многопоточность именно посредством прерываний — по наступлению прерываний от таймера ядро системы переключает ход выполнения на очередной процесс, запомнив состояние предыдущего со стека прерываний. Нам эти сложности тут не нужны, но сам механизм прерываний пригодится. Генерация аппаратного прерывания изначально приводит к запуску кода, находящегося в ROM BIOS-а консоли — этот код нам недоступен, но он в свою очередь вызывает функцию адрес которой находится по адресу 0x3007FFC — т.е. в самом последнем слове IW_RAM. Здесь же, в самом конце IW_RAM так же выделено место под стек на который переключается процессор во время исполнения обработчика прерываний и этого места очень мало! Всего лишь около 160-180 байт на всё, поэтому обработчик прерывания должен содержать минимум локальных переменных! Несоблюдение этого может приводить к зависанию консоли. Так же разумно первым действием в обработчике запрещать генерацию прерываний, а при выходе разрешать обратно (хотя иногда такое может привести к нежелательному игнорированию случившихся прерываний).
Еще одно очень важное замечание касается режима работы процессора. Процессор ARM GBA может работать с двумя видами инструкций — Arm и Thumb. Arm это изначальная система команд архитектуры RISC в которой каждая команда занимает 32 бита (4 байта) и биты в ней разложены удобным для декодера инструкций образом. Например абсолютно любая команда может быть условной — в любой из них в одном и то же месте лежат биты условного выполнения, просто одна из их комбинаций означает «выполнять всегда». Это действительно позволяет сократить набор инструкций (RIS это Reduced Instruction Set, т.е. «сокращенный набор инструкций»), но так же приводит к менее «плотному» коду. Особенно нехорошо это сказывается на машинах как раз навроде GBA — где только IW_RAM обладает 32-битной шиной, а вся остальная память, в том числе память программ на картридже читается и записывается через 16-битную шину — считывание даже одной инструкции требует двух считываний. С целью повысить плотность кода и был разработан набор инструкций Thumb, который выступает в роли компромисса — команды в этом наборе инструкций 16-битные, что правда далось ценой меньшей их гибкости и сокращения числа доступных коду регистров. Еще чуть позже расширение Thumb-2 (которого у GBA нет) поборолось и с этим недостатком — в нём уже команды могут обладать переменной длиной — 16 или 32 бита, что и повысило плотность кода, но и несильно урезало его гибкость в случае чего. Режим Thumb является предпочитительным для GBA в связи с расположением кода в ПЗУ с 16-битной шиной данных. Поэтому мы указываем ключ компиляции у gcc -mthumb — это как раз включение генерации кода в системе команд Thumb. Если его не указать или указать ключ -marm, то компиляция произойдет в инструкции Arm. Кроме того возможно скомпилировать разные модули программы в разных режимах и после слинковать их в рабочую программу, но для этого надо указывать еще один ключ -mthumb-interwork во время компиляции всех таких модулей, что мы тоже всегда делали «на всякий случай».
И вот сейчас такой случай настал — дело в том, что генерация прерывания переключает процессор в режим Arm и обработчик прерываний должен быть скопилирован в таком режиме. И действительно можно было сделать новый модуль с функций void arm_intr_handler(void), скомпилировать его в режиме Arm, получить файл имя_модуля.o и указатель его в команде компиляции основной программы так же как мы указывали fixed.cpp. Это действительно работает. Но я решил использовать другой метод — указание режима Arm специальными ключевыми словами gcc для одной конкретной функции.

В первую очередь, чтобы включить обработку аппаратных прерываний надо записать 1 в самый младший (нулевой) бит регистра REG_IME (Interrupt Master Enable). Одним этим битом можно отключить обработку всех прерываний, что удобно использовать для временного их запрета в том же обработчике прерываний или в коде меняющем параметры обработки прерываний.
Во вторую очередь в регистре REG_IE нужно включить (выставить в 1) конкретный бит для того чтобы процессор стал реагировать на конкретный вид прерывания — для клавиатуры — один, для VBlank — другой и т.д.
Но кроме включения реакции ЦП на конкретное прерывание нужно еще включить его генерацию соответствующим устройством в составе GBA. Например генерацию прерывания INT_VBLANK, которое наступает в начале входа видеочипа в состояние VBlank, надо включать в регистре REG_DISPSTAT битом VBLANK_IRQ_ENABLE.
Только включив генерацию прерывания (в соответствующем регистре соответствующего устройства), реакцию на прерывание (в REG_IE) и разрешив прерывания (в REG_IME) мы начнём их получать в виде вызовов функции адрес которой находится в конце IW_RAM (ниже мы присвоим этой ячейке памяти имя REG_INTERRUPT). Эта функция не принимает параметров и ничего не возвращает (сигнатура void(void)), поэтому чтобы понять какое прерывание произошло оно должно проверять еще один регистр — REG_IF. В REG_IF включены такие же биты маскок прерываний как в REG_IE, но только один битовый флаг взводится системой прежде чем входить в обработчик прерывания, два флага не богут быть одновременно включены. Важно то, что обработчик прерывания должен сбросить флаг записью 1 (единицы!) в соответствующий бит, чтобы просигнализировать об успехе обработки прерывания. Это надо сделать обязательно, иначе система пойдет вразнос. Такое странное соглашение может быть выполнено одной строчкой REG_IF = REG_IF на выходе из обработчика.
Опишем всё вышенаписанное макросами в gba_defs.h:


// *** Регистр общего включения прерываний
#define REG_IME         (*((volatile u16 *) 0x4000208))
// *** Регистр включения/выключения реакции ЦП на конкретные виды прерываний
#define REG_IE          (*((volatile u16 *) 0x4000200))
// *** Регистр текущего выполняемого прерывания
#define REG_IF          (*((volatile u16 *) 0x4000202))
// Адрес процедуры обработки прерывания
typedef void (*intr_handler_type)(void);
#define REG_INTERRUPT       (*((volatile intr_handler_type *) 0x3007FFC))
// Маски видов прерываний в регистрах REG_IE и REG_IF
#define INT_VBLANK 0x0001
#define INT_HBLANK 0x0002
#define INT_VCOUNT 0x0004
#define INT_TIMER0 0x0008
#define INT_TIMER1 0x0010
#define INT_TIMER2 0x0020
#define INT_TIMER3 0x0040
#define INT_COM    0x0080
#define INT_DMA0   0x0100
#define INT_DMA1   0x0200
#define INT_DMA2   0x0400
#define INT_DMA3   0x0800
#define INT_BUTTON 0x1000
#define INT_CART   0x2000


Теперь мы готовы сделать очень простую программу, которая выведет один текстовый фон и через прерывание от видеочипа по входу в фазу VBlank будет инкрементировать индекс первого тайла. Это будет происходить автоматически с частотой ~60 раз в секунду и как раз в моменты когда видеоадаптер «отдыхает», поэтому рабочий цикл будет полностью пуст, не нужна даже синхронизация по VSync.
08_intrs.cpp:

#include "string.h"
#include "gba_defs.h"

// Начало тайловых данных для фона
#define TILE_DATA   ((u16*) (VID_RAM_START + 0x0000) )
// Начало тайловой карты
#define TILE_MAP0   ((u16*) BG_TILE_MAP_ADDR( 16 ) )

// Обработчик прерываний.
// Атрибут GCC __target__ ("arm") переключает компилятор в
// создание кода функции в режиме Arm даже внутри модуля
// в котором режим компиляции по умолчанию - Thumb.
void __attribute__ ((__target__ ("arm"))) intr_handler(void)
{
	// Выключим прерывания
	REG_IME = 0;
	// Запомним входной флаг
	u16 enter_IF = REG_IF;
	// Проверяем, что прерывание равно INT_VBLANK
	// (сразу несколько флагов одновременно быть не может
	// так что это могло бы быть простое сравнение)
	if ( enter_IF & INT_VBLANK )
	{
		// Инкрементируем 60 раз в секунду
		// первый тайл текстового фона BG0 по модулю 255.
		int ti = TILE_MAP0[ 0 + 32 * 0 ];
		TILE_MAP0[ 0 + 32 * 0 ] = (ti + 1) & 255;
	};
	// Сбросим входной флаг (записью 1!)
	REG_IF = enter_IF;
	// Включим прерывания
	REG_IME = 1;
};

int main(void)
{
	// Инициализируем палитру так, чтобы 8-битный индекс цвета 
	// совпадал с цветовой маской BBGGGRRR
	for ( unsigned int i = 0; i < 256; i++ )
	{
		BGR_PALETTE[ i ] = RGB( (i & 7) << 2, (i & 56) >> 1, (i & 192) >> 3 );
		SPR_PALETTE[ i ] = RGB( (i & 7) << 2, (i & 56) >> 1, (i & 192) >> 3 );
	};

	// Очистим видеопамять
	memset( (void*) VID_RAM_START, 0, VID_RAM_SIZE );

	// Включаем MODE_0 и BG0
	REG_DISPCNT = MODE_0 | BG0_ENABLE;

	// Параметры фона BG0
	REG_BG0CNT =	BG_PRIORITY( 2 ) | BG_TILE_DATA_AT_0000 | BG_COLOR_256 | 
			BG_TILE_MAP_AT( 16 ) | BG_SIZE_256x256;

	// Заполним 256 тайлов фона и спрайтов специальным образом
	// (помним, что запись в видепамять должна производится
	// минимум 2-байтными порциями, поэтому циклы подчинены
	// этой логике)
	for ( int i = 0; i < 256; i++ )
	{
		// сперва зальём весь тайл белым цветом
		u16 *bg_tile_base = TILE_DATA + i * 8 * 4;
		for ( int j = 0; j < 8 * 4; j++ )
		{
			bg_tile_base[ j ] = (255 << 8) + (255);
		};
		// выведем в верхней половине столько зеленых пикселей
		// чему равен первый разряд индекса как 16-чного числа
		int cnt = i & 15;
		for ( int j = 0; j < cnt; j++ )
		{
			bg_tile_base[ j ] = (255 << 8) + (48 + j);
		};
		// выведем в нижней половине столько красных пикселей
		// чему равен второй разряд индекса как 16-чного числа
		bg_tile_base = TILE_DATA + i * 8 * 4 + 16;
		cnt = (i >> 4) & 15;
		for ( int j = 0; j < cnt; j++ )
		{
			bg_tile_base[ j ] = (255 << 8) + (192 + j);
		};
	};
	// Заполним тайловую карту нарастающими индексами, но
	// не больше 255.
	int d = 0;
	for ( int y = 0; y < 32; y++ )
	{
		for ( int x = 0; x < 32; x++ )
		{
			TILE_MAP0[ x + 32 * y ] = d & 255;
			d += 1;
		};
	};

	// Запускаем обработку прерываний
	REG_IME = 0;			// Выключим прерывания
	REG_INTERRUPT = &intr_handler;	// Установим обработчик
	REG_IE |= INT_VBLANK;		// Включим реакцию на INT_VBLANK
	REG_DISPSTAT |= VBLANK_IRQ_ENABLE;	// Включим генерацию INT_VBLANK
	REG_IME = 1;			// Включим прерывания

	// Бесконечный цикл
	while ( true )
	{
	};

	return 0;
}

В build_08_intrs.bat описываем всё как в первых уроках.

Немного о «модели вычисления» для прерываний: прерывание может случится в любой точке кода основной программы, но не «внутри машинной инструкции», т.е. на входе в прерывание основной поток вычислений «замирает» где-то между двумя инструкциями кода. Поэтому можно сказать, что любая запись или чтение примитивных типов данных в память атомарны, но код прерывания должен иметь ввиду, что в вычисление выражения a=b+1 основного потока он может вклинится «посередине» между чтением b и записью a. Так как процессор и его ядро у нас единственные, то не нужно так же беспокоиться о таких тонких материях настоящего многопоточного программирования как read/write reordering в конвеере процессора (да и сам конвеер тут простенький). Однако определенную проблему представляют оптимизации производимые компилятором — если не дать ему подсказок о том, что та или иная переменная будучи изменена в игровом цикле потом используется в обработчике прерывания, то он может ложно решить, что она нигде не используется. Таким образом можно получить отбрасывание целых кусков кода и получить непонятные глюки на ровном месте. Выключать оптимизацию не надо, но надо давать компилятору необходимые подсказки: прежде всего помечать ключевые переменные синхронизации между основной программой и прерыванием ключевым словом volatile, а в некоторых случаях даже использовать барьеры памяти (memory barriers), о которых я расскажу в десятой главе. В качестве сильного средства синхронизации со стороны основного потока программы может служить запрет прерываний посредством записи 0 в REG_IME, так как после выполнения этой записи прерывания уже не могут случится, это позволяет считать и записать нужные синхронизирующие переменные так как это требуется (возможно с применением барьеров памяти), после чего в REG_IME записывается 1 и прерывания могут вызываться снова.
Очень маленький стек и, как следствие, общий размер локальных переменных обработчика прерываний не позволяет нам (по крайней мере без существенных переделок) переносить в него существенное количество логики программы. Кроме того, как оказалось из чтения литературы, обработка нажатия кнопок посредством прерываний тоже не практикуется на GBA — как я понял это каким то образом связано с тем, что таким образом системой обрабатывается выход из спящего режима и игровой код уже прерываниями от кнопок пользоваться не должен (поэтому я не буду описывать даже управляющие ими регистры). Правильный же цикл обработки нажатия кнопок в GBA всё таки должен привязываться к VBlank-у любым образом и от этого плясать — всё равно человек неспособен генерировать нажатия чаще 60 раз в секунду, так что это вполне нормально и во всех играх делается как правило именно так. Таким образом, в сущности, прерывания нам оказываются почти что не нужны, кроме одной задачи — генерации звука и звуковых эффектов. Эта самая генерация звука осуществляется на стыке трёх вещей — таймеров (которые мы уже изучали), прерываний (которые мы уже изучили) и DMA, который мы сейчас изучим.

9. DMA


DMA (Direct Memory Access) или «прямой доступ к памяти» по классическому определению это чип, передающий данные между периферийными устройствами и основной памятью компьютерной системы без участия центрального процессора. DMA в GBA представляет собой чип, который способен скопировать блок памяти по системной шине из одного места в другое. Копирование тут происходит память-память, но т.к. порты ввода-вывода в виде «регистров» отображены на память тоже, то действительно возможно таким образом передавать данные в или из периферийных устройств. Существует 4 независимых канала DMA (от 0 до 3), т.е. возможны 4 таких передачи… но не совсем одновременно. Во первых — DMA в активной фазе (копирования) подавляет работу процессора и тот приостанавливается на это время. Во вторых — DMA-каналы с меньшими номерами так же подавляют DMA-каналы с большими номерами, так образом только один DMA-канал может быть активным в один момент времени, исключая при этом работу ЦП и других каналов. Преимущества DMA-контроллера в копировании памяти заключаются в том, что он заточен на цикл копирования и в его процессе не извлекает (читай — занимает шину считыванием) инструкции из той же памяти, поэтому теоретически разумно копировать большие блоки памяти (memcpy) посредством DMA. Однако судя по литературе скорость DMA в GBA нередко переоценивается — выигрыш в сравнении с оптимизированным кодом составляет не «в разы», но десятки процентов, а на задаче «заливки» DMA может даже немного, но проигрывать ЦП. Я обойду полевые сравнения производительности вниманием, DMA нам здесь понадобится только для одного из способов воспроизведения звука.
Каждый из DMA-каналов имеет следующие настраиваемые параметры (буквой X обозначается номер канала 0-3):
  • Адрес откуда копировать (DMAXSAD, source address), 32-битный адрес, но значащих бит меньше, чем 32. Для канала DMA0 значащих бит всего 27, это значит что первый (самый старший) байт адреса не может превышать 0x07, т.е. адрес источника канала DMA0 не может находится в банке памяти ROM_START или выше. У остальных каналов значащих бит в этом адресе 28, т.е. старший байт адреса не может превышать 0x0F, таким образом они могут производить копирование из ROM картриджа.
  • Адрес куда копировать (DMAXDAD, destination address), 32-битный адрес, но тут всё обстоит наоборот — у всех каналов значащих бит в нём 27, т.е. копировать ими можно только в память ниже ROM картриджа (что, в общем то, логично) и только DMA3 имеет 28-битный этот адрес «назначения». Копировать в ROM всё-равно нельзя, на то он и ROM, но на картридже может располагаться Flash-память для сохранения игр и вот здесь DMA3 может оказаться полезным.
  • Число элементов к копированию (DMAXCNT_L), 16-битное число, но у первых трёх каналов только 14 бит его являются значащими. При этом нулевое значение означает не 0, а на 1 больше, чем максимум (как бы максимум с переполнением, т.е. 16384 для DMA0-2 и 65536 для DMA3). Копирование происходит или 16-битными (2-байтными) или 32-битными (4-байтными) элементами, таким образом даже при 14 значащих битах максимальный размер копируемого блока составляет 65536 байт. В случае же DMA3 максимальный размер копируемого блока составляет 262144 байта.
  • Управляющие флаги (DMAXCNT_H). Иногда этот 16-битный порт соединяют с DMAXCNT_L в один 32-битный порт DMAXCNT, что объясняет принятые нами названия. Здесь содержится ряд флагов, управляющих режимом работы порта: бит включения канала, размер передачи (16 или 32 бита), оставлять ли неизменными или инкрементировать или декрементировать адреса источника и приёмника (каждый контролируется по отдельности), генерировать ли прерывание по завершению передачи и режим активации канала. Самый простой режим активации канала — когда бит включения переводится из 0 в 1. Но все каналы можно перевести в режим активации при наступлении VBlank или HBlank, а у некоторых каналов есть «особые режимы», которые и используются в связке со звуковым чипом.
Опишем все вышенаписанное кодом в gba_defs.h:

// *** Адрес источника (source)
// DMA0 не может адресовать выше ROM_START (остальные могут)
#define REG_DMA0SAD     (*((volatile u32 *) 0x40000b0))
#define REG_DMA1SAD     (*((volatile u32 *) 0x40000bc))
#define REG_DMA2SAD     (*((volatile u32 *) 0x40000c8))
#define REG_DMA3SAD     (*((volatile u32 *) 0x40000d4))

// *** Адрес приёмника (destination)
// DMA3 может адресовать выше ROM_START (остальные не могут!)
#define REG_DMA0DAD     (*((volatile u32 *) 0x40000b4))
#define REG_DMA1DAD     (*((volatile u32 *) 0x40000c0))
#define REG_DMA2DAD     (*((volatile u32 *) 0x40000cc))
#define REG_DMA3DAD     (*((volatile u32 *) 0x40000d8))

// *** Число элементов (16 или 32-битных) к передаче (count)
// Счётчик DMA3 16-битный, остальные 14-битные. Значение 0 
// соответствует "максимуму" (65536 для DMA3 и 16384 для остальных).
#define REG_DMA0CNT_L       (*((volatile u16 *) 0x40000b8))
#define REG_DMA1CNT_L       (*((volatile u16 *) 0x40000c4))
#define REG_DMA2CNT_L       (*((volatile u16 *) 0x40000d0))
#define REG_DMA3CNT_L       (*((volatile u16 *) 0x40000dc))

// *** Регистр управления каналом (см. флаги ниже)
#define REG_DMA0CNT_H       (*((volatile u16 *) 0x40000ba))
#define REG_DMA1CNT_H       (*((volatile u16 *) 0x40000c6))
#define REG_DMA2CNT_H       (*((volatile u16 *) 0x40000d2))
#define REG_DMA3CNT_H       (*((volatile u16 *) 0x40000de))

// *** Флаги регистров управления:
// Биты 5-6: режим шага адреса приёмника:
// INCR - увеличение, DECR - уменьшение, FIXED - фиксированный адрес,
// INCR_RELOAD - имеет смысл для режима автоповтора (REPEAT) -
//   DMA-контроллер при повторе будет перезагружать внутренний
//   адрес приёмника содержимым этого регистра.
#define DMA_DEST_INCR       0x0000
#define DMA_DEST_DECR       0x0020
#define DMA_DEST_FIXED      0x0040
#define DMA_DEST_INCR_RELOAD    0x0060
// Биты 7-8: режим шага адреса источника
// Нет опции аналогичной DEST_INCR_RELOAD!
#define DMA_SRC_INCR        0x0000
#define DMA_SRC_DECR        0x0080
#define DMA_SRC_FIXED       0x0100
// Бит 9: режим автоповтора
#define DMA_REPEAT      0x0200
// Бит 10: размер передаваемых элементов
#define DMA_16BIT       0x0000
#define DMA_32BIT       0x0400
// Бит 11: (только для DMA3) GamePack DRQ (?)
#define DMA_GAMEPACK_DRQ    0x0800
// Биты 12-13: момент запуска DMA-передачи:
// IMMEDIATE - сразу после перехода бита включения (DMA_ENABLE) из 0 в 1
// VBLANK    - при входе в VBlank
// HBLANK    - при входе в HBlank
// SPECIAL   - для DMA1 и DMA2 - "подпитывание" звуковых FIFO-буферов,
//             для DMA3 - видеозахват
#define DMA_START_IMMEDIATE 0x0000
#define DMA_START_VBLANK    0x1000
#define DMA_START_HBLANK    0x2000
#define DMA_START_SPECIAL   0x3000
// Бит 14: генерировать прерывание при завершении копирования
#define DMA_IRQ_ENABLE      0x4000
// Бит 15: бит включения DMA-канала
#define DMA_ENABLE      0x8000


Регистры адресов и числа элементов никогда не изменяются DMA-контроллером, они только содержат начальные значения для внутренних (невидимых программисту) регистров контроллера. Загрузка этих начальных значений происходит при следующих событиях:
а) Бит DMA_ENABLE меняется из 0 в 1 — в этот момент заполняются все внутренние регистры контроллера.
б) Если установлен бит DMA_REPEAT и происходит повторная активация DMA-передачи, то перезагружается начальное значение из регистра числа элементов (CNT_L) и, если установлен режим DEST_INCR_RELOAD, то перезагружается начальное значение из DAD.
Если бит DMA_REPEAT не установлен, то после окончания передачи контроллер сам сбрасывает бит DMA_ENABLE. В противном случае передача будет инициироваться по событию запуска пока этот бит не будет сброшен извне.
Режимы загрузки/сохранения во Flash на картридже (GAMEPACK_DRQ) и видеозахвата (START_SPECIAL) для DMA3 и плохо описаны в литературе и неинтересны нам тут.
А вот режим «подпитывания» звуковых FIFO-буферов (START_SPECIAL) у каналов DMA1 и DMA2 нас интересует. В этом режиме должен быть так же установлен бит DMA_REPEAT. Адрес назначения должен быть установлен или в 0x040000A0 (FIFO_A) или в 0x040000A4 (FIFO_B) — это регистры буферов PCM-каналов звукового чипа. При поступлении прерывания от звукового чипа DMA-канал производит передачу четырёх 32-битных значения, то есть 16 байт суммарно. Содержимое CNT_L и бита DMA_16/32BIT игнорируется в этом режиме, а так же адрес назначения не изменяется несмотря на биты DMA_SCR_*.
Зная всё вышенаписанное мы наконец то готовы к изучению вывода звука на Game Boy Advance в следующей главе.

Продолжение...

3 комментария

avatar
Всё же термин «плотность кода» относится к размеру довольно крупных осмысленных фрагментов, а не размеру опкодов отдельных команд; первое со вторым необязательно сильно связано, и необязательно однозначно положительно во всех случаях. Thumb понадобился не для уменьшения статического размера кода (для чего эффективнее программные методы), а в основном, действительно, во избежание двукратной потери скорости на 16-битной шине, весьма распространённой в нише встроек и мобилок (особенно в те года). Вот для Thumb-2 уже соображения экономии размера кэша играли роль.
avatar
Тем не менее, если смотреть в доки от самого ARM, то там написано: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0210c/CACBCAAE.html
"...ideally suited to embedded applications with restricted memory bandwidth, where code density and footprint is important". Поэтому я нисколько не отклоняюсь от официальной точки зрения на вопрос. :)
avatar
отклоняешься, там на первом месте как раз bandwidth, далее — второстепенное дополнение
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.