diff --git a/erlang/apps/sim/src/sim_core.erl b/erlang/apps/sim/src/sim_core.erl
index bacafa4..2bb3a35 100644
--- a/erlang/apps/sim/src/sim_core.erl
+++ b/erlang/apps/sim/src/sim_core.erl
@@ -280,6 +280,8 @@ dispatch(Core, Mem, IR, EA) ->
8#215 -> sim_moves:handle_MOVEI(Core, Mem, IR, EA); % MOVMI = MOVEI
8#216 -> sim_moves:handle_MOVMM(Core, Mem, IR, EA);
8#217 -> sim_moves:handle_MOVMS(Core, Mem, IR, EA);
+ 8#242 -> sim_shifts:handle_LSH(Core, Mem, IR, EA);
+ 8#246 -> sim_shifts:handle_LSHC(Core, Mem, IR, EA);
8#250 -> sim_moves:handle_EXCH(Core, Mem, IR, EA);
8#251 -> sim_moves:handle_BLT(Core, Mem, IR, EA);
8#252 -> sim_arithmetic:handle_AOBJP(Core, Mem, IR, EA);
diff --git a/erlang/apps/sim/src/sim_shifts.erl b/erlang/apps/sim/src/sim_shifts.erl
new file mode 100644
index 0000000..7eb3c22
--- /dev/null
+++ b/erlang/apps/sim/src/sim_shifts.erl
@@ -0,0 +1,105 @@
+%%% -*- 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 .
+%%%
+%%%=============================================================================
+%%%
+%%% 2.5 Shift and Rotate
+
+-module(sim_shifts).
+
+-export([ handle_LSH/4
+ , handle_LSHC/4
+ ]).
+
+-include("sim_core.hrl").
+
+%% 2.5 Shift and Rotate ========================================================
+
+%% LSH - Logical Shift
+
+-spec handle_LSH(#core{}, sim_mem:mem(), IR :: word(), #ea{})
+ -> {#core{}, sim_mem:mem(), {ok, integer()} | {error, {module(), term()}}}.
+handle_LSH(Core, Mem, IR, EA) ->
+ AC = IR band 8#17,
+ CA = sim_core:get_ac(Core, AC),
+ Word = lsh(CA, EA#ea.offset),
+ set_ac_next_pc(Core, Mem, AC, Word).
+
+%% LSHC - Logical Shift Combined
+
+-spec handle_LSHC(#core{}, sim_mem:mem(), IR :: word(), #ea{})
+ -> {#core{}, sim_mem:mem(), {ok, integer()} | {error, {module(), term()}}}.
+handle_LSHC(Core, Mem, IR, EA) ->
+ AC = IR band 8#17,
+ CA0 = sim_core:get_ac(Core, AC),
+ CA1 = sim_core:get_ac(Core, (AC + 1) band 8#17),
+ {Word0, Word1} = lshc(CA0, CA1, EA#ea.offset),
+ set_acs_next_pc(Core, Mem, AC, Word0, Word1).
+
+%% Miscellaneous ===============================================================
+
+lsh(CA, Offset) ->
+ case Offset band (1 bsl 17) of
+ 0 -> % left shift
+ Count = Offset band ((1 bsl 8) - 1),
+ if Count >= 36 -> 0;
+ true -> (CA band ((1 bsl (36 - Count)) - 1)) bsl Count
+ end;
+ _ -> % right shift
+ Count = (-Offset) band ((1 bsl 8) - 1),
+ if Count >= 36 -> 0;
+ true -> CA bsr Count
+ end
+ end.
+
+lshc(CA0, CA1, Offset) ->
+ case Offset band (1 bsl 17) of
+ 0 -> % left shift
+ Count = Offset band ((1 bsl 8) - 1),
+ if Count >= 72 ->
+ {_Word0 = 0, _Word1 = 0};
+ Count >= 36 ->
+ Count1 = Count - 36,
+ Word0 = (CA1 band ((1 bsl (36 - Count1)) - 1)) bsl Count1,
+ {Word0, _Word1 = 0};
+ true ->
+ Word0 = ((CA0 band ((1 bsl (36 - Count)) - 1)) bsl Count) bor (CA1 bsr (36 - Count)),
+ Word1 = ((CA1 band ((1 bsl (36 - Count)) - 1)) bsl Count),
+ {Word0, Word1}
+ end;
+ _ -> % right shift
+ Count = (-Offset) band ((1 bsl 8) - 1),
+ if Count >= 72 ->
+ {_Word0 = 0, _Word1 = 0};
+ Count >= 36 ->
+ Word1 = CA0 bsr (Count - 36),
+ {_Word0 = 0, Word1};
+ true ->
+ Word0 = CA0 bsr Count,
+ Word1 = ((CA0 band ((1 bsl Count) - 1)) bsl (36 - Count)) bor (CA1 bsr Count),
+ {Word0, Word1}
+ end
+ end.
+
+set_acs_next_pc(Core, Mem, AC, Word0, Word1) ->
+ set_ac_next_pc(sim_core:set_ac(Core, AC, Word0), Mem, (AC + 1) band 8#17, Word1).
+
+set_ac_next_pc(Core, Mem, AC, Word) ->
+ sim_core:next_pc(sim_core:set_ac(Core, AC, Word), Mem).
diff --git a/erlang/apps/sim/test/sim_shifts_tests.erl b/erlang/apps/sim/test/sim_shifts_tests.erl
new file mode 100644
index 0000000..b8da560
--- /dev/null
+++ b/erlang/apps/sim/test/sim_shifts_tests.erl
@@ -0,0 +1,161 @@
+%%% -*- 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 .
+%%%
+%%%=============================================================================
+%%%
+%%% Test cases for 2.5 Shift and Rotate
+
+-module(sim_shifts_tests).
+
+-include("../src/sim_core.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-define(DEFAULT_FLAGS, (1 bsl ?PDP10_PF_USER)).
+
+-define(LOW18(X), ((X) band ((1 bsl 18) - 1))).
+-define(LOW36(X), ((X) band ((1 bsl 36) - 1))).
+
+-define(INSN(OP, AC, I, X, Y),
+ (((OP) bsl (35 - 8)) bor
+ ((AC) bsl (35 - 12)) bor
+ ((I) bsl (35 - 13)) bor
+ ((X) bsl (35 - 17)) bor
+ ?LOW18(Y))).
+
+-define(COMMA2(LEFT, RIGHT), ((?LOW18(LEFT) bsl 18) bor ?LOW18(RIGHT))). % LEFT,,RIGHT in MACRO-10
+
+-define(EA(S, O), #ea{section = S, offset = O, islocal = false}).
+-define(AC(A), ?EA(1, A)).
+
+-define(INSN_INVALID, ?INSN(0, 0, 0, 0, 0)).
+
+-define(OP_LSH, 8#242).
+-define(OP_LSHC, 8#246).
+
+%% 2.5 Shift and Rotate ========================================================
+
+%% LSH - Logical Shift
+
+lsh_test() ->
+ ACS =
+ [ {1, ?COMMA2(8#000111, 8#222333)} % AC1 = 000111222333
+ ],
+ Prog1 =
+ [ {1, 8#100, ?INSN(?OP_LSH, 1, 0, 0, 9)} % 1,,100/ LSH 1,9
+ , {1, 8#101, ?INSN_INVALID} % 1,,101/
+ ],
+ expect(Prog1, ACS, {1, 8#101}, ?DEFAULT_FLAGS,
+ [ {?AC(1), ?COMMA2(8#111222, 8#333000)} % AC1 = 111222333000
+ ]),
+ Prog2 =
+ [ {1, 8#100, ?INSN(?OP_LSH, 1, 0, 0, -9)} % 1,,100/ LSH 1,-9
+ , {1, 8#101, ?INSN_INVALID} % 1,,101/
+ ],
+ expect(Prog2, ACS, {1, 8#101}, ?DEFAULT_FLAGS,
+ [ {?AC(1), ?COMMA2(8#000000, 8#111222)} % AC1 = 000000111222
+ ]).
+
+%% LSHC - Logical Shift Combined
+
+lshc_test() ->
+ ACS =
+ [ {1, ?COMMA2(8#000111, 8#222333)} % AC1 = 000111222333
+ , {2, ?COMMA2(8#444555, 8#666777)} % AC2 = 444555666777
+ ],
+ Prog1 =
+ [ {1, 8#100, ?INSN(?OP_LSHC, 1, 0, 0, 9)} % 1,,100/ LSHC 1,9
+ , {1, 8#101, ?INSN_INVALID} % 1,,101/
+ ],
+ expect(Prog1, ACS, {1, 8#101}, ?DEFAULT_FLAGS,
+ [ {?AC(1), ?COMMA2(8#111222, 8#333444)} % AC1 = 111222333444
+ , {?AC(2), ?COMMA2(8#555666, 8#777000)} % AC2 = 555666777000
+ ]),
+ Prog2 =
+ [ {1, 8#100, ?INSN(?OP_LSHC, 1, 0, 0, 45)} % 1,,100/ LSHC 1,55
+ , {1, 8#101, ?INSN_INVALID} % 1,,101/
+ ],
+ expect(Prog2, ACS, {1, 8#101}, ?DEFAULT_FLAGS,
+ [ {?AC(1), ?COMMA2(8#555666, 8#777000)} % AC1 = 555666777000
+ , {?AC(2), ?COMMA2(8#000000, 8#000000)} % AC2 = 000000000000
+ ]),
+ Prog3 =
+ [ {1, 8#100, ?INSN(?OP_LSHC, 1, 0, 0, -9)} % 1,,100/ LSHC 1,-9
+ , {1, 8#101, ?INSN_INVALID} % 1,,101/
+ ],
+ expect(Prog3, ACS, {1, 8#101}, ?DEFAULT_FLAGS,
+ [ {?AC(1), ?COMMA2(8#000000, 8#111222)} % AC1 = 000000111222
+ , {?AC(2), ?COMMA2(8#333444, 8#555666)} % AC2 = 333444555666
+ ]),
+ Prog4 =
+ [ {1, 8#100, ?INSN(?OP_LSHC, 1, 0, 0, -45)} % 1,,100/ LSHC 1,-55
+ , {1, 8#101, ?INSN_INVALID} % 1,,101/
+ ],
+ expect(Prog4, ACS, {1, 8#101}, ?DEFAULT_FLAGS,
+ [ {?AC(1), ?COMMA2(8#000000, 8#000000)} % AC1 = 000000000000
+ , {?AC(2), ?COMMA2(8#000000, 8#111222)} % AC2 = 000000111222
+ ]).
+
+%% Common code to run short sequences ==========================================
+
+expect(Prog, ACs, ExpectedPC, ExpectedFlags, ExpectedEs) ->
+ {Core, Mem} = init(Prog, ACs),
+ {Core1, Mem1, {error, {sim_core, {dispatch, PC, _IR, _EA}}}} = sim_core:run(Core, Mem),
+ ActualPC = {PC bsr 18, PC band ((1 bsl 18) - 1)},
+ ?assertEqual(ExpectedPC, ActualPC),
+ ?assertEqual(ExpectedFlags, Core1#core.flags),
+ lists:foreach(fun({EA, ExpectedE}) ->
+ {ok, ActualE} = sim_core:c(Core1, Mem1, EA),
+ ?assertEqual(ExpectedE, ActualE)
+ end, ExpectedEs),
+ sim_mem:delete(Mem).
+
+init(Prog, ACs) ->
+ {PCSection, PCOffset} = prog_pc(Prog),
+ Mem = init_mem(Prog),
+ Core = init_core(PCSection, PCOffset, ACs),
+ {Core, Mem}.
+
+prog_pc([{Section, Offset, _Word} | _Rest]) -> {Section, Offset}.
+
+init_mem(Prog) -> init_mem(Prog, sim_mem:new()).
+
+init_mem([], Mem) -> Mem;
+init_mem([{Section, Offset, Word} | Rest], Mem) ->
+ init_word(Section, Offset, Word, Mem),
+ init_mem(Rest, Mem).
+
+init_word(Section, Offset, Word, Mem) ->
+ Address = (Section bsl 18) bor Offset,
+ PFN = Address bsr 9,
+ case sim_mem:mquery(Mem, PFN) of
+ false -> sim_mem:mmap(Mem, PFN, 4+2, core);
+ {_Prot, _What} -> ok
+ end,
+ ok = sim_mem:write_word(Mem, Address, Word).
+
+init_core(PCSection, PCOffset, ACs) ->
+ #core{ pc_section = PCSection
+ , pc_offset = PCOffset
+ , acs = init_acs(ACs, list_to_tuple(lists:duplicate(16, 0)))
+ , flags = ?DEFAULT_FLAGS
+ }.
+
+init_acs([], ACS) -> ACS;
+init_acs([{AC, Val} | Rest], ACS) -> init_acs(Rest, setelement(AC + 1, ACS, Val)).