[VERILOG] Basic Verilog Testflow (#70)
* [VERILOG] Basic Verilog Testflow * fix build * fix the comment * fix lint in verilog
This commit is contained in:
Родитель
2548cedcb8
Коммит
9e660dbe74
|
@ -37,12 +37,14 @@ addons:
|
||||||
- python3-dev
|
- python3-dev
|
||||||
- python3-nose
|
- python3-nose
|
||||||
- graphviz
|
- graphviz
|
||||||
|
- bison
|
||||||
|
- flex
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- source dmlc-core/scripts/travis/travis_setup_env.sh
|
|
||||||
- export PYTHONPATH=${PYTHONPATH}:${PWD}/python
|
- export PYTHONPATH=${PYTHONPATH}:${PWD}/python
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- source dmlc-core/scripts/travis/travis_setup_env.sh
|
||||||
- source tests/travis/setup.sh
|
- source tests/travis/setup.sh
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -11,7 +11,7 @@ endif
|
||||||
include $(config)
|
include $(config)
|
||||||
|
|
||||||
# specify tensor path
|
# specify tensor path
|
||||||
.PHONY: clean all test doc pylint cpplint lint
|
.PHONY: clean all test doc pylint cpplint lint verilog
|
||||||
|
|
||||||
all: lib/libtvm.so lib/libtvm_runtime.so lib/libtvm.a
|
all: lib/libtvm.so lib/libtvm_runtime.so lib/libtvm.a
|
||||||
|
|
||||||
|
@ -79,6 +79,9 @@ include tests/cpp/unittest.mk
|
||||||
|
|
||||||
test: $(TEST)
|
test: $(TEST)
|
||||||
|
|
||||||
|
include verilog/verilog.mk
|
||||||
|
verilog: $(VER_LIBS)
|
||||||
|
|
||||||
build/%.o: src/%.cc
|
build/%.o: src/%.cc
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
|
$(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d
|
||||||
|
@ -92,6 +95,8 @@ lib/libtvm_runtime.so: $(RUNTIME_DEP)
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
|
$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
lib/libtvm.a: $(ALL_DEP)
|
lib/libtvm.a: $(ALL_DEP)
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
ar crv $@ $(filter %.o, $?)
|
ar crv $@ $(filter %.o, $?)
|
||||||
|
@ -102,7 +107,7 @@ LIBHALIDEIR:
|
||||||
+ cd HalideIR; make lib/libHalideIR.a ; cd $(ROOTDIR)
|
+ cd HalideIR; make lib/libHalideIR.a ; cd $(ROOTDIR)
|
||||||
|
|
||||||
cpplint:
|
cpplint:
|
||||||
python2 dmlc-core/scripts/lint.py tvm cpp include src
|
python2 dmlc-core/scripts/lint.py tvm cpp include src verilog
|
||||||
|
|
||||||
pylint:
|
pylint:
|
||||||
pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
|
pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 53751c3da2999d841f4f952639bd766505b51d84
|
Subproject commit c2871f5db50830f5278ff6e323e8e51a6d5516dd
|
|
@ -23,6 +23,8 @@ class Channel : public NodeRef {
|
||||||
* \return the pointer to the internal node container
|
* \return the pointer to the internal node container
|
||||||
*/
|
*/
|
||||||
inline const ChannelNode* operator->() const;
|
inline const ChannelNode* operator->() const;
|
||||||
|
// The container type
|
||||||
|
using ContainerType = ChannelNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
"""Information about nnvm."""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from .. import _api_internal
|
||||||
|
from .._base import string_types
|
||||||
|
from .._ctypes._node import NodeBase, register_node
|
||||||
|
from . import testing
|
||||||
|
|
||||||
|
@register_node
|
||||||
|
class VPISession(NodeBase):
|
||||||
|
"""Verilog session"""
|
||||||
|
def __init__(self, handle):
|
||||||
|
super(VPISession, self).__init__(handle)
|
||||||
|
self.proc = None
|
||||||
|
self.execpath = None
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.proc.kill()
|
||||||
|
super(VPISession, self).__del__()
|
||||||
|
|
||||||
|
def arg(self, index):
|
||||||
|
"""Get handle passed to host session.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
index : int
|
||||||
|
The index value.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
handle : VPIHandle
|
||||||
|
The handle
|
||||||
|
"""
|
||||||
|
return _api_internal._vpi_SessGetArg(self, index)
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
if not isinstance(name, string_types):
|
||||||
|
raise ValueError("have to be string types")
|
||||||
|
return _api_internal._vpi_SessGetHandleByName(self, name)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return _api_internal._vpi_SessGetHandleByName(self, name)
|
||||||
|
|
||||||
|
def yield_until_posedge(self):
|
||||||
|
"""Yield until next posedge"""
|
||||||
|
return _api_internal._vpi_SessYield(self)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
"""Shutdown the simulator"""
|
||||||
|
return _api_internal._vpi_SessShutdown(self)
|
||||||
|
|
||||||
|
|
||||||
|
@register_node
|
||||||
|
class VPIHandle(NodeBase):
|
||||||
|
"""Handle to a verilog variable."""
|
||||||
|
def __init__(self, handle):
|
||||||
|
super(VPIHandle, self).__init__(handle)
|
||||||
|
self._name = None
|
||||||
|
self._size = None
|
||||||
|
|
||||||
|
def get_int(self):
|
||||||
|
"""Get integer value from handle.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
value : int
|
||||||
|
"""
|
||||||
|
return _api_internal._vpi_HandleGetInt(self)
|
||||||
|
|
||||||
|
def put_int(self, value):
|
||||||
|
"""Put integer value to handle.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
value : int
|
||||||
|
The value to put
|
||||||
|
"""
|
||||||
|
return _api_internal._vpi_HandlePutInt(self, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
if self._name is None:
|
||||||
|
self._name = _api_internal._vpi_HandleGetName(self)
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
if self._size is None:
|
||||||
|
self._size = _api_internal._vpi_HandleGetSize(self)
|
||||||
|
return self._size
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
if not isinstance(name, string_types):
|
||||||
|
raise ValueError("have to be string types")
|
||||||
|
return _api_internal._vpi_HandleGetHandleByName(self, name)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return _api_internal._vpi_HandleGetHandleByName(self, name)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_vpi_path():
|
||||||
|
curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
|
||||||
|
api_path = os.path.join(curr_path, '../../../lib/')
|
||||||
|
vpi_path = [curr_path, api_path]
|
||||||
|
vpi_path = [os.path.join(p, 'tvm_vpi.vpi') for p in vpi_path]
|
||||||
|
vpi_found = [p for p in vpi_path if os.path.exists(p) and os.path.isfile(p)]
|
||||||
|
if vpi_found:
|
||||||
|
return os.path.dirname(vpi_found[0])
|
||||||
|
else:
|
||||||
|
raise ValueError("Cannot find tvm_vpi.vpi, make sure you did `make verilog`")
|
||||||
|
|
||||||
|
def search_path():
|
||||||
|
"""Get the search directory."""
|
||||||
|
curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
|
||||||
|
ver_path = [os.path.join(curr_path, '../../../verilog/')]
|
||||||
|
ver_path += [os.path.join(curr_path, '../../../tests/verilog/')]
|
||||||
|
return ver_path
|
||||||
|
|
||||||
|
|
||||||
|
def find_file(file_name):
|
||||||
|
"""Find file in the search directories.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
file_name : str
|
||||||
|
The file name
|
||||||
|
|
||||||
|
Return
|
||||||
|
------
|
||||||
|
file_name : str
|
||||||
|
The absolute path to the file, raise Error if cannot find it.
|
||||||
|
"""
|
||||||
|
ver_path = search_path()
|
||||||
|
flist = [os.path.join(p, file_name) for p in ver_path]
|
||||||
|
found = [p for p in flist if os.path.exists(p) and os.path.isfile(p)]
|
||||||
|
if len(found):
|
||||||
|
return found[0]
|
||||||
|
else:
|
||||||
|
raise ValueError("Cannot find %s in %s" % (file_name, flist))
|
||||||
|
|
||||||
|
|
||||||
|
def compile_file(file_name, file_target, options=None):
|
||||||
|
"""Compile verilog via iverilog
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
file_name : str or list of str
|
||||||
|
The cuda code.
|
||||||
|
|
||||||
|
file_target : str
|
||||||
|
The target file.
|
||||||
|
"""
|
||||||
|
cmd = ["iverilog"]
|
||||||
|
for path in search_path():
|
||||||
|
cmd += ["-I%s" % path]
|
||||||
|
|
||||||
|
cmd += ["-o", file_target]
|
||||||
|
if options:
|
||||||
|
cmd += options
|
||||||
|
|
||||||
|
if isinstance(file_name, string_types):
|
||||||
|
file_name = [file_name]
|
||||||
|
cmd += file_name
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
(out, _) = proc.communicate()
|
||||||
|
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise ValueError("Compilation error:\n%s" % out)
|
||||||
|
|
||||||
|
|
||||||
|
def session(file_name):
|
||||||
|
"""Create a new iverilog session by compile the file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
file_name : str or list of str
|
||||||
|
The name of the file
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
sess : VPISession
|
||||||
|
The created session.
|
||||||
|
"""
|
||||||
|
if isinstance(file_name, string_types):
|
||||||
|
file_name = [file_name]
|
||||||
|
|
||||||
|
for name in file_name:
|
||||||
|
if not os.path.exists(name):
|
||||||
|
raise ValueError("Cannot find file %s" % name)
|
||||||
|
|
||||||
|
path = testing.tempdir()
|
||||||
|
target = path.relpath(os.path.basename(file_name[0].rsplit(".", 1)[0]))
|
||||||
|
compile_file(file_name, target)
|
||||||
|
vpi_path = _find_vpi_path()
|
||||||
|
|
||||||
|
cmd = ["vvp"]
|
||||||
|
cmd += ["-M", vpi_path]
|
||||||
|
cmd += ["-m", "tvm_vpi"]
|
||||||
|
cmd += [target]
|
||||||
|
env = os.environ.copy()
|
||||||
|
|
||||||
|
read_device, write_host = os.pipe()
|
||||||
|
read_host, write_device = os.pipe()
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import msvcrt
|
||||||
|
env['TVM_DREAD_PIPE'] = str(msvcrt.get_osfhandle(read_device))
|
||||||
|
env['TVM_DWRITE_PIPE'] = str(msvcrt.get_osfhandle(write_device))
|
||||||
|
read_host = msvcrt.get_osfhandle(read_host)
|
||||||
|
write_host = msvcrt.get_osfhandle(write_host)
|
||||||
|
else:
|
||||||
|
env['TVM_DREAD_PIPE'] = str(read_device)
|
||||||
|
env['TVM_DWRITE_PIPE'] = str(write_device)
|
||||||
|
|
||||||
|
env['TVM_HREAD_PIPE'] = str(read_host)
|
||||||
|
env['TVM_HWRITE_PIPE'] = str(write_host)
|
||||||
|
|
||||||
|
proc = subprocess.Popen(cmd, env=env, close_fds=False)
|
||||||
|
# close device side pipe
|
||||||
|
os.close(read_device)
|
||||||
|
os.close(write_device)
|
||||||
|
|
||||||
|
sess = _api_internal._vpi_SessMake(read_host, write_host)
|
||||||
|
sess.proc = proc
|
||||||
|
sess.execpath = path
|
||||||
|
return sess
|
|
@ -4,6 +4,7 @@ Header files in include are public APIs that share across modules.
|
||||||
There can be internal header files within each module that sit in src.
|
There can be internal header files within each module that sit in src.
|
||||||
|
|
||||||
The current code modules in src.
|
The current code modules in src.
|
||||||
|
- common Internal common utilities.
|
||||||
- api API function registration
|
- api API function registration
|
||||||
- lang The definition of DSL related data structure
|
- lang The definition of DSL related data structure
|
||||||
- arithmetic Arithmetic expression and set simplification
|
- arithmetic Arithmetic expression and set simplification
|
||||||
|
|
|
@ -0,0 +1,242 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2017 by Contributors
|
||||||
|
* \file vpi_session.cc
|
||||||
|
* \brief IPC session call to verilog simulator via VPI.
|
||||||
|
*/
|
||||||
|
#include <tvm/api_registry.h>
|
||||||
|
#include "./vpi_session.h"
|
||||||
|
|
||||||
|
namespace tvm {
|
||||||
|
namespace codegen {
|
||||||
|
|
||||||
|
using namespace vpi;
|
||||||
|
|
||||||
|
/*! \brief Container for session. */
|
||||||
|
class VPISessionNode : public Node {
|
||||||
|
public:
|
||||||
|
// Whether in control.
|
||||||
|
bool in_control{false};
|
||||||
|
// Internal reader and writer.
|
||||||
|
common::Pipe reader;
|
||||||
|
common::Pipe writer;
|
||||||
|
|
||||||
|
// internal constructor
|
||||||
|
VPISessionNode(int h_pipe_read, int h_pipe_write)
|
||||||
|
: reader(h_pipe_read), writer(h_pipe_write) {
|
||||||
|
}
|
||||||
|
~VPISessionNode() {
|
||||||
|
if (in_control) {
|
||||||
|
VPIReturnCode cd;
|
||||||
|
writer.Write(kShutDown);
|
||||||
|
reader.Read(&cd);
|
||||||
|
}
|
||||||
|
reader.Close();
|
||||||
|
writer.Close();
|
||||||
|
}
|
||||||
|
// visit all attributes
|
||||||
|
void VisitAttrs(AttrVisitor* v) final {
|
||||||
|
}
|
||||||
|
void ReadExpect(VPIReturnCode rcode) {
|
||||||
|
VPIReturnCode code;
|
||||||
|
CHECK(reader.Read(&code));
|
||||||
|
CHECK_EQ(code, rcode) << "Error in simulation";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const char* _type_key = "VPISession";
|
||||||
|
TVM_DECLARE_NODE_TYPE_INFO(VPISessionNode, Node);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief Container for handle */
|
||||||
|
class VPIHandleNode : public Node {
|
||||||
|
public:
|
||||||
|
// The internal session.
|
||||||
|
VPISession sess;
|
||||||
|
// Internal handle
|
||||||
|
VPIRawHandle handle;
|
||||||
|
|
||||||
|
void VisitAttrs(AttrVisitor* v) final {
|
||||||
|
v->Visit("sess", &sess);
|
||||||
|
}
|
||||||
|
static VPIHandle make(const VPISession& sess, VPIRawHandle handle) {
|
||||||
|
std::shared_ptr<VPIHandleNode> n =
|
||||||
|
std::make_shared<VPIHandleNode>();
|
||||||
|
n->sess = sess;
|
||||||
|
n->handle = handle;
|
||||||
|
return VPIHandle(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const char* _type_key = "VPIHandle";
|
||||||
|
TVM_DECLARE_NODE_TYPE_INFO(VPIHandleNode, Node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inline implementations
|
||||||
|
inline VPISessionNode* VPISession::get() const {
|
||||||
|
return static_cast<VPISessionNode*>(node_.get());
|
||||||
|
}
|
||||||
|
inline VPIHandleNode* VPIHandle::get() const {
|
||||||
|
return static_cast<VPIHandleNode*>(node_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
VPISession VPISession::make(int h_pipe_read, int h_pipe_write) {
|
||||||
|
std::shared_ptr<VPISessionNode> n = std::make_shared<VPISessionNode>(
|
||||||
|
h_pipe_read, h_pipe_write);
|
||||||
|
n->ReadExpect(kPosEdgeTrigger);
|
||||||
|
n->in_control = true;
|
||||||
|
return VPISession(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
VPIHandle VPISession::operator[](const std::string& name) const {
|
||||||
|
return GetByName(name, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
VPIHandle VPISession::GetByName(const std::string& name, VPIRawHandle handle) const {
|
||||||
|
VPISessionNode* n = get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kGetHandleByName);
|
||||||
|
n->writer.Write(name);
|
||||||
|
n->writer.Write(handle);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
CHECK(n->reader.Read(&handle));
|
||||||
|
CHECK(handle != nullptr)
|
||||||
|
<< "Cannot find handle with name=" << name;
|
||||||
|
return VPIHandleNode::make(*this, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VPISession::yield() {
|
||||||
|
VPISessionNode* n = get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kYield);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
n->in_control = false;
|
||||||
|
n->ReadExpect(kPosEdgeTrigger);
|
||||||
|
n->in_control = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VPISession::shutdown() {
|
||||||
|
VPISessionNode* n = get();
|
||||||
|
if (n->in_control) {
|
||||||
|
n->writer.Write(kShutDown);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
n->in_control = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int VPIHandle::size() const {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
VPISessionNode* n = h->sess.get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kGetSize);
|
||||||
|
n->writer.Write(h->handle);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
int value;
|
||||||
|
CHECK(n->reader.Read(&value));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VPIHandle::put_int(int value) {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
VPISessionNode* n = h->sess.get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kPutInt32);
|
||||||
|
n->writer.Write(h->handle);
|
||||||
|
n->writer.Write(value);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
int VPIHandle::get_int() const {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
VPISessionNode* n = h->sess.get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kGetInt32);
|
||||||
|
n->writer.Write(h->handle);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
int value;
|
||||||
|
CHECK(n->reader.Read(&value));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VPIHandle::name() const {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
VPISessionNode* n = h->sess.get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kGetName);
|
||||||
|
n->writer.Write(h->handle);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
std::string str;
|
||||||
|
CHECK(n->reader.Read(&str));
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VPIHandle::put_vec(const std::vector<VPIVecVal>& vec) const {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
VPISessionNode* n = h->sess.get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kPutVec);
|
||||||
|
n->writer.Write(h->handle);
|
||||||
|
n->writer.Write(vec);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VPIHandle::get_vec(std::vector<VPIVecVal>* vec) const {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
VPISessionNode* n = h->sess.get();
|
||||||
|
CHECK(n->in_control);
|
||||||
|
n->writer.Write(kPutVec);
|
||||||
|
n->writer.Write(h->handle);
|
||||||
|
n->ReadExpect(kSuccess);
|
||||||
|
CHECK(n->reader.Read(&vec));
|
||||||
|
}
|
||||||
|
|
||||||
|
VPIHandle VPIHandle::operator[](const std::string& name) const {
|
||||||
|
VPIHandleNode* h = get();
|
||||||
|
return h->sess.GetByName(name, h->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API registration
|
||||||
|
TVM_REGISTER_API(_vpi_SessMake)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
*ret = VPISession::make(args[0], args[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_SessGetHandleByName)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
*ret = args[0].operator VPISession().operator[](args[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_SessYield)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
args[0].operator VPISession().yield();
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_SessShutdown)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
args[0].operator VPISession().shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_HandlePutInt)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
args[0].operator VPIHandle().put_int(args[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_HandleGetInt)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
*ret = args[0].operator VPIHandle().get_int();
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_HandleGetName)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
*ret = args[0].operator VPIHandle().name();
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_HandleGetSize)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
*ret = args[0].operator VPIHandle().size();
|
||||||
|
});
|
||||||
|
|
||||||
|
TVM_REGISTER_API(_vpi_HandleGetHandleByName)
|
||||||
|
.set_body([](TVMArgs args, TVMRetValue *ret) {
|
||||||
|
*ret = args[0].operator VPIHandle().operator[](args[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
} // namespace codegen
|
||||||
|
} // namespace tvm
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2017 by Contributors
|
||||||
|
* \file vpi_session.h
|
||||||
|
* \brief IPC session call to verilog simulator via VPI.
|
||||||
|
*/
|
||||||
|
#ifndef TVM_CODEGEN_VERILOG_VPI_SESSION_H_
|
||||||
|
#define TVM_CODEGEN_VERILOG_VPI_SESSION_H_
|
||||||
|
|
||||||
|
#include <tvm/base.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "../../common/pipe.h"
|
||||||
|
#include "../../../verilog/tvm_vpi.h"
|
||||||
|
|
||||||
|
namespace tvm {
|
||||||
|
namespace codegen {
|
||||||
|
// node containers
|
||||||
|
class VPISessionNode;
|
||||||
|
class VPIHandleNode;
|
||||||
|
class VPIHandle;
|
||||||
|
|
||||||
|
/*! \brief Environment */
|
||||||
|
class VPISession : public NodeRef {
|
||||||
|
public:
|
||||||
|
VPISession() {}
|
||||||
|
explicit VPISession(std::shared_ptr<Node> n) : NodeRef(n) {}
|
||||||
|
/*!
|
||||||
|
* \brief Get handle by name.
|
||||||
|
* \param name The name of the handle.
|
||||||
|
*/
|
||||||
|
VPIHandle operator[](const std::string& name) const;
|
||||||
|
/*!
|
||||||
|
* \brief Yield control back to the simulator
|
||||||
|
* Block until next cycle.
|
||||||
|
*/
|
||||||
|
void yield();
|
||||||
|
/*!
|
||||||
|
* \brief Shutdown the session.
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
/*!
|
||||||
|
* \brief Create new session by giving a read and write pipe to VPI process.
|
||||||
|
* \param h_pipe_read a read pipe from VPI process.
|
||||||
|
* \param h_pipe_write a write pipe from VPI process.
|
||||||
|
*/
|
||||||
|
static VPISession make(int h_pipe_read, int h_pipe_write);
|
||||||
|
// Internal methods.
|
||||||
|
using ContainerType = VPISessionNode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class VPIHandle;
|
||||||
|
inline VPISessionNode* get() const;
|
||||||
|
// Get handle by name
|
||||||
|
VPIHandle GetByName(const std::string& name, vpi::VPIRawHandle handle) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief VPI Handle */
|
||||||
|
class VPIHandle : public NodeRef {
|
||||||
|
public:
|
||||||
|
VPIHandle() {}
|
||||||
|
explicit VPIHandle(std::shared_ptr<Node> n) : NodeRef(n) {}
|
||||||
|
/*!
|
||||||
|
* \brief Get handle by name.
|
||||||
|
* \param name The name of the handle.
|
||||||
|
*/
|
||||||
|
VPIHandle operator[](const std::string& name) const;
|
||||||
|
/*! \return number of bits */
|
||||||
|
int size() const;
|
||||||
|
/*!
|
||||||
|
* \brief Set int value to the handle.
|
||||||
|
* \param value The value to set.
|
||||||
|
*/
|
||||||
|
void put_int(int value);
|
||||||
|
/*!
|
||||||
|
* \brief Get int value from handle.
|
||||||
|
* \return The result int value.
|
||||||
|
*/
|
||||||
|
int get_int() const;
|
||||||
|
/*! \return Name of the handle. */
|
||||||
|
std::string name() const;
|
||||||
|
/*!
|
||||||
|
* \brief Put byte vector into the handle.
|
||||||
|
* \param vec The vector to be put.
|
||||||
|
* \return The result int value.
|
||||||
|
*/
|
||||||
|
void put_vec(const std::vector<vpi::VPIVecVal>& vec) const;
|
||||||
|
/*!
|
||||||
|
* \brief Get byte vector from handle.
|
||||||
|
* \param vec The result data container.
|
||||||
|
*/
|
||||||
|
void get_vec(std::vector<vpi::VPIVecVal>* vec) const;
|
||||||
|
// Internal methods
|
||||||
|
using ContainerType = VPIHandleNode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline VPIHandleNode* get() const;
|
||||||
|
};
|
||||||
|
} // namespace codegen
|
||||||
|
} // namespace tvm
|
||||||
|
#endif // TVM_CODEGEN_VERILOG_VPI_SESSION_H_
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2017 by Contributors
|
||||||
|
* \file pipe.h
|
||||||
|
* \brief Platform independent pipe, used for IPC.
|
||||||
|
*/
|
||||||
|
#ifndef TVM_COMMON_PIPE_H_
|
||||||
|
#define TVM_COMMON_PIPE_H_
|
||||||
|
|
||||||
|
#include <dmlc/logging.h>
|
||||||
|
#include <dmlc/io.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace tvm {
|
||||||
|
namespace common {
|
||||||
|
|
||||||
|
/*! \brief Platform independent pipe */
|
||||||
|
class Pipe : public dmlc::Stream {
|
||||||
|
public:
|
||||||
|
#ifdef _WIN32
|
||||||
|
using PipeHandle = HANDLE;
|
||||||
|
#else
|
||||||
|
using PipeHandle = int;
|
||||||
|
#endif
|
||||||
|
/*! \brief Construct a pipe from system handle. */
|
||||||
|
explicit Pipe(int64_t handle)
|
||||||
|
: handle_(static_cast<PipeHandle>(handle)) {}
|
||||||
|
/*! \brief destructor */
|
||||||
|
~Pipe() {
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
using Stream::Read;
|
||||||
|
using Stream::Write;
|
||||||
|
/*!
|
||||||
|
* \brief reads data from a file descriptor
|
||||||
|
* \param ptr pointer to a memory buffer
|
||||||
|
* \param size block size
|
||||||
|
* \return the size of data read
|
||||||
|
*/
|
||||||
|
size_t Read(void *ptr, size_t size) final {
|
||||||
|
if (size == 0) return 0;
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD nread;
|
||||||
|
CHECK(ReadFile(handle_, static_cast<TCHAR*>(ptr),
|
||||||
|
&nread, nullptr))
|
||||||
|
<< "Read Error: " << GetLastError();
|
||||||
|
#else
|
||||||
|
ssize_t nread;
|
||||||
|
nread = read(handle_, ptr, size);
|
||||||
|
CHECK_GE(nread, 0)
|
||||||
|
<< "Write Error: " << strerror(errno);
|
||||||
|
#endif
|
||||||
|
return static_cast<size_t>(nread);
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
* \brief write data to a file descriptor
|
||||||
|
* \param ptr pointer to a memory buffer
|
||||||
|
* \param size block size
|
||||||
|
* \return the size of data read
|
||||||
|
*/
|
||||||
|
void Write(const void *ptr, size_t size) final {
|
||||||
|
if (size == 0) return;
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD nwrite;
|
||||||
|
CHECK(WriteFile(handle_, static_cast<const TCHAR*>(ptr),
|
||||||
|
&nwrite, nullptr) &&
|
||||||
|
static_cast<size_t>(nwrite) == size)
|
||||||
|
<< "Write Error: " << GetLastError();
|
||||||
|
#else
|
||||||
|
ssize_t nwrite;
|
||||||
|
nwrite = write(handle_, ptr, size);
|
||||||
|
CHECK_EQ(static_cast<size_t>(nwrite), size)
|
||||||
|
<< "Write Error: " << strerror(errno);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
* \brief Flush the pipe;
|
||||||
|
*/
|
||||||
|
void Flush() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
FlushFileBuffers(handle_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
/*! \brief close the pipe */
|
||||||
|
void Close() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
CloseHandle(handle_);
|
||||||
|
#else
|
||||||
|
close(handle_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PipeHandle handle_;
|
||||||
|
};
|
||||||
|
} // namespace common
|
||||||
|
} // namespace tvm
|
||||||
|
|
||||||
|
#endif // TVM_COMMON_PIPE_H_
|
|
@ -0,0 +1,10 @@
|
||||||
|
# rules for gtest
|
||||||
|
.PHONY: iverilog
|
||||||
|
|
||||||
|
iverilog: | ${CACHE_PREFIX}/bin/vvp
|
||||||
|
|
||||||
|
${CACHE_PREFIX}/bin/vvp:
|
||||||
|
rm -rf verilog-10.1.tar.gz verilog-10.1
|
||||||
|
wget ftp://icarus.com/pub/eda/verilog/v10/verilog-10.1.tar.gz
|
||||||
|
tar xf verilog-10.1.tar.gz
|
||||||
|
cd verilog-10.1;./configure --prefix=${CACHE_PREFIX}; make install
|
|
@ -30,6 +30,16 @@ else
|
||||||
echo "USE_OPENCL=0" >> config.mk
|
echo "USE_OPENCL=0" >> config.mk
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ${TASK} == "verilog_test" ] || [ ${TASK} == "all_test" ]; then
|
||||||
|
if [ ! ${TRAVIS_OS_NAME} == "osx" ]; then
|
||||||
|
echo ${PATH}
|
||||||
|
make -f tests/travis/packages.mk iverilog
|
||||||
|
make verilog || exit -1
|
||||||
|
make all || exit -1
|
||||||
|
nosetests -v tests/verilog || exit -1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ${TASK} == "cpp_test" ] || [ ${TASK} == "all_test" ]; then
|
if [ ${TASK} == "cpp_test" ] || [ ${TASK} == "all_test" ]; then
|
||||||
make -f dmlc-core/scripts/packages.mk gtest
|
make -f dmlc-core/scripts/packages.mk gtest
|
||||||
make test || exit -1
|
make test || exit -1
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import tvm
|
||||||
|
import os
|
||||||
|
from tvm.addon import verilog
|
||||||
|
|
||||||
|
def test_counter():
|
||||||
|
# Start a new session by run simulation on test_counter.v
|
||||||
|
# Find file will search root/verilog and root/tests/verilog
|
||||||
|
sess = verilog.session([
|
||||||
|
verilog.find_file("test_counter.v"),
|
||||||
|
verilog.find_file("example_counter.v")
|
||||||
|
])
|
||||||
|
# Get the handles by their names
|
||||||
|
rst = sess.main.rst
|
||||||
|
counter = sess.main.counter
|
||||||
|
cnt = sess.main["counter_unit1"]
|
||||||
|
assert(counter.name == "main.counter")
|
||||||
|
assert(counter.size == 4)
|
||||||
|
rst.put_int(1)
|
||||||
|
# This will advance the cycle to next pos-edge of clk.
|
||||||
|
sess.yield_until_posedge()
|
||||||
|
rst.put_int(0)
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
# get value of counter.
|
||||||
|
assert(counter.get_int() == i)
|
||||||
|
sess.yield_until_posedge()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_counter()
|
|
@ -0,0 +1,15 @@
|
||||||
|
module main();
|
||||||
|
parameter PER = 10;
|
||||||
|
reg clk;
|
||||||
|
reg rst;
|
||||||
|
wire [3:0] counter;
|
||||||
|
|
||||||
|
counter counter_unit1(.clk(clk), .rst(rst), .out(counter));
|
||||||
|
always begin
|
||||||
|
#(PER/2) clk =~ clk;
|
||||||
|
end
|
||||||
|
initial begin
|
||||||
|
// This will allow tvm session to be called every cycle.
|
||||||
|
$tvm_session(clk);
|
||||||
|
end
|
||||||
|
endmodule
|
|
@ -0,0 +1,36 @@
|
||||||
|
import tvm
|
||||||
|
import os
|
||||||
|
from tvm.addon import verilog
|
||||||
|
|
||||||
|
def test_loop():
|
||||||
|
sess = verilog.session([
|
||||||
|
verilog.find_file("test_loop.v")
|
||||||
|
])
|
||||||
|
# Get the handles by their names
|
||||||
|
rst = sess.main.rst
|
||||||
|
init = sess.main.init
|
||||||
|
iter0 = sess.main.iter0
|
||||||
|
iter1 = sess.main.iter1
|
||||||
|
enable = sess.main.enable
|
||||||
|
invalid = sess.main.done
|
||||||
|
|
||||||
|
rst.put_int(1)
|
||||||
|
# This will advance the cycle to next pos-edge of clk.
|
||||||
|
sess.yield_until_posedge()
|
||||||
|
rst.put_int(0)
|
||||||
|
init.put_int(1)
|
||||||
|
sess.yield_until_posedge()
|
||||||
|
enable.put_int(1)
|
||||||
|
init.put_int(0)
|
||||||
|
|
||||||
|
for i in range(0, 3):
|
||||||
|
for j in range(0, 4):
|
||||||
|
while invalid.get_int():
|
||||||
|
sess.yield_until_posedge()
|
||||||
|
assert(iter1.get_int() == i)
|
||||||
|
assert(iter0.get_int() == j)
|
||||||
|
sess.yield_until_posedge()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_loop()
|
|
@ -0,0 +1,24 @@
|
||||||
|
`include "tvm_marcos.v"
|
||||||
|
|
||||||
|
module main();
|
||||||
|
parameter PER = 10;
|
||||||
|
reg clk;
|
||||||
|
reg rst;
|
||||||
|
wire init;
|
||||||
|
wire done;
|
||||||
|
wire enable;
|
||||||
|
|
||||||
|
`NORMAL_LOOP_LEAF(iter0, 4, init0, enable, iter0_done, 0, 4, 1)
|
||||||
|
`NORMAL_LOOP_NEST(iter1, 4, init, iter0_done, iter1_done, 0, 3, 1, init0)
|
||||||
|
|
||||||
|
assign done = iter0_done;
|
||||||
|
|
||||||
|
always begin
|
||||||
|
#(PER/2) clk =~ clk;
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
// This will allow tvm session to be called every cycle.
|
||||||
|
$tvm_session(clk);
|
||||||
|
end
|
||||||
|
endmodule
|
|
@ -0,0 +1,17 @@
|
||||||
|
// a counter that counts up
|
||||||
|
// Use as example of testcaase
|
||||||
|
module counter(clk, rst, out);
|
||||||
|
input clk;
|
||||||
|
input rst;
|
||||||
|
output [3:0] out;
|
||||||
|
reg [3:0] counter;
|
||||||
|
assign out = counter;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (rst) begin
|
||||||
|
counter <= 0;
|
||||||
|
end else begin
|
||||||
|
counter <= counter +1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endmodule
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Leaf of a normal loop nest
|
||||||
|
// Starts at done = 1
|
||||||
|
// Need init to reset to done = 0
|
||||||
|
// increases when enabled = 1
|
||||||
|
`define NORMAL_LOOP_LEAF(iter, width, init, enable, done, min, max, incr)\
|
||||||
|
reg [width-1:0] iter;\
|
||||||
|
reg valid;\
|
||||||
|
reg done;\
|
||||||
|
always@(posedge clk) begin\
|
||||||
|
if(rst) begin\
|
||||||
|
iter <= 0;\
|
||||||
|
done <= 1;\
|
||||||
|
end else if(init) begin\
|
||||||
|
iter <= (min);\
|
||||||
|
done <= 0;\
|
||||||
|
end else if(done) begin\
|
||||||
|
iter <= 0;\
|
||||||
|
done <= 1;\
|
||||||
|
end else if(enable) begin\
|
||||||
|
if (iter < ((max)-(incr))) begin\
|
||||||
|
iter <= iter + (incr);\
|
||||||
|
done <= 0;\
|
||||||
|
end else begin\
|
||||||
|
iter <= 0;\
|
||||||
|
done <= 1;\
|
||||||
|
end\
|
||||||
|
end else begin\
|
||||||
|
iter <= iter;\
|
||||||
|
done <= done;\
|
||||||
|
end\
|
||||||
|
end
|
||||||
|
|
||||||
|
// Normal loop nest that can connect to a child which is a normal loop
|
||||||
|
`define NORMAL_LOOP_NEST(iter, width, init, body_done, done, min, max, incr, body_init)\
|
||||||
|
reg [width-1:0] iter;\
|
||||||
|
reg done;\
|
||||||
|
reg body_init;\
|
||||||
|
always@(posedge clk) begin\
|
||||||
|
if(rst) begin\
|
||||||
|
iter <= 0;\
|
||||||
|
done <= 1;\
|
||||||
|
body_init <= 0;\
|
||||||
|
end else if(init) begin\
|
||||||
|
iter <= (min);\
|
||||||
|
done <= 0;\
|
||||||
|
body_init <= 1;\
|
||||||
|
end else if(done) begin\
|
||||||
|
iter <= 0;\
|
||||||
|
done <= 1;\
|
||||||
|
body_init <= 0;\
|
||||||
|
end else if (body_init) begin\
|
||||||
|
iter <= iter;\
|
||||||
|
done <= done;\
|
||||||
|
body_init <= 0;\
|
||||||
|
end else if (body_done) begin\
|
||||||
|
if (iter < ((max)-(incr))) begin\
|
||||||
|
iter <= iter + (incr);\
|
||||||
|
done <= 0;\
|
||||||
|
body_init <= 1;\
|
||||||
|
end else begin\
|
||||||
|
iter <= 0;\
|
||||||
|
done <= 1;\
|
||||||
|
body_init <= 0;\
|
||||||
|
end\
|
||||||
|
end else begin\
|
||||||
|
iter <= iter;\
|
||||||
|
done <= done;\
|
||||||
|
body_init <= 0;\
|
||||||
|
end\
|
||||||
|
end
|
|
@ -0,0 +1,244 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2017 by Contributors
|
||||||
|
* \file tvm_vpi.cc
|
||||||
|
* \brief Messages passed around VPI used for simulation.
|
||||||
|
*/
|
||||||
|
#include <dmlc/logging.h>
|
||||||
|
#include <vpi_user.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include "./tvm_vpi.h"
|
||||||
|
#include "../src/common/pipe.h"
|
||||||
|
|
||||||
|
namespace tvm {
|
||||||
|
namespace vpi {
|
||||||
|
static_assert(sizeof(vpiHandle) == sizeof(VPIRawHandle),
|
||||||
|
"VPI handle condition");
|
||||||
|
// IPC client for VPI
|
||||||
|
class IPCClient {
|
||||||
|
public:
|
||||||
|
// constructor
|
||||||
|
IPCClient(int64_t hread, int64_t hwrite)
|
||||||
|
: reader_(hread), writer_(hwrite) {
|
||||||
|
}
|
||||||
|
void Init() {
|
||||||
|
vpiHandle argv = vpi_handle(vpiSysTfCall, 0);
|
||||||
|
vpiHandle arg_iter = vpi_iterate(vpiArgument, argv);
|
||||||
|
clock_ = vpi_scan(arg_iter);
|
||||||
|
CHECK(vpi_scan(arg_iter) == nullptr)
|
||||||
|
<< "tvm_session can only take in one clock";
|
||||||
|
PutInt(clock_, 0);
|
||||||
|
}
|
||||||
|
int Callback() {
|
||||||
|
if (GetInt(clock_)) {
|
||||||
|
try {
|
||||||
|
return AtPosEedge();
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
reader_.Close();
|
||||||
|
writer_.Close();
|
||||||
|
vpi_printf("ERROR: encountered %s\n", e.what());
|
||||||
|
vpi_control(vpiFinish, 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// called at positive edge.
|
||||||
|
int AtPosEedge() {
|
||||||
|
writer_.Write(kPosEdgeTrigger);
|
||||||
|
VPICallCode rcode;
|
||||||
|
VPIRawHandle handle;
|
||||||
|
int32_t index, value;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
CHECK(reader_.Read(&rcode));
|
||||||
|
switch (rcode) {
|
||||||
|
case kGetHandleByName: {
|
||||||
|
std::string str;
|
||||||
|
CHECK(reader_.Read(&str));
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
handle = vpi_handle_by_name(
|
||||||
|
str.c_str(), static_cast<vpiHandle>(handle));
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
writer_.Write(handle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kGetHandleByIndex: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
CHECK(reader_.Read(&index));
|
||||||
|
handle = vpi_handle_by_index(
|
||||||
|
static_cast<vpiHandle>(handle), index);
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
writer_.Write(handle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kGetName: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
std::string name = vpi_get_str(
|
||||||
|
vpiFullName, static_cast<vpiHandle>(handle));
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
writer_.Write(name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kGetInt32: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
value = GetInt(static_cast<vpiHandle>(handle));
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
writer_.Write(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kPutInt32: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
CHECK(reader_.Read(&value));
|
||||||
|
CHECK(handle != clock_) << "Cannot write to clock";
|
||||||
|
PutInt(static_cast<vpiHandle>(handle), value);
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kGetSize: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
value = vpi_get(vpiSize, static_cast<vpiHandle>(handle));
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
writer_.Write(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kGetVec: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
vpiHandle h = static_cast<vpiHandle>(handle);
|
||||||
|
int bits = vpi_get(vpiSize, h);
|
||||||
|
int nwords = (bits + 31) / 32;
|
||||||
|
s_vpi_value value_s;
|
||||||
|
value_s.format = vpiVectorVal;
|
||||||
|
vpi_get_value(h, &value_s);
|
||||||
|
vec_buf_.resize(nwords);
|
||||||
|
for (size_t i = 0; i < vec_buf_.size(); ++i) {
|
||||||
|
vec_buf_[i].aval = value_s.value.vector[i].aval;
|
||||||
|
vec_buf_[i].bval = value_s.value.vector[i].bval;
|
||||||
|
}
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
writer_.Write(vec_buf_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kPutVec: {
|
||||||
|
CHECK(reader_.Read(&handle));
|
||||||
|
CHECK(reader_.Read(&vec_buf_));
|
||||||
|
CHECK(handle != clock_) << "Cannot write to clock";
|
||||||
|
vpiHandle h = static_cast<vpiHandle>(handle);
|
||||||
|
size_t nwords = vec_buf_.size();
|
||||||
|
svec_buf_.resize(nwords);
|
||||||
|
reader_.Read(&vec_buf_[0], nwords * sizeof(s_vpi_vecval));
|
||||||
|
for (size_t i = 0; i < vec_buf_.size(); ++i) {
|
||||||
|
svec_buf_[i].aval = vec_buf_[i].aval;
|
||||||
|
svec_buf_[i].bval = vec_buf_[i].bval;
|
||||||
|
}
|
||||||
|
s_vpi_value value_s;
|
||||||
|
value_s.format = vpiVectorVal;
|
||||||
|
value_s.value.vector = &svec_buf_[0];
|
||||||
|
vpi_put_value(h, &value_s, 0, vpiNoDelay);
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kYield: {
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case kShutDown : {
|
||||||
|
writer_.Write(kSuccess);
|
||||||
|
vpi_control(vpiFinish, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create a new FSM from ENV.
|
||||||
|
static IPCClient* Create() {
|
||||||
|
const char* d_read = getenv("TVM_DREAD_PIPE");
|
||||||
|
const char* d_write = getenv("TVM_DWRITE_PIPE");
|
||||||
|
const char* h_read = getenv("TVM_HREAD_PIPE");
|
||||||
|
const char* h_write = getenv("TVM_HWRITE_PIPE");
|
||||||
|
if (d_write == nullptr ||
|
||||||
|
d_read == nullptr ||
|
||||||
|
h_read == nullptr ||
|
||||||
|
h_write == nullptr) {
|
||||||
|
vpi_printf("ERROR: need environment var TVM_READ_PIPE, TVM_WRITE_PIPE\n");
|
||||||
|
vpi_control(vpiFinish, 1);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// close host side pipe.
|
||||||
|
common::Pipe(atoi(h_read)).Close();
|
||||||
|
common::Pipe(atoi(h_write)).Close();
|
||||||
|
IPCClient* client = new IPCClient(atoi(d_read), atoi(d_write));
|
||||||
|
client->Init();
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
// Get integer from handle.
|
||||||
|
static int GetInt(vpiHandle h) {
|
||||||
|
s_vpi_value value_s;
|
||||||
|
value_s.format = vpiIntVal;
|
||||||
|
vpi_get_value(h, &value_s);
|
||||||
|
return value_s.value.integer;
|
||||||
|
}
|
||||||
|
// Put integer into handle.
|
||||||
|
static void PutInt(vpiHandle h, int value) {
|
||||||
|
s_vpi_value value_s;
|
||||||
|
value_s.format = vpiIntVal;
|
||||||
|
value_s.value.integer = value;
|
||||||
|
vpi_put_value(h, &value_s, 0, vpiNoDelay);
|
||||||
|
}
|
||||||
|
// Handles
|
||||||
|
vpiHandle clock_;
|
||||||
|
// the communicator
|
||||||
|
common::Pipe reader_, writer_;
|
||||||
|
// data buf
|
||||||
|
std::vector<VPIVecVal> vec_buf_;
|
||||||
|
std::vector<s_vpi_vecval> svec_buf_;
|
||||||
|
};
|
||||||
|
} // namespace vpi
|
||||||
|
} // namespace tvm
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
static PLI_INT32 tvm_host_clock_cb(p_cb_data cb_data) {
|
||||||
|
return reinterpret_cast<tvm::vpi::IPCClient*>(
|
||||||
|
cb_data->user_data)->Callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
static PLI_INT32 tvm_init(char* cb) {
|
||||||
|
s_vpi_value value_s;
|
||||||
|
s_vpi_time time_s;
|
||||||
|
s_cb_data cb_data_s;
|
||||||
|
tvm::vpi::IPCClient* client = tvm::vpi::IPCClient::Create();
|
||||||
|
if (client) {
|
||||||
|
cb_data_s.user_data = reinterpret_cast<char*>(client);
|
||||||
|
cb_data_s.reason = cbValueChange;
|
||||||
|
cb_data_s.cb_rtn = tvm_host_clock_cb;
|
||||||
|
cb_data_s.time = &time_s;
|
||||||
|
cb_data_s.value = &value_s;
|
||||||
|
time_s.type = vpiSuppressTime;
|
||||||
|
value_s.format = vpiIntVal;
|
||||||
|
cb_data_s.obj = client->clock_;
|
||||||
|
vpi_register_cb(&cb_data_s);
|
||||||
|
} else {
|
||||||
|
vpi_printf("ERROR: canot initalize host\n");
|
||||||
|
vpi_control(vpiFinish, 1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tvm_vpi_register() {
|
||||||
|
s_vpi_systf_data tf_data;
|
||||||
|
tf_data.type = vpiSysTask;
|
||||||
|
tf_data.tfname = "$tvm_session";
|
||||||
|
tf_data.calltf = tvm_init;
|
||||||
|
tf_data.compiletf = nullptr;
|
||||||
|
tf_data.sizetf = nullptr;
|
||||||
|
tf_data.user_data = nullptr;
|
||||||
|
vpi_register_systf(&tf_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void (*vlog_startup_routines[])() = {
|
||||||
|
tvm_vpi_register,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
} // extern "C"
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2017 by Contributors
|
||||||
|
* \file tvm_vpi.h
|
||||||
|
* \brief Messages passed around VPI used for simulation.
|
||||||
|
*/
|
||||||
|
#ifndef VERILOG_TVM_VPI_H_
|
||||||
|
#define VERILOG_TVM_VPI_H_
|
||||||
|
|
||||||
|
namespace tvm {
|
||||||
|
namespace vpi {
|
||||||
|
|
||||||
|
enum VPICallCode : int {
|
||||||
|
kGetHandleByName,
|
||||||
|
kGetHandleByIndex,
|
||||||
|
kGetName,
|
||||||
|
kGetInt32,
|
||||||
|
kPutInt32,
|
||||||
|
kGetSize,
|
||||||
|
kGetVec,
|
||||||
|
kPutVec,
|
||||||
|
kYield,
|
||||||
|
kShutDown
|
||||||
|
};
|
||||||
|
|
||||||
|
enum VPIReturnCode : int {
|
||||||
|
kPosEdgeTrigger = 0,
|
||||||
|
kSuccess = 1,
|
||||||
|
kFail = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief The vector value used in trasmission */
|
||||||
|
struct VPIVecVal {
|
||||||
|
int aval;
|
||||||
|
int bval;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! \brief User facing vpi handle. */
|
||||||
|
typedef void* VPIRawHandle;
|
||||||
|
|
||||||
|
} // namespace vpi
|
||||||
|
} // namespace tvm
|
||||||
|
#endif // VERILOG_TVM_VPI_H_
|
|
@ -0,0 +1,10 @@
|
||||||
|
VPI_CFLAGS=`iverilog-vpi --cflags`
|
||||||
|
VPI_LDLAGS=`iverilog-vpi --ldlags`
|
||||||
|
|
||||||
|
VER_SRCS = $(wildcard verilog/*.v)
|
||||||
|
|
||||||
|
VER_LIBS=lib/tvm_vpi.vpi
|
||||||
|
|
||||||
|
lib/tvm_vpi.vpi: verilog/tvm_vpi.cc verilog/tvm_vpi.h
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
$(CXX) $(VPI_CFLAGS) $(CFLAGS) -shared -o $@ $(filter %.cc, $^) $(LDFLAGS) $(VPI_LDFLAGS)
|
Загрузка…
Ссылка в новой задаче