2024-02-26 01:02:48 -05:00
|
|
|
Copyright 2023-2024 (C) Peter McGoron.
|
|
|
|
|
|
|
|
This file is a part of Upsilon, a free and open source software project.
|
|
|
|
For license terms, refer to the files in `doc/copying` in the Upsilon
|
|
|
|
source distribution.
|
|
|
|
|
|
|
|
*******************************
|
|
|
|
|
|
|
|
=============
|
|
|
|
Preqreuisites
|
|
|
|
=============
|
|
|
|
|
|
|
|
You must know basic Linux shell (change directories, edit files with `vi`)
|
|
|
|
and basic SSH usage (sftp, ssh).
|
|
|
|
|
|
|
|
I assume you know Python.
|
|
|
|
|
|
|
|
===========
|
|
|
|
Micropython
|
|
|
|
===========
|
|
|
|
|
|
|
|
MicroPython is a programming language that is very similar to Python. It is
|
|
|
|
stripped down and designed to run on very small devices. If you have written
|
|
|
|
Python, you will be able to use MicroPython without issue. If you are not
|
|
|
|
a hardcore Python programmer, you might not even notice a difference.
|
|
|
|
|
|
|
|
Everything you need to know is here <https://docs.micropython.org>.
|
|
|
|
|
|
|
|
-------------
|
|
|
|
Memory Access
|
|
|
|
-------------
|
|
|
|
|
|
|
|
The ``machine`` module contains arrays called ``mem8``, ``mem16``, and ``mem32``.
|
|
|
|
They are used to directly access memory locations on the main CPU bus. Note
|
|
|
|
that ``mem32`` accesses must be word aligned.
|
|
|
|
|
2024-02-28 08:28:06 -05:00
|
|
|
Example::
|
|
|
|
|
|
|
|
import machine
|
|
|
|
from mmio import *
|
|
|
|
machine.mem32[pico0_dbg_reg]
|
|
|
|
|
|
|
|
This reads the first register from ``pico0_dbg_reg``.
|
|
|
|
|
2024-02-26 22:48:22 -05:00
|
|
|
-------------------
|
|
|
|
Accessing Registers
|
|
|
|
-------------------
|
2024-02-26 01:02:48 -05:00
|
|
|
|
2024-02-28 08:28:06 -05:00
|
|
|
At the lowest level, a program will write to and read from "registers" which
|
|
|
|
are mapped to memory. These registers control the operations of various parts
|
|
|
|
of the system.
|
2024-02-26 22:48:22 -05:00
|
|
|
|
|
|
|
The main bus has two register buses: "CSR" (which is the LiteX default), and
|
|
|
|
custom Wishbone code. CSR register information is in the ``csr.json`` file.
|
|
|
|
Wishbone bus registers are allocated with regions that are specified in
|
|
|
|
``csr.json``, while the actual registers inside that region are located in
|
|
|
|
``soc_subregions.json``. These should be automatically dumped to the Micropython
|
|
|
|
file ``mmio.py`` for easy usage.
|
|
|
|
|
2024-02-28 08:28:06 -05:00
|
|
|
``csr.json`` is not that well documented and can change from version to version
|
|
|
|
of LiteX.
|
|
|
|
|
|
|
|
``soc_subregions.json`` is a JSON object where the keys denote ``memories`` in
|
|
|
|
``csr.json``. If the object of that key is ``null``, then that region is
|
|
|
|
uniform (e.g. it is RAM, which is one continuous block). The objects of each of
|
|
|
|
these are registers that reside in the memory region. The keys of the registers
|
|
|
|
are
|
|
|
|
|
|
|
|
1. ``origin``: offset of the register from the beginning of the memory.
|
|
|
|
2. ``size``: Size of the register in bytes. Right now this is always ``4``,
|
|
|
|
even if the writable space is smaller. Always access with words.
|
|
|
|
3. ``rw``: True if writable and False if not. Sometimes this is not there
|
|
|
|
because the writable might be dynamic or must be inferred from other
|
|
|
|
properties.
|
|
|
|
4. ``direction``: Specifcally for registers that are Main-write-Pico-read
|
|
|
|
or Main-read-Pico-write. ``PR`` denotes Pico-read, ``PW`` denotes Pico-write.
|
|
|
|
|
|
|
|
``pico0.json`` (and other PicoRV32 JSON files) are JSON objects whose keys are
|
|
|
|
memory regions. Their values are objects with keys:
|
|
|
|
|
|
|
|
1. ``origin``: Absolute position of the memory region.
|
|
|
|
2. ``width``: Width of the memory region in bytes.
|
|
|
|
3. ``registers``: Either ``null`` (uniform region, like above), or an object
|
|
|
|
whose keys are the names of registers in the region. The values of these
|
|
|
|
keys have the same interpretation as ``soc_subregions.json`` above.
|
|
|
|
|
2024-02-26 22:48:22 -05:00
|
|
|
====================
|
|
|
|
System Within a Chip
|
|
|
|
====================
|
|
|
|
|
|
|
|
Systems Within a Chip (**SWiCs**) are CPUs that are controlled by the main CPU
|
|
|
|
but run seperately (they have their own registers, RAM, etc.) They can be
|
|
|
|
programmed and controlled through Micropython.
|
|
|
|
|
|
|
|
The SWiC is a RV32IMC core. Code for the SWiC needs to be compiled for a start
|
|
|
|
address of ``0x10000`` and a IRQ handler at ``0x10010``. The default length of
|
2024-02-28 08:28:06 -05:00
|
|
|
|
2024-02-26 22:48:22 -05:00
|
|
|
the SWiC region is ``0x1000`` bytes.
|
|
|
|
|
|
|
|
Each core is given the name ``pico0``, ``pico1``, etc. The regions of each CPU
|
|
|
|
are stored in ``pico0.json``, ``pico1.json``, etc. The system used to control
|
|
|
|
slave access to the CPU bus is a CSR (and should be in ``mmio.py``).
|
|
|
|
|
2024-02-28 08:28:06 -05:00
|
|
|
----------------------------
|
|
|
|
Compiling and Executing Code
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
There is a Makefile in /swic/ that contains the commands to compile a source
|
|
|
|
file (with start function ``_start``) to a binary file without static variables
|
|
|
|
or RO data.
|
|
|
|
|
|
|
|
Each CPU has a header file (for example ``pico0_mmio.h`` that contains the
|
|
|
|
offsets where each word-sized register can be accessed.
|
|
|
|
|
|
|
|
If there is only program code (no RODATA, static variables, etc.) then you can
|
|
|
|
dump the ``.text`` section using objdump (this requires a RISC-V compiler
|
|
|
|
installed, 64 bit is fine). Afterwards the data can be loaded by writing each
|
|
|
|
byte into the RAM section (the start of the ram section in the main CPU
|
|
|
|
corresponds to ``0x10000`` on the SWiC).
|
|
|
|
|
|
|
|
More advanced options would require more advanced linker script knowledge.
|
|
|
|
|
|
|
|
----------------
|
|
|
|
Complete Example
|
|
|
|
----------------
|
|
|
|
|
|
|
|
The compiler can be accessed in the docker container, you can also install it
|
|
|
|
under Ubuntu.
|
|
|
|
|
|
|
|
I haven't tested this yet, but this is how the code should work::
|
|
|
|
|
|
|
|
#include "pico0_mmio.h"
|
|
|
|
|
|
|
|
void _start(void) {
|
|
|
|
uint32_t i = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
*DAC0_TO_SLAVE = i;
|
|
|
|
*DAC0_ARM = 1;
|
|
|
|
while (*!DAC_FINISHED_OR_READY);
|
|
|
|
|
|
|
|
i += *DAC_FROM_SLAVE;
|
|
|
|
*DAC0_ARM = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
This code does reads and writes to registers defined in ``pico0_mmio.h``.
|
|
|
|
This file in this example is saved as ``test.c``.
|
|
|
|
|
|
|
|
To compile it use::
|
|
|
|
|
|
|
|
riscv64-unknown-elf-gcc \
|
|
|
|
-march=rv32imc \
|
|
|
|
-mabi=ilp32 \
|
|
|
|
-ffreestanding \
|
|
|
|
-nostdlib \
|
|
|
|
-Os \
|
|
|
|
-Wl,-build-id=none,-Bstatic,-T,riscv.ld,--strip-debug \
|
|
|
|
-nostartfiles \
|
|
|
|
-lgcc \
|
|
|
|
test.c -o test.elf
|
|
|
|
|
|
|
|
In order:
|
|
|
|
|
|
|
|
1. ``-march=rv32imc`` compiles for RISC-V, 32 bit registers, multiplication,
|
|
|
|
and compressed instructions.
|
|
|
|
2. ``-mabi=ilp32`` compiles for the 32 bit ABI without floating pint.
|
|
|
|
3. ``-ffreestanding`` compiles as "Freestanding C" <https://en.cppreference.com/w/c/language/conformance>.
|
|
|
|
4. ``-Os`` means "optimize for size."
|
|
|
|
5. ``-Wl`` introduces linker commands, I don't know how the linker works.
|
|
|
|
6. ``-nostartfiles`` does not include the default ``_start`` in the binary.
|
|
|
|
7. ``-lgcc`` links the base GCC library, which is used for builtins (I think).
|
|
|
|
8. ``test.c -o test.elf`` compiles the C file and outputs it to ``test.elf``.
|
|
|
|
|
|
|
|
The resulting ELF can be inspected using ``riscv64-unknown-elf-objdump`` (look up
|
|
|
|
the instructions). To copy the machine code to ``test.bin``, execute::
|
|
|
|
|
|
|
|
riscv64-unknown-elf-objcopy -O binary -j .text test.elf test.bin
|
|
|
|
|
|
|
|
This code can now be loaded into the PicoRV32's ram. The next code sections
|
|
|
|
will be written in Micropython.
|
|
|
|
|
|
|
|
First include the Micropython MMIO locations::
|
|
|
|
|
|
|
|
from mmio import *
|
|
|
|
|
|
|
|
Next import ``machine``. You will almost always need this library.::
|
|
|
|
|
|
|
|
import machine
|
|
|
|
|
|
|
|
Next import the PicoRV32 standard library. (Currently it is hard-coded for pico0,
|
|
|
|
this might change if more CPUs are required.)::
|
|
|
|
|
|
|
|
import picorv32
|
|
|
|
|
|
|
|
Next load the code into Micropython::
|
|
|
|
|
|
|
|
with open('test.bin', 'rb') as f:
|
|
|
|
prog = f.read()
|
|
|
|
|
|
|
|
This assumes that ``test.bin`` has been uploaded to the FPGA. We now turn off
|
|
|
|
the CPU and switch control of the RAM to the main CPU::
|
|
|
|
|
|
|
|
machine.mem32[pico0_enable] = 0
|
|
|
|
machine.mem32[pico0ram_iface_master_select] = 0
|
|
|
|
|
|
|
|
Both of these values default to ``0`` on startup.
|
|
|
|
|
|
|
|
Now the program can be loaded into the PicoRV32's ram::
|
|
|
|
|
|
|
|
assert len(prog) < 0xFFF
|
|
|
|
for offset, b in enumerate(prog, start=pico0_ram):
|
|
|
|
machine.mem8[offset] = b
|
|
|
|
|
|
|
|
The first line of the code makes sure that the code will overflow from the
|
|
|
|
RAM region and write other parts of memory. (TODO: this is hardcoded, and
|
|
|
|
the size of the region should also be written to memory.)
|
|
|
|
|
|
|
|
The loop goes though each byte of the program and writes it to the RAM,
|
|
|
|
starting at the beginning of the RAM. ``enumerate`` is just a fancy Python way
|
|
|
|
of a for loop with an increasing counter.
|
|
|
|
|
|
|
|
As a sanity test, check that the program was written correctly::
|
|
|
|
|
|
|
|
for i in range(len(prog)):
|
|
|
|
assert machine.mem8[pico_ram + i] == prog[i]
|
|
|
|
|
|
|
|
This can detect overwrite errors (a write to a read-only area will silently
|
|
|
|
fail) and cache mismatch errors.
|
|
|
|
|
|
|
|
After the program is loaded, the CPU can finally be started::
|
|
|
|
|
|
|
|
machine.mem32[pico0ram_iface_master_select] = 1
|
|
|
|
assert machine.mem8[pico0_ram] == 0
|
|
|
|
machine.mem32[pico0_enable] = 1
|
|
|
|
|
|
|
|
The first line switches control of the RAM to the PicoRV32. The second line
|
|
|
|
checks if the switch worked. If this line fails, most likely the preemptive
|
|
|
|
interface is not properly connected to the PicoRV32 (or my code is buggy).
|
|
|
|
The final line starts the CPU.
|
|
|
|
|
|
|
|
The state of the CPU can be inspected using ``picorv32.dump()``. This will
|
|
|
|
tell you if the CPU is in a trap state and what registers the CPU is currently
|
|
|
|
reading.
|
|
|
|
|
2024-02-26 22:48:22 -05:00
|
|
|
================
|
2024-02-26 01:02:48 -05:00
|
|
|
Computer Control
|
2024-02-26 22:48:22 -05:00
|
|
|
================
|
2024-02-26 01:02:48 -05:00
|
|
|
|
|
|
|
Micropython code can be loaded manually with SSH but this gets cumbersome.
|
|
|
|
Python scripts on the controlling computer connected to the Upsilon FPGA can
|
|
|
|
upload, execute, and read data back from the FPGA automatically. The code that
|
2024-02-28 08:28:06 -05:00
|
|
|
does this is in /client/ . They don't work right now and need to be updated.
|
2024-02-26 01:02:48 -05:00
|
|
|
|
|
|
|
===
|
|
|
|
FAQ
|
|
|
|
===
|
|
|
|
|
|
|
|
------------------
|
|
|
|
SCP Is Not Working
|
|
|
|
------------------
|
|
|
|
|
|
|
|
SCP by default uses SFTP, which dropbear does not support. Pass `-O` to all
|
|
|
|
SCP invocations to use the legacy SCP protocol.
|