Skip to content

Commit

Permalink
Generalized RNG into TVGenerator
Browse files Browse the repository at this point in the history
* Generation reworked: Each test vector is generated entirely ahead of time then cut into bits instead of generated segment by segment
* Added new TVGenerator, `PatternGenerator`, that is not random and uses the following pattern(s) depending on the iteration count:
   * All 0
   * All 1
   * Half 0, Half 1
   * Half 1, Half 0
   * Alternating 0s and 1s
   * Alternating 1s and 0s
   * Moving `max(1, min(32, bits/4))` window of 1s (even) and the complement of that (odd)
* TVGenerators now take a seed, fixed default seed but can be changed over CLI
* Fixed test
* Incorporated `ARC4RandomNumberGenerator`from Swift TensorFlow sources (takes a seed)
  • Loading branch information
donn committed Jan 15, 2024
1 parent 683fb60 commit 88be7fc
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 195 deletions.
37 changes: 24 additions & 13 deletions Sources/Fault/Entries/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ let yosysExecutable = env["FAULT_YOSYS"] ?? "yosys"
_ = [ // Register all RNGs
SwiftRNG.registered,
LFSR.registered,
PatternGenerator.registered,
]
_ = [ // Register all TVGens
Atalanta.registered,
Expand Down Expand Up @@ -88,7 +89,8 @@ func main(arguments: [String]) -> Int32 {
let defaultTVIncrement = "50"
let defaultMinimumCoverage = "80"
let defaultCeiling = "1000"
let defaultRNG = "swift"
let defaultTVGen = "swift"
let defaultSeed: UInt = 0xDEADCAFEDEADF00D

let version = BoolOption(
shortFlag: "V",
Expand Down Expand Up @@ -163,18 +165,24 @@ func main(arguments: [String]) -> Int32 {
)
cli.addOptions(ceiling)

let rng = StringOption(
longFlag: "rng",
helpMessage: "Type of the RNG used in Internal TV Generation: LFSR or swift. (Default: swift.)"
)
cli.addOptions(rng)

let tvGen = StringOption(
shortFlag: "g",
longFlag: "tvGen",
helpMessage: "Use an external TV Generator: Atalanta or PODEM. (Default: Internal.)"
helpMessage: "Type of the (pseudo-random) internal Test Vector generator: \(TVGeneratorFactory.validNames.joined(separator: "|")) (Default: \(defaultTVGen))"
)
cli.addOptions(tvGen)

let rngSeed = StringOption(
longFlag: "rngSeed",
helpMessage: "A \(MemoryLayout<UInt>.size)-byte value to use as an RNG seed for test vector generators, provided as a hexadecimal string (without 0x). (Default: \(String(defaultSeed, radix: 16)))"
)
cli.addOptions(rngSeed)

let etvGen = StringOption(
longFlag: "etvGen",
helpMessage: "Use an external TV Generator: Atalanta or PODEM. (Default: Internal.)"
)
cli.addOptions(etvGen)

let bench = StringOption(
shortFlag: "b",
Expand Down Expand Up @@ -261,13 +269,13 @@ func main(arguments: [String]) -> Int32 {
return EX_USAGE
}

let randomGenerator = rng.value ?? defaultRNG
let tvGenName = tvGen.value ?? defaultTVGen

guard
let tvAttempts = Int(testVectorCount.value ?? defaultTVCount),
let tvIncrement = Int(testVectorIncrement.value ?? defaultTVIncrement),
let tvMinimumCoverageInt = Int(minimumCoverage.value ?? defaultMinimumCoverage),
Int(ceiling.value ?? defaultCeiling) != nil, URNGFactory.validNames.contains(randomGenerator), (tvGen.value == nil) == (bench.value == nil)
Int(ceiling.value ?? defaultCeiling) != nil, TVGeneratorFactory.validNames.contains(tvGenName), (etvGen.value == nil) == (bench.value == nil)
else {
cli.printUsage()
return EX_USAGE
Expand Down Expand Up @@ -369,9 +377,9 @@ func main(arguments: [String]) -> Int32 {
print("Read \(etvSetVectors.count) externally-generated vectors to verify.")
}

if let tvGenerator = tvGen.value, ETVGFactory.validNames.contains(tvGenerator) {
if let tvGenerator = etvGen.value, ETVGFactory.validNames.contains(tvGenerator) {
let etvgen = ETVGFactory.get(name: tvGenerator)!
let benchUnwrapped = bench.value! // Program exits if tvGen.value isn't nil and bench.value is or vice versa
let benchUnwrapped = bench.value! // Program exits if etvGen.value isn't nil and bench.value is or vice versa

if !fileManager.fileExists(atPath: benchUnwrapped) {
Stderr.print("Bench file '\(benchUnwrapped)' not found.")
Expand All @@ -395,6 +403,8 @@ func main(arguments: [String]) -> Int32 {
String(etvSetVectors.count)
)
)!

let finalRNGSeed = rngSeed.value != nil ? UInt(rngSeed.value!, radix: 16)! : defaultSeed

do {
let (ports, inputs, outputs) = try Port.extract(from: definition)
Expand Down Expand Up @@ -493,7 +503,8 @@ func main(arguments: [String]) -> Int32 {
incrementingBy: tvIncrement,
minimumCoverage: tvMinimumCoverage,
ceiling: finalTvCeiling,
randomGenerator: randomGenerator,
tvGenerator: TVGeneratorFactory.get(name: tvGenName)!,
rngSeed: finalRNGSeed,
initialTVInfo: initialTVInfo,
externalTestVectors: etvSetVectors,
sampleRun: sampleRun.value,
Expand Down
112 changes: 112 additions & 0 deletions Sources/Fault/RNG.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//===-- Random.swift ------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// A (pseudo-)random number generation library. The library separates concerns
// into engines which generate random bytes and distributions which use an
// engine to generate values from some statistical distribution.
//
//===----------------------------------------------------------------------===//

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import Darwin
#else
import Glibc
#endif

/// A type that provides seedable deterministic pseudo-random data.
///
/// A SeedableRandomNumberGenerator can be used anywhere where a
/// RandomNumberGenerator would be used. It is useful when the pseudo-random
/// data needs to be reproducible across runs.
///
/// Conforming to the SeedableRandomNumberGenerator Protocol
/// ========================================================
///
/// To make a custom type conform to the `SeedableRandomNumberGenerator`
/// protocol, implement the `init(seed: [UInt8])` initializer, as well as the
/// requirements for `RandomNumberGenerator`. The values returned by `next()`
/// must form a deterministic sequence that depends only on the seed provided
/// upon initialization.
public protocol SeedableRandomNumberGenerator: RandomNumberGenerator {
init(seed: [UInt8])
init<T: BinaryInteger>(seed: T)
}

extension SeedableRandomNumberGenerator {
public init<T: BinaryInteger>(seed: T) {
var newSeed: [UInt8] = []
for i in 0..<seed.bitWidth / UInt8.bitWidth {
newSeed.append(UInt8(truncatingIfNeeded: seed >> (UInt8.bitWidth * i)))
}
self.init(seed: newSeed)
}
}

/// An implementation of `SeedableRandomNumberGenerator` using ARC4.
///
/// ARC4 is a stream cipher that generates a pseudo-random stream of bytes. This
/// PRNG uses the seed as its key.
///
/// ARC4 is described in Schneier, B., "Applied Cryptography: Protocols,
/// Algorithms, and Source Code in C", 2nd Edition, 1996.
///
/// An individual generator is not thread-safe, but distinct generators do not
/// share state. The random data generated is of high-quality, but is not
/// suitable for cryptographic applications.
@frozen
public struct ARC4RandomNumberGenerator: SeedableRandomNumberGenerator {
public static var global = ARC4RandomNumberGenerator(seed: UInt32(time(nil)))
var state: [UInt8] = Array(0...255)
var iPos: UInt8 = 0
var jPos: UInt8 = 0

/// Initialize ARC4RandomNumberGenerator using an array of UInt8. The array
/// must have length between 1 and 256 inclusive.
public init(seed: [UInt8]) {
precondition(seed.count > 0, "Length of seed must be positive")
precondition(seed.count <= 256, "Length of seed must be at most 256")
var j: UInt8 = 0
for i: UInt8 in 0...255 {
j &+= S(i) &+ seed[Int(i) % seed.count]
swapAt(i, j)
}
}

// Produce the next random UInt64 from the stream, and advance the internal
// state.
public mutating func next() -> UInt64 {
var result: UInt64 = 0
for _ in 0..<UInt64.bitWidth / UInt8.bitWidth {
result <<= UInt8.bitWidth
result += UInt64(nextByte())
}
return result
}

// Helper to access the state.
private func S(_ index: UInt8) -> UInt8 {
return state[Int(index)]
}

// Helper to swap elements of the state.
private mutating func swapAt(_ i: UInt8, _ j: UInt8) {
state.swapAt(Int(i), Int(j))
}

// Generates the next byte in the keystream.
private mutating func nextByte() -> UInt8 {
iPos &+= 1
jPos &+= S(iPos)
swapAt(iPos, jPos)
return S(S(iPos) &+ S(jPos))
}
}
41 changes: 0 additions & 41 deletions Sources/Fault/RNGFactory.swift

This file was deleted.

26 changes: 6 additions & 20 deletions Sources/Fault/Simulation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ enum Simulator {
incrementingBy increment: Int,
minimumCoverage: Float,
ceiling: Int,
randomGenerator: String,
tvGenerator: TVGenerator.Type,
rngSeed: UInt,
initialTVInfo: TVInfo? = nil,
externalTestVectors: [TestVector],
sampleRun: Bool,
Expand Down Expand Up @@ -266,7 +267,7 @@ enum Simulator {

let simulateOnly = (externalTestVectors.count != 0)
let totalBitWidth = inputs.reduce(0) { $0 + $1.width }
let backupRng: URNG = URNGFactory.get(name: randomGenerator)!.init(allBits: totalBitWidth)
var rng: TVGenerator = tvGenerator.init(allBits: totalBitWidth, seed: rngSeed)

while coverage < minimumCoverage, totalTVAttempts < ceiling {
if totalTVAttempts > 0 {
Expand All @@ -280,30 +281,15 @@ enum Simulator {
var testVectors: [TestVector] = []
for index in 0 ..< tvAttempts {
let overallIndex = totalTVAttempts + index

var rng: URNG = backupRng
if overallIndex == 0 {
rng = PatternGenerator(allBits: totalBitWidth, pattern: .allZero, complement: false)
} else if overallIndex == 1 {
rng = PatternGenerator(allBits: totalBitWidth, pattern: .allZero, complement: true)
} else if overallIndex == 2 {
rng = PatternGenerator(allBits: totalBitWidth, pattern: .alternating, complement: false)
} else if overallIndex == 3 {
rng = PatternGenerator(allBits: totalBitWidth, pattern: .alternating, complement: true)
} else if overallIndex == 4 {
rng = PatternGenerator(allBits: totalBitWidth, pattern: .halfAndHalf, complement: false)
} else if overallIndex == 5 {
rng = PatternGenerator(allBits: totalBitWidth, pattern: .halfAndHalf, complement: true)
}
rng.generate(count: overallIndex)
// print(rng.current.pad(digits: totalBitWidth, radix: 2))
var testVector: TestVector = []
if simulateOnly {
testVector = externalTestVectors[overallIndex]
} else {
var assembled: BigUInt = 0
var bitsSoFar = 0
for input in inputs {
let value = rng.generate(bits: input.width)
assembled |= (value << bitsSoFar)
let value = rng.get(bits: input.width)
bitsSoFar += input.width
testVector.append(value)
}
Expand Down
Loading

0 comments on commit 88be7fc

Please sign in to comment.