[Xmos] Xmos架构

xmos 架构及 程序编程

1.事件驱动多个线程

2.每个处理器都由硬件支援并行运行多个线程的功能

3.线程利用单一指令完成数据处理

4.数据传输速度可以用计时器或时序控制完成

5.同一处理器线程间通讯没有时间延迟,处理器间时延固定

6系统被分成多个但愿,一个单元由一组硬件资源组成

7.每个单元(title) 中有多个核心(core)

程序设计模式

XC C与 C++ 混合编写

XC 具有C 延伸特性

并发运行 输入输出及时间控制

多重回传

XC 不支持float longlong 位元 volatile 数据类型 也不支持 goto

输入输出

通过 port连接处理器和实体管脚

所有port必须时全局变量,
可以将函数传给port,但不能出现超过 两个以上的函数引用

管脚发生转变时

oneBit when pinsneq

计时器

32位计时器100MHz 主频

系统中的每个 title 都有一个参考时钟。在XMOS设备上,这被定义为始终为100MHz时钟



要获取计时器计数器的当前值,可以使用 :> 运算符

bash 复制代码
uint32_t time;
 t :> time; // this reads the current timer value into the variable 'time'

测量程序 运行时间

bash 复制代码
 uint32_t start_time, end_time;
 t :> start_time;
 // Do something here
 t :> end_time;
 printf("Number of timer ticks elapsed: %u", end_time- start_time);

记住32位计数器的范围。在100MHz的情况下,你只能有意义地测量到232-1个刻度(约42秒)。

定时触发

计时器可以在计数器达到某个值时触发事件。可以在选择语句中对这些事件作出反应。此类情况的语法是:

bash 复制代码
 case timer when timerafter ( value ) :> [ var | void ] 

如下是定时触发的案例

bash 复制代码
 uint32_t x;
 timer t;
 ...
 select {
 case t when timerafter(x) :> void:
 // handler the timer event
 ...
 break;
 }

1ms的定时中断

bash 复制代码
 timer t;
 uint32_t time;
 const uint32_t period = 100000; // 100000 timer ticks = 1ms
 // get the initial timer value
 t :> time;
 while(1) {
 select {
 case t when timerafter(time) :> void:
 // perform periodic task
 ...
 time += period;
 break;
 }
 }

串口 115200 接收

事件触发编程

任务可以使用select结构对事件进行操作,不满足任何条件时,任务会暂停

一个 select 可以等待多个事件,并处理第一个发生的事件。select 语句的语法类似于 C 语言的 switch 语句:

复制代码
select {
 case event1 :
 // handle the event
 ...
 break;
 case event2 :
 // handle the event
 ...
 break;
 }

跟mcu 中断类似,但对比中断,对事件响应的时间提高

代码执行期间不会被中断

事件主要包括 其他任务发起的事务、定时器时间、外部io事件

内存

在每个title 内有256kB 到64kB 内存

内存在title内无缓存

每个内存只能在对应的tile 内访问,没有数据总线

内存加载或存储 在每个core 内执行1到2个周期

不同的core 之间不共享内存,可以通过核间通讯进行交流

并行运算

并行运算可以指定到对应的title 及core 中

bash 复制代码
 #include <platform.h>
 ...
 int main() {
 par {
 on tile[0]: task1();
 on tile[1].core[0]: task2();
 on tile[1].core[0]: task3();
 }
 }

如果不进行指定,系统会自动分配给芯片空闲的core上

通讯

任务之间都是点对点进行连接的

xC 编程提供三种通讯方法 interfaces,channels ,streamingchannels

interfaces

接口

接口提供一个在任务之间执行类型实务的方法

允许程序在任务之间拥有多个事物功能 (远程函数调用)

接口允许在同一个core内运行的task之间进行通讯

接口也可作为其他同步通讯过程中异步发送通知

接口可使用结构体定义多个数据类型如下:

bash 复制代码
 interface my_interface {
 void fA(int x, int y);
 void fB(float x);
 };

客户端

client interface T

服务器端

server interface T

