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)

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.