/*
 * Copyright (C) 2014-2016 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

/*
 * For most of the comments, infos, ... see:
 * ARMv7-A/R:
 *	ARM Architecture Reference Manual
 *	ARMv7-A and ARMv7-R edition
 *	ARM DDI 0406C.c (ID051414)
 * ARMv7-M:
 *	ARMv7-M Architecture
 *	Reference Manual
 *	ARM DDI 0403E.b (ID120114)
 */

/*
 * Data-processing instructions
 * A3.4, page A3-7.
 */
static uint32_t
NAME_(alu)(
	struct cpssp *cpssp,
	uint8_t opcode,
	uint32_t op0,
	uint32_t op1,
	int *cp,
	int *vp
)
{
	uint32_t res;

	switch (opcode) {
	case 0x0:
		/* AND, A4.1.4, A4-8 */
		res = NAME_(ands)(cpssp, op0, op1, cp, vp);
		break;
	case 0x1:
		/* EOR, A4.1.18, A4-32 */
		res = NAME_(eors)(cpssp, op0, op1, cp, vp);
		break;
	case 0x2:
		/* SUB, A4.1.106, A4-208 */
		res = NAME_(sbcs)(cpssp, op0, op1, 1, cp, vp);
		break;
	case 0x3:
		/* RSB, A4.1.60, A4-115 */
		res = NAME_(sbcs)(cpssp, op1, op0, 1, cp, vp);
		break;
	case 0x4:
		/* ADD, A4.1.3, A4-6 */
		res = NAME_(adcs)(cpssp, op0, op1, 0, cp, vp);
		break;
	case 0x5:
		/* ADC, A4.1.2, A4-4 */
		res = NAME_(adcs)(cpssp, op0, op1, cpssp->NAME.flag_c, cp, vp);
		break;
	case 0x6:
		/* SBC, A4.1.65, A4-125 */
		res = NAME_(sbcs)(cpssp, op0, op1, cpssp->NAME.flag_c, cp, vp);
		break;
	case 0x7:
		/* RSC, A4.1.61, A4-117 */
		res = NAME_(sbcs)(cpssp, op1, op0, cpssp->NAME.flag_c, cp, vp);
		break;
	case 0x8:
		/* TST, A4.1.117, A4-230 */
		res = NAME_(ands)(cpssp, op0, op1, cp, vp);
		/* res not used. */
		break;
	case 0x9:
		/* TEQ, A4.1.116, A4-228 */
		res = NAME_(eors)(cpssp, op0, op1, cp, vp);
		/* res not used. */
	case 0xa:
		/* CMP, A4.1.15, A4-28 */
		res = NAME_(sbcs)(cpssp, op0, op1, 1, cp, vp);
		/* res not used. */
		break;
	case 0xb:
		/* CMN, A4.1.14, A4-26 */
		res = NAME_(adcs)(cpssp, op0, op1, 0, cp, vp);
		/* res not used. */
		break;
	case 0xc:
		/* ORR, A4.1.42, A4-84 */
		res = NAME_(orrs)(cpssp, op0, op1, cp, vp);
		break;
	case 0xd:
		/* MOV, A4.1.35, A4-68 */
		res = NAME_(movs)(cpssp, op0, op1, cp, vp);
		break;
	case 0xe:
		/* BIC, A4.1.6, A4-12 */
		res = NAME_(bics)(cpssp, op0, op1, cp, vp);
		break;
	case 0xf:
		/* MVN, A4.1.41, A4-82 */
		res = NAME_(mvns)(cpssp, op0, op1, cp, vp);
		break;
	default: assert(0); /* Cannot happen. */
	}

	return res;
}

/*
 * Immediate shifts
 * A5.1.1, page A5-3.
 */
static uint32_t
NAME_(imm_shift)(struct cpssp *cpssp, uint32_t insn)
{
	uint8_t shift;
	uint8_t op;
	unsigned int rm;

	shift = (insn >> 7) & 0x1f;
	op = (insn >> 5) & 0x3;
	rm = (insn >> 0) & 0xf;

	return NAME_(shift)(cpssp, rm, op, shift);
}

/*
 * Register shifts
 * A5.1.1, page A5-3.
 */
static uint32_t
NAME_(reg_shift)(struct cpssp *cpssp, uint32_t insn)
{
	unsigned int rs;
	uint8_t shift;
	uint8_t op;
	unsigned int rm;
	uint32_t val;

	rs = (insn >> 8) & 0xf;
	op = (insn >> 5) & 0x3;
	rm = (insn >> 0) & 0xf;

	shift = NAME_(load_reg)(cpssp, rs) & 0xff;
	val = NAME_(load_reg)(cpssp, rm);

	switch (op) {
	case 0x0: /* LSL */
		if (shift == 0) {
			cpssp->NAME.flag_shift_c = cpssp->NAME.flag_c;
		} else if (shift < 32) {
			cpssp->NAME.flag_shift_c = (val >> (32 - shift)) & 1;
			val <<= shift;
		} else if (shift == 32) {
			cpssp->NAME.flag_shift_c = (val >> 0) & 1;
			val = 0x00000000;
		} else {
			cpssp->NAME.flag_shift_c = 0;
			val = 0x00000000;
		}
		break;
	case 0x1: /* LSR */
		if (shift == 0) {
			cpssp->NAME.flag_shift_c = cpssp->NAME.flag_c;
		} else if (shift < 32) {
			cpssp->NAME.flag_shift_c = (val >> (shift - 1)) & 1;
			val >>= shift;
		} else if (shift == 32) {
			cpssp->NAME.flag_shift_c = (val >> 31) & 1;
			val = 0x00000000;
		} else {
			cpssp->NAME.flag_shift_c = 0;
			val = 0x00000000;
		}
		break;
	case 0x2: /* ASR */
		if (shift == 0) {
			cpssp->NAME.flag_shift_c = cpssp->NAME.flag_c;
		} else if (shift < 32) {
			cpssp->NAME.flag_shift_c = (val >> (shift - 1)) & 1;
			val = (uint32_t) ((int32_t) val >> shift);
		} else if (32 <= shift) {
			if ((val >> 31) & 1) {
				val = 0xffffffff;
				cpssp->NAME.flag_shift_c = 1;
			} else {
				val = 0x00000000;
				cpssp->NAME.flag_shift_c = 0;
			}
		}
		break;
	case 0x3: /* ROR */
		if (shift == 0) {
			cpssp->NAME.flag_shift_c = cpssp->NAME.flag_c;
		} else if ((shift & 0x1f) == 0) {
			cpssp->NAME.flag_shift_c = (val >> 31) & 1;
		} else {
			shift &= 0x1f;
			cpssp->NAME.flag_shift_c = (val >> (shift - 1)) & 1;
			val = (val << (32 - shift)) | (val >> shift);
		}
		break;
	default:
		assert(0); /* Cannot happen. */
	}

	return val;
}

/*
 * Immediate
 * A5.1.3, page A5-6.
 */
static uint32_t
NAME_(imm)(struct cpssp *cpssp, uint32_t insn)
{
	uint32_t val;
	uint8_t shift;

	shift = ((insn >> 8) & 0xf) << 1;
	val = insn & 0xff;

	if (shift == 0) {
		val = val;
		cpssp->NAME.flag_shift_c = cpssp->NAME.flag_c;
	} else {
		val = (val << (32 - shift)) | (val >> shift);
		cpssp->NAME.flag_shift_c = (val >> 31) & 1;
	}

	return val;
}

/*
 * Immediate Offset
 * A5.2.2, page A5-20.
 */
static uint32_t
NAME_(imm_offset)(struct cpssp *cpssp, uint32_t insn)
{
	return insn & 0xfff;
}

/*
 * Scaled Register Offset
 * A5.2.4, page A5-22.
 */
static uint32_t
NAME_(reg_offset)(struct cpssp *cpssp, uint32_t insn)
{
	uint8_t shift;
	uint8_t op;
	unsigned int rm;
	uint32_t val;

	shift = (insn >> 7) & 0x1f;
	op = (insn >> 5) & 0x3;
	rm = (insn >> 0) & 0xf;

	val = NAME_(load_reg)(cpssp, rm);

	switch (op) {
	case 0x0: /* LSL */
		if (shift == 0) {
			/* Nothing to do... */
		} else {
			val <<= shift;
		}
		break;
	case 0x1: /* LSR */
		if (shift == 0) {
			val = 0x00000000;
		} else {
			val >>= shift;
		}
		break;
	case 0x2: /* ASR */
		if (shift == 0) {
			if ((val >> 31) & 1) {
				val = 0xffffffff;
			} else {
				val = 0x00000000;
			}
		} else {
			val = (uint32_t) ((int32_t) val >> shift);
		}
		break;
	case 0x3:
		if (shift == 0) {
			/* RRX */
			val = (cpssp->NAME.flag_c << 31) | (val >> 1);
		} else {
			/* ROR */
			val = (val << (32 - shift)) | (val >> shift);
		}
		break;
	default:
		assert(0);
	}

	return val;
}

/*
 * Instruction set encoding
 *
 * A3.1, A3-2
 * Figure A3-1, A3-2
 */
