|
| 1 | +- Start Date: (fill in with date at which the RFC is merged, YYYY-MM-DD) |
| 2 | +- RFC PR: [amaranth-lang/rfcs#0000](https://github.com/amaranth-lang/rfcs/pull/0000) |
| 3 | +- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) |
| 4 | + |
| 5 | +# Structured VCD Output |
| 6 | + |
| 7 | +## Summary |
| 8 | +[summary]: #summary |
| 9 | + |
| 10 | +When generating VCD files, use the `scope` keyword to distinguish aggregate signals. |
| 11 | + |
| 12 | +## Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +These changes are intended to make it easier for waveform viewers to recover information about aggregate signals from VCD files. |
| 16 | +This is partially motivated by an issue with VCD parsing that occurs in Surfer (see [ekiwi/wellen#36](https://github.com/ekiwi/wellen/issues/36)). |
| 17 | +Ideally, this leads to a better user experience when inspecting simulated Amaranth designs with a waveform viewer. |
| 18 | + |
| 19 | +## Guide-level explanation |
| 20 | +[guide-level-explanation]: #guide-level-explanation |
| 21 | + |
| 22 | +This RFC describes changes to the way Amaranth generates a VCD file. |
| 23 | +When reading a VCD file, waveform viewers like [Surfer](https://surfer-project.org/) and [GTKWave](https://gtkwave.sourceforge.net/) support use of the `scope` keyword for organizing variables into groups. |
| 24 | +Currently, when writing a VCD file, Amaranth uses the `module` scope to distinguish between signals belonging to different modules. |
| 25 | +For instance: |
| 26 | + |
| 27 | +``` |
| 28 | +$scope module top $end # top |
| 29 | + $var wire 1 <id> clk $end # top.clk |
| 30 | + $var wire 1 <id> rst $end # top.rst |
| 31 | + $var wire 32 <id> in $end # top.in |
| 32 | + $var wire 32 <id> out $end # top.out |
| 33 | + $scope module submodule $end # top.submodule |
| 34 | + $var wire 1 <id> clk $end # top.submodule.clk |
| 35 | + $var wire 1 <id> rst $end # top.submodule.rst |
| 36 | + $var wire 5 <id> in $end # top.submodule.in |
| 37 | + $var wire 5 <id> out $end # top.submodule.out |
| 38 | + $upscope $end |
| 39 | +$upscope $end |
| 40 | +``` |
| 41 | + |
| 42 | +However, it does *not* attempt to use scopes for organizing the members of aggregate signals with array-like or struct-like datatypes. |
| 43 | +Instead, when creating VCD variables for aggregate signals, members are distinguished only by appending to the name of the parent signal. |
| 44 | +For instance, a signal `top.submodule.my_array` with the type `ArrayLayout(unsigned(32), 4)` is currently represented as: |
| 45 | + |
| 46 | +``` |
| 47 | +$scope module top $end # top |
| 48 | + ... |
| 49 | + $scope module submodule $end # top.submodule |
| 50 | + ... |
| 51 | + $var wire 128 <id> my_array $end # top.submodule.my_array |
| 52 | + $var wire 32 <id> my_array[0] $end # top.submodule.my_array[0] |
| 53 | + $var wire 32 <id> my_array[1] $end # top.submodule.my_array[1] |
| 54 | + $var wire 32 <id> my_array[2] $end # top.submodule.my_array[2] |
| 55 | + $var wire 32 <id> my_array[3] $end # top.submodule.my_array[3] |
| 56 | + $upscope $end |
| 57 | +$upscope $end |
| 58 | +``` |
| 59 | + |
| 60 | +This is not sufficient for explicitly conveying the relationship between aggregate signals and their members to a waveform viewer. |
| 61 | +In this case, `my_array` does not explicitly *contain* its members, and waveform viewers may only *infer* this relationship by attempting to recover it from the names. |
| 62 | + |
| 63 | +This RFC proposes the use of `scope vhdl_array` and `scope vhdl_record` to pass information about these relationships to a waveform viewer. |
| 64 | +After this change, the VCD output above would become: |
| 65 | + |
| 66 | +``` |
| 67 | +$scope module top $end # top |
| 68 | + ... |
| 69 | + $scope module submodule $end # top.submodule |
| 70 | + ... |
| 71 | + $comment Flattened representation of 'my_array' $end |
| 72 | + $var wire 128 <id> my_array $end # top.submodule.my_array |
| 73 | +
|
| 74 | + $comment Hierarchical representation of 'my_array' members $end |
| 75 | + $scope vhdl_array my_array $end # top.submodule.my_array |
| 76 | + $var wire 32 <id> 0 $end # top.submodule.my_array.0 |
| 77 | + $var wire 32 <id> 1 $end # top.submodule.my_array.1 |
| 78 | + $var wire 32 <id> 2 $end # top.submodule.my_array.2 |
| 79 | + $var wire 32 <id> 3 $end # top.submodule.my_array.3 |
| 80 | + $upscope $end |
| 81 | +
|
| 82 | + $upscope $end |
| 83 | +$upscope $end |
| 84 | +``` |
| 85 | + |
| 86 | +## Reference-level explanation |
| 87 | +[reference-level-explanation]: #reference-level-explanation |
| 88 | + |
| 89 | +Currently, Amaranth represents aggregate signals in the VCD by appending the names of elements/members to the name of the signal. |
| 90 | +When simulator output is written to a VCD file, we intend that signals with aggregate datatypes are explicitly given their own scope and split into multiple VCD variables. |
| 91 | +We propose the following conventions: |
| 92 | + |
| 93 | +- The `module` scope defines a group of signals belonging to the same `Module` |
| 94 | +- The `vhdl_array` scope defines a group of signals belonging to an array (such as a signal with `ArrayLayout`) |
| 95 | +- The `vhdl_record` scope defines a structured group of signals (such as a signal with `StructLayout`) |
| 96 | + |
| 97 | +> **NOTE**: (Implementation goes here) |
| 98 | +> ... |
| 99 | +
|
| 100 | + |
| 101 | +### Expected Output: Array-like |
| 102 | + |
| 103 | +```python |
| 104 | +from amaranth import * |
| 105 | +from amaranth.lib.data import * |
| 106 | + |
| 107 | +foo = Signal(ArrayLayout(unsigned(32), 4)) |
| 108 | +``` |
| 109 | + |
| 110 | +For `ArrayLayout`, each element in the array should become a VCD variable whose name is integer array index. |
| 111 | +In this case, the signal `foo` is split into `foo.0`, `foo.1`, `foo.2`, and `foo.3`: |
| 112 | + |
| 113 | +``` |
| 114 | +$scope vhdl_array foo $end |
| 115 | + $var wire 32 <id> 0 $end |
| 116 | + $var wire 32 <id> 1 $end |
| 117 | + $var wire 32 <id> 2 $end |
| 118 | + $var wire 32 <id> 3 $end |
| 119 | +$upscope $end |
| 120 | +``` |
| 121 | + |
| 122 | +### Expected Output: Struct-like |
| 123 | + |
| 124 | +```python |
| 125 | +from amaranth import * |
| 126 | +from amaranth.lib.data import * |
| 127 | + |
| 128 | +foo = Signal(StructLayout({ |
| 129 | + "a": unsigned(1), |
| 130 | + "b": unsigned(4), |
| 131 | + "c": unsigned(32), |
| 132 | +})) |
| 133 | +``` |
| 134 | + |
| 135 | +For `StructLayout`, each member should become a VCD variable with the member name. |
| 136 | +In this case, the signal `bar` is split into `bar.a`, `bar.b`, and `bar.c`: |
| 137 | + |
| 138 | +``` |
| 139 | +$scope vhdl_record bar $end |
| 140 | + $var wire 1 id a $end |
| 141 | + $var wire 4 id b $end |
| 142 | + $var wire 32 id c $end |
| 143 | +$upscope $end |
| 144 | +``` |
| 145 | + |
| 146 | +### Expected Output: Nested Aggregates |
| 147 | + |
| 148 | +```python |
| 149 | +from amaranth import * |
| 150 | +from amaranth.lib.data import * |
| 151 | + |
| 152 | +class MyStruct(Struct): |
| 153 | + a: unsigned(1) |
| 154 | + b: unsigned(4) |
| 155 | + c: ArrayLayout(unsigned(32), 4), |
| 156 | + |
| 157 | +# A struct-like signal |
| 158 | +foo = Signal(MyStruct) |
| 159 | + |
| 160 | +``` |
| 161 | + |
| 162 | +For signals with nested aggregate types, the scopes are nested in the same way that `scope module` is already used for nested modules. |
| 163 | +In this case, the signal `foo` is split like this: |
| 164 | + |
| 165 | +``` |
| 166 | +$scope vhdl_record bar $end |
| 167 | + $var wire 1 id a $end |
| 168 | + $var wire 4 id b $end |
| 169 | + $scope vhdl_array c $end |
| 170 | + $var wire 32 id 0 $end |
| 171 | + $var wire 32 id 1 $end |
| 172 | + $var wire 32 id 2 $end |
| 173 | + $var wire 32 id 3 $end |
| 174 | + $upscope $end |
| 175 | +$upscope $end |
| 176 | +``` |
| 177 | + |
| 178 | +## Drawbacks |
| 179 | +[drawbacks]: #drawbacks |
| 180 | + |
| 181 | +- VCD is not a particularly efficient format, and this adds even more bytes to generated VCD files. |
| 182 | + |
| 183 | +- This breaks any existing compatibility with waveform viewers that do not handle the `vhdl_record` and `vhdl_array` scope types. |
| 184 | + Since these are not part of the VCD specification, some waveform viewers may fail to handle this. |
| 185 | + |
| 186 | +## Rationale and alternatives |
| 187 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 188 | + |
| 189 | +- The choice of `scope vhdl_array` and `scope vhdl_record` are suggested due to known compatibility with Surfer and GTKWave. |
| 190 | + However, note that these are not part of the VCD specification (which is somewhat old and under-defined). |
| 191 | + |
| 192 | +- One alternative would be to include support for a different waveform format that has better-defined support for variables with composite datatypes. |
| 193 | + |
| 194 | +## Prior art |
| 195 | +[prior-art]: #prior-art |
| 196 | + |
| 197 | +- As far as I can tell, use of the `vhdl_record` and `vhdl_array` scope types comes from the [ghdl/ghdl](https://github.com/ghdl/ghdl) simulator. |
| 198 | +- [verilator/verilator](https://github.com/verilator/verilator) makes no attempt to use these scope types |
| 199 | +- [steveicarus/iverilog](https://github.com/steveicarus/iverilog) makes no attempt to use these scopes types |
| 200 | + |
| 201 | +## Unresolved questions |
| 202 | +[unresolved-questions]: #unresolved-questions |
| 203 | + |
| 204 | +- Should we continue to include the "flattened" [pure bit-vector] representation of aggregate signals in the VCD? |
| 205 | +- Does this feature need to be gated/opt-in by default? |
| 206 | + |
| 207 | +## Future possibilities |
| 208 | +[future-possibilities]: #future-possibilities |
| 209 | + |
| 210 | +Since this adds more overhead to VCD output, it's worth mentioning that VCD may be unsuitable for testing very large designs. |
| 211 | +This RFC may be a stepping stone to considering support for alternative waveform formats in the future. |
| 212 | + |
0 commit comments