Chapter 2. Other Major Features

The enhancement of generics that we described in Chapter 1 is one of several major new features in VHDL. In this chapter, we highlight the other major features that bring significant new power to the language.

External Names

One of the characteristics of VHDL is that it allows a verification testbench to be written in the same language as the design to be verified. However, some aspects of earlier versions of VHDL make it hard to verify designs. In particular, the scope and visibility rules are intended to help us manage name spaces in complex designs by enforcing abstraction of interfaces and hiding of internal information. While they are good for a design in isolation, they can prevent a testbench from accessing items internal to a design. A test-bench may need to monitor the state of internal signals, or force internal signals to particular values.

VHDL-2008 provides a new naming feature, external names, that allows us to write a testbench that accesses items not normally visible according to the hierarchical scope and visibility rules. An external name specifies a hierarchical path through the design hierarchy to reach a declared constant, shared variable, or signal. Thus, a testbench using an external name must have sufficient knowledge of the hierarchical structure of the design for the path to be valid. Validity of the external name is assumed during analysis of the testbench, and is checked during elaboration of the complete design hierarchy.

An external name is written in the following form:

<< class external_pathname : subtype >>

where the class is one of the object classes constant, signal, or variable; the external pathname is the hierarchical path; and the subtype specifies a view of the object, in a way similar to an alias. As an example, a testbench might use the following external name to monitor the value of a signal within a design under verification:

assert <<signal .tb.duv.controller.state:
                  std_logic_vector(0 to 4)>> /= "00000"
   report "Illegal controller state";

Within the testbench, this external name is a reference to a signal nested within the component labeled controller, which is nested within the component labeled duv, which is within the top-level entity tb. The signal is interpreted as a std_logic_vector indexed from 0 to 4. When the testbench is analyzed, the existence and type of the signal is not checked. However, once the complete design hierarchy is elaborated, the signal must exist and be of an appropriate type to match the subtype specified in the external name.

An external name is just a new form of name for a constant, signal or variable, so we can use an external name at any place where a name is appropriate, subject to some rules that we will return to shortly. That means we can refer to a constant or signal value in an expression, and we can assign to a signal or include it in a port map. The rules for forming a pathname only allow us to refer to items declared in concurrent regions of a design (packages, entities, architectures, blocks and generate statements), so an external variable name can only refer to a shared variable. We can use an external variable name to invoke a method of the shared variable, for example:

<<variable .tb.duv_behavior.msg_fifo:
             fifo_type<<.put(corrupt_msg);

For an external name that refers to an object of a composite type, we can refer to an element of the object. For example, given an array signal declared within a design, we can index the array with an external name as the prefix:

<<signal .tb.duv_rtl.data_bus:
           std_logic_vector(0 to 15)>>(8) >= '1';

One common use case is to declare an alias for an external name. If we do that, we need only write the full external name in the alias declaration. Thereafter, we can just use the shorter alias name, making the model more succinct. For example, if we need to refer to the data_bus signal in several places in a testbench, we could declare an alias for it:

alias duv_data_bus is
  <<signal .tb.duv_rtl.data_bus : std_logic_vector(0 to 15)>>;

and then just use the alias in the assignment and other places:

duv_data_bus(8) <= '1';
sign <= duv_data_bus(0);

In an alias declaration, we have the option of specifying a subtype after the alias name, giving us a view of the named object as being of that subtype, for example:

alias identifier : subtype is name;

However, when the name we are aliasing is an external name, the subtype is specified in the external name. We do not repeat the subtype (or specify a conflicting sub-type!) after the alias name. So the following two alias declarations are illegal:

alias duv_data_bus : std_logic_vector(0 to 15) is -- illegal!
  <<signal .tb.duv_rtl.data_bus : std_logic_vector(0 to 15)>>;

alias duv_data_bus : std_logic_vector(15 downto 0) is -- illegal!
  <<signal .tb.duv_rtl.data_bus : std_logic_vector(0 to 15)>>;

We can use an external constant name (or an alias of such a name) in an expression, provided the constant has been elaborated and given a value by the time the expression is evaluated. In some cases, expressions are evaluated during elaboration of a design. For example, initial-value expressions and index-bound expressions in declarations are evaluated when the declaration is elaborated, so an external constant name appearing in those places must refer to a constant that has already been elaborated. We can ensure this is the case by writing the part of the design that includes the constant declaration prior to the part of the design that contains the external constant name. VHDL’s elaboration rules specify that the design is elaborated in depth-first top-to-bottom order. To illustrate how we can take account of this order, suppose we have an entity and architecture for a design that declares a constant, as follows:

entity design is
  port ( . . . );
end entity design;

architecture rtl is
  constant width : natural := 32;
  . . .
begin
  . . .
end architecture rtl;

Suppose also that we have a testbench entity and architecture:

entity testbench is
end entity testbench;

architecture directed of testbench is
  signal test_in:
     bit_vector(0 to <<constant .top.duv.width : natural>> - 1) ;
  . . .
begin
  . . .
end architecture directed;

We now assemble the design and testbench in a top-level entity and architecture:

entity top is
end entity top;

architecture level of top is
begin
  assert false
     "Width = " &
     integer'image(<<constant .top.duv.width : natural>>);
  duv : entity work.design(rtl);
  tb  : entity work.testbench(directed);
end architecture level;

In this case, the instance of the design under verification is elaborated before the testbench instance. Thus, the constant declaration is elaborated and given a value before the external constant name within the tb instance is elaborated. Had we written the two instances in the reverse order, the constant would not have been elaborated at the time of elaborating the external constant name, and an error would occur. The external constant name in the assertion statement, on the other hand, is not evaluated until the model is executed, by which time the model is completely elaborated. Thus, the external constant name is allowed to precede the instance of the design under test in which the constant is declared.

VHDL-2008 has a related rule regarding elaboration of a signal referenced by an external signal name. If such a name (or an alias of such a name) is used in a port map, the signal declaration must have been previously elaborated. The reason is that the hierarchy of signal nets and drivers is built during elaboration. If a signal used in a port map is not yet elaborated, the elaborator would have to revisit elaboration of that part of the design hierarchy once the signal declaration was encountered. In general, allowing such use of external signal names would make elaboration of signal nets indefinitely complicated. The rule preventing such use allows elaboration to proceed in a well-defined order, and is not onerous in practice. It usually just requires that the component instance in which the signal is declared be written before the instance referencing the signal in a port map. The typical scenario is that a design under verification be instantiated before the testbench code containing external names.

The pathname in an external name identifies the location of the referenced object within the design hierarchy. A design hierarchy has an instance of an entity and some associated architecture at the top level. The entity and architecture can contain declarations of objects. We can identify such an object by naming the entity followed by the object name. The entity and architecture can also contain nested regions, which can in turn contain declarations of objects. We can identify an object in such a nested region by joining the object name onto the name for the region and the name for the top-level entity. In the case of a block, the name for the region is the block label. In the case of a generate statement, the name for the region is the generate label. A for-generate also requires a value to indicate which iteration of the generate to use. In the case of a local package (see Section 1.3), the name for the region is the package name. Note that we cannot use the name of an uninstantiated package (see Section 1.2) in this way; we can only use the name of an instance of the uninstantiated package. In the case of a component instance, the name of the region is the component instance label, and the region is that corresponding to the bound entity and architecture. We can apply these rules recursively to build up a chain of region names, starting from the entity at the top of the design hierarchy and leading through levels of nesting to identify any object instantiated within the hierarchy.

The pathnames in the preceding external names are all examples of absolute path-names, which specify the full chain of region names, starting from the top of the design hierarchy containing the external name. An absolute pathname starts with a dot symbol and separates each region name within the pathname with further dot symbols. The pathname ends with the simple name of the referenced object. Thus, the absolute pathname

    .tb.duv_rtl.data_bus

refers to the object named data_bus declared within the entity and architecture bound to the component labeled duv_rtl within the top-level entity tb. Similarly, the absolute pathname

   .tb.duv_rtl.memory(3).addr_bus

refers to the object named addr_bus within the for-generate iteration with index 3 within the component instance mentioned.

Example 2.1. Monitoring states in an embedded state machine

Suppose we are verifying a system that includes a finite-state machine control unit embedded as a subcomponent. The control unit is described by the following entity and architecture:

library IEEE; use IEEE. std_logic_ll64.all;
entity control is
   port ( clk, reset : in  std_logic; . . . );
end entity control ;

architecture fsm of control is
   subtype state_type is std_logic_vector(3 down to 0);
   constant idle     : state_type := "0000";
   constant pending1 : state_type := "0001";
   . . .
   signal current_state, next_state : state_type;
begin
   state_reg : process (clk) is . . .
   fsm_logic : process (all) is . . .
end architecture fsm;

Note, in passing, that the fsm_logic process uses the notation all in its sensitivity list, indicating that the process is sensitive to changes in all signals read within the process. This new VHDL-2008 feature is described in Section 6.2. The entity and architecture for the system being designed are:

library IEEE; use IEEE.std_logic_1164.all;
entity system is
   port ( clk, reset : in std_logic; . . . );
end entity system;

architecture rtl of system is
   component control is
      port ( clk, reset : in std_logic; . . . );
   end component control ;
begin
   control_unit : component control
      port map ( clk => clk, reset => reset, . . . );
   . . .
end architecture rtl;

We can define a testbench entity and architecture that traces the sequence of states in the control unit, writing each to a file:

entity state_monitor is
   generic ( state_file_name : string );
end entity state_monitor;

architecture tracing of state_monitor is
   alias fsm_clk is
      <<signal .tb.system_duv.control_unit.clk : std_logic>>;
   alias fsm_state is
      <<signal .tb.system_duv.control_unit.current_state:
                 std_logic_vector(3 downto O)>>;
begin
   monitor : process (fsm_clk) is
      use std.textio.all;
      file state_file : text open write_mode is state_file_name;
   begin
      if falling_edge(fsm_clk) then
         write(L, fsm_state); writeline(state_file, L);
      end if;
   end process monitor;
end architecture tracing;

Note here that the external reference to the clk port of the control_unit instance treats the port as a signal declared in the region corresponding to the instance. This reflects the rule in VHDL that a port is a signal declared in the declarative region of an entity. A generic constant of an instance would similarly be referenced using an external constant name with a pathname for the instance.

The external references in this architecture assume that the complete design hierarchy has an entity named tb at the root, and that the instance of the system to be monitored is labeled system_duv within the top-level architecture. To satisfy those assumptions, we write the top-level entity and architecture as:

library IEEE; use IEEE.std_logic_ll64.all;
entity tb is
end entity tb;

architecture monitoring of tb is
   signal system_clk, system_reset : std_logic;
   . . .
begin
   . . . -- clock and reset generation
   system_duv : entity work.system(rtl)
     port map ( clk => system_clk, reset => system_reset, . . . ) ;

   state_monitor : entity work. state_monitor(tracing)
     generic map ( state_file_name => "fsm_states.dat" );

end architecture monitoring ;

Within the tracing architecture of the state_monitor entity, we write an external name for the current_state signal with a std_logic_vector subtype. Normally, we would declare an enumeration type for the states of a finite-state machine. If we declare such a type locally within the control unit architecture, it would not be visible to the external monitor. We would not be able to write an external name with an appropriate subtype for the referenced signal. That is why we used a std_logic_vector subtype for the state type in this example. If we want to declare an enumeration type for an object that is to be externally monitored, we would have to declare the type in a package that is visible both in the object declaration and in the monitor.

In some testbenches, the testbench code is written in the same region as an instance of the design under verification. In those cases, there is no need to specify the absolute path starting from the top-level entity. Instead, we can use a relative pathname, consisting of the chain of region names starting from the immediately enclosing region, without the leading dot symbol. For example, if a testbench architecture includes an instance of the design under verification labeled duv, then the architecture could also contain the assertion statement:

assert <<signal duv.controller.state :
                   std_logic_vector(0 to 4)>> /= "00000"
  report "Iilegal controller state" ;

Since the starting point for the relative pathname is the enclosing architecture region, the first part of the pathname refers to the component instance, and subsequent parts refer to items nested within the bound entity and architecture.

An important point to note when we are talking about the innermost region for a relative pathname is that only concurrent regions are considered. If we write an external name with a relative pathname within a process or subprogram, that region does not count, since it is not a concurrent region. Moreover, if the name is within a package that is declared within a process or subprogram, the package region does not count either. We need to look outward in the design hierarchy to find an enclosing entity, architecture, block, or generate statement, or a package that is declared in such a region.

Example 2.2. Revised state monitoring for an embedded state machine

Returning to the test bench of Example 2.1, we can write the state-monitoring code directly in the top-level architecture rather than in an instantiated entity and architecture. In that case, we can use relative pathnames, and so do not have to assume the name of the top-level entity. The revised top-level architecture is:

architecture monitoring of tb is
   signal system_clk, system_reset : std_logic;

   alias fsm_clk is
      <<signal system_duv.control_unit.clk : std_logic>>;
   . . .
begin

   . . . -- clock and reset generation

   system_duv : entity work.system(rtl)
     port map ( clk => system_clk, reset => system_reset, ... );

   monitor : process (fsm_clk) is
     use std.textio.all;
     file state_file : text open write_mode is state_file_name;
     alias fsm_state is
         <<signal system_duv.control_unit.current_state:
                             std_logic_vector(3 downto 0)>>;
   begin
      if falling_edge(fsm_clk) then
          write(L, fsm_state); writeline(state_file, L) ;
      end if;
   end process monitor;

end architecture monitoring;

In this architecture, the alias declarations refer to external names identified with relative pathnames. The component label system_duv is declared in the same enclosing architecture region as the alias declarations, so that label is the one used in the pathnames. Even though the external name aliased to fsm_state is written within the process region, the innermost region considered is that of the enclosing architecture.

A further form of relative pathname allows us to identify an outer region as the starting point for the pathname. We write such a pathname using one or more leading “^” symbols in place of names, for example:

<<constant ^.^.comp.c : real>>

As for the relative pathname without the “^” symbols, we initially start with the innermost concurrent region enclosing the external name. Then, for each “^” symbol, we look in the next enclosing region. In the case of instantiated components, the region enclosing an instance of a bound entity and architecture is the region in which the instantiation is written. Thus, if we use this form of pathname in an entity or architecture, we are making a strong assumption about the context in which the entity and architecture are instantiated. Specifically, we are assuming that context also includes the names written in the pathname. The complete design hierarchy must be built in such a way as to ensure the assumption is met, otherwise an error will occur during elaboration.

Example 2.3. Relative pathname in a nested monitor

Suppose we are verifying a multicore platform, in which each core includes an instance of a CPU described by the following entity and architecture.

entity CPU is
   . . .
end entity CPU;

architecture BFM of CPU is
   use work.CPU_types.all;
   signal fetched_instruction : instruction_type;
   . . .
begin
   . . .
end architecture BFM;

The architecture includes a signal representing a fetched instruction. The multi-core platform is described by an entity with a generic constant specifying the number of cores. The architecture of the entity uses a for-generate statement to replicate instances of the CPU.

entity platform is
   generic ( num_cores : positive );
   port    ( . . . );
end entity platform;

architecture BFM_multicore of platform is
   . . .
begin
   cores : for core_num in 1 to num_cores generate
     processor : entity work. CPU(BFM) . . . ;
     . . .
   end generate cores;
   . . .
end architecture BFM_multicore ;

We now consider the testbench that instantiates the platform entity and architecture. Again, we use a generic constant to determine the number of cores in the design under verification. We can include a monitor for each instantiated core by writing a for-generate statement in the testbench, mirroring that in the platform architecture.

entity testbench is
   generic ( num_cores : positive );
end entity testbench;

architecture test_BFM of testbench is
   . . .
begin
   duv : entity work.platform(BFM_multicore)
     generic map ( num_cored  => num_cores )
     port map ( ... );
   monitors : for core_num  in 1 to num_cores generate
     use work.CPU_types.all, work.CPU_trace.all;
     process is
     begin
       ...
       trace_instruction
         ( <<signal
            ^.duv.cores(core_num).processor.fetched_instruction:
               instruction_type>>,
            . . . );
       . . .
     end process;
  end generate monitors;
end architecture test_BFM;

The process within the generate statement includes an external name referring to the fetched_instruction signal in the corresponding core instance. The pathname uses the value of the core_num generate parameter to identify the corresponding iteration of the generate statement labeled cores in the design under verification. Since the external name is in a process nested within a generate statement, the generate statement region is the innermost region used as the starting point for the relative pathname. For that reason, the pathname starts with a “A” symbol to look outside the starting region to the enclosing architecture region. The duv component instance is declared in that region, so it can be used as the next part of the pathname.

We also need to be able to refer to an object declared in a package referenced by a design. For objects declared in the package declaration, we can just use the package name as a prefix in a normal selected name to refer to the object. However, objects declared in the package body are not visible to designs. They would normally be referenced indirectly using procedures declared in the package declaration. A testbench, on the other hand, can use an external name to refer to such a hidden object. An object in a package is not nested within the design hierarchy, but is considered to be nested within the library containing the package. So the chain of region names starts with the library logical name (the name defined by a library clause) and leads through the top-level package name and any nested package names to the referenced object.

A package pathname takes a similar form to an absolute pathname, but starts with an "@" symbol instead. That is followed by logical name of the library containing the package, then the package name, then the names of any intervening nested packages, and finally the object name. For example, given the following package declaration and body analyzed into the working library:

package p1 is
   . . .
end package p1;

package body p1 is
   . . .
   package p2 is
      signal s : bit;
   end package p2;
   . . .
end package body p1;

we could write the following external name to refer to the signal:

<<signal @work.p1.p2.s : bit>>

Force and Release

When verifying a design, we often would like to be able to override the value assigned to a signal in the normal course of design operation and force a different value onto the signal. One reason for doing this is to set up a test scenario by forcing values to a state that would normally be arrived at through a complex initialization sequence. Forcing the values allows us to bypass the sequence and set up the scenario quickly, and so reduce the verification time significantly. Another reason for forcing values is to inject erroneous values into the design to ensure that it detects the error or otherwise responds appropriately.

In earlier versions of VHDL, there was no way to override the value assigned to a signal by the design, other than using commands provided by a simulator. That meant we could not write VHDL testbench code to force signal values. VHDL-2008 now provides features for forcing and releasing the values of signals. We can force a signal with a force assignment of the form

signal_name <= force expression;

This is a sequential assignment written within a process forming part of the test-bench. The effect is to cause a delta cycle and to force the named signal to take on the value of the expression in that delta cycle, regardless of any value assigned to the signal by any normal signal assignment. The signal is considered to be active during the delta cycle, and if the forcing value is different from the previous value, an event occurs on the signal. Processes sensitive to changes on the signal value would then respond to the value change in the normal way.

The usual rules relating the type of the expression to the type of the target signal apply for force assignments. The target signal name can be a normal signal name, or it can be an external signal name or alias (see Section 2.1). Using external names would be a common use case, since we would often need to force an internal signal of a design from a testbench.

Once a signal has been forced, we can update the signal with another force assignment to change the overriding value, again causing the signal to become active and possibly to have another event. We can do this as often as needed. Eventually, if we want to stop forcing a signal, we can execute a release assignment of the form

signal_name <= release ;

This causes a further delta cycle, with the signal being active. However, since the signal is no longer forced, the current values of its sources are used to determine the signal value in the normal way. We can think of this as the design “taking back control” of the signal.

Example 2.4. Simulated corruption of a state machine’s state value

Clocked sequential systems are usually controlled by a finite-state machine. If the storage for the current state is corrupted, the system may be able to recover by transitioning from the illegal state back to an initial state. A testbench can verify that a design under verification recovers correctly by forcing the signal storing the current state of the state machine to an illegal value. It can then release the signal and monitor recovery. The testbench process is:

verify_state_recovery : process is
  use work. control_pkg .all ;
  alias clk is <<signal duv.clk : std_logic>>;
  alias current_state is
          <<signal duv.control.current_state : state_type>>;
begin
  . . .
  -- inject corrupt state
  wait until falling_edge(clk);
  current_state   <= force illegal_state_12;
  wait until falling_edge(clk);
  current_state   <= release;
  -- monitor recovery activity
  . . .
end process verify_state_recovery;

Our discussion of force assignments has so far focused on signals. We can also force and release ports of a design, since they are a form of signal. However, for a port, we distinguish between the driving value and the effective value. The driving value is the value presented externally by an entity, and is determined by the internal sources within the entity. The effective value is the value seen internally by an entity and is determined by whatever is externally connected to the port, whether that be an explicitly declared signal or a port of an enclosing entity. Depending on the port mode and the external connections, the driving and effective values may be different. For example, an inout-mode port of type std_logic might drive a ’0’ value, but the externally connected signal might have another source driving a ‘1’ value. In that case, the resolved value of the signal is ‘XI’, and that value is seen as the effective value of the inout-mode port.

VHDL-2008 allows us to force the driving and effective values of a signal or port independently by including a force mode in an assignment. For explicitly declared signals, where the driving and effective values are the same, the distinction makes no difference. For ports and signal parameters, we can force the driving value by including the keyword out in the force assignment:

signal-name  <= force out expression;

Alternatively, we force the effective value by including the keyword in in the force assignment:

signal-name  <= force in expression;

Once we’ve forced a port’s or signal parameter’s driving value, we can stop forcing it by writing a release assignment with the keyword out:

signal-name  <= release out expression;

Similarly, to release a forced effective value, we write a release assignment with the keyword in:

signal-name  <= release in expression;

We can force and release driving values of ports of mode out, inout, and buffer, but not ports of mode in. Similarly, we can force and release driving values of signal parameters of mode out and inout, but not signal parameters of mode in. One of the VHDL-2008 changes for ports and parameters, described in Section 6.3, is that we can read the value of an out-mode port or parameter. This means that ports and signal parameters of all modes except linkage have effective values, and so we can force and release the effective value of a port or signal parameter of any mode except linkage.

