├── example ├── step1.json ├── test1.json ├── step3.json ├── step7.json ├── step4.json ├── sync_sram_only.json ├── ahbl_basic.json ├── step2.json ├── sync_sram_ahb_access.json ├── ahbl_basic_stall.json ├── step5.json ├── step6.json ├── ahbl_mm_simult1.json ├── ahbl_mm_simult2.json ├── ahbl_mm_simult3.json ├── ahbl_mm_latearrival.json ├── sync_sram_ahb_stall.json └── sync_sram_ahb_buffer.json ├── wavedrom_png_sample.png ├── LICENSE ├── Readme.md └── asciiwave /example/step1.json: -------------------------------------------------------------------------------- 1 | { signal: [{ name: "Alfa", wave: "01.zx=ud.23.45" }] } 2 | -------------------------------------------------------------------------------- /wavedrom_png_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wren6991/asciiwave/HEAD/wavedrom_png_sample.png -------------------------------------------------------------------------------- /example/test1.json: -------------------------------------------------------------------------------- 1 | {signal: [ 2 | { wave: "1p0nhpln"}, 3 | { wave: "HpLnHPLN"}, 4 | { wave: "phnlp1n1"}, 5 | ]} 6 | -------------------------------------------------------------------------------- /example/step3.json: -------------------------------------------------------------------------------- 1 | { signal: [ 2 | { name: "clk", wave: "P......" }, 3 | { name: "bus", wave: "x.==.=x", data: ["head", "body", "tail", "data"] }, 4 | { name: "wire", wave: "0.1..0." } 5 | ]} 6 | -------------------------------------------------------------------------------- /example/step7.json: -------------------------------------------------------------------------------- 1 | { signal: [ 2 | { name: "clk", wave: "p...." }, 3 | { name: "Data", wave: "x345x", data: ["head", "body", "tail"] }, 4 | { name: "Request", wave: "01..0" } 5 | ], 6 | config: { hscale: 1 } 7 | } 8 | -------------------------------------------------------------------------------- /example/step4.json: -------------------------------------------------------------------------------- 1 | { signal: [ 2 | { name: "clk", wave: "p.....|..." }, 3 | { name: "Data", wave: "x.345x|=.x", data: ["head", "body", "tail", "data"] }, 4 | { name: "Request", wave: "0.1..0|1.0" }, 5 | {}, 6 | { name: "Acknowledge", wave: "1.....|01." } 7 | ]} 8 | -------------------------------------------------------------------------------- /example/sync_sram_only.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.......'}, 4 | {name: 'addr', wave: 'x=345=xx', data: ['A', 'B', 'C', 'D', 'E']}, 5 | {name: 'wen', wave: '01010...'}, 6 | {name: 'wdata', wave: 'x=x4xxxx', data: ['A', 'C']}, 7 | {name: 'rdata', wave: 'xxx3x5=x', data: ['B', 'D', 'E']}, 8 | ]} 9 | 10 | -------------------------------------------------------------------------------- /example/ahbl_basic.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.......'}, 4 | {name: 'htrans', wave: '=.===...', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr', wave: 'xx==xxxx', data: ['A', 'B', 'C', 'D']}, 6 | {name: 'hwrite', wave: 'xx10xxxx'}, 7 | {name: 'hwdata', wave: 'xxx=xxxx', data: ['A']}, 8 | {name: 'hrdata', wave: 'xxxx=xxx', data: ['B']}, 9 | ]} 10 | 11 | -------------------------------------------------------------------------------- /example/step2.json: -------------------------------------------------------------------------------- 1 | { signal: [ 2 | { name: "pclk", wave: 'p.......' }, 3 | { name: "Pclk", wave: 'P.......' }, 4 | { name: "nclk", wave: 'n.......' }, 5 | { name: "Nclk", wave: 'N.......' }, 6 | {}, 7 | { name: 'clk0', wave: 'phnlPHNL' }, 8 | { name: 'clk1', wave: 'xhlhLHl.' }, 9 | { name: 'clk2', wave: 'hpHplnLn' }, 10 | { name: 'clk3', wave: 'nhNhplPl' }, 11 | { name: 'clk4', wave: 'xlh.L.Hx' }, 12 | ]} 13 | -------------------------------------------------------------------------------- /example/sync_sram_ahb_access.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.......'}, 4 | {name: 'htrans', wave: '==....=.', data: ['IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr', wave: 'x=345=xx', data: ['A', 'B', 'C', 'D', 'E']}, 6 | {name: 'hwrite', wave: '01010...'}, 7 | {name: 'hwdata', wave: 'xx=x4xxx', data: ['A', 'C']}, 8 | {name: 'hrdata', wave: 'xxx3x5=x', data: ['B', 'D', 'E']}, 9 | ]} 10 | 11 | -------------------------------------------------------------------------------- /example/ahbl_basic_stall.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.......'}, 4 | {name: 'htrans', wave: '=.==.=..', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr', wave: 'xx==.xxx', data: ['A', 'B', 'C']}, 6 | {name: 'hwrite', wave: 'xx10.xxx'}, 7 | {name: 'hready', wave: '1..0101.'}, 8 | {name: 'hwdata', wave: 'xxx=.xxx', data: ['A']}, 9 | {name: 'hrdata', wave: 'xxxxxx=x', data: ['B']}, 10 | ]} 11 | 12 | -------------------------------------------------------------------------------- /example/step5.json: -------------------------------------------------------------------------------- 1 | { signal: [ 2 | { name: 'clk', wave: 'p..Pp..P'}, 3 | ['Master', 4 | ['ctrl', 5 | {name: 'write', wave: '01.0....'}, 6 | {name: 'read', wave: '0...1..0'} 7 | ], 8 | { name: 'addr', wave: 'x3.x4..x', data: 'A1 A2'}, 9 | { name: 'wdata', wave: 'x3.x....', data: 'D1' }, 10 | ], 11 | {}, 12 | ['Slave', 13 | ['ctrl', 14 | {name: 'ack', wave: 'x01x0.1x'}, 15 | ], 16 | { name: 'rdata', wave: 'x.....4x', data: 'Q2'}, 17 | ] 18 | ]} 19 | -------------------------------------------------------------------------------- /example/step6.json: -------------------------------------------------------------------------------- 1 | { "signal" : [ 2 | { "name": "CK", "wave": "P.......", "period":2 }, 3 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data" : "RAS NOP CAS NOP NOP NOP NOP", "phase" :0.5 }, 4 | { "name": "ADDR","wave": "x.=x..=x........", "data" : "ROW COL", "phase" :0.5 }, 5 | { "name": "DQS", "wave": "z.......0.1010z." , "phase": -0.35}, 6 | { "name": "DQ", "wave": "z.........5555z.", "data" : "D0 D1 D2 D3", "phase": -0.35} 7 | ]} 8 | -------------------------------------------------------------------------------- /example/ahbl_mm_simult1.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.........'}, 4 | {name: 'htrans0', wave: '=.==......', data: ['IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr0', wave: 'xx=xxxxxxx', data: ['A']}, 6 | {name: 'hready0', wave: '1..01.....'}, 7 | {name: 'htrans1', wave: '=.==......', data: ['IDLE', 'NSEQ', 'IDLE']}, 8 | {name: 'haddr1', wave: 'xx=xxxxxxx', data: ['B']}, 9 | {name: 'hready1', wave: '1..0..1...'}, 10 | {name: 'htrans_slave', wave: '=.==.=....', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 11 | {name: 'haddr_slave', wave: 'xx==.xxxxx', data: ['A', 'B']}, 12 | {name: 'hreadyout_slave', wave: '1..0101...'} 13 | ]} 14 | 15 | -------------------------------------------------------------------------------- /example/ahbl_mm_simult2.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.........'}, 4 | {name: 'htrans0', wave: '=.==......', data: ['IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr0', wave: 'xx=xxxxxxx', data: ['A']}, 6 | {name: 'hready0', wave: '1..01.....'}, 7 | {name: 'htrans1', wave: '=.==...=..', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 8 | {name: 'haddr1', wave: 'xx==...xxx', data: ['B', 'C']}, 9 | {name: 'hready1', wave: '1..0..101.'}, 10 | {name: 'htrans_slave', wave: '=.==.=....', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 11 | {name: 'haddr_slave', wave: 'xx==.=.xxx', data: ['A', 'B', 'C']}, 12 | {name: 'hreadyout_slave', wave: '1..010101.'} 13 | ]} 14 | 15 | -------------------------------------------------------------------------------- /example/ahbl_mm_simult3.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P..........'}, 4 | {name: 'htrans0', wave: '=.==.==....', data: ['IDLE', 'NSEQ', 'IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr0', wave: 'xx=xx=xxxxx', data: ['A', 'D']}, 6 | {name: 'hready0', wave: '1..01.0.1..'}, 7 | {name: 'htrans1', wave: '=.==...=...', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 8 | {name: 'haddr1', wave: 'xx==...xxxx', data: ['B', 'C']}, 9 | {name: 'hready1', wave: '1..0..10..1'}, 10 | {name: 'htrans_slave', wave: '=.==.=.=.=.', data: ['IDLE', 'NSEQ', 'NSEQ', 'NSEQ', 'NSEQ', 'IDLE']}, 11 | {name: 'haddr_slave', wave: 'xx==.=.=.xx', data: ['A', 'B', 'D', 'C']}, 12 | {name: 'hreadyout_slave', wave: '1..01010101'} 13 | ]} 14 | 15 | -------------------------------------------------------------------------------- /example/ahbl_mm_latearrival.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P..........'}, 4 | {name: 'htrans0', wave: '=.==..==...', data: ['IDLE', 'NSEQ', 'IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr0', wave: 'xx=xxx=xxxx', data: ['A', 'D']}, 6 | {name: 'hready0', wave: '1..01..01..'}, 7 | {name: 'htrans1', wave: '=.==...=...', data: ['IDLE', 'NSEQ', 'NSEQ', 'IDLE']}, 8 | {name: 'haddr1', wave: 'xx==...xxxx', data: ['B', 'C']}, 9 | {name: 'hready1', wave: '1..0..10..1'}, 10 | {name: 'htrans_slave', wave: '=.==.=.=.=.', data: ['IDLE', 'NSEQ', 'NSEQ', 'NSEQ', 'NSEQ', 'IDLE']}, 11 | {name: 'haddr_slave', wave: 'xx==.===.xx', data: ['A', 'B', 'C', 'D', 'C']}, 12 | {name: 'hreadyout_slave', wave: '1..01010101'} 13 | ]} 14 | 15 | -------------------------------------------------------------------------------- /example/sync_sram_ahb_stall.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P........'}, 4 | {name: 'htrans', wave: '==......=', data: ['IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr', wave: 'x=3.45.=x', data: ['A', 'B', 'C', 'D', 'E']}, 6 | {name: 'hready', wave: '1.01.01..'}, 7 | {name: 'hwrite', wave: '010.10...'}, 8 | {name: 'hwdata', wave: 'xx=.x4.xx', data: ['A', 'C']}, 9 | {name: 'hrdata', wave: 'xxxx3xx5=', data: ['B', 'D', 'E']}, 10 | {}, 11 | {name: 'sram_addr', wave: 'xx=3x45=x', data: ['A', 'B', 'C', 'D', 'E']}, 12 | {name: 'sram_wen', wave: '0.10.10..'}, 13 | {name: 'sram_wdata', wave: 'xx=xx4xxx', data: ['A', 'C']}, 14 | {name: 'sram_rdata', wave: 'xxxx3xx5=', data: ['B', 'D', 'E']} 15 | ]} 16 | 17 | -------------------------------------------------------------------------------- /example/sync_sram_ahb_buffer.json: -------------------------------------------------------------------------------- 1 | {config: {hscale: 2}, 2 | signal: [ 3 | {name: 'clk', wave: 'P.......'}, 4 | {name: 'htrans', wave: '==....==', data: ['IDLE', 'NSEQ', 'IDLE']}, 5 | {name: 'haddr', wave: 'x=345=xx', data: ['A', 'B', 'C', 'D', 'E']}, 6 | {name: 'hwrite', wave: '01010...'}, 7 | {name: 'hwdata', wave: 'xx=x4xxx', data: ['A', 'C']}, 8 | {name: 'hrdata', wave: 'xxx3x5=x', data: ['B', 'D', 'E']}, 9 | {}, 10 | {name: 'sram_addr', wave: 'xx325=4x', data: ['B', 'A', 'D', 'E', 'C']}, 11 | {name: 'sram_wen', wave: '0..10.10'}, 12 | {name: 'sram_wdata', wave: 'xxx=xx4x', data: ['A', 'C']}, 13 | {name: 'sram_rdata', wave: 'xxx3x5=x', data: ['B', 'D', 'E']}, 14 | {}, 15 | {name: 'data_buf', wave: 'xxx=.4..', data: ['A', 'C']}, 16 | {name: 'buf_capture', wave: '0.1010..'}, 17 | ]} 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Luke Wren 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | asciiwave: WaveDrom to ASCII art 2 | ================================ 3 | 4 | This utility reads WaveDrom JSON files like this: 5 | 6 | ``` 7 | { signal: [ 8 | { name: "clk", wave: "P......" }, 9 | { name: "bus", wave: "x.==.=x", data: ["head", "body", "tail", "data"] }, 10 | { name: "wire", wave: "0.1..0." } 11 | ]} 12 | ``` 13 | 14 | And produces ASCII art like this: 15 | 16 | ``` 17 | $ ./asciiwave example/step3.json 18 | ┏──┐ ┏──┐ ┏──┐ ┏──┐ ┏──┐ ┏──┐ ┏──┐ 19 | clk : ┛ └──┛ └──┛ └──┛ └──┛ └──┛ └──┛ └── 20 | xxxxxxxxxxxx╱ ╲╱ ╲╱ ╲xxxxxx 21 | bus : xxxxxxxxxxxx╲head╱╲ body ╱╲tail╱xxxxxx 22 | ┐ ┌─────────────────┐ 23 | wire: └───────────┘ └─────────── 24 | ``` 25 | 26 | WaveDrom would usually render a PNG or SVG like the below: 27 | 28 | ![](wavedrom_png_sample.png) 29 | 30 | However, PNGs can not be pasted into comments in your HDL project! 31 | 32 | asciiwave requires the `json5` library from PyPI, as a lot of WaveJSON samples floating around on the internet rely on non-vanilla-JSON features like unquoted keys, single-quoted strings and trailing commas. The `jsonschema` library is also required, for input validation. These can be obtained via: 33 | 34 | ``` 35 | $ pip3 install json5 jsonschema 36 | ``` 37 | 38 | asciiwave features a watch mode (`-w`), which will continously poll a file on disk, and redraw whenever the 39 | file changes. This can be used interactively alongside a text editor. 40 | 41 | 42 | ``` 43 | $ ./asciiwave --watch example/step4.json 44 | 45 | ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┆┌──┐ ┌──┐ ┌──┐ 46 | clk : ┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┆┘ └──┘ └──┘ └── 47 | xxxxxxxxxxxx╱ ╲╱ ╲╱ ╲xxxxxx┆╱ ╲xxxxxx 48 | Data : xxxxxxxxxxxx╲head╱╲body╱╲tail╱xxxxxx┆╲ data ╱xxxxxx 49 | ┐ ┌─────────────────┐ ┆┌───────────┐ 50 | Request : └───────────┘ └─────┆┘ └───── 51 | 52 | ┌───────────────────────────────────┆┐ ┌─────────── 53 | Acknowledge: ┘ ┆└─────┘ 54 | 55 | Watching file example/step4.json 56 | Ctrl-C to exit 57 | ``` 58 | 59 | There are simple command-line options for formatting: 60 | 61 | ``` 62 | $ ./asciiwave --hscale=4 --graphics=tall example/step4.json 63 | ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┆┌────┐ ┌────┐ ┌────┐ 64 | │ │ │ │ │ │ │ │ │ │ │ │ ┆│ │ │ │ │ │ 65 | clk : ┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┆┘ └────┘ └────┘ └──── 66 | xxxxxxxxxxxxxxxxxxxx╱ ╲╱ ╲╱ ╲xxxxxxxxxx┆╱ ╲xxxxxxxxxx 67 | xxxxxxxxxxxxxxxxxxxx head body tail xxxxxxxxxx┆ data xxxxxxxxxx 68 | Data : xxxxxxxxxxxxxxxxxxxx╲ ╱╲ ╱╲ ╱xxxxxxxxxx┆╲ ╱xxxxxxxxxx 69 | ┐ ┌─────────────────────────────┐ ┆┌───────────────────┐ 70 | │ │ │ ┆│ │ 71 | Request : └───────────────────┘ └─────────┆┘ └───────── 72 | 73 | ┌───────────────────────────────────────────────────────────┆┐ ┌─────────────────── 74 | │ ┆│ │ 75 | Acknowledge: ┘ ┆└─────────┘ 76 | 77 | $ ./asciiwave --hscale=1 --graphics=tiny example/step4.json 78 | clk : ┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐_┆┌─┐_┌─┐_┌─┐_ 79 | Data : xxxxxxxxxxxx┆< data >xxxx 80 | Request : ┐_______┌───────────┐___┆┌───────┐___ 81 | 82 | Acknowledge: ┌───────────────────────┆┐___┌─────── 83 | 84 | ``` 85 | 86 | WaveJSON Subset 87 | --------------- 88 | 89 | asciiwave does not implement the full gamut of WaveJSON features. It supports: 90 | 91 | - `wave` commands: `1hHu 0lLd pPnN =2345 zx |` 92 | - The `hscale` config property: the width of each time unit is `hscale * 2 + 2` characters. This is overridden by the `--hscale` command line parameter. 93 | - The `period` signal property: this can be a floating point number. The width of each wave time unit is multiplied by `period` and rounded down. 94 | - The `phase` signal property: this can be a floating point number. The signal is advanced (positive) or retarded (negative) by this number of periods. 95 | - The `data` signal property: either an array of strings, or a single string containing whitespace-separated values. 96 | 97 | Graphics 98 | -------- 99 | 100 | asciiwave defines its graphics like this: 101 | 102 | ``` 103 | graphics_default = [ 104 | "0+1-rfxz< >|UuDd", 105 | " ┌─┐┏┓x_╱ ╲┆╭┄ ", 106 | "─┘ └┛┗x ╲ ╱┆ ╰┄" 107 | ] 108 | ``` 109 | 110 | The first line is a key which maps asciiwave's internal representation of wire state to columns of the graphics; the following lines contain the actual graphics. These can be modified if you can't use the Unicode box drawing characters, or have found better-looking characters. 111 | 112 | The height is not fixed at 2 lines; any positive number of lines will do. However, the width of each wire state is limited to one column, to simplify rendering (this will be fixed) -------------------------------------------------------------------------------- /asciiwave: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | import itertools 6 | import json5 7 | import jsonschema 8 | import time 9 | import traceback 10 | import sys 11 | 12 | __doc__ = """ 13 | asciiwave is a tool to convert WaveDrom timing diagrams into ASCII art. 14 | 15 | For example: 16 | 17 | ``` 18 | $ cat example/step3.json 19 | { signal: [ 20 | { name: "clk", wave: "P......" }, 21 | { name: "bus", wave: "x.==.=x", data: ["head", "body", "tail", "data"] }, 22 | { name: "wire", wave: "0.1..0." } 23 | ]} 24 | 25 | $ ./asciiwave example/step3.json 26 | ┏──┐ ┏──┐ ┏──┐ ┏──┐ ┏──┐ ┏──┐ ┏──┐ 27 | clk : ┛ └──┛ └──┛ └──┛ └──┛ └──┛ └──┛ └── 28 | xxxxxxxxxxxx╱ ╲╱ ╲╱ ╲xxxxxx 29 | bus : xxxxxxxxxxxx╲head╱╲ body ╱╲tail╱xxxxxx 30 | ┐ ┌─────────────────┐ 31 | wire: └───────────┘ └─────────── 32 | ``` 33 | 34 | It supports: 35 | - `wave` commands: `1hHu 0lLd pPnN =2345 zx |` 36 | - The `data` signal property: either an array of strings, or a single string 37 | containing whitespace-separated values. 38 | - The `hscale` config property: the width of each time unit is `hscale * 2 + 2` 39 | characters. 40 | - The `period` signal property: this can be a floating point number. The width 41 | of each wave unit, in characters, is multiplied by `period` and rounded down. 42 | - The `phase` signal property: this can be a floating point number. The signal 43 | is advanced (if positive) or retarded (negative) by this number of periods. 44 | 45 | Watch mode (-w) will continously poll a file on disk, and redraw whenever the 46 | file changes. This can be used interactively alongside a text editor. 47 | 48 | """ 49 | 50 | # First the key, then the graphics lines 51 | graphics_tiny = [ 52 | "0+1-rfxz< >|UuDd", 53 | "_┌─┐┏┓xz< >┆╭┄╰┄", 54 | ] 55 | 56 | graphics_default = [ 57 | "0+1-rfxz< >|UuDd", 58 | " ┌─┐┏┓x_╱ ╲┆╭┄ ", 59 | "─┘ └┛┗x ╲ ╱┆ ╰┄" 60 | ] 61 | 62 | graphics_tall = [ 63 | "0+1-rfxz< >|UuDd", 64 | " ┌─┐┏┓x ╱ ╲┆╭┄ ", 65 | " │ │┃┃x─ ┆┆ ┆ ", 66 | "─┘ └┛┗x ╲ ╱┆ ╰┄" 67 | ] 68 | 69 | # Define the WaveJSON subset we support 70 | wavejson_schema = { 71 | "type": "object", 72 | "properties": { 73 | "config": { 74 | "type": "object", 75 | "properties": { 76 | "hscale": {"type": "number"} 77 | } 78 | }, 79 | "signal": { 80 | "type": "array", 81 | "items": { 82 | "type": "object", 83 | "properties": { 84 | "name": {"type": "string"}, 85 | "wave": {"type": "string"}, 86 | "data": { 87 | "type": ["array", "string"], 88 | "items": {"type": "string"} 89 | }, 90 | "phase": {"type": "number"}, 91 | "period": {"type": "number"} 92 | }, 93 | "additionalProperties": False 94 | } 95 | } 96 | }, 97 | "additionalProperties": False 98 | } 99 | 100 | # WaveDrom behaviour is surprisingly complex; this is an approximation 101 | def cmd_10(cmd, prev_cmd, width, charwidth, data, put): 102 | h_cmds = "1hHu" 103 | l_cmds = "0lLd" 104 | high = cmd in h_cmds 105 | hard_edge = cmd in "HL" or \ 106 | (high and prev_cmd in (l_cmds + "zx=2345")) or \ 107 | (not high and prev_cmd in (h_cmds + "zx=2345")) or \ 108 | (cmd in "1h" and prev_cmd in "1pP") or \ 109 | (cmd in "0l" and prev_cmd in "0nN") 110 | if cmd == "u": 111 | edge, flat = ("U", "u") 112 | elif cmd == "d": 113 | edge, flat = ("D", "d") 114 | elif cmd == "H": 115 | edge, flat = ("r", "1") 116 | elif cmd == "L": 117 | edge, flat = ("f", "0") 118 | elif high: 119 | edge, flat = ("+", "1") 120 | else: 121 | edge, flat = ("-", "0") 122 | rendered = edge * hard_edge + flat * (width - hard_edge) 123 | for s in rendered: 124 | put(s) 125 | 126 | def cmd_clk(cmd, prev_cmd, width, charwidth, data, put): 127 | shape = { 128 | "p": "+1-0", 129 | "P": "r1-0", 130 | "n": "-0+1", 131 | "N": "f0+1" 132 | }[cmd] 133 | if (cmd == "p" and prev_cmd in "hH") or (cmd == "n" and prev_cmd in "lL"): 134 | shape = shape[1] + shape[1:] 135 | for i in range(width // charwidth): 136 | put(shape[0]) 137 | for j in range(charwidth // 2 - 1): 138 | put(shape[1]) 139 | put(shape[2]) 140 | for j in range(charwidth // 2 - 1): 141 | put(shape[3]) 142 | 143 | def cmd_bus(cmd, prev_cmd, width, charwidth, data, put): 144 | assert(cmd in "=2345") 145 | if len(data) > 0: 146 | contents = data.pop(0) 147 | else: 148 | contents = "" 149 | space = width - 2 150 | contents = contents[:min(len(contents), space)] 151 | contents = "{: ^{}}".format(contents, space) 152 | put("<") 153 | for c in contents: 154 | put(c, raw_char=True) 155 | # for i in range(len(outs)): 156 | # outs[i] += c if i == len(outs) // 2 else " " 157 | put(">") 158 | 159 | def cmd_separator(cmd, prev_cmd, width, charwidth, data, put): 160 | put("|") 161 | 162 | 163 | def cmd_other(cmd, prev_cmd, width, charwidth, data, put): 164 | for i in range(width): 165 | put(cmd.lower()) 166 | 167 | # Create callback for appending some wave state's graphics to an array of strings 168 | def create_put(outs, graphics_map): 169 | def put(c, raw_char=False): 170 | for i in range(len(outs)): 171 | if raw_char: 172 | outs[i] += c if i == len(outs) // 2 else " " 173 | else: 174 | outs[i] += graphics_map[c][i] 175 | return put 176 | 177 | def render_signal(wave, data, charwidth, graphics_map): 178 | if not hasattr(render_signal, "handlers"): 179 | render_signal.handlers = {} 180 | for c in "1hHu0lLd": 181 | render_signal.handlers[c] = cmd_10 182 | for c in "pPnN": 183 | render_signal.handlers[c] = cmd_clk 184 | for c in "=2345": 185 | render_signal.handlers[c] = cmd_bus 186 | for c in "xXzZ": 187 | render_signal.handlers[c] = cmd_other 188 | render_signal.handlers["|"] = cmd_separator 189 | assert(charwidth >=4 and charwidth % 2 == 0) 190 | 191 | outs = [""] * len(graphics_map[" "]) 192 | put = create_put(outs, graphics_map) 193 | if type(data) is str: 194 | data = data.split() 195 | else: 196 | data = data[:] 197 | prev_cmd = "x" 198 | wstream = iter(wave) 199 | 200 | while True: 201 | cmd = next(wstream, None) 202 | if cmd is None: 203 | break 204 | if cmd == ".": 205 | cmd = prev_cmd 206 | if cmd not in render_signal.handlers: 207 | cmd = "x" 208 | width = charwidth 209 | # . refers to command *before* |, so terminate early and don't touch prev_cmd 210 | while cmd != "|": 211 | next_cmd = next(wstream, None) 212 | if next_cmd == ".": 213 | width += charwidth 214 | else: 215 | wstream = itertools.chain([next_cmd], wstream) 216 | break 217 | render_signal.handlers[cmd](cmd, prev_cmd, width, charwidth, data, put) 218 | if cmd != "|": 219 | prev_cmd = cmd 220 | 221 | return outs 222 | 223 | def render_json(src, out, graphics, force_hscale=None): 224 | # Currently we spend ~95% of our time in the JSON parser 225 | obj = json5.loads(src) 226 | jsonschema.Draft6Validator(wavejson_schema).validate(obj) 227 | graphics_map = dict(zip(graphics[0], zip(*graphics[1:]))) 228 | # Find longest signal name, and handle config 229 | max_name_len = 0 230 | for s in obj["signal"]: 231 | if "name" in s: 232 | max_name_len = max(max_name_len, len(s["name"])) 233 | try: 234 | hscale = int(obj["config"]["hscale"] * 2 + 2) 235 | except KeyError: 236 | hscale = 6 237 | if force_hscale is not None: 238 | hscale = force_hscale * 2 + 2 239 | 240 | # Second pass: actually render 241 | for s in (obj["signal"] if "signal" in obj else []): 242 | if len(s) == 0: 243 | print("") 244 | continue 245 | name = s["name"] if "name" in s else "" 246 | name_just = "{: <{}}: ".format(name, max_name_len) 247 | charwidth = int(s["period"] * hscale) if "period" in s else hscale 248 | lead = int(s["phase"] * charwidth) if "phase" in s else 0 249 | lag = max(-lead, 0) 250 | lead = max(lead, 0) 251 | sig_lines = render_signal( 252 | s["wave"] if "wave" in s else "", 253 | s["data"] if "data" in s else [], charwidth, graphics_map) 254 | for i, l in enumerate(sig_lines): 255 | if i == len(graphics_map[" "]) - 1: 256 | out.write(name_just) 257 | else: 258 | out.write(" " * len(name_just)) 259 | out.write(" " * lag) 260 | out.write(l[lead:] + "\n") 261 | 262 | def posint(x): 263 | _x = int(x) 264 | if _x <= 0: 265 | raise TypeError(x) 266 | return _x 267 | 268 | if __name__ == "__main__": 269 | # Preserve newlines in docstring: 270 | parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter) 271 | parser.epilog = __doc__ 272 | parser.add_argument("src", help="WaveJSON source file") 273 | parser.add_argument("-w", "--watch", action="store_true", 274 | help="Watch file, continuously update and redraw") 275 | parser.add_argument("-g", "--graphics", choices=["default", "tiny", "tall"], 276 | default="default", help="Use a different graphics tileset") 277 | parser.add_argument("--hscale", type=posint, 278 | help="Force hscale to a different value than specified in src config.") 279 | args = parser.parse_args() 280 | 281 | graphics = { 282 | "default": graphics_default, 283 | "tiny": graphics_tiny, 284 | "tall": graphics_tall 285 | }[args.graphics] 286 | 287 | if args.watch: 288 | prev_src = None 289 | while True: 290 | ifile = open(args.src) 291 | src = ifile.read() 292 | ifile.close() 293 | if src != prev_src: 294 | print("\033[H\033[J") # VT100 clear command 295 | try: 296 | render_json(src, sys.stdout, graphics, force_hscale=args.hscale) 297 | except Exception: 298 | traceback.print_exc(file=sys.stdout) 299 | print("\nWatching file " + args.src) 300 | print("Ctrl-C to exit") 301 | prev_src = src 302 | time.sleep(0.2) 303 | else: 304 | ifile = open(args.src) 305 | render_json(ifile.read(), sys.stdout, graphics, force_hscale=args.hscale) 306 | --------------------------------------------------------------------------------