Download as pdf or txt
Download as pdf or txt
You are on page 1of 13

Specman Elite

From Verisity (http://www.verisity.com)


Specman Notes Presents a high-level language for writing
test environments
! Test Benches
! Coverage
! Constraint based test generation and checking

e language On Line Help


Looks like verilog to me… Verisity has all their help on line
Has support for data types ! e language ref
! Command ref for Specman Elite
! With statistical values
! Usage etc.
! With constraints (hard and soft)
! Verification Advisor/Vault on their web page
Stimulus Tutorial is in CLASS directory (large pdf)
Checking ! Do not print it, just do it.
Events ! Its not great

File Format Comments


A code segment is enclosed with a begin-code marker <' e files begin as a comment which ends when the first begin-
and an end-code marker '>. code marker <' is encountered.
Both the begin-code and the end-code markers must be Comments within code segments can be marked with double
placed at the beginning of a line (left most), with no other dashes (--) or double slashes (//):
text on that same line (no code and no comments).
a = 5; -- This is an inline comment
The following three lines of code form a code segment:
<' b = 7; // This is also an inline comment
import cpu_test_env;
'> The end-code '> and the begin-code <' markers can be used in
Several code segments can appear in one file. Each code the middle of code sections, to write several consecutive
segment consists of one or more statements. lines of comment

1
Pre Defined Constants Keywords
Constant Description all of bytes default fail
TRUE For Boolean variables and expressions. all_values c export define fall
and case delay file
FALSE For Boolean variables and expressions.
as a change detach first of
NULL For structs, specifies a NULL pointer. For as_a check that do for
character strings, specifies an empty string. assert compute down to force
UNDEF UNDEF indicates NONE where an index is assume computed dut_error from
expected. async consume each gen
MAX_INT Represents the largest 32-bit int (231 -1) attribute continue edges global
before cover else hdl pathname
MIN_INT Represents the smallest 32-bit int (-231).
bit cross emit if
MAX_UINT Represents the largest 32-bit uint (232-1). bits cvl call event #ifdef
bool cvl callback exec #ifndef
break cvl method expect in
byte cycle extend index

Keywords Keywords
int key or soft
is a like others start type vhdl code
is also line pass state machine uint vhdl driver
is c routine list of prev step unit vhdl function
is empty matching print struct until vhdl procedure
is first me range string using
vhdl driver
is inline nand ranges sync var
vhdl simulator
is instance new release sys verilog code
vhdl time
is not a nor repeat that verilog function
is not empty not return then verilog import when
is only not in reverse time verilog simulator while
is undefined now rise to verilog task with
item nxor routine transition verilog time within
keep on select true verilog timescale
keeping only session try verilog trace
verilog variable

Syntactic Elements Statements


Statements
Statements are top-level constructs and are valid within the begin-code <' and Statements are top-level constructs and are valid
end-code '> markers. Statements end with a semicolon ‘;’
Struct members within thebegin-code <' and end-code '> markers.
Struct members are second-level constructs and are valid only within a struct
definition.
Key Statement Types:
Actions ! Struct – defines a new data structure
Actions are third-level constructs and are valid only when associated with a ! Type – defines an enumerated/subtype
struct member, such as a method or an event.
Expressions ! Extend – extends a previously defined struct or type
Expressions are lower-level constructs that can be used only within another e ! Define – extends language with new commands,
construct. actions, expressions
The syntax hierarchy roughly corresponds to the level of indentation
shown below: " More: import, verilog-x, vhdl-x …
statements ! Order is not critical – but imports must be first (after
struct members macro defines)
actions
expressions

2
Struct & Struct Members Struct Members
field declaration
Struct members are second-level constructs and ! Defines a data entity that is a member of the enclosing struct and has an
explicit data type.
are valid only within a struct definition. method declaration
! Defines an operational procedure that can manipulate the fields of the
enclosing struct and access run-time values in the DUT.
struct struct-type: struct-descriptor [like base-struct-type: struct-descriptor]
{ subtype declaration
[member: struct-member; ...]} ! Defines an instance of the parent struct in which specific struct members
Example: have particular values or behavior. (e.g., when)
type packet_kind: [atm, eth]; constraint declaration
struct packet { ! Influences the distribution of values generated for data entities and the
order in which values are generated. (e.g., keep)
len: int;
keep len < 256; coverage declaration
kind: packet_kind; ! Defines functional test goals and collects data on how well the testing is
meeting those goals.
};
temporal declaration
! Defines e events and their associated actions.(e.g., on, expect, assume)

Fields Fields
[!][%] field: field-name[: type: type] [[min-val: int .. max-val: int]] ! Ungenerated Fields
[((bits | bytes):num: int)] A field defined as ungenerated (with the “!” option) is not generated
automatically.
Syntax example:
This is useful for fields that are to be explicitly assigned during the
type NetworkType: [IP=0x0800, ARP=0x8060] (bits: 16); test, or whose values involve computations that cannot be expressed in
struct header { constraints.
address: uint (bits: 48); Ungenerated fields get default initial values (0 for scalars, NULL for
hdr_type: NetworkType; structs, empty list for lists).
!counter: int; An ungenerated field whose value is a range (such as [0..100]) gets the
}; first value in the range.
If the field is a struct, it will not be allocated and none of the fields in it
will be generated.

Fields Actions
% Physical Fields Actions are third-level constructs and are valid
A field defined as a physical field (with the “%” option) is packed
when the struct is packed.
only when associated with a struct member, such
Fields that represent data that is to be sent to the HDL device in the as a method or an event.
simulator or that are to be used for memories in the simulator or in <'
Specman Elite, need to be physical fields.
struct packet{
Nonphysical fields are called virtual fields and are not packed
automatically when the struct is packed, although they can be packed event xmit_ready is rise('top.ready');
individually. on xmit_ready {transmit();};
If no range is specified, the width of the field is determined by the
field’s type. For a physical field, if the field’s type does not have a transmit() is {
known width, you must use the (bits | bytes : num) syntax to specify out("transmitting packet...");
the width.
};
};
'>

3
Actions Actions
Creating & modifying variables Invoking methods and routines
" var, = , op=, " method(), tcm(), start tcm(), routine(), compute method(),
return
Interacting with the DUT
" force, release Performing time consuming actions
" emit, sync, wait, all of , first of, state machine
Flow control
! Conditionals Generating data items
" gen
" if then else, labeled case, boolean case
! Iteratation Detecting/handling errors
" While, repeat until, for each, for from-to, for each-line, for " check that, dut_error(), assert, warning(), error(), fatal(),
each-file-matching try
! Flow control Printing
! break, continue " print, set_config()

Expressions Struct Hierarchy


global
Expressions are lower-level constructs that can be ! sys
" switch
used only within another e construct ! ctrl_stub
! port_stub1
Expressions are constructs that combine operands ! sender

and operators to represent a value ! listener

! port_stub2
" A literal value
! port_stub3
" A constant ! port_stub4
" An e entity, such as a method, field, list, or struct ! packing
" An HDL entity, such as a signal ! files
" A compound expression applies one or more operators to one ! scheduler
or more operands. ! simulator
! session

Implicit Variables Implicit Variables


it : The implicit variable it always refers Me: The implicit variable me refers to the
current struct and can be used anywhere in the
to the current item. struct.
for each in sys.packets{ struct packet {
data: uint;
it.len = 5; stm() is {
var tmp: uint;
.good = TRUE; -- it is assumed gen tmp keeping {it < me.data}; - - it is tmp
}; print data, tmp using hex;
};
};

4
Implicit Variables Implicit Variables
result:The result variable returns a value index: The index variable holds the
of the method’s return type. If no return current index of the item referred to by it.
action is encountered, result is returned by The scope of the index variable is limited
default. The following method returns the to the action block.
sum of “a” and “b”: for each in packets do {
sum(a: int, b: int): int is { packets[index].len = 5;
result = a + b; .id = index;
}; };

Operators & Precedence Types: Scalars


[ ] List indexing (subscripting) >> << Shift right, shift left int Represents numeric data, byte An unsigned integer in
[..] List slicing < <= > >= Comparison
is [not] a Subtype identification
both negative and non- the range 0–255. (8 bits)
[ : ] Bit slicing (selection)
== != Equality, inequality negative integers. (32 bits) time An unsigned integer in
f(…) Method and routine calls
. Field selection
=== !== Verilog four-state uint Represents unsigned the range 0–263 -1.( 64
comparison
~ Bitwise not ~ !~ String matching
numeric data, non- bits)
! (not) Boolean not in Range list operator negative integers only. (32 bool Represents truth
{;} List concatenation & Bitwise AND bits) (logical) values, TRUE(1)
%{…} Bit concatenation | Bitwise OR
bit An unsigned integer in and FALSE(0). (1 bit)
+ - Unary plus, minus ^ Bitwise XOR
&& (and) Boolean AND
the range 0–1. (1 bit)
*, /, % Binary multiply, divide,
modulus || Boolean or
+, - Binary add and subtract => Boolean implication
? : Conditional operator

Subtypes Enumerated Types


By range You can define the valid values for a variable or
! int [0..100] field as a list of symbolic constants.
var kind: [immediate, register];
By width
You can extend the definition:
! int (bits: 8)
type packet_protocol: [];
Named Subtypes
extend the definition of the type with
! type int_count : int [0..99] (bits:7);
extend packet_protocol : [Ethernet, IEEE,
! var count : int_count; foreign];

5
Struct Subtypes Subtypes with extend & when
type packet_protocol: [Ethernet, IEEE, foreign]; type packet_protocol: [Ethernet, IEEE, foreign];
struct packet { struct packet {
protocol: packet_protocol; protocol: packet_protocol;
size: int [0..1k];
size: int [0..1k];
data[size]: list of byte;
data[size]: list of byte;
// when Ethernet packet {e_field: int; -- same as extend below
legal: bool; // show() is {out("I am an Ethernet packet")
}; // };
extend sys { };
gen_eth_packet () is { extend Ethernet packet {
var packet: legal Ethernet packet; -- local sub-type e_field: int;
gen packet keeping {it.size < 10;}; show() is {out("I am an Ethernet packet")};
print packet; };
};
};

Accessing sub-typed structs List types


type packet_protocol: [Ethernet, IEEE, foreign];
perl-like array/list semantics
struct packet {
protocol: packet_protocol; my_list[0] refers to the first item in the list
when IEEE packet { i_val: int;}; var lob: list of byte = {15;31;63;127;255};
}; print lob[0..2];
var pk_inst: IEEE packet;
No multi-dimensional lists. To create a list with
pk_inst.i_val = 1;
sublists in it, create a struct to contain the sublists
if pk_inst is a IEEE packet (ip) {ip.i_val = 1; };
pk_list.first(it is a IEEE packet (ip) and ip.i_val == 1);

Example Run system:


type packet_protocol : [Ethernet, IEEE, foreign]; sys = packets =
struct packet { item type protocol len
protocol: packet_protocol; all_lengths =
len: int [0..10]; 0. packet Ethernet 10 0. 10
}; 1. packet Ethernet 10 1. 10
extend sys { 2. packet IEEE 4 2. 4
packets[10] : list of packet; 3. packet IEEE 0 3. 0
do_print() is { 4. packet Ethernet 7 4. 7
var all_lengths: list of packet'len; 5. packet Ethernet 8 5. 8
all_lengths = packets.len; -- multi-element assignment 6. packet foreign 8 6. 8
print packets; 7. packet Ethernet 5 7. 5
print all_lengths; 8. packet Ethernet 3 8. 3
}; 9. packet foreign 6 9. 6
};

6
Keyed Lists Constraints with Keep
keep constraint-bool-exp
A keyed list data type is similar to hash Syntax example:
tables or association lists found in other keep kind != tx or len == 16;
programming languages. Parameters
struct person { constraint-bool-exp A simple or a compound Boolean expression.
name: string; States restrictions on the values generated for fields in the struct or the
id: int; struct subtree,or describes required relationships between field values
}; and other items in the struct or its subtree.
struct city { Hard constraints are applied whenever the enclosing struct is
persons: list(key: name) of person; generated. For any keep constraint in a generated struct, the generator
street_names: list(key: it) of string; either meets the constraint or issues a constraint contradiction message.
}; Note If the keep constraint appears under a when construct, the
constraint is considered

Keep examples Keep examples


struct pkt { Using the predefined method “is_a_permutation()”
kind: [tx, rx]; struct astr {
len: int; l_1: list of int;
keep kind == tx => len == 16; l_2: list of int;
// when tx pkt { -- this acts exactly the same way keep l_2.is_a_permutation(l_1);
// keep len == 16; };
// }; struct transaction {
}; address: uint;
Both these constraints are identical to the constraint: keep soft address == select { -- using keep soft and select
keep kind != tx or len == 16; 10: [0..49];
60: 50;
Note: this is just the true meaning of “=>” Boolean
implication 30: [51..99];
};
};

Keep examples Keep examples


struct transaction { extend instr { type transaction_kind: [good, bad]; extend sys {
address: uint; keep soft opcode == select { struct transaction { t: transaction;
keep soft address == select { kind: transaction_kind;
40: [ADD, ADDI, SUB, keep me.t.length != 0;
10: [0..49];
SUBI]; address: uint; };
60: 50;
20: [AND, ANDI, XOR, length: uint;
30: [51..99];
}; XORI]; data: list of byte;
}; 10: [JMP, CALL, RET, };
extend transaction { NOP]; extend transaction {
keep address in [10..50]; ‘top.carry' * 90: JMPC; keep length < 24;
keep soft address == select {
}; keep data[0] == 0x9a;
10: min; keep address in [0x100..0x200];
60: others;
};
keep me.kind == good;
30: max; };
};
};

7
Generation with keep Events --
Generation order is important because it influences the The e language provides temporal
distribution of values. For example, in the keep constraint
shown below, if “kind” is generated first, “kind” is “tx” constructs for specifying and verifying
about 1/2 the time because there are only two legal values behavior over time.
for “kind”:
struct packet { All e temporal language features depend on
kind: [tx, rx];
size: byte;
the occurrence of events, which are used to
keep size > 15 => kind == rx; synchronize activity with a simulator and
};
within Specman Elite.
On the other hand, if “size” is generated first, there is only
a 1 in 16 chance that “size” will be less than or equal to 15,
so “kind” will be “tx” about 1/16 of the time.

Event Example Temporal Expressions


An event named "rclk" is defined
to be the rising edge of a signal Expect
named "top.clk" at another event
named "sim".
! expect {[1]; true(chk)@rclk};
event rclk is rise(‘top.clk’)@sim
The @ symbol is used with an
event name, @event, to mean
On
‘top.clk’
"when the event is true". ! on rclk {…};
The special @sim syntax means at
a callback from the simulator. Sampling points for TCMs
The rise() expression always causes
a callback when the signal rises. ! set_chk()@rclk is {..};
Therefore, this event definition rclk
means "a rise of top.clk causes rclk
to occur".

Event details Redefinition with “is only”


event event-type[is [only] temporal-expression] struct m_str {
Syntax example: event start_ct;
event top_clk is fall('top.r_clk') @sim;
event clk is rise('top.cpu_clk') @sim;
event stop_ct is {@start_ct; [1]} @top_clk;
Events can be attached to temporal expressions, using the
option is [only] temporal-expression syntax, or they can be };
unattached. extend m_str {
An attached event is emitted automatically during any event stop_ct is only {@start_ct; [3] }@top_clk;
Specman Elite tick in which the temporal expression };
attached to it succeeds.

8
Callbacks: from simulator and
emit Emit example
struct sys_ready { <' run() is also {
event sim_ready is change('top/ready') @sim; // callback struct xmit_recv { start transmit();
event rec_ev;
bar() @sys.clk is { start receive();
transmit() @sys.clk is {
while TRUE { wait cycle; };
wait until @sys.ok; emit rec_ev; };
wait [1] *cycle; out("rec_ev emitted"); extend sys {
};
emit sim_ready; // force the event event clk is @sys.any;
receive() @sys.clk is {
}; wait until @rec_ev;
xmtrcv_i: xmit_recv;
}; out("rec_ev occurred"); '>
}; stop_run();
};

Predefined Events sys.any


sys.any Emitted on every Specman Elite tick. This is a special event that defines the highest granularity of time. The
sys.tick_start Emitted at the start of every Specman Elite tick. occurrence of any event in the system causes an occurrence of the any
sys.tick_end Emitted at the end of every Specman Elite tick. event at the same tick. For any temporal expression used without an
explicit sampling event, sys.any is used by default.
session.start_of_test Emitted once at test start.
In stand-alone Specman Elite operation (that is, with no simulator
session.end_of_test Emitted once at test end. attached), the sys.any event is the only one that occurs automatically. It
struct.quit Emitted when a struct's quit() method is called. typically is used as the clock for stand-alone operation, as in the
Only exists in structs that contain events or following example.
have members that consume time (for Original clock definition for simulation:
example, time-consuming methods and on extend sys {
struct members).
event clk is rise('top.clk')@sim; // clk drives the system
sys.new_time In stand-alone operation (no simulator), this
event is emitted on every sys.any event. When };
a simulator is being used, this event is emitted Extension to override the clock to tie it to sys.any for stand-alone
every time a callback occurs, if the attached operation:
simulator's time has changed since the extend sys {
previous callback. event clk is only cycle @sys.any;
};

Configuration Files
On pitteda3 or pitteda4
# Verisity user Environment variables (no newlines) Copy the tutorial tar file
setenv SPECMAN_HOME
$CAD_DIR/verisity/specman_3.3.3/sn_rel3.3.3
Copy the emacs specman-mode file
setenv PATH ! If you use emacs
{SPECMAN_HOME}/`${SPECMAN_HOME}/bin/sn_arch.sh`:${SPECM
AN_HOME}/bin:${PATH} ! If you like language editors
setenv SPECMAN_DIR
$SPECMAN_HOME/`${SPECMAN_HOME}/bin/sn_arch.sh` Untar the tutorial directories into your own
setenv VERISITYLD_LICENSE_FILE account ( ./src and ./gold )
5286@pitteda1.ee.pitt.edu

9
Running Specman Tutorial Example
(make sure X and DISPLAY are right) Use src files from tar archive
CPU and Testbench are both modeled in
specview & specman
! No need for modelsim simulator
! “easy” high level modeling of CPU
! Black box testing

CPU_top.e CPU_instr.e
<' <'
// Basic: type cpu_opcode: [ // Opcodes
import CPU_instr, CPU_misc; ADD, ADDI, SUB, SUBI,
AND, ANDI, XOR, XORI,
// Add dut and drive:
JMP, JMPC, CALL, RET,
import CPU_dut, CPU_drive;
NOP
] (bits: 4);
// Add Coverage:
import CPU_cover; type reg : [ // Register names
REG0, REG1, REG2, REG3
// Add Checking: ] (bits: 2);
import CPU_checker;

'>

Instruction Formats to test Extend the system struc


struct instr { // defines legal opcodes for reg instr extend sys {
%opcode : cpu_opcode ; keep opcode in [ADD, SUB, AND,
%op1 : reg ; XOR, RET, NOP] // creates the stream of instructions
kind : [imm, reg]; => kind == reg; !instrs: list of instr; -- don’t pre-generate the list

// defines 2nd op of reg instruction // defines legal opcodes for imm instr
when reg instr { keep opcode in [ADDI, SUBI, ANDI, };
%op2 : reg ; XORI, JMP, JMPC, CALL]
}; => kind == imm;
'>
// defines 2nd op of imm instruction // ensures 4-bit addressing scheme
when imm instr { when imm instr {
%op2 : byte; keep (opcode in [JMP, JMPC,
CALL]) => op2 < 16;
};
};
};

