Signal语言简介

Signal语言简介

 

1.        引言

Signal是为实时应用而设计的一种同步(synchronous)语言,而Ada等则被称为(asynchronous)异步语言。在有代表性的三种同步语言中,SignalLustre是声明式的(declarative),Esterel是命令式的(imperative)。

2.        一个例子­WATCHDOG进程

2.1.       问题

一个过程发出一个ORDER,它将在DELAY这段时间内被执行,执行结束后产生一个DONE信号。进程WATCHDOG用于控制DELAY,它也收到ORDERDONE的拷贝。如果一个ORDER不能完成,进程WATCHDOG必须发出一个ALARM

再有,如果一个ORDER还未完成又出现一个新的ORDER,那么重新开始计时。如果一个DONEDELAY以后出现,或者另一个ORDER,那么这个DONE将被忽略。

2.2.       输入和输出信号

假定用整数表示ORDER。这样WATCHDOG将收到一个整数序列,整数之间有若干时间间隔。

WATCHDOG还收到一个DONE信号序列。DONE信号只是一个脉冲,就象按键按下后产生的单个信息。在signal语言中它的类型是event,其编码是一个永远为真的布尔量。

为了计算时间,同步语言不采用语言定义的设施,如秒,其精度不够。时间源也是一个event类型的信号。两个时间event的时间间隔由环境确定。我们假定时间信号的名称是TICK。参数DELAY即是若干个TICK,因此不能给出时间的物理量纲。

当超过了ORDERDONE之间的DELAYWATCHDOG输出ALARM,它可以是一个event,或者更好的是从执行开始统计的TICK数,假定称之为HOUR,其类型是整数。

因此,输入和输出是某种类型的值序列,序列中的每个值在某个瞬间出现。这样的序列称为signalsignal取值的瞬间的集合称为clock

2.3.       WATCHDOG的运行例子

说明WATCHDOG的运行情景,我们使用时间图。在一条水平线上表示每个信号,如果某个时刻出现值,则在该时刻用星号标记,在星号上方标注该值。

假定DELAY=5


 

7               8                      9

ORDER : -----*---------------*----------------------*-------

t                            t     t

DONE : ------------*----------------------------*-----*----

t  t  t  t  t  t  t  t  t  t  t  t  t  t  t  t  t

TICK : --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-

12

ALARM : -----------------------------------*----------------

 

ORDER8出现后的第5TICK出现ALARM此时是自开始以来的第12TICK

另一种更精确的表达方法是只显示出现信号的时刻,用符号表示没有信号。

ORDER :

7

8

9

DONE :

t

t

t

TICK :

t

t

t

t

t

t

t

t

t

t

t

t

t

t

t

t

t

ALARM :

12

 

上图精确地指出ORDER7在第2TICK上到达,第1DONE在第4和第5TICK之间出现。

2.4.       Signal语言的同步假设

一个Signal程序不研究两个信号之间的确切间隔。它只知道信号的相对次序,即是同时出现还是先后出现。

Signal语言的第一个假设是环境可以告知两个信号的发生次序,即是同时出现还是先后出现。在任意一个瞬间,可以没有信号出现,也可以有一个或多个信号出现。Signal程序只考虑至少有一个信号出现的瞬间,此时对信号进行某些计算,产生新值。

第二个假设是计算所花费的时间为0。因此新值是在使用已有数据进行计算的同一逻辑瞬间产生的。

例如,如果在一个ORDER的第5TICK或之前没有DONE信号到达,那么在第5TICK或之后产生ALARM信号。ALARM信号的产生取决于TICKDONE信号,并且必须在它们之后。但我们假设信号产生的时间为0,因此可以在第5TICK的同一瞬间产生ALARM

2.5.       Signal编写的WATCHDOG

以下有编号的是Signal程序。

1 : process WATCHDOG =

2 : % emits an ALARM if an ORDER is not DONE within some DELAY %

3 : { integer DELAY;}

4 : ( ? integer ORDER;

5 : event DONE, TICK;

6 : ! integer ALARM; )

