MRVE32 Python API
The mrve32 package runs RISC-V firmware from Python. The emulator core
(an RV32IMAC microcontroller: CPU, bus, devices, ELF loader) is Rust;
Python drives it and can add devices and custom instructions. Typical uses
are testbenches for firmware, behavioural models of peripherals, and
prototyping hardware accelerators before writing RTL.
This page is the Python reference. The emulator itself, its command-line runner, the Rust API and how it is verified are described in the repository.
pip install mrve32 # the emulator
pip install mrve32-snn # optional: the SNN accelerator device
Wheels exist for Linux (x86_64, aarch64), macOS (x86_64, arm64) and
Windows (x64), CPython 3.10 and newer. Elsewhere pip builds from source
and needs a Rust toolchain. The package ships type stubs (py.typed), so
editors and mypy see every signature below.
Contents:
- Quick start
- Concepts: memory map, steps and cycles, traps, why a run stops
Emulator: construction, devices, execution, loading, memory, CPU stateRunResultandTrap- Built-in devices:
Ram,Clint,Uart,TestFinisher - Devices in Python
- Custom instructions (coprocessors)
- Debugging: breakpoints, tracing, disassembly
- Errors
mrve32-snn
Quick start
A complete program, without a cross-compiler: four instructions that tell the test finisher "pass".
import mrve32
emu = mrve32.Emulator() # 64 KiB RAM at 0x8000_0000, CLINT
emu.map_device(0x0010_0000, mrve32.TestFinisher())
emu.load_program(0x8000_0000, [
0x001002B7, # lui t0, 0x100 t0 = 0x0010_0000 (the finisher)
0x00005337, # lui t1, 0x5
0x55530313, # addi t1, t1, 0x555 t1 = 0x5555 ("pass")
0x0062A023, # sw t1, 0(t0)
])
res = emu.run(1000)
print(res.reason, res.exit_code, res.steps) # exit 0 4
print(hex(emu.read_reg(6))) # 0x5555 (t1 = x6)
With real firmware, build an ELF (for example with
-march=rv32imac_zicsr -mabi=ilp32 -nostdlib, linked at 0x8000_0000)
and load it instead:
emu = mrve32.Emulator(ram_size=1 << 20)
uart = mrve32.Uart()
emu.map_device(0x1000_0000, uart) # same map as QEMU virt
emu.map_device(0x0010_0000, mrve32.TestFinisher())
emu.load_elf("firmware.elf") # sets pc to the entry point
res = emu.run(10_000_000)
print(res.reason, res.exit_code)
print(uart.output().decode()) # what the firmware printed
Concepts
Memory map
An Emulator starts with RAM and a CLINT; every other device is added with
map_device. The defaults follow QEMU's virt machine, so
firmware written for it runs unchanged:
| Device | Default base | Size | How to get it |
|---|---|---|---|
| RAM | 0x8000_0000 |
64 KiB | Emulator(ram_base=..., ram_size=...) |
| CLINT (timer, software interrupt) | 0x0200_0000 |
64 KiB | Emulator(clint_base=...), None to omit |
| UART (NS16550A) | 0x1000_0000 |
256 B | map_device(0x1000_0000, Uart()) |
| Test finisher | 0x0010_0000 |
4 KiB | map_device(0x0010_0000, TestFinisher()) |
Device windows must not overlap. An access to an address no device covers
is an access fault for the CPU and a BusError for Python.
Steps, cycles and instructions
One step is one clock cycle. Every instruction takes one cycle, so a
step normally executes one instruction. While the hart sleeps in wfi, a
step executes nothing but still advances time: devices tick, mcycle
counts, minstret does not.
emu.cyclesismcycle,emu.instretisminstret(both 64-bit).RunResult.stepsis the number of cycles arun()call executed.- The CLINT's
mtimeadvances once per cycle (or everydividercycles, seeClint).
Traps
The emulated hart is an RV32IMAC machine-mode core. Exceptions and
interrupts behave as on hardware: mepc, mcause, mtval and mstatus
are updated and pc jumps to mtvec. step() and run() report the trap
after it was taken, so emu.pc already points at the handler.
mtvec is 0 after reset, and nothing is mapped at address 0. Firmware
that can trap must set mtvec first, or the first trap jumps to 0 and
faults again (an instruction access fault, mcause 1) forever. In a quick
experiment without a handler, use run(..., stop_on_trap=True) to stop at
the first trap instead.
Common mcause values:
mcause |
Meaning | mtval |
|---|---|---|
| 0 | Instruction address misaligned | target address |
| 1 | Instruction access fault | address |
| 2 | Illegal instruction | the instruction |
| 3 | Breakpoint (ebreak) |
pc |
| 4 / 6 | Load / store address misaligned (atomics only) | address |
| 5 / 7 | Load / store access fault | address |
| 11 | ecall from M-mode |
0 |
0x8000_0003 |
Machine software interrupt (CLINT msip) |
0 |
0x8000_0007 |
Machine timer interrupt (CLINT mtimecmp) |
0 |
0x8000_000B |
Machine external interrupt (devices) | 0 |
Misaligned ordinary loads and stores to RAM do not trap. They are performed in hardware.
Why a run stops
run(max_steps) returns a RunResult whose reason is one
of:
reason |
What happened |
|---|---|
"exit" |
A device asked to stop, e.g. the firmware wrote to the TestFinisher. exit_code holds the status. |
"max_steps" |
max_steps cycles ran. Call run() again to continue. |
"breakpoint" |
pc reached a breakpoint. The instruction there has not executed yet. |
"trap" |
A trap was taken and stop_on_trap=True. trap holds it. |
"idle" |
The hart is in wfi and no enabled interrupt can ever wake it. |
Emulator
Emulator(
ram_base: int = 0x8000_0000,
ram_size: int = 64 * 1024,
*,
clint_base: int | None = 0x0200_0000,
compressed: bool = True,
)
A machine: one RV32IMAC hart, a bus with RAM and (unless clint_base is
None) a CLINT. pc starts at ram_base. With compressed=False the core
is RV32IMA: the C extension is off (misa.C = 0), and a jump to an address
that is only 2-byte aligned raises an instruction-address-misaligned trap.
An Emulator belongs to the thread that created it.
| Attribute | |
|---|---|
ram_base: int |
read-only |
clint_base: int | None |
read-only |
compressed: bool |
read/write; the same switch as the constructor argument |
repr(emu) shows pc and the devices:
Emulator(pc=0x80000000, devices=[Clint@0x02000000, Ram@0x80000000]).
Devices
map_device(base: int, device, *, size: int | None = None) -> None
Maps a device with its window starting at base. device is either
- a native device module: an object with a
__mrve32_device__()method, such as the built-in devices ormrve32-snn'sSnnAccelerator. Its window size is fixed, sosizemust not be given; - or a Python object with
readandwritemethods. Its window issizebytes or, ifsizeis not given,device.size.
Raises ValueError if the window overlaps another device or does not fit
below 4 GiB, TypeError if device is neither kind.
devices: list[tuple[int, int, str]]
(base, size, type name) of every mapped device, sorted by address.
set_coprocessor and set_tracer are described under
Custom instructions and
Tracing.
Execution
step() -> Trap | None
Executes one cycle. Returns the trap taken in this cycle, if any.
run(max_steps: int, *, stop_on_trap: bool = False) -> RunResult
Runs up to max_steps cycles. Stops earlier on a device exit request, a
breakpoint, a trap (with stop_on_trap) or when the hart is idle forever;
see why a run stops. Without stop_on_trap, traps are
taken and execution continues in the handler, as on hardware.
tick(cycles: int) -> None
Advances the devices by cycles without executing instructions. Use it to
drive a peripheral directly from Python, e.g. to let an accelerator finish
a job started by a host write.
Loading
load_elf(source: str | os.PathLike | bytes | bytearray) -> int
Loads a 32-bit RISC-V ELF from a path or from bytes: copies its loadable
segments onto the bus, sets pc to the entry point and returns it. Raises
ElfError if the file is not a valid RV32 ELF or a segment does not fit in
mapped memory.
symbol(name: str) -> int | None
The address of a symbol of the last loaded ELF.
symbols: dict[str, int]
All symbols of the last loaded ELF.
load_program(addr: int, program: list[int]) -> None
Writes 32-bit words starting at addr. Handy for small hand-assembled
programs; pc is not changed.
Memory
These go through the bus, exactly like accesses from the CPU, so they
reach devices too (reading a UART register from Python has the same side
effects as reading it from firmware). Values are little-endian. Failures
raise BusError.
| Method | |
|---|---|
read_u8(addr), read_u16(addr), read_u32(addr) -> int |
|
write_u8(addr, val), write_u16(addr, val), write_u32(addr, val) |
val must fit the width |
read_bytes(addr, size) -> bytes |
byte by byte |
write_bytes(addr, data: bytes | bytearray) |
byte by byte |
To exchange arrays with firmware, use NumPy:
emu.write_bytes(addr, arr.astype("<i2").tobytes()) and
np.frombuffer(emu.read_bytes(addr, 2 * n), dtype="<i2").
CPU state
| Member | |
|---|---|
read_reg(reg) -> int, write_reg(reg, val) |
x0..x31 by number; writes to x0 are ignored; IndexError outside 0..31 |
regs: list[int] |
a snapshot of all 32 registers |
pc: int |
read/write |
read_csr(csr) -> int, write_csr(csr, val) |
by CSR number; ValueError for an unimplemented or read-only CSR |
cycles: int |
mcycle |
instret: int |
minstret |
is_waiting: bool |
the hart is sleeping in wfi |
Register numbers follow the ABI: ra=1, sp=2, t0..t2=5..7,
s0/fp=8, s1=9, a0..a7=10..17, s2..s11=18..27,
t3..t6=28..31.
Useful CSR numbers: mstatus 0x300, misa 0x301, mie 0x304, mtvec
0x305, mscratch 0x340, mepc 0x341, mcause 0x342, mtval 0x343,
mip 0x344, mcycle 0xB00, minstret 0xB02, mhartid 0xF14.
All integers are unsigned 32-bit (64-bit for the counters). Passing a
negative number or one that does not fit raises OverflowError; write
-1 & 0xFFFF_FFFF for all ones, and convert a register to signed with
v - (1 << 32) if v >> 31 else v.
RunResult and Trap
RunResult
| Attribute | |
|---|---|
reason: str |
"exit", "max_steps", "breakpoint", "trap" or "idle" |
steps: int |
cycles executed by this run() call |
exit_code: int | None |
the device's exit status when reason == "exit" |
trap: Trap | None |
the trap when reason == "trap" |
Trap
A trap that has already been taken (the CSRs below hold the same values).
| Attribute | |
|---|---|
cause: int |
mcause; bit 31 set for interrupts |
is_interrupt: bool |
|
epc: int |
mepc: the faulting instruction, or where an interrupt struck |
tval: int |
mtval: address or instruction, see Traps |
description: str |
e.g. "illegal instruction: 0x00000000" |
Built-in devices
Native devices are written in Rust and run at full speed. Create one, then
pass it to map_device; its window size is fixed. A device object can be
mapped only once (RuntimeError otherwise).
Ram
Ram(size: int): zero-initialised memory of size bytes. The emulator
already has one; map more for separate regions (e.g. a "flash" at another
address).
Clint
Clint(divider: int = 1): the SiFive core-local interruptor: msip at
offset 0, mtimecmp at 0x4000, mtime at 0xBFF8. mtime advances by one
every divider cycles. It raises the machine timer interrupt while
mtime >= mtimecmp and the software interrupt while msip is 1. The
Emulator maps one unless clint_base=None, so you only need this class to
place it yourself.
Uart
Uart(*, reg_shift: int = 0, echo: bool = False): an NS16550A subset as in
QEMU virt: transmit, receive, line status, interrupt enable (the receive
and transmit interrupts drive the machine external interrupt). Registers
are 1 << reg_shift bytes apart. The object keeps working after it is
mapped, so the testbench can talk to the firmware:
| Member | |
|---|---|
output() -> bytes |
everything transmitted so far |
take_output() -> bytes |
transmitted since the last call, then clears |
write_input(data: bytes) |
queue bytes for the firmware to receive |
pending_input: int |
queued bytes not read yet |
With echo=True, transmitted text is also written to sys.stdout line by
line.
uart = mrve32.Uart()
emu.map_device(0x1000_0000, uart)
uart.write_input(b"help\n")
emu.run(1_000_000)
assert b"usage" in uart.take_output()
TestFinisher
TestFinisher(): the sifive,test0 device. The firmware writes 0x5555
for "pass" (exit code 0) or (code << 16) | 0x3333 for "fail" with code;
run() then returns reason == "exit" with that exit_code.
Module constants
mrve32.__version__: the package version.mrve32.DEVICE_ABI_VERSION: version of the C ABI for native device modules (include/mrve32_device.h).
Devices in Python
Any object with read and write methods can be mapped. This is the
fastest way to model a peripheral; for speed, or to ship a device as its
own package, write a native module instead (see
docs/devices.md).
class Device(Protocol): # mrve32.Device
def read(self, offset: int, width: int) -> int: ...
def write(self, offset: int, width: int, value: int) -> None: ...
# optional:
# def tick(self, cycles: int) -> None: ...
# def irq_lines(self) -> int: ...
# def exit_request(self) -> int | None: ...
| Method | Called | |
|---|---|---|
read(offset, width) |
on a load | offset is relative to the window base; width is 1, 2 or 4; return a value that fits in width bytes |
write(offset, width, value) |
on a store | |
tick(cycles) |
after every step, and by Emulator.tick |
|
irq_lines() |
before every step | a mask in mip layout: 1 << 11 external, 1 << 7 timer, 1 << 3 software; lines of all devices are OR-ed |
exit_request() |
after every step | return an exit code to stop run() with reason == "exit", otherwise None |
The window size is map_device(base, dev, size=...) or, if not given, the
object's size attribute.
- Raise
mrve32.BusErrorfromread/writefor an access fault the firmware should see (e.g. an unmapped register). - Any other exception is treated as a bug in the model:
step()/run()stops and re-raises it. tick,irq_linesandexit_requestcross from Rust into Python on every step. Define them only if the device needs them.
An example: a countdown timer that raises the external interrupt.
import mrve32
class Countdown:
size = 4
def __init__(self):
self.left = 0
self.armed = False
def read(self, offset, width):
if offset != 0 or width != 4:
raise mrve32.BusError("Countdown: only a 32-bit register at 0")
return self.left
def write(self, offset, width, value):
if offset != 0 or width != 4:
raise mrve32.BusError("Countdown: only a 32-bit register at 0")
self.left, self.armed = value, value != 0
def tick(self, cycles):
self.left = max(0, self.left - cycles)
def irq_lines(self):
return 1 << 11 if self.armed and self.left == 0 else 0 # MEIP
emu = mrve32.Emulator()
timer = Countdown()
emu.map_device(0x4000_0000, timer)
emu.map_device(0x0010_0000, mrve32.TestFinisher())
emu.load_program(0x8000_0000, [
0x10500073, # wfi sleep until the interrupt
0xFFDFF06F, # j .-4
])
emu.load_program(0x8000_0040, [ # the interrupt handler
0x400002B7, # lui t0, 0x40000
0x0002A023, # sw zero, 0(t0) disarm the timer
0x001002B7, # lui t0, 0x100
0x00005337, # lui t1, 0x5
0x55530313, # addi t1, t1, 0x555
0x0062A023, # sw t1, 0(t0) finisher: pass
])
emu.write_csr(0x305, 0x8000_0040) # mtvec = handler
emu.write_csr(0x304, 1 << 11) # mie.MEIE
emu.write_csr(0x300, 1 << 3) # mstatus.MIE
emu.write_u32(0x4000_0000, 100) # interrupt in 100 cycles
res = emu.run(10_000)
print(res.reason, emu.read_csr(0x342) == 0x8000_000B) # exit True
Custom instructions (coprocessors)
The custom-0 (opcode 0x0B) and custom-1 (0x2B) instruction spaces
are reserved for vendor extensions. Attach a coprocessor to give them a
meaning, e.g. to try out an accelerator instruction before building it:
Emulator.set_coprocessor(coprocessor | None): coprocessor is any
object with execute(call: CoprocessorCall) -> bool | None (the
mrve32.Coprocessor protocol). None detaches it. Without a coprocessor
these opcodes are illegal instructions.
execute is called for each custom instruction. Return None or True
when it executed, False to raise an illegal-instruction trap (an encoding
you do not implement).
CoprocessorCall describes the instruction and gives access to the hart:
| Member | |
|---|---|
opcode, word, pc |
0x0B/0x2B, the 32-bit instruction, its address |
rd, rs1, rs2, funct3, funct7 |
R-type fields, decoded for you (use word for other formats) |
read_reg(reg), write_reg(reg, value) |
registers; writes to x0 are ignored |
next_pc |
read/write; pc + 4 by default, set it to branch |
read_u8/16/32(addr), write_u8/16/32(addr, value) |
bus access, as from the CPU |
Register writes and next_pc take effect only if execute succeeds, so a
trapping instruction leaves no partial result. Memory writes are not undone.
If a bus access raises BusError and execute does not catch it, the
instruction raises a load/store access fault instead. Any other exception
stops run() and is re-raised. call is valid only during execute.
A multiply-accumulate instruction, mac rd, rs1, rs2 (rd += rs1 * rs2),
in custom-0 with funct3 = 0:
class Mac:
def execute(self, call):
if call.funct3 != 0:
return False # illegal instruction
acc = call.read_reg(call.rd) + call.read_reg(call.rs1) * call.read_reg(call.rs2)
call.write_reg(call.rd, acc & 0xFFFF_FFFF)
emu = mrve32.Emulator()
emu.set_coprocessor(Mac())
emu.write_reg(10, 5) # a0
emu.write_reg(11, 6) # a1
emu.write_reg(12, 7) # a2
emu.load_program(0x8000_0000, [0x00C5850B]) # mac a0, a1, a2 (.insn r 0x0B, 0, 0, a0, a1, a2)
emu.step()
print(emu.read_reg(10)) # 47
In C firmware, emit it with the assembler's .insn directive:
asm volatile(".insn r 0x0B, 0, 0, %0, %1, %2" : "+r"(acc) : "r"(a), "r"(b));
Debugging
Breakpoints
| Method | |
|---|---|
add_breakpoint(pc) |
run() stops with reason == "breakpoint" before executing the instruction at pc |
remove_breakpoint(pc) -> bool |
False if there was none |
clear_breakpoints() |
|
breakpoints: list[int] |
run() checks for breakpoints after each step, so calling it again from a
breakpoint executes that instruction and continues. For the same reason a
breakpoint at the pc where run() starts is not hit on that call. A
breakpoint does not stop step().
emu.load_elf("firmware.elf")
emu.add_breakpoint(emu.symbol("main"))
res = emu.run(1_000_000)
assert res.reason == "breakpoint" and emu.pc == emu.symbol("main")
Tracing
set_tracer(tracer | None): calls tracer(pc, word, trap) after every
step: pc of the instruction, word the instruction (None if the fetch
faulted or the hart slept), trap the trap taken or None. An exception in
the tracer stops run() and is re-raised. Tracing calls into Python on
every step, so it slows the run down a lot.
def trace(pc, word, trap):
if trap:
print(f"{pc:08x} trap: {trap.description}")
elif word is not None:
print(f"{pc:08x} {mrve32.disassemble(word, pc)}")
emu.set_tracer(trace)
Disassembly
mrve32.disassemble(word: int, pc: int | None = None) -> str: one
instruction in GNU objdump style, with its pseudo-instructions and CSR
names. A word whose low two bits are not 0b11 is a 16-bit compressed
instruction. With pc, branch and jump targets are absolute.
mrve32.disassemble(0x00A58533) # 'add a0, a1, a0'
mrve32.disassemble(0x4515) # 'li a0, 5'
mrve32.disassemble(0x0000006F, 0x8000_0000) # 'j 0x80000000'
Errors
| Exception | Raised by |
|---|---|
mrve32.BusError |
a memory access from Python to an unmapped address, or one a device rejects (wrong width, busy) |
mrve32.ElfError |
load_elf with an invalid file or segments outside mapped memory |
ValueError |
overlapping device windows; an unimplemented or read-only CSR; invalid constructor arguments |
TypeError |
map_device with an object that is not a device |
IndexError |
a register number outside 0..31 |
OverflowError |
an integer that does not fit (negative, or too wide for the access) |
RuntimeError |
mapping a native device twice; using a CoprocessorCall after execute returned |
Exceptions raised by your own devices, coprocessors or tracer come out of
the step()/run()/memory call that triggered them, unchanged.
mrve32-snn
The SNN layer accelerator (a behavioural model of a spiking-neural-network
accelerator, with a NumPy reference model and a host-side driver) is a
separate package, pip install mrve32-snn. Its documentation is on
its PyPI page and in
modules/snn/README.md.
Metadata
Release files for mrve32 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mrve32-0.1.1.tar.gz | 176.7 kB | With provenance | Details for mrve32-0.1.1.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| mrve32-0.1.1-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details for mrve32-0.1.1-cp310-abi3-win_amd64.whl |
| mrve32-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details for mrve32-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| mrve32-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details for mrve32-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| mrve32-0.1.1-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details for mrve32-0.1.1-cp310-abi3-macosx_11_0_arm64.whl |
| mrve32-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details for mrve32-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl |
Total release size: 1.9 MB
Release files / mrve32-0.1.1.tar.gz
| Download URL | mrve32-0.1.1.tar.gz |
|---|---|
| Size | 176.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c5178fea9af918b15bc38d1fee205af7f349ee04096bbe4608223e9ddf3aea45
|
|
BLAKE2b-256 checksum How to use checksums |
027539bcb489f2e37c9453be289dbd1c665fa4082a663be934ce1e03615e7de0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / mrve32-0.1.1-cp310-abi3-win_amd64.whl
| Download URL | mrve32-0.1.1-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 253.2 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f814b2ae54bd00890b9b31479cd707e3c0f3120ddd49e164b6dead811ffdeda7
|
|
BLAKE2b-256 checksum How to use checksums |
0a5d2030fb10f986c24986fc9f3844906984680377dbacc048684249958a32a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / mrve32-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | mrve32-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 400.6 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
ebc069ec0b0e70895fe169f32af2409c495454f866f6aceb611cabed083d146a
|
|
BLAKE2b-256 checksum How to use checksums |
f05638068784e1177a2b37e75f289e08437e4dea572f6d1b64ff98d30d8d06b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / mrve32-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | mrve32-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 396.8 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
8de71eb4cb5667964a22502214ed2fcfc3bbcd8cc23521e814748dd01301a18c
|
|
BLAKE2b-256 checksum How to use checksums |
44b5a36527472e7151cf1045fd2932586defce3bcd04ff727bc6234418ad9328
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / mrve32-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | mrve32-0.1.1-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 356.1 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
a928296cfcc737533452c21612dda7f8347e768ce8af2c564920ef886e2f716f
|
|
BLAKE2b-256 checksum How to use checksums |
517abb98b83ba08b223fb3c701658b48a2084e4707e4c3e8af99aa3fcdcd75bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / mrve32-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | mrve32-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 355.8 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
9e717d5bff1c99ab303457050487f7f2758f9bd88e50bf81a9ace34ac5a8169e
|
|
BLAKE2b-256 checksum How to use checksums |
55051b52ae82760aa8753cb7437fb1d87bcbb0e2d842e94e91525a76eb4778c4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logSecurity for mrve32 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 6 files in this release have been uploaded with provenance metadata.
Attestation for mrve32-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.