Introduction
Before we can implement a VHDL domain for Sphinx, we will need to make a few decisions about how to display and link descriptions of objects. I expect that the most important objects to document will be functions, procedures and entities. Functions and procedures have an advantage, since their syntax is effectively identical to traditional function calls in a programming language. Entities, on the other hand, are very different from function calls.
In Sphinx, an object description starts with a signature node that describes the objects call signature. The object body contains fields or field groups. For example, a field might be the function return type, while a field group would be a list of the function parameters. This structure is pretty baked in, and if we stray too far from this structure, we will end up having to re-implement many of the pieces ourselves.
Below is my proposed design. It is meant purely as a starting point to discuss the merits of different ideas. I still have many open questions, and I'm not at all sure of the best way to do this. Broadly, I think there is a tension between two different approaches. We could make the reStructuredText easy to write, and put a lot of work into parsing within Sphinx. Or we could make the reStructuredText more complex to ease the burden on Sphinx.
The example below represents an approach that is closer to the former, but I think it gives the nicest looking end result.
Proposed reStructuredText
.. vhdl:entity::
entity lib.entity_name is
generic(
type type_t;
const1 : type_t;
const2 : pkg.other_type_t;
const3 : integer := 0;
function func(param : type_t) return std_ulogic);
port(
reset : in std_ulogic;
clock : in std_ulogic;
input : in std_ulogic := '1';
output : out pkg.record_t);
:generic type_t: Description of type
:generic const1: Description of constant
:generic const2: Description of constant
:generic const3: Description of constant
:generic func: Description of subprogram. I would have to manually
describe the function parameters here.
:port reset: Description of port
:port clock: Description of port
:port input: Description of port
:port output: Description of port
Corresponding proposed layout
entity lib.entity_name is
generic (
type type_t;
const1 : type_t;
const2 : pkg.other_type_t
;
const3 : integer
:= 0;
function func(param : type_t) return std_ulogic
));
port (
reset : in std_ulogic
;
clock : in std_ulogic
;
input : in std_ulogic
:= '1';
output : out pkg.record_t
);
Description of entity
Generics: • type_t — Description of type
• const1 — Description of constant
• const2 — Description of constant
• const3 — Description of constant
• func — Description of subprogram. I would have to manually
describe the function parameters here.
Ports: • reset — Description of port
• clock — Description of port
• input — Description of port
• output — Description of port
Discussion
The object description begins with the word entity, which is placed as a signature annotation. I think this fits well, because entity is similar to class in Python, it annotates the object with its use in the language.
The library is prepended to the entity name as a signature addname. Like modules or class names in Python, an addname tells you the fully specified name of the object.
The entity name is added as a signature name.
The generics and ports would have to be added as two separate signature parameter lists with custom formatting. By default, signature parameter list nodes print all parameters on a single line that is only wrapped if necessary. Personally, I think this approach would make VHDL entities extremely hard to read, so I think it would be worth it to implement custom behavior here.
In Python documentation, parameter types are not usually given in the object signature. However, because generic and port types are integral to the entity signature, I thought it would be good to keep them. If we do keep them in the signature, then we should probably cross-reference them where possible. The literal boxes
in the layout above are meant to represent types that would link to their corresponding descriptions. However, I see one tricky thing here; how would Sphinx know that occurrences of the generic type type_t
in the generic list should not be turned into links? If there happened to be a type_t
defined somewhere in the documentation, then Sphinx could produce an incorrect link. I think this is a tricky parsing problem.
Parsing generic subprograms is also a challenge. We could potentially nest generic subprograms, like methods in a class, but that might get complicated quickly.
In general, the above approach leaves a lot of parsing to Sphinx.
Alternatives
One major alternative would be to limit the entity signature to just the annotation, addname and name nodes. There would be no parameter lists providing the generics and ports in the entity signature. Instead, descriptions of the types and default values would fall to the field groups.
.. vhdl:entity:: lib.entity_name
:gent type_t: Description of type
:genc const1: Description of constant
:genc_type const1: type_t
:genc const2: Description of constant
:genc_type const2: pkg.other_type_t
:genc const3: Description of constant
:genc_type const3: integer := 0
:genf func: Description of subprogram. I would have to manually
describe the function parameters here.
:genf_type func: (param : type_t) returns std_ulogic
:port reset: Description of port
:port_type reset: in std_ulogic
:port clock: Description of port
:port_type clock: in std_ulogic
:port input: Description of port
:port_type input: in std_ulogic
:port output: Description of port
:port_type ouput: out pkg.record_t
There's still some parsing to be done by Sphinx, but the burden is reduced. For example, Sphinx no longer needs to tell the difference between generic types, constants, functions and procedures. The field name tells it what the generic is. However, things are still a little bit awkward, because the "type" for each field has more than just the type. For instance, the "type" for a generic constant could contain both a type and a default value. The "type" for a port contains the port direction and the type.
Right now, Sphinx is able to automatically parse a TypedField. For example, for Python parameters, it can take
:param foo: Description of foo
:type foo: str
and deliver a pair of dictionaries with the descriptions and types both accessed by key foo
. However, for VHDL generics and ports, there is more than just one extra piece of information that needs to go along with each parameter. We can either lump both the port type and port direction into the "port_type" field, or we can write our own field parser that will allow us to split them out into three fields, i.e.
:port foo: Description of foo
:port_type foo: std_ulogic
:port_dir foo: in
This is easier to write, but it adds extra work on the Sphinx side of things.
Finally, using this strategy, how would you display the documentation. Something like this?
entity lib.entity_name
Description of entity
Generic • type_t — Description of type
types:
Generic • const1 : type_t — Description of constant
constants: • const2 : pkg.other_type_t
— Description of constant
• const3 : integer
:= 0 — Description of constant
Generic • func(param : type_t) return std_ulogic
subprograms: Description of subprogram. I would have to manually
describe the function parameters here.
Ports: • reset : in std_ulogic
— Description of port
• clock : in std_ulogic
— Description of port
• input : in std_ulogic
— Description of port
• output : out pkg.record_t
— Description of port
This is closer to the way Sphinx displays Python code, but the entity lacks any signature. You can't see all of the parameters at a glance. Rather, you have to parse the corresponding lists. Although this option may be preferable, since the signature in the first example wasn't the most readable anyway.
Conclusions
I think we need to settle on answers to these questions before we can move forward. Moreover, I think we need several educated opinions on this issue who know the Sphinx code base and have some idea of how we might implement the solution we choose. I think I qualify, now that I've read quite a bit of the Sphinx code for Python. But I would really like to see some other thoughts on the issue.