logictools WaveDrom Tutorial

WaveDrom is a tool for rendering digital timing waveforms. The waveforms are defined in a simple textual format.
This notebook will show how to render digital waveforms using the pynq library.

The logictools overlay uses the same format as WaveDrom to specify and generate real signals on the board.

A full tutorial of WaveDrom can be found here

Step 1: Import the draw_wavedrom() method from the pynq library


In [1]:
from pynq.lib.logictools.waveform import draw_wavedrom

A simple function to add wavedrom diagrams into an jupyter notebook. It utilises the wavedrom java script library.

**Example usage:**

from pynq.lib.logictools.waveform import draw_wavedrom   

        clock = {'signal': [{'name': 'clk', 'wave': 'h....l...'}]}   
        draw_wavedrom(clock)

**Method:**

def draw_wavedrom(data, width=None):            
        # Note the optional argument width forces the width in pixels

Step 2: Specify and render a waveform


In [2]:
clock = {'signal': [{'name': 'clock_0', 'wave': 'hlhlhlhlhlhlhlhl'}],
         'foot': {'tock': 1},
         'head': {'text': 'Clock Signal'}}

draw_wavedrom(clock)


Notes on waveform specification

Step 3: Adding more signals to the waveform


In [3]:
pattern = {'signal': [{'name': 'clk', 'wave': 'hl' * 8},
                      {'name': 'clkn', 'wave': 'lh' * 8},
                      {'name': 'data0', 'wave': 'l.......h.......'},
                      {'name': 'data1', 'wave': 'h.l...h...l.....'}],
           'foot': {'tock': 1},
           'head': {'text': 'Pattern'}}

draw_wavedrom(pattern)


Notes on waveform specification

Adding multiple wave groups and spaces


In [4]:
pattern_group = {'signal': [['Group1',
                             {'name': 'clk', 'wave': 'hl' * 8},
                             {'name': 'clkn', 'wave': 'lh' * 8},
                             {'name': 'data0', 'wave': 'l.......h.......'},
                             {'name': 'data1', 'wave': 'h.l...h...l.....'}],
                            {},
                            ['Group2',
                             {'name': 'data2', 'wave': 'l...h..l.h......'},
                             {'name': 'data3', 'wave': 'l.h.' * 4}]],
                 'foot': {'tock': 1},
                 'head': {'text': 'Pattern'}}

draw_wavedrom(pattern_group)


Notes on waveform specification

WaveDrom for real-time pattern generation and trace analysis

The logictools overlay uses WaveJSON format to specify and generate real signals on the board.

  • As shown in the figure above, the Pattern Generator is an output-only block that specifies a sequence of logic values (patterns) which appear on the output pins of the ARDUINO interface. The logictools API for Pattern Generator accepts WaveDrom specification syntax with some enhancements.
  • The Trace Analyzer is an input-only block that captures and records all the IO signals. These signals may be outputs driven by the generators or inputs to the PL that are driven by external circuits. The Trace Analyzer allows us to verify that the output signals we have specified from the generators are being applied correctly. It also allows us to debug and analyze the opearion of the external interface.
  • The signals generated or captured by both the blocks can be displayed in the notebook by populating the WaveJSON dictionary that we have seen in this notebook. Users can access this dictionary through the provided API to extend or modify the waveform with special annotations.
  • we use a subset of the wave tokens that are allowed by WaveDrom to specify the waveforms for the Pattern Generator. However, users can call the draw_waveform() method on the dictionary populated by the Trace Analyzer to extend and modify the dictionary with annotations.

In the example below, we are going to generate 3 signals on the Arduino interface pins D0, D1 and D2 using the Pattern Generator. Since all IOs are accessible to the Trace analyzer, we will capture the data on the pins as well. This operation will serve as an internal loopback.

Step 1: Download the logictools overlay and specify the pattern

The pattern to be generated is specified in the waveJSON format. The Waveform class is used to display the specified waveform.


In [5]:
from pynq.lib.logictools import Waveform
from pynq.overlays.logictools import LogicToolsOverlay

logictools_olay = LogicToolsOverlay('logictools.bit')

loopback_test = {'signal': [
    ['stimulus',
     {'name': 'output0', 'pin': 'D0', 'wave': 'lh' * 8},
     {'name': 'output1', 'pin': 'D1', 'wave': 'l.h.' * 4},
     {'name': 'output2', 'pin': 'D2', 'wave': 'l...h...' * 2}],
    {},
    ['analysis',
     {'name': 'input0', 'pin': 'D0'},
     {'name': 'input1', 'pin': 'D1'},
     {'name': 'input2', 'pin': 'D2'}]],

    'foot': {'tock': 1},
    'head': {'text': 'loopback_test'}}

waveform = Waveform(loopback_test)
waveform.display()


Note: Since there are no captured samples at this moment, the analysis group will be empty.

Notes on the enhanced WaveJSON specification format

Step 2: Run the pattern generator and trace the loopback signals.

This step populates the WaveJSON dict with the captured trace analyzer samples. The dict can now serve as an ouput that we can further modify. It is shown in the next step.


In [6]:
pattern_generator = logictools_olay.pattern_generator

pattern_generator.trace(num_analyzer_samples=16)
pattern_generator.setup(loopback_test,
                        stimulus_group_name='stimulus',
                        analysis_group_name='analysis')

pattern_generator.run()
pattern_generator.show_waveform()


Step 3: View the output waveJSON dict.


In [7]:
import pprint

output_wavejson = pattern_generator.waveform.waveform_dict
pprint.pprint(output_wavejson)


{'foot': {'tock': 1},
 'head': {'text': 'loopback_test'},
 'signal': [['stimulus',
             {'name': 'output0', 'pin': 'D0', 'wave': 'lhlhlhlhlhlhlhlh'},
             {'name': 'output1', 'pin': 'D1', 'wave': 'l.h.l.h.l.h.l.h.'},
             {'name': 'output2', 'pin': 'D2', 'wave': 'l...h...l...h...'}],
            {},
            ['analysis',
             {'name': 'input0', 'pin': 'D0', 'wave': 'lhlhlhlhlhlhlhlh'},
             {'name': 'input1', 'pin': 'D1', 'wave': 'l.h.l.h.l.h.l.h.'},
             {'name': 'input2', 'pin': 'D2', 'wave': 'l...h...l...h...'}]]}

Step 4: Extending the output waveJSON dict with state annotation


In [8]:
state_list = ['S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7',
              'S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7']

color_dict = {'white': '2', 'yellow': '3', 'orange': '4', 'blue': '5'}

output_wavejson['signal'].extend([{}, ['Annotation',
                                       {'name': 'state',
                                        'wave': color_dict['yellow'] * 8 +
                                                color_dict['blue'] * 8,
                                        'data': state_list}]])

Note: The color_dict is a color code map as defined by WaveDrom


In [9]:
draw_wavedrom(output_wavejson)