diff --git a/.github/actions/setup-flexpret/action.yml b/.github/actions/setup-flexpret/action.yml new file mode 100644 index 0000000000..9866c15c6b --- /dev/null +++ b/.github/actions/setup-flexpret/action.yml @@ -0,0 +1,43 @@ +name: Install FlexPRET and dependencies (Linux only) +description: Install FlexPRET and dependencies (Linux only) +runs: + using: "composite" + steps: + - name: Setup + run: | + # Clone the FlexPRET repository + git clone --recurse-submodules https://github.com/pretis/flexpret + + # This rest is copied directly from FlexPRET's `azure-pipelines.yml` + + # Ubuntu 20.04 only has Verilator 4.028 but we neeed a more modern version + # so we do not use 'sudo apt-get install -y -qq verilator' here. + wget -q https://github.com/sifive/verilator/releases/download/4.036-0sifive2/verilator_4.036-0sifive2_amd64.deb -O verilator.deb + sudo dpkg -i verilator.deb + + # Install riscv compiler + wget https://github.com/stnolting/riscv-gcc-prebuilt/releases/download/rv32i-4.0.0/riscv32-unknown-elf.gcc-12.1.0.tar.gz + sudo mkdir /opt/riscv + sudo tar -xzf riscv32-unknown-elf.gcc-12.1.0.tar.gz -C /opt/riscv/ + rm riscv32-unknown-elf.gcc-12.1.0.tar.gz + + # Update submodules + git submodule update --init --recursive + + # Save location of RISC-V compiler to reuse later + echo "FP_RISCV_COMPILER=/opt/riscv" >> "$GITHUB_ENV" + shell: bash + - name: Build FlexPRET and install to SDK + run: | + # Step into cloned directory + cd flexpret + + # Save pwd to environment variable so we can find `env.bash` in later steps + echo "FP_DIR=$(pwd)" >> "$GITHUB_ENV" + + # Source environment + source env.bash + + # Build FlexPRET's high memory configuration and install it to SDK + cd $FP_PATH && cmake -DFP_CONFIG=highmem -B build && cd build && make all install + shell: bash diff --git a/.github/workflows/all-embedded.yml b/.github/workflows/all-embedded.yml new file mode 100644 index 0000000000..4776a1e530 --- /dev/null +++ b/.github/workflows/all-embedded.yml @@ -0,0 +1,30 @@ +# Tests for embedded platforms +name: CI (embedded platforms) + +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + types: [synchronize, opened, reopened, ready_for_review, converted_to_draft] + merge_group: + +env: + # 2020.11 + vcpkgGitRef: 0bf3923f9fab4001c00f0f429682a0853b5749e0 + +concurrency: + group: all-embedded-${{ github.ref }}-${{ github.event_path }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + check-diff: + uses: ./.github/workflows/check-diff.yml + + c-embedded: + uses: ./.github/workflows/c-embedded.yml + needs: check-diff + if: ${{ needs.check-diff.outputs.run_c == 'true' }} + + # Add more languages if supported diff --git a/.github/workflows/c-embedded.yml b/.github/workflows/c-embedded.yml new file mode 100644 index 0000000000..f36bf3c73b --- /dev/null +++ b/.github/workflows/c-embedded.yml @@ -0,0 +1,25 @@ +name: C embedded + +on: + workflow_dispatch: + workflow_call: + + +concurrency: + group: c-embedded-${{ github.ref }}-${{ github.event_path }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + # Run the C Arduino integration tests. + arduino: + uses: ./.github/workflows/c-arduino-tests.yml + with: + all-platforms: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + + # Run the C Zephyr integration tests. + zephyr: + uses: ./.github/workflows/c-zephyr-tests.yml + + # Run the C FlexPRET integration tests. + flexpret: + uses: ./.github/workflows/c-flexpret-tests.yml diff --git a/.github/workflows/c-flexpret-tests.yml b/.github/workflows/c-flexpret-tests.yml new file mode 100644 index 0000000000..0df6abe62f --- /dev/null +++ b/.github/workflows/c-flexpret-tests.yml @@ -0,0 +1,58 @@ +name: C FlexPRET tests + +on: + workflow_call: + inputs: + compiler-ref: + required: false + type: string + runtime-ref: + required: false + type: string + use-cpp: + required: false + type: boolean + default: false + scheduler: + required: false + type: string + all-platforms: + required: false + default: true + type: boolean + +jobs: + flexpret-tests: + runs-on: ubuntu-latest + steps: + - name: Check out lingua-franca repository + uses: actions/checkout@v3 + with: + repository: lf-lang/lingua-franca + submodules: true + ref: ${{ inputs.compiler-ref }} + fetch-depth: 0 + - name: Prepare build environment + uses: ./.github/actions/prepare-build-env + - name: Setup and build FlexPRET + uses: ./.github/actions/setup-flexpret + - name: Check out specific ref of reactor-c + uses: actions/checkout@v3 + with: + repository: lf-lang/reactor-c + path: core/src/main/resources/lib/c/reactor-c + ref: ${{ inputs.runtime-ref }} + if: ${{ inputs.runtime-ref }} + - name: Run FlexPRET smoke tests + run: | + cd "$FP_DIR" && source env.bash && cd - + export RISCV_TOOL_PATH_PREFIX=$FP_RISCV_COMPILER + ./gradlew core:integrationTest \ + --tests org.lflang.tests.runtime.CFlexPRETTest.* \ + core:integrationTestCodeCoverageReport + rm -rf test/C/src-gen + - name: Report to CodeCov + uses: ./.github/actions/report-code-coverage + with: + files: core/build/reports/jacoco/integrationTestCodeCoverageReport/integrationTestCodeCoverageReport.xml + if: ${{ github.repository == 'lf-lang/lingua-franca' }} diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index e49d0b2d81..32d539fb49 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -25,16 +25,6 @@ jobs: with: target: "C" - # Run the C Arduino integration tests. - arduino: - uses: ./.github/workflows/c-arduino-tests.yml - with: - all-platforms: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} - - # Run the C Zephyr integration tests. - zephyr: - uses: ./.github/workflows/c-zephyr-tests.yml - # Run the CCpp integration tests. ccpp: uses: ./.github/workflows/c-tests.yml diff --git a/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java b/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java index 12c71eed7c..2f170ec488 100644 --- a/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java @@ -232,7 +232,7 @@ public static boolean compatibleWithThreadingOff(TestCategory category) { // CONCURRENT, FEDERATED, DOCKER_FEDERATED, DOCKER // are not compatible with single-threaded execution. - // ARDUINO and ZEPHYR have their own test suites, so we don't need to rerun. + // ARDUINO, ZEPHYR and FLEXPRET have their own test suites, so we don't need to rerun. boolean excluded = category == TestCategory.CONCURRENT || category == TestCategory.SERIALIZATION @@ -244,7 +244,8 @@ public static boolean compatibleWithThreadingOff(TestCategory category) { || category == TestCategory.VERIFIER || category == TestCategory.ZEPHYR_UNTHREADED || category == TestCategory.ZEPHYR_BOARDS - || category == TestCategory.ZEPHYR_THREADED; + || category == TestCategory.ZEPHYR_THREADED + || category == TestCategory.FLEXPRET; // SERIALIZATION and TARGET tests are excluded on Windows. excluded |= isWindows() && category == TestCategory.TARGET; diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CFlexPRETTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CFlexPRETTest.java new file mode 100644 index 0000000000..38a4e52cbb --- /dev/null +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CFlexPRETTest.java @@ -0,0 +1,80 @@ +/************* + * Copyright (c) 2023, The University of California at Berkeley. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ +package org.lflang.tests.runtime; + +import java.util.List; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.lflang.target.Target; +import org.lflang.tests.Configurators; +import org.lflang.tests.TestBase; +import org.lflang.tests.TestRegistry.TestCategory; +import org.lflang.tests.Transformers; + +public class CFlexPRETTest extends TestBase { + + public CFlexPRETTest() { + super(Target.C); + } + + @Test + public void buildFlexPRETConcurrent() { + Assumptions.assumeTrue(isLinux(), "FlexPRET tests only supported on Linux"); + super.runTestsFor( + List.of(Target.C), + "Build concurrent tests for FlexPRET.", + TestCategory.CONCURRENT::equals, + Transformers::noChanges, + Configurators::makeFlexPRETCompatible, + TestLevel.BUILD, + false); + } + + @Test + public void buildFlexPRETBasicTestsUnthreaded() { + Assumptions.assumeTrue(isLinux(), "FlexPRET tests only supported on Linux"); + super.runTestsFor( + List.of(Target.C), + "Build basic tests for FlexPRET in single threaded mode.", + TestCategory.BASIC::equals, + Transformers::noChanges, + Configurators::makeFlexPRETCompatibleUnthreaded, + TestLevel.BUILD, + false); + } + + @Test + public void buildFlexPRETBasicTests() { + Assumptions.assumeTrue(isLinux(), "FlexPRET tests only supported on Linux"); + super.runTestsFor( + List.of(Target.C), + "Build basic tests for FlexPRET.", + TestCategory.BASIC::equals, + Transformers::noChanges, + Configurators::makeFlexPRETCompatible, + TestLevel.BUILD, + false); + } +} diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 1ef19bcd79..cf74cd2dae 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -452,6 +452,21 @@ public static String generateFederateNeighborStructure(FederateInstance federate return code.toString(); } + public static String surroundWithIfElseFederated(String insideIf, String insideElse) { + if (insideElse == null) { + return surroundWithIfFederated(insideIf); + } else { + return """ + #ifdef FEDERATED + %s + #else + %s + #endif // FEDERATED + """ + .formatted(insideIf, insideElse); + } + } + /** * Surround {@code code} with blocks to ensure that code only executes if the program is * federated. @@ -465,6 +480,21 @@ public static String surroundWithIfFederated(String code) { .formatted(code); } + public static String surroundWithIfElseFederatedCentralized(String insideIf, String insideElse) { + if (insideElse == null) { + return surroundWithIfFederatedCentralized(insideIf); + } else { + return """ + #ifdef FEDERATED_CENTRALIZED + %s + #else + %s + #endif // FEDERATED_CENTRALIZED + """ + .formatted(insideIf, insideElse); + } + } + /** * Surround {@code code} with blocks to ensure that code only executes if the program is federated * and has a centralized coordination. diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 9b984f5fc9..4349b91dec 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -40,6 +40,7 @@ import org.lflang.target.property.CompileDefinitionsProperty; import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.PlatformProperty; +import org.lflang.target.property.PlatformProperty.Option; import org.lflang.target.property.ProtobufsProperty; import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.TracePluginProperty; @@ -128,8 +129,8 @@ CodeBuilder generateCMakeCode( // arduino String[] boardProperties = {}; var platformOptions = targetConfig.getOrDefault(PlatformProperty.INSTANCE); - if (platformOptions.board() != null) { - boardProperties = platformOptions.board().trim().split(":"); + if (platformOptions.board().setByUser()) { + boardProperties = platformOptions.board().value().trim().split(":"); // Ignore whitespace for (int i = 0; i < boardProperties.length; i++) { boardProperties[i] = boardProperties[i].trim(); @@ -150,9 +151,9 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("if(EXISTS prj.conf)"); cMakeCode.pr(" set(OVERLAY_CONFIG prj.conf)"); cMakeCode.pr("endif()"); - if (platformOptions.board() != null) { + if (platformOptions.board().setByUser()) { cMakeCode.pr("# Selecting board specified in target property"); - cMakeCode.pr("set(BOARD " + platformOptions.board() + ")"); + cMakeCode.pr("set(BOARD " + platformOptions.board().value() + ")"); } else { cMakeCode.pr("# Selecting default board"); cMakeCode.pr("set(BOARD qemu_cortex_m3)"); @@ -188,7 +189,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); cMakeCode.newLine(); // board type for rp2040 based boards - if (platformOptions.board() != null) { + if (platformOptions.board().setByUser()) { if (boardProperties.length < 1 || boardProperties[0].equals("")) { cMakeCode.pr("set(PICO_BOARD pico)"); } else { @@ -197,6 +198,34 @@ CodeBuilder generateCMakeCode( } // remove warnings for rp2040 only to make debug easier cMakeCode.pr("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -w\")"); + break; + case FLEXPRET: + if (System.getenv("FP_PATH") == null) { + messageReporter.nowhere().warning("No FP_PATH environment variable found"); + } + if (System.getenv("FP_SDK_PATH") == null) { + messageReporter.nowhere().warning("No FP_SDK_PATH environment variable found"); + } + cMakeCode.newLine(); + cMakeCode.pr("# Include toolchain file and set project"); + cMakeCode.pr("include($ENV{FP_SDK_PATH}/cmake/riscv-toolchain.cmake)"); + cMakeCode.pr("Project(" + executableName + " LANGUAGES C ASM)"); + cMakeCode.newLine(); + + Option selectedBoard = platformOptions.board(); + if (selectedBoard.setByUser()) { + cMakeCode.pr("# Board selected from target property"); + cMakeCode.pr("set(TARGET " + selectedBoard.value() + ")"); + cMakeCode.newLine(); + } // No TARGET will automatically become emulator + + Option selectedFlashDevice = platformOptions.port(); + if (selectedFlashDevice.setByUser()) { + cMakeCode.pr("# Flash device selected from target property"); + cMakeCode.pr("set(FP_FLASH_DEVICE " + selectedFlashDevice.value() + ")"); + cMakeCode.newLine(); + } // No FP_FLASH_DEVICE will automatically become /dev/ttyUSB0 + break; default: cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); @@ -292,6 +321,13 @@ CodeBuilder generateCMakeCode( executableName, Stream.concat(additionalSources.stream(), sources.stream()))); break; + case FLEXPRET: + cMakeCode.pr( + setUpMainTargetFlexPRET( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + break; default: cMakeCode.pr( setUpMainTarget.getCmakeCode( @@ -315,6 +351,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/platform)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/modal_models)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); + cMakeCode.newLine(); // post target definition board configurations switch (platformOptions.platform()) { @@ -322,13 +359,21 @@ CodeBuilder generateCMakeCode( // set stdio output boolean usb = true; boolean uart = true; - if (platformOptions.board() != null && boardProperties.length > 1) { + if (platformOptions.board().setByUser() && boardProperties.length > 1) { uart = !boardProperties[1].equals("usb"); usb = !boardProperties[1].equals("uart"); } cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} " + (usb ? 1 : 0) + ")"); cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} " + (uart ? 1 : 0) + ")"); break; + case FLEXPRET: + cMakeCode.pr("# Include necessary commands to generate .mem, .dump, and executable files"); + cMakeCode.pr("include($ENV{FP_SDK_PATH}/cmake/fp-app.cmake)"); + cMakeCode.pr("fp_add_outputs(${LF_MAIN_TARGET})"); + cMakeCode.newLine(); + break; + default: + break; } if (targetConfig.get(AuthProperty.INSTANCE)) { @@ -348,7 +393,8 @@ CodeBuilder generateCMakeCode( } if (!targetConfig.get(SingleThreadedProperty.INSTANCE) - && platformOptions.platform() != Platform.ZEPHYR) { + && platformOptions.platform() != Platform.ZEPHYR + && platformOptions.platform() != Platform.FLEXPRET) { // If threaded computation is requested, add the threads option. cMakeCode.pr("# Find threads and link to it"); cMakeCode.pr("find_package(Threads REQUIRED)"); @@ -367,7 +413,7 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } else { cMakeCode.pr("# Set flag to indicate a single-threaded runtime"); - cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_SINGLE_THREADED=1)"); + cMakeCode.pr("target_compile_definitions(${LF_MAIN_TARGET} PUBLIC LF_SINGLE_THREADED=1)"); } cMakeCode.newLine(); @@ -401,9 +447,28 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } - // Add the install option - cMakeCode.pr(installCode); - cMakeCode.newLine(); + if (platformOptions.platform() == Platform.FLEXPRET) { + cMakeCode.pr( + """ + # FlexPRET SDK generates a script that runs the program; + # install it to the top-level bin + install( + FILES ${CMAKE_SOURCE_DIR}/bin/${LF_MAIN_TARGET} + DESTINATION ${CMAKE_INSTALL_BINDIR} + PERMISSIONS + OWNER_EXECUTE # Need execute, the others are normal permissions + OWNER_READ + OWNER_WRITE + GROUP_READ + WORLD_READ + ) + """); + cMakeCode.newLine(); + } else { + // Add the install option + cMakeCode.pr(installCode); + cMakeCode.newLine(); + } // Add the include file for (String includeFile : targetConfig.getOrDefault(CmakeIncludeProperty.INSTANCE)) { @@ -516,4 +581,38 @@ private static String setUpMainTargetRp2040( code.newLine(); return code.toString(); } + + private static String setUpMainTargetFlexPRET( + boolean hasMain, String executableName, Stream cSources) { + var code = new CodeBuilder(); + code.pr("add_subdirectory(core)"); + code.newLine(); + + code.pr("# Add FlexPRET's out-of-tree SDK"); + code.pr("add_subdirectory($ENV{FP_SDK_PATH} BINARY_DIR)"); + code.newLine(); + + code.pr("set(LF_MAIN_TARGET " + executableName + ")"); + code.newLine(); + + if (hasMain) { + code.pr("# Declare a new executable target and list all its sources"); + code.pr("add_executable("); + } else { + code.pr("# Declare a new library target and list all its sources"); + code.pr("add_library("); + } + code.indent(); + code.pr("${LF_MAIN_TARGET}"); + + cSources.forEach(code::pr); + code.unindent(); + code.pr(")"); + code.newLine(); + + code.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE fp-sdk)"); + code.newLine(); + + return code.toString(); + } } diff --git a/core/src/main/java/org/lflang/generator/c/CCompiler.java b/core/src/main/java/org/lflang/generator/c/CCompiler.java index 98ca19aacf..9f921254ce 100644 --- a/core/src/main/java/org/lflang/generator/c/CCompiler.java +++ b/core/src/main/java/org/lflang/generator/c/CCompiler.java @@ -41,6 +41,7 @@ import org.lflang.target.property.BuildTypeProperty; import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.PlatformProperty; +import org.lflang.target.property.PlatformProperty.Option; import org.lflang.target.property.PlatformProperty.PlatformOptions; import org.lflang.target.property.type.BuildTypeType.BuildType; import org.lflang.target.property.type.PlatformType.Platform; @@ -177,7 +178,7 @@ public boolean runCCompiler(GeneratorBase generator, LFGeneratorContext context) + " finished with no errors."); } var options = targetConfig.getOrDefault(PlatformProperty.INSTANCE); - if (options.platform() == Platform.ZEPHYR && options.flash()) { + if (options.platform() == Platform.ZEPHYR && options.flash().value()) { messageReporter.nowhere().info("Invoking flash command for Zephyr"); LFCommand flash = buildWestFlashCommand(options); int flashRet = flash.run(); @@ -310,9 +311,10 @@ public LFCommand buildCmakeCommand() { public LFCommand buildWestFlashCommand(PlatformOptions options) { // Set the build directory to be "build" Path buildPath = fileConfig.getSrcGenPath().resolve("build"); - String board = options.board(); + Option board = options.board(); + String boardValue = board.value(); LFCommand cmd; - if (board == null || board.startsWith("qemu") || board.equals("native_posix")) { + if (!board.setByUser() || boardValue.startsWith("qemu") || boardValue.equals("native_posix")) { cmd = commandFactory.createCommand("west", List.of("build", "-t", "run"), buildPath); } else { cmd = commandFactory.createCommand("west", List.of("flash"), buildPath); diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index eb3a21dc26..0d47f7db74 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -105,6 +105,7 @@ import org.lflang.target.property.type.SchedulerType.Scheduler; import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; +import org.lflang.util.FlexPRETUtil; /** * Generator for C target. This class generates C code defining each reactor class given in the @@ -446,6 +447,8 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { var isArduino = targetConfig.getOrDefault(PlatformProperty.INSTANCE).platform() == Platform.ARDUINO; + var isFlexPRET = + targetConfig.getOrDefault(PlatformProperty.INSTANCE).platform() == Platform.FLEXPRET; // If cmake is requested, generate the CMakeLists.txt if (!isArduino) { @@ -552,6 +555,21 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { context.finish(GeneratorResult.Status.COMPILED, null); } } + if (isFlexPRET) { + var platform = targetConfig.getOrDefault(PlatformProperty.INSTANCE); + if (platform.flash().value()) { + /** + * Flash will result in two widely different responses when board is set to `emulator` + * and `fpga`. For emulator, it will immediately run the emulator. For fpga, it will + * attempt to transfer the program to the fpga. + * + *

