simple_bus是一个简单的总线模型的抽象描述,基于时钟同步的事务级建模。
时钟同步:这种时钟节拍精度的仿真会导致仿真速度变慢。目标是在一个时钟一个时钟的基础上对系统中的组织和数据移动进行建模,并与等效的真实系统进行比较,会忽略节拍内的事件。
事务建模:各功能组件之间的通信被描述为函数调用,一组总线的事件或事件序列由抽象的软件接口以及相应的函数调用表示。接口的事务级建模比基于pin的建模具有更高的仿真速度,同时也加快了建模构建过程。
此示例设计使用一种同步时钟形式,其中连接到总线的模块在时钟上升沿执行,而总线本身在时钟下降沿执行。这种用于实现抽象总线模型的建模技术可以做到非常高的仿真性能,当然了,这并不意味着实际实现必须使用对时钟下降沿敏感的总线。实际上,最终的实现可能只有一个时钟,所有逻辑只对上升沿敏感。当然也可以使用其他同步方案(例如,依赖原始信道和或者更新请求等),不过这可能会导致此设计的代码更复杂、更慢。
SystemC自带example的系列:
SystemC自带example的simple_perf研习
1 总线功能描述
总线的主接口描述了主设备和总线之间的通信。总线连接的主设备,每个主设备都由一个唯一的优先级标识,该优先级由一个无符号整数表示,值越小,优先级就越高,每个总线接口函数都使用这个优先级来设置调用的重要性。
主设备可以通过使用锁标志,请求保留总线的使用权。设置了该标志,并且在给定的节拍内批准了该请求,那么在接下来的时钟节拍内总线将被该主设备锁定使用权限。
在主设备和总线之间定义了三组通信函数(接口):阻塞、非阻塞和直接。
总线的从接口描述总线和从设备之间的通信。可以将多个从设备连接到总线,每个从设备都模拟了一些可以通过从设备接口访问的存储模型。
因为每个主设备都是独立的,所以每个主设备可以随时发出总线请求。为了可以将多个主设备连接到总线,应对在给定的节拍内对总线发出的一个或多个请求时,这些请求被收集并传递给仲裁器。从这个集合中选出最合适的请求发出,其他请求保持在请求状态。仲裁器通过专用接口连接到总线,并通过集合中的一个或多个请求调用。
以下是示例,总线挂接了3个主设备和2个从设备以及1个仲裁器:
2 总线的Interface
主从接口以及事务级的描述,可以看出,主接口使用了接口的基类,因为这里需要定义一组方法,比如阻塞、非阻塞和直接的请求以及处理方法。
class simple_bus : public simple_bus_direct_if , public simple_bus_non_blocking_if , public simple_bus_blocking_if , public sc_module
{
public:
// ports
sc_in_clk clock;
sc_port<simple_bus_arbiter_if> arbiter_port;
sc_port<simple_bus_slave_if, 0> slave_port;
SC_HAS_PROCESS(simple_bus);
// constructor
simple_bus(sc_module_name name_, bool verbose = false)
: sc_module(name_), m_verbose(verbose), m_current_request(0)
{
// process declaration
SC_METHOD(main_action);
dont_initialize();
sensitive << clock.neg();
}
// process
void main_action();
// direct BUS interface
bool direct_read(int *data, unsigned int address);
bool direct_write(int *data, unsigned int address);
// non-blocking BUS interface
void read(unsigned int unique_priority, int *data, unsigned int address, bool lock = false);
void write(unsigned int unique_priority, int *data, unsigned int address, bool lock = false);
simple_bus_status get_status(unsigned int unique_priority);
// blocking BUS interface
simple_bus_status burst_read(unsigned int unique_priority, int *data, unsigned int start_address, unsigned int length = 1, bool lock = false);
simple_bus_status burst_write(unsigned int unique_priority, int *data, unsigned int start_address, unsigned int length = 1, bool lock = false);
private:
void handle_request();
void end_of_elaboration();
simple_bus_slave_if * get_slave(unsigned int address);
simple_bus_request * get_request(unsigned int priority);
simple_bus_request * get_next_request();
void clear_locks();
private:
bool m_verbose;
simple_bus_request_vec m_requests;
simple_bus_request *m_current_request;
}; // end class simple_bus
void simple_bus::end_of_elaboration()
{
// perform a static check for overlapping memory areas of the slaves
bool no_overlap;
for (int i = 1; i < slave_port.size(); ++i) {
simple_bus_slave_if *slave1 = slave_port[i];
for (int j = 0; j < i; ++j) {
simple_bus_slave_if *slave2 = slave_port[j];
no_overlap = ( slave1->end_address() < slave2->start_address() ) || ( slave1->start_address() > slave2->end_address() );
if ( !no_overlap ) {
sb_fprintf(stdout,"Error: overlapping address spaces of 2 slaves : \n");
sb_fprintf(stdout,"slave %i : %0X..%0X\n",i,slave1->start_address(),slave1->end_address());
sb_fprintf(stdout,"slave %i : %0X..%0X\n",j,slave2->start_address(),slave2->end_address());
exit(0);
}
}
}
}
void simple_bus::main_action()
{
if (!m_current_request)
m_current_request = get_next_request();
else
// monitor slave wait states
if (m_verbose)
sb_fprintf(stdout, "%s SLV [%d]\n", sc_time_stamp().to_string().c_str(), m_current_request->address);
if (m_current_request)
handle_request();
if (!m_current_request)
clear_locks();
}
void simple_bus::handle_request()
{
if (m_verbose)
sb_fprintf(stdout, "%s %s Handle Slave(%d)\n",sc_time_stamp().to_string().c_str(), name(), m_current_request->priority);
m_current_request->status = SIMPLE_BUS_WAIT;
simple_bus_slave_if *slave = get_slave(m_current_request->address);
if ((m_current_request->address)%4 != 0 ) {// address not word alligned
sb_fprintf(stdout, " BUS ERROR --> address %04X not word alligned\n",m_current_request->address);
m_current_request->status = SIMPLE_BUS_ERROR;
m_current_request = (simple_bus_request *)0;
return;
}
if (!slave) {
sb_fprintf(stdout, " BUS ERROR --> no slave for address %04X \n",m_current_request->address);
m_current_request->status = SIMPLE_BUS_ERROR;
m_current_request = (simple_bus_request *)0;
return;
}
simple_bus_status slave_status = SIMPLE_BUS_OK;
if (m_current_request->do_write)
slave_status = slave->write(m_current_request->data, m_current_request->address);
else
slave_status = slave->read(m_current_request->data,m_current_request->address);
if (m_verbose)
sb_fprintf(stdout, " --> status=(%s)\n", simple_bus_status_str[slave_status]);
switch(slave_status){
case SIMPLE_BUS_ERROR:
m_current_request->status = SIMPLE_BUS_ERROR;
m_current_request->transfer_done.notify();
m_current_request = (simple_bus_request *)0;
break;
case SIMPLE_BUS_OK:
m_current_request->address+=4; //next word (byte addressing)
m_current_request->data++;
if (m_current_request->address > m_current_request->end_address){
sb_fprintf(stdout, "%s %s Handle Slave(@ %x> %x)\n",sc_time_stamp().to_string().c_str(), name(), m_current_request->address, m_current_request->end_address);
// burst-transfer (or single transfer) completed
m_current_request->status = SIMPLE_BUS_OK;
m_current_request->transfer_done.notify();
m_current_request = (simple_bus_request *)0;
}
else{ // more data to transfer, but the (atomic) slave transfer is done
sb_fprintf(stdout, "%s %s Handle Slave(@ %x- %x)\n",sc_time_stamp().to_string().c_str(), name(), m_current_request->address, m_current_request->end_address);
m_current_request = (simple_bus_request *)0;
}
break;
case SIMPLE_BUS_WAIT:
// the slave is still processing: no clearance of the current request
break;
default:
break;
}
}
simple_bus_slave_if *simple_bus::get_slave(unsigned int address)
{
for (int i = 0; i < slave_port.size(); ++i){
simple_bus_slave_if *slave = slave_port[i];
if ((slave->start_address() <= address) && (address <= slave->end_address()))
return slave;
}
return (simple_bus_slave_if *)0;
}
simple_bus_request * simple_bus::get_request(unsigned int priority)
{
simple_bus_request *request = (simple_bus_request *)0;
for (unsigned int i = 0; i < m_requests.size(); ++i){
request = m_requests[i];
if ((request) && (request->priority == priority))
return request;
}
request = new simple_bus_request;
request->priority = priority;
m_requests.push_back(request);
return request;
}
simple_bus_request * simple_bus::get_next_request()
{
simple_bus_request_vec Q;
for (unsigned int i = 0; i < m_requests.size(); ++i){
simple_bus_request *request = m_requests[i];
if ((request->status == SIMPLE_BUS_REQUEST) || (request->status == SIMPLE_BUS_WAIT)){
if (m_verbose)
sb_fprintf(stdout, "%s %s : request (%d) [%s]\n",sc_time_stamp().to_string().c_str(), name(), request->priority, simple_bus_status_str[request->status]);
Q.push_back(request);
}
}
if (Q.size() > 0)
return arbiter_port->arbitrate(Q);
return (simple_bus_request *)0;
}
void simple_bus::clear_locks()
{
for (unsigned int i = 0; i < m_requests.size(); ++i)
if (m_requests[i]->lock == SIMPLE_BUS_LOCK_GRANTED)
m_requests[i]->lock = SIMPLE_BUS_LOCK_SET;
else
m_requests[i]->lock = SIMPLE_BUS_LOCK_NO;
}
2.1 Blocking Interface 和阻塞设备
阻塞接口功能是以突发模式在总线上传输数据。声明如下代码所示,读写一块数据,给定长度(以字为单位)且从起始地址开始,使用字节寻址,即每个有效字地址是4的倍数。unique_priority的值指定主设备的优先级及id,这个值在总线上是唯一的。
如果设置了锁,则有两种不同的效果:
1) 保留总线以供同一主机的下一个请求独占使用。此请求必须紧跟在前一个突发请求完成之后,即当burst_read()或burst_write()函数返回时。
2) 该事务不能被具有更高优先级的请求中断。
每次事务完成后,总线返回一个simple_bus_status,该状态为:
-SIMPLE_BUS_OK:传输成功。
-SIMPLE_BUS_ERROR:传输过程中发生错误,并非所有数据都可以读/写。包括非法地址或地址范围,以及写入只读从设备。注意事务的中断不会导致SIMPLE_BUS_ERROR。
simple_bus_status simple_bus::burst_read(unsigned int unique_priority, int *data, unsigned int start_address, unsigned int length, bool lock)
{
if (m_verbose) {
sb_fprintf(stdout, "%s %s : burst_read(%d) @ %x\n",sc_time_stamp().to_string().c_str(), name(), unique_priority, start_address);
}
simple_bus_request *request = get_request(unique_priority);
request->do_write = false; // we are reading
request->address = start_address;
request->end_address = start_address + (length-1)*4;
request->data = data;
if (lock)
request->lock = (request->lock == SIMPLE_BUS_LOCK_SET) ? SIMPLE_BUS_LOCK_GRANTED : SIMPLE_BUS_LOCK_SET;
request->status = SIMPLE_BUS_REQUEST;
wait(request->transfer_done);
wait(clock->posedge_event());
return request->status;
}
simple_bus_status simple_bus::burst_write(unsigned int unique_priority, int *data, unsigned int start_address, unsigned int length , bool lock)
{
if (m_verbose)
sb_fprintf(stdout, "%s %s : burst_write(%d) @ %x\n",sc_time_stamp().to_string().c_str(), name(), unique_priority, start_address);
simple_bus_request *request = get_request(unique_priority);
request->do_write = true; // we are writing
request->address = start_address;
request->end_address = start_address + (length-1)*4;
request->data = data;
if (lock)
request->lock = (request->lock == SIMPLE_BUS_LOCK_SET) ? SIMPLE_BUS_LOCK_GRANTED : SIMPLE_BUS_LOCK_SET;
request->status = SIMPLE_BUS_REQUEST;
wait(request->transfer_done);
wait(clock->posedge_event());
return request->status;
}
阻塞式的主设备在时钟上升沿时刻调用总线接口函数请求发起。总线接口函数在请求表中注册请求。总线状态变为SIMPLE_BUS_REQUEST。在请求完全完成后的第一个上升时钟边缘返回SIMPLE_BUS_OK or SIMPLE_BUS_ERROR状态,至此接口函数终止。在请求发出后的下一个时钟下降沿,总线处理请求。因为可能会有一个(或多个)请求,所以会调用仲裁器来确定最合适的请求。此请求的状态会设置为SIMPLE_BUS_WAIT。然后执行此请求。
SC_MODULE(simple_bus_master_blocking)
{
// ports
sc_in_clk clock;
sc_port<simple_bus_blocking_if> bus_port;
SC_HAS_PROCESS(simple_bus_master_blocking);
// constructor
simple_bus_master_blocking(sc_module_name name_, unsigned int unique_priority, unsigned int address, bool lock, int timeout)
: sc_module(name_), m_unique_priority(unique_priority), m_address(address), m_lock(lock), m_timeout(timeout)
{
// process declaration
SC_THREAD(main_action);
sensitive << clock.pos();
}
// process
void main_action();
private:
unsigned int m_unique_priority;
unsigned int m_address;
bool m_lock;
int m_timeout;
}; // end class simple_bus_master_blocking
id simple_bus_master_blocking::main_action()
{
const unsigned int mylength = 0x10; // storage capacity/burst length in words
int mydata[mylength];
unsigned int i;
simple_bus_status status;
while (true){
wait(); // ... for the next rising clock edge
status = bus_port->burst_read(m_unique_priority, mydata, m_address, mylength, m_lock);
if (status == SIMPLE_BUS_ERROR)
sb_fprintf(stdout, "%s %s : blocking-read failed at address %x\n", sc_time_stamp().to_string().c_str(), name(), m_address);
for (i = 0; i < mylength; ++i){
mydata[i] += i;
wait();
}
status = bus_port->burst_write(m_unique_priority, mydata, m_address, mylength, m_lock);
if (status == SIMPLE_BUS_ERROR)
sb_fprintf(stdout, "%s %s : blocking-write failed at address %x\n", sc_time_stamp().to_string().c_str(), name(), m_address);
wait(m_timeout, SC_NS);
}
}
对于每次要进行的数据传输,从start_address to start_address + length - 1,获取当前地址并选择相应的从设备。总线接口函数实际上不会等到调用的状态变为SIMPLE_BUS_OK or SIMPLE_BUS_ERROR,而是等待来自总线进程的事件指示传输已完成。这个事件发生在时钟下降缘。为了同步,必须对下一个时钟上升边缘发出额外的等待。
2.2 Non-Blocking Interface 和非阻塞设备
非阻塞接口的优先级要高于阻塞的。读取和写入单个字的数据,直接指向数据的地址。此请求将根据给定的唯一优先级unique_priority进行处理。
如果设置了锁,则保留总线以供同一主设备的下一个请求独占使用。注意后续请求必须在上一个请求完成后立即执行,即当获取总线状态后。
当获取总线状态的函数会立即返回,调用方必须使用该函数检查最后一个请求的状态。此函数用于查询总线上一次请求的状态,这里必须传递主设备的唯一优先级unique_priority,以便获得相应请求的状态:
- SIMPLE_BUS_REQUEST : 请求被发出并放置在队列中,等待服务。
- SIMPLE_BUS_WAIT : 请求正在服务,但尚未完成。
- SIMPLE_BUS_OK : 请求已经完成,没有错误。
- SIMPLE_BUS_ERROR : 在处理请求时发生错误。请求已完成,但传输未成功完成。
如果上一个请求的状态为SIMPLE_BUS_OK or SIMPLE_BUS_ERROR,则可以发出新的非阻塞请求,请求发出后,状态在所有情况下都会变为SIMPLE_BUS_REQUEST。只有当总线处理请求时,状态才会改变。当发出新请求而当前请求尚未完成时,将生成错误消息并中止执行。
void simple_bus::read(unsigned int unique_priority, int *data, unsigned int address, bool lock)
{
if (m_verbose)
sb_fprintf(stdout, "%s %s : read(%d) @ %x\n",sc_time_stamp().to_string().c_str(), name(), unique_priority, address);
simple_bus_request *request = get_request(unique_priority);
// abort when the request is still not finished
sc_assert((request->status == SIMPLE_BUS_OK) || (request->status == SIMPLE_BUS_ERROR));
request->do_write = false; // we are reading
request->address = address;
request->end_address = address;
request->data = data;
if (lock)
request->lock = (request->lock == SIMPLE_BUS_LOCK_SET) ? SIMPLE_BUS_LOCK_GRANTED : SIMPLE_BUS_LOCK_SET;
request->status = SIMPLE_BUS_REQUEST;
}
void simple_bus::write(unsigned int unique_priority, int *data, unsigned int address, bool lock)
{
if (m_verbose)
sb_fprintf(stdout, "%s %s : write(%d) @ %x\n",sc_time_stamp().to_string().c_str(), name(), unique_priority, address);
simple_bus_request *request = get_request(unique_priority);
// abort when the request is still not finished
sc_assert((request->status == SIMPLE_BUS_OK) ||(request->status == SIMPLE_BUS_ERROR));
request->do_write = true; // we are writing
request->address = address;
request->end_address = address;
request->data = data;
if (lock)
request->lock = (request->lock == SIMPLE_BUS_LOCK_SET) ?SIMPLE_BUS_LOCK_GRANTED : SIMPLE_BUS_LOCK_SET;
request->status = SIMPLE_BUS_REQUEST;
}
simple_bus_status simple_bus::get_status(unsigned int unique_priority)
{
return get_request(unique_priority)->status;
}
主接口的非阻塞请求在上升时钟边缘完成。通过请求表中注册来注册总线请求,请求的状态将被设置为SIMPLE_BUS_REQUEST。非阻塞函数返回后且当总线处理请求时,请求的状态就会改变,这将发生在时钟下降边缘。此时的主设备必须检查请求的状态,直到状态为SIMPLE_BUS_OK or SIMPLE_BUS_ERROR。
在请求发出后,只有总线才能更改请求的状态。这只会发生在时钟下降的边缘。后续通过总线传输单个数据元素的过程与阻塞接口请求函数的过程相同。
非阻塞方式的主设备从内存位置读取数据,对该数据执行算术运算并将其写回内存。这种情况在循环中发生,因此每次循环迭代时,内存位置至少会发生变化。
SC_MODULE(simple_bus_master_non_blocking)
{
// ports
sc_in_clk clock;
sc_port<simple_bus_non_blocking_if> bus_port;
SC_HAS_PROCESS(simple_bus_master_non_blocking);
// constructor
simple_bus_master_non_blocking(sc_module_name _name, unsigned int unique_priority, unsigned int start_address, bool lock, int timeout)
: sc_module(_name), m_unique_priority(unique_priority), m_start_address(start_address), m_lock(lock), m_timeout(timeout)
{
// process declaration
SC_THREAD(main_action);
sensitive << clock.pos();
}
// process
void main_action();
private:
unsigned int m_unique_priority;
unsigned int m_start_address;
bool m_lock;
int m_timeout;
}; // end class simple_bus_master_non_blocking
void simple_bus_master_non_blocking::main_action()
{
int mydata;
int cnt = 0;
unsigned int addr = m_start_address;
wait(); // ... for the next rising clock edge
while (true){
bus_port->read(m_unique_priority, &mydata, addr, m_lock);
while ((bus_port->get_status(m_unique_priority) != SIMPLE_BUS_OK) && (bus_port->get_status(m_unique_priority) != SIMPLE_BUS_ERROR))
wait();
if (bus_port->get_status(m_unique_priority) == SIMPLE_BUS_ERROR)
sb_fprintf(stdout, "%s %s : ERROR cannot read from %x\n",sc_time_stamp().to_string().c_str(), name(), addr);
mydata += cnt;
cnt++;
bus_port->write(m_unique_priority, &mydata, addr, m_lock);
while ((bus_port->get_status(m_unique_priority) != SIMPLE_BUS_OK) && (bus_port->get_status(m_unique_priority) != SIMPLE_BUS_ERROR))
wait();
if (bus_port->get_status(m_unique_priority) == SIMPLE_BUS_ERROR)
sb_fprintf(stdout, "%s %s : ERROR cannot write to %x\n",sc_time_stamp().to_string().c_str(), name(), addr);
wait(m_timeout, SC_NS);
wait(); // ... for the next rising clock edge
addr+=4; // next word (byte addressing)
if (addr > (m_start_address+0x80)) {
addr = m_start_address; cnt = 0;
}
}
}
2.3 总线的Direct Interface和直连式的主设备
直连接口功能通过总线执行数据传输,不使用总线协议。调用函数时,传输事件立即发生。返回状态为:
- true : The transfer was successful.
- false : The transfer was not successful.
可能的原因是给定的地址无法映射到从设备上,或者是无法读写的内存位置。
bool simple_bus::direct_read(int *data, unsigned int address)
{
if (address%4 != 0 ) {// address not word alligned
sb_fprintf(stdout, " BUS ERROR --> address %04X not word alligned\n",address);
return false;
}
simple_bus_slave_if *slave = get_slave(address);
if (!slave) return false;
return slave->direct_read(data, address);
}
bool simple_bus::direct_write(int *data, unsigned int address)
{
if (address%4 != 0 ) {// address not word alligned
sb_fprintf(stdout, " BUS ERROR --> address %04X not word alligned\n",address);
return false;
}
simple_bus_slave_if *slave = get_slave(address);
if (!slave) return false;
return slave->direct_write(data, address);
}
来自主设备的直接接口请求不遵循总线协议,而是即时处理的。一旦请求由主设备发出,它就直接转换为相应的直连从接口函数调用。总线执行的唯一操作是根据直接接口函数调用的地址参数选择从设备。在从设备上,也会立即处理此直接请求,而不遵守从设备的等待状态。如果给定的地址可以读写,则函数将返回,其返回值为true;否则返回值为false。总线接口函数调用的直接请求返回值与接口的从端相同,只有当地址参数不能映射到从端时,才会返回false。
SC_MODULE(simple_bus_master_direct)
{
// ports
sc_in_clk clock;
sc_port<simple_bus_direct_if> bus_port;
SC_HAS_PROCESS(simple_bus_master_direct);
// constructor
simple_bus_master_direct(sc_module_name name_, unsigned int address, int timeout, bool verbose = true)
: sc_module(name_), m_address(address), m_timeout(timeout), m_verbose(verbose)
{
// process declaration
SC_THREAD(main_action);
}
// process
void main_action();
private:
unsigned int m_address;
int m_timeout;
bool m_verbose;
}; // end class simple_bus_master_direct
void simple_bus_master_direct::main_action()
{
int mydata[4];
while (true){
bus_port->direct_read(&mydata[0], m_address);
bus_port->direct_read(&mydata[1], m_address+4);
bus_port->direct_read(&mydata[2], m_address+8);
bus_port->direct_read(&mydata[3], m_address+12);
if (m_verbose)
sb_fprintf(stdout, "%s %s : mem[%x:%x] = (%x, %x, %x, %x)\n", sc_time_stamp().to_string().c_str(), name(), m_address, m_address+15, mydata[0], mydata[1], mydata[2], mydata[3]);
wait(m_timeout, SC_NS);
3 总线的Arbiter interface
每个主设备都是独立的,所以每个主设备可以随时发出总线请求。为了可以将多个主设备连接到总线上。应对在给定的节拍内对总线发出一个或多个请求时,这些请求被收集并传递给仲裁器。从这个集合中选出最合适的请求,其他请求保持在请求状态。仲裁器通过专用接口连接到总线,并通过集合中的一个或多个请求调用。
仲裁器根据以下规则选择最合适的请求:
1.如果当前请求是锁定的突发请求,则始终选中它。
2.如果最后一个请求设置了锁标志,并且再次被“请求”,则从集合Q中选择此请求并返回,否则,
3.从集合Q中选择优先级最高(即最低编号)的请求,并返回。
仲裁器检查请求的所有优先级是否唯一。如果不是这样,仲裁器将产生错误消息并中止执行。 每当总线完全处理最后一个主请求时,以及当一组主请求发出一个或多个新请求时,就会调用仲裁器。
注意:最高优先级由unique_priority参数的最低数值指定。
class simple_bus_arbiter_if : public virtual sc_interface
{
public:
virtual simple_bus_request * arbitrate(const simple_bus_request_vec &requests) = 0;
}; // end class simple_bus_arbiter_if
class simple_bus_arbiter : public simple_bus_arbiter_if , public sc_module
{
public:
// constructor
simple_bus_arbiter(sc_module_name name_ , bool verbose = false)
: sc_module(name_)
, m_verbose(verbose)
{}
simple_bus_request *arbitrate(const simple_bus_request_vec &requests);
private:
bool m_verbose;
}; // end class simple_bus_arbiter
simple_bus_request *simple_bus_arbiter::arbitrate(const simple_bus_request_vec &requests)
{
unsigned int i;
// at least one request is here
simple_bus_request *best_request = requests[0];
if (m_verbose) { // shows the list of pending requests
sb_fprintf(stdout, "%s %s :", sc_time_stamp().to_string().c_str(), name());
for (i = 0; i < requests.size(); ++i) {
simple_bus_request *request = requests[i];
// simple_bus_lock_status encoding
const char lock_chars[] = { '-', '=', '+' };
// simple_bus_status encoding
sb_fprintf(stdout, "\n R[%d](%c%s@%x)", request->priority, lock_chars[request->lock], simple_bus_status_str[request->status], request->address);
}
}
// highest priority: status==SIMPLE_BUS_WAIT and lock is set:
// locked burst-action
for (i = 0; i < requests.size(); ++i) {
simple_bus_request *request = requests[i];
if ((request->status == SIMPLE_BUS_WAIT) && (request->lock == SIMPLE_BUS_LOCK_SET)){
// cannot break-in a locked burst
if (m_verbose)
sb_fprintf(stdout, " -> R[%d] (rule 1)\n", request->priority);
return request;
}
}
// second priority: lock is set at previous call,
// i.e. SIMPLE_BUS_LOCK_GRANTED
for (i = 0; i < requests.size(); ++i)
if (requests[i]->lock == SIMPLE_BUS_LOCK_GRANTED){
if (m_verbose)
sb_fprintf(stdout, " -> R[%d] (rule 2)\n", requests[i]->priority);
return requests[i];
}
// third priority: priority
for (i = 1; i < requests.size(); ++i) {
sc_assert(requests[i]->priority != best_request->priority);
if (requests[i]->priority < best_request->priority)
best_request = requests[i];
}
if (best_request->lock != SIMPLE_BUS_LOCK_NO)
best_request->lock = SIMPLE_BUS_LOCK_GRANTED;
if (m_verbose)
sb_fprintf(stdout, " -> R[%d] (rule 3)\n", best_request->priority);
return best_request;
}
4 总线的slave interface和从设备
总线的从接口描述总线和从设备之间的通信,可以将多个从设备连接到总线。示例中每个从设备都模拟了一些可以通过从设备接口访问的存储。
函数指向从设备数据在内存的地址,读写单个数据元素。调用会立即返回,调用者必须通过检查函数的返回值来查看传输的状态,如果返回的状态是SIMPLE_BUS_WAIT,则调用者必须再次调用函数,直到状态发生变化。
下面的从设备模拟了多个状态,接口函数的状态将是SIMPLE_BUS_WAIT;如果发生错误,状态将为简SIMPLE_BUS_ERROR,传输将中止。随后的节拍必须再次检查状态直到状态变为SIMPLE_BUS_OK,这才表示传输完成且从设备准备好了接受下一个请求。
从设备的慢内存具有可配置数量的等待状态(构造函数参数),并包含一个时钟端口。请求一旦发出后,状态设置为SIMPLE_BUS_WAIT,并设置计数器。此计数器的每个上升时钟边沿都被递减并检查,如果它变为零,则状态设置为SIMPLE_BUS_OK。此状态由总线在下一个下降时钟边缘检测。
class simple_bus_slave_if : public simple_bus_direct_if
{
public:
// Slave interface
virtual simple_bus_status read(int *data, unsigned int address) = 0;
virtual simple_bus_status write(int *data, unsigned int address) = 0;
virtual unsigned int start_address() const = 0;
virtual unsigned int end_address() const = 0;
}; // end class simple_bus_slave_if
class simple_bus_slow_mem : public simple_bus_slave_if , public sc_module
{
public:
// ports
sc_in_clk clock;
SC_HAS_PROCESS(simple_bus_slow_mem);
// constructor
simple_bus_slow_mem(sc_module_name name_
, unsigned int start_address
, unsigned int end_address
, unsigned int nr_wait_states)
: sc_module(name_)
, m_start_address(start_address)
, m_end_address(end_address)
, m_nr_wait_states(nr_wait_states)
, m_wait_count(-1)
{
// process declaration
SC_METHOD(wait_loop);
dont_initialize();
sensitive << clock.pos();
sc_assert(m_start_address <= m_end_address);
sc_assert((m_end_address-m_start_address+1)%4 == 0);
unsigned int size = (m_end_address-m_start_address+1)/4;
MEM = new int [size];
for (unsigned int i = 0; i < size; ++i)
MEM[i] = 0;
}
// destructor
~simple_bus_slow_mem();
// process
void wait_loop();
// direct Slave Interface
bool direct_read(int *data, unsigned int address);
bool direct_write(int *data, unsigned int address);
// Slave Interface
simple_bus_status read(int *data, unsigned int address);
simple_bus_status write(int *data, unsigned int address);
unsigned int start_address() const;
unsigned int end_address() const;
private:
int *MEM;
unsigned int m_start_address;
unsigned int m_end_address;
unsigned int m_nr_wait_states;
int m_wait_count;
}; // end class simple_bus_slow_mem
inline simple_bus_slow_mem::~simple_bus_slow_mem()
{
if (MEM) delete [] MEM;
MEM = (int *)0;
}
inline void simple_bus_slow_mem::wait_loop()
{
if (m_wait_count >= 0) m_wait_count--;
}
inline bool simple_bus_slow_mem::direct_read(int *data, unsigned int address)
{
*data = MEM[(address - m_start_address)/4];
return true;
}
inline bool simple_bus_slow_mem::direct_write(int *data, unsigned int address)
{
MEM[(address - m_start_address)/4] = *data;
return true;
}
inline simple_bus_status simple_bus_slow_mem::read(int *data, unsigned int address)
{
// accept a new call if m_wait_count < 0)
if (m_wait_count < 0){
m_wait_count = m_nr_wait_states;
return SIMPLE_BUS_WAIT;
}
if (m_wait_count == 0) {
*data = MEM[(address - m_start_address)/4];
return SIMPLE_BUS_OK;
}
return SIMPLE_BUS_WAIT;
}
inline simple_bus_status simple_bus_slow_mem::write(int *data, unsigned int address)
{
// accept a new call if m_wait_count < 0)
if (m_wait_count < 0){
m_wait_count = m_nr_wait_states;
return SIMPLE_BUS_WAIT;
}
if (m_wait_count == 0) {
MEM[(address - m_start_address)/4] = *data;
return SIMPLE_BUS_OK;
}
return SIMPLE_BUS_WAIT;
}
inline unsigned int simple_bus_slow_mem::start_address() const
{
return m_start_address;
}
inline unsigned int simple_bus_slow_mem::end_address() const
{
return m_end_address;
}
直连接口,此接口忽略从机的可能等待状态。直接接口请求不遵循总线协议,而是即时处理的。一旦请求由主发出,它就直接转换为相应的直接从接口函数调用。总线执行的唯一操作是根据直接接口函数调用的地址参数选择从设备。
从设备上,也会立即处理此直接请求,而不遵守从设备的等待状态。如果给定的地址可以读写,则函数将返回,其返回值为true;否则返回值为false。快速存储器从机没有等待状态和时钟端口。它立即对总线请求作出反应,并相应地设置状态。
class simple_bus_fast_mem : public simple_bus_slave_if, public sc_module
{
public:
// constructor
simple_bus_fast_mem(sc_module_name name_, unsigned int start_address, unsigned int end_address)
: sc_module(name_), m_start_address(start_address), m_end_address(end_address)
{
sc_assert(m_start_address <= m_end_address);
sc_assert((m_end_address-m_start_address+1)%4 == 0);
unsigned int size = (m_end_address-m_start_address+1)/4;
MEM = new int [size];
for (unsigned int i = 0; i < size; ++i)
MEM[i] = 0;
}
// destructor
~simple_bus_fast_mem();
// direct Slave Interface
bool direct_read(int *data, unsigned int address);
bool direct_write(int *data, unsigned int address);
// Slave Interface
simple_bus_status read(int *data, unsigned int address);
simple_bus_status write(int *data, unsigned int address);
unsigned int start_address() const;
unsigned int end_address() const;
private:
int * MEM;
unsigned int m_start_address;
unsigned int m_end_address;
}; // end class simple_bus_fast_mem
inline bool simple_bus_fast_mem::direct_read(int *data, unsigned int address)
{
return (read(data, address) == SIMPLE_BUS_OK);
}
inline bool simple_bus_fast_mem::direct_write(int *data, unsigned int address)
{
return (write(data, address) == SIMPLE_BUS_OK);
}
inline simple_bus_status simple_bus_fast_mem::read(int *data, unsigned int address)
{
*data = MEM[(address - m_start_address)/4];
return SIMPLE_BUS_OK;
}
inline simple_bus_status simple_bus_fast_mem::write(int *data , unsigned int address)
{
MEM[(address - m_start_address)/4] = *data;
return SIMPLE_BUS_OK;
}
inline simple_bus_fast_mem::~simple_bus_fast_mem()
{
if (MEM) delete [] MEM;
MEM = (int *)0;
}
inline unsigned int simple_bus_fast_mem::start_address() const
{
return m_start_address;
}
inline unsigned int simple_bus_fast_mem::end_address() const
{
return m_end_address;
}
5 示例的top层
以下是顶层互联,同时对主设备设定了优先级和存储分配了地址空间。
SC_MODULE(simple_bus_test)
{
// channels
sc_clock C1;
// module instances
simple_bus_master_blocking *master_b;
simple_bus_master_non_blocking *master_nb;
simple_bus_master_direct *master_d;
simple_bus_slow_mem *mem_slow;
simple_bus *bus;
simple_bus_fast_mem *mem_fast;
simple_bus_arbiter *arbiter;
// constructor
SC_CTOR(simple_bus_test) : C1("C1")
{
// create instances
master_b = new simple_bus_master_blocking("master_b", 4, 0x4c, false, 300);
master_nb = new simple_bus_master_non_blocking("master_nb", 3, 0x38, false, 20);
master_d = new simple_bus_master_direct("master_d", 0x78, 100);
mem_fast = new simple_bus_fast_mem("mem_fast", 0x00, 0x7f);
mem_slow = new simple_bus_slow_mem("mem_slow", 0x80, 0xff, 1);
// bus = new simple_bus("bus",true); // verbose output
bus = new simple_bus("bus");
// arbiter = new simple_bus_arbiter("arbiter",true); // verbose output
arbiter = new simple_bus_arbiter("arbiter");
// connect instances
bus->clock(C1);
master_d->clock(C1);
master_b->clock(C1);
master_nb->clock(C1);
mem_slow->clock(C1);
master_d->bus_port(*bus);
master_b->bus_port(*bus);
master_nb->bus_port(*bus);
bus->arbiter_port(*arbiter);
bus->slave_port(*mem_slow);
bus->slave_port(*mem_fast);
}
// destructor
~simple_bus_test()
{
if (master_b) {delete master_b; master_b = 0;}
if (master_nb) {delete master_nb; master_nb = 0;}
if (master_d) {delete master_d; master_d = 0;}
if (mem_slow) {delete mem_slow; mem_slow = 0;}
if (bus) {delete bus; bus = 0;}
if (mem_fast) {delete mem_fast; mem_fast = 0;}
if (arbiter) {delete arbiter; arbiter = 0;}
}
};