User Reference
The Quickstart is a good reference for how to use Sentinel from the source repo. These next three sections discuss usage outside of the source tree.
Use in Verilog Code
Pre-Generated Verilog
Each Codeberg Release contains a standalone generated Verilog file of Sentinel CPU. If you opt to download the Verilog from releases, you do not need Python installed to use Sentinel. You can automate downloading the Verilog with a script like this:
SENTINEL_VER=v0.1.0-beta.2
wget -O sentinel-v-$SENTINEL_VER.zip https://codeberg.org/cr1901/sentinel/releases/download/$SENTINEL_VER/sentinel-v-$SENTINEL_VER.zip
unzip sentinel-v-$SENTINEL_VER.zip
set SentinelVer v0.1.0-beta.2
wget -OutFile sentinel-v-$SentinelVer.zip https://codeberg.org/cr1901/sentinel/releases/download/$SentinelVer/sentinel-v-$SentinelVer.zip
Expand-Archive -Path sentinel-v-$SentinelVer.zip -DestinationPath .
Verilog From Arbitrary Commits
Generating Verilog of arbitrary commits requires Python >= 3.11, pdm, and
git.
If you’re using Sentinel with pdm as your top-level build system,
I suggest adding a pdm script
to provide a shortcut for Verilog generation in your pyproject.toml
(call = "python -m sentinel_cpu.gen" does not work!):
[tool.pdm.scripts]
gen = { call = "sentinel_cpu.gen:cli", help="generate Sentinel Verilog file" }
For all other use cases, I recommend using pdm from your top-level build
system, which manages the Sentinel checkout as a venv wrapper. Here is a
user-configurable script as an example:
SENTINEL_PATH=./sentinel
SENTINEL_REF=next
SENTINEL_V=sentinel_cpu.v
if ! [ -d $SENTINEL_PATH/.venv ]; then
git clone https://codeberg.org/cr1901/sentinel_cpu.git $SENTINEL_PATH
git -C $SENTINEL_PATH checkout $SENTINEL_REF
pdm install -p $SENTINEL_PATH -G yowasp
fi
pdm run -p $SENTINEL_PATH gen -o $SENTINEL_V
set SentinelPath ./sentinel
set SentinelRef next
set SentinelV sentinel_cpu.v
if (!(Test-Path $SentinelPath/.venv)){
git clone https://codeberg.org/cr1901/sentinel_cpu.git $SentinelPath
git -C $SentinelPath checkout $SentinelRef
pdm install -p $SentinelPath -G yowasp
}
pdm run -p $SentinelPath gen -o $SentinelV
Use In Amaranth Code
Right now, even from Python, Sentinel consists of rather few tunable knobs.
The only public Sentinel CPU module is the appropriately-named
Top.
Top is an interface object
whose Signature consists of a Wishbone
Classic bus and an Interrupt ReQuest (IRQ) line. All interface members are
synchronous to the sync clock domain.
Explicit clk and rst lines are generated for the sync domain in generated
Verilog code.
I expect most users to only need to import from sentinel_cpu.top to create
their SoC:
from amaranth import Elaboratable
from sentinel_cpu.top import Top
class MySoC(Elaboratable):
def __init__(self):
self.cpu = Top()
...
def elaborate(self, plat):
m = Module()
m.submodules.cpu = self.cpu
...
Since the Sentinel top-level is only a CPU, not a full computer system, the user must provide some sort of memory, and I/O to effectively run programs. One common way to do this is to connect Sentinel’s Wishbone bus to a Wishbone address decoder, behind which memory and I/O live.
See the AttoSoC class, and the corresponding
section in the Quickstart,
for a full working example.
Public API
The Sentinel RISC-V CPU Package.
There is no __all__; star imports are not presently supported. Users will
likely want to import or run one of the following modules directly:
from sentinel_cpu.top import Top
python -m sentinel_cpu.gen --help
Top-Level Module for Amaranth Components of Sentinel CPU.
- class sentinel_cpu.top.Top(*args, src_loc_at=0, **kwargs)
The Sentinel CPU Top-Level.
The Sentinel CPU top-level provides an interface to a Wishbone Classic bus and Interrupt ReQuest (IRQ) signal. Optionally, one may generate extra signals which are used for verifying core functionality using the RISC-V Formal Interface (RVFI).
Sentinel uses a single clock domain, called
syncby convention. On the first cycle after reset is de-asserted, Sentinel begins execution at address0.The Wishbone bus provides your typical address, data, and control lines for transferring data to and from the CPU. Sentinel only support Wishbone Classic Single Xfers. Specifically, when
CYCandSTBare both asserted1on a given clock cycle, the following holds:If
WEis asserted, Sentinel wants to write data over its bus to peripherals on itsDAT_Wlines. Any byte inDAT_WwhereSELis also asserted is valid data. Sentinel will wait forACKto be asserted.If
WEis not asserted, Sentinel wants to read data from a peripheral over itsDAT_Rlines. Peripherals should ensure that any byte inDAT_RwhereSELis also asserted is valid data before assertingACK.When
ACKis sent to Sentinel on a given clock cycle, Sentinel will deassertCYCandSTBon the next cycle, for at least one cycle.
At present (12/3/2024), I could not find good opportunities in the microcode program to try Block Xfers without potentially violating the spec.
The IRQ line triggers Machine External Interrupts when asserted (
1)- i.e. it is level-triggered. For any peripherals, devices, etc that can assert an IRQ, the user must provide software, hardware, or a combination to also deassert their IRQ logic upon acknowledgement from the CPU. The Machine Software and Machine Timer interrupt lines are not presently implemented.Todo
Seeing that it’s memory-mapped, there should probably be provisions for a user-supplied Machine Timer.
Sentinel directly reads the IRQ line in two related, but distinct, scenarios:
An instruction wants to read the Machine Interrupt Pending (
MIP) register. In this case, Sentinel will directly latch the value of the IRQ line intoMIP’s Machine External Interrupt Pending (MEIP) bit on the next positive edge of thesyncclock domain.The microcode is
queryingexceptions fromthe decoder. In this case, severalcontrol Componentsand theMCAUSEregister depend on the sampled value of the IRQ line on the next positive edge ofsync.
To ensure that all internal components of Sentinel see the same value of the IRQ line on a given clock cycle, a user must place their own
synchronizationlogic before the IRQ input if interrupt sources can be triggerred asynchronously tosync.See the CSRs section for more information on exception handling (including interrupts).
- Parameters:
formal (bool) –
The Wishbone bus and IRQ line alone don’t give enough information to properly use the RISC-V Formal Interface to verify core properties. If
True, Sentinel will gain an extrarvfisignatureMemberwith signals required to implement RVFI.As RVFI is meant for verification, the
rvfiMemberis not meant to be used in a synthesized design. It is best to leave this option disabled unless you are usingTopin conjunction withFormalTop.
- bus
Wishbone Classic Bus.
Connect your memory and I/O to this bus. The signature is of the form
Signature({ "adr": Out(30), "dat_w": Out(32), "dat_r": In(32), "sel": Out(4), "cyc": Out(1), "stb": Out(1), "we": Out(1), "ack": In(1), })
where each
Membercorresponds to the equivalently-named Wishbone Classic signal.- Type:
Out(Signature)
- irq
Interrupt Request Line.
External peripherals signal they need attention when this line is high.
- Type:
In(1)
- rvfi
Internal connections required for implementing the RISC-V Formal Interface.
The signature is of the form
Signature({ "exception": Out(1), "decode": Out(self.decode.rvfi.signature) })
where
- exception: Out(1)
Asserted if an exception occurred this cycle.
- decode: Out(Signature)
Forwarded RVFI signals from
sentinel_cpu.decode.Decode.
Todo
Right now, the formal harness tends to directly “reach” into
TopandComponentsto read the appropriate signals.I would prefer encapsulation via an explicity
rvfiportMember, but this process has been slow. I will wait to document until this interfaceMemberis more stable.- Type:
Out(Signature)
- alu
- Type:
- addr_align
- a_src
- Type:
- b_src
- Type:
- control
- datapath
- decode
- exception_router
- wdata_align
- elaborate(platform)
Verilog generation module/script for Sentinel.
This module can be run directly from the command-line as __main__:
python -m sentinel_cpu.gen --help
Individual functions are documented for completeness. Only cli() should
be treated as public (see Development Guidelines).
- sentinel_cpu.gen.cli()
Scripting entry point to generate Sentinel core.
This function examines
sys.argvand then generates a Verilog representation of Sentinel.Todo
Use sphinx-argparse to document arguments automatically.
This function can be called in a number of ways, all of which should be equivalent:
From within a wrapper Python script:
import sentinel_cpu.gen if __name__ == "__main__": sentinel_cpu.gen.cli()
Running the
genmodule (as__main__) from a shell script:python -m sentinel_cpu.gen --help
From within
pdm:[tool.pdm.scripts] gen = { call = "sentinel_cpu.gen:cli", help="generate Sentinel Verilog file" }