Converting VHDL logic vectors to user-defined strings for simulation

1.2k views Asked by At

I'm using active-hdl to simulate my FPGA designs and I'd like to know if it's possible to use dynamically generated strings to represent my signals in the simulator. For example, let's say I have a 4-bit std_logic_vector containing an op-code, I'd like the simulator to display the op-code strings "nop", "add", "sub" etc instead of the vector value.

I tried declaring a custom enumeration type at first but quickly discovered that you don't get to choose the values of the individual elements. My next solution was to use the enumeration for simulation display only and convert with a translation function:

type op_code_type is (nop, add, sub, unknown); -- not in order
signal op_code_str: op_code_type;
signal op_code: std_logic_vector(3 downto 0);

function to_string(op_code : std_logic_vector(3 downto 0))
return op_code_type is
begin
    case op_code is
        when "0000" => return nop;
        when "0010" => return add;
        when "0011" => return sub;
        when others => return unknown;
    end case;
end to_string;

begin

    ----- testbench -----
    process 
    begin
        op_code <= "0000";
        wait for 1ns;
        op_code <= "0001";
        wait for 1ns;
        op_code <= "0010";
        wait for 1ns;
        op_code <= "0011";
        wait for 1ns;
    end process;

    op_code_str <= to_string(op_code);

end architecture;

This actually works quite well, and is probably adequate for most things I want to do:

enter image description here

The main problem though is I'm stuck with string constants, so it'll be too impractical for more complex stuff like mov acc,x and all the many other variants a real-world design would have.

Are there ways to construct dynamic simulation identifiers like this? Or is it a fundamental limitation of HDLs?

2

There are 2 answers

3
PlayDough On

In Modelsim, you can use virtual types and functions. For example, consider the following vector:

signal opcode : std_logic_vector(2 downto 0);

You can then at the Modelsim command line define a virtual type, such as:

virtual type {{0 nop} {1 load} {2 store} {3 invalid}} opcode_type

This is a type known only to the simulator. You can then create a virtual signal based on this type to convert the vector, such as:

virtual function {(opcode_type)opcode} opcode_str

Then wave opcode_str, and it will give you a custom formatted string..

I don't know if you can do the same with Active-HDL.

Now, as for doing it dynamically, the only possibility might be if the returned string is defined by a TCL function, such as:

# TCL code to read a file, or otherwise dynamically generate opcodes
# returning the appropriately formatted virtual type
proc generate_opcode_type {} {
  ...
}

virtual type [generate_opcode_type] opcode_type
virtual function {(opcode_type)opcode} opcode_str

Then wave opcode_str.

0
PlayDough On

For posterity, and at the request of @B. Go, here is my previous answer:

@Paebbels has it. We use this frequently, especially when doing post place-and-route simulations to convert state codes to their equivalent enumerated type. So for completeness, I'll show you how we do it. The example below considers a case where binary encoding is used. If trying to convert from grey or one-hot, things are a bit different. For one-hot, I tend to use a function.

Consider an 3-bit vector with associated names:

|-----------|----------|
| 000       |  Idle    |
| 001       |  Start   |
| 010       |  Running |
| 011       |  Paused  |
| 100       |  Done    |
| 101 - 111 | Invalid  |
|-----------|----------|

So, if you have a signal, such as:

signal opcode : std_logic_vector(2 downto 0);

Then you want to convert to an enumerated type, which will show up cleanly in your waveform viewer. First, create the enumerated type and associated signal:

type opcode_names is (idle, start, running, paused, done, invalid);
signal opcode_name : opcode_names;

Then it is a simple with/select:

with to_integer(unsigned(opcode)) select
  opcode_name <= idle when 0,
                 start when 1,
                 running when 2,
                 paused when 3,
                 done when 4,
                 invalid when others;

Though if you have a complete set, it is a bit simpler. Consider a 2-bit vector with names "idle, start, running, done".

    type opcode_names is (idle, start, running, done);
    signal opcode_name : opcode_names;

    ...

    opcode_name <= opcode_names'image(to_integer(unsigned(opcode));

For more complex vectors with unusual, non-contiguous values, I typically use a function, such as:

signal opcode : std_logic_vector(31 downto 0);

type opcode_names is (idle, start, running1, running2, paused, done, invalid);
signal opcode_name : opcode_names;

function get_opcode_name(opcode : in std_logic_vector) return opcode_names is
  variable ret : opcode_names;
begin
  case to_integer(unsigned(opcode)) is
    when 0 =>
      ret := idle;
    when 13 =>
      ret := start;
    when 87 =>
      ret := running1;
    when 131 =>
      ret := running2;
    when 761 =>
      ret := paused;
    when 3213 =>
      ret := done;
    when others =>
      ret := invalid;
  end case;

  return ret;
end function get_opcode_name;

...

opcode_name <= get_opcode_name(opcode);