Showing posts with label FPGA. Show all posts
Showing posts with label FPGA. Show all posts

Sunday, December 20, 2020

iCE40 FPGA Programming with WSL and Open Source Tools

Here are some notes on programming the Lattice Semiconductor iCE40 FPGA chip.

This is supplementary information to the awesome learn-fpga walk-through by @BrunoLevy01 that I worked on a couple of weekends ago, specifically the IceStick Tutorial, but for Windows and WSL instead of Linux.


The default Windows drivers are FTDIBUS. Unfortunately this driver will typically NOT work to program the iCE40. If later changed with Zadig, they can be returned to the Windows default by right-clicking on the item in Device Manager and selecting "Update Driver". Choosing "Search Automatically for Driver" should assign the default FTDIBUS drivers. (either that, or just wait for Windows to arbitrarily change it back when you least expected it)

It may be best to start with plugging the device directly into the computer, rather than using a hub. Sometimes there are "issues". 

To program the iCE40 with iceprog assign the libusbK drivers with Zadig:  

Oddly, I found that I needed to assign the libusbK drivers to BOTH Interface 0 and Interface 1 in order to be able to successfully program the iCE40. Further, not only did the drivers need to be installed on both interfaces, but after doing so - I had to unplug (wait a few seconds) and plug in the device for successful programming when the drivers were first changed.

The drivers will almost certainly revert back to the FTDI default at Windows update time, and perhaps arbitrary other times as well.

It seems that the WinUSB drivers also work for successful programming.

Note that nether the libusbK nor the WinUSB drivers will show up as a COM port in Windows, so no TTY connection with something like putty.

Assigning the USB Serial drivers, and the Lattice shows up as a COM port. But here, iceprog will not see the iCE40 to program it.

Program only on the first interface:  iceprog.exe -I A femtosoc.bin

Attempting to program on the second interface gets stuck here at the erase step:

$ /mnt/c/Users/gojimmypi/.icestudio/apio/packages/toolchain-ice40/bin/iceprog.exe -I A femtosoc.bin
init..
cdone: high
reset..
cdone: low
init..
cdone: high
reset..
cdone: high
Extended Device String Length is 0xFF, this is likely a read error. Ignorig...
flash ID: 0xFF 0xFF 0xFF 0xFF
file size: 32220
erase 64kB sector at 0x000000..


This is what it looks like when iceprog cannot find the USB device:
0 $ /mnt/c/Users/gojimmypi/.icestudio/apio/packages/toolchain-ice40/bin/iceprog.exe -I A femtosoc.bin
init..
Can't find iCE FTDI USB device (vendor_id 0x0403, device_id 0x6010 or 0x6014).
ABORT.

If Zadig was used to assign the USBSER driver, the above step may not work. Assign it to something else (recommended libusbK) and then try again.

WinUSB devices show up here in Device Manager: 


Changing Interface 1 appears to also change Interface 0. However the reverse is not true. Thus it is best to assign drivers to Interface 1 first.


Reminder: When assigning new drivers, it is usually best to unplug and re-plug the iCE40 stick after drivers are changed with Zadig, otherwise iceprog may fail.

Note that for a "real" Ubuntu - in this case on a VM on a Windows host, the iCEStick shows up as 3 devices, no fussing with Zadig or anything else:


Here, the make terminal just works:


I was unable to persuade WSL, with any sort of Zadig drivers, to recognize the iCEStick in terminal mode. If it was a plain TTY terminal, it might have worked, but since I am using WSL1 and the target is /dev/ttyUSB1 - I don't this this would ever work on WSL1. Perhaps with WSL2 native USB support?


Sunday, August 9, 2020

Sigrok on WSL for iua: ice40 USB Analyzer

In my previous post on the iCEBreaker Bitsy FPGA - early adopter version, I mentioned the interesting iua: ice40 USB Analyzer project that I learned about on the iCEBreaker discord channel in the context of troubleshooting the odd problem I was seeing on my "Power Share" USB3.0 port.

There's minimal documentation on Getting Started, so here I am creating my notes.

The iua is meant for the iCEBreaker, not the Bitsy (although I wonder if it would still work on the Bisty? another rabbit hole for another day...)

As with all prior posts, I'm a Visual Studio developer at the day job, and continue that with my FPGA learning on the weekends... thus I use Windows, WSL, and Visual Studio here.


cd /mnt/c/workspace/
git clone https://github.com/smunaut/iua.git
cd iua/hdl
make

As reminder, if you have a VM running, make sure you are not having problems with a prompt awaiting device connection:

Also, even if you've used iceprog before and it decides to not find the icEBreaker today, check the drivers with Zadig. Windows seems to like to replace the drivers during updates. The iCEBreaker needs the libusbK drivers.

Unlike the Bitsy that needs to have bootloader boot set by holding the button at power-on time for dfu-util, the iCEBreaker does NOT need that when using iceprog

As there's no native USB support in my WSL, I use the DOS exe to send the binary to the device either from DOS:

C:\workspace\ulx3s-toolchain\bin\iceprog.exe 

or from WSL:

/mnt/c/workspace/ulx3s-toolchain/bin/iceprog.exe iua_icebreaker_top.bin
When successful, there should be output that looks something like this:

init..
cdone: high
reset..
cdone: low
flash ID: 0xEF 0x40 0x18 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
file size: 104090
erase 64kB sector at 0x000000..
erase 64kB sector at 0x010000..
programming..
reading..
VERIFY OK
cdone: high
Bye.


So that was the easy part. (The easy part is always the thing you already know). The next part - sigrok - is something that I've wanted to learn more about for quite some time. There's another smunaut/libsigrok project needed, which is a modified fork of sigrokproject/libsigrok and needs to be built like this:

git clone git://sigrok.org/libsigrok
cd libsigrok
./autogen.sh
./configure
make


People often ask why I deal with the annoyances of Windows. Well, Linux has plenty as well, imho. I had most of the dependencies already installed, but receieved this error diring make
configure: error: Package requirements (glib-2.0 >= 2.32.0 libftdi1 >= 1.0 libusb-1.0 >= 1.0.16 gio-2.0 >= 2.24.0 libzip >= 0.10) were not met:

No package 'libzip' found


And so what's the first thing to try? Well, sudo apt-get install libzip, right? Nope. :/

Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package libzip


grumble, grumble, google, grumble....

There's only one reference on the wiki, and it only notes I need libzip >= 0.10. But where to find it? Ah yes, it is just the development code library, not a tool, so we need the -dev suffix:

sudo apt-get install libzip-dev


Ok, now to run ./configure again...

Closer this time; I am still missing some things:

Enabled language bindings:
 - C++............................. no (missing: glibmm, Doxygen)
 - Python.......................... no (missing: C++ bindings, PyGObject, SWIG)
 - Ruby............................ no (missing: C++ bindings, SWIG)
 - Java............................ no (missing: C++ bindings, SWIG, JavaC, JNI headers)

===============================================================================
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
===============================================================================
===                                                                         ===
===         The libsigrok C++ bindings are not being built since you        ===
===             are missing one or more dependencies (see above)!           ===
===                                                                         ===
===      This means you won't be able to compile frontends that require     ===
===  the C++ bindings (such as PulseView)! You also won't be able to build  ===
===     other bindings and frontends using those (such as sigrok-meter)!    ===
===                                                                         ===
===============================================================================
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
===============================================================================


ugh.
gojimmypi:/mnt/c/workspace/libsigrok
$ sudo apt-get install glibmm
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package glibmm
gojimmypi:/mnt/c/workspace/libsigrok
$ sudo apt-get install glibmm-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package glibmm-dev
gojimmypi:/mnt/c/workspace/libsigrok


grumble, grumble, google, grumble....

This one requires a version number in the name. Of course it does. So onward to install: glibmm. I'll grab doxygen too, and then one more ./configure as well:

sudo apt-get install libglibmm-2.4-dev
sudo apt-get install doxygen
./configure


No warnings! Hurray!! So on to a (very long running) make, then:

  
$ make check
make
make  all-am
make  check-TESTS
============================================================================
Testsuite summary for libsigrok 0.6.0
============================================================================
# TOTAL: 0
# PASS:  0
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================


$ sudo make install
 /bin/mkdir -p '/usr/local/lib'
 /bin/bash ./libtool --silent  --mode=install /usr/bin/install -c   libsigrok.la bindings/cxx/libsigrokcxx.la '/usr/local/lib'