10
CPU_misc.e CPU_drive
<‘
<' extend sys {
extend global { event cpuclk is (fall('top.clk')@sys.any);
cpu_env : cpu_env;
setup_test() is also { cpu_dut : cpu_dut; //cpu_refmodel : cpu_refmodel;
set_config(print,radix,hex);
set_config(cover,mode,normal,show_mode,both); };
set_config(print, items, 100); struct cpu_env {
reset_cpu() @sys.cpuclk is {
}; 'top.rst' = 0;
finalize_test() is also{ wait [1] * cycle;
specman("display print sys.instrs"); 'top.rst' = 1;
wait [5] * cycle;
}; //sys.cpu_refmodel.reset(); // reset reference model
}; 'top.rst' = 0;
'> };

CPU_drive CPU_drive
drive_one_instr(instr: instr) @sys.cpuclk is { !next_instr : instr;
var fill0 : uint(bits : 2) = 0b00; num_instrs : uint;
wait until rise('top.fetch1');
keep soft num_instrs in [40..60];
emit instr.start_drv_DUT;
if instr.kind == reg then {
'top.data' = pack(packing.high, instr); gen_and_drive_instrs() @sys.cpuclk is {
} else { for i from 0 to num_instrs do {
// immediate instruction
gen next_instr;
'top.data' = pack(packing.high, instr.opcode, instr.op1, fill0);
wait until rise('top.fetch2');
sys.instrs.add(next_instr);
'top.data' = pack(packing.high, instr.imm'op2); drive_one_instr(next_instr);
}; };
wait until rise('top.exec'); };
};

CPU_drive CPU_drive
drive_pregen_instrs() @sys.cpuclk is { run() is also {
for i from 0 to sys.instrs.size() - 1 { start drive_cpu();
drive_one_instr(sys.instrs[i]);
};
};
}; };
drive_cpu() @sys.cpuclk is { extend instr {
reset_cpu(); event start_drv_DUT;
if sys.instrs.size() > 0 then {
};
drive_pregen_instrs();
} else {
'>
gen_and_drive_instrs();
};
wait [1] * cycle;
stop_run();
};

11
Test1.e
<'
Calc 1 – from IBM
import CPU_top;
extend instr { // test constraints This testbench provides the basic structure
keep opcode in [ADD, ADDI];
keep op1 == REG0;
to the calc1 design.
when reg instr {keep op2 == REG1}; // when reg instr
when imm instr {keep op2 == 0x5}; // when imm instr
It is only set to test ADD operations on port
}; 1.
extend sys {
// generate 5 instructions Your exercise is to test the other ports and
keep instrs.size() == 5;
};
operations.
extend sys {
post_generate() is also {
gen instrs; // start generating stream of instructions
};
};
'>

Calc1.e: extend system with


Calc1.e: define opcodes, cmds cmd list
<‘
type op: [ // Opcodes extend sys {
NOP, ADD, SUB, inv, cmds : list of command;
inv1, SHL, SHR
keep me.cmds.size() == 10;
] (bits: 4);
setup() is also {
struct command { set_check("...", ERROR_CONTINUE); //don't quit the simulation
%cmd_in : op ; on error
%din1 : uint (bits:32); };
%din2 : uint (bits:32);
}; // extend sys
!resp : int (bits:2);
!dout : uint (bits:32);
keep cmd_in == ADD;
}; // struct command

