|
С и asm-вставки, Локальные переменные |
|
|
|
Mar 30 2012, 06:32
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (Xeon @ Mar 30 2012, 09:05)  Вопрос следующий: как назначить переменной конкретный регистр или как узнать в каком регистре находится переменная, чтоб в дальнейшем иметь к ней доступ? Неправильный вопрос вы задаете. Спрашивать надо "как на языке С написать этот алгоритм более оптимально?". Своими благими намерениями вы будете только мешать оптимизатору и в пределе можете сделать свою программу работающей только под одной версией компилятора при строго определенном наборе ключей. Хотите - балуйтесь: CODE register uint16_t Tmp __asm__(("r16")) Чтобы узнать, в каком регистре находится переменная, надо читать листинг. Но это мало что вам даст - добавление одной строчки в другом месте программы может привести к перераспределению регистров и ваша программа работать перестанет. Это во-первых. А во-вторых - локальная переменная за время своей жизни легко может кочевать из одного регистра в другой, на стек и обратно. Хотите более надежное решение - изучите соглашение о вызовах и напишите отдельный узкий кусок программы чисто на асме или в крайнем случае на инлайн-асме, а лучше выложите свой код и возможно фуромчане насоветукют вам, как его улучшить не прибегая к извращениям. Поверьте, это возможно.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 30 2012, 07:24
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Чем отличается обьявление: Код register uint16_t Tmp __asm__(("r16")) от : Код register uint16_t Tmp asm("r16"); ?
|
|
|
|
|
Mar 30 2012, 08:13
|
Частый гость
 
Группа: Участник
Сообщений: 140
Регистрация: 21-04-11
Пользователь №: 64 524

