-
Notifications
You must be signed in to change notification settings - Fork 23
RISC V Co Pro Notes
- Introduction
- RISC-V Co Processor
- RISC-V Tube ROM
- RISC-V MOS API
- RISC-V Debugger
- RISC-V GNU Toolchain
- Building Sample Application
- RISC-V BBC Basic
The indigo release of PiTubeDirect (under development) includes an experimental RISC-V Co Processor.
This includes:
- the RISC-V Co Processor itself (Co Pro number 23, based on the Mini RV32ima emulator)
- the RISC-V Tube ROM, written in assembler, that provides a MOS API
- a low level RISC-V debugger (break points, single step disassembler, etc. accessed via the Pi serial port)
- a standard RISC-V GCC toolchain that allows C/assembler programs to be compiled and linked with newlib-nano
- a sample application (written in C) that calculates Pi
- a port of Richard Russell's BBC Basic that supports 32/64 bit integers and 64-bit floats
This page is an initial attempt at documentation for the above.
The RISC-V Co Processor has been assigned Co Pro number 23, so can be selected using:
*FX 151,230,23
<control break>
This RISC-V Co Processor uses the Mini RV32ima emulator, which emulates the rv32ima flavour of RISC-V. This includes the 32-bit integer core, plus the multiple/divide and atomic operation extensions.
There is currently no support for floating point at the instruction level, but there are two ways that floating point can be added:
- using a undefined instruction handler and a software emulation
- using a library, such as Newlib-nano that is included with the RISC-V GNU C toolchain
The rational for excluding floating point from the core is to allow the PiTubeDirect RISC-V Co Processor to be compatible with a future hardware RISC-V Co Processor implemented in the FPGA-based Matchbox Co Processor platform. This hardware is very resource limited, and hardware floating point is definitely out of scope. This decision could be revisited in the future.
At the moment, there is 16MB of RAM available to the RISC-V Co Processor. This is fixed, but could be increased if necessary.
The current memory map is:
&00000000-&00F7FFFF : User RAM
&00F80000-&00FBFFFF : Spare, can used for a language (256KB)
&00FC0000-&00FFFFDF : Tube ROM (256KB, 5K used)
&00FFFFE0-&00FFFFFF : Tube Registers
OSBYTE &83 returns the start of user RAM as &00000000.
OSBYTE &84 returns the end of user RAM as &00F80000.
The default stack is starts just below &00F80000.
The RISC-V Tube ROM provides:
- a full set of MOS APIs invoked using the RISC-V ecall instruction
- a RISC-V interrupt handler (handling the tube interrupt)
- a RISC-V exception handler (handling the dispatch of ecall instructions)
- a small number of built-in *commands
- tube data transfer routines, supporting all transfer modes
Much of this is derived from Jonathan Harston's various Tube ROMs, most notably the PDP11 Tube ROM.
The following commands are built in to the RISC-V Tube ROM
*HELP
*GO <address>
*TEST <hex>
*PI <decimal>
The MOS API makes use of the RISC-V ecall (environment call) instruction, with the following conventions:
- a0..a6 contain the API parameters
- a7 contains the API function number
- on exit, the a0 register typically contains the result
Here is an example of calling OSWRCH:
li a7, 0xAC0004
li a0, 'X'
ecall
The API function numbers are as follows:
OS_QUIT 0xAC0000
OS_CLI 0xAC0001
OS_BYTE 0xAC0002
OS_WORD 0xAC0003
OS_WRCH 0xAC0004
OS_NEWL 0xAC0005
OS_RDCH 0xAC0006
OS_FILE 0xAC0007
OS_ARGS 0xAC0008
OS_BGET 0xAC0009
OS_BPUT 0xAC000A
OS_GBPB 0xAC000B
OS_FIND 0xAC000C
OS_SYS_CTRL 0xAC000D
OS_HANDLERS 0xAC000E
OS_ERROR 0xAC000F
The API parameters and result registers are specific to the function. The API is based on JGH's PDP11 MOS API.
Here is a current API documentation extracted from the RISC-V Tube ROM source code:
# --------------------------------------------------------------
# MOS interface
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 0 - OSQUIT - Exit current program
# On entry:
# no parameters
# On exit:
# this call does not return
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 1 - OSCLI - Send command line to host
# --------------------------------------------------------------
# On entry:
# a0: pointer to command string, terminated by 0D
# On exit (for a command that runs on the host):
# t0-t3: undefined, all other registers preserved
# On exit (for a program that runs on the parasite):
# a0: program result
# t0-t3: undefined, all other registers as set by the program
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 2 - OSBYTE - Call MOS OSBYTE function
# --------------------------------------------------------------
# On entry:
# a0: OSBYTE A parameter (see AUG)
# a1: OSBYTE X parameter (see AUG)
# a2: OSBYTE Y parameter (see AUG)
# On exit:
# a0: preserved
# a1: OSBYTE X result
# a2: OSBYTE Y result (if a0 >= 0x80, otherwise preserved)
# a3: OSBYTE C result (if a0 >= 0x80, otherwise preserved)
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 3 - OSWORD - Call MOS OSWORD function
# --------------------------------------------------------------
# On entry:
# a0: OSWORD number (see AUG)
# a1: pointer to OSWORD block (see AUG)
# On exit:
# a0: preserved
# a1: preserved, OSWORD block updated with response data
# t0-t3: undefined, all other registers preserved
#
# In addition, for OSWORD 0:
# a2: response length, or -1 if input terminated by escape
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 4 - OSWRCH - Write character to output stream
# --------------------------------------------------------------
# On entry:
# a0: character to output
# On exit:
# a0: preserved
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 5 - OSNEWL - Write <NL><CR> to output stream
# --------------------------------------------------------------
# On entry:
# no parameters
# On exit:
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 6 - OSRDCH - Wait for character from input stream
# --------------------------------------------------------------
# On entry:
# no parameters
# On exit:
# a0: character read from input, or -1 if escape pressed
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 7 - OSFILE - Read/write a whole files or its attributes
# --------------------------------------------------------------
# On entry:
# a0: function (see AUG)
# a1: pointer to filename, terminated by zero
# a2: load address
# a3: exec address
# a4: start address
# a5: end address
# On exit:
# a0: result
# a1: preserved
# a2: updated with response data
# a3: updated with response data
# a4: updated with response data
# a5: updated with response data
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 8 - OSARGS - Read/write an open files's arguments
# --------------------------------------------------------------
# On entry:
# a0: function (see AUG)
# a1: file handle
# a2: 32-bit value to read/write (Note: NOT a block pointer)
# On exit:
# a0: preserved (except for a0=0 a1=0, where its the FS number)
# a1: preserved
# a2: 32-bit value to read/write (updated for a read operation)
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 9 - OSBGET - Get a byte from open file
# --------------------------------------------------------------
# On entry:
# a1: file handle
# On exit:
# a0: byte read (0..255), or -1 if EOF reached
# a1: preserved
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 10 - OSBPUT - Put a byte to an open file
# --------------------------------------------------------------
# On entry:
# a0: byte to write
# a1: file handle
# On exit:
# a0: preserved
# a1: preserved
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 11 - OSGBPB - Read or write multiple bytes
# --------------------------------------------------------------
# On entry:
# a0: function (see AUG)
# a1: file handle (8 bits)
# a2: start address of data (32 bits)
# a3: number of bytes to transfer (32 bits)
# a4: sequential file pointer (32 bits)
#
# On exit:
# a0: bits 0-7 are the 8-bit result, bit 31 indicates EOF
# a1: preserved
# a2: updated with response data
# a3: updated with response data
# a4: updated with response data
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 12 - OSFIND - Open or Close a file
# --------------------------------------------------------------
# On entry:
# a0: function (see AUG)
# a1: file handle (if close file), or
# pointer to filename terminated by 0D (if open file)
# On exit:
# a0=zero or handle
# t0-t3: undefined, all other registers preserved
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 13 - OS SYSTEM CONTROL - Miscellaneous
# --------------------------------------------------------------
# On entry:
# a0: function
# On exit:
# a0: result
# t0-t3: undefined, all other registers preserved
# Only one function is currently defined:
# a0 = &01: setup new program enviroment. This must be called by
# code that wants to become the current program instead of being
# transient code. The current program is re-entered at Soft Break.
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 14 - OS HANDLERS - Reads/writes environment handlers
# or EMT dispatch table entries.
# --------------------------------------------------------------
#
# a0 >= 0: Reads or writes ECALL dispatch address
#
# On entry:
# a0: ECALL number (0..15)
# a1: address of ECALL routine, or zero to read
# On exit:
# a0: preserved
# a1: previous ECALL dispatch address
#
# a0 < 0: Reads or writes environment handler:
#
# On entry:
# a0: Environment handler number
# a1: address of environment handler or zero to read
# a2: address of environment data block or zero to read
# On exit:
# a0: preserved
# a1: previous environment handler address
# a2: previous environment data address
#
# Environment handler numbers are:
# a0 a1 = handler a2 = data
# -1 Exit version
# -2 Escape Escape flag (one byte)
# -3 Error Error buffer (256 bytes)
# -4 Event unused
# -5 Unknown IRQ (used during data transfer)
# -6 Unknown ECALL Ecall dispatch table (64 bytes)
# -7 Uncaught EXCEPTION unused
#
# The Exit handler is entered with a0=return value.
#
# The Escape handler is entered with a0=new escape state in
# b6, must preserve all registers other than a0 and return
# with RET.
#
# The Error handler is entered with a0=>error block. Note that
# this may not be the address of the error buffer, the error
# buffer is used for dynamically generated error messages.
#
# The Event handler is entered with a0,a1,a2 holding the event
# parameters, must preserve all registers, and return with RET.
#
# The Unknown IRQ handler must preserve all registers, and
# return with RET.
#
# The Unknown CALL handler is entered a7=unknown ecall number
# and a0..a7 with the call parameters. It should return with RET.
#
# The Uncaught Exception handler must preserve all registers, and
# return with RET.
# --------------------------------------------------------------
# --------------------------------------------------------------
# ECall 15 - OS ERROR - invoke the current error handler
# --------------------------------------------------------------
# On entry:
# the error block should follow the ecall instruction
# On exit:
# this call does not return
# --------------------------------------------------------------
The RISC-V Debugger works the same as the other PiTubeDirect debuggers.
To use it, edit the config.txt file to select the debug kernel, and connect a Pi Serial cable to the serial port on the PiTubeDirect level shifter.
The Help command lists the available commands:
>> help
PiTubeDirect debugger
cpu = RISCV
base = 16
width = 8 bits (1 byte)
Commands:
info
help [ <command> ]
continue
step [ <num instructions> ]
next
regs [ <name> [ <value> ]]
traps
dis <start> [ <end> ]
fill <start> <end> <data>
crc <start> <end>
mem <start> [ <end> ]
rd <address>
wr <address> <data>
trace <interval>
clear <address> | <number>
list
breakx <address> [ <mask> ]
watchx <address> [ <mask> ]
breakr <address> [ <mask> ]
watchr <address> [ <mask> ]
breakw <address> [ <mask> ]
watchw <address> [ <mask> ]
base 8 | 16
width 8 | 16 | 32
To compile C programs to run on the RISC-V Coprocessor, you need to build the RISC-V GNU toolchain with some options specific to the RISC-V architecture used by PiTubeDirect:
Create a directory for the RISC-V GCC binaries:
sudo mkdir /opt/riscv
sudo chown $USER:$USER /opt/riscv
Clone the main RISC-V GNU repository:
git clone https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
Install the prerequisites needed by the build:
sudo apt-get install autoconf automake autotools-dev curl python3 python3-pip libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build git cmake libglib2.0-dev
(these are the dependencies for Ubuntu, see the README for other operating systems)
Configure the target toolchain to match PiTubeDirect's Co Processor:
./configure --target=riscv32-unknown-elf --prefix=/opt/riscv --with-arch=rv32ima --with-abi=ilp32
And run the build:
make
The build can take a very long time (hours on an older machine)
When it's complete, you should have the following binaries available:
$ ls -l /opt/riscv/bin/
total 493664
-rwxr-xr-x 1 dmb dmb 5677944 Jul 29 12:16 riscv32-unknown-elf-addr2line
-rwxr-xr-x 2 dmb dmb 5918048 Jul 29 12:16 riscv32-unknown-elf-ar
-rwxr-xr-x 2 dmb dmb 8469368 Jul 29 12:16 riscv32-unknown-elf-as
-rwxr-xr-x 2 dmb dmb 7014016 Jul 29 13:17 riscv32-unknown-elf-c++
-rwxr-xr-x 1 dmb dmb 5624176 Jul 29 12:16 riscv32-unknown-elf-c++filt
-rwxr-xr-x 1 dmb dmb 7007296 Jul 29 13:17 riscv32-unknown-elf-cpp
-rwxr-xr-x 1 dmb dmb 282992 Jul 29 12:16 riscv32-unknown-elf-elfedit
-rwxr-xr-x 2 dmb dmb 7014016 Jul 29 13:17 riscv32-unknown-elf-g++
-rwxr-xr-x 2 dmb dmb 6991176 Jul 29 13:17 riscv32-unknown-elf-gcc
-rwxr-xr-x 2 dmb dmb 6991176 Jul 29 13:17 riscv32-unknown-elf-gcc-12.2.0
-rwxr-xr-x 1 dmb dmb 167384 Jul 29 13:17 riscv32-unknown-elf-gcc-ar
-rwxr-xr-x 1 dmb dmb 167272 Jul 29 13:17 riscv32-unknown-elf-gcc-nm
-rwxr-xr-x 1 dmb dmb 167288 Jul 29 13:17 riscv32-unknown-elf-gcc-ranlib
-rwxr-xr-x 1 dmb dmb 4810480 Jul 29 13:17 riscv32-unknown-elf-gcov
-rwxr-xr-x 1 dmb dmb 3459136 Jul 29 13:17 riscv32-unknown-elf-gcov-dump
-rwxr-xr-x 1 dmb dmb 3727552 Jul 29 13:17 riscv32-unknown-elf-gcov-tool
-rwxr-xr-x 1 dmb dmb 151378264 Jul 29 13:39 riscv32-unknown-elf-gdb
-rwxr-xr-x 1 dmb dmb 4627 Jul 29 13:39 riscv32-unknown-elf-gdb-add-index
-rwxr-xr-x 1 dmb dmb 6373608 Jul 29 12:16 riscv32-unknown-elf-gprof
-rwxr-xr-x 4 dmb dmb 9062016 Jul 29 12:16 riscv32-unknown-elf-ld
-rwxr-xr-x 4 dmb dmb 9062016 Jul 29 12:16 riscv32-unknown-elf-ld.bfd
-rwxr-xr-x 1 dmb dmb 196518032 Jul 29 13:17 riscv32-unknown-elf-lto-dump
-rwxr-xr-x 2 dmb dmb 5746992 Jul 29 12:16 riscv32-unknown-elf-nm
-rwxr-xr-x 2 dmb dmb 6573960 Jul 29 12:16 riscv32-unknown-elf-objcopy
-rwxr-xr-x 2 dmb dmb 9496320 Jul 29 12:16 riscv32-unknown-elf-objdump
-rwxr-xr-x 2 dmb dmb 5918072 Jul 29 12:16 riscv32-unknown-elf-ranlib
-rwxr-xr-x 2 dmb dmb 4591992 Jul 29 12:16 riscv32-unknown-elf-readelf
-rwxr-xr-x 1 dmb dmb 9295384 Jul 29 13:39 riscv32-unknown-elf-run
-rwxr-xr-x 1 dmb dmb 5667760 Jul 29 12:16 riscv32-unknown-elf-size
-rwxr-xr-x 1 dmb dmb 5685432 Jul 29 12:16 riscv32-unknown-elf-strings
-rwxr-xr-x 2 dmb dmb 6573960 Jul 29 12:16 riscv32-unknown-elf-strip
To makes these commands available, add /opt/riscv/bin to your path:
export PATH=/opt/riscv/bin:$PATH
TODO:
An experimental port of Richard Russell's BBC Basic is available here: https://github.com/hoglet67/BBCSDL/releases
This disc image is bootable, and will load RVBASIC to correct address, then *GO to the start address. It also contains a few example programs: SPHERE, CLOCKSP and TSTEST. The latter is Tom Seddon's file system test suite, which needs to be run from ADFS.
The !BOOT script executes the following commands to start RVBASIC:
*LOAD RVBASIC F80000
*GO F80000
It's not possible to use *RUN with DFS-based filesystems, because they only provide 18 bits of addressing, and 24 bits are needed to load at F80000.
Richard Russell's BBC BASICs use a different line structure when storing BASIC programs in memory compared to Acorn's 6502 BBC BASICs.
The line structure with Richard Russell's BBC BASICs is:
<Line Length><Line Number LSB><Line Number MSB><0D>
...
<Line Length><Line Number LSB><Line Number MSB><0D>
<00> <FF> <FF>
The line structure with Acorn's BBC BASIC is:
<0D><Line Number MSB><Line Number LSB><Line Length>
...
<0D><Line Number MSB><Line Number LSB><Line Length>
<0D><FF?
See: https://beebwiki.mdfs.net/Program_format for more details.
In practice, this means that attempting to LOAD an Acorn 6502 BASIC program into RISC-V BASIC will result in a Bad Program error.
To work around this, you must first write the 6502 BASIC program out as a plain text file, using *SPOOL. On the 6502 BASIC:
LOAD "MYPROG"
*SPOOL MYPROGR
LIST
*SPOOL
Then on the RISCV BASIC:
*EXEC MYPROGR
In fact, you will find that LOAD also works with plain text files:
LOAD "MYPROGR"
The memory map when BBC Basic is running is:
&00000000-&0000FFFF : Basic String Buffer (64KB)
&00010000-&00010BFF : Basic Miscellaneous Buffers 3KB)
&00010C00 : default PAGE
&00780000 : default HIMEM (about half of available memory)
&00F00000 : maximum HIMEM
&00F00000-&00F3FFFF : C language heap (256KB, grows upwards)
&00F40000-&00F7FFFF : RISC-V stack (256KB, grows downwards)
&00F80000-&00FBFFFF : BBC Basic (256KB, 140KB used)
&00FC0000-&00FFFFDF : Tube ROM (256KB, 5K used)
&00FFFFE0-&00FFFFFF : Tube Registers
Here's the same info slightly more graphically (thanks to Charles, see #177):
RISC-V BBC Basic includes a build-in assembker.
For details on the the RISC-V instruction set, see the RISC-V Reference by James Zhu
The following instructions are available:
- all RV32I Base Integer instructions
- all RV32M Multiply Extension instructions
- all pseudo instructions
Here's a small example program that prints the characters 32 to 126 using OSWRCH:
>LIST
10 DIM code% 256
20 FOR I%=0 TO 2 STEP 2
30 P%=code%
40 [OPT I%
50 align
60 .test
70 li a0, 32
80 .loop
90 li a7, &AC0004
100 ecall
110 addi a0, a0, 1
120 li t0, 127
130 bne a0, t0, loop
140 ret
150 ]
160 NEXT
170 CALL test
180 PRINT
>RUN
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
To rebuild BBC Basic from source, there are two dependencies:
- the RISC-V GNU toolchain (see above)
- Stephen Harris's mmbutils (to build the .ssd disk image)
These tools need to be available on your PATH.
To build the disk image from source:
git clone https://github.com/hoglet67/BBCSDL.git
cd BBCSDL/console/riscv
make
This should result in:
riscv32-unknown-elf-as -I ../../include -march=rv32im_zicsr -mabi=ilp32 crt0.s -o crt0.o
riscv32-unknown-elf-gcc --specs=nano.specs -u _printf_float -Wall -I ../../include -march=rv32im_zicsr -mabi=ilp32 -c -Os ../../src/riscv_wrapper.c -o riscv_wrapper.o
riscv32-unknown-elf-gcc --specs=nano.specs -u _printf_float -Wall -I ../../include -march=rv32im_zicsr -mabi=ilp32 -c -Os ../../src/bbmain.c -o bbmain.o
riscv32-unknown-elf-gcc --specs=nano.specs -u _printf_float -Wall -I ../../include -march=rv32im_zicsr -mabi=ilp32 -c -Os ../../src/bbexec.c -o bbexec.o
riscv32-unknown-elf-gcc -Wno-array-bounds --specs=nano.specs -u _printf_float -Wall -I ../../include -march=rv32im_zicsr -mabi=ilp32 -c -Os ../../src/bbeval.c -o bbeval.o
riscv32-unknown-elf-as -I ../../include -march=rv32im_zicsr -mabi=ilp32 ../../../BBCSDL/src/bbdata_riscv.s -o bbdata.o
riscv32-unknown-elf-gcc --specs=nano.specs -u _printf_float -Wall -I ../../include -march=rv32im_zicsr -mabi=ilp32 -c -Os ../../src/bbasmb_riscv.c -o bbasmb.o
riscv32-unknown-elf-gcc crt0.o riscv_wrapper.o bbmain.o bbexec.o bbeval.o bbdata.o bbasmb.o --specs=nano.specs -u _printf_float -lm -nostartfiles -Tbbcbasic.ld -Os -o bbcbasic
/opt/riscv/lib/gcc/riscv32-unknown-elf/12.2.0/../../../../riscv32-unknown-elf/bin/ld: warning: bbcbasic has a LOAD segment with RWX permissions
riscv32-unknown-elf-objcopy -O binary bbcbasic RVBASIC
rm -f rvbasic.ssd
beeb blank_ssd rvbasic.ssd
Blank rvbasic.ssd created
beeb title rvbasic.ssd "RISC-V BASIC"
rvbasic.ssd updated
beeb putfile rvbasic.ssd RVBASIC
beeb putfile rvbasic.ssd examples/*
beeb opt4 rvbasic.ssd 3
beeb info rvbasic.ssd
Disk title: RISC-V BASIC (1) Disk size: &320 - 200K
Boot Option: 3 (EXEC) File count: 5
Filename: Lck Lo.add Ex.add Length Sct
$.TSTEST 000000 000000 0029D8 246
$.SPHERE 000000 000000 00014F 244
$.CLOCKSP 000000 000000 000BE0 238
$.!BOOT 000000 000000 000020 237
$.RVBASIC 000000 000000 0234CC 002
The result is a bootable single sided disk image (rvbasic.ssd)
Hardware
Software
- Build dependencies
- Running cmake
- Compiling kernel.img
- Deploying on a Pi
- Recommended config.txt and cmdline.txt options
- Validation
- Compilation flags
Implementation Notes