ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Понедельник
8 июня
1590611 Топик полностью
klen (Сегодня, 02:35, просмотров: 18)
для меня таки настала новая эра - мульти ядерных микроконтроллеров. с алика приехала muse lab nanoCH32H417. посчупал за вымя.. две сиськи, а не одна как обычно, на осчупь приятно....карашооо 

0. сабж на алике

https://aliexpress.ru/item/1005012151617553.html

1. доки на платку

https://github.com/wuxx/nanoCH32H417

2. никогда мультипроцессорной железной мелочи не трогал, давно хотел понять как оно отлаживается там где нет ОС. не мог отдуплить. оказалось что я не правильно представлял этот космос - как одно устройство с двумя вычислительными модулями. это порочное представлен. правильно понимать в контексте прошивки и отладки - два независимых устройства имеющих как побочный эффект - общее адресное пространство и который могут взаимодействовать..... а могут и нет!

с точки зрения отладчика каждый процессор это отдельный девайс.

для начала работы нужно прицепится через отладочны интерфейс к процам. мы используем openocd:


в файле конфига openocd прописывается не не один таргет, а два:


#interface wlink
adapter driver wlinke
adapter speed 6000
transport select sdi

wlink_set_address 0x00000000
set _CHIPNAME wch_riscv
sdi newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00001

set _TARGETNAME $_CHIPNAME.cpu

#---------------- target 0 ----------------------------------------------------------------------------------

target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME -coreid 0
$_TARGETNAME.0 configure -event reset-init {

    echo "core 0 reset initialized."
}
$_TARGETNAME.0 configure -gdb-port 3333 

$_TARGETNAME.0 configure -rtos FreeRTOS -work-area-phys 0x20100000 -work-area-size 0x4000 -work-area-backup 1
set _FLASHNAME $_CHIPNAME.flash0

flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0

#---------------- target 1 ----------------------------------------------------------------------------------

target create $_TARGETNAME.1 wch_riscv -chain-position $_TARGETNAME -coreid 1
$_TARGETNAME.1 configure -event reset-init {
    echo "Core 1 reset initialized."
}
$_TARGETNAME.1 configure -gdb-port 3334  

$_TARGETNAME.1 configure -rtos FreeRTOS -work-area-phys 0x20104000 -work-area-size 0x4000 -work-area-backup 1
set _FLASHNAME $_CHIPNAME.flash1

flash bank $_FLASHNAME wch_riscv 0x00005000 0 0 0 $_TARGETNAME.1

#----------------config event ------------------------------------------------------------------------------

echo "ready for remote connections"

$_TARGETNAME.0 configure -event gdb-attach {
     echo "GDB_EVENT $_TARGETNAME.0  attach"
     # TODO command
   }
$_TARGETNAME.0 configure -event gdb-detach { 
     echo "GDB_EVENT $_TARGETNAME.0  detach"
     # TODO command
   }
$_TARGETNAME.1 configure -event gdb-attach {
     echo "GDB_EVENT $_TARGETNAME.1  attach"
     # TODO command
   }
$_TARGETNAME.1 configure -event gdb-detach { 
     echo "GDB_EVENT $_TARGETNAME.1  detach"
     # TODO command
   }

что приводит к созданию процессом openocd двух копий gdb monitor-серверов, которые вниз независимо по одному отладочному интерфейсу через wlink педалируют каждый свой таргет hart, то есть процеесор:




таким образом в данном месте можно совершенно независимым копиям GDB присосатся к пртам 3333 и 3334 ( в скрипте конфига это указано явно и можно изменить на свои красивые значения )


слева будут картинки с hart0 ( мелкий процессор v3f ) справа hart1 ( жирненький v5f)

итак - пускаем GDB две шткуи на соответствующие открытые tcp-порты openocd:





тут происходит следующее

1. запуск GDB

riscv32-kgp-elf-gdb -ex "tar ext :3333"
riscv32-kgp-elf-gdb -ex "tar ext :3334"

запись двух прошивок

(gdb) load '/home/klen/mounriver-studio-projects/ch32h417qeu/V3F/obj/ch32h417qeu_v3f.elf'


(gdb) load '/home/klen/mounriver-studio-projects/ch32h417qeu/V5F/obj/ch32h417qeu_v5f.elf'


также подгружены элф файлы как символы чтоб поотлаживать с исходниками.


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

но это не точно.


ФИСЕЁЁ - двухглавй дивайс готов к работе - можно пускать :)


при ресете hart1 спит. запускается hart0 который v3f. он должен себя запустит, настроить RCC и прочие вещи и разбудиь hart1.


в коде для hart0 это овыглядит так

int main(....)
{
SystemInit(); SystemAndCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); Delay_Ms(1000); printf("Привет \"multiсorem\" космос!!!\r\n"); printf("SystemClk:%d\r\n", SystemClock); printf("V3F SystemCoreClk:%d\r\n", SystemCoreClock); Delay_Ms(500); NVIC_WakeUp_V5F(Core_V5F_StartAddr);//wake up V5 HSEM_ITConfig(HSEM_ID0, ENABLE); NVIC->SCTLR |= 1<<4; RCC_HB1PeriphClockCmd(RCC_HB1Periph_PWR, ENABLE); PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFE); HSEM_ClearFlag(HSEM_ID0);

.......

Жирным выделены пасы ручками - обеспечивающие оживление второго жирного ядра hart1 - то есть процессор v5f


если вы дожили до этого места - поздравляю. мы научились запускать многоядерные процессоры.


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


как я понимаю шинная матрица - все таже, за исключением того что теперь она имеет не два мастер-порта - cpu и dma, а три порта - cpu0, cpu1 и dma, то есть с точки зрения системы в общем то ничего не произошло принципиально нового - вместо двух акоров, стало три - шинная матритрца ка диспетчерезировала запросы чтения записи на адресное пространство, так и продолжает это делать, только желающих стало больше на одного.


ФСЁЁЁ, этого в целом достаточно понимать про ТАКОЕ ЖЕЛЕЗО чтоб тупо писать прикладной С-говнокод и зарабатывать баблосики.



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

два проца трутся жёпами на одной поляне, могут друг другу и лапы оттдавить - поэтому нужно как то организовать их в послушное стадо, на это есть три аппаратных механизма:

1. для синхронизации имеется 32 канальныей аппаратный семафор: работает по принципу - кто первый того и тапки. наверно это аппаратный SpinLock

2. имеется стандартное riscv расширение "A"-значит atomic команд. это уже дергалки через интерфейс памяти... ну типа эксклюзивного чтения-записи. накидал по быстрому обертки чтоб поюзать эту фичу

/*
 * riscv32а++.h
 *
 *      Author: klen
 */

#pragma once

// The RISC-V Instruction Set Manual Volume I, Unprivileged Architecture Version 20240411
// Chapter 14. "A" Extension for Atomic Instructions, Version 2.1

// RISC-V: Managing concurrency using spinlocks
// https://strajabot.com/posts/kernel-concurrency-riscv/

// определения общие для расширения riscv32a
namespace riscv32a
{
  __ais__ auto amo_lrw(volatile  int32_t& obj, const  int32_t val) noexcept {  /*TODO  lr.w */ }
  __ais__ auto amo_scw(volatile  int32_t& obj, const  int32_t val) noexcept {  /*TODO  sc.w */ }