static void
NAME_(insn_exec)(struct cpssp *cpssp, uint32_t insn)
{
	uint8_t opcode;
	int p, u, b, i, w, l, s, h;
	uint8_t rn, rd, rm, rs;
	uint16_t reglist;
	uint32_t addr, offset;
	uint32_t op0, op1, op2, res;
	uint64_t res64;
	int c, v;

	if ((insn & 0xf0000000) == 0xf0000000) {
		/*
		 * Unconditional instructions.
		 *
		 * Figure A3-6, A3-42
		 */
#if 6 <= CONFIG_VERSION
		if ((insn & 0xfff10020) == 0xf1000000) {
			/*
			 * Change Processor State
			 *
			 * CPS, A4.1.16, A4-29
			 */
			uint8_t imod;
			uint8_t mmod;
			uint8_t a;
			uint8_t i;
			uint8_t f;
			uint8_t mode;

			assert((insn & 0x0000fe00) == 0x00000000);

			imod = (insn >> 18) & 3;
			mmod = (insn >> 17) & 1;
			a = (insn >> 8) & 1;
			i = (insn >> 7) & 1;
			f = (insn >> 6) & 1;
			mode = (insn >> 0) & 0b11111;

			assert(imod != 0b01); /* UNPREDICTABLE */
			assert(imod != 0b00 || mmod); /* UNPREDICTABLE */

			if ((imod >> 1) & 1) {
				/* Set A, I, F flags. */
				if (a) {
					assert(0); /* FIXME */
				}
				if (i) {
					NAME_(primask_set)(cpssp, imod & 1);
				}
				if (f) {
					assert(0); /* FIXME */
				}
			}
			if (mmod) {
				/* Set mode. */
				assert(0); /* FIXME */
			}
		} else
#endif /* 6 <= CONFIG_VERSION */

#if 6 <= CONFIG_VERSION
		if ((insn & 0xffff00f0) == 0xf1010000) {
			/*
			 * Set Endianness
			 *
			 * SETEND
			 */
			assert(0); /* FIXME */
		} else
#endif /* 6 <= CONFIG_VERSION */

#if 6 <= CONFIG_VERSION
		if ((insn & 0xfd70f000) == 0xf550f000) {
			/*
			 * Cache Preload
			 *
			 * PLD
			 */
			assert(0); /* FIXME */
		} else
#endif /* 6 <= CONFIG_VERSION */

#if 6 <= CONFIG_VERSION
		if ((insn & 0xfe5f0f00) == 0xf84d0500) {
			/*
			 * Save Return State
			 *
			 * SRS
			 */
			assert(0); /* FIXME */
		} else
#endif /* 6 <= CONFIG_VERSION */

#if 6 <= CONFIG_VERSION
		if ((insn & 0xfe500f00) == 0xf8100a00) {
			/*
			 * Return From Exception
			 *
			 * SRS
			 */
			assert(0); /* FIXME */
		} else
#endif /* 6 <= CONFIG_VERSION */

#if 5 <= CONFIG_VERSION
		if ((insn & 0xfe000000) == 0xfa000000) {
			/*
			 * Branch with Link and change to Thumb
			 *
			 * BLX
			 */
			assert(0); /* FIXME */
		} else
#endif /* 5 <= CONFIG_VERSION */

#if 6 <= CONFIG_VERSION
		/*
		 * MCRR2
		 * MRRC2
		 */
		/* FIXME */
#endif /* 6 <= CONFIG_VERSION */

#if 5 <= CONFIG_VERSION
		/*
		 * STC2
		 * LDC2
		 * CDP2
		 * MCR2
		 * MRC2
		 */
		/* FIXME */
#endif /* 5 <= CONFIG_VERSION */

		{
			assert(0); /* FIXME */
		}

	} else if (NAME_(condition)(cpssp, (insn >> 28) & 0xf)) {
		/*
		 * NOTE:
		 * Be careful! Sequence order of "if" matters!!!
		 */
		if ((insn & 0x0f900010) == 0x01000000) {
			/*
			 * Miscellaneous instructions.
			 *
			 * Figure A3-4, A3-37
			 *
			 * MRS, A4.1.38, A4-74
			 * MSR, A4.1.39, A4-76
			 */
			if ((insn & 0x0fb000f0) == 0x01000000) {
				/* MRS, A4.1.38, A4-74 */
				assert((insn & 0x000f0f00) == 0x000f0000);

				rd = (insn >> 12) & 0xf;

				if ((insn >> 22) & 1) {
					/* MRS Rd, SPSR */
					res = NAME_(load_spsr)(cpssp);
				} else {
					/* MRS Rd, CPSR */
					res = NAME_(load_cpsr)(cpssp);
				}
				NAME_(store_reg)(cpssp, rd, res);

			} else if ((insn & 0x0fb000f0) == 0x01200000) {
				/* MSR, A4.1.39, A4-76 */
				uint32_t bytemask;

				assert((insn & 0x0000ff00) == 0x0000f000);
				assert((insn & 0x000f0000) != 0x00000000);

				rn = (insn >> 16) & 0xf;
				bytemask = 0x00000000;
				if ((rn >> 0) & 1) bytemask |= 0x000000ff;
				if ((rn >> 1) & 1) bytemask |= 0x0000ff00;
				if ((rn >> 2) & 1) bytemask |= 0x00ff0000;
				if ((rn >> 3) & 1) bytemask |= 0xff000000;

				if ((insn >> 22) & 1) {
					/* MSR SPSR, Rd */
					op0 = NAME_(load_spsr)(cpssp);
				} else {
					/* MSR CPSR, Rd */
					op0 = NAME_(load_cpsr)(cpssp);
				}
				if ((insn >> 25) & 1) {
					op1 = NAME_(imm)(cpssp, insn);
				} else {
					rm = (insn >> 0) & 0xf;
					op1 = NAME_(load_reg)(cpssp, rm);
				}
				res = (op0 & ~bytemask) | (op1 & bytemask);
				if ((insn >> 22) & 1) {
					NAME_(store_spsr)(cpssp, res);
				} else {
					NAME_(store_cpsr)(cpssp, res);
				}

			} else {
				assert(0); /* FIXME */
			}

		} else if ((insn & 0x0e000010) == 0x00000000) {
			/*
			 * Data processing immediate shift.
			 *
			 * Data-processing instructions, A3.4, A3-7
			 * Instruction encoding, A3.4.1, A3-8
			 * List of data-processing Instructions, A3.4.2, A3-9
			 * Addressing Mode 1 - Data-processing operands,
			 * A5.1, A5-2
			 * Encoding, A5.1.1, A5-3
			 */
			opcode = (insn >> 21) & 0xf;
			s = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			rd = (insn >> 12) & 0xf;

			assert(! s || rd != 15); /* FIXME */

			op0 = NAME_(load_reg)(cpssp, rn);
			op1 = NAME_(imm_shift)(cpssp, insn);
			res = NAME_(alu)(cpssp, opcode, op0, op1, &c, &v);
			if (opcode < 0x8
			 || 0xc <= opcode) {
				NAME_(store_reg)(cpssp, rd, res);
			} else {
				assert(s);
			}
			if (s) {
				NAME_(store_flags)(cpssp, c, v, res);
			}

		} else if ((insn & 0x0f900090) == 0x01000010) {
			/*
			 * Miscellaneous instructions.
			 *
			 * Figure A3-4, A3-37
			 *
			 * BKPT, A4.1.7, A4-14
			 * BLX(2), A4.1.9, A4-16
			 * BX, A4.1.10, A4-20
			 * CLZ, A4.1.13, A4-25
			 */
#if (4 == CONFIG_VERSION && CONFIG_THUMB) || 5 <= CONFIG_VERSION
			if ((insn & 0x0ff000f0) == 0x01200010) {
				/*
				 * Branch/exchange instruction set Thumb
				 *
				 * BX, A4.1.10, A4-20
				 */
				assert((insn & 0x000fff00) == 0x000fff00);

				rm = (insn >> 0) & 0xf;
				op0 = NAME_(load_reg)(cpssp, rm);
				cpssp->NAME.flag_t = op0 & 1;
				NAME_(store_pc)(cpssp, op0 & ~1);

			} else
#endif /* (4 == CONFIG_VERSION && CONFIG_THUMB) || 5 <= CONFIG_VERSION */
#if 5 <= CONFIG_VERSION
			if ((insn & 0x0ff000f0) == 0x01600010) {
				/* CLZ, A4.1.13, A4-25 */
				assert((insn & 0x000f0f00) == 0x000f0f00);

				rd = (insn >> 12) & 0xf;
				rm = (insn >> 0) & 0xf;
				op0 = NAME_(load_reg)(cpssp, rm);
				res = NAME_(clz)(cpssp, op0);
				NAME_(store_reg)(cpssp, rd, res);

			} else
#endif /* 5 <= CONFIG_VERSION */
#if 5 <= CONFIG_VERSION
			if ((insn & 0x0ff000f0) == 0x01200030) {
				/*
				 * Branch and link/exchange instruction set Thumb
				 *
				 * BLX(2), A4.1.9, A4-16
				 */
				assert((insn & 0x000fff00) == 0x000fff00);

				rm = (insn >> 0) & 0xf;
				op0 = NAME_(load_reg)(cpssp, rm);
				NAME_(store_lr)(cpssp, cpssp->NAME.pc_next | cpssp->NAME.flag_t);
				cpssp->NAME.flag_t = op0 & 1;
				NAME_(store_pc)(cpssp, op0 & ~1);

			} else
#endif /* 5 <= CONFIG_VERSION */
#if 5 <= CONFIG_VERSION && CONFIG_E
			if ((insn & 0x0f9000f0) == 0x01000050) {
				/*
				 * Saturating add/subtract
				 */
				assert(0); /* FIXME */

			} else
#endif /* 5 <= CONFIG_VERSION && CONFIG_E */
#if 5 <= CONFIG_VERSION
			if ((insn & 0x0ff000f0) == 0x01200070) {
				/* BKPT, A4.1.7, A4-14 */
				assert(0);

			} else
#endif /* 5 <= CONFIG_VERSION */
#if 5 <= CONFIG_VERSION && CONFIG_E
			if ((insn & 0x0f9000f0) == 0x01000090) {
				/*
				 * Signed multiplies (type 2)
				 */
				assert(0); /* FIXME */

			} else
#endif /* 5 <= CONFIG_VERSION && CONFIG_E */
			{
				assert(0); /* FIXME */
			}

		} else if ((insn & 0x0e000090) == 0x00000010) {
			/*
			 * Data-processing register shift
			 *
			 * Data-processing instructions, A3.4, A3-7
			 * Instruction encoding, A3.4.1, A3-8
			 * List of data-processing Instructions, A3.4.2, A3-9
			 * Addressing Mode 1 - Data-processing operands,
			 * A5.1, A5-2
			 * Encoding, A5.1.1, A5-3
			 */
			opcode = (insn >> 21) & 0xf;
			s = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			rd = (insn >> 12) & 0xf;

			assert(! s || rd != 15); /* FIXME */

			op0 = NAME_(load_reg)(cpssp, rn);
			op1 = NAME_(reg_shift)(cpssp, insn);
			res = NAME_(alu)(cpssp, opcode, op0, op1, &c, &v);
			if (opcode < 0x8
			 || 0xc <= opcode) {
				NAME_(store_reg)(cpssp, rd, res);
			} else {
				assert(s);
			}
			if (s) {
				NAME_(store_flags)(cpssp, c, v, res);
			}

		} else if ((insn & 0x0e000090) == 0x00000090) {
			/*
			 * Multplies/Extra load/stores
			 *
			 * Figure A3-3, A3-35
			 * Figure A3-5, A3-39
			 */
			if ((insn & 0x0fc000f0) == 0x00000090) {
				/*
				 * Figure A3-3, A3-35
				 *
				 * MUL, A4.1.40, A4-80
				 * MLA, A4.1.34, A4-66
				 */
				s = (insn >> 20) & 1;
				rd = (insn >> 16) & 0xf;
				rn = (insn >> 12) & 0xf;
				rs = (insn >> 8) & 0xf;
				rm = (insn >> 0) & 0xf;
				op0 = NAME_(load_reg)(cpssp, rm);
				op1 = NAME_(load_reg)(cpssp, rs);
				if ((insn >> 21) & 1) {
					op2 = NAME_(load_reg)(cpssp, rn);
				} else {
					assert(rn == 0x0);
					op2 = 0x00000000;
				}
				res = NAME_(mlas)(cpssp, op0, op1, op2, &c, &v);
				NAME_(store_reg)(cpssp, rd, res);
				if (s) {
					NAME_(store_flags)(cpssp, c, v, res);
				}

#if 6 <= CONFIG_VERSION
			} else if ((insn & 0x0ff000f0) == 0x00400090) {
				/*
				 * Figure A3-3, A3-35
				 *
				 * UMAAL
				 */
				assert(0); /* FIXME */
#endif /* 6 <= CONFIG_VERSION */
				
			} else if ((insn & 0x0f8000f0) == 0x00800090) {
				/*
				 * Figure A3-3, A3-35
				 *
				 * SMLAL, A4.1.76, A4-146
				 * SMULL, A4.1.87, A4-168
				 * UMLAL, A4.1.128, A4-249
				 * UMULL, A4.1.129, A4-251
				 */
				s = (insn >> 20) & 1;
				rd = (insn >> 16) & 0xf;
				rn = (insn >> 12) & 0xf;
				rs = (insn >> 8) & 0xf;
				rm = (insn >> 0) & 0xf;

				op0 = NAME_(load_reg)(cpssp, rm);
				op1 = NAME_(load_reg)(cpssp, rs);

				switch ((insn >> 21) & 0x3) {
				case 0:
					/* UMULL, A4.1.129, A4-251 */
					res64 = NAME_(umulls)(cpssp, op0, op1,
							&c, &v);
					break;
				case 1:
					/* UMLAL, A4.1.128, A4-249 */
					assert(0); /* FIXME */
				case 2:
					/* SMULL, A4.1.87, A4-168 */
					assert(0); /* FIXME */
				case 3:
					/* SMLAL, A4.1.76, A4-146 */
					assert(0); /* FIXME */
				default: assert(0); /* Cannot happen. */
				}

				NAME_(store_reg)(cpssp, rd,
						(uint32_t) (res64 >> 32));
				NAME_(store_reg)(cpssp, rn,
						(uint32_t) (res64 >> 0));
				if (s) {
					NAME_(store_flags64)(cpssp, c, v, res64);
				}

			} else if ((insn & 0x0fb000f0) == 0x01000090) {
				/*
				 * Extra Load/store instructions
				 *
				 * Figure A3-5, A3-39
				 * SWP
				 * SWPB
				 */
				assert(0); /* FIXME */

			} else
#if 6 <= CONFIG_VERSION
			if ((insn & 0x0fe000f0) == 0x01800090) {
				/*
				 * Extra Load/store instructions
				 *
				 * Figure A3-5, A3-39
				 * LDREX
				 * STREX
				 */
				assert(0); /* FIXME */

			} else
#endif /* 6 <= CONFIG_VERSION */

			if ((insn & 0x0e000090) == 0x00000090) {
				/*
				 * Addressing Mode 3, A5.3, A5-33
				 *
				 * LDRD, A4.1.26, A4-50
				 * LDRH, A4.1.28, A4-54
				 * LDRSB, A4.1.29, A4-56
				 * LDRSH, A4.1.30, A4-58
				 * STRD, A4.1.102, A4-199
				 * STRH, A4.1.104, A4-204
				 */
				p = (insn >> 24) & 1;
				u = (insn >> 23) & 1;
				i = (insn >> 22) & 1;
				w = (insn >> 21) & 1;
				l = (insn >> 20) & 1;
				s = (insn >> 6) & 1;
				h = (insn >> 5) & 1;
				rn = (insn >> 16) & 0xf;
				rd = (insn >> 12) & 0xf;

				assert(p || ! w);
				assert(rd != 0xf);

				if (i) {
					offset = (((insn >> 8) & 0xf) << 4)
							| ((insn >> 0) & 0xf);
				} else {
					rm = (insn >> 0) & 0xf;
					offset = NAME_(load_reg)(cpssp, rm);
				}
				addr = NAME_(load_reg)(cpssp, rn);
				if (p) {
					if (u) {
						addr += offset;
					} else {
						addr -= offset;
					}
				}

				switch ((l << 2) | (s << 1) | (h << 0)) {
				case 0b000:
					assert(0); /* See above. */
				case 0b001:
					/* STRH, A4.1.104, A4-204 */
					op0 = NAME_(load_reg)(cpssp, rd);
					NAME_(st16)(cpssp, addr, op0);
					break;
#if (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION
				case 0b010:
					/* LDRD, A4.1.26, A4-50 */
					res64 = NAME_(ld64)(cpssp, addr);
					NAME_(store_reg64)(cpssp, rd, res64);
					break;
				case 0b011:
					/* STRD, A4.1.102, A4-199 */
					res64 = NAME_(load_reg64)(cpssp, rd);
					NAME_(st64)(cpssp, addr, res64);
					break;
#endif /* (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION */
				case 0b100:
					assert(0); /* See above. */
				case 0b101:
					/* LDRH, A4.1.28, A4-54 */
					res = (uint32_t) (uint16_t) NAME_(ld16)(cpssp, addr);
					NAME_(store_reg)(cpssp, rd, res);
					break;
				case 0b110:
					/* LDRSB, A4.1.29, A4-56 */
					res = (uint32_t) (int32_t) (int8_t) NAME_(ld8)(cpssp, addr);
					NAME_(store_reg)(cpssp, rd, res);
					break;
				case 0b111:
					/* LDRSH, A4.1.30, A4-58 */
					res = (uint32_t) (int32_t) (int16_t) NAME_(ld16)(cpssp, addr);
					NAME_(store_reg)(cpssp, rd, res);
					break;
				default:
					assert(0); /* Cannot happen. */
				}

				if (! p) {
					if (u) {
						addr += offset;
					} else {
						addr -= offset;
					}
				}
				if (! p || w) {
					NAME_(store_reg)(cpssp, rn, addr);
				}

			} else {
				assert(0); /* FIXME */
			}

		} else if ((insn & 0x0fb00000) == 0x03000000) {
			/*
			 * Undefined instruction.
			 */
			assert(0); /* FIXME */

		} else if ((insn & 0x0fb00000) == 0x03200000) {
			/*
			 * Move immediate to status register.
			 */
			assert(0); /* FIXME */

		} else if ((insn & 0x0e000000) == 0x02000000) {
			/*
			 * Data processing immediate
			 *
			 * Data-processing instructions, A3.4, A3-7
			 * Instruction encoding, A3.4.1, A3-8
			 * List of data-processing Instructions, A3.4.2, A3-9
			 * Addressing Mode 1 - Data-processing operands,
			 * A5.1, A5-2
			 * Encoding, A5.1.1, A5-3
			 */
			opcode = (insn >> 21) & 0xf;
			s = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			rd = (insn >> 12) & 0xf;

			assert(! s || rd != 15); /* FIXME */

			op0 = NAME_(load_reg)(cpssp, rn);
			if (rn == 15
			 && cpssp->NAME.flag_t) {
				op0 &= ~3;
			}
			op1 = NAME_(imm)(cpssp, insn);

			res = NAME_(alu)(cpssp, opcode, op0, op1, &c, &v);
			if (opcode < 0x8
			 || 0xc <= opcode) {
				NAME_(store_reg)(cpssp, rd, res);
			} else {
				assert(s);
			}
			if (s) {
				NAME_(store_flags)(cpssp, c, v, res);
			}

		} else if ((insn & 0x0e000000) == 0x04000000) {
			/*
			 * Load/store immediate offset.
			 *
			 * A5.2.2, page A5-20.
			 */
			p = (insn >> 24) & 1;
			u = (insn >> 23) & 1;
			b = (insn >> 22) & 1;
			w = (insn >> 21) & 1;
			l = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			rd = (insn >> 12) & 0xf;
			offset = (insn >> 0) & 0xfff;

			if (! p || w) {
				assert(rn != rd); /* UNPREDICTABLE */
			}

			addr = NAME_(addr_mode_2_pre)(cpssp, p, u, w, rn, offset);
			NAME_(ldst)(cpssp, p, b, w, l, rd, addr);

			NAME_(addr_mode_2_post)(cpssp, p, u, w, rn, addr, offset);

		} else if ((insn & 0x0e000010) == 0x06000000) {
			/*
			 * Load/store register offset.
			 *
			 * A5.2.4, page A5-22.
			 */
			p = (insn >> 24) & 1;
			u = (insn >> 23) & 1;
			b = (insn >> 22) & 1;
			w = (insn >> 21) & 1;
			l = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			rd = (insn >> 12) & 0xf;

			if (! p || w) {
				assert(rn != rd); /* UNPREDICTABLE */
			}

			offset = NAME_(reg_offset)(cpssp, insn);

			addr = NAME_(addr_mode_2_pre)(cpssp, p, u, w, rn, offset);
			NAME_(ldst)(cpssp, p, b, w, l, rd, addr);

			NAME_(addr_mode_2_post)(cpssp, p, u, w, rn, addr, offset);

		} else if ((insn & 0x0ff000f0) == 0x07f000f0) {
			/*
			 * Architecturally undefined.
			 *
			 * A3.16.5
			 */
			assert(0); /* FIXME */

#if 6 <= CONFIG_VERSION
		} else if ((insn & 0x0e000010) == 0x06000010) {
			/*
			 * Media instructions.
			 *
			 * Figure A3-2, A3-34
			 */
			if ((insn & 0x0f800010) == 0x06000010) {
				/*
				 * Parallel add/subtract
				 */
				assert((insn & 0x00000f00) == 0x00000f00);

				assert(0); /* FIXME */

			} else if ((insn & 0x0ff00030) == 0x06800010) {
				/*
				 * Halfword pack
				 */
				assert(0); /* FIXME */

			} else if ((insn & 0x0fa00030) == 0x06a00010) {
				/*
				 * Word saturate
				 */
				assert(0); /* FIXME */

			} else if ((insn & 0x0fb000f0) == 0x06a00030) {
				/*
				 * Parallel halfword saturate
				 */
				assert(0); /* FIXME */

			} else if ((insn & 0x0fb00070) == 0x06b00030) {
				/*
				 * Reverse
				 *
				 * REV, A4.1.56, A4-109
				 * REV16, A4.1.57, A4-110
				 * REVSH, A4.1.58, A4-111
				 */
				assert((insn & 0x000f0f00) == 0x000f0f00);

				rd = (insn >> 12) & 0xf;
				rm = (insn >> 0) & 0xf;

				assert(rd != 15); /* UNPREDICTABLE */
				assert(rm != 15); /* UNPREDICTABLE */

				op0 = NAME_(load_reg)(cpssp, rm);

				if ((insn & 0x0ff000f0) == 0x06b00030) {
					/* REV, A4.1.56, A4-109 */
					res = ((op0 >> 24) & 0xff)
						| ((op0 >> 8) & 0xff00)
						| ((op0 << 8) & 0xff0000)
						| ((op0 << 24) & 0xff000000);

				} else if ((insn & 0x0ff000f0) == 0x06b000b0) {
					/* REV16, A4.1.57, A4-110 */
					res = ((op0 >> 8) & 0xff0000)
						| ((op0 << 8) & 0xff000000)
						| ((op0 >> 8) & 0xff)
						| ((op0 << 8) & 0xff00);

				} else if ((insn & 0x0ff000f0) == 0x06f000b0) {
					/* REVSH, A4.1.58, A4-111 */
					op0 = ((op0 >> 8) & 0xff)
						| ((op0 << 8) & 0xff00);
					res = (int32_t) (int16_t) op0;

				} else {
					assert(0); /* FIXME */
				}

				NAME_(store_reg)(cpssp, rd, res);

			} else if ((insn & 0x0ff000f0) == 0x068000b0) {
				/*
				 * Select bytes
				 */
				assert((insn & 0x00000f00) == 0x00000f00);

				assert(0); /* FIXME */

			} else if ((insn & 0x0f8000f0) == 0x06800070) {
				/*
				 * Sign/zero extend (add)
				 *
				 * SXTAB, A4.1.110, A4-216
				 * SXTB, A4.1.113, A4-222
				 * SXTAB16, A4.1.111, A4-218
				 * SXTB16, A4.1.114, A4-224
				 * SXTAH, A4.1.112, A4-220
				 * SXTH, A4.1.115, A4-226
				 * UXTAB, A4.1.143, A4-274
				 * UXTAH, A4.1.145, A4-278
				 * UXTB, A4.1.146, A4-280
				 * UXTAB16, A4.1.144, A4-276
				 * UXTB16, A4.1.147, A4-282
				 * UXTH, A4.1.148, A4-284
				 */
				unsigned int rotate;

				assert((insn & 0x00000300) == 0x00000000);

				rn = (insn >> 16) & 0xf;
				rd = (insn >> 12) & 0xf;
				rotate = ((insn >> 10) & 3) << 3;
				rm = (insn >> 0) & 0xf;

				assert(rd != 15); /* UNPREDICTABLE */
				assert(rm != 15); /* UNPREDICTABLE */

				op0 = NAME_(load_reg)(cpssp, rm);
				op0 = (op0 >> rotate) | (op0 << (32 - rotate));
				if (rn == 15) {
					op1 = 0;
				} else {
					op1 = NAME_(load_reg)(cpssp, rn);
				}
				switch ((insn >> 20) & 0x7) {
				case 0:
					/* SXTAB16, A4.1.111, A4-218 */
					/* SXTB16, A4.1.114, A4-224 */
					assert(0); /* FIXME */

				case 1:
					assert(0); /* FIXME */

				case 2:
					/* SXTAB, A4.1.110, A4-216 */
					/* SXTB, A4.1.113, A4-222 */
					res = op1 + (int32_t) (int8_t) op0;
					break;

				case 3:
					/* SXTAH, A4.1.112, A4-220 */
					/* SXTH, A4.1.115, A4-226 */
					res = op1 + (int32_t) (int16_t) op0;
					break;

				case 4:
					/* UXTAB16, A4.1.144, A4-276 */
					/* UXTB16, A4.1.147, A4-282 */
					assert(0); /* FIXME */

				case 5:
					assert(0); /* FIXME */

				case 6:
					/* UXTAB, A4.1.143, A4-274 */
					/* UXTB, A4.1.146, A4-280 */
					res = op1 + (uint32_t) (uint8_t) op0;
					break;

				case 7:
					/* UXTAH, A4.1.145, A4-278 */
					/* UXTH, A4.1.148, A4-284 */
					res = op1 + (uint32_t) (uint16_t) op0;
					break;

				default: assert(0); /* Cannot happen. */
				}
				NAME_(store_reg)(cpssp, rd, res);

			} else if ((insn & 0x0f800010) == 0x07000010) {
				/*
				 * Multiplies (type 3)
				 */
				assert(0); /* FIXME */

			} else if ((insn & 0x0ff000f0) == 0x07800010) {
				/*
				 * Unsigned sum of abs differences
				 * Unsigned sum of abs differences, acc
				 */
				assert(0); /* FIXME */

			} else {
				assert(0); /* FIXME */
			}
#endif /* 6 <= CONFIG_VERSION */

		} else if ((insn & 0x0e000000) == 0x08000000) {
			/*
			 * Load/store multiple
			 *
			 * Addressing Mode 4 - Load and Store Multiple,
			 * A5.4, A5-41
			 * Encoding, A5.4.1, A5-42
			 *
			 * LDM(1), A4.1.20, A4-36
			 * LDM(2), A4.1.21, A4-38
			 * LDM(3), A4.1.22, A4-40
			 * STM(1), A4.1.97, A4-189
			 * STM(2), A4.1.98, A4-191
			 */
			p = (insn >> 24) & 1;
			u = (insn >> 23) & 1;
			s = (insn >> 22) & 1;
			w = (insn >> 21) & 1;
			l = (insn >> 20) & 1;
			rn = (insn >> 16) & 0xf;
			reglist = (insn >> 0) & 0xffff;
			assert(! l || ! w || ! ((reglist >> rn) & 1)); /* UNPREDICTABLE */
			addr = NAME_(addr_mode_4_pre)(cpssp, rn);
			addr = NAME_(ldstm)(cpssp, p, u, s, l, addr, reglist);
			NAME_(addr_mode_4_post)(cpssp, w, rn, addr);

		} else if ((insn & 0x0e000000) == 0x0a000000) {
			/*
			 * Branch and branch with link.
			 * B, BL, A4.1.5, A4-10
			 */
			int32_t offset;

			if ((insn >> 24) & 1) {
				NAME_(store_lr)(cpssp, cpssp->NAME.pc_next | cpssp->NAME.flag_t);
			}

			offset = (int32_t) ((insn >> 0) & 0xffffff);
			offset <<= 8;
			offset >>= 8;
			if (cpssp->NAME.flag_t) {
				offset <<= 1;
			} else {
				offset <<= 2;
			}
			NAME_(store_pc)(cpssp, NAME_(load_pc)(cpssp) + offset);

#if CONFIG_ARM
		} else if ((insn & 0x0e000000) == 0x0c000000) {
			/*
			 * Coprocessor load/store and double register transfers.
			 */
			assert(0); /* FIXME */

		} else if ((insn & 0x0f000010) == 0x0e000000) {
			/*
			 * Coprocessor data processing.
			 */
			assert(0); /* FIXME */

		} else if ((insn & 0x0f000010) == 0x0e000010) {
			/*
			 * Coprocessor register transfers.
			 */
			if ((insn & 0x0f100010) == 0x0e000010) {
				/* MCR, A4.1.32, page A4-62. */
				rd = (insn >> 12) & 0xf;

				op0 = NAME_(load_reg)(cpssp, rd);
				NAME_(mcr)(cpssp, insn, op0);

			} else if ((insn & 0x0f100010) == 0x0e100010) {
				/* MRC, A4.1.36, page A4-70. */
				rd = (insn >> 12) & 0xf;

				NAME_(mrc)(cpssp, insn, &res);
				if (rd == 15) {
					assert(0); /* Store to flags */
				} else {
					NAME_(store_reg)(cpssp, rd, res);
				}
			} else {
				assert(0); /* FIXME */
			}
#endif /* CONFIG_ARM */

		} else if ((insn & 0x0f000000) == 0x0f000000) {
			/*
			 * Software interrupt.
			 *
			 * SWI, A4.1.107, A4-210
			 */
			NAME_(SVCall_pending_set)(cpssp, 1);
			assert(cpssp->NAME.exception_pending);

		} else {
			/* FIXME */
			fprintf(stderr, "insn=0x%08x\n", insn);
			assert(0);
		}
	}
}

