首先分析事件驱动的步骤:

  • 在gem5模拟器中,simulate.cc 中 doSimLoop循环的调用eventq的service成员函数;该成员函数会从二级链表的event中,抽取event,调用event→process();
  • event是一个指针,是gem5中其他组件的基类之一,因此可以通过多态的方法,访问其他组件的process函数;
  • 在调用process函数之前,调用setCurTick(event→when()),即将event的when设置为当前的curTick(),这时也就对系统的时间进行了更新;然后调用event→process();即调用了一个具体对象的process,假如这个对象是ALU;
  • 这时ALU将会执行其功能的具体任务,这时它可能只能完成一个颗粒度的任务,比如一个时钟的加,或者三个时钟的乘法(具体乘法是由process函数分三步完成,每步一个cycle还是一步完成,一步三个cycle是可以DIY的),完成之后。然后调用schedule函数,这个函数是经由Eventwrapper包装,实际上是EventQueue的成员函数,它实现了:
  • 更新when,也就是下一次什么时候再次触发event
  • 将event插入到event queue中,在插入event queue时,queue时二级链表,这个链表是按照各个器件下一次发生的时间进行排序的,在插入时,也是要进行一次排序,找到合适的位置进行插入。(在queue中 有bin,怀疑是同一批时间的在一个bin,不同时间的是不同的bin)。
  • when的值,是在调用schedule时,可以将下一次的时间通过调用clockEdge(Cycles(n))获取的。

总体说来,就是不同的器件在process中:

1)完成自身的功能;

2)将下一次何时触发,插入queue中

 

要注意的是,插入queue中的是每个对象的event指针,不需要复制,也不是完整的对象,只是指针。

如果A器件在100 cycle时,执行process,完成自身功能后,将event->when设置为107,插入queue中;表示下一次在107时刻触发;然后,B器件在100 cycle时,执行process,完成自身功能后,将event->when设置为120,插入queue中;表示下一次在120时刻触发;然后C器件在103 cycle时,执行process,完成自身功能后,将event->when设置为109,插入queue中;表示下一次在109时刻触发;

那么在gem5中,执行的过程将是

  1. a调度,更新系统时间为100,执行完毕后,插入事件a 107;
  2. b调度,更新系统时间(或不更新),执行完毕后,插入事件b 120;
  3. 查找queue,发现下一次是c触发,将系统时间更新为103,执行完毕后,插入事件c109;
  4. 查找queue,发现下一次是a触发,将系统时间更新为107,执行完毕后,插入下一次a事件;
  5. 查找queue,发现下一次是c触发,将系统时间更新为109,执行完毕后,插入下一次c事件;
  6. 查找queue,发现下一次是b触发,将系统时间更新为120,执行完毕后,插入下一次b事件;

这就是基于事件驱动的原理。