Verilator如何向verilog传递长位宽数据
向verilog传递数据
假设我们需要仿真的是一个加法器模块:
1
2
3
module adder ( input [ 31 : 0 ] x , input [ 31 : 0 ] y , output [ 31 : 0 ] out );
assign out = x + y ;
endmodule
要使用Verilator仿真上述加法器,并向x和y两个输入端口传入数据,可以编写如下c++ wrapper:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <verilated.h>
#include "Vadder.h"
#include <iostream>
int main ( int argc , char ** argv ) {
Verilated :: commandArgs ( argc , argv );
Vadder * tb = new Vadder ;
uint32_t x = 190 , y = 11 ;
tb -> x = 190 ; tb -> y = 11 ;
tb -> eval ();
std :: cout << ( tb -> out ) << std :: endl ;
tb -> final ();
delete tb ;
return 0 ;
}
编译运行后也可以正确输出执行的结果“201”。
长位宽数据输入
当位宽达到一百位,几百位时,int/long long长度已无法满足要求。例如,将上述加法器的位宽增加到128位:
1
2
3
module adder ( input [ 127 : 0 ] x , input [ 127 : 0 ] y , output [ 127 : 0 ] out );
assign out = x + y ;
endmodule
如果不改动c++ wrapper,则在编译阶段会报如下错误:
1
2
adder_tb.cpp: In function ‘int main( int, char**) ’:
adder_tb.cpp:22:17: 错误:no match for ‘operator= ’ ( operand types are ‘VlWide<4>’ and ‘int’)
这是由于在位宽超出单个整型数据所能承受的范围时,verilator提供了VlWide结构体类型用于数据的输入和输出。
Verilator提供的VlWide类型
利用IDE的代码提示跳转功能,在verilated_types.h中可以找到如下定义:
1
2
3
4
5
6
template < std :: size_t T_Words >
struct VlWide final {
// MEMBERS
// This should be the only data member, otherwise generated static initializers need updating
EData m_storage [ T_Words ]; // Contents of the packed array
...
根据上述代码,VlWide类型通过泛型参数传入数据长度,并通过一个名为m_storage的成员数组存储实际数据。其中EData为32位无符号整型,即泛型参数T_Words表示该数据存储多少个32位。
例如VlWide<4> data就是声明了一个长度为4 Words,即4×32位=128位的数据变量,通过内部的长度为4的32位整型数组存储。
向VlWide对象写入数据
为向该模块传入数据,可以直接向m_storage数组写入对应的数据。在c++ wrapper可以编写如下代码:
1
2
3
4
5
// 127 0
uint32_t a [ 4 ] = { 0x12345678 , 0x9abcdeff , 0x11111111 , 0xffffffff };
VlWide < 4UL > data ;
std :: copy ( std :: rbegin ( a ), std :: rend ( a ), std :: begin ( data . m_storage ));
tb -> x = data ;
这样就完成了向输入端口x传入0x123456789abcdeff11111111ffffffff这一128位数据的操作。
注意: 在第4行的copy语句中,使用的是反向迭代器std::rbegin(a)和std::rend(a)。这是由于在verilog中,一般为左侧为高位,verilator在传递数据时在数组下标上也按照“高位对高位”的关系对应。而在C++代码中,右侧为数组的下标高位元素,对于单个4字节整型变量,在使用16进制表示时右侧又是下标低位。这就造成直接书写数组常量时左右顺序与数据字面顺序不符。
因此为了方便在C++数组中填充数据时直接按照左侧高位 ,这里对数组元素顺序进行了一次逆序。
上述过程可以用如下过程和图示描述。
作为原始数据的数组a在书写时左侧为下标低位,右侧为下标高位; 手动复制到data中时,采用反向迭代器反转下标,但每个元素内部位顺序不受影响; Verilator在传入数据时,按照数组高位在MSB的关系重新排列。这一步发生在Verilator代码内部,但这一步带来的反转被我们此前的手动反转抵消了,也就保证了书写顺序就是数据最终传入的顺序; 数据最终传递到电路模块,其顺序与原始数组a中的书写顺序一致。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──────────┬──────────┬──────────┬──────────┐
│ a[0] │ a[1] │ a[2] │ a[3] │
┌───┼──────────┼──────────┼──────────┼──────────┤
│ a │0x12345678│0x9abcdeff│0x11111111│0xffffffff│
└───┴──────────┴──────────┴──────────┴──────────┘
▼Array Reversal
┌──────────┬──────────┬──────────┬──────────┐
│ [0] │ [1] │ [2] │ [3] │
┌────┼──────────┼──────────┼──────────┼──────────┤
│data│0xffffffff│0x11111111│0x9abcdeff│0x12345678│
└────┴──────────┴──────────┴──────────┴──────────┘
▼High-Index -> MSB (internal)
┌──────────┬──────────┬──────────┬──────────┐
│ [3] │ [2] │ [1] │ [0] │
┌───┼──────────┼──────────┼──────────┼──────────┤
│ = │0x12345678│0x9abcdeff│0x11111111│0xffffffff│
└───┴──────────┴──────────┴──────────┴──────────┘
▼Input Assignment (internal)
┌───────────────────────────────────────────┐
│127 96 95 64 63 32 31 0│
┌───┼───────────────────────────────────────────┤
│ x │0x12345678 0x9abcdeff 0x11111111 0xffffffff│
└───┴───────────────────────────────────────────┘
长位宽数据输出
加法器计算还需一个操作数y,为检查正确性,我们用同样的方式将零值传入y端口:
1
2
3
4
uint32_t b [ 4 ] = { 0 };
VlWide < 4UL > zero ;
std :: copy ( std :: rbegin ( b ), std :: rend ( b ), std :: begin ( zero . m_storage ));
tb -> y = zero ;
在执行tb->eval()后,输出端口的加法结果tb->out也是VlWide类型的对象。为检查电路仿真运行正确性,除了生成波形文件外,更加方便的方法是直接输出结果。
std::cout的陷阱
在输出位宽较长时,仍然直接使用std::cout<<(tb->out)是不可行的 。
因为VlWide结构体没有重载ostream& operator<<运算符,上述写法的默认fallback是basic_ostream& operator<<( const void* value )(见cppreference关于operator«的文档 ),该重载的行为是输出形如0x?????????????的tb->out的地址值 。这样的结果形式很容易与输出数据格式相混淆,从而错误地将地址值认为是输出的数据值。
VL_TO_STRING
实际上,Verilator提供了函数**VL_TO_STRING**用于将VlWide转换为std::string类型。如果只是想查看输出的值,可以先转为字符串再使用cout输出:
1
2
std :: cout << VL_TO_STRING ( tb -> out ) << std :: endl ;
//'h123456789abcdeff11111111ffffffff
VL_TO_STRING输出为十六进制表示,并添加'h前缀,与verilog语法相同。
自定义输出格式/其他数据操作
既然tb-out也是VlWide类型对象,那么当然也可以直接对其进行读取、操作,实现更多输出形式或是与现有测试结果进行验证。
例如,可以实现一个自己的十六进制输出(注意数组迭代同样需要从高位开始,VlWide重载了operator [],可以直接获取m_storage中的整型元素:):
1
2
3
4
5
6
7
8
9
10
11
12
template < std :: size_t T_Words >
void print_my_hex ( VlWide < T_Words > data )
{
for ( int i = T_Words - 1 ; i >= 0 ; -- i )
{
uint32_t word_pack = data [ i ];
std :: cout << std :: hex << word_pack ;
}
}
// in main():
print_my_hex ( tb -> out );
//123456789abcdeff11111111ffffffff
非整字长数据
上面使用的例子是128位,正好是32(一个字长, 1 WORD)的倍数,因此可以直接使用若干个32位的整数组成数组表示数据。当位宽不是32的整数倍时,电路的端口就无法与C++中的数组对齐了。例如,将前面的加法器位宽修改为100位:
1
2
3
module adder ( input [ 99 : 0 ] x , input [ 99 : 0 ] y , output [ 99 : 0 ] out );
assign out = x + y ;
endmodule
在这种情况下,只需要取大于位宽的最小32倍数,然后在数组[0]元素中只写出低位的部分,或者在前方补零即可。例如:
1
2
3
4
5
// 127 96 0
uint32_t a [ 4 ] = { 0x00000008 , 0x9abcdeff , 0x11111111 , 0xffffffff };
VlWide < 4UL > data ;
std :: copy ( std :: rbegin ( a ), std :: rend ( a ), std :: begin ( data . m_storage ));
tb -> x = data ;
这样就向电路传入了x的值为0x89abcdeff11111111ffffffff。
附录:完整c++ wrapper 代码
(100位宽加法器)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <verilated.h>
#include "Vadder.h"
#include <iostream>
template < std :: size_t T_Words >
void print_my_hex ( VlWide < T_Words > data )
{
for ( int i = T_Words - 1 ; i >= 0 ; -- i ){
std :: cout << std :: hex << data [ i ];
}
}
int main ( int argc , char ** argv ) {
Verilated :: commandArgs ( argc , argv );
Vadder * tb = new Vadder ;
//仿真输入,只需100位
//100'h89abcdeff11111111ffffffff
uint32_t a [ 4 ] = { 0x00000008 , 0x9abcdeff , 0x11111111 , 0xffffffff };
VlWide < 4UL > data_x ;
std :: copy ( std :: rbegin ( a ), std :: rend ( a ), std :: begin ( data_x . m_storage ));
tb -> x = data_x ;
//100'h2eadadada446653ec239919ad
uint32_t b [ 4 ] = { 0x00000002 , 0xeadadada , 0x446653ec , 0x239919ad };
VlWide < 4UL > data_y ;
std :: copy ( std :: rbegin ( b ), std :: rend ( b ), std :: begin ( data_y . m_storage ));
tb -> y = data_y ;
tb -> eval ();
// std::cout<<VL_TO_STRING(tb->out)<<std::endl;
print_my_hex ( tb -> out );
//100'hb8597b9d9557764fe239919ac
tb -> final ();
delete tb ;
return 0 ;
}