1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
|
.HTML "Using SPIN
.\" runoff as:
.\" eqn file | tbl | troff -ms
.\"
.ds P \\s-1PROMELA\\s0
.ds V \\s-1SPIN\\s0
.ds C pcc
.\" .tr -\(hy
.EQ
delim $#
.EN
.TL
Using \*V
.AU
Gerard J. Holzmann
gerard@plan9.bell-labs.com
.AB
\*V can be used for proving or disproving logical properties
of concurrent systems.
To render the proofs, a concurrent system is first
modeled in a formal specification language called \*P.
The language allows one to specify the behaviors
of asynchronously executing
processes that may interact through synchronous
or asynchronous message passing, or through direct
access to shared variables.
.LP
System models specified in this way can be verified
for both safety and liveness properties. The specification
of general properties in linear time temporal logic is
also supported.
.LP
The first part of this manual
discusses the basic features of the specification language \*P.
The second part describes the verifier \*V.
.AE
.NH 1
The Language \*P
.LP
\*P is short for Protocol Meta Language [Ho91].
\*P is a \f2modeling\f1 language, not a programming language.
A formal model differs in two essential ways from an implementation.
First, a model is meant to be an abstraction of a design
that contains only those aspects of the design that are
directly relevant to the properties one is interested in proving.
Second, a formal model must contain things that are typically not part
of an implementation, such as worst-case assumptions about
the behavior of the environment that may interact with the
system being studied, and a formal statement of relevant correctness
properties. It is possible to mechanically extract abstract models
from implementation level code, as discussed, for instance in [HS99].
.LP
Verification with \*V is often performed in a series of steps,
with the construction of increasingly detailed models.
Each model can be verified under different types of
assumptions about the environment and for different
types of correctness properties.
If a property is not valid for the given assumptions about
system behavior, the verifier can produce a counter-example
that demonstrates how the property may be violated.
If a property is valid, it may be possible to simplify the
model based on that fact, and prove still other properties.
.LP
Section 1.1 covers the basic building blocks of the language.
Section 1.2 introduces the control flow structures.
Section 1.3 explains how correctness properties are specified.
Section 1.4 concludes the first part with a discussion of
special predefined variables and functions that can be used to
express some correctness properties.
.LP
Up to date manual pages for \*V can always be found online at:
.CW
http://cm.bell-labs.com/cm/cs/what/spin/Man/
.R
.NH 2
Basics
.LP
A \*P model can contain three different types of objects:
.IP
.RS
\(bu Processes (section 1.1.1),
.br
\(bu Variables (section 1.1.2),
.br
\(bu Message channels (section 1.1.3).
.RE
.LP
All processes are global objects.
For obvious reasons, a \*P model must contain at least one
process to be meaningful.
Since \*V is specifically meant to prove properties of
concurrent systems, a model typically contains more than
one process.
.LP
Message channels and variables, the two basic types of data objects,
can be declared with either a global scope or a local scope.
A data object with global scope can be referred to by all processes.
A data object with a local scope can be referred to by just a
single process: the process that declares and instantiates the object.
As usual, all objects must be declared in the specification
before they are referenced.
.NH 3
Processes
.LP
Here is a simple process that does nothing except print
a line of text:
.P1
init {
printf("it works\en")
}
.P2
There are a few things to note.
.CW Init
is a predefined keyword from the language.
It can be used to declare and instantiate
a single initial process in the model.
(It is comparable to the
.CW main
procedure of a C program.)
The
.CW init
process does not take arguments, but it can
start up (instantiate) other processes that do.
.CW Printf
is one of a few built-in procedures in the language.
It behaves the same as the C version.
Note, finally, that no semicolon follows the single
.CW printf
statement in the above example.
In \*P, semicolons are used as statement separators,
not statement terminators. (The \*V parser, however, is
lenient on this issue.)
.LP
Any process can start new processes by using another
built-in procedure called
.CW run .
For example,
.P1
proctype you_run(byte x)
{
printf("my x is: %d\en", x)
}
.P2
.P1
init {
run you_run(1);
run you_run(2)
}
.P2
The word
.CW proctype
is again a keyword that introduces the declaration
of a new type of process.
In this case, we have named that type
.CW you_run
and declared that all instantiations of processes
of this type will take one argument: a data object
of type
.CW byte ,
that can be referred to within this process by the name
.CW x .
Instances of a
.CW proctype
can be created with the predefined procedure
.CW run ,
as shown in the example.
When the
.CW run
statement completes, a copy of the process
has been started, and all its arguments have been
initialized with the arguments provided.
The process may, but need not, have performed
any statement executions at this point.
It is now part of the concurrent system,
and its execution can be interleaved arbitrarily with
those of the other, already executing processes.
(More about the semantics of execution follows shortly.)
.LP
In many cases, we are only interested in creating a
single instance of each process type that is declared,
and the processes require no arguments.
We can define this by prefixing the keyword
.CW proctype
from the process declaration with another keyword:
.CW active .
Instances of all active proctypes are created when the
system itself is initialized.
We could, for instance, have avoided the use of
.CW init
by declaring the corresponding process in the last example
as follows:
.P1
active proctype main() {
run you_run(1);
run you_run(2)
}
.P2
Note that there are no parameters to instantiate in this
case. Had they been declared, they would default to a
zero value, just like all other data objects
that are not explicitly instantiated.
.LP
Multiple copies of a process type can also be created in
this way. For example:
.P1
active [4] proctype try_me() {
printf("hi, i am process %d\en", _pid)
}
.P2
creates four processes.
A predefined variable
.CW _pid
is assigned to each running process, and holds
its unique process instantiation number.
In some cases, this number is needed when a reference
has to be made to a specific process.
.LP
Summarizing: process behavior is declared in
.CW proctype
definitions, and it is instantiated with either
.CW run
statements or with the prefix
.CW active .
Within a proctype declaration, statements are separated
(not terminated) by semicolons.
As we shall see in examples that follow, instead of the
semicolon, one can also use the alternative separator
.CW "->"
(arrow), wherever that may help to clarify the structure
of a \*P model.
.SH
Semantics of Execution
.LP
In \*P there is no difference between a condition or
expression and a statement.
Fundamental to the semantics of the language is the
notion of the \f2executability\f1 of statements.
Statements are either executable or blocked.
Executability is the basic means of enforcing
synchronization between the processes in a distributed system.
A process can wait for an event to happen by waiting
for a statement to become executable.
For instance, instead of writing a busy wait loop:
.P1
while (a != b) /* not valid Promela syntax */
skip; /* wait for a==b */
\&...
.P2
we achieve the same effect in \*P with the statement
.P1
(a == b);
\&...
.P2
Often we indicate that the continuation of an execution
is conditional on the truth of some expression by using
the alternate statement separator:
.P1
(a == b) -> \&...
.P2
Assignments and
.CW printf
statements are always executable in \*P.
A condition, however, can only be executed (passed) when it holds.
If the condition does not hold, execution blocks until it does.
There are similar rules for determining the executability
of all other primitive and compound statements in the
language.
The semantics of each statement is defined in terms of
rules for executability and effect.
The rules for executability set a precondition on the state
of the system in which a statement can be executed.
The effect defines how a statement will alter a
system state when executed.
.LP
\*P assumes that all individual statements are executed
atomically: that is, they model the smallest meaningful entities
of execution in the system being studied.
This means that \*P defines the standard asynchronous interleaving
model of execution, where a supposed scheduler is free at
each point in the execution to select any one of the processes
to proceed by executing a single primitive statement.
Synchronization constraints can be used to influence the
interleaving patterns. It is the purpose of a concurrent system's
design to constrain those patterns in such a way that no
correctness requirements can be violated, and all service
requirements are met. It is the purpose of the verifier
either to find counter-examples to a designer's claim that this
goal has been met, or to demonstrate that the claim is indeed valid.
.NH 3
Variables
.LP
The table summarizes the five basic data types used in \*P.
.CW Bit
and
.CW bool
are synonyms for a single bit of information.
The first three types can store only unsigned quantities.
The last two can hold either positive or negative values.
The precise value ranges of variables of types
.CW short
and
.CW int
is implementation dependent, and corresponds
to those of the same types in C programs
that are compiled for the same hardware.
The values given in the table are most common.
.KS
.TS
center;
l l
lw(10) lw(12).
=
\f3Type Range\f1
_
bit 0..1
bool 0..1
byte 0..255
short $-2 sup 15# .. $2 sup 15 -1#
int $-2 sup 31# .. $2 sup 31 -1#
_
.TE
.KE
.LP
The following example program declares a array of
two elements of type
.CW bool
and a scalar variable
.CW turn
of the same type.
Note that the example relies on the fact that
.CW _pid
is either 0 or 1 here.
.MT _sec5critical
.P1
/*
* Peterson's algorithm for enforcing
* mutual exclusion between two processes
* competing for access to a critical section
*/
bool turn, want[2];
active [2] proctype user()
{
again:
want[_pid] = 1; turn = _pid;
/* wait until this condition holds: */
(want[1 - _pid] == 0 || turn == 1 - _pid);
/* enter */
critical: skip;
/* leave */
want[_pid] = 0;
goto again
}
.P2
In the above case, all variables are initialized to zero.
The general syntax for declaring and instantiating a
variable, respectively for scalar and array variables, is:
.P1
type name = expression;
type name[constant] = expression
.P2
In the latter case, all elements of the array are initialized
to the value of the expression.
A missing initializer fields defaults to the value zero.
As usual, multiple variables of the same type can be grouped
behind a single type name, as in:
.P1
byte a, b[3], c = 4
.P2
In this example, the variable
.CW c
is initialized to the value 4; variable
.CW a
and the elements of array
.CW b
are all initialized to zero.
.LP
Variables can also be declared as structures.
For example:
.P1
typedef Field {
short f = 3;
byte g
};
typedef Msg {
byte a[3];
int fld1;
Field fld2;
chan p[3];
bit b
};
Msg foo;
.P2
introduces two user-defined data types, the first named
.CW Field
and the second named
.CW Msg .
A single variable named
.CW foo
of type
.CW Msg
is declared.
All fields of
.CW foo
that are not explicitly initialized (in the example, all fields except
.CW foo.fld2.f )
are initialized to zero.
References to the elements of a structure are written as:
.P1
foo.a[2] = foo.fld2.f + 12
.P2
A variable of a user-defined type can be passed as a single
argument to a new process in
.CW run
statements.
For instance,
.P1
proctype me(Msg z) {
z.a[2] = 12
}
init {
Msg foo;
run me(foo)
}
.P2
.LP
Note that even though \*P supports only one-dimensional arrays,
a two-dimensional array can be created indirectly with user-defined
structures, for instance as follows:
.P1
typedef Array {
byte el[4]
};
Array a[4];
.P2
This creates a data structure of 16 elements that can be
referenced, for instance, as
.CW a[i].el[j] .
.LP
As in C, the indices of an array of
.CW N
elements range from 0 to
.CW N-1 .
.SH
Expressions
.LP
Expressions must be side-effect free in \*P.
Specifically, this means that an expression cannot
contain assignments, or send and receive operations (see section 1.1.3).
.P1
c = c + 1; c = c - 1
.P2
and
.P1
c++; c--
.P2
are assignments in \*P, with the same effects.
But, unlike in C,
.P1
b = c++
.P2
is not a valid assignment, because the right-hand side
operand is not a valid expression in \*P (it is not side-effect free).
.LP
It is also possible to write a side-effect free conditional
expression, with the following syntax:
.P1
(expr1 -> expr2 : expr3)
.P2
The parentheses around the conditional expression are required to
avoid misinterpretation of the arrow.
The example expression has the value of \f(CWexpr2\f1 when \f(CWexpr1\f1
evaluates to a non-zero value, and the value of \f(CWexpr3\f1 otherwise.
.LP
In assignments like
.P1
variable = expression
.P2
the values of all operands used inside the expression are first cast to
signed integers before the operands are applied.
After the evaluation of the expression completes, the value produced
is cast to the type of the target variable before the assignment takes place.
.NH 3
Message Channels
.LP
Message channels are used to model the transfer of data
between processes.
They are declared either locally or globally,
for instance as follows:
.P1
chan qname = [16] of { short, byte }
.P2
The keyword
.CW chan
introduces a channel declaration.
In this case, the channel is named
.CW qname ,
and it is declared to be capable of storing up
to 16 messages.
Each message stored in the channel is declared here to
consist of two fields: one of type
.CW short
and one of type
.CW byte .
The fields of a message can be any one of the basic types
.CW bit ,
.CW bool ,
.CW byte ,
.CW short ,
.CW int ,
and
.CW chan ,
or any user-defined type.
Message fields cannot be declared as arrays.
.LP
A message field of type
.CW chan
can be used to pass a channel identifier
through a channel from one process to another.
.LP
The statement
.P1
qname!expr1,expr2
.P2
sends the values of expressions
.CW expr1
and
.CW expr2
to the channel that we just created. It appends
the message field created from the values of the two
expressions (and cast to the appropriate types of the
message fields declared for
.CW qname )
to the tail of the message buffer of 16 slots that belongs
to channel
.CW qname .
By default the send statement is only executable if the target
channel is non-full.
(This default semantics can be changed in the verifier into
one where the send statement is always executable, but the
message will be lost when an attempt is made to append it to
a full channel.)
.LP
The statement
.P1
qname?var1,var2
.P2
retrieves a message from the head of the same buffer,
and stores the two expressions in variables
.CW var1
and
.CW var2 .
.LP
The receive statement is executable only if the source channel
is non-empty.
.LP
If more parameters are sent per message than were declared
for the message channel, the redundant parameters are lost.
If fewer parameters are sent than declared,
the value of the remaining parameters is undefined.
Similarly, if the receive operation tries to retrieve more
parameters than available, the value of the extra parameters is
undefined; if it receives fewer than the number of parameters
sent, the extra information is lost.
.LP
An alternative, and equivalent, notation for the
send and receive operations is to structure the
message fields with parentheses, as follows:
.P1
qname!expr1(expr2,expr3)
qname?var1(var2,var3)
.P2
In the above case, we assume that
.CW qname
was declared to hold messages consisting of three fields.
.PP
Some or all of the arguments of the receive operation
can be given as constants instead of as variables:
.P1
qname?cons1,var2,cons2
.P2
In this case, an extra condition on the executability of the
receive operation is that the value of all message fields
specified as constants match the value of the corresponding
fields in the message that is to be received.
.LP
Here is an example that uses some of the mechanisms introduced
so far.
.P1
proctype A(chan q1)
{ chan q2;
q1?q2;
q2!123
}
.P2
.P1
proctype B(chan qforb)
{ int x;
qforb?x;
printf("x = %d\en", x)
}
.P2
.P1
init {
chan qname = [1] of { chan };
chan qforb = [1] of { int };
run A(qname);
run B(qforb);
qname!qforb
}
.P2
The value printed by the process of type
.CW B
will be
.CW 123 .
.LP
A predefined function
.CW len(qname)
returns the number of messages currently
stored in channel
.CW qname .
Two shorthands for the most common uses of this
function are
.CW empty(qname)
and
.CW full(qname) ,
with the obvious connotations.
.LP
Since all expressions must be side-effect free,
it is not valid to say:
.P1
(qname?var == 0)
.P2
or
.P1
(a > b && qname!123)
.P2
We could rewrite the second example (using an atomic sequence,
as explained further in section 1.2.1):
.P1
atomic { (a > b && !full(qname)) -> qname!123 }
.P2
The meaning of the first example is ambiguous. It could mean
that we want the condition to be true if the receive operation
is unexecutable. In that case, we can rewrite it without
side-effects as:
.P1
empty(qname)
.P2
It could also mean that we want the condition
to be true when the channel does contain a message with
value zero.
We can specify that as follows:
.P1
atomic { qname?[0] -> qname?var }
.P2
The first statement of this atomic sequence is
an expression without side-effects that
evaluates to a non-zero value only if the
receive operation
.P1
qname?0
.P2
would have been executable at that
point (i.e., channel
.CW qname
contains at least one message and the oldest
message stored consists of one message field
equal to zero).
Any receive statement can be turned into
a side-effect free expression by placing square
brackets around the list of all message parameters.
The channel contents remain undisturbed by the
evaluation of such expressions.
.LP
Note carefully, however, that in non-atomic sequences
of two statements such as
.P1
!full(qname) -> qname!msgtype
.P2
and
.P1
qname?[msgtype] -> qname?msgtype
.P2
the second statement is not necessarily executable
after the first one has been executed.
There may be race conditions when access to the channels
is shared between several processes.
Another process can send a message to the channel
just after this process determined that it was not full,
or another process can steal away the
message just after our process determined its presence.
.LP
Two other types of send and receive statements are used
less frequently: sorted send and random receive.
A sorted send operation is written with two, instead of one,
exclamation marks, as follows:
.P1
qname!!msg
.P2
A sorted send operation will insert a message into the channel's buffer
in numerical order, instead of in FIFO order.
The channel contents are scanned from the first message towards the
last, and the message is inserted immediately before the first message
that follows it in numerical order.
To determine the numerical order, all message fields are
taken into account.
.LP
The logical counterpart of the sorted send operation
is the random receive.
It is written with two, instead of one, question marks:
.P1
qname??msg
.P2
A random receive operation is executable if it is executable for \f2any\f1
message that is currently buffered in a message channel (instead of
only for the first message in the channel).
Normal send and receive operations can freely be combined with
sorted send and random receive operations.
.SH
Rendezvous Communication
.LP
So far we have talked about asynchronous communication between processes
via message channels, declared in statements such as
.P1
chan qname = [N] of { byte }
.P2
where
.CW N
is a positive constant that defines the buffer size.
A logical extension is to allow for the declaration
.P1
chan port = [0] of { byte }
.P2
to define a rendezvous port.
The channel size is zero, that is, the channel
.CW port
can pass, but cannot store, messages.
Message interactions via such rendezvous ports are
by definition synchronous.
Consider the following example:
.P1
#define msgtype 33
chan name = [0] of { byte, byte };
active proctype A()
{ name!msgtype(124);
name!msgtype(121)
}
.P2
.P1
active proctype B()
{ byte state;
name?msgtype(state)
}
.P2
Channel
.CW name
is a global rendezvous port.
The two processes will synchronously execute their first statement:
a handshake on message
.CW msgtype
and a transfer of the value 124 to local variable
.CW state .
The second statement in process
.CW A
will be unexecutable,
because there is no matching receive operation in process
.CW B .
.LP
If the channel
.CW name
is defined with a non-zero buffer capacity,
the behavior is different.
If the buffer size is at least 2, the process of type
.CW A
can complete its execution, before its peer even starts.
If the buffer size is 1, the sequence of events is as follows.
The process of type
.CW A
can complete its first send action, but it blocks on the
second, because the channel is now filled to capacity.
The process of type
.CW B
can then retrieve the first message and complete.
At this point
.CW A
becomes executable again and completes,
leaving its last message as a residual in the channel.
.LP
Rendezvous communication is binary: only two processes,
a sender and a receiver, can be synchronized in a
rendezvous handshake.
.LP
As the example shows, symbolic constants can be defined
with preprocessor macros using
.CW #define .
The source text of a \*P model is translated by the standard
C preprocessor.
The disadvantage of defining symbolic names in this way is,
however, that the \*P parser will only see the expanded text,
and cannot refer to the symbolic names themselves.
To prevent that, \*P also supports another way to define
symbolic names, which are preserved in error reports.
For instance, by including the declaration
.P1
mtype = { ack, msg, error, data };
.P2
at the top of a \*P model, the names provided between the
curly braces are equivalent to integers of type
.CW byte ,
but known by their symbolic names to the \*V parser and the
verifiers it generates.
The constant values assigned start at 1, and count up.
There can be only one
.CW mtype
declaration per model.
.NH 2
Control Flow
.LP
So far, we have seen only some of the basic statements
of \*P, and the way in which they can be combined to
model process behaviors.
The five types of statements we have mentioned are:
.CW printf ,
.CW assignment ,
.CW condition ,
.CW send ,
and
.CW receive .
.LP
The pseudo-statement
.CW skip
is syntactically and semantically equivalent to the
condition
.CW (1)
(i.e., to true), and is in fact quietly replaced with this
expression by the lexical analyzer of \*V.
.LP
There are also five types of compound statements.
.IP
.RS
\(bu
Atomic sequences (section 1.2.1),
.br
\(bu
Deterministic steps (section 1.2.2),
.br
\(bu
Selections (section 1.2.3),
.br
\(bu
Repetitions (section 1.2.4),
.br
\(bu
Escape sequences (section 1.2.5).
.RE
.LP
.NH 3
Atomic Sequences
.LP
The simplest compound statement is the
.CW atomic
sequence:
.P1
atomic { /* swap the values of a and b */
tmp = b;
b = a;
a = tmp
}
.P2
In the example, the values of two variables
.CW a
and
.CW b
are swapped in a sequence of statement executions
that is defined to be uninterruptable.
That is, in the interleaving of process executions, no
other process can execute statements from the moment that
the first statement of this sequence begins to execute until
the last one has completed.
.LP
It is often useful to use
.CW atomic
sequences to start a series of processes in such a
way that none of them can start executing statements
until all of them have been initialized:
.P1
init {
atomic {
run A(1,2);
run B(2,3);
run C(3,1)
}
}
.P2
.CW Atomic
sequences may be non-deterministic.
If any statement inside an
.CW atomic
sequence is found to be unexecutable, however,
the atomic chain is broken, and another process can take over
control.
When the blocking statement becomes executable later,
control can non-deterministically return to the process,
and the atomic execution of the sequence resumes as if
it had not been interrupted.
.NH 3
Deterministic Steps
.LP
Another way to define an indivisible sequence of actions
is to use the
.CW d_step
statement.
In the above case, for instance, we could also have written:
.P1
d_step { /* swap the values of a and b */
tmp = b;
b = a;
a = tmp
}
.P2
The difference between a
.CW d_step
sequence
and an
.CW atomic
sequence are:
.IP \(bu
A
.CW d_step
sequence must be completely deterministic.
(If non-determinism is nonetheless encountered,
it is always resolved in a fixed and deterministic
way: i.e., the first true guard in selection or
repetition structures is always selected.)
.IP \(bu
No
.CW goto
jumps into or out of a
.CW d_step
sequence are permitted.
.IP \(bu
The execution of a
.CW d_step
sequence cannot be interrupted when a
blocking statement is encountered.
It is an error if any statement other than
the first one in a
.CW d_step
sequence is found to be unexecutable.
.IP \(bu
A
.CW d_step
sequence is executed as one single statement.
In a way, it is a mechanism for adding new types
of statements to the language.
.LP
None of the items listed above apply to
.CW atomic
sequences.
This means that the keyword
.CW d_step
can always be replaced with the keyword
.CW atomic ,
but the reverse is not true.
(The main, perhaps the only, reason for using
.CW d_step
sequences is to improve the efficiency of
verifications.)
.NH 3
Selection Structures
.LP
A more interesting construct is the selection structure.
Using the relative values of two variables
.CW a
and
.CW b
to choose between two options, for instance, we can write:
.P1
if
:: (a != b) -> option1
:: (a == b) -> option2
fi
.P2
The selection structure above contains two execution sequences,
each preceded by a double colon.
Only one sequence from the list will be executed.
A sequence can be selected only if its first statement is executable.
The first statement is therefore called a \f2guard\f1.
.LP
In the above example the guards are mutually exclusive, but they
need not be.
If more than one guard is executable, one of the corresponding sequences
is selected nondeterministically.
If all guards are unexecutable the process will block until at least
one of them can be selected.
There is no restriction on the type of statements that can be used
as a guard: it may include sends or receives, assignments,
.CW printf ,
.CW skip ,
etc.
The rules of executability determine in each case what the semantics
of the complete selection structure will be.
The following example, for instance, uses receive statements
as guards in a selection.
.P1
mtype = { a, b };
chan ch = [1] of { byte };
active proctype A()
{ ch!a
}
.P2
.P1
active proctype B()
{ ch!b
}
.P2
.P1
active proctype C()
{ if
:: ch?a
:: ch?b
fi
}
.P2
The example defines three processes and one channel.
The first option in the selection structure of the process
of type
.CW C
is executable if the channel contains
a message named
.CW a ,
where
.CW a
is a symbolic constant defined in the
.CW mtype
declaration at the start of the program.
The second option is executable if it contains a message
.CW b ,
where, similarly,
.CW b
is a symbolic constant.
Which message will be available depends on the unknown
relative speeds of the processes.
.LP
A process of the following type will either increment
or decrement the value of variable
.CW count
once.
.P1
byte count;
active proctype counter()
{
if
:: count++
:: count--
fi
}
.P2
Assignments are always executable, so the choice made
here is truly a non-deterministic one that is independent
of the initial value of the variable (zero in this case).
.NH 3
Repetition Structures
.LP
We can modify the above program as follows, to obtain
a cyclic program that randomly changes the value of
the variable up or down, by replacing the selection
structure with a repetition.
.P1
byte count;
active proctype counter()
{
do
:: count++
:: count--
:: (count == 0) -> break
od
}
.P2
Only one option can be selected for execution at a time.
After the option completes, the execution of the structure
is repeated.
The normal way to terminate the repetition structure is
with a
.CW break
statement.
In the example, the loop can be
broken only when the count reaches zero.
Note, however, that it need not terminate since the other
two options remain executable.
To force termination we could modify the program as follows.
.P1
active proctype counter()
{
do
:: (count != 0) ->
if
:: count++
:: count--
fi
:: (count == 0) -> break
od
}
.P2
A special type of statement that is useful in selection
and repetition structures is the
.CW else
statement.
An
.CW else
statement becomes executable only if no other statement
within the same process, at the same control-flow point,
is executable.
We could try to use it in two places in the above example:
.P1
active proctype counter()
{
do
:: (count != 0) ->
if
:: count++
:: count--
:: else
fi
:: else -> break
od
}
.P2
The first
.CW else ,
inside the nested selection structure, can never become
executable though, and is therefore redundant (both alternative
guards of the selection are assignments, which are always
executable).
The second usage of the
.CW else ,
however, becomes executable exactly when
.CW "!(count != 0)"
or
.CW "(count == 0)" ,
and is therefore equivalent to the latter to break from the loop.
.LP
There is also an alternative way to exit the do-loop, without
using a
.CW break
statement: the infamous
.CW goto .
This is illustrated in the following implementation of
Euclid's algorithm for finding the greatest common divisor
of two non-zero, positive numbers:
.P1
proctype Euclid(int x, y)
{
do
:: (x > y) -> x = x - y
:: (x < y) -> y = y - x
:: (x == y) -> goto done
od;
done:
skip
}
.P2
.P1
init { run Euclid(36, 12) }
.P2
The
.CW goto
in this example jumps to a label named
.CW done .
Since a label can only appear before a statement,
we have added the dummy statement
.CW skip .
Like a
.CW skip ,
a
.CW goto
statement is always executable and has no other
effect than to change the control-flow point
of the process that executes it.
.LP
As a final example, consider the following implementation of
a Dijkstra semaphore, which is implemented with the help of
a synchronous channel.
.P1
#define p 0
#define v 1
chan sema = [0] of { bit };
.P2
.P1
active proctype Dijkstra()
{ byte count = 1;
do
:: (count == 1) ->
sema!p; count = 0
:: (count == 0) ->
sema?v; count = 1
od
}
.P2
.P1
active [3] proctype user()
{ do
:: sema?p;
/* critical section */
sema!v;
/* non-critical section */
od
}
.P2
The semaphore guarantees that only one of the three user processes
can enter its critical section at a time.
It does not necessarily prevent the monopolization of
the access to the critical section by one of the processes.
.LP
\*P does not have a mechanism for defining functions or
procedures. Where necessary, though, these may be
modeled with the help of additional processes.
The return value of a function, for instance, can be passed
back to the calling process via global variables or messages.
The following program illustrates this by recursively
calculating the factorial of a number
.CW n .
.P1
proctype fact(int n; chan p)
{ chan child = [1] of { int };
int result;
if
:: (n <= 1) -> p!1
:: (n >= 2) ->
run fact(n-1, child);
child?result;
p!n*result
fi
}
.P2
.P1
init
{ chan child = [1] of { int };
int result;
run fact(7, child);
child?result;
printf("result: %d\en", result)
}
.P2
Each process creates a private channel and uses it
to communicate with its direct descendant.
There are no input statements in \*P.
The reason is that models must always be complete to
allow for logical verifications, and input statements
would leave at least the source of some information unspecified.
A way to read input
would presuppose a source of information that is not
part of the model.
.LP
We have already discussed a few special types of statement:
.CW skip ,
.CW break ,
and
.CW else .
Another statement in this class is the
.CW timeout .
The
.CW timeout
is comparable to a system level
.CW else
statement: it becomes executable if and only if no other
statement in any of the processes is executable.
.CW Timeout
is a modeling feature that provides for an escape from a
potential deadlock state.
The
.CW timeout
takes no parameters, because the types of properties we
would like to prove for \*P models must be proven independent
of all absolute and relative timing considerations.
In particular, the relative speeds of processes can never be
known with certainty in an asynchronous system.
.NH 3
Escape Sequences
.LP
The last type of compound structure to be discussed is the
.CW unless
statement.
It is used as follows:
.MT _sec5unless
.P1
{ P } unless { E }
.P2
where the letters
.CW P
and
.CW E
represent arbitrary \*P fragments.
Execution of the
.CW unless
statement begins with the execution of statements from
.CW P .
Before each statement execution in
.CW P
the executability of the first statement of
.CW E
is checked, using the normal \*P semantics of executability.
Execution of statements from
.CW P
proceeds only while the first statement of
.CW E
remains unexecutable.
The first time that this `guard of the escape sequence'
is found to be executable, control changes to it,
and execution continues as defined for
.CW E .
Individual statement executions remain indivisible,
so control can only change from inside
.CW P
to the start of
.CW E
in between individual statement executions.
If the guard of the escape sequence
does not become executable during the
execution of
.CW P ,
then it is skipped entirely when
.CW P
terminates.
.LP
An example of the use of escape sequences is:
.P1
A;
do
:: b1 -> B1
:: b2 -> B2
\&...
od
unless { c -> C };
D
.P2
As shown in the example, the curly braces around the main sequence
(or the escape sequence) can be deleted if there can be no confusion
about which statements belong to those sequences.
In the example, condition
.CW c
acts as a watchdog on the repetition construct from the main sequence.
Note that this is not necessarily equivalent to the construct
.P1
A;
do
:: b1 -> B1
:: b2 -> B2
\&...
:: c -> break
od;
C; D
.P2
if
.CW B1
or
.CW B2
are non-empty.
In the first version of the example, execution of the iteration can
be interrupted at \f2any\f1 point inside each option sequence.
In the second version, execution can only be interrupted at the
start of the option sequences.
.NH 2
Correctness Properties
.LP
There are three ways to express correctness properties in \*P,
using:
.IP
.RS
.br
\(bu
Assertions (section 1.3.1),
.br
\(bu
Special labels (section 1.3.2),
.br
\(bu
.CW Never
claims (section 1.3.3).
.RE
.LP
.NH 3
Assertions
.LP
Statements of the form
.P1
assert(expression)
.P2
are always executable.
If the expression evaluates to a non-zero value (i.e., the
corresponding condition holds), the statement has no effect
when executed.
The correctness property expressed, though, is that it is
impossible for the expression to evaluate to zero (i.e., for
the condition to be false).
A failing assertion will cause execution to be aborted.
.NH 3
Special Labels
.LP
Labels in a \*P specification ordinarily serve as
targets for unconditional
.CW goto
jumps, as usual.
There are, however, also three types of labels that
have a special meaning to the verifier.
We discuss them in the next three subsections.
.NH 4
End-State Labels
.LP
When a \*P model is checked for reachable deadlock states
by the verifier, it must be able to distinguish valid \f2end state\f1s
from invalid ones.
By default, the only valid end states are those in which
every \*P process that was instantiated has reached the end of
its code.
Not all \*P processes, however, are meant to reach the
end of their code.
Some may very well linger in a known wait
state, or they may sit patiently in a loop
ready to spring into action when new input arrives.
.LP
To make it clear to the verifier that these alternate end states
are also valid, we can define special end-state labels.
We can do so, for instance, in the process type
.CW Dijkstra ,
from an earlier example:
.P1
proctype Dijkstra()
{ byte count = 1;
end: do
:: (count == 1) ->
sema!p; count = 0
:: (count == 0) ->
sema?v; count = 1
od
}
.P2
The label
.CW end
defines that it is not an error if, at the end of an
execution sequence, a process of this type
has not reached its closing curly brace, but waits at the label.
Of course, such a state could still be part of a deadlock state, but
if so, it is not caused by this particular process.
.LP
There may be more than one end-state label per \*P model.
If so, all labels that occur within the same process body must
be unique.
The rule is that every label name with the prefix
.CW end
is taken to be an end-state label.
.NH 4
Progress-State Labels
.LP
In the same spirit, \*P also allows for the definition of
.CW progress
labels.
Passing a progress label during an execution is interpreted
as a good thing: the process is not just idling while
waiting for things to happen elsewhere, but is making
effective progress in its execution.
The implicit correctness property expressed here is that any
infinite execution cycle allowed by the model that does not
pass through at least one of these progress labels is a
potential starvation loop.
In the
.CW Dijkstra
example, for instance, we can label the
successful passing of a semaphore test as progress and
ask a verifier to make sure that there is no cycle elsewhere
in the system.
.P1
proctype Dijkstra()
{ byte count = 1;
end: do
:: (count == 1) ->
progress: sema!p; count = 0
:: (count == 0) ->
sema?v; count = 1
od
}
.P2
If more than one state carries a progress label,
variations with a common prefix are again valid.
.NH 4
Accept-State Labels
.LP
The last type of label, the accept-state label, is used
primarily in combination with
.CW never
claims.
Briefly, by labeling a state with any label starting
with the prefix
.CW accept
we can ask the verifier to find all cycles that \f2do\f1
pass through at least one of those labels.
The implicit correctness claim is that this cannot happen.
The primary place where accept labels are used is inside
.CW never
claims.
We discuss
.CW never
claims next.
.NH 3
Never Claims
.LP
Up to this point we have talked about the specification
of correctness criteria with assertions
and with three special types of labels.
Powerful types of correctness criteria can already
be expressed with these tools, yet so far our only option is
to add them to individual
.CW proctype
declarations.
We can, for instance, express the claim ``every system state
in which property
.CW P
is true eventually leads to a system state in which property
.CW Q
is true,'' with an extra monitor process, such as:
.P1
active proctype monitor()
{
progress:
do
:: P -> Q
od
}
.P2
If we require that property
.CW P
must \f2remain\f1 true while we are waiting
.CW Q
to become true, we can try to change this to:
.P1
active proctype monitor()
{
progress:
do
:: P -> assert(P || Q)
od
}
.P2
but this does not quite do the job.
Note that we cannot make any assumptions about the
relative execution speeds of processes in a \*P model.
This means that if in the remainder of the system the
property
.CW P
becomes true, we can move to the state just before the
.CW assert ,
and wait there for an unknown amount of time (anything between
a zero delay and an infinite delay is possible here, since
no other synchronizations apply).
If
.CW Q
becomes true, we may pass the assertion, but we need not
do so.
Even if
.CW P
becomes false only \f2after\f1
.CW Q
has become true, we may still fail the assertion,
as long as there exists some later state where neither
.CW P
nor
.CW Q
is true.
This is clearly unsatisfactory, and we need another mechanism
to express these important types of liveness properties.
.SH
The Connection with Temporal Logic
.LP
A general way to express system properties of the type we
have just discussed is to use linear time temporal logic (LTL)
formulae.
Every \*P expression is automatically also a valid LTL formula.
An LTL formula can also contain the unary temporal operators □
(pronounced always), ◊ (pronounced eventually), and
two binary temporal operators
.CW U
(pronounced weak until) and
.BI U
(pronounced strong until).
.LP
Where the value of a \*P expression without temporal operators can be
defined uniquely for individual system states, without further context,
the truth value of an LTL formula is defined for sequences of states:
specifically, it is defined for the first state of a given infinite
sequence of system states (a trace).
Given, for instance, the sequence of system states:
.P1
s0;s1;s2;...
.P2
the LTL formula
.CW pUq ,
with
.CW p
and
.CW q
standard \*P expressions, is true for
.CW s0
either if
.CW q
is true in
.CW s0 ,
or if
.CW p
is true in
.CW s0
and
.CW pUq
holds for the remainder of the sequence after
.CW s0 .
.LP
Informally,
.CW pUq
says that
.CW p
is required to hold at least until
.CW q
becomes true.
If, instead, we would write \f(CWp\f(BIU\f(CWq\f1,
then we also require that there exists at least
one state in the sequence where
.CW q
does indeed become true.
.LP
The temporal operators □ and ◊
can be defined in terms of the strong until operator
.BI U ,
as follows.
.P1
□ p = !◊ !p = !(true \f(BIU\f(CW !p)
.P2
Informally, □
.CW p
says that property
.CW p
must hold in all states of a trace, and ◊
.CW p
says that
.CW p
holds in at least one state of the trace.
.LP
To express our original example requirement: ``every system state
in which property
.CW P
is true eventually leads to a system state in which property
.CW Q
is true,''
we can write the LTL formula:
.P1
□ (P -> ◊ Q)
.P2
where the logical implication symbol
.CW ->
is defined in the usual way as
.P1
P => Q means !P || Q
.P2
.SH
Mapping LTL Formulae onto Never Claims
.LP
\*P does not include syntax for specifying LTL formulae
directly, but it relies on the fact that every such
formula can be translated into a special type of
automaton, known as a Büchi automaton.
In the syntax of \*P this automaton is called a
.CW never
claim.
If you don't care too much about the details of
.CW never
claims, you can skip the remainder of this section and
simple remember that \*V can convert any LTL formula
automatically into the proper never claim syntax with
the command:
.P1
spin -f "...formula..."
.P2
Here are the details.
The syntax of a never claim is:
.P1
never {
\&...
}
.P2
where the dots can contain any \*P fragment, including
arbitrary repetition, selection, unless constructs,
jumps, etc.
.LP
There is an important difference in semantics between a
.CW proctype
declaration and a
.CW never
claim.
Every statement inside a
.CW never
claim is interpreted as a proposition, i.e., a condition.
A
.CW never
claim should therefore only contain expressions and never
statements that can have side-effects (assignments, sends or
receives, run-statements, etc.)
.LP
.CW Never
claims are used to express behaviors that are considered
undesirable or illegal.
We say that a
.CW never
claim is `matched' if the undesirable behavior can be realized,
contrary to the claim, and thus the correctness requirement violated.
The claims are evaluated over system executions, that is, the
propositions that are listed in the claim are evaluated over the
traces from the remainder of the system.
The claim, therefore, should not alter that behavior: it merely
monitors it.
Every time that the system reaches a new state, by asynchronously
executing statements from the model, the claim will evaluate the
appropriate propositions to determine if a counter-example can
be constructed to the implicit LTL formula that is specified.
.LP
Since LTL formulae are only defined for infinite executions,
the behavior of a
.CW never
claim can only be matched by an infinite system execution.
This by itself would restrict us to the use of progress labels
and accept labels as the only means we have discussed so far
for expressing properties of infinite behaviors.
To conform to standard omega automata theory, the behaviors of
.CW never
claims are expressed exclusively with
.CW accept
labels (never with
.CW progress
labels).
To match a claim, therefore, an infinite sequence of true propositions
must exist, at least one of which is labeled with an
.CW accept
label (inside the never claim).
.LP
Since \*P models can also express terminating system behaviors,
we have to define the semantics of the
.CW never
claims also for those behaviors.
To facilitate this, it is defined that a
.CW never
claim can also be matched when it reaches its closing curly brace
(i.e., when it appears to terminate).
This semantics is based on what is usually referred to as a `stuttering
semantics.'
With stuttering semantics, any terminating execution can be extended
into an equivalent infinite execution (for the purposes of evaluating
LTL properties) by repeating (stuttering) the final state infinitely often.
As a syntactical convenience, the final state of a
.CW never
claim is defined to be accepting, i.e., it could be replaced with
the explicit repetition construct:
.P1
accept: do :: skip od
.P2
Every process behavior, similarly, is (for the purposes of evaluating the
.CW never
claims) thought to be extended with a dummy self-loop on all final states:
.P1
do :: skip od
.P2
(Note the
.CW accept
labels only occur in the
.CW never
claim, not in the system.)
.SH
The Semantics of a Never Claim
.LP
.CW Never
claims are probably the hardest part of the language to understand,
so it is worth spending a few extra words on them.
On an initial reading, feel free to skip the remainder of this
section.
.LP
The difference between a
.CW never
claim and the remainder of a \*P system can be explained
as follows.
A \*P model defines an asynchronous interleaving product of the
behaviors of individual processes.
Given an arbitrary system state, its successor states are
conceptually obtained in two steps.
In a first step, all the executable statements in the
individual processes are identified.
In a second step, each one of these statements is executed,
each one producing one potential successor for the current state.
The complete system behavior is thus defined recursively and
represents all possible interleavings of the individual process behaviors.
It is this asynchronous product machine that we call the `global
system behavior'.
.LP
The addition of a
.CW never
claim defines a \f2synchronous\f1 product of the global system behavior
with the behavior expressed in the claim.
This synchronous product can be thought of as the construction of a
new global state machine, in which every state is defined as a pair
.CW (s,n)
with
.CW s
a state from the global system (the asynchronous product of processes), and
.CW n
a state from the claim.
Every transition in the new global machine is similarly defined by a pair
of transitions, with the first element a statement from the system, and the
second a proposition from the claim.
In other words, every transition in this final synchronous product is
defined as a joint transition of the system and the claim.
Of course, that transition can only occur if the proposition from the
second half of the transition pair evaluates to true in the current state
of the system (the first half of the state pair).
.SH
Examples
.LP
To manually translate an LTL formula into a
.CW never
claim (e.g. foregoing the builtin translation that \*V
offers), we must carefully consider whether the
formula expresses a positive or a negative property.
A positive property expresses a good behavior that we
would like our system to have.
A negative property expresses a bad behavior that we
claim the system does not have.
A
.CW never
claim can express only negative claims, not positive ones.
Fortunately, the two are exchangeable: if we want to express
that a good behavior is unavoidable, we can formalize all
ways in which the good behavior could be violated, and express
that in the
.CW never
claim.
.LP
Suppose that the LTL formula ◊□
.CW p ,
with
.CW p
a \*P expression, expresses a negative claim
(i.e., it is considered a correctness violation if
there exists any execution sequence in which
.CW p
can eventually remain true infinitely long).
This can be written in a
.CW never
claim as:
.P1
never { /* <>[]p */
do
:: skip /* after an arbitrarily long prefix */
:: p -> break /* p becomes true */
od;
accept: do
:: p /* and remains true forever after */
od
}
.P2
Note that in this case the claim does not terminate, and
also does not necessarily match all system behaviors.
It is sufficient if it precisely captures all violations
of our correctness requirement, and no more.
.LP
If the LTL formula expressed a positive property, we first
have to invert it to the corresponding negative property
.CW ◊!p
and translate that into a
.CW never
claim.
The requirement now says that it is a violation if
.CW p
does not hold infinitely long.
.P1
never { /* <>!p*/
do
:: skip
:: !p -> break
od
}
.P2
We have used the implicit match of a claim upon reaching the
closing terminating brace.
Since the first violation of the property suffices to disprove
it, we could also have written:
.P1
never { /* <>!p*/
do
:: p
:: !p -> break
od
}
.P2
or, if we abandon the connection with LTL for a moment,
even more tersely as:
.P1
never { do :: assert(p) od }
.P2
Suppose we wish to express that it is a violation of our
correctness requirements if there exists any execution in
the system where
.CW "□ (p -> ◊ q)"
is violated (i.e., the negation of this formula is satisfied).
The following
.CW never
claim expresses that property:
.P1
never {
do
:: skip
:: p && !q -> break
od;
accept:
do
:: !q
od
}
.P2
Note that using
.CW "(!p || q)"
instead of
.CW skip
in the first repetition construct would imply a check for just
the first occurrence of proposition
.CW p
becoming true in the execution sequence, while
.CW q
is false.
The above formalization checks for all occurrences, anywhere in a trace.
.LP
Finally, consider a formalization of the LTL property
.CW "□ (p -> (q U r))" .
The corresponding claim is:
.P1
never {
do
:: skip /* to match any occurrence */
:: p && q && !r -> break
:: p && !q && !r -> goto error
od;
do
:: q && !r
:: !q && !r -> break
od;
error: skip
}
.P2
Note again the use of
.CW skip
instead of
.CW "(!p || r)"
to avoid matching just the first occurrence of
.CW "(p && !r)"
in a trace.
.NH 2
Predefined Variables and Functions
.LP
The following predefined variables and functions
can be especially useful in
.CW never
claims.
.LP
The predefined variables are:
.CW _pid
and
.CW _last .
.LP
.CW _pid
is a predefined local variable in each process
that holds the unique instantiation number for
that process.
It is always a non-negative number.
.LP
.CW _last
is a predefined global variable that always holds the
instantiation number of the process that performed the last
step in the current execution sequence.
Its value is not part of the system state unless it is
explicitly used in a specification.
.P1
never {
/* it is not possible for the process with pid=1
* to execute precisely every other step forever
*/
accept:
do
:: _last != 1 -> _last == 1
od
}
.P2
The initial value of
.CW _last
is zero.
.LP
Three predefined functions are specifically intended to be used in
.CW never
claims, and may not be used elsewhere in a model:
.CW pc_value(pid) ,
.CW enabled(pid) ,
.CW procname[pid]@label .
.LP
The function
.CW pc_value(pid)
returns the current control state
of the process with instantiation number
.CW pid ,
or zero if no such process exists.
.LP
Example:
.P1
never {
/* Whimsical use: claim that it is impossible
* for process 1 to remain in the same control
* state as process 2, or one with smaller value.
*/
accept: do
:: pc_value(1) <= pc_value(2)
od
}
.P2
The function
.CW enabled(pid)
tells whether the process with instantiation number
.CW pid
has an executable statement that it can execute next.
.LP
Example:
.P1
never {
/* it is not possible for the process with pid=1
* to remain enabled without ever executing
*/
accept:
do
:: _last != 1 && enabled(1)
od
}
.P2
The last function
.CW procname[pid]@label
tells whether the process with instantiation number
.CW pid
is currently in the state labeled with
.CW label
in
.CW "proctype procname" .
It is an error if the process referred to is not an instantiation
of that proctype.
.NH 1
Verifications with \*V
.LP
The easiest way to use \*V is probably on a Windows terminal
with the Tcl/Tk implementation of \s-1XSPIN\s0.
All functionality of \*V, however, is accessible from
any plain ASCII terminal, and there is something to be
said for directly interacting with the tool itself.
.LP
The description in this paper gives a short walk-through of
a common mode of operation in using the verifier.
A more tutorial style description of the verification
process can be found in [Ho93].
More detail on the verification of large systems with the
help of \*V's supertrace (bitstate) verification algorithm
can be found in [Ho95].
.IP
.RS
.br
\(bu
Random and interactive simulations (section 2.1),
.br
\(bu
Generating a verifier (section 2.2),
.br
\(bu
Compilation for different types of searches (section 2.3),
.br
\(bu
Performing the verification (section 2.4),
.br
\(bu
Inspecting error traces produced by the verifier (section 2.5),
.br
\(bu
Exploiting partial order reductions (section 2.6).
.RE
.LP
.NH 2
Random and Interactive Simulations
.LP
Given a model in \*P, say stored in a file called
.CW spec ,
the easiest mode of operation is to perform a random simulation.
For instance,
.P1
spin -p spec
.P2
tells \*V to perform a random simulation, while printing the
process moves selected for execution at each step (by default
nothing is printed, other than explicit
.CW printf
statements that appear in the model itself).
A range of options exists to make the traces more verbose,
e.g., by adding printouts of local variables (add option
.CW -l ),
global variables (add option
.CW -g ),
send statements (add option
.CW -s ),
or receive statements (add option
.CW -r ).
Use option
.CW -n N
(with N any number) to fix the seed on \*V's internal
random number generator, and thus make the simulation runs
reproducible.
By default the current time is used to seed the random number
generator.
For instance:
.P1
spin -p -l -g -r -s -n1 spec
.P2
.LP
If you don't like the system randomly resolving non-deterministic
choices for you, you can select an interactive simulation:
.P1
spin -i -p spec
.P2
In this case you will be offered a menu with choices each time
the execution could proceed in more than one way.
.LP
Simulations, of course, are intended primarily for the
debugging of a model. They cannot prove anything about it.
Assertions will be evaluated during simulation runs, and
any violations that result will be reported, but none of
the other correctness requirements can be checked in this way.
.NH 2
Generating the Verifier
.LP
A model-specific verifier is generated as follows:
.P1
spin -a spec
.P2
This generates a C program in a number of files (with names
starting with
.CW pan ).
.NH 2
Compiling the Verifier
.LP
At this point it is good to know the physical limitations of
the computer system that you will run the verification on.
If you know how much physical (not virtual) memory your system
has, you can take advantage of that.
Initially, you can simply compile the verifier for a straight
exhaustive verification run (constituting the strongest type
of proof if it can be completed).
Compile as follows.
.P1
\*C -o pan pan.c # standard exhaustive search
.P2
If you know a memory bound that you want to restrict the run to
(e.g., to avoid paging), find the nearest power of 2 (e.g., 23
for the bound $2 sup 23# bytes) and compile as follows.
.P1
\*C '-DMEMCNT=23' -o pan pan.c
.P2
or equivalently in terms of MegaBytes:
.P1
\*C '-DMEMLIM=8' -o pan pan.c
.P2
If the verifier runs out of memory before completing its task,
you can decide to increase the bound or to switch to a frugal
supertrace verification. In the latter case, compile as follows.
.P1
\*C -DBITSTATE -o pan pan.c
.P2
.NH 2
Performing the Verification
.LP
There are three specific decisions to make to
perform verifications optimally: estimating the
size of the reachable state space (section 2.4.1),
estimating the maximum length of a unique execution
sequence (2.4.2), and selecting the type of correctness
property (2.4.3).
No great harm is done if the estimates from the first two
steps are off. The feedback from the verifier usually provides
enough clues to determine quickly what the optimal settings
for peak performance should be.
.NH 3
Reachable States
.LP
For a standard exhaustive run, you can override the default choice
for the size for the hash table ($2 sup 18# slots) with option
.CW -w .
For instance,
.P1
pan -w23
.P2
selects $2 sup 23# slots.
The hash table size should optimally be roughly equal to the number of
reachable states you expect (within say a factor of two or three).
Too large a number merely wastes memory, too low a number wastes
CPU time, but neither can affect the correctness of the result.
.sp
For a supertrace run, the hash table \f2is\f1 the memory arena, and
you can override the default of $2 sup 22# bits with any other number.
Set it to the maximum size of physical memory you can grab without
making the system page, again within a factor of say two or three.
Use, for instance
.CW -w23
if you expect 8 million reachable states and have access to at least
8 million ($2 sup 23#) bits of memory (i.e., $2 sup 20# or 1 Megabyte of RAM).
.NH 3
Search Depth
.LP
By default the analyzers have a search depth restriction of 10,000 steps.
If this isn't enough, the search will truncate at 9,999 steps (watch for
it in the printout).
Define a different search depth with the -m flag.
.P1
pan -m100000
.P2
If you exceed also this limit, it is probably good to take some
time to consider if the model you have specified is indeed finite.
Check, for instance, if no unbounded number of processes is created.
If satisfied that the model is finite, increase the search depth at
least as far as is required to avoid truncation completely.
.LP
If you find a particularly nasty error that takes a large number of steps
to hit, you may also set lower search depths to find the shortest variant
of an error sequence.
.P1
pan -m40
.P2
Go up or down by powers of two until you find the place where the
error first appears or disappears and then home in on the first
depth where the error becomes apparent, and use the error trail of
that verification run for guided simulation.
.sp
Note that if a run with a given search depth fails to find
an error, this does not necessarily mean that no violation of a
correctness requirement is possible within that number of steps.
The verifier performs its search for errors by using a standard
depth-first graph search. If the search is truncated at N steps,
and a state at level N-1 happens to be reachable also within fewer
steps from the initial state, the second time it is reached it
will not be explored again, and thus neither will its successors.
Those successors may contain errors states that are reachable within
N steps from the initial state.
Normally, the verification should be run in such a way that no
execution paths can be truncated, but to force the complete exploration
of also truncated searches one can override the defaults with a compile-time
flag
.CW -DREACH .
When the verifier is compiled with that additional directive, the depth at
which each state is visited is remembered, and a state is now considered
unvisited if it is revisited via a shorter path later in the search.
(This option cannot be used with a supertrace search.)
.NH 3
Liveness or Safety Verification
.LP
For the last, and perhaps the most critical, runtime decision:
it must be decided if the system is to be checked for safety
violations or for liveness violations.
.P1
pan -l # search for non-progress cycles
pan -a # search for acceptance cycles
.P2
(In the first case, though, you must compile pan.c with -DNP as an
additional directive. If you forget, the executable will remind you.)
If you don't use either of the above two options, the default types of
correctness properties are checked (assertion violations,
completeness, race conditions, etc.).
Note that the use of a
.CW never
claim that contains
.CW accept
labels requires the use of the
.CW -a
flag for complete verification.
.LP
Adding option
.CW -f
restricts the search for liveness properties further under
a standard \f2weak fairness\f1 constraint:
.P1
pan -f -l # search for weakly fair non-progress cycles
pan -f -a # search for weakly fair acceptance cycles
.P2
With this constraint, each process is required to appear
infinitely often in the infinite trace that constitutes
the violation of a liveness property (e.g., a non-progress cycle
or an acceptance cycle), unless it is permanently blocked
(i.e., has no executable statements after a certain point in
the trace is reached).
Adding the fairness constraint increases the time complexity
of the verification by a factor that is linear in the number
of active processes.
.LP
By default, the verifier will report on unreachable code in
the model only when a verification run is successfully
completed.
This default behavior can be turned off with the runtime option
.CW -n ,
as in:
.P1
pan -n -f -a
.P2
(The order in which the options such as these are listed is
always irrelevant.)
A brief explanation of these and other runtime options can
be determined by typing:
.P1
pan --
.P2
.NH 2
Inspecting Error Traces
.LP
If the verification run reports an error,
any error, \*V dumps an error trail into a file named
.CW spec.trail ,
where
.CW spec
is the name of your original \*P file.
To inspect the trail, and determine the cause of the error,
you must use the guided simulation option.
For instance:
.P1
spin -t -c spec
.P2
gives you a summary of message exchanges in the trail, or
.P1
spin -t -p spec
.P2
gives a printout of every single step executed.
Add as many extra or different options as you need to pin down the error:
.P1
spin -t -r -s -l -g spec
.P2
Make sure the file
.CW spec
didn't change since you generated the analyzer from it.
.sp
If you find non-progress cycles, add or delete progress labels
and repeat the verification until you are content that you have found what
you were looking for.
.sp
If you are not interested in the first error reported,
use pan option
.CW -c
to report on specific others:
.P1
pan -c3
.P2
ignores the first two errors and reports on the third one that
is discovered.
If you just want to count all errors and not see them, use
.P1
pan -c0
.P2
.SH
State Assignments
.LP
Internally, the verifiers produced by \*V deal with a formalization of
a \*P model in terms of extended finite state machines.
\*V therefore assigns state numbers to all statements in the model.
The state numbers are listed in all the relevant output to make it
completely unambiguous (source line references unfortunately do not
have that property).
To confirm the precise state assignments, there is a runtime option
to the analyzer generated:
.P1
pan -d # print state machines
.P2
which will print out a table with all state assignments for each
.CW proctype
in the model.
.NH 2
Exploiting Partial Order Reductions
.LP
The search algorithm used by \*V is optimized
according to the rules of a partial order theory explained in [HoPe94].
The effect of the reduction, however, can be increased considerably if the verifier
has extra information about the access of processes to global
message channels.
For this purpose, there are two keywords in the language that
allow one to assert that specific channels are used exclusively
by specific processes.
For example, the assertions
.P1
xr q1;
xs q2;
.P2
claim that the process that executes them is the \f2only\f1 process
that will receive messages from channel
.CW q1 ,
and the \f2only\f1 process that will send messages to channel
.CW q2 .
.LP
If an exclusive usage assertion turns out to be invalid, the
verifier will be able to detect this, and report it as a violation
of an implicit correctness requirement.
.LP
Every read or write access to a message channel can introduce
new dependencies that may diminish the maximum effect of the
partial order reduction strategies.
If, for instance, a process uses the
.CW len
function to check the number of messages stored in a channel,
this counts as a read access, which can in some cases invalidate
an exclusive access pattern that might otherwise exist.
There are two special functions that can be used to poll the
size of a channel in a safe way that is compatible with the
reduction strategy.
.LP
The expression
.CW nfull(qname)
returns true if channel
.CW qname
is not full, and
.CW nempty(qname)
returns true if channel
.CW qname
contains at least one message.
Note that the parser will not recognize the free form expressions
.CW !full(qname)
and
.CW !empty(qname)
as equally safe, and it will forbid constructions such as
.CW !nfull(qname)
or
.CW !nempty(qname) .
More detail on this aspect of the reduction algorithms can be
found in [HoPe94].
.SH
Keywords
.LP
For reference, the following table contains all the keywords,
predefined functions, predefined variables, and
special label-prefixes of the language \*P,
and refers to the section of this paper in
which they were discussed.
.KS
.TS
center;
l l l l.
_last (1.4) _pid (1.1.1) accept (1.3.2) active (1.1.1)
assert (1.3.1) atomic (1.2.1) bit (1.1.2) bool (1.1.2)
break (1.2.4) byte (1.1.2) chan (1.1.3) d_step (1.2.2)
do (1.2.4) else (1.2.4) empty (1.1.3) enabled (1.4)
end (1.3.2) fi (1.2.3) full (1.1.3) goto (1.2.2)
hidden (not discussed) if (1.2.3) init (1.1.1) int (1.1.2)
len (1.1.3) mtype (1.1.3) nempty (2.6) never (1.3.3)
nfull (2.6) od (1.2.4) of (1.1.3) pc_value (1.4)
printf (1.1.1) proctype (1.1.1) progress (1.3.2) run (1.1.1)
short (1.1.2) skip (1.2) timeout (1.2.4) typedef (1.1.2)
unless (1.2.5) xr (2.6) xs (2.6)
.TE
.KE
.SH
References
.LP
[Ho91]
G.J. Holzmann,
.I
Design and Validation of Computer Protocols,
.R
Prentice Hall, 1991.
.LP
[Ho93]
G.J. Holzmann, ``Tutorial: Design and Validation of Protocols,''
.I
Computer Networks and ISDN Systems,
.R
1993, Vol. 25, No. 9, pp. 981-1017.
.LP
[HoPe94]
G.J. Holzmann and D.A. Peled, ``An improvement in
formal verification,''
.I
Proc. 7th Int. Conf. on Formal Description Techniques,
.R
FORTE94, Berne, Switzerland. October 1994.
.LP
[Ho95]
G.J. Holzmann, ``An Analysis of Bitstate Hashing,''
technical report 2/95, available from author.
.LP
[HS99]
G.J. Holzmann, ``Software model checking: extracting
verification models from source code,''
.I
Proc. Formal Methods in Software Engineering and Distributed
Systems,
.R
PSTV/FORTE99, Beijng, China, Oct. 1999, Kluwer,pp. 481-497.
|