Initial place matrix serialization/deserialization support.

- Adds library for handling capnp files and generic NdMatrix support.

Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9fffea5..78dfaa0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,6 +38,7 @@
 #Allow the user to decide weather to compile the graphics library
 set(VPR_USE_EZGL "auto" CACHE STRING "Specify whether vpr uses the graphics library")
 set_property(CACHE VPR_USE_EZGL PROPERTY STRINGS auto off on)
+option(VTR_ENABLE_CAPNPROTO "Enable capnproto binary serialization support in VPR." ON)
 
 option(WITH_BLIFEXPLORER "Enable build with blifexplorer" OFF)
 option(WITH_LIBRTLNUMBER "Enable build with librtlnumber" OFF)
@@ -276,8 +277,13 @@
 #
 # Set final flags
 #
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARN_FLAGS} ${SANITIZE_FLAGS} ${PROFILING_FLAGS} ${COVERAGE_FLAGS} ${LOGGING_FLAGS} ${COLORED_COMPILE}")
-message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
+separate_arguments(
+    ADDITIONAL_FLAGS UNIX_COMMAND "${SANITIZE_FLAGS} ${PROFILING_FLAGS} ${COVERAGE_FLAGS} ${LOGGING_FLAGS} ${COLORED_COMPILE}"
+    )
+add_compile_options(${ADDITIONAL_FLAGS})
+separate_arguments(
+    WARN_FLAGS UNIX_COMMAND "${WARN_FLAGS}"
+    )
 
 
 #
@@ -331,6 +337,9 @@
 
 #Add the various sub-projects
 add_subdirectory(libs)
+
+# Only add warn flags for VPR internal libraries.
+add_compile_options(${WARN_FLAGS})
 add_subdirectory(vpr)
 add_subdirectory(abc)
 add_subdirectory(ODIN_II)
diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt
index 22ee5a2..f95ac8d 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -1,9 +1,14 @@
-#VTR developed libraries
+#Externally developed libraries
+add_subdirectory(EXTERNAL)
+
+# VTR developed libraries
+#  Only add warn flags for VPR internal libraries.
+add_compile_options(${WARN_FLAGS})
 add_subdirectory(libarchfpga)
 add_subdirectory(libvtrutil)
 add_subdirectory(liblog)
 add_subdirectory(libpugiutil)
 add_subdirectory(librtlnumber)
-
-#Externally developed libraries
-add_subdirectory(EXTERNAL)
+if(${VTR_ENABLE_CAPNPROTO})
+    add_subdirectory(libvtrcapnproto)
+endif()
diff --git a/libs/EXTERNAL/CMakeLists.txt b/libs/EXTERNAL/CMakeLists.txt
index a2118ef..cb96998 100644
--- a/libs/EXTERNAL/CMakeLists.txt
+++ b/libs/EXTERNAL/CMakeLists.txt
@@ -13,3 +13,20 @@
 if(VPR_USE_EZGL STREQUAL "on")
     add_subdirectory(libezgl)
 endif()
+
+if(${VTR_ENABLE_CAPNPROTO})
+    # Override default policy for capnproto (CMake policy version 3.1)
+    # Enable new IPO variables
+    set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
+
+    # Enable option overrides via variables
+    set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
+
+    # Re-enable CXX extensions for capnproto.
+    set(CMAKE_CXX_EXTENSIONS ON)
+
+    # Disable capnproto tests
+    set(BUILD_TESTING OFF)
+
+    add_subdirectory(capnproto EXCLUDE_FROM_ALL)
+endif()
diff --git a/libs/libvtrcapnproto/CMakeLists.txt b/libs/libvtrcapnproto/CMakeLists.txt
new file mode 100644
index 0000000..ccf3b6b
--- /dev/null
+++ b/libs/libvtrcapnproto/CMakeLists.txt
@@ -0,0 +1,39 @@
+if(NOT MSCV)
+    # These flags generate noisy but non-bug warnings when using lib kj,
+    # supress them.
+    set(WARN_FLAGS_TO_DISABLE
+        -Wno-undef
+        -Wno-non-virtual-dtor
+        )
+    foreach(flag ${WARN_FLAGS_TO_DISABLE})
+        CHECK_CXX_COMPILER_FLAG(${flag} CXX_COMPILER_SUPPORTS_${flag})
+        if(CXX_COMPILER_SUPPORTS_${flag})
+            #Flag supported, so enable it
+            add_compile_options(${flag})
+        endif()
+    endforeach()
+endif()
+
+# Create generated headers from capnp schema files
+#
+# Each schema used should appear here.
+capnp_generate_cpp(CAPNP_SRCS CAPNP_HDRS
+    place_delay_model.capnp
+    matrix.capnp
+    )
+
+add_library(libvtrcapnproto STATIC
+            ${CAPNP_SRCS}
+            mmap_file.h
+            mmap_file.cpp
+            serdes_utils.h
+            serdes_utils.cpp
+            )
+target_include_directories(libvtrcapnproto PUBLIC
+    ${CMAKE_CURRENT_SOURCE_DIR}
+    ${CMAKE_CURRENT_BINARY_DIR}
+    )
+target_link_libraries(libvtrcapnproto
+    libvtrutil
+    CapnProto::capnp
+    )
diff --git a/libs/libvtrcapnproto/README.md b/libs/libvtrcapnproto/README.md
new file mode 100644
index 0000000..38187e0
--- /dev/null
+++ b/libs/libvtrcapnproto/README.md
@@ -0,0 +1,84 @@
+Capnproto usage in VTR
+======================
+
+Capnproto is a data serialization framework designed for portabliity and speed.
+In VPR, capnproto is used to provide binary formats for internal data
+structures that can be computed once, and used many times.  Specific examples:
+ - rrgraph
+ - Router lookahead data
+ - Place matrix delay estimates
+
+What is capnproto?
+==================
+
+capnproto can be broken down into 3 parts:
+ - A schema language
+ - A code generator
+ - A library
+
+The schema language is used to define messages.  Each message must have an
+explcit capnproto schema, which are stored in files suffixed with ".capnp".
+The capnproto documentation for how to write these schema files can be found
+here: https://capnproto.org/language.html
+
+The schema by itself is not especially useful.  In order to read and write
+messages defined by the schema in a target language (e.g. C++), a code
+generation step is required.  Capnproto provides a cmake function for this
+purpose, `capnp_generate_cpp`.  This generates C++ source and header files.
+These source and header files combined with the capnproto C++ library, enables
+C++ code to read and write the messages matching a particular schema.  The C++
+library API can be found here: https://capnproto.org/cxx.html
+
+Contents of libvtrcapnproto
+===========================
+
+libvtrcapnproto should contain two elements:
+ - Utilities for working capnproto messages in VTR
+ - Generate source and header files of all capnproto messages used in VTR
+
+I/O Utilities
+-------------
+
+Capnproto does not provide IO support, instead it works from arrays (or file
+descriptors).  To avoid re-writing this code, libvtrcapnproto provides two
+utilities that should be used whenever reading or writing capnproto message to
+disk:
+ - `serdes_utils.h` provides the writeMessageToFile function - Writes a
+   capnproto message to disk.
+ - `mmap_file.h` provides MmapFile object - Maps a capnproto message from the
+   disk as a flat array.
+
+NdMatrix Utilities
+------------------
+
+A common datatype which appears in many data structures that VPR might want to
+serialize is the generic type `vtr::NdMatrix`.  `ndmatrix_serdes.h` provides
+generic functions ToNdMatrix and FromNdMatrix, which can be used to generically
+convert between the provideid capnproto message `Matrix` and `vtr::NdMatrix`.
+
+Capnproto schemas
+-----------------
+
+libvtrcapnproto should contain all capnproto schema definitions used within
+VTR.  To add a new schema:
+1. Add the schema to git in `libs/libvtrcapnproto/`
+2. Add the schema file name to `capnp_generate_cpp` invocation in
+   `libs/libvtrcapnproto/CMakeLists.txt`.
+
+The schema will be available in the header file `schema filename>.h`.  The
+actual header file will appear in the CMake build directory
+`libs/libvtrcapnproto` after `libvtrcapnproto` has been rebuilt.
+
+Writing capnproto binary files to text
+======================================
+
+The `capnp` tool (found in the CMake build directiory
+`libs/EXTERNAL/capnproto/c++/src/capnp`) can be used to convert from a binary
+capnp message to a textual form.
+
+Example converting VprOverrideDelayModel from binary to text:
+
+```
+capnp convert binary:text place_delay_model.capnp VprOverrideDelayModel \
+  < place_delay.bin > place_delay.txt
+```
diff --git a/libs/libvtrcapnproto/matrix.capnp b/libs/libvtrcapnproto/matrix.capnp
new file mode 100644
index 0000000..14250a4
--- /dev/null
+++ b/libs/libvtrcapnproto/matrix.capnp
@@ -0,0 +1,9 @@
+@0xafffc9c1f309bc00;
+
+struct Matrix(Value) {
+    struct Entry {
+        value @0 :Value;
+    }
+    dims @0 :List(Int64);
+    data @1 :List(Entry);
+}
diff --git a/libs/libvtrcapnproto/mmap_file.cpp b/libs/libvtrcapnproto/mmap_file.cpp
new file mode 100644
index 0000000..57d29b5
--- /dev/null
+++ b/libs/libvtrcapnproto/mmap_file.cpp
@@ -0,0 +1,37 @@
+#include "mmap_file.h"
+#include "vtr_error.h"
+#include "vtr_util.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "kj/filesystem.h"
+
+MmapFile::MmapFile(const std::string& file)
+    : size_(0) {
+    try {
+        auto fs = kj::newDiskFilesystem();
+        auto path = fs->getCurrentPath().evalNative(file);
+
+        const auto& dir = fs->getRoot();
+        auto stat = dir.lstat(path);
+        auto f = dir.openFile(path);
+        size_ = stat.size;
+        data_ = f->mmap(0, stat.size);
+    } catch (kj::Exception& e) {
+        throw vtr::VtrError(e.getDescription().cStr(), e.getFile(), e.getLine());
+    }
+}
+
+const kj::ArrayPtr<const ::capnp::word> MmapFile::getData() const {
+    if ((size_ % sizeof(::capnp::word)) != 0) {
+        throw vtr::VtrError(
+            vtr::string_fmt("size_ %d is not a multiple of capnp::word", size_),
+            __FILE__, __LINE__);
+    }
+
+    return kj::arrayPtr(reinterpret_cast<const ::capnp::word*>(data_.begin()),
+                        size_ / sizeof(::capnp::word));
+}
diff --git a/libs/libvtrcapnproto/mmap_file.h b/libs/libvtrcapnproto/mmap_file.h
new file mode 100644
index 0000000..ee1a93e
--- /dev/null
+++ b/libs/libvtrcapnproto/mmap_file.h
@@ -0,0 +1,19 @@
+#ifndef MMAP_FILE_H_
+#define MMAP_FILE_H_
+
+#include <string>
+#include "capnp/message.h"
+#include "kj/array.h"
+
+// Platform independent mmap, useful for reading large capnp's.
+class MmapFile {
+  public:
+    explicit MmapFile(const std::string& file);
+    const kj::ArrayPtr<const ::capnp::word> getData() const;
+
+  private:
+    size_t size_;
+    kj::Array<const kj::byte> data_;
+};
+
+#endif /* MMAP_FILE_H_ */
diff --git a/libs/libvtrcapnproto/ndmatrix_serdes.h b/libs/libvtrcapnproto/ndmatrix_serdes.h
new file mode 100644
index 0000000..5525c0b
--- /dev/null
+++ b/libs/libvtrcapnproto/ndmatrix_serdes.h
@@ -0,0 +1,87 @@
+#ifndef NDMATRIX_SERDES_H_
+#define NDMATRIX_SERDES_H_
+
+#include <functional>
+#include "vtr_ndmatrix.h"
+#include "vpr_error.h"
+#include "matrix.capnp.h"
+
+// Generic function to convert from Matrix capnproto message to vtr::NdMatrix.
+//
+// Template arguments:
+//  N = Number of matrix dimensions, must be fixed.
+//  CapType = Source capnproto message type that is a single element the
+//            Matrix capnproto message.
+//  CType = Target C++ type that is a single element of vtr::NdMatrix.
+//
+// Arguments:
+//  m_out = Target vtr::NdMatrix.
+//  m_in = Source capnproto message reader.
+//  copy_fun = Function to convert from CapType to CType.
+//
+template<size_t N, typename CapType, typename CType>
+void ToNdMatrix(
+    vtr::NdMatrix<CType, N>* m_out,
+    const typename Matrix<CapType>::Reader& m_in,
+    const std::function<void(CType*, const typename CapType::Reader&)>& copy_fun) {
+    const auto& dims = m_in.getDims();
+    if (N != dims.size()) {
+        VPR_THROW(VPR_ERROR_OTHER,
+                  "Wrong dimension of template N = %zu, m_in.getDims() = %zu",
+                  N, dims.size());
+    }
+
+    std::array<size_t, N> dim_sizes;
+    size_t required_elements = 1;
+    for (size_t i = 0; i < N; ++i) {
+        dim_sizes[i] = dims[i];
+        required_elements *= dims[i];
+    }
+    m_out->resize(dim_sizes);
+
+    const auto& data = m_in.getData();
+    if (data.size() != required_elements) {
+        VPR_THROW(VPR_ERROR_OTHER,
+                  "Wrong number of elements, expected %zu, actual %zu",
+                  required_elements, data.size());
+    }
+
+    for (size_t i = 0; i < required_elements; ++i) {
+        copy_fun(&m_out->get(i), data[i].getValue());
+    }
+}
+
+// Generic function to convert from vtr::NdMatrix to Matrix capnproto message.
+//
+// Template arguments:
+//  N = Number of matrix dimensions, must be fixed.
+//  CapType = Target capnproto message type that is a single element the
+//            Matrix capnproto message.
+//  CType = Source C++ type that is a single element of vtr::NdMatrix.
+//
+// Arguments:
+//  m_out = Target capnproto message builder.
+//  m_in = Source vtr::NdMatrix.
+//  copy_fun = Function to convert from CType to CapType.
+//
+template<size_t N, typename CapType, typename CType>
+void FromNdMatrix(
+    typename Matrix<CapType>::Builder* m_out,
+    const vtr::NdMatrix<CType, N>& m_in,
+    const std::function<void(typename CapType::Builder*, const CType&)>& copy_fun) {
+    size_t elements = 1;
+    auto dims = m_out->initDims(N);
+    for (size_t i = 0; i < N; ++i) {
+        dims.set(i, m_in.dim_size(i));
+        elements *= dims[i];
+    }
+
+    auto data = m_out->initData(elements);
+
+    for (size_t i = 0; i < elements; ++i) {
+        auto elem = data[i].getValue();
+        copy_fun(&elem, m_in.get(i));
+    }
+}
+
+#endif /* NDMATRIX_SERDES_H_ */
diff --git a/libs/libvtrcapnproto/place_delay_model.capnp b/libs/libvtrcapnproto/place_delay_model.capnp
new file mode 100644
index 0000000..df2a674
--- /dev/null
+++ b/libs/libvtrcapnproto/place_delay_model.capnp
@@ -0,0 +1,26 @@
+@0xfee98b707b92aa09;
+
+using Matrix = import "matrix.capnp";
+
+struct VprFloatEntry {
+    value @0 :Float32;
+}
+struct VprDeltaDelayModel {
+    delays @0 :Matrix.Matrix(VprFloatEntry);
+}
+
+struct VprOverrideEntry {
+    fromType @0 :Int16;
+    toType @1 :Int16;
+    fromClass @2 :Int16;
+    toClass @3 :Int16;
+    deltaX @4 :Int16;
+    deltaY @5 :Int16;
+
+    delay @6 :Float32;
+}
+
+struct VprOverrideDelayModel {
+    delays @0 :Matrix.Matrix(VprFloatEntry);
+    delayOverrides @1 :List(VprOverrideEntry);
+}
diff --git a/libs/libvtrcapnproto/serdes_utils.cpp b/libs/libvtrcapnproto/serdes_utils.cpp
new file mode 100644
index 0000000..2201fa5
--- /dev/null
+++ b/libs/libvtrcapnproto/serdes_utils.cpp
@@ -0,0 +1,22 @@
+#include "serdes_utils.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "vtr_error.h"
+#include "kj/filesystem.h"
+
+void writeMessageToFile(const std::string& file, ::capnp::MessageBuilder* builder) {
+    try {
+        auto fs = kj::newDiskFilesystem();
+        auto path = fs->getCurrentPath().evalNative(file);
+
+        const auto& dir = fs->getRoot();
+        auto f = dir.openFile(path, kj::WriteMode::CREATE | kj::WriteMode::MODIFY);
+        f->truncate(0);
+        auto f_app = kj::newFileAppender(std::move(f));
+        capnp::writeMessage(*f_app, *builder);
+    } catch (kj::Exception& e) {
+        throw vtr::VtrError(e.getDescription().cStr(), e.getFile(), e.getLine());
+    }
+}
diff --git a/libs/libvtrcapnproto/serdes_utils.h b/libs/libvtrcapnproto/serdes_utils.h
new file mode 100644
index 0000000..55e3299
--- /dev/null
+++ b/libs/libvtrcapnproto/serdes_utils.h
@@ -0,0 +1,11 @@
+#ifndef SERDES_UTILS_H_
+#define SERDES_UTILS_H_
+
+#include <string>
+#include "capnp/serialize.h"
+
+// Platform indepedent way to file message to a file on disk.
+void writeMessageToFile(const std::string& file,
+        ::capnp::MessageBuilder* builder);
+
+#endif /* SERDES_UTILS_H_ */
diff --git a/vpr/CMakeLists.txt b/vpr/CMakeLists.txt
index 5c99091..df5a7eb 100644
--- a/vpr/CMakeLists.txt
+++ b/vpr/CMakeLists.txt
@@ -41,6 +41,10 @@
 file(GLOB_RECURSE LIB_HEADERS src/*/*.h)
 files_to_dirs(LIB_HEADERS LIB_INCLUDE_DIRS)
 
+if(${VTR_ENABLE_CAPNPROTO})
+    add_definitions("-DVTR_ENABLE_CAPNPROTO")
+endif()
+
 #Create the library
 add_library(libvpr STATIC
              ${LIB_HEADERS}
@@ -98,6 +102,10 @@
 
 target_compile_definitions(libvpr PUBLIC ${GRAPHICS_DEFINES})
 
+if(${VTR_ENABLE_CAPNPROTO})
+    target_link_libraries(libvpr libvtrcapnproto)
+endif()
+
 add_executable(vpr ${EXEC_SOURCES})
 
 target_link_libraries(vpr libvpr)
diff --git a/vpr/src/place/place_delay_model.cpp b/vpr/src/place/place_delay_model.cpp
index ee015eb..9756c45 100644
--- a/vpr/src/place/place_delay_model.cpp
+++ b/vpr/src/place/place_delay_model.cpp
@@ -8,6 +8,15 @@
 
 #include "vtr_log.h"
 #include "vtr_math.h"
+#include "vpr_error.h"
+
+#ifdef VTR_ENABLE_CAPNPROTO
+#    include "capnp/serialize.h"
+#    include "place_delay_model.capnp.h"
+#    include "ndmatrix_serdes.h"
+#    include "mmap_file.h"
+#    include "serdes_utils.h"
+#endif /* VTR_ENABLE_CAPNPROTO */
 
 /*
  * DeltaDelayModel
@@ -110,3 +119,178 @@
 
     vtr::fclose(f);
 }
+
+float OverrideDelayModel::get_delay_override(int from_type, int from_class, int to_type, int to_class, int delta_x, int delta_y) const {
+    t_override key;
+    key.from_type = from_type;
+    key.from_class = from_class;
+    key.to_type = to_type;
+    key.to_class = to_class;
+    key.delta_x = delta_x;
+    key.delta_y = delta_y;
+
+    auto iter = delay_overrides_.find(key);
+    if (iter == delay_overrides_.end()) {
+        VPR_THROW(VPR_ERROR_PLACE, "Key not found.");
+    }
+    return iter->second;
+}
+
+const DeltaDelayModel* OverrideDelayModel::base_delay_model() const {
+    return base_delay_model_.get();
+}
+
+void OverrideDelayModel::set_base_delay_model(std::unique_ptr<DeltaDelayModel> base_delay_model) {
+    base_delay_model_ = std::move(base_delay_model);
+}
+
+// When writing capnp targetted serialization, always allow compilation when
+// VTR_ENABLE_CAPNPROTO=OFF.  Generally this means throwing an exception
+// instead.
+//
+#ifndef VTR_ENABLE_CAPNPROTO
+
+#    define DISABLE_ERROR                              \
+        "is disable because VTR_ENABLE_CAPNPROTO=OFF." \
+        "Re-compile with CMake option VTR_ENABLE_CAPNPROTO=ON to enable."
+
+void DeltaDelayModel::read(const std::string& /*file*/) {
+    VPR_THROW(VPR_ERROR_PLACE, "DeltaDelayModel::read " DISABLE_ERROR);
+}
+
+void DeltaDelayModel::write(const std::string& /*file*/) const {
+    VPR_THROW(VPR_ERROR_PLACE, "DeltaDelayModel::write " DISABLE_ERROR);
+}
+
+void OverrideDelayModel::read(const std::string& /*file*/) {
+    VPR_THROW(VPR_ERROR_PLACE, "OverrideDelayModel::read " DISABLE_ERROR);
+}
+
+void OverrideDelayModel::write(const std::string& /*file*/) const {
+    VPR_THROW(VPR_ERROR_PLACE, "OverrideDelayModel::write " DISABLE_ERROR);
+}
+
+#else /* VTR_ENABLE_CAPNPROTO */
+
+static void ToFloat(float* out, const VprFloatEntry::Reader& in) {
+    // Getting a scalar field is always "get<field name>()".
+    *out = in.getValue();
+}
+
+static void FromFloat(VprFloatEntry::Builder* out, const float& in) {
+    // Setting a scalar field is always "set<field name>(value)".
+    out->setValue(in);
+}
+
+void DeltaDelayModel::read(const std::string& file) {
+    // MmapFile object creates an mmap of the specified path, and will munmap
+    // when the object leaves scope.
+    MmapFile f(file);
+
+    // FlatArrayMessageReader is used to read the message from the data array
+    // provided by MmapFile.
+    ::capnp::FlatArrayMessageReader reader(f.getData());
+
+    // When reading capnproto files the Reader object to use is named
+    // <schema name>::Reader.
+    //
+    // Initially this object is an empty VprDeltaDelayModel.
+    VprDeltaDelayModel::Reader model;
+
+    // The reader.getRoot performs a cast from the generic capnproto to fit
+    // with the specified schema.
+    //
+    // Note that capnproto does not validate that the incoming data matches the
+    // schema.  If this property is required, some form of check would be
+    // required.
+    model = reader.getRoot<VprDeltaDelayModel>();
+
+    // ToNdMatrix is a generic function for converting a Matrix capnproto
+    // to a vtr::NdMatrix.
+    //
+    // The use must supply the matrix dimension (2 in this case), the source
+    // capnproto type (VprFloatEntry),
+    // target C++ type (flat), and a function to convert from the source capnproto
+    // type to the target C++ type (ToFloat).
+    //
+    // The second argument should be of type Matrix<X>::Reader where X is the
+    // capnproto element type.
+    ToNdMatrix<2, VprFloatEntry, float>(&delays_, model.getDelays(), ToFloat);
+}
+
+void DeltaDelayModel::write(const std::string& file) const {
+    // MallocMessageBuilder object is the generate capnproto message builder,
+    // using malloc for buffer allocation.
+    ::capnp::MallocMessageBuilder builder;
+
+    // initRoot<X> returns a X::Builder object that can be used to set the
+    // fields in the message.
+    auto model = builder.initRoot<VprDeltaDelayModel>();
+
+    // FromNdMatrix is a generic function for converting a vtr::NdMatrix to a
+    // Matrix message.  It is the mirror function of ToNdMatrix described in
+    // read above.
+    auto delays = model.getDelays();
+    FromNdMatrix<2, VprFloatEntry, float>(&delays, delays_, FromFloat);
+
+    // writeMessageToFile writes message to the specified file.
+    writeMessageToFile(file, &builder);
+}
+
+void OverrideDelayModel::read(const std::string& file) {
+    MmapFile f(file);
+    ::capnp::FlatArrayMessageReader reader(f.getData());
+
+    vtr::Matrix<float> delays;
+    auto model = reader.getRoot<VprOverrideDelayModel>();
+    ToNdMatrix<2, VprFloatEntry, float>(&delays, model.getDelays(), ToFloat);
+
+    base_delay_model_ = std::make_unique<DeltaDelayModel>(delays);
+
+    // Reading non-scalar capnproto fields is roughly equivilant to using
+    // a std::vector of the field type.  Actual type is capnp::List<X>::Reader.
+    auto overrides = model.getDelayOverrides();
+    std::vector<std::pair<t_override, float> > overrides_arr(overrides.size());
+    for (size_t i = 0; i < overrides.size(); ++i) {
+        const auto& elem = overrides[i];
+        overrides_arr[i].first.from_type = elem.getFromType();
+        overrides_arr[i].first.to_type = elem.getToType();
+        overrides_arr[i].first.from_class = elem.getFromClass();
+        overrides_arr[i].first.to_class = elem.getToClass();
+        overrides_arr[i].first.delta_x = elem.getDeltaX();
+        overrides_arr[i].first.delta_y = elem.getDeltaY();
+
+        overrides_arr[i].second = elem.getDelay();
+    }
+
+    delay_overrides_ = vtr::make_flat_map2(std::move(overrides_arr));
+}
+
+void OverrideDelayModel::write(const std::string& file) const {
+    ::capnp::MallocMessageBuilder builder;
+    auto model = builder.initRoot<VprOverrideDelayModel>();
+
+    auto delays = model.getDelays();
+    FromNdMatrix<2, VprFloatEntry, float>(&delays, base_delay_model_->delays(), FromFloat);
+
+    // Non-scalar capnproto fields should be first initialized with
+    // init<field  name>(count), and then accessed from the returned
+    // std::vector-like Builder object (specifically capnp::List<X>::Builder).
+    auto overrides = model.initDelayOverrides(delay_overrides_.size());
+    auto dst_iter = overrides.begin();
+    for (const auto& src : delay_overrides_) {
+        auto elem = *dst_iter++;
+        elem.setFromType(src.first.from_type);
+        elem.setToType(src.first.to_type);
+        elem.setFromClass(src.first.from_class);
+        elem.setToClass(src.first.to_class);
+        elem.setDeltaX(src.first.delta_x);
+        elem.setDeltaY(src.first.delta_y);
+
+        elem.setDelay(src.second);
+    }
+
+    writeMessageToFile(file, &builder);
+}
+
+#endif
diff --git a/vpr/src/place/place_delay_model.h b/vpr/src/place/place_delay_model.h
index f2412b1..9235c29 100644
--- a/vpr/src/place/place_delay_model.h
+++ b/vpr/src/place/place_delay_model.h
@@ -41,9 +41,8 @@
 class DeltaDelayModel : public PlaceDelayModel {
   public:
     DeltaDelayModel() {}
-    DeltaDelayModel(vtr::Matrix<float> delta_delays, t_router_opts router_opts)
-        : delays_(std::move(delta_delays))
-        , router_opts_(router_opts) {}
+    DeltaDelayModel(vtr::Matrix<float> delta_delays)
+        : delays_(std::move(delta_delays)) {}
 
     void compute(
         const RouterDelayProfiler& router,
@@ -53,16 +52,14 @@
     float delay(int from_x, int from_y, int /*from_pin*/, int to_x, int to_y, int /*to_pin*/) const override;
     void dump_echo(std::string filepath) const override;
 
-    void read(const std::string& /*file*/) override {
-        VPR_THROW(VPR_ERROR_ROUTE, "DeltaDelayModel::read unimplemented");
-    }
-    void write(const std::string& /*file*/) const override {
-        VPR_THROW(VPR_ERROR_ROUTE, "DeltaDelayModel::write unimplemented");
+    void read(const std::string& file) override;
+    void write(const std::string& file) const override;
+    const vtr::Matrix<float>& delays() const {
+        return delays_;
     }
 
   private:
     vtr::Matrix<float> delays_;
-    t_router_opts router_opts_;
 };
 
 class OverrideDelayModel : public PlaceDelayModel {
@@ -75,19 +72,20 @@
     float delay(int from_x, int from_y, int from_pin, int to_x, int to_y, int to_pin) const override;
     void dump_echo(std::string filepath) const override;
 
-    void read(const std::string& /*file*/) override {
-        VPR_THROW(VPR_ERROR_ROUTE, "OverrideDelayModel::read unimplemented");
-    }
-    void write(const std::string& /*file*/) const override {
-        VPR_THROW(VPR_ERROR_ROUTE, "OverrideDelayModel::write unimplemented");
-    }
+    void read(const std::string& file) override;
+    void write(const std::string& file) const override;
 
   public: //Mutators
-  private:
-    std::unique_ptr<PlaceDelayModel> base_delay_model_;
-
+    void set_base_delay_model(std::unique_ptr<DeltaDelayModel> base_delay_model);
+    const DeltaDelayModel* base_delay_model() const;
+    float get_delay_override(int from_type, int from_class, int to_type, int to_class, int delta_x, int delta_y) const;
     void set_delay_override(int from_type, int from_class, int to_type, int to_class, int delta_x, int delta_y, float delay);
-    void compute_override_delay_model(const RouterDelayProfiler& router);
+
+  private:
+    std::unique_ptr<DeltaDelayModel> base_delay_model_;
+
+    void compute_override_delay_model(const RouterDelayProfiler& router,
+                                      const t_router_opts& router_opts);
 
     struct t_override {
         short from_type;
@@ -104,7 +102,6 @@
     };
 
     vtr::flat_map2<t_override, float> delay_overrides_;
-    t_router_opts router_opts_;
 };
 
 #endif
diff --git a/vpr/src/place/timing_place_lookup.cpp b/vpr/src/place/timing_place_lookup.cpp
index c3c4fac..48dee35 100644
--- a/vpr/src/place/timing_place_lookup.cpp
+++ b/vpr/src/place/timing_place_lookup.cpp
@@ -183,7 +183,6 @@
     const t_placer_opts& placer_opts,
     const t_router_opts& router_opts,
     int longest_length) {
-    router_opts_ = router_opts;
     delays_ = compute_delta_delay_model(
         route_profiler,
         placer_opts, router_opts, /*measure_directconnect=*/true,
@@ -195,15 +194,14 @@
     const t_placer_opts& placer_opts,
     const t_router_opts& router_opts,
     int longest_length) {
-    router_opts_ = router_opts;
     auto delays = compute_delta_delay_model(
         route_profiler,
         placer_opts, router_opts, /*measure_directconnect=*/false,
         longest_length);
 
-    base_delay_model_ = std::make_unique<DeltaDelayModel>(delays, router_opts);
+    base_delay_model_ = std::make_unique<DeltaDelayModel>(delays);
 
-    compute_override_delay_model(route_profiler);
+    compute_override_delay_model(route_profiler, router_opts);
 }
 
 /******* File Accessible Functions **********/
@@ -855,8 +853,10 @@
     return true;
 }
 
-void OverrideDelayModel::compute_override_delay_model(const RouterDelayProfiler& route_profiler) {
-    t_router_opts router_opts2 = router_opts_;
+void OverrideDelayModel::compute_override_delay_model(
+    const RouterDelayProfiler& route_profiler,
+    const t_router_opts& router_opts) {
+    t_router_opts router_opts2 = router_opts;
     router_opts2.astar_fac = 0.;
 
     //Look at all the direct connections that exist, and add overrides to delay model
diff --git a/vpr/test/test_place_delay_model_serdes.cpp b/vpr/test/test_place_delay_model_serdes.cpp
new file mode 100644
index 0000000..1db5124
--- /dev/null
+++ b/vpr/test/test_place_delay_model_serdes.cpp
@@ -0,0 +1,87 @@
+#include "catch.hpp"
+
+#include "place_delay_model.h"
+
+namespace {
+
+#ifdef VTR_ENABLE_CAPNPROTO
+static constexpr const char kDeltaDelayBin[] = "test_delta_delay.bin";
+static constexpr const char kOverrideDelayBin[] = "test_override_delay.bin";
+
+TEST_CASE("round_trip_delta_delay_model", "[vpr]") {
+    constexpr size_t kDimX = 10;
+    constexpr size_t kDimY = 10;
+    vtr::Matrix<float> delays;
+    delays.resize({kDimX, kDimY});
+
+    for (size_t x = 0; x < kDimX; ++x) {
+        for (size_t y = 0; y < kDimY; ++y) {
+            delays[x][y] = (x + 1) * (y + 1);
+        }
+    }
+    DeltaDelayModel model(std::move(delays));
+    const auto& delays1 = model.delays();
+
+    model.write(kDeltaDelayBin);
+
+    DeltaDelayModel model2;
+    model2.read(kDeltaDelayBin);
+
+    const auto& delays2 = model2.delays();
+
+    REQUIRE(delays1.size() == delays2.size());
+    REQUIRE(delays1.ndims() == delays2.ndims());
+    for (size_t dim = 0; dim < delays1.ndims(); ++dim) {
+        REQUIRE(delays1.dim_size(dim) == delays2.dim_size(dim));
+    }
+
+    for (size_t x = 0; x < kDimX; ++x) {
+        for (size_t y = 0; y < kDimY; ++y) {
+            CHECK(delays1[x][y] == delays2[x][y]);
+        }
+    }
+}
+
+TEST_CASE("round_trip_override_delay_model", "[vpr]") {
+    constexpr size_t kDimX = 10;
+    constexpr size_t kDimY = 10;
+    vtr::Matrix<float> delays;
+    delays.resize({kDimX, kDimY});
+
+    for (size_t x = 0; x < kDimX; ++x) {
+        for (size_t y = 0; y < kDimY; ++y) {
+            delays[x][y] = (x + 1) * (y + 1);
+        }
+    }
+    OverrideDelayModel model;
+    auto base_model = std::make_unique<DeltaDelayModel>(delays);
+    model.set_base_delay_model(std::move(base_model));
+    model.set_delay_override(1, 2, 3, 4, 5, 6, -1);
+    model.set_delay_override(2, 2, 3, 4, 5, 6, -2);
+
+    model.write(kOverrideDelayBin);
+
+    OverrideDelayModel model2;
+    model2.read(kOverrideDelayBin);
+
+    const auto& delays1 = model.base_delay_model()->delays();
+    const auto& delays2 = model2.base_delay_model()->delays();
+
+    REQUIRE(delays1.size() == delays2.size());
+    REQUIRE(delays1.ndims() == delays2.ndims());
+    for (size_t dim = 0; dim < delays1.ndims(); ++dim) {
+        REQUIRE(delays1.dim_size(dim) == delays2.dim_size(dim));
+    }
+
+    for (size_t x = 0; x < kDimX; ++x) {
+        for (size_t y = 0; y < kDimY; ++y) {
+            CHECK(delays1[x][y] == delays2[x][y]);
+        }
+    }
+
+    CHECK(model2.get_delay_override(1, 2, 3, 4, 5, 6) == -1);
+    CHECK(model2.get_delay_override(2, 2, 3, 4, 5, 6) == -2);
+}
+#endif
+
+} // namespace