Initial commit:

* Xoroshiro128+ (works, tested)
 * MT19337 (work in progress)
 * Test benches
 * Reference C code.
This commit is contained in:
Joris van Rantwijk 2016-10-21 11:31:26 +02:00
commit 5303c34431
6 changed files with 814 additions and 0 deletions

48
refimpl/ref_mt19937.cpp Normal file
View File

@ -0,0 +1,48 @@
/*
* Reference implementation of Mersenne Twister MT19937 in C++11.
*
* Test bench by Joris van Rantwijk.
*/
#include <cstdlib>
#include <cstdio>
#include <random>
using namespace std;
int main(int argc, const char **argv)
{
if (argc != 3) {
fprintf(stderr, "Reference implementation of Mersenne Twister MT19937\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: ref_mt19937 SEED NUMVALUE\n");
fprintf(stderr, " SEED seed value in range 0 .. (2**31-1)\n");
fprintf(stderr, " NUMVALUE number of values to get from generator\n");
fprintf(stderr, "\n");
fprintf(stderr, "Example: ref_mt19937 0x31415926 100\n");
exit(1);
}
char *p;
unsigned long seed = strtoul(argv[1], &p, 0);
if (p == argv[1] || *p != '\0') {
fprintf(stderr, "ERROR: Invalid value for SEED0\n");
exit(1);
}
unsigned long numval = strtoul(argv[2], &p, 0);
if (p == argv[3] || *p != '\0') {
fprintf(stderr, "ERROR: Invalid value for NUMVALUE\n");
exit(1);
}
mt19937 rng(seed);
for (unsigned long k = 0; k < numval; k++) {
printf("0x%08lx\n", (unsigned long) rng());
}
return 0;
}

93
refimpl/ref_xoroshiro.c Normal file
View File

@ -0,0 +1,93 @@
/*
* Reference implementation of "xoroshiro128+" in C.
*
* Algorithm by David Blackman and Sebastiano Vigna.
* Test bench by Joris van Rantwijk.
*
* To the extent possible under law, the author has dedicated all copyright
* and related and neighboring rights to this software to the public domain
* worldwide. This software is distributed without any warranty.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
/* ========== BEGIN of reference implementation of xoroshiro128+ ==========
* See also http://xoroshiro.di.unimi.it/
*/
/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org)
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
int64_t s[2];
static inline uint64_t rotl(const uint64_t x, int k) {
return (x << k) | (x >> (64 - k));
}
uint64_t next(void) {
const uint64_t s0 = s[0];
uint64_t s1 = s[1];
const uint64_t result = s0 + s1;
s1 ^= s0;
s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b
s[1] = rotl(s1, 36); // c
return result;
}
/* ========== END of reference implementation of xoroshiro128+ ========== */
int main(int argc, const char **argv)
{
char *p;
unsigned long numval;
unsigned long k;
if (argc != 4) {
fprintf(stderr, "Reference implementation of RNG xoroshiro128+\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: ref_xoroshiro SEED0 SEED1 NUMVALUE\n");
fprintf(stderr, " SEED0 seed value in range 0 .. (2**64-1)\n");
fprintf(stderr, " SEED1 seed value in range 0 .. (2**64-1)\n");
fprintf(stderr, " NUMVALUE number of values to get from generator\n");
fprintf(stderr, "\n");
fprintf(stderr, "Example: ref_xoroshiro 0x3141592653589793 "
"0x0123456789abcdef 100\n");
exit(1);
}
s[0] = strtoull(argv[1], &p, 0);
if (p == argv[1] || *p != '\0') {
fprintf(stderr, "ERROR: Invalid value for SEED0\n");
exit(1);
}
s[1] = strtoull(argv[2], &p, 0);
if (p == argv[2] || *p != '\0') {
fprintf(stderr, "ERROR: Invalid value for SEED1\n");
exit(1);
}
numval = strtoul(argv[3], &p, 0);
if (p == argv[3] || *p != '\0') {
fprintf(stderr, "ERROR: Invalid value for NUMVALUE\n");
exit(1);
}
for (k = 0; k < numval; k++) {
printf("0x%016llx\n", (unsigned long long) next());
}
return 0;
}

275
rtl/rng_mt19937.vhdl Normal file
View File

