Перейти к содержанию
    

Многоядерность, параллельность: работает, но после сброса

Распределил задачи на три ядра:

1. CPU0 - инит периферии, загрузка задачи в CPU1 и DSP, кодер Рида-Соломона

2. CPU1 - декодер Рида-Соломона

3. DSP - управляющая программа + всё остальное.

 

Основная проблема: при подаче питания на плату - кодер Рида-Соломона на CPU0 возвращает правильнные данные, но DSP при приёме этих данных пакета из эфира с помощью приёмника (трансивер Si4463) - возвращает мусор в проверочных символах. При этом сам пакет приходит правильный.  Проблема именно в проверочных символах.  

Если же сделать сброс (нажать кнопку  RESET на плате MangoPi - allwinner T113-s3), то проверочные символы верны.  И после каждого сброса - данные верны.  Проблема возникает только при подаче питания.

 

Взаимодействие CPU0,CPU1 с DSP : управление - через волатильный флаг, слово в памяти - регион не кеширован, не буферизован, SHARED=1.  Данные - пара буферов - для кодера и декодера. Кешированы, буферизованы, SHARED=1.

#define FEC_CODER_DATA      0xC7D00000 //адрес буфера кодера (кеширован)
#define FEC_DECODER_DATA    0xC7D80000 //адрес буфера декодера (кеширован)

#define FEC_CODER_CONTROL   0x47E00000 //контроль кодера (не кеширован)
#define FEC_DECODER_CONTROL 0x47E80000 //контроль декодера (не кеширован)

#define rsc (*(IO u32*)FEC_CODER_CONTROL)
#define rsd (*(IO u32*)FEC_DECODER_CONTROL)

 

Процедура запуска кодирования со стороны DSP - buf - входной и выходной буфер.  Для простоты - приведен пример синхронной работы кодера (вынуждающий ждать его завершения работы, что сводит "на нет" преимущества многоядерности, но зато более понятен).  Есть и асинхронный режим - когда возвращается закодированное содержимое с предыдущей итерации, а даётся команда на кодирование новых данных - без ожидания.

static dtype *RSC=(dtype*)FEC_CODER_DATA;
static dtype *RSD=(dtype*)FEC_DECODER_DATA;

void FEC_Encode(u16 *buf)
{
 memcpy(RSC,buf,Fsize);
 __asm__ __volatile__ ("" ::: "memory");
 xthal_dcache_region_writeback(RSC,Fsize);
 __asm__ __volatile__ ("" ::: "memory");
 rsc=0;
 __asm__ __volatile__ ("" ::: "memory");
 while(!rsc);
 __asm__ __volatile__ ("" ::: "memory");
 xthal_dcache_region_invalidate(&RSC[Fsize>>1],EE<<1);
 __asm__ __volatile__ ("" ::: "memory");
 memcpy(&buf[Fsize>>1],&RSC[Fsize>>1],EE<<1);
}

Процедура кодирования со стороны CPU0:

void FEC_Encode(void)
{
 if(!rsc)
 {
  cache_inv_range((u32)RSC,((u32)RSC)+Fsize);
  __asm__ __volatile__ ("" ::: "memory");
  memcpy(EncodeBuf,RSC,Fsize);
  __asm__ __volatile__ ("" ::: "memory");
  encode_rs(EncodeBuf);
  memcpy(&RSC[Fsize>>1],&EncodeBuf[KK],EE<<1);
  __asm__ __volatile__ ("" ::: "memory");
  cache_flush_range((u32)&RSC[Fsize>>1],((u32)&RSC[Fsize>>1])+(EE<<1));
  __asm__ __volatile__ ("" ::: "memory");
  rsc=1;
 }
}

При приёме вместо содержимого проверочных символов:

 memcpy(&RSC[Fsize>>1],&EncodeBuf[KK],EE<<1);

Прилетает мусор (при каждом новом включении - разный).  При этом проверял пакет данных : перед передачей, во время передачи, после передачи - он правильный.  Проблема при приёме - проверочные символы битые.  При сбросе проблема уходит.

Если же сделать кодирование на одном ядре - средствами DSP, то проблема не возникает:

void FEC_Encode(u16 *buf)
{
 memcpy(EncodeBuf,buf,Fsize);
 encode_rs(EncodeBuf);
 memcpy(&buf[Fsize>>1],&EncodeBuf[KK],EE<<1);
}

Настройка MMU CPU0,1 для "регистров" rsc/rsd (управление):

		mmu_tlb_address[i + (dram_base>>20)] =
                            (dram_base + (i << 20))   |
									     (0 << 19)    |
										 (0 << 18)    |
										 (0 << 17)    |
										 (1 << 16)    | //SHARED
										 (0 << 15)    |
										 (0 << 12)    | //TEX
										 (3 << 10)    |
									         (0 <<  9)    |
										 (15 << 5)    |
										 (0  << 4)    |
										 (0  << 3)    |  //Cacheble
										 (0  << 2)    |  //Bufferable
										 (2  << 0);

Настройка MMU для буферов:

	mmu_tlb_address[i + (dram_base>>20)] =
                            (dram_base + (i << 20))   |
							             (0 << 19)    |
										 (0 << 18)    |
										 (0 << 17)    |
										 (1 << 16)    | //SHARED
										 (0 << 15)    |
										 (0 << 12)    | //TEX
										 (3 << 10)    |
									         (0 <<  9)    |
										 (15 << 5)    |
										 (0  << 4)    |
										 (1  << 3)    |  //Cachable
										 (1  << 2)    |  //Bufferable
										 (2  << 0);

 

 

В чём может быть проблема?

 

 

Изменено пользователем repstosw

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 часа назад, repstosw сказал:

В чём может быть проблема?

Чет много текста)) Проблема-то в чем, работа проца, ДСП или межпроцессорной передачи данных? Если ДСП при включении, значит что-то не очищается при сбросе, надо чистить вручную, если межпроцессорная передача, то делать через память, а не фифо всякие, ну и тестировать при отключенном кэше\мму...

Изменено пользователем mantech

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Just now, mantech said:

то делать через память, а не фифо всякие, ИМХО...

Читать пробовали? 🤣

Где вы здесь FIFO  увидели?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 часов назад, repstosw сказал:

но DSP при приёме этих данных пакета из эфира с помощью приёмника (трансивер Si4463) - возвращает мусор в проверочных символах.

А если это делать на CPU, все норм?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

16 minutes ago, mantech said:

А если это делать на CPU, все норм?

На CPU всё давно отлажено и отлично работает при любых условиях.   Проблема с DSP: слишком капризная архитектура.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 минут назад, repstosw сказал:

Проблема с DSP: слишком капризная архитектура.

Очищать все регистры при старте, которые возможно?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 10/22/2023 at 10:01 AM, repstosw said:

На CPU всё давно отлажено и отлично работает при любых условиях.   Проблема с DSP: слишком капризная архитектура.

аппаратный спинлок пробовали или капризный DSP мешает ? в мануале по шагам алгоритм расписан как обращаться к общим данным с двух ядер с поллингом спинлока

Quote

3.13.3.2 Typical Application

The following figure shows a typical application of the spinlock. A processor locks spinlock0 before executing
specific codes, and then unlocks the codes. After the lock is freed, other processors can read or write the data.

Quote

3.13.4.3 Taking/Freeing Spinlock

Take the synchronization between CPUX and DSP with Spinlock0 as an example, the CPUX and DSP perform
the following steps.

 

Изменено пользователем sasamy

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, sasamy said:

аппаратный спинлок пробовали или капризный DSP мешает ? в мануале по шагам алгоритм расписан как обращаться к общим данным с двух ядер с поллингом спинлока

А вы точно уверены, что он для моих целей подойдёт?

Мне нужен аппаратный флаг, который оба ядра могли бы читать и писать, чтобы знать, что один работу завершил - и можно забрать данные, и потом снова пнуть чтобы работал с новыми данными.

Семафор нужен.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 10/22/2023 at 1:37 PM, repstosw said:

Мне нужен аппаратный флаг, который оба ядра могли бы читать и писать, чтобы знать, что один работу завершил - и можно забрать данные, и потом снова пнуть чтобы работал с новыми данными.

прочтите мануал - у спинлока как раз такая семантика, одно ядро прочитало 0 значит блокировка захвачена и можно выполнять код над шареным буфером, а второе будет читать 1 и накручивать циклы ожидания 0, первое ядро закончило работу и пишет в спинлок 0 освобождая его, второе читает 0 захватывает блокировку и начинает свою работу над шареными данными, первое ядро при попытке получить блокировку будет читать 1 и накручивать циклы ожидания 0, и так по очереди.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, sasamy said:

прочтите мануал - у спинлока как раз такая семантика, одно ядро прочитало 0 значит блокировка захвачена и можно выполнять код над шареным буфером, а второе будет читать 1 и накручивать циклы ожидания 0, первое ядро закончило работу и пишет в спинлок 0 освобождая его, второе читает 0 захватывает блокировку и начинает свою работу над шареными данными, первое ядро при попытке получить блокировку будет читать 1 и накручивать циклы ожидания 0, и так по очереди.

Читаю сейчас соответствующую главу в мануале.   Я не вижу отличий между спинлоком и некешируемым волатильным флагом в памяти.   В чём преимущества спинлоков перед волатильными флагами?

Когда работал с TMS320C6745, то синхронизировал их без спинлоков, всё работало.

Ниже попытался объяснить, что в конечном итоге мне надо.

Со стороны CPU - код кодера.  Данные во время работы гарантированно не используются другими ядрами:

while(1) //вечный цикл, более ничего не делаем
{
 if(coder_flag==0) //DSP дал команду запустить кодер и произвести чтение-запись данных
 {
  read_data(); //читаем SHARED данные :  входные

  code();  //проводим вычисления

  write_data(); //записываем в SHARED данные : выходные
   
  barrier(); //барьер компилятору

  coder_flag=1; // готово!  флаг для DSP
 }
}

Cо стороны DSP - управляющий:

main()
{
 init_all(); // инит всего и вся
  
 while(1) //вечный цикл
 {
  do_something(); //что-то делаем

  encode_to_cpu(); //даём команду на запуск кодера для CPU

  do_something2(); //что-то другое делаем
 }
  
}

void encode_to_cpu()
{
 while(!coder_flag); //CPU свободен?  если нет, то ждём (на самом деле этот цикл быстро завершается,
                     //так как длительность итерации цикла главной программы - больше, чем время кодирования CPU)

 read_data(); //читаем SHARED данные - CPU отработал и дал нам данные предыдущей итерации

 write_data(); //записываем новые данные SHARED для CPU

 barrier(); //барьер компилятору

 coder_flag=0; //запускаем команду для CPU (кодер) - для новой итерации
}

 

В чём преимущества использования здесь спинлоков вместо coder_flag ?

Изменено пользователем repstosw

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 10/22/2023 at 3:25 PM, repstosw said:

Я не вижу отличий между спинлоком и некешируемым волатильным флагом в памяти. 

интересно посмотреть как с флагом в памяти можно сделать такое и не накручивать бесполезные циклы ожидания

Quote

3.13.4.2 Processing the Interrupt

The spinlock generates an interrupt when a lock is freed (the lock status converts from the locked status to the
unlocked status).

если от msgbox прерывания DSP 3,4 полагаю от спинлоков где-то рядом, для арма номер в таблице есть

Изменено пользователем sasamy

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

33 minutes ago, sasamy said:

если от msgbox прерывания DSP 3,4 полагаю от спинлоков где-то рядом, для арма номер в таблице есть

Вы слишком усложняете всё. Не нужны мне прерывания от спинлоков. Мне достаточно в нужное премя прочесть его состояние, что свободен.

Потому что дизайн полностью синхронный: 1 итерация цикла main = 1 итерация кодирования = 1 итерация декодирования.

33 minutes ago, sasamy said:

интересно посмотреть как с флагом в памяти можно сделать такое и не накручивать бесполезные циклы ожидания

Что значит "бесполезные циклы ожидания"?  Если данные не готовы, то по любому нужно будет ждать.  В моём случае кодер-декодер отрабатывают быстрее, чем итерация цикла в main.

И запускаются кодер-декодер синхронно. А это значит, что к следующей итерации цикла- кодер и декодер будут гарантированно свободны, и не нужно будет ожидать их освобождения.

В общем, я думаю, что спинлоки здесь не подойдут. Потому что мне не нужно лочить DSP, пока CPU считает данные.

Кроме того, мне не понятна сама реализация спинлоков. Есть пара регистров - STATUS и LOCK.  Первый проверяет занят ли спинлок.  Чтение второго приводит к захвату, =0 если захват успешен.  Запись в него нуля освобождает спинлок.   Теперь как это соотнести к функциям CPU и DSP?

Что первично?  Если  в цикле CPU сделать захват спинлока, затем произвести манипуляции с памятью, затем освободить спинлок, то CPU входит в бесконечный цикл - эти действия (захват- работа - освобождение)  делаются бесконечно.

#define SPINLOCK_STATUS  (*(IO u32*)(SPINLOCK_BASE+0x010))
#define SPINLOCK_LOCK(N) (*(IO u32*)(SPINLOCK_BASE+((N)<<2)+0x100))

#define CHECK_SPINLOCK_STATUS(N) while(SPINLOCK_STATUS&(1UL<<(N)))

#define CHECK_SPINLOCK_LOCK(N) while(SPINLOCK_LOCK(N))

#define SPINLOCK_FREE(N) SPINLOCK_LOCK(N)=0

void FEC_Encode(void)
{
 while(1)
 {
  CHECK_SPINLOCK_STATUS(0);
  CHECK_SPINLOCK_LOCK(0);

  shared_memory_read();

  encode_rs(shared_memory);

  shared_memory_write();

  SPINLOCK_FREE(0);
 }
}

  А мне надо, чтобы DSP пинал CPU когда нужно.

В общем нестыковка с этими спинлоками. И не уверен, что в моём контексте они точно подходят.

C флагами намного прозрачнее:  CPU ждёт флаг=0.   DSP производит действия с памятью и выставляет флаг=0.   CPU увидев что флаг=0 начинает  кодирование.  В конце CPU выставляет флаг =1 и снова ждёт пока DSP не скинет его в 0.   DSP в следующей итерации видит флаг =1,  готовит новые данные/забирает старые  и снова выставляет флаг =0 для CPU.   CPU ждал флага=0  и дождался: новая итерация кодирования.   А со спинлоками как??

 

От CPU требуется только считать кодер. Более он не нужен.

void main_secondary(void) //CPU1 - decode
{
 LowLevel_Init();

 while(1)FEC_Decode(); //decode cycles (when DSP set flag_decode=0)
} 

void main(void) //CPU0
{
 LowLevel_Init();

 FEC_Init();          //init codec

 CPU1_Run(main_secondary);                  //Run CPU1 - decoder
 sunxi_dsp_init(DSPimage,sizeof(DSPimage)); //Run DSP - main

 while(1)FEC_Encode();  //encode cycles (when DSP set flag_encode=0)
}

 

P.S. Кстати, проблема этого топика решена.  Была некорректная работа с кешем после сброса:

https://electronix.ru/forum/topic/157083-allwinner-t113-s3-udelal-hifi4-dsp-smeyatsya-ili-plakat/?do=findComment&comment=1882985

 

 

 

Изменено пользователем repstosw

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Наконец-то удалось заставить работать спинлоки, как нужно для моего алгоритма. Как всегда, в даташите пример программы с опечатками. Мне эта картинка дала 100% понимание о работе спинлоков:

image.png.398fcbdc4801dcc04cd3ec1cfdaec5c0.png

CPU0, инициализация кодера и декодера:

void FEC_Init(u8 mln)
{
 SPINLOCK_ENABLE(); //разрешение и сброс спинлоков

 while(SPINLOCK_LOCK(0)); //захват спинлока 0 (кодер)
 while(SPINLOCK_LOCK(1)); //захват спинлока 1 (декодер)

 printf("T113-s3 FEC\n");
}

 

CPU0 - кодер:

void FEC_Encode(void)
{
 if(!(SPINLOCK_STATUS&(1<<0))) //если спинлок 0 свободен
 {
  cache_inv_range((u32)RSC,((u32)RSC)+Fsize);

  memcpy(EncodeBuf,RSC,Fsize);

  encode_rs(EncodeBuf); //собственно кодирование

  memcpy(&RSC[Fsize>>1],&EncodeBuf[KK],EE<<1);

  cache_flush_range((u32)&RSC[Fsize>>1],((u32)&RSC[Fsize>>1])+(EE<<1));

  while(SPINLOCK_LOCK(0)); //захват спинлока 0 (для DSP, индикатор того, что данные закодированы и готовы)
 }
}

 

