Bug 458 - PartitionedSignal needs nmigen constructs "m.If", Switch etc
Summary: PartitionedSignal needs nmigen constructs "m.If", Switch etc
Status: CONFIRMED
Alias: None
Product: Libre-SOC's first SoC
Classification: Unclassified
Component: Source Code (show other bugs)
Version: unspecified
Hardware: Other Linux
: --- enhancement
Assignee: Luke Kenneth Casson Leighton
URL:
Depends on:
Blocks: 132 362
  Show dependency treegraph
 
Reported: 2020-08-18 01:06 BST by Luke Kenneth Casson Leighton
Modified: 2021-02-10 20:27 GMT (History)
3 users (show)

See Also:
NLnet milestone: NLNet.2019.10.Wishbone
total budget (EUR) for completion of task and all subtasks: 1250
budget (EUR) for this task, excluding subtasks' budget: 1250
parent task for budget allocation: 362
child tasks for budget allocation:
The table of payments (in EUR) for this task; TOML format:
awygle=1250


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Luke Kenneth Casson Leighton 2020-08-18 01:06:55 BST
PartitionedSignal is a dynamic SIMD version of Signal.  it needs to work with the following constructs:

* m.If / Elif / Else
* m.Switch / Case

without PartitionedSignal, SIMD has to be done as follows:

with m.If(partition == 64):
    operations treating Signals at full 64 bit
with m.Elif(partition == 2x32bit):
    for i in range(2):
         exactly the same code as 64 bit
         except now it is repeated twice,
         first on the low 32 bits and then
         on the hi 32

and that repetition continues right the way down to 8 bit, complicating design massively.

PartitionedSignal hides that entirely, as far as arithmetic and logic operations are concerned (__lt__, __or__, see bug #132)

where things break down is this:

ppts = PartitionPoints(64, 8)
x = PartitionedSignal(64, partition=ppts)

with m.If(x == 5):
    ... do something.

the reason it breaks down is because PartitionedSignal.__eq__ does *not* return a single bit object, it returns a *dynamic multi-bit* object that dynamically reflects the current state of the partitioning.

example:

* partition points is set to 8 breaks
* this subdivides the 64 bit signal into 8 separate 8 bit values
* comparison against a constant (5) creates EIGHT separate 8-bit comparisons
* those 8 comparisons are bundled together

when the partitions are cleared to indicate that the 64 bits are to be treated as a single 64 bit value, the comparison "5" is done against the entire 64 bit, however the answer still goes into the same 8 bit object used when the partition was set to 8x8bit.

the m.If construct cannot cope with this.

the current workaround is to completely avoid using m.If entirely and to use a special PartitionedMux construct, PMux, instead.

this however is less than ideal.

similar logic applies to Switch/Case. Arrays need some discussion.
Comment 1 Luke Kenneth Casson Leighton 2020-12-06 01:06:06 GMT
https://github.com/nmigen/nmigen/blob/59ef6e6a1c4e389a41148554f2dd492328820ecd/nmigen/hdl/dsl.py#L447

ah ha!

this is the location where the code-fragments for m.If/Elif/Else are
generated, and it should be really pretty straightforward to drop the appropriate replacement (parallel, SIMD) statements in at this location, with a replacement (override) of Module._pop_ctrl
Comment 2 Luke Kenneth Casson Leighton 2020-12-06 17:15:06 GMT
more context:
https://freenode.irclog.whitequark.org/nmigen/2020-12-06#28537093;
Comment 3 Luke Kenneth Casson Leighton 2020-12-07 00:33:17 GMT
relevant page for describing the dynamic partitioned signal operators

https://libre-soc.org/3d_gpu/architecture/dynamic_simd/
Comment 4 Luke Kenneth Casson Leighton 2021-01-13 18:41:08 GMT
taking a look at this more closely, i realised that Value (and UserValue) will need some tweaks.
at present, the code does this:

class Value:
     def part(....)
         return Part(self, ...)

where Part is a global function.  in order to support override capability this needs to change to:

class Value:
     def part(....)
         return ValuePart(self, ...)

where Part is defined as:

def Part(lhs, ....):
    return lhs.part(....)

and the *current* Part function is *renamed* to ValuePart.

UserValue then does:

class UserValue:
     def part(....)
         return UserValuePart(self, ...)

or more likely also calls ValuePart but it is clearly documented that user-derived overrides are perfectly well permitted.

PartitionedSignal would do *exactly that*.

then, a crucial addition to Value and UserValue would be:

    def mux(self, choice1, choice2):
         return ValueMux(self, choice1, choice2)

and PartitionedSignal could override it to provide the correct dynamic SIMD functionality.