@ -0,0 +1,275 @@
--
-- Pseudo Random Number Generator based on Mersenne Twister MT19937.
--
-- Author: Joris van Rantwijk <joris@jorisvr.nl>
--
-- This is a 32-bit random number generator in synthesizable VHDL.
-- The generator produces 32 new random bits on every (enabled) clock cycle.
--
-- See also M. Matsumoto, T. Nishimura, "Mersenne Twister:
-- a 623-dimensionally equidistributed uniform pseudorandom number generator",
-- ACM TOMACS, vol. 8, no. 1, 1998.
--
-- The generator requires a 32-bit seed value.
-- A default seed must be supplied at compile time and will be used
-- to initialize the generator at reset. The generator also supports
-- re-seeded at run time.
--
-- After reset, and after re-seeding, the generator needs 625 clock
-- cycles to initialize its internal state. During this time, the generator
-- is unable to provide correct output.
--
-- NOTE: This is not a cryptographic random number generator.
--
-- TODO : Multiplication in reseeding severely limits the maximum frequency
-- for this design.
-- Add pipelining and increase the number of clock cycles for reseeding.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity rng_mt19937 is
generic (
-- Default seed value.
init_seed: std_logic_vector(31 downto 0) );
port (
-- Clock, rising edge active.
clk: in std_logic;
-- Synchronous reset, active high.
rst: in std_logic;
-- High to generate new output value.
enable: in std_logic;
-- High to re-seed the generator (works regardless of enable signal).
reseed: in std_logic;
-- New seed value (must be valid when reseed = '1').
newseed: in std_logic_vector(31 downto 0);
-- Output value.
-- A new value appears on every rising clock edge where enable = '1'.
output: out std_logic_vector(31 downto 0);
-- High while re-seeding (normal function not available).
busy: out std_logic );
end entity;
architecture rng_mt19937_arch of rng_mt19937 is
-- Constants.
constant const_a: std_logic_vector(31 downto 0) := x"9908b0df";
constant const_b: std_logic_vector(31 downto 0) := x"9d2c5680";
constant const_c: std_logic_vector(31 downto 0) := x"efc60000";
constant const_f: natural := 1812433253;
-- Block RAM for generator state.
type mem_t is array(0 to 620) of std_logic_vector(31 downto 0);
signal mem: mem_t;
-- RAM access registers.
signal reg_a_addr: std_logic_vector(9 downto 0);
signal reg_b_addr: std_logic_vector(9 downto 0);
signal reg_a_wdata: std_logic_vector(31 downto 0);
signal reg_a_rdata: std_logic_vector(31 downto 0);
signal reg_b_rdata: std_logic_vector(31 downto 0);
-- Internal registers.
signal reg_enable: std_logic;
signal reg_reseeding1: std_logic;
signal reg_reseeding2: std_logic;
signal reg_a_wdata_p: std_logic_vector(31 downto 0);
signal reg_a_rdata_p: std_logic_vector(31 downto 0);
signal reg_reseed_cnt: std_logic_vector(9 downto 0);
-- Output register.
signal reg_output: std_logic_vector(31 downto 0) := (others => '0');
signal reg_busy: std_logic;
-- -- Multiply unsigned number with constant and discard overflowing bits.
-- function mulconst(x: unsigned)
-- return unsigned
-- is
-- variable t: unsigned(2*x'length-1 downto 0);
-- begin
-- t := x * const_f;
-- return t(x'length-1 downto 0);
-- end function;
-- Multiply unsigned number with constant and discard overflowing bits.
function mulconst(x: unsigned)
return unsigned
is
begin
return x
+ shift_left(x, 2)
+ shift_left(x, 5)
+ shift_left(x, 6)
+ shift_left(x, 8)
+ shift_left(x, 11)
- shift_left(x, 15)
+ shift_left(x, 19)
- shift_left(x, 26)
- shift_left(x, 28)
+ shift_left(x, 31);
end function;
begin
-- Drive output signal.
output <= reg_output;
busy <= reg_busy;
-- Main synchronous process.
process (clk) is
variable y: std_logic_vector(31 downto 0);
begin
if rising_edge(clk) then
-- Update memory pointers.
if reg_enable = '1' then
if unsigned(reg_a_addr) = 620 then
reg_a_addr <= (others => '0');
else
reg_a_addr <= std_logic_vector(unsigned(reg_a_addr) + 1);
end if;
if unsigned(reg_b_addr) = 620 then
reg_b_addr <= (others => '0');
else
reg_b_addr <= std_logic_vector(unsigned(reg_b_addr) + 1);
end if;
end if;
-- Keep previous values of registers.
if reg_enable = '1' then
reg_a_rdata_p <= reg_a_rdata;
reg_a_wdata_p <= reg_a_wdata;
end if;
-- Update reseeding counter.
reg_reseed_cnt <= std_logic_vector(unsigned(reg_reseed_cnt) + 1);
-- Determine end of reseeding.
reg_busy <= reg_reseeding2;
reg_reseeding2 <= reg_reseeding1;
if unsigned(reg_reseed_cnt) = 623 then
reg_reseeding1 <= '0';
end if;
-- Enable state machine on next cycle
-- a) during initialization, and
-- b) on-demand for new output.
reg_enable <= reg_reseeding2 or enable;
-- Update internal RNG state.
if reg_enable = '1' then
if reg_reseeding1 = '1' then
-- Continue re-seeding loop.
y := reg_a_wdata;
y(1 downto 0) := y(1 downto 0) xor y(31 downto 30);
reg_a_wdata <= std_logic_vector(
mulconst(unsigned(y)) +
unsigned(reg_reseed_cnt) );
else
-- Normal operation.
-- Perform one step of the "twist" function.
y := reg_a_rdata_p(31 downto 31) &
reg_a_rdata(30 downto 0);
if y(0) = '1' then
y := "0" & y(31 downto 1);
y := y xor const_a;
else
y := "0" & y(31 downto 1);
end if;
reg_a_wdata_p <= reg_a_wdata;
reg_a_wdata <= reg_b_rdata xor y;
end if;
end if;
-- Produce output value (when enabled).
if enable = '1' then
if reg_enable = '1' then
y := reg_a_wdata;
else
y := reg_a_wdata_p;
end if;
y(20 downto 0) := y(20 downto 0) xor y(31 downto 11);
y(31 downto 7) := y(31 downto 7) xor
(y(24 downto 0) and const_b(31 downto 7));
y(31 downto 15) := y(31 downto 15) xor
(y(16 downto 0) and const_c(31 downto 15));
y(13 downto 0) := y(13 downto 0) xor y(31 downto 18);
reg_output <= y;
end if;
-- Start re-seeding.
if reseed = '1' then
reg_reseeding1 <= '1';
reg_reseeding2 <= '1';
reg_reseed_cnt <= std_logic_vector(to_unsigned(1, 10));
reg_enable <= '1';
reg_a_wdata <= newseed;
reg_busy <= '1';
end if;
-- Synchronous reset.
if rst = '1' then
reg_a_addr <= std_logic_vector(to_unsigned(0, 10));
reg_b_addr <= std_logic_vector(to_unsigned(396, 10));
reg_reseeding1 <= '1';
reg_reseeding2 <= '1';
reg_reseed_cnt <= std_logic_vector(to_unsigned(1, 10));
reg_enable <= '1';
reg_a_wdata <= init_seed;
reg_output <= (others => '0');
reg_busy <= '1';
end if;
end if;
end process;
-- Synchronous process for block RAM.
process (clk) is
begin
if rising_edge(clk) then
if reg_enable = '1' then
-- Read from port A.
reg_a_rdata <= mem(to_integer(unsigned(reg_a_addr)));
-- Read from port B.
reg_b_rdata <= mem(to_integer(unsigned(reg_b_addr)));
-- Write to port A.
mem(to_integer(unsigned(reg_a_addr))) <= reg_a_wdata;
end if;
end if;
end process;
end architecture;

