/* RISC-V simulator.

   Copyright (C) 2005-2024 Free Software Foundation, Inc.
   Contributed by Mike Frysinger.

   This file is part of simulators.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* This file contains the main simulator decoding logic.  i.e. everything that
   is architecture specific.  */

/* This must come before any other includes.  */
#include "defs.h"

#include <inttypes.h>
#include <time.h>

#include "sim-main.h"
#include "sim-signal.h"
#include "sim-syscall.h"

#include "opcode/riscv.h"

#include "sim/sim-riscv.h"

#include "riscv-sim.h"

#define TRACE_REG(cpu, reg) \
  TRACE_REGISTER (cpu, "wrote %s = %#" PRIxTW, riscv_gpr_names_abi[reg], \
		  RISCV_SIM_CPU (cpu)->regs[reg])

static const struct riscv_opcode *riscv_hash[OP_MASK_OP + 1];
#define OP_HASH_IDX(i) ((i) & (riscv_insn_length (i) == 2 ? 0x3 : 0x7f))

#define RISCV_ASSERT_RV32(cpu, fmt, args...) \
  do { \
    if (RISCV_XLEN (cpu) != 32) \
      { \
	TRACE_INSN (cpu, "RV32I-only " fmt, ## args); \
	sim_engine_halt (CPU_STATE (cpu), cpu, NULL, sim_pc_get (cpu), \
			 sim_signalled, SIM_SIGILL); \
      } \
  } while (0)

#define RISCV_ASSERT_RV64(cpu, fmt, args...) \
  do { \
    if (RISCV_XLEN (cpu) != 64) \
      { \
	TRACE_INSN (cpu, "RV64I-only " fmt, ## args); \
	sim_engine_halt (CPU_STATE (cpu), cpu, NULL, sim_pc_get (cpu), \
			 sim_signalled, SIM_SIGILL); \
      } \
  } while (0)

static INLINE void
store_rd (SIM_CPU *cpu, int rd, unsigned_word val)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);

  if (rd)
    {
      riscv_cpu->regs[rd] = val;
      TRACE_REG (cpu, rd);
    }
}

static INLINE unsigned_word
fetch_csr (SIM_CPU *cpu, const char *name, int csr, unsigned_word *reg)
{
  /* Handle pseudo registers.  */
  switch (csr)
    {
    /* Allow certain registers only in respective modes.  */
    case CSR_CYCLEH:
    case CSR_INSTRETH:
    case CSR_TIMEH:
      RISCV_ASSERT_RV32 (cpu, "CSR: %s", name);
      break;
    }

  return *reg;
}

static INLINE void
store_csr (SIM_CPU *cpu, const char *name, int csr, unsigned_word *reg,
	   unsigned_word val)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);

  switch (csr)
    {
    /* These are pseudo registers that modify sub-fields of fcsr.  */
    case CSR_FRM:
      val &= 0x7;
      *reg = val;
      riscv_cpu->csr.fcsr = (riscv_cpu->csr.fcsr & ~0xe0) | (val << 5);
      break;
    case CSR_FFLAGS:
      val &= 0x1f;
      *reg = val;
      riscv_cpu->csr.fcsr = (riscv_cpu->csr.fcsr & ~0x1f) | val;
      break;
    /* Keep the sub-fields in sync.  */
    case CSR_FCSR:
      *reg = val;
      riscv_cpu->csr.frm = (val >> 5) & 0x7;
      riscv_cpu->csr.fflags = val & 0x1f;
      break;

    /* Allow certain registers only in respective modes.  */
    case CSR_CYCLEH:
    case CSR_INSTRETH:
    case CSR_TIMEH:
      RISCV_ASSERT_RV32 (cpu, "CSR: %s", name);
      ATTRIBUTE_FALLTHROUGH;

    /* All the rest are immutable.  */
    default:
      val = *reg;
      break;
    }

  TRACE_REGISTER (cpu, "wrote CSR %s = %#" PRIxTW, name, val);
}

static inline unsigned_word
ashiftrt (unsigned_word val, unsigned_word shift)
{
  uint32_t sign = (val & 0x80000000) ? ~(0xfffffffful >> shift) : 0;
  return (val >> shift) | sign;
}

static inline unsigned_word
ashiftrt64 (unsigned_word val, unsigned_word shift)
{
  uint64_t sign =
    (val & 0x8000000000000000ull) ? ~(0xffffffffffffffffull >> shift) : 0;
  return (val >> shift) | sign;
}

