[libre-riscv-dev] [Bug 64] data handling / io control / data routing API needed
bugzilla-daemon at libre-riscv.org
bugzilla-daemon at libre-riscv.org
Fri Apr 19 12:31:49 BST 2019
http://bugs.libre-riscv.org/show_bug.cgi?id=64
--- Comment #4 from Luke Kenneth Casson Leighton <lkcl at lkcl.net> ---
review of
https://salsa.debian.org/Kazan-team/simple-barrel-processor/blob/master/src/PipelineBuildingBlockAPIProposal.py
general comments
----------------
hard dependence on typing is causing readability issues and
will make contributions from experienced python programmers
that much harder. *nobody* uses typing in common-usage python,
because it gets in the way. there is an actual known paradigm
which python conforms to, that the use of typing violates
(i just can't recall what it is, right now).
basically python is based on the Liskov Substitution Principle,
of *semantic* relationships rather than *syntactical* relationships,
and introducing syntactical relationships increases code size,
decreases readability, decreases flexibility and breaks python
developer expectations.
https://en.wikipedia.org/wiki/Liskov_substitution_principle
can the typing be moved to a .pyi file and mypi deployed *if
the user wants to use it*?
Data, Shape, SignalShape, RecordShape, Empty, EmptyShape
----------.....
* addition of name extension (prefix) is a good idea. if
it can be achieved in the same way as nmigen, that would
be even better. nmigen uses this code (example):
tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
* restricting data usage using a syntactical hard limit instead
of defining a semantic relationship results in limitations
that a normal python developer will rebel against / reject.
the limitation of *all* data using *forcibly* being required
to go through the "Data" class instance is also a burden that
will result in significant user demands for "additional features"
on the Data paradigm, to add support for unanticipated
requirements.
* the Data paradigm needs to be much more in line with the
nmigen "Value" object (i.e. the nmigen API, such as
using __iter__ instead of members() (or perhaps ports()),
having an eq (instead of "assign") function etc.
also note that Record is going to have a "connect" function
added (see nmigen-migen Record compat library)
in discussions with whitequark it was agreed that at some
point a "flattening" system would be provided such that
the nmigen IR "Visitor" system could handle arbitrary objects.
therefore it becomes even more important that Data conform
to nmigen's API, to avoid the "Principle of Least Astonishment"
when presented with the Data paradigm:
https://en.wikipedia.org/wiki/Principle_of_least_astonishment
EntryPort (and ExitPort)
---------
* o_ready and
* i_valid names were selected due to their vertical alignment.
this increases code clarity.
* Signals default to width 1. the addition of the width 1 is not needed.
* the name "EntryPort" begins with the letter E, and so does "ExitPort".
this is problematic from a clarity perspective in later usage
(see below)
* use of Data.assign for ready_in/out and valid_in/out, they should just
be an eq.
* connect_from_entry is not clear what it does, by way of it being
an EntryPort connecting *to* an EntryPort. PrevControl._connect_in
is clear (by using the prefix underscore as a standard python convention
equivalent to "protected" in c++) that it should not be used outside
of the API, and its docstring warns against such.
with the name connect_from_entry being so similar to connect_from_exit
it is very confusing, particularly as they are the exact same names
in ExitPort
similar logic applies to ExitPort
CombSeg
-------
* comment is good. "side-effect-free data path", very clear.
* self.i and self.o (see above about Entry/Exit Port) get very confusing.
the naming "prev" and "next" (p and n for short) give clarity about
what shall be connected to what, particularly in the context of
ready/valid signalling going *both* directions.
* the use of the function create_data *on* an object - rather than being
a function itself - forces a requirement on chaining of segments to
provide an *intermediary* data object from which *both* the input
object *and* the output object are forced to inherit.
by simply having a function, the location of that function is irrelevant.
it could be a globally-imported function from a module. it could be
a lambda function. it could be a member class function. it could be
a static class function.
this is what is meant by *semantic* relationships (LSP) as opposed to
*syntactical* relationships.
* the use of an object (shape.create_data) *may* make the use of
static classes difficult. needs evaluation and investigation
* the hard requirement for all derivatives to be modules is unnecessary
(particularly in simple cases)
CombSegFn
---------
* Data.eq not Data.assign is better, although just "eq" is preferred
(which can be used as "from import library import data" then "data.eq(...)")
* not sure what this class is for. the large amount of data typing
information is interfering with being able to understand it.
* once removed, the typing information should result in such a small
class that it may turn out to be unnecessary.
CombChain
---------
* data typing is interfering with readability
* first_child can be merged into children in the constructor,
making children_tuple redundant
* use of a tuple and the forced requirement that the tuple be an array
of CombSegs is a design-flaw hard and unnecessary limitation.
simply passing in *children and assigning them to __children will
allow developers to pass in anything that is iterable. generators,
__iter__-objects, tuples, lists, sets, dictionaries (iterating a
dictionary returns its values) and so on.
* in elaborate() the assignment of submodules is a good idea... however
it assumes that the object *has* an elaborate function (i.e. is
a nmigen module), which is a burdensome requirement in simple cases
* the functionality StageChain._specallocate is missing. some use-cases,
if the creation of an intermediary from a module is not done, the
entire module is "flattened" with a warning issued.
Identity
--------
* not sure what this is for
Block
-----
* self.entry and self.exit are non-matching lengths, resulting in
loss of vertical alignment when reading and debugging the code
* reducing these to self.e and self.e is clearly not going to work
* this is why "p" and "n" were chosen in ControlBase.
* self.p and self.n are clearly "previous" and "next" yet at the same
time provide a base to put the "input" (i) and "output (o) into.
data is referred to as self.p.i_data and self.n.o_data which make
it really really obvious that input data comes from the *previous*
stage and output data goes to the *next* stage.
* why does Block.wrap exist? the need for Block and BlockLike is not
clear, and is probably down to the (unnecessary) type-restricting
CombBlock
---------
* why does CombBlock exist?
* why is connect_from_entry not being used in elaborate()?
Chain
-----
* all the same arguments applies to first_child and children as to
CombChain. can be greatly simplified.
* likewise same arguments apply to elaborate()
* naming convention connect_from_entry / connect_from_exit is causing
the very confusion predicted to occur. what's being connected to
what?
* the exposure of the inner details of Module is unnecessary. an
example of the equivalent code for the singlepipe.py API is:
def elaborate(self, platform):
m = Module()
pipe1 = ExampleBufPipe()
pipe2 = ExampleBufPipe()
m.submodules.pipe1 = pipe1
m.submodules.pipe2 = pipe2
m.d.comb += self.connect([pipe1, pipe2])
in this code it is really clear, the pipes are declared, they're added
with names *chosen by the developer* as submodules, and then connected.
by contrast, in Chain.elaborate(), the developer has no choice about
the names of the modules (they are forcibly called child_0/1/2/3).
(likewise for the names of CombChain segs)
SimpleReg
---------
* Signal(1, reset=0), both width 1 and reset=0 are the defaults and may
be removed
* super().__init__(shape, shape) is not clear
* shape = Shape.wrap(shape) is not clear
* shape.create_data() again, see explanation about forcing the use
of an object to become part of the API rather than allowing freedom
and flexibility by way of a function
* self.data_out does not vertically line up with self.valid_out.
- self.o_data lines up with
- self.o_valid
at least at the prefix. code that was sequentially as follows
would also line up, improving readability:
self.o_data
self.i_data
self.o_valid
self.i_ready
which would make a user consider tidying them up as follows:
self.o_valid
self.o_data
self.i_data
self.i_ready
Queue
-----
* like this class a lot, prefer that it be derived from FIFOInterface,
to make it clear that it conforms to the nmigen FIFO API.
* Queue entries can actually be zero, as long as fwft (first-word
fall-through) is enabled.
* using the name "fwft" as opposed to "allow_comb" makes it clear
that it has the same purpose and effect as fwft in the nmigen FIFO API.
--
You are receiving this mail because:
You are on the CC list for the bug.
More information about the libre-riscv-dev
mailing list