| # Simulator main loop for frv. -*- C -*- | |
| # Copyright (C) 1998-2023 Free Software Foundation, Inc. | |
| # Contributed by Red Hat. | |
| # | |
| # This file is part of the GNU 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/>. | |
| # Syntax: | |
| # /bin/sh mainloop.in command | |
| # | |
| # Command is one of: | |
| # | |
| # init | |
| # support | |
| # extract-{simple,scache,pbb} | |
| # {full,fast}-exec-{simple,scache,pbb} | |
| # | |
| # A target need only provide a "full" version of one of simple,scache,pbb. | |
| # If the target wants it can also provide a fast version of same. | |
| # It can't provide more than this. | |
| # ??? After a few more ports are done, revisit. | |
| # Will eventually need to machine generate a lot of this. | |
| case "x$1" in | |
| xsupport) | |
| cat <<EOF | |
| static INLINE const IDESC * | |
| extract (SIM_CPU *current_cpu, PCADDR pc, CGEN_INSN_INT insn, ARGBUF *abuf, | |
| int fast_p) | |
| { | |
| const IDESC *id = @cpu@_decode (current_cpu, pc, insn, insn, abuf); | |
| @cpu@_fill_argbuf (current_cpu, abuf, id, pc, fast_p); | |
| if (! fast_p) | |
| { | |
| int trace_p = PC_IN_TRACE_RANGE_P (current_cpu, pc); | |
| int profile_p = PC_IN_PROFILE_RANGE_P (current_cpu, pc); | |
| @cpu@_fill_argbuf_tp (current_cpu, abuf, trace_p, profile_p); | |
| } | |
| return id; | |
| } | |
| static INLINE SEM_PC | |
| execute (SIM_CPU *current_cpu, SCACHE *sc, int fast_p) | |
| { | |
| SEM_PC vpc; | |
| /* Force gr0 to zero before every insn. */ | |
| @cpu@_h_gr_set (current_cpu, 0, 0); | |
| if (fast_p) | |
| { | |
| vpc = (*sc->argbuf.semantic.sem_fast) (current_cpu, sc); | |
| } | |
| else | |
| { | |
| ARGBUF *abuf = &sc->argbuf; | |
| const IDESC *idesc = abuf->idesc; | |
| #if WITH_SCACHE_PBB | |
| int virtual_p = CGEN_ATTR_VALUE (NULL, idesc->attrs, CGEN_INSN_VIRTUAL); | |
| #else | |
| int virtual_p = 0; | |
| #endif | |
| if (! virtual_p) | |
| { | |
| /* FIXME: call x-before */ | |
| if (ARGBUF_PROFILE_P (abuf)) | |
| PROFILE_COUNT_INSN (current_cpu, abuf->addr, idesc->num); | |
| /* FIXME: Later make cover macros: PROFILE_INSN_{INIT,FINI}. */ | |
| if (FRV_COUNT_CYCLES (current_cpu, ARGBUF_PROFILE_P (abuf))) | |
| { | |
| @cpu@_model_insn_before (current_cpu, sc->first_insn_p); | |
| model_insn = FRV_INSN_MODEL_PASS_1; | |
| if (idesc->timing->model_fn != NULL) | |
| (*idesc->timing->model_fn) (current_cpu, sc); | |
| } | |
| else | |
| model_insn = FRV_INSN_NO_MODELING; | |
| CGEN_TRACE_INSN_INIT (current_cpu, abuf, 1); | |
| CGEN_TRACE_INSN (current_cpu, idesc->idata, | |
| (const struct argbuf *) abuf, abuf->addr); | |
| } | |
| #if WITH_SCACHE | |
| vpc = (*sc->argbuf.semantic.sem_full) (current_cpu, sc); | |
| #else | |
| vpc = (*sc->argbuf.semantic.sem_full) (current_cpu, abuf); | |
| #endif | |
| if (! virtual_p) | |
| { | |
| /* FIXME: call x-after */ | |
| if (FRV_COUNT_CYCLES (current_cpu, ARGBUF_PROFILE_P (abuf))) | |
| { | |
| int cycles; | |
| if (idesc->timing->model_fn != NULL) | |
| { | |
| model_insn = FRV_INSN_MODEL_PASS_2; | |
| cycles = (*idesc->timing->model_fn) (current_cpu, sc); | |
| } | |
| else | |
| cycles = 1; | |
| @cpu@_model_insn_after (current_cpu, sc->last_insn_p, cycles); | |
| } | |
| CGEN_TRACE_INSN_FINI (current_cpu, abuf, 1); | |
| } | |
| } | |
| return vpc; | |
| } | |
| static void | |
| @cpu@_parallel_write_init (SIM_CPU *current_cpu) | |
| { | |
| CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (current_cpu); | |
| CGEN_WRITE_QUEUE_CLEAR (q); | |
| previous_vliw_pc = CPU_PC_GET(current_cpu); | |
| frv_interrupt_state.f_ne_flags[0] = 0; | |
| frv_interrupt_state.f_ne_flags[1] = 0; | |
| frv_interrupt_state.imprecise_interrupt = NULL; | |
| } | |
| static void | |
| @cpu@_parallel_write_queued (SIM_CPU *current_cpu) | |
| { | |
| int i; | |
| FRV_VLIW *vliw = CPU_VLIW (current_cpu); | |
| CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (current_cpu); | |
| /* Loop over the queued writes, executing them. Set the pc to the address | |
| of the insn which queued each write for the proper context in case an | |
| interrupt is caused. Restore the proper pc after the writes are | |
| completed. */ | |
| IADDR save_pc = CPU_PC_GET (current_cpu); | |
| IADDR new_pc = save_pc; | |
| int branch_taken = 0; | |
| int limit = CGEN_WRITE_QUEUE_INDEX (q); | |
| frv_interrupt_state.data_written.length = 0; | |
| for (i = 0; i < limit; ++i) | |
| { | |
| CGEN_WRITE_QUEUE_ELEMENT *item = CGEN_WRITE_QUEUE_ELEMENT (q, i); | |
| /* If an imprecise interrupt was generated, then, check whether the | |
| result should still be written. */ | |
| if (frv_interrupt_state.imprecise_interrupt != NULL) | |
| { | |
| /* Only check writes by the insn causing the exception. */ | |
| if (CGEN_WRITE_QUEUE_ELEMENT_IADDR (item) | |
| == frv_interrupt_state.imprecise_interrupt->vpc) | |
| { | |
| /* Execute writes of floating point operations resulting in | |
| overflow, underflow or inexact. */ | |
| if (frv_interrupt_state.imprecise_interrupt->kind | |
| == FRV_FP_EXCEPTION) | |
| { | |
| if ((frv_interrupt_state.imprecise_interrupt | |
| ->u.fp_info.fsr_mask | |
| & ~(FSR_INEXACT | FSR_OVERFLOW | FSR_UNDERFLOW))) | |
| continue; /* Don't execute */ | |
| } | |
| /* Execute writes marked as 'forced'. */ | |
| else if (! (CGEN_WRITE_QUEUE_ELEMENT_FLAGS (item) | |
| & FRV_WRITE_QUEUE_FORCE_WRITE)) | |
| continue; /* Don't execute */ | |
| } | |
| } | |
| /* Only execute the first branch on the queue. */ | |
| if (CGEN_WRITE_QUEUE_ELEMENT_KIND (item) == CGEN_PC_WRITE | |
| || CGEN_WRITE_QUEUE_ELEMENT_KIND (item) == CGEN_FN_PC_WRITE) | |
| { | |
| if (branch_taken) | |
| continue; | |
| branch_taken = 1; | |
| if (CGEN_WRITE_QUEUE_ELEMENT_KIND (item) == CGEN_PC_WRITE) | |
| new_pc = item->kinds.pc_write.value; | |
| else | |
| new_pc = item->kinds.fn_pc_write.value; | |
| } | |
| CPU_PC_SET (current_cpu, CGEN_WRITE_QUEUE_ELEMENT_IADDR (item)); | |
| frv_save_data_written_for_interrupts (current_cpu, item); | |
| cgen_write_queue_element_execute (current_cpu, item); | |
| } | |
| /* Update the LR with the address of the next insn if the flag is set. | |
| This flag gets set in frvbf_set_write_next_vliw_to_LR by the JMPL, | |
| JMPIL and CALL insns. */ | |
| if (frvbf_write_next_vliw_addr_to_LR) | |
| { | |
| frvbf_h_spr_set_handler (current_cpu, H_SPR_LR, save_pc); | |
| frvbf_write_next_vliw_addr_to_LR = 0; | |
| } | |
| CPU_PC_SET (current_cpu, new_pc); | |
| CGEN_WRITE_QUEUE_CLEAR (q); | |
| } | |
| void | |
| @cpu@_perform_writeback (SIM_CPU *current_cpu) | |
| { | |
| @cpu@_parallel_write_queued (current_cpu); | |
| } | |
| static unsigned cache_reqno = 0x80000000; /* Start value is for debugging. */ | |
| #if 0 /* experimental */ | |
| /* FR400 has single prefetch. */ | |
| static void | |
| fr400_simulate_insn_prefetch (SIM_CPU *current_cpu, IADDR vpc) | |
| { | |
| int cur_ix; | |
| FRV_CACHE *cache; | |
| /* The cpu receives 8 bytes worth of insn data for each fetch aligned | |
| on 8 byte boundary. */ | |
| #define FR400_FETCH_SIZE 8 | |
| cur_ix = LS; | |
| vpc &= ~(FR400_FETCH_SIZE - 1); | |
| cache = CPU_INSN_CACHE (current_cpu); | |
| /* Request a load of the current address buffer, if necessary. */ | |
| if (frv_insn_fetch_buffer[cur_ix].address != vpc) | |
| { | |
| frv_insn_fetch_buffer[cur_ix].address = vpc; | |
| frv_insn_fetch_buffer[cur_ix].reqno = cache_reqno++; | |
| if (FRV_COUNT_CYCLES (current_cpu, 1)) | |
| frv_cache_request_load (cache, frv_insn_fetch_buffer[cur_ix].reqno, | |
| frv_insn_fetch_buffer[cur_ix].address, | |
| UNIT_I0 + cur_ix); | |
| } | |
| /* Wait for the current address buffer to be loaded, if necessary. */ | |
| if (FRV_COUNT_CYCLES (current_cpu, 1)) | |
| { | |
| FRV_PROFILE_STATE *ps = CPU_PROFILE_STATE (current_cpu); | |
| int wait; | |
| /* Account for any branch penalty. */ | |
| if (ps->branch_penalty > 0 && ! ps->past_first_p) | |
| { | |
| frv_model_advance_cycles (current_cpu, ps->branch_penalty); | |
| frv_model_trace_wait_cycles (current_cpu, ps->branch_penalty, | |
| "Branch penalty:"); | |
| ps->branch_penalty = 0; | |
| } | |
| /* Account for insn fetch latency. */ | |
| wait = 0; | |
| while (frv_insn_fetch_buffer[cur_ix].reqno != NO_REQNO) | |
| { | |
| frv_model_advance_cycles (current_cpu, 1); | |
| ++wait; | |
| } | |
| frv_model_trace_wait_cycles (current_cpu, wait, "Insn fetch:"); | |
| return; | |
| } | |
| /* Otherwise just load the insns directly from the cache. | |
| */ | |
| if (frv_insn_fetch_buffer[cur_ix].reqno != NO_REQNO) | |
| { | |
| frv_cache_read (cache, cur_ix, vpc); | |
| frv_insn_fetch_buffer[cur_ix].reqno = NO_REQNO; | |
| } | |
| } | |
| #endif /* experimental */ | |
| /* FR500 has dual prefetch. */ | |
| static void | |
| simulate_dual_insn_prefetch (SIM_CPU *current_cpu, IADDR vpc, int fetch_size) | |
| { | |
| int i; | |
| int cur_ix, pre_ix; | |
| SI pre_address; | |
| FRV_CACHE *cache; | |
| /* See if the pc is within the addresses specified by either of the | |
| fetch buffers. If so, that will be the current buffer. Otherwise, | |
| arbitrarily select the LD buffer as the current one since it gets | |
| priority in the case of interfering load requests. */ | |
| cur_ix = LD; | |
| vpc &= ~(fetch_size - 1); | |
| for (i = LS; i < FRV_CACHE_PIPELINES; ++i) | |
| { | |
| if (frv_insn_fetch_buffer[i].address == vpc) | |
| { | |
| cur_ix = i; | |
| break; | |
| } | |
| } | |
| cache = CPU_INSN_CACHE (current_cpu); | |
| /* Request a load of the current address buffer, if necessary. */ | |
| if (frv_insn_fetch_buffer[cur_ix].address != vpc) | |
| { | |
| frv_insn_fetch_buffer[cur_ix].address = vpc; | |
| frv_insn_fetch_buffer[cur_ix].reqno = cache_reqno++; | |
| if (FRV_COUNT_CYCLES (current_cpu, 1)) | |
| frv_cache_request_load (cache, frv_insn_fetch_buffer[cur_ix].reqno, | |
| frv_insn_fetch_buffer[cur_ix].address, | |
| UNIT_I0 + cur_ix); | |
| } | |
| /* If the prefetch buffer does not represent the next sequential address, then | |
| request a load of the next sequential address. */ | |
| pre_ix = (cur_ix + 1) % FRV_CACHE_PIPELINES; | |
| pre_address = vpc + fetch_size; | |
| if (frv_insn_fetch_buffer[pre_ix].address != pre_address) | |
| { | |
| frv_insn_fetch_buffer[pre_ix].address = pre_address; | |
| frv_insn_fetch_buffer[pre_ix].reqno = cache_reqno++; | |
| if (FRV_COUNT_CYCLES (current_cpu, 1)) | |
| frv_cache_request_load (cache, frv_insn_fetch_buffer[pre_ix].reqno, | |
| frv_insn_fetch_buffer[pre_ix].address, | |
| UNIT_I0 + pre_ix); | |
| } | |
| /* If counting cycles, account for any branch penalty and/or insn fetch | |
| latency here. */ | |
| if (FRV_COUNT_CYCLES (current_cpu, 1)) | |
| { | |
| FRV_PROFILE_STATE *ps = CPU_PROFILE_STATE (current_cpu); | |
| int wait; | |
| /* Account for any branch penalty. */ | |
| if (ps->branch_penalty > 0 && ! ps->past_first_p) | |
| { | |
| frv_model_advance_cycles (current_cpu, ps->branch_penalty); | |
| frv_model_trace_wait_cycles (current_cpu, ps->branch_penalty, | |
| "Branch penalty:"); | |
| ps->branch_penalty = 0; | |
| } | |
| /* Account for insn fetch latency. */ | |
| wait = 0; | |
| while (frv_insn_fetch_buffer[cur_ix].reqno != NO_REQNO) | |
| { | |
| frv_model_advance_cycles (current_cpu, 1); | |
| ++wait; | |
| } | |
| frv_model_trace_wait_cycles (current_cpu, wait, "Insn fetch:"); | |
| return; | |
| } | |
| /* Otherwise just load the insns directly from the cache. | |
| */ | |
| if (frv_insn_fetch_buffer[cur_ix].reqno != NO_REQNO) | |
| { | |
| frv_cache_read (cache, cur_ix, vpc); | |
| frv_insn_fetch_buffer[cur_ix].reqno = NO_REQNO; | |
| } | |
| if (frv_insn_fetch_buffer[pre_ix].reqno != NO_REQNO) | |
| { | |
| frv_cache_read (cache, pre_ix, pre_address); | |
| frv_insn_fetch_buffer[pre_ix].reqno = NO_REQNO; | |
| } | |
| } | |
| static void | |
| @cpu@_simulate_insn_prefetch (SIM_CPU *current_cpu, IADDR vpc) | |
| { | |
| SI hsr0; | |
| SIM_DESC sd; | |
| /* Nothing to do if not counting cycles and the cache is not enabled. */ | |
| hsr0 = GET_HSR0 (); | |
| if (! GET_HSR0_ICE (hsr0) && ! FRV_COUNT_CYCLES (current_cpu, 1)) | |
| return; | |
| /* Different machines handle prefetch defferently. */ | |
| sd = CPU_STATE (current_cpu); | |
| switch (STATE_ARCHITECTURE (sd)->mach) | |
| { | |
| case bfd_mach_fr400: | |
| case bfd_mach_fr450: | |
| simulate_dual_insn_prefetch (current_cpu, vpc, 8); | |
| break; | |
| case bfd_mach_frvtomcat: | |
| case bfd_mach_fr500: | |
| case bfd_mach_fr550: | |
| case bfd_mach_frv: | |
| simulate_dual_insn_prefetch (current_cpu, vpc, 16); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| int frv_save_profile_model_p; | |
| EOF | |
| ;; | |
| xinit) | |
| cat <<EOF | |
| /*xxxinit*/ | |
| /* If the timer is enabled, then we will enable model profiling during | |
| execution. This is because the timer needs accurate cycles counts to | |
| work properly. Save the original setting of model profiling. */ | |
| if (frv_interrupt_state.timer.enabled) | |
| frv_save_profile_model_p = PROFILE_MODEL_P (current_cpu); | |
| EOF | |
| ;; | |
| xextract-simple | xextract-scache) | |
| # Inputs: current_cpu, vpc, sc, FAST_P | |
| # Outputs: sc filled in | |
| # SET_LAST_INSN_P(last_p) called to indicate whether insn is last one | |
| cat <<EOF | |
| { | |
| CGEN_INSN_INT insn = frvbf_read_imem_USI (current_cpu, vpc); | |
| extract (current_cpu, vpc, insn, SEM_ARGBUF (sc), FAST_P); | |
| SET_LAST_INSN_P ((insn & 0x80000000) != 0); | |
| } | |
| EOF | |
| ;; | |
| xfull-exec-* | xfast-exec-*) | |
| # Inputs: current_cpu, vpc, FAST_P | |
| # Outputs: | |
| # vpc contains the address of the next insn to execute | |
| # pc of current_cpu must be up to date (=vpc) upon exit | |
| # CPU_INSN_COUNT (current_cpu) must be updated by number of insns executed | |
| # | |
| # Unlike the non-parallel case, this version is responsible for doing the | |
| # scache lookup. | |
| cat <<EOF | |
| { | |
| FRV_VLIW *vliw; | |
| int first_insn_p = 1; | |
| int last_insn_p = 0; | |
| int ninsns; | |
| CGEN_ATTR_VALUE_ENUM_TYPE slot; | |
| /* If the timer is enabled, then enable model profiling. This is because | |
| the timer needs accurate cycles counts to work properly. */ | |
| if (frv_interrupt_state.timer.enabled && ! frv_save_profile_model_p) | |
| sim_profile_set_option (current_state, "-model", PROFILE_MODEL_IDX, "1"); | |
| /* Init parallel-write queue and vliw. */ | |
| @cpu@_parallel_write_init (current_cpu); | |
| vliw = CPU_VLIW (current_cpu); | |
| frv_vliw_reset (vliw, STATE_ARCHITECTURE (CPU_STATE (current_cpu))->mach, | |
| CPU_ELF_FLAGS (current_cpu)); | |
| frv_current_fm_slot = UNIT_NIL; | |
| for (ninsns = 0; ! last_insn_p && ninsns < FRV_VLIW_SIZE; ++ninsns) | |
| { | |
| SCACHE *sc; | |
| const CGEN_INSN *insn; | |
| int error; | |
| /* Go through the motions of finding the insns in the cache. */ | |
| @cpu@_simulate_insn_prefetch (current_cpu, vpc); | |
| sc = @cpu@_scache_lookup (current_cpu, vpc, scache, hash_mask, FAST_P); | |
| sc->first_insn_p = first_insn_p; | |
| last_insn_p = sc->last_insn_p; | |
| /* Add the insn to the vliw and set up the interrupt state. */ | |
| insn = sc->argbuf.idesc->idata; | |
| error = frv_vliw_add_insn (vliw, insn); | |
| if (! error) | |
| frv_vliw_setup_insn (current_cpu, insn); | |
| frv_detect_insn_access_interrupts (current_cpu, sc); | |
| slot = (*vliw->current_vliw)[vliw->next_slot - 1]; | |
| if (slot >= UNIT_FM0 && slot <= UNIT_FM3) | |
| frv_current_fm_slot = slot; | |
| vpc = execute (current_cpu, sc, FAST_P); | |
| SET_H_PC (vpc); /* needed for interrupt handling */ | |
| first_insn_p = 0; | |
| } | |
| /* If the timer is enabled, and model profiling was not originally enabled, | |
| then turn it off again. This is the only place we can currently gain | |
| control to do this. */ | |
| if (frv_interrupt_state.timer.enabled && ! frv_save_profile_model_p) | |
| sim_profile_set_option (current_state, "-model", PROFILE_MODEL_IDX, "0"); | |
| /* Check for interrupts. Also handles writeback if necessary. */ | |
| frv_process_interrupts (current_cpu); | |
| CPU_INSN_COUNT (current_cpu) += ninsns; | |
| } | |
| EOF | |
| ;; | |
| *) | |
| echo "Invalid argument to mainloop.in: $1" >&2 | |
| exit 1 | |
| ;; | |
| esac |