Verilator如何向verilog传递长位宽数据

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++数组中填充数据时直接按照左侧高位,这里对数组元素顺序进行了一次逆序。

上述过程可以用如下过程和图示描述。

  1. 作为原始数据的数组a在书写时左侧为下标低位,右侧为下标高位;
  2. 手动复制到data中时,采用反向迭代器反转下标,但每个元素内部位顺序不受影响;
  3. Verilator在传入数据时,按照数组高位在MSB的关系重新排列。这一步发生在Verilator代码内部,但这一步带来的反转被我们此前的手动反转抵消了,也就保证了书写顺序就是数据最终传入的顺序;
  4. 数据最终传递到电路模块,其顺序与原始数组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;
}
0%