Calc1.e: initialize – define


Calc1.e: initialize (not timed) clock in simulator
struct init {
simulator_command("force -freeze sim:/demo_top/c_clk 1 0, 0 {50 ns} -r
initvals () is {
10 0");
-- restart the simulation and view the waves
simulator_command("restart -f");
-- reset the design
simulator_command("add wave *"); -- reset the design
-- force all signals to zeroes force '~/demo_top/reset' = 1111111;
force '~/demo_top/error_found'= 0; simulator_command("run 800");
force '~/demo_top/scan_in'= 0;
force '~/demo_top/reset' = 0000000;
force '~/demo_top/req1_cmd_in'= 0000;
force '~/demo_top/req2_cmd_in'= 0000; }; // initvals ()
force '~/demo_top/req3_cmd_in'= 0000; }; // struct init
force '~/demo_top/req4_cmd_in'= 0000;
force '~/demo_top/req1_data_in'= 0x00000000;
force '~/demo_top/req2_data_in'= 0x00000000;
force '~/demo_top/req3_data_in'= 0x00000000;
force '~/demo_top/req4_data_in' = 0x00000000;

12
Calc1.e: main test loop Calc1.e: extend system for
struct test {
event clk is fall('~/demo_top/c_clk')@sim;
test
event resp is change('~/demo_top/out_resp1')@sim; extend global {
body () @clk is { run_test() is also { -- Called from "test" after generation.
for each command (cmd) in sys.cmds do { -- run the initialization function
force '~/demo_top/req1_cmd_in'= pack(NULL, cmd.cmd_in);
var i: init = new;
force '~/demo_top/req1_data_in'= pack(NULL, cmd.din1);
i.initvals();
wait cycle;
force '~/demo_top/req1_cmd_in'= 0000; -- start the testing function
force '~/demo_top/req1_data_in'= pack(NULL, cmd.din2); var w: test = new;
wait @resp; -- wait for the response start w.body();
cmd.resp = '~/demo_top/out_resp1'; -- run the simulator until the tests are done
cmd.dout = '~/demo_top/out_data1'; simulator_command("run -all");
check that cmd.resp == 01; }; // run_test()
check that cmd.dout == (cmd.din1 + cmd.din2);
}; // extend global
wait cycle;
'>
}; // for each command
wait cycle;
stop_run();
}; // body
}; // struct test

13

You might also like