static sim_cia
execute_i (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
{
  SIM_DESC sd = CPU_STATE (cpu);
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  int rd = (iw >> OP_SH_RD) & OP_MASK_RD;
  int rs1 = (iw >> OP_SH_RS1) & OP_MASK_RS1;
  int rs2 = (iw >> OP_SH_RS2) & OP_MASK_RS2;
  const char *rd_name = riscv_gpr_names_abi[rd];
  const char *rs1_name = riscv_gpr_names_abi[rs1];
  const char *rs2_name = riscv_gpr_names_abi[rs2];
  unsigned int csr = (iw >> OP_SH_CSR) & OP_MASK_CSR;
  unsigned_word i_imm = EXTRACT_ITYPE_IMM (iw);
  unsigned_word u_imm = EXTRACT_UTYPE_IMM ((uint64_t) iw);
  unsigned_word s_imm = EXTRACT_STYPE_IMM (iw);
  unsigned_word sb_imm = EXTRACT_BTYPE_IMM (iw);
  unsigned_word shamt_imm = ((iw >> OP_SH_SHAMT) & OP_MASK_SHAMT);
  unsigned_word tmp;
  sim_cia pc = riscv_cpu->pc + 4;

  TRACE_EXTRACT (cpu,
		 "rd:%-2i:%-4s  "
		 "rs1:%-2i:%-4s %0*" PRIxTW "  "
		 "rs2:%-2i:%-4s %0*" PRIxTW "  "
		 "match:%#x mask:%#x",
		 rd, rd_name,
		 rs1, rs1_name, (int) sizeof (unsigned_word) * 2,
		 riscv_cpu->regs[rs1],
		 rs2, rs2_name, (int) sizeof (unsigned_word) * 2,
		 riscv_cpu->regs[rs2],
		 (unsigned) op->match, (unsigned) op->mask);

  switch (op->match)
    {
    case MATCH_ADD:
      TRACE_INSN (cpu, "add %s, %s, %s;  // %s = %s + %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] + riscv_cpu->regs[rs2]);
      break;
    case MATCH_ADDW:
      TRACE_INSN (cpu, "addw %s, %s, %s;  // %s = %s + %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		EXTEND32 (riscv_cpu->regs[rs1] + riscv_cpu->regs[rs2]));
      break;
    case MATCH_ADDI:
      TRACE_INSN (cpu, "addi %s, %s, %#" PRIxTW ";  // %s = %s + %#" PRIxTW,
		  rd_name, rs1_name, i_imm, rd_name, rs1_name, i_imm);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] + i_imm);
      break;
    case MATCH_ADDIW:
      TRACE_INSN (cpu, "addiw %s, %s, %#" PRIxTW ";  // %s = %s + %#" PRIxTW,
		  rd_name, rs1_name, i_imm, rd_name, rs1_name, i_imm);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd, EXTEND32 (riscv_cpu->regs[rs1] + i_imm));
      break;
    case MATCH_AND:
      TRACE_INSN (cpu, "and %s, %s, %s;  // %s = %s & %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] & riscv_cpu->regs[rs2]);
      break;
    case MATCH_ANDI:
      TRACE_INSN (cpu, "andi %s, %s, %" PRIiTW ";  // %s = %s & %#" PRIxTW,
		  rd_name, rs1_name, i_imm, rd_name, rs1_name, i_imm);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] & i_imm);
      break;
    case MATCH_OR:
      TRACE_INSN (cpu, "or %s, %s, %s;  // %s = %s | %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] | riscv_cpu->regs[rs2]);
      break;
    case MATCH_ORI:
      TRACE_INSN (cpu, "ori %s, %s, %" PRIiTW ";  // %s = %s | %#" PRIxTW,
		  rd_name, rs1_name, i_imm, rd_name, rs1_name, i_imm);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] | i_imm);
      break;
    case MATCH_XOR:
      TRACE_INSN (cpu, "xor %s, %s, %s;  // %s = %s ^ %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] ^ riscv_cpu->regs[rs2]);
      break;
    case MATCH_XORI:
      TRACE_INSN (cpu, "xori %s, %s, %" PRIiTW ";  // %s = %s ^ %#" PRIxTW,
		  rd_name, rs1_name, i_imm, rd_name, rs1_name, i_imm);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] ^ i_imm);
      break;
    case MATCH_SUB:
      TRACE_INSN (cpu, "sub %s, %s, %s;  // %s = %s - %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] - riscv_cpu->regs[rs2]);
      break;
    case MATCH_SUBW:
      TRACE_INSN (cpu, "subw %s, %s, %s;  // %s = %s - %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		EXTEND32 (riscv_cpu->regs[rs1] - riscv_cpu->regs[rs2]));
      break;
    case MATCH_LUI:
      TRACE_INSN (cpu, "lui %s, %#" PRIxTW ";", rd_name, u_imm);
      store_rd (cpu, rd, u_imm);
      break;
    case MATCH_SLL:
      TRACE_INSN (cpu, "sll %s, %s, %s;  // %s = %s << %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      u_imm = RISCV_XLEN (cpu) == 32 ? 0x1f : 0x3f;
      store_rd (cpu, rd,
		riscv_cpu->regs[rs1] << (riscv_cpu->regs[rs2] & u_imm));
      break;
    case MATCH_SLLW:
      TRACE_INSN (cpu, "sllw %s, %s, %s;  // %s = %s << %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd, EXTEND32 (
	(uint32_t) riscv_cpu->regs[rs1] << (riscv_cpu->regs[rs2] & 0x1f)));
      break;
    case MATCH_SLLI:
      TRACE_INSN (cpu, "slli %s, %s, %" PRIiTW ";  // %s = %s << %#" PRIxTW,
		  rd_name, rs1_name, shamt_imm, rd_name, rs1_name, shamt_imm);
      if (RISCV_XLEN (cpu) == 32 && shamt_imm > 0x1f)
	sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			 SIM_SIGILL);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] << shamt_imm);
      break;
    case MATCH_SLLIW:
      TRACE_INSN (cpu, "slliw %s, %s, %" PRIiTW ";  // %s = %s << %#" PRIxTW,
		  rd_name, rs1_name, shamt_imm, rd_name, rs1_name, shamt_imm);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		EXTEND32 ((uint32_t) riscv_cpu->regs[rs1] << shamt_imm));
      break;
    case MATCH_SRL:
      TRACE_INSN (cpu, "srl %s, %s, %s;  // %s = %s >> %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      u_imm = RISCV_XLEN (cpu) == 32 ? 0x1f : 0x3f;
      store_rd (cpu, rd,
		riscv_cpu->regs[rs1] >> (riscv_cpu->regs[rs2] & u_imm));
      break;
    case MATCH_SRLW:
      TRACE_INSN (cpu, "srlw %s, %s, %s;  // %s = %s >> %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd, EXTEND32 (
	(uint32_t) riscv_cpu->regs[rs1] >> (riscv_cpu->regs[rs2] & 0x1f)));
      break;
    case MATCH_SRLI:
      TRACE_INSN (cpu, "srli %s, %s, %" PRIiTW ";  // %s = %s >> %#" PRIxTW,
		  rd_name, rs1_name, shamt_imm, rd_name, rs1_name, shamt_imm);
      if (RISCV_XLEN (cpu) == 32 && shamt_imm > 0x1f)
	sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			 SIM_SIGILL);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] >> shamt_imm);
      break;
    case MATCH_SRLIW:
      TRACE_INSN (cpu, "srliw %s, %s, %" PRIiTW ";  // %s = %s >> %#" PRIxTW,
		  rd_name, rs1_name, shamt_imm, rd_name, rs1_name, shamt_imm);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		EXTEND32 ((uint32_t) riscv_cpu->regs[rs1] >> shamt_imm));
      break;
    case MATCH_SRA:
      TRACE_INSN (cpu, "sra %s, %s, %s;  // %s = %s >>> %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (RISCV_XLEN (cpu) == 32)
	tmp = ashiftrt (riscv_cpu->regs[rs1], riscv_cpu->regs[rs2] & 0x1f);
      else
	tmp = ashiftrt64 (riscv_cpu->regs[rs1], riscv_cpu->regs[rs2] & 0x3f);
      store_rd (cpu, rd, tmp);
      break;
    case MATCH_SRAW:
      TRACE_INSN (cpu, "sraw %s, %s, %s;  // %s = %s >>> %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd, EXTEND32 (
	ashiftrt ((int32_t) riscv_cpu->regs[rs1],
		  riscv_cpu->regs[rs2] & 0x1f)));
      break;
    case MATCH_SRAI:
      TRACE_INSN (cpu, "srai %s, %s, %" PRIiTW ";  // %s = %s >>> %#" PRIxTW,
		  rd_name, rs1_name, shamt_imm, rd_name, rs1_name, shamt_imm);
      if (RISCV_XLEN (cpu) == 32)
	{
	  if (shamt_imm > 0x1f)
	    sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			     SIM_SIGILL);
	  tmp = ashiftrt (riscv_cpu->regs[rs1], shamt_imm);
	}
      else
	tmp = ashiftrt64 (riscv_cpu->regs[rs1], shamt_imm);
      store_rd (cpu, rd, tmp);
      break;
    case MATCH_SRAIW:
      TRACE_INSN (cpu, "sraiw %s, %s, %" PRIiTW ";  // %s = %s >>> %#" PRIxTW,
		  rd_name, rs1_name, shamt_imm, rd_name, rs1_name, shamt_imm);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd, EXTEND32 (
	ashiftrt ((int32_t) riscv_cpu->regs[rs1], shamt_imm)));
      break;
    case MATCH_SLT:
      TRACE_INSN (cpu, "slt");
      store_rd (cpu, rd,
		!!((signed_word) riscv_cpu->regs[rs1] <
		   (signed_word) riscv_cpu->regs[rs2]));
      break;
    case MATCH_SLTU:
      TRACE_INSN (cpu, "sltu");
      store_rd (cpu, rd, !!((unsigned_word) riscv_cpu->regs[rs1] <
			    (unsigned_word) riscv_cpu->regs[rs2]));
      break;
    case MATCH_SLTI:
      TRACE_INSN (cpu, "slti");
      store_rd (cpu, rd, !!((signed_word) riscv_cpu->regs[rs1] <
			    (signed_word) i_imm));
      break;
    case MATCH_SLTIU:
      TRACE_INSN (cpu, "sltiu");
      store_rd (cpu, rd, !!((unsigned_word) riscv_cpu->regs[rs1] <
			    (unsigned_word) i_imm));
      break;
    case MATCH_AUIPC:
      TRACE_INSN (cpu, "auipc %s, %" PRIiTW ";  // %s = pc + %" PRIiTW,
		  rd_name, u_imm, rd_name, u_imm);
      store_rd (cpu, rd, riscv_cpu->pc + u_imm);
      break;
    case MATCH_BEQ:
      TRACE_INSN (cpu, "beq %s, %s, %#" PRIxTW ";  "
		       "// if (%s == %s) goto %#" PRIxTW,
		  rs1_name, rs2_name, sb_imm, rs1_name, rs2_name, sb_imm);
      if (riscv_cpu->regs[rs1] == riscv_cpu->regs[rs2])
	{
	  pc = riscv_cpu->pc + sb_imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_BLT:
      TRACE_INSN (cpu, "blt %s, %s, %#" PRIxTW ";  "
		       "// if (%s < %s) goto %#" PRIxTW,
		  rs1_name, rs2_name, sb_imm, rs1_name, rs2_name, sb_imm);
      if ((signed_word) riscv_cpu->regs[rs1] <
	  (signed_word) riscv_cpu->regs[rs2])
	{
	  pc = riscv_cpu->pc + sb_imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_BLTU:
      TRACE_INSN (cpu, "bltu %s, %s, %#" PRIxTW ";  "
		       "// if (%s < %s) goto %#" PRIxTW,
		  rs1_name, rs2_name, sb_imm, rs1_name, rs2_name, sb_imm);
      if ((unsigned_word) riscv_cpu->regs[rs1] <
	  (unsigned_word) riscv_cpu->regs[rs2])
	{
	  pc = riscv_cpu->pc + sb_imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_BGE:
      TRACE_INSN (cpu, "bge %s, %s, %#" PRIxTW ";  "
		       "// if (%s >= %s) goto %#" PRIxTW,
		  rs1_name, rs2_name, sb_imm, rs1_name, rs2_name, sb_imm);
      if ((signed_word) riscv_cpu->regs[rs1] >=
	  (signed_word) riscv_cpu->regs[rs2])
	{
	  pc = riscv_cpu->pc + sb_imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_BGEU:
      TRACE_INSN (cpu, "bgeu %s, %s, %#" PRIxTW ";  "
		       "// if (%s >= %s) goto %#" PRIxTW,
		  rs1_name, rs2_name, sb_imm, rs1_name, rs2_name, sb_imm);
      if ((unsigned_word) riscv_cpu->regs[rs1] >=
	  (unsigned_word) riscv_cpu->regs[rs2])
	{
	  pc = riscv_cpu->pc + sb_imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_BNE:
      TRACE_INSN (cpu, "bne %s, %s, %#" PRIxTW ";  "
		       "// if (%s != %s) goto %#" PRIxTW,
		  rs1_name, rs2_name, sb_imm, rs1_name, rs2_name, sb_imm);
      if (riscv_cpu->regs[rs1] != riscv_cpu->regs[rs2])
	{
	  pc = riscv_cpu->pc + sb_imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_JAL:
      TRACE_INSN (cpu, "jal %s, %" PRIiTW ";", rd_name,
		  EXTRACT_JTYPE_IMM (iw));
      store_rd (cpu, rd, riscv_cpu->pc + 4);
      pc = riscv_cpu->pc + EXTRACT_JTYPE_IMM (iw);
      TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
      break;
    case MATCH_JALR:
      TRACE_INSN (cpu, "jalr %s, %s, %" PRIiTW ";", rd_name, rs1_name, i_imm);
      pc = riscv_cpu->regs[rs1] + i_imm;
      store_rd (cpu, rd, riscv_cpu->pc + 4);
      TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
      break;

    case MATCH_LD:
      TRACE_INSN (cpu, "ld %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
	sim_core_read_unaligned_8 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm));
      break;
    case MATCH_LW:
      TRACE_INSN (cpu, "lw %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      store_rd (cpu, rd, EXTEND32 (
	sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm)));
      break;
    case MATCH_LWU:
      TRACE_INSN (cpu, "lwu %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      store_rd (cpu, rd,
	sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm));
      break;
    case MATCH_LH:
      TRACE_INSN (cpu, "lh %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      store_rd (cpu, rd, EXTEND16 (
	sim_core_read_unaligned_2 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm)));
      break;
    case MATCH_LHU:
      TRACE_INSN (cpu, "lbu %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      store_rd (cpu, rd,
	sim_core_read_unaligned_2 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm));
      break;
    case MATCH_LB:
      TRACE_INSN (cpu, "lb %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      store_rd (cpu, rd, EXTEND8 (
	sim_core_read_unaligned_1 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm)));
      break;
    case MATCH_LBU:
      TRACE_INSN (cpu, "lbu %s, %" PRIiTW "(%s);",
		  rd_name, i_imm, rs1_name);
      store_rd (cpu, rd,
	sim_core_read_unaligned_1 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1] + i_imm));
      break;
    case MATCH_SD:
      TRACE_INSN (cpu, "sd %s, %" PRIiTW "(%s);",
		  rs2_name, s_imm, rs1_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      sim_core_write_unaligned_8 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[rs1] + s_imm,
				  riscv_cpu->regs[rs2]);
      break;
    case MATCH_SW:
      TRACE_INSN (cpu, "sw %s, %" PRIiTW "(%s);",
		  rs2_name, s_imm, rs1_name);
      sim_core_write_unaligned_4 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[rs1] + s_imm,
				  riscv_cpu->regs[rs2]);
      break;
    case MATCH_SH:
      TRACE_INSN (cpu, "sh %s, %" PRIiTW "(%s);",
		  rs2_name, s_imm, rs1_name);
      sim_core_write_unaligned_2 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[rs1] + s_imm,
				  riscv_cpu->regs[rs2]);
      break;
    case MATCH_SB:
      TRACE_INSN (cpu, "sb %s, %" PRIiTW "(%s);",
		  rs2_name, s_imm, rs1_name);
      sim_core_write_unaligned_1 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[rs1] + s_imm,
				  riscv_cpu->regs[rs2]);
      break;

    case MATCH_CSRRC:
      TRACE_INSN (cpu, "csrrc");
      switch (csr)
	{
#define DECLARE_CSR(name, num, ...) \
	case num: \
	  store_rd (cpu, rd, \
		    fetch_csr (cpu, #name, num, &riscv_cpu->csr.name)); \
	  store_csr (cpu, #name, num, &riscv_cpu->csr.name, \
		     riscv_cpu->csr.name & !riscv_cpu->regs[rs1]); \
	  break;
#include "opcode/riscv-opc.h"
#undef DECLARE_CSR
	}
      break;
    case MATCH_CSRRS:
      TRACE_INSN (cpu, "csrrs");
      switch (csr)
	{
#define DECLARE_CSR(name, num, ...) \
	case num: \
	  store_rd (cpu, rd, \
		    fetch_csr (cpu, #name, num, &riscv_cpu->csr.name)); \
	  store_csr (cpu, #name, num, &riscv_cpu->csr.name, \
		     riscv_cpu->csr.name | riscv_cpu->regs[rs1]); \
	  break;
#include "opcode/riscv-opc.h"
#undef DECLARE_CSR
	}
      break;
    case MATCH_CSRRW:
      TRACE_INSN (cpu, "csrrw");
      switch (csr)
	{
#define DECLARE_CSR(name, num, ...) \
	case num: \
	  store_rd (cpu, rd, \
		    fetch_csr (cpu, #name, num, &riscv_cpu->csr.name)); \
	  store_csr (cpu, #name, num, &riscv_cpu->csr.name, \
		     riscv_cpu->regs[rs1]); \
	  break;
#include "opcode/riscv-opc.h"
#undef DECLARE_CSR
	}
      break;

    case MATCH_RDCYCLE:
      TRACE_INSN (cpu, "rdcycle %s;", rd_name);
      store_rd (cpu, rd,
		fetch_csr (cpu, "cycle", CSR_CYCLE, &riscv_cpu->csr.cycle));
      break;
    case MATCH_RDCYCLEH:
      TRACE_INSN (cpu, "rdcycleh %s;", rd_name);
      RISCV_ASSERT_RV32 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		fetch_csr (cpu, "cycleh", CSR_CYCLEH, &riscv_cpu->csr.cycleh));
      break;
    case MATCH_RDINSTRET:
      TRACE_INSN (cpu, "rdinstret %s;", rd_name);
      store_rd (cpu, rd,
		fetch_csr (cpu, "instret", CSR_INSTRET,
			   &riscv_cpu->csr.instret));
      break;
    case MATCH_RDINSTRETH:
      TRACE_INSN (cpu, "rdinstreth %s;", rd_name);
      RISCV_ASSERT_RV32 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		fetch_csr (cpu, "instreth", CSR_INSTRETH,
			   &riscv_cpu->csr.instreth));
      break;
    case MATCH_RDTIME:
      TRACE_INSN (cpu, "rdtime %s;", rd_name);
      store_rd (cpu, rd,
		fetch_csr (cpu, "time", CSR_TIME, &riscv_cpu->csr.time));
      break;
    case MATCH_RDTIMEH:
      TRACE_INSN (cpu, "rdtimeh %s;", rd_name);
      RISCV_ASSERT_RV32 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
		fetch_csr (cpu, "timeh", CSR_TIMEH, &riscv_cpu->csr.timeh));
      break;

    case MATCH_FENCE:
      TRACE_INSN (cpu, "fence;");
      break;
    case MATCH_FENCE_I:
      TRACE_INSN (cpu, "fence.i;");
      break;
    case MATCH_EBREAK:
      TRACE_INSN (cpu, "ebreak;");
      sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_stopped, SIM_SIGTRAP);
      break;
    case MATCH_ECALL:
      TRACE_INSN (cpu, "ecall;");
      riscv_cpu->a0 = sim_syscall (cpu, riscv_cpu->a7, riscv_cpu->a0,
				   riscv_cpu->a1, riscv_cpu->a2, riscv_cpu->a3);
      break;
    default:
      TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
      sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled, SIM_SIGILL);
    }

  return pc;
}

static uint64_t
mulhu (uint64_t a, uint64_t b)
{
#ifdef HAVE___INT128
  return ((__int128)a * b) >> 64;
#else
  uint64_t t;
  uint32_t y1, y2, y3;
  uint64_t a0 = (uint32_t)a, a1 = a >> 32;
  uint64_t b0 = (uint32_t)b, b1 = b >> 32;

  t = a1*b0 + ((a0*b0) >> 32);
  y1 = t;
  y2 = t >> 32;

  t = a0*b1 + y1;
  y1 = t;

  t = a1*b1 + y2 + (t >> 32);
  y2 = t;
  y3 = t >> 32;

  return ((uint64_t)y3 << 32) | y2;
#endif
}

static uint64_t
mulh (int64_t a, int64_t b)
{
  int negate = (a < 0) != (b < 0);
  uint64_t res = mulhu (a < 0 ? -a : a, b < 0 ? -b : b);
  return negate ? ~res + (a * b == 0) : res;
}

static uint64_t
mulhsu (int64_t a, uint64_t b)
{
  int negate = a < 0;
  uint64_t res = mulhu (a < 0 ? -a : a, b);
  return negate ? ~res + (a * b == 0) : res;
}

static sim_cia
execute_m (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  SIM_DESC sd = CPU_STATE (cpu);
  int rd = (iw >> OP_SH_RD) & OP_MASK_RD;
  int rs1 = (iw >> OP_SH_RS1) & OP_MASK_RS1;
  int rs2 = (iw >> OP_SH_RS2) & OP_MASK_RS2;
  const char *rd_name = riscv_gpr_names_abi[rd];
  const char *rs1_name = riscv_gpr_names_abi[rs1];
  const char *rs2_name = riscv_gpr_names_abi[rs2];
  unsigned_word tmp, dividend_max;
  sim_cia pc = riscv_cpu->pc + 4;

  dividend_max = -((unsigned_word) 1 << (WITH_TARGET_WORD_BITSIZE - 1));

  switch (op->match)
    {
    case MATCH_DIV:
      TRACE_INSN (cpu, "div %s, %s, %s;  // %s = %s / %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (riscv_cpu->regs[rs1] == dividend_max && riscv_cpu->regs[rs2] == -1)
	tmp = dividend_max;
      else if (riscv_cpu->regs[rs2])
	tmp = (signed_word) riscv_cpu->regs[rs1] /
	  (signed_word) riscv_cpu->regs[rs2];
      else
	tmp = -1;
      store_rd (cpu, rd, tmp);
      break;
    case MATCH_DIVW:
      TRACE_INSN (cpu, "divw %s, %s, %s;  // %s = %s / %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      if (EXTEND32 (riscv_cpu->regs[rs2]) == -1)
	tmp = 1 << 31;
      else if (EXTEND32 (riscv_cpu->regs[rs2]))
	tmp = EXTEND32 (riscv_cpu->regs[rs1]) / EXTEND32 (riscv_cpu->regs[rs2]);
      else
	tmp = -1;
      store_rd (cpu, rd, EXTEND32 (tmp));
      break;
    case MATCH_DIVU:
      TRACE_INSN (cpu, "divu %s, %s, %s;  // %s = %s / %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (riscv_cpu->regs[rs2])
	store_rd (cpu, rd, (unsigned_word) riscv_cpu->regs[rs1]
			   / (unsigned_word) riscv_cpu->regs[rs2]);
      else
	store_rd (cpu, rd, -1);
      break;
    case MATCH_DIVUW:
      TRACE_INSN (cpu, "divuw %s, %s, %s;  // %s = %s / %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      if ((uint32_t) riscv_cpu->regs[rs2])
	tmp = (uint32_t) riscv_cpu->regs[rs1] / (uint32_t) riscv_cpu->regs[rs2];
      else
	tmp = -1;
      store_rd (cpu, rd, EXTEND32 (tmp));
      break;
    case MATCH_MUL:
      TRACE_INSN (cpu, "mul %s, %s, %s;  // %s = %s * %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      store_rd (cpu, rd, riscv_cpu->regs[rs1] * riscv_cpu->regs[rs2]);
      break;
    case MATCH_MULW:
      TRACE_INSN (cpu, "mulw %s, %s, %s;  // %s = %s * %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd, EXTEND32 ((int32_t) riscv_cpu->regs[rs1]
				   * (int32_t) riscv_cpu->regs[rs2]));
      break;
    case MATCH_MULH:
      TRACE_INSN (cpu, "mulh %s, %s, %s;  // %s = %s * %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (RISCV_XLEN (cpu) == 32)
	store_rd (cpu, rd,
		  ((int64_t)(signed_word) riscv_cpu->regs[rs1]
		   * (int64_t)(signed_word) riscv_cpu->regs[rs2]) >> 32);
      else
	store_rd (cpu, rd, mulh (riscv_cpu->regs[rs1], riscv_cpu->regs[rs2]));
      break;
    case MATCH_MULHU:
      TRACE_INSN (cpu, "mulhu %s, %s, %s;  // %s = %s * %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (RISCV_XLEN (cpu) == 32)
	store_rd (cpu, rd, ((uint64_t)riscv_cpu->regs[rs1]
			    * (uint64_t)riscv_cpu->regs[rs2]) >> 32);
      else
	store_rd (cpu, rd, mulhu (riscv_cpu->regs[rs1], riscv_cpu->regs[rs2]));
      break;
    case MATCH_MULHSU:
      TRACE_INSN (cpu, "mulhsu %s, %s, %s;  // %s = %s * %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (RISCV_XLEN (cpu) == 32)
	store_rd (cpu, rd, ((int64_t)(signed_word) riscv_cpu->regs[rs1]
			    * (uint64_t)riscv_cpu->regs[rs2]) >> 32);
      else
	store_rd (cpu, rd, mulhsu (riscv_cpu->regs[rs1], riscv_cpu->regs[rs2]));
      break;
    case MATCH_REM:
      TRACE_INSN (cpu, "rem %s, %s, %s;  // %s = %s %% %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (riscv_cpu->regs[rs1] == dividend_max && riscv_cpu->regs[rs2] == -1)
	tmp = 0;
      else if (riscv_cpu->regs[rs2])
	tmp = (signed_word) riscv_cpu->regs[rs1]
	  % (signed_word) riscv_cpu->regs[rs2];
      else
	tmp = riscv_cpu->regs[rs1];
      store_rd (cpu, rd, tmp);
      break;
    case MATCH_REMW:
      TRACE_INSN (cpu, "remw %s, %s, %s;  // %s = %s %% %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      if (EXTEND32 (riscv_cpu->regs[rs2]) == -1)
	tmp = 0;
      else if (EXTEND32 (riscv_cpu->regs[rs2]))
	tmp = EXTEND32 (riscv_cpu->regs[rs1]) % EXTEND32 (riscv_cpu->regs[rs2]);
      else
	tmp = riscv_cpu->regs[rs1];
      store_rd (cpu, rd, EXTEND32 (tmp));
      break;
    case MATCH_REMU:
      TRACE_INSN (cpu, "remu %s, %s, %s;  // %s = %s %% %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      if (riscv_cpu->regs[rs2])
	store_rd (cpu, rd, riscv_cpu->regs[rs1] % riscv_cpu->regs[rs2]);
      else
	store_rd (cpu, rd, riscv_cpu->regs[rs1]);
      break;
    case MATCH_REMUW:
      TRACE_INSN (cpu, "remuw %s, %s, %s;  // %s = %s %% %s",
		  rd_name, rs1_name, rs2_name, rd_name, rs1_name, rs2_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      if ((uint32_t) riscv_cpu->regs[rs2])
	tmp = (uint32_t) riscv_cpu->regs[rs1] % (uint32_t) riscv_cpu->regs[rs2];
      else
	tmp = riscv_cpu->regs[rs1];
      store_rd (cpu, rd, EXTEND32 (tmp));
      break;
    default:
      TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
      sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled, SIM_SIGILL);
    }

  return pc;
}

static sim_cia
execute_a (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  SIM_DESC sd = CPU_STATE (cpu);
  struct riscv_sim_state *state = RISCV_SIM_STATE (sd);
  int rd = (iw >> OP_SH_RD) & OP_MASK_RD;
  int rs1 = (iw >> OP_SH_RS1) & OP_MASK_RS1;
  int rs2 = (iw >> OP_SH_RS2) & OP_MASK_RS2;
  const char *rd_name = riscv_gpr_names_abi[rd];
  const char *rs1_name = riscv_gpr_names_abi[rs1];
  const char *rs2_name = riscv_gpr_names_abi[rs2];
  struct atomic_mem_reserved_list *amo_prev, *amo_curr;
  unsigned_word tmp;
  sim_cia pc = riscv_cpu->pc + 4;

  /* Handle these two load/store operations specifically.  */
  switch (op->match)
    {
    case MATCH_LR_W:
      TRACE_INSN (cpu, "%s %s, (%s);", op->name, rd_name, rs1_name);
      store_rd (cpu, rd,
	sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
				   riscv_cpu->regs[rs1]));

      /* Walk the reservation list to find an existing match.  */
      amo_curr = state->amo_reserved_list;
      while (amo_curr)
	{
	  if (amo_curr->addr == riscv_cpu->regs[rs1])
	    goto done;
	  amo_curr = amo_curr->next;
	}

      /* No reservation exists, so add one.  */
      amo_curr = xmalloc (sizeof (*amo_curr));
      amo_curr->addr = riscv_cpu->regs[rs1];
      amo_curr->next = state->amo_reserved_list;
      state->amo_reserved_list = amo_curr;
      goto done;
    case MATCH_SC_W:
      TRACE_INSN (cpu, "%s %s, %s, (%s);", op->name, rd_name, rs2_name,
		  rs1_name);

      /* Walk the reservation list to find a match.  */
      amo_curr = amo_prev = state->amo_reserved_list;
      while (amo_curr)
	{
	  if (amo_curr->addr == riscv_cpu->regs[rs1])
	    {
	      /* We found a reservation, so operate it.  */
	      sim_core_write_unaligned_4 (cpu, riscv_cpu->pc, write_map,
					  riscv_cpu->regs[rs1],
					  riscv_cpu->regs[rs2]);
	      store_rd (cpu, rd, 0);
	      if (amo_curr == state->amo_reserved_list)
		state->amo_reserved_list = amo_curr->next;
	      else
		amo_prev->next = amo_curr->next;
	      free (amo_curr);
	      goto done;
	    }
	  amo_prev = amo_curr;
	  amo_curr = amo_curr->next;
	}

      /* If we're still here, then no reservation exists, so mark as failed.  */
      store_rd (cpu, rd, 1);
      goto done;
    }

  /* Handle the rest of the atomic insns with common code paths.  */
  TRACE_INSN (cpu, "%s %s, %s, (%s);",
	      op->name, rd_name, rs2_name, rs1_name);
  if (op->xlen_requirement == 64)
    tmp = sim_core_read_unaligned_8 (cpu, riscv_cpu->pc, read_map,
				     riscv_cpu->regs[rs1]);
  else
    tmp = EXTEND32 (sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
					       riscv_cpu->regs[rs1]));
  store_rd (cpu, rd, tmp);

  switch (op->match)
    {
    case MATCH_AMOADD_D:
    case MATCH_AMOADD_W:
      tmp = riscv_cpu->regs[rd] + riscv_cpu->regs[rs2];
      break;
    case MATCH_AMOAND_D:
    case MATCH_AMOAND_W:
      tmp = riscv_cpu->regs[rd] & riscv_cpu->regs[rs2];
      break;
    case MATCH_AMOMAX_D:
    case MATCH_AMOMAX_W:
      tmp = max ((signed_word) riscv_cpu->regs[rd],
		 (signed_word) riscv_cpu->regs[rs2]);
      break;
    case MATCH_AMOMAXU_D:
    case MATCH_AMOMAXU_W:
      tmp = max ((unsigned_word) riscv_cpu->regs[rd],
		 (unsigned_word) riscv_cpu->regs[rs2]);
      break;
    case MATCH_AMOMIN_D:
    case MATCH_AMOMIN_W:
      tmp = min ((signed_word) riscv_cpu->regs[rd],
		 (signed_word) riscv_cpu->regs[rs2]);
      break;
    case MATCH_AMOMINU_D:
    case MATCH_AMOMINU_W:
      tmp = min ((unsigned_word) riscv_cpu->regs[rd],
		 (unsigned_word) riscv_cpu->regs[rs2]);
      break;
    case MATCH_AMOOR_D:
    case MATCH_AMOOR_W:
      tmp = riscv_cpu->regs[rd] | riscv_cpu->regs[rs2];
      break;
    case MATCH_AMOSWAP_D:
    case MATCH_AMOSWAP_W:
      tmp = riscv_cpu->regs[rs2];
      break;
    case MATCH_AMOXOR_D:
    case MATCH_AMOXOR_W:
      tmp = riscv_cpu->regs[rd] ^ riscv_cpu->regs[rs2];
      break;
    default:
      TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
      sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled, SIM_SIGILL);
    }

  if (op->xlen_requirement == 64)
    sim_core_write_unaligned_8 (cpu, riscv_cpu->pc, write_map,
				riscv_cpu->regs[rs1], tmp);
  else
    sim_core_write_unaligned_4 (cpu, riscv_cpu->pc, write_map,
				riscv_cpu->regs[rs1], tmp);

 done:
  return pc;
}

static sim_cia
execute_c (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
{
  SIM_DESC sd = CPU_STATE (cpu);
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  int rd = (iw >> OP_SH_RD) & OP_MASK_RD;
  int rs1_c = ((iw >> OP_SH_CRS1S) & OP_MASK_CRS1S) + 8;
  int rs2 = (iw >> OP_SH_CRS2) & OP_MASK_CRS2;
  int rs2_c = ((iw >> OP_SH_CRS2S) & OP_MASK_CRS2S) + 8;
  const char *rd_name = riscv_gpr_names_abi[rd];
  const char *rs1_c_name = riscv_gpr_names_abi[rs1_c];
  const char *rs2_name = riscv_gpr_names_abi[rs2];
  const char *rs2_c_name = riscv_gpr_names_abi[rs2_c];
  signed_word imm;
  unsigned_word tmp;
  sim_cia pc = riscv_cpu->pc + 2;

  switch (op->match)
    {
    case MATCH_C_JR | MATCH_C_MV:
      switch (op->mask)
	{
	case MASK_C_MV:
	  TRACE_INSN (cpu, "c.mv %s, %s; // %s = %s",
		      rd_name, rs2_name, rd_name, rs2_name);
	  store_rd (cpu, rd, riscv_cpu->regs[rs2]);
	  break;
	case MASK_C_JR:
	  TRACE_INSN (cpu, "c.jr %s;",
		      rd_name);
	  pc = riscv_cpu->regs[rd];
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	  break;
	}
      break;
    case MATCH_C_J:
      imm = EXTRACT_CJTYPE_IMM (iw);
      TRACE_INSN (cpu, "c.j %" PRIxTW,
		  imm);
      pc = riscv_cpu->pc + imm;
      TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
      break;
    case MATCH_C_JAL | MATCH_C_ADDIW:
      /* JAL and ADDIW have the same mask but are only available on RV32 or
	 RV64 respectively.  */
      if (RISCV_XLEN (cpu) == 32)
	{
	  imm = EXTRACT_CJTYPE_IMM (iw);
	  TRACE_INSN (cpu, "c.jal %" PRIxTW,
		      imm);
	  store_rd (cpu, SIM_RISCV_RA_REGNUM, riscv_cpu->pc + 2);
	  pc = riscv_cpu->pc + imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      else if (RISCV_XLEN (cpu) == 64)
	{
	  imm = EXTRACT_CITYPE_IMM (iw);
	  TRACE_INSN (cpu, "c.addiw %s, %s, %#" PRIxTW ";  // %s += %#" PRIxTW,
		      rd_name, rd_name, imm, rd_name, imm);
	  store_rd (cpu, rd, EXTEND32 (riscv_cpu->regs[rd] + imm));
	}
      else
	{
	  TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
	  sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			   SIM_SIGILL);
	}
      break;
    case MATCH_C_JALR | MATCH_C_ADD | MATCH_C_EBREAK:
      switch (op->mask)
	{
	case MASK_C_ADD:
	  TRACE_INSN (cpu, "c.add %s, %s; // %s += %s",
		      rd_name, rs2_name, rd_name, rs2_name);
	  store_rd (cpu, rd, riscv_cpu->regs[rd] + riscv_cpu->regs[rs2]);
	  break;
	case MASK_C_JALR:
	  TRACE_INSN (cpu, "c.jalr %s, %s;",
		      riscv_gpr_names_abi[SIM_RISCV_RA_REGNUM], rd_name);
	  store_rd (cpu, SIM_RISCV_RA_REGNUM, riscv_cpu->pc + 2);
	  pc = riscv_cpu->regs[rd];
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	  break;
	case MASK_C_EBREAK:
	  TRACE_INSN (cpu, "ebreak");
	  sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_stopped,
			   SIM_SIGTRAP);
	}
      break;
    case MATCH_C_BEQZ:
      imm = EXTRACT_CBTYPE_IMM (iw);
      TRACE_INSN (cpu, "c.beqz %s, %#" PRIxTW ";  "
		       "// if (%s == 0) goto %#" PRIxTW,
		  rs1_c_name, imm, rs1_c_name, riscv_cpu->pc + imm);
      if (riscv_cpu->regs[rs1_c] == riscv_cpu->regs[0])
	{
	  pc = riscv_cpu->pc + imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_C_BNEZ:
      imm = EXTRACT_CBTYPE_IMM (iw);
      TRACE_INSN (cpu, "c.bnez %s, %#" PRIxTW ";  "
		       "// if (%s != 0) goto %#" PRIxTW,
		  rs1_c_name, imm, rs1_c_name, riscv_cpu->pc + imm);
      if (riscv_cpu->regs[rs1_c] != riscv_cpu->regs[0])
	{
	  pc = riscv_cpu->pc + imm;
	  TRACE_BRANCH (cpu, "to %#" PRIxTW, pc);
	}
      break;
    case MATCH_C_LWSP:
      imm = EXTRACT_CITYPE_LWSP_IMM (iw);
      TRACE_INSN (cpu, "c.lwsp %s, %" PRIiTW "(sp);",
		  rd_name, imm);
      store_rd (cpu, rd, EXTEND32 (
		sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
					   riscv_cpu->regs[SIM_RISCV_SP_REGNUM]
					   + imm)));
      break;
    case MATCH_C_LW:
      imm = EXTRACT_CLTYPE_LW_IMM (iw);
      TRACE_INSN (cpu, "c.lw %s, %" PRIiTW "(%s);",
		  rs2_c_name, imm, rs1_c_name);
      store_rd (cpu, rs2_c, EXTEND32 (
	    sim_core_read_unaligned_4 (cpu, riscv_cpu->pc, read_map,
				       riscv_cpu->regs[rs1_c] + imm)));
      break;
    case MATCH_C_SWSP:
      imm = EXTRACT_CSSTYPE_SWSP_IMM (iw);
      TRACE_INSN (cpu, "c.swsp %s, %" PRIiTW "(sp);",
		  rs2_name, imm);
      sim_core_write_unaligned_4 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[SIM_RISCV_SP_REGNUM] + imm,
				  riscv_cpu->regs[rs2]);
      break;
    case MATCH_C_SW:
      imm = EXTRACT_CLTYPE_LW_IMM (iw);
      TRACE_INSN (cpu, "c.sw %s, %" PRIiTW "(%s);",
		  rs2_c_name, imm, rs1_c_name);
      sim_core_write_unaligned_4 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[rs1_c] + (imm),
				  riscv_cpu->regs[rs2_c]);
      break;
    case MATCH_C_ADDI:
      imm = EXTRACT_CITYPE_IMM (iw);
      TRACE_INSN (cpu, "c.addi %s, %s, %#" PRIxTW ";  // %s += %#" PRIxTW,
		  rd_name, rd_name, imm, rd_name, imm);
      store_rd (cpu, rd, riscv_cpu->regs[rd] + imm);
      break;
    case MATCH_C_LUI:
      imm = EXTRACT_CITYPE_LUI_IMM (iw);
      TRACE_INSN (cpu, "c.lui %s, %#" PRIxTW ";",
		  rd_name, imm);
      store_rd (cpu, rd, imm);
      break;
    case MATCH_C_LI:
      imm = EXTRACT_CITYPE_IMM (iw);
      TRACE_INSN (cpu, "c.li %s, %#" PRIxTW ";  // %s = %#" PRIxTW,
		  rd_name, imm, rd_name, imm);
      store_rd (cpu, rd, imm);
      break;
    case MATCH_C_ADDI4SPN:
      imm = EXTRACT_CIWTYPE_ADDI4SPN_IMM (iw);
      TRACE_INSN (cpu, "c.addi4spn %s, %" PRIiTW "; // %s = sp + %" PRIiTW,
		  rs2_c_name, imm, rs2_c_name, imm);
      store_rd (cpu, rs2_c, riscv_cpu->regs[SIM_RISCV_SP_REGNUM] + (imm));
      break;
    case MATCH_C_ADDI16SP:
      imm = EXTRACT_CITYPE_ADDI16SP_IMM (iw);
      TRACE_INSN (cpu, "c.addi16sp %s, %" PRIiTW "; // %s = sp + %" PRIiTW,
		  rd_name, imm, rd_name, imm);
      store_rd (cpu, rd, riscv_cpu->regs[SIM_RISCV_SP_REGNUM] + imm);
      break;
    case MATCH_C_SUB:
      TRACE_INSN (cpu, "c.sub %s, %s;  // %s = %s - %s",
		  rs1_c_name, rs2_c_name, rs1_c_name, rs1_c_name, rs2_c_name);
      store_rd (cpu, rs1_c, riscv_cpu->regs[rs1_c] - riscv_cpu->regs[rs2_c]);
      break;
    case MATCH_C_AND:
      TRACE_INSN (cpu, "c.and %s, %s;  // %s = %s & %s",
		  rs1_c_name, rs2_c_name, rs1_c_name, rs1_c_name, rs2_c_name);
      store_rd (cpu, rs1_c, riscv_cpu->regs[rs1_c] & riscv_cpu->regs[rs2_c]);
      break;
    case MATCH_C_OR:
      TRACE_INSN (cpu, "c.or %s, %s;  // %s = %s | %s",
		  rs1_c_name, rs2_c_name, rs1_c_name, rs1_c_name, rs2_c_name);
      store_rd (cpu, rs1_c, riscv_cpu->regs[rs1_c] | riscv_cpu->regs[rs2_c]);
      break;
    case MATCH_C_XOR:
      TRACE_INSN (cpu, "c.xor %s, %s;  // %s = %s ^ %s",
		  rs1_c_name, rs2_c_name, rs1_c_name, rs1_c_name, rs2_c_name);
      store_rd (cpu, rs1_c, riscv_cpu->regs[rs1_c] ^ riscv_cpu->regs[rs2_c]);
      break;
    case MATCH_C_SLLI | MATCH_C_SLLI64:
      if (op->mask == MASK_C_SLLI64)
	{
	  /* Reserved for custom use.  */
	  TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
	  sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			   SIM_SIGILL);
	  break;
	}
      imm = EXTRACT_CITYPE_IMM (iw);
      TRACE_INSN (cpu, "c.slli %s, %" PRIiTW ";  // %s = %s << %#" PRIxTW,
		  rd_name, imm, rd_name, rd_name, imm);
      store_rd (cpu, rd, riscv_cpu->regs[rd] << imm);
      break;
    case MATCH_C_SRLI | MATCH_C_SRLI64:
      if (op->mask == MASK_C_SRLI64)
	{
	  /* Reserved for custom use.  */
	  TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
	  sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			   SIM_SIGILL);
	  break;
	}
      imm = EXTRACT_CITYPE_IMM (iw);
      TRACE_INSN (cpu, "c.srli %s, %" PRIiTW ";  // %s = %s >> %#" PRIxTW,
		  rs1_c_name, imm, rs1_c_name, rs1_c_name, imm);
      if (RISCV_XLEN (cpu) == 32)
	store_rd (cpu, rs1_c,
		  EXTEND32 ((uint32_t) riscv_cpu->regs[rs1_c] >> imm));
      else
	store_rd (cpu, rs1_c, riscv_cpu->regs[rs1_c] >> imm);
      break;
    case MATCH_C_SRAI | MATCH_C_SRAI64:
      if (op->mask == MASK_C_SRAI64)
	{
	  /* Reserved for custom use.  */
	  TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
	  sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			   SIM_SIGILL);
	  break;
	}
      imm = EXTRACT_CITYPE_IMM (iw);
      TRACE_INSN (cpu, "c.srai %s, %" PRIiTW ";  // %s = %s >> %#" PRIxTW,
		  rs1_c_name, imm, rs1_c_name, rs1_c_name, imm);
      if (RISCV_XLEN (cpu) == 32)
	{
	  if (imm > 0x1f)
	    sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			     SIM_SIGILL);
	  tmp = ashiftrt (riscv_cpu->regs[rs1_c], imm);
	}
      else
	tmp = ashiftrt64 (riscv_cpu->regs[rs1_c], imm);
      store_rd (cpu, rd, tmp);
      break;
    case MATCH_C_ANDI:
      imm = EXTRACT_CITYPE_IMM (iw);
      TRACE_INSN (cpu, "c.andi %s, %" PRIiTW ";  // %s = %s & %#" PRIxTW,
		  rs1_c_name, imm, rs1_c_name, rs1_c_name, imm);
      store_rd (cpu, rs1_c, riscv_cpu->regs[rs1_c] & imm);
      break;
    case MATCH_C_ADDW:
      TRACE_INSN (cpu, "c.addw %s, %s;  // %s = %s + %s",
		  rs1_c_name, rs2_c_name, rs1_c_name, rs1_c_name, rs2_c_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rs1_c,
		EXTEND32 (riscv_cpu->regs[rs1_c] + riscv_cpu->regs[rs2_c]));
      break;
    case MATCH_C_SUBW:
      TRACE_INSN (cpu, "c.subw %s, %s;  // %s = %s - %s",
		  rs1_c_name, rs2_c_name, rs1_c_name, rs1_c_name, rs2_c_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rs1_c,
		EXTEND32 (riscv_cpu->regs[rs1_c] - riscv_cpu->regs[rs2_c]));
      break;
    case MATCH_C_LDSP:
      imm = EXTRACT_CITYPE_LDSP_IMM (iw);
      TRACE_INSN (cpu, "c.ldsp %s, %" PRIiTW "(sp);",
		  rd_name, imm);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rd,
	  sim_core_read_unaligned_8 (cpu, riscv_cpu->pc, read_map,
				     riscv_cpu->regs[SIM_RISCV_SP_REGNUM]
				     + imm));
      break;
    case MATCH_C_LD:
      imm = EXTRACT_CLTYPE_LD_IMM (iw);
      TRACE_INSN (cpu, "c.ld %s, %" PRIiTW "(%s);",
		  rs1_c_name, imm, rs2_c_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      store_rd (cpu, rs2_c,
	  sim_core_read_unaligned_8 (cpu, riscv_cpu->pc, read_map,
				     riscv_cpu->regs[rs1_c] + imm));
      break;
    case MATCH_C_SDSP:
      imm = EXTRACT_CSSTYPE_SDSP_IMM (iw);
      TRACE_INSN (cpu, "c.sdsp %s, %" PRIiTW "(sp);",
		  rs2_name, imm);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      sim_core_write_unaligned_8 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[SIM_RISCV_SP_REGNUM] + imm,
				  riscv_cpu->regs[rs2]);
      break;
    case MATCH_C_SD:
      imm = EXTRACT_CLTYPE_LD_IMM (iw);
      TRACE_INSN (cpu, "c.sd %s, %" PRIiTW "(%s);",
		  rs2_c_name, imm, rs1_c_name);
      RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);
      sim_core_write_unaligned_8 (cpu, riscv_cpu->pc, write_map,
				  riscv_cpu->regs[rs1_c] + imm,
				  riscv_cpu->regs[rs2_c]);
      break;
    default:
      TRACE_INSN (cpu, "UNHANDLED INSN: %s", op->name);
      sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
		       SIM_SIGILL);
  }

  return pc;
}

