Skip to content

Commit 2128774

Browse files
committed
Extend port-dml documentation
1 parent 8cd62db commit 2128774

File tree

1 file changed

+126
-16
lines changed

1 file changed

+126
-16
lines changed

doc/1.4/port-dml.md

Lines changed: 126 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,39 @@
55

66
# Porting DML 1.2 to DML 1.4
77
When porting a DML 1.2 file to DML 1.4, most differences can be taken
8-
care of by the automatic conversion script `port-dml`. The
9-
script relies on the `dmlc` compiler to produce information on
10-
what changes need to be applied.
8+
care of by the automatic conversion script `port-dml`.
119

12-
The easiest way to invoke `port-dml` is through a wrapper
13-
script `port-dml-module`. The script ports all devices in one
14-
SIMICS module, and all imported files it depends on. The scripts works by
15-
invoking `make` and `port-dml`, and prints how they are invoked, which
16-
is instructive for understanding how to use `port-dml`
17-
standalone.
10+
The porting process consists of two phases: An *analysis phase*, where the
11+
`dmlc` compiler is run to produce a *tag file*, which describes the required
12+
changes; this is followed by a *conversion phase* where the `port-dml`
13+
script reads the tag file and applies changes.
1814

19-
The `port-dml-module` script works well for converting most DML
20-
devices, but has some limitations when it comes to common code that is
21-
shared between many devices. In particular, if some parts of common
22-
code is unused, e.g. if a provided template is never instantiated,
23-
then it may skip some conversions. For this reason, it can be better
24-
to use `port-dml` directly for common code.
15+
The easiest way to perform conversion is through the wrapper script
16+
`port-dml-module`. This script automatizes the porting process by first running an
17+
analysis step using `make` to build one or more modules, and then running a
18+
conversion phase by applying `port-dml` on the DML files that were compiled.
2519

20+
During the analysis phase, if some code is shared between multiple modules,
21+
then it often happens that some unused code is discarded by the compiler before
22+
it has been fully analysed. This happens in particular for unused templates and
23+
top-level `if` statements. Such unused code will only get basic syntactic
24+
conversions, like transforming `parameter` to `param`, and miss conversions
25+
that depend on semantic analysis, like transforming `read_access`
26+
to `read_register`. This
27+
can be solved by including multiple modules in the analysis step; the
28+
conversion phase for common code will then combine the analyses of all modules,
29+
and utilize semantic analysis from any code paths that is active in *any* of the
30+
included modules.
2631

32+
The `port-dml-module` script relies on
33+
rather crude heuristics which often may be
34+
incorrect; for this reason, the script also prints exactly how it invokes the
35+
lower level `make` and `port-dml` commands. This allows each step to be
36+
individually rerun manually with tweaked settings.
37+
38+
The `port-dml` script can also be used directly without `port-dml-module`; this
39+
mode of operation has a steeper learning curve but provides greater control
40+
which can be advantageous when porting a large code base.
2741

2842
## Using the port-dml script
2943
In order to port a DML 1.2 file to DML 1.4, first pass the <code>-P
@@ -54,7 +68,7 @@ If you build your device from a Simics project, you can use the variable
5468
be set to the absolute path of a file; `make` will pass that in the
5569
-P flag to `dmlc`. Note that if you want to re-run an analysis,
5670
then you need to first run <code>make clean-<em>module</em></code> to force
57-
DMLC to re-run on all devices in the SIMICS module.
71+
DMLC to re-run on all devices in the Simics module.
5872

5973
If parts of the device source code is unused, e.g. if a template is
6074
never instantiated within the device, then DMLC can not perform a full
@@ -75,3 +89,99 @@ tags](changes-auto.html) and apply the change manually.
7589

7690
</div>
7791

