SvPortSim (SvPortSim v0.1.0)

Copy Markdown View Source

A SvPortSim process owns exactly one simulator transport. The process implementation lives in SvPortSim.Server; this module is the public facade that keeps the stable user-facing API, validates command options, normalizes command bodies, and delegates normalized runtime operations to the server.

In production the default transport opens one external wrapper process with the port framing documented by SvPortSim.Protocol; tests and alternative runtimes may provide their own SvPortSim.Transport implementation.

Public contract

The initial stable public API surface is returned by public_functions/0:

Default-argument arities such as reset/1, tick/1, poke/3, peek/2, and stop/1 are exported for convenience.

Runtime commands return {:ok, body} for a successful wrapper response or {:error, error_body} for wrapper-side and Elixir-side failures. Error bodies follow the canonical shape documented by SvPortSim.Protocol and include string keys such as "code", "message", "details", and "fatal". stop/2 is terminal and returns :ok after a successful shutdown.

Options

start_link/1 and start/1 accept these simulation options:

  • :executable - path to the generated simulator wrapper executable. This is required when using the default SvPortSim.Transport.Port transport.
  • :args - command-line arguments passed to the wrapper executable. Defaults to [].
  • :transport - module implementing SvPortSim.Transport. Defaults to SvPortSim.Transport.Port.
  • :transport_opts - extra keyword options passed to the transport. Defaults to [].
  • :default_timeout - runtime command timeout in milliseconds or :infinity. Defaults to 5_000.

Standard GenServer start options :name, :debug, :spawn_opt, :hibernate_after, and :timeout are also accepted.

Runtime calls accept :timeout to override the instance default. reset/2 accepts :cycles and :reset; tick/2 accepts :cycles and :clock. poke/4 accepts encoded bit-vector values in the shape %{bits: bits, width: width} or %{"bits" => bits, "width" => width}. bits must be a string of 0, 1, x, or z characters whose length equals width.

Examples

iex> defmodule SvPortSim.DocTransport do
...>   @behaviour Elixir.SvPortSim.Transport
...>
...>   def open(_opts), do: {:ok, %{signals: %{}}}
...>
...>   def request(%{"op" => "reset"} = request, state, _timeout) do
...>     body = %{"cycles" => request["body"]["cycles"]}
...>     {:ok, response(request, body), %{state | signals: %{}}}
...>   end
...>
...>   def request(%{"op" => "tick"} = request, state, _timeout) do
...>     body = %{"cycles" => request["body"]["cycles"]}
...>     {:ok, response(request, body), state}
...>   end
...>
...>   def request(%{"op" => "poke"} = request, state, _timeout) do
...>     signal = request["body"]["signal"]
...>     value = request["body"]["value"]
...>     state = %{state | signals: Map.put(state.signals, signal, value)}
...>     {:ok, response(request, %{"signal" => signal}), state}
...>   end
...>
...>   def request(%{"op" => "peek"} = request, state, _timeout) do
...>     signal = request["body"]["signal"]
...>     value = Map.get(state.signals, signal, %{"bits" => "0", "width" => 1})
...>     {:ok, response(request, %{"value" => value}), state}
...>   end
...>
...>   def request(%{"op" => "shutdown"} = request, state, _timeout) do
...>     {:ok, response(request, %{}), state}
...>   end
...>
...>   def close(_state), do: :ok
...>
...>   defp response(request, body) do
...>     %{
...>       "v" => 1,
...>       "id" => request["id"],
...>       "kind" => "response",
...>       "op" => request["op"],
...>       "body" => body
...>     }
...>   end
...> end
iex> {:ok, sim} = Elixir.SvPortSim.start_link(transport: SvPortSim.DocTransport)
iex> Elixir.SvPortSim.reset(sim, cycles: 2)
{:ok, %{"cycles" => 2}}
iex> Elixir.SvPortSim.tick(sim)
{:ok, %{"cycles" => 1}}
iex> Elixir.SvPortSim.poke(sim, "enable", %{bits: "1", width: 1})
{:ok, %{"signal" => "enable"}}
iex> Elixir.SvPortSim.peek(sim, "enable")
{:ok, %{"value" => %{"bits" => "1", "width" => 1}}}
iex> Elixir.SvPortSim.stop(sim)
:ok
iex> Elixir.SvPortSim.start_link([])
{:error, {:missing_required_option, :executable}}
iex> {:reset, 2} in Elixir.SvPortSim.public_functions()
true

Summary

Types

Public API result returned by runtime commands except stop/2.

Per-command options accepted by poke/4, peek/3, and stop/2.

Encoded runtime bit-vector value accepted by poke/4.

Canonical runtime error body documented by SvPortSim.Protocol.

A running simulation instance pid, registered name, or via tuple.

Options accepted by reset/2.

Command response body returned by the wrapper.