static sim_cia
execute_one (SIM_CPU *cpu, unsigned_word iw, const struct riscv_opcode *op)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  SIM_DESC sd = CPU_STATE (cpu);

  if (op->xlen_requirement == 32)
    RISCV_ASSERT_RV32 (cpu, "insn: %s", op->name);
  else if (op->xlen_requirement == 64)
    RISCV_ASSERT_RV64 (cpu, "insn: %s", op->name);

  switch (op->insn_class)
    {
    case INSN_CLASS_ZAAMO:
    case INSN_CLASS_ZALRSC:
      return execute_a (cpu, iw, op);
    case INSN_CLASS_C:
      /* Check whether model with C extension is selected.  */
      if (riscv_cpu->csr.misa & 4)
	return execute_c (cpu, iw, op);
      else
	{
	  TRACE_INSN (cpu, "UNHANDLED EXTENSION: %d", op->insn_class);
	  sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled,
			   SIM_SIGILL);
	}
    case INSN_CLASS_I:
      return execute_i (cpu, iw, op);
    case INSN_CLASS_M:
    case INSN_CLASS_ZMMUL:
      return execute_m (cpu, iw, op);
    default:
      TRACE_INSN (cpu, "UNHANDLED EXTENSION: %d", op->insn_class);
      sim_engine_halt (sd, cpu, NULL, riscv_cpu->pc, sim_signalled, SIM_SIGILL);
    }

  return riscv_cpu->pc + riscv_insn_length (iw);
}