If we omit the force mode (out or in) in a force or release assignment, a default force mode applies. For assignments to ports and signal parameters of mode in and to explicitly declared signals, the default force mode is in, forcing the effective value. For assignments to ports of mode out, inout, or buffer, and to signal parameters of mode out or inout, the default force mode is out, forcing the driving value.

Example 2.5. Forcing disconnection of a port’s driving value

Serial buses such as I2c, USB and FireWire have bidirectional connections to the bus’ physical wires. This allows a device to drive the clock and data wires when transmitting data and to sense the clock and data values when receiving. A testbench can model a broken data driver connection by forcing a ‘Z’ value on the output part of the bidirectional port, while allowing the input part of the port to operate normally. The code in the testbench is

. . .
-- Test scenario: break in the output connection
<<signal duv.SDA : std_logic>> >= force out 'Z';
-- Monitor device operation under this fault condition
. . .
-- Restore connection for the next scenario
<<signal duv.SDA : std_logic>> >= release out;
. . .

VHDL allows us to assign a composite value to a collection of signals by writing the collection in the form of an aggregate on the left-hand side of the assignment, for example:

(carry_out, sum) >= ('0' & a) + ('0' & b);

Note, in passing, that this form of aggregate assignment is legal in VHDL-2008 (see Section 6.4). We cannot, however, write an aggregate of signal names as the target of a force or release assignment to force or release each of the signal values. Instead, we must write a separate force or release assignment for each of the signals. For example, if we want to force and release the driving values of the two ports carry_out and sum, we would have to write:

sum <= force out unsigned'("00000000");
carry_out <= force out '1';
. . .

sum <= release out;
carry_out <= release out;

There is a further form of target signal for which we cannot write a force or release assignment. Suppose we define a resolved signal of a composite type, such as an array type. By that, we mean a signal with multiple sources, each of which is a composite value. The resolution function for the signal takes an array of composite values and determines a composite value as the resolved value of the signal. We cannot write a force or release assignment with an element of such a signal as the target. We can only force or release the signal as a whole. This mirrors the requirements that a process driving such a signal have a driver for all elements of the signal, and that sources for such a signal be sources for the entire signal. Note that resolved composite signals are different from signals of resolved elements, for example, signals of type std_logic_vector. We can force and release individual elements or slices of those signals, since each element is resolved individually.

Another case to consider is a force or release assignment written in a subprogram. VHDL has a rule that a signal assignment written in a procedure that is not contained within a process can only assign to a signal parameter of the procedure. The rationale is that assignment to a signal implies a driver for the signal. For signal parameters, the driver used is the driver for the actual signal provided by the process that calls the procedure. For other signals, a driver for the target signal would be implied for every process that calls the procedure. Identifying all of the callers in a large model would be very difficult. Moreover, if the procedure body is written separately from the calling processes, determining what drivers are created for a given process would be difficult. Thus, the restriction makes VHDL designs easier to analyze and understand. Force and release assignments, on the other hand, do not imply drivers. Rather, they would typically occur in testbench code, often referring to the target signals with external names. For these reasons, VHDL-2008 allows force and release assignments in procedures outside of processes to signals other than signal parameters.

One final aspect to discuss is the effect of multiple concurrent force and release assignments. Since they are sequential assignments written in processes, it is possible that multiple forces and releases could occur for a given signal during a single simulation cycle. The VHDL-2008 rules specify that if a force and release both occur, the effect is as though the release is immediately overridden by the force, and so the signal remains forced, but with the new force value. The effect of multiple forces is not defined. We should write our testbench models to avoid that occurring. The effect of multiple releases, however, is the same as a single release, and a release assignment on a signal that is not forced has no effect.

Context Declarations

Complex designs often call upon design units from several libraries and make use of several packages. As a consequence, each design unit in the design is preceded by a long list of library and use clauses, many of which are common to all of the design units. VHDL-2008 provides a new form of design unit, a context declaration, in which we can gather a collection of library and use clauses. We can refer to a context declaration before a design unit, rather than having to repeat the collection of library and use clauses. The form of a context declaration is

context identifier is
   ... -- library clauses, use clauses and context references
end context identifier;

Within a context declaration, we write library and use clauses in the same form as in a context clause preceding a design unit. We refer to a declared context with a context reference of the form

context context_name;

or, if we wish to refer to several context declarations:

context context_name, context_name, . . . ;

We can write a context reference in the context clause preceding a design unit, or nested within another context declaration. In each case, the context reference is equivalent to replacement by the list of library clauses and use clauses contained within the named context declaration.

Example 2.6. Organization-wide and project context declarations

Suppose the methodology support team in Widgets, Inc., has assembled a library of reusable components, defined in a package widget_comps in a library with logical name widget_lib. The components package refers to a utility package, widget_defs, defining types and operations used across the organization. Both of these packages reference the standard std_logic_1164 and numeric_std packages defined in library IEEE. The methodology team can provide a context declaration for use by projects in the organization:

context widget_context is
   library IEEE;
   use IEEE. std_logic_ll64.all, IEEE. numeric_std.all;
   use widget_lib.widget_defs.all;
   use widget_lib.widget_comps.all;
end context widget_context;

This context declaration is analyzed into the widget_lib library. Given that a design needs to include a library clause for widget_lib in order to refer to the context declaration, there is no need to include that library clause in the context declaration itself. A design unit could reference the context declaration as follows:

library widget_lib;
context widget_lib.widget_context;
entity sample is
  ...
end entity sample;

Now suppose the Dongle project within Widgets, Inc., uses additional components provided by a third party, Gizmos Corp., defined by a package gizmo_pkg in library gizmo_lP_lib. The project also maintains a library dongle_lib for verified design units to be used in the project design flow, and a package dongle_comps with component declarations for the design units. The project’s EDA support person can provide a context declaration for these libraries and packages, as well as referring to the organization’s context declaration:

context dongle_context is
  library widget_lib;
  context widget_lib.widget_context;
  library gizmo_IP_lib;
  use gizmo_IP_lib.gizmo_pkg;
  use dongle_lib,dongle_comps. all;
end context dongle_context;

The EDA support person analyzes this context declaration into the dongle_lib library. A designer can then refer to the context in a design unit as follows:

library dongle_lib;
context dongle_lib.dongle_context;
entity frobber is
  . . .
end entity frobber;

The reference to dongle_context expands to include the reference to the organization’s context and the library and use clauses for the third-party IP and the project repository. The reference to the organization’s context in turn expands to include the library and use clauses for the standard packages and the organization’s packages. Thus, the context clause written is equivalent to the following expanded context clause:

library dongle_lib;
library widget_lib;
library IEEE;
use IEEE.std_logic_1164 .all , IEEE.numeric_std.all;
use widget_lib.widget_defs.all;
use widget_lib.widget_comps.all;
library gizmo_IP_lib;
use gizmo_IP_lib.gizmo_pkg;
use dongle_lib.dongle_comps.all;
entity frobber is
 . . .
end entity frobber;

VHDL uses library logical names to refer to physical design libraries. The mapping from a logical name to a physical library is implementation defined, and may vary between analysis of different design units. In order to avoid confusion when using context declarations, VHDL-2008 requires that a library logical name map to the same physical library during analysis of a context declaration and analysis of a reference to that context declaration. For example, if the logical name gizmo_IP_lib in Example 2.6 refers to /home/dongle/gizmo/gizmo_IP_lib when dongle_context is analyzed, the logical name must refer to the same physical library when entity frobber is analyzed.

As further reinforcement of this principle, we can’t include a context clause before a context declaration, as we can for other design units. Thus, the following would be illegal:

library fizz_lib;  -- Illegal: precedes context declaration
context frazzle_ctx is
   use fizz_lib.fizz_pkg.all;
end context frazzle_ctx;

Instead, we should write the library clause inside the context declaration, so that it is included for any design unit that references the context declaration. Another related rule is that we cannot include a library clause referring to the working library, WORK, within a context declaration. Nor can we refer to the library name WORK in a use clause. The reason is that WORK is not defined for a context declaration, since context declarations don’t have preceding context clauses.

Finally, VHDL-2008 defines two standard context declarations within the standard library IEEE:

context IEEE_BIT_CONTEXT is
   library IEEE;
   use IEEE.NUMERIC_BIT.all;
end context IEEE_BIT_CONTEXT;

context IEEE_STD_CONTEXT is
   library IEEE;
   use IEEE.STD_LOGIC_1164.all;
   use IEEE.NUMERIC-STD.all;
end context IEEE_STD_CONTEXT;

A design based on bit values might refer to the first of these context declarations, either in the context clause of a design unit or nested within a project context declaration. Similarly, a design based on std_logic values might refer to the second of these context declarations.

Integrated PSL

PSL is the IEEE Standard Property Specification Language (IEEE Std 1850). It allows specification of temporal properties of a model that can be verified either statically (using a formal proof tool) or dynamically (using simulation checkers). VHDL-2008 allows PSL code to be embedded as part of a VHDL model. This makes design for verification a much more natural activity, and simplifies development and maintenance of models. Since PSL is itself a significant language, we won’t describe all of its features in detail in this book. Instead, we will just describe the way in which PSL can be embedded in VHDL. For a full description of PSL and its use in verifying designs, the interested reader is referred to other published books on the subject.[1]

In VHDL-2008 we can include PSL property, sequence, and default clock declarations in the declarative part of an entity, architecture, block, generate statement, or package declaration. We can then use the declared properties and sequences in PSL directives written in the statement parts of entities, architectures, blocks and generate statements.

Any properties that we write in PSL declarations and directives must conform to PSL’s simple subset rules. In practice, this means that we can only write properties in which time moves forward from left to right through the property. Two examples from the PSL standard illustrate this. First, the following property is in the simple subset:

always (a -> next[3] b)

This property states that if a is true, then three cycles later, b is true; that is, time moves forward three cycles as we scan the property left to right. In contrast, the following property is not in the simple subset:

always ((a & next[3] b) -> c)

This property states that if a is true and b is true three cycles later, then c must have been true at the time a was true. The problem with this property is that time goes backward from b being true to c being true. A tool to check such a property is much more complex than one to check properties in the simple subset.

PSL directives require specification of a clock that determines when temporal expressions are evaluated. We can include a clock expression in a directive. However, since the same clock usually applies to all directives in a design, it is simpler to include a default clock declaration. If we write a default clock declaration in a region of a design, it applies to any PSL directives written in that region. We can include at most one default clock declaration in any given region.

There is one case where introduction of PSL embedded within VHDL leads to a possible ambiguity. Both PSL and VHDL include assert statements, but their meanings differ. If we write a statement of the form

assert not (a and b) report "a and b are both true";

