TCML

Timing Chart Markup Language — a small text language for sketching timing charts

日本語

Extension .tc

1.Overview

TCML (Timing Chart Markup Language) is a small text language for describing timing charts. With a simple ASCII-style notation it can express signal Low / High / bus / don't-care levels, transitions, arrows between signals, automatic clock expansion, and more.

This implementation builds on the prior art listed below. The author owes much to these works:

For polished, publication-quality figures use WaveDrom. Charts produced by this tool are not intended for datasheets.

That said, WaveDrom's notation is a bit involved and not the easiest thing to write while you are still thinking. The TCML-style notation pioneered by Prof. Kumagai (Tohoku Gakuin University) and Prof. Takeuchi (University of Tsukuba) is much friendlier in the thinking-and-sketching phase.

This tool was a long-weekend project to scratch the author's own itch from a few years ago, when designing hardware that could have used a notation like this. The author no longer does that kind of design, so honestly it isn't even for the author anymore.

2.Getting started

Each timing line has a signal name on the left and a level string on the right, separated by whitespace.

_ is Low, ~ is High, = is Bus, and ? is don't-care. Feed the source below to the tchart CLI and you get the SVG shown to the right.

@step 8
@slant 2
@bgcolor0 #f8f8ff
@bgcolor1 #f0f0f0

Clock   _~_~_~_~_~_~
"Data
-Data"  =<D0>====X<D1>====X<D2>====
Enable  ____~~~~____
Output  _______~~~~~____
<D0><D1><D2>ClockData-DataEnableOutput

3.Line types

KindLeading charDescription
Comment#Ignored.
Parameter / directive@Either a parameter setting, a row directive (@title / @skip / @clock / @->), or a per-signal attribute (@signal).
Overlay text%Places a text label at the given chart coordinates.
Timing lineAnything elseA signal name followed by a level string.

4.Signal names

Any valid UTF-8 string. Control characters are not permitted (with \n as the only exception inside quoted names). Empty names are rejected.

Multi-line names

Surround a name with " to embed newlines:

"Data
Bus" =<D0>====X<D1>====
  • The opening " must sit at the start of the line.
  • The closing " is followed by whitespace and then the level string.

Escapes (only inside "...")

SequenceMeaning
\"Literal "
\nNewline
\\Literal \

5.Level symbols

SymbolMeaningShape
_LowSingle line at the bottom
~HighSingle line at the top
-HiZDashed line in the middle
=BusTwo parallel rails
?Don't careFilled hatch with a line at the previous level's position

Example

@fontsize 14
@step 12
@slant 2

Low       ________
High      ~~~~~~~~
HiZ       --------
Bus       =<A>====
DontCare  ___?????
<A>LowHighHiZBusDontCare

Repeated identical symbols (__, ~~, ??, …) are merged into one segment by the parser.

6.Auxiliary symbols

SymbolMeaningx advance
:Gap (one unit of whitespace, breaks signal continuity)step
XBus value change (BusCross)step (cross part slant + body part step - slant; or body only of width step when the cross is omitted at the start of a signal)
?Don't-care marker (paints surrounding bus segment as don't-care)0
|Vertical guide line0
[ / ]Highlight start / end0
@{name} / @NAnchor0

X is composed of two parts: a cross transition followed by a body (one bus unit at the new value). It occupies the width of one level char (= step); the cross gets slant, the body gets step - slant. When there is no preceding bus signal (e.g. at the start of a signal line) the cross is omitted and the body alone takes the full step.

? is a zero-width marker. It paints the surrounding contiguous level segment as a don't-care region.

Example

@fontsize 14
@step 12
@slant 2

Gap        ____:____
BusX       =<A>====X<B>====
Guide      _~__|__~_
Highlight  __[~~~~]__
<A><B>GapBusXGuideHighlight

7.Don't care ?

The line position drawn inside a don't-care region is determined by the preceding level (resolved at parse time).

Preceding anchorLine drawn inside the region
_ LowBottom
~ HighTop
- HiZMiddle
= BusBus envelope (top + bottom)
XBus envelope

Don't care fill shapes inside a bus

? in a bus context (e.g. =?=) is filled with a polygon shaped to fit the surrounding waveform boundaries. It never extends past the full signal_box height.

@fontsize 14
@step 12
@slant 3

@title "DontCareAlongBus shapes"

# `@dontcare_color` can be flipped between rows to change the hatch color.
# (Cycle through #bbb / #c00 / #06c / #080 below.)

# Default color (#bbb)
=?=         ====?====