/* Decode & execute a single instruction.  */
void step_once (SIM_CPU *cpu)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  SIM_DESC sd = CPU_STATE (cpu);
  unsigned_word iw;
  unsigned int len;
  sim_cia pc = riscv_cpu->pc;
  const struct riscv_opcode *op;
  int xlen = RISCV_XLEN (cpu);

  if (TRACE_ANY_P (cpu))
    trace_prefix (sd, cpu, NULL_CIA, pc, TRACE_LINENUM_P (cpu),
		  NULL, 0, " "); /* Use a space for gcc warnings.  */

  iw = sim_core_read_aligned_2 (cpu, pc, exec_map, pc);

  len = riscv_insn_length (iw);
  if (len != 4 && len != 2)
    {
      sim_io_printf (sd, "sim: bad insn len %#x @ %#" PRIxTA ": %#" PRIxTW "\n",
		     len, pc, iw);
      sim_engine_halt (sd, cpu, NULL, pc, sim_signalled, SIM_SIGILL);
    }

  if (len == 4)
    iw |= ((unsigned_word) sim_core_read_aligned_2
	   (cpu, pc, exec_map, pc + 2) << 16);

  TRACE_CORE (cpu, "0x%08" PRIxTW, iw);

  op = riscv_hash[OP_HASH_IDX (iw)];
  if (!op)
    sim_engine_halt (sd, cpu, NULL, pc, sim_signalled, SIM_SIGILL);

  /* NB: Same loop logic as riscv_disassemble_insn.  */
  for (; op->name; op++)
    {
      /* Does the opcode match?  */
      if (! op->match_func || ! op->match_func (op, iw))
	continue;
      /* Is this a pseudo-instruction and may we print it as such?  */
      if (op->pinfo & INSN_ALIAS)
	continue;
      /* Is this instruction restricted to a certain value of XLEN?  */
      if (op->xlen_requirement != 0 && op->xlen_requirement != xlen)
	continue;

      /* It's a match.  */
      pc = execute_one (cpu, iw, op);
      break;
    }

  /* TODO: Handle overflow into high 32 bits.  */
  /* TODO: Try to use a common counter and only update on demand (reads).  */
  ++riscv_cpu->csr.cycle;
  ++riscv_cpu->csr.instret;

  riscv_cpu->pc = pc;
}

