Close

Programming an iCE40HX4K with an Arduino and Python

Frank BussFrank Buss wrote 04/19/2019 at 13:05 • 4 min read • Like

You can program an iCE40 FPGA with a microcontroller, for example an Arduino with 3.3V IO. I tested it with a SparkFun SAMD21 Mini Breakout and a custom board. This is the setup:

I have an additional flash and SRAM on my board, but you don't need this for a minimal test setup. This is the minimal circuit diagram how to use the FPGA (click on the image to zoom in) :

With the iCEcube2 IDE I wrote this test program:

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.numeric_std.all;

entity top is
	port (
		clk : in STD_LOGIC;
		led : out STD_LOGIC
	);
end top;

architecture Behavioral of top is
	signal blinker : natural range 0 to 25000000;
	signal blink : std_logic;

begin
	process (clk)
	begin
		if rising_edge(clk) then
			blinker <= blinker + 1;
			if blinker = 4000000 then
				blinker <= 0;
				blink <= not blink;
			end if;
			led <= blink;
		end if;
	end process; 
end Behavioral;

When I compile it, I get the file "test_Implmnt/sbt/outputs/bitmap/top_bitmap.bin".

It is easy to configure the FPGA: First set the CRESET_B pin to low to reset the FPGA. Then hold down the SS pin and set CRESET_B to high, which starts the SPI slave configuration mode. You can then send the bin file data with the SDI and SCK pins.

On the Arduino side I wrote this script for the transfer:

#include <SPI.h>

#define RESET_PIN 10

#define ACK 6
#define NAK 21

const int BUF_SIZE = 128;

uint8_t buffer[BUF_SIZE];

void setup() {
  pinMode(RESET_PIN, OUTPUT);
  SerialUSB.begin(115200);

  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
}

void readBlock() {
  // wait for block start
  unsigned long t0 = millis();
  while (true) {
    if (SerialUSB.available()) {
      break;
    }
    unsigned long t1 = millis();
    if (t1 - t0 > 1000) {
      // timeout
      SerialUSB.write(NAK);
      return;
    }
  }

  // read data
  int received = SerialUSB.readBytes((char*) buffer, BUF_SIZE);
  if (received != BUF_SIZE) {
    // timeout
    SerialUSB.write(NAK);
    return;
  }

  // send with SPI
  SPI.transfer(buffer, BUF_SIZE);

  // acknowledge
  SerialUSB.write(ACK);
}

void loop() {
  // read command
  char cmd;
  while (true) {
    if (SerialUSB.available()) {
      cmd = SerialUSB.read();
      break;
    }
  }

  // execute command
  switch (cmd) {
    case 'r':
      // reset
      digitalWrite(RESET_PIN, LOW);
      delay(10);
      digitalWrite(RESET_PIN, HIGH);
      delay(10);
      SerialUSB.write(ACK);
      break;
    case 'b':
      readBlock();
      break;
    default:
      SerialUSB.write(NAK);
  }
}

And then I could upload the bin file with this Python script: 

#!/usr/bin/python3

import serial

ACK = 6
NAK = 21

ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)

# read and evaluate response
def readResponse():
    d = ser.read(1)
    if len(d) == 0:
        raise Exception('response timeout')
    if d[0] == ACK:
        return
    if d[0] == NAK:
        raise Exception('NAK received')
    raise Exception('unkown response')

# send a command    
def sendCommand(cmd):
    ser.write(cmd.encode())
    readResponse()

# send a block
def sendBlock(data):
    ser.write('b'.encode())
    for d in data:
        ser.write([d])
    readResponse()

# read file and add some bytes at the end for the required 49 clocks for configuration
inputFile = open('top_bitmap.bin', 'rb')
bufSize = 128
data = bytearray(inputFile.read()) + bytearray([0] * (bufSize * 2))

# reset FPGA
sendCommand('r')

# send data to FPGA
while len(data) > bufSize:
    block = data[:bufSize]
    data = data[bufSize:]
    sendBlock(block)

At the end of the upload the CDONE pin goes low, if there was no error. I added an LED to it which goes off when it is done.

The other LED on pin 143  blinked with 1 Hz after the configuration, as programmed in the VHDL file.

Like

Discussions