@dontcare_color #c00
# Both sides Low: /=\ shape (red)
_=?=_       ____====?====____

# Both sides High: \=/ shape (red)
~=?=~       ~~~~====?====~~~~

@dontcare_color #06c
# Low only on the left (left slant, right vertical) (blue)
_=?=        ____====?====

# Low only on the right (left vertical, right slant) (blue)
=?=_        ====?====____

@dontcare_color #080
# Mixed High / Low on either side (green)
~=?=_       ~~~~====?====____

# HiZ on the left, bus continues on the right (green)
-=?=        ----====?====

# Bus + label (green)
=?=L        ==<A>==?====

# Bus (green)
=?=X        ==X==?==X==
<A>=?=_=?=_~=?=~_=?==?=_~=?=_-=?==?=L=?=XDontCareAlongBus shapes

Error conditions for ?

  • If a signal line begins with ?, or only zero-width elements (:, |, [, ], @{...}, @N) precede it, the parser raises ParseError::DontCareWithoutAnchor.
  • For example: foo ?==, bar ???, baz ?_~, qux :?_~, quux @{a}?_~ are all errors.

X / X? patterns

PatternInterpretation
=X=Bus(1) + X (cross + body, new value) + Bus(1)
=X?Bus(1) + X (cross + body) + ? (X body becomes don't-care)
=X?=Don't-care region = X body + the trailing = (2 units total)
=?X=Don't-care region = the leading = (1 unit); the X body and trailing = are bus at the new value
=X?X=Don't-care region = X1 body of 1 unit; the polygon spans X1 cross-midpoint to X2 cross-midpoint (hexagonal)
~X_High + BusOpen + Bus(1, X body) + BusClose + Low (X is valid even when the neighbours are non-bus)
XXXXA run of X's at the start of a signal. The first X drops its cross; subsequent X's behave as ordinary BusCross.
?X=Error (leading ?)

8.Labels in level strings <...>

Attach a text label to a level segment or a transition.

@step 10
@slant 2

BusLabel   =<A>====X<B>====X<C>====
LowLabel   ____<L>____
HighLabel  ~~~~<H>~~~~
HiZLabel   ----<Z>----
<A><B><C><L><H><Z>BusLabelLowLabelHighLabelHiZLabel
  • Labels cannot contain control characters (other than \n).
  • Escape with \< for <, \> for >, and \\ for \.

9.Anchors @{name} and arrows @->

An anchor is a zero-width marker that records a particular point on a waveform. An @-> line connects two anchors as an arrow. Anchors alone draw nothing; a line is rendered only when an arrow references them as an endpoint.

Minimal example

@fontsize 14
@step 10
@slant 2

Req   ___@{s}~~~~~~
Ack   ________@{a}~~~

@-> (@{s}, @{a}, red) request
ReqAckrequest

Anchor rules

  • Use @{name} or @N (a positive integer). Anchors are zero-width and do not advance x.
  • Named and numbered anchors live in separate namespaces (@{1} and @1 are different).
  • Defining the same id twice yields ParseError::DuplicateAnchor.
  • The character class for AnchorName is [A-Za-z_][A-Za-z0-9_-]*.
  • Anchors are not transparent for the ? lookup: an anchor by itself does not become the level used to determine a following ?.

Arrow syntax

@-> (<from>, <to> [, <attribute>, ...]) [<text>]

CategoryExamplesDisambiguation
Colorred, #f0f, #ff8800Anything Color::parse accepts
Width2, 2px, 1.5pxNumeric (the px suffix is optional)
Stylesolid, dashed, dottedKeyword
Arrowheadhead=end, head=both, head=nonehead= prefix

Defaults: color = signal_color, width = 1px, style = solid, arrowhead = end. Attributes can appear in any order; specifying two attributes from the same category raises ParseError::DuplicateArrowAttribute.

Combining color, width, style, and forward references

@step 10
@slant 2
@bgcolor0 #f8f8ff
@bgcolor1 #f0f0f0

Req     ___@{s}~~~~@{e}___
Ack     _______@{a}~~~~

@-> (@{s}, @{a}) request
@-> (@{e}, @{a}, dashed) ack

Bus     =<A>====@1X<B>====@2X<C>====@3
Flag    ____@4~~~~@5___

@-> (@1, @4, red, 2px) A
@-> (@2, @5, blue, head=both) B
@-> (@3, @4, green, dotted) forward ref

Data    __@{d1}~~~~@{d2}___
Out     ___@{o1}~~~~@{o2}__

@-> (@{d1}, @{o1}, #ff8800, solid) start
@-> (@{d2}, @{o2}, dashed, head=none) end
<A><B><C>ReqAckBusFlagDataOutrequestackABforward refstartend
  • Forward references are allowed; an @-> line may appear anywhere in the file.
  • Style values such as font or signal_color are taken from the local settings in effect at the position of the @-> line.
  • Referring to an undefined anchor produces ParseError::UndefinedAnchor.
  • The renderer does not auto-route arrows to avoid overlap (that's the author's responsibility).

10.Parameters

Syntax: @<parameter-name> <value>. Names are case-insensitive and treat - and _ as equivalent (@fontsize, @font-size, and @FONT_SIZE are all the same).

Global parameters (cannot be changed mid-file)

NameDefaultDescription
fontsize14Font size (px). The layout reference unit.
lineheight1.2Multiplier for waveform row height (= fontsize × lineheight).
capwidth0Width of the signal-name column (px). Auto-computed when 0.
namepad8Gap between the right edge of the name and the left edge of the waveform (px).
scale1.0Overall SVG scale factor.
page-margin10Fixed margin around the chart (px).
bgcolor0noneBackground colour for even rows.
bgcolor1noneBackground colour for odd rows.

Local parameters (may be changed mid-file; new value applies from that point onward)

NameDefaultDescription
step10X advance per level char (px). When there is a preceding transition, that transition is rendered as the leading slant portion of this step. step <= slant is a parse error.
slant2Transition width (px). Set to 0 for vertical edges. Applies to SingleEdge / BusOpen / BusClose / BusCross alike.
h_space4.0Total vertical padding for a signal row (px). The legacy name signal_gap is also accepted.
fontsans-serifFont family. Quote with " if it contains spaces; comma-separated lists are honoured as fallback chains.
signal_colorblackSignal line colour.
signal_width1Signal line width (px).
guide_colorredVertical guide line colour.
guide_width0.6Vertical guide line width (px).
bgnoneBackground colour for the next row only (local override).
highlight_stylefill="#ff8" stroke="none"Highlight rectangle style.
dontcare_color#bbbHatch colour for ?. A single colour value such as @dontcare_color #c00 applies from that point onward; redeclare to switch again.
titlealigncenterHorizontal alignment for @title (center / left / right).
clockmark_position0.5Position of the clock triangle marker's apex along the edge (0.0..=1.0).
clockmark_height5Clock marker height (px).
clockmark_width4Clock marker base width (px).
clockmark_colorInherits signal_colorClock marker fill colour. Inherits the current signal_color when unset.
overline_gap2Gap between the overline and the cap-top of the signal name (px).
overline_thickness1Overline thickness (px).

11.Background colour

The global @bgcolor0 / @bgcolor1 alternate row colours, while the local @bg overrides only the next row.

@fontsize 14
@step 10
@bgcolor0 #eef6ff
@bgcolor1 #fff4ee

A    _~_~_~_~
B    ~_~_~_~_

@bg #ffe4cc
Local _~_~_~_~

After _~_~_~_~
ABLocalAfter
  • @bg is consumed by the next row (Signal / Skip / Title) and then resets. @bg none discards the pending value explicitly.
  • Rows that have a pending @bg do not also paint bgcolor0/1.
  • Skip and Title rows are excluded from the even/odd counter for bgcolor0/1.

@highlight_style / @dontcare_color

@highlight_style fill="#8f8" stroke="green" stroke-width="1"
@dontcare_color #c00

@highlight_style takes whitespace-separated key="value" SVG attributes. @dontcare_color takes a single colour value (same notation as @bgcolor0 &c.) and switches the hatch colour from then on.

12.@skip — blank rows

@step 10
@slant 2
@bgcolor0 #f8f8ff
@bgcolor1 #f0f0f0

Clock   _~_~_~_~_~_~

@skip(1)

Data    =<A>====X<B>====

@skip(2)

Control ____~~~~____

@skip(0.5)

Flag    ~~____~~____

@skip(20px)

Out     _~~~~___~~~~
<A><B>ClockDataControlFlagOut
  • A bare number is interpreted as lh (line-height units).
  • Append px to specify pixels instead.
  • Negative or unparseable values raise ParseError::InvalidSkipAmount.
  • Zero is allowed, but no SkipRow is emitted.

13.@title — title rows

@step 10
@slant 2
@bgcolor0 #f8f8ff
@bgcolor1 #f0f0f0

@title "Default Center Title"

A     _~_~_~_~

@titlealign left
@title "Left Aligned"

B     _~_~_~_~

@titlealign right
@title "Right Aligned"

C     _~_~_~_~

@titlealign center
@title "Back to Center"

D     _~_~_~_~
ABCDDefault Center TitleLeft AlignedRight AlignedBack to Center
  • The argument string is rendered as a title row. Multi-line titles are quoted with "..." following the same escape rules as signal names.
  • @title may appear multiple times in a single file.
  • Title rows are excluded from the even/odd counter for bgcolor0/1.
  • @titlealign takes center / left / right and applies to every @title emitted after it.

14.@clock — automatic clock expansion

Treats the next signal row as a clock. If the body is empty or partial it is padded out from the last state, and triangle markers are placed automatically on the rising / falling edges.

@fontsize 14
@step 10
@slant 2

@clock(pos)
ClkPos  _~_~_~

@clock(neg)
ClkNeg  _~_~_~

@clock(both)
ClkBoth _~_~_~

@clock(pos, _=2, ~=1)
ClkWide
ClkPosClkNegClkBothClkWide

Syntax: @clock(<edge> [, _=<n>] [, ~=<n>] [, start=<low|high>] [, mark_position=<f32>] [, mark_height=<px>] [, mark_width=<px>] [, mark_color=<color>])

AttributeValueDescription
edgepos / neg / both / noneWhere to place triangle markers (required).
_=<n>Positive integerTime units in Low (default 1).
~=<n>Positive integerTime units in High (default 1).
startlow / highInitial phase (default low).
mark_position0.0..=1.0Position of the marker's apex.
mark_heightPositive valueMarker height.
mark_widthPositive valueMarker base width.
mark_colorColourFill colour (inherits signal_color when unset).
  • Attributes may appear in any order. Keys are case-insensitive, and - / _ are equivalent.
  • Clock markers are completely independent of @-> arrows; clock-derived markers never leak into Arrow output.

15.@signal — per-signal attributes

Applies an attribute to the next signal row only. The attribute resets immediately after that row. Currently only overline is provided.

@step 10
@slant 2
@bgcolor0 #f8f8ff
@bgcolor1 #f0f0f0

@signal(overline)
nReset    ~~~~__~~~~

@signal(overline)
nWrite    ~~__~~__~~

Enable    ____~~~~____

@signal(overline)
"nChip
Enable"   ~~~~____~~~~

Out       __~~~~____~~
nResetnWriteEnablenChipEnableOut
  • overline: draws an overline above the signal name (active-low convention). For multi-line names, only the topmost line gets the overline, with width fixed to the longest line.
  • Position and thickness are controlled by @overline_gap / @overline_thickness. The SVG output is an explicit <line> element rather than a text-decoration attribute.

16.% — overlay text rows

% <x> <y> <text>

Places a text overlay at the given pixel coordinates. The origin is the chart's top-left corner.

17.key=value rules

Every = attribute in TCML (the options to @clock(...), head= for @->, @highlight_style, …) follows the same rules.

  • Whitespace around = is optional: key=value, key =value, key= value, and key = value are equivalent.
  • Both key and value have leading/trailing whitespace stripped before evaluation.
  • Use "..." when the value needs to contain spaces.

18.Error reference

Every error is reported with a line and column.

ErrorCause
ParseError::DontCareWithoutAnchorNo level anchor precedes ?.
ParseError::DanglingBusTransitionX is not surrounded by Bus / ?, or appears alone at the start of a row.
ParseError::InvalidSkipAmount@skip argument is negative or unparseable.
ParseError::DuplicateAnchorThe same AnchorId is defined more than once.
ParseError::UndefinedAnchor@-> references an undefined anchor.
ParseError::DuplicateArrowAttribute@-> has multiple attributes from the same category.
ParseError::UnknownArrowAttribute@-> contains an attribute token that cannot be classified.
ParseError::UnknownParameterUnknown @<name> parameter.
ParseError::InvalidColorColor::parse failed.
ParseError::InvalidSignalNameSignal name contains a control character or is empty.
ParseError::InvalidLevelCharUnknown level character.
ParseError::UnclosedHighlight[ is not closed by ].
ParseError::UnopenedHighlightEnd] has no matching [.
ParseError::UnclosedQuote"..." is not closed.
ParseError::UnclosedLabel<...> is not closed.
ParseError::ClockBodyConflictThe body of the row following @clock contradicts the clock expansion.

19.Handing off to WaveDrom

tchart is a small tool for jotting down timing diagrams alongside your thoughts while editing. When the diagrams need to ship — datasheets, slides, papers — please switch to WaveDrom, a real timing-chart tool. tchart is not a true companion for the final stage of your work.

The work you have already done is not wasted. The tchart wavedrom subcommand converts a .tc file to WaveJSON. The mapping is approximate, not exhaustive: only constructs WaveDrom can render (signal levels, bus data, clocks, anchors / arrows, …) are translated, and styling that has no WaveDrom equivalent (background colour, fonts, overlines, highlights, …) is silently dropped. Carry your TCML thinking into WaveDrom and continue the journey there.

tchart wavedrom chart.tc            # → chart.json
tchart wavedrom chart.tc -o out.json

Three example cases are shown below. Each lists the TCML input, the tchart svg rendering, the tchart wavedrom WaveJSON output, and the wavedrom-cli rendering of that JSON, side by side. The WaveJSON shown is generated by tchart-core/examples/wavedrom_help_demo.rs.

Case 1: breaking continuity with Gap (:)

TCML's : Gap is a one-unit blank that breaks signal continuity. It maps to WaveDrom's |, which extends the previous level by one unit while drawing a visual break. The pictures are not identical, but the “continuity is broken here” semantic is preserved.

TCML input

@title 連続性の断絶
sig1   ~_~_:~_~_
sig2   ====:====

TCML rendering (tchart svg)

@title 連続性の断絶 sig1 ~_~_:~_~_ sig2 ====:==== sig1sig2連続性の断絶

WaveJSON output (tchart wavedrom)

{
  "head": { "text": "連続性の断絶" },
  "signal": [
    { "name": "sig1", "wave": "1010|1010" },
    { "name": "sig2", "wave": "=...|=..." }
  ]
}

WaveDrom rendering (wavedrom-cli)

連続性の断絶sig1sig2

Case 2: changing bus values (X = BusCross)

An X splits the bus segment, so each side of X becomes its own = segment in the WaveDrom output, and a corresponding data entry is generated for each. This expresses the typical “bus value changes here” pattern.

TCML input

@title バス値の切替
clk    ~_~_~_~_
data   ==A=X=B=X=C

TCML rendering (tchart svg)

@title バス値の切替 clk ~_~_~_~_ data ==A=X=B=X=C ABCclkdataバス値の切替

WaveJSON output (tchart wavedrom)

{
  "head": { "text": "バス値の切替" },
  "signal": [
    { "name": "clk",  "wave": "10101010" },
    { "name": "data", "wave": "=..=..=.",
      "data": ["A", "B", "C"] }
  ]
}

WaveDrom rendering (wavedrom-cli)

バス値の切替clkdataABC

Case 3: anchors @{name} and inter-signal arrows @->

Embed zero-width anchors (e.g. @{request}) in the waveform and connect any two with @->. Spread anchors across multiple signals to express dependencies that span signals (e.g. request → ack → complete). In the WaveDrom output, anchors map to per-signal node strings (same length as the wave; an anchor character at the anchor position, otherwise .) and a top-level edge array.

Some information is lost in the conversion. WaveDrom node identifiers are single characters (az, AZ, at most 52 of them), so any descriptive anchor name on the TCML side (such as @{request}) is replaced by a single character assigned in order of appearance (e.g. a) and the original name cannot be recovered. If there are more than 52 anchors, the surplus arrows are dropped and a warning is printed to stderr. Arrow colour, width, and style nuances are also dropped (WaveDrom has no equivalent); only the existence of the arrow, its endpoints, and its label survive.

TCML input

@title 信号間にまたがる矢印
clk    ~_~_~_~_
req    _@{request}~~~~~~_
ack    ___@{ack_received}~~~~_
done   ______@{complete}~_
@-> (@{request}, @{ack_received}) ack
@-> (@{ack_received}, @{complete}) done

TCML rendering (tchart svg)

@title 信号間にまたがる矢印 clk ~_~_~_~_ req _@{request}~~~~~~_ ack ___@{ack_received}~~~~_ done ______@{complete}~_ @-> (@{request}, @{ack_received}) ack @-> (@{ack_received}, @{complete}) done clkreqackdone信号間にまたがる矢印ackdone

WaveJSON output (tchart wavedrom)

{
  "head": { "text": "信号間にまたがる矢印" },
  "signal": [
    { "name": "clk",  "wave": "10101010" },
    { "name": "req",  "wave": "01.....0", "node": ".a......" },
    { "name": "ack",  "wave": "0..1...0", "node": "...b...." },
    { "name": "done", "wave": "0.....10", "node": "......c." }
  ],
  "edge": ["a->b ack", "b->c done"]
}

WaveDrom rendering (wavedrom-cli)

信号間にまたがる矢印clkreqackdoneackdoneabc