以上是进程的接口,它包括了使用WATCHDOG其它进程需要知道的信息。

DELAY是一个参数。它是一个编译前确定的常数。符号?后面是输入信号,以及它的类型。符号!后面是输出信号,以及它的类型。

7 : (|

这是进程体的开始。进程体由一组方程(equation)组成。方程定义或限制信号的值。方程之间用符号| 隔开:(|…|…|…|)

8 : HOUR ^= TICK

9 : | HOUR := (HOUR$ init 0) + 1

信号可以是输入输出信号,或是本地信号(如HOUR),其声明放在程序文本的最后。HOURTICK信号的计数器,通过它可以知道ALARM的时间。

8行规定HOURTICK在同一瞬间出现,即HOURTICK有相同的时钟(clock),它们是同步信号。^=是时钟等式运算符。当上下文本身不足以定义一个信号的时钟时,那么通过第8行定义HOUR的时钟。

9行定义了HOUR信号的值。由于Signal是声明式语言,它不使用象“HOUR := HOUR+1”这样的语句来修改变量,而是使用以“HOUR$”表达的前一个瞬间的值加1来设置HOUR信号的当前值。“init 0”表示HOUR信号在第一个瞬间的值被置为0

10 : | CNT ^= TICK ^+ ORDER ^+ DONE

11 : | ZCNT := CNT $ init (-1)

12 : | CNT := DELAY when ^ORDER

13 :          default -1 when DONE

14 :          default ZCNT - 1 when ZCNT >= 0

15 :          default -1

CNT是完成一个ORDER所花时间的递减计数器(请注意COUNTSignal的保留字,因此使用CNT)。在一个ORGER到达时把CNT的初值置为DELAY,然后在每个TICKCNT递减,直至出现一个DONE,或减到0

10行定义CNT出现的瞬间,它的时钟是3个输入信号的并集(运算符^+)。

ZCNT的值是CNT前一瞬间的值,它的第1个值是-1,这是WATCHDOG进程不等待终止时的CNT的“休息”值。

12行在ORDER到达时把值DELAYCNT。运算符^是从ORDER抽取类型为event的时钟。如果在该瞬间没有ORDER,但有DONE信号,CNT立刻取“休息”值-1。我们不需要写^,因为DONE的类型是event

如果既没有ORDER也没有DONE信号,那么只要CNT大于等于0,把CNT1,否则CNT保持为-1

whendefault是同步运算符,前者的优先级比后者高。

16 : | ALARM := HOUR when CNT = 0

当递减计数器为0ALARM信号取HOUR的值。注意当CNT=0HOUR总是出现的,因为CNT的时钟包含TICK的时钟,它与HOUR的时钟相同。

17 : |)

17行表示方程组结束。这些方程的次序是任意的,编译器将分析它们之间的依赖关系,并确定何时计算它们。

以下是本地信号的声明:

18 : where

19 : integer HOUR, ZCNT, CNT;

20 : end % WATCHDOG %;

2.6.       WATCHDOG进程的使用

WATCHDOG进程是一个应用程序的一个片段。应用程序将产生WATCHDOG进程的输入,并利用WATCHDOG进程的输出ALARM

可使用文本编辑器或文法引导的图形编辑器编写Signal程序。

可把WATCHDOG进程放在一个独立文件中进行编辑,文件名可以是watchdog.stg。可把DELAY及其值放在另一个名为watchdog.par的文件中,可单独编译该文件。

Signal语言的编译器分析程序的文法,验证时钟定义,并确定计算的顺序。编译器的一个重要阶段是时钟计算(clock calculus),它验证信号的时钟是否精确定义和内聚,是否有迂回性(circulayity)。这些分析可能暴露不是程序员设计的局限性。编译器可能确定某些时钟包含在其它时钟中,这样就不需要频繁计算值,从而提高目标码的效率。

编译器在确认Signal源程序正确后,首先生成的目标码是C程序,然后再通过C编译器生成C的目标码。这样可以自动访问输入/输出、数学函数等C程序库,也可与应用程序的其它部分连接。

要用独立的输入文件描述每个输入信号的值,文件名的命名规则是<信号名>.dat

要用独立的输入文件定义每个信号的时钟,文件名的命名规则是RC_<信号名>.dat。在文件中,用1表示信号的出现,0表示未出现。

以下是上述运行场景的各个输入文件:

RORDER.dat : 7 8 9

RC_ORDER.dat : 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0

RC_DONE.dat : 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0

RC_TICK.dat : 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1

请注意,RORDER.dat中的数值个数与RC_ORDER.dat中的1的个数相同。

输出信号的值放在一个名为WALARM.dat的文件中,其中没有时钟。这是一个包含本地信号的时间图:


 

ORDER :

7

8

9

DONE :

t

t

t

TICK :

t

t

t

t

t

t

t

t

t

t

t

t

t

t

t

t

t

HOUR:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

ZCNT:

-1

-1

5

4

3

-1

-1

-1

-1

5

4

3

2

1

0

-1

-1

5

-1

CNT:

-1

5

4

3

-1

-1

-1

-1

5

4

3

2

1

0

-1

-1

5

-1

-1

ALARM :

12

 

3.        信号

3.1.       Siginal语言中的信号

Signal语言中,信号是一个具有相同类型的值的序列,这些值在某个瞬间出现。信号出现的瞬间的集合是信号的时钟。

这些离散信号不同于其它应用中的连续的模拟信号,但可以通过对连续信号的采样获得离散信号。

Signal语言不考虑两个值之间的物理时间。如果需要,那么把物理时间定义为一个输入信号。Signal程序的环境负责提供间隔相同(例如1秒)的脉冲序列。

Signal语言只考虑事件的相对次序,即一个信号值是在另一个信号值之前出现,还是之后出现,或同时出现。也是由Signal程序的环境负责确定两个信号值的次序。Signal语言可以测试给定值的次序,然后以确定的方式定义随后的动作。

一个信号可在某个瞬间出现,然后在另一个瞬间缺席。用符号表示信号的缺席。在某一个瞬间,如果一个信号缺席,那么必然有另一个信号出现。Signal语言不考虑所有信号都缺席的瞬间。

例如, 以下序列:

a1:

1

2

7

5

a2:

10

123

5

-1

a3:

0

6

13

2

a4:

11

1

 

可以简化为:

a1:

1

2

7

5

a2:

10

123

5

a3:

0

6

13

2

a4:

11

1

 

两个信号如果在同一瞬间一起出现或一起缺席,那么称这两个信号是同步的,它们有相同的时钟(用符号^=表示)。在上例中,a1a3是同步的,即a1^=a3

时钟有偏序关系。例如,a4的所有瞬间也是a3的瞬间,a4的频度小于a3,因此a4的时钟被a3的时钟包含,即a4^<a3。但是a1的时钟与a2的时钟不可比较。

3.2.       信号的名称

signal程序中必须声明信号,声明包括一个标识符和信号值的类型。

信号的名称不是传统的变量,它表示一个值的序列,在某个瞬间,它只有一个值,或没有值。通过某种运算符可访问一个信号以前的值,可以是前一个值,也可以是前面第N个值,甚至可以是一个滑动窗口中的前面N个值的集合。

信号的当前值不能用S:=S+1来修改。

3.3.       信号的类型

信号值的类型可以是:预定义的简单类型(如整型)、字符串、枚举类型、复合类型(如数组),本文只介绍简单类型。

3.3.1.      数值类型

数值类型包括:integerrealdreal(即C语言中的double)、complex。他们具有传统的运算符和通常的特性。操作数的类型必须相同,但允许X+realN)。整数除法产生整数商。取模运算用modulo表示(%signal程序中是注释符)。函数与C语言中的函数相同,必须在本地声明函数头。

3.3.2.      布尔类型

