Small pyrope flow

This is a brainstorm of different options to specify alternative syntax.

Option 1:

pipe alu(in1, in2) -> (out_pipelined, out_live) {
    let tmp = await[3] mul(in1, in2) forward in2

    out_pipelined = await[1] add(tmp, in2)

    out_live = await[1] add(tmp, input.in2)
}

Option 2:

// A 'pipe' is a basic tool that performs one job.
pipe mul(a, b) -> (c) { c = a * b }
pipe add(a, b) -> (c) { c = a + b }

pipe alu(in1, in2) -> (out_pipelined1, out_piepelined2, out_live) {
    (tmp, in2_delayed) = await[3] (mul(in1, in2), in2)

    out_pipelined1 = await[1] add(tmp, in2_delayed)
    out_pipelined2 = await[1] add(tmp, await in2)

    out_live  = await[1] add(tmp, nowait in2)

    // compile error if inputs come from different times without await/noawait
}

//--------

Option 1:

// A 'pipe' is a basic tool that performs one job.
pipe mul(a, b) -> (c) { c = a * b }
pipe add(a, b) -> (c) { c = a + b }

// A 'flow' is an assembly line that wires tools together.
flow alu(in1, in2) -> (out_pipelined, out_live) {
    (tmp, in2_d) = await[3] (mul(in1, in2), in2)

    out_pipelined = await[1] add(tmp, in2_d)
    out_live      = await[1] add(tmp, nowait in2)
}

flow accum_alu(in1,in2) -> (out) {

    tmp = await[3] mul(in1, in2)

    out = total // Output before add accumulation
    total = await[1] add(nowait total, tmp) // accumulate over total
}

Option 2:

pipe mul(a, b) -> (c) { c = a * b }
pipe add(a, b) -> (c) { c = a + b }

flow alu(in1, in2) -> (out_pipelined, out_live) {
  (tmp, in2_d) = wait[3] (mul(in1, in2), in2)

  out_pipelined = wait[1] add(tmp, in2_d)
  out_live      = wait[1] add(tmp, wait[0] in2)
}

flow accum_alu(in1, in2) -> (out) {
  reg total=0

  let tmp = await[3] mul(in1, in2)

  out = total

  next total = await[1] add(wait[0] total, tmp)  // Pick value from reg output, no pipeline
}

Option 3

pipe mul(a, b) -> (c) { c = a * b }
pipe add(a, b) -> (c) { c = a + b }

flow alu(in1, in2) -> (out_pipelined, out_live) {
  let (tmp, in2_d) = delay[3] (mul(in1, in2), in2)

  out_pipelined = delay[1] add(tmp, in2_d)
  out_live      = delay[1] add(tmp, delay[0] in2)
}

flow accum_alu(in1, in2) -> (out) {
  reg total = 0

  let prod = delay[3] mul(in1, in2)           // prod @ t+3
  let sum  = add(total, prod)                 // ERROR total and prod different times

  let sum_aligned = add(delay[0] total, prod) // prod @ 3, total current flop output

  next total = delay[1] sum_aligned           // total value not visible until next cycle

  out = total                                 // total is the current value (held) at any given cycle

Option 4:

// Primitives: Defined with 'pipe'. Their logic is a single expression.
pipe mul(a, b) -> (c) { c = a * b }
pipe add(a, b) -> (c) { c = a + b }

flow alu(in1, in2) -> (out_pipelined, out_live) {
  let (tmp, in2_d) = delay[3] (mul(in1, in2), in2)

  out_pipelined = delay[1] add(tmp, in2_d)
  out_live      = delay[1] add(tmp, delay[0] in2)
}

flow accum_alu(in1, in2) -> (out) {
  reg total::[init=0]

  let tmp = delay[3] mul(in1, in2)

  let sum  = add(total, prod)                 // ERROR total and prod different times
  let sum_aligned = add(delay[0] total, tmp)

  next(total) = delay[1] sum_aligned

  out = total
}

Option 5:

pipe mul(a, b) -> (c) { c = a * b }
pipe add(a, b) -> (c) { c = a + b }

flow alu(in1, in2) -> (out_pipelined, out_live) {
  let (tmp@3, in2_d@3) = delay[3] (mul(in1, in2), in2)
  out_pipelined = delay[1] add(tmp@3, in2_d@3)
  out_live      = delay[1] add(tmp@3, in2@0)  // explicit timing annotation
}

flow accum_alu(in1, in2) -> (out) {
  reg total::[init=0]
  let tmp@3 = delay[3] mul(in1, in2)
  let sum_aligned = add(total@0, tmp@3)  // explicit timing makes alignment clear
  next(total) = delay[1] sum_aligned
  out = total@0  // current register output
}

// Check that e1 and e2 have the same delays since inputs start
assert_delay(e1 == e2)