This article is part of a series documenting my first foray into FPGA programming. You might find it helpful to read the summary article1 first.

Introduction

Lattice make a breakout board2 for their iCE40HX-8K FPGA. It is a significantly bigger array than the HX1K chip on the iCEstick3.

For full documentation on the board, see the user guide4.

Walkthrough

Two steps are common to all the boards:

  1. Install the iCE40 toolchain5.
  2. Clone the repo:
$ git clone https://github.com/mjoldfield/ice40-blinky.git

Now let’s tackle the hardware.

By default, the programmer on the board programs the external flash chip. However, it is more convenient and faster to use the SRAM inside the FPGA. Moreover, the programming configuration below assumes that you’re programming the SRAM.

To enable SRAM programming, you need to change a few links on the board:

For more details, see pages 5 and 6 of the user guide6.

Having moved the links, connect the board to a USB port.

Now, build the relevant demo, and flash it to the board:

$ cd HX8K-breakout/
$ make prog

Finally, enjoy the blinkenlights7!

Testing

If you have a frequency counter to hand, measure the frequency on test point A: it should be exactly 6MHz. If you prefer something slower, you should find a frequency of exactly 1Hz, with a duty cycle of 1/16 on test point B.

Hardware Notes

Full schematics of the board are available in the user manual8. Here are some highlights, relevant to our simple project.

FPGA

The FPGA is a iCE40HX-8K in a 256-pin LFBGA.

Clock and PLL

A 12MHz clock from a ceramic resonator is provided on pin J3.

This FPGA has a PLL which lets us scale the incoming clock. Arbitrarily, we will try to get a 96MHz system clock, and to do this we need some magic numbers with which we can configure the PLL. Enter icepll:

$ icepll -i 12 -o 96 -m -f pll.v	
					
F_PLLIN:    12.000 MHz (given)		
F_PLLOUT:   96.000 MHz (requested)	
F_PLLOUT:   96.000 MHz (achieved)	
					
FEEDBACK: SIMPLE			
F_PFD:   12.000 MHz			
F_VCO:  768.000 MHz			
					
DIVR:  0 (4'b0000)			
DIVF: 63 (7'b1000010)			
DIVQ:  3 (3'b011)			
					
FILTER_RANGE: 1 (3'b001)		
					
PLL configuration written to: pll.v	

As you can see the PLL can generate this clock exactly.

Notice too, that icepll helpfully writes the relevant verilog to a file. Sadly though, the verilog doesn’t use global clock buffers, so it needs to be tweaked by hand.

LEDs

The port sports eight red LEDs arranged in a line. They are attached to pins B5, B4, A2, A1, C5, C4, B3, and C3.

Test points

As befits the name breakout board, many spare IO pins exist, and we use two as test points: B1 and B2.

Programming

The board has a FTDI 2232H USB interface which can be used to program both external flash and internal SRAM with iceprog from the IceStorm Tools. You must supply the -S flag to iceprog when programming the SRAM.

Note: jumpers J6 and J7 on the board govern whether the flash or SRAM is programmed. As shipped they are set for flash, but the walkthrough above moves them to SRAM mode.

Software Notes

Please remember that you can download all of this from GitHub9.

There are only four small files: a couple of bits of verilog, the pin definitions, and a Makefile.

The main source code

The code is much as you’d expect, though it takes slightly more care than its counterpart for the iCEstick10.

In particular, we use the PLL’s locked signal to reset things on power-up. Rather than a free-running binary counter, we also generate a precise 16Hz clock so that the 16-cycle animation should take exactly one second (modulo the accuracy of the master oscillator).

/*									
 * Top module for HX8K breakout blinky					
 * 									
 * Sweep light along LED array						
 * 									
 * Generate test signals at 6.0MHz and 1Hz.				
 */									
									
module top(input CLK							
	   , output LED0						
	   , output LED1						
	   , output LED2						
	   , output LED3						
	   , output LED4						
	   , output LED5						
	   , output LED6						
	   , output LED7						
	   , output TSTA						
	   , output TSTB						
	   );								
									
   // PLL to get 96MHz clock						
   wire       sysclk;							
   wire       locked;							
   pll myPLL (.clock_in(CLK), .global_clock(sysclk), .locked(locked));	
									
   // We want to do a 16-cycle pattern in 1s, i.e. tick at		
   // 16Hz. log_2 (96M / 16) = 22.516.. so use a 23-bit counter		
   localparam ANIM_PERIOD    = 96000000 / 16;				
   localparam SYS_CNTR_WIDTH = 23;					
									
   reg [SYS_CNTR_WIDTH-1:0] syscounter;					
   reg 			    anim_stb;					
   									
   always @(posedge sysclk)						
     if (locked && syscounter < ANIM_PERIOD-1)				
       begin								
	  syscounter <= syscounter + 1;					
	  anim_stb   <= 0;						
       end								
     else								
       begin								
	  syscounter <= 0;						
	  anim_stb   <= 1;						
       end								
		     							
   // animation phase: 4-bits so 16 cycles				
   reg [3:0]  anim_phase;						
									
   // a register holding LED state.					
   reg [7:0]  leds;							
									
   always @(posedge sysclk)						
     if (!locked)							
       anim_phase <= 0;							
     else if (anim_stb)							
       begin								
	  anim_phase <= anim_phase + 1;					
	  								
	  case (anim_phase)						
	    4'b0000: leds <= 8'b00000001;				
	    4'b1000: leds <= 8'b10000000;				
	    default:							
	      if (anim_phase[3])					
		leds <= leds >> 1;					
	      else							
		leds <= leds << 1;					
	  endcase							
       end // if (anim_stb)						
									
   assign { LED0, LED1, LED2, LED3, LED4, LED5, LED6, LED7 } = leds;	
         								
   // test signals on counter						
   assign TSTA = syscounter[3];                // 96MHz / 2^4  = 6MHz	
   assign TSTB = anim_phase == 0;              // 1Hz			
   									
endmodule

The PLL code

The PLL code is generated by icepll, then edited to use global buffers to distribute the clock and locked status.

Technical note TN125111 discusses clocks and PLLs on the iCE40.

/**									
 * PLL configuration							
 *									
 * This Verilog module was generated automatically			
 * using the icepll tool from the IceStorm project.			
 * Use at your own risk.						
 * 									
 * Subsequent tweaks to use a Global buffer were made			
 * by hand.								
 *									
 * Given input frequency:        12.000 MHz				
 * Requested output frequency:   96.000 MHz				
 * Achieved output frequency:    96.000 MHz				
 */									
									
module pll(								
        input  clock_in,						
        output global_clock,						
        output locked							
        );								
									
   wire        g_clock_int;						
   wire        g_lock_int;						
    									
   SB_PLL40_CORE #(							
                .FEEDBACK_PATH("SIMPLE"),				
                .DIVR(4'b0000),         // DIVR =  0			
                .DIVF(7'b0111111),      // DIVF = 63			
                .DIVQ(3'b011),          // DIVQ =  3			
                .FILTER_RANGE(3'b001)   // FILTER_RANGE = 1		
        ) uut (								
                .LOCK(g_lock_int),					
                .RESETB(1'b1),						
                .BYPASS(1'b0),						
                .REFERENCECLK(clock_in),				
                .PLLOUTGLOBAL(g_clock_int)				
                );							
									
   SB_GB clk_gb ( .USER_SIGNAL_TO_GLOBAL_BUFFER(g_clock_int)		
                  , .GLOBAL_BUFFER_OUTPUT(global_clock) );		
   									
   SB_GB lck_gb ( .USER_SIGNAL_TO_GLOBAL_BUFFER(g_lock_int)		
                  , .GLOBAL_BUFFER_OUTPUT(locked) );			
   									
endmodule

Makefile

Most of the rules are shared across different dev. boards: we need only to specify the FPGA and the programming software:

ARACHNE_DEVICE = 8k							
PACKAGE        = ct256							
									
ICETIME_DEVICE = hx8k							
									
# the -S flag says program the SRAM, not flash				
PROG_BIN     = iceprog -S						
									
include ../std.mk

Note the the programming command now sports a -S flag: this means program the SRAM, not the external flash chip.

Pin summary

Finally, we need to tell the software which pins are associated with the signals:

$ cat pins.pcf
set_io LED0 B5
set_io LED1 B4
set_io LED2 A2
set_io LED3 A1
set_io LED4 C5
set_io LED5 C4
set_io LED6 B3
set_io LED7 C3
set_io CLK  J3
set_io TSTA B1
set_io TSTB B2