Commit 9e660dbe by Tianqi Chen Committed by GitHub

[VERILOG] Basic Verilog Testflow (#70)

* [VERILOG] Basic Verilog Testflow

* fix build

* fix the comment

* fix lint in verilog
parent 2548cedc
...@@ -37,12 +37,14 @@ addons: ...@@ -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:
......
...@@ -11,7 +11,7 @@ endif ...@@ -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 ...@@ -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) ...@@ -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: ...@@ -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
......
Subproject commit 53751c3da2999d841f4f952639bd766505b51d84 Subproject commit c2871f5db50830f5278ff6e323e8e51a6d5516dd
...@@ -23,6 +23,8 @@ class Channel : public NodeRef { ...@@ -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;
}; };
/*! /*!
......
"""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. ...@@ -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
......
/*!
* 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
/*!
* 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_
/*!
* 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_
# 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 ...@@ -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
......
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()
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
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()
`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
// 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
// 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
/*!
* 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"
/*!
* 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_
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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment