B-Lang-org / bsc

Bluespec Compiler (BSC)
Other
902 stars 141 forks source link

doc example for mkUniqueWrapper2 #694

Open postoroniy opened 2 months ago

postoroniy commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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