4

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?

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • It is possible. The problem in your current way of thinking is that what you are referring to as "string" is not a string, but an enumerated type. This type is limited to the defined values. You could try to construct a (concatenated) string using several functions. However, you should remember that vhdl doesn't support array objects with dynamic length. – JHBonarius Mar 03 '18 at 10:19
  • 2
    Active-HDL will display objects (e.g. signals) of enumerated types by there enumeration literals (e.g. `nop`). You can convert enumeration literals to integers and vice versa by using the attributes `'val` and `'pos`. You can also convert these literals to strings and vice versa by using `'image` and `'value`. In general, you should model your opcodes as enumeration instead of std_logic_vector. – Paebbels Mar 03 '18 at 14:21
  • GTKWave can use translate filters, tables in files or external processes (useful for disassemblers). Cure instrumentation problems in a programming language. The way to get variable length strings in VHDL is with access types and allocators (e.g. textio) and wouldn't convey string values through waveform dump files without fixed length string objects (statically declared). –  Mar 03 '18 at 19:30
  • An enumeration type 'LEFT has a scalar position value of 0. Providing all the enumerations for a binary range of scalar values is deterministic. `type op_code_type is (nop, unk1, add, sub, unk4, unk5, unk6, unk7, unk8, unk9, unk10, unk11, unk12, unk13, unk14, unk15);` The string value accessed by `op_code_type'IMAGE(op_code_type'VAL(to_integer(unsigned(op_code))))`. Note there are three different string lengths. –  Mar 03 '18 at 19:33
  • Using SignalTap as you are doing I am not sure, but with ModelSim when you define a type, for example to enumerate the states your CPU is going through (i.e FETCH, DECODE...), is capable of show you the name of the states instead of the bits conforming the state. – Marc43 Apr 29 '18 at 08:49

2 Answers2

0

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.

PlayDough
  • 1,116
  • 8
  • 16
  • Thanks for the answer, but unless I'm missing something this is basically doing the same thing as what I posted in my question? You're just converting to an int first and using that in a case statement instead. My question is whether there is any way to dynamically control how a signal or bus is displayed in the simulator....there can be thousands of op code variants, so hard-coding each and every one isn't an option. – Mark Feldman Jun 18 '19 at 21:01
  • You are right. I read through it quickly and then read the comments below. Sorry. I can delete this answer. In Modelsim, you can do things custom display types. I don't have access to Active-HDL, so I can't tell you there. If you like, I can do this for Modelsim as an example. And while you cannot chose the value of the individual element, the position is predictable. So in my example, `opcode_names'pos(x)` is predictable. – PlayDough Jun 18 '19 at 21:52
  • 1
    Please don't delete your answer, it's interesting even if "incomplete"! – B. Go Jun 18 '19 at 22:21
0

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);
PlayDough
  • 1,116
  • 8
  • 16