UART Bridge test (#1326)

Added a UART test in a new group of tests, 'bridges'.
This test group includes the bridges from firesim-lib, which rely on more external dependencies.
The previous `midasexamples` test suite is limited to simple tests with no dependencies.
This commit is contained in:
Nandor Licker 2022-12-13 07:07:36 +02:00 committed by GitHub
parent b3c39d171a
commit 5434084309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 446 additions and 38 deletions

View File

@ -267,9 +267,22 @@ jobs:
with:
test-name: "CIGroupB"
run-test-firesim-lib:
name: run-test-firesim-lib
needs: [run-test-groupB]
runs-on: aws-${{ github.run_id }}
env:
TERM: xterm-256-color
steps:
- uses: actions/checkout@v3
- name: Run firesim-lib Scala tests
uses: ./.github/actions/run-scala-test
with:
test-name: "BridgeTests"
run-chipyard-tests:
name: run-chipyard-tests
needs: [run-test-groupB]
needs: [run-test-firesim-lib]
runs-on: aws-${{ github.run_id }}
env:
TERM: xterm-256-color

View File

@ -171,7 +171,9 @@ resides in ``sim/``. These projects are:
:gh-file-ref:`sim/src/main/scala/midasexamples`, these are a set of simple chisel
circuits like GCD, that demonstrate how to use Golden Gate. These are useful test
cases for bringing up new Golden Gate features.
3. **fasedtests**: designs to do integration testing of FASED memory-system timing models.
3. **bridges**: tests for firesim-lib bridges. These have more dependencies and
involve more logic than `midasexamples`.
4. **fasedtests**: designs to do integration testing of FASED memory-system timing models.
Projects have the following directory structure:

View File

@ -42,10 +42,7 @@ void sighand(int s) {
}
#endif
uart_t::uart_t(simif_t *sim,
const UARTBRIDGEMODULE_struct &mmio_addrs,
int uartno)
: bridge_driver_t(sim), mmio_addrs(mmio_addrs) {
uart_fd_handler::uart_fd_handler(int uartno) {
this->loggingfd = 0; // unused
if (uartno == 0) {
@ -92,7 +89,42 @@ uart_t::uart_t(simif_t *sim,
fcntl(inputfd, F_SETFL, fcntl(inputfd, F_GETFL) | O_NONBLOCK);
}
uart_t::~uart_t() { close(this->loggingfd); }
uart_fd_handler::~uart_fd_handler() { close(this->loggingfd); }
std::optional<char> uart_fd_handler::get() {
char inp;
int readamt;
if (specialchar) {
// send special character (e.g. ctrl-c)
// for stdin handling
//
// PTY should never trigger this
inp = specialchar;
specialchar = 0;
readamt = 1;
} else {
// else check if we have input
readamt = ::read(inputfd, &inp, 1);
}
if (readamt <= 0)
return std::nullopt;
return inp;
}
void uart_fd_handler::put(char data) {
::write(outputfd, &data, 1);
if (loggingfd) {
::write(loggingfd, &data, 1);
}
}
uart_t::uart_t(simif_t *sim,
const UARTBRIDGEMODULE_struct &mmio_addrs,
int uartno)
: uart_t(sim, mmio_addrs, std::make_unique<uart_fd_handler>(uartno)) {}
uart_t::~uart_t() {}
void uart_t::send() {
if (data.in.fire()) {
@ -119,32 +151,14 @@ void uart_t::tick() {
this->recv();
if (data.in.ready) {
char inp;
int readamt;
if (specialchar) {
// send special character (e.g. ctrl-c)
// for stdin handling
//
// PTY should never trigger this
inp = specialchar;
specialchar = 0;
readamt = 1;
} else {
// else check if we have input
readamt = ::read(inputfd, &inp, 1);
}
if (readamt > 0) {
data.in.bits = inp;
if (auto bits = handler->get()) {
data.in.bits = *bits;
data.in.valid = true;
}
}
if (data.out.fire()) {
::write(outputfd, &data.out.bits, 1);
if (loggingfd) {
::write(loggingfd, &data.out.bits, 1);
}
handler->put(data.out.bits);
}
this->send();

View File

@ -2,9 +2,11 @@
#ifndef __UART_H
#define __UART_H
#include "serial.h"
#include <optional>
#include <signal.h>
#include "serial.h"
// The definition of the primary constructor argument for a bridge is generated
// by Golden Gate at compile time _iff_ the bridge is instantiated in the
// target. As a result, all bridge driver definitions conditionally remove
@ -15,28 +17,68 @@
// all-caps, suffixed with "_struct" and "_struct_guard" respectively.
#ifdef UARTBRIDGEMODULE_struct_guard
class uart_t : public bridge_driver_t {
/**
* Base class for callbacks handling data coming in and out a UART stream.
*/
class uart_handler {
public:
virtual ~uart_handler() {}
virtual std::optional<char> get() = 0;
virtual void put(char data) = 0;
};
/**
* Helper class which links the UART stream to either a file or PTY.
*/
class uart_fd_handler final : public uart_handler {
public:
uart_fd_handler(int uartno);
~uart_fd_handler();
std::optional<char> get() override;
void put(char data) override;
private:
int inputfd;
int outputfd;
int loggingfd;
};
class uart_t final : public bridge_driver_t {
public:
/// Creates a bridge which interacts with standard streams or PTY.
uart_t(simif_t *sim, const UARTBRIDGEMODULE_struct &mmio_addrs, int uartno);
/// Creates a bridge which pulls/pushes data using a custom handler.
uart_t(simif_t *sim,
const UARTBRIDGEMODULE_struct &mmio_addrs,
std::unique_ptr<uart_handler> &&handler)
: bridge_driver_t(sim), mmio_addrs(mmio_addrs),
handler(std::move(handler)) {}
~uart_t();
virtual void tick();
void tick();
// Our UART bridge's initialzation and teardown procedures don't
// require interaction with the FPGA (i.e., MMIO), and so we don't need
// to define init and finish methods (we can do everything in the
// ctor/dtor)
virtual void init(){};
virtual void finish(){};
void init(){};
void finish(){};
// Our UART bridge never calls for the simulation to terminate
virtual bool terminate() { return false; }
bool terminate() { return false; }
// ... and thus, never returns a non-zero exit code
virtual int exit_code() { return 0; }
int exit_code() { return 0; }
private:
const UARTBRIDGEMODULE_struct mmio_addrs;
serial_data_t<char> data;
int inputfd;
int outputfd;
int loggingfd;
std::unique_ptr<uart_handler> handler;
void send();
void recv();
};

View File

@ -0,0 +1,84 @@
// See LICENSE for license details.
#ifndef RTLSIM
#include "simif_f1.h"
#define SIMIF simif_f1_t
#else
#include "simif_emul.h"
#define SIMIF simif_emul_t
#endif
#include "bridges/uart.h"
#include "simif_peek_poke.h"
class UARTModuleTest final : public simif_peek_poke_t {
public:
class Handler final : public uart_handler {
public:
Handler(UARTModuleTest &test) { test.handler = this; }
~Handler() {
// Check that the input and output buffers are equal.
if (in_buffer != out_buffer) {
fprintf(stderr,
"Buffer mismatch:\n %s\n %s\n",
in_buffer.c_str(),
out_buffer.c_str());
abort();
}
}
std::optional<char> get() override {
if (in_index >= in_buffer.size())
return std::nullopt;
return in_buffer[in_index++];
}
void put(char data) override { out_buffer += data; }
const std::string in_buffer = "We are testing the UART bridge";
std::string out_buffer;
size_t in_index = 0;
};
Handler *handler;
UARTModuleTest(simif_t &simif)
: simif_peek_poke_t(&simif, PEEKPOKEBRIDGEMODULE_0_substruct_create),
uart(std::make_unique<uart_t>(&simif,
UARTBRIDGEMODULE_0_substruct_create,
std::make_unique<Handler>(*this))) {}
void run() {
// Initialise the UART bridge.
uart->init();
// Reset the device.
poke(reset, 1);
step(1);
poke(reset, 0);
step(1);
// Tick until the out buffer is filled in.
step(300000, false);
for (unsigned i = 0; i < 100000 && !simif->done(); ++i) {
uart->tick();
}
// Cleanup.
uart->finish();
}
private:
std::unique_ptr<uart_t> uart;
};
int main(int argc, char **argv) {
std::vector<std::string> args(argv + 1, argv + argc);
SIMIF simif(args);
simif.init(argc, argv);
UARTModuleTest test(simif);
test.run();
return test.teardown();
}

View File

@ -0,0 +1,110 @@
# See LICENSE for license details.
# These point at the main class of the target's Chisel generator
DESIGN_PACKAGE ?= firesim.bridges
DESIGN ?= GCD
TARGET_CONFIG_PACKAGE ?= firesim.bridges
TARGET_CONFIG ?= NoConfig
PLATFORM_CONFIG_PACKAGE ?= firesim.bridges
PLATFORM_CONFIG ?= DefaultF1Config
name_tuple := $(DESIGN)-$(TARGET_CONFIG)-$(PLATFORM_CONFIG)
GENERATED_DIR := $(firesim_base_dir)/generated-src/$(PLATFORM)/$(name_tuple)
OUTPUT_DIR := $(firesim_base_dir)/output/$(PLATFORM)/$(name_tuple)
# This is prefix is currently assumed by the fpga build flow
BASE_FILE_NAME := FireSim-generated
##########################
# RTL Generation #
##########################
long_name := $(DESIGN_PACKAGE).$(DESIGN).$(TARGET_CONFIG)
FIRRTL_FILE := $(GENERATED_DIR)/$(long_name).fir
ANNO_FILE := $(GENERATED_DIR)/$(long_name).anno.json
ifdef FIRESIM_STANDALONE
firesim_sbt_project := firesim
else
firesim_sbt_project := {file:${firesim_base_dir}/}firesim
endif
chisel_src_dirs = \
$(addprefix $(firesim_base_dir)/,. midas midas/targetutils firesim-lib) \
$(addprefix $(chipyard_dir)/generators/, rocket-chip/src, rocket-chip/api-config-chipsalliance)
chisel_srcs = $(foreach submodule,$(chisel_src_dirs),\
$(shell find $(submodule)/ -iname "[!.]*.scala" -print 2> /dev/null | grep 'src/main/scala'))
SIM_RUNTIME_CONF ?= $(GENERATED_DIR)/$(CONF_NAME)
mem_model_args = $(shell cat $(SIM_RUNTIME_CONF))
COMMON_SIM_ARGS ?= $(mem_model_args)
vcs_args = +vcs+initreg+0 +vcs+initmem+0
# Rocket Chip stage requires a fully qualified classname for each fragment, whereas Chipyard's does not.
# This retains a consistent TARGET_CONFIG naming convention across the different target projects.
subst_prefix=,$(TARGET_CONFIG_PACKAGE).
$(FIRRTL_FILE) $(ANNO_FILE): $(chisel_srcs) $(FIRRTL_JAR) $(SCALA_BUILDTOOL_DEPS)
mkdir -p $(@D)
$(call run_scala_main,$(firesim_sbt_project),freechips.rocketchip.system.Generator, \
--target-dir $(GENERATED_DIR) \
--name $(long_name) \
--top-module $(DESIGN_PACKAGE).$(DESIGN) \
--configs $(TARGET_CONFIG_PACKAGE).$(subst _,$(subst_prefix),$(TARGET_CONFIG)))
##########################
# Driver Sources & Flags #
##########################
TESTCHIPIP_CSRC_DIR = $(chipyard_dir)/generators/testchipip/src/main/resources/testchipip/csrc
DRIVER_DIR = $(firesim_base_dir)/src/main/cc
FIRESIM_LIB_DIR = $(firesim_base_dir)/firesim-lib/src/main/cc/
DRIVER_H = $(shell find $(DRIVER_DIR) -name "*.h")
DRIVER_CC := \
$(DRIVER_DIR)/midasexamples/simif_peek_poke.cc \
$(DRIVER_DIR)/bridges/$(DESIGN).cc \
$(TESTCHIPIP_CSRC_DIR)/testchip_tsi.cc \
$(wildcard $(addprefix $(FIRESIM_LIB_DIR)/, \
bridges/uart.cc \
bridges/serial.cc \
fesvr/firesim_tsi.cc \
))
TARGET_CXX_FLAGS := \
-isystem $(TESTCHIPIP_CSRC_DIR) \
-I$(DRIVER_DIR)/midasexamples \
-I$(FIRESIM_LIB_DIR) \
-I$(DRIVER_DIR) \
-I$(DRIVER_DIR)/bridges \
-g
TARGET_LD_FLAGS := \
-L$(RISCV)/lib \
-lfesvr
##########################
# Midas-Level Sim Recipes#
##########################
# These are from MIDAS examples
loadmem = $(if $(LOADMEM),+loadmem=$(abspath $(LOADMEM)),)
benchmark = $(notdir $(basename $(if $(LOADMEM),$(notdir $(LOADMEM)),$(DESIGN))))
logfile = $(if $(LOGFILE),$(abspath $(LOGFILE)),$(OUTPUT_DIR)/$(benchmark).$1.out)
waveform = $(if $(WAVEFORM),$(abspath $(WAVEFORM)),$(OUTPUT_DIR)/$(benchmark).$1.$2)
run-verilator-debug run-verilator: run-verilator% : $(GENERATED_DIR)/V$(DESIGN)% $(LOADMEM)
mkdir -p $(OUTPUT_DIR)
cd $(GENERATED_DIR) && ./$(notdir $<) $(COMMON_SIM_ARGS) $(ARGS) \
$(loadmem) \
+waveform=$(call waveform,verilator,vcd) 2> $(call logfile,verilator)
run-vcs run-vcs-debug: run-vcs%: $(GENERATED_DIR)/$(DESIGN)% $(LOADMEM)
mkdir -p $(OUTPUT_DIR)
cd $(GENERATED_DIR) && ./$(notdir $<) $(vcs_args) $(COMMON_SIM_ARGS) $(ARGS) \
$(loadmem) \
+waveform=$(call waveform,vcs,vpd) 2> $(call logfile,vcs)

View File

@ -0,0 +1,30 @@
//See LICENSE for license details.
package firesim.bridges
import firesim.configs.WithDefaultMemModel
import freechips.rocketchip.config._
import freechips.rocketchip.subsystem.{PeripheryBusKey, PeripheryBusParams}
class NoConfig extends Config(Parameters.empty)
class BaseBridgesConfig
extends Config(
new WithDefaultMemModel
)
class DefaultF1Config
extends Config(
new BaseBridgesConfig ++
new midas.F1Config
)
class DefaultVitisConfig
extends Config(
new BaseBridgesConfig ++
new midas.VitisConfig
)
class UARTConfig
extends Config((site, here, up) => { case PeripheryBusKey =>
PeripheryBusParams(beatBytes = 1, blockBytes = 8, dtsFrequency = Some(100000000))
})

View File

@ -0,0 +1,21 @@
//See LICENSE for license details.
package firesim.bridges
import chisel3._
import firesim.midasexamples.PeekPokeMidasExampleHarness
import freechips.rocketchip.config.Parameters
import sifive.blocks.devices.uart.{UART, UARTParams, UARTPortIO}
class UARTDUT(implicit val p: Parameters) extends Module {
val uartParams = UARTParams(address = 0x10013000)
val ep = Module(new UARTBridge(uartParams))
ep.io.reset := reset
ep.io.clock := clock
ep.io.uart.txd := ep.io.uart.rxd
}
class UARTModule(implicit p: Parameters) extends PeekPokeMidasExampleHarness(() => new UARTDUT)

View File

@ -0,0 +1,92 @@
//See LICENSE for license details.
package firesim.bridges
import java.io.File
import org.scalatest.Suites
import org.scalatest.matchers.should._
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.config._
abstract class BridgeSuite(
val targetName: String, // See GeneratorUtils
targetConfigs: String = "NoConfig",
platformConfigs: String = "HostDebugFeatures_DefaultF1Config",
tracelen: Int = 8,
simulationArgs: Seq[String] = Seq(),
) extends firesim.TestSuiteCommon
with Matchers {
val backendSimulator = "verilator"
val targetTuple = s"$targetName-$targetConfigs-$platformConfigs"
val commonMakeArgs = Seq(
s"TARGET_PROJECT=bridges",
s"DESIGN=$targetName",
s"TARGET_CONFIG=${targetConfigs}",
s"PLATFORM_CONFIG=${platformConfigs}",
)
def run(
backend: String,
debug: Boolean = false,
logFile: Option[File] = None,
waveform: Option[File] = None,
args: Seq[String] = Nil,
) = {
val makeArgs = Seq(
s"run-$backend%s".format(if (debug) "-debug" else ""),
"LOGFILE=%s".format(logFile.map(toStr).getOrElse("")),
"WAVEFORM=%s".format(waveform.map(toStr).getOrElse("")),
"ARGS=%s".format(args.mkString(" ")),
)
if (isCmdAvailable(backend)) {
make(makeArgs: _*)
} else 0
}
/** Runs MIDAS-level simulation on the design.
*
* @param b
* Backend simulator: "verilator" or "vcs"
* @param debug
* When true, captures waves from the simulation
* @param args
* A seq of PlusArgs to pass to the simulator.
* @param shouldPass
* When false, asserts the test returns a non-zero code
*/
def runTest(b: String, debug: Boolean = false, args: Seq[String] = simulationArgs, shouldPass: Boolean = true) {
val prefix = if (shouldPass) "pass in " else "fail in "
val testEnvStr = s"${b} MIDAS-level simulation"
val wavesStr = if (debug) " with waves enabled" else ""
val argStr = " with args: " + args.mkString(" ")
val haveThisBehavior = prefix + testEnvStr + wavesStr + argStr
if (isCmdAvailable(b)) {
it should haveThisBehavior in {
assert((run(b, debug, args = args) == 0) == shouldPass)
}
} else {
ignore should haveThisBehavior in {}
}
}
mkdirs()
behavior.of(s"$targetName")
elaborateAndCompile()
compileMlSimulator(backendSimulator)
runTest(backendSimulator)
}
class UARTF1Test extends BridgeSuite("UARTModule", "UARTConfig", "DefaultF1Config")
class UARTVitisTest extends BridgeSuite("UARTModule", "UARTConfig", "DefaultVitisConfig")
class BridgeTests
extends Suites(
new UARTF1Test,
new UARTVitisTest,
)