it could be interpreted as a regular VHDL concurrent assertion statement that is to be checked whenever either of a or b changes value. Alternatively, in VHDL-2008, it could be interpreted as a PSL assert directive that requires the property not (a and b) to hold at time 0. In the interest of backward compatibility, VHDL-2008 interprets such ambiguous statements as regular VHDL concurrent assertion statements. If we really want to write a PSL assert directive of this form, we could modify the property so that it is unambiguously a PSL property, for example:

assert next[O] not (a and b) report "a and b are both true";

Example 2.7. Pipelined handshake assertion

In their book Assertion-Based Design,1 Foster et al describe a verification pattern for a system in which handshaking is pipelined. In their example, a system can receive up to 16 requests before acknowledging any of them. The system counts the number of requests and acknowledgments and includes an assertion that, for every request with a given request count, there is an acknowledgment with the same count within 100 clock cycles. We can describe the system in VHDL as follows:

library IEEE; context IEEE.IEEE_STD_CONTEXT;
entity slave is
   port ( clk, reset : in  std_logic;
                req        : in  std_logic;
                ack        : out std_logic;
                ... );
end entity slave;

architecture pipelined of slave is

   signal req_cnt, ack_cnt : unsigned(3 downto 0) ;

   default clock is rising_edge(clk);

   property all_requests_acked is
     forall C in {O to 15}:
       always {req and req_cnt = C} | =>
                     {[*O to 99]; ack and ack_cnt = C};

begin

   req_ack_counter : process (clk) is
   begin
      if rising_edge(clk) then
        if reset = '1' then
            req_cnt <=  "0000"; ack_cnt <= "0000";
        else
            if req = '1' then req_cnt <= req_cnt + 1; end if;
            if ack = '1' then ack_cnt <= ack_cnt + 1; end if;
        end if;
     end if;
  end process req_ack_counter;
  . . .
  assert all_requests_acked;

end architecture pipelined;

The counters for requests and acknowledgments are implemented using the signals req_cnt and ack_cnt and the process req_ack_counter. We declare a property,[1]

all_requests_acked that expresses the verification condition for the design. We also include a default clock declaration for the architecture. It applies to the assert directive that we write in the statement part of the architecture, asserting that the verification condition holds.

In PSL, verification code can be written in verification units (vunit, vprop and vmode units) that are bound to instances of VHDL entities and architectures. VHDL-2008 considers such verification units as primary design units. Thus, they can be declared in VHDL design files and analyzed into VHDL design libraries.

A verification unit can include binding information that identifies a component instance to which directives apply. Alternatively, in VHDL-2008, we can bind a verification unit as part of the configuration of a design. One place to do that is in a configuration declaration. If we want to bind one or more verification units to the top-level entity in a configuration declaration, we include binding information as follows:

configuration config_name of entity_name is
    . . .   -- use clauses, attribute specifications,
            -- group declarations
    use vunit verification_unit_name, . . . ;
    for architecture_name
      . . .
    end for;
end configuration config_name;

Whenever the configuration declaration is instantiated, either at the top-level of a design hierarchy or as a component instance within a larger design, the named verification units are bound to the instance of the named entity and architecture. That means the names used in the verification units are interpreted in the context of the entity instance.

We can also bind verification units to component instances that are configured by a component configuration nested within a configuration declaration. The augmented form of component configuration, assuming the components are bound to an entity and architecture, and the architecture is further configured, is:

for instance_name, . . . : component_name
   use entity entity_name(architecture_name);
   use vunit verification_unit_name, . . .;
   for architecture_name
      . . .
   end for;
end for;

In this case, the named verification units are bound to the instances specified in the component configuration.

The third place in which we can bind verification units in a VHDL design is in a configuration specification in the architecture or block where components are instantiated. The augmented form, again assuming components are bound to an entity and architecture, is:

for instance_name, . . . : component_name
   use entity entity_name(architecture_name);
   use vunit verification_unit_name, . . .;
end for;

This is similar to the form in a component configuration, but without the nested configuration for the architecture. Indeed, in order to make the syntax of a configuration specification more consistent with that of a component configuration, VHDL-2008 allows the reserved words end for to be used in a configuration specification even if there is no verification unit binding. On the other hand, if verification unit bindings are included, the end for reserved words are required.

Since a verification unit may include binding information as part of its declaration, there is potential for that information to conflict with binding information we write in a configuration. VHDL-2008 prevents such conflict by making it illegal to bind a verification unit in a configuration if the declaration of the unit already includes binding information. Hence, we would normally only write verification bindings in configurations for general-purpose verification units, and not for those written with particular instances in mind. In any case, it would be an error if we wrote a verification unit binding for a component instance that had no bound entity and architecture.

In addition to binding verification units directly in their declaration or indirectly in configurations, VHDL-2008 allows a tool to bind additional verification units through implementation-defined means. That might include command-line options, script commands, or selection using a graphical user interface.

Example 2.8. Binding a verification unit for complementary outputs

Suppose we have a verification unit that ensures two outputs named Q and Q_n are complementary when sampled on rising edges of a signal named clk. The verification unit is:

vunit complementary_outputs {
   assert always Q = not Q_n;
}

We can bind this verification unit to various parts of a design. First, a gate-level model of a D Flip-flop might be described as follows:

entity D_FF is
  port ( clk, reset, D : in bit;
         Q, Q_n            : out bit );
end entity D_FF;

architecture gate_level of D_FF is
  component and2 is ...
  . . .
begin
  G1 : and2  . . .
  . . .
end architecture gate_level;

A configuration declaration for the D flip-flop can bind the verification unit to the top-level entity as follows:

configuration fast_sim of D_FF is
   use vunit complementary_outputs;
   for gate_level
      for all : and2
         . . .
      end for;
      . . .
   end for;
end configuration fast_sim;

We could then instantiate the configuration in a design, and for each instance, the verification unit complementary_out puts would be bound to the instantiated entity and architecture.

Second, suppose we instantiate a parallel-in/serial-out shift register within an RTL design:

entity system is
  . . .
end entity system;

architecture RTL of system is
  component shift_reg is
     . . .
  end component shift_reg;
  . . .
begin
  serializer : shift_reg . . . ;
  . . .
end architecture RTL;

We can write a configuration declaration that binds an entity and architecture to the component instance and that also binds the complementary_outputs verification unit:

configuration verifying of system is
   for RTL
     for serializer : shift_reg
       use entity work.shift_reg(RTL);
       use vunit complementary_outputs;
     end for;
   end for;
end configuration verifying;

Third, we could specify the same binding information directly in the architecture, rather than in a separate configuration. The revised architecture is:

architecture RTL of system is
   component shift_reg is
      . . .
   end component shift_reg;
   for serializer : shift_reg
      use entity work.shift_reg(RTL);
      use vunit complementary_outputs;
   end for;
begin
   serializer : shift_reg . . . ;
   . . .
end architecture RTL;

There are some further points to make about PSL embedded in VHDL. First, since we can declare properties and sequences within VHDL, we can also specify attribute values for them. To that end, we can use the reserved words property and sequence in attribute specifications for declared properties and sequences, respectively. For example:

property SingleCycleRequest is
  always req -> next not req;
sequence ReadCycle is
  { ba; {bb[*]} & {ar[->]; dr[->]}; not bb };

attribute enable_heuristics of
               SingleCycleRequest : propery is true;
attribute enable_heuristics of Readcycle : sequence is true;

Second, PSL has a rich set of reserved words, some of which may conflict with VHDL identifiers. In VHDL-2008, the following PSL keywords are VHDL reserved words, and cannot be used as identifiers:

assert
assume
assume_guarantee
cover
default
fairness
property
restrict
restrict_guarantee
sequence
strong
vmode
vprop
vunit

Other PSL reserved words are only recognized as such within VHDL code when they occur in PSL declarations and directives. They can be used as VHDL identifiers, but such identifiers are hidden within PSL declarations and directives. For example, we can legally write the following declaration:

function rose ( x : boolean ) return boolean is . . .;

But if we then declare a sequence:

sequence cover_fifo_empty is
  {reset_n && rose(cnt = 0)};

The reference to rose in the sequence declaration is to the PSL built-in function, not to the declaration written in VHDL.

Finally, PSL includes features for declaring and instantiating macros, and allows for preprocessor directives. These features can only be used in PSL verification units, not in other VHDL design units.

IP Encryption

As designs become more complex, designers are increasingly using intellectual property (IP) provided by IP vendors. IP providers invest considerable effort in developing their products, and may be loath to release them without protecting their investment. From the IP provider’s point of view, there are two potential places where their IP may be compromised. First, the IP provider must transmit the IP to a customer. During that process, a malicious third party could eavesdrop on the transmission and intercept the IP. Second, the customer must receive, store, and use the IP. During that process, an unscrupulous customer could reuse the IP without compensating the provider. Hence, the customer is technically treated as a malicious third party, though it would not be politic to express the relationship in those terms! The real recipient of the IP is the customer’s tool, which must use the IP only in ways approved by the IP provider and must avoid disclosing the IP to the customer.

One way of protecting IP is for the provider to encrypt it in a form that can be decrypted and used by a customer’s tools, but that cannot be read by the customer. VHDL-2008 provides a flexible set of features to support such protection. Before we describe them in detail, we will first review some of the basic principles and protocols for encryption so that we can understand how to use the language features.

Information to be communicated between two parties can be protected by transforming it with a cipher. A cipher is a function that takes plain text and a string of bits called a key as input and produces cipher text as output. This process is called encryption. The reverse process, decryption, takes the cipher text and a key as input and reproduces the original plain text. The quality of a cipher is determined by the difficulty of determining the plain text from the cipher text without the key. A good cipher will yield significantly different cipher text for minor changes to the key used for encryption.

There are two forms of cipher in widespread use. A symmetric cipher uses the same key for both encryption and decryption. The key is called a secret key, since it must be kept secret between the communicating parties. Should the secret be revealed to a third party, they could decrypt any intercepted encrypted information. Examples of symmetric ciphers are the Data Encryption Standard (DES), and the Advanced Encryption Standard (AES).

An asymmetric cipher uses a pair of related keys, one for encryption and the other for decryption. Key pairs are generated in such a way that it is infeasible to determine either key from the other. Information encrypted with one key of a pair can only be decrypted with the other key of the pair. Examples of asymmetric ciphers are RSA and ElGamal. Asymmetric ciphers are used in protocols where each communicating party generates a key pair. They keep one key of the pair, the private key, secret. They publish the other key, the public key, through some means of dissemination that associates the public key with the communicating party’s identity. For example, they might publish it on their web site. A sender of information can use an asymmetric cipher to protect information destined for a recipient. The sender encrypts the information using the recipient’s public key. Only the recipient can then decrypt the information, since only they have the corresponding private key.

While asymmetric ciphers can yield more secure communication, they involve significantly greater computation than symmetric ciphers. For that reason, most applications involving asymmetric ciphers use a two-stage encryption process called a digital envelope. First a session key is randomly generated, for use in one communication session only. Next, that session key is used with a symmetric cipher to encrypt the information. In order to communicate the session key to the recipient so that they can decrypt the information, the session key is encrypted using an asymmetric cipher with the recipient’s public key, and sent to the recipient. They are the only party able to decrypt the session key, since only they have the right private key. They can then proceed to decrypt the communicated information using the symmetric cipher with the decrypted session key. The advantage of this approach is that only a relatively small amount of information (the session key) need be processed using the computationally intensive asymmetric cipher. The bulk of the information is processed using the lighter-weight symmetric cipher.

One problem that arises in protected communication is the need to verify that received information did in fact originate with the purported sender, and that it was not changed in transit (either by corruption or maliciously) by a third party. This problem is addressed by having the sender transmit a digital signature for the information. The sender uses a hash function to compute a digest of the information. A hash function takes a (potentially large) string of bit as input and produces a small string of bits, the digest, that depends on all of the input bits. A good hash function has the property that two distinct input strings are highly unlikely to yield the same output string. Examples of hash functions include SHA1, MD2, MD5, and RIPEMD. Having computed the digest of the information, the sender encrypts it using an asymmetric cipher with their private key and transmits the result as the digital signature. A recipient decrypts the signature using the purported sender’s public key to retrieve the digest. The recipient also independently calculates the digest of the received information using the hash function. If the two digests are the same, the information has been received correctly, since only the real sender’s public key could decrypt the digest correctly, and only the real information would yield the same digest. If, on the other hand, the digests differ, then either the transmitted digest was encrypted with someone else’s key, or the transmitted message was changed. Either way, the transmission was compromised, and the recipient knows not to trust the received information.

If we are to apply cryptographic techniques to transmission of VHDL models, we need to consider the way in which the encrypted information is encoded. Plain-text VHDL models consist of printable ASCII or Latin-1 characters and are immune to the way ends of lines are encoded. Consequently, we can store and transmit plain-text models through a variety of media without being concerned about encodings. However, the process of encryption produces a string of bits, which cannot be guaranteed to be interpreted as printable characters. We cannot reliably transmit the encrypted model, since some media might transform sequences of bits interpreted as line ends, or might interpret sequences of bits as in-band control codes. To avoid these problems, we can encode the encrypted model using an encoding method that uses printable characters to represent the string of bits. A sender encrypts information and encodes it for transmission, and a recipient decodes the received information and decrypts the result. Examples of encoding methods include uuencode, base64, and quoted-printable, all of which are described by Internet message-transfer standards.

With this overview of cryptography in hand, we can now discuss the features provided in VHDL-2008 to support cryptographic protection of IP. The features use a standard set of tool directives (see Section 9.21). A tool directive is an annotation included in a VHDL design file that provides information to a tool processing the VHDL design. It does not logically form part of the design itself. For IP protection, VHDL-2008 defines protect directives that are used by an IP provider’s encryption tool to govern encryption of sections of a VHDL design and by a customer’s decyption tool to decrypt those sections. The decryption tool is typically a simulator, synthesis tool, or some other tool that deals with VHDL code. It uses the decrypted sections of the design, but does not store them in any form that could be revealed to the customer. Protect directives each takes one of three forms:

'protect keyword
'protect keyword = value
'protect keyword = ( keyword = value, . . . )

Like any tool directive, a protect directive starts with the “tick” symbol, and ends at the end of the line. The keyword or keywords in a protect directive identify the kind of information conveyed by the directive. Note that we write the keywords in boldface here to indicate that they have special meanings in protect directives. They are not reserved words outside of protect directives. The values are literal expressions of various types. If we have a number of consecutive protect directives, we can merge them into a single directive. Thus, we can write the sequence of directives

'protect keyword1 = value1
'protect keyword2 = value2
'protect keyword3

equivalently as

'protect keyword1 = value1, keyword2 = value2, keyword3

An IP provider starts the process by identifying one or more sections of a VHDL design file that they want to protect. They edit the design file to wrap each section in an encryption envelope, consisting of one or more protect directives at the start of the section, and a closing protect directive at the end of the section. The simplest form of encryption envelope is:

'protect begin
. . . -- protected source code in plain-text form
'protect end

This simply delimits the protected source code, and assumes an encryption tool will use default information about the ciphers, keys and encoding for encryption. More elaborate encryption envelopes precede the begin directive with protect directives specifying ciphers, keys, encoding and other optional information.

The IP provider then processes the design file with an encryption tool to produce a version of the design file with each encryption envelope replaced by a corresponding decryption envelope of the following form:

  'protect begin_protected
protect directives and encoded encrypted information
'protect end-protected

We will use a series of examples to show how the various directives are used to form encryption and decryption envelopes for various use cases. In each case we will assume that the decryption tool has access to the required keys, and that the encryption tool knows about those keys. We will return to the topic of key exchange in Section 2.1.

Example 2.9. Simple enc yption envelope with symmetric cipher

In one of the simplest use cases, an IP provider wants to provide protected IP to a customer for use with a single tool. We can use a symmetric cipher, for which the key is known to both the IP provider and to the customer’s decryption tool. The IP provider wraps the protected section in the source code in an encryption envelope, as follows:

entity accelerator is
   port ( ... );
end entity accelerator;

architecture RTL of accelerator is
'protect data_keyowner = "ACME IP User"
'protect data_keyname  = "ACME Sim Key"
'protect data_method   = "aesl92-cbc"
'protect encoding      = (enctype = "base64")
'protect begin
   signal . . .
begin
   process . . .
   . . .
'protect end
end architecture RTL;

The IP provider leaves the information about the entity’s interface and the name of the architecture unprotected so that the customer can instantiate the design. The entire inner workings of the architecture, however, are not to be revealed to the customer. The data-keyowner and data-keyname directives specify identifiers that the encryption and decryption tools can use to retrieve the key. The data-method directive specifies the cipher to use for encryption and decryption, and the encoding directive specifies the method to use to encode the cipher text produced by the encryption tool.

The IP provider processes the original source code file with an encryption tool, which produces a modified file with the encryption envelope replaced by a decryption envelope:

entity accelerator is
  port ( . . . );
end entity accelerator;

architecture RTL of accelerator is
'protect begin_protected
'protect encrypt_agent      = "Encryptomatic"
'protect encrypt_agent_info = "2.3.4a"
'protect data_keyowner = "ACME IP User"
'protect data_keyname  = "ACME Sim Key"
'protect data_method   = "aesl92-cbc"
'protect encoding=(enctype="base64", line_length=40,   bytes=4006)
'protect data_block
emphasis>encoded cipher_text
. . .
'protect end_protected
end architecture RTL;

The encrypt-agent and encrypt-agent-info directives provide information about the encryption tool. This can help in tracking down any problems that might arise. The directives specifying the key, cipher, and encoding method are replicated in the decryption envelope. In the case of the encoding directive, further information about the maximum line length for the encoded cipher text and the number of bytes of cipher text is also provided. The encoded cipher text then starts immediately after the data_block directive. The end_protected directive marks the end of the decryption envelope.

Example 2.10. Digital envelope encrypted for a single customer

One of the problems with using a symmetric cipher to encrypt IP is that the risk of the secret key being divulged. We can avoid that risk by using a digital envelope to transmit the IP. The IP provider includes directives in the encryption envelope to specify a cipher and key to use to encrypt a session key. The IP provider can also specify the symmetric cipher to use to encrypt the data with the session key. The design file with the revised encryption envelope is:

entity accelerator is
   port ( ... );
end entity accelerator;

architecture RTL of accelerator is
'protect key_keyowner = "ACME IP User"
'protect key_keyname  = "ACME Sim Key"
'protect key_method   = "rsa"
'protect key_block
'protect data_method  = "aesl92-cbc"
'protect encoding         = (enctype = "base64")
'protect begin
   signal . . .
begin
   process. . .
   . . .
'protect end
end architecture RTL;

The key-keyowner and key-keyname directives specify identifiers that the encryption tool can use to retrieve the customer’s public key. The key-method directive specifies the cipher to use to encrypt the session key. The key-block directive marks the end of the key information. Its presence in the encryption envelope specifies use of a digital envelope, since the preceding key directives can be omitted, implying default values. The data-method directive specifies the cipher to use for encryption and decryption with the session key. The encoding directive specifies the method to use to encode both the encrypted session key and the encrypted section of the model.

The IP provider processes this source code file with an encryption tool, which generates a session key and produces a modified file with the encryption envelope replaced by a decryption envelope specifying a digital envelope:

entity accelerator is
   port ( . . . );
end entity accelerator;

architecture RTL of accelerator is
'protect begin_protected
'protect encrypt_agent    = "Encryptomatic"
'protect encrypt_agent_info = "2.3.4a"
'protect key_keyowner = "ACME IP User"
'protect key_keyname  = "ACME Sim Key"
'protect key_method   = "rsa"
'protect encoding=(enctype="base64", line_length=40, bytes=256)
'protect key_block

encoded cipher-text for session key

'protect data_method = "aesl92-cbc"
'protect encoding=(enctype="base64", line_length=40, bytes=4006)
'protect data_block

encoded cipher-text for model code

 ...
'protect end_protected
end architecture RTL;

The directives specifying the key and cipher for encrypting the session key are replicated in the decryption envelope. The encoding directive is also replicated to specify the encoding for the encrypted session key, augmented with information about the maximum line length for the encoded cipher text and the number of bytes in the encrypted session key. The encoded cipher text for the session key then starts immediately after the key-block directive. Next, the data-method directive specifying the cipher for the model code is replicated in the decryption envelope. The encoding directive is also replicated here, augmented with information about the maximum line length and the number of bytes. The encoded cipher text for the model code starts immediately after the data-block directive. The end-protected directive marks the end of the decryption envelope.

Example 2.11. Digital envelope encrypted for multiple customers or tools

In Example 2.9 and Example 2.10, the IP is encrypted in a form that can be decrypted by a single customer or by a single tool. If the IP provider wants to distribute the IP to multiple customers or to a customer for use with multiple tools, he or she would have to generate multiple versions using the encryption tool, once per customer. We can avoid this repetition by using a variation on the digital envelope approach. Again, we specify that a session key be used to encrypt the model code. However, that session key is then encrypted multiple times, once per customer or customer’s tool. The revised source file with the encryption envelope is:

entity accelerator is
  port ( ... );
end entity accelerator;

architecture RTL of accelerator is
'protect key_keyowner = "ACME IP User1"
'protect key_keyname  = "ACME Sim Key"
'protect key_method   = " rsa"
'protect key_block
'protect key_keyowner = "ACME IP User2"
'protect key_keyname  = "ACME Synth Key"
'protect key_method   = "elgamal"
'protect key_block
'protect key_keyowner = "ACME IP User3"
'protect key_keyname  = "ACME P&R Key"
'protect key_method   = "aesl92-cbc"
'protectkey_block
'protect data_method  = "aesl92-cbc"
'protect encoding     = (enctype = "base64")
'protect begin
   signal . . .
begin
   process . . .
   . . .
'protect end
end architecture RTL;

Each group of key directives specifies information for encryption of a session key for decryption by a given decryption tool. The first two groups specify encryption using asymmetric ciphers, as is normally done in digital envelopes. However, we can also use a symmetric cipher to encrypt the session key, as specified in the third group of key directives.

As in the earlier examples, the IP provider processes this source code file with an encryption tool, which generates a session key and produces a modified file with the encryption envelope replaced by a decryption envelope specifying a digital envelope:

entity accelerator is
   port ( ... );
end entity accelerator;

architecture RTL of accelerator is
'protect begin_protected
'protect encrypt_agent      = "Encryptomatic"
'protect encrypt_agent_info = "2.3.4a"
'protect key_keyowner = "ACME IP Userl"
'protect key_keyname  = "ACME Sim Key"
'protect key_method   = "rsa"
'protect encoding=(enctype="base64", line_length=40, bytes=256)
'protect key_block

encoded cipher-text for session key

'protect key_keyowner = "ACME IP User2"
'protect key_keyname  = "ACME Synth Key"
'protect key_method   = "elgamal"
'protect encoding=(enctype="base64", line_length=40, bytes=256)
'protect key_block

encoded cipher-text for session key

'protect key_keyowner = "ACME IP User3"
'protect key_keyname  = "ACME P&R Key"
'protect key_method   = "aesl92-cbc"
'protect encoding=(enctype="base64", line_length=40, bytes=256)
'protect key_block

encoded cipher-text for session key

'protect data_method = "aes192-cbc"
'protect encoding=(enctype="base64", line_length=40, bytes=4006)
'protect data_block

encoded cipher-text for model code ...

'protect end_protected
end architecture RTL;

In this case, the decryption envelope includes a group of key directives and a key block corresponding to each group of key directives in the encryption envelope. Each of the targeted decryption tools, when it processes the decryption envelope, checks whether it has access to the key specified by each group of key directives. If it has one of the keys, it can use that key to decrypt the session key, and thus decrypt the model code.

Example 2.12. Digital signature for authentication of the provider

Suppose our IP provider delivers encrypted IP by making it available for download from a file server. They use our public key to deliver the IP in digital envelope form. An unscrupulous third-party IP provider could seek to besmirch the name of our trusted IP provider by spoofing their server and providing a buggy version of the IP. Since the IP is encrypted using our public key, which is widely known, we would not be aware of the switch.

The solution is for our trusted IP provider to include a digital signature in the delivered model. The encryption envelope, revised from that in Example 2.11, is:

entity accelerator is
  port ( ... );
end entity accelerator;

architecture RTL of accelerator is
'protect key_keyowner = "ACME IP User"
'protect key_keyname  = "ACME Sim Key"
'protect key_method   = "rsa"
'protect key_block
'protect data_method  = "aesl92-cbc"
'protect digest_keyowner  = "GoodGuys IP Author"
'protect digest_keyname    = "GoodGuys   Signing Key"
'protect digest_key_method = "rsa"
'protect digest_method     = "shal"
'protect digest_block
'protect encoding = (enctype = "base64")
'protect begin
   signal  . . .
begin
   process  . . .
   ...
'protect end
end architecture RTL;

The digest directives in the encryption envelope specify that a digital signature should be generated for the model code contained in the envelope. The digest_method directive specifies the hash function for computing the digest, and the digest_keyowner, digest_keyname and digest_key_method directives specify the cipher and key to use to encrypt the digest. The digest_key_method directive must specify an asymmetric cipher, since digital signatures are predicated on the use of such ciphers.

The IP provider processes this source code file with an encryption tool, which computes and encrypts the digest to form the digital signature. It uses the private key of the key pair specified by the digest key directives. It includes the digest in the decryption envelope corresponding to the encryption envelope:

entity accelerator is
   port ( . . . );
end entity accelerator;

architecture RTL of accelerator is
'protect begin_protected
'protect encrypt_agent      = "Encryptomatic"
'protect encrypt_agent_info = "2.3.4a"
'protect key_keyowner = "ACME IP User"
'protect key_keyname  = "ACME Sim Key"
'protect key_method   = "rsa"
'protect encoding=(enctype="base64", line_length=40, bytes=256)
'protect key_block

encoded cipher-text for session key

'protect data_method    =   "aesl92-cbc"
'protect encoding=(enctype="base64", line_length=40, bytes=4006)
'protect data_block

encoded cipher_text for model code

...
'protect digest_keyowner   = "GoodGuys IP Author"
'protect digest_keyname    = "GoodGuys Signing Key"
'protect digest_key_method = "rsa"
'protect digest_method     = "sha1"
'protect digest_block
'protect encoding=(enctype="base64", line_length=40, bytes=16)
'protect digest_block

encoded cipher-text for digest

 . . .
'protect end_protected
end architecture RTL;

Our trusted IP provider places this model on the file server for us to download. Now suppose the unscrupulous third-party IP provider performs their network hack and substitutes a buggy model. In their first attempt, they substitute the buggy code, encrypted with a session key that they generate, and encrypt the session key with our public key. Our decryption tool successfully decrypts the session key and uses it to decrypt the model. However, since we want to verify that we have the right model, the decryption tool computes the digest of the decrypted model using the hash function specified in the digest-method directive. The tool also uses the public key of the key pair identified in the digest key directives to decrypt the transmitted digest. Since the model code is different from the original code provided by the trusted IP provider, the two digests are not the same. Our decryption tool alerts us to this fact, and we contact our trusted IP provider to attempt to remedy the problem.

Now suppose the unscrupulous third-party IP provider realizes their ruse was unsuccessful, and tries a different tack. As well as substituting the buggy model, suitably encrypted, they also generate a digital signature for the buggy model and substitute it for the real digital signature. They use their own private key to encrypt the digest, and include digest key directives that identify their key pair. Again, our decryption tool successfully decrypts the model and calculates the digest. The tool also attempts to decrypt the transmitted digest in order to compare with the computed digest. At this point, there are two possible outcomes. First, if the tool does not have access to the unscrupulous provider’s public key, it will be unable to proceed and will warn us that it could not verify the digital signature. Alternatively, if the tool does have access to the unscrupulous provider’s public key, it will use it to decrypt the transmitted digest and compare it with the computed digest. In this case, the digests will match. It will be up to us to check that signature verification was performed with the key we expected. This illustrates that we need to be vigilant when checking digital signatures, so that we are not duped by a simple key substitution. We will discuss this more in Section 2.1, where we address the issue of key exchange.

Example 2.13. Viewport for accessing a declaration in a protected model

An IP provider may wish to allow limited access to some items declared within the protected source code. In Examples 2.1 and 2.2 in Section 2.1, we showed a test-bench monitoring the internal state of the control section of a design under verification. An IP provider can allow such access by including a viewport directive in the encryption envelope. An example is:

entity accelerator is
   port ( . . . );
end entity accelerator;

architecture RTL of accelerator is
'protect data_keyowner = "ACME IP User"
'protect data_keyname  = "ACME Sim Key"
'protect data_method   = "aesl92-cbc"
'protect encoding      = (enctype = "base64")
'protect viewport=(object="accelerator:RTL.state", access="RW");
'protect begin
   signal state : std_logic_vector(3 downto 0);
   . . .
begin
   process . . .
   . . .
'protect end
end architecture RTL;

While most of the inner workings of the architecture are not revealed to the customer, the viewport directive provides the pathname of the object representing the internal state signal and grants read/write access. The IP provider processes the source code file with an encryption tool, which includes the viewport directive in the decryption envelope:

entity accelerator is
   port ( ... );
end entity accelerator;

architecture RTL of accelerator is
'protect begin_protected
'protect encrypt_agent      = "Encryptomatic"
'protect encrypt_agent_info = "2.3.4a"
'protect viewport=(object="accelerator:RTL.state", access="RW");
'protect data_keyowner = "ACME IP User"
'protect data_keyname  = "ACME Sim Key"
'protect data_method   = "aesl92-cbc"
'protect encoding=(enctype="base64", line_1ength=40, bytes=4006)
'protect data_block

encoded cipher-text

...
'protect end_protected
end architecture RTL;

The customer can instantiate the IP in a design and use an external name to refer to the state signal:

architecture monitoring of tb is
  . . .
begin
  . . . -- clock and reset generation
  accelerator_duv : entity work.accelerator(rtl)
    port map  ( ... );
  monitor : process (clk) is
    use std.textio.all;
    file state_file : text open write_mode is state_file_name;
    alias accelerator_state is
          <<signal accelerator_duv.state:
                          std_logic_vector(3 downto 0)>>;
  begin
    if falling_edge(clk) then
       write(L, accelerator_state); writeline(state_file, L);
    end if;
  end process monitor;

end architecture monitoring;

While the viewport directive provides access to the internal signal, it does not provide complete information. The IP provider would also need to provide documentation describing the type of the signal and other relevant information.

Now that we have seen how protection envelopes are formed in various scenarios, we will describe the details of VHDL-2008’s IP protection mechanism. As we have mentioned, it is based on a set of tool directives. The full list of directives is as follows:

‘protect begin

  • Indicates the beginning of the source code to be encrypted in an encryption envelope.

‘protect end

  • Indicates the end of an encryption envelope.

‘protect begin_protected

  • Indicates the beginning of a decryption envelope.

‘protect end_protected

  • Indicates the end of a decryption envelope.

‘protect author = “author name”

  • Identifies the author of the protected IP. If this directive appears in an encryption envelope, the encryption tool copies it unchanged to the corresponding decryption envelope.

‘protect author_info = “author info”

  • Provides further information about the author of the protected IP, such as an organization name or contact details. If this directive appears in an encryption envelope, the encryption tool copies it unchanged to the corresponding decryption envelope.

‘’protect encrypt-agent = “encrypt agent name”

  • This directive must appear in a decryption envelope, and identifies the encryption tool that produced the decryption envelope.

‘protect encrypt-agent-info = “encrypt agent info”

  • This directive may appear in a decryption envelope, and provides further information about the encryption tool that produced the decryption envelope.

‘protect key-keyowner = “key owner name”

  • Identifies the owner of a key or key pair used to encrypt a session key.

protect key-keyname = “key name”

  • Used together with the key owner name to identify a particular key or key pair used to encrypt a session key.

protect key-method = “cipher name”

  • Specifies the cipher used to encrypt a session key.

‘protect key-block

  • In an encryption envelope, specifies use of a digital envelope. In the corresponding decryption envelope, indicates the beginning of the encoded cipher text of the session key.

‘protect data-keyowner = “key owner name”

  • Identifies the owner of a key or key pair used to encrypt the source code.

‘protect data-keyname = “key name”

  • Used together with the key owner name to identify a particular key or key pair used to encrypt the source code.

‘protect data-method = “cipher name”

  • Specifies the cipher used to encrypt the source code.

‘protect data-block

  • In a decryption envelope, indicates the beginning of the encoded cipher text of the source code.

‘protect digest_keyowner = “key owner name”

  • Identifies the owner of the key pair used to encrypt the digest in a digital signature.

‘protect digest-keyname = “key name”

  • Used together with the key owner name to identify a particular key pair used to encrypt the digest in a digital signature.

‘protect digest-key-method = “cipher name”

  • Specifies the asymmetric cipher used to encrypt the digest in a digital signature.

protect digest-method = “hash function name”

  • Specifies the hash function used to compute the digest in a digital signature.

‘protect digest-block

  • In an encryption envelope, specifies use of a digital signature. In the corresponding decryption envelope, indicates the beginning of the encoded cipher text of the digest.

‘protect encoding = ( enctype = “encoding name”, line-length = integer, bytes = integer)

  • In an encryption envelope, this directive specifies the encoding to be used for cipher text in the corresponding decryption envelope. The line_length keyword and value are optional and specify the maximum line length for encoded text. Text longer than this amount is split into multiple lines. The bytes keyword and value are also optional and are ignored in an encryption envelope in any case.

  • In a decryption envelope, this directive appears preceding each key, data, and digest block. It specifies the encoding, maximum line length, and number of bytes of cipher text encoded in the block.

‘protect viewport = ( object = “object pathname”, access = “access type” )

  • Identifies an object declared within the protected source code for which access is granted. If this directive appears in an encryption envelope, the encryption tool copies it unchanged to the corresponding decryption envelope.

  • The pathname consists of the names of regions enclosing the declaration, starting with the design unit name and continuing with the names of nested regions, separated by "." characters, for example,

         "my_entity.cycle_monitor.cycle_count"

If the object is declared within an architecture, the design unit name is the combination of the entity name and the architecture name, separated by a colon, for example,

         "my_entity:RTL.current_state"

If the object is declared within a package body, the design unit name consists of the package name, followed by ":body", for example,

         "IP_pkg:body.trace_file"

The access type string must be one of "R", "W", or "RW" (or the lowercase equivalents), indicating read access, write access, or read/write access, respectively.

‘protect decrypt-license =( library = “library name”,entry = “acquisition routine name”, feature = “feature name”,exit = “release routine name”, match = integer)

  • This directive specifies information for acquiring a decryption license. If the directive appears in an encryption envelope, the encryption tool copies it unchanged to the corresponding decryption envelope. If the directive appears in a decryption envelope, a decryption tool must attempt to acquire the specified license. If acquisition is successful, it continues decrypting the model. Otherwise, it is expected to stop further decryption.

  • The library name string identifies the object library in the decryption tool’s host file system containing routines for license management. The tool should call the routine identified by the acquisition routine name, passing the feature name string as an argument, to acquire a license. The tool should compare the return value of the routine with the match integer. If they are equal, acquisition succeeded. When the tool has completed decryption, it should relinquish the license by calling the routine identified by the release routine name.

‘protect runtime-license =( library = “library name”,entry = “acquisition routine name”, feature = “feature name”,exit = “release routine name”, match = integer)

  • This directive specifies information for acquiring a runtime license. If the directive appears in an encryption envelope, the encryption tool copies it unchanged to the corresponding decryption envelope. If the directive appears in a decryption envelope, a decryption tool must attempt to acquire the specified license. If acquisition is successful, the tool may continue with analysis and execution of the model. Otherwise, it is expected not to execute the model. The information in this directive is the same as that in a decrypt_license directive.

‘protect comment = “comment string”

  • This directive allows the IP author to provide comments in the model. If the directive appears in an encryption envelope, either preceding or within the source code, the encryption tool copies it unchanged to the corresponding decryption envelope. If it is within the source code, the encryption tool skips over it when encrypting the source code.

Several directives use strings to specify ciphers, encodings, and hash functions. VHDL-2008 defines particular string values for these directives. If a tool supports the given cipher, encoding, or hash function, it must use the defined string value to specify it. A tool may also support other methods, in which case it uses an implementation-defined string value. Table 2.1 shows the string values for specifying ciphers. Every tool must support at least the DES cipher. Table 2.2 shows the string values for specifying encodings. Every tool must support at least uuencode and base64. Table 2.3 shows the string values for specifying hash functions. Every tool must support at least SHAl and MD5.

Table 2.1. Strings for specifying ciphers

String

Cipher

Cipher type

“des-cbc”

DES in CBC mode.

Symmetric

“3des-cbc”

Triple DES in CBC mode.

Symmetric

“aesl28-cbc”

AES in CBC mode with 128-bit key.

Symmetric

“aesl92-cbc”

AES in CBC mode with 192-bit key.

Symmetric

“aes256-cbc”

AES in CBC mode with 256-bit key.

Symmetric

“blowfish-cbc”

Blowfish in CBC mode.

Symmetric

“twofishl28-cbc”

Twofish in CBC mode with 128-bit key.

Symmetric

“twofishl92-cbc”

Twofish in CBC mode with 192-bit key.

Symmetric

“twofish256-cbc”

Twofish in CBC mode with 256-bit key.

Symmetric

“serpentl28-cbc”

Serpent in CBC mode with 128-bit key.

Symmetric

“serpentl92-cbc”

Serpent in CBC mode with 192-bit key.

Symmetric

“serpent256-cbc”

Serpent in CBC mode with 256-bit key.

Symmetrict

“cast128-cbc”

CAST-128 in CBC mode.

Symmetric

“rsa”

RSA.

Asymmetric

“elgamal”

ElGamal.

Asymmetric

“pgp-rsa”

OpenPGP RSA key.

Asymmetric

Table 2.2. Strings for specifying encodings

String

Encoding methods

“uuencode”

IEEE Std 1003.1™-2001(uuencode Historical Algorithm)

“base64”

IETF RFC 2045, also IEEE Std 1003.1 (uuencode -m)

“quoted-printable”

IETF RFC 2045

“raw”

Identity transformation; no encoding is performed, and the data may contain non-printing characters.

Table 2.3. Strings for specifying hash functions

Digest method string

Required/optional

Hash function

“shal”

Required

Secure Hash Algorithm 1 (SHA-1).

“md5”

Required

Message Digest Algorithm 5.

“md2”

Optional

Message Digest Algorithm 2.

“ripemd-160”

Optional

RIPEMD-160.

We can now describe the rules for forming an encryption envelope in a model. The rules allow for considerable flexibility, but we must at least include the begin and end directives to mark out the source code to be encrypted.

We can precede the begin directive with a key-block directive if we want to specify use of digital envelopes. We can specify the cipher and key to use to encrypt the session key by including a key-method and a key-keyowner directive (and optionally a key-keyname directive). If we don’t specify the cipher and key, the encryption tool chooses a default cipher and key. The key-method, key-keyowner and key-keyname directives can appear in any order, but must immediately precede the key-block directive. We can include more than one group of key-related directives, as we described in Example 2.11.

We can also precede the begin directive with a data-method directive if we want to specify the cipher to use to encrypt the source code. If we are not using digital envelopes and we include a data-method directive, we must also include a data-keyowner directive and optionally a data-keyname directive to identify the key. If we are using digital envelopes, the encryption tool generates the session key, so we do not include directives to identify the key. If we omit the data-method directive, the encryption tool chooses a default cipher. All of the directives relating to encryption of the source code must appear together in an encryption envelope.

If we want to include a digital signature, we precede the begin directive with a digest-block directive. We can specify the cipher and key to use to encrypt the digest by including a digest-key-method and a digest-keyowner directive (and optionally a digest-keyname directive). If we don’t specify the cipher and key, the encryption tool chooses a default cipher and key. Similarly, we can specify the hash function to use by including a digest-method directive. If we don’t specify a hash function, the encryption tool chooses a default hash function. The digest-key-method, digest-keyowner, digest-keyname, and digest-method directives can appear in any order, but must immediately precede the digest-block directive.

Beyond these specifications, we can include directives to identify the IP author, describe licenses and viewports, and specify the encoding to use. If we don’t specify the encoding, the encryption tool chooses a default encoding. We can also include comment directives anywhere within the encryption envelope, including in the source code between the begin and end directives.

The rules that an encryption tool must follow to form a decryption envelope are somewhat more prescriptive. Groups of directives must appear in a specified order, even if the corresponding directives in the encryption envelope appeared in a different order or distributed among other directives, though not all groups are required in every decryption envelope. The layout of a decryption envelope is:

'protect  begin_protected
author directives
license directives
encrypt agent directives
viewport directives
key block directives
data block directives
digest block directives
'protect end_protected

The author, license, and viewport directives are those that appear in the encryption envelope, if any. The encrypt-agent directive and optionally and encypt-agent-info directive are included by the encryption tool. If a digital envelope is used, there is a group of key block directives for each encryption of the session key. The directives occur in the following order, with only the key-keyname directive being optional:

key_keyowner directive
key_keyname directive
key_keymethod directive
encoding directive
key_block directive
encoded cipher text for session key

The data block directives occur in the following order, with the data-keyowner and (optional) data-keyname directives only appearing if a digital envelope is not being used:

data_keyowner directive
data_keyname directive
data_method directive
encoding directive
data_block directive
encoded cipher text for source code

If a digital signature is used, the digest block directives occur in the following order, with only the digest_keyname directive being optional:

digest_keyowner directive
digest_keyname directive
digest_key_method directive
digest_method directive
encoding directive
digest_block directive
encoded cipher text for digest

Key Exchange

In our description of IP exchange so far we have assumed that the IP provider’s encryption tool and the customer’s decryption tool each have the required keys. What we have glossed over is how the tools get the keys. This is a very important topic, since protection of IP from disclosure relies on the security of the encryption and decryption keys. Should a key become known to an unauthorized party, the encrypted IP can be decrypted and disseminated. Normally, when encryption is used to secure communication between two parties, the parties are assumed to have an interest in the security of the encrypted messages and can be trusted to keep the keys secret. However, as we mentioned earlier, when an IP provider delivers a model to a customer, it is the customer’s tool that is really the communicating party. The IP provider may not trust the customer not to look at the code or use it in some unauthorized way. A further complication is that the customer may have to provide his or her tool’s key to an IP provider, creating an opportunity for the customer to copy the key and subsequently decrypt the code. Given these considerations, we can see that exchange of keys can be quite complicated. VHDL-2008 does not specify how keys should be exchanged; that is left to negotiation between IP providers, tool vendors, and customers. The following discussion, drawn from the VHDL standard, explores some of the issues.

Many applications that require secure exchange of keys rely on public key infrastructure (PKI). Parties to communication generate, or are given, key pairs for use with asymmetric ciphers. Each party keeps their private key secret, and publishes their public key, for example, in a directory. In order to establish that a public key does, in fact, belong to a given party, the public key is digitally signed by a trusted authority. The signed public key is represented in the form of a digital certificate, containing the key and the signature. The trusted authority is called a certification authority (CA). Many PKI systems have a hierarchy of CAs, allowing a certificate signed by a subordinate CA to be signed by a superior CA, allowing trust to be distributed hierarchically. One or more root CAs are required to be globally trusted.