布尔类型的名称是boolean,它与整数不同。通过常数truefalse、事件信号、值的比较运算(运算符是= /= < <= > >=)、布尔运算(运算符是notandor)等方式获得布尔类型的值。这些运算符具有通常的特性。

3.3.3.      事件类型

在纯Signal语言中只使用时钟。它只有一种值,即布尔常数true。当一个布尔表达式的值是true时接受一个事件。

时钟运算符^应用于一个信号时抽取了它的时钟,产生了一个事件信号:

ORDER : ---- 7 -------- 8 --------------- 9 ------

^ORDER : ---- t -------- t --------------- t ------

WATCHDOG程序中,ORDER的整数值没有被使用,只用了它的时钟,因此把该时钟作为输入也足够了。

3.4.       信号的声明

信号可声明为输入信号、输出信号或本地信号。输出信号和本地信号可被初始化,特别是用它们表示其它信号的过去值时需要被初始化。

下例解释前面所述的文法:

 

process DECLARE =

{ integer NBL, NBC; } % 参数 = 常数 %

( ? real a; % 输入信号%

event EV, HH_2; integer B;

! boolean ok init false, FOUND; ) % 输出信号 %

(| XX := sqrt(fabs(-A))

| ...

| OK := inter <= NBC when FOUND

|)

where % 本地信号 %

real XX, YY init -1.5; % only YY initialized %

integer inter;

% 函数接口 %

function sqrt = (?dreal A !dreal B); % double in C %

function fabs = (?dreal A !dreal B);

end %DECLARE%;

3.5.       常数和参数

常数是象18true 等这类常规的记号。还有一些常数可作为参数,如WATCHDOG中的DELAY,上例中的NBLNBC。它们必须是编译前已知的,例如,对上例DECLARE进程可以如下方式调用:

... DECLARE {20,M+1} (.. effective input signals ..)

这里M也是参数。常数没有时钟,它们在需要的时候出现。

4.        信号定义和运算符

一个Signal程序体是一组定义输出和本地信号的方程。对于每个信号,必须定义它的时钟,以及它出现时的值。

信号的运算符用于创建新的信号。有些运算符只能勇于同步信号,运算结果与运算对象有相的时钟,这些称之为monochronous运算符。还有一些运算符可以任何时钟作用于信号,其结果可能有其它时钟,这些称之为polychronous运算符(笔者注:未能找到与monochronouspolychronous对应的中文词)。

4.1.       信号的定义

4.1.1.      定义信号的方程

必须用如下格式的一个唯一的方程来定义一个输出信号或本地信号:

<信号名> := <信号表达式>

在上述方程中,<信号名><信号表达式>的时钟相等,<信号表达式>的值必须能被<信号名>接受,<信号表达式>的计算时间为0(同步假设),<信号名><信号表达式>的值。

当运算符是monochronous时,所涉及的所有信号的时钟相等。

4.1.2.      例子

在以下进程中:

process PLUS1 =

( ? integer IN;

! integer OUT; )

(| OUT := IN + 1 |)

INOUT时钟相等。如果把这个进程插入到一个更大的程序中,前者的时钟等式将被加入到后者的其它方程中。

上述程序运行时,只要IN的整数值可用,立即把这个整数值加1,然后在同一逻辑瞬间把结果送给OUT

在测试环境中,如果单独运行这个进程,则以常规速度读入含有输入值的文件,并以同样速度产生一个输出文件。此种情况不需要输入时钟文件。

在以下另一个进程中:

process ONES =

( ? % no input %

! integer S; )

(| S := 1 |)

常数表达式1本身没有时钟。该进程所处的环境必须设置S的时钟。每次询问S是否出现时,将把它的值置为1。如果单独运行该进程,S的时钟将是“主时钟”,即是宿主机的通常速度,运行结果将是无限长的一串1

如果一个进程包含几个无关的信号:

process TWO =

( ? % no input %

! integer S1, S2;)

(| S1 := 1

| S2 := 2 |)

该进程所处的环境必须设置S1S2的时钟。如果单独运行该进程,则必须提供S1S2的时钟文件。