  __ais__ auto amo_swapw(volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amoswap.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory"); return result; }

  __ais__ void amo_addw (volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amoadd.w  %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
  __ais__ void amo_andw (volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amoand.w  %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
  __ais__ void amo_maxw (volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amomax.w  %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
  __ais__ void amo_minw (volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amomin.w  %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
  __ais__ void amo_orw  (volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amoor.w   %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
  __ais__ void amo_xorw (volatile  int32_t& obj, const  int32_t val) noexcept {  int32_t result; asm volatile ("amoxor.w  %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}

  __ais__ void amo_maxuw(volatile uint32_t& obj, const uint32_t val) noexcept { uint32_t result; asm volatile ("amomaxu.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}
  __ais__ void amo_minuw(volatile uint32_t& obj, const uint32_t val) noexcept { uint32_t result; asm volatile ("amominu.w %0, %2, %1" : "=r"(result), "+A"(obj) : "r"(val) : "memory");}


  // реализация аппаратного spinlock


  template < auto (*gloabl_interrupt_enable)(), auto (*gloabl_interrupt_disable)() >
  class amo_spinlock_tt
    {
      public:
	     __ai__  amo_spinlock_tt () noexcept
	         {
/*	    	    if( !nested )*/ gloabl_interrupt_disable();  // запрещение прерываний
//	    	    nested++ ;
	    	    asm volatile (
	    	      " loop%=:                          \n"
	              "   li t0, 1 			             \n" //#load 1 into t0
	    	      "   amoswap.w.aq t0, t0, %[obj]    \n" //#   t0(1) => amo_obj(?) => t0(?)  ( захват объекта блокировки и чтенте его предидущего сотояния )
	    	      "   bnez t0, loop%=			     \n" //# циклическа проверка по t0 == 1 ( объекта блокировкт уже был занят )
	    	                  : [obj]"+A"(obj) :  : "memory" , "t0" ) ;

	         }
	     __ai__ ~amo_spinlock_tt () noexcept
	         {
	    	    asm volatile (
	              "   amoswap.w.rl zero, zero, %[obj] \n"  //  0 -> amo_obj (сброс объекта блокировкт)
	                         : [obj]"+A"(obj) :  : "memory" ) ;
//	    	    --nested ;
/*	    	    if( !nested )*/ gloabl_interrupt_enable(); // разрешение прерываний
	         }
	  //private:
	     static volatile uint32_t obj ;
	     //static volatile uint32_t nested ;
    };


  template < auto (*gloabl_interrupt_enable)(), auto (*gloabl_interrupt_disable)(), bool interrupt_handling = false >
  class amo_barrier_tt
    {
      public:
	     __ai__  amo_barrier_tt () noexcept : obj(0) {}
	     __ai__ ~amo_barrier_tt () noexcept {}

	     // return true if barrier reject
	     template <typename T>
	     __ai__ bool try_call ( T func ) noexcept
	         {
	    	    if (interrupt_handling)
	    	       {
	    	          if( !nested ) gloabl_interrupt_disable();  // запрещение прерываний
	    	          nested++ ;
	    	       }

                uint32_t res ;
                asm volatile (
	              "   li t0, 1 			              \n" //#load 1 into t0
	    	      "   amoswap.w.aq %[res], t0, %[obj] \n" //#   t0(1) => amo_obj(?) => t0(?)  ( захват объекта блокировки и чтенте его предидущего сотояния )
            	              :   [obj]"+A"(obj), [res]"=r"(res)   :  : "memory" , "t0" ) ;

                if ( !res ) { func(); asm volatile ("   amoswap.w.rl zero, zero, %[obj] \n" :   [obj]"+A"(obj) : : "memory") ; }  //  0 -> amo_obj (сброс объекта блокировкт)

                if (interrupt_handling)
	    	       {
	    	    	 --nested ;
	    	    	 if( !nested ) gloabl_interrupt_enable(); // разрешение прерываний
	    	       }
                return res ;
	         }

	  //private:
	     volatile uint32_t obj ;
	     volatile uint32_t nested ;
    };


#endif
}

using namespace riscv32a ;

это пока черновик но вроде бы заработало как обещают. обязательно почитайте вот это. иначе трудно понять че это за херь
https://strajabot.com/posts/kernel-concurrency-riscv/


3. ворос непосредственного передергивания веревок привязанных к йайцам друг к другу решает аппаратный периферийный модулем IPC который настраивается и потом разруливает какой как и когда проц дернет другой - ну типа генерация прерываний друг другу.








4. в одноядерных микроконтроллерах сигнал прерывания PFIC непосредственно пихается на процессор. а тут че??? какой из процессоров интерраптить? один, оба?? вопрос решается так - здесь PFIC имеет апгрейд на один финтик поросячим хвостиком:

раздел мануала 4.7.5.45 PFIC Interrupt Allocation Register (PFIC_IALLOCRx) (x=0-63) PFIC->IALLOCRx = CoreID ... ну вы все поняли :)... точно поняли? PFIC позволит добавить в микросхему еще процов.. 256 штук... есси кристал не треснет


в коде выглядит так

/*********************************************************************
 * @fn      NVIC_SetAllocateIRQ
 *
 * @brief   Set Interrupt Allocate
 *
 * @param   IRQn - Interrupt Numbers ( >31 )
 *          Core_ID - Core ID
 *            Core_ID_V5F - V5F
 *            Core_ID_V3F - V3F
 *
 * @return  none
 */
__attribute__( ( always_inline ) ) RV_STATIC_INLINE void NVIC_SetAllocateIRQ(IRQn_Type IRQn, uint8_t Core_ID)
{
  if(IRQn > 31)  return ;
  NVIC->IALLOCR[(uint32_t)(IRQn)] = Core_ID;
}


эксепшены ессессено в этот механизм не влазят - ибо это субстанция нативно-внутренне конкретного процессора, в отличие от прерываний - суть внешних событий. поэтому как бы намек

IRQn - Interrupt Numbers ( >31 ) что все что ниже 32 - не шарятся по ядрам






ну теперь осталось все это удобно втащить в эклипсу вместе со всеми тулсами


в первых тестах с ходу v3f завелся на 150мгц а v5f на 500МГц ... из флеша.... думаю загоним код в ITCM , данные в DTCM нажмем на педаль газа... и

охуеем от открывшись перспектив использования субгигарцовых мк:




немного говнеца на любителей масдая. MRS2 сама по себе гамно... так она под виндой еще и встроенный в платку отладчик засрала - пришлось wlinke препрошивать. не заработало под виндой оригинальный пак MRS2. все падает в непонятном месте. В линухе ниче не падат, ибо залитвать в флеш заливает но не стартует отладака. Ну его нах эти проприетарные софтины. за деньги видимо в принципе ничего сделать хорошего не возможно - идея баблосиков ставит крест на изящном... не даром еще древние заметили - сытый писатель не пишет стихов, а сытый музыкан делает музло.


утром я еще понятия не имел как это все бывает на самом деле. итог дня - все понятно. надеюсь Вам было также интересно как и мне. рот закрыл - рабочее место убрал! спасибо за внимание :).