Key exchange for IP protection may be built upon public key infrastructure. For example, a vendor of a decryption tool may embed a private key of a key pair in the tool and register the public key with a CA. The tool can then generate a key pair for the tool’s user, keeping the private key secret and signing the public key with both the vendor’s private key and the user’s private key. This allows verification that the public key originates with the instance of the vendor’s tool owned by the tool user. That public key may then be used by IP authors to provide IP for that use of that tool only. Similar mechanisms might also be employed within tools to allow exchange of private keys among tools without disclosure to the tools’ user.

In addition to providing for secure key exchange, a decryption tool must take measures to ensure that stored keys are not disclosed to the tool user. If a tool user could read a tool’s stored keys, the user could decrypt IP independently of the tool. One way of ensuring security of a tool’s keys is for the tool to encrypt its key store using a secret key embedded in the tool in a disguised manner, and to provide for update and re-encryption of the secret key in case it is compromised.

VHDL Procedural Interface (VHPI)

VHPI is an application-programming interface (API) to VHDL tools. Using VHPI, a program can access information about a VHDL model during analysis, elaboration, and execution of the model. VHPI allows development of add-in tools, such as linters, profilers, code coverage analyzers, timing and power analyzers, and external models, among others. Use of the VHPI to develop such tools is quite complex, and is beyond the scope of this book. Instead, we will describe the way in which we can invoke VHPI programs as part of a VHDL simulation.

VHPI programs are divided into two classes: foreign models and foreign applications. A foreign model corresponds to an architecture or a subprogram decorated with the "FOREIGN attribute. The VHPI program implements the behavior of the architecture or subprogram, respectively. A foreign application does not have a counterpart in the VHDL code. It is executed as part of simulation and performs application-specific processing. Both forms of VHPI program can use API calls to obtain information about the VHDL model, to react to changes in the simulation state, and to cause changes in the simulation state.

Direct Binding

If we are to instantiate a foreign model as part of a VHPI design, we need to identify where the VHPI program code is to be found. Typically, the provider of the foreign model would provide documentation listing the names of libraries and functions to which we should refer. The most straightforward method of referring to the VHPI program code is to provide the information in the value of the "FOREIGN attribute in a form known as direct binding. For a foreign architecture, we write the attribute value in the following form:

"VHPIDIRECT object_lib_path elab_function exec_function"

The keyword VHPIDIRECT specifies standard direct binding, and must be written in uppercase. The object-lib-path is a host-dependent path and file name identifying the binary object library in the host file system. It can contain any characters; however, if a space character is required, we must precede it with a backslash character, and if a backslash character is required, we must double the backslash. The elab-function is the name of a function within the object library that performs elaboration for the foreign architecture. It is called to elaborate each instance of the foreign architecture during elaboration of the enclosing design. The exec_function is similarly the name of a function in the object library that performs simulation for the foreign architecture. It is called once for each instance of the foreign architecture during the initialization phase of simulation.

In the attribute value, we can substitute the keyword null for the object library path. In that case, the host system locates the object library in an implementation-dependent way. It might, for example, use an environment variable containing a list of pathnames. We can also substitute the keyword null for the elaboration function name if the foreign model does not require any action during elaboration. In both cases, the keyword null must be written in lowercase.

Example 2.14. Foreign processor core model

Suppose a foreign model for a CPU32 processor core is provided in an object library called cpu32.a that we have installed in the directory /usr/local/cpu32. The elaboration and execution functions for a bus-functional version are named cpu32_bf_elab_f and cpu32_bf_exec_f, respectively. An entity and architecture that use standard direct binding for the bus-functional version are:

entity cpu32 is
  generic ( . . . );
  port ( ... );
end entity cpu_32;

architecture bus_functional of cpu32 is
  attribute FOREIGN of bus_functional : architecture is
     "VHPIDIRECT /usr/local/cpu32/cpu32.a " &
     "cpu32_bf_elab_f cpu32_bf_exec_f";
begin
end architecture bus_functional;

The attribute value for standard direct binding for a foreign subprogram takes a similar form:

"VHPIDIRECT object_library_path exec_function"

In this case, the execution function name identifies a function that performs the action of the foreign subprogram. It is called whenever the foreign subprogram is called during simulation. For foreign subprograms, we can substitute the keyword null for the execution function name. In that case, the execution function name is taken to be the same as that of the foreign subprogram declared in the VHDL model, using the case of letters in the VHDL declaration.

Example 2.15. Foreign display subprograms

Suppose we are given subprograms that show 7-segment display digits graphically on the screen during simulation. The subprograms are in the library displaylib.a, and include a function named create_digit and a procedure named update_digit. We can declare corresponding foreign subprograms in a package as follows:

package display_pkg is
   impure function create_digit (title : in string)
                                       return natural;
   attribute FOREIGN of create_digit : function is
     "VHPIDIRECT displaylib. a null" ;
   procedure update_digit (id : in natural;
                               val : in bit_vector(0 to 7));
   attribute FOREIGN of update_digit : procedure is
     "VHPIDIRECT displaylib. a null" ;
end package display_pkg ;

Tabular Registration and Indirect Binding

An alternative way of identifying the VHPI program code for a foreign model is to use a tabular registy, which is a text file containing the identifying information. A tool can be supplied with any number of tabular registry files, each describing one or more foreign models or applications. The way in which we specify use of a tabular registry file is tool-dependent. It might, for example, involve use of a command-line option or an entry in an options-setting file. Each line of a tabular registry is an entry describing one foreign model, foreign application, or library of VHPI programs. The file can also contain comment lines, starting with characters "--", and blank lines.

A foreign architecture is described by a line of the following form in a tabular registry:

object_lib_name model_name vhpiArchF elab_function exec_function

The object_lib_name is a logical name for the binary object library containing the VHPI program code. The host system maps the logical name to a physical object library in some host-dependent way. The model_name is an identifier for the foreign architecture in the object library. Both the library logical name and the model name can be written as a normal identifier or, if non-standard characters are required, as an extended identifier delimited by backslash characters. The keyword vhpiArchF indicates that the line in the tabular registry describes a foreign architecture. It must be written using the combination of uppercase and lowercase letters shown here. The elab_function and exec_function are the names of the elaboration function and execution function, respectively, in the object library. They serve the same purpose as described in Section 2.1, and, in a similar way, the elaboration function name can be replaced by the keyword null.

Having described a foreign architecture in a tabular registry file, we can specify a "FOREIGN attribute in the form of an indirect binding to use the foreign architecture for a VHDL architecture. This form of attribute value is:

"VHPI object_lib_name model_name"

The object-lib-name and model-name identifiers must correspond to the library logical name and model name identifiers specified in an entry in a tabular registry. The foreign architecture described in that entry is used for each instance of the VHDL architecture decorated with the attribute.

Example 2.16. Foreign processor core model using indirect binding

Suppose the provider of the CPU32 processor core model described in Example 2.14 also provides a tabular registry file for binding the bus-functional model. The file contains the following entry:

cpu32lib cpu32-bf vhpiArchF cpu32_bf_elab_f cpu_bf_exec_f

We decorate the architecture with the "FOREIGN attribute using indirect binding for the bus-functional model:

architecture bus_functional of cpu32 is
  attribute FOREIGN of bus_functional : architecture is
    "VHPI cpu32lib cpu32-bf";
begin
end architecture bus_functional;

Tabular registration and indirect binding for a foreign subprogram are similar. An entry in a tabular registry file for a foreign procedure takes the form:

object_lib_name model_name vhpiProcF null exec_function

and for a foreign function:

object_lib_name model_name vhpiFuncF null exec_function

In both cases, the object-lib-name and model-name serve the same purpose as for a foreign architecture, and the exec-function is the name of the function in the object library that implements the subprogram’s actions. The function name can be replaced by the keyword null, in which case the execution function is taken to be the same as the model name. The "FOREIGN attribute value for indirect binding to a foreign subprogram is the same as that for indirect binding to a foreign architecture, namely,

"VHPI object_lib_name model_name"

The library name and model name are used in the same way to locate the tabular registry entry for the foreign subprogram.

Example 2.17. Foreign display subprograms using indirect binding

The provider of the display subprograms described in Example 2.15 might provide a tabular registry file for the subprograms including the following entries:

displaylib create_digit vhpiFuncF null null
displaylib update_digit vhpiProcF null null

The second null in each entry indicates that the execution function names for the subprograms are the same as the foreign model names, namely, create_digit and update_digit. We declare the foreign subprograms and use indirect binding in the "FOREIGN attribute values as follows:

package display_pkg is
  impure function create_digit (title : in string)
                                   return natural;
  attribute FOREIGN of create_digit : function is
     "VHPI displaylib create_digit";
  procedure update_digit (id : in natural ;
                            val   : in  bit_vector(0 to 7)) ;
  attribute FOREIGN of update_digit : procedure is
     "VHPI displaylib update_digit";
end package display_pkg;

Registration of Applications and Libraries

We can use the tabular registration feature described in Section 2.2 to describe a VHPI application to be run as part of a simulation. A line in the file for a foreign application takes the form:

object_lib_name application_name vhpiAppF reg_function null

The object_lib_name is a logical name identifying the binary object library containing the program code, and the application_name is an identifier for the foreign application in the object library. The rules for these names are the same as those for names identifying foreign models. Thus, they can be written as normal identifiers or extended identifiers. The keyword vhpiAppF indicates that the line in the tabular registry describes a foreign application and must be written using the combination of uppercase and lowercase letters shown here. The reg_function is the names of a function in the object library that is called at the start of simulation, before elaboration or initialization, to initialize the state of the foreign application. This is all the information we need to supply to the tool to include a foreign application in a simulation. The registration function performs any further application-specific operations required.

Example 2.18. Registration of a power-estimation application

A third-party tool supplier might provide a tool for estimating dynamic power consumption based on activity during simulation of a model. The tool’s program code is installed in a binary object library in the host file system, with a logical name powerestlib mapping to the library file. The application is named powerest, and the registration function in the library is called powerest_reg_f. The supplier provides a tabular registry file with the following contents:

-- VHPI tabular registry for the PowerEst foreign application.
-- Map library name powerestlib to the pathname for the
-- powerest1ib.a file in your installation.

powerestlib powerestlib vhpiAppF powerest_reg_f null

We invoke the simulator with a command-line option identifying this tabular registry file to include the power estimator tool in a simulation.

The final form of entry in a tabular registry file describes a library of VHPI programs, including foreign models or applications. The form of the entry is:

object_lib_name null vhpiLibF reg-function null

As before, the object_lib_name is a logical name identifying the binary object library containing the program code. The reg_function is the names of a function in the object library that is called at the start of simulation. It uses the VHPI API to register each foreign model or application. This form of registration is convenient when a large suite of VHPI programs is provided.



[1] See, for example, Cindy Eisner and Dana Fisman, A Practical Introduction to PSL, Springer, 2006

[1] Harry D. Foster, Adam C. Krolnik, and David J. Lacey, Assertion-BasedDesign, Kluwer Academic Publishers, 2003.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.23.101.60