/picorv32-imt

PicoRV32 - A Size-Optimized RISC-V CPU

Primary LanguageCoq

PicoRV32-imt - A Size-Optimized RISC-V CPU extended with IMT

PicoRV32-imt is a CPU core based on Claire Wolf's PicoRV32 and implementing IMT with up to six threads. To simplify the implementation of interleaved multithreading in this core, some features of the source have been dropped (for now). This core just implements the RISC-V RV32I Instruction Set. The interrupt controller of the original is still there but yet to be tested. This CPU has not yet successfully run on real hardware, but it works well in simulation.

Tools (gcc, binutils, etc..) can be obtained via the RISC-V Website. The examples bundled with PicoRV32-imt expect various RV32 toolchains to be installed in /opt/riscv32i. See the build instructions below for details.

PicoRV32-imt is free and open hardware licensed under the ISC license (a license that is similar in terms to the MIT license or the 2-clause BSD license).

Table of Contents

Files in this Repository

README.md

You are reading it right now.

picorv32-imt.v

This Verilog file contains the picorv32-imt Verilog. Simply copy this file into your project.

Makefile

Automates installing the RISC-V toolchain. See below.

benchmarks/

This contains benchmarks to test performance and ressource usage (size).

picoramsoc/

A simple example SoC using PicoRV32-imt that can execute code from RAM via a memory file.

scripts/

This contains helper scripts.

Verilog Module Parameters

The following Verilog module parameters can be used to configure the PicoRV32 core.

ENABLE_COUNTERS (default = 1)

This parameter enables support for the RDCYCLE[H], RDTIME[H], and RDINSTRET[H] instructions. This instructions will cause a hardware trap (like any other unsupported instruction) if ENABLE_COUNTERS is set to zero.

Note: Strictly speaking the RDCYCLE[H], RDTIME[H], and RDINSTRET[H] instructions are not optional for an RV32I core. But chances are they are not going to be missed after the application code has been debugged and profiled. This instructions are optional for an RV32E core.

This is untested.

ENABLE_COUNTERS64 (default = 1)

This parameter enables support for the RDCYCLEH, RDTIMEH, and RDINSTRETH instructions. If this parameter is set to 0, and ENABLE_COUNTERS is set to 1, then only the RDCYCLE, RDTIME, and RDINSTRET instructions are available.

This is untested.

LATCHED_MEM_RDATA (default = 0)

Set this to 1 if the mem_rdata is kept stable by the external circuit after a transaction. In the default configuration the PicoRV32 core only expects the mem_rdata input to be valid in the cycle with mem_valid && mem_ready and latches the value internally.

This is untested, but should work without much problems.

TWO_STAGE_SHIFT (default = 0)

By default shift operations are performed in two stages: first shifts in units of 4 bits and then shifts in units of 1 bit. This speeds up shift operations, but adds additional hardware. Set this parameter to 0 to disable the two-stage shift to further reduce the size of the core.

This is unsupported for now.

BARREL_SHIFTER (default = 1)

By default shift operations are performed by successively shifting by a small amount (see TWO_STAGE_SHIFT above). With this option set, a barrel shifter is used instead.

TWO_CYCLE_COMPARE (default = 0)

This relaxes the longest data path a bit by adding an additional FF stage at the cost of adding an additional clock cycle delay to the conditional branch instructions.

Note: Enabling this parameter will be most effective when retiming (aka "register balancing") is enabled in the synthesis flow.

This is unsupported for now.

TWO_CYCLE_ALU (default = 0)

This adds an additional FF stage in the ALU data path, improving timing at the cost of an additional clock cycle for all instructions that use the ALU.

Note: Enabling this parameter will be most effective when retiming (aka "register balancing") is enabled in the synthesis flow.

This is unsupported for now.

CATCH_MISALIGN (default = 1)

Set this to 0 to disable the circuitry for catching misaligned memory accesses.

This is unsupported for now.

CATCH_ILLINSN (default = 1)

Set this to 0 to disable the circuitry for catching illegal instructions.

The core will still trap on EBREAK instructions with this option set to 0. With IRQs enabled, an EBREAK normally triggers an IRQ 1. With this option set to 0, an EBREAK will trap the processor without triggering an interrupt.

ENABLE_IRQ (default = 0)

Set this to 1 to enable IRQs. (see "Custom Instructions for IRQ Handling" below for a discussion of IRQs)

This is unsupported for now.

ENABLE_IRQ_QREGS (default = 0)

Set this to 0 to disable support for the getq and setq instructions. Without the q-registers, the irq return address will be stored in x3 (gp) and the IRQ bitmask in x4 (tp), the global pointer and thread pointer registers according to the RISC-V ABI. Code generated from ordinary C code will not interact with those registers.

Support for q-registers is always disabled when ENABLE_IRQ is set to 0.

This is unsupported for now.

ENABLE_IRQ_TIMER (default = 0)

Set this to 0 to disable support for the timer instruction.

Support for the timer is always disabled when ENABLE_IRQ is set to 0.

This is unsupported for now.

ENABLE_TRACE (default = 0)

Produce an execution trace using the trace_valid and trace_data output ports. For a demontration of this feature run make test_vcd to create a trace file and then run python3 showtrace.py testbench.trace firmware/firmware.elf to decode it.

REGS_INIT_ZERO (default = 0)

Set this to 1 to initialize all registers to zero (using a Verilog initial block). This can be useful for simulation or formal verification.

This is unsupported for now.

