Open postoroniy opened 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 ;
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
Sorry, I think I understand now.
The example from the doc has a few issues that need fixing:
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
.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.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.
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.
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 :)
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
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