mirror of
https://github.com/lowRISC/ibex.git
synced 2025-06-28 09:17:17 -04:00
[ci] Introduce multiple-configuration CI
This commit is contained in:
parent
e1aac0735c
commit
dba0529156
5 changed files with 312 additions and 47 deletions
18
Makefile
18
Makefile
|
@ -1,3 +1,7 @@
|
||||||
|
IBEX_CONFIG ?= small-3cmult
|
||||||
|
|
||||||
|
FUSESOC_CONFIG_OPTS = $(shell ./util/ibex_config.py $(IBEX_CONFIG) fusesoc_opts)
|
||||||
|
|
||||||
all: help
|
all: help
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
|
@ -15,7 +19,8 @@ build-all: build-riscv-compliance build-simple-system build-arty-100 \
|
||||||
.PHONY: build-riscv-compliance
|
.PHONY: build-riscv-compliance
|
||||||
build-riscv-compliance:
|
build-riscv-compliance:
|
||||||
fusesoc --cores-root=. run --target=sim --setup --build \
|
fusesoc --cores-root=. run --target=sim --setup --build \
|
||||||
lowrisc:ibex:ibex_riscv_compliance
|
lowrisc:ibex:ibex_riscv_compliance \
|
||||||
|
$(FUSESOC_CONFIG_OPTS)
|
||||||
|
|
||||||
|
|
||||||
# Simple system
|
# Simple system
|
||||||
|
@ -25,7 +30,8 @@ build-riscv-compliance:
|
||||||
.PHONY: build-simple-system
|
.PHONY: build-simple-system
|
||||||
build-simple-system:
|
build-simple-system:
|
||||||
fusesoc --cores-root=. run --target=sim --setup --build \
|
fusesoc --cores-root=. run --target=sim --setup --build \
|
||||||
lowrisc:ibex:ibex_simple_system
|
lowrisc:ibex:ibex_simple_system \
|
||||||
|
$(FUSESOC_CONFIG_OPTS)
|
||||||
|
|
||||||
simple-system-program = examples/sw/simple_system/hello_test/hello_test.vmem
|
simple-system-program = examples/sw/simple_system/hello_test/hello_test.vmem
|
||||||
sw-simple-hello: $(simple-system-program)
|
sw-simple-hello: $(simple-system-program)
|
||||||
|
@ -77,7 +83,8 @@ program-arty:
|
||||||
# Lint check
|
# Lint check
|
||||||
.PHONY: lint-core-tracing
|
.PHONY: lint-core-tracing
|
||||||
lint-core-tracing:
|
lint-core-tracing:
|
||||||
fusesoc --cores-root . run --target=lint lowrisc:ibex:ibex_core_tracing
|
fusesoc --cores-root . run --target=lint lowrisc:ibex:ibex_core_tracing \
|
||||||
|
$(FUSESOC_CONFIG_OPTS)
|
||||||
|
|
||||||
|
|
||||||
# CS Registers testbench
|
# CS Registers testbench
|
||||||
|
@ -99,3 +106,8 @@ $(Vtb_cs_registers):
|
||||||
run-csr-test: | $(Vtb_cs_registers)
|
run-csr-test: | $(Vtb_cs_registers)
|
||||||
fusesoc --cores-root=. run --target=sim --run \
|
fusesoc --cores-root=. run --target=sim --run \
|
||||||
--tool=verilator lowrisc:ibex:tb_cs_registers
|
--tool=verilator lowrisc:ibex:tb_cs_registers
|
||||||
|
|
||||||
|
# Echo the parameters passed to fusesoc for the chosen IBEX_CONFIG
|
||||||
|
.PHONY: test-cfg
|
||||||
|
test-cfg:
|
||||||
|
@echo $(FUSESOC_CONFIG_OPTS)
|
||||||
|
|
|
@ -113,15 +113,8 @@ jobs:
|
||||||
verilog_lint --version
|
verilog_lint --version
|
||||||
displayName: Display environment
|
displayName: Display environment
|
||||||
|
|
||||||
- bash: |
|
# Verible lint/format is experimental so only run on default config for now,
|
||||||
fusesoc --cores-root . run --no-export --target=lint --tool=verilator lowrisc:ibex:ibex_core_tracing
|
# will eventually become part of the per-config CI
|
||||||
if [ $? != 0 ]; then
|
|
||||||
echo -n "##vso[task.logissue type=error]"
|
|
||||||
echo "Verilog lint with Verilator failed. Run 'fusesoc --cores-root . run --target=lint --tool=verilator lowrisc:ibex:ibex_core_tracing' to check and fix all errors."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
displayName: Lint Verilog source files with Verilator
|
|
||||||
|
|
||||||
- bash: |
|
- bash: |
|
||||||
fusesoc --cores-root . run --no-export --target=lint --tool=veriblelint lowrisc:ibex:ibex_core_tracing
|
fusesoc --cores-root . run --no-export --target=lint --tool=veriblelint lowrisc:ibex:ibex_core_tracing
|
||||||
if [ $? != 0 ]; then
|
if [ $? != 0 ]; then
|
||||||
|
@ -161,7 +154,8 @@ jobs:
|
||||||
displayName: 'Use clang-format to check C/C++ coding style'
|
displayName: 'Use clang-format to check C/C++ coding style'
|
||||||
|
|
||||||
- bash: |
|
- bash: |
|
||||||
# Build and run CSR testbench
|
# Build and run CSR testbench, chosen Ibex configuration does not effect
|
||||||
|
# this so doesn't need to be part of per-config CI
|
||||||
fusesoc --cores-root=. run --target=sim --tool=verilator lowrisc:ibex:tb_cs_registers
|
fusesoc --cores-root=. run --target=sim --tool=verilator lowrisc:ibex:tb_cs_registers
|
||||||
displayName: Build and run CSR testbench with Verilator
|
displayName: Build and run CSR testbench with Verilator
|
||||||
|
|
||||||
|
@ -172,37 +166,9 @@ jobs:
|
||||||
git checkout "$(RISCV_COMPLIANCE_GIT_VERSION)"
|
git checkout "$(RISCV_COMPLIANCE_GIT_VERSION)"
|
||||||
displayName: Get RISC-V Compliance test suite
|
displayName: Get RISC-V Compliance test suite
|
||||||
|
|
||||||
- bash: |
|
# Run Ibex RTL CI per supported configuration
|
||||||
# Build simulation model of Ibex
|
- template : ci/ibex-rtl-ci-steps.yml
|
||||||
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_riscv_compliance --RV32M=1 --RV32E=0
|
parameters:
|
||||||
if [ $? != 0 ]; then
|
ibex_configs:
|
||||||
echo -n "##vso[task.logissue type=error]"
|
- small-3cmult
|
||||||
echo "Unable to build Verilator model of Ibex for compliance testing."
|
- maxperf-1cmult
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run compliance test suite
|
|
||||||
export TARGET_SIM=$PWD/build/lowrisc_ibex_ibex_riscv_compliance_0.1/sim-verilator/Vibex_riscv_compliance
|
|
||||||
export RISCV_PREFIX=riscv32-unknown-elf-
|
|
||||||
export RISCV_TARGET=ibex
|
|
||||||
export RISCV_DEVICE=rv32imc
|
|
||||||
fail=0
|
|
||||||
for isa in rv32i rv32im rv32imc rv32Zicsr rv32Zifencei; do
|
|
||||||
make -C build/riscv-compliance RISCV_ISA=$isa 2>&1 | tee run.log
|
|
||||||
if [ ${PIPESTATUS[0]} != 0 ]; then
|
|
||||||
echo -n "##vso[task.logissue type=error]"
|
|
||||||
echo "The RISC-V compliance test suite failed for $isa"
|
|
||||||
|
|
||||||
# There's no easy way to get the test results in machine-readable
|
|
||||||
# form to properly exclude known-failing tests. Going with an
|
|
||||||
# approximate solution for now.
|
|
||||||
if [ $isa == rv32i ] && grep -q 'FAIL: 4/48' run.log; then
|
|
||||||
echo -n "##vso[task.logissue type=error]"
|
|
||||||
echo "Expected failure for rv32i, see lowrisc/ibex#100 more more information."
|
|
||||||
else
|
|
||||||
fail=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
exit $fail
|
|
||||||
displayName: "Run RISC-V Compliance test for Ibex RV32IMC"
|
|
||||||
|
|
55
ci/ibex-rtl-ci-steps.yml
Normal file
55
ci/ibex-rtl-ci-steps.yml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
parameters:
|
||||||
|
ibex_configs: []
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- ${{ each config in parameters.ibex_configs }}:
|
||||||
|
# ibex_config.py will exit with error code 1 on any error which will cause
|
||||||
|
# the CI to fail if there's an issue with the configuration file or an
|
||||||
|
# incorrect configuration name being used
|
||||||
|
- bash: |
|
||||||
|
./util/ibex_config.py ${{ config }} fusesoc_opts
|
||||||
|
displayName: Display fusesoc config for ${{ config }}
|
||||||
|
|
||||||
|
- bash: |
|
||||||
|
fusesoc --cores-root . run --target=lint lowrisc:ibex:ibex_core_tracing $(./util/ibex_config.py ${{ parameters.ibex_config }} fusesoc_opts)
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
echo -n "##vso[task.logissue type=error]"
|
||||||
|
echo "Verilog lint failed. Run 'fusesoc --cores-root . run --target=lint lowrisc:ibex:ibex_core_tracing' to check and fix all errors."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
displayName: Lint Verilog source files with Verilator for ${{ config }}
|
||||||
|
|
||||||
|
- bash: |
|
||||||
|
# Build simulation model of Ibex
|
||||||
|
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_riscv_compliance $(./util/ibex_config.py ${{ parameters.ibex_config }} fusesoc_opts)
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
echo -n "##vso[task.logissue type=error]"
|
||||||
|
echo "Unable to build Verilator model of Ibex for compliance testing."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run compliance test suite
|
||||||
|
export TARGET_SIM=$PWD/build/lowrisc_ibex_ibex_riscv_compliance_0.1/sim-verilator/Vibex_riscv_compliance
|
||||||
|
export RISCV_PREFIX=riscv32-unknown-elf-
|
||||||
|
export RISCV_TARGET=ibex
|
||||||
|
export RISCV_DEVICE=rv32imc
|
||||||
|
fail=0
|
||||||
|
for isa in rv32i rv32im rv32imc rv32Zicsr rv32Zifencei; do
|
||||||
|
make -C build/riscv-compliance RISCV_ISA=$isa 2>&1 | tee run.log
|
||||||
|
if [ ${PIPESTATUS[0]} != 0 ]; then
|
||||||
|
echo -n "##vso[task.logissue type=error]"
|
||||||
|
echo "The RISC-V compliance test suite failed for $isa"
|
||||||
|
|
||||||
|
# There's no easy way to get the test results in machine-readable
|
||||||
|
# form to properly exclude known-failing tests. Going with an
|
||||||
|
# approximate solution for now.
|
||||||
|
if [ $isa == rv32i ] && grep -q 'FAIL: 4/48' run.log; then
|
||||||
|
echo -n "##vso[task.logissue type=error]"
|
||||||
|
echo "Expected failure for rv32i, see lowrisc/ibex#100 more more information."
|
||||||
|
else
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit $fail
|
||||||
|
displayName: Run RISC-V Compliance test for Ibex RV32IMC for ${{ config }}
|
25
ibex_configs.yaml
Normal file
25
ibex_configs.yaml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright lowRISC contributors.
|
||||||
|
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
# Ibex configurations files, holds the parameter sets that are tested under CI.
|
||||||
|
# Each configuration must specify the same set of parameters
|
||||||
|
|
||||||
|
# Two-stage pipeline without additional branch target ALU and 3 cycle multiplier
|
||||||
|
# (4 cycles for mulh), resulting in 2 stall cycles for mul (3 for mulh)
|
||||||
|
small-3cmult:
|
||||||
|
RV32E : False
|
||||||
|
RV32M : True
|
||||||
|
BranchTargetALU : False
|
||||||
|
WritebackStage : False
|
||||||
|
MultiplierImplementation : "fast"
|
||||||
|
|
||||||
|
# Three-stage pipeline with additional branch traget ALU and 1 cycle multiplier
|
||||||
|
# (2 cycles for mulh) so mul does not stall (mulh stall 1 cycles). This is the
|
||||||
|
# maximum performance configuration.
|
||||||
|
maxperf-1cmult:
|
||||||
|
RV32E : False
|
||||||
|
RV32M : True
|
||||||
|
BranchTargetALU : True
|
||||||
|
WritebackStage : True
|
||||||
|
MultiplierImplementation : "single-cycle"
|
207
util/ibex_config.py
Executable file
207
util/ibex_config.py
Executable file
|
@ -0,0 +1,207 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright lowRISC contributors.
|
||||||
|
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
_DEFAULT_CONFIG_FILE = 'ibex_configs.yaml'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_config(name, config_dict):
|
||||||
|
"""Checks a config_dict matches expectations.
|
||||||
|
|
||||||
|
A config_dict is the dictionary mapping parameters to values for a
|
||||||
|
particular config, it must obey the following rules:
|
||||||
|
- It's a mapping object e.g. OrderedDict
|
||||||
|
- Its values can only be strings, integers or booleans
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name of the config being checked (used to form useful error
|
||||||
|
messages)
|
||||||
|
config_dict: The config_dict to check, must be a mapping object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Nothing, an exception is thrown if an issue is found
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConfigException: An issue was found with config_dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(config_dict, collections.Mapping):
|
||||||
|
raise ConfigException('Config ' + name +
|
||||||
|
' must have dictionary giving parameters')
|
||||||
|
|
||||||
|
for k, v in config_dict.items():
|
||||||
|
if isinstance(v, int):
|
||||||
|
continue
|
||||||
|
if isinstance(v, str):
|
||||||
|
continue
|
||||||
|
if isinstance(v, bool):
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise ConfigException('Parameter ' + k + ' for config ' + name +
|
||||||
|
' must be string, int or bool got ' +
|
||||||
|
str(type(v)))
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_config_parameters(config_dicts):
|
||||||
|
"""Verifies all parameters across config_dicts match expectations.
|
||||||
|
|
||||||
|
Each configuration must obey the following fules:
|
||||||
|
- Each config has the same set of parameters specified
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_dicts: A dictionary of configurations, maps from configuration
|
||||||
|
name to a configuration (itself a dictionary)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Nothing, an exception is thrown if an issue is found
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConfigException: An issue was found with config_dicts
|
||||||
|
"""
|
||||||
|
|
||||||
|
parameters = set()
|
||||||
|
|
||||||
|
first = True
|
||||||
|
|
||||||
|
for name, config_dict in config_dicts.items():
|
||||||
|
parameters_this_config = set()
|
||||||
|
|
||||||
|
for parameter, value in config_dict.items():
|
||||||
|
if first:
|
||||||
|
parameters.add(parameter)
|
||||||
|
|
||||||
|
parameters_this_config.add(parameter)
|
||||||
|
|
||||||
|
if first:
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
parameter_difference = parameters ^ parameters_this_config
|
||||||
|
if parameter_difference:
|
||||||
|
raise ConfigException('Config ' + name +
|
||||||
|
' has differing parameters ' +
|
||||||
|
','.join(parameter_difference))
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_dicts(config_file):
|
||||||
|
"""Extracts a dictionary of configuration dictionaries from a file object
|
||||||
|
|
||||||
|
Given a file object parses YAML from it to obtain a dictionary of
|
||||||
|
configurations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_file: A file object for a file containing the YAML configuration
|
||||||
|
file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dictionary of configurations, maps from a configuration name to a
|
||||||
|
configuration (itself a dictionary mapping parameters to values)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConfigException: An issue was found with the configuration file
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_yaml = yaml.load(config_file, Loader=yaml.SafeLoader)
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
raise ConfigException('Could not decode yaml:\n' + str(e))
|
||||||
|
|
||||||
|
for k, v in config_yaml.items():
|
||||||
|
_verify_config(k, v)
|
||||||
|
|
||||||
|
_verify_config_parameters(config_yaml)
|
||||||
|
|
||||||
|
return config_yaml
|
||||||
|
|
||||||
|
|
||||||
|
def _config_dict_to_fusesoc_opts(config_dict):
|
||||||
|
fusesoc_cmd = []
|
||||||
|
for parameter, value in config_dict.items():
|
||||||
|
if isinstance(value, bool):
|
||||||
|
# For fusesoc boolean parameter are set to true if given on the
|
||||||
|
# command line otherwise false, it doesn't support an explicit
|
||||||
|
# --param=True style
|
||||||
|
if value:
|
||||||
|
fusesoc_cmd.append(shlex.quote('--' + parameter))
|
||||||
|
else:
|
||||||
|
fusesoc_cmd.append(shlex.quote('--' + parameter + '=' + str(value)))
|
||||||
|
|
||||||
|
return ' '.join(fusesoc_cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_file_location():
|
||||||
|
"""Returns the location of the config file
|
||||||
|
|
||||||
|
Default is _DEFAULT_CONFIG_FILE and the IBEX_CONFIG_FILE environment
|
||||||
|
variable overrides the default"""
|
||||||
|
|
||||||
|
return os.environ.get('IBEX_CONFIG_FILE', _DEFAULT_CONFIG_FILE)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
config_outputs = {'fusesoc_opts': _config_dict_to_fusesoc_opts}
|
||||||
|
|
||||||
|
argparser = argparse.ArgumentParser(description=(
|
||||||
|
'Outputs Ibex configuration '
|
||||||
|
'parameters for a named config in a number of formats. If not '
|
||||||
|
'specified on the command line the config will be read from {0}. This '
|
||||||
|
'default can be overridden by setting the IBEX_CONFIG_FILE environment '
|
||||||
|
'variable').format(get_config_file_location()))
|
||||||
|
|
||||||
|
argparser.add_argument('config_name',
|
||||||
|
help=('The name of the Ibex '
|
||||||
|
'configuration to output'))
|
||||||
|
|
||||||
|
argparser.add_argument('output_type',
|
||||||
|
help=('Format to output the '
|
||||||
|
'configuration parameters in'),
|
||||||
|
choices=config_outputs.keys())
|
||||||
|
|
||||||
|
argparser.add_argument('--config_filename',
|
||||||
|
help='Config file to read',
|
||||||
|
default=get_config_file_location())
|
||||||
|
|
||||||
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_file = open(args.config_filename)
|
||||||
|
config_dicts = get_config_dicts(config_file)
|
||||||
|
|
||||||
|
if args.config_name not in config_dicts:
|
||||||
|
print('ERROR: configuration',
|
||||||
|
args.config_name,
|
||||||
|
'not found in',
|
||||||
|
args.config_filename,
|
||||||
|
file=sys.stderr)
|
||||||
|
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(config_outputs[args.output_type](config_dicts[args.config_name]))
|
||||||
|
except ConfigException as ce:
|
||||||
|
print('ERROR: failure to read configuration from',
|
||||||
|
args.config_filename,
|
||||||
|
ce,
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print('ERROR: could not find configuration file',
|
||||||
|
args.config_filename,
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
Add table
Add a link
Reference in a new issue