Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 105 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.28.0)
cmake_minimum_required(VERSION 3.20...3.28)

project(hex-processor)

Expand Down Expand Up @@ -30,13 +30,88 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
option(USE_VERILATOR "Use Verilator for simulation" ON)
option(BUILD_DOCS "Create and install HTML documentation" OFF)

# Verilator
# Verilator. Prefer a sufficiently recent system install; otherwise fetch and
# build a pinned version from source (needs host tools: autoconf, flex, bison).
if(USE_VERILATOR)
find_package(verilator HINTS $ENV{VERILATOR_ROOT} ${VERILATOR_ROOT})
if(NOT verilator_FOUND)
message(FATAL_ERROR "Verilator was not found.")
set(VERILATOR_GIT_TAG "v5.048"
CACHE STRING "Verilator version to fetch/build when no system install is suitable")
set(VERILATOR_MIN_VERSION "5.0"
CACHE STRING "Minimum acceptable system Verilator version")

# First try a system install (honouring VERILATOR_ROOT). The version floor
# rejects too-old installs (e.g. 4.x lacks the VerilatedContext API hextb
# uses), so the source build below takes over.
find_package(verilator ${VERILATOR_MIN_VERSION} QUIET
HINTS $ENV{VERILATOR_ROOT} ${VERILATOR_ROOT})

if(verilator_FOUND)
message(STATUS "Using system Verilator ${verilator_VERSION} (>= ${VERILATOR_MIN_VERSION})")
else()
set(VERILATOR_INSTALL_DIR "${CMAKE_BINARY_DIR}/verilator")
set(VERILATOR_ROOT "${VERILATOR_INSTALL_DIR}/share/verilator")

# Build once; subsequent configures reuse the installed tree.
if(NOT EXISTS "${VERILATOR_ROOT}/verilator-config.cmake")
message(STATUS "No system Verilator >= ${VERILATOR_MIN_VERSION}; "
"fetching and building ${VERILATOR_GIT_TAG} "
"(one-time, needs autoconf/flex/bison)...")
FetchContent_Declare(
verilator
GIT_REPOSITORY https://github.com/verilator/verilator.git
GIT_TAG ${VERILATOR_GIT_TAG}
GIT_SHALLOW TRUE)
FetchContent_GetProperties(verilator)
if(NOT verilator_POPULATED)
FetchContent_Populate(verilator)
endif()

include(ProcessorCount)
ProcessorCount(NPROC)
if(NPROC EQUAL 0)
set(NPROC 1)
endif()

# Verilator uses an autoconf build; a git checkout has no generated
# configure, so run autoconf first.
execute_process(COMMAND autoconf
WORKING_DIRECTORY ${verilator_SOURCE_DIR}
RESULT_VARIABLE VL_AUTOCONF)
if(VL_AUTOCONF)
message(FATAL_ERROR
"Could not run 'autoconf' to build Verilator. Install the host build "
"tools first: autoconf, flex, bison (e.g. "
"'apt-get install autoconf flex bison'), then reconfigure.")
endif()
execute_process(COMMAND ${verilator_SOURCE_DIR}/configure
--prefix=${VERILATOR_INSTALL_DIR}
WORKING_DIRECTORY ${verilator_SOURCE_DIR}
RESULT_VARIABLE VL_CONFIGURE)
if(VL_CONFIGURE)
message(FATAL_ERROR "Verilator ./configure failed (need flex and bison).")
endif()
# Build only the binaries (verilator_exe) and install without man pages,
# so help2man is not required.
execute_process(COMMAND make -j${NPROC} verilator_exe
WORKING_DIRECTORY ${verilator_SOURCE_DIR}
RESULT_VARIABLE VL_MAKE)
if(VL_MAKE)
message(FATAL_ERROR "Verilator build (make) failed.")
endif()
execute_process(COMMAND make installbin installredirect installdata
install-msg
WORKING_DIRECTORY ${verilator_SOURCE_DIR}
RESULT_VARIABLE VL_INSTALL)
if(VL_INSTALL)
message(FATAL_ERROR "Verilator install failed.")
endif()
endif()

# Clear any cached binary (e.g. an earlier system-Verilator probe) so the
# config resolves verilator_bin against the fetched install.
unset(VERILATOR_BIN CACHE)
find_package(verilator REQUIRED HINTS ${VERILATOR_ROOT})
message(STATUS "Using fetched Verilator ${VERILATOR_GIT_TAG} from ${VERILATOR_ROOT}")
endif()
message(STATUS "Found Verilator binary = ${VERILATOR_BIN}")
endif()

# Compiler config
Expand Down Expand Up @@ -71,20 +146,38 @@ install(TARGETS hexasm xcmp xrun hexsim hexdis
# Verilator
if(USE_VERILATOR)
add_executable(hextb hex.cpp hextb.cpp)
target_link_libraries(hextb PUBLIC fmt::fmt)

verilate(
hextb
TRACE
VERILATOR_ARGS
--top-module
hex
PREFIX Vntb
TOP_MODULE network_top
VERILATOR_ARGS -Wall -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL
SOURCES
verilog/hex_pkg.sv
verilog/hex.sv
verilog/memory.sv
verilog/processor.sv
verilog/memory.sv)
verilog/link_interface.sv
verilog/core.sv
verilog/router.sv
verilog/network_top.sv)

install(TARGETS hextb DESTINATION ${CMAKE_INSTALL_BINDIR})

add_executable(router_tb tests/rtl/router_tb.cpp)
verilate(router_tb PREFIX Vrouter SOURCES verilog/hex_pkg.sv verilog/router.sv
TOP_MODULE router VERILATOR_ARGS -Wall -Wno-UNUSEDPARAM)

add_executable(liu_tb tests/rtl/liu_tb.cpp)
verilate(liu_tb SOURCES verilog/hex_pkg.sv verilog/link_interface.sv
PREFIX Vlink_interface TOP_MODULE link_interface
VERILATOR_ARGS -Wall -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL)

add_executable(core_tb tests/rtl/core_tb.cpp)
verilate(core_tb SOURCES verilog/hex_pkg.sv verilog/memory.sv
verilog/processor.sv verilog/link_interface.sv verilog/core.sv
PREFIX Vcore TOP_MODULE core
VERILATOR_ARGS -Wall -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL)
endif()

enable_testing()
Expand Down
88 changes: 88 additions & 0 deletions hexcontainer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#ifndef HEX_CONTAINER_HPP
#define HEX_CONTAINER_HPP

#include <cstdint>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>

//===---------------------------------------------------------------------===//
// Reader for the HEXN network container format, shared by the C++ simulator
// (hexsim) and the Verilator testbench (hextb) so they cannot drift.
//
// Layout (little-endian):
// uint32 magic = 0x4E584548 ("HEXN")
// uint32 numProcessors
// uint32 numEdges
// edges[numEdges]: uint32 procA, slotA, procB, slotB
// images[numProcessors]: uint32 imageSizeBytes, then imageSizeBytes of a
// standard single-image binary (size-word + code + debug info).
//
// A file without the magic is treated as a single plain image.
//===---------------------------------------------------------------------===//