139
rtl/xoroshiro128plus.vhdl Normal file
View File

@ -0,0 +1,139 @@
--
-- Pseudo Random Number Generator "xoroshiro128+".
--
-- Author: Joris van Rantwijk <joris@jorisvr.nl>
--
-- This is a 64-bit random number generator in synthesizable VHDL.
-- The generator produces 64 new random bits on every (enabled) clock cycle.
--
-- The algorithm "xoroshiro128+" is by David Blackman and Sebastiano Vigna.
-- See also http://xoroshiro.di.unimi.it/
--
-- The generator requires a 128-bit seed value, not equal to zero.
-- A default seed must be supplied at compile time and will be used
-- to initialize the generator at reset. The generator also supports
-- re-seeding at run time.
--
-- After reset, at least one enabled clock cycle is needed before
-- a random number appears on the output.
--
-- NOTE: This is not a cryptographic random number generator.
--
-- NOTE: The least significant output bit is less random than
-- all other output bits.
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity xoroshiro128plus is
generic (
-- Default seed value.
init_seed: std_logic_vector(127 downto 0) );
port (
-- Clock, rising edge active.
clk: in std_logic;
-- Synchronous reset, active high.
rst: in std_logic;
-- Clock enable, active high.
enable: in std_logic;
-- High to re-seed the generator (requires enable = '1').
reseed: in std_logic;
-- New seed value (must be valid when reseed = '1').
newseed: in std_logic_vector(127 downto 0);
-- Output value.
-- A new value appears on every rising clock edge where enable = '1'.
output: out std_logic_vector(63 downto 0) );
end entity;
architecture xoroshiro128plus_arch of xoroshiro128plus is
-- Internal state of RNG.
signal reg_state_s0: std_logic_vector(63 downto 0) := init_seed(63 downto 0);
signal reg_state_s1: std_logic_vector(63 downto 0) := init_seed(127 downto 64);
-- Output register.
signal reg_output: std_logic_vector(63 downto 0) := (others => '0');
-- Shift left.
function shiftl(x: std_logic_vector; b: integer)
return std_logic_vector
is
constant n: integer := x'length;
variable y: std_logic_vector(n-1 downto 0);
begin
y(n-1 downto b) := x(x'high-b downto x'low);
y(b-1 downto 0) := (others => '0');
return y;
end function;
-- Rotate left.
function rotl(x: std_logic_vector; b: integer)
return std_logic_vector
is
constant n: integer := x'length;
variable y: std_logic_vector(n-1 downto 0);
begin
y(n-1 downto b) := x(x'high-b downto x'low);
y(b-1 downto 0) := x(x'high downto x'high-b+1);
return y;
end function;
begin
-- Drive output signal.
output <= reg_output;
-- Synchronous process.
process (clk) is
begin
if rising_edge(clk) then
if enable = '1' then
-- Prepare output word.
reg_output <= std_logic_vector(unsigned(reg_state_s0) +
unsigned(reg_state_s1));
-- Update internal state.
reg_state_s0 <= reg_state_s0 xor
reg_state_s1 xor
shiftl(reg_state_s0, 14) xor
shiftl(reg_state_s1, 14) xor
rotl(reg_state_s0, 55);
reg_state_s1 <= rotl(reg_state_s0, 36) xor
rotl(reg_state_s1, 36);
-- Re-seed function.
if reseed = '1' then
reg_state_s0 <= newseed(63 downto 0);
reg_state_s1 <= newseed(127 downto 64);
end if;
end if;
-- Synchronous reset.
if rst = '1' then
reg_state_s0 <= init_seed(63 downto 0);
reg_state_s1 <= init_seed(127 downto 64);
reg_output <= (others => '0');
end if;
end if;
end process;
end architecture;