T是interface的类型 对应 my_interface

客户端调用函数,传递参数

bash 复制代码
 void task1(client interface my_interface i)
 {
 // 'i' is the client end of the connection,
 // let's communicate with the other end.
 i.fA(5, 10);
 }

服务器端作为函数引用,任务可以使用选择结构等待事务的发生

bash 复制代码
void task2(server interface my_interface i)
 {
 // wait for either fA or fB over connection 'i'.
 select {
 case i.fA(int x, int y):
 printf("Received fA: %d, %d\n", x, y);
 break;
 case i.fB(float x):
 printf("Received fB: %f\n", x);
 break;
 }
 }

select 可以处理多种不同类型的事务,可以等待来自多个不同源的多种不同类型的事物。select 中一个事务被初始化并处理时,其他事件不会做处理(同时只处理一件事)

bash 复制代码
 int main(void)
 {
 interface my_interface i1;
 interface my_interface i2;
 par {
 task1(i1);
 task3(i2);
 task4(i1, i2)
 }
 return 0;
 }

task4 同时 处理task1和task3的事物,task4是服务器端

bash 复制代码
void task4(interface my_interface server i1,
 interface my_interface server i2) {
 while (1) {
 // wait for either fA or fB over either connection.
 select {
 case i1.fA(int x, int y):
 printf("Received fA on interface end i1: %d, %d\n", x, y);
 break;
 case i1.fB(float x):
 printf("Received fB on interface end i1: %f\n", x);
 break;
 case i2.fA(int x, int y):
 printf("Received fA on interface end i2: %d, %d\n", x, y);
 break;
 case i2.fB(float x):
 printf("Received fB on interface end i2: %f\n", x);
 break;
 }
 }
 }

通知机制

通过接口客户端发起通讯,然而有时服务器端需要独立给客户端发消息

通知提供了一种方式,让服务器在客户端发起呼叫的情况下,单独联系客户。

他是异步非阻塞 的,即服务器端可以发出信号,然后继续处理

notification

C 复制代码
interface if1 {
 void f(int x);
 [[clears_notification]]
 int get_data();
 [[notification]] slave void data_ready(void);
 };

有两个正常函数 f和 getdata,还有一个通知函数 data_ready

  • 通知函数必须声明slave,用于指示通讯方向是服务器到客户端
  • 使用[[notification]]标定为通知函数
  • 通知函数返回和参数都为 void