DSP - кодер (даёт команду для CPU0):

void FEC_Encode(u16 *buf)
{
  while(!(SPINLOCK_STATUS&(1<<0))); //ждём, пока спинлок 0 свободен

  memcpy(EncodeBuf,buf,Fsize); //сохраняем текущий не закодированный буфер 

  xthal_dcache_region_invalidate(RSC,Bsize);

  memcpy(buf,RSC,Bsize); //копируем результат предыдущей итерации

  memcpy(RSC,EncodeBuf,Fsize); //загружаем данные для CPU0 - новая итерация

  xthal_dcache_region_writeback(RSC,Fsize);

  SPINLOCK_LOCK(0)=0; //освобождаем спинлок 0 (запуск CPU0)
}

 

Хедер:

#define SPINLOCK_BASE 0x03005000

#define SPINLOCK_STATUS  (*(IO u32*)(SPINLOCK_BASE+0x010))
#define SPINLOCK_LOCK(N) (*(IO u32*)(SPINLOCK_BASE+((N)<<2)+0x100))

#define SPINLOCK_ENABLE()                          \
{                                                  \
 SPINLOCK_BGR_REG&=~(1<<16); /* assert reset    */ \
 SPINLOCK_BGR_REG|=1;        /* gating pass     */ \
 SPINLOCK_BGR_REG|=(1<<16);  /* de-assert reset */ \
}                                                  \

 

Получилась немного инверсная логика работы, но на практике тот результат, что мне нужен.

И всё-же мне непонятно, чем эти спинлоки лучше, чем волатильные некешируемые флаги.  Именно в моём примере.  Атомарность? Вроде - нет, запись в регистр никогда не атомарна.  И немного выглядит странной логика захвата спинлока - чтением регистра.  Обычно статус-флаги записываются и потом читаются.  А тут непривычно.

Изменено пользователем repstosw

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

36 minutes ago, repstosw said:

Получилась немного инверсная логика работы, но на практике тот результат, что мне нужен.

Переделал на прямую логику. Тоже работает:

CPU0:

void FEC_Init(u8 mln)
{
 SPINLOCK_ENABLE();       //разрешение и сброс спинлоков.  Все спинлоки свободны -начальное состояние
 printf("T113-s3 FEC\n");
}

CPU1 Декодер:

void FEC_Decode(void)
{
 if(SPINLOCK_STATUS&(1<<1)) //если спинлок 1 занят
 {
  cache_inv_range((u32)RSD,((u32)RSD)+Bsize);

  memcpy(DecodeBuf,RSD,Fsize);
  memcpy(&DecodeBuf[KK],&RSD[Fsize>>1],EE<<1);

  int r=eras_dec_rs(DecodeBuf,NULL,0); //декодирование

  if(r==-1)printf("Decode Error\n");
  else if(r>0)
  {
   memcpy(RSD,DecodeBuf,Fsize);
   memcpy(&RSD[Fsize>>1],&DecodeBuf[KK],EE<<1);

   cache_flush_range((u32)RSD,((u32)RSD)+Bsize);
  }

  SPINLOCK_LOCK(1)=0; //освобождаем спинлок 1
 }
}

DSP - команда декодеру (CPU1):

void FEC_Decode(u16 *buf)
{
 while(SPINLOCK_STATUS&(1<<1)); //ждём пока спинлок  занят

 memcpy(DecodeBuf,buf,Bsize);

 xthal_dcache_region_invalidate(RSD,Bsize);

 memcpy(buf,RSD,Bsize);

 memcpy(RSD,DecodeBuf,Bsize);

 xthal_dcache_region_writeback(RSD,Bsize);

 while(SPINLOCK_LOCK(1)); //захват спинлока 1
}

 

Изменено пользователем repstosw

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

40 минут назад, repstosw сказал:

Атомарность? Вроде - нет, запись в регистр никогда не атомарна.

Да, спинлоки реализуются на инструкциях атомарного доступа CPU.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...