|
| 1 | +--- |
| 2 | +title: SMP Linux on De0 Nano Multicore |
| 3 | +layout: page |
| 4 | +parent: Linux |
| 5 | +nav_order: 6 |
| 6 | +--- |
| 7 | + |
| 8 | +# Prerequisites |
| 9 | + |
| 10 | +#### Tutorials |
| 11 | + |
| 12 | +Run our [Linux on De0 Nano tutorial](linux-on-de0nano.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 | + - `or1k-none-linux-musl-` - OpenRISC musl linux userspace toolchain |
| 20 | + |
| 21 | +#### System |
| 22 | + |
| 23 | +Additionally for the Linux tutorial we will need: |
| 24 | + |
| 25 | + - An x86 Linux workstation |
| 26 | + - The `curl` and `git` command line utilities |
| 27 | + - A serial terminal emulator, here we use `picocom` |
| 28 | + - 2.5 GB of disk space |
| 29 | + |
| 30 | +#### Files |
| 31 | + |
| 32 | +We will download these below. |
| 33 | + |
| 34 | + - linux - 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 | +# SMP Linux on De0 Nano Multicore Tutorial |
| 38 | + |
| 39 | +In this tutorial we will cover building a Symetrical Multi-Processing (SMP) 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 and the Linux on De0 Nano tutorials. |
| 42 | + |
| 43 | +In this tutorial we will be able to expirment with an OpenRISC design with 2 cpu's. |
| 44 | + |
| 45 | +We break this tutorial down into parts: |
| 46 | + |
| 47 | + - Downloading the pieces |
| 48 | + - Compiling the kernel |
| 49 | + - Building the FPGA bitstream |
| 50 | + - Running the kernel |
| 51 | + |
| 52 | +## Downloading the Pieces |
| 53 | + |
| 54 | +To get started we will create a temporary directory and setup our environment, if |
| 55 | +you plan to do a lot of OpenRISC development consider adding these tools to your |
| 56 | +`PATH` permanently. |
| 57 | + |
| 58 | +To get everything you need run: |
| 59 | + |
| 60 | +```bash |
| 61 | +mkdir /tmp/linux-on-de0nano-multicore/ |
| 62 | +cd /tmp/linux-on-de0nano-multicore/ |
| 63 | + |
| 64 | +# Get our config for openocd |
| 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 | + |
| 73 | +# Add IP cores to the environment |
| 74 | +fusesoc library add fusesoc-cores https://github.com/fusesoc/fusesoc-cores |
| 75 | +fusesoc library add elf-loader https://github.com/fusesoc/elf-loader.git |
| 76 | +fusesoc library add openrisc-cores https://github.com/openrisc/openrisc-cores |
| 77 | +fusesoc library add de0_nano-multicore https://github.com/stffrdhrn/de0_nano-multicore.git |
| 78 | + |
| 79 | +# Check the SoC design is available |
| 80 | +fusesoc core show de0_nano-multicore |
| 81 | + |
| 82 | +# Extract software needed for the kernel |
| 83 | +tar -xf busybox-small-rootfs-20250708.tar.xz |
| 84 | + |
| 85 | +# Check the OpenRISC linux toolchain is on the path, if not see the Linux on De0 Nano tutorial |
| 86 | +or1k-none-linux-musl-gcc --version |
| 87 | +``` |
| 88 | + |
| 89 | +## Compiling the Kernel |
| 90 | + |
| 91 | +To build a Linux kernel we use the toolchain, kernel source code and rootfs just downloaded |
| 92 | +in the following make commands: |
| 93 | + |
| 94 | +```bash |
| 95 | +make -C linux \ |
| 96 | + ARCH=openrisc \ |
| 97 | + CROSS_COMPILE=or1k-none-linux-musl- \ |
| 98 | + de0_nano_multicore_defconfig |
| 99 | + |
| 100 | +make -C linux \ |
| 101 | + -j$(nproc) \ |
| 102 | + ARCH=openrisc \ |
| 103 | + CROSS_COMPILE=or1k-none-linux-musl- \ |
| 104 | + CONFIG_INITRAMFS_SOURCE="$PWD/busybox-small-rootfs-20250708/initramfs/ $PWD/busybox-small-rootfs-20250708/initramfs.devnodes" |
| 105 | +``` |
| 106 | + |
| 107 | +The first command configures the kernel with the default configuration `de0_nano_multicore_defconfig` which is for the De0 Nano |
| 108 | +board using our Mutlicore design. The `make` arguments are as follows: |
| 109 | + |
| 110 | + * `-C linux` - This saves us from having to change directory, `make` will run from within the Linux source code directory. |
| 111 | + * `ARCH=openrisc` - This passes the `ARCH` variable to the build system selecting the OpenRISC architecture. |
| 112 | + * `CROSS_COMPILE=or1k-none-linux-musl-` - This passes the `CROSS_COMPILE` variable to the build system selecting our toolchain. |
| 113 | + * `de0_nano_multicore_defconfig` - The make target, configures the linux kernel for the build. |
| 114 | + |
| 115 | +The second command builds the kernel. The new argument is: |
| 116 | + |
| 117 | + * `CONFIG_INITRAMFS_SOURCE=...` - This configures the built in root filesystem. |
| 118 | + |
| 119 | +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) |
| 120 | +that is built directly into our kernel ELF binary. This is one of the most |
| 121 | +simple ways to boot Linux but it means that our data is only in RAM and will be |
| 122 | +lost after reset. Also, it means that updating the rootfs userspace utilties |
| 123 | +requires rebuilding the kernel. |
| 124 | + |
| 125 | +## Building the FPGA bitstream |
| 126 | + |
| 127 | +As with the De0 Nano hello world tutorial we can build the bitstream with: |
| 128 | + |
| 129 | +```bash |
| 130 | +fusesoc run de0_nano-multicore |
| 131 | +``` |
| 132 | + |
| 133 | +*Note*: The De0 Nano multicore design uses 16K or 73% of the Cyclone IV LEs |
| 134 | +compared to 10K or 49% for the single core design. Also, you may notice that |
| 135 | +the total number of memory bits in the multicore design is slightly less |
| 136 | +compared to the single core design as we shrank the cache sizes on each core to |
| 137 | +allow for the design to fit. |
| 138 | + |
| 139 | +See fitting details below: |
| 140 | + |
| 141 | +``` |
| 142 | +$ tail build/de0_nano-multicore_1.1/default-quartus/de0_nano-multicore_1_1.fit.summary |
| 143 | +Timing Models : Final |
| 144 | +Total logic elements : 16,245 / 22,320 ( 73 % ) |
| 145 | + Total combinational functions : 14,659 / 22,320 ( 66 % ) |
| 146 | + Dedicated logic registers : 7,440 / 22,320 ( 33 % ) |
| 147 | +Total registers : 7440 |
| 148 | +Total pins : 53 / 154 ( 34 % ) |
| 149 | +Total virtual pins : 0 |
| 150 | +Total memory bits : 312,576 / 608,256 ( 51 % ) |
| 151 | +Embedded Multiplier 9-bit elements : 12 / 132 ( 9 % ) |
| 152 | +Total PLLs : 1 / 4 ( 25 % ) |
| 153 | +
|
| 154 | +$ tail ../or1k-de0nano/build/de0_nano_1.1/default-quartus/de0_nano_1_1.fit.summary |
| 155 | +Timing Models : Final |
| 156 | +Total logic elements : 10,980 / 22,320 ( 49 % ) |
| 157 | + Total combinational functions : 9,706 / 22,320 ( 43 % ) |
| 158 | + Dedicated logic registers : 5,472 / 22,320 ( 25 % ) |
| 159 | +Total registers : 5472 |
| 160 | +Total pins : 73 / 154 ( 47 % ) |
| 161 | +Total virtual pins : 0 |
| 162 | +Total memory bits : 327,936 / 608,256 ( 54 % ) |
| 163 | +Embedded Multiplier 9-bit elements : 6 / 132 ( 5 % ) |
| 164 | +Total PLLs : 1 / 4 ( 25 % ) |
| 165 | +``` |
| 166 | + |
| 167 | +## Running the Kernel |
| 168 | + |
| 169 | +*(Note: this step is the same as Linux on De0 Nano)* |
| 170 | + |
| 171 | +To start Linux on the De0 Nano development board we need to load the bitstream then |
| 172 | +upload the kernel image into RAM over the debug interface. The steps being: |
| 173 | + |
| 174 | + 1. Program the FPGA bitstream onto the FPGA board |
| 175 | + 2. Using GDB load the kernel image - including the embedded rootfs - into RAM |
| 176 | + 3. Reset the FPGA design, kicking off the kernel boot process |
| 177 | + |
| 178 | +#### Terminal 1 |
| 179 | + |
| 180 | +We can program the OpenRISC FPGA bitstream and Linux kernel to the board with |
| 181 | +the following commands. |
| 182 | + |
| 183 | +```bash |
| 184 | +fusesoc run de0_nano-multicore --pgm quartus |
| 185 | + |
| 186 | +# Run openocd in the background so we can use one terminal. |
| 187 | +openocd -f interface/altera-usb-blaster.cfg -f de0_nano.cfg > openocd.log 2>&1 & |
| 188 | + |
| 189 | +or1k-elf-gdb ./linux/vmlinux \ |
| 190 | + -ex 'target remote :3333' \ |
| 191 | + -ex 'monitor reset' \ |
| 192 | + -ex 'monitor halt' \ |
| 193 | + -ex load \ |
| 194 | + -ex 'monitor reset' \ |
| 195 | + -ex detach \ |
| 196 | + -ex quit |
| 197 | + |
| 198 | +pkill openocd |
| 199 | +``` |
| 200 | + |
| 201 | +The GDB command will connect to OpenOCD, then reset and halt the board making |
| 202 | +it ready to program. The `load` command will load the `vmlinux` ELF binary into |
| 203 | +the De0 Nano RAM. The next `monitor reset` command will reset the FPGA design |
| 204 | +kicking off the Linux boot process. |
| 205 | + |
| 206 | +In a second terminal while the FPGA board is running connect |
| 207 | +to the serial console using `picocom`. |
| 208 | + |
| 209 | +#### Terminal 2 |
| 210 | + |
| 211 | +*(Note: this step is the same as Linux on De0 Nano)* |
| 212 | + |
| 213 | +We will use terminal 2 for connecting to the development board. |
| 214 | + |
| 215 | +Open `picocom` with the following: |
| 216 | + |
| 217 | +```bash |
| 218 | +cd /tmp/linux-on-de0nano-multicore/ |
| 219 | + |
| 220 | +picocom -b 115200 /dev/ttyUSB0 --receive-cmd 'rx -vv' --send-cmd 'sx -vv' |
| 221 | +``` |
| 222 | + |
| 223 | +To disconnect press `Ctrl-a` `Ctrl-q` |
| 224 | + |
| 225 | +To upload files to the board press `Ctrl-a` `Ctrl-s` |
| 226 | + |
| 227 | +## Example Boot Sequence |
| 228 | + |
| 229 | +For reference in the `picocom` terminal the boot sequence will look as follows: |
| 230 | + |
| 231 | +#### Terminal 2 |
| 232 | + |
| 233 | +``` |
| 234 | +[ 0.000000] Compiled-in FDT at (ptrval) |
| 235 | +[ 0.000000] Linux version 7.0.0-rc1-de0nano-smp (shorne@antec) (or1k-none-linux-musl-gcc (GCC) 15.1.0, GNU ld (GNU Binutils) 2.44) #2 SMP Thu Feb 26 21:37:09 GMT 2026 |
| 236 | +[ 0.000000] OF: reserved mem: Reserved memory: No reserved-memory node in the DT |
| 237 | +[ 0.000000] CPU: OpenRISC-10 (revision 0) @50 MHz |
| 238 | +[ 0.000000] -- dmmu: 64 entries, 1 way(s) |
| 239 | +[ 0.000000] -- immu: 64 entries, 1 way(s) |
| 240 | +[ 0.000000] -- additional features: |
| 241 | +[ 0.000000] -- debug unit |
| 242 | +[ 0.000000] -- PIC |
| 243 | +[ 0.000000] -- timer |
| 244 | +[ 0.000000] Initial ramdisk not found |
| 245 | +[ 0.000000] Setting up paging and PTEs. |
| 246 | +[ 0.000000] map_ram: Memory: 0x0-0x2000000 |
| 247 | +[ 0.000000] itlb_miss_handler (ptrval) |
| 248 | +[ 0.000000] dtlb_miss_handler (ptrval) |
| 249 | +[ 0.000000] OpenRISC Linux -- http://openrisc.io |
| 250 | +[ 0.000000] Zone ranges: |
| 251 | +[ 0.000000] Normal [mem 0x0000000000000000-0x0000000001ffffff] |
| 252 | +[ 0.000000] Movable zone start for each node |
| 253 | +[ 0.000000] Early memory node ranges |
| 254 | +[ 0.000000] node 0: [mem 0x0000000000000000-0x0000000001ffffff] |
| 255 | +[ 0.000000] Initmem setup node 0 [mem 0x0000000000000000-0x0000000001ffffff] |
| 256 | +[ 0.000000] percpu: Embedded 5 pages/cpu s16032 r0 d24928 u40960 |
| 257 | +[ 0.000000] Kernel command line: earlycon |
| 258 | +[ 0.000000] earlycon: ns16550a0 at MMIO 0x90000000 (options '115200') |
| 259 | +[ 0.000000] printk: legacy bootconsole [ns16550a0] enabled |
| 260 | +[ 0.000000] printk: log buffer data + meta data: 16384 + 51200 = 67584 bytes |
| 261 | +[ 0.000000] Dentry cache hash table entries: 4096 (order: 1, 16384 bytes, linear) |
| 262 | +[ 0.000000] Inode-cache hash table entries: 2048 (order: 0, 8192 bytes, linear) |
| 263 | +[ 0.000000] Sorting __ex_table... |
| 264 | +[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 4096 |
| 265 | +[ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off |
| 266 | +[ 0.000000] mem_init_done ........................................... |
| 267 | +[ 0.000000] SLUB: HWalign=16, Order=0-1, MinObjects=0, CPUs=2, Nodes=1 |
| 268 | +[ 0.000000] rcu: Hierarchical RCU implementation. |
| 269 | +[ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies. |
| 270 | +[ 0.000000] NR_IRQS: 32, nr_irqs: 32, preallocated irqs: 0 |
| 271 | +[ 0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention. |
| 272 | +[ 0.000000] clocksource: openrisc_timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 38225208935 ns |
| 273 | +[ 0.000000] 100.00 BogoMIPS (lpj=500000) |
| 274 | +[ 0.000000] pid_max: default: 32768 minimum: 301 |
| 275 | +[ 0.010000] Mount-cache hash table entries: 2048 (order: 0, 8192 bytes, linear) |
| 276 | +[ 0.020000] Mountpoint-cache hash table entries: 2048 (order: 0, 8192 bytes, linear) |
| 277 | +[ 0.040000] VFS: Finished mounting rootfs on nullfs |
| 278 | +[ 0.120000] rcu: Hierarchical SRCU implementation. |
| 279 | +[ 0.120000] rcu: Max phase no-delay instances is 1000. |
| 280 | +[ 0.140000] Timer migration: 1 hierarchy levels; 8 children per group; 1 crossnode level |
| 281 | +[ 0.160000] smp: Bringing up secondary CPUs ... |
| 282 | +[ 0.190000] CPU1: Booted secondary processor |
| 283 | +[ 0.190000] CPU: OpenRISC-10 (revision 0) @50 MHz |
| 284 | +[ 0.190000] -- dmmu: 64 entries, 1 way(s) |
| 285 | +[ 0.190000] -- immu: 64 entries, 1 way(s) |
| 286 | +[ 0.190000] -- additional features: |
| 287 | +[ 0.190000] -- debug unit |
| 288 | +[ 0.190000] -- PIC |
| 289 | +[ 0.190000] -- timer |
| 290 | +[ 0.190000] Synchronize counters for CPU 1: done. |
| 291 | +[ 0.230000] smp: Brought up 1 node, 2 CPUs |
| 292 | +[ 0.260000] Memory: 19960K/32768K available (5412K kernel code, 167K rwdata, 776K rodata, 5383K init, 79K bss, 12096K reserved, 0K cma-reserved) |
| 293 | +[ 0.280000] devtmpfs: initialized |
| 294 | +[ 0.360000] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns |
| 295 | +[ 0.370000] posixtimers hash table entries: 1024 (order: 0, 8192 bytes, linear) |
| 296 | +[ 0.370000] futex hash table entries: 512 (16384 bytes on 1 NUMA nodes, total 16 KiB, linear). |
| 297 | +[ 0.440000] NET: Registered PF_NETLINK/PF_ROUTE protocol family |
| 298 | +[ 0.540000] pps_core: LinuxPPS API ver. 1 registered |
| 299 | +[ 0.540000] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it> |
| 300 | +[ 0.550000] PTP clock support registered |
| 301 | +[ 0.620000] clocksource: Switched to clocksource openrisc_timer |
| 302 | +[ 0.740000] NET: Registered PF_INET protocol family |
| 303 | +[ 0.760000] IP idents hash table entries: 2048 (order: 1, 16384 bytes, linear) |
| 304 | +[ 0.800000] tcp_listen_portaddr_hash hash table entries: 1024 (order: 0, 8192 bytes, linear) |
| 305 | +[ 0.810000] Table-perturb hash table entries: 65536 (order: 5, 262144 bytes, linear) |
| 306 | +[ 0.820000] TCP established hash table entries: 2048 (order: 0, 8192 bytes, linear) |
| 307 | +[ 0.830000] TCP bind hash table entries: 2048 (order: 2, 32768 bytes, linear) |
| 308 | +[ 0.840000] TCP: Hash tables configured (established 2048 bind 2048) |
| 309 | +[ 0.850000] UDP hash table entries: 256 (order: 1, 14336 bytes, linear) |
| 310 | +[ 0.860000] UDP-Lite hash table entries: 256 (order: 1, 14336 bytes, linear) |
| 311 | +[ 0.880000] NET: Registered PF_UNIX/PF_LOCAL protocol family |
| 312 | +[ 0.910000] RPC: Registered named UNIX socket transport module. |
| 313 | +[ 0.920000] RPC: Registered udp transport module. |
| 314 | +[ 0.920000] RPC: Registered tcp transport module. |
| 315 | +[ 0.930000] RPC: Registered tcp-with-tls transport module. |
| 316 | +[ 0.940000] RPC: Registered tcp NFSv4.1 backchannel transport module. |
| 317 | +[ 0.990000] workingset: timestamp_bits=30 max_order=12 bucket_order=0 |
| 318 | +[ 1.140000] ledtrig-cpu: registered to indicate activity on CPUs |
| 319 | +[ 1.170000] Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled |
| 320 | +[ 1.260000] printk: legacy console [ttyS0] disabled |
| 321 | +[ 1.280000] 90000000.serial: ttyS0 at MMIO 0x90000000 (irq = 2, base_baud = 3125000) is a 16550A |
| 322 | +[ 1.290000] printk: legacy console [ttyS0] enabled |
| 323 | +[ 1.290000] printk: legacy console [ttyS0] enabled |
| 324 | +[ 1.310000] printk: legacy bootconsole [ns16550a0] disabled |
| 325 | +[ 1.310000] printk: legacy bootconsole [ns16550a0] disabled |
| 326 | +[ 1.340000] -- dcache: 4096 bytes total, 32 bytes/line, 128 set(s), 1 way(s) |
| 327 | +[ 1.350000] -- icache: 4096 bytes total, 32 bytes/line, 128 set(s), 1 way(s) |
| 328 | +[ 1.370000] -- dcache: 4096 bytes total, 32 bytes/line, 128 set(s), 1 way(s) |
| 329 | +[ 1.380000] -- icache: 4096 bytes total, 32 bytes/line, 128 set(s), 1 way(s) |
| 330 | +[ 1.500000] NET: Registered PF_PACKET protocol family |
| 331 | +[ 1.830000] clk: Disabling unused clocks |
| 332 | +[ 4.840000] Freeing unused kernel image (initmem) memory: 5376K |
| 333 | +[ 4.840000] This architecture does not have kernel memory protection. |
| 334 | +[ 4.850000] Run /init as init process |
| 335 | +ifconfig: SIOCSIFADDR: No such device |
| 336 | +route: SIOCADDRT: Network unreachable |
| 337 | +
|
| 338 | +Please press Enter to activate this console. run-parts: /etc/network/if-pre-up.d: No such file or directory |
| 339 | +~ # uname -a |
| 340 | +Linux openrisc 7.0.0-rc1-de0nano-smp #2 SMP Thu Feb 26 21:37:09 GMT 2026 openrisc GNU/Linux |
| 341 | +~ # cat /proc/cpuinfo |
| 342 | +processor : 0 |
| 343 | +cpu architecture : OpenRISC 1000 (1.1-rev0) |
| 344 | +cpu implementation id : 0x1 |
| 345 | +cpu version : 0x50001 |
| 346 | +frequency : 50000000 |
| 347 | +immu : 64 entries, 1 ways |
| 348 | +dmmu : 64 entries, 1 ways |
| 349 | +bogomips : 100.00 |
| 350 | +features : orbis32 |
| 351 | +
|
| 352 | +processor : 1 |
| 353 | +cpu architecture : OpenRISC 1000 (1.1-rev0) |
| 354 | +cpu implementation id : 0x1 |
| 355 | +cpu version : 0x50001 |
| 356 | +frequency : 50000000 |
| 357 | +immu : 64 entries, 1 ways |
| 358 | +dmmu : 64 entries, 1 ways |
| 359 | +bogomips : 100.00 |
| 360 | +features : orbis32 |
| 361 | +``` |
| 362 | + |
| 363 | +## Further Reading |
| 364 | + |
| 365 | + - [de0_nano-multicore](https://github.com/stffrdhrn/de0_nano-multicore) - A De0 Nano Multicore OpenRISC SoC design |
| 366 | + - [Linux Releases](https://kernel.org) - Linux release tarballs |
| 367 | + - [OpenRISC toolchain Releases](https://github.com/stffrdhrn/or1k-toolchain-build/releases) - Toolchain point releases |
| 368 | + - [OpenRISC rootfs Releases](https://github.com/stffrdhrn/or1k-rootfs-build/releases) - Rootfs point release |
0 commit comments