作者:Jackie Gao,AMD工程师;来源:AMD开发者社区
前言
当FPGA开发者需要做RTL和C/C++联合仿真的时候,一些常用的方法包括使用MicroBlaze软核,或者使用QEMU仿真ZYNQ的PS部分。
此教程介绍一种通过SystemC做RTL/C/C++联合仿真的方法,所有的BFMs(Bus Function Module)都是通过SystemC完成。
文中所涉及的所有代码均在Vivado和Modelsim/Questasim上做了验证。
简介
SystemC基础
SystemC是标准C/C++语言的延伸,即可以描述硬件,也具有C/C++的特性。这个特点让SystemC特别适合做系统级别的设计、建模以及验证。
SystemC是周期精确的,其主要的组包括
在SystemC中实例化一个RTL的实体
在SystemC中实例化一个RTL的实体很直观,你只需要为其RTL实体手写一个对应的SystemC的外部模块,之后这个模块就可以在SystemC的环境被被其它类调用。
下面将举例说明如果在SystemC中构建一个VHDL的实体
示例 VHDL的设计
entity counter is
port(
clk : in std_logic;
reset: in std_logic;
count: std_logic_vector(7 downto 0)
);
end;
architecture rtl of counter is
…
end rtl;
对应的SystemC模块
class counter : public sc_foreign_module {
public:
sc_in
sc_in
sc_out
counter(sc_module_name nm) : sc_foreign_module(nm, "work.counter"),
clk("clk"),
reset("reset"),
count("count") {
}
};
需要注意的是,你还可以通过ModelSim/QuestaSim提供的scgenmod工具自动生成VHDL、Verilog对应的SystemC的模块。
用SystemC设计BFMs
因为SytemC是通过时钟驱动的, 在SystemC里面写BFMs就变得很直接
在SystemC中实现AXI4-Lite的读操作可以如下,
u32 Xil_In32(UINTPTR Addr) {
u32 data;
if (aresetn_i.read() == SC_LOGIC_0) {
awaddr.write(0);
awvalid.write(SC_LOGIC_0);
wdata.write(0);
wvalid.write(SC_LOGIC_0);
bready.write(SC_LOGIC_1);
araddr.write(0);
arvalid.write(SC_LOGIC_0);
rready.write(SC_LOGIC_1);
}
else {
sc_core::wait(aclk_i.posedge_event());
araddr.write(addr);
arvalid.write(SC_LOGIC_1);
uint32_t num_clocks = 0;
while (arready.read() != SC_LOGIC_1) {
sc_core::wait(aclk_i.posedge_event());
num_clocks++;
if (num_clocks == m_timeout_clks) {
arvalid.write(SC_LOGIC_0);
return -1;
}
}
arvalid.write(SC_LOGIC_0);
num_clocks = 0;
while (rvalid.read() != SC_LOGIC_1) {
sc_core::wait(aclk_i.posedge_event());
num_clocks++;
if (num_clocks == m_timeout_clks) {
return -1;
}
}
data = rdata.read().to_uint();
}
return data;
}
在SystemC中实现AXI4-Lite的写操作可以如下
void Xil_Out32(UINTPTR Addr, u32 Value) {
if (aresetn_i.read() == SC_LOGIC_0) {
awaddr.write(0);
awvalid.write(SC_LOGIC_0);
wdata.write(0);
wvalid.write(SC_LOGIC_0);
bready.write(SC_LOGIC_1);
araddr.write(0);
arvalid.write(SC_LOGIC_0);
rready.write(SC_LOGIC_1);
}
else {
sc_core::wait(aclk_i.posedge_event());
awaddr.write(Addr);
awvalid.write(SC_LOGIC_1);
wdata.write(Value);
wvalid.write(SC_LOGIC_1);
uint32_t num_clocks = 0;
bool m_awready = false;
bool m_wready = false;
while ((!m_awready) || (!m_wready)) {
sc_core::wait(aclk_i.posedge_event());
if (awready.read() == SC_LOGIC_1) {
m_awready.write(true);
awvalid.write(SC_LOGIC_0);
}
if (wready.read() == SC_LOGIC_1) {
m_wready.write(true);
wvalid.write(SC_LOGIC_0);
}
num_clocks++;
if (num_clocks == m_timeout_clks) {
awvalid.write(SC_LOGIC_0);
wvalid.write(SC_LOGIC_0);
return;
}
}
num_clocks = 0;
while (bvalid.read() != SC_LOGIC_1) {
sc_core::wait(aclk_i.posedge_event());
num_clocks++;
if (num_clocks == m_timeout_clks) {
return;
}
}
}
}
系统设计框图
完整的系统框图如下所示。清楚的展示是SystemC /DUT之间的调用关系。
软件需求
在教程中,需要以下设计工具
示例工程
测试结果
以下是Questasim的仿真结果,通过打印的信息可以看到 RTL和C/C++的联合仿真功能的运行。
# vsim -t 1ps xil_defaultlib.Env xil_defaultlib.glbl -onfinish stop -L xpm -L unisims_ver -L secureip -L unimacro_ver -L xil_defaultlib -l simlog.txt -assertdebug
# ******************************
# * User Peripheral Self Test
# ******************************
# User logic slave module test...
#
# - slave register write/read passed
#
# ** Note: (vsim-6574) SystemC simulation stopped by user.