Hase++
Hase++ appears to the programmer as a superset of C++ containing
simulation methods that allow entities to change state, to update
parameter values, to send and receive packets, etc. These
changes can subsequently be visualised.
Visualisation
Once a simulation has been run, the tracefile generated by that
simulation can be loaded back into HASE and used to animate the
project display, allowing the user to see what happened during
the simulation. Three types of event can be visualised: state
changes, changes to parameter values and the movement of packets
between entities.
State Changes
For each entity, HASE automatically creates a my_state
variable to which any of the states declared for that entity in the
project definition file can be assigned. A change of state will only
be recorded in the tracefile, however, when a subsequent
dump_state() statement is executed. In
the Example Project included in this User
Guide, the memory entity has the states M_IDLE and M_BUSY. Since
M_IDLE is declared first, my_state will initially be set to
M_IDLE and the icon assigned to this state in the screen layout
(.elf) file) will be the one drawn when the project is first
loaded. To change this state to M_BUSY once the Memory has received a
packet, the following statements are used in memory.hase
my_state = M_BUSY;
dump_state();
Parameter Updates
An entry created by a dump_state() statement also records in
the tracefile entry the current values of the parameters (other than
global parameters and array parameters) declared for that entity in
the entity definition file.
Updating Global Parameter Values
An entry is created in the tracefile for the current value of any
global parameters declared in the model when a dump_globals()
statement is executed. Since global parameters can be accessed by any
entity in the model, a mutual exclusion mechanism is provided to
ensure proper synchronisation (see GLOBALS).
Updating Array Parameter Values
Updating array parameter values is less straightforward than updating
e.g. integer parameters. For display purposes, HASE creates a
separate copy of each array. To update both the display of the
array parameter and the array itself, the Update function is
used. For example, in
data_mem.Update(mem_address, write_data);
write_data is a new value being written into location
mem_address of the array data_mem. write_data
must have the same type as the elements of the array.
The Update function can have a third parameter, step, that
allows events that occur at the same time to be ordered in the
animation. For example, if two messages are sent at the same time, the
one with the lowest step number will be animated first. If they have
the same step number they will be animated at the same time. The
default value is 0.
Sending Packets
Packets may be sent using using two different methods, one of which causes
packets to be sent along specific links between entities and one which
causes packets to be sent between entities that do not have specific
links connecting them.
send_link_pkt_type
This method schedules an event to a port (and thus to be sent along
the link attached to that port) and causes an entry to be made in the
trace file so that the packet can be visualised, i.e. seen
moving along the link during animation. (This visualisation can be
inhibited by declaring the link to be of width 0.)
Syntax
send_link_pkt_type(port, pkt_name)
- link_pkt_type: the type of packet to be sent; this type is
defined inside the LINK definition for the type of the link attached
to the port, e.g.
LINK(l_bus,[(RESULT, RINT(value,0))]);
which defines a link of type l_bus, along which packets
of type RESULT (containing an integer with type_name
value) can be sent.
- port: name of the port used to send the event.
- pkt_name: the name of the packet to be sent. This packet
must be of the same type as link_pkt_type.
sim_schedule
This method schedules an event to an entity; it does not cause an
entry to be made in the trace file. sim_schedule is
normally used in conjunction with SIM_PUT (which is strictly
speaking a Hase++ macro since it invokes more primitive Hase++
functions)
Syntax
sim_schedule(entity_name, delay, link_pkt_type,
SIM_PUT(pkt_type, pkt_name));
Example
sim_schedule(pipe, 0.0, INSTRUCTION, SIM_PUT(t_instrn_reg, ID_Input_Reg));
Receiving Packets
HASE places packets sent by entities in an event queue. Receiving
entities use a a number of methods to dequeue these events and to
extract the relevant information from the packets.
Predicates
Predicates can be used to filter incoming events. The criterion can be
the incoming port (sim_from_port), the tag carried by the
event(sim_type_p) or a user-defined predicate. Predicates are
typically used within a sim_waiting command (see below).
sim_waiting
sim_waiting computes the number of events (from within the
queue of events that are waiting to be processed) that are for the
entity containing the sim_waiting statement and that match the
predicate requirements. It is normally used in clocked entities to
look for packets sent by some other entity in the previous clock
period.
E.g. in
if (sim_waiting(ev, I_Input) > 0)
where I_Input is a predicate defined using
sim_from_port I_Input(instr_in);
the condition will be true if one or more packets is waiting at port
instr_in.
sim_select
When an entity can have received packets on a number of ports since it
processed the last one, sim_select is used in conjunction
with sim_waiting to select the appropriate packet for a
particular action.
Example
The following example is taken from the EMMA project:
sim_from_port InputA(input1);
sim_from_port InputB(input2);
if (sim_waiting(InputA) > 0)
{
sim_select (InputA, ev);
SIM_CAST_DEL( int, inputA, ev);
mpx_input1 = inputA;
}
if (sim_waiting(InputB) > 0)
{
sim_select (InputB, ev);
SIM_CAST_DEL( int, inputB, ev);
mpx_input2 = inputB;
}
Here the entity can receive packets on two ports, input1 and
input2, and uses a predicate for each to determine whether a
packet has arrived. It uses sim_select to select the
appropriate packet and SIM_CAST_DEL (described below) to copy
the content of the packet into a local variable (inputA or
inputB).
sim_select can also use the built-in predicate SIM_ANY
which matches any event.
sim_get_next
sim_get_next(ev) can be used in entities in a clocked system
that are not themselves clocked, e.g. the Registers entities in
the DLX and MIPS projects, which are essentially slave entities to the
Instruction Decode and Write Back entities. It takes the next event
for the entity from the event queue and returns the packet content as
ev.
Example
sim_get_next(ev);
if (ev.type()== SET_DEST_BUSY)
{
SIM_CAST( set_dest_busy_struct, set_dest_busy_pkt, ev);
ACTION 1
}
else if (ev.from_port(from_instr_decode1))
{
ACTION 2
}
else if (ev.from_port(from_instr_decode2))
{
ACTION 3
}
else if (ev.from_port(from_write_back))
{
ACTION 4
}
Here the next event can come via a sim_schedule operation or from one
of three ports.
sim_get_next_before
sim_get_next_before(ev, timeout) is similar to
sim_get_next(ev) in that it takes the next event for the entity
from the event queue and returns the packet content as ev,
but only if the event occurs before a simulation time equal to
timeout has elapsed.
Timing
sim_hold
sim_hold(delay) holds the entity for delay simulation
time units.
Macro instructions
Macro instructions are used to marshal (SIM_PUT) and unmarshal
(SIM_CAST) a packet associated with an event. SIM_PUT
is usually used before calling sim_schedule while SIM_CAST is
used after sim_select, sim_waiting, sim_get_next
or sim_get_next_before.
SIM_PUT(type,val)
Produces a packet with the C++ type type and the value val.
SIM_CAST(type,var,ev)
Extracts the packet contained in the event ev assuming its
C++ type is type. The value of this event is put in the C++ variable
var. Obviously, the type of this variable must be compatible
with type.
SIM_CAST_DEL(type,var,ev)
As for SIM_CAST except that the entry on the event queue is
deleted and cannot be re-accessed. This is useful in large projects
where the amount of memory space used during runtime is an issue.