Skip to content

Example: Debug Probes

Julian Kemmerer edited this page Nov 14, 2020 · 8 revisions

Say you are describing some hardware (ex. a DDR3 mem test) using your friendly neighborhood PipelineC HDL...you have a state machine and some related signals (ex. state, address, data) you want to probe for debug in hardware testing.

Your existing option is something like a MARK_DEBUG attribute in your HDL + going through your manufacturer specific 'chipscope/signal tap' flow - which I have not had much fun with personally - annoying every time I need to use it.

Otherwise, doing this without manufacturer support, maybe you are lucky enough to have setup some kind of debug structs/record ports where you easily can route debug signals around your design hierarchy for some existing debug ports you've planned up. Or worse you are adding new ports and connections all over your design hierarchy to get to these signals - boo.

Here is why this is easy in PipelineC:

To start, look to the ability to connect point-to-point wires from one point in the design hierarchy to other. With a single line of code (a macro), a design can connect a point to point wire from the signal you want to capture to some debug probe module/port (the compiler wires up and down across whatever design hierarchy exists for you).

WIRE_WRITE(some_c_type_t, probe3, test_data) 
// probe3=test_data 
// some signal test_data of type some_c_type_t in the design

This lets you write a stand alone 'library module' for capture of debug signals probes.c. Each probe is exposed as a point to point wire from the probes module to wherever the user uses it. Here is how to 'use the library':

  1. Define the probe data type in a header file #define probe3_t some_c_type_t //probe3.h
  2. #include "probes.c" in the code you want to debug
  3. Add one or more WIRE_WRITE 's driving the probes you've defined

So what does this probes.c library module do for you? It samples your debug probe and streams out the data to a host pc (over UART for now). Here is a high level diagram of whats going on:

A few 'this is cool' notes about this logic analyzer probes example:

  • An optional read_enable signal for the probe can be WIRE_READ similar to how the probe is driven with WIRE_WRITE. This allows the design to know when the probe has been sampled. Good for if this probe is connected to a FIFO or RAM reading elements over time.
  • The FSMs are written with inferred clock enable signals - meaning - you can write naturally understandable if(this) sub_fsm() logic that only clock enables the sub_fsm module if the condition is met. See my previous post for more fsm examples.
  • Async FIFOs clock crossings look very similar to point-to-point wires, a c-type, depth specifier, and READ and WRITE functions fifo_c_type_t fifo_name[FIFO_DEPTH];
  • Easy modular reuse of existing byte IO UART MAC modules - could be any byte stream interface.

Ok so this lets you get a one time capture of some_c_type_t of data. Thats neat. But combined with a bit more logic its pretty powerful:

Say your some_c_type_t is defined like so from that memory test app example:

#define DEBUG_SAMPLES 32
typedef struct app_debug_t
{
  mem_test_state_t state[DEBUG_SAMPLES];
  uint32_t test_addr[DEBUG_SAMPLES];
  uint8_t test_data[DEBUG_SAMPLES];
}app_debug_t;

With a little FSM to shift data into those arrays inside the app_debug_t reg each clock cycle, you can capture samples over time (32 clocks here), and then one time sample that entire debug probe.

WIRE_WRITE(app_debug_t, probe0, debug_shift_reg) 

PipelineC comes with code generation support so the packing of app_debug_t to and from bytes is handled for you. That makes writing the supporting software C code very easy: write the UART port to identify the probe to read, then read back the probe bytes - converted to the struct form by generated code.

The simple debug_probes.c is such a little C program and plot_probes.py is a helper script that takes the probes printout and starts up GTKWave to view the probe data (see image at top of post). Let's you do a one liner capture and show waveforms:

./debug_probes 0 | plot_probes.py

A final note is that this 'capture logic' of shifting sampled data into registers over time - it can be whatever you want. Want to capture data only when your FSM changes state? Every 3rd cycle? Into block ram / fifos instead of shift regs? 'Advanced trigger and capture settings' you might configure in a manufacturer tool are just whatever logic you describe to store samples.

And how fun that this whole time I didn't even mention the core 'auto-pipelining' features of the PipelineC compiler - another day!

As always very happy to answer questions and help you get a project going in PipelineC if you want :) Thanks folks!

Clone this wiki locally