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:
- 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:
......
......@@ -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
......
Subproject commit 53751c3da2999d841f4f952639bd766505b51d84
Subproject commit c2871f5db50830f5278ff6e323e8e51a6d5516dd
......@@ -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;
};
/*!
......
"""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.
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
......
/*!
* 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
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
......
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