diff --git a/.travis.yml b/.travis.yml index 3b967d8aac2c..f5a0b4a3c170 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,12 +37,14 @@ addons: - python3-dev - python3-nose - graphviz + - bison + - flex before_install: - - source dmlc-core/scripts/travis/travis_setup_env.sh - export PYTHONPATH=${PYTHONPATH}:${PWD}/python install: + - source dmlc-core/scripts/travis/travis_setup_env.sh - source tests/travis/setup.sh script: diff --git a/Makefile b/Makefile index d3d707eb25a5..19af20faba52 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ endif include $(config) # 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 @@ -79,6 +79,9 @@ include tests/cpp/unittest.mk test: $(TEST) +include verilog/verilog.mk +verilog: $(VER_LIBS) + build/%.o: src/%.cc @mkdir -p $(@D) $(CXX) $(CFLAGS) -MM -MT build/$*.o $< >build/$*.d @@ -92,6 +95,8 @@ lib/libtvm_runtime.so: $(RUNTIME_DEP) @mkdir -p $(@D) $(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS) + + lib/libtvm.a: $(ALL_DEP) @mkdir -p $(@D) ar crv $@ $(filter %.o, $?) @@ -102,7 +107,7 @@ LIBHALIDEIR: + cd HalideIR; make lib/libHalideIR.a ; cd $(ROOTDIR) cpplint: - python2 dmlc-core/scripts/lint.py tvm cpp include src + python2 dmlc-core/scripts/lint.py tvm cpp include src verilog pylint: pylint python/tvm --rcfile=$(ROOTDIR)/tests/lint/pylintrc diff --git a/dmlc-core b/dmlc-core index 53751c3da299..c2871f5db508 160000 --- a/dmlc-core +++ b/dmlc-core @@ -1 +1 @@ -Subproject commit 53751c3da2999d841f4f952639bd766505b51d84 +Subproject commit c2871f5db50830f5278ff6e323e8e51a6d5516dd diff --git a/include/tvm/channel.h b/include/tvm/channel.h index 81f9e5248677..0e7c4edb2deb 100644 --- a/include/tvm/channel.h +++ b/include/tvm/channel.h @@ -23,6 +23,8 @@ class Channel : public NodeRef { * \return the pointer to the internal node container */ inline const ChannelNode* operator->() const; + // The container type + using ContainerType = ChannelNode; }; /*! diff --git a/python/tvm/addon/verilog.py b/python/tvm/addon/verilog.py new file mode 100644 index 000000000000..1321c966e2fd --- /dev/null +++ b/python/tvm/addon/verilog.py @@ -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 diff --git a/src/README.md b/src/README.md index 7455bb2987f1..6567cc840bb8 100644 --- a/src/README.md +++ b/src/README.md @@ -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. The current code modules in src. +- common Internal common utilities. - api API function registration - lang The definition of DSL related data structure - arithmetic Arithmetic expression and set simplification diff --git a/src/codegen/verilog/vpi_session.cc b/src/codegen/verilog/vpi_session.cc new file mode 100644 index 000000000000..14cc815debd1 --- /dev/null +++ b/src/codegen/verilog/vpi_session.cc @@ -0,0 +1,242 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file vpi_session.cc + * \brief IPC session call to verilog simulator via VPI. + */ +#include +#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 n = + std::make_shared(); + 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(node_.get()); +} +inline VPIHandleNode* VPIHandle::get() const { + return static_cast(node_.get()); +} + +VPISession VPISession::make(int h_pipe_read, int h_pipe_write) { + std::shared_ptr n = std::make_shared( + 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& 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* 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 diff --git a/src/codegen/verilog/vpi_session.h b/src/codegen/verilog/vpi_session.h new file mode 100644 index 000000000000..bc648adf7cba --- /dev/null +++ b/src/codegen/verilog/vpi_session.h @@ -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 +#include +#include +#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 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 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& vec) const; + /*! + * \brief Get byte vector from handle. + * \param vec The result data container. + */ + void get_vec(std::vector* vec) const; + // Internal methods + using ContainerType = VPIHandleNode; + + private: + inline VPIHandleNode* get() const; +}; +} // namespace codegen +} // namespace tvm +#endif // TVM_CODEGEN_VERILOG_VPI_SESSION_H_ diff --git a/src/common/pipe.h b/src/common/pipe.h new file mode 100644 index 000000000000..fce764509d68 --- /dev/null +++ b/src/common/pipe.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 +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#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(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(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(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(ptr), + &nwrite, nullptr) && + static_cast(nwrite) == size) + << "Write Error: " << GetLastError(); +#else + ssize_t nwrite; + nwrite = write(handle_, ptr, size); + CHECK_EQ(static_cast(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_ diff --git a/tests/travis/packages.mk b/tests/travis/packages.mk new file mode 100644 index 000000000000..81056517d791 --- /dev/null +++ b/tests/travis/packages.mk @@ -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 diff --git a/tests/travis/run_test.sh b/tests/travis/run_test.sh index 0c130276f21b..755fcc2f162e 100755 --- a/tests/travis/run_test.sh +++ b/tests/travis/run_test.sh @@ -30,6 +30,16 @@ else echo "USE_OPENCL=0" >> config.mk 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 make -f dmlc-core/scripts/packages.mk gtest make test || exit -1 diff --git a/tests/verilog/test_counter.py b/tests/verilog/test_counter.py new file mode 100644 index 000000000000..795b2d691a24 --- /dev/null +++ b/tests/verilog/test_counter.py @@ -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() diff --git a/tests/verilog/test_counter.v b/tests/verilog/test_counter.v new file mode 100644 index 000000000000..e36d2cfd64bf --- /dev/null +++ b/tests/verilog/test_counter.v @@ -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 diff --git a/tests/verilog/test_loop.py b/tests/verilog/test_loop.py new file mode 100644 index 000000000000..2ba19f771cc4 --- /dev/null +++ b/tests/verilog/test_loop.py @@ -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() diff --git a/tests/verilog/test_loop.v b/tests/verilog/test_loop.v new file mode 100644 index 000000000000..7c807b384eda --- /dev/null +++ b/tests/verilog/test_loop.v @@ -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 diff --git a/verilog/example_counter.v b/verilog/example_counter.v new file mode 100644 index 000000000000..08305a077964 --- /dev/null +++ b/verilog/example_counter.v @@ -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 diff --git a/verilog/tvm_marcos.v b/verilog/tvm_marcos.v new file mode 100644 index 000000000000..a8758d7b61fd --- /dev/null +++ b/verilog/tvm_marcos.v @@ -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 diff --git a/verilog/tvm_vpi.cc b/verilog/tvm_vpi.cc new file mode 100644 index 000000000000..d1a41e0bdd2d --- /dev/null +++ b/verilog/tvm_vpi.cc @@ -0,0 +1,244 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file tvm_vpi.cc + * \brief Messages passed around VPI used for simulation. + */ +#include +#include +#include +#include +#include +#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(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(handle), index); + writer_.Write(kSuccess); + writer_.Write(handle); + break; + } + case kGetName: { + CHECK(reader_.Read(&handle)); + std::string name = vpi_get_str( + vpiFullName, static_cast(handle)); + writer_.Write(kSuccess); + writer_.Write(name); + break; + } + case kGetInt32: { + CHECK(reader_.Read(&handle)); + value = GetInt(static_cast(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(handle), value); + writer_.Write(kSuccess); + break; + } + case kGetSize: { + CHECK(reader_.Read(&handle)); + value = vpi_get(vpiSize, static_cast(handle)); + writer_.Write(kSuccess); + writer_.Write(value); + break; + } + case kGetVec: { + CHECK(reader_.Read(&handle)); + vpiHandle h = static_cast(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(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 vec_buf_; + std::vector svec_buf_; +}; +} // namespace vpi +} // namespace tvm + +extern "C" { +static PLI_INT32 tvm_host_clock_cb(p_cb_data cb_data) { + return reinterpret_cast( + 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(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" diff --git a/verilog/tvm_vpi.h b/verilog/tvm_vpi.h new file mode 100644 index 000000000000..5696438f371d --- /dev/null +++ b/verilog/tvm_vpi.h @@ -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_ diff --git a/verilog/verilog.mk b/verilog/verilog.mk new file mode 100644 index 000000000000..5234e63cc2d5 --- /dev/null +++ b/verilog/verilog.mk @@ -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)