namespace hexcontainer {

const uint32_t MAGIC = 0x4E584548; // "HEXN"

struct Edge {
uint32_t procA, slotA, procB, slotB;
};

struct Container {
bool isNetwork = false; // false => single plain image
std::vector<Edge> edges; // channel wiring (network only)
std::vector<std::vector<char>> images; // per-processor image bytes
};

inline uint32_t readU32(std::istream &file) {
uint32_t value = 0;
file.read(reinterpret_cast<char *>(&value), sizeof(uint32_t));
return value;
}

/// Read a container file. If it lacks the HEXN magic, returns a single-image
/// container (isNetwork = false, one image holding the whole file).
inline Container read(const std::string &filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error("could not open file: " + filename);
}
file.seekg(0, std::ios::end);
auto fileSize = static_cast<size_t>(file.tellg());
file.seekg(0, std::ios::beg);

Container container;
uint32_t magic = readU32(file);
if (magic != MAGIC) {
// Single plain image: the whole file is one image.
file.seekg(0, std::ios::beg);
std::vector<char> image(fileSize);
file.read(image.data(), fileSize);
container.images.push_back(std::move(image));
return container;
}

container.isNetwork = true;
uint32_t numProcessors = readU32(file);
uint32_t numEdges = readU32(file);
container.edges.resize(numEdges);
for (auto &e : container.edges) {
e.procA = readU32(file);
e.slotA = readU32(file);
e.procB = readU32(file);
e.slotB = readU32(file);
}
for (uint32_t i = 0; i < numProcessors; i++) {
uint32_t imageSize = readU32(file);
std::vector<char> image(imageSize);
file.read(image.data(), imageSize);
container.images.push_back(std::move(image));
}
return container;
}

} // namespace hexcontainer

#endif // HEX_CONTAINER_HPP
52 changes: 9 additions & 43 deletions hexsim.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <vector>

#include "hex.hpp"
#include "hexcontainer.hpp"
#include "hexsimio.hpp"

namespace hexsim {
Expand Down Expand Up @@ -522,52 +523,17 @@ class System {
/// Load a network container, or fall back to a single-processor system if the
/// file is a plain image (no network magic).
void loadNetwork(const char *filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error(std::string("could not open file: ") + filename);
}
file.seekg(0, std::ios::end);
auto fileSize = file.tellg();
file.seekg(0, std::ios::beg);

uint32_t magic;
file.read(reinterpret_cast<char *>(&magic), sizeof(uint32_t));
if (magic != NETWORK_MAGIC) {
// Plain single image: rewind and load the whole file as one processor.
file.seekg(0, std::ios::beg);
addProcessor(file, static_cast<unsigned>(fileSize), 0);
return;
}

uint32_t numProcessors;
uint32_t numEdges;
file.read(reinterpret_cast<char *>(&numProcessors), sizeof(uint32_t));
file.read(reinterpret_cast<char *>(&numEdges), sizeof(uint32_t));

struct Edge {
uint32_t procA, slotA, procB, slotB;
};
std::vector<Edge> edges(numEdges);
for (auto &e : edges) {
file.read(reinterpret_cast<char *>(&e.procA), sizeof(uint32_t));
file.read(reinterpret_cast<char *>(&e.slotA), sizeof(uint32_t));
file.read(reinterpret_cast<char *>(&e.procB), sizeof(uint32_t));
file.read(reinterpret_cast<char *>(&e.slotB), sizeof(uint32_t));
}

// Read each embedded image into its own processor.
for (uint32_t i = 0; i < numProcessors; i++) {
uint32_t imageSize;
file.read(reinterpret_cast<char *>(&imageSize), sizeof(uint32_t));
std::vector<char> buffer(imageSize);
file.read(buffer.data(), imageSize);
std::istringstream imageStream(std::string(buffer.begin(), buffer.end()),
auto container = hexcontainer::read(filename);
// One processor per image (a plain single image yields one processor).
for (size_t i = 0; i < container.images.size(); i++) {
auto &image = container.images[i];
std::istringstream imageStream(std::string(image.begin(), image.end()),
std::ios::binary);
addProcessor(imageStream, imageSize, i);
addProcessor(imageStream, static_cast<unsigned>(image.size()),
static_cast<unsigned>(i));
}

// Wire up the channels.
for (auto &e : edges) {
for (auto &e : container.edges) {
auto channel = std::make_unique<Channel>();
procs[e.procA]->setLink(e.slotA, channel.get());
procs[e.procB]->setLink(e.slotB, channel.get());
Expand Down
Loading
Loading