/* Return the program counter for this cpu. */
static sim_cia
pc_get (sim_cpu *cpu)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);

  return riscv_cpu->pc;
}

/* Set the program counter for this cpu to the new pc value. */
static void
pc_set (sim_cpu *cpu, sim_cia pc)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);

  riscv_cpu->pc = pc;
}

static int
reg_fetch (sim_cpu *cpu, int rn, void *buf, int len)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);

  if (len <= 0 || len > sizeof (unsigned_word))
    return -1;

  switch (rn)
    {
    case SIM_RISCV_ZERO_REGNUM:
      memset (buf, 0, len);
      return len;
    case SIM_RISCV_RA_REGNUM ... SIM_RISCV_T6_REGNUM:
      memcpy (buf, &riscv_cpu->regs[rn], len);
      return len;
    case SIM_RISCV_FIRST_FP_REGNUM ... SIM_RISCV_LAST_FP_REGNUM:
      memcpy (buf, &riscv_cpu->fpregs[rn - SIM_RISCV_FIRST_FP_REGNUM], len);
      return len;
    case SIM_RISCV_PC_REGNUM:
      memcpy (buf, &riscv_cpu->pc, len);
      return len;

#define DECLARE_CSR(name, num, ...) \
    case SIM_RISCV_ ## num ## _REGNUM: \
      memcpy (buf, &riscv_cpu->csr.name, len); \
      return len;
#include "opcode/riscv-opc.h"
#undef DECLARE_CSR

    default:
      return -1;
    }
}

