Skip to content

Implement pseudorandom number generator#14

Open
JesseDeMeulemeester wants to merge 5 commits intoproteus-core:mainfrom
JesseDeMeulemeester:rng
Open

Implement pseudorandom number generator#14
JesseDeMeulemeester wants to merge 5 commits intoproteus-core:mainfrom
JesseDeMeulemeester:rng

Conversation

@JesseDeMeulemeester
Copy link
Contributor

This PR implements a pseudorandom number generator. It provides an RngService that can be called in any component that requires random numbers. Each component holds its own RNG buffer to buffer outputs from the generator. The RNG component will fill these buffers in Round Robin fashion. The RNG component, in turn, also has an internal buffer to buffer values between the AES core and the individual RNG buffers.

We currently use AES in OFB mode to generate a continuous random stream, where a single AES encryption produces four 32-bit (or two 64-bit) random values.
For this, we rely on a modified SpinalCrypto library:

  • The default SpinalCrypto AES core takes 22 cycles per round. We optimized this to 4 cycles per round.
  • We also customized the number of rounds per AES encryption. This allows you to reduce the number of computed rounds in cases where you don't need cryptographically secure (pseudo)random numbers. E.g., for our current project, we use 4 rounds. This reduces the latency while maintaining high "randomness".

I wasn't sure how to best include the modified AES core here. Currently I forked SpinalCrypto here and pull this forked repo.

Initialization

The initial seed for the AES core (i.e., the IV) can be set at runtime through CSRs. By default, the RNG component will not generate any random numbers until the seed has been updated. The RNG component can also be disabled through a control CSR. If the disable bit is set, timings will remain the same, but the RNG component will only return zero.

Usage

The usage is very similar to CSRs:
You add the RNG plugin (note that this plugin has to be added after any other plugin that requires it):

pipeline.addPlugins(
  Seq(
    ...
    new Rng(8, true),
    ...
  ) ++ extraPlugins
)

You register a buffer with the RngService:

val rngService = pipeline.service[RngService]
rngBufferIndex = rngService.registerRngBuffer(new RngFifo(8))

And you can then get random numbers from this buffer:

val rngService = pipeline.service[RngService]
private val rng = slave(new RngIo)
rng <> rngService.getRngBuffer(rngBufferIndex)

val (rngValid, rngValue) = rng.get()
when (rngValid) {
  ...
}

Issues

There are still a few minor issues (most are marked with TODOs), most notably:

  • You cannot add the RNG plugin if there are no RNG buffers registered.
  • If one RNG buffer is full, none of the other buffers will be filled until this buffer can accept values again.

JesseDeMeulemeester and others added 5 commits January 9, 2026 17:13
Co-authored-by:  Quinten Norga <43999019+qnorga1@users.noreply.github.com>
* Replaced AES core with Bivium core for more efficient random number generation. This reduces area usage and improves performance to 1 random number per clock cycle.
* Refactored the connection between the RngCore and the RngFifos to remove unnecessary buffers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant