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

doc example for mkUniqueWrapper2 #694

Open
postoroniy opened this issue Apr 23, 2024 · 7 comments
Open

doc example for mkUniqueWrapper2 #694

postoroniy opened this issue Apr 23, 2024 · 7 comments

Comments

@postoroniy
Copy link

Hi,
tried to follow the example in "Bluespec Compiler (BSC) Libraries Reference Guide"
from page 218, and can't find ArithOpGP2#(CFP) anywhere.
Could you please suggest if below snippet is correct(checked in bsc compiler and to me it's ok)?
Or may be suggest proper snippet as per example and interface.
thanks

import FIFO :: * ;
import FIFOF :: * ;
import StmtFSM :: * ;
import UniqueWrappers :: * ;
import Complex :: *;

typedef Int#(18) CFP;

interface ArithOpGP2#(type t);
    method Action enqueue(Complex#(t) a, Complex#(t) b);
    method ActionValue#(Maybe#(Complex#(t))) getResult();
endinterface

(* synthesize *)
module mkComplexMult1Fifo( ArithOpGP2#(CFP) ) ;
    FIFO#(Complex#(CFP)) infifo1 <- mkFIFO;
    FIFO#(Complex#(CFP)) infifo2 <- mkFIFO;
    let arg1 = infifo1.first ;
    let arg2 = infifo2.first ;
    
    FIFOF#(Complex#(CFP)) outfifo <- mkFIFOF;
    Reg#(CFP) rr <- mkReg(0);
    Reg#(CFP) ii <- mkReg(0);
    Reg#(CFP) ri <- mkReg(0);
    Reg#(CFP) ir <- mkReg(0);

    // Declare and instantiate an interface that takes 2 arguments, multiplies them
    // and returns the result. It is a Wrapper2 because there are 2 arguments.
    Wrapper2#(CFP,CFP, CFP) smult <- mkUniqueWrapper2( \* ) ;
    // Define a sequence of actions
    // Since smult is a UnqiueWrapper the method called is smult.func
    Stmt multSeq =
        seq
            action
                let mr <- smult.func( arg1.rel, arg2.rel ) ;
                rr <= mr ;
            endaction
            action
                let mr <- smult.func( arg1.img, arg2.img ) ;
                ii <= mr ;
            endaction
            action
                // Do the first add in this step
                let mr <- smult.func( arg1.img, arg2.rel ) ;
                ir <= mr ;
                rr <= rr - ii ;
            endaction
            action
                let mr <- smult.func( arg1.rel, arg2.img );
                ri <= mr ;
                // We are done with the inputs so deq the in fifos
                infifo1.deq ;
                infifo2.deq ;
            endaction
            action
                let ii2 = ri + ir ;
                let res = Complex{ rel: rr , img: ii2 } ;
                outfifo.enq( res ) ;
            endaction
        endseq;
    // Now convert the sequence into a FSM ;
    // Bluespec can assign the state variables, and pick up implict
    // conditions of the actions
    FSM multfsm <- mkFSM(multSeq);
    rule startFSM;
        multfsm.start;
    endrule

    method Action enqueue(Complex#(CFP) a, Complex#(CFP) b);
        infifo1.enq(a);
        infifo2.enq(b);
    endmethod

    method ActionValue#(Maybe#(Complex#(CFP))) getResult();
        if (outfifo.notEmpty) begin
            let res = outfifo.first;
            outfifo.deq;
            return tagged Valid res;
        end else
            return tagged Invalid;
    endmethod 
endmodule
@quark17
Copy link
Collaborator

quark17 commented Apr 23, 2024

The type ArithOpGP2 is defined in the example:

interface ArithOpGP2#(type t);
    ...
endinterface

And the type CFP is defined above that:

typedef Int#(18) CFP;

They aren't from a library. They are user-defined types that only exist within this example.

These types add some realism to the example, by showing how the UniqueWrappers library can be used with your own types. However, they are not necessary for illustrating how to use UniqueWrappers, and I can see how they make the example less clear, by distracting from the part that's important.

Does that answer your question?

The UniqueWrappers library is not commonly used, so you probably don't need to worry about it. But here is a simpler example of what it does. (Well, it started simple, and then I added extra features, but hopefully it is still clear).

import UniqueWrappers::*;

// Change this to False to see the result of not using UniqueWrappers
Bool share = True;

(* synthesize *)
module mkTestUniqueWrappers ();

  // -----
  // Declare and instantiate an interface that takes 2 arguments, multiplies them
  // and returns the result. It is a Wrapper2 because there are 2 arguments.

  function Bit#(8) multFn (Bit#(8) x, Bit#(8) y);
    return (x * y);
  endfunction

  Wrapper2#(Bit#(8),Bit#(8), Bit#(8)) shared_mult <- mkUniqueWrapper2(multFn);

  // Below we define two rules that will share the function.
  // Because they share it, they cannot both execute at the same time,
  // so we introduce a condition:

  Reg#(Bool) rg_cond <- mkRegU;

  // -----
  // The first user of the function

  Reg#(Bit#(8)) rgA1 <- mkRegU;
  Reg#(Bit#(8)) rgA2 <- mkRegU;
  Reg#(Bit#(8)) rgA3 <- mkRegU;

  rule rlA (rg_cond);
    if (! share) begin
      // If we wrote this, the logic would not be shared
      rgA3 <= rgA1 * rgA2;
    end
    else begin
      // By using an instantiated wrapper module, we force the logic to be shared
      // in the generated Verilog from BSC:

      // Note that "shared_mult" is a "Wrapper2" interface, so we call
      // the method "func".  It is an ActionValue method, so we need to get
      // the value first, before assigning it to the register.

      let mr <- shared_mult.func (rgA1, rgA2);
      rgA3 <= mr;
    end
  endrule

  // -----
  // The second user of the function

  Reg#(Bit#(8)) rgB1 <- mkRegU;
  Reg#(Bit#(8)) rgB2 <- mkRegU;
  Reg#(Bit#(8)) rgB3 <- mkRegU;

  rule rlB (! rg_cond);
    if (! share) begin
      rgB3 <= rgB1 * rgB2;
    end
    else begin
      let mr <- shared_mult.func (rgB1, rgB2);
      rgB3 <= mr;
    end
  endrule

endmodule

If you compile with with -verilog and search for * in the generated Verilog file, you should see only one occurrence:

assign IF_shared_mult_arg_whas_THEN_shared_mult_arg_w_ETC___d11 =
           shared_mult_arg$wget[15:8] * shared_mult_arg$wget[7:0] ;

However, if you change the variable shared to False and recompile, you should see two copies of * in the Verilog:

assign rgA1_MUL_rgA2___d4 = rgA1 * rgA2 ;
assign rgB1_MUL_rgB2___d9 = rgB1 * rgB2 ;

@postoroniy
Copy link
Author

postoroniy commented Apr 23, 2024

the type is not defined in example, the code I provided is from myself and not from the doc!
that what the point. I know what is wrapper I pointed to the thing - example from doc is not working

@quark17
Copy link
Collaborator

quark17 commented Apr 23, 2024

Sorry, I think I understand now.

The example from the doc has a few issues that need fixing:

  1. The type ComplexP should be Complex
    The example is from 2005, when the the Complex library was still being developed, and was not yet part of the BSC release. At that time, the type was called ComplexP. To work now, the name just needs to be changed to Complex.
  2. Several libraries need importing
    It's possible that examples do not include the import statements, for brevity. But it is probably a good idea to include it, so that the example is complete and so that readers know whether they have included all the necessary libraries.
  3. The interface of the module should be Empty
    There is no need to define ArithOpGP2, because it is unused. Just remove it:
module mkComplexMult1Fifo() ;

or this, if it's more consistent with the style used by the guide:

module mkComplexMult1Fifo (Empty) ;

Yes, these should be fixed in the document. Thank you for reporting this. Your version is good, except that the definition of ArithOpGP2 should be removed.

@quark17
Copy link
Collaborator

quark17 commented Apr 23, 2024

I guess I would also changed the name to something other than mkComplexMult1Fifo, since it is no longer a FIFO module.

If we did keep the interface, your definition for ArithOpGP2 is okay, but I would remove the Maybe and the check for notEmpty.

The example has two input FIFOs, but they could be one FIFO of Tuple2.

And I think the GP in the name probably indicated GetPut, so possibly it looked something like this:

interface ArithOpGP2#(type t);
  interface Put#(Tuple2#(t,t)) put;
  interface Get#(t) get;
endinterface

But I can't find any example of ArithOpGP2 existing, only of ArithOp2, which was this in an early 2005 example that led to the creation of UniqueWrappers:

interface ArithOp#(type a_type) ;
   method Action start( a_type in1, a_type in2 ) ;
   method a_type result ;
endinterface

And in that example, no input or output FIFOs were being used.

I don't think it's important to follow the original example(s). We can just consider what it's helpful in an example now. Leaving out the interface and making it a standalone example might be best, and that way readers can compile it and explore the output.

@postoroniy
Copy link
Author

for users coming from verilog world it's useful to see inputs and outputs in produced verilog code. for establishing the connection between func world and real one. when I am not able to test out examples from "reference blah guide" I'd think that language sucks or not maintained well, and in any case not user friendly at all.
what I did in provided example - it generates actual working hardware and not empirical unicorns.
however, if you think empty interface is better for understanding, ok...fine :)

@postoroniy
Copy link
Author

btw, the latest example I did is having 2 fifos only

import FIFO :: * ;
import FIFOF :: * ;
import StmtFSM :: * ;
import UniqueWrappers :: * ;
import Complex :: *;

typedef Int#(18) CFP;

typedef struct {
    Complex#(CFP) a;
    Complex#(CFP) b;
} CFP2 deriving (Eq, Bits);

interface ArithOpGP2#(type t);
    method Action enqueue(Complex#(t) a, Complex#(t) b);
    method ActionValue#(Maybe#(Complex#(t))) getResult();
endinterface

(* synthesize *)
module mkComplexMult1Fifo( ArithOpGP2#(CFP) ) ;
    FIFO#(CFP2) infifo <- mkFIFO;
    let arg = infifo.first ;
    
    FIFOF#(Complex#(CFP)) outfifo <- mkFIFOF;
    Reg#(CFP) rr <- mkReg(0);
    Reg#(CFP) ii <- mkReg(0);
    Reg#(CFP) ri <- mkReg(0);
    Reg#(CFP) ir <- mkReg(0);

    // Declare and instantiate an interface that takes 2 arguments, multiplies them
    // and returns the result. It is a Wrapper2 because there are 2 arguments.
    Wrapper2#(CFP,CFP, CFP) smult <- mkUniqueWrapper2( \* ) ;
    // Define a sequence of actions
    // Since smult is a UnqiueWrapper the method called is smult.func
    Stmt multSeq =
        seq
            action
                let mr <- smult.func( arg.a.rel, arg.b.rel ) ;
                rr <= mr ;
            endaction
            action
                let mr <- smult.func( arg.a.img, arg.b.img ) ;
                ii <= mr ;
            endaction
            action
                // Do the first add in this step
                let mr <- smult.func( arg.a.img, arg.b.rel ) ;
                ir <= mr ;
                rr <= rr - ii ;
            endaction
            action
                let mr <- smult.func( arg.a.rel, arg.b.img );
                ri <= mr ;
                // We are done with the inputs so deq the in fifos
                infifo.deq ;
            endaction
            action
                let ii2 = ri + ir ;
                let res = Complex{ rel: rr , img: ii2 } ;
                outfifo.enq( res ) ;
            endaction
        endseq;
    // Now convert the sequence into a FSM ;
    // Bluespec can assign the state variables, and pick up implict
    // conditions of the actions
    FSM multfsm <- mkFSM(multSeq);
    rule startFSM;
        multfsm.start;
    endrule

    method Action enqueue(Complex#(CFP) a, Complex#(CFP) b);
        CFP2 ab;
        ab.a=a;
        ab.b=b;
        infifo.enq(ab);
    endmethod

    method ActionValue#(Maybe#(Complex#(CFP))) getResult();
        if (outfifo.notEmpty) begin
            let res = outfifo.first;
            outfifo.deq;
            return tagged Valid res;
        end else
            return tagged Invalid;
    endmethod 
endmodule

@quark17
Copy link
Collaborator

quark17 commented Jul 13, 2024

Thank you again for reporting this. I will make the change in the Library Guide.

Also, I discussed this with @rsnikhil and he agrees that the Library Guide examples should be fully working examples, that can be compiled and run. He suggests that these examples should exist as files in this repo, and the Library Guide should just point to the example files. He mentions that there is already a collection of examples which we use as the starting point: the examples accompanying the book BSV By Example. Nikhil suggests that every section of the Library Guide should point to an example there and, if no suitable example exists yet, an example file should be added. The BSV By Example book is available for download from a different repo at the moment (here), so Nikhil will take on the task of putting the document source and examples into this repo. Then we can look at updating the Library Guide to point to examples there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants