I come from C background and am being introduced to VHDL. I read about the syntax and the concurrency/consecutiveness of actions.

Now I am getting to wonder how are development-only features implemented. Things like assert() and #ifndef NDEBUG LOG_MSG_UART("99 bottles of beer").

The scenario, that I am thinking of, is this. Let us have a development board with an FPGA and several debugging headers. Various signals are output to those debug headers for measurement with an oscilloscope or logic analyzer. In the release version, there will be no such headers. How does one write the code so that they can easily switch between the Release and Debug builds?

An idiomatic example would help a lot.

  • 1
    \$\begingroup\$ You are going to need to make a complete change in perspective. There is no "debug" build because there can be no bugs in the released code. You must spend much more time in verification, preferably in simulation. Get used to strong type checking in VHDL and make it your friend instead of your enemy. \$\endgroup\$ – Joe Hass Jan 21 '14 at 11:35

In VHDL, the basic ASSERT statement is used to ensure that when you are simulating, you catch all those conditions. Simulation is one step of the debugging process, which is most software-like, as you have access to everything within the design.

You can also (with access to sufficiently high-calibre tools) use PSL to write more complex assertions which can involve timings as well as simple conditionals.

Once you have simulated to your satisfaction, if you need to debug the real hardware, then you can use the following tools:

  • embedded logic analyser (Xilinx: Chipscope, Altera: SignalTap) - you build this in at compile time and can then trigger and monitor signals. The signals are captured in real-time to internal memory, so there is a limit on how many and for how long, but it does give excellent visibility. It can be time-consuming though as each time you change your mind about the setup, you have to rebuild the FPGA (which is a process measured in tens of minutes or even hours, depending ont he complexity of the design)
  • spare pins - with or without LEDs. Bring out critical internal signals and monitor them with an oscilloscope and logic analyser. Again, rebuild each time you change it. This can also be done more dynamically in Xilinx-land with the FPGA editor, so you can make relatively quick updates to the bitstream, if you can find the signal you want within the optimised netlist!

As Brian pointed out, you will also need an oscilloscope (and potentially logic analyser) to debug the external interfaces - checking the timing etc.

Regarding the issue of having "debug" and "release" builds, that is not handled in a "standard" way (unlike in the software world). The behaviour would not be wildly different in most cases: As the FPGA is very deterministic, you don't lose speed by having debug logic in there (or if you do, it still meets the required spec, as otherwise you couldn't debug the chip!). You do end up with more logic than you need, but again, it has to fit int he device you have (unless you are planning to downsize for release, but given the step ups in device size, you'd need a lot of debug logic for that to be worthwhile!).

Also note that (as far as I've heard) no-one build with different optimiser settings for debug vs. release, which can make finding the signals you want tricky, so you have to add attributes to them to stop them being as optimised as they otherwise would be, which is good as the rest of the design gets optimised. Not so good that you have to rebuild to change the attributes. (At least there's plenty of time for keeping your documentation up-to-date while you wait for the builds :)

If you do want to do this then I would suggest a couple of ways:

  • Have a boolean generic on the top level instance - it can default to debugging off, and you can override it on the command-line/from the GUI of your synthesiser.
  • Have a boolean constant in a package that you use in your main code - select between the two modes by editing this file

With the first option, only the top-level can see the state of the debug status (unless you pass it down the hierarchy, but that can get nasty quickly as you usually need to pass the debug signals back up then too!).

With the second, any entity can get at the debug constant, and make use of it - again you need to pass signals up the tree.

You can use the boolean in an if..generate..else generate to instantiate the logic you want (or don;t want) depending on the "mode".

  • \$\begingroup\$ +1 for covering some of what's left after simulation. For Xilinx FPGAs, the embedded analyzer is "Chipscope". Also : interfacing to external chips can be tricky : modifying I/O timings or the phase of external clocks may be necessary for reliable communication to them. \$\endgroup\$ – Brian Drummond Jan 21 '14 at 11:50
  • \$\begingroup\$ Concretely, my question is how to output some signals on e.g. LEDs when developing, then remove this logic for the release, and then have it back in while developing v2. \$\endgroup\$ – Vorac Jan 21 '14 at 13:41
  • \$\begingroup\$ Answer updated... \$\endgroup\$ – Martin Thompson Jan 21 '14 at 15:09

Probably more easily.

You get the bulk of the design right in simulation before ever moving to hardware.

Use self-checking testbenches (or higher order verification techniques including PSL, constrained random testing or OSVVM) in the role of unit testing to verify that the block meets its basic functionality.

When it doesn't, the simulator has a waveform viewer which can show you the value of every signal at every moment in time, helpfully displaying unknowns ('X' or 'Z' for example) in red. Use this judiciously to work back from incorrect results to their cause; edit, compile, re-simulate etc until the block does what you want.

Self-checking testbenches can compare the low-level (synthesisable) design against a higher level algorithmic model : any basic algorithm you can write in C can also be written in VHDL (but may not be synthesisable!) or the testbench can read the expected results from a file. Testbenches are thus development-only. (They don't generally have access to the innards of a hardware block; only it's inputs and outputs so they are black box testers). But a testbench can wire up several black boxes to test how they interact.

So, compare the actual results from a block with the expected results (you may need to store the expected results until the actuals are ready) ... this is normally done with an assert statement :

VHDL helps a lot thanks to having a decent type system : use it!
(Or get frustrated by fighting it : your choice!)

Bad VHDL :

addr : integer;
assert addr >= 0 report "Address arithmetic overflow" severity FAILURE;

Better VHDL :

subtype address is natural range 0 to 2**16 - 1;
addr : address;

The assert is now redundant because the simulator does it for you : values outside the specified range are trapped. (Synthesis can still generate overflows; the synth tools are allowed to assume you got the design right already! Therefore they won't waste hardware on overflow checks unless you explicitly code them)

You can do many good things with the type system : this ought to be second nature to SW developers but doesn't seem to be taught any more. A couple of examples:

   variable mem : array(addr) of integer;

   for i in addr loop
      mem(i) := 0;
   end loop;

Array size and loop bounds defined by the type : no loop bound errors or buffer overruns.

assert addr'high + 1 = 65536 report "Wrong address size" severity failure;

Attributes ('high, 'low, 'range (defines an integer subtype) etc allow runtime introspection of the type (set loop bounds for an unconstrained array, etc) or as here to check assumptions about a type declared somewhere else...

type colour is (red, green, blue); 

a bit like a C enum except...

type RGB is array(colour) of real;
constant gain : RGB := (red => 0.31, green => 0.58, blue => 0.11);
signal YUV,RGB_in : RGB;
for c in colour loop
   YUV(c) <= RGB_in(c) * gain(c);
end loop;

having defined such a type you can use it as an array index or loop over it etc. And so on.

Named association for arguments, array components, etc reduce mistakes and make the code clearer.

Packages ... put together types and their operations in safe reusable components. And so on...

My designs tend to have a "common" package defining widely used things like "address" above : everything uses it and if I change the address size, all the loops readjust to the new size, and anything with hard-coded assumptions should fall over at an assert (as above) until I fix it...

  • \$\begingroup\$ Not really answering the question "how do I sometimes dump some internal/input states from the FPGA", but good in that it will steer me in the new way of thinking. \$\endgroup\$ – Vorac Jan 21 '14 at 13:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.