Skip to content

Commit 44a6b16

Browse files
committed
Linux-on-de0nano: New tutorial
1 parent 8a2bd6c commit 44a6b16

2 files changed

Lines changed: 393 additions & 0 deletions

File tree

docs/de0-nano-gpios.png

4.5 MB
Loading

docs/linux-on-de0nano.md

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
---
2+
title: Linux on De0 Nano
3+
layout: page
4+
parent: Linux
5+
nav_order: 2
6+
---
7+
8+
# Prerequisites
9+
10+
#### Tutorials
11+
12+
Run our [De0 Nano tutorial](../de0_nano/index.html) to ensure you have:
13+
14+
- A working version of OpenOCD
15+
- A UART connected to the De0 Nano development board
16+
- `fusesoc` - The [FuseSoC build system](../fusesoc.html).
17+
- OpenOCD 0.10.0 (As of 2025, versions 0.11.0 and 0.12.0 have jtag timing issues)
18+
- `or1k-elf-` Toolchain as installed in our [newlib tutorial](../newlib.html).
19+
20+
#### System
21+
22+
Additionally for the Linux tutorial we will need:
23+
24+
- An x86 Linux workstation
25+
- The `curl` and `git` command line utilities
26+
- A serial terminal emulator, here we use `picocom`
27+
- 2.5 GB of disk space
28+
- (*Optional*) The `sx` utility for sending binaries to the De0 Nano over serial. This is available in the `lrzsz` package.
29+
30+
#### Files
31+
32+
We will download these below.
33+
34+
- [linux](https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.5.tar.xz) - Linux kernel source code
35+
- [busybox-small-rootfs-20250708.tar.xz](https://github.com/stffrdhrn/or1k-rootfs-build/releases/download/or1k-20250708/busybox-small-rootfs-20250708.tar.xz) - Linux rootfs for userspace programs
36+
37+
# Linux on De0 Nano Tutorial
38+
39+
In this tutorial we will cover building a Linux kernel and booting it on [De0 Nano](../de0_nano/index.html)
40+
with a [busybox](https://busybox.net) root filesystem. This builds on the De0 Nano
41+
hello world FGPA board bring up tutorial.
42+
This is a fun way to experiment with a bare bones Linux embedded system.
43+
44+
We break this tutorial down into parts:
45+
46+
- Downloading the pieces
47+
- Compiling the kernel
48+
- Building the FPGA bitstream
49+
- Running the kernel
50+
- Uploading userspace programs over serial
51+
- Using the GPIOs
52+
53+
## Downloading the Pieces
54+
55+
To get started we will create a temporary directory and setup our environment, if
56+
you plan to do a lot of OpenRISC development consider adding these tools to your
57+
`PATH` permanently.
58+
59+
To get everything you need run:
60+
61+
```bash
62+
mkdir /tmp/linux-on-de0nano/
63+
cd /tmp/linux-on-de0nano/
64+
65+
curl -L -O https://openrisc.io/tutorials/de0_nano/de0_nano.cfg
66+
67+
# Clone kernel source, ~2GiB
68+
git clone --depth 1 --branch v7.0.0-rc1 https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux.git
69+
70+
# Download a busybox rootfs and our toolchain
71+
curl -L -O https://github.com/stffrdhrn/or1k-rootfs-build/releases/download/or1k-20250708/busybox-small-rootfs-20250708.tar.xz
72+
curl -L -O https://github.com/stffrdhrn/or1k-toolchain-build/releases/download/or1k-15.1.0-20250621/or1k-none-linux-musl-15.1.0-20250621.tar.xz
73+
74+
# Add IP cores to the environment
75+
fusesoc library add fusesoc-cores https://github.com/fusesoc/fusesoc-cores
76+
fusesoc library add elf-loader https://github.com/fusesoc/elf-loader.git
77+
fusesoc library add openrisc-cores https://github.com/openrisc/openrisc-cores
78+
fusesoc library add de0_nano https://github.com/olofk/de0_nano.git
79+
80+
# Check the SoC design is available
81+
fusesoc core show de0_nano
82+
83+
# Extract software needed for the kernel
84+
tar -xf busybox-small-rootfs-20250708.tar.xz
85+
tar -xf or1k-none-linux-musl-15.1.0-20250621.tar.xz
86+
87+
export PATH=$PATH:$PWD/or1k-none-linux-musl/bin
88+
```
89+
90+
## Compiling the Kernel
91+
92+
To build a Linux kernel we use the toolchain, kernel source code and rootfs just downloaded
93+
in the following make commands:
94+
95+
```bash
96+
make -C linux \
97+
ARCH=openrisc \
98+
CROSS_COMPILE=or1k-none-linux-musl- \
99+
de0_nano_defconfig
100+
101+
make -C linux \
102+
-j$(nproc) \
103+
ARCH=openrisc \
104+
CROSS_COMPILE=or1k-none-linux-musl- \
105+
CONFIG_INITRAMFS_SOURCE="$PWD/busybox-small-rootfs-20250708/initramfs/ $PWD/busybox-small-rootfs-20250708/initramfs.devnodes"
106+
```
107+
108+
The first command configures the kernel with the default configuration `de0_nano_defconfig` which is for the De0 Nano board. The `make`
109+
arguments are as follows:
110+
111+
* `-C linux` - This saves us from having to change directory, `make` will run from within the Linux source code directory.
112+
* `ARCH=openrisc` - This passes the `ARCH` variable to the build system selecting the OpenRISC architecture.
113+
* `CROSS_COMPILE=or1k-none-linux-musl-` - This passes the `CROSS_COMPILE` variable to the build system selecting our toolchain.
114+
* `de0_nano_defconfig` - The make target, configures the linux kernel for the build.
115+
116+
The second command builds the kernel. The new argument is:
117+
118+
* `CONFIG_INITRAMFS_SOURCE=...` - This configures the built in root filesystem.
119+
120+
When running on machines with no disk capabilities such as De0 Nano we can use an [initfamfs](https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt)
121+
that is built directly into our kernel ELF binary. This is one of the most
122+
simple ways to boot Linux but it means that our data is only in RAM and will be
123+
lost after reset. Also, it means that updating the rootfs userspace utilties
124+
requires rebuilding the kernel.
125+
126+
## Building the FPGA bitstream
127+
128+
As with the `de0 nano` hello world tutorial we can build the bitstream with:
129+
130+
```bash
131+
fusesoc run de0_nano --bootrom_file ./build/de0_nano_*/default-quartus/clear_r3_and_jump_to_0x100.vh
132+
```
133+
134+
## Running the Kernel
135+
136+
To start Linux on the De0 Nano development board we need to load the bitstream then
137+
upload the kernel image into RAM over the debug interface. The steps being:
138+
139+
1. Program the FPGA bitstream onto the FPGA board
140+
2. Using GDB load the kernel image - including the embedded rootfs - into RAM
141+
3. Reset the FPGA design, kicking off the kernel boot process
142+
143+
#### Terminal 1
144+
145+
We can program the OpenRISC FPGA bitstream and Linux kernel to the board with
146+
the following commands.
147+
148+
```bash
149+
fusesoc run de0_nano --pgm quartus
150+
151+
# Run openocd in the background so we can use one terminal.
152+
openocd -f interface/altera-usb-blaster.cfg -f de0_nano.cfg > openocd.log 2>&1 &
153+
154+
or1k-elf-gdb ./linux/vmlinux \
155+
-ex 'target remote :3333' \
156+
-ex 'monitor reset' \
157+
-ex 'monitor halt' \
158+
-ex load \
159+
-ex 'monitor reset' \
160+
-ex detach \
161+
-ex quit
162+
163+
pkill openocd
164+
```
165+
166+
The GDB command will connect to OpenOCD, then reset and halt the board making
167+
it ready to program. The `load` command will load the `vmlinux` ELF binary into
168+
the De0 Nano RAM. The next `monitor reset` command will reset the FPGA design
169+
kicking off the Linux boot process.
170+
171+
In a second terminal while the FPGA board is running connect
172+
to the serial console using `picocom`.
173+
174+
#### Terminal 2
175+
176+
We will use terminal 2 for connecting to the development board.
177+
178+
Open `picocom` with the following:
179+
180+
```bash
181+
cd /tmp/linux-on-de0nano/
182+
183+
picocom -b 115200 /dev/ttyUSB0 --receive-cmd 'rx -vv' --send-cmd 'sx -vv'
184+
```
185+
186+
To disconnect press `Ctrl-a` `Ctrl-q`
187+
188+
To upload files to the board press `Ctrl-a` `Ctrl-s`
189+
190+
## Adding Custom User Space Programs
191+
192+
To add your own programs to a running board you can use the [musl](https://musl.libc.org)
193+
toolchain to build executables as follows:
194+
195+
```
196+
curl -L -O https://openrisc.io/tutorials/sw/hello/hello.c
197+
198+
or1k-none-linux-musl-gcc hello.c -o hello
199+
```
200+
201+
This compiles the `hello.c` program and outputs the binary directly into our
202+
local folder.
203+
204+
Next to send the program to the board we can use the [XModem](https://en.wikipedia.org/wiki/XMODEM) command `rx` per the below steps.
205+
Remember, the `rx` (receive XModem) command runs on the dev board, while the `sx` (send XModem) command runs
206+
on your local machine.
207+
208+
- While in `picocom`
209+
- On the board run `rx hello`
210+
- Press `Ctrl-a` `Ctrl-s` to initialize send
211+
- Select file `hello` from your local machine
212+
- Next `chmod` and execute the program
213+
214+
### Example program upload and execution
215+
216+
```
217+
~ # rx hello
218+
C
219+
*** file: hello
220+
$ sx -vv hello
221+
Sending hello, 81 blocks: Give your local XMODEM receive command now.
222+
Bytes Sent: 10496 BPS:1575
223+
224+
Transfer complete
225+
226+
*** exit status: 0 ***
227+
~ # chmod 755 ./hello
228+
~ # ./hello
229+
Hello World!
230+
~ #
231+
```
232+
233+
If you wish to add your files permanently to the rootfs you can do that
234+
using the method described in the [Linux on or1ksim](./linux-on-or1ksim.html)
235+
tutorial.
236+
237+
## Using the GPIOs
238+
239+
The GPIOs on the De0 Nano are available in userspace via the `sysfs` interface.
240+
241+
There are 2 gpio banks configured in the [De0 Nano device tree](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/openrisc/boot/dts/de0-nano-common.dtsi?h=v7.0-rc1)
242+
as follows:
243+
244+
```devicetree
245+
/* 8 Green LEDs */
246+
gpio0: gpio@91000000 {
247+
compatible = "opencores,gpio";
248+
reg = <0x91000000 0x1>, <0x91000001 0x1>;
249+
reg-names = "dat", "dirout";
250+
gpio-controller;
251+
#gpio-cells = <2>;
252+
};
253+
254+
/* 4 DIP Switches */
255+
gpio1: gpio@92000000 {
256+
compatible = "opencores,gpio";
257+
reg = <0x92000000 0x1>, <0x92000001 0x1>;
258+
reg-names = "dat", "dirout";
259+
gpio-controller;
260+
#gpio-cells = <2>;
261+
status = "disabled";
262+
};
263+
```
264+
265+
Note: This is a device tree include file; the `gpio1` block is enabled on the De0 Nano board with `status = "okay"`.
266+
267+
We can read from the DIP switches and write to the LEDs with the following commands:
268+
269+
### LEDs
270+
271+
```
272+
# Export a GPIO pin
273+
echo 1 > /sys/class/gpio/chip0/export
274+
275+
# Set it as an output
276+
echo out > /sys/class/gpio/chip0/gpio1/direction
277+
278+
# Turn the LED on (1) or off (0)
279+
echo 1 > /sys/class/gpio/chip0/gpio1/value
280+
echo 0 > /sys/class/gpio/chip0/gpio1/value
281+
```
282+
283+
### DIP Switches
284+
285+
```
286+
# Export a GPIO pin
287+
echo 0 > /sys/class/gpio/chip1/export
288+
289+
# Set it as an input
290+
echo in > /sys/class/gpio/chip1/gpio0/direction
291+
292+
# Read the DIP Switch
293+
cat /sys/class/gpio/chip1/gpio0/value
294+
```
295+
296+
The board with LED 1 on looks like the following. LED 0 is configured to show the
297+
heartbeat indicator, hence we export LED 1 in the example above.
298+
299+
![gpios](de0-nano-gpios.png "De0 Nano GOIOs")
300+
301+
## Example Boot Sequence
302+
303+
For reference in the `picocom` terminal the boot sequence will look as follows:
304+
305+
#### Terminal 2
306+
307+
```
308+
[ 0.000000] Compiled-in FDT at (ptrval)
309+
[ 0.000000] Linux version 7.0.0-rc1 (shorne@antec) (or1k-none-linux-musl-gcc (GCC) 15.1.0, GNU ld (GNU Binutils) 2.44) #1 Tue Feb 24 18:26:47 GMT 2026
310+
[ 0.000000] OF: reserved mem: Reserved memory: No reserved-memory node in the DT
311+
[ 0.000000] CPU: OpenRISC-10 (revision 0) @50 MHz
312+
[ 0.000000] -- dmmu: 64 entries, 1 way(s)
313+
[ 0.000000] -- immu: 64 entries, 1 way(s)
314+
[ 0.000000] -- additional features:
315+
[ 0.000000] -- debug unit
316+
[ 0.000000] -- PIC
317+
[ 0.000000] -- timer
318+
[ 0.000000] Initial ramdisk not found
319+
[ 0.000000] Setting up paging and PTEs.
320+
[ 0.000000] map_ram: Memory: 0x0-0x2000000
321+
[ 0.000000] itlb_miss_handler (ptrval)
322+
[ 0.000000] dtlb_miss_handler (ptrval)
323+
[ 0.000000] OpenRISC Linux -- http://openrisc.io
324+
[ 0.000000] Zone ranges:
325+
[ 0.000000] Normal [mem 0x0000000000000000-0x0000000001ffffff]
326+
[ 0.000000] Movable zone start for each node
327+
[ 0.000000] Early memory node ranges
328+
[ 0.000000] node 0: [mem 0x0000000000000000-0x0000000001ffffff]
329+
[ 0.000000] Initmem setup node 0 [mem 0x0000000000000000-0x0000000001ffffff]
330+
[ 0.000000] Kernel command line:
331+
[ 0.000000] printk: log buffer data + meta data: 16384 + 51200 = 67584 bytes
332+
[ 0.000000] Dentry cache hash table entries: 4096 (order: 1, 16384 bytes, linear)
333+
[ 0.000000] Inode-cache hash table entries: 2048 (order: 0, 8192 bytes, linear)
334+
[ 0.000000] Sorting __ex_table...
335+
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 4096
336+
[ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off
337+
[ 0.000000] mem_init_done ...........................................
338+
[ 0.000000] SLUB: HWalign=16, Order=0-1, MinObjects=0, CPUs=1, Nodes=1
339+
[ 0.000000] NR_IRQS: 32, nr_irqs: 32, preallocated irqs: 0
340+
[ 0.000000] clocksource: openrisc_timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 38225208935 ns
341+
[ 0.000000] 100.00 BogoMIPS (lpj=500000)
342+
[ 0.000000] pid_max: default: 32768 minimum: 301
343+
[ 0.000000] Mount-cache hash table entries: 2048 (order: 0, 8192 bytes, linear)
344+
[ 0.000000] Mountpoint-cache hash table entries: 2048 (order: 0, 8192 bytes, linear)
345+
[ 0.010000] VFS: Finished mounting rootfs on nullfs
346+
[ 0.040000] Memory: 21608K/32768K available (4457K kernel code, 164K rwdata, 520K rodata, 5344K init, 65K bss, 10784K reserved, 0K cma-reserved)
347+
[ 0.040000] devtmpfs: initialized
348+
[ 0.070000] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
349+
[ 0.070000] posixtimers hash table entries: 512 (order: 0, 2048 bytes, linear)
350+
[ 0.070000] futex hash table entries: 256 (4096 bytes on 1 NUMA nodes, total 4 KiB, linear).
351+
[ 0.090000] NET: Registered PF_NETLINK/PF_ROUTE protocol family
352+
[ 0.130000] pps_core: LinuxPPS API ver. 1 registered
353+
[ 0.130000] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
354+
[ 0.150000] clocksource: Switched to clocksource openrisc_timer
355+
[ 0.180000] NET: Registered PF_INET protocol family
356+
[ 0.190000] IP idents hash table entries: 2048 (order: 1, 16384 bytes, linear)
357+
[ 0.200000] tcp_listen_portaddr_hash hash table entries: 2048 (order: 0, 8192 bytes, linear)
358+
[ 0.210000] Table-perturb hash table entries: 65536 (order: 5, 262144 bytes, linear)
359+
[ 0.210000] TCP established hash table entries: 2048 (order: 0, 8192 bytes, linear)
360+
[ 0.210000] TCP bind hash table entries: 2048 (order: 1, 16384 bytes, linear)
361+
[ 0.210000] TCP: Hash tables configured (established 2048 bind 2048)
362+
[ 0.210000] UDP hash table entries: 256 (order: 0, 8192 bytes, linear)
363+
[ 0.210000] UDP-Lite hash table entries: 256 (order: 0, 8192 bytes, linear)
364+
[ 0.210000] NET: Registered PF_UNIX/PF_LOCAL protocol family
365+
[ 0.250000] workingset: timestamp_bits=30 max_order=12 bucket_order=0
366+
[ 0.320000] ledtrig-cpu: registered to indicate activity on CPUs
367+
[ 0.340000] Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled
368+
[ 0.430000] printk: legacy console [ttyS0] disabled
369+
[ 0.450000] 90000000.serial: ttyS0 at MMIO 0x90000000 (irq = 2, base_baud = 3125000) is a 16550A
370+
[ 0.450000] printk: legacy console [ttyS0] enabled
371+
[ 1.280000] -- dcache: 16384 bytes total, 32 bytes/line, 256 set(s), 2 way(s)
372+
[ 1.290000] -- icache: 16384 bytes total, 32 bytes/line, 256 set(s), 2 way(s)
373+
[ 1.430000] clk: Disabling unused clocks
374+
[ 2.360000] Freeing unused kernel image (initmem) memory: 5344K
375+
[ 2.370000] This architecture does not have kernel memory protection.
376+
[ 2.380000] Run /init as init process
377+
ifconfig: SIOCSIFADDR: No such device
378+
route: SIOCADDRT: Network unreachable
379+
380+
Please press Enter to activate this console.
381+
~ #
382+
~ # uname -a
383+
Linux openrisc 7.0.0-rc1 #1 Tue Feb 24 18:26:47 GMT 2026 openrisc GNU/Linux
384+
```
385+
386+
387+
## Further Reading
388+
389+
- [openrisc/or1ksim](https://github.com/openrisc/or1ksim) - The or1ksim home page and git repo
390+
- [or1ksim Releases](https://github.com/openrisc/or1ksim/releases) - Nightly build and point release
391+
- [Linux Releases](https://kernel.org) - Linux release tarballs
392+
- [OpenRISC toolchain Releases](https://github.com/stffrdhrn/or1k-toolchain-build/releases) - Toolchain point releases
393+
- [OpenRISC rootfs Releases](https://github.com/stffrdhrn/or1k-rootfs-build/releases) - Rootfs point release

0 commit comments

Comments
 (0)