aboutsummaryrefslogtreecommitdiff
path: root/sim/or1k/traps.c
blob: 6e689cfe9ab5ae16005c7ab2cb83bc6d491a0b63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/* OpenRISC exception, interrupts, syscall and trap support
   Copyright (C) 2017-2023 Free Software Foundation, Inc.

   This file is part of GDB, the GNU debugger.

   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 must come before any other includes.  */
#include "defs.h"

#define WANT_CPU_OR1K32BF
#define WANT_CPU

#include "sim-main.h"
#include "sim-fpu.h"
#include "sim-signal.h"
#include "cgen-ops.h"

/* Implement the sim invalid instruction function.  This will set the error
   effective address to that of the invalid instruction then call the
   exception handler.  */

SEM_PC
sim_engine_invalid_insn (SIM_CPU *current_cpu, IADDR cia, SEM_PC vpc)
{
  SET_H_SYS_EEAR0 (cia);

#ifdef WANT_CPU_OR1K32BF
  or1k32bf_exception (current_cpu, cia, EXCEPT_ILLEGAL);
#endif

  return vpc;
}

/* Generate the appropriate OpenRISC fpu exception based on the status code from
   the sim fpu.  */
void
or1k32bf_fpu_error (CGEN_FPU* fpu, int status)
{
  SIM_CPU *current_cpu = (SIM_CPU *)fpu->owner;

  /* If floating point exceptions are enabled.  */
  if (GET_H_SYS_FPCSR_FPEE() != 0)
    {
      /* Set all of the status flag bits.  */
      if (status
	  & (sim_fpu_status_invalid_snan
	     | sim_fpu_status_invalid_qnan
	     | sim_fpu_status_invalid_isi
	     | sim_fpu_status_invalid_idi
	     | sim_fpu_status_invalid_zdz
	     | sim_fpu_status_invalid_imz
	     | sim_fpu_status_invalid_cvi
	     | sim_fpu_status_invalid_cmp
	     | sim_fpu_status_invalid_sqrt))
	SET_H_SYS_FPCSR_IVF (1);

      if (status & sim_fpu_status_invalid_snan)
	SET_H_SYS_FPCSR_SNF (1);

      if (status & sim_fpu_status_invalid_qnan)
	SET_H_SYS_FPCSR_QNF (1);

      if (status & sim_fpu_status_overflow)
	SET_H_SYS_FPCSR_OVF (1);

      if (status & sim_fpu_status_underflow)
	SET_H_SYS_FPCSR_UNF (1);

      if (status
	  & (sim_fpu_status_invalid_isi
	     | sim_fpu_status_invalid_idi))
	SET_H_SYS_FPCSR_INF (1);

      if (status & sim_fpu_status_invalid_div0)
	SET_H_SYS_FPCSR_DZF (1);

      if (status & sim_fpu_status_inexact)
	SET_H_SYS_FPCSR_IXF (1);

      /* If any of the exception bits were actually set.  */
      if (GET_H_SYS_FPCSR()
	  & (SPR_FIELD_MASK_SYS_FPCSR_IVF
	     | SPR_FIELD_MASK_SYS_FPCSR_SNF
	     | SPR_FIELD_MASK_SYS_FPCSR_QNF
	     | SPR_FIELD_MASK_SYS_FPCSR_OVF
	     | SPR_FIELD_MASK_SYS_FPCSR_UNF
	     | SPR_FIELD_MASK_SYS_FPCSR_INF
	     | SPR_FIELD_MASK_SYS_FPCSR_DZF
	     | SPR_FIELD_MASK_SYS_FPCSR_IXF))
	{
	  SIM_DESC sd = CPU_STATE (current_cpu);

	  /* If the sim is running in fast mode, i.e. not profiling,
	     per-instruction callbacks are not triggered which would allow
	     us to track the PC.  This means we cannot track which
	     instruction caused the FPU error.  */
	  if (!PROFILE_ANY_P (current_cpu) && !TRACE_ANY_P (current_cpu))
	    sim_io_eprintf
	      (sd, "WARNING: ignoring fpu error caught in fast mode.\n");
	  else
	    or1k32bf_exception (current_cpu, GET_H_SYS_PPC (), EXCEPT_FPE);
	}
    }
}


/* Implement the OpenRISC exception function.  This is mostly used by the
   CGEN generated files.  For example, this is used when handling a
   overflow exception during a multiplication instruction. */

void
or1k32bf_exception (sim_cpu *current_cpu, USI pc, USI exnum)
{
  SIM_DESC sd = CPU_STATE (current_cpu);
  struct or1k_sim_cpu *or1k_cpu = OR1K_SIM_CPU (current_cpu);

  if (exnum == EXCEPT_TRAP)
    {
      /* Trap, used for breakpoints, sends control back to gdb breakpoint
	 handling.  */
      sim_engine_halt (sd, current_cpu, NULL, pc, sim_stopped, SIM_SIGTRAP);
    }
  else
    {
      IADDR handler_pc;

      /* Calculate the exception program counter.  */
      switch (exnum)
	{
	case EXCEPT_RESET:
	  break;

	case EXCEPT_FPE:
	case EXCEPT_SYSCALL:
	  SET_H_SYS_EPCR0 (pc + 4 - (or1k_cpu->delay_slot ? 4 : 0));
	  break;

	case EXCEPT_BUSERR:
	case EXCEPT_ALIGN:
	case EXCEPT_ILLEGAL:
	case EXCEPT_RANGE:
	  SET_H_SYS_EPCR0 (pc - (or1k_cpu->delay_slot ? 4 : 0));
	  break;

	default:
	  sim_io_error (sd, "unexpected exception 0x%x raised at PC 0x%08x",
			exnum, pc);
	  break;
	}

      /* Store the current SR into ESR0.  */
      SET_H_SYS_ESR0 (GET_H_SYS_SR ());

      /* Indicate in SR if the failed instruction is in delay slot or not.  */
      SET_H_SYS_SR_DSX (or1k_cpu->delay_slot);

      or1k_cpu->next_delay_slot = 0;

      /* Jump program counter into handler.  */
      handler_pc =
	(GET_H_SYS_SR_EPH () ? 0xf0000000 : 0x00000000) + (exnum << 8);

      sim_engine_restart (sd, current_cpu, NULL, handler_pc);
    }
}

/* Implement the return from exception instruction.  This is used to return
   the CPU to its previous state from within an exception handler.  */

void
or1k32bf_rfe (sim_cpu *current_cpu)
{
  struct or1k_sim_cpu *or1k_cpu = OR1K_SIM_CPU (current_cpu);

  SET_H_SYS_SR (GET_H_SYS_ESR0 ());
  SET_H_SYS_SR_FO (1);

  or1k_cpu->next_delay_slot = 0;

  sim_engine_restart (CPU_STATE (current_cpu), current_cpu, NULL,
		      GET_H_SYS_EPCR0 ());
}

/* Implement the move from SPR instruction.  This is used to read from the
   CPU's special purpose registers.  */

USI
or1k32bf_mfspr (sim_cpu *current_cpu, USI addr)
{
  SIM_DESC sd = CPU_STATE (current_cpu);
  SI val;

  if (!GET_H_SYS_SR_SM () && !GET_H_SYS_SR_SUMRA ())
    {
      sim_io_eprintf (sd, "WARNING: l.mfspr in user mode (SR 0x%x)\n",
		      GET_H_SYS_SR ());
      return 0;
    }

  if (addr >= NUM_SPR)
    goto bad_address;

  val = GET_H_SPR (addr);

  switch (addr)
    {

    case SPR_ADDR (SYS, VR):
    case SPR_ADDR (SYS, UPR):
    case SPR_ADDR (SYS, CPUCFGR):
    case SPR_ADDR (SYS, SR):
    case SPR_ADDR (SYS, PPC):
    case SPR_ADDR (SYS, FPCSR):
    case SPR_ADDR (SYS, EPCR0):
    case SPR_ADDR (MAC, MACLO):
    case SPR_ADDR (MAC, MACHI):
      break;

    default:
      if (addr < SPR_ADDR (SYS, GPR0) || addr > SPR_ADDR (SYS, GPR511))
	goto bad_address;
      break;

    }

  return val;

bad_address:
  sim_io_eprintf (sd, "WARNING: l.mfspr with invalid SPR address 0x%x\n", addr);
  return 0;

}

/* Implement the move to SPR instruction.  This is used to write too the
   CPU's special purpose registers.  */

void
or1k32bf_mtspr (sim_cpu *current_cpu, USI addr, USI val)
{
  SIM_DESC sd = CPU_STATE (current_cpu);
  struct or1k_sim_cpu *or1k_cpu = OR1K_SIM_CPU (current_cpu);

  if (!GET_H_SYS_SR_SM () && !GET_H_SYS_SR_SUMRA ())
    {
      sim_io_eprintf
	(sd, "WARNING: l.mtspr with address 0x%x in user mode (SR 0x%x)\n",
	 addr, GET_H_SYS_SR ());
      return;
    }

  if (addr >= NUM_SPR)
    goto bad_address;

  switch (addr)
    {

    case SPR_ADDR (SYS, FPCSR):
    case SPR_ADDR (SYS, EPCR0):
    case SPR_ADDR (SYS, ESR0):
    case SPR_ADDR (MAC, MACHI):
    case SPR_ADDR (MAC, MACLO):
      SET_H_SPR (addr, val);
      break;

    case SPR_ADDR (SYS, SR):
      SET_H_SPR (addr, val);
      SET_H_SYS_SR_FO (1);
      break;

    case SPR_ADDR (SYS, NPC):
      or1k_cpu->next_delay_slot = 0;

      sim_engine_restart (sd, current_cpu, NULL, val);
      break;

    case SPR_ADDR (TICK, TTMR):
      /* Allow some registers to be silently cleared.  */
      if (val != 0)
	sim_io_eprintf
	  (sd, "WARNING: l.mtspr to SPR address 0x%x with invalid value 0x%x\n",
	   addr, val);
      break;

    default:
      if (addr >= SPR_ADDR (SYS, GPR0) && addr <= SPR_ADDR (SYS, GPR511))
	SET_H_SPR (addr, val);
      else
	goto bad_address;
      break;

    }

  return;

bad_address:
  sim_io_eprintf (sd, "WARNING: l.mtspr with invalid SPR address 0x%x\n", addr);

}