MuraxArduino is an implementation of an Arduino board for open source FPGAs. It uses a RISC-V CPU. It currently only supports the mystorm BlackIce II ice40 board.
It allows the FPGA to be programmed using the Arduino IDE, the Arduino API and standard Arduino libraries, but it makes use of the FPGA to provide accelerated hardware peripherals.
It allows you to configure as many peripherals such UART, SPI, I2C, PWM, timers, pin interrupts etc. as you need for your application, and is not limited by a small number of such hardware peripherals that hard-wired microcontrollers typically support. You do not need to resort to slow CPU-intensive bit-banged versions of such peripherals.
It can drive peripherals such as LED panels, LED strips, VGA, HDMI, ADC, etc., much faster than microcontrollers can, and all without CPU involvement. (Not all of these are implemented yet).
It is a version of f32c/arduino that works with the SpinalHDL Vexriscv Murax SoC.
There is Arduino Board Manager support.
In File->Preferences->Additional Boards Manager URLs enter:
https://raw.githubusercontent.com/lawrie/MuraxArduino/master/package_murax_core_index.json
Select pull down menu Tools->Board->Board Manager and install Murax Arduino.
Then select, Board: Blackice Murax FPGA board, CPU Speed: 50 Mhz, RAM Size: 512KB SRAM external, port /dev/tyyUSB0
The SpinalHDL scripts including the Makefile, pcf file and top level Verilog files are at https://github.com/lawrie/VexRiscv/tree/master/scripts/Murax/BlackIce, but there is a copy of the BlackIce II binary and the pcf file in the MuraxArduino repository, so you can try MuraxArduino out without installing SpinalHDL, by doing
git clone https://github.com/lawrie/MuraxArduino
cd MuraxArduino/fpga/BlackIce
stty -F /dev/ttyACM0 raw
cat bin/toplevel.bin /dev/ttyACM0
You need both USB connections to the Blackice II board, as /dev/ttyUSB0 is used for uploading programs from the Arduino IDE and for the Arduino console.
As with other implementations of Arduino, the CPU is reset and the Arduino program restarts when a program is uploaded, an Arduino console is opened, or another connection is made to /dev/ttyUSB0, but the syncronisation with the Arduino console is not perfect -see "Bugs" below.
My version of the Vexriscv is needed to configure a bitstream for MuraxArduino.
A configuration file specifies the peripheral required and the cpu and sram configuration.
The configuration file references pin names defined in the toplevel.pcf file. It should be possible to change the pin names by just changing toplevel.pcf and config.txt, but currently the whole of toplevel.v is not being generated, and the pin names would also need to be changed in that file, so currently it is best to leave the pin name in toplevel.pcf, unchanged.
The config.txt configuration file is processed by running:
git clone https://github.com/lawrie/VexRiscv
cd scripts/Murax/Blackice
python3 parse.py
This generates a config.scala file that is to generate the Verilog file, MuraxVerilog.v, that implements the CPU and peripherals.
A toplevel.v verilog file is also used, which does the ice40-specific pin configuration using SB_IO and also invokes the PLL generated by icepll to set the desired CPU speed. The toplevel.v file is configured to include the reqyured peripherals using two file generated from the config.txt file, config.vh and assignments.vh.
C headers file in the Arduino variants directory are also generated. These are io.h and variant.h.
These files are then installed by running install.sh.
The install.sh file might need modifying for your system. It copies config.scala to MuraxBlacxkiceTest.scala in the scala src/main directory.
And it copies io.h and variant.h to a BlackiceTest Arduino variant directory.
To generate the MuraxArduino.v file you need sbt and the other SpinalHDL dependencies installed - see the VexRiscv README file.
To generate MuraxArduino.v, you do:
cd scripts/Murax/Blackice
make generate
and then to generate and upload the Blackice II bitstream, you do:
make prog
The binary is in bin/toplevel.bin.
The CPU is the Vexriscv Risc-V 32-bit SpinalHDL implementation running at 50Mhz. See MuraxArduino.scala for the configuration used for the CPU.
The CPU speed is set by the pll in toplevel_pll.v.
A typical CPU configuraion in config.txt is:
cpu
coreFrequency=50 MHz
input mainClk=CLK # Not currently used
onChipRamSize=10 kB
onChipRamHexFile="src/main/ressource/hex/muraxArduino.hex"
ioAddress=0xF0000000l
ramAddress=0x80000000l
If you select the 8Kb BRAM internal option, you get an 8kb device with a bootloader of just over 2kb, leaving just under 6kb for the Arduino sketch. The BRAM is mapped onto address 0x80000000. The BRAM could be increased to 12kb, but currently the top 4kb is just used for the stack and function pointers for interrupts. The BRAM version may not currently be fully working.
The SRAM implementation is MuraxSram.scala.
If you select the 512KB SRAM external option (the default) , you get the Blackice II external SRAM. The bootloader still runs in BRAM and the stack is still in BRAM, but the code and static data is in external SRAM which is mapped to address 0x90000000. The heap size is currently fixed as 64kb.
It is recommended that SRAM is used as the BRAM implementation is not very robust or well tested.
A typical sram configuration is:
sram
size=512 kB
addressWidth=19 # Needs to be 18 in toplevel.v as in half-words
dataWidth=16
address=0x90000000l
output addr=ADR[17:0]
inout dat=DAT[15:0]
output we=RAMWE
output oe=RAMOE
output cs=RAMCS
output lb=RAMLB
output ub=RAMUB
There are currently two 32-bit GPIO peripherals, GPIO A and GPIO B.
A typical config.txt entry for GPIO A is:
gpio A
address=0x00000
width=32
inout gpioA=GPIOA[31:0]
GPIO A corresponds to Arduino digital pins 0 - 31, with that definitiopn and the mapping of pin names in toplevel.pcf, the Arduino pin maopping for Blackice II is:
Pin 0 : LED 1 (Red)
Pin 1 : LED 2 (Yellow)
Pin 2 : LED 3 (Green)
Pin 3 : LED 4 (Blue)
Pin 4 : Blackice pin 33 on Pmod 11
Pin 5 : Blackice pin 21 on Pmod 11
Pin 6 : Blackice Pin 31 on Pmod 12
Pin 7 : Blackice Pin 32 on Pmod 12
Pin 8 : Button 1
Pin 9 : Button 2
Pin 10 : Switch 1
Pin 11 : Switch 2
Pin 12 : Switch 3
Pin 13 : Switch 4
Pin 14 : Blackice pin 87 on Pmod 2
Pin 15: : Blackice pin 90 on Pmod 2
Pins 16-19: Pmod 5
Pins 20-23: Pmod 6
Pins 24-27: Pmod 3
Pins 28-31: Pmod 4
A typical config.txt definition for GPIO B is:
gpio B
address=0x08000
width=17
inout gpioB=GPIOB[16:0]
GPIO B corresponds to Arduino digital pins 32 - 50 and with that definition and the pin mapping in toplevel.pcf, Blackice II pins are mapped as follows:
Pins 32-35: Pmod 7
Pins 36-39: Pmod 9
Pins 40-43: Pmod 10
Pin 44 : Blackice pin 34 on Pmod 11
Pin 45 : Blackice pin 22 on Pmod 11
Pin 46 : Blackice pin 94 on Pmod 1
Pin 47 : Blackice pin 26 on Pmod 12
Pin 48 : Blackice Pin 25 on Pmod 12
The following can also be used for output:
Pin 49 : DEBUG LED pin (green)
Pin 50 : DONE LED pin (red)
Various pins can be multiplexed with peripherals - see Mux below.
The GPIO pins are accessed using the Arduino pinMode, digitalRead and digitalWrite functions. The INPUT_PULLUP mode is not implemented and is treated as INPUT.
The GPIO peripheral is implemented by the spinal.lib TristateArray. The SB_IO instances are in toplevel.v.
A typical config.txt entry for the main uart is:
uart
address=0x10000
input rxd=UART_RX
output txd=UART_TX
input reset=GRESET
and for a second uart is
uart A
address=0x11000
input rxd=GPIOA[16]
output txd=GPIO[17]
mux=13
The main UART peripheral maps to the Blackice USB 2 connector on pins 85 and 88. It is accesssed using the Arduino Serial class. There is no flow control. This peripheral is required. It is used by the bootloader and accessed in Arduino by the Serial class.
Up to two extra uart peripherals, uart A and uart B, corresponding to Serial1 and Serial2, can be configred.
The UART peripheral uses the spinal.lib implementation.
A typical config.txt entry is:
machineTimer
address=0xB0000
A 32-bit microsecond machine timer is used for the implementation of the millis, micros, delay and delayMicroseconds methods, and so is mandatory.
MachineTimer is implemented by MachineTimer.scala.
A typical config.txt entry is:
mux
address=0xD0000
width=32
There is a mandatory single Mux peripheral which has 32 pins controlled by a 32-bit register. The mux pins are implemented in assignments.v.
It allows FPGA pins to be multiplexed between different peripherals, such as between GPIO and another peripheral.
12 muxes are currently used as follows, in the configuration described in this README:
Mux 0 : shiftIn clk on Blackice pin 21
Mux 1 : shiftOut clk and data on Blackice pins 31 and 32
Mux 2 : 7-segment display 1 on Pmods 5 and 9
Mux 3 : 4 servos on Pmod 12
Mux 4 : 7-segment display 0 on Pmods 3 and 5
Mux 5 : SPI on Pmod 10
Mux 6 : PWM on Blackice pin 95
Mux 7 : PWM on DEBUG pin
Mux 8 : PWM on DBG1 pin
Mux 9 : Tone on Blackice Pin 26
Mux 10: PWM on Blackice pin 34 on Pmod 11
Mux 11: PWM Blackice pin 22 on Pmod 11
Mux 12: Ws2811 Led strip on Pmod 12, Blackice pin 34
Mux 13: Uart A
Mux 14: SPA a
The Mux is implemented by Mux.scala.
A typical config.txt entry for i2c is:
i2c
address=0x70000
inout sda=SDA
inout scl=SCL
There is a single I2C peripheral on Blackice pins 95 (SDA) and 93 (SCL).
It is accessed using the Arduino Wire class.
Both master and slave are supported, but only master has been tested, and the current Wire library does not support it.
The I2c peripheral uses the spinal.lib implementation.
Here is the I2C master being used to drive an ssd1306 OLED display using Arduino Examples/SSD1306/ssd1306_128x64_i2c:
Typical config.txt entries are:
spiMaster
address=0x60000
mux=5
output sclk=GPIOB[8]
output mosi=GPIOB[9]
input miso=GPIOB[10]
output ss=GPIOB[11]
and
spiMaster A
address=0x61000
mux=14
output sclk=GPIOA[28]
output mosi=GPIOA[29]
input miso=GPIOA[30]
output ss=GPIOA[31]
This gives a SPI master peripheral on Pmod 10. SPI slave is not currently supported.
The optional second SPI master corresponds to the Arduino C++ object, SPI1.
The SPI peripheral uses the spinal.lib implementation.
Here is harware SPI driving an SSD1306 display using Arduino Examples/SSD1306/ssd1306_128x64_spi with hardware SPI selected:
A typical config.txt entry for the pulseIn peripheral is:
pulseIn
address=0x80000
width=2
input pins=GPIOB[12], GPIOB[13]
This gives a single PulseIn peripheral with two pulseIn pins on BlackIce pins 34 and 22. It can be used in combination with a trigger pin to drive an HC-SR04 ping sensor.
It is accessed using the Arduino pulseIn and pulseInLong methods. The pin number is the channel (0 or 1).
The PulseIn peripheral is implemented by PulseIn.scala.
Here is the PulseIn peripheral used with a 3.3v HC-SR04 Ping sensor, reading the display to the nearest object and displaying in in hex centimeters on a 7-segment display:
A typical config.txt entry is:
pwm
address=0x30000
width=5
output pins=GPIOB[13], GPIOB[12], DONE, DEBUG, GPIOB[14]
mux=11,10,8,7,6
There makes 5 PWM pins available on BlackIce II: pin 94 and on the DBG1 and DONE LED pins, and two others.
The PWM duty cycle is set using the Arduino analogWrite method. The pin number is the PWM channel.
Any number (up to 64) of PWM pins can be configured, but currently only 3 are used.
The PWM is peripheral is implemented by PWM.scala.
A typical entry in config.txt is:
address=0x40000
output pin=GPIOB[15]
mux=9
This gives a single Tone peripheral available on Blackice Pmod 12, pin 26.
It corresponds to the Arduino tone() and notTone() methods. The pin number on these methods is ignored.
The Tone is peripheral is implemented by Tone.scala.
Typical entries in config.txt are:
sevenSegment A
address=0x90000
mux=4
output digitPin=GPIOB[3]
output segPins=GPIOB[7:4], GPIOB[2:0]
sevenSegment B
address=0x98000
mux=2
output digitPin=GPIOA[27]
output segPins=GPIOA[19:16], GPIOA[26:24]
This gives two 7-segment peripherals designed to be used with Digilent Pmods, Pmod SSD.
The first (channel 0) is accessed by the SevenSegment class and is on Blackice Pmods 7 and 9.
The second (channel 1) is multiplexed with GPIO and is on Blackice Pmods 3 and 5.
These are supported by the SevenSegment library. You need to declare "SevenSegmentClass SevenSegment(1);" to access the channel 1 instance.
The 7-segment peripheral is implemented by SevenSegment.scala.
Here are two 7-segment displays being used:
A typical entry in config.txt is:
shiftIn
address=0xA00000
mux=0
output clockPin=GPIOA[5]
input dataPin=GPIOB[13]
This gives a single ShiftIn peripheral on BlackIce Pmod 11, pins 31 and 32.
It is accessed using the Arduino shiftIn function.
As the ShiftIn clk pin is muxed with GPIO pin 5, mux 0 is set by the shiftIn method and can only be unset by direct use of the Mux peripheral.
The ShiftIn peripheral is implemented by ShiftIn.scala.
Here is the ShiftIn peripheral being using with a CD4021BE 8-stage static shift register following the Arduino tutotrial:
A typical entry is config.txt is:
shiftOut
address=0x50000
output clockPin=GPIOA[6]
output dataPin=GPIOA[7]
mux=1
This gives a single ShiftOut peripheral on BlackIce Pmod 12, pins 21 and 22.
It is accessed using the Arduino shiftOut function.
As the ShiftOut clk and data pins are muxed with GPIO pins 6 and 7, mux 1 is set by the shiftOut method and can only be unset by direct use of the Mux peripheral.
The ShiftOut peripheral is implemented by ShiftOut.scala.
Here is the ShiftOut peripheral driving a 74HC595 8-Bit Shift Register following the Arduino tutorial:
A typical config.txt entry for the Timer peripheral that supports timer interrupts is:
timer
address=0x20000
Timer interrupts are implemented usinfg the MsTimer2 library and the spinal.lib Timer implementation.
A typical entry for the PinInterrupt peripheral, which implements pin-change interrupts, is:
pinInterrupt
address=0xE0000
width=2
input pins=GPIOA[9:8]
The Arduino atachInterrupt and detachInterrupt functions are supported on up to 32 pins. The pinInterrupt config.txt given above supports pin interrupts on just the two Blackice buttons with interrupt numbers 0 and 1.
The PinInterrupt peripheral is implemented by PinInterrupt.scala.
Interrupts currently go to an interrupt function in the bootloader, which determines the type of interrupt and calls the appropriate interrupt function.
A typical config.txt entry for thw Quadrature peripheral is:
quadrature
address=0xF8000
input quadA=GPIOA[16]
input quadB=GPIOA[17]
This gives a single quadrature peripheral on Pmod 5 that supports encoders and encoder motors.
The Quadrature peripheral is multiplexed with GPIO pins 16 and 17 and with the channel 1 7-segment display.
The encoder position is read using the read method of the Quadrature library. Using this method sets mux 3 and it can only be unset by direct use of the Mux peripheral.
The Quadrature peripheral is implemented by Quadrature.scala.
Here is a rotary encoder using the Quadrature peripheral, and displaying an 8-bit position on a 7-segment display:
A typical config.txt entry for the Servo peripheral is:
servo
address=0xC0000
width=4
mux=3
output pins=GPIOB[16], GPIOB[15], GPIOA[6], GPIOA[7]
The Servo peripheral supports up to 32 servos.
The config.txt entry given above supports one instance with 4 servos on Pmod 12. A Digilent CON3 R/C Servo connectors can be used.
Here is a servo being driven using the Digilent Pmod and Arduino Examples/Servo/Sweep:
A typical config.txt entry for Jtag is:
jtag # Pin mappings not currently used
input tck=JTAG_TCK
input tms=JTAG_TMS
input tdi=JTAG_TDI
output tdo=JTAG_TDO
A Jtag debugger for the Risc-V processor is supported on Blackice Pmod 8. Most USB Jtag devices are supported. It has been tested wiith an Anlogic Lychee Tang device.
Jtag uses the spinal.lib implementation.
You need to clone and build the Risc-V version of open ocd.
And then add lichee_tang.cfg to openocd_risc/tcl/interface/ftdi, with the contents:
adapter_khz 1000
#source [find interface/ftdi/olimex-arm-usb-tiny-h.cfg]
interface ftdi
ftdi_device_desc "Dual RS232"
ftdi_vid_pid 0x0403 0x6010
ftdi_layout_init 0x0008 0x001b
ftdi_layout_signal nSRST -oe 0x0020 -data 0x0020
To run openocd, do:
cd openocd_risc
src/openocd -f tcl/interface/ftdi/liichee_tang.cfg -c 'set MURAX_CPU0_YAML ../VexRiscv/cpu0.yaml' -f tcl/target/murax.cfg
It responds with:
Open On-Chip Debugger 0.10.0+dev-01202-gced8dcd (2019-02-22-18:40)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
adapter speed: 1000 kHz
../VexRiscv/cpu0.yaml
adapter speed: 800 kHz
adapter_nsrst_delay: 260
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
jtag_ntrst_delay: 250
Info : set servers polling period to 50ms
Info : clock speed 800 kHz
Info : JTAG tap: fpga_spinal.bridge tap/device found: 0xc8001fff (mfg: 0x7ff (<invalid>), part: 0x8001, ver: 0xc)
Warn : JTAG tap: fpga_spinal.bridge UNEXPECTED: 0xc8001fff (mfg: 0x7ff (<invalid>), part: 0x8001, ver: 0xc)
Error: JTAG tap: fpga_spinal.bridge expected 1 of 1: 0x10001fff (mfg: 0x7ff (<invalid>), part: 0x0001, ver: 0x1)
Error: Trying to use configured scan chain anyway...
Warn : Bypassing JTAG setup events due to errors
Info : Listening on port 3333 for gdb connections
requesting target halt and executing a soft reset
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Then to run gdb, do:
/opt/riscv/bin/riscv64-unknown-elf-gdb ~/VexRiscv/smallest.ino.elf
target remote localhost:3333
monitor reset halt
load
continue
That responds with:
GNU gdb (GDB) 8.0.50.20170724-git
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=riscv64-unknown-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /home/lawrie/VexRiscv/smallest.ino.elf...(no debugging symbols found)...done.
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
0x00000002 in ?? ()
(gdb) monitor reset halt
JTAG tap: fpga_spinal.bridge tap/device found: 0xc8001fff (mfg: 0x7ff (<invalid>), part: 0x8001, ver: 0xc)
JTAG tap: fpga_spinal.bridge UNEXPECTED: 0xc8001fff (mfg: 0x7ff (<invalid>), part: 0x8001, ver: 0xc)
JTAG tap: fpga_spinal.bridge expected 1 of 1: 0x10001fff (mfg: 0x7ff (<invalid>), part: 0x0001, ver: 0x1)
Trying to use configured scan chain anyway...
(gdb) load
Loading section .init, size 0x58 lma 0x90000000
Loading section .text, size 0x20 lma 0x90000058
Loading section .sdata, size 0x4 lma 0x90000078
Start address 0x90000000, load size 124
Transfer rate: 9 KB/sec, 41 bytes/write.
(gdb) continue
Continuing.
Here is a Anlogic Lychee Tang USB FTDI adapter being used to run a prtogram using ddb and openocd:
A typical entry in config.txt for the QSPIAnalog peripheral is:
qspiAnalog
address=0xF0000
input qss=QSS
input qck=QCK
inout qd=QD[3:0]
The 6 10-bit analog channels connected to the Blackice STM32 co-processor are supported via a QspiAnalog peripheral.
The analog pins are on the Blackice Arduino header.
An Arduino version of the Blackice iceboot firmware is required, which sends the analog values to the ice40, continuously.
The QspiAnalog peripheral is implemented by QspiAnalog.scala.
Here is a Grove analog potentiometer being read using the QSPI Analog sensor and displaying values on a 7-segment display:
The WS2811 peripheral supports WS2812B LED strips including Adafruit Neopixels.
A typical entry for the WS2811 peripheral is:
ws2811
address=0xD8000
maxLeds=16
mux=12
output dout=GPIOB[12]
Thw WS2811 peripheral is implemented by Ws2811.scala.
The above config.txt entry gives one instance of the peripheral on Pmod 12, pin 34.
Here it is driving a 16-LED Neopixel ring:
The Blackice SD card can be accessed using the SD library. It is currently set up to use software SPI, with pins SCK = 10, SS = 11, MOSI = 13 and MISO = 8. The first 3 correspond to the switches on the Blackice II board, and the switches must be in the OFF (inward) position for the SD card to work. Pin 8 is button 1, so this must not be used when the SD card is in use.
The Arduino Examples/SD/Cardinfo example can be used to test the SD card with a FAT32 card in the slot. The chip select pin should be changed to 11 and the button pin to 9.
Custom versions odf the Arduino SPI and Wire libraries are implemented, which use the spinal.lib SPI and I2C peripheral implementations.
The SD library is supported using the Blackice SD card, which shares pins with the switches and buttons. It currently uses software SPI.
There is a custom implementation of the Servo library, which uses the Servo peripheral.
There is a custom implementation of the MsTimer2 third-party timer library, which can be used for millisecond resolution timer interrupts.
There is a simple SevenSegment library to display a hex character on a Digilent Pmod SSD.
There is a simple Quadrature library.
There is a version of the Firmata library, but it is not yet tested.
There is a Mux library for setting and unsetting the mux pins.
There is a custom version of the EEPROM library, which needs an i2c EEPROM module attached to the hardware i2c pins on Blackice Pmod 2.
Here is an EEPROM module connected to the I2C master:
The Adafruit BME280 and SSD1306 libraries have been tested. The latter in i2c, SPI hardware and SPI software modes.
The LiquidCrystal library has been tested.
Here it is driving a text LCD:
The analogReadResolution, analogWriteResolution and analogReference methods are no ops.
The String toFloat method is not supported.
USB classes are not supported.
Lots of other features have not been tested.
The Arduino console is not synchronised correctly with rebooting the CPU, so if the console is open when an Arduino program is uploaaded, the program is started and you see some console output, which is then cut short, and the CPU is rebooted and you see the full output.