LET模型的时间语义编程语言

2022-03-02 08:31陈香兰
计算机工程与应用 2022年4期
关键词:语法结构编译器指令

吴 昊,章 博,陈香兰,王 超,李 曦

中国科学技术大学 计算机科学与技术学院,合肥230026

在实时系统中,如一个汽车、机器人系统,系统的正确性同时取决于逻辑和时间的正确性。系统行为的定时、及时发生需要在软件中明确指定,并在实时平台上正确执行。然而,现有的编程语言缺乏显示表达系统时间属性的语法结构,为此实时系统领域逐渐衍生出了各种实时语言来显示表达系统的时间属性。

嵌入式实时系统编程模型根据其发展历程和应用场景的需求主要可以分为三类:有界执行时间模型(bounded execution time,BET)[1]、零执行时间模型(zero execution time,ZET)[2]以及逻辑执行时间模型(logical execution time,LET)[3]。目前的实时编程语言便基于这三种编程模型。

有界执行时间模型又叫作异步模型,模型通过优先级决定任务的执行顺序,要求任务在既定的截止时间之前完成执行。异步模型的代表性编程语言为Ada[4]。Ada通过调用时间控制函数来指定任务的时间属性与时间行为,其运行时系统是基于线程模型实现的执行环境,在执行环境里多个并发的Ada 任务异步交错执行。但在异步模型中,由于运行时系统缺乏对任务时序行为的约束,可抢占线程在并发情况下的执行过程具有不确定性,导致系统实际执行的时序语义很难与模型层保持一致性,系统行为具有不可预测性。

零执行时间模型又叫作同步模型,模型基于同步假设,即在逻辑上任务的执行时间为零。同步模型抽象层次高,具有数学抽象能力,可采用形式化工具证明其行为正确性。同步模型主要刻画任务的因果关系,通过逻辑时间表达任务的时间行为。基于同步模型的编程语言有Esterel[5]、PRET-C[6]、Lustre[7]以及Signal[8]等。然而,在实际的物理平台中,无法实现无限快的运算速度,系统实际运行过程中不可能按照ZET的时序假设运行。

逻辑执行时间模型介于异步模型和同步模型之间,模型为每个任务指定了一段逻辑执行时间,任务只能在该时间段的开始和结束时刻分别执行输入和输出过程,即进行任务间的同步,而计算过程异步进行,且计算结果必须在输出时刻之前有效。LET 模型约束了决定任务时序行为的关键过程(输入和输出),能直接有效地刻画任务的时间行为。

Giotto[9]、HTL[10]、TDL[11]等现有的基于LET 模型的编程语言,其语言只描述了系统的时间约束,而具体的功能代码则通过C、Java 等常见的高级语言来实现;语言通过特定编译器编译为中间代码E-code[12],在执行时,虚拟机E-machine[12]根据E-code 描述的时间行为驱动功能代码的执行,虚拟机本身则是运行在实时操作系统之上。这种将时间控制程序和功能代码分开的方式并不方便设计,希望通过扩展一种高级语言的方式来实现一种实时编程语言。

本文提出一种基于C 扩展的实时编程语言TBC。TBC基于LET编程模型,在语言层次上,从设计的时间需求出发,引入可观测事件的概念,来简化系统内部的时间约束,降低系统响应延迟,并通过扩展的语法结构来表达系统中的各种时间属性。在编译器层次,本文提出了一种针对实时编程语言的编译技术,通过在编译器前端增加相应的数据结构来表示语法结构中的时间属性,同时对中间语言LLVM IR进行时间指令扩展,使其可以表示系统的时间行为。在之前的工作中作者提出了包含时间语义的指令集TTI[13],将其作为编译器的目标指令。

1 实时语言框架

除了编程模型外,实时语言的区别主要在于其实现方式和编译方式,本文针对这两点提出一种实时语言设计框架,基于此架构来设计实时语言TBC。如图1所示,整个语言框架分为高级语言、中间语言和汇编语言三个层次。

图1 语言设计整体架构Fig.1 Overall architecture of language design