libtool: warning: relinking 'bindings/cxx/libsigrokcxx.la'
 /bin/mkdir -p '/usr/local/include/libsigrokcxx'
 /usr/bin/install -c -m 644 bindings/cxx/include/libsigrokcxx/libsigrokcxx.hpp '/usr/local/include/libsigrokcxx'
 /bin/mkdir -p '/usr/local/include/libsigrok'
 /usr/bin/install -c -m 644 include/libsigrok/libsigrok.h include/libsigrok/proto.h '/usr/local/include/libsigrok'
 /bin/mkdir -p '/usr/local/share/mime/packages'
 /usr/bin/install -c -m 644 contrib/vnd.sigrok.session.xml '/usr/local/share/mime/packages'
 /bin/mkdir -p '/usr/local/share/icons/hicolor/48x48/mimetypes'
 /usr/bin/install -c -m 644 contrib/libsigrok.png '/usr/local/share/icons/hicolor/48x48/mimetypes'
 /bin/mkdir -p '/usr/local/share/icons/hicolor/scalable/mimetypes'
 /usr/bin/install -c -m 644 contrib/libsigrok.svg '/usr/local/share/icons/hicolor/scalable/mimetypes'
 /bin/mkdir -p '/usr/local/include/libsigrokcxx'
 /usr/bin/install -c -m 644 bindings/cxx/include/libsigrokcxx/enums.hpp '/usr/local/include/libsigrokcxx'
 /bin/mkdir -p '/usr/local/include/libsigrok'
 /usr/bin/install -c -m 644 include/libsigrok/version.h '/usr/local/include/libsigrok'
 /bin/mkdir -p '/usr/local/lib/pkgconfig'
 /usr/bin/install -c -m 644 libsigrok.pc bindings/cxx/libsigrokcxx.pc '/usr/local/lib/pkgconfig'  


Next is to install decoder:
git clone https://github.com/sigrokproject/libsigrokdecode.git
cd libsigrokdecode
./autogen.sh
./configure
make
sudo make install


Next is to install Pulseview! (see also build info). Despite having the requirements installed, I also needed:

sudo apt-get install qttools5-dev


  
git clone https://github.com/sigrokproject/pulseview.git
cd pulseview
cmake .
make
sudo make install


Still following along? After all this, it is time to actually run pulseview! Alas on WSL we have an extra challenge: X-Windows. I'm using vcxsrv from the ad-spam-ridden sourceforge. (it is really a shame to see what that site has become).

C:\Program Files\VcXsrv\vcxsrv.exe


See building libsigrok, but use the smunaut/libsigrok

sudo apt-get install git-core gcc make autoconf automake libtool
git clone https://github.com/sigrokproject/libserialport.git  
cd libserialport
./autogen.sh
./configure
make
sudo make install 


git clone https://github.com/smunaut/libsigrok.git smunaut-libsigrok
cd smunaut-libsigrok
./autogen.sh  
./configure
make
sudo make install


See build failure for smunaut/libsigrok #2

If after doing a make clean for pulseview, and an error like this is enountered:
$ make
[  1%] Automatic MOC for target pulseview
[  1%] Built target pulseview_autogen
make[2]: *** No rule to make target 'l10n/de.qm', needed by 'qrc_translations.cpp'.  Stop.
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/pulseview.dir/all' failed
make[1]: *** [CMakeFiles/pulseview.dir/all] Error 2
Makefile:151: recipe for target 'all' failed
make: *** [all] Error 2


The problem seems to be that make clean improperly deletes a couple of files:

gojimmypi:/mnt/c/workspace/pulseview
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    l10n/de.ts
        deleted:    l10n/es_MX.ts

no changes added to commit (use "git add" and/or "git commit -a")
gojimmypi:/mnt/c/workspace/pulseview


So simply put them back:

git checkout master
git checkout l10n/de.ts
git checkout l10n/es_MX.ts  


Despite
gojimmypi:/mnt/c/workspace/smunaut-libsigrok
$ ./configure
...
Detected libraries (optional):
 - libserialport >= 0.1.1.......... 0.1.1


I still saw wthis error:
pulseview: error while loading shared libraries: libserialport.so.0: cannot open shared object file: No such file or directory  


sudo apt-get install libserialport-dev 


Saturday, August 8, 2020

iCEBreaker Bitsy FPGA - early adopter version

A couple of weeks ago, I ordered the early adopter iCEBreaker bitsy FPGA board. It arrived promptly just a few days later!


Key details:
  • Use dfu-util to upload binary files to the Bitsy (see Windows binaries)
  • The board needs to be in DFU boot mode to upload FPGA bin file
  • Hold button down when powering on device to enter DFU boot mode
  • Click button while in DFU boot mode to exit boot mode
  • FPGA Toolchain needs to be installed.

Note that I am using WSL on Windows. All the FPGA synthesis stuff happens in WSL. However, there is no native USB support in my WSL 1, so I use the DOS executables (either from WSL or Windows DOS prompt) for the actual device programming. Although I have several blog entries that mention this topic, I have a summary for Programming FPGA Devices from WSL.

For open source toolchain, you can install everything from GitHub, or use Multi-platform nightly builds of open source FPGA tools. I also have a Visual Studio FPGA Extension that will soon build and upload from the comfort of the VS IDE. (currently, the release only does syntax highlighting)

Unlike the similarly named iCEBreaker, this board does NOT use iceprog to upload binaries.
 
If when running dfu-util.exe -l, the device cannot be seen:

dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/


Make sure you are using a known good USB cable. Recall the button needs to be pressed before power is applied. There's apparently an issue with USB3.0 PowerShare ports. Try using a different port, or perhaps a USB hub.

 
If when running dfu-util.exe -l, the device cannot be opened:

Cannot open DFU device 1d50:6146

C:\Users\gojimmypi>C:\Download\dfu-util\dfu-util.exe -l
dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Cannot open DFU device 1d50:6146


This typically means the Windows drivers are not installed. Zadig to the rescue:



If when running dfu-util.exe -l, the device is working properly:

dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Found DFU: [1d50:6146] ver=0004, devnum=12, cfg=1, intf=0, path="1-1.3", alt=1, name="RISC-V firmware", serial="your serial"
Found DFU: [1d50:6146] ver=0004, devnum=12, cfg=1, intf=0, path="1-1.3", alt=0, name="iCE40 bitstream", serial="your serial"

We can proceed to actually programming the FPGA.

From what I can tell, it seems the Bitsy is iCEBreaker compatible:
cd /mnt/c/workspace
git clone https://github.com/icebreaker-fpga/icebreaker-examples.git
cd icebreaker-examples/blink_count_shift
make

To program the Bitsy, as there are multiple, identical DFU numbers, the "alt" needs to be specified:

dfu-util.exe -a 0 -D blink_count_shift.bin

Beyond blinky, a pinout is useful: 

iCEBreaker Bitsy Pinout by 0xdec

There's a main repo: https://github.com/icebreaker-fpga/icebreaker-examples but I learned on the discord channel that the code for the Bitsy has not yet been pushed there.

That repo will likely at some point have all the latest code. For now it is still in esden/icebreaker-examples
git clone https://github.com/esden/icebreaker-examples.git esden-icebreaker-examples  
cd esden-icebreaker-examples
git branch --all
git checkout bitsy
cd blink_count_shift  
make clean
make BOARD=bitsy1  
Note the `make` will be expecting parameters:

$ make
../board.mk:18: *** Please specify BOARD, options are "icebreaker", "bitsy0" or "bitsy1".  Stop.


then upload:
dfu-util.exe -a 0 -D C:\workspace\esden-icebreaker-examples\blink_count_shift\blink_count_shift.bin

Here we should be able to press the Bitsy button to get the green LED to illuminate.

Beyond blinky, I learned there's a DFU UART on FPGA pin 44 (aka IOB_4a, aka P2).

Hookup a USB Serial Port and at DFU boot time at 1000000 baud, a message is displayed:
  
Booting DFU image..
Flash Manufacturer : ef 40 18
Flash Unique ID    : e4 69 98 d2 43 59 15 2f
Command>  

I also learned on discord that USB signals-D/+D are actually 3.3V, even though the supply voltage is 5V

There's this interesting iua: ice40 USB Analyzer that is meant to run on an iCEBreaker FPGA that can apparently be used to "dump all the USB traffic". Note there's an minor problem building, but I have a PR to fix that. (thanks to @tnt on discord for the info). Basically just needed to add a magic parameter --placer sa to run nextpnr like this:

nextpnr-ice40 --placer sa  --up5k --package sg48 --json iua_icebreaker_top.json --pcf ../impl/iua_icebreaker_pins.pcf --asc iua_icebreaker_top.asc --freq 50  

This will happen during make.

Also from @tnt on discord:
There are debug pads on the bity bottom where you can easily solder a header.
set_io -nowarn usb_dp_pad 31
set_io -nowarn usb_dn_pad 34

 Those are the fpga pins where to connect on the icebreaker side.


To be continued in my next post... Sigrok on WSL for iua: ice40 USB Analyzer
 



Saturday, June 6, 2020

Programming FPGA Devices from WSL

Today I received a DM on Twitter asking for help to program an FPGA (specifically the 1bitsquared iCEBreaker) from WSL. I realized that most of the useful information is buried in my long (and admittedly sometimes rambling) prior blogs. So this is a summary for just that topic.

The first thing to do, is plug in your device. In the case of the iCEBreaker, make sure the drivers are set to libusbk using Zadig as noted here:


Note that native USB drivers are not supported in WSL (well, WSL1, anyhow; I've not been able to install WSL2 just yet)

If after changing the drivers the board is *still* not recognized by iceprog, try unplugging, waiting a few seconds and plugging the iCEBreaker back into the USB port. I've also seen mixed results with hubs, so perhaps try connecting directly if it still does not work.

Next, make sure you have the DOS version of the programmer on the Windows filesystem. I have mine in my C:\workspace\ulx3s-toolchain\bin\ directory:



The really crazy thing about WSL, is that not only is the Windows file system available in the /mnt/ directory such as /mnt/c for c:\ ... but Windows executables can be launched from the WSL prompt as well! And of course there's plenty of native USB driver support in Windows.

Here's an example in /mnt/c/users/gojimmypi/source/repos/Verilog12/Verilog12 - a WSL directory which is C:\users\gojimmypi\source\repos\Verilog12\Verilog12 (created by Visual Studio)

Calling the DOS executable to program the iCEBreaker:


That's it!

Links:



Saturday, April 11, 2020

Circuit Python on the FOMU in WSL (plus the pains of building your own RISC-V toolchain)


I started down the road of learning FPGA Circuit Python last month, with the (admittedly overly-ambitious) goal of getting Circuit Python working on the ULX3S. I was inspired to revisit this today when I saw Adafruit Blog - CircuitPython 5.2.0 Released - that specifically mentioned LiteX for Circuit Python!! In this case, for the FOMU.

TL;DR - WSL is by default NOT Case Sensitive. Use fsutil.exe in a DOS admin window to change this for each directory. To compile, riscv64-unknown-elf-gcc is needed, but the RISC-V GitHub GNU Repo does not work here to build Circuit Python. Use the FOMU toolchain. The make parameter should be lower case: make BOARD=fomu

If you are interested in Circuit Python, check out this CircuitBrains Deluxe on Crowd Supply.

See Adafruit instructions on building Circuit Python. All of the downloads for Circuit Python are available online. Well, all but of course the FOMU that I am most interested in. Hopefully it will be there soon, so for now we are doing this the hard way. (no fun if it is too easy, right?)

I could not find any documentation on the FOMU build, so I had to dig and search for keywords. The initial part of fetching from GitHub is pretty standard, so we'll start there:

git clone https://github.com/adafruit/circuitpython.git
cd circuitpython
git submodule sync
git submodule update --init
make -C mpy-cross
When doing the git submodule update --init in DOS (I sometimes do that, since my git clones are in C:\workspace - even though I compile in WSL from /mnt/c/workspace/)... I saw these errors:
git submodule update --init
Submodule 'extmod/ulab' (https://github.com/v923z/micropython-ulab/) registered for path 'extmod/ulab'
...etc
Submodule path 'lib/nrfutil': checked out '9e7dfb28a5c6f3d7a19340971b32e0c2b4128ecf'
Submodule path 'lib/tinyusb': checked out '1f95f439e11f519e69d75a4a8b7b9f28eaf5060e'
error: unable to create file tests/decomp-bad-inputs/00/id:000000,sig:11,src:000000,op:flip1,pos:10: Invalid argument
...etc
error: unable to create file tests/decomp-bad-inputs/00/id:000012,sig:11,src:000000,op:arith8,pos:11,val:+6: Invalid argument
error: unable to create file tests/decomp-bad-inputs/00/id:000013,sig:11,src:000000,op:arith8,pos:12,val:-9: Invalid argument
error: unable to create file tests/decomp-bad-inputs/00/id:000014,sig:11,src:000000,op:havoc,rep:16: Invalid argument
error: unable to create file tests/decomp-bad-inputs/00/id:000015,sig:11,src:000000,op:havoc,rep:2: Invalid argument
... many, many more similar errors
When doing the git submodule update --init from WSL just a few moments later, I did not see those errors. Perhaps there's some issue with Windows file names. (edit: I learned later this is likely a case sensitivity issue, see below).

For the FOMU, there's a Makefile in ./ports/litex
cd ./ports/litex
make BOARD=fomu
Edit: note that a case insensitive directory setting may allow BOARD=FOMU to work. ymmv. The safest one to use is a lower-case: BOARD=fomu.

If you see an error like this:
xargs: riscv64-unknown-elf-gcc: No such file or directory
../../py/mkrules.mk:81: recipe for target 'build-FOMU/genhdr/qstr.i.last' failed
make: *** [build-FOMU/genhdr/qstr.i.last] Error 127
make: *** Deleting file 'build-FOMU/genhdr/qstr.i.last'
Then try riscv64-unknown-elf-gcc --version to see if this RISC-V compiler is installed (perhaps you need to install the compiler). sadly, this is a different version of the compiler than is used in my ULX3S toolchain builder, that uses rv32i installed to /opt/riscv32i. Of course it does. Sometimes I wonder if this is all a hard drive manufacturer conspiracy to use as much disk space as possible. ;)

If like me you don't have the right compiler installed, make sure you have plenty of disk space:
This process will start by downloading about 200 MiB of upstream sources, then will patch, build, and install the toolchain. If a local cache of the upstream sources exists in $(DISTDIR), it will be used; the default location is /var/cache/distfiles. Your computer will need about 8 GiB of disk space to complete the process.

Here's what I did in WSL:
sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain  
cd riscv-gnu-toolchain  
./configure --prefix=/opt/riscv
sudo mkdir /opt/riscv
sudo chown $USER /opt/riscv
if [ "$(echo $PATH | grep  /opt/riscv/bin)" == "" ]; then export PATH=$PATH:/opt/riscv/bin; fi
make
You may wish to put the export PATH=$PATH:/opt/riscv/bin in your ./bash.rc file.

Do NOT run make linux if you want riscv64-unknown-elf-gcc files! The linux option creates riscv64-unknown-linux-gnu files, not elf!.

My first attempt at install failed. After hours of downloading and building, the errors started here (and yes, I also incorrectly specified linux):

mv -f /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/rtld-libc.aT /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/rtld-libc.a
make[4]: Leaving directory '/mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc/elf'
riscv64-unknown-linux-gnu-gcc    -nostdlib -nostartfiles -r -o /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/librtld.os '-Wl,-(' /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/dl-allobjs.os /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/rtld-libc.a -lgcc '-Wl,-)' \
          -Wl,-Map,/mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/librtld.os.map
riscv64-unknown-linux-gnu-gcc    -nostdlib -nostartfiles -shared -o /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/ld.so.new               \
          -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both -Wl,-z,defs       \
          /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/librtld.os -Wl,--version-script=/mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/ld.map            \
          -Wl,-soname=ld-linux-riscv64-lp64d.so.1                       \
          -Wl,-defsym=_begin=0
/opt/riscv/lib/gcc/riscv64-unknown-linux-gnu/9.2.0/../../../../riscv64-unknown-linux-gnu/bin/ld: /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/librtld.os: in function `process_envvars':
/mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc/elf/rtld.c:2484: undefined reference to `__GI___open64_nocancel'
/opt/riscv/lib/gcc/riscv64-unknown-linux-gnu/9.2.0/../../../../riscv64-unknown-linux-gnu/bin/ld: /mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/librtld.os: in function `dl_main':
/mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc/elf/rtld.c:1317: undefined reference to `__access'
/opt/riscv/lib/gcc/riscv64-unknown-linux-gnu/9.2.0/../../../../riscv64-unknown-linux-gnu/bin/ld: /mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc/elf/rtld.c:2103: undefined reference to `__access'
/opt/riscv/lib/gcc/riscv64-unknown-linux-gnu/9.2.0/../../../../riscv64-unknown-linux-gnu/bin/ld: /mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc/elf/rtld.c:1564: undefined reference to `__GI___close_nocancel'
and eventually culminated here:

/opt/riscv/lib/gcc/riscv64-unknown-linux-gnu/9.2.0/../../../../riscv64-unknown-linux-gnu/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status
Makefile:496: recipe for target '/mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/ld.so' failed
make[3]: *** [/mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d/elf/ld.so] Error 1
make[3]: Leaving directory '/mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc/elf'
Makefile:258: recipe for target 'elf/subdir_lib' failed
make[2]: *** [elf/subdir_lib] Error 2
make[2]: Leaving directory '/mnt/c/workspace/riscv-gnu-toolchain/riscv-glibc'
Makefile:9: recipe for target 'all' failed
make[1]: *** [all] Error 2
make[1]: Leaving directory '/mnt/c/workspace/riscv-gnu-toolchain/build-glibc-linux-rv64imafdc-lp64d'
Makefile:234: recipe for target 'stamps/build-glibc-linux-rv64imafdc-lp64d' failed
make: *** [stamps/build-glibc-linux-rv64imafdc-lp64d] Error 2
I was about to open a GitHub issue, and then thought - hm... perhaps I just read the rest of the README, eh? I know, I know, actually RTFM'ing takes away all the fun, but it is rather embarrassing when someone tells you to read the fine manual in response to asking for help. Sure enough, I found this:
If building a linux toolchain on a MacOS system, or on a Windows system using the Linux subsystem or cygwin, you must ensure that the filesystem is case-sensitive. A build on a case-insensitive filesystem will fail when building glibc because *.os and *.oS files will clobber each other during the build eventually resulting in confusing link errors. @tnt aka smunaut has a RISC-V USB project in the works.
In order to install RISC-V for WSL I found this article that noted after Windows 10 version 17093 there's a command fsutil.exe for setting case sensitivity:

fsutil.exe file setCaseSensitiveInfo riscv-gnu-toolchain enable

Note this needs to be run in a DOS Window, not WSL, and with Administrative permissions:


BUT! Note the command is only for the specified directory, and the attribute is NOT inherited  (and certainly does not get applied to each existing sub-directory). So we need to iterate through all sub-directories individually. Yes, I'm sure many readers are rolling their eyes at this point, asking why I am doing this in WSL and not a "real" Linux machine or even a VM; what am I doing in Windows? I ask myself that sometimes, too. Well, I just think WSL is just too cool! Plus, I suppose that I like the challenge. So anyway, here's what I did to set case sensitivity:

cd \workspace\riscv-gnu-toolchain\
for /f %f in ('dir /ad /s /b') do fsutil.exe file setCaseSensitiveInfo %f enable
There are many directories. Be patient...

As a reminder, that command is meant to be executed interactively. If placed into a batch file, the percent symbols need to be doubled up.

Now that everything in the riscv-gnu-toolchain directory is case sensitive, I ran make again:

cd /mnt/c/workspace/riscv-gnu-toolchain
make
This does a full rebuild, taking again a very long time. Did I mention being patient?

If you can run /opt/riscv/bin/riscv64-unknown-elf-gcc --version but see a lot of errors when doing a make BOARD=fomu like:
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/include/stdint.h:9:16: fatal error: stdint.h: No such file or directory
That means the RISC-V make either failed... or you didn't wait until it finished. (for me, it was the latter; did I mention being patient?) Once the RISC-V make is complete, the FOMU code can be built:
cd /mnt/c/workspace/circuitpython/ports/litex
make BOARD=FOMU

Which resulted in this output:

$ make BOARD=FOMU
Use make V=1, make V=2 or set BUILD_VERBOSE similarly in your environment to increase build verbosity.
QSTR updated
make: msgfmt: Command not found
../../py/py.mk:338: recipe for target 'build-FOMU/genhdr/en_US.mo' failed
make: *** [build-FOMU/genhdr/en_US.mo] Error 127

so ok, a quick google yields something useful on stackoverflow

apt-get install gettext

and so even after than, I saw even more errors like this:
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/../../../../riscv64-unknown-elf/bin/ld: build-FOMU/lib/libm/math.o: in function `floorf':
/mnt/c/workspace/circuitpython/ports/litex/../../lib/libm/math.c:790: undefined reference to `__addsf3'
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/../../../../riscv64-unknown-elf/bin/ld: /mnt/c/workspace/circuitpython/ports/litex/../../lib/libm/math.c:795: undefined reference to `__addsf3'
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/../../../../riscv64-unknown-elf/bin/ld: build-FOMU/lib/libm/math.o:/mnt/c/workspace/circuitpython/ports/litex/../../lib/libm/math.c:817: more undefined references to `__addsf3' follow
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/../../../../riscv64-unknown-elf/bin/ld: build-FOMU/firmware.elf(.text): relocation ".L15+0x0 (type R_RISCV_RVC_JUMP)" goes out of range
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/../../../../riscv64-unknown-elf/bin/ld: /opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/libgcc.a(divsf3.o): file class ELFCLASS64 incompatible with ELFCLASS32
/opt/riscv/lib/gcc/riscv64-unknown-elf/9.2.0/../../../../riscv64-unknown-elf/bin/ld: final link failed: file in wrong format
collect2: error: ld returned 1 exit status
Makefile:177: recipe for target 'build-FOMU/firmware.elf' failed
make: *** [build-FOMU/firmware.elf] Error 1
So I did another one of these, this time for the circuitpython directory:

for /f %f in ('dir /ad /s /b') do fsutil.exe file setCaseSensitiveInfo %f enable

and things got even worse:
$ make BOARD=FOMU
Makefile:30: *** Invalid BOARD specified.  Stop.
So I tried to clean it, same error:
$ make BOARD=FOMU clean
Makefile:30: *** Invalid BOARD specified.  Stop.
Turns out that was case sensitive, so then it is:
make BOARD=fomu clean
make BOARD=fomu
Ok, at the point, readers must really, really wonder why I use Windows. I know, I know. But I'm persistent.

It still failed. I gave up and asked for help.
I didn't get an answer to "should I use the FOMU toolchain instead of the RISC-V, so I thought I'd try it:

# fetch and extract
wget https://github.com/im-tomu/fomu-toolchain/releases/download/v1.5.6/fomu-toolchain-linux_x86_64-v1.5.6.tar.gz 
tar -xzf fomu-toolchain-linux_x86_64-v1.5.6.tar.gz
sudo mv fomu-toolchain-linux_x86_64-v1.5.6 /opt/fomu-toolchain-linux_x86_64-v1.5.6

# set the path
if [ "$(echo $PATH | grep fomu-toolchain-linux_x86_64-v1.5.6)" == "" ]; then export PATH=$PATH:/opt/fomu-toolchain-linux_x86_64-v1.5.6/bin; fi

# verify it is in the path:
which riscv64-unknown-elf-gcc
riscv64-unknown-elf-gcc --version

cd /mnt/c/workspace/circuitpython/ports/litex
make BOARD=fomu  clean

And... TADA! A successful Circuit Python build for the FPGA FOMU!! Whew.

gojimmypi:/mnt/c/workspace/circuitpython/ports/litex
$ make BOARD=fomu
Use make V=1, make V=2 or set BUILD_VERBOSE similarly in your environment to increase build verbosity.
/opt/fomu-toolchain-linux_x86_64-v1.5.6/bin/../lib/gcc/riscv64-unknown-elf/8.3.0/../../../../riscv64-unknown-elf/bin/ld: warning: section `.bss' type changed to PROGBITS

739752 bytes free in flash firmware space out of 1048576 bytes (1024.0kB).
116920 bytes free in ram for stack and heap out of 131072 bytes (128.0kB).

Create build-fomu/firmware.dfu
python3 ../../tools/dfu.py -b build-fomu/firmware.bin -D 0x1209:0x5bf0 "build-fomu/firmware.dfu"

Other notes (thanks @pdp7): Using Python for creating hardware

Migen Step by Step Tutorial

LiteX is a fork of MiSoC

MiSoc and LiteX are both based on Migen


Sunday, March 29, 2020

LiteX Soft CPU on the ULX3S - reloading firmware without reflashing the soft CPU

My next post (aka notes to self) on getting a RISC-V compiled app onto the ULX3S using LiteX.

TL;DR; press the reset button!

Quite a diversion from my day job with SQL and C# and web apps is this fascinating world of soft CPU's and embedded devices. Here's what I learned this weekend:

In my prior blogs, I considered getting Circuit Python running on the ULX3S. There are a lot of moving parts, so I got started with implementing the RISC-V soft CPU on the FPGA. Recall we cannot run Circuit Python on the ESP32 because of hardware limitations (missing USB OTG), however the ESP32-S2 will (in theory) work with Circuit Python. Next, I rambled on a bit regarding various features of LiteX that I was exploring in my prior blog, in the end creating a soft_cpu.sh script with everything I learned.

Refresher:  Onboard the ULX3S is an ECP5 FPGA. We first need to create a soft CPU bitstream that is loaded onto the ECP5. LiteX also creates a BIOS that gets loaded over the serial port. Ok, all fine and dandy, but I seem to only be able to load that BIOS once and then the terminal becomes unresponsive. This makes sense, as the processor stays in the while loop after liftoff. The bios does not appears to be an RTOS.

ULX3S shown with FPGA/BIOS/firmware programming USB cable

Supposedly that BIOS should be able to reload firmware apps. Well, there's this mystery boot_helper. What does this extern do? I suspect that's what loads the BIOS. Then what? There's this 4 line boot-helper-picorv32.S file. Not much help there. The "What is LiteX" introduction does seem to indicate that the development cycle includes reloading the soft CPU, as the "design flow in a nutshell" indicates going back to step 1. I'd like to go back to step 6, skipping the re-synthesis of the soft CPU every time I change the firmware.

The LiteX for the ULX3S has a serial port configured as Tx on Pin L4 (FTDI RxD) and Rx on Pin M1 (FTDI TxD). See the schematic. These pins are not available on either of the external ULX3S headers.

FTDI RxD/TxD USB Serial on ULX3S FPGA pins L4 and M1
I had thought perhaps the BIOS would be looking on a pair of pins implemented in the FPGA as a serial port. Nope. Those pins are definitely hard wired the the FTDI USB serial port.

So what else is there? Well, the BIOS is clearly not an RTOS. So we're definitely waiting forever and doing nothing in that while (1) statement. How else does a CPU begin again? Reset of course! Check out the rst implementation in LiteX. It is hooked to R1 of the FPGA. What's that?

R1 (aka BTN_F1) on the ULX3S
That's the F1 button!! (The "Fire 1" position when the ULX3S is in gamer mode! lol). This is also labeled as "B1" on the PCB, located near the SD card, right under the text "2.5V/3.3V"

It turns out the architecture is not [CPU] - [BIOS] - [firmware]. Instead, the simpler: [CPU (with tiny, baked in BIOS] - [BIOS (with external firmware)].  Now, one could certainly write something in the BIOS to, say load an OS. But that's not what LiteX is doing out of the box in this example.

The architecture naming is a bit misleading, as there is part of the CPU implemented as a BIOS as seen at processor boot time on the LiteX. Unfortunately the external "C" program is also called BIOS (firmware).



So just leave litex_term running and press the F1 (CPU reset) button to reload the BIOS firmware. See my soft_cpu.sh bash script. Each time you update and compile the BIOS code, press the F1 button to reload it onto the soft CPU.

I've created LiteX Pull Request #445 that adds some comments to hopefully help others that may encounter these issues.

How many times can an FPGA be reprogrammed? Well, the folks at Numato claim "SRAM based FPGAs can be programmed as many times as necessary. There is no limit..."

There's also the possibility of using a logic analyzer internally to the FPGA. Also mentioned in the What is LiteX is something called litescope. See the Debugging with ChipScope for more information, as there's not much in the Wiki as of the data of this blog. The architecture image provides some insight. See also the test_analyzer.py.

So now.. I wonder if I can single-step code soft CPU code with OpenOCD. And that litescope! That sounds super interesting...

Saturday, March 28, 2020

RISC-V on the ULX3S with LiteX - Part 2

First .. the exciting news: With 24 days still left in the Crowd Funding Campaign, the ULX3S is within a few dollars of reaching their $40K stretch goal!! I'm so happy to see their success. I've been super happy with mine. (Edit: now nearly 300% funded by the time I publish this!)

TL;DR: Check out this soft_cpu.sh script used to create a soft RISC-V CPU on the ULX3S.

Back to my fomu! I revisited the Adafruit Feather M0 Express - A request for the USB device descriptor failed: Issue #293 on GitHub in the hathach/tinyusb repo. The suggestion was to simply update my fomu to see if the Feather M0 problems would go away:

The last few lines of the release.sh
dfu-suffix -v 1209 -p 70b1 -a $output/${platform}-updater-${release}.dfu
dfu-suffix -v 1209 -p 70b1 -a $output/${platform}-foboot-${release}.dfu
but I saw an error when trying to do that:
C:\Download\dfu-util>dfu-suffix -v 1209 -p 70b1 -a fomu\hacker-updater-v2.0.3.dfu
dfu-suffix (dfu-util) 0.9

Copyright 2011-2012 Stefan Schmidt, 2013-2014 Tormod Volden
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Please remove existing DFU suffix before adding a new one.
so I next tried using dfu-util, as noted here:
C:\Download\dfu-util>dfu-util -D fomu\hacker-updater-v2.0.3.dfu
dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Match vendor ID from file: 1209
Match product ID from file: 70b1
Opening DFU capable USB device...
ID 1209:5bf0
Run-time device DFU version 0101
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 0101
Device returned transfer size 1024
Copying data from PC to DFU device
Download        [=========================] 100%       112828 bytes
Download done.
state(7) = dfuMANIFEST, status(0) = No error condition is present
state(8) = dfuMANIFEST-WAIT-RESET, status(0) = No error condition is present
Done!

Since that seems to have been successful, I will return to the ULX3S for now.

A quick check of the rxrbln/picorv32 ULX3S fork that is is included in my full toolchain install and I'm still not yet able to build:

gojimmypi@ubuntu:~/workspace/rxrbln-picorv32/picosoc$ git fetch
gojimmypi@ubuntu:~/workspace/rxrbln-picorv32/picosoc$ git pull
Already up to date.
gojimmypi@ubuntu:~/workspace/rxrbln-picorv32/picosoc$ make
iverilog -s testbench -o hx8kdemo_tb.vvp hx8kdemo_tb.v hx8kdemo.v spimemio.v simpleuart.v picosoc.v ../picorv32.v spiflash.v `yosys-config --datdir/ice40/cells_sim.v`
picosoc.v:70: error: NULL port declarations are not allowed.
Makefile:20: recipe for target 'hx8kdemo_tb.vvp' failed
make: *** [hx8kdemo_tb.vvp] Error 1
gojimmypi@ubuntu:~/workspace/rxrbln-picorv32/picosoc$ 

So ok, I'll give that one some more time.

Refresher on getting Micropython onto the fomu:
cd /mnt/c/workspace
mkdir -p fomu
cd fomu
wget https://github.com/im-tomu/fomu-workshop/raw/master/micropython-fomu.dfu
dfu-util -D micropython-fomu.dfu
Should give an output like this:
dfu-util 0.9

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2016 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Match vendor ID from file: 1209
Match product ID from file: 5bf0
Opening DFU capable USB device...
ID 1209:5bf0
Run-time device DFU version 0101
Claiming USB DFU Interface...
Setting Alternate Setting #0 ...
Determining device status: state = dfuIDLE, status = 0
dfuIDLE, continuing
DFU mode device DFU version 0101
Device returned transfer size 4096
Copying data from PC to DFU device
Download        [=========================] 100%       136164 bytes
Download done.
state(7) = dfuMANIFEST, status(0) = No error condition is present
state(8) = dfuMANIFEST-WAIT-RESET, status(0) = No error condition is present
Done!
Mine showed up as COM8 and For more details on how a RISC-V soft CPU runs MicroPython, see the Fomu as a CPU section of the fomu workshop.

I've been following along with the development of picosoc for the ULX3S. This is included in the full toolchain build, or can be fetched from here:

cd $WORKSPACE
git clone https://github.com/rxrbln/picorv32.git
To build:

# use the rxrbln firmwware
cd $WORKSPACE/rxrbln-picorv32/picosoc

# there have been some recent additions to the source code; some files not included in the repo
# so get an older, know-to-compile version:
git checkout b1cd395b

make ulx3s_fw.img
$WORKSPACE/ulx3s-examples/bin/ujprog.exe -j FLASH -f 0x200000 ulx3s_fw.img

Use the LiteX CPU
# make the soft CPU
cd $WORKSPACE/litex-boards/litex_boards/targets
./ulx3s.py --device LFE5U-85F --cpu-type picorv32

# show the files built
echo "ULX3S Gateware:"
ls $WORKSPACE/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/gateware -al

echo "ULX3S BIOSL"
ls $WORKSPACE/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/software/bios -al

# put the soft CPU on the ULX3S
cd $WORKSPACE/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/gateware
$WORKSPACE/ulx3s-examples/bin/ujprog.exe top.bit

cd $WORKSPACE/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/software/bios
litex_term --serial-boot --kernel bios.bin /dev/ttyS15

Press enter, and you should see a litex> prompt. Type reboot As a reminder, if you keep getting an error like this:

ULX2S / ULX3S JTAG programmer v 3.0.92 (built Feb 18 2019 10:55:47)
FT_Open() failed
Cannot find JTAG cable.
Check to make sure NOTHING is using the ULX3S, including perhaps a litex_term or putty session. (yes, I've bumped into that more than once).

A big Thank You to @GregDavill  for these tips on the twitter thread:

The LiteX bios is what you're seeing running.  
By default, LiteX builds this to execute from address 0x00000000. This is an address space inside the FPGA, using blockram. Litex embeds the code inside so it's baked into the bit-stream. 
uart_sync() just waits until internal uart FIFOs are cleared. (Which ensures that printf data  has been sent to the PC...) printf also relies on interrupts, so once you've disable interrupts printf no longer works.

The bios exists to initialise things like SDRAM, which you can see it doing here. It then tries to load a USER program from SD/FLASH/Serial/Ethernet.

According to the timvideos "what is litex" the flterm program is what is needed to interact with the bios. As of the date of this blog, I've included that in the full ULX3S toolchain install.

Beware there's an old implementation of traps on riscv32 that may cause code crashes. (e.g. soft debugging)

I found an example using flterm: This next section is an unsuccessful attempt to upload firmware with flterm. (it just sits there waiting for something)

cd $WORKSPACE/flterm
./flterm --port /dev/ttyS15 --kernel $WORKSPACE/rxrbln-picorv32/firmware/firmware.bin --kernel-adr 0x40000000

Searching for the term "liftoff" and I found two occurrences here and here:
# remote.origin.url=https://github.com/enjoy-digital/litex

# this is the one that gets compiled:
C:\workspace\litex\litex\soc\software\bios\boot.c

# plus this older file:
C:\workspace\litex-buildenv\third_party\litex\litex\soc\software\bios\boot.c


I created a soft_cpu.sh script soon to be pushed to the ulx3s-toolchain setup to illustrate how I created a picorv32 soft CPU on the ULX3S using LiteX. See my next blog on updating the firmware.

Saturday, March 14, 2020

RISC-V on the ULX3S with LiteX

Today is ULX3S Campaign Launch Day on Crowd Supply!!

As I write this, funding is at 40% in just the first hour!

In pursuit of my ongoing quest to get Circuit Python working on my ULX3S.... I decided to try out LiteX. I wasn't able to get the rxrbln picorv32 picosoc for ULX3S working (yet) due to some missing files that have not yet been checked in. Part of the journey is simply figuring out which working components to use.

LiteX is included in my full toolchain build for the ULX3S (see my prior blog).

Full ULX3S toolchain directory list
Once everything is installed (OMG, it takes the better part of a day)... we can get started.

First: if you are considering getting your own ULX3S from the upcoming Crowd Supply campaign, be aware that only the larger 45F and 85F versions are available options (and capable?) of running the LiteX VexRiscv.

Some key terminology from timvideos to get started: Gateware (as in Field Programmable Gate Array) is the stuff that gets loaded onto the FPGA. Firmware is the application code that the soft CPU will execute. The BIOS is bootstrapping code baked into FPGA.

Note that I installed my development toolchain on WSL Ubuntu. I've also tested the install multiple times on VM's with real Ubuntu. But here, my blog is about using WSL, so all of my stuff is installed on my local C:\workspace directory, or from WSL's perspective: /mnt/c/workpace. On a real Ubuntu machine, simply replace that with your own ~/workspace/ directory.

Why didn't I just put my workspace in the WSL ~/workspace directory instead? Well for one, I can completely wipe out WSL and reinstall, and all my Windows workspace files are still there. I can also access those files easily from Windows and Visual Studio in particular. I'm still waiting on WSL2 (only available to Windows insider / preview at this time, which I chose not to sign up for) - and I believe I need to rip & replace for that version.

LiteX is a code generator. Not only does it create Verilog, but also a bash script to run yosys / nextpnr / ecppack to actually generate an ECP5 FPGA bit file. The fact that it can generate code to build a complete soft CPU is frankly astonishing.

Run the ulx3s.py for the respective device:
cd /mnt/c/workspace/litex-boards/litex_boards/targets
# ./ulx3s.py --device LFE5U-45F
./ulx3s.py --device LFE5U-85F
It is not super intuitive where files end up. There's certainly not a lot of documentation for the litex-boards repo. I searched in powershell:
Get-ChildItem -Recurse | Where-Object { $_.LastWriteTime -ge "03/08/2020 4:00pm" }
the top.bit file ended up in:
C:\workspace\litex-boards\litex_boards\targets\soc_basesoc_ulx3s\gateware
So from WSL it can be loaded onto the ULX3S like this:
# make sure nothing is using the ULX3S, including VM's, terminal sessions in putty, etc!
cd /mnt/c/workspace/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/gateware

/mnt/c/workspace/ulx3s-examples/bin/ujprog.exe top.bit
Note how Windows executables can be called from the WSL prompt. The same command can be used in a DOS window:
# make sure nothing is using the ULX3S, including VM's, terminal sessions in putty, etc!
c:
cd \workspace\litex-boards\litex_boards\targets\soc_basesoc_ulx3s\gateware

c:\workspace\ulx3s-examples\bin\ujprog.exe top.bit
Stupid things to be aware of: the serial port is running at 11500 8N1. Don't ask me how much time I wasted when using a putty saved connection for the COM port preset at 19200 and kept wondering why there was no response when trying to connect. I don't think I'll ever make that mistake again. lol

There's also an interesting bios.bin file in
C:\workspace\litex-boards\litex_boards\targets\soc_basesoc_ulx3s\software\bios
My script for synthesizing the CPU ended up in:
/mnt/c/workspace/linux-on-litex-vexriscv/build/ulx3s/gateware/build_top.sh
I had found that build_top.sh file prior to realizing ulx3s.py accepted a device parameter. I  have two ULX3S devices, the 12F and the 85F. Guess which one is the defined default for the LiteX build? Yup, the 45F. Did I mention if things are too easy it's no fun? At first I manually edited the build file (note the above call to ulx3s.py does everything, including synthesizing the FPGA VexRiscv core). The default looks like this:
# Autogenerated by LiteX / git: e801dc02
set -e
yosys -l top.rpt top.ys
nextpnr-ecp5 --json top.json --lpf top.lpf --textcfg top.config --45k --package CABGA381 --speed 6 --timing-allow-fail
ecppack top.config --svf top.svf --bit top.bit
Ok, so one of the parameters looks easy enough. Ooph, but what about the "CABGA381"? Well, a quick google search and it turns out that's just a package specification. See the prjtrellis devices.json for a list of all of them. I'll assume that I each of the ECP5 options on the ULX3S will use the same package, and simply change the "45k" to "85k".

There are some bios docs.

Constraint (pin-level) implementation details can be found in platforms/ulx3s.py.

As shown in the ulx3s.py platform file, there's no Ethernet port configured and the serial port is configured for ECP5 pins L4 and M1:

    ("serial", 0,
        Subsignal("tx", Pins("L4"), IOStandard("LVCMOS33")),
        Subsignal("rx", Pins("M1"), IOStandard("LVCMOS33"))
    ),
To build the binaries:

cd $WORKSPACE/litex-boards/litex_boards/targets

./ulx3s.py --device LFE5U-85F --cpu-type picorv32

cd $WORKSPACE/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/gateware

# to program the FPGA as a CPU:
$WORKSPACE/ulx3s-examples/bin/ujprog.exe top.bit

cd /mnt/c/workspace/litex-boards/litex_boards/targets/soc_basesoc_ulx3s/software/bios

litex_term --serial-boot --kernel bios.bin /dev/ttyS15

More to come... see my next blog: ULX3S RISC-V with LiteX - Part 2.


Sunday, February 16, 2020

C# on the Radiona ULX3S using the nanoFramework dotnet CLR

It has been over a year since I first learned about the Radiona ULX3S development board. This is the perfect "crossover" board that has appeal for developers interested in electronics, FPGA development, and even just regular software development. With both an Espressif ESP32 and the Lattice ECP5, along with a ton of peripherals and GPIO pins - there's something for everyone here.

Radiona ULX3S from https://radiona.org/ulx3s/
The ULX3S is coming soon to CrowdSupply!

If you just want to get started with C#, see my notes here or visit docs.nanoframework.net.

A lot has changed since I first heard the name Espressif way back in 2015 and ordered this thing called a "Diymall® Esp8266 Esp-12e Serial Wifi Wireless Transceiver Module for Arduino UNO 2560 R3 Nano" from Amazon. Ooph. What a name.






And what an ordeal it was to get that device going... I remember the convoluted process of manually holding some GPIO pin low while it powered up, and then releasing another, all with level-shifting the 5V USB-TTL on a rats-nest breadboard of jumper wires.  The "excitement' was seeing the old modem-style "AT" command prompt. Admittedly, it was pretty cool. How really surreal that was - to see the Hayes-like AT prompt for a WiFi device.

ESP Boot Modes from the ESP8266 WiKi

Indeed it seemed interesting to have modem-like commands available in such a small and inexpensive package (who knows what it would take to actually send and receive WiFi packets with AT commands!). The documentation was pretty bleak and ridiculously incomplete at the time.

Fast forward just 5 years (that's 103 in technology years) - and Espressif continues to amaze with awesome documentation and ridiculously inexpensive hardware. And the languages! I've not seen an AT prompt on any of my devices for a long time. Starting with Lua on the ESP8266, the tech community soon realized that other languages could be implemented on the Espressif hardware. Of  course, C/C++ was the first and most obvious choice for embedded devices. But then something really crazy came along: MicroPython on the inexpensive ESP8266 WiFi device. Just a few months after seeing that first AT prompt, I backed the MicroPython on the ESP8266 crowd funding project. One thing led to another and now there are many languages available, and of course the next gen ESP32 hardware platform. It was when looking at the languages on the ESP32 Wikipedia page, of all places - that I learned about a truly amazing language capability: C# on an embedded device!

I've been programming C/C++ on and off over the course of many years. At the Day Job however, my focus these days is on C# in the Visual Studio environment. That's where I am probably most comfortable and the most productive. For programming "Arduino Style" C/C++ on embedded devices, my go-to tool has been the Visual Micro add-in extension for Visual Studio. With hundreds of thousands of downloads and a solid 5 star rating, I'm clearly not alone in liking Visual Micro. I've mentioned them numerous times in prior blogs, such as this one for the ULX3S.



If you've never used the Visual Micro extension, I highly recommend giving it a try.

Back to the topic of this blog. C#. On. an. embedded. device. WOW! As mentioned above, I originally learned of this capability on the Wikipedia page. There's also a reference on the Espressif web site with a relatively simple name of "nanoFramework":

"Developers can harness the powerful and familiar Microsoft Visual Studio IDE and their .NETC# knowledge to quickly write code without having to worry about the low-level hardware intricacies of a microcontroller. Desktop .NET developers will feel “at home” and are able to use their skills in embedded systems development, enlarging the pool of qualified embedded developers. It includes a reduced version of the .NET Common Language Runtime (CLR) and features a subset of the .NET base class libraries along with the most common APIs included in the Universal Windows Platform (UWP) allowing code reuse from desktop applications, IoT Core applications, thousands of code examples and open source projects. Using Microsoft Visual Studio, a developer can deploy and debug the code directly on real hardware." --Espressif Systems
Ok, so C# on an embedded device is amazing in itself. If you've read some of my prior blogs, you've seen my interest in single-step JTAG debugging the Espressif chips. The ESP8266 in particular can be quite difficult, depending on the vendor. Still, even with JTAG working, I've never been able to get the real experience of full debugging as seen in Visual Studio with high-level language debugging, or even the Atmel Studio with the Atmel ICE for their chips.

Enter nanoFramework. This is one of the most amazing implementations I've seen in quite some time. They have the .Net CLR on an embedded device! Not just the CLR, but the fully integrated Visual Studio debugging, single-step, hover-text values and more. Getting started is pretty straightforward: first step of course is to install the nanoFramework extension from the Visual Studio Marketplace. Note there are two flavors: the nanoFramework for VS2017 and a preview nanoFramework for VS2019.

I do think their instructions are a bit overly complicated for the first time user. For one - compiling the entire framework is certainly not required to program C# on your device, yet it is featured front and center on the getting started page. I have some notes of my own on Programming the ULX3S ESP32 using C# in Visual Studio. Basically the firmware needs to be uploaded to the device, then Visual Studio needs to connect with Device Explorer.

I tweeted this video of single-step blinky in C# to show single step in action.

Beyond blinky, I was curious as to what else could be done with this framework. So how about a simple "Hello World" printed on the screen? Ha! What a twisty little passage all alike rabbit-hole that turned out to be.

First, I discovered there's currently no direct support of the SSD1331 SPI display that I am using with the ULX3S. However, there are plenty of Arduino-style libraries out there. Hm. Arduino libraries... ah yes, it is one thing to be able to write code, it is another issue altogether to actually be able to leverage the existing libraries for peripherals. All the open source code out there that allows the ESP32 to use Ardunio libraries has certainly helped the self-sustaining fire of maker interest and development. But what about C#?

I started out in the #Targets-ESP32 thread on the nanoFramework Discord. Yes, they need multiple threads for different platforms, as they also have C# working on STM32,  TI CC13x2, CC26x2, CC32xx, and NXP MIMXRT1060_EVAL boards! As my issue was not ESP32 specific, they kindly referred me to the #UI thread (UI meaning "display hardware", not the UI of the nanoFramework extension in Visual Studio). It is there that I learned a lot of great information from some clearly talented developers that very kindly and patiently answered my newbie questions.

I only recently started using Discord. I think I like gitter channels more, but both seem to be weak for linking to specific comments of interest. Part of this blog is for me to gather up the key tidbits all in one place. Be sure to check out the ULX3S gitter channel, too.

I learned quite a bit about the SSD1331 display during a exchange with emard in ULX3S issue #8. For the nanoFramework here I started with a known-working project, my ULX3S Visual Micro SSD1331 Display Example. In particular, the key pin numbers:

// working ULX3S  
#define oled_csn  17 // aka cs - chip select
#define oled_dc   16 // aka ds aka a0 -  SPI data or command selector pin
#define oled_resn 25 // aka rst - reset
#define oled_mosi 15 // aka mosi - data
#define oled_clk  14 // aka sclk - clock
#define oled_miso -1 // 12 not used

from the FPGA perspective:

N2 to N3 for oled_csn /CS  to wifi_gpio17 (GPIO17)
P1 to L1 for oled_dc /DC   to wifi_gpio16 (GPIO16)
P2 to E3 for oled_resn/RES to wifi_gpio25 (GPIO25)
P3 to J1 for oled_mosi/SDA to sd_cmd      (GPIO15)
P4 to H2 for oled_clk /SCL to sd_clk      (GPIO14)

See also this code on the espressif/arduino-esp32 repo for VSPI and HSPI initialization and pins:


void setup() {
  //initialise two instances of the SPIClass attached to VSPI and HSPI respectively
  vspi = new SPIClass(VSPI);
  hspi = new SPIClass(HSPI);
  
  //clock miso mosi ss

  //initialise vspi with default pins
  //SCLK = 18, MISO = 19, MOSI = 23, SS = 5
  vspi->begin();
  //alternatively route through GPIO pins of your choice
  //hspi->begin(0, 2, 4, 33); //SCLK, MISO, MOSI, SS
  
  //initialise hspi with default pins
  //SCLK = 14, MISO = 12, MOSI = 13, SS = 15
  hspi->begin(); 
  //alternatively route through GPIO pins
  //hspi->begin(25, 26, 27, 32); //SCLK, MISO, MOSI, SS

  //set up slave select pins as outputs as the Arduino API
  //doesn't handle automatically pulling SS low
  pinMode(5, OUTPUT); //VSPI SS
  pinMode(15, OUTPUT); //HSPI SS

}

I tried to use the existing SPI capabilities of the nanoFramework, but I was unable to get the display to cooperate. Nothing happened. But why?

This is where more serious hardware debugging tools are needed. One method I used in the past to debug some serial port communication problems - involved a Digital Storage Oscilloscope (in my case, a Rigol DS1054z). Even though that one is relatively small as oscilloscopes go, it was at my workbench and I was working at the kitchen table (and ok, the workbench needs to be cleaned up an organized). Not only inconvenient, but also perhaps not the most practical (power cord, size, etc). Although my serial decoding was an interesting exercise on the oscilloscope - what I really needed is a logic analyzer.

It was well over 5 years ago that I first started using the Saleae logic analyzer software. The cool thing here is that it is a computer-based debugging tool. Whereas the oscilloscope is an oscilloscope all on its own, complete with its own power cord and display - the Saleae is just a USB peripheral. It doesn't even have a wall-wart power supply: just a USB connection and logic probes.

Sure enough - after a ridiculously long time trying to figure out the display problem with software, the logic analyzer instead made the problem abundantly obvious: nothing was happening as the D/C (data command) pin. I was hoping that somehow the SPI driver would have just "known" how to control the bus for the display during NativeInit. Silly me. Of course not. Nope.



I opened this issue to implement more flexible SPI pin definitions, in particular something to manage that D/C pin. @AdrianSoundy responded with:
"The DC and Reset pins are not part of the SPI bus. They are specific to the displays. You have to create separate gpio pins for those functions."
Hm. Well, yes, I guess that is technically true. Still, I wanted to see if I could get my SPI  display to work in C#, so I took the advice and wrote my own DC-pin-controlling code to manage the data/control line, and let the native drivers handle the actual data transfer.

I created this nanoFramework SSD1331 example to manually bit-bang the SPI bus. As of the date of this blog, there's not a lot there. I leveraged the Adafruit SSD1331 C++ library to create my own limited C# OLED SSD1331 class library. I also created another class library project, this one an Arduino-style nanoFramework "pins" helper to allow the use of the familiar pinMode and digitalWrite functions when converting the Adafruit library to C#.

It is a bit of a mess at the moment, but it does have the capability of initializing the display and poking some pixels into place. It doesn't sound like much - but again, doing this from C# in Visual Studio is what made it really quite cool. I had this little victory tweet at the end of that weekend:


Drawing a line is one thing... drawing it quickly, well that's another. After the excitement had warn off (ok, it was just a blue line)... I realized just how slowly the pixels were being drawn. Why? Did I mention the little twisty passages? Yes, this rabbit hole goes much deeper.

To start, let's go back to the known-working SPI display example. See also this blog where I go into more detail with that example.

Again, there's nothing like a logic analyzer to see what's going on with the voltage levels of ones and zeros on the wires. Looking at the source code, the SPI bus for the display gets interesting at startup time, or specifically during Adafruit_SSD1331::begin - where initialization bytes are sent to the display:


void Adafruit_SSD1331::begin(uint32_t freq) {
    initSPI(freq, SPI_MODE0);

    // Initialization Sequence
    sendCommand(SSD1331_CMD_DISPLAYOFF);   // 0xAE
    sendCommand(SSD1331_CMD_SETREMAP);          // 0xA0
#if defined SSD1331_COLORORDER_RGB
    sendCommand(0x72);    // RGB Color
#else
    sendCommand(0x76);    // BGR Color
#endif
    sendCommand(SSD1331_CMD_STARTLINE);  // 0xA1
    sendCommand(0x0);
    sendCommand(SSD1331_CMD_DISPLAYOFFSET);  // 0xA2
    sendCommand(0x0);
    sendCommand(SSD1331_CMD_NORMALDISPLAY);   // 0xA4
    sendCommand(SSD1331_CMD_SETMULTIPLEX);  // 0xA8
    sendCommand(0x3F);     // 0x3F 1/64 duty
    sendCommand(SSD1331_CMD_SETMASTER);   // 0xAD
    sendCommand(0x8E);
    sendCommand(SSD1331_CMD_POWERMODE);   // 0xB0
    sendCommand(0x0B);
    sendCommand(SSD1331_CMD_PRECHARGE);   // 0xB1

...etc


This is where I had a chance to try out my new Saleae logic analyzer. It's funny how once you buy something, you start noticing other people that have that same "thing". I noticed that @GregDavill had posted something on Twitter with his Saleae - and so I commented on it. His reply was crazy! I don't think I'll be using mine as a coaster anytime soon LOL. So here's my new Saleae, sans mug, hooked up to my Radiona ULX3S:

ULX3S with SSD1331 display on a breadboard with Saleae Logic Analyzer
In order to most easily connect the logic analyzer, I disconnected the display from the main ULX3S board and plugged it into the breadboard. Here, I would have preferred that the Saleae had male connector pins instead of female, but a few short M-M jumpers to the rescue.

I did buy my own Dupont crimping too la few years back, so I will probably make my own Saleae male-pin probe set for breadboards. The only think needed is some wire and a Dupont header kit such as this one on ebay.

So back to the setup: Not completely intuitive is the trigger and decoding of the SPI signals. The first thing is to setup the start of capture based on the falling edge of the clock:


The next is to tell the Saleae software *which* pins are being used for decoding. First select the SPI decoder on the right side of the app:


Next, click on the little gear and "edit settings" to select which channels are which SPI pins:


The good thing is that if you don't need to change protocols, the software remembers this even after uninstalling and re-installing new software. So until you change debugging projects, the setup of the logic analyzer stays consistent.

From a software perspective, we just simply expect the bytes to be sent. But to actually see what's going on, the logic analyzer is indispensable:


Here we can see the same hex codes in the Salaea decoded from the logic analyzer probes as seen in the code segment, above. Setup of the decoder in the software was also vastly easier than the serial decoder mentioned above on my oscilloscope. The first byte sent on the SPI bus is 0xAE, the command turn turn the display off during initialization. How cool is that?

Although the Salaea software is not implemented with the proper Windows UI (banner toolbar across the top, File-Save, Help-About, etc)... for the most part it is quite easy to use and intuitive. One of the less-obvious features is that you can double click on the decoded values in the lower right, and the display auto-scrolls and auto-scales to the trace for that value. Very nice.

Once I had a benchmark of sending a byte of SPI data in under 8 micro-seconds, let's see what happens when manually bit-banging the pins from C#. Here's the code to send an SPI byte:


        private void spiWrite(byte b)
        {
            for (byte bit = 0; bit < 8; bit++)
            {
                if ((b & 0x80) > 0)
                {
                    SPI_MOSI_HIGH();
                }
                else
                {
                    SPI_MOSI_LOW();
                }
                SPI_SCK_HIGH();
                b <<= 1;
                SPI_SCK_LOW();
            }
        }


This is pretty straightforward and almost identical to the Adafruit C/C++ code in their SPITFT library (minus the hardware-specific conditional compile directives) that I based my C# code on. See also the spiWrite() in esp32-hal-spi.c.

Next, I tried to use the built-in nanoFramework SPI driver, but manually control the D/C line.


private void WriteCommand(byte command)
        {
            _buffer1[0] = command;

            // the time between the next two statements is a whopping 0.3ms
            _DS.Write(GpioPinValue.Low);
            _DS.Write(GpioPinValue.High);

            // here we manually bring DS low
            _DS.Write(GpioPinValue.Low);
            _spi.Write(_buffer1); // all 8 bits in about 54uS
            _DS.Write(GpioPinValue.High);
        }


Here's what it looks like on the logic analyzer:


Notice in particular the vastly longer delay in switching the D/C line as compared to the bits sent during the native _spi.Write(). There's nearly three-quarters of a millisecond window for the D/C line, but only about 54 microseconds to send the entire 8 bits of data:


When I posted this information on the Discord channel, @terryfogg suggested that I review a similar example being worked on. From what I could tell, it was VSPI with a different set of pins and also confirmed that C# is just too slow for this kind of, so instead this was using native code. Ah yes, the twisty little passage - the Rabbit hole goes on to another whole level: calling native code from C# on an embedded device.

The next level of complexity? Interop of course. To get started, there's a pretty good tutorial on Interop in the .Net nanoFramework. Yes, interop of course pretty much screws the entire concept of managed code, but with the benefit of vastly better performance. Hm. See the links at the bottom of this page for more information on calling unmanaged code from C#. I think that's probably a whole topic in and of itself. Stay tuned...


Full disclosure: I purchased the Saleae Logic Pro 16 at a substantial discount since I (sadly) don't do this fun stuff for a living. Saleae has a "Electronics Enthusiasts (non-commercial use)" discount. My primary motivation was the USB3 sampling speeds of up to 125MHz to be used as I dig deeper into the ULX3S FPGA capabilities. The discount of course made all the difference for my hobbyist budget. In fact, there's an "Enthusiast Discount Extreme Edition" that I hope to qualify for with this blog entry.

I purchased the ULX3S early last year from Goran after reading about it on Hackaday and sending an email. Although I am listed as one of the members of the Radiona CrowdSupply team, I am not employed by Radiona, nor do I receive any financial compensation from them.

All opinions expressed here are my own and not representative of my employer.


Resources, Inspiration, Credits, and Other Links:

Find gojimmypi at gojimmypi.github.io

I'm currently working on my new blog home at  gojimmypi.github.io After implementing a variety of features such as dark mode , syntax hi...