Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| tools:chp2prs [2025/10/14 02:21] – karthi | tools:chp2prs [2025/11/04 03:06] (current) – [External Constants] karthi | ||
|---|---|---|---|
| Line 21: | Line 21: | ||
| i.e. a (possibly empty) semi-colon separated list of initializations to a subset of variables used in the CHP, followed by an infinite repetition of an arbitrary CHP program P. The values '' | i.e. a (possibly empty) semi-colon separated list of initializations to a subset of variables used in the CHP, followed by an infinite repetition of an arbitrary CHP program P. The values '' | ||
| + | |||
| + | ===== Synthesis Commands ===== | ||
| + | The standard synthesis steps to produce a bundled-data circuit looks like this: | ||
| + | < | ||
| + | synth2 -F decomp -p proc -o mid.act in.act | ||
| + | synth2 -F ring -ref=1 -C bd -p decomp_proc -o out.act mid.act | ||
| + | </ | ||
| + | You can then simulate at PRS-level with: | ||
| + | < | ||
| + | actsim -ref=2 out.act ring_decomp_proc | ||
| + | </ | ||
| + | |||
| + | Note that user-defined channel-types are not allowed for synthesis. Only the built-in '' | ||
| + | If your CHP has no nested loops or multiple-channel accesses (more about this below), then you can (optionally) skip the first step and directly do: | ||
| + | < | ||
| + | synth2 -F ring -C bd -p proc -o out.act in.act | ||
| + | </ | ||
| + | and simulate at PRS-level with: | ||
| + | < | ||
| + | actsim -ref=1 out.act ring_proc | ||
| + | </ | ||
| + | |||
| + | This two-step process will soon be integrated into one command (TODO). To know more about what the '' | ||
| ===== Program Structure ===== | ===== Program Structure ===== | ||
| Line 54: | Line 77: | ||
| *[ | *[ | ||
| [ c=0 -> r!x; l?c , x:=0 , y:=1 | [ c=0 -> r!x; l?c , x:=0 , y:=1 | ||
| - | []c> | + | []c>0 -> x:=x+y ; y:=x+y ; c:=c-1 |
| ] | ] | ||
| ] | ] | ||
| Line 74: | Line 97: | ||
| < | < | ||
| - | [ c -> y: | + | [ c -> y: |
| - | []else-> y:=0 | + | []else -> y:=0 |
| ] | ] | ||
| </ | </ | ||
| Line 124: | Line 147: | ||
| < | < | ||
| - | [c=0 -> Y!e0 | + | [ c=0 -> Y!e0 |
| []c=1 -> Y!e1 | []c=1 -> Y!e1 | ||
| []c=2 -> x:=x+1 | []c=2 -> x:=x+1 | ||
| Line 134: | Line 157: | ||
| < | < | ||
| - | [c=0 -> y:=e0 | + | [ c=0 -> y:=e0 |
| []c=1 -> y:=e1 | []c=1 -> y:=e1 | ||
| []c=2 -> x:=x+1 | []c=2 -> x:=x+1 | ||
| Line 162: | Line 185: | ||
| ===== External Constants ===== | ===== External Constants ===== | ||
| - | To Do... | + | Maelstrom allows Booleans as input ports to any process to facilitate chip-level configuration bits. These booleans are assumed to be constant for the entire runtime of the circuit, i.e. these are set to a certain value before or during chip reset and never changed after that. Hence, these Booleans are also not latched, the values on the wires are used directly as if they are logical constants, such as '' |
| + | |||
| + | These are useful for global configuration. For example, consider a neuromorphic core where the state-update functions of all the neurons need to be set to something. Instead of sending a config bit to each of the potentially thousands of neurons, one can simply use a '' | ||
| + | |||
| + | < | ||
| + | defproc neuron ( ... ; bool model[2] ) | ||
| + | { | ||
| + | ... | ||
| + | chp | ||
| + | { | ||
| + | *[ | ||
| + | ... | ||
| + | [ ~model[0] & ~model[1] -> state := lif(state) | ||
| + | []~model[0] & model[1] -> state := aif(state) | ||
| + | [] model[0] & ~model[1] -> state := hodg_hux(state) | ||
| + | [] model[0] & model[1] -> state := izhikevich(state) | ||
| + | ]; | ||
| + | ... | ||
| + | ] | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| ===== Nested Loop Constructs ===== | ===== Nested Loop Constructs ===== | ||
| Line 185: | Line 231: | ||
| </ | </ | ||
| + | Although this looks like the state-machine style CHP that was advised against, the general method to excise nested loops out recursively cannot avoid this. In general, it is better to write CHP that does not contain a loop within the top-level loop at all, since the automatic excision process may not always result in the optimal implementation. | ||
| + | |||
| + | ===== Transient Variables ===== | ||
| + | |||
| + | Conventionally, | ||
| < | < | ||
| + | *[ L1?x, L2?y; t:=x; x:=y; y:=t; R!f(x,y) ] | ||
| </ | </ | ||
| + | |||
| + | and this program: | ||
| + | |||
| + | < | ||
| + | *[ L1?x, L2?y; R!f(y,x) ] | ||
| + | </ | ||
| + | |||
| + | result in precisely the same circuit. This also makes the usage of structures have no overhead: | ||
| + | |||
| + | < | ||
| + | *[ ... ; L?s; x:=s.x; y:=s.y; ... ] | ||
| + | </ | ||
| + | |||
| + | generates no additional latches for '' | ||
| + | |||
| + | ===== Probe Trick ===== | ||
| + | |||
| + | The previous sections lead to an interesting trick. Although Maelstrom prohibits shared variables (ports to a process must be channels), this can be used to correctly implement shared variables. For example, consider the following CHP: | ||
| + | |||
| + | < | ||
| + | *[ .. ; R!x; L?s; ... ] | ||
| + | || | ||
| + | *[ R?x; big_expensive_program; | ||
| + | </ | ||
| + | |||
| + | If '' | ||
| + | |||
| + | < | ||
| + | *[ .. ; R!x, L?s; ... ] | ||
| + | || | ||
| + | *[ [ #R -> x:=R ]; big_expensive_program; | ||
| + | </ | ||
| + | |||
| + | is much cheaper! The probed assignment waits for valid data on '' | ||
| + | |||
| + | ===== Dynamic Array Accesses ===== | ||
| + | |||
| + | Dynamic array accesses happen when the index of an arrayed variable is not a constant. In such cases, the arrayed variable is factored out into a memory and accesses are replaced with accesses to this memory block over channels. Needless to say, this is quite expensive and must be avoided unless absolutely necessary. For example, consider the following program with the variable '' | ||
| + | |||
| + | < | ||
| + | *[ C?c; x[c]: | ||
| + | </ | ||
| + | |||
| + | Here, the access to the arrayed variable '' | ||
| + | |||
| + | < | ||
| + | *[ C?c; [ ([] j : 4 : c=j -> x[j]: | ||
| + | </ | ||
| + | |||
| + | which uses a syntactic replication for selections. Here, there will be 4 copies of a '' | ||
| + | |||
| + | < | ||
| + | *[ C?c; | ||
| + | [ ([] j : 4 : c=j -> y:=x[j] ]; | ||
| + | y: | ||
| + | [ ([] j : 4 : c=j -> x[j]:=y ] | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | which uses a single function block. | ||
| + | |||
| + | It is also important to realize the difference between a CHP loop and a syntactic replication loop. A CHP loop actually results in a looping control circuit. A syntactic replication loop is substituted with its expanded form as a pre-processing step. | ||
| + | Suppose there is an array of '' | ||
| + | |||
| + | < | ||
| + | j:=0; *[ j<N -> x[j]:=0; j:=j+1 ] | ||
| + | </ | ||
| + | |||
| + | Apart from the dynamic array access, this loop is entirely unnecessary and is a huge control overhead. The correct CHP to write would be: | ||
| + | |||
| + | < | ||
| + | ( , j : N : x[j]:=0 ) | ||
| + | </ | ||
| + | |||
| + | which would get substituted to; | ||
| + | |||
| + | < | ||
| + | x[0]:=0, x[1]:=0, ... , x[N-1]:=0 | ||
| + | </ | ||
| + | |||
| + | which has no dynamic array accesses and is simply a list of assignments. | ||