17bd9597be
This should be done by all target and, since commit 53f6672bcf
("gen-icount: use tcg_ctx.tcg_env instead of cpu_env", 2017-06-30),
is causing the NIOS2 target to hang.
This is because the test for "should I exit to the main loop"
was being done with the correct offset to the icount decrementer,
but using TCG temporary 0 (the frame pointer) rather than the
env pointer.
Cc: qemu-stable@nongnu.org
Cc: Marek Vasut <marex@denx.de>
Reported-by: Thomas Huth <thuth@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
965 lines
31 KiB
C
965 lines
31 KiB
C
/*
|
|
* 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>
|
|
*/
|
|
|
|
#include "cpu.h"
|
|
#include "tcg-op.h"
|
|
#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"
|
|
#include "exec/translator.h"
|
|
|
|
/* is_jmp field values */
|
|
#define DISAS_JUMP DISAS_TARGET_0 /* only pc was modified dynamically */
|
|
#define DISAS_UPDATE DISAS_TARGET_1 /* cpu state was modified dynamically */
|
|
#define DISAS_TB_JUMP DISAS_TARGET_2 /* only pc was modified statically */
|
|
|
|
#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 */
|
|
#define I_TYPE(instr, code) \
|
|
struct { \
|
|
uint8_t op; \
|
|
union { \
|
|
uint16_t u; \
|
|
int16_t s; \
|
|
} imm16; \
|
|
uint8_t b; \
|
|
uint8_t a; \
|
|
} (instr) = { \
|
|
.op = extract32((code), 0, 6), \
|
|
.imm16.u = extract32((code), 6, 16), \
|
|
.b = extract32((code), 22, 5), \
|
|
.a = extract32((code), 27, 5), \
|
|
}
|
|
|
|
/* R-Type instruction parsing */
|
|
#define R_TYPE(instr, code) \
|
|
struct { \
|
|
uint8_t op; \
|
|
uint8_t imm5; \
|
|
uint8_t opx; \
|
|
uint8_t c; \
|
|
uint8_t b; \
|
|
uint8_t a; \
|
|
} (instr) = { \
|
|
.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 */
|
|
#define J_TYPE(instr, code) \
|
|
struct { \
|
|
uint8_t op; \
|
|
uint32_t imm26; \
|
|
} (instr) = { \
|
|
.op = extract32((code), 0, 6), \
|
|
.imm26 = extract32((code), 6, 26), \
|
|
}
|
|
|
|
typedef struct DisasContext {
|
|
TCGv_ptr cpu_env;
|
|
TCGv *cpu_R;
|
|
TCGv_i32 zero;
|
|
int is_jmp;
|
|
target_ulong pc;
|
|
TranslationBlock *tb;
|
|
int mem_idx;
|
|
bool singlestep_enabled;
|
|
} DisasContext;
|
|
|
|
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;
|
|
}
|
|
|
|
static TCGv load_zero(DisasContext *dc)
|
|
{
|
|
if (TCGV_IS_UNUSED_I32(dc->zero)) {
|
|
dc->zero = tcg_const_i32(0);
|
|
}
|
|
return dc->zero;
|
|
}
|
|
|
|
static TCGv load_gpr(DisasContext *dc, uint8_t reg)
|
|
{
|
|
if (likely(reg != R_ZERO)) {
|
|
return dc->cpu_R[reg];
|
|
} else {
|
|
return load_zero(dc);
|
|
}
|
|
}
|
|
|
|
static void t_gen_helper_raise_exception(DisasContext *dc,
|
|
uint32_t index)
|
|
{
|
|
TCGv_i32 tmp = tcg_const_i32(index);
|
|
|
|
tcg_gen_movi_tl(dc->cpu_R[R_PC], dc->pc);
|
|
gen_helper_raise_exception(dc->cpu_env, tmp);
|
|
tcg_temp_free_i32(tmp);
|
|
dc->is_jmp = DISAS_UPDATE;
|
|
}
|
|
|
|
static bool use_goto_tb(DisasContext *dc, uint32_t dest)
|
|
{
|
|
if (unlikely(dc->singlestep_enabled)) {
|
|
return false;
|
|
}
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
return (dc->tb->pc & TARGET_PAGE_MASK) == (dest & TARGET_PAGE_MASK);
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static void gen_goto_tb(DisasContext *dc, int n, uint32_t dest)
|
|
{
|
|
TranslationBlock *tb = dc->tb;
|
|
|
|
if (use_goto_tb(dc, dest)) {
|
|
tcg_gen_goto_tb(n);
|
|
tcg_gen_movi_tl(dc->cpu_R[R_PC], dest);
|
|
tcg_gen_exit_tb((uintptr_t)tb + n);
|
|
} else {
|
|
tcg_gen_movi_tl(dc->cpu_R[R_PC], dest);
|
|
tcg_gen_exit_tb(0);
|
|
}
|
|
}
|
|
|
|
static void gen_excp(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
t_gen_helper_raise_exception(dc, flags);
|
|
}
|
|
|
|
static void gen_check_supervisor(DisasContext *dc)
|
|
{
|
|
if (dc->tb->flags & CR_STATUS_U) {
|
|
/* CPU in user mode, privileged instruction called, stop. */
|
|
t_gen_helper_raise_exception(dc, EXCP_SUPERI);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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));
|
|
dc->is_jmp = DISAS_TB_JUMP;
|
|
}
|
|
|
|
static void call(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
tcg_gen_movi_tl(dc->cpu_R[R_RA], dc->pc + 4);
|
|
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();
|
|
TCGv data;
|
|
|
|
/*
|
|
* WARNING: Loads into R_ZERO are ignored, but we must generate the
|
|
* memory access itself to emulate the CPU precisely. Load
|
|
* from a protected page to R_ZERO will cause SIGSEGV on
|
|
* the Nios2 CPU.
|
|
*/
|
|
if (likely(instr.b != R_ZERO)) {
|
|
data = dc->cpu_R[instr.b];
|
|
} else {
|
|
data = tcg_temp_new();
|
|
}
|
|
|
|
tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s);
|
|
tcg_gen_qemu_ld_tl(data, addr, dc->mem_idx, flags);
|
|
|
|
if (unlikely(instr.b == R_ZERO)) {
|
|
tcg_temp_free(data);
|
|
}
|
|
|
|
tcg_temp_free(addr);
|
|
}
|
|
|
|
/* 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();
|
|
tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s);
|
|
tcg_gen_qemu_st_tl(val, addr, dc->mem_idx, flags);
|
|
tcg_temp_free(addr);
|
|
}
|
|
|
|
/* Branch instructions */
|
|
static void br(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
I_TYPE(instr, code);
|
|
|
|
gen_goto_tb(dc, 0, dc->pc + 4 + (instr.imm16.s & -4));
|
|
dc->is_jmp = DISAS_TB_JUMP;
|
|
}
|
|
|
|
static void gen_bxx(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
I_TYPE(instr, code);
|
|
|
|
TCGLabel *l1 = gen_new_label();
|
|
tcg_gen_brcond_tl(flags, dc->cpu_R[instr.a], dc->cpu_R[instr.b], l1);
|
|
gen_goto_tb(dc, 0, dc->pc + 4);
|
|
gen_set_label(l1);
|
|
gen_goto_tb(dc, 1, dc->pc + 4 + (instr.imm16.s & -4));
|
|
dc->is_jmp = DISAS_TB_JUMP;
|
|
}
|
|
|
|
/* Comparison instructions */
|
|
#define gen_i_cmpxx(fname, op3) \
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
{ \
|
|
I_TYPE(instr, (code)); \
|
|
tcg_gen_setcondi_tl(flags, (dc)->cpu_R[instr.b], (dc)->cpu_R[instr.a], \
|
|
(op3)); \
|
|
}
|
|
|
|
gen_i_cmpxx(gen_cmpxxsi, instr.imm16.s)
|
|
gen_i_cmpxx(gen_cmpxxui, instr.imm16.u)
|
|
|
|
/* Math/logic instructions */
|
|
#define gen_i_math_logic(fname, insn, resimm, op3) \
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
{ \
|
|
I_TYPE(instr, (code)); \
|
|
if (unlikely(instr.b == R_ZERO)) { /* Store to R_ZERO is ignored */ \
|
|
return; \
|
|
} else if (instr.a == R_ZERO) { /* MOVxI optimizations */ \
|
|
tcg_gen_movi_tl(dc->cpu_R[instr.b], (resimm) ? (op3) : 0); \
|
|
} else { \
|
|
tcg_gen_##insn##_tl((dc)->cpu_R[instr.b], (dc)->cpu_R[instr.a], \
|
|
(op3)); \
|
|
} \
|
|
}
|
|
|
|
gen_i_math_logic(addi, addi, 1, instr.imm16.s)
|
|
gen_i_math_logic(muli, muli, 0, instr.imm16.s)
|
|
|
|
gen_i_math_logic(andi, andi, 0, instr.imm16.u)
|
|
gen_i_math_logic(ori, ori, 1, instr.imm16.u)
|
|
gen_i_math_logic(xori, xori, 1, instr.imm16.u)
|
|
|
|
gen_i_math_logic(andhi, andi, 0, instr.imm16.u << 16)
|
|
gen_i_math_logic(orhi , ori, 1, instr.imm16.u << 16)
|
|
gen_i_math_logic(xorhi, xori, 1, instr.imm16.u << 16)
|
|
|
|
/* 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 */
|
|
INSTRUCTION_UNIMPLEMENTED(), /* rdprs */
|
|
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)
|
|
{
|
|
tcg_gen_mov_tl(dc->cpu_R[CR_STATUS], dc->cpu_R[CR_ESTATUS]);
|
|
tcg_gen_mov_tl(dc->cpu_R[R_PC], dc->cpu_R[R_EA]);
|
|
|
|
dc->is_jmp = DISAS_JUMP;
|
|
}
|
|
|
|
/* PC <- ra */
|
|
static void ret(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
tcg_gen_mov_tl(dc->cpu_R[R_PC], dc->cpu_R[R_RA]);
|
|
|
|
dc->is_jmp = DISAS_JUMP;
|
|
}
|
|
|
|
/* PC <- ba */
|
|
static void bret(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
tcg_gen_mov_tl(dc->cpu_R[R_PC], dc->cpu_R[R_BA]);
|
|
|
|
dc->is_jmp = DISAS_JUMP;
|
|
}
|
|
|
|
/* PC <- rA */
|
|
static void jmp(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, code);
|
|
|
|
tcg_gen_mov_tl(dc->cpu_R[R_PC], load_gpr(dc, instr.a));
|
|
|
|
dc->is_jmp = DISAS_JUMP;
|
|
}
|
|
|
|
/* rC <- PC + 4 */
|
|
static void nextpc(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, code);
|
|
|
|
if (likely(instr.c != R_ZERO)) {
|
|
tcg_gen_movi_tl(dc->cpu_R[instr.c], dc->pc + 4);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ra <- PC + 4
|
|
* PC <- rA
|
|
*/
|
|
static void callr(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, code);
|
|
|
|
tcg_gen_mov_tl(dc->cpu_R[R_PC], load_gpr(dc, instr.a));
|
|
tcg_gen_movi_tl(dc->cpu_R[R_RA], dc->pc + 4);
|
|
|
|
dc->is_jmp = DISAS_JUMP;
|
|
}
|
|
|
|
/* rC <- ctlN */
|
|
static void rdctl(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, code);
|
|
|
|
gen_check_supervisor(dc);
|
|
|
|
switch (instr.imm5 + CR_BASE) {
|
|
case CR_PTEADDR:
|
|
case CR_TLBACC:
|
|
case CR_TLBMISC:
|
|
{
|
|
#if !defined(CONFIG_USER_ONLY)
|
|
if (likely(instr.c != R_ZERO)) {
|
|
tcg_gen_mov_tl(dc->cpu_R[instr.c], dc->cpu_R[instr.imm5 + CR_BASE]);
|
|
#ifdef DEBUG_MMU
|
|
TCGv_i32 tmp = tcg_const_i32(instr.imm5 + CR_BASE);
|
|
gen_helper_mmu_read_debug(dc->cpu_R[instr.c], dc->cpu_env, tmp);
|
|
tcg_temp_free_i32(tmp);
|
|
#endif
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (likely(instr.c != R_ZERO)) {
|
|
tcg_gen_mov_tl(dc->cpu_R[instr.c], dc->cpu_R[instr.imm5 + CR_BASE]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ctlN <- rA */
|
|
static void wrctl(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, code);
|
|
|
|
gen_check_supervisor(dc);
|
|
|
|
switch (instr.imm5 + CR_BASE) {
|
|
case CR_PTEADDR:
|
|
case CR_TLBACC:
|
|
case CR_TLBMISC:
|
|
{
|
|
#if !defined(CONFIG_USER_ONLY)
|
|
TCGv_i32 tmp = tcg_const_i32(instr.imm5 + CR_BASE);
|
|
gen_helper_mmu_write(dc->cpu_env, tmp, load_gpr(dc, instr.a));
|
|
tcg_temp_free_i32(tmp);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
default:
|
|
tcg_gen_mov_tl(dc->cpu_R[instr.imm5 + CR_BASE], load_gpr(dc, instr.a));
|
|
break;
|
|
}
|
|
|
|
/* If interrupts were enabled using WRCTL, trigger them. */
|
|
#if !defined(CONFIG_USER_ONLY)
|
|
if ((instr.imm5 + CR_BASE) == CR_STATUS) {
|
|
gen_helper_check_interrupts(dc->cpu_env);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Comparison instructions */
|
|
static void gen_cmpxx(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, code);
|
|
if (likely(instr.c != R_ZERO)) {
|
|
tcg_gen_setcond_tl(flags, dc->cpu_R[instr.c], dc->cpu_R[instr.a],
|
|
dc->cpu_R[instr.b]);
|
|
}
|
|
}
|
|
|
|
/* Math/logic instructions */
|
|
#define gen_r_math_logic(fname, insn, op3) \
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
{ \
|
|
R_TYPE(instr, (code)); \
|
|
if (likely(instr.c != R_ZERO)) { \
|
|
tcg_gen_##insn((dc)->cpu_R[instr.c], load_gpr((dc), instr.a), \
|
|
(op3)); \
|
|
} \
|
|
}
|
|
|
|
gen_r_math_logic(add, add_tl, load_gpr(dc, instr.b))
|
|
gen_r_math_logic(sub, sub_tl, load_gpr(dc, instr.b))
|
|
gen_r_math_logic(mul, mul_tl, load_gpr(dc, instr.b))
|
|
|
|
gen_r_math_logic(and, and_tl, load_gpr(dc, instr.b))
|
|
gen_r_math_logic(or, or_tl, load_gpr(dc, instr.b))
|
|
gen_r_math_logic(xor, xor_tl, load_gpr(dc, instr.b))
|
|
gen_r_math_logic(nor, nor_tl, load_gpr(dc, instr.b))
|
|
|
|
gen_r_math_logic(srai, sari_tl, instr.imm5)
|
|
gen_r_math_logic(srli, shri_tl, instr.imm5)
|
|
gen_r_math_logic(slli, shli_tl, instr.imm5)
|
|
gen_r_math_logic(roli, rotli_tl, instr.imm5)
|
|
|
|
#define gen_r_mul(fname, insn) \
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
{ \
|
|
R_TYPE(instr, (code)); \
|
|
if (likely(instr.c != R_ZERO)) { \
|
|
TCGv t0 = tcg_temp_new(); \
|
|
tcg_gen_##insn(t0, dc->cpu_R[instr.c], \
|
|
load_gpr(dc, instr.a), load_gpr(dc, instr.b)); \
|
|
tcg_temp_free(t0); \
|
|
} \
|
|
}
|
|
|
|
gen_r_mul(mulxss, muls2_tl)
|
|
gen_r_mul(mulxuu, mulu2_tl)
|
|
gen_r_mul(mulxsu, mulsu2_tl)
|
|
|
|
#define gen_r_shift_s(fname, insn) \
|
|
static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
|
|
{ \
|
|
R_TYPE(instr, (code)); \
|
|
if (likely(instr.c != R_ZERO)) { \
|
|
TCGv t0 = tcg_temp_new(); \
|
|
tcg_gen_andi_tl(t0, load_gpr((dc), instr.b), 31); \
|
|
tcg_gen_##insn((dc)->cpu_R[instr.c], load_gpr((dc), instr.a), t0); \
|
|
tcg_temp_free(t0); \
|
|
} \
|
|
}
|
|
|
|
gen_r_shift_s(sra, sar_tl)
|
|
gen_r_shift_s(srl, shr_tl)
|
|
gen_r_shift_s(sll, shl_tl)
|
|
gen_r_shift_s(rol, rotl_tl)
|
|
gen_r_shift_s(ror, rotr_tl)
|
|
|
|
static void divs(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, (code));
|
|
|
|
/* Stores into R_ZERO are ignored */
|
|
if (unlikely(instr.c == R_ZERO)) {
|
|
return;
|
|
}
|
|
|
|
TCGv t0 = tcg_temp_new();
|
|
TCGv t1 = tcg_temp_new();
|
|
TCGv t2 = tcg_temp_new();
|
|
TCGv t3 = tcg_temp_new();
|
|
|
|
tcg_gen_ext32s_tl(t0, load_gpr(dc, instr.a));
|
|
tcg_gen_ext32s_tl(t1, load_gpr(dc, instr.b));
|
|
tcg_gen_setcondi_tl(TCG_COND_EQ, t2, t0, INT_MIN);
|
|
tcg_gen_setcondi_tl(TCG_COND_EQ, t3, t1, -1);
|
|
tcg_gen_and_tl(t2, t2, t3);
|
|
tcg_gen_setcondi_tl(TCG_COND_EQ, t3, t1, 0);
|
|
tcg_gen_or_tl(t2, t2, t3);
|
|
tcg_gen_movi_tl(t3, 0);
|
|
tcg_gen_movcond_tl(TCG_COND_NE, t1, t2, t3, t2, t1);
|
|
tcg_gen_div_tl(dc->cpu_R[instr.c], t0, t1);
|
|
tcg_gen_ext32s_tl(dc->cpu_R[instr.c], dc->cpu_R[instr.c]);
|
|
|
|
tcg_temp_free(t3);
|
|
tcg_temp_free(t2);
|
|
tcg_temp_free(t1);
|
|
tcg_temp_free(t0);
|
|
}
|
|
|
|
static void divu(DisasContext *dc, uint32_t code, uint32_t flags)
|
|
{
|
|
R_TYPE(instr, (code));
|
|
|
|
/* Stores into R_ZERO are ignored */
|
|
if (unlikely(instr.c == R_ZERO)) {
|
|
return;
|
|
}
|
|
|
|
TCGv t0 = tcg_temp_new();
|
|
TCGv t1 = tcg_temp_new();
|
|
TCGv t2 = tcg_const_tl(0);
|
|
TCGv t3 = tcg_const_tl(1);
|
|
|
|
tcg_gen_ext32u_tl(t0, load_gpr(dc, instr.a));
|
|
tcg_gen_ext32u_tl(t1, load_gpr(dc, instr.b));
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, t1, t1, t2, t3, t1);
|
|
tcg_gen_divu_tl(dc->cpu_R[instr.c], t0, t1);
|
|
tcg_gen_ext32s_tl(dc->cpu_R[instr.c], dc->cpu_R[instr.c]);
|
|
|
|
tcg_temp_free(t3);
|
|
tcg_temp_free(t2);
|
|
tcg_temp_free(t1);
|
|
tcg_temp_free(t0);
|
|
}
|
|
|
|
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 */
|
|
INSTRUCTION_UNIMPLEMENTED(), /* wrprs */
|
|
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(),
|
|
INSTRUCTION_FLG(gen_excp, EXCP_TRAP), /* trap */
|
|
INSTRUCTION(wrctl), /* wrctl */
|
|
INSTRUCTION_ILLEGAL(),
|
|
INSTRUCTION_FLG(gen_cmpxx, TCG_COND_LTU), /* cmpltu */
|
|
INSTRUCTION(add), /* add */
|
|
INSTRUCTION_ILLEGAL(),
|
|
INSTRUCTION_ILLEGAL(),
|
|
INSTRUCTION_FLG(gen_excp, EXCP_BREAK), /* break */
|
|
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);
|
|
}
|
|
|
|
static void handle_instruction(DisasContext *dc, CPUNios2State *env)
|
|
{
|
|
uint32_t code;
|
|
uint8_t op;
|
|
const Nios2Instruction *instr;
|
|
#if defined(CONFIG_USER_ONLY)
|
|
/* FIXME: Is this needed ? */
|
|
if (dc->pc >= 0x1000 && dc->pc < 0x2000) {
|
|
env->regs[R_PC] = dc->pc;
|
|
t_gen_helper_raise_exception(dc, 0xaa);
|
|
return;
|
|
}
|
|
#endif
|
|
code = cpu_ldl_code(env, dc->pc);
|
|
op = get_opcode(code);
|
|
|
|
if (unlikely(op >= ARRAY_SIZE(i_type_instructions))) {
|
|
goto illegal_op;
|
|
}
|
|
|
|
TCGV_UNUSED_I32(dc->zero);
|
|
|
|
instr = &i_type_instructions[op];
|
|
instr->handler(dc, code, instr->flags);
|
|
|
|
if (!TCGV_IS_UNUSED_I32(dc->zero)) {
|
|
tcg_temp_free(dc->zero);
|
|
}
|
|
|
|
return;
|
|
|
|
illegal_op:
|
|
t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
|
|
}
|
|
|
|
static const char * const regnames[] = {
|
|
"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",
|
|
"status", "estatus", "bstatus", "ienable",
|
|
"ipending", "cpuid", "reserved0", "exception",
|
|
"pteaddr", "tlbacc", "tlbmisc", "reserved1",
|
|
"badaddr", "config", "mpubase", "mpuacc",
|
|
"reserved2", "reserved3", "reserved4", "reserved5",
|
|
"reserved6", "reserved7", "reserved8", "reserved9",
|
|
"reserved10", "reserved11", "reserved12", "reserved13",
|
|
"reserved14", "reserved15", "reserved16", "reserved17",
|
|
"rpc"
|
|
};
|
|
|
|
static TCGv_ptr cpu_env;
|
|
static TCGv cpu_R[NUM_CORE_REGS];
|
|
|
|
#include "exec/gen-icount.h"
|
|
|
|
static void gen_exception(DisasContext *dc, uint32_t excp)
|
|
{
|
|
TCGv_i32 tmp = tcg_const_i32(excp);
|
|
|
|
tcg_gen_movi_tl(cpu_R[R_PC], dc->pc);
|
|
gen_helper_raise_exception(cpu_env, tmp);
|
|
tcg_temp_free_i32(tmp);
|
|
dc->is_jmp = DISAS_UPDATE;
|
|
}
|
|
|
|
/* generate intermediate code for basic block 'tb'. */
|
|
void gen_intermediate_code(CPUState *cs, TranslationBlock *tb)
|
|
{
|
|
CPUNios2State *env = cs->env_ptr;
|
|
DisasContext dc1, *dc = &dc1;
|
|
int num_insns;
|
|
int max_insns;
|
|
|
|
/* Initialize DC */
|
|
dc->cpu_env = cpu_env;
|
|
dc->cpu_R = cpu_R;
|
|
dc->is_jmp = DISAS_NEXT;
|
|
dc->pc = tb->pc;
|
|
dc->tb = tb;
|
|
dc->mem_idx = cpu_mmu_index(env, false);
|
|
dc->singlestep_enabled = cs->singlestep_enabled;
|
|
|
|
/* Set up instruction counts */
|
|
num_insns = 0;
|
|
if (cs->singlestep_enabled || singlestep) {
|
|
max_insns = 1;
|
|
} else {
|
|
int page_insns = (TARGET_PAGE_SIZE - (tb->pc & TARGET_PAGE_MASK)) / 4;
|
|
max_insns = tb->cflags & CF_COUNT_MASK;
|
|
if (max_insns == 0) {
|
|
max_insns = CF_COUNT_MASK;
|
|
}
|
|
if (max_insns > page_insns) {
|
|
max_insns = page_insns;
|
|
}
|
|
if (max_insns > TCG_MAX_INSNS) {
|
|
max_insns = TCG_MAX_INSNS;
|
|
}
|
|
}
|
|
|
|
gen_tb_start(tb);
|
|
do {
|
|
tcg_gen_insn_start(dc->pc);
|
|
num_insns++;
|
|
|
|
if (unlikely(cpu_breakpoint_test(cs, dc->pc, BP_ANY))) {
|
|
gen_exception(dc, EXCP_DEBUG);
|
|
/* The address covered by the breakpoint must be included in
|
|
[tb->pc, tb->pc + tb->size) in order to for it to be
|
|
properly cleared -- thus we increment the PC here so that
|
|
the logic setting tb->size below does the right thing. */
|
|
dc->pc += 4;
|
|
break;
|
|
}
|
|
|
|
if (num_insns == max_insns && (tb->cflags & CF_LAST_IO)) {
|
|
gen_io_start();
|
|
}
|
|
|
|
/* Decode an instruction */
|
|
handle_instruction(dc, env);
|
|
|
|
dc->pc += 4;
|
|
|
|
/* Translation stops when a conditional branch is encountered.
|
|
* Otherwise the subsequent code could get translated several times.
|
|
* Also stop translation when a page boundary is reached. This
|
|
* ensures prefetch aborts occur at the right place. */
|
|
} while (!dc->is_jmp &&
|
|
!tcg_op_buf_full() &&
|
|
num_insns < max_insns);
|
|
|
|
if (tb->cflags & CF_LAST_IO) {
|
|
gen_io_end();
|
|
}
|
|
|
|
/* Indicate where the next block should start */
|
|
switch (dc->is_jmp) {
|
|
case DISAS_NEXT:
|
|
/* Save the current PC back into the CPU register */
|
|
tcg_gen_movi_tl(cpu_R[R_PC], dc->pc);
|
|
tcg_gen_exit_tb(0);
|
|
break;
|
|
|
|
default:
|
|
case DISAS_JUMP:
|
|
case DISAS_UPDATE:
|
|
/* The jump will already have updated the PC register */
|
|
tcg_gen_exit_tb(0);
|
|
break;
|
|
|
|
case DISAS_TB_JUMP:
|
|
/* nothing more to generate */
|
|
break;
|
|
}
|
|
|
|
/* End off the block */
|
|
gen_tb_end(tb, num_insns);
|
|
|
|
/* Mark instruction starts for the final generated instruction */
|
|
tb->size = dc->pc - tb->pc;
|
|
tb->icount = num_insns;
|
|
|
|
#ifdef DEBUG_DISAS
|
|
if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM)
|
|
&& qemu_log_in_addr_range(tb->pc)) {
|
|
qemu_log_lock();
|
|
qemu_log("IN: %s\n", lookup_symbol(tb->pc));
|
|
log_target_disas(cs, tb->pc, dc->pc - tb->pc, 0);
|
|
qemu_log("\n");
|
|
qemu_log_unlock();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void nios2_cpu_dump_state(CPUState *cs, FILE *f, fprintf_function cpu_fprintf,
|
|
int flags)
|
|
{
|
|
Nios2CPU *cpu = NIOS2_CPU(cs);
|
|
CPUNios2State *env = &cpu->env;
|
|
int i;
|
|
|
|
if (!env || !f) {
|
|
return;
|
|
}
|
|
|
|
cpu_fprintf(f, "IN: PC=%x %s\n",
|
|
env->regs[R_PC], lookup_symbol(env->regs[R_PC]));
|
|
|
|
for (i = 0; i < NUM_CORE_REGS; i++) {
|
|
cpu_fprintf(f, "%9s=%8.8x ", regnames[i], env->regs[i]);
|
|
if ((i + 1) % 4 == 0) {
|
|
cpu_fprintf(f, "\n");
|
|
}
|
|
}
|
|
#if !defined(CONFIG_USER_ONLY)
|
|
cpu_fprintf(f, " mmu write: VPN=%05X PID %02X TLBACC %08X\n",
|
|
env->mmu.pteaddr_wr & CR_PTEADDR_VPN_MASK,
|
|
(env->mmu.tlbmisc_wr & CR_TLBMISC_PID_MASK) >> 4,
|
|
env->mmu.tlbacc_wr);
|
|
#endif
|
|
cpu_fprintf(f, "\n\n");
|
|
}
|
|
|
|
void nios2_tcg_init(void)
|
|
{
|
|
int i;
|
|
|
|
cpu_env = tcg_global_reg_new_ptr(TCG_AREG0, "env");
|
|
tcg_ctx.tcg_env = cpu_env;
|
|
|
|
for (i = 0; i < NUM_CORE_REGS; i++) {
|
|
cpu_R[i] = tcg_global_mem_new(cpu_env,
|
|
offsetof(CPUNios2State, regs[i]),
|
|
regnames[i]);
|
|
}
|
|
}
|
|
|
|
void restore_state_to_opc(CPUNios2State *env, TranslationBlock *tb,
|
|
target_ulong *data)
|
|
{
|
|
env->regs[R_PC] = data[0];
|
|
}
|