| # Simulator main loop for frv. -*- C -*- |
| # Copyright (C) 1998-2021 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 |