92+
## Porting common code still used from DML 1.2 code
93+
94+
When porting a large code base to DML 1.4, you likely want to work
95+
incrementally, porting some devices at a time. It can then happen that some of
96+
your newly ported 1.4 files share common code with devices that are still in DML 1.2.
97+
98+
It is not allowed to import a DML 1.2 file from a DML 1.4 device, but a DML 1.2 device may import a DML 1.4 file with some caveats. Thus, any code common between DML 1.2 and 1.4 must be ported to 1.4 before any device can be converted.
99+
There are two possible strategies for this: Either convert the common file in place,
100+
or duplicate it into separate 1.2 and 1.4 versions.
101+
102+
### Keep a separate DML 1.4 copy
103+
104+
After letting conversion tools convert `foo.dml` to DML 1.4, you can rename the
105+
converted file into `foo-dml14.dml`, and restore the original 1.2 version as
106+
`foo-dml12.dml`, and finally create a trampoline file `foo.dml` containing:
107+
108+
```
109+
dml 1.4;
110+
#if (dml_1_2) {
111+
import "foo-dml12.dml";
112+
} #else {
113+
import "foo-dml14.dml";
114+
}
115+
```
116+
117+
This way, existing `import "foo.dml";` statements from both DML 1.2 and 1.4 devices
118+
will continue to work.
119+
120+
The apparent downside of this approach is that the logic of the common code is
121+
duplicated across two files, which is a problem if the DML 1.2 variant is expected
122+
to be maintained over a longer period of time. However, if all uses from DML 1.2 of
123+
the common code are expected to be ported within a short migration period,
124+
then this is likely the preferred approach.
125+
126+
After the last DML 1.2 use of the common code has been ported, `foo-dml12.dml` can be removed, and `foo-dml14.dml` can be moved back to `foo.dml`, overwriting the trampoline.
127+
128+
Note that the `#if` trick in the `foo.dml` trampoline above utilizes an
129+
otherwise undocumented DML feature: DML normally doesn't allow `import`
130+
statements within `#if` blocks, but a special exception was added to permit it
131+
specifically within `#if (dml_1_2)`, in order to support this use case.
132+
133+
### In-place conversion, preserving DML 1.2 compatibility
134+
135+
A common file can be ported to DML 1.4 and still be useful from DML 1.2, with a
136+
number of caveats. For instance, devices often implement functionality by
137+
overriding standard methods, and some methods have been renamed between DML 1.2
138+
and 1.4. For instance, an override of the `read_access` register method in a
139+
DML 1.2 device roughly corresponds to a `read_register` override in a DML 1.4
140+
device, and an attribute with `parameter allocate_type = "uint64"` in DML 1.2
141+
corresponds to an event with `is uint64_attr` in DML 1.4. Much of this can be
142+
taken care of by the `dml12-compatibility.dml` layer: A shared DML 1.4 file can
143+
say `import "dml12-compatibility.dml";`. This does nothing when imported from a
144+
DML 1.4, but when imported from DML 1.2, it provides some glue that ties DML
145+
1.4 constructs to the DML 1.2 API. For instance, it defines templates such that
146+
`is uint64_attr` in the DML 1.4 file will expand to define `allocate_type` when
147+
imported from DML 1.2. This file also provides some templates for explicit
148+
instantiation. In particular, the `dml12_compat_read_register` template can be
149+
instantiated on a DML 1.4 register that overrides the `read_register` method;
150+
this has no effect in a DML 1.4 device, but in a DML 1.2 device it overrides
151+
the DML 1.2 method `read_access` to call the provided override. Similarly,
152+
the `dml12_compat_write_register` template can be used on registers that override
153+
`write_register`; `dml12_compat_read_field` and
154+
`dml12_compat_write_field` can be used on field that override the `read_field` or `write_field` method; and `dml12_compat_io_memory_access` can be used on banks that override the `io_memory_access` method.
155+
156+
Sometimes, the facilities in `dml12-compatibility.dml` are not sufficient for
157+
full DML 1.2 compatibility. For instance, suppose you want to use the `shared` annotation on a `read` method when writing the DML 1.4 version of a template. There are fundamental limitations in DML 1.2 that prevent such overrides. This can be overcome with an `#if (dml_1_2)` block on the top level:
158+
```
159+
dml 1.4;
160+
161+
#if (dml_1_2) {
162+
template read_twelve {
163+
method read() -> (uint64) {
164+
log info: "read";
165+
return 12;
166+
}
167+
}
168+
} #else {
169+
template read_twelve is read {
170+
shared method read() -> (uint64) {
171+
log info: "read";
172+
return 12;
173+
}
174+
}
175+
}
176+
```
177+
This is somewhat similar to the `foo-dml14.dml` trampoline approach discussed above, with
178+
the difference that it can be applied selectively only on problematic parts of the
179+
file.
180+
181+
If the flag `--compat` is passed to the `port-dml` script, then the script will
182+
automatically detect some cases where similar `#if` clauses are needed for
183+
compatibility, and insert them automatically. The script will also add an
184+
`dml12-compatibility.dml` import. The `--compat` flag can also be passed to the
185+
`port-dml-module` script; in this case, the script will pass on `--compat` to
186+
`port-dml` when converting DML files that don't reside in the directory of any
187+
of the ported modules.

0 commit comments

Comments
 (0)