141
sim/tb_mt19937.vhdl Normal file
View File

@ -0,0 +1,141 @@
--
-- Test bench for PRNG MT19937.
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb_mt19937 is
end entity;
architecture arch of tb_mt19937 is
signal clk: std_logic;
signal clock_active: boolean := false;
signal s_rst: std_logic;
signal s_enable: std_logic;
signal s_reseed: std_logic;
signal s_newseed: std_logic_vector(31 downto 0);
signal s_output: std_logic_vector(31 downto 0);
signal s_busy: std_logic;
function to_hex_string(s: std_logic_vector)
return string
is
constant alphabet: string(1 to 16) := "0123456789abcdef";
variable y: string(1 to s'length/4);
begin
for i in y'range loop
y(i) := alphabet(to_integer(unsigned(s(s'high+4-4*i downto s'high+1-4*i))) + 1);
end loop;
return y;
end function;
begin
-- Instantiate PRNG.
inst_prng: entity work.rng_mt19937
generic map (
init_seed => x"31415926" )
port map (
clk => clk,
rst => s_rst,
enable => s_enable,
reseed => s_reseed,
newseed => s_newseed,
output => s_output,
busy => s_busy );
-- Generate clock.
clk <= (not clk) after 10 ns when clock_active else '0';
-- Main simulation process.
process is
begin
report "Start test bench";
-- Reset.
s_rst <= '1';
s_enable <= '0';
s_reseed <= '0';
s_newseed <= (others => '0');
-- Start clock.
clock_active <= true;
-- Wait 2 clock cycles, then end reset.
wait for 30 ns;
wait until falling_edge(clk);
s_rst <= '0';
-- Check that generator is initializing.
assert s_busy = '1' report "Generator fails to indicate BUSY";
wait until falling_edge(clk);
assert s_busy = '1' report "Generator fails to indicate BUSY";
-- Give generator time to complete initialization.
for i in 0 to 623 loop
wait until falling_edge(clk);
end loop;
assert s_busy = '0' report "Generator should be ready but still indicates BUSY";
-- Produce numbers
for i in 0 to 1500 loop
if i mod 5 = 0 or i mod 7 = 0 then
s_enable <= '0';
wait until falling_edge(clk);
else
s_enable <= '1';
wait until falling_edge(clk);
report "Got 0x" & to_hex_string(s_output);
end if;
end loop;
-- Re-seed generator.
report "Re-seed generator";
s_enable <= '0';
s_reseed <= '1';
s_newseed <= x"fedcba98";
wait until falling_edge(clk);
s_reseed <= '0';
s_newseed <= (others => '0');
-- Check that generator is initializing.
assert s_busy = '1' report "Generator fails to indicate BUSY";
wait until falling_edge(clk);
assert s_busy = '1' report "Generator fails to indicate BUSY";
-- Give generator time to complete initialization.
for i in 0 to 623 loop
wait until falling_edge(clk);
end loop;
assert s_busy = '0' report "Generator should be ready but still indicates BUSY";
-- Produce numbers
for i in 0 to 1500 loop
if i mod 5 = 1 or i mod 7 = 1 then
s_enable <= '0';
wait until falling_edge(clk);
else
s_enable <= '1';
wait until falling_edge(clk);
report "Got 0x" & to_hex_string(s_output);
end if;
end loop;
-- End simulation.
report "End testbench";
clock_active <= false;
wait;
end process;
end architecture;

View File

@ -0,0 +1,118 @@
--
-- Test bench for PRNG "xoroshiro128+".
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb_xoroshiro128plus is
end entity;
architecture arch of tb_xoroshiro128plus is
signal clk: std_logic;
signal clock_active: boolean := false;
signal s_rst: std_logic;
signal s_enable: std_logic;
signal s_reseed: std_logic;
signal s_newseed: std_logic_vector(127 downto 0);
signal s_output: std_logic_vector(63 downto 0);
function to_hex_string(s: std_logic_vector)
return string
is
constant alphabet: string(1 to 16) := "0123456789abcdef";
variable y: string(1 to s'length/4);
begin
for i in y'range loop
y(i) := alphabet(to_integer(unsigned(s(s'high+4-4*i downto s'high+1-4*i))) + 1);
end loop;
return y;
end function;
begin
-- Instantiate PRNG.
inst_prng: entity work.xoroshiro128plus
generic map (
init_seed => x"0123456789abcdef3141592653589793" )
port map (
clk => clk,
rst => s_rst,
enable => s_enable,
reseed => s_reseed,
newseed => s_newseed,
output => s_output );
-- Generate clock.
clk <= (not clk) after 10 ns when clock_active else '0';
-- Main simulation process.
process is
begin
report "Start test bench";
-- Reset.
s_rst <= '1';
s_enable <= '0';
s_reseed <= '0';
s_newseed <= (others => '0');
-- Start clock.
clock_active <= true;
-- Wait 2 clock cycles, then end reset.
wait for 30 ns;
wait until falling_edge(clk);
s_rst <= '0';
-- Produce numbers
for i in 0 to 150 loop
if i mod 5 = 0 or i mod 7 = 0 then
s_enable <= '0';
wait until falling_edge(clk);
else
s_enable <= '1';
wait until falling_edge(clk);
report "Got 0x" & to_hex_string(s_output);
end if;
end loop;
-- Re-seed generator.
report "Re-seed generator";
s_enable <= '1';
s_reseed <= '1';
s_newseed <= x"3141592653589793fedcba9876543210";
wait until falling_edge(clk);
s_reseed <= '0';
s_newseed <= (others => '0');
-- Produce numbers
for i in 0 to 150 loop
if i mod 5 = 0 or i mod 7 = 0 then
s_enable <= '0';
wait until falling_edge(clk);
else
s_enable <= '1';
wait until falling_edge(clk);
report "Got 0x" & to_hex_string(s_output);
end if;
end loop;
-- End simulation.
report "End testbench";
clock_active <= false;
wait;
end process;
end architecture;