MC3172在评测区刚出现的时候,就被其描述吸引“硬件并行处理的64个线程。”
光从字面上来理解,我认为应该是一个带64个RISC-V的处理器,还可以手动分配频率。
自己所在的公司一直在寻找一个轻量级的多核处理器,用于长时间的神经信号数据记录;30Khz, 16bits, 512~1024 channels的高速数据吞吐率往往无法用一些简单的单片机来满足;这款芯片便引起了我的好奇心。
用MounRiver Studio打开的样板工程也和我之前处理的各类单片机很不一样,根据硬件预配置好的64个线程的入口,归类到函数数组“thread_initial_pointer”中;由entry thread_start 去启动单个线程。thread_start被link到地址“0x00000010”;这应该是硬件限定的启动地址,上电会指令会直接跳转到该入口处开始执行。
128KB的SRAM空间可以被手工划分,具体划分的范围会被定义在lds中;根据提供的3种layout划分的选择,可以看到,CodeSpace的entry地址和DataSpace的起始地址是不变的,应该是用一个简单的逻辑器件来选择地址线的某些bit;这个逻辑器件应该有3种选项来预设长度(注意地址边界的二进制bit的变化规律)
64KB/64KB的分布如下:
MEMORY
{
    CODE_SPACE (x)  : ORIGIN = 0x00000010, LENGTH =  65520 (0x00000010 ~ 0x00010000)
    DATA_SPACE (rw) : ORIGIN = 0x20000010, LENGTH =  64320 (0x20000010 ~ 0x2000FB50)
}

32KB/96KB的分布如下:
MEMORY
{
    CODE_SPACE (x)  : ORIGIN = 0x00000010, LENGTH =  32752 (0x00000010 ~ 0x00008000)
    DATA_SPACE (rw) : ORIGIN = 0x20000010, LENGTH =  98112 (0x20000010 ~ 0x2001 7F50)
}

96KB/32KB的分布为:
MEMORY
{
    CODE_SPACE (x)  : ORIGIN = 0x00000010, LENGTH =  98288 (0x00000010 ~ 0x1 8000)
    DATA_SPACE (rw) : ORIGIN = 0x20000010, LENGTH =  32576 (0x20000010 ~ 0x2000 7F50)
}

MC3172启动的时候会根据配置好的线程数,从地址 0x50000000把当前硬件线程的index取出来,作为数组的映射去调用相应的软件定义的硬件线程对应的入口:
#define THREAD_ID                               (*(volatile u8*)(0x50000000))
void thread_start(void)
{
    (*thread_initial_pointer[THREAD_ID])();
}

thread0是要负责初始化所有的硬件资源;包括内存区域动态映射配置,时钟源,和每个线程的频率分配,最后设置thread0的栈空间,正式跳转到thread0的用户入口处开始执行用户的指令。正是这一段代码,让我对这个芯片是否有64个运算单元产生了一些怀疑,因为thread0的硬件初始化配置必须在其他thread1~63个线程启动之前准备好,如果是64个运算单元必然会在此处需要同步其他core,但是这边并没有相关同步的代码,合理的解释是在thread0进行硬件相关资源初始化的时候应该并行处理还没有开始启动,等到thread0_main正式kick in的时候,硬件并行逻辑才会正式启动;那是不是硬件刻意让出了固定指令的时间留给thread0做初始化(这一段代码是工具自动生成),在跑完指定N个指令后正式启动多个core呢,需要写一些代码来验证这个逻辑。首先来验证其运算器的同步性。
挑选thread0,1,2;在main_entry中toggle不同的GPIO,通过示波器进一步分析指令的同步性,代码很简单,GPIO_PIN0, 1, 2而已。
    GPIO_ConfOutput(GPIOA_BASE_ADDR, GPIO_PIN0);
    while(1){
        //user code section
        GPIO_Toggle(GPIOA_BASE_ADDR, GPIO_PIN0);
   }

从示波器的图形我们可以看的比较清楚了,mc3172本质上是一个时分系统,黄色的是thread0,绿色的是thread1;在toggle的瞬间,两者相差23ns左右。我选择的是外部48Mhz晶振,1s/48Mhz~~20ns;mc3172会按照每个clk的周期,按顺序执行thead 0 , 1, 2~ 63对应的指令。提供的线程配置工具恰恰是用来选择分配外部48Mhz CLK的比例。
scope_2.png
image.png
我选择了4个线程,各自使用12Mhz CLK的资源,可以看到Toggle的周期是~333nS;相当于执行了4条单时钟周期指令。1s/12Mhz ~ 83.3nS;反汇编代码也确实证明Toggle GPIO的代码被编译会2条单周期指令。
scope_0.png
到此,我们不妨来猜测一下MC3172的内部设计,应该只有一个运算器,64个可选的硬件上下文用来保存(sp, pc, cspi,数据寄存器等),用时分的方法在不同的硬件线程中进行顺序切换。在我的程序中,相当于每一个硬件线程分配了一个12Mhz的处理器,这其实并不能等同于软件的实时操作系统,软件系统中不同的thread之间可抢占或者主动释放对CPU的控制等,在这个硬件实时的设计上是没有的;不过,这确实也是一个聪明的做法,非常适用于一些简单的逻辑需要高速和稳定的数据传输的应用案例,考虑到主频最高可以到120Mhz,12个外设口可以配置为SPI/USART,让其扩展性有了进一步的想象空间。对于我所在公司的需求,确实值得去尝试一下,12个SPI口可以连高速ADC芯片,内部安排12~16个硬件线程进行数据的中转,GPIO操作外接Cypress 的FX2LP USB2.0 FX3LP USB3.0芯片等可以实现高速稳定的数据传输需求。这个芯片设计思路上实际可类比的是单片机中常用的DMA器件,这些DMA器件内嵌在总线上,负责不同外设之间的数据中转,大幅度的降低了CPU的负载工作;是我在实际工作中最喜欢的一个器件,但是DMA逻辑比较简单,远不如mc3172来的灵活。我认为MC3172可以作为一个可编程的DMA器件在复杂系统中负责数据的搬运工作,分担其他设备的工作负担,相信在一些高速稳定的数据传输设备中会得到创新性的应用。