2017-01-18 23:01:41 +01:00
|
|
|
/*
|
|
|
|
* Altera Nios II emulation for qemu: main translation routines.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2016 Marek Vasut <marex@denx.de>
|
|
|
|
* Copyright (C) 2012 Chris Wulff <crwulff@gmail.com>
|
|
|
|
* Copyright (C) 2010 Tobias Klauser <tklauser@distanz.ch>
|
|
|
|
* (Portions of this file that were originally from nios2sim-ng.)
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, see
|
|
|
|
* <http://www.gnu.org/licenses/lgpl-2.1.html>
|
|
|
|
*/
|
|
|
|
|
2017-10-17 18:44:01 +02:00
|
|
|
#include "qemu/osdep.h"
|
2017-01-18 23:01:41 +01:00
|
|
|
#include "cpu.h"
|
2020-01-01 12:23:00 +01:00
|
|
|
#include "tcg/tcg-op.h"
|
2017-01-18 23:01:41 +01:00
|
|
|
#include "exec/exec-all.h"
|
|
|
|
#include "disas/disas.h"
|
|
|
|
#include "exec/helper-proto.h"
|
|
|
|
#include "exec/helper-gen.h"
|
|
|
|
#include "exec/log.h"
|
|
|
|
#include "exec/cpu_ldst.h"
|
2017-07-14 10:21:37 +02:00
|
|
|
#include "exec/translator.h"
|
2019-04-17 21:18:02 +02:00
|
|
|
#include "qemu/qemu-print.h"
|
2020-07-13 15:36:11 +02:00
|
|
|
#include "exec/gen-icount.h"
|
2022-04-21 17:17:02 +02:00
|
|
|
#include "semihosting/semihost.h"
|
2017-07-14 10:21:37 +02:00
|
|
|
|
|
|
|
/* is_jmp field values */
|
|
|
|
#define DISAS_UPDATE DISAS_TARGET_1 /* cpu state was modified dynamically */
|
2017-01-18 23:01:41 +01:00
|
|
|
|
|
|
|
#define INSTRUCTION_FLG(func, flags) { (func), (flags) }
|
|
|
|
#define INSTRUCTION(func) \
|
|
|
|
INSTRUCTION_FLG(func, 0)
|
|
|
|
#define INSTRUCTION_NOP() \
|
|
|
|
INSTRUCTION_FLG(nop, 0)
|
|
|
|
#define INSTRUCTION_UNIMPLEMENTED() \
|
|
|
|
INSTRUCTION_FLG(gen_excp, EXCP_UNIMPL)
|
|
|
|
#define INSTRUCTION_ILLEGAL() \
|
|
|
|
INSTRUCTION_FLG(gen_excp, EXCP_ILLEGAL)
|
|
|
|
|
|
|
|
/* Special R-Type instruction opcode */
|
|
|
|
#define INSN_R_TYPE 0x3A
|
|
|
|
|
|
|
|
/* I-Type instruction parsing */
|
2022-04-21 17:17:14 +02:00
|
|
|
typedef struct {
|
|
|
|
uint8_t op;
|
|
|
|
union {
|
|
|
|
uint16_t u;
|
|
|
|
int16_t s;
|
|
|
|
} imm16;
|
|
|
|
uint8_t b;
|
|
|
|
uint8_t a;
|
|
|
|
} InstrIType;
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
#define I_TYPE(instr, code) \
|
2022-04-21 17:17:14 +02:00
|
|
|
InstrIType (instr) = { \
|
2017-01-18 23:01:41 +01:00
|
|
|
.op = extract32((code), 0, 6), \
|
2017-03-03 17:28:49 +01:00
|
|
|
.imm16.u = extract32((code), 6, 16), \
|
2017-01-18 23:01:41 +01:00
|
|
|
.b = extract32((code), 22, 5), \
|
|
|
|
.a = extract32((code), 27, 5), \
|
|
|
|
}
|
|
|
|
|
2022-04-22 18:16:43 +02:00
|
|
|
typedef target_ulong ImmFromIType(const InstrIType *);
|
|
|
|
|
|
|
|
static target_ulong imm_unsigned(const InstrIType *i)
|
|
|
|
{
|
|
|
|
return i->imm16.u;
|
|
|
|
}
|
|
|
|
|
|
|
|
static target_ulong imm_signed(const InstrIType *i)
|
|
|
|
{
|
|
|
|
return i->imm16.s;
|
|
|
|
}
|
|
|
|
|
2022-04-22 18:19:16 +02:00
|
|
|
static target_ulong imm_shifted(const InstrIType *i)
|
|
|
|
{
|
|
|
|
return i->imm16.u << 16;
|
|
|
|
}
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
/* R-Type instruction parsing */
|
2022-04-21 17:17:14 +02:00
|
|
|
typedef struct {
|
|
|
|
uint8_t op;
|
|
|
|
uint8_t imm5;
|
|
|
|
uint8_t opx;
|
|
|
|
uint8_t c;
|
|
|
|
uint8_t b;
|
|
|
|
uint8_t a;
|
|
|
|
} InstrRType;
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
#define R_TYPE(instr, code) \
|
2022-04-21 17:17:14 +02:00
|
|
|
InstrRType (instr) = { \
|
2017-01-18 23:01:41 +01:00
|
|
|
.op = extract32((code), 0, 6), \
|
|
|
|
.imm5 = extract32((code), 6, 5), \
|
|
|
|
.opx = extract32((code), 11, 6), \
|
|
|
|
.c = extract32((code), 17, 5), \
|
|
|
|
.b = extract32((code), 22, 5), \
|
|
|
|
.a = extract32((code), 27, 5), \
|
|
|
|
}
|
|
|
|
|
|
|
|
/* J-Type instruction parsing */
|
2022-04-21 17:17:14 +02:00
|
|
|
typedef struct {
|
|
|
|
uint8_t op;
|
|
|
|
uint32_t imm26;
|
|
|
|
} InstrJType;
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
#define J_TYPE(instr, code) \
|
2022-04-21 17:17:14 +02:00
|
|
|
InstrJType (instr) = { \
|
2017-01-18 23:01:41 +01:00
|
|
|
.op = extract32((code), 0, 6), \
|
|
|
|
.imm26 = extract32((code), 6, 26), \
|
|
|
|
}
|
|
|
|
|
2022-04-22 18:19:16 +02:00
|
|
|
typedef void GenFn2i(TCGv, TCGv, target_long);
|
2022-04-22 18:25:30 +02:00
|
|
|
typedef void GenFn3(TCGv, TCGv, TCGv);
|
2022-04-22 18:28:38 +02:00
|
|
|
typedef void GenFn4(TCGv, TCGv, TCGv, TCGv);
|
2022-04-22 18:19:16 +02:00
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
typedef struct DisasContext {
|
2021-06-20 06:44:48 +02:00
|
|
|
DisasContextBase base;
|
2017-01-18 23:01:41 +01:00
|
|
|
target_ulong pc;
|
|
|
|
int mem_idx;
|
2022-04-21 17:17:24 +02:00
|
|
|
uint32_t tb_flags;
|
2022-04-21 17:17:16 +02:00
|
|
|
TCGv sink;
|
2022-04-21 17:17:08 +02:00
|
|
|
const ControlRegState *cr_state;
|
2022-04-21 17:17:25 +02:00
|
|
|
bool eic_present;
|
2017-01-18 23:01:41 +01:00
|
|
|
} DisasContext;
|
|
|
|
|
2022-04-21 17:16:50 +02:00
|
|
|
static TCGv cpu_R[NUM_GP_REGS];
|
2022-04-21 17:16:47 +02:00
|
|
|
static TCGv cpu_pc;
|
2022-04-21 17:17:24 +02:00
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
|
|
static TCGv cpu_crs_R[NUM_GP_REGS];
|
|
|
|
#endif
|
2021-06-20 06:38:36 +02:00
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
typedef struct Nios2Instruction {
|
|
|
|
void (*handler)(DisasContext *dc, uint32_t code, uint32_t flags);
|
|
|
|
uint32_t flags;
|
|
|
|
} Nios2Instruction;
|
|
|
|
|
|
|
|
static uint8_t get_opcode(uint32_t code)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, code);
|
|
|
|
return instr.op;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t get_opxcode(uint32_t code)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, code);
|
|
|
|
return instr.opx;
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:13 +02:00
|
|
|
static TCGv load_gpr(DisasContext *dc, unsigned reg)
|
2017-01-18 23:01:41 +01:00
|
|
|
{
|
2022-04-21 17:17:13 +02:00
|
|
|
assert(reg < NUM_GP_REGS);
|
2022-04-21 17:17:24 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* With shadow register sets, register r0 does not necessarily contain 0,
|
|
|
|
* but it is overwhelmingly likely that it does -- software is supposed
|
|
|
|
* to have set r0 to 0 in every shadow register set before use.
|
|
|
|
*/
|
|
|
|
if (unlikely(reg == R_ZERO) && FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0)) {
|
2022-04-21 17:17:13 +02:00
|
|
|
return tcg_constant_tl(0);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
2022-04-21 17:17:24 +02:00
|
|
|
if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) {
|
|
|
|
return cpu_R[reg];
|
|
|
|
}
|
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
|
|
|
return cpu_crs_R[reg];
|
|
|
|
#endif
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:16 +02:00
|
|
|
static TCGv dest_gpr(DisasContext *dc, unsigned reg)
|
|
|
|
{
|
|
|
|
assert(reg < NUM_GP_REGS);
|
2022-04-21 17:17:24 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The spec for shadow register sets isn't clear, but we assume that
|
|
|
|
* writes to r0 are discarded regardless of CRS.
|
|
|
|
*/
|
2022-04-21 17:17:16 +02:00
|
|
|
if (unlikely(reg == R_ZERO)) {
|
|
|
|
if (dc->sink == NULL) {
|
|
|
|
dc->sink = tcg_temp_new();
|
|
|
|
}
|
|
|
|
return dc->sink;
|
|
|
|
}
|
2022-04-21 17:17:24 +02:00
|
|
|
if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) {
|
|
|
|
return cpu_R[reg];
|
|
|
|
}
|
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
|
|
|
return cpu_crs_R[reg];
|
|
|
|
#endif
|
2022-04-21 17:17:16 +02:00
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:28 +02:00
|
|
|
static void t_gen_helper_raise_exception(DisasContext *dc, uint32_t index)
|
2017-01-18 23:01:41 +01:00
|
|
|
{
|
2022-04-21 17:17:28 +02:00
|
|
|
/* Note that PC is advanced for all hardware exceptions. */
|
|
|
|
tcg_gen_movi_tl(cpu_pc, dc->base.pc_next);
|
2022-04-21 17:17:13 +02:00
|
|
|
gen_helper_raise_exception(cpu_env, tcg_constant_i32(index));
|
2021-06-20 06:44:48 +02:00
|
|
|
dc->base.is_jmp = DISAS_NORETURN;
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_goto_tb(DisasContext *dc, int n, uint32_t dest)
|
|
|
|
{
|
2021-06-20 06:44:48 +02:00
|
|
|
const TranslationBlock *tb = dc->base.tb;
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2021-06-21 01:24:00 +02:00
|
|
|
if (translator_use_goto_tb(&dc->base, dest)) {
|
2017-01-18 23:01:41 +01:00
|
|
|
tcg_gen_goto_tb(n);
|
2022-04-21 17:16:47 +02:00
|
|
|
tcg_gen_movi_tl(cpu_pc, dest);
|
2018-05-31 03:06:23 +02:00
|
|
|
tcg_gen_exit_tb(tb, n);
|
2017-01-18 23:01:41 +01:00
|
|
|
} else {
|
2022-04-21 17:16:47 +02:00
|
|
|
tcg_gen_movi_tl(cpu_pc, dest);
|
2022-04-21 17:17:22 +02:00
|
|
|
tcg_gen_lookup_and_goto_ptr();
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
2022-04-21 17:17:20 +02:00
|
|
|
dc->base.is_jmp = DISAS_NORETURN;
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:19 +02:00
|
|
|
static void gen_jumpr(DisasContext *dc, int regno, bool is_call)
|
|
|
|
{
|
2022-04-21 17:17:23 +02:00
|
|
|
TCGLabel *l = gen_new_label();
|
|
|
|
TCGv test = tcg_temp_new();
|
|
|
|
TCGv dest = load_gpr(dc, regno);
|
|
|
|
|
|
|
|
tcg_gen_andi_tl(test, dest, 3);
|
|
|
|
tcg_gen_brcondi_tl(TCG_COND_NE, test, 0, l);
|
|
|
|
|
|
|
|
tcg_gen_mov_tl(cpu_pc, dest);
|
2022-04-21 17:17:19 +02:00
|
|
|
if (is_call) {
|
|
|
|
tcg_gen_movi_tl(dest_gpr(dc, R_RA), dc->base.pc_next);
|
|
|
|
}
|
2022-04-21 17:17:22 +02:00
|
|
|
tcg_gen_lookup_and_goto_ptr();
|
2022-04-21 17:17:23 +02:00
|
|
|
|
|
|
|
gen_set_label(l);
|
|
|
|
tcg_gen_st_tl(dest, cpu_env, offsetof(CPUNios2State, ctrl[CR_BADADDR]));
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_UNALIGND);
|
|
|
|
|
2022-04-21 17:17:19 +02:00
|
|
|
dc->base.is_jmp = DISAS_NORETURN;
|
|
|
|
}
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
static void gen_excp(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
t_gen_helper_raise_exception(dc, flags);
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:16:45 +02:00
|
|
|
static bool gen_check_supervisor(DisasContext *dc)
|
2017-01-18 23:01:41 +01:00
|
|
|
{
|
2022-04-21 17:17:24 +02:00
|
|
|
if (FIELD_EX32(dc->tb_flags, TBFLAGS, U)) {
|
2017-01-18 23:01:41 +01:00
|
|
|
/* CPU in user mode, privileged instruction called, stop. */
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_SUPERI);
|
2022-04-21 17:16:45 +02:00
|
|
|
return false;
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
2022-04-21 17:16:45 +02:00
|
|
|
return true;
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Used as a placeholder for all instructions which do not have
|
|
|
|
* an effect on the simulator (e.g. flush, sync)
|
|
|
|
*/
|
|
|
|
static void nop(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
/* Nothing to do here */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* J-Type instructions
|
|
|
|
*/
|
|
|
|
static void jmpi(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
J_TYPE(instr, code);
|
|
|
|
gen_goto_tb(dc, 0, (dc->pc & 0xF0000000) | (instr.imm26 << 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void call(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_movi_tl(dest_gpr(dc, R_RA), dc->base.pc_next);
|
2017-01-18 23:01:41 +01:00
|
|
|
jmpi(dc, code, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* I-Type instructions
|
|
|
|
*/
|
|
|
|
/* Load instructions */
|
|
|
|
static void gen_ldx(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, code);
|
|
|
|
|
|
|
|
TCGv addr = tcg_temp_new();
|
2022-04-21 17:17:16 +02:00
|
|
|
TCGv data = dest_gpr(dc, instr.b);
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2017-03-03 17:28:49 +01:00
|
|
|
tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s);
|
2017-01-18 23:01:41 +01:00
|
|
|
tcg_gen_qemu_ld_tl(data, addr, dc->mem_idx, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Store instructions */
|
|
|
|
static void gen_stx(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, code);
|
|
|
|
TCGv val = load_gpr(dc, instr.b);
|
|
|
|
|
|
|
|
TCGv addr = tcg_temp_new();
|
2017-03-03 17:28:49 +01:00
|
|
|
tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s);
|
2017-01-18 23:01:41 +01:00
|
|
|
tcg_gen_qemu_st_tl(val, addr, dc->mem_idx, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Branch instructions */
|
|
|
|
static void br(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, code);
|
|
|
|
|
2021-06-28 23:20:55 +02:00
|
|
|
gen_goto_tb(dc, 0, dc->base.pc_next + (instr.imm16.s & -4));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_bxx(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, code);
|
|
|
|
|
|
|
|
TCGLabel *l1 = gen_new_label();
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_brcond_tl(flags, load_gpr(dc, instr.a), load_gpr(dc, instr.b), l1);
|
2021-06-28 23:20:55 +02:00
|
|
|
gen_goto_tb(dc, 0, dc->base.pc_next);
|
2017-01-18 23:01:41 +01:00
|
|
|
gen_set_label(l1);
|
2021-06-28 23:20:55 +02:00
|
|
|
gen_goto_tb(dc, 1, dc->base.pc_next + (instr.imm16.s & -4));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Comparison instructions */
|
2022-04-22 18:16:43 +02:00
|
|
|
static void do_i_cmpxx(DisasContext *dc, uint32_t insn,
|
|
|
|
TCGCond cond, ImmFromIType *imm)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, insn);
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_setcondi_tl(cond, dest_gpr(dc, instr.b),
|
|
|
|
load_gpr(dc, instr.a), imm(&instr));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 18:16:43 +02:00
|
|
|
#define gen_i_cmpxx(fname, imm) \
|
|
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
|
|
{ do_i_cmpxx(dc, code, flags, imm); }
|
|
|
|
|
|
|
|
gen_i_cmpxx(gen_cmpxxsi, imm_signed)
|
|
|
|
gen_i_cmpxx(gen_cmpxxui, imm_unsigned)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
|
|
|
/* Math/logic instructions */
|
2022-04-22 18:19:16 +02:00
|
|
|
static void do_i_math_logic(DisasContext *dc, uint32_t insn,
|
|
|
|
GenFn2i *fn, ImmFromIType *imm,
|
|
|
|
bool x_op_0_eq_x)
|
|
|
|
{
|
|
|
|
I_TYPE(instr, insn);
|
|
|
|
target_ulong val;
|
|
|
|
|
|
|
|
if (unlikely(instr.b == R_ZERO)) {
|
|
|
|
/* Store to R_ZERO is ignored -- this catches the canonical NOP. */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
val = imm(&instr);
|
|
|
|
|
2022-04-21 17:17:24 +02:00
|
|
|
if (instr.a == R_ZERO && FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0)) {
|
2022-04-22 18:19:16 +02:00
|
|
|
/* This catches the canonical expansions of movi and movhi. */
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_movi_tl(dest_gpr(dc, instr.b), x_op_0_eq_x ? val : 0);
|
2022-04-22 18:19:16 +02:00
|
|
|
} else {
|
2022-04-21 17:17:16 +02:00
|
|
|
fn(dest_gpr(dc, instr.b), load_gpr(dc, instr.a), val);
|
2022-04-22 18:19:16 +02:00
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 18:19:16 +02:00
|
|
|
#define gen_i_math_logic(fname, insn, x_op_0, imm) \
|
|
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
|
|
{ do_i_math_logic(dc, code, tcg_gen_##insn##_tl, imm, x_op_0); }
|
|
|
|
|
|
|
|
gen_i_math_logic(addi, addi, 1, imm_signed)
|
|
|
|
gen_i_math_logic(muli, muli, 0, imm_signed)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-22 18:19:16 +02:00
|
|
|
gen_i_math_logic(andi, andi, 0, imm_unsigned)
|
|
|
|
gen_i_math_logic(ori, ori, 1, imm_unsigned)
|
|
|
|
gen_i_math_logic(xori, xori, 1, imm_unsigned)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-22 18:19:16 +02:00
|
|
|
gen_i_math_logic(andhi, andi, 0, imm_shifted)
|
|
|
|
gen_i_math_logic(orhi , ori, 1, imm_shifted)
|
|
|
|
gen_i_math_logic(xorhi, xori, 1, imm_shifted)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-21 17:17:25 +02:00
|
|
|
/* rB <- prs.rA + sigma(IMM16) */
|
|
|
|
static void rdprs(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
if (!dc->eic_present) {
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!gen_check_supervisor(dc)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
|
|
|
I_TYPE(instr, code);
|
|
|
|
TCGv dest = dest_gpr(dc, instr.b);
|
|
|
|
gen_helper_rdprs(dest, cpu_env, tcg_constant_i32(instr.a));
|
|
|
|
tcg_gen_addi_tl(dest, dest, instr.imm16.s);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
/* Prototype only, defined below */
|
|
|
|
static void handle_r_type_instr(DisasContext *dc, uint32_t code,
|
|
|
|
uint32_t flags);
|
|
|
|
|
|
|
|
static const Nios2Instruction i_type_instructions[] = {
|
|
|
|
INSTRUCTION(call), /* call */
|
|
|
|
INSTRUCTION(jmpi), /* jmpi */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_UB), /* ldbu */
|
|
|
|
INSTRUCTION(addi), /* addi */
|
|
|
|
INSTRUCTION_FLG(gen_stx, MO_UB), /* stb */
|
|
|
|
INSTRUCTION(br), /* br */
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_SB), /* ldb */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_GE), /* cmpgei */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_UW), /* ldhu */
|
|
|
|
INSTRUCTION(andi), /* andi */
|
|
|
|
INSTRUCTION_FLG(gen_stx, MO_UW), /* sth */
|
|
|
|
INSTRUCTION_FLG(gen_bxx, TCG_COND_GE), /* bge */
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_SW), /* ldh */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_LT), /* cmplti */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_NOP(), /* initda */
|
|
|
|
INSTRUCTION(ori), /* ori */
|
|
|
|
INSTRUCTION_FLG(gen_stx, MO_UL), /* stw */
|
|
|
|
INSTRUCTION_FLG(gen_bxx, TCG_COND_LT), /* blt */
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_UL), /* ldw */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_NE), /* cmpnei */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_NOP(), /* flushda */
|
|
|
|
INSTRUCTION(xori), /* xori */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_bxx, TCG_COND_NE), /* bne */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_EQ), /* cmpeqi */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_UB), /* ldbuio */
|
|
|
|
INSTRUCTION(muli), /* muli */
|
|
|
|
INSTRUCTION_FLG(gen_stx, MO_UB), /* stbio */
|
|
|
|
INSTRUCTION_FLG(gen_bxx, TCG_COND_EQ), /* beq */
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_SB), /* ldbio */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxxui, TCG_COND_GEU), /* cmpgeui */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_UW), /* ldhuio */
|
|
|
|
INSTRUCTION(andhi), /* andhi */
|
|
|
|
INSTRUCTION_FLG(gen_stx, MO_UW), /* sthio */
|
|
|
|
INSTRUCTION_FLG(gen_bxx, TCG_COND_GEU), /* bgeu */
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_SW), /* ldhio */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxxui, TCG_COND_LTU), /* cmpltui */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_UNIMPLEMENTED(), /* custom */
|
|
|
|
INSTRUCTION_NOP(), /* initd */
|
|
|
|
INSTRUCTION(orhi), /* orhi */
|
|
|
|
INSTRUCTION_FLG(gen_stx, MO_SL), /* stwio */
|
|
|
|
INSTRUCTION_FLG(gen_bxx, TCG_COND_LTU), /* bltu */
|
|
|
|
INSTRUCTION_FLG(gen_ldx, MO_UL), /* ldwio */
|
2022-04-21 17:17:25 +02:00
|
|
|
INSTRUCTION(rdprs), /* rdprs */
|
2017-01-18 23:01:41 +01:00
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(handle_r_type_instr, 0), /* R-Type */
|
|
|
|
INSTRUCTION_NOP(), /* flushd */
|
|
|
|
INSTRUCTION(xorhi), /* xorhi */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* R-Type instructions
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* status <- estatus
|
|
|
|
* PC <- ea
|
|
|
|
*/
|
|
|
|
static void eret(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
2022-04-21 17:16:45 +02:00
|
|
|
if (!gen_check_supervisor(dc)) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-21 17:16:44 +02:00
|
|
|
|
2022-04-21 17:16:48 +02:00
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
2022-04-21 17:17:26 +02:00
|
|
|
if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) {
|
|
|
|
TCGv tmp = tcg_temp_new();
|
|
|
|
tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPUNios2State, ctrl[CR_ESTATUS]));
|
|
|
|
gen_helper_eret(cpu_env, tmp, load_gpr(dc, R_EA));
|
|
|
|
} else {
|
|
|
|
gen_helper_eret(cpu_env, load_gpr(dc, R_SSTATUS), load_gpr(dc, R_EA));
|
|
|
|
}
|
2022-04-21 17:16:48 +02:00
|
|
|
dc->base.is_jmp = DISAS_NORETURN;
|
|
|
|
#endif
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* PC <- ra */
|
|
|
|
static void ret(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
2022-04-21 17:17:19 +02:00
|
|
|
gen_jumpr(dc, R_RA, false);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 17:16:49 +02:00
|
|
|
/*
|
|
|
|
* status <- bstatus
|
|
|
|
* PC <- ba
|
|
|
|
*/
|
2017-01-18 23:01:41 +01:00
|
|
|
static void bret(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
2022-04-21 17:16:49 +02:00
|
|
|
if (!gen_check_supervisor(dc)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-21 17:16:49 +02:00
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
2022-04-21 17:16:50 +02:00
|
|
|
TCGv tmp = tcg_temp_new();
|
2022-04-21 17:16:53 +02:00
|
|
|
tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPUNios2State, ctrl[CR_BSTATUS]));
|
2022-04-21 17:17:16 +02:00
|
|
|
gen_helper_eret(cpu_env, tmp, load_gpr(dc, R_BA));
|
2022-04-21 17:16:50 +02:00
|
|
|
|
2022-04-21 17:16:49 +02:00
|
|
|
dc->base.is_jmp = DISAS_NORETURN;
|
|
|
|
#endif
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* PC <- rA */
|
|
|
|
static void jmp(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, code);
|
|
|
|
|
2022-04-21 17:17:19 +02:00
|
|
|
gen_jumpr(dc, instr.a, false);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* rC <- PC + 4 */
|
|
|
|
static void nextpc(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, code);
|
|
|
|
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_movi_tl(dest_gpr(dc, instr.c), dc->base.pc_next);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ra <- PC + 4
|
|
|
|
* PC <- rA
|
|
|
|
*/
|
|
|
|
static void callr(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, code);
|
|
|
|
|
2022-04-21 17:17:19 +02:00
|
|
|
gen_jumpr(dc, instr.a, true);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* rC <- ctlN */
|
|
|
|
static void rdctl(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
2022-04-21 17:17:08 +02:00
|
|
|
if (!gen_check_supervisor(dc)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
2017-01-18 23:01:41 +01:00
|
|
|
R_TYPE(instr, code);
|
2022-04-21 17:17:16 +02:00
|
|
|
TCGv t1, t2, dest = dest_gpr(dc, instr.c);
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-21 17:17:08 +02:00
|
|
|
/* Reserved registers read as zero. */
|
|
|
|
if (nios2_cr_reserved(&dc->cr_state[instr.imm5])) {
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_movi_tl(dest, 0);
|
2022-02-26 12:06:07 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:16:53 +02:00
|
|
|
switch (instr.imm5) {
|
2022-02-26 12:29:42 +01:00
|
|
|
case CR_IPENDING:
|
|
|
|
/*
|
|
|
|
* The value of the ipending register is synthetic.
|
|
|
|
* In hw, this is the AND of a set of hardware irq lines
|
|
|
|
* with the ienable register. In qemu, we re-use the space
|
|
|
|
* of CR_IPENDING to store the set of irq lines, and so we
|
|
|
|
* must perform the AND here, and anywhere else we need the
|
|
|
|
* guest value of ipending.
|
|
|
|
*/
|
2022-04-21 17:16:50 +02:00
|
|
|
t1 = tcg_temp_new();
|
|
|
|
t2 = tcg_temp_new();
|
2022-04-21 17:16:53 +02:00
|
|
|
tcg_gen_ld_tl(t1, cpu_env, offsetof(CPUNios2State, ctrl[CR_IPENDING]));
|
|
|
|
tcg_gen_ld_tl(t2, cpu_env, offsetof(CPUNios2State, ctrl[CR_IENABLE]));
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_and_tl(dest, t1, t2);
|
2022-02-26 12:29:42 +01:00
|
|
|
break;
|
2017-01-18 23:01:41 +01:00
|
|
|
default:
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_ld_tl(dest, cpu_env,
|
2022-04-21 17:16:53 +02:00
|
|
|
offsetof(CPUNios2State, ctrl[instr.imm5]));
|
2017-01-18 23:01:41 +01:00
|
|
|
break;
|
|
|
|
}
|
2022-04-21 17:17:08 +02:00
|
|
|
#endif
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ctlN <- rA */
|
|
|
|
static void wrctl(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
2022-04-21 17:16:45 +02:00
|
|
|
if (!gen_check_supervisor(dc)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-21 17:16:45 +02:00
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
2022-02-26 12:27:32 +01:00
|
|
|
R_TYPE(instr, code);
|
|
|
|
TCGv v = load_gpr(dc, instr.a);
|
2022-04-21 17:17:08 +02:00
|
|
|
uint32_t ofs = offsetof(CPUNios2State, ctrl[instr.imm5]);
|
|
|
|
uint32_t wr = dc->cr_state[instr.imm5].writable;
|
|
|
|
uint32_t ro = dc->cr_state[instr.imm5].readonly;
|
|
|
|
|
|
|
|
/* Skip reserved or readonly registers. */
|
|
|
|
if (wr == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2022-02-26 12:27:32 +01:00
|
|
|
|
2022-04-21 17:16:53 +02:00
|
|
|
switch (instr.imm5) {
|
2017-01-18 23:01:41 +01:00
|
|
|
case CR_PTEADDR:
|
2022-02-26 12:27:32 +01:00
|
|
|
gen_helper_mmu_write_pteaddr(cpu_env, v);
|
|
|
|
break;
|
2017-01-18 23:01:41 +01:00
|
|
|
case CR_TLBACC:
|
2022-02-26 12:27:32 +01:00
|
|
|
gen_helper_mmu_write_tlbacc(cpu_env, v);
|
|
|
|
break;
|
2017-01-18 23:01:41 +01:00
|
|
|
case CR_TLBMISC:
|
2022-02-26 12:27:32 +01:00
|
|
|
gen_helper_mmu_write_tlbmisc(cpu_env, v);
|
2017-01-18 23:01:41 +01:00
|
|
|
break;
|
2022-02-26 12:56:15 +01:00
|
|
|
case CR_STATUS:
|
|
|
|
case CR_IENABLE:
|
|
|
|
/* If interrupts were enabled using WRCTL, trigger them. */
|
|
|
|
dc->base.is_jmp = DISAS_UPDATE;
|
|
|
|
/* fall through */
|
2017-01-18 23:01:41 +01:00
|
|
|
default:
|
2022-04-21 17:17:08 +02:00
|
|
|
if (wr == -1) {
|
|
|
|
/* The register is entirely writable. */
|
|
|
|
tcg_gen_st_tl(v, cpu_env, ofs);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* The register is partially read-only or reserved:
|
|
|
|
* merge the value.
|
|
|
|
*/
|
|
|
|
TCGv n = tcg_temp_new();
|
|
|
|
|
|
|
|
tcg_gen_andi_tl(n, v, wr);
|
|
|
|
|
|
|
|
if (ro != 0) {
|
|
|
|
TCGv o = tcg_temp_new();
|
|
|
|
tcg_gen_ld_tl(o, cpu_env, ofs);
|
|
|
|
tcg_gen_andi_tl(o, o, ro);
|
|
|
|
tcg_gen_or_tl(n, n, o);
|
|
|
|
}
|
|
|
|
|
|
|
|
tcg_gen_st_tl(n, cpu_env, ofs);
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:25 +02:00
|
|
|
/* prs.rC <- rA */
|
|
|
|
static void wrprs(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
if (!dc->eic_present) {
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!gen_check_supervisor(dc)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
g_assert_not_reached();
|
|
|
|
#else
|
|
|
|
R_TYPE(instr, code);
|
|
|
|
gen_helper_wrprs(cpu_env, tcg_constant_i32(instr.c),
|
|
|
|
load_gpr(dc, instr.a));
|
|
|
|
/*
|
|
|
|
* The expected write to PRS[r0] is 0, from CRS[r0].
|
|
|
|
* If not, and CRS == PRS (which we cannot tell from here),
|
|
|
|
* we may now have a non-zero value in our current r0.
|
|
|
|
* By ending the TB, we re-evaluate tb_flags and find out.
|
|
|
|
*/
|
|
|
|
if (instr.c == 0
|
|
|
|
&& (instr.a != 0 || !FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0))) {
|
|
|
|
dc->base.is_jmp = DISAS_UPDATE;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
/* Comparison instructions */
|
|
|
|
static void gen_cmpxx(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, code);
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_setcond_tl(flags, dest_gpr(dc, instr.c),
|
|
|
|
load_gpr(dc, instr.a), load_gpr(dc, instr.b));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Math/logic instructions */
|
2022-04-22 18:25:30 +02:00
|
|
|
static void do_ri_math_logic(DisasContext *dc, uint32_t insn, GenFn2i *fn)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, insn);
|
2022-04-21 17:17:16 +02:00
|
|
|
fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), instr.imm5);
|
2022-04-22 18:25:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void do_rr_math_logic(DisasContext *dc, uint32_t insn, GenFn3 *fn)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, insn);
|
2022-04-21 17:17:16 +02:00
|
|
|
fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), load_gpr(dc, instr.b));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 18:25:30 +02:00
|
|
|
#define gen_ri_math_logic(fname, insn) \
|
|
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
|
|
{ do_ri_math_logic(dc, code, tcg_gen_##insn##_tl); }
|
|
|
|
|
|
|
|
#define gen_rr_math_logic(fname, insn) \
|
|
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
|
|
{ do_rr_math_logic(dc, code, tcg_gen_##insn##_tl); }
|
|
|
|
|
|
|
|
gen_rr_math_logic(add, add)
|
|
|
|
gen_rr_math_logic(sub, sub)
|
|
|
|
gen_rr_math_logic(mul, mul)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-22 18:25:30 +02:00
|
|
|
gen_rr_math_logic(and, and)
|
|
|
|
gen_rr_math_logic(or, or)
|
|
|
|
gen_rr_math_logic(xor, xor)
|
|
|
|
gen_rr_math_logic(nor, nor)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-22 18:25:30 +02:00
|
|
|
gen_ri_math_logic(srai, sari)
|
|
|
|
gen_ri_math_logic(srli, shri)
|
|
|
|
gen_ri_math_logic(slli, shli)
|
|
|
|
gen_ri_math_logic(roli, rotli)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-22 18:28:38 +02:00
|
|
|
static void do_rr_mul_high(DisasContext *dc, uint32_t insn, GenFn4 *fn)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, insn);
|
2022-04-21 17:17:16 +02:00
|
|
|
TCGv discard = tcg_temp_new();
|
2022-04-22 18:28:38 +02:00
|
|
|
|
2022-04-21 17:17:16 +02:00
|
|
|
fn(discard, dest_gpr(dc, instr.c),
|
|
|
|
load_gpr(dc, instr.a), load_gpr(dc, instr.b));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 18:28:38 +02:00
|
|
|
#define gen_rr_mul_high(fname, insn) \
|
|
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
|
|
{ do_rr_mul_high(dc, code, tcg_gen_##insn##_tl); }
|
|
|
|
|
|
|
|
gen_rr_mul_high(mulxss, muls2)
|
|
|
|
gen_rr_mul_high(mulxuu, mulu2)
|
|
|
|
gen_rr_mul_high(mulxsu, mulsu2)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-22 18:32:26 +02:00
|
|
|
static void do_rr_shift(DisasContext *dc, uint32_t insn, GenFn3 *fn)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, insn);
|
2022-04-21 17:17:16 +02:00
|
|
|
TCGv sh = tcg_temp_new();
|
2022-04-22 18:32:26 +02:00
|
|
|
|
2022-04-21 17:17:16 +02:00
|
|
|
tcg_gen_andi_tl(sh, load_gpr(dc, instr.b), 31);
|
|
|
|
fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), sh);
|
2022-04-22 18:32:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#define gen_rr_shift(fname, insn) \
|
|
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
|
|
{ do_rr_shift(dc, code, tcg_gen_##insn##_tl); }
|
|
|
|
|
|
|
|
gen_rr_shift(sra, sar)
|
|
|
|
gen_rr_shift(srl, shr)
|
|
|
|
gen_rr_shift(sll, shl)
|
|
|
|
gen_rr_shift(rol, rotl)
|
|
|
|
gen_rr_shift(ror, rotr)
|
2017-01-18 23:01:41 +01:00
|
|
|
|
|
|
|
static void divs(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, (code));
|
2022-04-21 17:17:16 +02:00
|
|
|
gen_helper_divs(dest_gpr(dc, instr.c), cpu_env,
|
2022-04-21 17:17:12 +02:00
|
|
|
load_gpr(dc, instr.a), load_gpr(dc, instr.b));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void divu(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
R_TYPE(instr, (code));
|
2022-04-21 17:17:16 +02:00
|
|
|
gen_helper_divu(dest_gpr(dc, instr.c), cpu_env,
|
2022-04-21 17:17:12 +02:00
|
|
|
load_gpr(dc, instr.a), load_gpr(dc, instr.b));
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2021-12-21 03:50:06 +01:00
|
|
|
static void trap(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_USER_ONLY
|
|
|
|
/*
|
|
|
|
* The imm5 field is not stored anywhere on real hw; the kernel
|
|
|
|
* has to load the insn and extract the field. But we can make
|
|
|
|
* things easier for cpu_loop if we pop this into env->error_code.
|
|
|
|
*/
|
|
|
|
R_TYPE(instr, code);
|
|
|
|
tcg_gen_st_i32(tcg_constant_i32(instr.imm5), cpu_env,
|
|
|
|
offsetof(CPUNios2State, error_code));
|
|
|
|
#endif
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_TRAP);
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:02 +02:00
|
|
|
static void gen_break(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
|
|
/* The semihosting instruction is "break 1". */
|
2022-08-22 16:12:28 +02:00
|
|
|
bool is_user = FIELD_EX32(dc->tb_flags, TBFLAGS, U);
|
2022-04-21 17:17:02 +02:00
|
|
|
R_TYPE(instr, code);
|
2022-08-22 16:12:28 +02:00
|
|
|
if (semihosting_enabled(is_user) && instr.imm5 == 1) {
|
2022-04-21 17:17:02 +02:00
|
|
|
t_gen_helper_raise_exception(dc, EXCP_SEMIHOST);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_BREAK);
|
|
|
|
}
|
|
|
|
|
2017-01-18 23:01:41 +01:00
|
|
|
static const Nios2Instruction r_type_instructions[] = {
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(eret), /* eret */
|
|
|
|
INSTRUCTION(roli), /* roli */
|
|
|
|
INSTRUCTION(rol), /* rol */
|
|
|
|
INSTRUCTION_NOP(), /* flushp */
|
|
|
|
INSTRUCTION(ret), /* ret */
|
|
|
|
INSTRUCTION(nor), /* nor */
|
|
|
|
INSTRUCTION(mulxuu), /* mulxuu */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_GE), /* cmpge */
|
|
|
|
INSTRUCTION(bret), /* bret */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(ror), /* ror */
|
|
|
|
INSTRUCTION_NOP(), /* flushi */
|
|
|
|
INSTRUCTION(jmp), /* jmp */
|
|
|
|
INSTRUCTION(and), /* and */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_LT), /* cmplt */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(slli), /* slli */
|
|
|
|
INSTRUCTION(sll), /* sll */
|
2022-04-21 17:17:25 +02:00
|
|
|
INSTRUCTION(wrprs), /* wrprs */
|
2017-01-18 23:01:41 +01:00
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(or), /* or */
|
|
|
|
INSTRUCTION(mulxsu), /* mulxsu */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_NE), /* cmpne */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(srli), /* srli */
|
|
|
|
INSTRUCTION(srl), /* srl */
|
|
|
|
INSTRUCTION(nextpc), /* nextpc */
|
|
|
|
INSTRUCTION(callr), /* callr */
|
|
|
|
INSTRUCTION(xor), /* xor */
|
|
|
|
INSTRUCTION(mulxss), /* mulxss */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_EQ), /* cmpeq */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(divu), /* divu */
|
|
|
|
INSTRUCTION(divs), /* div */
|
|
|
|
INSTRUCTION(rdctl), /* rdctl */
|
|
|
|
INSTRUCTION(mul), /* mul */
|
|
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_GEU), /* cmpgeu */
|
|
|
|
INSTRUCTION_NOP(), /* initi */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
2021-12-21 03:50:06 +01:00
|
|
|
INSTRUCTION(trap), /* trap */
|
2017-01-18 23:01:41 +01:00
|
|
|
INSTRUCTION(wrctl), /* wrctl */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_LTU), /* cmpltu */
|
|
|
|
INSTRUCTION(add), /* add */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
2022-04-21 17:17:02 +02:00
|
|
|
INSTRUCTION(gen_break), /* break */
|
2017-01-18 23:01:41 +01:00
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(nop), /* nop */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION(sub), /* sub */
|
|
|
|
INSTRUCTION(srai), /* srai */
|
|
|
|
INSTRUCTION(sra), /* sra */
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
INSTRUCTION_ILLEGAL(),
|
|
|
|
};
|
|
|
|
|
|
|
|
static void handle_r_type_instr(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
|
|
{
|
|
|
|
uint8_t opx;
|
|
|
|
const Nios2Instruction *instr;
|
|
|
|
|
|
|
|
opx = get_opxcode(code);
|
|
|
|
if (unlikely(opx >= ARRAY_SIZE(r_type_instructions))) {
|
|
|
|
goto illegal_op;
|
|
|
|
}
|
|
|
|
|
|
|
|
instr = &r_type_instructions[opx];
|
|
|
|
instr->handler(dc, code, instr->flags);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
illegal_op:
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:16:53 +02:00
|
|
|
static const char * const gr_regnames[NUM_GP_REGS] = {
|
2017-01-18 23:01:41 +01:00
|
|
|
"zero", "at", "r2", "r3",
|
|
|
|
"r4", "r5", "r6", "r7",
|
|
|
|
"r8", "r9", "r10", "r11",
|
|
|
|
"r12", "r13", "r14", "r15",
|
|
|
|
"r16", "r17", "r18", "r19",
|
|
|
|
"r20", "r21", "r22", "r23",
|
|
|
|
"et", "bt", "gp", "sp",
|
|
|
|
"fp", "ea", "ba", "ra",
|
2022-04-21 17:16:53 +02:00
|
|
|
};
|
|
|
|
|
2022-04-21 17:16:54 +02:00
|
|
|
#ifndef CONFIG_USER_ONLY
|
2022-04-21 17:16:53 +02:00
|
|
|
static const char * const cr_regnames[NUM_CR_REGS] = {
|
2017-01-18 23:01:41 +01:00
|
|
|
"status", "estatus", "bstatus", "ienable",
|
2022-04-21 17:16:54 +02:00
|
|
|
"ipending", "cpuid", "res6", "exception",
|
2017-01-18 23:01:41 +01:00
|
|
|
"pteaddr", "tlbacc", "tlbmisc", "reserved1",
|
|
|
|
"badaddr", "config", "mpubase", "mpuacc",
|
2022-04-21 17:16:54 +02:00
|
|
|
"res16", "res17", "res18", "res19",
|
|
|
|
"res20", "res21", "res22", "res23",
|
|
|
|
"res24", "res25", "res26", "res27",
|
|
|
|
"res28", "res29", "res30", "res31",
|
2017-01-18 23:01:41 +01:00
|
|
|
};
|
2022-04-21 17:16:54 +02:00
|
|
|
#endif
|
2017-01-18 23:01:41 +01:00
|
|
|
|
|
|
|
/* generate intermediate code for basic block 'tb'. */
|
2021-06-20 07:12:17 +02:00
|
|
|
static void nios2_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
|
2017-01-18 23:01:41 +01:00
|
|
|
{
|
2021-06-20 07:12:17 +02:00
|
|
|
DisasContext *dc = container_of(dcbase, DisasContext, base);
|
2017-07-14 10:17:35 +02:00
|
|
|
CPUNios2State *env = cs->env_ptr;
|
2022-04-21 17:17:08 +02:00
|
|
|
Nios2CPU *cpu = env_archcpu(env);
|
2021-06-20 07:12:17 +02:00
|
|
|
int page_insns;
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
dc->mem_idx = cpu_mmu_index(env, false);
|
2022-04-21 17:17:08 +02:00
|
|
|
dc->cr_state = cpu->cr_state;
|
2022-04-21 17:17:24 +02:00
|
|
|
dc->tb_flags = dc->base.tb->flags;
|
2022-04-21 17:17:25 +02:00
|
|
|
dc->eic_present = cpu->eic_present;
|
2021-06-20 06:44:48 +02:00
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
/* Bound the number of insns to execute to those left on the page. */
|
|
|
|
page_insns = -(dc->base.pc_first | TARGET_PAGE_MASK) / 4;
|
|
|
|
dc->base.max_insns = MIN(page_insns, dc->base.max_insns);
|
|
|
|
}
|
2021-06-20 06:44:48 +02:00
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
static void nios2_tr_tb_start(DisasContextBase *db, CPUState *cs)
|
|
|
|
{
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
static void nios2_tr_insn_start(DisasContextBase *dcbase, CPUState *cs)
|
|
|
|
{
|
|
|
|
tcg_gen_insn_start(dcbase->pc_next);
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
static void nios2_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs)
|
|
|
|
{
|
|
|
|
DisasContext *dc = container_of(dcbase, DisasContext, base);
|
|
|
|
CPUNios2State *env = cs->env_ptr;
|
2021-06-28 23:04:24 +02:00
|
|
|
const Nios2Instruction *instr;
|
|
|
|
uint32_t code, pc;
|
|
|
|
uint8_t op;
|
2021-06-20 06:44:48 +02:00
|
|
|
|
2021-06-28 23:04:24 +02:00
|
|
|
pc = dc->base.pc_next;
|
|
|
|
dc->pc = pc;
|
|
|
|
dc->base.pc_next = pc + 4;
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
/* Decode an instruction */
|
2021-06-28 23:04:24 +02:00
|
|
|
code = cpu_ldl_code(env, pc);
|
|
|
|
op = get_opcode(code);
|
|
|
|
|
|
|
|
if (unlikely(op >= ARRAY_SIZE(i_type_instructions))) {
|
|
|
|
t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:17:16 +02:00
|
|
|
dc->sink = NULL;
|
|
|
|
|
2021-06-28 23:04:24 +02:00
|
|
|
instr = &i_type_instructions[op];
|
|
|
|
instr->handler(dc, code, instr->flags);
|
2021-06-20 07:12:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void nios2_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs)
|
|
|
|
{
|
|
|
|
DisasContext *dc = container_of(dcbase, DisasContext, base);
|
2017-01-18 23:01:41 +01:00
|
|
|
|
|
|
|
/* Indicate where the next block should start */
|
2021-06-20 06:44:48 +02:00
|
|
|
switch (dc->base.is_jmp) {
|
2021-06-20 07:12:17 +02:00
|
|
|
case DISAS_TOO_MANY:
|
2022-04-21 17:17:21 +02:00
|
|
|
gen_goto_tb(dc, 0, dc->base.pc_next);
|
|
|
|
break;
|
|
|
|
|
2020-07-13 15:36:10 +02:00
|
|
|
case DISAS_UPDATE:
|
2022-04-21 17:17:21 +02:00
|
|
|
/* Save the current PC, and return to the main loop. */
|
2022-04-21 17:16:47 +02:00
|
|
|
tcg_gen_movi_tl(cpu_pc, dc->base.pc_next);
|
2018-05-31 03:06:23 +02:00
|
|
|
tcg_gen_exit_tb(NULL, 0);
|
2017-01-18 23:01:41 +01:00
|
|
|
break;
|
|
|
|
|
2020-07-13 15:36:10 +02:00
|
|
|
case DISAS_NORETURN:
|
2017-01-18 23:01:41 +01:00
|
|
|
/* nothing more to generate */
|
|
|
|
break;
|
|
|
|
|
2021-06-20 07:12:17 +02:00
|
|
|
default:
|
|
|
|
g_assert_not_reached();
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
2021-06-20 07:12:17 +02:00
|
|
|
}
|
|
|
|
|
2022-04-17 20:29:52 +02:00
|
|
|
static void nios2_tr_disas_log(const DisasContextBase *dcbase,
|
|
|
|
CPUState *cpu, FILE *logfile)
|
2021-06-20 07:12:17 +02:00
|
|
|
{
|
2022-04-17 20:29:52 +02:00
|
|
|
fprintf(logfile, "IN: %s\n", lookup_symbol(dcbase->pc_first));
|
|
|
|
target_disas(logfile, cpu, dcbase->pc_first, dcbase->tb->size);
|
2021-06-20 07:12:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static const TranslatorOps nios2_tr_ops = {
|
|
|
|
.init_disas_context = nios2_tr_init_disas_context,
|
|
|
|
.tb_start = nios2_tr_tb_start,
|
|
|
|
.insn_start = nios2_tr_insn_start,
|
|
|
|
.translate_insn = nios2_tr_translate_insn,
|
|
|
|
.tb_stop = nios2_tr_tb_stop,
|
|
|
|
.disas_log = nios2_tr_disas_log,
|
|
|
|
};
|
|
|
|
|
2023-01-29 02:19:22 +01:00
|
|
|
void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int *max_insns,
|
2022-08-11 22:48:03 +02:00
|
|
|
target_ulong pc, void *host_pc)
|
2021-06-20 07:12:17 +02:00
|
|
|
{
|
|
|
|
DisasContext dc;
|
2022-08-11 22:48:03 +02:00
|
|
|
translator_loop(cs, tb, max_insns, pc, host_pc, &nios2_tr_ops, &dc.base);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
2019-04-17 21:18:02 +02:00
|
|
|
void nios2_cpu_dump_state(CPUState *cs, FILE *f, int flags)
|
2017-01-18 23:01:41 +01:00
|
|
|
{
|
|
|
|
Nios2CPU *cpu = NIOS2_CPU(cs);
|
|
|
|
CPUNios2State *env = &cpu->env;
|
|
|
|
int i;
|
|
|
|
|
2022-04-21 17:16:47 +02:00
|
|
|
qemu_fprintf(f, "IN: PC=%x %s\n", env->pc, lookup_symbol(env->pc));
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-21 17:16:53 +02:00
|
|
|
for (i = 0; i < NUM_GP_REGS; i++) {
|
|
|
|
qemu_fprintf(f, "%9s=%8.8x ", gr_regnames[i], env->regs[i]);
|
|
|
|
if ((i + 1) % 4 == 0) {
|
|
|
|
qemu_fprintf(f, "\n");
|
|
|
|
}
|
|
|
|
}
|
2022-04-21 17:16:54 +02:00
|
|
|
|
|
|
|
#if !defined(CONFIG_USER_ONLY)
|
2022-04-21 17:17:08 +02:00
|
|
|
int j;
|
|
|
|
|
|
|
|
for (i = j = 0; i < NUM_CR_REGS; i++) {
|
|
|
|
if (!nios2_cr_reserved(&cpu->cr_state[i])) {
|
|
|
|
qemu_fprintf(f, "%9s=%8.8x ", cr_regnames[i], env->ctrl[i]);
|
|
|
|
if (++j % 4 == 0) {
|
|
|
|
qemu_fprintf(f, "\n");
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
}
|
2022-04-21 17:17:08 +02:00
|
|
|
if (j % 4 != 0) {
|
|
|
|
qemu_fprintf(f, "\n");
|
|
|
|
}
|
|
|
|
if (cpu->mmu_present) {
|
|
|
|
qemu_fprintf(f, " mmu write: VPN=%05X PID %02X TLBACC %08X\n",
|
|
|
|
env->mmu.pteaddr_wr & R_CR_PTEADDR_VPN_MASK,
|
|
|
|
FIELD_EX32(env->mmu.tlbmisc_wr, CR_TLBMISC, PID),
|
|
|
|
env->mmu.tlbacc_wr);
|
|
|
|
}
|
2017-01-18 23:01:41 +01:00
|
|
|
#endif
|
2019-04-17 21:18:02 +02:00
|
|
|
qemu_fprintf(f, "\n\n");
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void nios2_tcg_init(void)
|
|
|
|
{
|
2022-04-21 17:17:24 +02:00
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
|
|
TCGv_ptr crs = tcg_global_mem_new_ptr(cpu_env,
|
|
|
|
offsetof(CPUNios2State, regs), "crs");
|
2017-01-18 23:01:41 +01:00
|
|
|
|
2022-04-21 17:17:24 +02:00
|
|
|
for (int i = 0; i < NUM_GP_REGS; i++) {
|
|
|
|
cpu_crs_R[i] = tcg_global_mem_new(crs, 4 * i, gr_regnames[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define offsetof_regs0(N) offsetof(CPUNios2State, shadow_regs[0][N])
|
|
|
|
#else
|
|
|
|
#define offsetof_regs0(N) offsetof(CPUNios2State, regs[N])
|
|
|
|
#endif
|
|
|
|
|
|
|
|
for (int i = 0; i < NUM_GP_REGS; i++) {
|
|
|
|
cpu_R[i] = tcg_global_mem_new(cpu_env, offsetof_regs0(i),
|
2022-04-21 17:16:53 +02:00
|
|
|
gr_regnames[i]);
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|
2022-04-21 17:17:24 +02:00
|
|
|
|
|
|
|
#undef offsetof_regs0
|
|
|
|
|
2022-04-21 17:16:47 +02:00
|
|
|
cpu_pc = tcg_global_mem_new(cpu_env,
|
|
|
|
offsetof(CPUNios2State, pc), "pc");
|
2017-01-18 23:01:41 +01:00
|
|
|
}
|