|
Нашел немного инфы по asm-вставкам здесь (может кому будет полезна): http://www.simple-devices.ru/attachments/a...AVR_04_2011.pdfhttp://www.simple-devices.ru/attachments/a...AVR_05_2011.pdfВ пдфке по второй ссылке, говориться что в asm-вставку операнды можно передавать по имени, т.е. вместо: CODE asm(«in %0, %1» : «=r» (value) : «I» (_SFR_IO_ADDR(PORTD)) ); можно писать: CODE asm volatile («in [x1], [x2]» : [x1] «=r» (value) : [x2] «I» (_SFR_IO_ADDR(PORTD)) ); У меня почему то это не вышло, писал следующее: CODE asm volatile( "push R0 \n" "push R1 \n" "muls %0, %1 \n" // загрузить синус "pop R1 \n" "pop R0" : :[SINVAL] "d" (sinVal), [COSVAL] "d" (cosVal) );
В чём я ошибся? Цитата(Сергей Борщ @ Mar 30 2012, 09:32)  Хотите более надежное решение - изучите соглашение о вызовах и напишите отдельный узкий кусок программы чисто на асме или в крайнем случае на инлайн-асме, а лучше выложите свой код и возможно фуромчане насоветукют вам, как его улучшить не прибегая к извращениям. Поверьте, это возможно. Конечно, последую Вашему совету... А у Вас не найдется, что почитать? По поводу переноса на другой компилятор... сейчас такого вопроса не стоит и скарей всего не когда и не будет. Вот кусок коды на С который хочу заставить работать быстрей: CODE // вычисляем вещественную и мнимую части int8_t sinVal = 2;// pgm_read_byte(&FsinTable[FLookignForFreq][CurVal]); int8_t cosVal = -3;//pgm_read_byte(&FcosTable[FLookignForFreq][CurVal]);
FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal); FsummIm[FLookignForFreq] += (int32_t) (Values[CurVal]*sinVal); CurVal++; if(CurVal >= FAMOUNT_POINTS) { ADCA.CTRLA = 0; Flags = ADC_CONVERSION_COMPLETE; } DMA.CH0.CTRLB |= (1 << 4) | (1 << 5); // clear interrupt flags
Собствненно узкое место здесь: CODE FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal); FsummIm[FLookignForFreq] += (int32_t) (Values[CurVal]*sinVal);
Здесь делаю перемножение двух знаковых одно-байтовых чисел и после всё кладу в "сумматор", так я должен делать 256 раз (выше сказанное делается 2 раза, 1 раз для FsummRe и 1 раз для FsummIm). 8x8 == 16 разрядов и поскольку всё кладу в сумматор 256 раз, то конечный результат для FsummRe и FsummIm будет 24 разрядный. Вот, что получаю от компилятора: CODE FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal); 000003EF LDS R30,0x2427 Load direct from data space 000003F1 LDI R31,0x00 Load immediate 000003F2 LDS R26,0x2431 Load direct from data space 000003F4 LDI R27,0x00 Load immediate 000003F5 SUBI R26,0xCA Subtract immediate 000003F6 SBCI R27,0xDA Subtract immediate with carry 000003F7 LD R18,X Load indirect 000003F8 CLR R19 Clear Register 000003F9 SBRC R18,7 Skip if bit in register cleared 000003FA COM R19 One's complement 000003FB LSL R30 Logical Shift Left 000003FC ROL R31 Rotate Left Through Carry 000003FD LSL R30 Logical Shift Left 000003FE ROL R31 Rotate Left Through Carry 000003FF MOVW R16,R30 Copy register pair 00000400 SUBI R16,0xD8 Subtract immediate 00000401 SBCI R17,0xDC Subtract immediate with carry 00000402 MOVW R14,R18 Copy register pair 00000403 LSL R14 Logical Shift Left 00000404 ROL R15 Rotate Left Through Carry 00000405 ADD R18,R14 Add without carry 00000406 ADC R19,R15 Add with carry 00000407 COM R19 One's complement 00000408 NEG R18 Two's complement 00000409 SBCI R19,0xFF Subtract immediate with carry 0000040A CLR R20 Clear Register 0000040B SBRC R19,7 Skip if bit in register cleared 0000040C COM R20 One's complement 0000040D MOV R21,R20 Copy register 0000040E MOVW R26,R16 Copy register pair 0000040F LD R22,X+ Load indirect and postincrement 00000410 LD R23,X+ Load indirect and postincrement 00000411 LD R24,X+ Load indirect and postincrement 00000412 LD R25,X Load indirect 00000413 SBIW R26,0x03 Subtract immediate from word 00000414 ADD R18,R22 Add without carry 00000415 ADC R19,R23 Add with carry 00000416 ADC R20,R24 Add with carry 00000417 ADC R21,R25 Add with carry 00000418 ST X+,R18 Store indirect and postincrement 00000419 ST X+,R19 Store indirect and postincrement 0000041A ST X+,R20 Store indirect and postincrement 0000041B ST X,R21 Store indirect 0000041C SBIW R26,0x03 Subtract immediate from word
Честно, особо не вникал как конкретно делает компилятор... По мне можно сделать так: 1) взять нужные значения и перемножить знаково используя MULS 2) после произвести знаковое сложение 24 разрядного "сумматора" и полученного 16 разрядного результата перемножения По этому, как мне кажется, реализовать это дело можно намного проще и быстрее... Жду Ваших комментариев...
|
|
|
|
|
Mar 30 2012, 09:22
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (MaxiMuz @ Mar 30 2012, 10:24)  Чем отличается обьявление: Ничем. Они эквивалентны. QUOTE (Xeon @ Mar 30 2012, 11:13)  В пдфке по второй ссылке, говориться что в asm-вставку операнды можно передавать по имени Да, можно. Смотрите: CODE uint8_t Tmp; bpm_driver::state * pTmp;
asm volatile ( // pTmp = get_update_src(); "PUSH %[tmp] \r\n" "IN %[tmp], %[sreg] \r\n" "PUSH %[tmp] \r\n" "PUSH %A[ptr] \r\n" "PUSH %B[ptr] \r\n" "LDS %A[ptr], %[state] \r\n" "LDS %B[ptr], %[state]+1 \r\n" :[ptr]"=z"(pTmp), [tmp]"=r"(Tmp) :[sreg] "I" (_SFR_IO_ADDR(SREG)) ,[state]"m"(bpm_driver::pState) ); QUOTE (Xeon @ Mar 30 2012, 11:13)  У меня почему то это не вышло, писал следующее: По симптомам "у меня не вышло" можно поставить лишь один диагноз: "значит что-то делали неправильно". Объясните, что вы хотели сделать и что получили? QUOTE (Xeon @ Mar 30 2012, 11:13)  Вот кусок коды на С который хочу заставить работать быстрей: CODE FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal); В этой строке приведение типа либо абсолютно лишнее либо используется не там, где нужно. Как объявлено Values? Выложите всю функцию со всеми объявлениями в виде одного файла, который можно компилить отдельно и экспериментировать. QUOTE (Xeon @ Mar 30 2012, 12:02)  Тип процессора правильный, точно... Использую XMega32 A4... По даташиту данные микроконтроллеры поддерживают команду MULS. Может на самом деле есть где нибудь у компилятора настройка по этому поводу. С хмегами не работал и не собираюсь, но, возможно, компилятор не очень свежий и еще не умеет гененрить MULS.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 30 2012, 14:17
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (MaxiMuz @ Mar 30 2012, 15:17)  пишите перед этим квалификатор volatile , меньше ошибок с исчезновением операций будет. Вы в который раз топчетесь по одним и тем же граблям (уже однажды разбирали в одной из ваших веток, и вы сами смотрели листинг и убеждались). НЕ РАБОТАЕТ в gcc volatile register. В сообщении 30 той ветки было решение.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 31 2012, 07:12
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Цитата(Сергей Борщ @ Mar 30 2012, 17:17)  НЕ РАБОТАЕТ в gcc volatile register. В сообщении 30 той ветки было решение. как раз volatile решил проблему
|
|
|
|
|
Apr 1 2012, 05:59
|
Частый гость
 
Группа: Участник
Сообщений: 140
Регистрация: 21-04-11
Пользователь №: 64 524

|
Привожу исходники:
sources.rar ( 4.77 килобайт )
Кол-во скачиваний: 74Немного опишу: первоночальная цель - нужно найти некоторые частоты используя преобразование Фурье, всякие вызовы подобные _DebugMes, _DebugByte и др. - это макросы для вывода в консоль... В ADC.c я делаю 255 измерений и после каждого измерения срабатывает прерывание от DMA и внем я вычисляю вещественную и мнимую части. Почему DMA - просто когда я пробовал без него получалось, что в присудствее задержки распространения АЦП при частоте 250КГц АЦП делал 50000 выборок в секунду, при 500КГц 100000 выборок в секунду, а мне надо мерить сигнал в 100КГц, значит по Найквисту/Котельникову частота выборок в два раза больше, т.е. 200 килосемплов/секунда. Сделав через DMA получилось, что от задержке распространения избавился (может я не прав, но сигнал который дискретизую очень похож на то, что вижу на осциле). К тому же DMA позволило производить измерения, когда на АЦП подаю 1МГц, без DMA не работало. В файлике FTransform.c собственно само преобразование, вобщем там на мой взгляд все довольно тривиально... Если вдруг будет, что не понятно распишу всё подробней...
Сообщение отредактировал Xeon - Apr 1 2012, 06:01
|
|
|
|
|
Apr 2 2012, 04:56
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
Перед тем как оптимизировать на асме, разберитесь сначала с оптимизацией ни СИ. Во-первых, при оптимизации на асме можно сделать очепятку, которую потом будете долго и упорно искать. Во-вторых, оптимизация на Си выполняется гораздо быстрее. В-третьих, при хорошо изученном поведении своего компилятора, отимизированный код на Си будет всего на 5...15% больше своего собрата на асме, зато скока сэкономленого времени?! И если только оптимизация на Си не помогает, то необходимо залезать в ассемблер. Но и при ассемблерной оптимизации есть куча подводных камней. Примеры: 1. Использование указателей вместо индексации массивов Код int8_t * val = &Values[0]; // в не тела цикла .................................. // в цикле: FsummRe[FLookignForFreq] += (int32_t) (*val) * cosVal; FsummIm[FLookignForFreq] += (int32_t) (*val) * sinVal; .................................. val++; Это сокращает код на одно вычисление адреса Values[CurVal]. FsummIm и FsummRe, FsinTable и FcosTable также можно привести к использованию указателей 2. Оптимизация циклов. Цикл i=N;do{.....}while(i--); в большинстве случаев более оптимизировано компилируется, чем цикл for(i = 0; i < N; i++){........}; 3. При целочисленной арифметике, деление заменяется на умножение и сдвиг. 4. Объявление наиболее используемых локальных переменных как register (не вовсех компиляторах) 5. и т.д. и т.п..........................
|
|
|
|
|
Apr 2 2012, 07:46
|
Частый гость
 
Группа: Участник
Сообщений: 140
Регистрация: 21-04-11
Пользователь №: 64 524

|
Цитата(alexeyv @ Apr 2 2012, 07:56)  Спасибо! Весьма полезные советы!!!
|
|
|
|
|
Apr 2 2012, 09:04
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
Цитата(XVR @ Apr 2 2012, 14:30)  На достаточном уровне оптимизации (-О3 достаточно) gcc это все сделает сам. (Хотя в конверсии массивов в указатели может и запутаться - у вас там весьма накрученно массивы индексируются) 1. Поверьте мне - GCC НЕ ВСЕ ЭТО сделает сам, в нем заложены только типичные алгоритмы, которые впрочем постоянно расширяются и периодически обновляются. 2. Мои высказывания относятся ко всем компиляторам, а не только к GCC. И если писать сразу отпимизировано, то не надо будет думать, что "я это делать не буду - это компилятор САМ прооптимизирует", а потом локти себе кусать. Это путь к деградации, если думать что за тебя все сделает компилятор/оптимизатор. 3. -О3 конечно хорошо, но когда идет отладка, то -О3 отключаешь, и как раз в этот момент не хватает - то оперативки, то флеша, то быстродействия......... (если конечно при проектировании не заложили запас, а все сделали в притык, как часто бывает)
|
|
|
|
|
Apr 2 2012, 09:19
|

Ambidexter
    
Группа: Свой
Сообщений: 1 589
Регистрация: 22-06-06
Из: Oxford, UK
Пользователь №: 18 282

|
Цитата(XVR @ Apr 2 2012, 07:30)  ... у вас там весьма накрученно массивы индексируются Да там индексы не нужны вообще. Например, здесь FsummRe[FreqNum] += Values[i]*cosVal; дважды по 256 раз вычисляется адрес по индексу FreqNum, но этот индекс - константа в подпрограмме. Надо бы поставить A=A+Values[i]*cosVal; в теле цикла, а после цикла сделать обратное присвоение FsummRe[FreqNum]=А
--------------------
Делай сразу хорошо, плохо само получится
|
|
|
|
|
Apr 6 2012, 08:46
|
Частый гость
 
Группа: Участник
Сообщений: 140
Регистрация: 21-04-11
Пользователь №: 64 524

|
Возник следующий вопрос - как в на асме прочитать байтик из памяти программ? Покопавшись в просторах интернета нашёл следующее: CODE ldi ZH, hi8(pm(F64_cos)) ldi ZL, lo8(pm(F64_cos)) lpm r18, Z
F64_cos - таблица значений косинуса. Но чёт это не работает. Код в AVR stidio 4 помнется выглядит примерно так: CODE ldi ZH, high(F64_cos*2) ldi ZL, low(F64_cos*2) lpm r18, Z
Т.е. в AVR stidio 4 нужно делать сдвиг в лево, что равно умножению на 2. Почитав доки на avr-gcc нашёл что макрос pm надо использовать если нужен доступ к памяти программ, но он равносилен деления на 2 или, что тоже самое, сдвигу на 1 в право. На этом моменте у меня сразу возникло не понимание принципа доступа к памяти программ на ассемблере в avr-gcc. Может кто в курсе как делается правильно? Вот весь код на асме, который написал: CODE #include "../Headers/asm_ftransform.h" #include <avr/io.h>
.extern FsummRe // == r9, r8, r7, r6 .extern FsummIM // == r13, r12, r11, r10 .extern CurVal // == r5 .extern Flags .extern FsinTable .extern FcosTable .extern F64_cos
.global DMA_CH0_vect DMA_CH0_vect: push r0 push r1 push ZL push ZH push r17 push r18 push r19
/* load Sin and Cos */ // calc offset ldi r17, 0x35 //mul r5, r17 // r1:r0 - offset
// sin addres ldi ZH, hi8(pm(F64_cos)) ldi ZL, lo8(pm(F64_cos)) //add r16, r0 //adc r17, r1 lpm r18, Z sts _SFR_IO_ADDR(USARTE0_DATA), r18
// DMA.CH0.CTRLB |= 48; // clear interrupt flags ldi r26, 0x30 sts _SFR_IO_ADDR(DMA_CH0_CTRLB), r26 // stop ADC clr r17 sts _SFR_IO_ADDR(ADCA_CTRLA), r17
ldi r17, 2 sts Flags, r17
pop r19 pop r18 pop r17 pop ZH pop ZL pop r1 pop r0
reti
Нашел в чём проблема, мой косяк, F64_cos была в ОЗУ. И собственно чтоб прочитать из памяти программ нужно: ldi ZH, hi8(F64_cos) ldi ZL, lo8(F64_cos) lpm r18, Z без всяких макросов pm(). И новый вопрос: я правильно понимаю, макрос pm() используется для вызова подпрограмм, например: ldi ZH, hi8(pm(s_func)) ldi ZL, lo8(pm(s_func)) call Z так?
|
|
|
|
|
Apr 9 2012, 03:56
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
1. pm не используется для вызова подпрограм. Для вызова подпрограмм используются команды CALL/ICALL/EICALL/RCALL. pm() - program memory, макрос просто умножает параметр на 2. Дело в том, что ОЗУ у AVR адресуется байтово, а flash-память словарно, и адреса всех объектов, лежащих во flash-памяти являются словарными. Поэтому что бы из словарного адреса получить байтовый (для доступа к любому байту flash-памяти), необходимо умножить на 2, или сдвинуть на 1 влево. Доступ к flash-памяти: Код ldi ZH, high(Table_1<<1) ; Initialize Z pointer ldi ZL, low(Table_1<<1) lpm r16, Z+ ; Load constant from program Low-adress + increment Z lpm r16, Z ; Load constant from program High-adress 2. call Z - нет такой команды, есть icall. И насколько я помню при загрузке ZH:ZL для команды icall, сдвигать адрес не надо. Косвенный вызов функции: Код ldi ZH, high(func) ; Initialize Z pointer ldi ZL, low(func) icall ; Load constant from program
Сообщение отредактировал alexeyv - Apr 9 2012, 04:11
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|