#ifdef CONFIG_THUMB
static void
NAME_(insn_exec_thumb)(struct cpssp *cpssp, uint16_t insn16)
{
	uint16_t reglist;
	uint16_t op;
	uint16_t rd;
	uint16_t rm;
	uint16_t rn;
	uint32_t offset;
	uint8_t cond;
	int inside;
	uint32_t insn;

	inside = NAME_(it_inside)(cpssp);
	if (! NAME_(it_true)(cpssp)) {
		return;
	}

	switch ((insn16 >> 12) & 0xf) {
	case 0x0: case 0x1:
		if (((insn16 >> 11) & 0x3) != 0x3) {
			/*
			 * Move shifted register.
			 *
			 * ASR(immediate), ARMv7-A/R: A8-330, ARMv7-M: A7-203
			 * LSL(immediate), ARMv7-A/R: A8-468, ARMv7-M: A7-298
			 * LSR(immediate), ARMv7-A/R: A8-472, ARMv7-M: A7-304
			 */
			COUNT_INST(shift);

			offset = (insn16 >> 6) & 0x1f;
			rm = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			switch ((insn16 >> 11) & 0b11) {
			case 0b00:
				/* LSL(immediate), ARMv7-A/R: A8-468, ARMv7-M: A7-298 */
				insn = 0xe1a00000;
				break;

			case 0b01:
				/* LSR(immediate), ARMv7-A/R: A8-472, ARMv7-M: A7-304 */
				insn = 0xe1a00020;
				break;

			case 0b10:
				/* ASR(immediate), ARMv7-A/R: A8-330, ARMv7-M: A7-203 */
				insn = 0xe1a00040;
				break;

			case 0b11:
			default:
				assert(0); /* Cannot happen. */
			}
			insn |= (! inside) << 20;
			insn |= (rd << 12) | (offset << 7) | (rm << 0);

			NAME_(insn_exec)(cpssp, insn);

		} else if ((insn16 >> 10) & 1) {
			/*
			 * Add/subtract immediate.
			 *
			 * ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189
			 * SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448
			 */
			COUNT_INST(addsub);

			offset = (insn16 >> 6) & 0x7;
			rn = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			switch ((insn16 >> 9) & 1) {
			case 0:
				/* ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189 */
				insn = 0xe2800000;
				break;
			case 1:
				/* SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448 */
				insn = 0xe2400000;
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			insn |= (! inside) << 20;
			insn |= (rn << 16) | (rd << 12) | (offset << 0);

			NAME_(insn_exec)(cpssp, insn);

		} else {
			/*
			 * Add/subtract register.
			 *
			 * ADD(register), ARMv7-A/R: A8-310, ARMv7-M: A7-191
			 * SUB(register), ARMv7-A/R: A8-712, ARMv7-M: A7-450
			 */
			COUNT_INST(addsub);

			rm = (insn16 >> 6) & 0x7;
			rn = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			switch ((insn16 >> 9) & 1) {
			case 0:
				/* ADD(register), ARMv7-A/R: A8-310, ARMv7-M: A7-191 */
				insn = 0xe0800000;
				break;
			case 1:
				/* SUB(register), ARMv7-A/R: A8-712, ARMv7-M: A7-450 */
				insn = 0xe0400000;
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			insn |= (! inside) << 20;
			insn |= (rn << 16) | (rd << 12) | (rm << 0);

			NAME_(insn_exec)(cpssp, insn);
		}
		break;

	case 0x2: case 0x3:
		/*
		 * Move/compare/add/subtract immediate.
		 *
		 * ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189
		 * CMP(immediate), ARMv7-A/R: A8-370, ARMv7-M: A7-229
		 * MOV(immediate), ARMv7-A/R: A8-484, ARMv7-M: A7-312
		 * SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448
		 */
		COUNT_INST(immediate);

		op = (insn16 >> 11) & 0x3;
		rd = (insn16 >> 8) & 0x7;
		offset = (insn16 >> 0) & 0xff;

		switch (op) {
		case 0x0:
			/* MOV(immediate), ARMv7-A/R: A8-484, ARMv7-M: A7-312 */
			insn = 0xe3a00000;
			insn |= (0 << 16) | (rd << 12) | (offset << 0);
			break;
		case 0x1:
			/* CMP(immediate), ARMv7-A/R: A8-370, ARMv7-M: A7-229 */
			insn = 0xe3400000;
			insn |= (rd << 16) | (0 << 12) | (offset << 0);
			break;
		case 0x2:
			/* ADD(immediate), ARMv7-A/R: A8-306, ARMv7-M: A7-189 */
			insn = 0xe2800000;
			insn |= (rd << 16) | (rd << 12) | (offset << 0);
			break;
		case 0x3:
			/* SUB(immediate), ARMv7-A/R: A8-708, ARMv7-M: A7-448 */
			insn = 0xe2400000;
			insn |= (rd << 16) | (rd << 12) | (offset << 0);
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		if (! inside
		 || op == 0x1) { /* CMP */
			insn |= 1 << 20;
		}

		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0x4:
		switch ((insn16 >> 10) & 0x3) {
		case 0x0:
			/*
			 * Data processing, ARMv7-A/R: A6-225, ARMv7-M: A5-129
			 *
			 * ADC, ARMv7-A/R: A8-302, ARMv7-M: A7-187
			 * AND, ARMv7-A/R: A8-326, ARMv7-M: A7-201
			 * ASR, ARMv7-A/R: A8-332, ARMv7-M: A7-205
			 * BIC, ARMv7-A/R: A8-342, ARMv7-M: A7-213
			 * CMN, ARMv7-A/R: A8-366, ARMv7-M: A7-227
			 * CMP, ARMv7-A/R: A8-372, ARMv7-M: A7-231
			 * EOR, ARMv7-A/R: A8-384, ARMv7-M: A7-239
			 * LSL, ARMv7-A/R: A8-468, ARMv7-M: A7-300
			 * LSR, ARMv7-A/R: A8-472, ARMv7-M: A7-304
			 * MVN, ARMv7-A/R: A8-506, ARMv7-M: A7-328
			 * MUL, ARMv7-A/R: A8-502, ARMv7-M: A7-324
			 * RSB, ARMv7-A/R: A8-574, ARMv7-M: A7-372
			 * ORR, ARMv7-A/R: A8-518, ARMv7-M: A7-336
			 * ROR, ARMv7-A/R: A8-570, ARMv7-M: A7-368
			 * SBC, ARMv7-A/R: A8-594, ARMv7-M: A7-380
			 * TST, ARMv7-A/R: A8-746, ARMv7-M: A7-466
			 */
			COUNT_INST(alu);

			op = (insn16 >> 6) & 0xf;
			rm = (insn16 >> 3) & 0x7;
			rd = (insn16 >> 0) & 0x7;

			switch (op) {
			case 0x0:
				/* AND, ARMv7-A/R: A8-326, ARMv7-M: A7-201 */
				insn = 0xe0000000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0x1:
				/* EOR, ARMv7-A/R: A8-384, ARMv7-M: A7-239 */
				insn = 0xe0200000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0x2:
				/* LSL, ARMv7-A/R: A8-468, ARMv7-M: A7-300 */
				insn = 0xe1a00010;
				insn |= (0 << 16) | (rd << 12) | (rm << 8) | (rd << 0);
				break;
			case 0x3:
				/* LSR, ARMv7-A/R: A8-472, ARMv7-M: A7-304 */
				insn = 0xe1a00030;
				insn |= (0 << 16) | (rd << 12) | (rm << 8) | (rd << 0);
				break;
			case 0x4:
				/* ASR, ARMv7-A/R: A8-332, ARMv7-M: A7-205 */
				insn = 0xe1a00050;
				insn |= (0 << 16) | (rd << 12) | (rm << 8) | (rd << 0);
				break;
			case 0x5:
				/* ADC, ARMv7-A/R: A8-302, ARMv7-M: A7-187 */
				insn = 0xe0a00000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0x6:
				/* SBC, ARMv7-A/R: A8-594, ARMv7-M: A7-380 */
				insn = 0xe0c00000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0x7:
				/* ROR, ARMv7-A/R: A8-570, ARMv7-M: A7-368 */
				insn = 0xe1a00070;
				insn |= (0 << 16) | (rd << 12) | (rm << 8) | (rd << 0);
				break;
			case 0x8:
				/* TST, ARMv7-A/R: A8-746, ARMv7-M: A7-466 */
				insn = 0xe1000000;
				insn |= (rd << 16) | (0 << 12) | (rm << 0);
				break;
			case 0x9:
				/* RSB, ARMv7-A/R: A8-574, ARMv7-M: A7-372 */
				insn = 0xe2600000;
				insn |= (rm << 16) | (rd << 12);
				break;
			case 0xa:
				/* CMP, ARMv7-A/R: A8-372, ARMv7-M: A7-231 */
				insn = 0xe1400000;
				insn |= (rd << 16) | (0 << 12) | (rm << 0);
				break;
			case 0xb:
				/* CMN, ARMv7-A/R: A8-366, ARMv7-M: A7-227 */
				insn = 0xe1600000;
				insn |= (rd << 16) | (0 << 12) | (rm << 0);
				break;
			case 0xc:
				/* ORR, ARMv7-A/R: A8-518, ARMv7-M: A7-336 */
				insn = 0xe1800000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0xd:
				/* MUL, ARMv7-A/R: A8-502, ARMv7-M: A7-324 */
				insn = 0xe0000090;
				insn |= (rd << 16) | (0 << 12) | (rd << 8) | (rm << 0);
				break;
			case 0xe:
				/* BIC, ARMv7-A/R: A8-342, ARMv7-M: A7-213 */
				insn = 0xe1c00000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0xf:
				/* MVN, ARMv7-A/R: A8-506, ARMv7-M: A7-328 */
				insn = 0xe1e00000;
				insn |= (0 << 16) | (rd << 12) | (rm << 0);
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			if (! inside
			 || op == 0x8 /* TST */
			 || op == 0xa /* CMP */
			 || op == 0xb /* CMN */) {
				insn |= 1 << 20;
			}

			NAME_(insn_exec)(cpssp, insn);
			break;

		case 0x1:
			/*
			 * Hi register operations/branch exchange
			 *
			 * ADD, ARMv7-A/R: 310, ARMv7-M: A7-191
			 * BLX, ARMv7-A/R: 350, ARMv7-M: A7-217
			 * BX,  ARMv7-A/R: 352, ARMv7-M: A7-218
			 * CMP, ARMv7-A/R: 372, ARMv7-M: A7-231
			 * MOV, ARMv7-A/R: 486, ARMv7-M: A7-314
			 */
			COUNT_INST(hireg);

			rm = (insn16 >> 3) & 0x7;
			rm |= ((insn16 >> 6) & 0x1) << 3;
			rd = (insn16 >> 0) & 0x7;
			rd |= ((insn16 >> 7) & 0x1) << 3;

			switch ((insn16 >> 8) & 0x3) {
			case 0x0:
				/* ADD, ARMv7-A/R: 310, ARMv7-M: A7-191 */
				insn = 0xe0800000;
				insn |= (rd << 16) | (rd << 12) | (rm << 0);
				break;
			case 0x1:
				/* CMP, ARMv7-A/R: 372, ARMv7-M: A7-231 */
				insn = 0xe1500000;
				insn |= (rd << 16) | (0 << 12) | (rm << 0);
				break;
			case 0x2:
				/* MOV, ARMv7-A/R: 486, ARMv7-M: A7-314 */
				insn = 0xe1a00000;
				insn |= (0 << 16) | (rd << 12) | (rm << 0);
				break;
			case 0x3:
				/* BLX, ARMv7-A/R: 350, ARMv7-M: A7-217 */
				/* BX,  ARMv7-A/R: 352, ARMv7-M: A7-218 */
				insn = 0xe1200010;
				insn |= ((insn16 >> 7) & 1) << 5;
				insn |= (0xf << 16) | (0xf << 12) | (0xf << 8) | (rm << 0);
				break;
			default:
				assert(0); /* Cannot happen. */
			}

			NAME_(insn_exec)(cpssp, insn);
			break;

		case 0x2: case 0x3:
			/*
			 * LDR(literal), ARMv7-A/R: A8-410, ARMv7-M: A7-254
			 */
			COUNT_INST(mempcrel);

			rd = (insn16 >> 8) & 0x7;
			offset = (insn16 >> 0) & 0xff;

			insn = 0xe59f0000;
			insn |= (rd << 12) | (offset << 2);

			NAME_(insn_exec)(cpssp, insn);
			break;

		default:
			assert(0); /* Cannot happen. */
		}
		break;

	case 0x5:
		/*
		 * Load/Store, ARMv7-A/R: A6-227
		 *
		 * LDR(register), ARMv7-A/R: A8-412, ARMv7-M: A7-256
		 * LDRB(register), ARMv7-A/R: A8-422, ARMv7-M: A7-262
		 * LDRH(register), ARMv7-A/R: A8-446, ARMv7-M: A7-278
		 * LDRSB(register), ARMv7-A/R: A8-454, ARMv7-M: A7-286
		 * LDRSH(register), ARMv7-A/R: A8-462, ARMv7-M: A7-294
		 * STR(register), ARMv7-A/R: A8-676, ARMv7-M: A7-428
		 * STRB(register), ARMv7-A/R: A8-682, ARMv7-M: A7-432
		 * STRH(register), ARMv7-A/R: A8-702, ARMv7-M: A7-444
		 */
		COUNT_INST(memreg);

		rm = (insn16 >> 6) & 0x7;
		rn = (insn16 >> 3) & 0x7;
		rd = (insn16 >> 0) & 0x7;

		switch ((insn16 >> 9) & 0b111) {
		case 0b000:
			/* STR(register), ARMv7-A/R: A8-676, ARMv7-M: A7-428 */
			insn = 0xe7800000;
			break;
		case 0b001:
			/* STRH(register), ARMv7-A/R: A8-702, ARMv7-M: A7-444 */
			insn = 0xe18000b0;
			break;
		case 0b010:
			/* STRB(register), ARMv7-A/R: A8-682, ARMv7-M: A7-432 */
			insn = 0xe7c00000;
			break;
		case 0b011:
			/* LDRSB(register), ARMv7-A/R: A8-454, ARMv7-M: A7-286 */
			insn = 0xe19000d0;
			break;
		case 0b100:
			/* LDR(register), ARMv7-A/R: A8-412, ARMv7-M: A7-256 */
			insn = 0xe7900000;
			break;
		case 0b101:
			/* LDRH(register), ARMv7-A/R: A8-446, ARMv7-M: A7-278 */
			insn = 0xe19000b0;
			break;
		case 0b110:
			/* LDRB(register), ARMv7-A/R: A8-422, ARMv7-M: A7-262 */
			insn = 0xe7d00000;
			break;
		case 0b111:
			/* LDRSH(register), ARMv7-A/R: A8-462, ARMv7-M: A7-294 */
			insn = 0xe19000f0;
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		insn |= (rn << 16) | (rd << 12) | (rm << 0);

		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0x6:
	case 0x7:
		/*
		 * load/store with immediate offset
		 *
		 * LDR(immediate), ARMv7-A/R: A8-406, ARMv7-M: A7-252
		 * LDRB(immediate), ARMv7-A/R: A8-416, ARMv7-M: A7-258
		 * STR(immediate), ARMv7-A/R: A8-672, ARMv7-M: A7-426
		 * STRB(immediate), ARMv7-A/R: A8-678, ARMv7-M: A7-430
		 */
		COUNT_INST(memimmediate);

		offset = (insn16 >> 6) & 0x1f;
		rn = (insn16 >> 3) & 0x7;
		rd = (insn16 >> 0) & 0x7;

		switch ((insn16 >> 11) & 0x3) {
		case 0:
			/* STR(immediate), ARMv7-A/R: A8-672, ARMv7-M: A7-426 */
			insn = 0xe5800000;
			insn |= (rn << 16) | (rd << 12) | (offset << 2);
			break;
		case 1:
			/* LDR(immediate), ARMv7-A/R: A8-406, ARMv7-M: A7-252 */
			insn = 0xe5900000;
			insn |= (rn << 16) | (rd << 12) | (offset << 2);
			break;
		case 2:
			/* STRB(immediate), ARMv7-A/R: A8-678, ARMv7-M: A7-430 */
			insn = 0xe5c00000;
			insn |= (rn << 16) | (rd << 12) | (offset << 0);
			break;
		case 3:
			/* LDRB(immediate), ARMv7-A/R: A8-416, ARMv7-M: A7-258 */
			insn = 0xe5d00000;
			insn |= (rn << 16) | (rd << 12) | (offset << 0);
			break;
		default:
			assert(0); /* Cannot happen. */
		}

		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0x8:
		/*
		 * load/store halfword, ARMv7-A/R: A6-227
		 *
		 * LDRH(immediate), ARMv7-A/R: A8-440, ARMv7-M: A7-274
		 * STRH(immediate), ARMv7-A/R: A8-698, ARMv7-M: A7-442
		 */
		COUNT_INST(memimmediate);

		offset = (insn16 >> 6) & 0x1f;
		rn = (insn16 >> 3) & 0x7;
		rd = (insn16 >> 0) & 0x7;

		switch ((insn16 >> 11) & 1) {
		case 0:
			/* STRH(immediate), ARMv7-A/R: A8-698, ARMv7-M: A7-442 */
			insn = 0xe1c000b0;
			break;
		case 1:
			/* LDRH(immediate), ARMv7-A/R: A8-440, ARMv7-M: A7-274 */
			insn = 0xe1d000b0;
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		insn |= (rn << 16) | (rd << 12);
		insn |= ((offset >> 3) & 0x3) << 8;
		insn |= ((offset >> 0) & 0x7) << 1;

		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0x9:
		/*
		 * SP-relative load/store, ARMv7-A/R: A6-227
		 *
		 * LDR(immediate), ARMv7-A/R: 406, ARMv7-M: A7-252
		 * STR(immediate), ARMv7-A/R: 672, ARMv7-M: A7-426
		 */
		COUNT_INST(memsprel);

		rd = (insn16 >> 8) & 0x7;
		offset = (insn16 >> 0) & 0xff;
		
		switch ((insn16 >> 11) & 1) {
		case 0:
			/* STR(immediate), ARMv7-A/R: 672, ARMv7-M: A7-426 */
			insn = 0xe58d0000;
			break;
		case 1:
			/* LDR(immediate), ARMv7-A/R: 406, ARMv7-M: A7-252 */
			insn = 0xe59d0000;
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		insn |= (rd << 12) | (offset << 2);

		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0xa:
		/*
		 * Load address
		 *
		 * ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193
		 * ADR, ARMv7-A/R: A8-322, ARMv7-M: A7-197
		 */
		COUNT_INST(lea);

		rd = (insn16 >> 8) & 7;
		offset = (insn16 & 0xff);

		insn = 0xe2800f00;
		switch ((insn16 >> 11) & 1) {
		case 0:
			/* ADR, ARMv7-A/R: A8-322, ARMv7-M: A7-197 */
			insn |= 15 << 16; /* PC */
			break;
		case 1:
			/* ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193 */
			insn |= 13 << 16; /* SP */
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		insn |= (rd << 12) | (offset << 0);
			
		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0xb:
		/* Misc */
		op = (insn16 >> 8) & 0xf;
		switch (op) {
		case 0x0:
			/*
			 * Add offset to stack pointer
			 *
			 * ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193
			 * SUB(SP minus immediate), ARMv7-A/R: A8-716, ARMv7-M: A7-452
			 */
			COUNT_INST(addsubsp);

			offset = (insn16 >> 0) & 0x7f;

			switch ((insn16 >> 7) & 1) {
			case 0:
				/* ADD(SP plus immediate), ARMv7-A/R: A8-316, ARMv7-M: A7-193 */
				insn = 0xe28ddf00;
				break;
			case 1:
				/* SUB(SP minus immediate), ARMv7-A/R: A8-716, ARMv7-M: A7-452 */
				insn = 0xe24ddf00;
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			insn |= (offset << 0);

			NAME_(insn_exec)(cpssp, insn);
			break;

#if 7 <= CONFIG_VERSION
		case 0x1: case 0x3:
		case 0x9: case 0xb: {
			/*
			 * Compare and Branch on Zero
			 *
			 * CBNZ, CBZ, ARMv7-A/R: A8-356, ARMv7-M: A7-219
			 */
			/* Should use ARM instructions! FIXME */
			uint32_t addr;

			offset = ((insn16 >> 9) & 1) << 5;
			offset |= ((insn16 >> 3) & 0x1f) << 0;
			rn = (insn16 >> 0) & 0x7;

			op0 = NAME_(load_reg)(cpssp, rn);

			if (((insn16 >> 11) & 1) ^ (op0 == 0)) {
				addr = NAME_(load_pc)(cpssp);
				addr += offset << 1;
				NAME_(store_pc)(cpssp, addr);
			}
			break;
		    }
#endif /* CONFIG_VERSION < 7 */

#if 6 <= CONFIG_VERSION
		case 0x2:
			/*
			 * sign/zero extend.
			 *
			 * SXTB, ARMv7-A/R: A8-730, ARMv7-M: A7-459
			 * SXTH, ARMv7-A/R: A8-734, ARMv7-M: A7-461
			 * UXTB, ARMv7-A/R: A8-812, ARMv7-M: A7-498
			 * UXTH, ARMv7-A/R: A8-816, ARMv7-M: A7-500
			 */
			COUNT_INST(extend);

			rd = (insn16 >> 0) & 0x7;
			rm = (insn16 >> 3) & 0x7;

			switch ((insn16 >> 6) & 0x3) {
			case 0x0:
				/* SXTH, ARMv7-A/R: A8-734, ARMv7-M: A7-461 */
				insn = 0xe6bf0070;
				break;
			case 0x1:
				/* SXTB, ARMv7-A/R: A8-730, ARMv7-M: A7-459 */
				insn = 0xe6af0070;
				break;
			case 0x2:
				/* UXTH, ARMv7-A/R: A8-816, ARMv7-M: A7-500 */
				insn = 0xe6ff0070;
				break;
			case 0x3:
				/* UXTB, ARMv7-A/R: A8-812, ARMv7-M: A7-498 */
				insn = 0xe6ef0070;
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			insn |= (rd << 12) | (rm << 0);

			NAME_(insn_exec)(cpssp, insn);
			break;
#endif /* 6 <= CONFIG_VERSION */

		case 0x4: case 0x5:
		case 0xc: case 0xd:
			/*
			 * push/pop registers
			 *
			 * POP, ARMv7-A/R: A8-534, ARMv7-M: A7-348
			 * PUSH, ARMv7-A/R: A8-538, ARMv7-M: A7-350
			 */
			COUNT_INST(pushpop);

			reglist = (insn16 >> 0) & 0xff;

			switch ((insn16 >> 11) & 1) {
			case 0:
				/* PUSH, ARMv7-A/R: A8-538, ARMv7-M: A7-350 */
				insn = 0xe92d0000;
				reglist |= (((insn16 >> 8) & 1) << 14);
				break;
			case 1:
				/* POP, ARMv7-A/R: A8-534, ARMv7-M: A7-348 */
				insn = 0xe8bd0000;
				reglist |= (((insn16 >> 8) & 1) << 15);
				break;
			default:
				assert(0); /* Cannot happen. */
			}
			insn |= reglist;

			NAME_(insn_exec)(cpssp, insn);
			break;

		case 0x6:
			/*
			 * Change Processor State
			 *
			 * CPS, ARMv7-A/R: B9-1978, ARMv7-M: B5-731
			 */
			COUNT_INST(cps);

			switch ((insn16 >> 5) & 0x7) {
			case 0x3:
				/* CPS, ARMv7-A/R: B9-1978, ARMv7-M: B5-731 */
				insn = 0xf1000000;
				insn |= (0b10 | ((insn >> 4) & 1)) << 18;
				insn |= ((insn >> 0) & 7) << 6;
				
				break;
			default:
				assert(0); /* UNDEFINED */
			}

			NAME_(insn_exec)(cpssp, insn);
			break;

		case 0x7:
			assert(0); /* UNDEFINED */
			break;
		case 0x8:
			assert(0); /* UNDEFINED */
			break;

#if 6 <= CONFIG_VERSION
		case 0xa:
			/*
			 * Reverse
			 *
			 * REV, ARMv7-A/R: A8-564, ARMv7-M: A7-363
			 * REV16, ARMv7-A/R: A8-564, ARMv7-M: A7-364
			 * REVSH, ARMv7-A/R: A8-566, ARMv7-M: A7-365
			 */
			COUNT_INST(reverse);

			rd = (insn16 >> 0) & 0x7;
			rm = (insn16 >> 3) & 0x7;

			switch ((insn16 >> 6) & 0b11) {
			case 0b00:
				/* REV, ARMv7-A/R: A8-564, ARMv7-M: A7-363 */
				insn = 0xe6b00030;
				break;
			case 0b01:
				/* REV16, ARMv7-A/R: A8-564, ARMv7-M: A7-364 */
				insn = 0xe6b000b0;
				break;
			case 0b11:
				/* REVSH, ARMv7-A/R: A8-566, ARMv7-M: A7-365 */
				insn = 0xe6f000b0;
				break;
			default:
				assert(0); /* UNDEFINED */
			}
			insn |= (rd << 12) | (rm << 0);

			NAME_(insn_exec)(cpssp, insn);
			break;
#endif /* 6 <= CONFIG_VERSION */

#if 5 <= CONFIG_VERSION
		case 0xe:
			/*
			 * Software breakpoint
			 *
			 * BKPT, ARMv7-A/R: A8-346, ARMv7-M: A7-215
			 */
			offset = (insn16 >> 0) & 0xff;

			insn = 0xe1200000;
			insn |= ((offset >> 4) & 0xf) << 8;
			insn |= ((offset >> 0) & 0xf) << 0;

			NAME_(insn_exec)(cpssp, insn);
			break;
#endif /* 5 <= CONFIG_VERSION */

#if (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION
		case 0xf:
			/*
			 * If-Then and Hints, ARMv7-A/R: A6-229, ARMv7-M: A5-133
			 *
			 * IT, ARMv7-A/R: A8-390, ARMv7-M: A7-242
			 * NOP, ARMv7-A/R: A8-510, ARMv7-M: A7-331
			 * SEV, ARMv7-A/R: A8-606, ARMv7-M: A7-385
			 * WFE, ARMv7-A/R: A8-1108, ARMv7-M: A7-560
			 * WFI, ARMv7-A/R: A8-1106, ARMv7-M: A7-561
			 * YIELD, ARMv7-A/R: A8-1108, ARMv7-M: A7-562
			 */
			COUNT_INST(hint);

			if (((insn16 >> 0) & 0xf) != 0x0) {
				/* IT, ARMv7-A/R: A8-390, ARMv7-M: A7-242 */
				cpssp->NAME.itstate = insn16 & 0xff;

			} else
#if CONFIG_VERSION < 7
			{
				/* NOP, ARMv7-A/R: A8-510, ARMv7-M: A7-331 */
				/* SEV, ARMv7-A/R: A8-606, ARMv7-M: A7-385 */
				/* WFE, ARMv7-A/R: A8-1108, ARMv7-M: A7-560 */
				/* WFI, ARMv7-A/R: A8-1106, ARMv7-M: A7-561 */
				/* YIELD, ARMv7-A/R: A8-1108, ARMv7-M: A7-562 */
				/* Nothing to do... */
			}
#else /* CONFIG_VERSION < 7 */
			switch ((insn16 >> 4) & 0xf) {
			case 0x0:
				/* NOP, ARMv7-A/R: A8-510, ARMv7-M: A7-331 */
				/* Nothing to do... */
				break;
			case 0x1:
				/* YIELD, ARMv7-A/R: A8-1108, ARMv7-M: A7-562 */
				/* FIXME */
				break;
			case 0x2:
				/* WFE, ARMv7-A/R: A8-1108, ARMv7-M: A7-560 */
				/* FIXME */
				break;
			case 0x3:
				/* WFI, ARMv7-A/R: A8-1106, ARMv7-M: A7-561 */
				cpssp->NAME.wfi = 1;
				NAME_(stop)(cpssp, cpssp->NAME.sleepdeep);
				break;
			case 0x4:
				/* SEV, ARMv7-A/R: A8-606, ARMv7-M: A7-385 */
				/* FIXME */
				break;
			default:
				assert(0); /* UNDEFINED */
			}
#endif /* CONFIG_VERSION < 7 */
			break;
#endif /* (CONFIG_VERSION == 6 && CONFIG_THUMB2) || 7 <= CONFIG_VERSION */

		default:
			assert(0); /* UNDEFINED */
		}
		break;

	case 0xc:
		/*
		 * Multiple load/store, ARMv7-M: A4-115
		 *
		 * LDM, LDMIA, ARMv7-A/R: A8-396, ARMv7-M: A7-248
		 * STMIA, ARMv7-A/R: A8-664, ARMv7-M: A7-422
		 */
		COUNT_INST(memmultiple);

		rn = (insn16 >> 8) & 0x7;
		reglist = (insn16 >> 0) & 0xff;

		switch ((insn16 >> 11) & 1) {
		case 0:
			/* STMIA, ARMv7-A/R: A8-664, ARMv7-M: A7-422 */
			insn = 0xe8a00000;
			break;
		case 1:
			/* LDM, LDMIA, ARMv7-A/R: A8-396, ARMv7-M: A7-248 */
			insn = 0xe8900000;
			insn |= ! ((insn16 >> rn) & 1);
			break;
		default:
			assert(0); /* Cannot happen. */
		}
		insn |= (rn << 16) | (reglist << 0);

		NAME_(insn_exec)(cpssp, insn);
		break;

	case 0xd:
		cond = (insn16 >> 8) & 0xf;

		switch (cond) {
		case 0b1110:
			/*
			 * permanently undefined
			 *
			 * UDF, ARMv7-A/R: A8-758, ARMv7-M: A7-471
			 */
			assert(0); /* UNDEFINED */
			break;

		case 0b1111:
			/*
			 * Supervisor call, Software interrupt
			 *
			 * SVC, SWI, ARMv7-A/R: A8-720, ARMv7-M: A7-455
			 */
			offset = (insn16 >> 0) & 0xff;

			insn = 0xef000000;
			insn |= (offset << 0);

			NAME_(insn_exec)(cpssp, insn);
			break;

		default:
			/*
			 * conditional branch
			 *
			 * B, ARMv7-A/R: A8-334, ARMv7-M: A7-207
			 */
			COUNT_INST(branchconditional);

			offset = (int32_t) (int8_t) ((insn16 >> 0) & 0xff);

			insn = 0x0a000000;
			insn |= (cond << 28) | ((offset & 0xffffff) << 0);

			NAME_(insn_exec)(cpssp, insn);
			break;
		}
		break;

	case 0xe:
		if (((insn16 >> 11) & 1) == 0) {
			/*
			 * unconditional branch
			 *
			 * B, ARMv7-A/R: A8-334, ARMv7-M: A7-207
			 */
			COUNT_INST(branchunconditional);

			offset = ((int32_t) insn16 << 21) >> 21;

			insn = 0xea000000;
			insn |= (offset & 0xffffff) << 0;

			NAME_(insn_exec)(cpssp, insn);

		} else {
			/* first part of thumb2 instruction */
			/* A5.1 */
			assert(0); /* Mustn't happen. */
		}
		break;

	case 0xf:
		/*
		 * first part of thumb2 instruction
		 */
		assert(0); /* Mustn't happen. */
		break;

	default:
		assert(0); /* Cannot happen. */
	}
}
#endif /* CONFIG_THUMB */


static void
NAME_(insn_step)(struct cpssp *cpssp)
{
	uint32_t pc_next;

	pc_next = cpssp->NAME.pc_next;

	loglevel = 1;
#if 0
	if (pc_next == 0xc1080000) {
		loglevel = 1;
	}
#endif
	if (unlikely(
	    (cpssp->NAME.brkstart[0] <= pc_next && pc_next < cpssp->NAME.brkend[0])
	 || (cpssp->NAME.brkstart[1] <= pc_next && pc_next < cpssp->NAME.brkend[1])
	 || (cpssp->NAME.brkstart[2] <= pc_next && pc_next < cpssp->NAME.brkend[2])
	 || (cpssp->NAME.brkstart[3] <= pc_next && pc_next < cpssp->NAME.brkend[3]))) {
		gdb_stop(cpssp->NAME.gdb, 5); /* SIGTRAP */

	} else if (unlikely(cpssp->NAME.wfi)) {
		/*
		 * Waiting for Interrupt
		 */
		/* Nothing to do... */
		COUNT_INST(wfi);

#if 0 /* FIXME */
	} else if (! cpssp->NAME.flush
		&& ((pc_next >> 28) & 0xf) == 0xf) {
		/*
		 * Exception Return
		 */
		if (DEBUG_CONTROL_FLOW
		 && loglevel) {
			fprintf(stderr, "Exception return\n");
		}
		NAME_(exc_return)(cpssp);
#endif /* 0 */

#if 0 /* FIXME */
	} else if (! cpssp->NAME.flush
		&& cpssp->NAME.exception_pending
		&& ! cpssp->NAME.insn_prev) {
		/*
		 * Exception/Interrupt Pending
		 */
		if (DEBUG_CONTROL_FLOW
		 && loglevel) {
			fprintf(stderr, "Exception at %08lx\n",
					cpssp->NAME.pc_next);
		}
		NAME_(exc)(cpssp);
#endif /* 0 */

	} else {
		/*
		 * Execute Instruction
		 */
		if (DEBUG_CONTROL_FLOW
		 && loglevel) {
			fprintf(stderr, "Executing %08lx at %08lx\n", cpssp->NAME.insn_next, pc_next);
#if DEBUG_CONTROL_FLOW_REGS
			NAME_(dump)(cpssp);
#endif
		}

#if CONFIG_THUMB
		if (cpssp->NAME.flag_t) {
			if (cpssp->NAME.stall) {
				/*
				 * Pipeline stalled because of ld/st.
				 */
				/* Just wait... */
				cpssp->NAME.stall = 0;
				return;
			}

			cpssp->NAME.pc = cpssp->NAME.pc_next;
			cpssp->NAME.pc_next = cpssp->NAME.pc_next_next;
			cpssp->NAME.pc_next_next += 2;

			cpssp->NAME.insn = cpssp->NAME.insn_next;
			cpssp->NAME.insn_next = NAME_(ld16_code)(cpssp, cpssp->NAME.pc_next);

			if (cpssp->NAME.flush) {
				/*
				 * Flush pipeline after branch taken.
				 */
				/* Just wait... */
				cpssp->NAME.flush = 0;
				return;
			}

#if 0
			fprintf(stderr, "DIS %08x %04x\n", cpssp->NAME.pc, cpssp->NAME.insn);
#endif

			NAME_(insn_exec_thumb)(cpssp, cpssp->NAME.insn);
		} else
#endif /* CONFIG_THUMB */
		{
			cpssp->NAME.pc = cpssp->NAME.pc_next;
			cpssp->NAME.pc_next = cpssp->NAME.pc_next_next;
			cpssp->NAME.pc_next_next += 4;

			cpssp->NAME.insn = cpssp->NAME.insn_next;
			cpssp->NAME.insn_next = NAME_(ld32_code)(cpssp, cpssp->NAME.pc_next);

			if (cpssp->NAME.flush) {
				/*
				 * Flush pipeline after branch taken.
				 */
				/* Just wait... */
				cpssp->NAME.flush = 0;
				return;
			}

#if 0
			fprintf(stderr, "DIS %08x %08x\n", cpssp->NAME.pc, cpssp->NAME.insn);
#endif

			NAME_(insn_exec)(cpssp, cpssp->NAME.insn);
		}
	}
}