static int
reg_store (sim_cpu *cpu, int rn, const void *buf, int len)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);

  if (len <= 0 || len > sizeof (unsigned_word))
    return -1;

  switch (rn)
    {
    case SIM_RISCV_ZERO_REGNUM:
      /* Ignore writes.  */
      return len;
    case SIM_RISCV_RA_REGNUM ... SIM_RISCV_T6_REGNUM:
      memcpy (&riscv_cpu->regs[rn], buf, len);
      return len;
    case SIM_RISCV_FIRST_FP_REGNUM ... SIM_RISCV_LAST_FP_REGNUM:
      memcpy (&riscv_cpu->fpregs[rn - SIM_RISCV_FIRST_FP_REGNUM], buf, len);
      return len;
    case SIM_RISCV_PC_REGNUM:
      memcpy (&riscv_cpu->pc, buf, len);
      return len;

#define DECLARE_CSR(name, num, ...) \
    case SIM_RISCV_ ## num ## _REGNUM: \
      memcpy (&riscv_cpu->csr.name, buf, len); \
      return len;
#include "opcode/riscv-opc.h"
#undef DECLARE_CSR

    default:
      return -1;
    }
}

/* Initialize the state for a single cpu.  Usuaully this involves clearing all
   registers back to their reset state.  Should also hook up the fetch/store
   helper functions too.  */
void
initialize_cpu (SIM_DESC sd, SIM_CPU *cpu, int mhartid)
{
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  const char *extensions;
  int i;

  memset (riscv_cpu->regs, 0, sizeof (riscv_cpu->regs));

  CPU_PC_FETCH (cpu) = pc_get;
  CPU_PC_STORE (cpu) = pc_set;
  CPU_REG_FETCH (cpu) = reg_fetch;
  CPU_REG_STORE (cpu) = reg_store;

  if (!riscv_hash[0])
    {
      const struct riscv_opcode *op;

      for (op = riscv_opcodes; op->name; op++)
	if (!riscv_hash[OP_HASH_IDX (op->match)])
	  riscv_hash[OP_HASH_IDX (op->match)] = op;
    }

  riscv_cpu->csr.misa = 0;
  /* RV32 sets this field to 0, and we don't really support RV128 yet.  */
  if (RISCV_XLEN (cpu) == 64)
    riscv_cpu->csr.misa |= (uint64_t)2 << 62;

  /* Skip the leading "rv" prefix and the two numbers.  */
  extensions = MODEL_NAME (CPU_MODEL (cpu)) + 4;
  for (i = 0; i < 26; ++i)
    {
      char ext = 'A' + i;

      if (ext == 'X')
	continue;
      else if (strchr (extensions, ext) != NULL)
	{
	  if (ext == 'G')
	    riscv_cpu->csr.misa |= 0x1129;  /* G = IMAFD.  */
	  else
	    riscv_cpu->csr.misa |= (1 << i);
	}
    }

  riscv_cpu->csr.mimpid = 0x8000;
  riscv_cpu->csr.mhartid = mhartid;
}

/* Some utils don't like having a NULL environ.  */
static const char * const simple_env[] = { "HOME=/", "PATH=/bin", NULL };

/* Count the number of arguments in an argv.  */
static int
count_argv (const char * const *argv)
{
  int i;

  if (!argv)
    return -1;

  for (i = 0; argv[i] != NULL; ++i)
    continue;
  return i;
}

void
initialize_env (SIM_DESC sd, const char * const *argv, const char * const *env)
{
  SIM_CPU *cpu = STATE_CPU (sd, 0);
  struct riscv_sim_cpu *riscv_cpu = RISCV_SIM_CPU (cpu);
  int i;
  int argc, argv_flat;
  int envc, env_flat;
  address_word sp, sp_flat;
  unsigned char null[8] = { 0, 0, 0, 0, 0, 0, 0, 0, };

  /* Figure out how many bytes the argv strings take up.  */
  argc = count_argv (argv);
  if (argc == -1)
    argc = 0;
  argv_flat = argc; /* NUL bytes.  */
  for (i = 0; i < argc; ++i)
    argv_flat += strlen (argv[i]);

  /* Figure out how many bytes the environ strings take up.  */
  if (!env)
    env = simple_env;
  envc = count_argv (env);
  env_flat = envc; /* NUL bytes.  */
  for (i = 0; i < envc; ++i)
    env_flat += strlen (env[i]);

  /* Make space for the strings themselves.  */
  sp_flat = (DEFAULT_MEM_SIZE - argv_flat - env_flat) & -sizeof (address_word);
  /* Then the pointers to the strings.  */
  sp = sp_flat - ((argc + 1 + envc + 1) * sizeof (address_word));
  /* Then the argc.  */
  sp -= sizeof (unsigned_word);
  /* Align to 16 bytes.  */
  sp = align_down (sp, 16);

  /* Set up the regs the libgloss crt0 expects.  */
  riscv_cpu->a0 = argc;
  riscv_cpu->sp = sp;

  /* First push the argc value.  */
  sim_write (sd, sp, &argc, sizeof (unsigned_word));
  sp += sizeof (unsigned_word);

  /* Then the actual argv strings so we know where to point argv[].  */
  for (i = 0; i < argc; ++i)
    {
      unsigned len = strlen (argv[i]) + 1;
      sim_write (sd, sp_flat, argv[i], len);
      sim_write (sd, sp, &sp_flat, sizeof (address_word));
      sp_flat += len;
      sp += sizeof (address_word);
    }
  sim_write (sd, sp, null, sizeof (address_word));
  sp += sizeof (address_word);

  /* Then the actual env strings so we know where to point env[].  */
  for (i = 0; i < envc; ++i)
    {
      unsigned len = strlen (env[i]) + 1;
      sim_write (sd, sp_flat, env[i], len);
      sim_write (sd, sp, &sp_flat, sizeof (address_word));
      sp_flat += len;
      sp += sizeof (address_word);
    }
}