4.1.3.      时钟等式方程

在上一个例子进程TWO中,定义信号的方程还不足以定义它的时钟。可以用另一种形式的方程:

| S1 ^= S2

来显式地说明S1S2的时钟相等。

如果我们希望信号S随另一个信号A产生,但S的值与A无关,我们必须用以下的等式:

process S_ON_A = % produces a 1 on each input A %

( ? event A;

! integer S;)

(| S := 1

| S ^= A |) % or A ^= S %

如果我们知道2个输入信号是同步的,或者我们希望使它们同步,可以如下写:

process SYNC_IN =

( ? integer A, B;

! ... )

(| A ^= B

| ...

这将同时读输入AB的值。在一个更大的程序中,时钟方程的集合将保证AB是同步的。如果单独运行该进程,将并行地读AB的值文件。

时钟等式可以有多个项,其中一个可以是一个表达式(参见例子WATCHDOG)。

定义S的值的方程必须是唯一的,但可以用多种方式定义它的时钟。当然,编译器将检查其相容性。

 

4.2.       MONOCHRONOUS运算符

4.2.1.      与类型相关的运算符

传统的运算符(加、比较、与、或。。。。。。)和库函数只能用于同步信号。这些运算符也是使信号的时钟相等的一种手段。运算结果与操作数的时钟相同。

process ADD =

( ? integer A, B;

! integer S; )

(| S := A + B |)

在上面进程中,运算符+AB之间引入了隐含的时钟等式。表达式A+B和信号S有相同的时钟,加法和赋值的时间假设为0

单目运算符(-、非、时钟运算符^)也是与操作数同步的。

4.2.2.      延时运算符

延时运算符$用于访问信号的过去值。信号A的延时值与A的时钟相同。

A$A的前一个值。

A$NA的前N次出现时值。N是一个正的常数表达式,表达式中可含参数,但不能含信号。例如,PIXEL $ (NBL*NBC-1)

A$N的前N个值是未定义的。可用两种方式对其初始化。第一是在使用的地方:

| S := A $ init 0

| Y := 5*( X $ 2 init [10,20]) + X$ init 0

第二是在声明具有延时值的信号时:

| ZA := A $ 1

| ZB := A $

| ZC := A $ 3

这里:

integer ZA, ZB init 0, ZC init [10,20,30];

A : --- 1  --- 2  ------- 3  --- 4 --- 5 ------- 6 ---

ZA : --- ?  --- 1  ------- 2  --- 3 --- 4 ------- 5 ---

ZB : --- 0  --- 1  ------- 2  --- 3 --- 4 ------- 5 ---

ZC : --- 10 --- 20 ------- 30 --- 1 --- 2 ------- 3 ---

 

以下是使用延时信号的例子。

要编写一个计数器123,。。。。。。,可以这样写:

| CNT := (CNT$ init 0) + 1

也可以:

| ZCNT := CNT$

| CNT := ZCNT + 1 |)

这里

integer ZCNT init 0;

ZCNTCNT的时钟相同。但上面的定义没有设置它们的公用时钟。如果单独运行,对于CNT将产生一个无限序列1234。。。。。。。一般用一个单独的时钟等式把该计数器与其它信号连接,例如:

HOUR ^= TICK

| HOUR := (HOUR$ init 0) + 1

4.3.       when运算符

when运算符从信号的布尔表达式抽取值为true的瞬间,其作用象采样。因此运算结果的时钟频度要低于表达式的时钟。

4.3.1.      一元when

一元when的使用格式是:

when <布尔表达式>

例子:

NULL := when CNT = 0

上式的结果是事件类型。当布尔表达式为true时它出现,如下图:

CNT : --- 1 --- 0 ------ 2 --- 0 --- 0 --- 3 ----

NULL : --------- t ------------ t --- t ----------

该运算符用于“标记”信号有特殊性质的瞬间,可在用于同步另一个信号的时钟等式中使用它。例如,对某个布尔信号统计值为false的个数:

| CNT ^= when not B

| CNT :=(CNT $ init 0) + 1

 

4.3.2.      二元when

二元when的使用格式是:

<信号表达式> when <布尔表达式>

例子:

ALARM := HOUR when CNT = 0

结果如下图:

HOUR : --- 2 ------------- 4 --- 6 --- 8 -----

CNT : -------- 1 -- 0 --- 0 --- 3 --- 0 -----

ALARM : ------------------- 4 --------- 8 -----

二元when的优先级比传统运算符的优先级低。A when B的时钟频度比A的时钟和B的时钟小,它是^Awhen B的交集。

例子:

A when propA

上式表示在A的特性(property)得到验证时抽取A的值。

例子:

A when ^C

上式表示在C也出现时抽取A的值。

注意不能用when C来使其它信号与信号C同步。例如,下式是非法的:

CNT := (CNT $ init 0) + 1 when ^C

when ^C时钟频度要比CNT$+1的时钟频度低,因此不能把它的值给CNT。这是Signal程序的常见错误之一。但是,下式是合法的:

S := 1 when ^C

它等价于:

S := 1 | S ^= C

4.4.       default运算符

default运算符的作用是合并,它的使用格式是:

<信号A> default <信号B>

合并时A的优先级高于B的优先级,即如果在某一瞬间AB都出现则取A的值,否则取B的值。

例如:

S := A default B

A : ---- 1 ----------  2 ---- 3 -------------- 4 ---  5 ---

B : ---------- 10 --- 20 --------- 30 -- 40 -------- 50 ---

S : ---- 1 --- 10 ---  2 ---- 3 -- 30 -- 40 -- 4 ---  5 ---

AB的类型必须是相容的。S的时钟是AB的时钟并集。default运算符的优先级低于其它运算符的优先级。

default运算符的左边信号不能是常数。如果右边信号是常数,那么default结果的时钟是不定的,必须从外部来确定。例如:

S := X default false

上式中S的时钟必须在其它地方确定,并且必须包含X的时钟,如:S ^= X ^+ Y

以下表达式是允许的:

S := A when B default C

上式看上去象if then else语句,但必须考虑时钟,即SC值的条件不仅是Bfalse,而且AB缺席。

在下式中SIGN不能获得X的时钟:

| SIGN := 1 when X >= 0 default -1

因此还必须有:SIGN ^= X

可用default合并多个信号。例如:

| S := val1 when cond1

default val2 when cond2

default val3 when cond3

5.        高级特色

5.1.       cell运算符

5.1.1.      定义

某些Signal程序只有唯一的基本时钟,限定所有信号使用这个时钟,这样信号间只允许传统的操作。这样的程序优化程度低,但可解决烦人的时钟问题。

为了在另一个信号的瞬间重复一个信号,Signal语言设计了cell运算符,其使用格式是:

C := A cell B

这里B是布尔表达式。C包含A 的所有值,并且还在Btrue的瞬间取A的前值,以及在没有读A之前取某个初值,如下图所示:

A : --------- 1 ------------------ 2 ------- 3 ------------

B : --- t --------- t -- f -- t ------- t -- f -- f -- t --

C (init 0) : --- 0 --- 1 --- 1 ------- 1 -- 2 -- 2 -- 3 ------- 3 --

C的时钟是A的时钟和when B的时钟的并集。

cell运算符的优先级与when相同。

对于delay运算符,可在使用cell时初始化,或在声明被赋值信号时初始化,如下例所示:

C1 := A cell B init 0

| C2 := A cell B

cell运算符不属于Signal语言的核心,因为可以用其它运算符来实现它:

C ^= A ^+ (when B)

C := A default (C $ init 0)

5.1.2.      cell运算符的使用

我们用以下式子来使所有信号XY、……具有公共时钟H

H ^= X ^+ Y ^+ ...

XX := X cell H

YY := Y cell H

....

为了在另一个信号X的瞬间重复信号A的值,把X的时钟用作为一个永远为true的布尔信号:

AX := A cell ^X

如果只在X的瞬间移动A而不保持A本身:

A_ON_X := (A cell ^X) when ^X

所以A_ON_X具有A的时钟。

以下是一个例子:

( ? real COEF, % one value %

X; % sequence of reals %

! real Y; ) % Y = COEF * X for all X %

(| Y := ((COEF cell ^X) when ^X ) * X |)

5.2.       模块化

一个进程可以被一个系统的其它进程作为子进程来调用。调用者进程设置常数参数(如果有),给定输入信号,使用输出信号。例如,可以如下方式调用WATCHDOG

PB_TIME := WATCHDOG {5} (DEMAND, when OVER, ^SECONDS)

参数值可以是任何常数表达式,它可包括本地参数,但不能包括信号。有效的输入信号是信号的表达式。如果被调用进程只有一个输出,那么调用语句可作为函数调用而放入任何信号表达式。

5.2.1.      调用格式例子

下例说明了其它可能的调用格式:

process MODU =

( ? integer A;

! integer S;)

(| BOOL ^= A

| S := -1 when BOOL

default 2 * ONE (A) + 1

default INT

| (INT, BOOL) := NO_IN {false} ()

| NO_OUT (INT, BOOL)

|)

where

integer INT; boolean BOOL;

 

process ONE =

( ? integer I;

! integer RES; )

(| RES := I/2 when I modulo 2 = 1 |)

;

process NO_IN = {boolean B0;}

( ?

! integer I; boolean B; )

(| I := 0

| ZB := B$ | B := ZB |)

where

boolean ZB init B0;

end % NO_IN %;

 

process NO_OUT =

( ? integer I; boolean B;

! )

(| I ^= when not B |)

end % MODU %;

5.2.2.      取模计数器子进程

假设输入信号TICK每秒出现。进程要在TICK出现的瞬间产生天、时、分、秒数。

进程CNT_MOD有两个事件输入,一是增加计数器,二是获得计数器的值。当计数器变成RESET0时进程发出一个事件。

process BIG_BEN =

( ? event TICK;

! integer DAY, HOUR, MINUTE, SECOND; )

 

(| (SECOND, NEW_MINUTE) := CNT_MOD {60} (TICK, TICK)

| (MINUTE, NEW_HOUR )  := CNT_MOD {60} (TICK, NEW_MINUTE)

| (HOUR , NEW_DAY )    := CNT_MOD {24} (TICK, NEW_HOUR)

| DAY ^= TICK

| DAY := DAY$ + 1 when NEW_DAY default DAY$ init 1

|)

where

event NEW_MINUTE, NEW_HOUR, NEW_DAY;

 

process CNT_MOD = % Counter modulo N %

{ integer N;}

( ? event EV_OUT, % when Counter must be output %

EV_INC; % when Counter must be increased %

! integer CNT;

event RESET; )

(| CNT ^= EV_OUT ^+ EV_INC

| CNT := (ZCNT+1) modulo N when EV_INC

default ZCNT

| ZCNT := CNT$ init 0

| RESET := when CNT = 0 when EV_INC

|)

where

integer ZCNT;

end % CNT_MOD %;

end % BIG_BEN %;

 