It is FlexPRET's software development kit that handles all this; we just run the + * script it generates. + */ + FlexPRETUtil flexPRETUtil = new FlexPRETUtil(context, commandFactory, messageReporter); + flexPRETUtil.flashTarget(fileConfig, targetConfig); + } + } } } @@ -636,8 +654,8 @@ private void generateCodeFor(String lfModuleName) throws IOException { String.join( "\n", "void logical_tag_complete(tag_t tag_to_send) {", - CExtensionUtils.surroundWithIfFederatedCentralized( - " lf_latest_tag_complete(tag_to_send);"), + CExtensionUtils.surroundWithIfElseFederatedCentralized( + " lf_latest_tag_complete(tag_to_send);", " (void) tag_to_send;"), "}")); // Generate an empty termination function for non-federated @@ -647,7 +665,9 @@ private void generateCodeFor(String lfModuleName) throws IOException { code.pr( """ #ifndef FEDERATED - void lf_terminate_execution(environment_t* env) {} + void lf_terminate_execution(environment_t* env) { + (void) env; + } #endif"""); } } @@ -1981,7 +2001,8 @@ protected void setUpGeneralParameters() { final var platformOptions = targetConfig.get(PlatformProperty.INSTANCE); if (!targetConfig.get(SingleThreadedProperty.INSTANCE) && platformOptions.platform() == Platform.ARDUINO - && (platformOptions.board() == null || !platformOptions.board().contains("mbed"))) { + && (!platformOptions.board().setByUser() + || !platformOptions.board().value().contains("mbed"))) { // non-MBED boards should not use threading messageReporter .nowhere() @@ -1993,7 +2014,7 @@ protected void setUpGeneralParameters() { if (platformOptions.platform() == Platform.ARDUINO && !targetConfig.get(NoCompileProperty.INSTANCE) - && platformOptions.board() == null) { + && !platformOptions.board().setByUser()) { messageReporter .nowhere() .info( @@ -2006,16 +2027,12 @@ protected void setUpGeneralParameters() { if (platformOptions.platform() == Platform.ZEPHYR && !targetConfig.get(SingleThreadedProperty.INSTANCE) - && platformOptions.userThreads() >= 0) { + && platformOptions.userThreads().value() >= 0) { targetConfig .get(CompileDefinitionsProperty.INSTANCE) - .put(PlatformOption.USER_THREADS.name(), String.valueOf(platformOptions.userThreads())); - } else if (platformOptions.userThreads() > 0) { - messageReporter - .nowhere() - .warning( - "Specifying user threads is only for threaded Lingua Franca on the Zephyr platform." - + " This option will be ignored."); // FIXME: do this during validation instead + .put( + PlatformOption.USER_THREADS.name(), + String.valueOf(platformOptions.userThreads().value())); } pickCompilePlatform(); } diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index 603e7d7928..ebd0c68682 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -57,7 +57,9 @@ private String generateMainFunction() { "}\n", "// Arduino setup() and loop() functions", "void setup() {", - "\tSerial.begin(" + targetConfig.get(PlatformProperty.INSTANCE).baudRate() + ");", + "\tSerial.begin(" + + targetConfig.get(PlatformProperty.INSTANCE).baudRate().value() + + ");", "\tlf_register_print_function(&_lf_arduino_print_message_function, LOG_LEVEL);", "\tlf_reactor_c_main(0, NULL);", "}\n", diff --git a/core/src/main/java/org/lflang/target/property/PlatformProperty.java b/core/src/main/java/org/lflang/target/property/PlatformProperty.java index 4e5ea72503..ec9d97e0d7 100644 --- a/core/src/main/java/org/lflang/target/property/PlatformProperty.java +++ b/core/src/main/java/org/lflang/target/property/PlatformProperty.java @@ -32,17 +32,34 @@ private PlatformProperty() { @Override public PlatformOptions initialValue() { - return new PlatformOptions(Platform.AUTO, null, null, 9600, false, 0); + Option board = new Option(false, null); + Option port = new Option(false, null); + Option baudRate = new Option(false, 0); + Option flash = new Option(false, false); + Option userThreads = new Option(false, 0); + + return new PlatformOptions(Platform.AUTO, board, port, baudRate, flash, userThreads); } @Override public PlatformOptions fromAst(Element node, MessageReporter reporter) { var platform = Platform.AUTO; - String board = null; - String port = null; - var baudRate = 9600; - var flash = false; - var userThreads = 0; + + String boardValue = null; + boolean boardSet = false; + + String portValue = null; + boolean portSet = false; + + int baudRateValue = 0; + boolean baudRateSet = false; + + boolean flashValue = false; + boolean flashSet = false; + + int userThreadsValue = 0; + boolean userThreadsSet = false; + if (node.getLiteral() != null || node.getId() != null) { platform = new PlatformType().forName(ASTUtils.elementToSingleString(node)); } else if (node.getKeyvalue() != null) { @@ -55,15 +72,37 @@ public PlatformOptions fromAst(Element node, MessageReporter reporter) { platform = new PlatformType().forName(ASTUtils.elementToSingleString(entry.getValue())); } - case BAUDRATE -> baudRate = ASTUtils.toInteger(entry.getValue()); - case BOARD -> board = ASTUtils.elementToSingleString(entry.getValue()); - case FLASH -> flash = ASTUtils.toBoolean(entry.getValue()); - case PORT -> port = ASTUtils.elementToSingleString(entry.getValue()); - case USER_THREADS -> userThreads = ASTUtils.toInteger(entry.getValue()); + case BAUDRATE -> { + baudRateSet = true; + baudRateValue = ASTUtils.toInteger(entry.getValue()); + } + case BOARD -> { + boardSet = true; + boardValue = ASTUtils.elementToSingleString(entry.getValue()); + } + case FLASH -> { + flashSet = true; + flashValue = ASTUtils.toBoolean(entry.getValue()); + } + case PORT -> { + portSet = true; + portValue = ASTUtils.elementToSingleString(entry.getValue()); + } + case USER_THREADS -> { + userThreadsSet = true; + userThreadsValue = ASTUtils.toInteger(entry.getValue()); + } } } } } + + Option board = new Option(boardSet, boardValue); + Option port = new Option(portSet, portValue); + Option baudRate = new Option(baudRateSet, baudRateValue); + Option flash = new Option(flashSet, flashValue); + Option userThreads = new Option(userThreadsSet, userThreadsValue); + return new PlatformOptions(platform, board, port, baudRate, flash, userThreads); } @@ -74,14 +113,83 @@ protected PlatformOptions fromString(String string, MessageReporter reporter) { @Override public void validate(TargetConfig config, MessageReporter reporter) { + var platform = config.get(PlatformProperty.INSTANCE).platform; + switch (platform) { + case RP2040: + validateRP2040(config, reporter); + break; + case FLEXPRET: + validateFlexPRET(config, reporter); + break; + case ZEPHYR: + validateZephyr(config, reporter); + break; + default: + break; + } + } + + private void validateRP2040(TargetConfig config, MessageReporter reporter) { var singleThreaded = config.get(SingleThreadedProperty.INSTANCE); - if (!singleThreaded && config.get(PlatformProperty.INSTANCE).platform == Platform.RP2040) { + if (!singleThreaded) { reporter .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) .error("Platform " + Platform.RP2040 + " does not support threading."); } } + private void validateFlexPRET(TargetConfig config, MessageReporter reporter) { + var platform = config.get(PlatformProperty.INSTANCE); + var board = platform.board(); + if (board.setByUser()) { + if (!board.value().equals("emulator") && !board.value().equals("fpga")) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .error( + "Only \"emulator\" and \"fpga\" are valid options for board property. Got " + + board + + "."); + } + + // Do validation specific to emulator + if (board.value().equals("emulator")) { + if (platform.port().setByUser()) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .warning("Port property ignored for emulator"); + } + if (platform.baudRate().setByUser()) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .warning("Baudrate property ignored for emulator"); + } + } else { + if (platform.baudRate().setByUser()) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .error( + "Baudrate property is entirely controlled by FlexPRET's SDK and cannot be set by" + + " the user"); + } + } + } + } + + private void validateZephyr(TargetConfig config, MessageReporter reporter) { + var platform = config.get(PlatformProperty.INSTANCE); + var singleThreaded = config.get(SingleThreadedProperty.INSTANCE); + + if (singleThreaded) { + if (platform.userThreads().value() > 0) { + reporter + .nowhere() + .warning( + "Specifying user threads is only for threaded Lingua Franca on the Zephyr platform." + + " This option will be ignored."); + } + } + } + @Override public Element toAstElement(PlatformOptions value) { Element e = LfFactory.eINSTANCE.createElement(); @@ -91,11 +199,11 @@ public Element toAstElement(PlatformOptions value) { pair.setName(opt.toString()); switch (opt) { case NAME -> pair.setValue(ASTUtils.toElement(value.platform.toString())); - case BAUDRATE -> pair.setValue(ASTUtils.toElement(value.baudRate)); - case BOARD -> pair.setValue(ASTUtils.toElement(value.board)); - case FLASH -> pair.setValue(ASTUtils.toElement(value.flash)); - case PORT -> pair.setValue(ASTUtils.toElement(value.port)); - case USER_THREADS -> pair.setValue(ASTUtils.toElement(value.userThreads)); + case BAUDRATE -> pair.setValue(ASTUtils.toElement(value.baudRate.value)); + case BOARD -> pair.setValue(ASTUtils.toElement(value.board.value)); + case FLASH -> pair.setValue(ASTUtils.toElement(value.flash.value)); + case PORT -> pair.setValue(ASTUtils.toElement(value.port.value)); + case USER_THREADS -> pair.setValue(ASTUtils.toElement(value.userThreads.value)); } kvp.getPairs().add(pair); } @@ -111,6 +219,9 @@ public String name() { return "platform"; } + /** Keep track of whether a value was set by user or not. */ + public record Option(boolean setByUser, T value) {} + /** Settings related to Platform Options. */ public record PlatformOptions( /** @@ -118,37 +229,35 @@ public record PlatformOptions( * developing for specific OS/Embedded Platform */ Platform platform, + /** * The string value used to determine what type of embedded board we work with and can be used * to simplify the build process. This string has the form "board_name[:option]*" (zero or * more options separated by colons). For example, "pico:usb" specifies a Raspberry Pi Pico * where stdin and stdout go through a USB serial port. */ - String board, + Option board, /** * The string value used to determine the port on which to flash the compiled program (i.e. * /dev/cu.usbmodem21301) */ - String port, + Option port, /** * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate * amongst systems like Arduino, so it's the default value. */ - int baudRate, + Option baudRate, /** * Whether we should automatically attempt to flash once we compile. This may require the use * of board and port values depending on the infrastructure you use to flash the boards. */ - boolean flash, + Option flash, - /** - * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate - * amongst systems like Arduino, so it's the default value. - */ - int userThreads) { + /** The number of threads requested by the user. */ + Option userThreads) { public String[] boardArray() { // Parse board option of the platform target property @@ -158,7 +267,7 @@ public String[] boardArray() { // arduino String[] boardProperties = {}; if (this.board != null) { - boardProperties = this.board.trim().split(":"); + boardProperties = this.board.value.trim().split(":"); // Ignore whitespace for (int i = 0; i < boardProperties.length; i++) { boardProperties[i] = boardProperties[i].trim(); diff --git a/core/src/main/java/org/lflang/target/property/type/PlatformType.java b/core/src/main/java/org/lflang/target/property/type/PlatformType.java index 6382291c61..56715c2a9c 100644 --- a/core/src/main/java/org/lflang/target/property/type/PlatformType.java +++ b/core/src/main/java/org/lflang/target/property/type/PlatformType.java @@ -18,6 +18,7 @@ public enum Platform { LINUX("Linux", true), MAC("Darwin", true), ZEPHYR("Zephyr", true), + FLEXPRET("FlexPRET", true), WINDOWS("Windows", true); final String cMakeName; diff --git a/core/src/main/java/org/lflang/util/ArduinoUtil.java b/core/src/main/java/org/lflang/util/ArduinoUtil.java index 00d11431be..098e166e15 100644 --- a/core/src/main/java/org/lflang/util/ArduinoUtil.java +++ b/core/src/main/java/org/lflang/util/ArduinoUtil.java @@ -57,12 +57,12 @@ private LFCommand arduinoCompileCommand(FileConfig fileConfig, TargetConfig targ var srcGenPath = fileConfig.getSrcGenPath(); String board = - targetConfig.get(PlatformProperty.INSTANCE).board() != null - ? targetConfig.get(PlatformProperty.INSTANCE).board() + targetConfig.get(PlatformProperty.INSTANCE).board().setByUser() + ? targetConfig.get(PlatformProperty.INSTANCE).board().value() : "arduino:avr:leonardo"; String compileDefs = - (targetConfig.get(PlatformProperty.INSTANCE).board().contains("mbed") + (targetConfig.get(PlatformProperty.INSTANCE).board().value().contains("mbed") ? "" : "-DLF_SINGLE_THREADED") + " -DPLATFORM_ARDUINO" @@ -109,8 +109,8 @@ public void buildArduino(FileConfig fileConfig, TargetConfig targetConfig) { "SUCCESS: Compiling generated code for " + fileConfig.name + " finished with no errors."); - if (targetConfig.get(PlatformProperty.INSTANCE).flash()) { - if (targetConfig.get(PlatformProperty.INSTANCE).port() != null) { + if (targetConfig.get(PlatformProperty.INSTANCE).flash().value()) { + if (targetConfig.get(PlatformProperty.INSTANCE).port().value() != null) { messageReporter.nowhere().info("Invoking flash command for Arduino"); LFCommand flash = commandFactory.createCommand( @@ -118,9 +118,9 @@ public void buildArduino(FileConfig fileConfig, TargetConfig targetConfig) { List.of( "upload", "-b", - targetConfig.get(PlatformProperty.INSTANCE).board(), + targetConfig.get(PlatformProperty.INSTANCE).board().value(), "-p", - targetConfig.get(PlatformProperty.INSTANCE).port()), + targetConfig.get(PlatformProperty.INSTANCE).port().value()), fileConfig.getSrcGenPath()); if (flash == null) { messageReporter.nowhere().error("Could not create arduino-cli flash command."); diff --git a/core/src/main/java/org/lflang/util/FlexPRETUtil.java b/core/src/main/java/org/lflang/util/FlexPRETUtil.java new file mode 100644 index 0000000000..bf565bf915 --- /dev/null +++ b/core/src/main/java/org/lflang/util/FlexPRETUtil.java @@ -0,0 +1,44 @@ +package org.lflang.util; + +import java.util.Collections; +import org.lflang.FileConfig; +import org.lflang.MessageReporter; +import org.lflang.generator.GeneratorCommandFactory; +import org.lflang.generator.LFGeneratorContext; +import org.lflang.target.TargetConfig; + +public class FlexPRETUtil { + private LFGeneratorContext context; + private GeneratorCommandFactory commandFactory; + private MessageReporter messageReporter; + + public FlexPRETUtil( + LFGeneratorContext context, + GeneratorCommandFactory commandFactory, + MessageReporter messageReporter) { + this.context = context; + this.commandFactory = commandFactory; + this.messageReporter = messageReporter; + } + + public void flashTarget(FileConfig fileConfig, TargetConfig config) { + var binPath = fileConfig.binPath; + var exeName = fileConfig.name; + + /** FlexPRET's SDK generates a runnable bash script. We just need to run it. */ + var cmd = binPath + "/" + exeName; + var args = Collections.emptyList(); + + LFCommand flash = commandFactory.createCommand(cmd, args); + + if (flash != null) { + int ret = flash.run(context.getCancelIndicator()); + if (ret != 0) { + messageReporter + .nowhere() + .error( + "Command " + cmd + " failed with exit code: " + ret + ". Is the target connected?"); + } + } + } +} diff --git a/core/src/main/java/org/lflang/util/LFCommand.java b/core/src/main/java/org/lflang/util/LFCommand.java index 97325f8e9e..68a29b89d1 100644 --- a/core/src/main/java/org/lflang/util/LFCommand.java +++ b/core/src/main/java/org/lflang/util/LFCommand.java @@ -236,6 +236,11 @@ public void setQuiet() { quiet = true; } + /** Require this to be verbose, overriding the verbosity specified at construction time. */ + public void setVerbose() { + quiet = false; + } + /** * Create a LFCommand instance from a given command and argument list in the current working * directory. diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 0a4272234b..c7583dcd64 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 0a4272234b0e7109271e4797338f099facb8e48d +Subproject commit c7583dcd64f0726fea898b118cb00e853251de42 diff --git a/core/src/testFixtures/java/org/lflang/tests/Configurators.java b/core/src/testFixtures/java/org/lflang/tests/Configurators.java index aed02e9d31..91522e57bd 100644 --- a/core/src/testFixtures/java/org/lflang/tests/Configurators.java +++ b/core/src/testFixtures/java/org/lflang/tests/Configurators.java @@ -27,6 +27,7 @@ import org.lflang.target.TargetConfig; import org.lflang.target.property.LoggingProperty; import org.lflang.target.property.PlatformProperty; +import org.lflang.target.property.PlatformProperty.Option; import org.lflang.target.property.PlatformProperty.PlatformOptions; import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.WorkersProperty; @@ -79,10 +80,10 @@ public static boolean makeZephyrCompatibleUnthreaded(TargetConfig config) { config, new PlatformOptions( Platform.ZEPHYR, - "qemu_cortex_m3", + new Option(true, "qemu_cortex_m3"), platform.port(), platform.baudRate(), - false, + new Option(true, false), platform.userThreads())); return true; } @@ -96,13 +97,41 @@ public static boolean makeZephyrCompatible(TargetConfig config) { config, new PlatformOptions( Platform.ZEPHYR, - "qemu_cortex_m3", + new Option(true, "qemu_cortex_m3"), platform.port(), platform.baudRate(), - false, + new Option(true, false), platform.userThreads())); return true; } + + public static boolean makeFlexPRETCompatible(TargetConfig config) { + /** + * FlexPRET has a maximum of eight hardware threads; override the chosen number of worker + * threads to be 0 (meaning run-time selects it). + * + *

This is to avoid failing tests that have e.g., `workers: 16`. + */ + WorkersProperty.INSTANCE.override(config, 0); + + var platform = config.get(PlatformProperty.INSTANCE); + PlatformProperty.INSTANCE.override( + config, + new PlatformOptions( + Platform.FLEXPRET, + new Option(true, "emulator"), + platform.port(), + platform.baudRate(), + new Option(true, false), + platform.userThreads())); + return true; + } + + public static boolean makeFlexPRETCompatibleUnthreaded(TargetConfig config) { + disableThreading(config); + return makeFlexPRETCompatible(config); + } + /** * Make no changes to the configuration. * diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index 92876aa5f6..f0df742fa8 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -335,6 +335,7 @@ public enum TestCategory { ZEPHYR_THREADED(false, "zephyr" + File.separator + "threaded", TestLevel.BUILD), ZEPHYR_UNTHREADED(false, "zephyr" + File.separator + "unthreaded", TestLevel.BUILD), ZEPHYR_BOARDS(false, "zephyr" + File.separator + "boards", TestLevel.BUILD), + FLEXPRET(false, "flexpret", TestLevel.BUILD), VERIFIER(false, "verifier", TestLevel.EXECUTION), TARGET(false, "", TestLevel.EXECUTION);