MASKED_IRQ (default = 32'h 0000_0000)

A 1 bit in this bitmask corresponds to a permanently disabled IRQ.

This is unsupported for now.

LATCHED_IRQ (default = 32'h ffff_ffff)

A 1 bit in this bitmask indicates that the corresponding IRQ is "latched", i.e. when the IRQ line is high for only one cycle, the interrupt will be marked as pending and stay pending until the interrupt handler is called (aka "pulse interrupts" or "edge-triggered interrupts").

Set a bit in this bitmask to 0 to convert an interrupt line to operate as "level sensitive" interrupt.

This is unsupported for now.

PROGADDR_RESET (default = 32'h 0000_0000)

The start address of the program.

PROGADDR_IRQ (default = 32'h 0000_0010)

The start address of the interrupt handler.

This is unsupported for now.

STACKADDR (default = 32'h ffff_ffff)

When this parameter has a value different from 0xffffffff, then register x2 (the stack pointer) is initialized to this value on reset. (All other registers remain uninitialized.) Note that the RISC-V calling convention requires the stack pointer to be aligned on 16 bytes boundaries (4 bytes for the RV32I soft float calling convention).

PicoRV32 Native Memory Interface

The native memory interface of PicoRV32 is a simple valid-ready interface that can run one memory transfer and one instruction transfer at a time:

output        mem_valid
input         mem_ready

output [31:0] mem_addr
output [31:0] mem_wdata
output [ 3:0] mem_wstrb
input  [31:0] mem_rdata

output        mem_instr
output        instr_valid
input         instr_ready

output [31:0] instr_addr
input  [31:0] instr_rdata

The core initiates a memory transfer by asserting mem_valid. The valid signal stays high until the peer asserts mem_ready. All core outputs are stable over the mem_valid period. The instruction interface works the same way, except that the core asserts mem_instr.

Read Transfer

In a read transfer mem_wstrb has the value 0 and mem_wdata is unused.

The memory reads the address mem_addr and makes the read value available on mem_rdata in the cycle mem_ready is high.

There is no need for an external wait cycle. The memory read can be implemented asynchronously with mem_ready going high in the same cycle as mem_valid, or mem_ready being tied to constant 1.

Again, the instruction interface works the same way.

Write Transfer

In a write transfer mem_wstrb is not 0 and mem_rdata is unused. The memory write the data at mem_wdata to the address mem_addr and acknowledges the transfer by asserting mem_ready.

The 4 bits of mem_wstrb are write enables for the four bytes in the addressed word. Only the 8 values 0000, 1111, 1100, 0011, 1000, 0100, 0010, and 0001 are possible, i.e. no write, write 32 bits, write upper 16 bits, write lower 16, or write a single byte respectively.

There is no need for an external wait cycle. The memory can acknowledge the write immediately with mem_ready going high in the same cycle as mem_valid, or mem_ready being tied to constant 1.

Look-Ahead Interface

The PicoRV32 core also provides a "Look-Ahead Memory Interface" that provides all information about the next memory transfer one clock cycle earlier than the normal interface.

output        mem_la_read
output        mem_la_write
output [31:0] mem_la_addr
output [31:0] mem_la_wdata
output [ 3:0] mem_la_wstrb

In the clock cycle before mem_valid goes high, this interface will output a pulse on mem_la_read or mem_la_write to indicate the start of a read or write transaction in the next clock cycle.

Note: The signals mem_la_read, mem_la_write, and mem_la_addr are driven by combinatorial circuits within the PicoRV32 core. It might be harder to achieve timing closure with the look-ahead interface than with the normal memory interface described above.

Building a pure RV32I Toolchain

TL;DR: Run the following commands to build the complete toolchain:

make download-tools
make -j$(nproc) build-tools

The default settings in the riscv-tools build scripts will build a compiler, assembler and linker that can target any RISC-V ISA, but the libraries are built for RV32G and RV64G targets. Follow the instructions below to build a complete toolchain (including libraries) that target a pure RV32I CPU.

The following commands will build the RISC-V GNU toolchain and libraries for a pure RV32I target, and install it in /opt/riscv32i:

# Ubuntu packages needed:
sudo apt-get install autoconf automake autotools-dev curl libmpc-dev \
        libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo \
    gperf libtool patchutils bc zlib1g-dev git libexpat1-dev

sudo mkdir /opt/riscv32i
sudo chown $USER /opt/riscv32i

git clone https://github.com/riscv/riscv-gnu-toolchain riscv-gnu-toolchain-rv32i
cd riscv-gnu-toolchain-rv32i
git checkout 411d134
git submodule update --init --recursive

mkdir build; cd build
../configure --with-arch=rv32i --prefix=/opt/riscv32i
make -j$(nproc)

The commands will all be named using the prefix riscv32-unknown-elf-, which makes it easy to install them side-by-side with the regular riscv-tools (those are using the name prefix riscv64-unknown-elf- by default).

Alternatively you can simply use one of the following make targets from PicoRV32's Makefile to build a RV32I[M][C] toolchain. You still need to install all prerequisites, as described above. Then run any of the following commands in the PicoRV32 source directory:

Command Install Directory ISA
make -j$(nproc) build-riscv32i-tools /opt/riscv32i/ RV32I

Or simply run make -j$(nproc) build-tools to build and install all four tool chains.

By default calling any of those make targets will (re-)download the toolchain sources. Run make download-tools to download the sources to /var/cache/distfiles/ once in advance.

Note: These instructions are for git rev 411d134 (2018-02-14) of riscv-gnu-toolchain.