A top-level SystemVerilog signal name exposed by the wrapper.

Options accepted by start_link/1, start/1, and child_spec/1.

Options accepted by tick/2.

Public API timeout in milliseconds or :infinity.

Functions

Returns a child spec for supervising one simulation instance.

Reads a signal from the simulator.

Writes an encoded value to a signal.

Returns the stable public API surface for one simulation instance.

Resets the simulator.

Starts one simulation instance without linking it to the caller.

Starts one simulation instance and links it to the caller.

Sends the terminal shutdown command and stops the simulation instance.

Advances the simulator by one or more clock cycles.

Types

api_result()

@type api_result() :: {:ok, response_body()} | {:error, error_body()}

Public API result returned by runtime commands except stop/2.

command_option()

@type command_option() :: {:timeout, timeout_ms()}

Per-command options accepted by poke/4, peek/3, and stop/2.

encoded_value()

@type encoded_value() :: %{
  optional(:bits) => String.t(),
  optional(:width) => pos_integer(),
  optional(String.t()) => String.t() | pos_integer()
}

Encoded runtime bit-vector value accepted by poke/4.

error_body()

@type error_body() :: %{required(String.t()) => term()}

Canonical runtime error body documented by SvPortSim.Protocol.

instance()

@type instance() :: GenServer.server()

A running simulation instance pid, registered name, or via tuple.

reset_option()

@type reset_option() ::
  command_option()
  | {:cycles, pos_integer()}
  | {:reset, signal()}
  | {:clock, signal()}

Options accepted by reset/2.

response_body()

@type response_body() :: %{required(String.t()) => term()}

Command response body returned by the wrapper.

signal()

@type signal() :: atom() | String.t()

A top-level SystemVerilog signal name exposed by the wrapper.

start_option()

@type start_option() ::
  {:executable, Path.t()}
  | {:args, [String.t()]}
  | {:transport, module()}
  | {:transport_opts, keyword()}
  | {:default_timeout, timeout()}
  | {:name, GenServer.name()}
  | {:debug, term()}
  | {:spawn_opt, [term()]}
  | {:hibernate_after, non_neg_integer()}
  | {:timeout, timeout_ms()}
  | {:id, term()}

Options accepted by start_link/1, start/1, and child_spec/1.

tick_option()

@type tick_option() ::
  command_option() | {:cycles, pos_integer()} | {:clock, signal()}

Options accepted by tick/2.

timeout_ms()

@type timeout_ms() :: pos_integer() | :infinity

Public API timeout in milliseconds or :infinity.

Functions

child_spec(opts)

@spec child_spec([start_option()]) :: Supervisor.child_spec()

Returns a child spec for supervising one simulation instance.

Pass :id to override the child id. Other options are passed to start_link/1.

peek(instance, signal, opts \\ [])

@spec peek(instance(), signal(), [command_option()]) :: api_result()

Reads a signal from the simulator.

poke(instance, signal, encoded_value, opts \\ [])

@spec poke(instance(), signal(), encoded_value(), [command_option()]) :: api_result()

Writes an encoded value to a signal.

encoded_value must be %{bits: bits, width: width} or %{"bits" => bits, "width" => width}. The value is normalized to string-keyed JSON-compatible data before it is sent to the transport.

public_functions()

@spec public_functions() :: [{atom(), non_neg_integer()}]

Returns the stable public API surface for one simulation instance.

Internal server functions are intentionally not included in this list.

Examples

iex> SvPortSim.public_functions() |> Enum.member?({:start_link, 1})
true
iex> SvPortSim.public_functions() |> Enum.member?({:poke, 4})
true

reset(instance, opts \\ [])

@spec reset(instance(), [reset_option()]) :: api_result()

Resets the simulator.

Options:

  • :cycles - number of reset cycles. Defaults to 1.
  • :reset - reset signal name. Defaults to wrapper policy.
  • :clock - clock signal name. Defaults to wrapper policy.
  • :timeout - per-request timeout.

start(opts)

@spec start([start_option()]) :: GenServer.on_start()

Starts one simulation instance without linking it to the caller.

start_link(opts)

@spec start_link([start_option()]) :: GenServer.on_start()

Starts one simulation instance and links it to the caller.

stop(instance, opts \\ [])

@spec stop(instance(), [command_option()]) :: :ok | {:error, error_body()}

Sends the terminal shutdown command and stops the simulation instance.

Returns :ok after the wrapper acknowledges shutdown and the transport is closed. Returns {:error, error_body} if the shutdown request fails.

tick(instance, opts \\ [])

@spec tick(instance(), [tick_option()]) :: api_result()

Advances the simulator by one or more clock cycles.

Options:

  • :cycles - number of cycles. Defaults to 1.
  • :clock - clock signal name. Defaults to wrapper policy.
  • :timeout - per-request timeout.