Bluepsec Examples
NIOS Custom Instruction
Overview
Altera's NIOS soft core (processor) allows custom instructions to be created. Below is a simple generic wrapper for custom instruction and an example saturation-add instruction (including C driver code).
The Verilog output from this Bluespec imports cleanly into Altera's SOPC Builder tool. Note that to make this happen, the names of instances, methods and variables have been carefully chosen so that the produced Verilog contains names that SOPC Builder recognizes. This feels like a bit of a hack, but in practise I've not found it to be a big deal. For further information, please see note from the ACD course.
mkSaturationAdd is an example of how interfaces can be passed to a module. Note that modules have a default interface which isn't used by mkSaturationAdd, so its default interface is empty (i.e. "Empty ifc").
Custom Instruction Wrapper
/*****************************************************************************
NIOS Custom Instruction Wrapper
===============================
Simon Moore, Feb 2010
This package provides an wrapper for a two-operand NIOS custom instruction
with one (optional) result.
client.request.get() - provides the two operands
client.response.put() - returns the result as a Maybe type
(tagged Invalid if no data to be returned)
*****************************************************************************/
package CustomInstructionNIOS;
import FIFO::*;
import GetPut::*;
import ClientServer::*;
import SpecialFIFOs::*;
typedef Int#(32) NIOSwordT;
typedef struct {
NIOSwordT opA;
NIOSwordT opB;
} NIOS_Two_OperandsT deriving (Bits,Eq);
(* always_ready, always_enabled *)
interface NIOS_Two_Operand_Physical_Ifc;
method Action instruction_slave(Bool start, Bool clk_en, NIOSwordT dataa, NIOSwordT datab);
method Bool instruction_slave_done; // done flag
method NIOSwordT instruction_slave_result; // result output
endinterface
interface NIOS_Two_Operand_Physical_Embedded_Ifc;
interface NIOS_Two_Operand_Physical_Ifc nios_custom;
endinterface
interface NIOS_Two_Operand_Instruction_Ifc;
interface NIOS_Two_Operand_Physical_Ifc nios_custom;
interface Client#(NIOS_Two_OperandsT, Maybe#(NIOSwordT)) client;
endinterface
module mkNIOS_Two_Operand_Custom_Instruction(NIOS_Two_Operand_Instruction_Ifc);
Wire#(NIOS_Two_OperandsT) ops_bw <- mkBypassWire;
Wire#(Bool) start_trigger <- mkBypassWire;
Wire#(Bool) pipeline_enable <- mkBypassWire;
PulseWire start_handled <- mkPulseWire;
Reg#(Bool) start_saved <- mkReg(False);
FIFO#(Maybe#(NIOSwordT)) result_fifo <- mkLFIFO;
Wire#(NIOSwordT) result_dw <- mkDWire(0);
PulseWire done_pw <- mkPulseWire;
// save the start signal in case the abstract "request" interface
// isn't read to "get"
rule save_start_signals;
if(start_trigger && !start_handled) start_saved <= True;
if(start_handled) start_saved <= False;
endrule
// send the result to the NIOS if the custom instruction hasn't
// been stalled by the NIOS pipeline
rule deq_result (pipeline_enable);
result_dw <= fromMaybe(0,result_fifo.first);
done_pw.send();
result_fifo.deq;
endrule
// wire up the physical interface
interface NIOS_Two_Operand_Physical_Ifc nios_custom;
method Action instruction_slave(start, clk_en, dataa, datab);
ops_bw <= NIOS_Two_OperandsT{opA: dataa, opB: datab};
start_trigger <= start;
pipeline_enable <= clk_en;
endmethod
method Bool instruction_slave_done;
return done_pw;
endmethod
method NIOSwordT instruction_slave_result;
return result_dw;
endmethod
endinterface
// provide the abstract interface
interface Client client;
interface Get request;
method ActionValue#(NIOS_Two_OperandsT) get if (start_trigger || start_saved);
start_handled.send();
return ops_bw;
endmethod
endinterface
method response = toPut(result_fifo);
endinterface
endmodule
/*****************************************************************************
NIOS Custom Instruction with all Operands (i.e. supports internal memory)
*****************************************************************************/
typedef UInt#(5) Custom_RegT;
typedef struct {
NIOSwordT opA;
NIOSwordT opB;
Bool readOpA; // True=use opA, False=use internal register
Bool readOpB;
Bool result2nios;
Custom_RegT rA;
Custom_RegT rB;
Custom_RegT rC;
} NIOS_All_OperandsT deriving (Bits,Eq);
(* always_ready, always_enabled *)
interface NIOS_All_Operands_Physical_Ifc;
method Action instruction_slave(Bool start, Bool clk_en, NIOSwordT dataa, NIOSwordT datab,
Bool readra, Bool readrb, Bool writerc, Custom_RegT a, Custom_RegT b, Custom_RegT c);
method Bool instruction_slave_done; // done flag
method NIOSwordT instruction_slave_result; // result output
endinterface
interface NIOS_All_Operands_Physical_Embedded_Ifc;
interface NIOS_All_Operands_Physical_Ifc nios_custom;
endinterface
interface NIOS_All_Operands_Instruction_Ifc;
interface NIOS_All_Operands_Physical_Ifc nios_custom;
interface Client#(NIOS_All_OperandsT, Maybe#(NIOSwordT)) client;
endinterface
module mkNIOS_All_Operands_Custom_Instruction(NIOS_All_Operands_Instruction_Ifc);
Wire#(NIOS_All_OperandsT) ops_bw <- mkBypassWire;
Wire#(Bool) start_trigger <- mkBypassWire;
Wire#(Bool) pipeline_enable <- mkBypassWire;
PulseWire start_handled <- mkPulseWire;
Reg#(Bool) start_saved <- mkReg(False);
FIFO#(Maybe#(NIOSwordT)) result_fifo <- mkLFIFO;
Wire#(NIOSwordT) result_dw <- mkDWire(0);
PulseWire done_pw <- mkPulseWire;
// save the start signal in case the abstract "request" interface
// isn't read to "get"
rule save_start_signals;
if(start_trigger && !start_handled) start_saved <= True;
if(start_handled) start_saved <= False;
endrule
// send the result to the NIOS if the custom instruction hasn't
// been stalled by the NIOS pipeline
rule deq_result (pipeline_enable);
result_dw <= fromMaybe(0,result_fifo.first);
done_pw.send();
result_fifo.deq;
endrule
// wire up the physical interface
interface NIOS_All_Operands_Physical_Ifc nios_custom;
method Action instruction_slave(start, clk_en, dataa, datab,
readra, readrb, writerc, a, b, c);
ops_bw <= NIOS_All_OperandsT{opA: dataa, opB: datab,
readOpA: readra, readOpB: readrb, result2nios: writerc,
rA: a, rB: b, rC: c};
start_trigger <= start;
pipeline_enable <= clk_en;
endmethod
method Bool instruction_slave_done;
return done_pw;
endmethod
method NIOSwordT instruction_slave_result;
return result_dw;
endmethod
endinterface
// provide the abstract interface
interface Client client;
interface Get request;
method ActionValue#(NIOS_All_OperandsT) get if (start_trigger || start_saved);
start_handled.send();
return ops_bw;
endmethod
endinterface
method response = toPut(result_fifo);
endinterface
endmodule
endpackage: CustomInstructionNIOS
Link to the CustomInstructionNIOS.bsv source
Saturation Add Instruction
/*****************************************************************************
Custom Instruction Example - Saturation Add
===========================================
Simon Moore, Feb 2010
This package provides an example of a NIOS custom instruction which computes
saturation add on unsigned 32-bit numbers (i.e. adding two very large
numbers doen't overflow but instead results in the maximum integer).
*****************************************************************************/
package CustomInstruction_SaturationAdd;
import FIFO::*;
import GetPut::*;
import ClientServer::*;
import CustomInstructionNIOS::*;
import Clocks::*;
// module which describes the the functionality of new instruction
module mkSaturationAdd(Get#(NIOS_Two_OperandsT) operands, Put#(Maybe#(NIOSwordT)) result, Empty inf);
rule do_instruction;
let ops <- operands.get(); // get operands
UInt#(33) res = extend(unpack(pack(ops.opA))) + extend(unpack(pack(ops.opB)));
if((res>>32) == 1) res = (1<<32)-1;
result.put(tagged Valid unpack(pack(truncate(res))));
endrule
endmodule
// top-level module which glues the Bluespec wrapper (from CustomIntructionNIOS)
// to the new instruction module
(* synthesize,
clock_prefix = "nios_custom_instruction_slave_clk",
reset_prefix = "nios_custom_instruction_slave_reset" *)
module mkCustomInstruction_SaturationAdd(NIOS_Two_Operand_Physical_Embedded_Ifc);
// expose and the invert reset since Bluespec uses a negative reset
// and the NIOS interface uses a negative reset
Reset rst <- exposeCurrentReset;
Reset rst_n <- mkResetInverter(rst);
// instantiate the wrapper
NIOS_Two_Operand_Instruction_Ifc physical <-
mkNIOS_Two_Operand_Custom_Instruction(reset_by rst_n);
// instantiate the new instruction implementation
Empty my_instruction <-
mkSaturationAdd(physical.client.request, physical.client.response,
reset_by rst_n);
// N.B. don't put any rules here since they will receive the
// wrong reset signal and will be held permenatnly in reset
interface nios_custom = physical.nios_custom;
endmodule
endpackage: CustomInstruction_SaturationAdd
Link to the CustomInstruction_SaturationAdd.bsv source
Saturation Add C-code
#include <stdio.h>
#include "system.h"
int main()
{
int j;
printf("Starting\n");
for(j=1; j<10; j++) {
printf("%d+10 = %d\n",j,ALT_CI_MKCUSTOMINSTRUCTION_SATURATIONADD_INST(j,10));
}
for(j=0xfffffff0; j<10; j++) {
printf("0x%08x+10 = 0x%08x\n",j,ALT_CI_MKCUSTOMINSTRUCTION_SATURATIONADD_INST(j,10));
}
printf("The End\n");
return 0;
}
Link to the sattest.c source
Custom Instruction with Internal Registers
This is a version of the satuation add instruction with the addition of internal registers. This isn't particularly useful in of itself but it does illustrate how the custom instruction can have internal registers and how these can be accessed
/*****************************************************************************
Custom Instruction Example - Saturation Add with Memory
=======================================================
Simon Moore, May 2010
This package provides an example of a NIOS custom instruction which computes
saturation add on unsigned 32-bit numbers (i.e. adding two very large
numbers doen't overflow but instead results in the maximum integer).
Extended to provide an internal memory (a bit pointless, but tests the full
operand interface.
*****************************************************************************/
package CustomInstruction_SaturationAddMem;
import FIFO::*;
import GetPut::*;
import ClientServer::*;
import CustomInstructionNIOS::*;
import Clocks::*;
import RegFile::*;
// module which describes the the functionality of new instruction
module mkSaturationAddMem(Get#(NIOS_All_OperandsT) operands, Put#(Maybe#(NIOSwordT)) result, Empty inf);
// Vector#(Exp#(SizeOf(Custom_RegT)), Reg#(NIOSwordT)) rf <- replicateM(mkReg(0));
RegFile#(Custom_RegT,NIOSwordT) rf <- mkRegFile(0,31);
rule do_instruction;
let ops <- operands.get(); // get operands
let opa = ops.readOpA ? ops.opA : rf.sub(ops.rA);
let opb = ops.readOpB ? ops.opB : rf.sub(ops.rB);
UInt#(33) res = extend(unpack(pack(opa))) + extend(unpack(pack(opb)));
if((res>>32) == 1) res = (1<<32)-1;
if(!ops.result2nios)
begin
rf.upd(ops.rC, unpack(pack(truncate(res))));
result.put(tagged Invalid);
end
else
result.put(tagged Valid unpack(pack(truncate(res))));
endrule
endmodule
// top-level module which glues the Bluespec wrapper (from CustomIntructionNIOS)
// to the new instruction module
(* synthesize,
clock_prefix = "nios_custom_instruction_slave_clk",
reset_prefix = "nios_custom_instruction_slave_reset" *)
module mkCustomInstruction_SaturationAddMem(NIOS_All_Operands_Physical_Embedded_Ifc);
// expose and the invert reset since Bluespec uses a negative reset
// and the NIOS interface uses a negative reset
Reset rst <- exposeCurrentReset;
Reset rst_n <- mkResetInverter(rst);
// instantiate the wrapper
NIOS_All_Operands_Instruction_Ifc physical <-
mkNIOS_All_Operands_Custom_Instruction(reset_by rst_n);
// instantiate the new instruction implementation
Empty my_instruction <-
mkSaturationAddMem(physical.client.request, physical.client.response,
reset_by rst_n);
// N.B. don't put any rules here since they will receive the
// wrong reset signal and will be held permenatnly in reset
interface nios_custom = physical.nios_custom;
endmodule
endpackage: CustomInstruction_SaturationAddMem
Link to the CustomInstruction_SaturationAddMem.bsv source
C-code to Test Custom Instruction with Internal Registers
//*****************************************************************************
// SatMemTst
// =========
// Simon Moore, May 2010
//
// This (rather hacky) code tests out the Saturation-Add with memory test
// custom instruction. Saturation-Add operations can be performed on
// values held in NIOS registers or in custom registers within the custom
// instruction block. Similarly, the destination register can be a NIOS
// register or a custom register.
// Please remember that this design is simply illustrative rather than being
// directly useful.
//*****************************************************************************
#include <stdio.h>
#include <system.h>
void tst_sataddmem()
{
int a,b;
int j,r,k;
int aval[4];
int correct[16];
aval[0] = 3;
aval[1] = 0;
aval[2] = -1;
aval[3] = -3;
printf("Test direct satuation add computation\n");
for(j=k=0; j<4; j++) {
a=aval[j];
for(b=-3; b<=6; b+=3) {
r=ALT_CI_MKCUSTOMINSTRUCTION_SATURATIONADDMEM_INST(a,b);
printf("%d+%d=%d 0x%08x+0x%08x=0x%08x\n",a,b,r,a,b,r);
correct[k]=r;
k++;
}
}
printf("\nTest saturation add memory\n");
b=0;
a=3;
// write values
a++; asm volatile ( "custom 0, c0, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a++; asm volatile ( "custom 0, c1, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a++; asm volatile ( "custom 0, c2, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a++; asm volatile ( "custom 0, c3, %0, %1" : /* no output */ : "r" (a), "r" (b) );
// read back values
asm volatile ( "custom 0, %0, %1, c3" : "=r" (a) : "r" (b) ); printf("read=%d\n",a);
asm volatile ( "custom 0, %0, %1, c2" : "=r" (a) : "r" (b) ); printf("read=%d\n",a);
asm volatile ( "custom 0, %0, %1, c1" : "=r" (a) : "r" (b) ); printf("read=%d\n",a);
asm volatile ( "custom 0, %0, %1, c0" : "=r" (a) : "r" (b) ); printf("read=%d\n",a);
printf("\nTest saturation add via memory\n");
b=0;
j=0;
// write values
a=aval[j]; j++; asm volatile ( "custom 0, c0, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a=aval[j]; j++; asm volatile ( "custom 0, c1, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a=aval[j]; j++; asm volatile ( "custom 0, c2, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a=aval[j]; j++; asm volatile ( "custom 0, c3, %0, %1" : /* no output */ : "r" (a), "r" (b) );
a=0;
b=-3; asm volatile ( "custom 0, c4, %0, %1" : /* no output */ : "r" (a), "r" (b) );
b+=3; asm volatile ( "custom 0, c5, %0, %1" : /* no output */ : "r" (a), "r" (b) );
b+=3; asm volatile ( "custom 0, c6, %0, %1" : /* no output */ : "r" (a), "r" (b) );
b+=3; asm volatile ( "custom 0, c7, %0, %1" : /* no output */ : "r" (a), "r" (b) );
#define printresults printf("read = %d = 0x%08x %s (expected %d)\n",a,a,a==correct[k] ? "PASS": "FAIL",correct[k]);
// read back values
k=0; asm volatile ( "custom 0, %0, c0, c4" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c0, c5" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c0, c6" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c0, c7" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c1, c4" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c1, c5" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c1, c6" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c1, c7" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c2, c4" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c2, c5" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c2, c6" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c2, c7" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c3, c4" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c3, c5" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c3, c6" : "=r" (a) ); printresults;
k++; asm volatile ( "custom 0, %0, c3, c7" : "=r" (a) ); printresults;
}
int main()
{
printf("The Start\n");
tst_sataddmem();
printf("The End\n");
return 0;
}
Link to the satmemtst.c source