Computer Laboratory

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