5.3.       外加采样(Oversampling

我们已看到可以约束输入信号的时钟:

process ADD =

( ? integer A, B;

! integer S; )

(| S := A + B |)

这里要求AB的时钟是相等的。

可以更复杂的方式关联输入信号的时钟:

( ? integer X, Y;

....

| X ^= when Y = 0

这里Y的时钟是“主时钟”。上式表示只有当Y的值是0时才读X的值。

某些输入的时钟还可与一个本地信号关联:

( ? integer X;

...

(| FLIP := not (FLIP$ init false)

| X ^= when FLIP

where

boolean FLIP; ...

因此输入时钟不一定是较快的。FLIP信号在两次读X之间的“中间”瞬间出现。如果两次读X之间有多个瞬间,那么FLIP信号有足够时间来出现。这些“中间”瞬间称为外加采样(Oversampling)。

这个较快的信号可与其它信号同步,并且可以更精确地设置其时钟。

当然,可以对时钟施加的约束是有限制的。例如,when A=0 ^= when B=0是不可接受的,因为不可能在编译时保证AB永远同时为0

以下约束也是不可接受的:

A default B ^= when condition

6.        应用

6.1.       事件的间隔

以下假设STARTFINISH是两个相隔一定时间的事件,FINISHSTART之后。

6.1.1.      STARTFINISH之间的间隔

STARTFINISH之间的间隔用一个时钟事件H在这个间隔中法出的脉冲个数来度量。CNT是一个计数器,在每个事件上出现,但只在事件H上增加。它在START上复位,在FINISH上给出STARTFINISH的间隔。

| CNT ^= START ^+ FINISH ^+ H

| CNT := 0 when START

default CNT$ + 1 when H

default CNT$

| DURATION := CNT when FINISH

 

START : -----*-------------*------------------------

FINISH : -------------*-------------------------*----

H : -*---*---*---*---*---*---*---*---*---*---*--

CNT : -?---0---1---2---3-0-1---2---3---4---5-5-6--

DURATION: ------------ 2 ----------------------- 5 ---

如果STARTFINISH精确地在H上出现,那么时间度量是精确的,否则,DURATIONSTARTFINISH之间的H的个数,并包含边界,误差小于一个间隔。

6.1.2.      事件是否在STARTFINISH之间出现

可以测试一个事件是否在STARTFINISH之间出现。假设H是一个事件,IN是一个H是否出现的布尔信号,可以把H的所有是否出现的状态保存在MEM中:

| MEM ^= START ^+ FINISH ^+ H

| MEM := START default not FINISH default (MEM$ init false)

% true when START default false when FINISH default MEM$ %

| IN := MEM when H

HSTARTFINISH同时出现时是什么情况?根据以上条件,HSTART同时出现时H在间隔中,与FINISH同时出现时在间隔外。

6.2.       自动机

有限状态自动机经常被用于实时系统的建模,一个自动机有若干状态Si,其中一个状态是初始状态S0。当一个事件ei出现时,自动机可能改变当前状态,或在当前状态上循环,并执行某些合适的动作。

Signal中,用一个变量S来标记状态迁移的到达状态,它的延迟值ZS表示出发状态。S的时钟是所有输入信号的时钟的并集。其它信号改变了值或达到某个值将引起状态迁移。

在异步语言中,自动机的状态可能是不确定的,例如当两个事件同时发生时,将迁移到哪个状态是不定的。而Signal程序必须规定事件的次序。

下例是关于灯的开关按钮。假设按钮按一下灯亮,再按一下灯灭,或在一分钟后自动灭。可以用一个两状态的自动机来描述:灯OFFS=1 ONS=2

process LIGHT =

( ? event SWITCH, H; % H every second %

! event PUT_ON, PUT_OFF;

)

% State changes %

(| S ^= SWITCH ^+ H ^= CNT

| S := 3 - ZS when SWITCH

default 1 when ZS = 2 when ZCNT = 1

default ZS % loop on current state %

| ZS := S $ init 1

% Actions on transitions %

| CNT := (60 when ZS = 1 default ZCNT - 1) when S = 2

default ZCNT

| ZCNT := CNT $ init 0

| PUT_ON := when S = 2 when ZS = 1

| PUT_OFF := when S = 1 when ZS = 2

|)

where

integer S, ZS, CNT, ZCNT;

end % LIGHT %;

计数器CNT只是在S2 时使被使用,但它的延迟值ZCNT有相同的时钟,并且在S1 时也被使用。CNT的定义方程是递归的,因此必须在外部定义它的时钟。

 

参考资料

【1】             Bernard HOUSSAISThe Synchronous Programming Language A Tutorial24th September 2004

【2】             Bernard HOUSSAISCours de Programmation en Langage Synchrone SIGNAL24 septembre 2004

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值