72 www.finestreet.ru компоненты ЦСП
Оптимизация программного кода
для ЦСП TMS320C6000
Игорь ГУК
В четвертой статье цикла рассматриваются способы оптимизации программного кода на основе конвейера и параллельной обработки ассемблерных команд для цифровых сигнальных процессоров семейства С6000 компании Texas Instruments (TI). Для усвоения материала рекомендуется ознакомиться с предыдущими публикациями («КиТ» № 7—9'2005).
Оптимизация программного кода может быть выполнена на нескольких уровнях:
• на уровне алгоритма — оптимизируется С-код;
• на уровне архитектуры ЦСП — оптимизируется ассемблерный код.
В рассматриваемом примере оптимизацию проведем для функции свертки convolution() из проекта, рассмотренного в предыдущих статьях цикла.
Вначале необходимо запустить CCS и создать новый проект. Напомним, что для этого в главном меню ИСР нужно выбрать раздел Project, в нем — пункт New, затем в появившемся диалоговом окне выбираются семейство ЦСП и тип проекта, а также указывается место размещения проекта на жестком диске и его имя.
В папку проекта скопировать файлы с исходным Си-кодом рассмотренного в прошлой статье фильтра. Скопированные файлы, за исключением заголовочного файла «filter.h», подключаются к проекту (в главном меню — раздел Project, а в нем — пункт Add Files to Project). Напомним, что заголовочный файл подключается автоматически при компиляции проекта.
В Си-коде оптимизируемой функции удаляется цикл сдвига за счет использования цикловой адресации. Модернизированный программный код функции convolution() примет вид:
// Чтение коэффициента coeff = *pCoeffBuff++;
// Умножение коэффициента на отсчет mpy = simpl * coeff;
// Суммирование с накоплением summa. += mpy;
// Организация циклической адресации if(pSimplBuff > pSimplMax) flag = 1; else flag = 0;
if(flag) pSimplBuff = pSimplMin;
}
// Нормализация результата summa. >>= 15;
// Возврат из функции результата свертки return summa;
}
Появились дополнительные параметры вызова функции:
• pSimplMin — указатель на начало буфера задержки;
• lenSimpl — длина линии задержки. Особенность кода в том, что длина линии
задержки должна быть кратна двум байтам и быть больше длины фильтра. Первое ограничение обусловлено способом реализации цикловой адресации в семействе ЦСП TMS320C6000. Изменение интерфейса функции влечет необходимость изменений и в других компонентах проекта.
В заголовочном файле «filter.h» необходимо:
• изменить объявление функции свертки:
extern word16 convolution(word16*, word16*, word16*, word16, word32);
• дополнить объявление типа контекстной структуры параметрами pSimplMin, и lenSimpl:
typedef struct {
word16* pInpBuff; // Указатель на входной буфер
word16* pOutBuff; // Указатель на выходной буфер
word16 lenBuff; // Длина, входного и выходного буферов
word16* pSimplBuff; // Текущий указатель на буфер линии // задержки
word16* pSimplMin; // Указатель на начало буфера линии // задержки word16* pCoeffBuff; // Указатель на буфер
// с коэффициентами фильтра word16 lenFilter; // Длина буферов линии задержки
// и коэффициентов word32 mCoeff; // Масштабирующий коэффициент
word32 lenSimpl; // Длина, линии задержки
} CONTEXTFILTER;
• определить макрос для инициализации нового поля контекстной структуры:
#define MASKINDEX 255.
В функции т^Шет() необходимо добавить инициализацию новых полей контекстной структуры:
pCntx->pSimplMin = simplBuff;
pCntx->lenSimpl = LENSIMPL;
а также изменить верхний предел в цикле начальной очистки буфера линии задержки:
for(i = 0; i < LENSIMPL; i++) pCntx->pSimplBuff[i] = 0;
В файле «const.cpp» изменить создание буфера задержанных отсчетов:
#pragma DATA_ALIGN (LENSIMPL << 1); word16 simplBuff[LENSIMPL];
Директива #pragma DATA_ALIGN (LENSIMPL << 1) выравнивает адрес массива simplBuff.
Листинг новой функция запуска фильтрации runFilter() приведен ниже.
void runFilter(CONTEXTFILTER* pCntx){
// Локальные переменные
word16* pInpBuff; // Указатель на входной буфер
word16* pOutBuff; // Указатель на выходной буфер
word16 lenBuff; // Длина входного и выходного буферов
word16* pSimplBuff; // Текущий указатель на буфер линии
// задержки
word16* pSimplMin; // Указатель на начало буфера линии // задержки
word16* pSimplMax; // Указатель на конец буфера линии // задержки word16* pCoeffBuff; // Указатель на буфер
// с коэффициентами фильтра word16 lenFilter; // Длина буферов линии задержки // и коэффициентов word32 mCoeff; // Масштабирующий коэффициент
word32 count; // Переменная цикла
word32 coeff; // Вспомогательная переменная
word32 lenSimpl; // Длина линии задержки
// Инициализация локальных переменных pInpBuff = pCntx->pInpBuff; pOutBuff = pCntx->pOutBuff; lenBuff = pCntx->lenBuff; pSimplBuff = pCntx->pSimplBuff; pSimplMin = pCntx->pSimplMin; pCoeffBuff = pCntx->pCoeffBuff; lenFilter = pCntx->lenFilter; mCoeff = pCntx->mCoeff; lenSimpl = pCntx->lenSimpl;
word16 convolution (word16* pSimplBuff, word16* pCoeffBuff, word16* pSimplMin, word16 lenFir, word32 lenSimpl){
// Объявление переменных
word32 count; // Переменная цикла
word32 summa; // Переменная для временного
// хранения результата накопления // умножений word32 coeff; // Коэффициент фильтра
word32 simpl; // Текущий отсчет
word32 mpy; // Результат умножения
word16* pSimplMax; // Максимальный адрес буфера // задержанных отсчетов word16 flag; // Флаг циклической адресации
// Инициализация переменных summa. = 0;
pSimplMax = pSimplMin + lenSimpl - 1;
// Вычисление свертки
for (count = lenFir - 1; count >=0; count--){
// Чтение отсчета simpl = *pSimplBuff++;
pSimplMax = pSimplMin + lenSimpl - 1;
// Цикл обработки входного буфера for(count = 0; count < lenBuff; count++){
// Чтение входного отсчета coeff = pInpBuff[count];
// Умножение на масштабирующий коэффициент coeff *= mCoeff;
// Нормирование результата coeff >>= 15;
// Запись в буфер задержанных отсчетов *pSimplBuff = (word16) coeff;
// Определение выходного отсчета
coeff = convolution(pSimplBuff, pCoeffBuff, pSimplMin,
lenFilter, lenSimpl);
// Запись выходного отсчета pOutBuff[count] = (word16) coeff;
// Декрементация адреса буфера задержанных отсчетов pSimplBuff--;
// Организация циклической адресации if(pSimplBuff < pSimplMin) pSimplBuff = pSimplMax;
}
// Сохранение текущего значения адреса буфера pCntx->pSimplBuff = pSimplBuff;
Необходимо создать новый исходный файл, набрать текст ассемблерного кода оптимизируемой функцией свертки и сохранить файл с именем «сото\иПоп_ту_2.азт». Затем подключить файл с ассемблерным кодом функции свертки к проекту, а файл с Си-кодом исключить из процесса компиляции. Как это сделать, было показано в предыдущих статьях цикла. Ассемблерный код функции свертки имеет вид:
; Назначение имен регистрам
.asg A2, flag_A ; word16 flag; // Флаг цикли// ческой адре-// сации
.asg A3, mpy_A ; word32 mpy; // Результат // умножения
.asg A4, pSimp1Ruff_A ; word16* pSimplBuff; // Первый
// параметр
// функции
.asg A5, simp1_A ; word32 simpl; // Текущий // отсчет
.asg A6, pSimp1Min_A ; word16* pSimplMin // Третий
// параметр
// функции
.asg A7, summa_A ; word32 sum; // Переменная // для накопле-// ния умно// жений
.asg A8, lenSimpl_A ; word32 masklndex; // Пятый // параметр // функции
.asg A9, pSimp1Max_A ; word16* pSimplMax; // Максималь-// ный адрес // буфера // отсчетов
.asg B0, count_B ; Переменная цикла
.asg B3, adrReturn_B ; Адрес возврата
.asg B4, pCoeffBuff_B ; word16* pCoeffBuff; // Второй // параметр // функции
.asg B5, coeff_B ; word32 coeff; // Коэффици-// ент фильтра
.asg B6, 1enFir_B ; word16 lenFir; // Четвертый // параметр // функции
.asg B15, SP ; Указатель на стек
; Определение функции
.sect «.text» ; секция размещения функции
.global _convo1ution FPsN21si ; имя функции
_convo1ution FPsN21si: ; точка входа в функцию
; Запрет прерываний
MVC .S2 CSR, B0
AND .S2 B0, -2, B1
MVC .S2 B1, CSR
; Сохранение регистров в стеке
STW .D2T2 B0, *-SP[11]
; Алгоритм обработки
; Инициализация переменных
ZERO .D1 summa_A ; summa = 0;
; pSimplMax = pSimplMin + lenSimpl - 1
SUB .S1 lenSimpl_A, 1, lenSimpl_A
ADDAH .D1 pSimplMin_A, lenSimpl_A, pSimplMax_A
; Вычисление свертки
31
Резерв
26 25
ВК1
21 20
ВКО
16
15 В7 15 13 В6 12 11 В5 10 9 В4 8 I 7 А7 6 I 5 Ai 4 3 А5 11 A4 0
Рис. 1. Структура служебного регистра ARM
;for (count = lenFir - 1; count >=0; count--){
; Определение начального значения переменной цикла SUB .L2 lenFir_B, 1, count_B
loop01: ; Точка возврата при циклических вычислениях ; Чтение отсчета ; simpl = *pSimplBuff++;
; Чтение коэффициента ; coeff = *pCoeffBuff++;
LDH .D2T2 *pCoeffBuff_B++, coeff_B
; coeff = *pCoeffBuff++;
II LDH .D1T1 *pSimplBuff_A++, simpl_A
; simpl = *pSimplBuff++;
NOP 4 ; Ожидание завершения операции чтения ; данных из памяти
; Умножение коэффициента на отсчет MPY .M1X coeff_B, simpl_A, mpy_A
; mpy = coeff * simpl;
NOP ; Ожидание завершения операции умножения
ADD .S1 summa_A, mpy_A, summa_A
; summa += mpy;
Организация циклической адресации if(pSimplBuff > pSimplMax) flag = 1; else flag = 0;
CMPGT .L1 pSimplBuff_A, pSimplMax_A, flag_A
; if(flag) pSimplBuff = pSimplMin;
[flag_A] MV .S1 pSimplMin_A, pSimplBuff_A,
; Условный переход при циклических вычислениях и ; одновременная условная декрементация переменной ; цикла
[count_B] B .S2 loop01
[count_B] SUB .L2 count_B, 1,count_B
NOP 5 ; Ожидание завершения операции условного ; перехода
; Нормирование результата суммирования SHR .S1 summa_A, 15, summa_A ; sum>>=15;
; Восстановление служебных регистров LDW .D2T2 *-8Р[11], В0
NOP 4 ; Ожидание завершения последней операции ; чтения (4 такта)
; Восстановление регистра С8Я
МТС .Б2 В0, CSR
; Выход из функции
; Перемещение возвращаемого значения в регистр А4 MV .S1 $ишта_А, А4
B
.S2
adrReturn_B
NOP 5 ; Ожидание завершения операции безусловного ; перехода (5 тактов)
Программный код, представленный выше, не является оптимальным. Его необходимо модернизировать. На первом шаге используем возможность организации цикловой адресации в ЦСП TMS320C6000.
Для организации циклической адресации используются регистры общего назначения А4-А7 и В4-В7. Режим работы данных РОН определяется служебным регистром АМИ. Структура регистра АМИ показана на рис. 1.
Режим работы регистра, выбранного для циклической адресации, определяется значением полей А4-А7 и В4-В7 (соответствующих РОН аналогичного наименования):
• «00» — линейная адресация (режим по умолчанию);
• «01» — циклическая адресация с указанием длины буфера в поле ВК0;
• «10» — циклическая адресация с указанием длины буфера в поле ВК1;
• «11» — резерв.
Длина буфера циклической адресации в байтах определяется значением полей ВК0 и ВК1 (табл. 1).
Код ассемблерной функции с цикловой адресацией показан ниже. Хранится он в файле «сото\иПоп_ту_3.азт». Необходимо его создать, подключить к проекту и исключить из процесса компиляции предыдущий файл «сото\иПоп_ту_2.азт».
; Назначение имен регистрам
.asg A3, mpy_A ; word32 mpy; // Результат // умножения
uff; lB pl pS чо "3 о < uff_ lB pl pS < g .as // Первый
// параметр
// функции
.asg A5, simp1_A ; word32 simpl; // Текущий // отсчет
.asg A7, summa_A ; word32 sum; // Переменная // для накопле-// ния умноже-// ний
.asg A8, maskIndex_A ; word32 masklndex; // Пятый // параметр // функции
.asg B0, count_B ; Переменная цикла
.asg B3, adrReturn_B ; Адрес возврата
.asg B4, pCoeffBuff_B ; word16* pCoeffBuff; // Второй // параметр // функции
.asg B5, coeff_B ; word32 coeff; // Коэффициент // фильтра
.asg B6, 1enFir_B ; word16 lenFir; // Четвертый // параметр // функции
.asg B15, SP ; Указатель на стек
; Определение функции
.sect «.text» ; секция размещения функции
.global _convolution FPsN21si ; имя функции
_convolution FPsN21si: ; точка входа в функцию
Таблица 1. Размер буфера в зависимости от значения полей BK0 и BK1 регистра ARM
Значение Размер Значение Размер Значение Размер Значение Размер
00000 2 01000 512 10000 131 072 11000 33 554 432
00001 4 01001 1024 10001 262 144 11001 67 108 864
00010 8 01010 2048 10010 524288 11010 67 108 864
00011 16 01011 4096 10011 1 048 576 11011 268 435 456
00100 32 01100 8192 10100 2 097 152 11100 536 870 912
00101 64 01101 16 384 10101 4194304 11101 1 073 741 824
00110 128 01110 32 768 10110 8 388 608 11110 2 147 483 648
00111 256 01111 65 536 10111 16 777 216 11111 4 294 967 296
74
компоненты
ЦСП
1 команда | ЧК | ДК ЧА1 | ЧА2 | ВК | СР
2 команда' ЧК ДК ЧА1 ЧА2 ВК СР
3 команда| I I I ЧК ДК ЧА1 ЧА2 ВК СР
1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19
а) Выполнение команд без конвейера
(такт)
1 команда | ЧК ДК ЧА1 ЧА2 ВК СР
2 команда j ЧК ДК ЧА1 ЧА2 ВК СР
3 команда . ЧК ДК ЧА1 ЧА2 ВК СР |
'
1 2 3 4 5 6 7 8
Время,
6) Выполнение команд с конвейером ^такт^
Рис. 2. Выполнение команд с конвейером и без него
; Запрет прерываний
MVC .S2 CSR, B0
AND .S2 B0, -2, B1
MVC .S2 B1, CSR
; Определить режим циклической адресации для регистра A4 MVC .S2 AMR, B2
MVKL .S2 0x1, B1
MVKLH .S2 0x7, B1
MVC .S2 B1, AMR
; Сохранение служебных регистров в стеке STW .D2T2 B0, *-SP[11]
STW .D2T2 B2, *-SP[12]
; Алгоритм обработки
; Инициализация переменных ZERO .D1 summa_A
; summa = 0;
; Цикл вычисления свертки ; for (count = lenFir - 1; count >=0; count--){
; Определение начального значения переменной цикла SUB .L2 lenFir_B, 1, count_B
loop01: ; Точка возврата при циклических вычислениях ; Чтение отсчета ; simpl = *pSimplBuff++;
; Чтение коэффициента ; coeff = *pCoeffBuff++;
LDH .D2T2 *pCoeffBuff_B++, coeff_B
; coeff = *pCoeffBuff++;
II LDH .D1T1 *pSimplBuff_A++, simpl_A
; simpl = *pSimplBuff++;
NOP ; Ожидание завершения операции чтения
; данных из памяти
; Условный переход при циклических вычислениях и ; одновременная условная декрементация переменной ;цикла
[count_B] B .S2 loop01 II [count_B] SUB .L2 count_B, 1, count_B
NOP ; Ожидание завершения операции чтения ; данных из памяти
NOP ; Ожидание завершения операции чтения ; данных из памяти
; Умножение коэффициента на отсчет MPY .M1X coeff_B, simpl_A, mpy_A
; mpy = coeff * simpl;
NOP ; Ожидание завершения операции умножения
ADD .S1 summa_A, mpy_A, summa_A ;
summa += mpy;
; } Конец цикла вычисления свертки
; Нормирование результата суммирования SHR .S1 summa_A, 15, summa_A
; sum>>=15;
; Восстановление служебных регистров LDW .D2T2 *-SP[11], B0
LDW .D2T2 *-SP[12], B2
NOP 4 ; Ожидание завершения последней операции
; чтения (4 такта)
; Восстановление регистров
MVC .S2 B0, CSR
MVC .S2 B2, AMR
; Выход из функции
; Перемещение возвращаемого значения в регистр A4 MV .S1 summa_A, A4
B .S2 adrReturn_B
NOP 5 ; Ожидание завершения операции
; безусловного перехода (5 тактов)
При этом из вынесенных итераций исключается команда условного перехода. Часть программного кода, соответствующего данному этапу, имеет вид:
; Цикл вычисления свертки
;for (count = lenFir - 1; count >=0; count--){
; Определение начального значения переменной цикла SUB .L2 lenFir_B, 1, count_B
Затем необходимо провести компиляцию проекта, загрузить полученный бинарный файл в ЦСП, запустить на выполнение программу и убедиться в корректности результата. Как это сделать, было подробно рассмотрено в предыдущих статьях цикла. Особенности приведенного кода:
• команда ожидания завершения загрузки данных из памяти «NOP 4» заменена четырьмя последовательными командами «NOP» для удобства дальнейшей оптимизации;
• команда условного перехода «[count_B] B .S2 loop01» перенесена на 5 тактов вверх, что позволяет отказаться от команды ожидания завершения условного перехода «NOP 5» в конце цикла.
Дальнейшая оптимизация заключается
в организации параллельных вычислений и конвейерной обработки команд. Суть конвейера в том, что очередная команда начинает выполняться до завершения предыдущей. Это возможно в силу того, что каждая команда выполняется за несколько тактов: чтение команды (ЧК), дешифрование команды (ДК), чтение операндов команды (ЧА1, ЧА2 и т. д.), выполнение команды (ВК) и сохранение результата (СР). Градация достаточно условная, но позволяет проиллюстрировать идею конвейера. На рис. 2a показана работа ЦСП без конвейера, а на рис. 2б — с конвейером. Таким образом, выполнение команд (после определенной задержки) происходит за один такт.
Для ЦСП TMS320C6000 существует два типа конвейеров — программный и аппаратный. Аппаратный конвейер «прозрачен» для программирования и позволяет считать, что большинство команд выполняется за один такт (имеет нулевую задержку). А вот программный конвейер является мощным инструментом оптимизации кода.
Реализация программного конвейера для функции свертки показана на примере блока «Цикл вычисления свертки» в ее ассемблерном коде. Примерная процедура организации конвейера заключается в следующем:
1. Вынести из цикла несколько итераций циклических вычислений и выполнить их последовательно. Количество выносимых итераций равно количеству тактов (включая пустые операции) в одном шаге цикла.
; 1-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LDH .D2T2 *pCoeffBuff_B++, coeff_B II LDH .D1T1 *pSimplBuff_A++, simpl_A
NOP
[count_B] SUB .L2 count_B, 1, count_B
NOP NOP
MPY .M1X coeff_B, simpl_A, mpy_A NOP
ADD .S1 summa_A, mpy_A, summa_A
; 2-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
NOP
[count_B] SUB .L2 count_B, 1, count_B
NOP NOP
MPY .M1X coeff_B, simpl_A, mpy_A
NOP
ADD .S1 summa_A, mpy_A, summa_A
3-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
6-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
8-я итерация цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Оставшиеся итерации цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; loop01: ; Точка возврата при циклических вычислениях ; Чтение отсчета simpl = *pSimplBuff++;
; Чтение коэффициента coeff = *pCoeffBuff++; LDH .D2T2 *pCoeffBuff_B++, coeff_B ; coeff = *pCoeffBuff++;
II LDH .D1T1 *pSimplBuff_A++, simpl_A
; simpl = *pSimplBuff++;
NOP ; Ожидание завершения операции чтения ; данных из памяти
[count_B] B .S2 loop01
II [count_B] SUB .L2 count_B, 1,count_B
NOP ; Ожидание завершения операции чтения ; данных из памяти
NOP ; Ожидание завершения операции чтения ; данных из памяти
; Умножение коэффициента на отсчет MPY .M1X coeff_B, simpl_A, mpy_A
; mpy = coeff * simpl;
NOP ; Ожидание завершения операции ; умножения
ADD .S1 summa_A, mpy_A, summa_A
; summa += mpy;
; } Конец цикла вычисления свертки
Рекомендуется в учебных целях создать новый ассемблерный файл (например, с именем «сото\иПоп_ту_4.азт») и сохранить в нем измененный код функции свертки.
Таблица 2. Пример «склеивания» первых трех итераций
1 итерация 2 итерация 3 итерация Результат "склеивания"
LDH .D2T2 *pCoeffBuff_B++, coeff_B || LDH .D1T1 *pSimplBuff_A++, simpl_A LDH .D2T2 *pCoeffBuff_B++, coeff_B || LDH .D1T1 *pSimplBuff_A++, simpl_A
NOP LDH .D2T2 *pCoeffBuff_B++, coeff_B jj LDH .D1T1 *pSimplBuff_A++, simpl_A LDH .D2T2 *pCoeffBuff_B++, coeff_B || LDH .D1T1 *pSimplBuff_A++, simpl_A
[count_B] B .S2 loop01 || [count_B] SUB .L2 count_B, 1,count_B NOP LDH .D2T2 *pCoeffBuff_B++, coeff_B jj LDH .D1T1 *pSimplBuff_A++, simpl_A [count_B] B .S2 loop01 || [count_B] SUB .L2 count_B, 1,count_B LDH .D2T2 *pCoeffBuff_B++, coeff_B LDH .D1T1 *pSimplBuff_A++, simpl_A
NOP [count_B] B .S2 loopG1 jj [count_B] SUB .L2 count_B, 1,count_B NOP [count_B] B .S2 loop01 || [count_B] SUB .L2 count_B, 1,count_B
NOP NOP [count_B] B .S2 loopG1 jj [count_B] SUB .L2 count_B, 1,count_B [count_B] B .S2 loop01 || [count_B] SUB .L2 count_B, 1,count_B
MPY .M1X coeff_B, simpl_A, mpy_A NOP NOP MPY .M1X coeff_B, simpl_A, mpy_A
NOP MPY .M1X coeff_B, simpl_A, mpy_A NOP MPY .M1X coeff_B, simpl_A, mpy_A
ADD .S1 summa_A, mpy_A, summa_A NOP MPY .M1X coeff_B, simpl_A, mpy_A ADD .S1 summa_A, mpy_A, summa_A || MPY .M1X coeff_B, simpl_A, mpy_A
ADD .S1 summa_A, mpy_A, summa_A NOP ADD .S1 summa_A, mpy_A, summa_A
ADD .S1 summa_A, mpy_A, summa_A ADD .S1 summa_A, mpy_A, summa_A
2. Определить возможность организации параллельных вычислений. Это зависит от количества операций и их типа. За один такт можно выполнить 8 операций. Две из них должны быть умножениями. Не более двух операций чтения-записи памяти. Две операции необходимы для организации циклических вычислений. Следует проанализировать, какие модули АЛУ задействованы и нет ли пересечений — здесь важным является распределение переменных по сторонам А и В регистров общего назначения. Кроме этого, необходимо выяснить, нет ли конфликта в использовании путей коммутации и кроссировки. Пустые операции ^ОР) не учитываются. В результате определяется количество блоков с параллельным выполнением команд, необходимых для выполнения всех действий на одном шаге цикла. Формировать блоки из операций цикла необходимо без учета того, что результат одной операции может быть исходным значением для другой.
В рассматриваемом примере можно выполнить все операции в одном блоке (параллельно): две операции чтения данных из памяти (пути коммутации не пересекаются), операция условного перехода, операция декремен-тации переменной цикла, одно умножение и одно суммирование (модули не пересекаются, используется только один кросс-путь).
3. Произвести «склейку» первых восьми (для данного примера) итераций цикла. С учетом того, что все операции в одном цикле могут быть выполнены в одном блоке параллельных команд, «склейка» производится со смещением на один шаг. Как это сделать, показано в таблице 2.
Первые 8 «склеенных» шагов цикла представляют собой программный конвейер вычислений: чтение из памяти новых данных начинается до окончания обработки уже прочитанных. В процессе загрузки текущих данных (которая выполняется с задержкой на 4 такта) производится обработка предыдущих. К моменту появления данных в приемных регистрах для текущего коэффициента фильтра и текущего отсчета линии задержки эти регистры оказываются свободны от предыдущих значений.
Результат данного этапа показан ниже. Рекомендуется эти изменения тоже сохранить в отдельном файле (например, с именем «сото\иПоп_ту_5.азт»).
; Цикл вычисления свертки
for (count = lenFir - 1; count >=0; count--){
; Определение начального значения переменной
цикла SUB .L2 lenFir_B, 1, count_B
j 1 O ti I CjM.I yyl yi цидЛа >>>>>>>>>> ;1 LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
;2 LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH ;3 .D1T1 *pSimplBuff_A++, simpl_A
[count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH ^4 .D1T1 *pSimplBuff_A++, simpl_A
[count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH ^5 .D1T1 *pSimplBuff_A++, simpl_A
[count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
;6 MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH ;В .D1T1 *pSimplBuff_A++, simpl_A
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
;9 ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
;10
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
;12
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
;13
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
;14
ADD .S1 summa_A, mpy_A, summa_A
;15
ADD .S1 summa_A, mpy_A, summa_A
; Чтение коэффициента coeff = *pCoeffBuff++; LDH .D2T2 *pCoeffBuff_B++, coeff_B ; coeff = *pCoeffBuff++;
LDH .D1T1 *pSimplBuff_A++, simpl_A ; simpl = *pSimplBuff++;
NOP ; Ожидание завершения операции чтения ; данных из памяти
[count_B] B .S2
II [count_B] SUB .L2
loop01
count_B, 1,count_B
NOP ; Ожидание завершения операции чтения ; данных из памяти
NOP ; Ожидание завершения операции чтения ; данных из памяти
; Умножение коэффициента на отсчет MPY .MIX coeff_B, simp1_A, mpy_A ; mpy = coeff * simpl;
NOP ; Ожидание завершения операции умножения
ADD .S1 summa_A, mpy_A, summa_A ; summa += mpy;
; } Конец цикла вычисления свертки
4. Организация циклических вычислений на базе конвейера. В блоке команд № 8 первых 8 итераций циклических вычислений (см. п. 3) выполняются все команды одного шага цикла (за исключением операции перехода). Этот блок будет ядром цикла с конвейером. Все блоки команд до него — это пролог цикла, после — эпилог.
Для организации циклических вычислений на базе конвейера необходимо (в рассматриваемом примере) исключить из программного кода блок команд «Оставшиеся итерации цикла» (см. п. 3). Затем установить метку для перехода при циклических вычислениях, а также в эпилоге и ядре цикла необходимо добавить команды перехода. Результат преобразования кода:
; Цикл вычисления свертки
;for (count = lenFir - 1; count >=0; count--){
; Определение начального значения переменной
цикла
SUB .L2 1enFir_B, 8, count_B ; Пролог цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;1
LDH .D2T2 *pCoeffBuff_B++, coeff_B ll LDH .D1T1 *pSimp1Buff_A++, simp1_A
;2
LDH .D2T2 *pCoeffBuff_B++, coeff_B LDH .D1T1 *pSimplBuff_A++, simpl_A
; Оставшиеся итерации цикла ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; loop01: ; Точка возврата при циклических вычислениях ; Чтение отсчета simpl = *pSimplBuff++;
[count_B] Il [count_B] SUB .L2
B .S2 loop01 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
[count_B] B .S2 loop01
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
[count_B] B .S2 loop01
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
;6 [count_B] B .S2 loop01
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
[count_B] B .S2 loop01
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
;8 loop01:
[count_B] B .S2 loop01
II ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
II LDH .D2T2 *pCoeffBuff_B++, coeff_B
II LDH .D1T1 *pSimplBuff_A++, simpl_A
; Эпилог цикла ;;;;;; ■9
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
;10
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
II [count_B] SUB .L2 count_B, 1, count_B
;11
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
;12
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
;13
ADD .S1 summa_A, mpy_A, summa_A
II MPY .M1X coeff_B, simpl_A, mpy_A
;14
ADD .S1 summa_A, mpy_A, summa_A
;15
ADD .S1 summa_A, mpy_A, summa_A
; } Конец цикла вычисления свертки
Рекомендуется сохранить данный вариант ассемблерного кода в отдельном файле (например, с именем «сотоїиПоп_ту_6.азт»).
В 8-м блоке команд появилась метка «Іоор01» для организации цикла, а также команда условного перехода на эту метку ([соип_В] В .Б2 Іоор01), выполняемая параллельно с остальными командами блока. Данная команда будет выполнена только через 5 тактов. Поэтому необходимо за 5 тактов до блока команд ядра поставить первую операцию условного перехода (блок команд номер 3) — эта команда перехода будет выполнена только после 8-го блока команд. Команда перехода в 4-м блоке команд будет выполнена после того, как 8-й блок будет повторен дважды. Затем будет выполнена команда перехода 5-го блока, затем 6-го, 7-го, и только после этого начнет выполняться команда перехода ядра цикла.
Задержка выполнения операции условного перехода приводит к тому, что после запрета
на выполнение условного перехода в ядре цикла (значение регистра условия «count_B» равно нулю), переход будет осуществлен еще 5 раз (будут выполняться предыдущие команды перехода). Это влечет необходимость, во-первых, уменьшить значение переменной цикла «count_B» еще на 7 единиц:
SUB .L2 lenFir_A, 8, count_B,
во-вторых, заблокировать декрементацию переменной цикла после достижения значения «ноль»:
[count_B] SUB .L2 count_B, 1, count_B.
Уменьшение переменной цикла на 7 единиц обусловлено тем, что выполнение 7 итераций происходит вне ядра цикла (в прологе и эпилоге).
Откомпилировать проект, запустить его на выполнение и убедиться в корректной работе. Таким образом, получен программный код циклических вычислений, оптимизированный с использованием конвейерной и параллельной обработки команд.
В следующей статье будет показано, как использовать возможности RTDX для ускорения и визуализации процесса отладки, а также контроля времени выполнения программного кода. Все файлы проекта можно найти на сайте www.scanti.ru. ■