\[notification\]\] 和slave 是方便语言扩展,有可能salve 函数不需要通知函数 当server 端发起通知,client 端会触发事件,重复的通知是无效的,直到client 清除通知。 client 调用 \[\[clears_notification\]\] 标识的函数时,会清除通知 ```bash void task(server interface if1 i) { ... i.data_ready(); ``` 通过上述代码进行client通知,task 非阻塞的,当调用data_ready() 后会触发一次 后续未清除通知,再次调用不生效 client 端接收通知 ```bash void task2(client interface if1 i) { i.f(5); select { case i.data_ready(): int x = i.get_data(); printf("task2: Got data %d\n",x); break; } } ``` client 通过select 处理接收通知,并调用get_data()清除通知,方便以后server 端继续发送通知 #### 通过interface 进行数据传输 ```bash interface my_interface { int get_value(void); }; ``` client 端可以使用从服务器传回的接口函数调用的结果 ```bash void task1(client interface my_interface i) { int x; x = i.get_value(); printintln(x); } ``` server 端 可以声明一个变量用于返回值,可以在事件主体中,进行变量赋值,然后在事件完成后返回给client端 ```bash void task2(server interface my_interface i) { int data = 33; select { case i.get_value()-> int return_val: // Set the return value return_val = data; break; } } ``` interface 之间也可以使用 数组进行数据的双向传递 ```bash interface my_interface { void f(int a[]); }; ``` client 端传入数据 ```bash void task1(client interface my_interface i) { int a[5] = {0,1,2,3,4}; i.f(a); } ``` server端修改数据 ```bash ... select { case i.f(int a[]): x = a[2]; a[3] = 7; break; ``` 传递数组时是传递的数据引用句柄,允许服务器进行数组的访问及修改,server端可以使用 memcpy 进行数据的修改操作 ### channels * 允许在任务之间同步传递未指定类型的数据 * 提供的连接是一种阻塞连接 ```bash chan c; par { task1(c); task2(c); } ``` channel中 通过 \<: 进行发送 数据,通过 \>:接收数据 task 1 发送数据5 ```bash void task1(chanend c) { c <: 5; } ``` task2 接收数据, ```bash void task2(chanend c) { select { case c :> int i: printintln(i); break; } } ``` 如果只是接收数据,可以使用一下方式 ```bash void task1(chanend c) { int x; ... // Input a value from the channel into x c :> x ``` 通道输入输出是同步的,通道发送数据后,会被阻塞,直到channel另一端接收到数据后,继续执行代码 如果想解决因为等待而花费的时间 ,可以使用stream channels的形式 ### streamschannels streamschannels 实在两个任务之间建立一个永久的通道,这个channel可以有效的传输数据而不需要同步。 流通道 允许任务之间异步通讯,利用硬件的缓冲fifo,通常是1到2个 word 的大小 通道主要是用于跨Core 通讯使用,主要因为没有类型检查,不同在同一个core内的任务之间进行通讯 ```bash streaming chan c; par { f1(c); f2(c); } ``` ### interfaces arrays ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/76bce32b61264af3a2c195d3617cde0e.png) 一个task 连接多个task 通过一组 interface 进行连接 ,如下 ```bash int main() { interface if1 a[2]; par { task1(a[0]); task2(a[1]); task3(a, 2); } return 0; } ``` task 1 task2 进行函数调用 参数赋值 ```bash void task1(client interface if1 i) { i.f(5); } ``` task3 中对 client 的请求进行处理 ```bash void task3(server interface if1 a[n], unsigned n) { while (1) { select { case a[int i].f(int x): printf("Received value %d from connection %d\n", x, i); break; } } } ``` ### channel arrays ```bash int main() { chan c[2]; par { task1(c[0]); task2(c[1]); task3(c, 2); } return 0; } ``` server 接收数据并处理 ```bash void task3(chanend c[n], unsigned n) { while (1) { select { case c[int i] :> int x: printf("Received value %d from connection %d\n", x, i); break; } } } ``` ### interface client 端扩展 interface 可以为系统的一个组件提供api client 接口扩展提供了一种方式来扩展这个API,增加扩展功能,从而在基本接口之上提供一层 如下uart 接口 ```bash interface uart_tx_if { void output_char(uint8_t data); }; ``` 为了扩展client 接口函数需要声明一个新的函数 ```bash extends client interface T { function-declarations } ``` ```bash extends client interface uart_tx_if : { void output_string(client interface uart_tx_if self, uint8_t data[n], unsigned n) { for (size_t i = 0; i < n; i++) { self.output_char(data[i]); } } } ``` 这里 output_string 扩展了 uart_tx_if ,第一个参数必须是 被扩展的函数,上面的例子名字是self,也可以用其他名字,类似c++ 的继承 这个扩展函数可以被当做 interface func 使用 ```bash void f(client interface uart_tx_if i) { uint8_t data[8]; ... i.output_string(data, 8); } ``` i 用于 output_string 第一个参数传递 ## 灵活创建任务(task) task 有三种类型 * 标准task 在core上运行,并且与其他task 任务独立运行,任务具有可预测的运行事件,并且能够高效地响应外部事件 * 组合task combinable 组合任务可以组合一起运行,方便同一个core内运行多个任务,core 跟进编译器驱动的任务之间的协作多任务处理来切换上下文 * 分发task Distributable 分发任务可以在多个核心上运行,在连接到它们的任务需要时运行 通过任务的形式和时间要求最大化设备的资源使用 ### 组合task 循环处理多个任务事件 具有特点 * 返回值为void * 最后是一个while(1) 语句 * 组合运行在相同的core内 ```bash void task1(args) { .. initialization ... while (1) { select { case ... : break; case ... : break; ... } } } ``` ```bash [[combinable]] void counter_task(const char *taskId) { int count = 0; timer tmr; unsigned time; tmr :> time; // This task performs a timed count a certain number of times, then exits while (1) { select { case tmr when timerafter(time) :> int now: printf("Counter tick at time %x on task %s\n", now, taskId); count++; time += 1000; break; } } } ``` 类似将多个task 组合到一起 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d1828d59e0a347c5b173d658b97650d9.png) 当任务被组合时,编译器生成的代码首先按照未指定的顺序运行每个函数的初始序列,然后进入一个主循环。这个循环使每个任务的主要选择中的案例得以执行,并等待其中一个事件的发生。当事件发生时,将调用一个函数来实现相关任务案例的主体,然后再返回主循环。 ```bash void f() { [[combine]] par { counter_task("task1"); counter_task("task2"); } } ``` 如果不可组合的函数放置在同一个核心上,编译器会报错。或者,可以将一个并行语句标记为在程序中的任何地方组合任务。 在同一逻辑核心上运行的任务可以相互通信,但有一个限制:**组合任务之间不能使用通道。必须使用接口连接。** 可组合函数可以由较小的可组合函数构建而成。例如,以下代码从两个较小的函数 task1 和 task2 构建了可组合函数 combined_task: ```bash [[combinable]] void task1(server interface ping_if i); [[combinable]] void task2(server interface pong_if i_pong, client interface ping_if i_ping); [[combinable]] void combined_task(server interface pong_if i_pong) { interface ping_if i_ping; [[combine]] par { task1(i_ping); task2(i_pong, i_ping); } } ``` ### 分发task distributable 有时任务包含状态并向其他任务提供服务,但不需要自行对任何外部事件做出反应。这类任务仅在与其他任务通信时运行任何代码。因此,它们不需要自己的核心,而可以共享与其通信的任务的逻辑核心 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/2c3685a9739848d1bd2645c767390172.png) task可被分发的条件: * 满足组合task 条件(返回void 最后是while(1) * 函数只响应接口 以下示例展示了一个分布式任务,该任务通过接口连接 i 响应交易以访问端口 p ```bash [[distributable]] void port_wiggler(server interface wiggle_if i, port p) { // This task waits for a transaction on the interface i and // wiggles the port p the required number of times. while (1) { select { case i.wiggle(int n): printstrln("Wiggling port."); for (int j = 0; j < n;j++) { p <: 1; p <: 0; } break; case i.finish(): return; } } } ``` 如果所有连接的任务都在同一块上的话,分布式任务可以非常高效地实现。在这种情况下,编译器不会为其分配一个独立的逻辑核心。例如,假设在以下方式中使用了 port_wiggler 任务: ```bash int main() { interface wiggle_if i; par { on tile[0]: task1(i); on tile[0]: port_wiggler(i, p); } return 0; } ``` 在这种情况下,task1 将被分配一个核心,但 port_wiggler 将不会。当 task1 创建一个与 port_wiggler 相关的事务时,其核心上的上下文将被切换以执行 port_wiggler 中的情况;完成后,上下文又切换回 task1。图13 显示了这种事务的进展。该实现要求客户端任务的核心直接访问分布式任务的状态,因此只有在它们位于同一个 tiles 上时才有效。如果任务跨 tiles 连接,则分布式任务将作为普通任务运行(尽管它仍然是一个可组合的功能,因此可以与其他任务共享一个核心)。如果一个分布式任务连接到多个任务,它们不能安全地同时改变其状态。在这种情况下,编译器隐式使用锁来保护任务的状态。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/14b0a3d4c6954d0caea768275fb48132.png) ## 中断管理 ### Guards 守护 当一个选择在等待多个事件时,有些事件是有条件启用的,即代码只应该在某些保护表达式评估为非零时对它们做出反应。其语法为: ```bash case expr => ... ``` 以下示例仅在变量 periodic_enabled 非零时对定时器事件作出反应: ```bash int periodic_enabled; timer tmr; uint32_t t; ... select { case periodic_enabled => tmr when timerafter(t) :> void: .. break; case ... } ``` 如果被保护的案例是一个接口事务,编译器需要额外的信息才能实现保护。为了使编译器能够做到这一点,如果程序中的任何地方都有接口函数被保护,则必须在接口声明中使用 \[\[guarded\]\] 属性将其标记为可能受保护。例如 ```bash interface if1 { void f(); [[guarded]] void g(); // this function may be guarded in the program } .. select { case i.f(): ... break; case e => i.g(): ... break; } ``` ### Ordering 让select 有优先级 一般来说,select 中的事件没有优先级。如果在 select 执行时有多个事件准备就绪,选择的事件是未指定的。有时,**通过使用 \[\[ordered\]\] 属性强制优先级是有用的,该属性表示 select 以从高到低的优先级顺序呈现事件**。例如,如果在 select 时所有三个事件都已经准备好,选择将是情况 : ```bash [[ordered]] select { case A: ... break; case B: ... break; case C: ... break; } ``` ordered 不能在组合函数和分发函数中使用 ### Replicated cases 重复案例多次迭代相同的案例。如果程序有一个资源数组用于反应,这很有用。例如,以下代码对一个定时器数组及其相关超时进行迭代。 ```bash timer tmr_array[5]; uint32_t timeout[5] unit32_t period[5]; ... select { case(size_t i = 0; i < 5; i++) tmr_array[i] when timerafter(timeout[i]) :> void: .... timeout[i] += period[i]; break; } ``` ## 数据处理和内存保护 ### null 类型 在xC中,资源如接口、通道端、端口和时钟,必须始终具有有效值。可为空的限定符允许这些类型为特殊值null,表示没有值。这类似于某些编程语言中的可选类型。可为空的限定符是一个?符号。因此,以下声明是一个可为空的端口。 ```bash port ?p; ``` 用 isnull 检查空 ```bash if (!isnull(p)) { // We know p is not null so can use it here ... } ``` 在xC中,数组声明需要是一个常量大小。唯一的例外是可以根据一个参数声明为可变大小的局部数组,前提是该参数同时被标记为static和const: ```bash void f(static const int n) { printf("Array length = %d\n", n); int arr[n]; for (int i = 0; i < n; i++) { arr[i] = i; for (int j = 0; j < i; j++) { arr[i] += arr[j]; } } printf("-------\n"); for (int i = 0; i < n; i++) { printf("Element %d of arr is %d\n", i, arr[i]); } printf("-------\n\n"); } ``` 在调用带有静态参数的函数时,参数必须是:一个常量表达式或调用函数的静态常量参数。 ```bash void g(static const int n) { // static parameter can be called with a constant expression argument f(2); // or passing on a static const parameter f(n); } ``` ### 多返回 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/512c4f665e984eaa9608b185724cea6e.png) 内存访问 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4b477e5d8ef0464db2248d06c177d904.png)

相关推荐
Boilermaker19928 分钟前
【Java EE】Mybatis-Plus
java·开发语言·java-ee
aramae14 分钟前
C++ -- STL -- vector
开发语言·c++·笔记·后端·visual studio
Tony小周14 分钟前
实现一个点击输入框可以弹出的数字软键盘控件 qt 5.12
开发语言·数据库·qt
lixzest34 分钟前
C++ Lambda 表达式详解
服务器·开发语言·c++·算法
沉默媛1 小时前
如何安装python以及jupyter notebook
开发语言·python·jupyter
怀揣小梦想1 小时前
微服务项目远程调用时的负载均衡是如何实现的?
微服务·架构·负载均衡
GateWorld1 小时前
RISC-V:开源芯浪潮下的技术突围与职业新赛道 (二) RISC-V架构深度解剖(上)
架构·risc-v·指令集精简·寄存器设计·特权架构·模块化扩展
_Chipen1 小时前
C++基础问题
开发语言·c++
止观止2 小时前
JavaScript对象创建9大核心技术解析
开发语言·javascript·ecmascript