diff --git a/erlang/Makefile b/erlang/Makefile
index d827816..92b24ad 100644
--- a/erlang/Makefile
+++ b/erlang/Makefile
@@ -21,7 +21,7 @@ REBAR3=$(shell type -p rebar3 || echo ./rebar3)
REBAR3_GIT=https://github.com/erlang/rebar3.git
REBAR3_VSN=3.7.5
-PROGRAMS=8to9 ar as ld nm od readelf
+PROGRAMS=8to9 ar as ld nm od readelf sim
default: compile link
diff --git a/erlang/apps/sim/src/sim.app.src b/erlang/apps/sim/src/sim.app.src
new file mode 100644
index 0000000..dcbbf16
--- /dev/null
+++ b/erlang/apps/sim/src/sim.app.src
@@ -0,0 +1,25 @@
+%%% Copyright (C) 2020 Mikael Pettersson
+%%%
+%%% This file is part of pdp10-tools.
+%%%
+%%% pdp10-tools is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% pdp10-tools is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with pdp10-tools. If not, see .
+
+{application, sim,
+ [{description, "simulator for pdp10-elf"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {applications, [kernel, stdlib, lib]},
+ {env, []},
+ {modules, []}
+ ]}.
diff --git a/erlang/apps/sim/src/sim.erl b/erlang/apps/sim/src/sim.erl
new file mode 100644
index 0000000..73a39ee
--- /dev/null
+++ b/erlang/apps/sim/src/sim.erl
@@ -0,0 +1,159 @@
+%%% -*- erlang-indent-level: 2 -*-
+%%%
+%%% simulator for pdp10-elf
+%%% Copyright (C) 2020 Mikael Pettersson
+%%%
+%%% This file is part of pdp10-tools.
+%%%
+%%% pdp10-tools is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% pdp10-tools is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with pdp10-tools. If not, see .
+
+-module(sim).
+
+-export([ main/1
+ , format_error/1
+ ]).
+
+-record(options,
+ { exe = "" :: string() % exe
+ , argv = [] :: [string()] % argv for exe
+ , stack_size = 510*512 :: pos_integer() % stack size
+ , trace = false :: boolean() % trace mode
+ }).
+
+%% Command-line interface ======================================================
+
+main(Argv) ->
+ escript_runtime:start(fun main_/1, Argv).
+
+-spec main_([string()]) -> no_return().
+main_(Argv) ->
+ case sim(Argv) of
+ ok -> halt(0);
+ {error, Reason} ->
+ escript_runtime:fatal("~s\n", [error:format(Reason)])
+ end.
+
+%% Usage: pdp10-elf-sim [] []
+%%
+%% Simulates the execution of the given pdp10-elf .
+%%
+%% Options for the simulator:
+%%
+%% -s
+%% --stack-size=
+%% Sets the stack size for main thread.
+%% Default: 261120 words (510 pages, one section with first and last
+%% page unmapped).
+%%
+%% -t []
+%% --trace[=]
+%% Enables ( true, non-zero, or absent) or disables (
+%% zero or false) tracing. Default: false.
+%%
+%% -v
+%% --version
+%% Outputs the version of the simulator and exits.
+
+sim(Argv) ->
+ case getopt:parse(Argv, "+s:t::v",
+ [ { "stack-size", required, $s }
+ , { "trace", optional, $t }
+ , { "version", no, $v }
+ ]) of
+ {ok, {Opts0, NonOpts0}} ->
+ case parse_options(Opts0, NonOpts0) of
+ {ok, Options} -> do_sim(Options);
+ Else -> Else % ok or {error, _Reason}
+ end;
+ {error, _Reason} = Error -> Error
+ end.
+
+parse_options(Opts, NonOpts) ->
+ parse_options(Opts, NonOpts, #options{}).
+
+parse_options([Opt | Opts], NonOpts, Options) ->
+ case parse_option(Opt, Options) of
+ {ok, NewOptions} -> parse_options(Opts, NonOpts, NewOptions);
+ Else -> Else % ok or {error, _Reason}
+ end;
+parse_options([], [Exe | Argv], Options) ->
+ {ok, Options#options{exe = Exe, argv = Argv}};
+parse_options([], [], _Options) ->
+ {error, {?MODULE, no_exe}}.
+
+parse_option(Opt, Options) ->
+ case Opt of
+ {$s, _} -> handle_stack_size(Opt, Options);
+ {$t, _} -> handle_trace(Opt, Options);
+ $t -> handle_trace(Opt, Options);
+ $v -> handle_version(Opt, Options)
+ end.
+
+%% Option Handlers =============================================================
+
+handle_stack_size({$s, Arg}, Options) ->
+ case strtol:parse(Arg, _Base = 0) of
+ {ok, {Number, Rest}} ->
+ case Rest of
+ [] -> do_handle_stack_size(Arg, Number, Options);
+ [$p] -> do_handle_stack_size(Arg, Number*512, Options);
+ _ -> invalid_stack_size(Arg)
+ end;
+ {error, _Reason} = Error -> Error
+ end.
+
+do_handle_stack_size(_Arg, Number, Options) when Number > 0 ->
+ {ok, Options#options{stack_size = Number}};
+do_handle_stack_size(Arg, _Number, _Options) ->
+ invalid_stack_size(Arg).
+
+invalid_stack_size(Arg) -> {error, {?MODULE, {invalid_stack_size, Arg}}}.
+
+handle_trace({$t, Arg}, Options) ->
+ case Arg of
+ "false" -> do_handle_trace(false, Options);
+ "true" -> do_handle_trace(true, Options);
+ _ ->
+ case strtol:parse(Arg, _Base = 0) of
+ {ok, {Number, _Rest = []}} -> do_handle_trace(Number =/= 0, Options);
+ {ok, {_Number, [_|_]}} -> {error, {?MODULE, {invalid_trace, Arg}}};
+ {error, _Reason} = Error -> Error
+ end
+ end;
+handle_trace($t, Options) -> do_handle_trace(true, Options).
+
+do_handle_trace(Mode, Options) -> {ok, Options#options{trace = Mode}}.
+
+handle_version($v, _Options) ->
+ version(),
+ ok. % causes main_/1 to halt(0)
+
+version() ->
+ io:format("pdp10-tools-sim version 0.1\n").
+
+%% Simulation ==================================================================
+
+do_sim(_Options) -> ok. % FIXME
+
+%% Error Formatting ============================================================
+
+-spec format_error(term()) -> io_lib:chars().
+format_error(Reason) ->
+ case Reason of
+ {invalid_stack_size, Arg} ->
+ io_lib:format("invalid value for -s/--stack-size: ~s", [Arg]);
+ {invalid_trace, Arg} ->
+ io_lib:format("invalid value for -t/--trace: ~s", [Arg]);
+ no_exe -> "no given"
+ end.
diff --git a/erlang/rebar.config b/erlang/rebar.config
index 8bb6b0d..95fa370 100644
--- a/erlang/rebar.config
+++ b/erlang/rebar.config
@@ -48,6 +48,7 @@
, {nm, main, 1}
, {od, main, 1}
, {readelf, main, 1}
+ , {sim, main, 1}
%% indirect calls to Module:format_error/1
, {assemble, format_error, 1}
, {getopt, format_error, 1}
@@ -61,6 +62,7 @@
, {pdp10_stdio, format_error, 1}
, {scan, format_error, 1}
, {scan_state, format_error, 1}
+ , {sim, format_error, 1}
, {strtol, format_error, 1}
%% actual unused exports
, {pdp10_opcodes, cpu_device_from_name, 2}
@@ -77,4 +79,5 @@
, {nm, [{escript_main_app, nm}]}
, {od, [{escript_main_app, od}]}
, {readelf, [{escript_main_app, readelf}]}
+ , {sim, [{escript_main_app, sim}]}
]}.