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:
parent
b3c39d171a
commit
5434084309
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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)
|
|
@ -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))
|
||||
})
|
|
@ -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)
|
|
@ -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,
|
||||
)
|
Loading…
Reference in New Issue