Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exp operator in FuTIL. #299

Closed
cgyurgyik opened this issue Dec 7, 2020 · 6 comments
Closed

exp operator in FuTIL. #299

cgyurgyik opened this issue Dec 7, 2020 · 6 comments
Assignees
Labels
C: Relay Relay-to-FuTIL compiler S: Available Can be worked upon

Comments

@cgyurgyik
Copy link
Collaborator

Hi Adrian and Rachit, both of you mentioned on separate occasions to use FuTIL to represent the exp operator. I just want to confirm whether or not this is along the lines of what you meant.

Right now, for nn.softmax, I’m taking a Dahlia program and lowering it to FuTIL using external memories. Part of this includes inserting the import for std_exp():
import “std_exp.h” { def exp(x: ubit<32>): ubit<32>; }

This is then defined in the primitives library. The suggestion seems to imply that we should avoid the primitives library, and instead use a FuTIL component to represent std_exp.

A current constraint is I’m lowering my programs from Dahlia, so I’d need to take in a Dahlia-level function to then lower into a FuTIL component. My naive approach is I’d need to create a component std_exp that takes in a constant N and outputs the result of exp(N).

In Dahlia, that would look something like:

// In some way, define function std_exp() { ... }

let N: ubit<32> = 3;
let out:ubit<32> = 0; 
--- 
out := std_exp(N); // Save `e^3` to `out`.

In FuTIL, the component would be something like what I've written below. This was written before @rachitnigam 's updates in #273 . So instead, it is probably correct to use the invoke statement when implemented.

// `out_done`, `write_en`, `out` are all wires to a register which will contain the output value.
component std_exp(N: 32, out_done: 1) -> (write_en: 1, out: 32) { ... }
...
component main() -> () {
  cells {
    output0 = prim std_mem_d1_ext(32,1,1);
    exponent0 = exponent;
    reg0 = prim std_reg(32);
  }
  wires {
    group run_exp {
      // Pass in N.
      exponent0.N = 32'd8;

      // Save value e^8 in a register.
      exponent0.out_done = reg0.done;
      reg0.in = exponent0.out;
      reg0.write_en = exponent0.write_en;
      exponent0.go = 1'd1;

      // Show we can then pass it into a output.
      output0.write_data = reg0.done ? reg0.out;
      output0.write_en = 1'd1;
      output0.addr0 = 1'd0;

      run_exp[done] = exponent0.done ? 1'd1;
    }
  }
  control { seq { run_exp; }
  }
}
@cgyurgyik cgyurgyik added the S: Discussion needed Issues blocked on discussion label Dec 7, 2020
@rachitnigam
Copy link
Contributor

I wrote this for a general exponent operator. Maybe something similar to this?

import "primitives/std.lib";
component exponent(base: 32, exp: 4) -> (out: 32) {
  cells {
    pow = prim std_reg(32);
    count = prim std_reg(4);
    mul = prim std_mult(32);
    lt = prim std_lt(4);
    incr = prim std_add(4);
  }
  wires {
    group init {
      pow.in = 32'd1;
      pow.write_en = 1'd1;
      count.in = 4'd0;
      count.write_en = 1'd1;
      init[done] = pow.done & count.done ? 1'd1;
    }
    group do_mul {
      mul.left = base;
      mul.right = pow.out;
      pow.in = mul.out;
      pow.write_en = 1'd1;
      do_mul[done] = pow.done;
    }
    group incr_count {
      incr.left = 4'd1;
      incr.right = count.out;
      count.in = incr.out;
      count.write_en = 1'd1;
      incr_count[done] = count.done;
    }
    group cond {
      lt.right = exp;
      lt.left = count.out;
      cond[done] = 1'd1;
    }

    out = pow.out;
  }
  control {
    seq {
      init;
      while lt.out with cond {
        par { do_mul; incr_count; }
      }
    }
  }
}

component main() -> () {
  cells {
    a0 = prim std_mem_d1_ext(32,10,4);
    a_read0_0 = prim std_reg(32);
    add0 = prim std_add(4);
    const0 = prim std_const(4,0);
    const1 = prim std_const(4,9);
    const2 = prim std_const(4,1);
    exp0 = exponent;
    i0 = prim std_reg(4);
    le0 = prim std_le(4);
    tmp_0 = prim std_reg(32);
  }
  wires {
    group cond0 {
      cond0[done] = 1'd1;
      le0.left = i0.out;
      le0.right = const1.out;
    }
    group let0 {
      i0.in = const0.out;
      i0.write_en = 1'd1;
      let0[done] = i0.done;
    }
    group let1 {
      tmp_0.in = exp0.out;
      tmp_0.write_en = exp0.done;
      let1[done] = tmp_0.done;
      exp0.base = a_read0_0.out;
      exp0.exp = 4'd3;
      exp0.go = !exp0.done ? 1'd1;
    }
    group upd0 {
      a_read0_0.write_en = 1'd1;
      a0.addr0 = i0.out;
      a_read0_0.in = 1'd1 ? a0.read_data;
      upd0[done] = a_read0_0.done ? 1'd1;
    }
    group upd1 {
      a0.addr0 = i0.out;
      a0.write_en = 1'd1;
      a0.write_data = 1'd1 ? tmp_0.out;
      upd1[done] = a0.done ? 1'd1;
    }
    group upd2 {
      i0.write_en = 1'd1;
      add0.left = i0.out;
      add0.right = const2.out;
      i0.in = 1'd1 ? add0.out;
      upd2[done] = i0.done ? 1'd1;
    }
  }
  control {
    seq {
      let0;
      while le0.out with cond0 {
        seq {
          upd0;
          let1;
          upd1;
          upd2;
        }
      }
    }
  }
}

@sampsyo
Copy link
Contributor

sampsyo commented Dec 8, 2020

That looks great!

For situations like this, would it make sense to have a way to invoke FuTIL primitives as Dahlia functions? That is, sort of like how we can "import" C header functions when Dahlia targets HLS C, we could "import" black-box FuTIL functions an invoke them. It could simplify the process of integrating Dahlia with other stuff. And as in the exp case, it could help replace the built-in math.h support that traditional HLS tools offer.

@cgyurgyik
Copy link
Collaborator Author

@rachitnigam Yeah I wrote a working example (below) before reading your post in the invoke issue. We can then run this component by passing in an exponent value N and hooking in a register in main.

import "primitives/std.lib";
component exponent(N: 32, out_done: 1) -> (write_en: 1, out: 32) {
  cells {
    sub = prim std_sub(32);
    add = prim std_add(32);
    initial_e = prim std_const(32,2);

    e_0 = prim std_reg(32);
    exp_0 = prim std_reg(32);

    i0 = prim std_reg(32);
    le0 = prim std_le(32);
    bin_read0_0 = prim std_reg(32);
    mult_pipe0 = prim std_mult_pipe(32);
  }
  wires {
    group cond0<"static"=0> {
      sub.left = N;
      sub.right = 32'd1;
      le0.left = i0.out;
      le0.right = sub.out;
      cond0[done] = 1'd1;
    }
    group let0<"static"=1> {
      e_0.in = initial_e.out;
      e_0.write_en = 1'd1;
      let0[done] = e_0.done;
    }
    group let1<"static"=1> {
      exp_0.in = 32'd1;
      exp_0.write_en = 1'd1;
      let1[done] = exp_0.done;
    }
    group let2<"static"=1> {
      i0.in = 32'd0;
      i0.write_en = 1'd1;
      let2[done] = i0.done;
    }
    group let3<"static"=4> {
      bin_read0_0.in = mult_pipe0.out;
      bin_read0_0.write_en = mult_pipe0.done;
      let3[done] = bin_read0_0.done;
      mult_pipe0.left = e_0.out;
      mult_pipe0.right = exp_0.out;
      mult_pipe0.go = !mult_pipe0.done ? 1'd1;
    }
    group upd0<"static"=1> {
      exp_0.write_en = 1'd1;
      exp_0.in = 1'd1 ? bin_read0_0.out;
      upd0[done] = exp_0.done ? 1'd1;
    }
    group upd1<"static"=1> {
      i0.write_en = 1'd1;
      add.left = i0.out;
      add.right = 32'd1;
      i0.in = 1'd1 ? add.out;
      upd1[done] = i0.done ? 1'd1;
    }
    group upd2<"static"=1> {
      out = exp_0.out;
      // addr0 = 1'd0;
      write_en = 1'd1;
      upd2[done] = out_done ? 1'd1;
    }
  }
  control {
    seq {
      par {
        let0;
        let1;
      }
      let2;
      while le0.out with cond0 {
        seq {
          let3;
          upd0;
          upd1;
        }
      }
      upd2;
    }
  }
}

That looks great!

For situations like this, would it make sense to have a way to invoke FuTIL primitives as Dahlia functions? That is, sort of like how we can "import" C header functions when Dahlia targets HLS C, we could "import" black-box FuTIL functions an invoke them. It could simplify the process of integrating Dahlia with other stuff. And as in the exp case, it could help replace the built-in math.h support that traditional HLS tools offer.

That's exactly what I was thinking as well.
An imported function would be lowered to a FuTIL component. So a statement like
let x: ubit<32> = exponent(3);
would import the FuTIL component exponent above, and then hook in the register wires of x into the component, and have a group run it.

I just wasn't sure if I was trying to, in a way, re-invent the wheel with the efforts behind invoke, or if this went again any FuTIL / Dahlia design philosophies. Or, perhaps it is not this simple and I'm missing something.

@sampsyo
Copy link
Contributor

sampsyo commented Dec 8, 2020

Right, something like that would be cool. What I was referring to above is that we already have a similar import mechanism for running black-box C function calls when Dahlia generates HLS C for its Vivado backend. Like this:
https://github.com/cucapra/dahlia-evaluation/blob/7e1cb8f1ad261dc091b9ffd8adec41f042f5ec96/rewrite/machsuite-fft-transpose/fft.fuse#L1

That imports cos and sin from math.h and then invokes them later in the kernel.

@cgyurgyik
Copy link
Collaborator Author

Interesting, I wasn't aware of this.

@cgyurgyik
Copy link
Collaborator Author

#404 provides a working exp operator for signed and unsigned fixed points. It is written in Calyx, and not very efficient, given it uses a Taylor Series approximation. The next step is to explore efficient table-based versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C: Relay Relay-to-FuTIL compiler S: Available Can be worked upon
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants