<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 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
Step1:  A simple function to add wavedrom diagrams into an jupyter notebook. It utilises the wavedrom java script library.  
<font color="DodgerBlue">Example usage
Step2:  Notes on waveform specification
Step 3
Step3:  Notes on waveform specification
Adding multiple wave groups and spaces
Step4:  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
Step5:  Note
Step6:  Step 3
Step7:  Step 4
Step8:  Note
<ASSISTANT_TASK:>
Python Code:
from pynq.lib.logictools.waveform import draw_wavedrom
Explanation: 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
End of explanation
clock = {'signal': [{'name': 'clock_0', 'wave': 'hlhlhlhlhlhlhlhl'}],
         'foot': {'tock': 1},
         'head': {'text': 'Clock Signal'}}
draw_wavedrom(clock)
Explanation: A simple function to add wavedrom diagrams into an jupyter notebook. It utilises the wavedrom java script library.  
<font color="DodgerBlue">Example usage:</font> 
```python
        from pynq.lib.logictools.waveform import draw_wavedrom   
    clock = {'signal': [{'name': 'clk', 'wave': 'h....l...'}]}   
    draw_wavedrom(clock)
&lt;font color="DodgerBlue"&gt;**Method:**&lt;/font&gt;python
        def draw_wavedrom(data, width=None):          
        # Note the optional argument width forces the width in pixels
```
Step 2: Specify and render a waveform
End of explanation
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)
Explanation: Notes on waveform specification
Step 3: Adding more signals to the waveform
End of explanation
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)
Explanation: Notes on waveform specification
Adding multiple wave groups and spaces
End of explanation
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()
Explanation: 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.
End of explanation
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()
Explanation: 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.
End of explanation
import pprint
output_wavejson = pattern_generator.waveform.waveform_dict
pprint.pprint(output_wavejson)
Explanation: Step 3: View the output waveJSON dict.
End of explanation
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}]])
Explanation: Step 4: Extending the output waveJSON dict with state annotation
End of explanation
draw_wavedrom(output_wavejson)
Explanation: Note:  The color_dict is a color code map as defined by WaveDrom
End of explanation
<END_TASK>