-
Notifications
You must be signed in to change notification settings - Fork 8
OpenRISC SoC usage basics
This page details how to use the mor1kx-dev-env environment to simulate an OpenRISC-based SoC containing the mor1kx processor.
You will need to be on a relatively recent Linux machine.
If you haven't already, ensure you have a set of tools and a copy of the source following the installation guide.
The OpenRISC project deals with architecture and implementation and tools development. Think of the architecture side as the instruction set manuals, the implementation side the model code (RTL, C, SystemC). The tools are things like the GNU compiler/debugger tool chain and chip debugger.
The synthesisable models we develop aren't much use (or fun) on their own. So we then integrate our synthesisable models into larger systems - a System on (a) Chip (SoC). When they are brought together with peripheral controllers (eg. I2C, SPI, Singlewire) and communications I/O (GPIO, UART, Ethernet, PCIE, etc.) and system infrastructure (memory, debug, interconnect) we then have a system which is capable of many things. Typically the "brains" of the system is the programmable CPU.
In the OpenRISC's case, these CPUs are relatively low-performance embedded-class processors. In an FPGA implementation we can run them up to 100MHz, and they execute up to a single instruction per cycle. However, in certain configurations they are capable of running full operating system kernels like Linux. They are more suited, however, to running embedded real-time operating systems (RTOS).
The OpenRISC 1000 architecture (OR1K or or1k) has a 32-bit instruction word and either 32 or 64-bit data. It it a reduced instruction set computer (RISC) meaning its instructions are relatively simple like:
add register 3 with register 6 and store in register 8
or
load the data at the memory address held in register 4 into register 5
In contrast, a more complex instruction set computer might be capable of doing much more in a single instruction word:
load the data at the memory address in register 2, increment it, compare with zero, and store back at the address held in register 3 while incrementing both registers 2 and 3
This should indicate something rather obvious, which is that the latter seems a lot harder to implement than the former. This means implementations of RISC computers require less logic, and the idea is that the complexity is offloaded onto the software compiler.
The OpenRISC project is lucky enough to have a good quality GNU tool chain port, and an LLVM port also exists. This allows us to compile C and C++ to OpenRISC 1000 machine code and execute it on our models. We also have software libraries in newlib (for baremetal) and uClibc (for Linux userspace, an EGLIBC port is in the works I believe), and the GNU debugger (GDB) which understands the OR1K architecture.
This guide will demonstrate how to get an OpenRISC system up and running. It will go over how to launch and see what happened during simulations.
The mor1kx core is the CPU which the mor1kx-dev-env is set up to base systems around.
This core is a relatively new OR1K processor implementation, started from scratch less than 2 years ago. It has a choice of 3 major variants, based on the pipeline architecture. They are the 3-stage pipeline espresso core, the 3-stage delay-slot-free pronto espresso core, and the 5-stage cappuccino core which can optionally have MMUs and caches, making it capable and powerful enough to run Linux.
It's development environment, the mor1kx-dev-env is a branch of the main OpenRISC SoC project, ORPSoC. This environment contains the test beds for the various mor1kx processor variants, and also build systems allowing us to synthesise the core for a variety of different FPGA boards.
We can simulate the RTL of the SoC with Icarus and view its internal signals.
We will run a technology-generic SoC build of the espresso mor1kx core.
cd boards/generic/mor1kx-espresso/sim/run
make rtl-test VCD=1
This will run a simulation, and the VCD=1 option will create a file known as a value change dump which records the status of every signal in the design for viewing afterwards.
If everything went OK you should see:
- Starting simulation of ORPSoC RTL.
- Test: or1k-basic
- VCD in ../out/or1k-basic.vcd
VCD info: dumpfile ../out/or1k-basic.vcd opened for output.
report(0x8000000d);
exit(0x00000000);
The report() functions lines are software indicating status. In this case it has sent the value 0x8000000d, or a hexadecimal pseudo-representation of GOOD. The exit code is 0 meaning no errors.
The software run by the processor in this test is in mor1kx-dev-env/sw/tests/or1k/sim/or1k-basic.S. It a test of a lot of basic instructions such as flag setting, jumping and arithmetic.
The results of the test are in the directory called out/ parallel to the run/ directory.
A trace of the processor's execution is created for each test, too. Inspect it with the following command
less ../out/or1k-basic-trace.log
The line has the format of an 'S' or 'U' to indicate whether the processor is in supervisor or user mode, the program counter (32-bits), the instruction for that PC (32-bits) the disassembly of the instruction, the result if any, and the status of the flag bit.
This pipeline has a delay slot which means that each jump or branch instruction will have its following instruction also executed before the branch is taken. The mor1kx's pronto espresso core does not use this delay slot behaviour.
We can load the VCD of the run with the following:
gtkwave ../out/or1k-basic.vcd
Once it has loaded we can navigate the hierarchy of the design. Expand the orpsoc_testbench and select the dut or design under test. Type in i_or12 to the Filter box and select those that come up and click Insert to add them to the waveform.
Use the plus and minus buttons to zoom in and out - at the beginning of a simulation signals are usually X which means undefined. They will not take a value until their reset condition has been met (an edge of the reset signal).
Here we can see the system being taken out of reset (wb_rst going from 1 to 0) and the processor starting to fetch from its reset address 0x100.
The bus interface (Wishbone bus protocol) is bursting in instructions from the memory, and we can see the first instruction request being acknowledged, or acked, by the bus slave and the address begins to change for the next instruction.
Zooming out further shows the behaviour of the bus over a greater a mount of time, indicating just how much is going on in a simple test.
We can run arbitrary tests by specifying TEST=<testname> at runtime. For example, we can run the simplest test, or1k-simple like so:
make rtl-test TEST=or1k-simple VCD=1
The software run by the processor in this test is in mor1kx-dev-env/sw/tests/or1k/sim/or1k-simple.c. It is just a C main() function. The C-runtime setup code is in mor1kx-dev-env/sw/drivers/mor1kx/crt0.S, an OR1K assembly file.
We can modify this file to printf something to the screen during simulation.
Open up the mor1kx-dev-env/sw/tests/or1k/sim/or1k-simple.c file and add the following:
#include "printf.h"- In the main loop do:
- printf("Hello World from the OpenRISC!\n");
The code should look something like this:
#include "cpu-utils.h"
#include "printf.h"
int main()
{
printf("Hello World from the OpenRISC!\n");
report(0x8000000d);
return 0;
}
Now re-run the test with:
make rtl-test TEST=or1k-simple
And we will see the hello world!
Note: The mechanism to get the characters to the console is not a simulated UART in this case, rather a method of signalling the characters to print to the simulator and the testbench code then simply prints the character using the Verilog $print() function.
We can now simulate a hello world over the UART. Add the following to or1k-simple.c:
-
#include "uart.h"and#include "board.h"before the printf include - call
uart_init(0);as the first thing inmain()
eg.:
#include "cpu-utils.h"
#include "board.h"
#include "uart.h"
#include "printf.h"
int main()
{
uart_init(0);
printf("Hello World from the OpenRISC via UART!\n");
report(0x8000000d);
return 0;
}
Run the test again:
make rtl-test TEST=or1k-simple
It will take a lot longer, as it's simulating the UART transmitting at 115200 baud.
Running and generating the VCD:
make rtl-test TEST=or1k-simple VCD=1
We can then look at the UART lines, and the processor's data accesses to the UART core over the bus.
This may take a little while to run on slower PCs
We can see the UART TX line (uart0_stx_pad_o) and the Wishbone interface to the UART peripheral - a lot of accesses are going on as the processor is polling the core to determine when the last byte has been transmitted.
We can, instead, run an example of the UART transmitting with interrupts. Run the software which already exists to demonstrate this:
make rtl-test TEST=uart-txinterrupt VCD=1
The software for this test is in mor1kx-dev-env/sw/tests/uart/sim/uart-txinterrupt.c.
Inspecting the VCD we get:
We can see much less activity on the UART's bus interface (the ack signal is no longer thrashing), and the IRQ line is now visible indicating when it finishes each character.
At this stage you might like to attempt to build the SoC and run it on an FPGA.
See this guide for building and running the system on the DE0 Nano.


