FleetCode TutorialA First ProgramThe code below can be found in src/edu/berkeley/fleet/demo/Test.java. First, we'll need to import the Fleet API and the software interpreter execution platform:
import edu.berkeley.fleet.api.*;
import static edu.berkeley.fleet.api.Instruction.Set.*; import edu.berkeley.fleet.interpreter.Interpreter; public class Test { public static void main(String[] s) { Next, we will instantiate a software interpreter.
Fleet fleet = new Interpreter(new String[] {
"Alu2", "Debug" }, /* logging */ true); The first argument to the constructor is a list of the types of ships we would like our Fleet to have (in this case, one Alu2 ship and one Debug ship); the second argument is a boolean which enables logging on the console. Now that our Fleet has been created, let's get access to the ships and docks we need:
Ship alu = fleet.getShip("Alu2", 0);
Ship debug = fleet.getShip("Debug", 0); Dock debugIn = debug.getDock("in"); The integer argument to getShip indicates the ordinal number; if we had more than one Alu2 ship, we could pass the argument 1 to get the second Alu2. Now, we will create an array of instructions to be dispatched:
Instruction[] instructions = new Instruction[] {
new Instruction.Set(debugIn, false, Predicate.Default, SetDest.DataLatch, 12), new Instruction.Move(debugIn, false, Predicate.Default, /* interruptible */ false, /* path */ null, /* tokenIn */ false, /* dataIn */ false, /* latchData */ false, /* latchPath */ false, /* dataOut */ true, /* tokenOut */ false ), }; The first instruction sets the data latch at the debug.in dock to the immediate value 12. The second instruction delivers the value currently in the data latch. At this point it's probably a very good idea to take a look at the architecture manual and javadoc, if you haven't already. All that remains is to dispatch our instructions and read back the output:
FleetProcess fp = fleet.run(instructions);
BitVector bv = fp.readWord(); System.out.println(bv.toLong()); fp.terminate(); When we run this, we should see something like this:
The green lines show instructions being dispatched, and the purple lines show data moving through the switch fabric. Recall that instructions are sent as packets through the switch fabric as well; in the software interpreter, dispatched instructions are sent “from” the debug.in dock by default. A Second ProgramIn this section we will write a program that uses the Alu2 ship to add two numbers together. The code below can be found in src/edu/berkeley/fleet/demo/Test2.java. First, we're going to write a few helper methods to make our code more readable:
private static Instruction literal(Dock dock, int literal) {
return new Instruction.Set(dock, false, Predicate.Default, SetDest.DataLatch, literal); } private static Instruction deliver(Dock dock) { return new Instruction.Move(dock, false, Predicate.Default, false, null, false, false, false, false, true, false); } private static Instruction collectAndSend(Dock dock, Destination dest) { return new Instruction.Move(dock, false, Predicate.Default, false, dock.getPath(dest,null), false, true, true, false, true, false); } private static Instruction recvAndDeliver(Dock dock) { return new Instruction.Move(dock, false, Predicate.Default, false, null, false, true, true, false, true, false); } Each of these methods generates a particular kind of Move instruction, so the code that invokes these methods will be easier to read. The only other change we'll make to our original program is the set of instructions we dispatch:
Instruction[] instructions = new Instruction[] {
literal(alu.getDock("in1"), 1), literal(alu.getDock("in2"), 2), literal(alu.getDock("inOp"), 0 /* opcode for ADD */), deliver(alu.getDock("in1")), deliver(alu.getDock("in2")), deliver(alu.getDock("inOp")), collectAndSend(alu.getDock("out"), debugIn.getDataDestination()), recvAndDeliver(debugIn), }; Here we're putting literals 1 and 2 at the operand inputs to the adder, supplying the ADD opcode, delivering all three values, collecting the result and sending it to the Debug ship, and delivering the value once it arrives. Here's what you should see when you run it:
Indeed, Fleet knows that 1+2=3. A Third ProgramThe code below can be found in src/edu/berkeley/fleet/demo/Test3.java. In our third program, we'll show an example of flow control. To do this, we'll need five more helper functions (building up a small but good library of helper functions is the secret to generating Fleet code with ease!):
private static Instruction collectWaitAndSend(Dock dock, Destination dest) {
return new Instruction.Move(dock, false, Predicate.Default, false, dock.getPath(dest,null), true, true, true, false, true, false); } private static Instruction recvDeliverAndSendToken(Dock dock, Destination ack) { return new Instruction.Move(dock, false, Predicate.Default, false, dock.getPath(ack,null), false, true, true, false, true, true); } private static Instruction recvDeliver(Dock dock) { return new Instruction.Move(dock, false, Predicate.Default, false, null, false, true, true, false, true, false); } private static Instruction sendToken(Dock dock, Destination ack) { return new Instruction.Move(dock, false, Predicate.Default, false, dock.getPath(ack,null), false, false, false, false, false, true); } private static Instruction setInnerCounter(Dock dock, int immediate) { return new Instruction.Set(dock, false, Predicate.Default, SetDest.InnerLoopCounter, immediate); } We're also going to add another ship to our Fleet, a Fifo ship:
Fleet fleet = new Interpreter(new String[] {
"Alu2", "Fifo", "Debug" }, /* logging */ true); Now, we will generate instructions to deposit eight values into the fifo:
// fill up the fifo with numbers 1..8
ArrayList<Instruction> instructions = new ArrayList<Instruction>(); for(int i=0; i<8; i++) { instructions.add(literal(fifo.getDock("in"), i)); instructions.add(deliver(fifo.getDock("in"))); } And then additional instructions to pull those values out of the fifo and send them to the first input of the Alu, where they will be incremented.
for(Instruction i : new Instruction[] {
// repeat 8 times: wait for a token, sent a datum fifo.out->alu.in1 setInnerCounter(fifo.getDock("out"), 8), collectWaitAndSend(fifo.getDock("out"), alu.getDock("in1").getDataDestination()), // prime the pump with two tokens setInnerCounter(alu.getDock("in1"), 2), sendToken(alu.getDock("in1"), fifo.getDock("out").getDataDestination()), // literal to be added to each argument literal(alu.getDock("in2"), 1), // opcode=ADD literal(alu.getDock("inOp"), 0), // 8x: recieve an argument, add it, send acknowledgement token setInnerCounter(alu.getDock("in1"), 8), recvDeliverAndSendToken(alu.getDock("in1"), fifo.getDock("out").getDataDestination()), // 8x: deliver at in2 and inOp setInnerCounter(alu.getDock("in2"), 8), deliver(alu.getDock("in2")), setInnerCounter(alu.getDock("inOp"), 8), deliver(alu.getDock("inOp")), // 8x: take alu output and send it to the debug ship setInnerCounter(alu.getDock("out"), 8), collectAndSend(alu.getDock("out"), debugIn.getDataDestination()), setInnerCounter(debug.getDock("in"), 8), recvDeliver(debug.getDock("in")), }) instructions.add(i);
We need to use tokens to “flow-control” the connection from the Fifo to Alu2.in because if we do not, all eight items might spill out of the fifo and clog up the input to Alu2.in, possibly blocking off access to its instruction port. If Alu2.in never gets its instructions, the program will not run. Other failure modes are also possible. Finally, code to run our program and read back the output:
FleetProcess fp = fleet.run(instructions.toArray(new Instruction[0]));
for(int i=0; i<8; i++) System.out.println(fp.readWord().toLong()); fp.terminate(); You should get something like this:
|