高级语言指实时语言本身,在本文中通过基于C扩展语法结构的方式来实现语言时间语义的扩展。在这里语法结构是指如if、for 一样的语言结构,通过编译器来进行分析处理,相对于Ada、RTOS通过API来指定时间属性的方式,软件程序更加简单,不需要大量的代码来设置定时器,处理不同的时间格式。同时如果程序使用某个平台依赖的API,则该程序的运行不再灵活,时间操作必须是平台依赖的,更重要的是,语法结构可以通过形式化的方式表示其语法和语义,并能对语义进行验证。在高级语言层次,通过扩展的语法结构来表达多个任务并发执行的时间语义。

现有的许多编程框架[14-15]或实时语言基于RTOS来实现。如文献[16]将实时语言Timed-C编译成RTOS代码,而本文将具有时间语义的指令集作为编译器的目标代码,这使得目标平台不需要支持功能复杂的RTOS,只需要增加相应定时器来支持时间语义指令的实现。

具体的,编译器基于LLVM[17]编译器框架,分为前端和后端,并将LLVM IR 作为编译器的中间语言。扩展的语法结构在编译器前端经过分析处理转换为中间语言中的时间指令,此时语法结构所表示的任务并发的执行行为转换为时间指令所表示的串行执行行为。最后中间语言通过编译器后端转换成具体的汇编语言,即中间语言中的时间指令映射到汇编语言中的时间指令。

高级语言中的语法结构和中间语言及汇编语言中的时间指令都是为了约束各个层次中系统的时间行为,但为了便于系统设计者进行编程,语法结构应表示更高层次的时间约束,如任务级的任务周期、任务优先关系等;而时间指令应该更注重平台实现,表示时间操作更具体,如延迟操作delay等。

另外,本文注重时间的本身,即各层次语言都应该明确语言中时间的连续离散与否、时间粒度等时间属性。本文采用文献[18]中时间域的概念来描述时间。时间域指时间所基于的一个数域(如R、N等),系统中的时间可取范围为数域的值域。如图1所示,各层次的时间随着语言的转换也进行相应的转换。

2 TBC的语言模型

2.1 时间域

本文将数域N 作为语言TBC 的时间域,即在TBC中,时间为离散的,并且时间域上的时间粒度为一段实时间,如1 ms、1 μs等,系统中的动作发生在基于离散时间域的某个时间点上,多个动作可以发生在同一个时刻。

CCSL(clock constraint specification language)[19]是嵌入式实时系统的标准描述语言(UML)[20]中描述时钟约束的规范语言,可以用来表示各种连续和离散的时钟。本文通过CCSL来形式化地定义TBC的离散时间域,在TBC默认时间粒度为1 ms,时间域通过CCSL定义为:

其中IdealClk在CCSL中表示一个连续时钟,其单位为“s”,通过对该时钟离散化可以得到一个离散时钟。TBC的离散时间域std 通过一个以1 ms为tick间隔的离散时钟来表示。

2.2 可观测事件

在TBC 中,引入可观测事件的概念。将系统中的事件根据外界环境是否可以观测到,分为可观测事件和不可观测事件两种。可观测事件主要指获取外部传感器的信号、更新执行器的值等动作,这些事件都和外部环境有直接的关联。不可观测事件主要指系统内部各个任务之间的数据通讯等,这些事件不被外界环境可见,其时间行为对外界环境也没有直接的影响。

从设计需求的角度来看,构建实时系统时,所需的时间约束需求便是可观测事件的时间约束,因此,主要关注可观测事件的时间约束。本文以三元组(E,type,P)来表示可观测事件的时间约束。其中E表示一个可观测事件的标识符;type为事件的种类,分为输入事件I和输出事件O;P表示事件发生的周期,为一段实时间,取时间粒度的整数倍。如:(E1,I,10)表示周期为10 ms的输入事件E1。本文以一个事件集合的形式作为系统设计的时间约束需求。

2.3 时间行为

在TBC中,系统包括多个并发的周期性任务,每个任务分为相对独立的I、C、O三部分。任务的输入(输出)可能包括一个或者多个事件来与外界或其他任务进行交互,可以根据任务的输入(输出)是否包含可观测事件,即是否与外界环境进行通讯,将包含可观测事件的输入(输出)叫作外部输入(输出),将包含不可观测事件的输入(输出)叫作内部输入(输出)。

本文主要关注外部输入(输出)的时间约束。外部输入(输出)本身蕴含了两种时间语义:外部输入(输出)首先作为LET编程模型的输入(输出),应在逻辑执行时间的开始(结束)时刻执行;同时因为外部输入(输出)包含了可观测事件,所以应该满足可观测事件的时间约束。在TBC中,任务的逻辑执行时间等于任务周期,因此在构建任务时,通常将可观测事件的周期作为其所在任务的任务周期。

内部输入(输出)适用于任务之间的数据通讯,从外部环境无法观测,其时间行为并不直接影响系统与外界环境的通讯,因此不专门考虑内部输入(输出)的时间行为。从编程模型的角度来看,TBC实际上基于一种放松的LET模型。

2.4 TBC的LET模型

在单个任务内部,拥有可观测事件的外部输入(输出)基于时间触发,每到任务逻辑执行时间的开始时刻(结束时刻)便会触发任务的外部输入(外部输出)的执行。如图2 所示,周期为T的任务T1 的外部输入输出分别在0时刻和T时刻进行触发执行。

图2 外部输入输出的时间行为Fig.2 Time behavior on external inputs and outputs

任务的内部输入输出用于两个数据相关任务的数据同步,两个任务会在逻辑执行时间的某个时间点进行同步。如图3 所示,两个周期都为T的任务T1 和T2,两任务数据相关,T2 依赖于T1 的计算结果;在0时刻,T2 读取外界传感器传来的数据,并在对数据进行计算后,在t1 时刻便通过内部输出和任务T2 的内部输入对计算结果进行同步,任务T2 在t1 时刻得到T1 的计算结果后,再经过自己的计算过程,在T时刻通过外部输出向外输出计算结果;t1 为基于离散时域的某个时刻,不过本文并不关注t1 时刻的具体数值,t1 时刻只需要处于T2 的计算过程完成之后以及T3 的计算过程开始之前。

图3 内部输入输出的时间行为Fig.3 Time behavior on internal inputs and outputs

TBC放松了LET编程模型的时间约束,允许任务存在内部同步点,任务可在其逻辑执行时间段内进行通信,消除了逻辑执行时间模型中周期延迟问题,提升了系统响应能力。如图4所示,对于图3中的两个任务T1和T2,在一般的LET模型中,系统在0时刻获取传感器的数据,在2T时刻才会产生相应的输出;而在TBC中,如图5所示,由于系统可以通过T1 的内部输出和T2 的内部输入在t1 时刻提前进行同步,在T时刻便可向外产生同样的输出,提升了系统的响应能力。TBC语言模型在保障系统与外界交互的时间行为不变的情况下,将系统的内部同步提前,来降低系统的响应延迟。

图4 LET模型系统响应延迟Fig.4 System response delay of LET model

图5 TBC系统响应延迟Fig.5 System response delay of TBC

当系统出现欠采样和过采样的情况时,需要指定系统的时间行为来保障输入与输出关系的确定性,本文规定系统在短周期任务的第一个周期内进行同步。

如图6所示,周期和谐的两个任务T1 和T2,T1 周期为T2 的两倍,在0 时刻T1 读取传感器数据,在T和2T时刻T2 向外界输出结果,本文规定这种欠采样情况下同步点t1 位于任务T2 的第一个周期内,这样保障T时刻和2T时刻的输出结果对应于0 时刻的输入,输入和输出之间可以相互确定。

图6 系统欠采样的时间行为Fig.6 Time behavior of system under-sampling

同理,对于系统发生过采样的情况,如图7所示,周期和谐的两个任务T1 和T2,T2 周期为T1 的两倍,在0时刻和T时刻T1 读取传感器数据,在2T时刻T2 向外界输出结果,本文规定两任务的同步点t1 位于任务T1 的第一个周期内,保障2T时刻的输出结果对应于0时刻的输入,输入和输出之间关系可以确定。

图7 系统过采样的时间行为Fig.7 Time behavior of system over-sampling

任务中的输入输出过程,在逻辑层次被认为是在触发时刻瞬时执行,在具体的物理执行平台上,输入输出过程势必要花费一段时间来执行(即使时间很短),执行过程处于一个时间执行区间上。如图8所示,对于外部输入过程,逻辑层次在0时刻执行,到物理层次上,其执行在一个时间区间[0,t1]上,其中t1 为输入的执行时间长度;同理,对于外部输出过程,逻辑层次在T时刻执行,到物理层次上,其执行在一个时间区间[t2,T]上。

图8 时间映射Fig.8 Time mapping

对于内部输入输出过程,在逻辑层次上,由于其并非在某确定时间点定时触发,只需要满足进行同步的两个输入输出,输出过程在输入过程之前执行。

3 语法结构

3.1 组件

本文用组件来统一表示任务中的I、C、O 过程。组件一共有五种:内部输入组件、外部输入组件、计算组件、外部输出组件、内部输出组件。每个组件会被分配一个执行区间(slot),组件在执行区间执行时不可抢占,组件若提前执行完成,可主动放弃处理器资源;对于任务的输入输出组件来说,执行区间即为从逻辑层次映射到物理层次的时间区间。组件的执行区间通常设置为大于或等于其最坏情况执行时间(worst-case execution time,WCET)的值,避免组件执行出现超时的情况。为了便于分析计算,执行区间的长度以离散时域的时间粒度(ms)为单位,取整数值。

3.2 扩展的语法结构

如表1所示,TBC扩展了7种时间语法结构,来表示系统中的任务、各种组件及其时间属性。

表1 扩展的语法结构Table 1 Extended syntax structure

Task语法结构用于定义一个并发的LET任务,语法结构显示声明了任务的两个参数:任务的唯一标识符以及任务周期。如Task(T1,50){…}表示一个周期为50 ms的LET 任务,Task 语法结构大括号内的部分为任务体,任务体由三个组件构成,即一个输入组件(内部输入组件或者外部输入组件)、一个输出组件(内部输出组件或者外部输出组件)以及一个计算组件。

E_I 语法结构和I 语法结构分别代表外部输入组件和内部输入组件,两个语法结构显示声明了任务输入的两个参数:输入组件的标识符以及组件的执行区间长度;E_O语法结构和O语法结构分别代表外部输出组件或者内部输出组件,两个语法结构显示声明了输出组件的标识符以及组件的执行区间长度;与PRET-C和文献[21]中的语言相同,TBC 采用全局变量作为任务间通讯方式。C语法结构代表任务的计算组件,显示声明了组件标识符和计算过程的执行区间长度。

Pre 语法结构规定了任务之间的数据依赖关系,包括两个参数,分别表示属于不同任务的内部输出组件和内部输入组件,任务通过这两个组件会进行数据同步。如对于两个任务T1、T2,T2 数据依赖于T1,T2 拥有一个内部输入P2,T1 拥有一个内部输出P1,则可通过Pre(P1,P2)表示两个任务之间的数据依赖关系。

3.3 形式化语法

本文通过扩展的巴科斯范式(extended Backus-Naur form,EBNF)来形式化描述TBC的语法规则。EBNF是巴科斯范式(Backus-Naur form,BNF)的一种扩展,通常作为计算机编程语言和形式语言的形式化语法表示方法。TBC 的EBNF 表示如下所示,其中"c_code"表示常规的C语言代码。

4 编译技术

本章将介绍TBC特有的编译技术。编译器通过对TBC程序中的时间语法结构进行分析处理,最终将其转换为特定的汇编指令。

4.1 编译器总体架构

如图9 所示,TBC 编译器基于LLVM 编译器框架,主要包括编译器前端、IR和编译器后端三部分。

图9 编译器总体架构Fig.9 Compiler general architecture

TBC前端分为词法分析、语法分析、分析调度、中间代码生成四个过程。词法分析和语法分析分别通过Flex 和Bison 工具来实现。与常规的编译器不同,TBC程序经过词法分析和语法分析后除了生成抽象语法树(abstract syntax tree,AST)以外,还会生成TBC 特有的中间表示DAG(directed acyclic graph)和TAST(timed AST)。DAG和TAST分别用于表示TBC程序中的时序关系和时间值信息。两种中间表示接下来作为分析调度器的输入,经过静态分析、调度后会生成系统全局的静态调度表。在中间代码生成阶段,抽象语法树会结合静态调度表的调度信息生成LLVM的中间语言IR。

在TBC 编译器中,本文基于IR 扩展了具有时间操作的指令,使得IR拥有定时触发的语义,这里将扩展了时间指令的LLVM IR 叫作TIR(timed LLVM IR)。在编译器的后端,将基于RISCV的时间语义指令集TTI作为后端的目标指令集。

如图10 所示,TBC 程序经过编译器各个步骤进行了多次转换,最终映射到汇编指令。在高级语言层次,TBC通过扩展的语法结构来表示程序中的时间语义;表示时间语义的语法结构在编译器前端转换为中间表示TAST和DAG,分别表示程序中的时序关系和时间值信息;经过分析调度后,程序中的时序关系和时间值信息再转换为调度表所表示的串行时间行为;中间语言TIR通过扩展时间操作指令,根据调度表中的调度信息以定时触发的方式驱动系统运行;最后经过编译器后端的指令映射,TIR中的时间指令最终转换为具有时间语义的汇编指令TTI。

图10 程序转换流程Fig.10 Program translation flow

4.2 中间表示

在编译器前端,程序的功能部分和时间控制部分被分开处理。TBC 对于功能部分的处理和普通编译器相同,通过词法分析和语法分析得到AST,并对AST 进行相应的的语义分析。为了使得编译器能够更好地分析处理时间,TBC编译器将程序中所有与时间有关的信息分为时序关系和时间值信息两部分,并分别用两种中间数据结构DAG图和TAST表示。

DAG 图用于表示组件之间的时序关系,时序关系分为两种。DAG 图如图11 所示,图中实线表示不同任务组件之间的时序关系,即程序中Pre 语法结构所声明的任务之间的数据依赖关系;图中虚线表示LET任务内固有的时序关系,即任务的计算组件要在输入组件和输出组件之间执行。

图11 DAG图Fig.11 DAG figure

TAST 主要有两个作用:一是用于表示系统的结构层次,二是充当一个特殊符号表。如图12 所示,TAST展现了系统的整体结构,根节点TBC 中每个分支代表了一个并发的LET任务,而每个任务则包含多个不同类型的组件节点。同时作为一个符号表,树储存着系统的时间值信息:每个任务节点储存任务的周期值,每个组件节点储存着组件的执行区间长度。

图12 TAST内部结构Fig.12 Internal structure of TAST

4.3 分析与调度

本文采用静态调度的方式,在编译器前端生成静态调度表,调度表中指定了组件执行的顺序和具体时刻。

4.3.1 必要性分析

在得到TAST 后,编译器便可以检查系统可调度的必要条件,即任务利用率和系统利用率都是否小于等于1。具体的,将任务各组件执行区间长度之和除以任务周期,作为任务的利用率,所有任务的利用率都必须要小于等于1,否则终止编译,并给出错误信息。将一个超周期内,系统执行所有组件用时总和除以系统的超周期,作为系统的利用率,系统利用率必须小于1,否则同样终止编译,并给出错误信息。用P表示任务的周期,TT表示系统的超周期,其中TT为所有任务的最小公倍数,Ti(Mj)表示任务i的第j个组件,slot表示组件的执行区间长度。必要性分析形式化表示如下,其中N表示系统任务数量,Ni表示第i个任务组件的数量。

4.3.2 调度表

在初步检查满足系统可调度的必要条件后,通过DAG 图和TAST 中的信息进行静态调度。本文通过对多个周期任务进行循环展开,来集成一个周期为超周期的大循环块,静态调度表中的每一个表项用于表示组件一次执行的具体时间行为,可以通过三元组(M,t1,t2)来形式化表示,其中M表示组件的标识符,t1 表示组件在循环块中的开始执行时间,t2 表示组件的执行区间长度。

4.3.3 调度规则

对于系统复杂的情况,静态调度有可能是个NP-hard问题,常通过启发式算法等来解决。本文不涉及具体的调度算法,而是规定调度中所要遵循的调度规则,调度规则主要指静态调度需要遵循的优先关系,主要包括以下两部分:

(1)静态调度需要满足DAG图中指定的时序关系:同一任务的计算组件要在输入组件和输出组件之间执行;进行数据同步的两个组件,输入组件要在输出组件之后执行。

(2)对于一个实际的系统,会出现多个任务的外部输入(输出)同时定时触发的情况,如两个任务的外部输入同时在0时刻触发执行,需要指定多个任务同时输入(输出)的优先顺序:首先根据RM 调度规则,即任务的周期越短,其输入(输出)越优先执行;若两个任务的周期相同,则按照任务的标识符的大小,如两个周期相同的任务T1 和T2,T1 的输入(输出)优先于T2 的输入(输出)。

4.3.4 调度表处理

调度表中两个相邻表项之间会出现存在空闲时间段的情况,如两个相邻的表项(M1,t1,t2),(M2,t3,t4),如果t1+t2

4.4 中间语言

在IR层次,本文扩展了具有时间操作的指令,使得IR可以表示调度表中组件串行执行的时间行为。本文中将具有时间指令的LLVM IR叫作TIR。

在TIR 中,采用时间触发的系统执行模式[22],根据调度表中预先安排的组件时序行为,来定时驱动组件的执行,且一次只需要处理一个组件,时间触发的方式不但可以提高系统的可预测性,更便于对系统功能和时序的正确性进行验证。

TIR和TBC基于同样的离散时间域,以1 ms为时间粒度,TIR 的时间触发方式通过点时间来展现:对于调度表中的一个表项(M1,t1,t2),代表了系统会在点时间t1 触发组件M的开始执行,与TBC的并发执行语义不同,在TIR中,组件根据调度表中的信息串行执行,执行时间为组件的执行区间长度。

TIR 扩展了绝对延时指令delay_until 指令,其语义表示从当前时间开始延迟到某个时间点,如delay_until i32 10表示延迟到系统时间点10 ms。

同时TIR 扩展了两条时间初始化指令timeinit 和timegran。timegran 用于设置系统的时间粒度,时间粒度为μs的整数倍,如timegran i32 2表示设置时间粒度为2 μs。timeinit指令用于设置当前系统时间。

TIR 程序的整体结构为一个大的循环,TIR 程序结构如图13所示,程序最开始时设置系统时间粒度为1 ms(与TBC 保持一致),每个循环开始时重置系统时间为0,每次循环系统按调度表中的调度信息定时执行组件。

图13 TIR程序结构Fig.13 TIR program structure

对于调度表中的表项,将其映射到的TIR 指令如图13所示,如果表项是一个组件表项,组件功能代码的转换和普通基于LLVM 架构的编译器相同。图13 中,(M1,tstart1,tinterval1)是调度表中的第一个表项,组件M1从时刻tstart1=0 开始执行,通过绝对延时指令delay_until保证M1 占用系统时间到tstart2=tstart1+tinterval1 时刻,因设置的执行区间长度tinterval1 大于组件的WCET,不会出现超时的情况。

4.5 编译器后端

TIR通过定时触发的方式来驱动系统执行,因此作为编译器后端的目标指令集需要支持TIR 的定时触发语义。本文使用时间指令集TTI 作为TBC 编译器的目标指令集,TTI指令集是本实验室提出的具有时间语义的指令集,TTI 基于RISC-V 指令集进行时间语义扩展。RISC-V是一个典型的三操作数、加载-存储形式的RISC 指令集架构,其扩展指令分为标准扩展和非标准扩展两类,其中非标准扩展作为一个高度特殊化的扩展,由用户根据功能需求自定义完成,TTI 指令集基于RISC-V非标准扩展进行定义。

在TTI 中,时间采用时间点的形式来表示,对于段时间,通过段时间的开始时间点和结束时间点来表示。

TTI扩展了多个时间语义的指令,用来表示时间触发的语义,扩展的指令如下:

(1)时间管理指令:setti/getti 指令用于设置/获取系统当前时间点;settg 指令用于设置系统当前时间粒度,为了和TIR的时间粒度统一,将TTI的时间粒度默认设为1 ms。

(2)实时操作指令:delay指令用于将时间延迟到一个绝对时间点。

LLVM 的后端通常可以分为通用代码生成器和后端移植接口两部分,而增加一个LLVM新后端主要指增加相关的后端移植接口。后端移植接口主要包括全局描述实现、寄存器描述实现、指令集描述实现、汇编输出描述实现等。一般后端信息主要采用LLVM 架构中的Tablegen 语言进行后端的描述。TableGen 是LLVM 中用来专门描述目标后端的语言,主要用于描述子目标平台、寄存器文件、调用惯例和指令集等目标平台相关信息。

TBC编译器后端基于开源项目LLVM-RISCV进行扩展,扩展主要包括TTI时间指令的扩展和TIR时间指令与TTI时间指令之间的映射两部分。本文通过Tablegen来描述指令的扩展和指令的映射。

TIR与TTI的映射关系如表2所示,本文通过Tablegen中的DAG匿名匹配模式来实现指令的映射。

表2 指令映射Table 2 Instruction mapping

5 实验设计与分析

5.1 基于TBC构建遥控车控制系统

本文通过一个遥控车控制实例来展示如何用TBC构建实时系统。遥控车控制系统如下:遥控车每隔50 ms接受手柄的控制信号,根据控制信号,计算得到小车当前的速度、加速度等状态信息,以及控制小车电机转动的PWM 波;小车每隔100 ms 将状态信息通过zigbee 通讯模块传送给PC 端,使得小车的状态信息可以被实时获取;同时小车每隔50 ms 将PWM 波传送给电机,电机驱动车轮转动。以上信息可以规约为一个外部事件的集合:(E1,I,50),(E2,O,50),(E3,O,100) ;事件E1 表示接受手柄信号,E2 表示将PWM波传送给电机,E3 表示将状态信息传送给PC端,该集合作为系统设计的时间需求。由此基于TBC模型构建遥控车控制系统如表3 所示。遥控车控制系统包括三个并发的任务,其中任务T1 包含输入事件E1,任务T2 和T3 的分别包含输出事件E2 和E3。

表3 遥控车控制系统任务集Table 3 Task set of remote control vehicle system

遥控车控制系统如伪代码1所示,其中com1到com5表示任务中的计算函数。任务T1 通过计算得到小车的状态信息和PWM 波,通过内部输出P2 将这些数据传送给其他任务,任务T2 和T3 分别通过内部输入P3和P5 获得T1 的输出数据。伪代码1 中的第24、25 行“Pre(P2,P3)”“Pre(P2,P5)”用来表示三个任务之间的数据依赖关系。

伪代码1遥控车控制系统TBC程序伪代码

TBC程序经过前端的分析调度后执行序列如图14所示。对于外部输入输出组件P1、P4 和P6,其执行位于周期开始或结束的时刻,其余组件遵照调度规则中的优先关系执行。

图14 组件执行序列Fig.14 Component execution sequence

具体的调度表信息如下:(P1,0,1),(C1,1,10),(P2,11,1),(P3,12,1),(C2,13,5),(P5,18,1),(C3,19,10),(D,29,20),(P4,49,1),(P1,50,1),(C1,51,10),(P2,61,1),(P3,62,1),(C2,63,5),(D,68,30),(P4,98,1)(P6,99,1)。其中(D,29,20)和(D,68,30)表示系统空闲状态,对应图14 中的空白部分。

