PyHCL/pyhcl/simulator/simlite_verilog.py

444 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Copyright (c) 2019 scutdig
# Licensed under the MIT license.
import os
import subprocess
import re
class DpiConfig(object):
def __init__(self, pysv_li, pkg_sv_path=".sv/pkg/pysv_pkg.sv", lib_path=".build/libpysv.so"):
self.sv = pkg_sv_path
self.lib = lib_path
self.bname = " ".join(pysv_li)
...
class Simlite(object):
# init for fork method
# 根据传入的Simlite对象实例深度复制得到新的Simlite对象实例
def __fork_init(self, other):
import copy
self.top_module_name = other.top_module_name
self.dut_path = other.dut_path
self.dpiconfig = other.dpiconfig
if hasattr(other, "efn"):
self.efn = other.efn
self.inputs = copy.deepcopy(other.inputs)
self.outputs = copy.deepcopy(other.outputs)
self.results = copy.deepcopy(other.outputs)
self.cnt = copy.deepcopy(other.cnt)
self.dut_name = copy.deepcopy(other.dut_name)
# recover the status base on self.steps
self.steps = []
self.debug = False
# 开始仿真
self.start()
for inputs in other.steps:
self.step(inputs)
assert (self.steps == other.steps)
self.debug = other.debug
self.name = other.name + "_" + str(other.fork_cnt)
other.fork_cnt += 1
# dpiconfig对象
# self.sv = pkg_sv_path # .sv/pkg/pysv_pkg.sv
# self.lib = lib_path # .build/libpysv.so
# self.bname = " ".join(pysv_li) # pysv_li存放调用了python函数的SV列表 (Add.sv)
def __init__(self, top_module_name='', dut_path='', harness_code=None, dpiconfig: DpiConfig = None, debug=False, name="sim0", module=None):
self.raw_in = None
self.efn = None
self.ofn = None
self.ifn = None
self.dut_path = dut_path
self.top_module_name = top_module_name
# module为Simlite对象实例
if isinstance(module, Simlite):
self.__fork_init(module)
else:
self.dpiconfig = dpiconfig
# 输入端口名列表
self.inputs = []
self.inputs_values = []
# 输出端口名列表
self.outputs = []
# 结果列表
self.results = []
# 计数器
self.cnt = 0
# 步骤列表
self.steps = []
# 仿真名
self.name = name
self.debug = debug
self.fork_cnt = 0
self.inputs, self.outputs = self.verilog_parse(dut_path, top_module_name)
# dut_name: Top top_module_name: Top.v
self.dut_name = top_module_name.split('.')[0] # 模块名
# 通过harness代码开始仿真
if harness_code:
self.compile(harness_code)
else:
# 传入module_name和ports生成harness代码然后仿真
self.compile(self.codegen(self.dut_name))
# 解析verilog代码, 返回输入端口名列表 和 输出端口名列表
def verilog_parse(self, dut_path, top_module_name):
dut_name = top_module_name.split('.')[0] # 模块名
top_module_path = dut_path + top_module_name
# print(top_module_path)
module_begin_match = r"module\s*([a-zA-Z0-9_]+)"
# 匹配输入端口 input clock, input [31:0] io_a
input_port_match = r"input\s*(reg|wire)*\s*(\[[0-9]+\:[0-9]+\]*)*\s*([a-zA-Z0-9_]+)"
# 匹配输出端口 output [31:0] io_c
output_port_match = r"output\s*(reg|wire)*\s*(\[[0-9]+\:[0-9]+\]*)*\s*([a-zA-Z0-9_]+)"
current_module_name = ''
input_ports_name = []
output_ports_name = []
with open(top_module_path, "r") as verilog_file:
while verilog_file:
verilog_line = verilog_file.readline().strip(' ') # 读取一行
# print(verilog_line)
if verilog_line == "": # 注:如果是空行,为'\n'
break
module_begin = re.search(module_begin_match, verilog_line)
if module_begin:
current_module_name = module_begin.group(1)
# print(current_module_name)
if current_module_name == dut_name:
input_port = re.search(input_port_match, verilog_line)
output_port = re.search(output_port_match, verilog_line)
if input_port:
# 输入端口名列表
input_ports_name.append(input_port.group(3))
if output_port:
# 输出端口名列表
output_ports_name.append(output_port.group(3))
# print(dut_name)
# print(input_ports_name)
# print(output_ports_name)
return input_ports_name, output_ports_name
# --timescale <timescale> Sets default timescale
# --timescale <timeunit>/<timeprecision>
def setTimeScale(self, timescale):
pass
def stop(self):
instr = '-1'.encode(encoding="utf-8") + b'\n'
self.p.stdin.write(instr)
self.p.stdin.flush()
def close(self):
# 2表示标准错误stderr, >表示重定向 /dev/mull表示空设备
# 2>/dev/nul,将标准错误重定向到空设备里,即不输出错误信息
# 追加 2>/dev/null 在命令末尾,表示:把错误输出到 “黑洞”
os.system("cd .. && rm -r .sv .fir .build 2>/dev/null")
# self.p.kill()
# pass
# 传入harness代码调用firrtl命令得到verilog代码调用verilater最终得到V{dut_name}
def compile(self, harness_code):
print("\n\n---------------------verilator build info--------------------------\n")
dpiconfig = self.dpiconfig
# 在当前目录创建simulation文件夹
try:
os.mkdir("simulation")
except FileExistsError:
pass
# 把所有dut文件复制到simulation文件夹下
os.system("cp {}* ./simulation/".format(self.dut_path))
# 在simulation文件夹下创建dut_name-harness.cpp写入harness代码
with open(f"./simulation/{self.dut_name}-harness.cpp", "w+") as f:
f.write(harness_code)
# vfn = "{}.v".format(self.dut_name) # {dut_name}.v
vfn = self.top_module_name
hfn = "{}-harness.cpp".format(self.dut_name) # {dut_name}-harness.cpp
mfn = "V{}.mk".format(self.dut_name) # V{dut_name}.mk
efn = "V{}".format(self.dut_name) # V{dut_name}
# dpi
if self.dpiconfig:
# dpiconfig对象
# self.sv = pkg_sv_path # .sv/pkg/pysv_pkg.sv
# self.lib = lib_path # .build/libpysv.so
# self.bname = " ".join(pysv_li) # pysv_li存放调用了python函数的SV列表 (Add.sv)
pysv_pkg = "{}_pysv_pkg.sv".format(self.dut_name) # {dut_name}_pysv_pkg.sv
pysv_lib = "libpysv_{}.so".format(self.dut_name) # libpysv_{dut_name}.so
# cp .sv/pkg/pysv_pkg.sv ./simulation/{dut_name}_pysv_pkg.sv # 由各python函数生成得到的SV binding文件
os.system("cp {} ./simulation/{}_pysv_pkg.sv".format(dpiconfig.sv, self.dut_name))
# cp .build/libpysv.so ./simulation/libpysv_{dut_name}.so # 由各python函数编译得到的共享库
os.system("cp {} ./simulation/libpysv_{}.so".format(dpiconfig.lib, self.dut_name))
# 转换目录到./simulation文件夹下
os.chdir("./simulation")
# Using verilator backend
# --cc Create C++ output
# --trace Enable waveform creation
# --exe Link to create executable
# --prefix <topname> Name of top level class
# --top-module <topname> Name of top level input module
# .so为 与 Verilog 代码链接的可选对象或库文件
# In the verilator command, include the shared library and the generated binding file
# verilator --cc --trace --exe --prefix VTop --top-module Top Top_pysv_pkg.sv {bbx} libpysv_Top.so Top-harness.cpp
# verilator --cc --trace --exe --prefix VTop --top-module Top Top_pysv_pkg.sv Add.sv Top.v libpysv_Top.so Top-harness.cpp
print(
"verilator --cc --trace --exe --prefix {prefix} --top-module {top} {pkg} {bbx} {vfn} {lib} {hfn}" \
.format(top=self.dut_name, bbx=dpiconfig.bname, vfn=vfn, hfn=hfn, pkg=pysv_pkg, lib=pysv_lib,
prefix=efn))
os.system(
"verilator --cc --trace --exe --prefix {prefix} --top-module {top} {pkg} {bbx} {vfn} {lib} {hfn}" \
.format(top=self.dut_name, bbx=dpiconfig.bname, vfn=vfn, hfn=hfn, pkg=pysv_pkg, lib=pysv_lib,
prefix=efn))
# cp libpysv_Top.so ./obj_dir/
os.system("cp {} ./obj_dir/".format(pysv_lib))
else:
# 改变当前工作目录到指定的路径--simulation
os.chdir("./simulation")
# Using verilator backend
# 传入verilog代码和harness代码使用verilator进行仿真
# --cc Create C++ output
# --trace Enable waveform creation
# --exe Link to create executable
# verilator --cc {dut_name}.v --trace --exe {dut_name}-harness.cpp
print("verilator --cc {vfn} --trace --exe {hfn}".format(vfn=vfn, hfn=hfn))
os.system(
"verilator --cc {vfn} --trace --exe {hfn}".format(vfn=vfn, hfn=hfn))
# make -j -C ./obj_dir -f V{dut_name}.mk V{dut_name}
os.system(
"make -j -C ./obj_dir -f {mfn} {efn}".format(mfn=mfn, efn=efn))
# Run simulation backend program
"""
if dpiconfig:
os.system("LD_LIBRARY_PATH=. ./obj_dir/{}&".format(efn))
else:
os.system("./obj_dir/{}&".format(efn))
"""
self.efn = efn # V{dut_name}
# 开启仿真默认模式为ia
def start(self, mode="ia", ofn=None, ifn=None):
env = None
if self.dpiconfig:
# 确保 libpysv.so共享库 在 LD_LIBRARY_PATH 中-- pysv要求的
env = {"LD_LIBRARY_PATH": "."} # 环境变量
if mode == "ia":
args = [f"./obj_dir/{self.efn}"] # ./obj_dir/V{dut_name}
# 创建子进程执行./obj_dir/{dut_name}
self.p = subprocess.Popen(args, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
self.dropinfo()
elif mode == "task":
args = [f"./obj_dir/{self.efn}"] # ./obj_dir/V{dut_name}
infile = open(ifn, "r")
outfile = open(ofn, "w")
# subprocess 模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值。
# Popen 是 subprocess的核心子进程的创建和管理都靠它处理
# argsshell命令可以是字符串或者序列类型list元组
# env用于指定子进程的环境变量。如果 env = None子进程的环境变量将从父进程中继承
# shell如果该参数为 True将通过操作系统的 shell 执行指定的命令
# stdin、stdout 和 stderr子进程的标准输入、输出和错误句柄
# 创建子进程执行./obj_dir/VAdder
# self.p = subprocess.Popen(args, env=env, shell=True, stdin=subprocess.PIPE, stdout=outfile)
self.p = subprocess.Popen(args, env=env, stdin=infile, stdout=outfile)
# 读掉stdout缓冲区里的5行info
def dropinfo(self):
for i in range(5):
self.p.stdout.readline()
if self.debug:
print("\n\n--------------------------sim result---------------------------")
# 传入整个任务tsk开始仿真task模式 name为模块名tsk为二维列表包含每一次的输入端口列表值
def start_task(self, name, tsk):
try:
os.mkdir("tmp")
except FileExistsError:
pass
# input file
ifn = f"./tmp/{name}_inputs"
ofn = f"./tmp/{name}_outputs"
fd = open(ifn, "a")
instr = ""
for inputs in tsk:
# 一次输入端口值列表
inputs = [str(x) for x in inputs]
# 输入端口值--str
self.raw_in = " ".join(inputs)
self.raw_in = "0 " + self.raw_in # 0表示状态值状态值小于0会退出
instr += self.raw_in + "\n"
# 所有输入端口值,每一次以\n分隔
instr += "-1\n"
# 将tsk整个任务包含每一次的输入端口列表值传入ifn输入文件
fd.write(instr)
fd.close()
# 传入输入文件和输出文件,开始仿真
self.start("task", ofn, ifn)
# 传入输入端口值列表
def step(self, inputs):
# 每一步存入steps列表
self.steps.append(inputs)
self.inputs_values = inputs
# 输入端口值列表
inputs = [str(x) for x in inputs]
# 输入端口值--str
self.raw_in = " ".join(inputs)
self.raw_in = "0 " + self.raw_in
instr = self.raw_in.encode(encoding="utf-8") + b'\n'
# 将输入端口值--str写入子进程的标准输入stdin缓冲区--传入inputs数组
self.p.stdin.write(instr)
# 刷新stdin缓冲区, 即将缓冲区中的数据立刻写入
self.p.stdin.flush()
# 读取子进程的标准输出stdout里的内容--即outputs数组的值
line = self.p.stdout.readline()
# 刷新stdout缓冲区
self.p.stdout.flush()
# 从outputs数组中读取的结果--输出端口值
self.raw_res = str(line, encoding="utf-8").strip()
strs = self.raw_res.split(" ")
# 结果列表
res = [int(x) for x in strs]
self.results = res
# debug模式
if self.debug:
self.pprint()
# 计数器值加一
self.cnt += 1
return res
# 输出仿真相关值--输入端口值、输出端口值
def pprint(self):
print("")
# 仿真名 计数器值 IN 输入端口值
# 仿真名 计数器值 OUT 输出端口值
print(f"[{self.name}\t\t{self.cnt}]IN : {self.raw_in}")
print(f"[{self.name}\t\t{self.cnt}]OUT : {self.raw_res}")
def getRes(self):
return self.results
# 传入module_name和ports生成harness代码
def codegen(self, name):
tempfile = """#include "V{modname}.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include <vector>
#include <iostream>
#include <cstdio>
vluint64_t main_time = 0; // See comments in first example
const vluint64_t sim_time = 1024;
double sc_time_stamp() {{ return main_time; }}
std::vector<unsigned long long> inputs, outputs;
#define INN {innum}
#define OUTN {outnum}
int status = 0;
void ioinit(){{
setvbuf(stdout,0,_IONBF, 0);
setvbuf(stdin,0,_IONBF, 0);
setvbuf(stderr,0,_IONBF, 0);
inputs.resize(INN);
outputs.resize(OUTN);
return;
}}
void input_handler(){{
std::cin>>status;
if(status==-1)
return;
for(int i = 0; i < INN; i++){{
std::cin>>inputs[i];
}}
return;
}}
void output_handler(){{
for(int i = 0; i < OUTN; i++){{
std::cout<<outputs[i]<<" ";
}}
std::cout<<std::endl;
return;
}}
int main(int argc, char** argv, char** env) {{
Verilated::commandArgs(argc, argv);
ioinit();
V{modname}* top = new V{modname};
Verilated::internalsDump(); // See scopes to help debug
Verilated::traceEverOn(true);
VerilatedVcdC* tfp = new VerilatedVcdC;
top->trace(tfp, 99);
tfp->open("wave.vcd");
while (!Verilated::gotFinish()) {{
input_handler();
if(status==-1) break;
//get inputs
{inputs_init}
top->eval();
tfp->dump(main_time);
{outputs_log}
//get output
output_handler();
main_time++;
}}
top->final();
tfp->close();
delete top;
return 0;
}}""".format(modname=name, innum=len(self.inputs), outnum=len(self.outputs), inputs_init=self.handle_inputs(),
outputs_log=self.handle_outputs())
return tempfile
# 处理input端口得到对应的harness代码 # 用inputs数组对输入端口进行赋值
def handle_inputs(self):
res = ""
taps = " "
i = 0
for n in self.inputs:
res += taps + f"top->{n} = inputs[{i}];\n" # 对输入端口进行赋值
i += 1
return res
# 处理output端口得到对应的harness代码 # 对输出端口进行取值放入outputs数组
def handle_outputs(self):
res = ""
taps = " "
i = 0
for n in self.outputs:
res += taps + f"outputs[{i}] = top->{n};\n" # 对输出端口进行取值
i += 1
return res