Sequential simulation

Header: mockturtle/algorithms/simulation_sequential.hpp

simulate evaluates the combinational logic of a network exactly once and has no notion of a register. On a sequential network it never assigns the register outputs at all, so every value in their fanout cone is meaningless.

simulate_sequential runs the network over a number of clock cycles instead. Every register starts at its reset value, the combinational logic is evaluated once per cycle, the primary outputs are recorded, and the register inputs are latched into the register outputs for the next cycle.

It returns a simulate_sequential_result, which carries two traces indexed by clock cycle: outputs[cycle][index] is the value primary output index took in that cycle, and states[cycle][index] the value register index held while that cycle was evaluated. The state trace is one entry longer than the output trace, because simulating n cycles crosses n + 1 state boundaries – reset_state() is the one the run started from and final_state() the one it ended in.

Examples

A design with no primary inputs runs off its reset state alone. A 4-bit LFSR seeded with 0b0001 walks through all 15 of its non-zero states:

sequential<aig_network> lfsr = ...;

auto const result = simulate_sequential<bool>( lfsr, 15, default_simulator<bool>( std::vector<bool>{} ) );

for ( auto const& outputs : result.outputs )
{
  std::cout << outputs[0];
}

The state trace answers what the registers were doing while that happened, and where they ended up:

#include <fmt/ranges.h>

for ( auto const& state : result.states )
{
  std::cout << fmt::format( "{}\n", fmt::join( state, "" ) );
}

assert( result.final_state() == result.reset_state() );  // a full period

Primary inputs that change from one cycle to the next are supplied by stimulus_simulator, which holds one assignment vector per cycle and repeats its last one for the rest of the run:

sequential<aig_network> shift_register = ...;

/* a single 1 on the input, then silence */
stimulus_simulator sim( { { true }, { false } } );

auto const result = simulate_sequential<bool>( shift_register, 6, sim );

Any simulator that works with simulate works here too, holding its assignment for the whole run. Truth tables, for instance, give the outputs of each cycle as a function of the primary inputs:

auto const result = simulate_sequential<kitty::dynamic_truth_table>(
    ntk, 3, default_simulator<kitty::dynamic_truth_table>( ntk.num_pis() ) );

Result

template<class SimulationType>
struct simulate_sequential_result

The result of simulating a sequential network.

Both traces are indexed by clock cycle first. outputs[cycle][index] is the value primary output index took in that cycle, and states[cycle][index] the value register index held while that cycle was evaluated.

states is one entry longer than outputs: simulating n cycles crosses n + 1 state boundaries. states.front() is the reset state the run started from and states.back() the state it ended in, so a run of zero cycles still reports the reset state and nothing else.

Public Functions

inline uint32_t num_cycles() const

Number of clock cycles simulated.

inline std::vector<SimulationType> const &reset_state() const

The state the registers were reset to.

inline std::vector<SimulationType> const &final_state() const

The state the registers held after the last cycle.

Public Members

std::vector<std::vector<SimulationType>> outputs

Primary output values, one vector per clock cycle.

std::vector<std::vector<SimulationType>> states

Register values, one vector per state boundary.

Parameters

struct simulate_sequential_params

Parameters for simulate_sequential.

Public Members

bool undefined_reset_value = {false}

Value a register starts at when its reset value is not defined.

A register may declare no reset value at all &#8212; register_init::dont_care or register_init::unknown, which is what register_t defaults to and what an AIGER latch with a nondeterministic reset reads back as. Simulation needs a concrete value, so this is the one it uses.

Simulators

class stimulus_simulator

Simulates Boolean assignments that change from cycle to cycle.

A simulator for simulate_sequential holding one assignment vector per clock cycle. Cycles past the end of the stimulus repeat its last assignment, so a stimulus shorter than the run holds its final value for the remainder of it.

Algorithm

template<class SimulationType, class Ntk, class Simulator = default_simulator<SimulationType>>
simulate_sequential_result<SimulationType> mockturtle::simulate_sequential(Ntk const &ntk, uint32_t num_cycles, Simulator const &sim = Simulator(), simulate_sequential_params const &ps = {})

Simulates a sequential network over a number of clock cycles.

Every register starts at its reset value, the combinational logic is evaluated once per cycle, the primary outputs are recorded, and the register inputs are latched into the register outputs for the next cycle.

This is what distinguishes it from simulate, which evaluates the combinational logic exactly once and has no notion of a register: on a sequential network simulate never assigns the register outputs at all, and every value in their fanout cone is meaningless.

The simulator follows the same concept as for simulate, with one addition. If it provides compute_pi( index, cycle ), that overload is used and the primary inputs may take a different value in every cycle &#8212; see stimulus_simulator. A simulator offering only compute_pi( index ) holds its assignment for the whole run, which is what a design with no primary inputs, such as an LFSR, wants anyway.

A register whose reset value is undefined starts at simulate_sequential_params::undefined_reset_value.

Required network functions:

  • num_registers

  • register_at

  • foreach_ro

  • foreach_ri

  • foreach_pi

  • foreach_po

  • foreach_gate

  • foreach_fanin

  • fanin_size

  • get_constant

  • constant_value

  • get_node

  • is_complemented

  • compute

Example

sequential<aig_network> aig = ...; // a 4-bit LFSR, say

auto const result = simulate_sequential<bool>( aig, 15, default_simulator<bool>( std::vector<bool>{} ) );

for ( auto const& outputs : result.outputs )
{
  std::cout << outputs[0];
}

// where it ended up
auto const& state = result.final_state();

Parameters:
  • ntk – The sequential network to simulate

  • num_cycles – Number of clock cycles to run

  • sim – The simulator

  • ps – Parameters

Returns:

The primary output values and the register values, per clock cycle