|
| 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 | + |
| 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