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