本实验实现一个参数化的FIFO(First In, First Out)先进先出缓冲器,深入探索SystemC中的SC_THREAD进程模型和时序控制。
- 理解并实现使用SC_THREAD进程的时序设计
- 掌握SystemC中的wait()语句用法
- 学习如何进行对照模型验证
- 实现并验证满/空条件的正确处理
FIFO是一种重要的硬件缓冲结构,用于在不同速率的模块间传输数据。其特点是:
- 先进先出:最先写入的数据最先被读出
- 满/空状态:表示缓冲区的极限状态
- 异步接口:支持同时读写操作
FIFO支持两种基本操作:
- 写入(Write):当FIFO不满时,数据被添加到队尾
- 读取(Read):当FIFO不空时,从队首取出数据
- 满(Full):表示FIFO已达到最大深度,无法再写入
- 空(Empty):表示FIFO中没有数据,无法读取
- 大小(Size):当前存储的数据量
在本实验中,我们使用SC_THREAD而非前几个实验中的SC_METHOD,这两种SystemC进程模型有显著区别:
| 特性 | SC_METHOD | SC_THREAD |
|---|---|---|
| 执行模式 | 每次触发完整执行 | 可以暂停和恢复 |
| 时间控制 | 不能等待时间 | 可以使用wait()等待 |
| 适用场景 | 组合逻辑、简单时序 | 复杂时序、协议建模 |
| 状态保存 | 需要显式变量 | 自动保存执行状态 |
| 代码风格 | 离散事件处理 | 类似连续流程描述 |
使用SC_THREAD进程模型和wait()语句实现时序控制:
SC_THREAD(write_process);
sensitive << clk.pos(); // 初始敏感信号
void write_process() {
// 初始化代码
while (true) {
wait(clk.posedge_event()); // 等待时钟上升沿
// 本周期的逻辑...
}
}这种模式下,进程第一次执行到wait()时会暂停,直到指定事件(时钟上升沿)发生后再继续执行wait()后面的代码。
复位逻辑放置在循环开始处,确保系统可以在任何时刻复位:
while (true) {
wait(clk.posedge_event());
if (!rst_n.read()) { // 低电平有效复位
buffer.clear();
empty.write(true);
full.write(false);
size.write(0);
continue; // 跳过本周期其他操作
}
// 正常操作...
}FIFO实现中的一个关键点是保证状态信号(empty/full/size)与实际缓冲区状态一致:
// 先处理读取和写入操作
bool did_read = false;
bool did_write = false;
if (read_en.read() && !empty.read()) {
// 读取操作...
did_read = true;
}
if (write_en.read() && !full.read()) {
// 写入操作...
did_write = true;
}
// 在所有操作完成后统一更新状态
if (did_read || did_write) {
empty.write(buffer.empty());
full.write(buffer.size() >= DEPTH);
size.write(buffer.size());
}这种模式确保了:
- 状态更新只在有实际读/写操作时发生
- 所有状态更新都在操作后同时进行,保持一致性
- 在同一个周期中进行的操作会被正确处理
本实验中使用C++标准库中的std::deque实现FIFO缓冲区,展示了SystemC与C++无缝集成的优势:
std::deque<T> buffer; // 使用模板参数T测试平台采用对照测试方法,同时使用一个参考模型(reference_fifo)执行相同操作,然后比较结果:
// 参考模型
std::queue<int> reference_fifo;
// 如果写入,同时更新被测FIFO和参考模型
if (do_write) {
int data = data_dist(rng);
data_in.write(data);
reference_fifo.push(data);
}
// 如果读取,比较被测FIFO和参考模型的输出
if (do_read) {
int expected = reference_fifo.front();
reference_fifo.pop();
int actual = data_out.read();
// 验证读取的数据
if (actual != expected) {
// 报错...
}
}在测试时,需要确保给FIFO足够的时间来更新其内部状态和输出:
// 设置输入信号
write_en.write(do_write);
read_en.write(do_read);
// 等待时钟上升沿,让FIFO处理输入
wait(clk.posedge_event());
// 等待一小段时间,让输出信号稳定
wait(1, SC_NS);
// 现在可以安全地验证输出和状态信号
// ...验证代码...这种时序安排模拟了实际硬件中的信号传播延迟,确保了测试的准确性。
测试平台采用以下策略验证FIFO功能:
- 随机测试:随机生成读写操作和数据值
- 边界测试:专门测试FIFO满/空的极限情况
- 状态验证:验证full/empty/size信号的正确性
- 数据正确性:验证读出的数据与参考模型一致
-
状态不一致:最常见的FIFO实现错误是状态信号(empty/full/size)与实际缓冲区状态不一致,应在所有操作后一次性更新所有状态。
-
边界条件处理:FIFO为空时读取或满时写入需要特别注意,确保这些情况被正确处理。
-
时序相关问题:在波形查看器中检查信号变化的时序关系,确保状态更新发生在正确的时钟沿后。
-
参考模型同步:确保参考模型的操作与FIFO模型严格同步,尤其是在边界条件下。
通过本实验,您应该能够理解:
- SC_THREAD的优势:如何使用SC_THREAD实现复杂的时序行为
- wait()机制:如何使用wait()创建时序依赖
- 进程分离:如何将读写操作分离到不同进程
- 状态管理:如何管理FIFO的满/空状态和计数
- 参考模型验证:如何使用对照测试方法验证设计
- 修改FIFO实现,使用数组而非std::deque
- 添加几乎满(almost_full)和几乎空(almost_empty)阈值信号
- 实现异步FIFO,读写端使用不同时钟域
- 增加数据有效性验证功能,如奇偶校验
本实验通过FIFO设计展示了SystemC中SC_THREAD进程的强大功能,特别适合于实现复杂的时序行为和状态机。与前面实验中的SC_METHOD相比,SC_THREAD提供了更自然的编程模型,特别适合于测试平台和复杂协议的建模。