Merge zizzer.eecs.umich.edu:/bk/newmem/
[gem5.git] / src / cpu / simple / timing.cc
1 /*
2 * Copyright (c) 2002-2005 The Regents of The University of Michigan
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met: redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer;
9 * redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution;
12 * neither the name of the copyright holders nor the names of its
13 * contributors may be used to endorse or promote products derived from
14 * this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * Authors: Steve Reinhardt
29 */
30
31 #include "arch/locked_mem.hh"
32 #include "arch/utility.hh"
33 #include "cpu/exetrace.hh"
34 #include "cpu/simple/timing.hh"
35 #include "mem/packet.hh"
36 #include "mem/packet_access.hh"
37 #include "sim/builder.hh"
38 #include "sim/system.hh"
39
40 using namespace std;
41 using namespace TheISA;
42
43 Port *
44 TimingSimpleCPU::getPort(const std::string &if_name, int idx)
45 {
46 if (if_name == "dcache_port")
47 return &dcachePort;
48 else if (if_name == "icache_port")
49 return &icachePort;
50 else
51 panic("No Such Port\n");
52 }
53
54 void
55 TimingSimpleCPU::init()
56 {
57 BaseCPU::init();
58 #if FULL_SYSTEM
59 for (int i = 0; i < threadContexts.size(); ++i) {
60 ThreadContext *tc = threadContexts[i];
61
62 // initialize CPU, including PC
63 TheISA::initCPU(tc, tc->readCpuId());
64 }
65 #endif
66 }
67
68 Tick
69 TimingSimpleCPU::CpuPort::recvAtomic(PacketPtr pkt)
70 {
71 panic("TimingSimpleCPU doesn't expect recvAtomic callback!");
72 return curTick;
73 }
74
75 void
76 TimingSimpleCPU::CpuPort::recvFunctional(PacketPtr pkt)
77 {
78 //No internal storage to update, jusst return
79 return;
80 }
81
82 void
83 TimingSimpleCPU::CpuPort::recvStatusChange(Status status)
84 {
85 if (status == RangeChange) {
86 if (!snoopRangeSent) {
87 snoopRangeSent = true;
88 sendStatusChange(Port::RangeChange);
89 }
90 return;
91 }
92
93 panic("TimingSimpleCPU doesn't expect recvStatusChange callback!");
94 }
95
96
97 void
98 TimingSimpleCPU::CpuPort::TickEvent::schedule(PacketPtr _pkt, Tick t)
99 {
100 pkt = _pkt;
101 Event::schedule(t);
102 }
103
104 TimingSimpleCPU::TimingSimpleCPU(Params *p)
105 : BaseSimpleCPU(p), icachePort(this, p->clock), dcachePort(this, p->clock),
106 cpu_id(p->cpu_id)
107 {
108 _status = Idle;
109
110 icachePort.snoopRangeSent = false;
111 dcachePort.snoopRangeSent = false;
112
113 ifetch_pkt = dcache_pkt = NULL;
114 drainEvent = NULL;
115 fetchEvent = NULL;
116 previousTick = 0;
117 changeState(SimObject::Running);
118 }
119
120
121 TimingSimpleCPU::~TimingSimpleCPU()
122 {
123 }
124
125 void
126 TimingSimpleCPU::serialize(ostream &os)
127 {
128 SimObject::State so_state = SimObject::getState();
129 SERIALIZE_ENUM(so_state);
130 BaseSimpleCPU::serialize(os);
131 }
132
133 void
134 TimingSimpleCPU::unserialize(Checkpoint *cp, const string &section)
135 {
136 SimObject::State so_state;
137 UNSERIALIZE_ENUM(so_state);
138 BaseSimpleCPU::unserialize(cp, section);
139 }
140
141 unsigned int
142 TimingSimpleCPU::drain(Event *drain_event)
143 {
144 // TimingSimpleCPU is ready to drain if it's not waiting for
145 // an access to complete.
146 if (status() == Idle || status() == Running || status() == SwitchedOut) {
147 changeState(SimObject::Drained);
148 return 0;
149 } else {
150 changeState(SimObject::Draining);
151 drainEvent = drain_event;
152 return 1;
153 }
154 }
155
156 void
157 TimingSimpleCPU::resume()
158 {
159 if (_status != SwitchedOut && _status != Idle) {
160 assert(system->getMemoryMode() == System::Timing);
161
162 // Delete the old event if it existed.
163 if (fetchEvent) {
164 if (fetchEvent->scheduled())
165 fetchEvent->deschedule();
166
167 delete fetchEvent;
168 }
169
170 fetchEvent =
171 new EventWrapper<TimingSimpleCPU, &TimingSimpleCPU::fetch>(this, false);
172 fetchEvent->schedule(nextCycle());
173 }
174
175 changeState(SimObject::Running);
176 previousTick = curTick;
177 }
178
179 void
180 TimingSimpleCPU::switchOut()
181 {
182 assert(status() == Running || status() == Idle);
183 _status = SwitchedOut;
184 numCycles += curTick - previousTick;
185
186 // If we've been scheduled to resume but are then told to switch out,
187 // we'll need to cancel it.
188 if (fetchEvent && fetchEvent->scheduled())
189 fetchEvent->deschedule();
190 }
191
192
193 void
194 TimingSimpleCPU::takeOverFrom(BaseCPU *oldCPU)
195 {
196 BaseCPU::takeOverFrom(oldCPU);
197
198 // if any of this CPU's ThreadContexts are active, mark the CPU as
199 // running and schedule its tick event.
200 for (int i = 0; i < threadContexts.size(); ++i) {
201 ThreadContext *tc = threadContexts[i];
202 if (tc->status() == ThreadContext::Active && _status != Running) {
203 _status = Running;
204 break;
205 }
206 }
207
208 if (_status != Running) {
209 _status = Idle;
210 }
211
212 Port *peer;
213 if (icachePort.getPeer() == NULL) {
214 peer = oldCPU->getPort("icache_port")->getPeer();
215 icachePort.setPeer(peer);
216 } else {
217 peer = icachePort.getPeer();
218 }
219 peer->setPeer(&icachePort);
220
221 if (dcachePort.getPeer() == NULL) {
222 peer = oldCPU->getPort("dcache_port")->getPeer();
223 dcachePort.setPeer(peer);
224 } else {
225 peer = dcachePort.getPeer();
226 }
227 peer->setPeer(&dcachePort);
228 }
229
230
231 void
232 TimingSimpleCPU::activateContext(int thread_num, int delay)
233 {
234 assert(thread_num == 0);
235 assert(thread);
236
237 assert(_status == Idle);
238
239 notIdleFraction++;
240 _status = Running;
241 // kick things off by initiating the fetch of the next instruction
242 fetchEvent =
243 new EventWrapper<TimingSimpleCPU, &TimingSimpleCPU::fetch>(this, false);
244 fetchEvent->schedule(nextCycle(curTick + cycles(delay)));
245 }
246
247
248 void
249 TimingSimpleCPU::suspendContext(int thread_num)
250 {
251 assert(thread_num == 0);
252 assert(thread);
253
254 assert(_status == Running);
255
256 // just change status to Idle... if status != Running,
257 // completeInst() will not initiate fetch of next instruction.
258
259 notIdleFraction--;
260 _status = Idle;
261 }
262
263
264 template <class T>
265 Fault
266 TimingSimpleCPU::read(Addr addr, T &data, unsigned flags)
267 {
268 Request *req =
269 new Request(/* asid */ 0, addr, sizeof(T), flags, thread->readPC(),
270 cpu_id, /* thread ID */ 0);
271
272 if (traceData) {
273 traceData->setAddr(req->getVaddr());
274 }
275
276 // translate to physical address
277 Fault fault = thread->translateDataReadReq(req);
278
279 // Now do the access.
280 if (fault == NoFault) {
281 PacketPtr pkt =
282 new Packet(req, Packet::ReadReq, Packet::Broadcast);
283 pkt->dataDynamic<T>(new T);
284
285 if (!dcachePort.sendTiming(pkt)) {
286 _status = DcacheRetry;
287 dcache_pkt = pkt;
288 } else {
289 _status = DcacheWaitResponse;
290 // memory system takes ownership of packet
291 dcache_pkt = NULL;
292 }
293 } else {
294 delete req;
295 }
296
297 // This will need a new way to tell if it has a dcache attached.
298 if (req->isUncacheable())
299 recordEvent("Uncached Read");
300
301 return fault;
302 }
303
304 #ifndef DOXYGEN_SHOULD_SKIP_THIS
305
306 template
307 Fault
308 TimingSimpleCPU::read(Addr addr, uint64_t &data, unsigned flags);
309
310 template
311 Fault
312 TimingSimpleCPU::read(Addr addr, uint32_t &data, unsigned flags);
313
314 template
315 Fault
316 TimingSimpleCPU::read(Addr addr, uint16_t &data, unsigned flags);
317
318 template
319 Fault
320 TimingSimpleCPU::read(Addr addr, uint8_t &data, unsigned flags);
321
322 #endif //DOXYGEN_SHOULD_SKIP_THIS
323
324 template<>
325 Fault
326 TimingSimpleCPU::read(Addr addr, double &data, unsigned flags)
327 {
328 return read(addr, *(uint64_t*)&data, flags);
329 }
330
331 template<>
332 Fault
333 TimingSimpleCPU::read(Addr addr, float &data, unsigned flags)
334 {
335 return read(addr, *(uint32_t*)&data, flags);
336 }
337
338
339 template<>
340 Fault
341 TimingSimpleCPU::read(Addr addr, int32_t &data, unsigned flags)
342 {
343 return read(addr, (uint32_t&)data, flags);
344 }
345
346
347 template <class T>
348 Fault
349 TimingSimpleCPU::write(T data, Addr addr, unsigned flags, uint64_t *res)
350 {
351 Request *req =
352 new Request(/* asid */ 0, addr, sizeof(T), flags, thread->readPC(),
353 cpu_id, /* thread ID */ 0);
354
355 // translate to physical address
356 Fault fault = thread->translateDataWriteReq(req);
357
358 // Now do the access.
359 if (fault == NoFault) {
360 assert(dcache_pkt == NULL);
361 dcache_pkt = new Packet(req, Packet::WriteReq, Packet::Broadcast);
362 dcache_pkt->allocate();
363 dcache_pkt->set(data);
364
365 bool do_access = true; // flag to suppress cache access
366
367 if (req->isLocked()) {
368 do_access = TheISA::handleLockedWrite(thread, req);
369 }
370
371 if (do_access) {
372 if (!dcachePort.sendTiming(dcache_pkt)) {
373 _status = DcacheRetry;
374 } else {
375 _status = DcacheWaitResponse;
376 // memory system takes ownership of packet
377 dcache_pkt = NULL;
378 }
379 }
380 } else {
381 delete req;
382 }
383
384 // This will need a new way to tell if it's hooked up to a cache or not.
385 if (req->isUncacheable())
386 recordEvent("Uncached Write");
387
388 // If the write needs to have a fault on the access, consider calling
389 // changeStatus() and changing it to "bad addr write" or something.
390 return fault;
391 }
392
393
394 #ifndef DOXYGEN_SHOULD_SKIP_THIS
395 template
396 Fault
397 TimingSimpleCPU::write(uint64_t data, Addr addr,
398 unsigned flags, uint64_t *res);
399
400 template
401 Fault
402 TimingSimpleCPU::write(uint32_t data, Addr addr,
403 unsigned flags, uint64_t *res);
404
405 template
406 Fault
407 TimingSimpleCPU::write(uint16_t data, Addr addr,
408 unsigned flags, uint64_t *res);
409
410 template
411 Fault
412 TimingSimpleCPU::write(uint8_t data, Addr addr,
413 unsigned flags, uint64_t *res);
414
415 #endif //DOXYGEN_SHOULD_SKIP_THIS
416
417 template<>
418 Fault
419 TimingSimpleCPU::write(double data, Addr addr, unsigned flags, uint64_t *res)
420 {
421 return write(*(uint64_t*)&data, addr, flags, res);
422 }
423
424 template<>
425 Fault
426 TimingSimpleCPU::write(float data, Addr addr, unsigned flags, uint64_t *res)
427 {
428 return write(*(uint32_t*)&data, addr, flags, res);
429 }
430
431
432 template<>
433 Fault
434 TimingSimpleCPU::write(int32_t data, Addr addr, unsigned flags, uint64_t *res)
435 {
436 return write((uint32_t)data, addr, flags, res);
437 }
438
439
440 void
441 TimingSimpleCPU::fetch()
442 {
443 if (!curStaticInst || !curStaticInst->isDelayedCommit())
444 checkForInterrupts();
445
446 Request *ifetch_req = new Request();
447 ifetch_req->setThreadContext(cpu_id, /* thread ID */ 0);
448 Fault fault = setupFetchRequest(ifetch_req);
449
450 ifetch_pkt = new Packet(ifetch_req, Packet::ReadReq, Packet::Broadcast);
451 ifetch_pkt->dataStatic(&inst);
452
453 if (fault == NoFault) {
454 if (!icachePort.sendTiming(ifetch_pkt)) {
455 // Need to wait for retry
456 _status = IcacheRetry;
457 } else {
458 // Need to wait for cache to respond
459 _status = IcacheWaitResponse;
460 // ownership of packet transferred to memory system
461 ifetch_pkt = NULL;
462 }
463 } else {
464 delete ifetch_req;
465 delete ifetch_pkt;
466 // fetch fault: advance directly to next instruction (fault handler)
467 advanceInst(fault);
468 }
469
470 numCycles += curTick - previousTick;
471 previousTick = curTick;
472 }
473
474
475 void
476 TimingSimpleCPU::advanceInst(Fault fault)
477 {
478 advancePC(fault);
479
480 if (_status == Running) {
481 // kick off fetch of next instruction... callback from icache
482 // response will cause that instruction to be executed,
483 // keeping the CPU running.
484 fetch();
485 }
486 }
487
488
489 void
490 TimingSimpleCPU::completeIfetch(PacketPtr pkt)
491 {
492 // received a response from the icache: execute the received
493 // instruction
494 assert(pkt->result == Packet::Success);
495 assert(_status == IcacheWaitResponse);
496
497 _status = Running;
498
499 numCycles += curTick - previousTick;
500 previousTick = curTick;
501
502 if (getState() == SimObject::Draining) {
503 delete pkt->req;
504 delete pkt;
505
506 completeDrain();
507 return;
508 }
509
510 preExecute();
511 if (curStaticInst->isMemRef() && !curStaticInst->isDataPrefetch()) {
512 // load or store: just send to dcache
513 Fault fault = curStaticInst->initiateAcc(this, traceData);
514 if (_status != Running) {
515 // instruction will complete in dcache response callback
516 assert(_status == DcacheWaitResponse || _status == DcacheRetry);
517 assert(fault == NoFault);
518 } else {
519 if (fault == NoFault) {
520 // early fail on store conditional: complete now
521 assert(dcache_pkt != NULL);
522 fault = curStaticInst->completeAcc(dcache_pkt, this,
523 traceData);
524 delete dcache_pkt->req;
525 delete dcache_pkt;
526 dcache_pkt = NULL;
527 }
528 postExecute();
529 advanceInst(fault);
530 }
531 } else {
532 // non-memory instruction: execute completely now
533 Fault fault = curStaticInst->execute(this, traceData);
534 postExecute();
535 advanceInst(fault);
536 }
537
538 delete pkt->req;
539 delete pkt;
540 }
541
542 void
543 TimingSimpleCPU::IcachePort::ITickEvent::process()
544 {
545 cpu->completeIfetch(pkt);
546 }
547
548 bool
549 TimingSimpleCPU::IcachePort::recvTiming(PacketPtr pkt)
550 {
551 if (pkt->isResponse()) {
552 // delay processing of returned data until next CPU clock edge
553 Tick mem_time = pkt->req->getTime();
554 Tick next_tick = cpu->nextCycle(mem_time);
555
556 if (next_tick == curTick)
557 cpu->completeIfetch(pkt);
558 else
559 tickEvent.schedule(pkt, next_tick);
560
561 return true;
562 }
563 else {
564 //Snooping a Coherence Request, do nothing
565 return true;
566 }
567 }
568
569 void
570 TimingSimpleCPU::IcachePort::recvRetry()
571 {
572 // we shouldn't get a retry unless we have a packet that we're
573 // waiting to transmit
574 assert(cpu->ifetch_pkt != NULL);
575 assert(cpu->_status == IcacheRetry);
576 PacketPtr tmp = cpu->ifetch_pkt;
577 if (sendTiming(tmp)) {
578 cpu->_status = IcacheWaitResponse;
579 cpu->ifetch_pkt = NULL;
580 }
581 }
582
583 void
584 TimingSimpleCPU::completeDataAccess(PacketPtr pkt)
585 {
586 // received a response from the dcache: complete the load or store
587 // instruction
588 assert(pkt->result == Packet::Success);
589 assert(_status == DcacheWaitResponse);
590 _status = Running;
591
592 numCycles += curTick - previousTick;
593 previousTick = curTick;
594
595 Fault fault = curStaticInst->completeAcc(pkt, this, traceData);
596
597 if (pkt->isRead() && pkt->req->isLocked()) {
598 TheISA::handleLockedRead(thread, pkt->req);
599 }
600
601 delete pkt->req;
602 delete pkt;
603
604 postExecute();
605
606 if (getState() == SimObject::Draining) {
607 advancePC(fault);
608 completeDrain();
609
610 return;
611 }
612
613 advanceInst(fault);
614 }
615
616
617 void
618 TimingSimpleCPU::completeDrain()
619 {
620 DPRINTF(Config, "Done draining\n");
621 changeState(SimObject::Drained);
622 drainEvent->process();
623 }
624
625 bool
626 TimingSimpleCPU::DcachePort::recvTiming(PacketPtr pkt)
627 {
628 if (pkt->isResponse()) {
629 // delay processing of returned data until next CPU clock edge
630 Tick mem_time = pkt->req->getTime();
631 Tick next_tick = cpu->nextCycle(mem_time);
632
633 if (next_tick == curTick)
634 cpu->completeDataAccess(pkt);
635 else
636 tickEvent.schedule(pkt, next_tick);
637
638 return true;
639 }
640 else {
641 //Snooping a coherence req, do nothing
642 return true;
643 }
644 }
645
646 void
647 TimingSimpleCPU::DcachePort::DTickEvent::process()
648 {
649 cpu->completeDataAccess(pkt);
650 }
651
652 void
653 TimingSimpleCPU::DcachePort::recvRetry()
654 {
655 // we shouldn't get a retry unless we have a packet that we're
656 // waiting to transmit
657 assert(cpu->dcache_pkt != NULL);
658 assert(cpu->_status == DcacheRetry);
659 PacketPtr tmp = cpu->dcache_pkt;
660 if (sendTiming(tmp)) {
661 cpu->_status = DcacheWaitResponse;
662 // memory system takes ownership of packet
663 cpu->dcache_pkt = NULL;
664 }
665 }
666
667
668 ////////////////////////////////////////////////////////////////////////
669 //
670 // TimingSimpleCPU Simulation Object
671 //
672 BEGIN_DECLARE_SIM_OBJECT_PARAMS(TimingSimpleCPU)
673
674 Param<Counter> max_insts_any_thread;
675 Param<Counter> max_insts_all_threads;
676 Param<Counter> max_loads_any_thread;
677 Param<Counter> max_loads_all_threads;
678 Param<Tick> progress_interval;
679 SimObjectParam<System *> system;
680 Param<int> cpu_id;
681
682 #if FULL_SYSTEM
683 SimObjectParam<TheISA::ITB *> itb;
684 SimObjectParam<TheISA::DTB *> dtb;
685 Param<Tick> profile;
686
687 Param<bool> do_quiesce;
688 Param<bool> do_checkpoint_insts;
689 Param<bool> do_statistics_insts;
690 #else
691 SimObjectParam<Process *> workload;
692 #endif // FULL_SYSTEM
693
694 Param<int> clock;
695 Param<int> phase;
696
697 Param<bool> defer_registration;
698 Param<int> width;
699 Param<bool> function_trace;
700 Param<Tick> function_trace_start;
701 Param<bool> simulate_stalls;
702
703 END_DECLARE_SIM_OBJECT_PARAMS(TimingSimpleCPU)
704
705 BEGIN_INIT_SIM_OBJECT_PARAMS(TimingSimpleCPU)
706
707 INIT_PARAM(max_insts_any_thread,
708 "terminate when any thread reaches this inst count"),
709 INIT_PARAM(max_insts_all_threads,
710 "terminate when all threads have reached this inst count"),
711 INIT_PARAM(max_loads_any_thread,
712 "terminate when any thread reaches this load count"),
713 INIT_PARAM(max_loads_all_threads,
714 "terminate when all threads have reached this load count"),
715 INIT_PARAM(progress_interval, "Progress interval"),
716 INIT_PARAM(system, "system object"),
717 INIT_PARAM(cpu_id, "processor ID"),
718
719 #if FULL_SYSTEM
720 INIT_PARAM(itb, "Instruction TLB"),
721 INIT_PARAM(dtb, "Data TLB"),
722 INIT_PARAM(profile, ""),
723 INIT_PARAM(do_quiesce, ""),
724 INIT_PARAM(do_checkpoint_insts, ""),
725 INIT_PARAM(do_statistics_insts, ""),
726 #else
727 INIT_PARAM(workload, "processes to run"),
728 #endif // FULL_SYSTEM
729
730 INIT_PARAM(clock, "clock speed"),
731 INIT_PARAM_DFLT(phase, "clock phase", 0),
732 INIT_PARAM(defer_registration, "defer system registration (for sampling)"),
733 INIT_PARAM(width, "cpu width"),
734 INIT_PARAM(function_trace, "Enable function trace"),
735 INIT_PARAM(function_trace_start, "Cycle to start function trace"),
736 INIT_PARAM(simulate_stalls, "Simulate cache stall cycles")
737
738 END_INIT_SIM_OBJECT_PARAMS(TimingSimpleCPU)
739
740
741 CREATE_SIM_OBJECT(TimingSimpleCPU)
742 {
743 TimingSimpleCPU::Params *params = new TimingSimpleCPU::Params();
744 params->name = getInstanceName();
745 params->numberOfThreads = 1;
746 params->max_insts_any_thread = max_insts_any_thread;
747 params->max_insts_all_threads = max_insts_all_threads;
748 params->max_loads_any_thread = max_loads_any_thread;
749 params->max_loads_all_threads = max_loads_all_threads;
750 params->progress_interval = progress_interval;
751 params->deferRegistration = defer_registration;
752 params->clock = clock;
753 params->phase = phase;
754 params->functionTrace = function_trace;
755 params->functionTraceStart = function_trace_start;
756 params->system = system;
757 params->cpu_id = cpu_id;
758
759 #if FULL_SYSTEM
760 params->itb = itb;
761 params->dtb = dtb;
762 params->profile = profile;
763 params->do_quiesce = do_quiesce;
764 params->do_checkpoint_insts = do_checkpoint_insts;
765 params->do_statistics_insts = do_statistics_insts;
766 #else
767 params->process = workload;
768 #endif
769
770 TimingSimpleCPU *cpu = new TimingSimpleCPU(params);
771 return cpu;
772 }
773
774 REGISTER_SIM_OBJECT("TimingSimpleCPU", TimingSimpleCPU)
775