/* Copyright (C) 1998-2000 T. Scott Dattalo This file is part of gpsim. gpsim 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 2, or (at your option) any later version. gpsim 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 gpsim; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "14bit-processors.h" #include "interface.h" #include #include "stimuli.h" #include "errors.h" #include "../config.h" #include "xref.h" #include "protocol.h" //#define __DEBUG_CYCLE_COUNTER__ //-------------------------------------------------- // Global instantiation of the cycle counter // and the stop watch; //-------------------------------------------------- Cycle_Counter cycles; // create an instance of inline get_cycles() method by taking its address Cycle_Counter &(*dummy_cycles)(void) = get_cycles; StopWatch stop_watch; //-------------------------------------------------- // member functions for the Cycle_Counter class //-------------------------------------------------- /* Overview of Cycle Counter break points. The Cycle Counter break points coordinate simulation time. At the moment, the cycle counter advances one count for every instruction cycle. Thus "real time" is defined in terms of "instruction cycles". (NOTE - This will change!) Now, the way gpsim uses these break points is by allowing peripherals to grab control of the simulation at a particular instance of time. For example, if the UART peripheral needs to send the next data bit in 100 microseconds, then it needs to have control of the simulator in exactly 100 microseconds from right now. The way this is handled, is that the cycle counter is advanced at every instruction cycle (This will change...) and when the cycle counter matches the cycle that corresponds to that 100 microsecond gap, gpsim will divert control to the UART peripheral. There are 3 components to a cycle counter break point. First there's the obvious thing: the number that corresponds to the cycle at which we wish to break. This is a 64-bit integer and thus should cover a fairly significant simulation interval! A 32-bit integer only covers about 7 minutes of simulation time for a pic running at 20MHz. 64-bits provides over 100,000 years of simulation time! The next component is the call back function. This is a pointer to a function, or actually a class that contains a function, that is called when the current value of the cycle counter matches the break point value. In the UART example, this function will be something that the UART peripheral passed when it set the break point. This is how the UART peripheral gets control of the simulator. Finally, the third component is the linked list mechanism. Each time a break point is set, it gets inserted into a linked list that is sorted by the cycle break point value. Thus the first element in the list (if there are any elements at all) is always the next cycle counter break point. */ Cycle_Counter_breakpoint_list *Cycle_Counter_breakpoint_list::getNext() { return next; } Cycle_Counter_breakpoint_list *Cycle_Counter_breakpoint_list::getPrev() { return prev; } void Cycle_Counter_breakpoint_list::clear() { bActive = false; if(f) f->clear_trigger(); } void Cycle_Counter_breakpoint_list::invoke() { if(bActive) { clear(); if(f) f->callback(); } } //-------------------------------------------------- void Cycle_Counter::preset(guint64 new_value) { value = new_value; get_trace().cycle_counter(value); } void Cycle_Counter::set_instruction_cps(guint64 cps) { if(cps) { m_instruction_cps = (double)cps; m_seconds_per_cycle = 1.0/m_instruction_cps; } } //-------------------------------------------------- // get(double seconds_from_now) // // Return the cycle number that corresponds to a time // biased from the current time. // // INPUT: time in seconds (note that it's a double) // // OUTPUT: cycle count guint64 Cycle_Counter::get(double future_time_from_now) { return value + (guint64)(m_instruction_cps * future_time_from_now); } //-------------------------------------------------- // set_break // set a cycle counter break point. Return 1 if successful. // // The break points are stored in a singly-linked-list sorted by the // order in which they will occur. When this routine is called, the // value of 'future_cycle' is compared against the values in the // 'active' list. bool Cycle_Counter::set_break(guint64 future_cycle, TriggerObject *f, unsigned int bpn) { Cycle_Counter_breakpoint_list *l1 = &active, *l2; static unsigned int CallBackID_Sequence=1; #ifdef __DEBUG_CYCLE_COUNTER__ cout << "Cycle_Counter::set_break cycle = 0x" << hex<next) && !break_set) { // If the next break point is at a cycle greater than the // one we wish to set, then we found the insertion point. // Otherwise if(l1->next->break_value >= future_cycle) break_set = 1; else l1 = l1->next; } // At this point, we have the position where we need to insert the // break point: it's at l1->next. l2 = l1->next; l1->next = inactive.next; // remove the break point from the 'inactive' list inactive.next = inactive.next->next; l1->next->next = l2; l1->next->prev = l1; if(l2) l2->prev = l1->next; l1->next->break_value = future_cycle; l1->next->f = f; l1->next->breakpoint_number = bpn; l1->next->bActive = true; if(f) f->CallBackID = ++CallBackID_Sequence; #ifdef __DEBUG_CYCLE_COUNTER__ cout << "cycle break " << future_cycle << " bpn " << bpn << '\n'; if(f) cout << "call back sequence number = "<< f->CallBackID <<'\n'; #endif } break_on_this = active.next->break_value; return 1; } //-------------------------------------------------- // remove_break // remove break for TriggerObject void Cycle_Counter::clear_break(TriggerObject *f) { Cycle_Counter_breakpoint_list *l1 = &active, *l2 = 0; if(!f) return; while( (l1->next) && !l2) { if(l1->next->f == f) l2 = l1; l1=l1->next; } if(!l2) { //#ifdef __DEBUG_CYCLE_COUNTER__ cout << "WARNING Cycle_Counter::clear_break could not find break point\n Culprit:\t"; f->callback_print(); //#endif return; } // at this point l2->next points to our break point // It needs to be removed from the 'active' list and put onto the 'inactive' list. l1 = l2; l2 = l1->next; // save a copy for a moment l1->next = l1->next->next; // remove the break if(l1->next) l1->next->prev = l1; // fix the backwards link. l2->clear(); #ifdef __DEBUG_CYCLE_COUNTER__ if (f) cout << "Clearing break call back sequence number = "<< f->CallBackID <<'\n'; #endif // Now move the break to the inactive list. l1 = inactive.next; if(!l1) return; l2->next = l1; inactive.next = l2; break_on_this = active.next ? active.next->break_value : 0; } //-------------------------------------------------- // set_break_delta // set a cycle counter break point relative to the current cpu cycle value. Return 1 if successful. // bool Cycle_Counter::set_break_delta(guint64 delta, TriggerObject *f, unsigned int bpn) { #ifdef __DEBUG_CYCLE_COUNTER__ cout << "Cycle_Counter::set_break_delta delta = 0x" << hex<next && !found) { // If the next break point is at the same cycle as the // one we wish to clear, then we found the deletion point. // Otherwise keep searching. if(l1->next->break_value == at_cycle) found = 1; else l1=l1->next; } if(!found) { cout << "Cycle_Counter::clear_break could not find break at cycle 0x" << hex << setw(16) << setfill('0') << at_cycle << endl; return; } l2 = l1->next; // save a copy for a moment l1->next = l1->next->next; // remove the break if(l1->next) l1->next->prev = l2; l2->clear(); // Now move the break to the inactive list. l1 = inactive.next; if(!l1) return; l2->next = l1; inactive.next = l2; break_on_this = active.next ? active.next->break_value : 0; } //------------------------------------------------------------------------ // breakpoint // // When the cycle counter has encountered a cycle that has a breakpoint, // this routine is called. // void Cycle_Counter::breakpoint() { // There's a break point set on this cycle. If there's a callback function, then call // it other wise halt execution by setting the global break flag. // Loop in case there are multiple breaks //while(value == break_on_this && active.next) { while(active.next && value == active.next->break_value) { if(active.next->f) { // This flag will get set true if the call back // function moves the break point to another cycle. Cycle_Counter_breakpoint_list *l1 = active.next; TriggerObject *lastBreak = active.next->f; l1->bActive = false; l1->f->callback(); clear_current_break(lastBreak); } else { get_bp().check_cycle_break(active.next->breakpoint_number); clear_current_break(); } } if(active.next) break_on_this = active.next->break_value; } //------------------------------------------------------------------------ // reassign_break // change the cycle of an existing break point. // // This is only called by the internal peripherals and not (directly) by // the user. It's purpose is to accommodate the dynamic and unpredictable // needs of the internal cpu timing. For example, if tmr0 is set to roll // over on a certain cycle and the program changes the pre-scale value, // then the break point has to be moved to the new cycle. bool Cycle_Counter::reassign_break(guint64 old_cycle, guint64 new_cycle, TriggerObject *f) { Cycle_Counter_breakpoint_list *l1 = &active, *l2; bool found_old = false; bool break_set = false; reassigned = true; // assume that the break point does actually get reassigned. #ifdef __DEBUG_CYCLE_COUNTER__ cout << "Cycle_Counter::reassign_break, old " << old_cycle << " new " << new_cycle; if(f) cout << " Call back ID = " << f->CallBackID; cout << '\n'; dump_breakpoints(); #endif // // First, we search for the break point by comparing the 'old_cycle' // with the break point cycle of all active break points. Two criteria // must be satisfied for a match: // // 1) The 'old_cycle' must exactly match the cycle of an active // break point. // 2) The Call back function pointer must exactly match the call back // function point of the same active break point. // // The reason for both of these, is so that we can differentiate multiple // break points set at the same cycle. // // NOTE to consider: // // It would be far more efficient to have the "set_break" function return // a handle or a pointer that we could use here to immediately identify // the break we wish to reassign. This would also disambiguate multiple // breaks set at the same cycle. We'd still have to perform a search through // the linked list to find the new point, but that search would be limited // (i.e. the reassignment is either before *or* after the current). A bi- // directional search can be optimized with a doubly-linked list... while( (l1->next) && !found_old) { // If the next break point is at a cycle greater than the // one we wish to set, then we found the insertion point. if(l1->next->f == f && l1->next->break_value == old_cycle) { #ifdef __DEBUG_CYCLE_COUNTER__ cout << " cycle match "; if(f && (f->CallBackID == l1->next->f->CallBackID)) cout << " Call Back IDs match = " << f->CallBackID << ' '; #endif found_old = true; /* if(l1->next->f == f) found_old = true; else l1 = l1->next; */ } else l1 = l1->next; } if(found_old) { // Now move the break point #ifdef __DEBUG_CYCLE_COUNTER__ cout << " found old "; if(l1->next->bActive == false) { cout << "CycleCounter - reassigning in active break "; if(l1->next->f) l1->next->f->callback_print(); cout << endl; } #endif if(new_cycle > old_cycle) { // First check to see if we can stay in the same relative position within the list // Is this the last one in the list? (or equivalently, is the one after this one // a NULL?) if(l1->next->next == 0) { l1->next->break_value = new_cycle; break_on_this = active.next->break_value; #ifdef __DEBUG_CYCLE_COUNTER__ cout << " replaced at current position (next is NULL)\n"; dump_breakpoints(); // debug #endif return 1; } // Is the next one in the list still beyond this one? if(l1->next->next->break_value >= new_cycle) { l1->next->break_value = new_cycle; break_on_this = active.next->break_value; #ifdef __DEBUG_CYCLE_COUNTER__ cout << " replaced at current position (next is greater)\n"; dump_breakpoints(); // debug #endif return 1; } // Darn. Looks like we have to move it. #ifdef __DEBUG_CYCLE_COUNTER__ cout << " moving \n"; #endif l2 = l1->next; // l2 now points to this break point l1->next = l1->next->next; // Unlink this break point l1->next->prev = l1; while( l1->next && !break_set) { // If the next break point is at a cycle greater than the // one we wish to set, then we found the insertion point. // Otherwise, continue searching. if(l1->next->break_value > new_cycle) break_set = 1; else l1 = l1->next; } // At this point, we know that our breakpoint needs to be // moved to the position just after l1 l2->next = l1->next; l1->next = l2; l2->prev = l1; if(l2->next) l2->next->prev = l2; break_on_this = active.next->break_value; l2->break_value = new_cycle; #ifdef __DEBUG_CYCLE_COUNTER__ dump_breakpoints(); // debug #endif } else { // old_cycle < new_cycle // First check to see if we can stay in the same relative position within the list #ifdef __DEBUG_CYCLE_COUNTER__ cout << " old cycle is less than new one\n"; #endif // Is this the first one in the list? if(l1 == &active) { l1->next->break_value = new_cycle; break_on_this = new_cycle; #ifdef __DEBUG_CYCLE_COUNTER__ cout << " replaced at current position\n"; dump_breakpoints(); // debug #endif return 1; } // Is the previous one in the list still before this one? if(l1->break_value < new_cycle) { l1->next->break_value = new_cycle; break_on_this = active.next->break_value; #ifdef __DEBUG_CYCLE_COUNTER__ cout << " replaced at current position\n"; dump_breakpoints(); // debug #endif return 1; } // Darn. Looks like we have to move it. l2 = l1->next; // l2 now points to this break point l1->next = l1->next->next; // Unlink this break point if(l1->next) l1->next->prev = l1; l1 = &active; // Start searching from the beginning of the list while( (l1->next) && !break_set) { // If the next break point is at a cycle greater than the // one we wish to set, then we found the insertion point. // Otherwise if(l1->next->break_value > new_cycle) break_set = 1; else l1 = l1->next; } l2->next = l1->next; l2->next->prev = l2; l1->next = l2; l2->prev = l1; l2->break_value = new_cycle; break_on_this = active.next->break_value; #ifdef __DEBUG_CYCLE_COUNTER__ dump_breakpoints(); // debug #endif } } else { // oops our assumption was wrong, we were unable to reassign the break point // to another cycle because we couldn't find the old one! reassigned = false; // If the break point was not found, it can't be moved. So let's just create // a new break point. cout << "WARNING Cycle_Counter::reassign_break could not find old break point\n"; cout << " a new break will created at cycle: 0x"<callback_print(); } set_break(new_cycle, f); } return 1; } void Cycle_Counter::clear_current_break(TriggerObject *f) { if(active.next == 0) return; if(value == break_on_this && (!f || (f && f==active.next->f))) { #ifdef __DEBUG_CYCLE_COUNTER__ cout << "current cycle " << hex << setw(16) << setfill('0') << value << endl; cout << "clearing current cycle break " << hex << setw(16) << setfill('0') << break_on_this; if(active.next->f) cout << " Call Back ID = " << active.next->f->CallBackID; cout <<'\n'; if(active.next->next->break_value == break_on_this) { cout << " but there's one pending at the same cycle"; if(active.next->next->f) cout << " With ID = " << active.next->next->f->CallBackID; cout << '\n'; } #endif Cycle_Counter_breakpoint_list *l1; active.next->bActive = false; l1 = inactive.next; // ptr to 1st inactive bp inactive.next = active.next; // let the 1st active bp become the 1st inactive one active.next = active.next->next; // The 2nd active bp is now the 1st inactive.next->next = l1; // The 2nd inactive bp used to be the 1st if(active.next) { break_on_this = active.next->break_value; active.next->prev = &active; } else break_on_this = END_OF_TIME; } else { // If 'value' doesn't equal 'break_on_this' then what's most probably // happened is that the breakpoint associated with 'break_on_this' // has invoked a callback function that then did a ::reassign_break(). // There's a slight chance that we have a bug - but very slight... if(verbose & 4) { cout << "debug::Didn't clear the current cycle break because != break_on_this\n"; cout << "value = " << value << "\nbreak_on_this = " << break_on_this <<'\n'; } } } void Cycle_Counter::dump_breakpoints(void) { Cycle_Counter_breakpoint_list *l1 = &active; cout << "Current Cycle " << hex << setw(16) << setfill('0') << value << '\n'; cout << "Next scheduled cycle break " << hex << setw(16) << setfill('0') << break_on_this << '\n'; while(l1->next) { //cout << cpu->name_str << " " << "internal cycle break " << cout << "internal cycle break " << hex << setw(16) << setfill('0') << l1->next->break_value << ' '; if(l1->next->f) l1->next->f->callback_print(); else cout << "does not have callback\n"; l1 = l1->next; } } Cycle_Counter::Cycle_Counter(void) { value = 0; break_on_this = END_OF_TIME; m_instruction_cps = 5.0e6; m_seconds_per_cycle = 1 / m_instruction_cps; active.next = 0; active.prev = 0; inactive.next = 0; inactive.prev = 0; Cycle_Counter_breakpoint_list *l1 = &inactive; for(int i=0; inext = new Cycle_Counter_breakpoint_list; l1->next->prev = l1; l1 = l1->next; } l1->next = 0; } //------------------------------------------------------------------------ // StopWatch // //======================================================================== // Stop Watch Attributes //======================================================================== class StopWatchValue : public Integer { private: StopWatch *sw; public: StopWatchValue(StopWatch *_sw) : Integer(0), sw(_sw) { m_bClearableSymbol = false; new_name("stopwatch"); set_description(" A timer for monitoring and controlling the simulation.\n" " The units are in simulation cycles.\n" " stopwatch.rollover - specifies rollover value.\n" " stopwatch.direction - specifies count direction.\n" " stopwatch.enable - enables counting if true.\n" ); } virtual void set(Value *v) { Integer::set(v); if(sw) sw->update(); } virtual void get(gint64 &i) { i = (sw) ? sw->get() : 0; Integer::set(i); } virtual int set_break(ObjectBreakTypes bt=eBreakAny, Expression *expr=0) { if(sw) sw->set_break(true); return -1; // FIXME } virtual int clear_break() { if(sw) sw->set_break(false); return -1; // FIXME } }; class StopWatchRollover : public Integer { private: StopWatch *sw; public: StopWatchRollover(StopWatch *_sw) : Integer(1000000), sw(_sw) { m_bClearableSymbol = false; new_name("stopwatch.rollover"); set_description(" specifies the stop watch roll over time."); } virtual void set(Value *v) { Integer::set(v); if(sw) sw->update(); } }; class StopWatchEnable : public Boolean { private: StopWatch *sw; public: StopWatchEnable(StopWatch *_sw) : Boolean(true) , sw(_sw) { m_bClearableSymbol = false; new_name("stopwatch.enable"); set_description(" If true, the stop watch is enabled."); } virtual void set(Value *v) { if(sw) sw->update(); Boolean::set(v); } }; class StopWatchDirection : public Boolean { private: StopWatch *sw; public: StopWatchDirection(StopWatch *_sw) : Boolean(true) , sw(_sw) { m_bClearableSymbol = false; new_name("stopwatch.direction"); set_description(" If true, the stop watch counts up otherwise down."); } virtual void set(Value *v) { if(!v) return; bool bOldVal = getVal(); bool bNewVal; v->get(bNewVal); if(sw && bOldVal != bNewVal) sw->set_direction(bNewVal); } }; //------------------------------------------------------------ StopWatch::StopWatch() { offset = 0; initialized = false; value = new StopWatchValue(this); rollover = new StopWatchRollover(this); enable = new StopWatchEnable(this); direction = new StopWatchDirection(this); if(!value || !rollover || !enable || !direction) throw Error("StopWatch"); } void StopWatch::init() { if(!initialized) { get_symbol_table().add(value); get_symbol_table().add(rollover); get_symbol_table().add(enable); get_symbol_table().add(direction); update(); initialized = true; } } //---------------------------------------- // get() // If the stopwatch is running, then compute // the current value based on the cycle_counter. guint64 StopWatch::get(void) { if(enable->getVal()) { gint64 v = (cycles.value - offset) % rollover->getVal(); if(!direction->getVal()) v = rollover->getVal() - v; return v; } return value->getVal(); } //---------------------------------------- // get() // If the stopwatch is running, then compute // the current value based on the cycle_counter. double StopWatch::get_time(void) { guint64 current_value =get(); if(current_value) return current_value /4000000.0; return 1.0; } void StopWatch::set_enable(bool b) { if(enable->getVal() != b) enable->set(b); update(); } void StopWatch::set_direction(bool b) { if(direction->getVal() == b) return; direction->set(b); offset = cycles.value - ((rollover->getVal() - value->getVal()) % rollover->getVal()); if(break_cycle) set_break(true); } void StopWatch::set_rollover(guint64 new_rollover) { // setting the rollover attribute will update the stopwatch too. if(rollover) rollover->set((gint64)new_rollover); } void StopWatch::set_value(guint64 new_value) { if(value) value->set((gint64)new_value); } // update() compute a new offset such that the current // value of the stopwatch is correlated with the cycle counter. void StopWatch::update() { if(enable->getVal()) { if(direction->getVal()) offset = cycles.value - value->getVal(); else offset = cycles.value - (rollover->getVal() - value->getVal()); if(break_cycle) set_break(true); } } void StopWatch::set_break(bool b) { if(!b) { cycles.clear_break(this); break_cycle = 0; return; } if(!enable->getVal()) return; guint64 old_break_cycle = break_cycle; if(direction->getVal()) break_cycle = cycles.value + rollover->getVal() - get(); else break_cycle = cycles.value + get(); if(old_break_cycle == break_cycle) return; if(old_break_cycle) cycles.reassign_break(old_break_cycle,break_cycle ,this); else cycles.set_break(break_cycle ,this); } void StopWatch::callback() { break_cycle = cycles.value + rollover->getVal(); cycles.set_break(break_cycle,this); cout << " stopwatch break\n"; get_bp().halt(); } void StopWatch::callback_print() { cout << "stopwatch\n"; }