diff --git a/sim/Makefile b/sim/Makefile
index 333b814..a825da6 100644
--- a/sim/Makefile
+++ b/sim/Makefile
@@ -20,7 +20,7 @@ CC=gcc
CFLAGS=-O2 -g -Wall
CPPFLAGS=-I../include
-SIMOBJS=sim.o pdp10-virt-mem.o
+SIMOBJS=sim.o pdp10-virt-mem.o pdp10-ea.o
LIBOBJS=../lib/pdp10-elf36.o ../lib/pdp10-extint.o ../lib/pdp10-stdio.o
sim: $(SIMOBJS) $(LIBOBJS)
diff --git a/sim/pdp10-ea.c b/sim/pdp10-ea.c
new file mode 100644
index 0000000..6a0e624
--- /dev/null
+++ b/sim/pdp10-ea.c
@@ -0,0 +1,173 @@
+/*
+ * pdp10-ea.c -- PDP10 Effective-Address Calculation
+ * Copyright (C) 2018 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 .
+ */
+
+#include "pdp10-core.h"
+#include "pdp10-arith.h"
+
+/* c.f. Figure 1.9 in the Toad-1 Architecture Manual */
+static
+uint64_t pdp10_section_zero_ea(struct pdp10_cpu *cpu, uint64_t MB)
+{
+
+ /* Instruction Fetch (not done here)
+ *
+ * MB := C(PC)
+ * IR := MB_<0:12>
+ */
+
+ for (;;) {
+ uint32_t Y = BITS36(MB, 18, 35); /* Y := MB_<18:35> */
+ unsigned int X = BITS36(MB, 14, 17); /* X := MB_<14:17> */
+ unsigned int I = BIT36(MB, 13); /* I := MB_<13> */
+ uint32_t E; /* 18 bits */
+
+ if (X == 0) {
+ E = Y;
+ } else {
+ E = BITS36LOW18(Y + BITS36LOW18(cpu->AC[X])); /* E := Y + C(X)_<18:35> */
+ }
+
+ if (I == 0) {
+ return E;
+ }
+
+ /* MB := C(E), but handle ACs */
+ if (E <= 017) {
+ MB = cpu->AC[E];
+ } else {
+ MB = pdp10_vmem_read(&cpu->vmem, E);
+ if (MB == (uint64_t)-1)
+ return MB; /* propagate page fault */
+ }
+ }
+}
+
+/* c.f. Figure 1.11 in the Toad-1 Architecture Manual */
+static
+uint64_t pdp10_extended_ea(struct pdp10_cpu *cpu, uint64_t MB)
+{
+ uint32_t E; /* 30 bits */
+
+ /* Instruction Fetch (not done here)
+ *
+ * if PC_<18:31> == 0 then MB := C(PC_<32:35>)
+ * else MB := C(PC_<6:35>)
+ * IR := MB_<0:12>
+ */
+ E = BITS36(cpu->PC, 6, 17) << 18; /* E_<6:17> := PC_<6:17> */
+
+ /* Local-Format Address Word */
+ /* A Local Address is in the same section as this Address Word */
+
+ for (;;) {
+ unsigned int I = BIT36(MB, 13); /* I := MB_<13> */
+ {
+ uint32_t Y = BITS36(MB, 18, 35); /* Y_<18:35> := MB_<18:35> */
+ unsigned int X = BITS36(MB, 14, 17); /* X := MB_<14:17> */
+ /* Indexed Address? Test X field. */
+ if (X == 0) {
+ /* No Indexing */
+ E = BITS36(E, 6, 17) | Y; /* E_<18:35> := Y_<18:35> */
+ } else {
+ /* X field != 0 */
+ pdp10_uint36_t CX = cpu->AC[X];
+ /* Test Section Number in E_<6:17> */
+ if (BITS36(E, 6, 17) == 0) { /* Section 0 */
+ /* Local Index */
+ E = BITS36(E, 6, 17) | BITS36LOW18(CX + Y); /* E_<18:35> := C(X)_<18:35> + Y_<18:35> */
+ } else {
+ /* Section != 0 */
+ /* Test C(X). Global Index when (C(X)_<0> == 0) && (C(X)_<6:17> != 0) */
+ if (!(BIT36(CX, 0) == 0 && BITS36(CX, 6, 17) != 0)) {
+ /* Local Index */
+ E = BITS36(E, 6, 17) | BITS36LOW18(CX + Y); /* E_<18:35> := C(X)_<18:35> + Y_<18:35> */
+ } else {
+ /* Global Index */
+ Y = pdp10_sext_uint18(Y); /* Y_<6:17> := 07777 * Y_<18> */
+ E = BITS36((CX + Y), 6, 35); /* E_<6:35> := C(X)_<6:35> + Y_<6:35> */
+ }
+ }
+ }
+ }
+
+ for (;;) {
+ /* Indirect Addressing? Test I bit. */
+ if (I == 0) { /* Done! */
+ /* E is the Effective Address */
+ /* The "XCT Continues" loop is implemented by the caller. */
+ return E;
+ }
+ /* Fetch the Indirect Word: MB := C(E) */
+ /* FIXME: handle ACs? */
+ MB = pdp10_vmem_read(&cpu->vmem, E);
+ if (MB == (uint64_t)-1)
+ return MB; /* propagate page fault */
+ /* Non-Zero Section? Test E_<6:17> */
+ if (BITS36(E, 6, 17) == 0) {
+ /* Section 0 */
+ break;
+ } else {
+ /* Section != 0 */
+ /* Decode Indirect Word MB_<0:1> */
+ switch (BITS36(MB, 0, 1)) {
+ case 2:
+ /* Local Indirect */
+ break;
+ case 3:
+ /* Page Failure */
+ return (uint64_t)-1;
+ case 0:
+ case 1: {
+ /* Global Indirect Word */
+ uint32_t Y = BITS36(MB, 6, 35); /* Y := MB_<6:35> */
+ unsigned int X = BITS36(MB, 2, 5); /* X := MB_<2:5> */
+ I = BIT36(MB, 1); /* I := MB_<1> */
+ /* Indexed Address? Test X field. */
+ if (X == 0) {
+ E = Y; /* E_<6:35> := Y_<6:35> */
+ } else {
+ E = BITS36((cpu->AC[X] + Y), 6, 35); /* E_<6:35> := C(X)_6:35 + Y_<6:35> */
+ }
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+static
+int pdp10_section_zero_p(struct pdp10_cpu *cpu)
+{
+ /* My interpretation is that the choice of EA procedure is only based on
+ * whether the CPU is extended or not, so an extended CPU should always
+ * use the extended EA procedure, regardless of which section PC is in.
+ */
+ return 0;
+}
+
+uint64_t pdp10_ea(struct pdp10_cpu *cpu, uint64_t MB)
+{
+ if (pdp10_section_zero_p(cpu))
+ return pdp10_section_zero_ea(cpu, MB);
+ else
+ return pdp10_extended_ea(cpu, MB);
+}
diff --git a/sim/pdp10-ea.h b/sim/pdp10-ea.h
new file mode 100644
index 0000000..2212df9
--- /dev/null
+++ b/sim/pdp10-ea.h
@@ -0,0 +1,31 @@
+/*
+ * pdp10-ea.h -- PDP10 Effective-Address Calculation
+ * Copyright (C) 2018 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 .
+ */
+#ifndef PDP10_EA_H
+#define PDP10_EA_H
+
+#include "pdp10-core.h"
+
+/* This performs Effective Address Calculation, but not Instruction Fetch
+ * or looping on XCT instructions; the caller is assumed to handle that.
+ * Returns (uint64_t)-1 on failure (page fault), a pdp10_uint36_t word on success.
+ */
+uint64_t pdp10_ea(struct pdp10_cpu *cpu, uint64_t MB);
+
+#endif /* PDP10_EA_H */