程序经过前端的分析调度以及后端的指令映射后,最后转换为TTI 汇编代码,汇编程序代码如伪代码2 所示,这里主要展示TTI程序的时间行为。

伪代码2遥控车系统TTI程序伪代码

其中第1行到第2行表示系统时间粒度的设置,第4行表示每次循环系统时间的重置,第5 行到第7 行则对应三元组表项(P1,0,1),用来控制组件P1 的时间行为。

本文使用实验室开发的专门支持TTI指令的RISCV仿真器来测试TBC 各方面性能,仿真器可精确模拟各TTI指令在流水线各段的行为,以此获得系统运行的具体时间行为(精确到时钟cycle 级别),仿真器的频率设置为1 MHz。

5.2 实验对比分析

除了通过TBC 来实现上述遥控车控制系统外,本文还通过传统的LET模型,基于PSPM[15]编程框架来实现,PSPM运行在stm32F407开发板上,开发板采用基于RAM架构的CortexM4芯片,芯片具有168 MHz的时钟频率。

两种实现方式的系统输入输出抖动如表4 所示。这里系统输入输出抖动是指系统实际I/O开始时间和系统设计时间的差值。基于传统LET 模型的PSPM 编程框架,其底层通过FreeRTOS实时操作系统来实现,输入输出抖动由系统内核开销、时间调度器开销导致,高达几百个时钟cycle;相比之下,TBC使用时间指令来保障系统行为的定时触发,输入输出抖动由delay指令引起,其导致系统实际输入输出的时间比预期时间延迟一个时钟cycle,远小于前者,具有更强的时间可预测性。

表4 系统输入输出抖动Table 4 System input and output jitter

表5为两种实现方式的系统平均端到端延迟,其中端到端延迟1为从接收到遥控信号到输出PWM波的时间,端到端延迟2为从接收到遥控信号到输出状态信息的时间。在传统的LET模型中,任务之间只能在逻辑执行时间的开始和结束时刻进行交互,导致整个系统的端到端延迟时间较长;而TBC 的LET 模型与传统的LET模型相比,增加了外部、内部事件,允许任务在逻辑执行时间内与其他任务进行交互,使得系统端到端延迟降低了大约50%。综上,TBC采用了放松的LET模型,在提高了时间可预测性的同时,相对于传统的LET模型降低了系统的端到端延迟。

表5 系统端到端延迟Table 5 System end-to-end latency

6 结束语

本文提出了一种基于LET 的时间语义编程语言TBC以及相应的编译技术。主要包含以下工作:

(1)提出一种实时语言设计和编译的通用模型,基于此模型可以方便条理地设计一种实时语言。

(2)基于通用语言C,扩展了具有时间语义的语法结构,来实现实时语言TBC;同时基于LET模型,增加了可观测事件的概念,旨在保证系统中与外界环境相关联事件的时间行为,同时降低了系统的响应延迟。

(3)提出了一种实时语言的编译技术,通过在编译器前端增加中间表示DAG 和TAST 来表示语法结构中的时间属性。同时通过对中间语言IR进行时间指令扩展,使得其可以定时触发系统中的行为,并可以映射到特定的时间语义指令集。

未来的主要工作集中于TBC语言设计的完善。目前TIR中的时间和操作相对分离,并不能很好体现定时触发的语义,计划添加定时触发指令TT,其语义为某操作在某时刻定时触发执行;TBC 中的任务皆为周期任务,对于系统中存在的非安全关键的非周期任务,可将其插入到空闲块(D,t1,t2)中执行,使其对周期任务的时间行为不产生影响。

猜你喜欢
语法结构编译器指令
面向理想性能空间的跨架构编译分析方法
《单一形状固定循环指令G90车外圆仿真》教案设计
基于相异编译器的安全计算机平台交叉编译环境设计
运行速度大突破华为《方舟编译器》详解
长沙方言中的特色词尾
浅析古代汉语的名词动用
培养阅读技巧,提高阅读能力
大学英语B级等级考试词汇用法和语法结构解题技巧
中断与跳转操作对指令串的影响
一种基于滑窗的余度指令判别算法