简单强大的时序图绘制工具

今天分享一个简单强大的时序图绘制工具——WaveDrom。

WaveDrom

Digital Timing Diagram everywhere

WaveDrom draws your Timing Diagram or Waveform from simple textual description.
It comes with description language, rendering engine and the editor.
WaveDrom editor works in the browser or can be installed on your system.
Rendering engine can be embeded into any webpage.

Wavedrom 是一款功能强大且简单易用的文本转图表工具,被广泛应用于生成时序图、波形图等交互式波形。其特点在于使用简单的文本语法,使得开发人员能够以可视化的方式表示数字信号和时间序列数据。Wavedrom 的优势在于其高度灵活性和可扩展性,使用户能够快速绘制复杂的波形和图表,并轻松与其他文档和代码进行整合。

Wavedrom 的基本语法相对简单,以 JSON 对象或简洁的文本描述波形和时序信息。开发人员只需编写简短的描述,即可生成直观的波形图。通过使用不同的标记和元素,用户可以定义时序图中的信号波形、时钟周期、状态转换等内容。Wavedrom 支持自定义样式和布局,用户可以根据需求美化波形图,增加标签和注释以增强可读性。

时序图是 Wavedrom 最常见的用途之一,通过 Wavedrom 绘制的时序图可以清晰地展示数字信号和数据的传输过程。例如,在硬件设计中,时序图可以用于描述寄存器读写、信号传输和时钟脉冲的情况,从而帮助开发人员更好地理解和分析系统的工作状态。

值得一提的是,Wavedrom 不仅可以独立使用,还可以与 Markdown 等文档格式无缝整合。通过将 Wavedrom 图表代码嵌入文档中,开发人员可以直接在文档中呈现交互式的波形图,提升文档的可读性和交互性。

总体来说,Wavedrom 是一个强大而简便的文本转图表工具,适用于各种应用场景,如硬件设计、软件开发、文档编写等。其简单的语法和可视化的输出,为开发人员提供了一个高效、直观的工具,帮助他们更好地表达和展示数字信号和时间序列数据。

用法和示例

WaveDrom是一个基于JavaScript的应用程序。WaveJSON是一种描述数字时序图的格式。WaveDrom可以直接在浏览器中渲染这些图表。”signal”元素是WaveLane的数组。每个WaveLane都有两个必填字段:”name”和”wave”。

WaveDrom是一个强大的工具,可用于可视化数字信号和时序数据。通过使用WaveJSON格式来描述信号波形,用户可以轻松地定义时序图的各个部分,包括信号波形的名称、周期和状态。WaveDrom支持多个WaveLane,可以同时显示多个信号波形,从而实现更复杂的时序图表。

“wave”字段是WaveLane的关键部分,用于定义信号波形。它由一系列字符组成,包括数字0和1,代表数字信号的高和低电平,以及”.”,代表未定义或无效状态。此外,WaveDrom还支持其他特殊字符,如”p”代表时钟周期,”n”代表一个时钟周期内的半个周期,”|”用于分隔不同的时钟周期。

通过将这些WaveLane组合成一个”signal”数组,并为每个WaveLane指定名称和波形描述,用户可以创建详细且直观的数字时序图。WaveDrom渲染引擎会将这些描述解析并在浏览器中实时绘制出时序图形。

总的来说,WaveDrom提供了一种简单但强大的方式,通过WaveJSON格式和WaveLane的组织,使用户能够在浏览器中生成各种数字时序图。它在硬件设计、嵌入式系统开发、通信协议分析等领域中有着广泛的应用,帮助开发人员更好地理解和分析数字信号的行为和传输过程。

信号

从一个简单的例子开始。下面的代码将创建一个名为”Alfa”的1位信号,并随时间改变其状态。

1
2
3
4
5
{ 
"signal": [
{"name": "Alfa", "wave": "01.zx=ud.23.456789"}
]
}

在”wave”字符串中,每个字符代表一个时间周期。符号”.”将前一个状态延续一个周期。现在,让我们看一下它的图示:

"Alfa" 1位信号

时钟

数字时钟是一种特殊类型的信号。它在每个时间周期内变化两次,可以具有正极性或负极性。此外,它还可以在工作边沿上带有可选的标记。时钟的各个块可以与其他信号状态混合,以创建时钟门控效果。下面是代码和生成的图示:

1
2
3
4
5
6
7
8
9
10
11
12
{ "signal": [
{ "name": "pclk", "wave": "p......." },
{ "name": "Pclk", "wave": "P......." },
{ "name": "nclk", "wave": "n......." },
{ "name": "Nclk", "wave": "N......." },
{},
{ "name": "clk0", "wave": "phnlPHNL" },
{ "name": "clk1", "wave": "xhlhLHl." },
{ "name": "clk2", "wave": "hpHplnLn" },
{ "name": "clk3", "wave": "nhNhplPl" },
{ "name": "clk4", "wave": "xlh.L.Hx" }
]}

渲染后的时钟图示如下:

时钟信号

合在一起

在典型的时序图中,我们通常会包含时钟信号和其他信号(线路)。对于多位信号,我们可以从”data”数组中获取相应的标签。

下面是一个例子,展示了一个包含时钟信号、多位信号和单位信号的典型时序图:

1
2
3
4
5
{ "signal": [
{ "name": "clk", "wave": "P......" },
{ "name": "bus", "wave": "x.==.=x", "data": ["head", "body", "tail", "data"] },
{ "name": "wire", "wave": "0.1..0." }
]}

在这个例子中,我们有三个信号:”clk”代表时钟信号,”bus”代表多位信号,”wire”代表单位信号。

  • 时钟信号”clk”用”P”表示,代表正极性的时钟边沿。
  • 多位信号”bus”用”x.==.=x”表示,其中”x”表示未定义的状态,”=”表示稳定的高电平或低电平,”.”表示未稳定状态。”data”数组包含多位信号的标签,分别是:”head”、”body”、”tail”和”data”。
  • 单位信号”wire”用”0.1..0.”表示,表示在时间周期内信号从低电平切换到高电平再切换回低电平。

渲染后的时序图如下:

典型时序信号

空白和间隙

在时序图中,我们有时需要添加间距和空白,以便更好地组织信号和使时序图更易于阅读。下面是一个带有间距和空白的时序图示例:

1
2
3
4
5
6
7
{ "signal": [
{ "name": "clk", "wave": "p.....|..." },
{ "name": "Data", "wave": "x.345x|=.x", "data": ["head", "body", "tail", "data"] },
{ "name": "Request", "wave": "0.1..0|1.0" },
{},
{ "name": "Acknowledge", "wave": "1.....|01." }
]}

在这个例子中,我们添加了一些间距和空白,以便更好地分隔不同的信号。

  • “clk”信号用”p”表示,代表正极性时钟边沿,后面有3个间距”.”,然后是”|…”,代表3个空白周期。
  • “Data”信号由”x”、”=”、”.”组成,数据数组”data”提供了多位信号各个部分的标签:”head”、”body”、”tail”和”data”。后面有一个间距”|”,然后是”=.”,代表一个空白周期后紧跟着一个稳定高电平。
  • “Request”信号由”0”、”1”、”.”组成,代表低电平、高电平和未定义状态。后面有一个间距”|”,然后是”1.0”,代表一个高电平后紧跟着一个空白周期。
  • 接着有一个空白行,表示两个信号之间的空白。
  • 最后,”Acknowledge”信号由”1”、”.”组成,后面有一个间距”|”,然后是”01.”,代表一个高电平后紧跟着一个低电平和一个空白周期。

渲染后的时序图如下:

带间隙的时序图

在这个时序图中,我们可以看到信号之间的间距和空白,使得时序图更加整齐和易读。通过添加适当的间距和空白,我们可以更好地组织信号和时钟边沿,使时序图更具可视化效果。接下来,我们将继续探索WaveDrom的其他高级功能和实际应用,帮助您更好地运用这个强大的文本转图表工具。

分组

在时序图中,我们可以将WaveLane组合成具有名称的分组,分组表示为数组形式。['分组名称', {...}, {...}, ...] 数组的第一个条目是分组的名称。分组之间还可以嵌套。

下面是一个包含分组的时序图示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{ "signal": [
{ "name": "clk", "wave": "p..Pp..P" },
["Master",
["ctrl",
{ "name": "write", "wave": "01.0...." },
{ "name": "read", "wave": "0...1..0" }
],
{ "name": "addr", "wave": "x3.x4..x", "data": "A1 A2" },
{ "name": "wdata", "wave": "x3.x....", "data": "D1" },
],
{},
["Slave",
["ctrl",
{ "name": "ack", "wave": "x01x0.1x" },
],
{ "name": "rdata", "wave": "x.....4x", "data": "Q2" },
]
]}

在这个例子中,我们使用了分组来组织不同的信号,将它们放在名为”Master”和”Slave”的两个分组中。

  • “clk”信号用”p..Pp..P”表示,代表正极性时钟边沿和负极性时钟边沿。
  • “Master”分组包含了三个子信号,它们分别在名称为”ctrl”的子分组中,表示控制信号”write”和”read”,以及名称为”addr”的信号,”data”数组提供了信号的标签。
  • 接着有一个空白行,表示两个分组之间的空白。
  • “Slave”分组包含了两个子信号,都在名称为”ctrl”的子分组中,表示控制信号”ack”,以及名称为”rdata”的信号,”data”数组提供了信号的标签。

渲染后的时序图如下:

分组时序图

在这个时序图中,我们可以看到不同分组内的信号在不同时间周期内的状态。使用分组可以更好地组织和显示复杂的时序图,帮助我们更好地理解和分析数字信号之间的时序关系。通过WaveDrom的强大功能,我们可以轻松地在时序图中添加分组,使其更具可视化效果和清晰度。

周期和相位

在时序图中,我们可以使用”period”和”phase”参数来调整每个WaveLane的周期和相位。

下面是一个DDR读取事务的时序图示例:

1
2
3
4
5
6
7
{ "signal": [
{ "name": "CK", "wave": "P.......", "period": 2 },
{ "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 },
{ "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 },
{ "name": "DQS", "wave": "z.......0.1010z." },
{ "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" }
]}

在这个例子中,我们使用”period”参数来设置时钟信号”CK”的周期为2个时间周期。这意味着时钟信号每隔2个时间周期变化一次。

同时,我们使用”phase”参数来调整信号”CMD”和”ADDR”的相位。相位的值为0.5,表示信号的波形在时间轴上整体向右偏移了0.5个时间周期。这样做可以让信号在时钟边沿之前或之后发生状态变化。

“CMD”信号表示了DDR读取事务的命令序列,”ADDR”信号表示了地址序列。”data”数组提供了每个部分的标签。

“DQS”信号和”DQ”信号分别表示数据校验和数据信号。其中,”DQS”信号在时钟边沿之前有一个延迟,”DQ”信号在时钟边沿之后有一个延迟。

渲染后的时序图如下:

DDR读时序

在这个时序图中,我们可以看到时钟信号”CK”每隔2个时间周期发生一次变化。”CMD”和”ADDR”信号的波形整体向右偏移了0.5个时间周期,以达到与时钟信号的相位差。”DQS”信号在时钟边沿之前有一个延迟,”DQ”信号在时钟边沿之后有一个延迟。

通过调整”period”和”phase”参数,我们可以更灵活地控制时序图中各个信号的周期和相位,从而更好地表达复杂的数字信号行为。WaveDrom提供了丰富的功能,帮助我们创建详细和直观的数字时序图,用于硬件设计、嵌入式系统开发、通信协议分析等领域。

config{}属性

在时序图中,config{}属性用于控制渲染的不同方面。

箭头

曲线

在WaveDrom中,我们可以使用箭头和曲线来连接不同的信号,以表示它们之间的关联和数据传递。

以下是一些常用的箭头和曲线符号:

  • ~:普通曲线
  • -~:从左到右的曲线箭头
  • <~>:双向曲线箭头
  • <~>:双向曲线箭头
  • ~>:从左到右的直线箭头
  • -~>:从左到右的实心箭头
  • ~->:从右到左的实心箭头

下面是一个示例,展示了如何使用箭头和曲线来连接不同的信号:

1
2
3
4
5
6
7
8
9
10
11
12
{ "signal": [
{ "name": "A", "wave": "01........0....", "node": ".a........j" },
{ "name": "B", "wave": "0.1.......0.1..", "node": "..b.......i" },
{ "name": "C", "wave": "0..1....0...1..", "node": "...c....h.." },
{ "name": "D", "wave": "0...1..0.....1.", "node": "....d..g..." },
{ "name": "E", "wave": "0....10.......1", "node": ".....ef...." }
],
"edge": [
"a~b t1", "c-~a t2", "c-~>d time 3", "d~-e",
"e~>f", "f->g", "g-~>h", "h~>i some text", "h~->j"
]
}

渲染后的时序图如下:

箭头和曲线

折线

在WaveDrom中,我们还可以使用尖锐的线条符号来表示不同信号之间的连接关系。这些尖锐的线条符号可以更直观地展示信号之间的交互和数据传递。

以下是一些常用的尖锐线条符号:

  • -:直线连接
  • -|:从左到右的尖锐连接
  • |->:从右到左的尖锐连接
  • <->:双向尖锐连接
  • <-|>:从左到右的双向尖锐连接
  • |-|:从左到右的尖锐连接(包含短横线)

下面是一个示例,展示了如何使用尖锐线条来连接不同的信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
{ "signal": [
{ "name": "A", "wave": "01..0..", "node": ".a..e.." },
{ "name": "B", "wave": "0.1..0.", "node": "..b..d.", "phase": 0.5 },
{ "name": "C", "wave": "0..1..0", "node": "...c..f" },
{ "node": "...g..h" },
{ "node": "...I..J", "phase": 0.5 },
{ "name": "D", "wave": "0..1..0", "phase": 0.5 }
],
"edge": [
"b-|a t1", "a-|c t2", "b-|-c t3", "c-|->e t4", "e-|>f more text",
"e|->d t6", "c-g", "f-h", "g<->h 3 ms", "I+J 5 ms"
]
}

在这个例子中,我们有6个信号(A、B、C、D)和4个连接节点(e、g、h、I、J),它们之间通过尖锐线条连接起来。每个信号都有相应的波形图,而尖锐线条则通过edge属性来定义连接关系。

例如,b-|a t1表示从信号B到信号A的从左到右的尖锐连接,并在连接上方添加了文本标签”t1”。c-|->e t4表示从信号C到节点e的从左到右的尖锐连接,并在连接上方添加了文本标签”t4”。

渲染后的时序图如下:

箭头和折线

一些代码

在WaveDrom中,我们可以使用JavaScript代码来生成复杂的时序图。这些代码可以用来生成特定的信号和波形,以及自定义时序图的展示效果。

以下是一个示例代码,展示了如何使用JavaScript代码生成一个特定的时序图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function (bits, ticks) {
var i, t, gray, state, data = [], arr = [];
for (i = 0; i < bits; i++) {
arr.push({name: i + '', wave: ''});
state = 1;
for (t = 0; t < ticks; t++) {
data.push(t + '');
gray = (((t >> 1) ^ t) >> i) & 1;
arr[i].wave += (gray === state) ? '.' : gray + '';
state = gray;
}
}
arr.unshift('gray');
return {signal: [
{name: 'bin', wave: '='.repeat(ticks), data: data}, arr
]};
})(5, 16)

在这个例子中,我们使用了一个自执行函数来生成一个包含5个信号的时序图。每个信号的波形图都是根据格雷码(Gray code)生成的,其中bits参数表示信号的位数,ticks参数表示时间周期数。代码使用循环来生成信号的波形图,并将生成的数据存储在data数组中。

最终的时序图包含6个信号,其中一个是用来表示二进制计数的信号(bin),其波形图是一个等号序列。其他5个信号的波形图是根据格雷码生成的,分别对应0位、1位、2位、3位和4位格雷码的波形。

渲染后的时序图如下:

js代码生成时序图