diff --git a/apio/resources/ecp5/SConstruct b/apio/resources/ecp5/SConstruct index 4ff44842..bc6e277a 100644 --- a/apio/resources/ecp5/SConstruct +++ b/apio/resources/ecp5/SConstruct @@ -1,3 +1,5 @@ +"""Scons script of ECP5 FPGAs.""" + # -*- coding: utf-8 -*- # ---------------------------------------------------------------------- # -- Generic Scons script for Sintesizing hardware on an FPGA and more. @@ -7,25 +9,47 @@ # -- Licence GPLv2 # ---------------------------------------------------------------------- +# W0511: TODO: (fixme) +# pylint: disable=W0511 + +# C0103: Module name doesn't conform to snake_case naming style (invalid-name) +# pylint: disable=C0103 + +# C0209: Formatting could be an f-string (consider-using-f-string) +# pylint: disable=C0209 + +# Similar lines in 2 files +# pylint: disable=R0801 + +# W0613: Unused argument 'xx' (unused-argument) +# pylint: disable=W0613 + +# TODO: Can we fix this? +# E0611: No name in module (no-name-in-module) +# pylint: disable=E0611 + +# TODO: Can we fix this? +# E1101: Instance of 'Base' has no 'X' member (no-member) +# pylint: disable=E1101 + +# TODO: Remove this disable after moving the functions with 'env' argument +# to scons_util.py. +# W0621: Redefining name 'env' from outer scope (redefined-outer-name) +# pylint: disable=W0621 + + import os -import re from platform import system -from apio import scons_util from SCons.Script import ( Builder, Action, - DefaultEnvironment, - Default, AlwaysBuild, GetOption, Exit, COMMAND_LINE_TARGETS, ARGUMENTS, - Variables, - Help, Glob, ) -from SCons.Script.SConscript import SConsEnvironment from apio.scons_util import ( get_constraint_file, error, @@ -35,6 +59,7 @@ from apio.scons_util import ( arg_str, get_verilator_param_str, get_programmer_cmd, + make_verilog_src_scanner, ) # -- Create the environment @@ -77,17 +102,8 @@ FPGA_TYPE_PARAM = "25k" if (FPGA_TYPE == "12k") else "{0}".format(FPGA_TYPE) # -- Target name TARGET = "hardware" -# -- Scan required .list files -list_files_re = re.compile(r"[\n|\s][^\/]?\"(.*\.list?)\"", re.M) - - -def list_files_scan(node, env, path): - contents = node.get_text_contents() - includes = list_files_re.findall(contents) - return env.File(includes) - - -list_scanner = env.Scanner(function=list_files_scan) +# -- Create scannenr to identify dependencies in verilog files. +verilog_src_scanner = make_verilog_src_scanner(env) # -- Get a list of all the verilog files in the src folfer, in ASCII, with # -- the full path. All these files are used for the simulation @@ -109,15 +125,17 @@ synth_builder = Builder( ), suffix=".json", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"Synth": synth_builder}) # -- Place and route Builder. pnr_builder = Builder( - action="nextpnr-ecp5 --{0} --package {2} --json $SOURCE --textcfg $TARGET --lpf {3} {4} --timing-allow-fail --force".format( + action=( + "nextpnr-ecp5 --{0} --package {1} --json $SOURCE --textcfg $TARGET " + "--lpf {2} {3} --timing-allow-fail --force" + ).format( FPGA_TYPE_PARAM, - FPGA_SIZE, FPGA_PACK, LPF, "" if VERBOSE_ALL or VERBOSE_PNR else "-q", @@ -139,27 +157,35 @@ env.Append(BUILDERS={"Bin": bitstream_builder}) # -- No time analysis report implemented for the ECP5 family time_rpt_builder = Builder( - action='echo "Time analysis report is not impelemnted for the ECP5 family." > $TARGET', + action=( + 'echo "Time analysis report is not impelemnted for the ECP5 family." ' + "> $TARGET" + ), suffix=".rpt", src_suffix=".config", ) env.Append(BUILDERS={"Time": time_rpt_builder}) # -- Generate the bitstream -json_out_target = env.Synth(TARGET, [src_synth]) -config_out_target = env.PnR(TARGET, [json_out_target, LPF]) -bitstream_target = env.Bin(TARGET, config_out_target) - -build_target = env.Alias("build", bitstream_target) -AlwaysBuild(build_target) +synth_target = env.Synth(TARGET, [src_synth]) +pnr_target = env.PnR(TARGET, [synth_target, LPF]) +bin_target = env.Bin(TARGET, pnr_target) +build_target = env.Alias("build", bin_target) + +if VERBOSE_YOSYS: + AlwaysBuild(synth_target) +if VERBOSE_PNR: + AlwaysBuild(pnr_target) +if VERBOSE_ALL: + AlwaysBuild(synth_target, pnr_target, build_target) # -- Upload the bitstream into FPGA programmer_cmd = get_programmer_cmd(env) -upload_target = env.Alias("upload", bitstream_target, programmer_cmd) +upload_target = env.Alias("upload", bin_target, programmer_cmd) AlwaysBuild(upload_target) # -- Target time: calculate the time -time_rpt_target = env.Time(config_out_target) +time_rpt_target = env.Time(pnr_target) AlwaysBuild(time_rpt_target) time_target = env.Alias("time", time_rpt_target) @@ -168,32 +194,28 @@ time_target = env.Alias("time", time_rpt_target) def iverilog_generator(source, target, env, for_signature): """Constructs dynamically a commands for iverlog targets builders.""" - target_name, _ = os.path.splitext( - str(target[0]) - ) # E.g. "my_module" or"my_module_tb" - # Testbenches use the value macro VCD_OUTPUT to know the name of the waves output file. - # We also pass a dummy when the verify command to avoid a warning about the undefined macro. + # E.g. "my_module" or "my_module_tb" + target_name, _ = os.path.splitext(str(target[0])) + # Testbenches use the value macro VCD_OUTPUT to know the name of the waves + # output file. We also pass a dummy when the verify command to avoid a + # warning about the undefined macro. is_testbench = target_name.upper().endswith("_TB") is_verify = "verify" in COMMAND_LINE_TARGETS vcd_output_flag = ( - f"-D VCD_OUTPUT=dummy_vcd_output" + "-D VCD_OUTPUT=dummy_vcd_output" if is_verify else f"-D VCD_OUTPUT={target_name}" if is_testbench else "" ) verbose_flag = "-v" if VERBOSE_ALL else "" - # If running a testbench with the sim command, we define the macro INTERACTIVE_SIM that - # allows the testbench to supress assertions so we can examine the waves in gtkwave. - # For example, with an assertion macro like this one that fails when running apio test. - # `define EXPECT(signal, value) \ - # if (signal !== value) begin \ - # $display("ASSERTION FAILED in %m: signal != value"); \ - # `ifndef INTERACTIVE_SIM \ - # $fatal; \ - # `endif \ - # end + # The INTERACTIVE_SIM macro allow testbenchs to distinguish between an + # automatic simulation (apio test) and interactive simulation (apio sim), + # For example, for continuing despire errors in interactive mode. is_interactive_sim = is_testbench and "sim" in COMMAND_LINE_TARGETS - interactive_sim_flag = f"-D INTERACTIVE_SIM" if is_interactive_sim else "" - result = 'iverilog {0} {1} -o $TARGET {2} {3} -D NO_INCLUDES "{3}/ecp5/cells_bb.v" "{4}" $SOURCES'.format( + interactive_sim_flag = "-D INTERACTIVE_SIM" if is_interactive_sim else "" + result = ( + "iverilog {0} {1} -o $TARGET {2} {3} -D NO_INCLUDES " + '"{3}/ecp5/cells_bb.v" "{4}" $SOURCES' + ).format( IVER_PATH, verbose_flag, vcd_output_flag, @@ -208,18 +230,22 @@ iverilog_builder = Builder( generator=iverilog_generator, suffix=".out", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"IVerilog": iverilog_builder}) dot_builder = Builder( - action='yosys -f verilog -p "show -format dot -colors 1 -prefix hardware {0}" {1} $SOURCES'.format( + action=( + "yosys -f verilog -p " + '"show -format dot -colors 1 -prefix hardware {0}" ' + "{1} $SOURCES" + ).format( TOP_MODULE if TOP_MODULE else "unknown_top", "" if VERBOSE_ALL else "-q", ), suffix=".dot", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"DOT": dot_builder}) @@ -228,7 +254,6 @@ svg_builder = Builder( action="dot -Tsvg $SOURCES -o $TARGET", suffix=".svg", src_suffix=".dot", - source_scanner=list_scanner, ) env.Append(BUILDERS={"SVG": svg_builder}) @@ -260,17 +285,18 @@ if "sim" in COMMAND_LINE_TARGETS: # Explicit testbench file name is given via --testbench. sim_testbench = TESTBENCH else: - # No --testbench flag was specified. If there is exactly one testbench then pick - # it, otherwise fail. + # No --testbench flag was specified. If there is exactly one testbench + # then pick it, otherwise fail. if len(list_tb) == 0: fatal_error(env, "No testbench found for simulation.") if len(list_tb) > 1: - # TODO: consider to allow specifying the default testbench in apio.ini. + # TODO: consider to allow specifying the default testbench + # in apio.ini. error( env, - "Found {} testbranches, please use the --testbench flag.".format( - len(list_tb) - ), + ( + "Found {} testbranches, please use the --testbench flag." + ).format(len(list_tb)), ) for tb in list_tb: print("- {}".format(tb)) @@ -304,7 +330,8 @@ if "sim" in COMMAND_LINE_TARGETS: if "test" in COMMAND_LINE_TARGETS: assert "sim" not in COMMAND_LINE_TARGETS, COMMAND_LINE_TARGETS if TESTBENCH: - # Explicit testbench file name is given via --testbench. We test just that one. + # Explicit testbench file name is given via --testbench. We test just + # that one. test_tbs = [TESTBENCH] else: # No --testbench flag specified. We will test all them. @@ -313,13 +340,14 @@ if "test" in COMMAND_LINE_TARGETS: test_tbs = list_tb # All testbenches. tests = [] # Targets of all tests for test_tb in test_tbs: - # Create a list of source files. All the modules + the current testbench. + # Create a list of source files. All the modules + the current + # testbench. src_test = [] src_test.extend(src_synth) # All the .v files. src_test.append(test_tb) # Create the targets for the 'out' and 'vcd' files of the testbench. - # NOTE: Remove the two AlwaysBuild() calls below for an incremental test. Fast, correct, - # but may confuse the user seeing nothing happens. + # NOTE: Remove the two AlwaysBuild() calls below for an incremental + # test. Fast, correct, but may confuse the user seeing nothing happens. test_name, _ = os.path.splitext(test_tb) # e.g. my_module_tb test_out_target = env.IVerilog(test_name, src_test) AlwaysBuild(test_out_target) @@ -327,14 +355,16 @@ if "test" in COMMAND_LINE_TARGETS: AlwaysBuild(test_vcd_target) test_target = env.Alias(test_name, [test_out_target, test_vcd_target]) tests.append(test_target) - # Create a target for the test command that depends on all the test targets. + # Create a target for the test command that depends on all the test + # targets. tests_target = env.Alias("test", tests) AlwaysBuild(tests_target) # -- Verilator config file builder def verilator_config_func(target, source, env): - with open(target[0].get_path(), "w") as target_file: + """Creates a verilator .vlt config files.""" + with open(target[0].get_path(), "w", encoding="utf-8") as target_file: # NOTE: This config was copied from ICE40. Adjust as needed. target_file.write( "`verilator_config\n" @@ -347,33 +377,34 @@ def verilator_config_func(target, source, env): verilator_config_builder = Builder( action=Action(verilator_config_func, "Creating verilator config file."), suffix=".vlt", - source_scanner=list_scanner, ) env.Append(BUILDERS={"VerilatorConfig": verilator_config_builder}) # -- Verilator builder verilator_builder = Builder( - action='verilator --lint-only --bbox-unsup --timing -Wno-TIMESCALEMOD -Wno-MULTITOP {0} {1} {2} {3} $SOURCES "{4}"'.format( + action=( + "verilator --lint-only --bbox-unsup --timing -Wno-TIMESCALEMOD " + '-Wno-MULTITOP {0} {1} {2} {3} {4} $SOURCES "{5}"' + ).format( "-Wall" if VERILATOR_ALL else "", "-Wno-style" if VERILATOR_NO_STYLE else "", VERILATOR_PARAM_STR, "--top-module " + TOP_MODULE if TOP_MODULE else "", + TARGET + ".vlt", YOSYS_CELLS_PATH, ), src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"Verilator": verilator_builder}) # --- Lint lint_config_target = env.VerilatorConfig(TARGET, []) -lint_out_target = env.Verilator( - TARGET, [TARGET + ".vlt"] + src_synth + list_tb -) +lint_out_target = env.Verilator(TARGET, src_synth + list_tb) +env.Depends(lint_out_target, lint_config_target) lint_target = env.Alias("lint", lint_out_target) AlwaysBuild(lint_target) -Default(bitstream_target) # -- These is for cleaning the artifact files. if GetOption("clean"): @@ -389,8 +420,8 @@ if GetOption("clean"): [ time_target, build_target, - json_out_target, - config_out_target, + synth_target, + pnr_target, graph_target, lint_target, ] diff --git a/apio/resources/gowin/SConstruct b/apio/resources/gowin/SConstruct index 47276693..be63d073 100644 --- a/apio/resources/gowin/SConstruct +++ b/apio/resources/gowin/SConstruct @@ -1,3 +1,5 @@ +"""Scons script of Gowin FPGAs.""" + # -*- coding: utf-8 -*- # ---------------------------------------------------------------------- # -- Generic Scons script for Sintesizing hardware on an FPGA and more. @@ -7,25 +9,47 @@ # -- Licence GPLv2 # ---------------------------------------------------------------------- +# W0511: TODO: (fixme) +# pylint: disable=W0511 + +# C0103: Module name doesn't conform to snake_case naming style (invalid-name) +# pylint: disable=C0103 + +# C0209: Formatting could be an f-string (consider-using-f-string) +# pylint: disable=C0209 + +# Similar lines in 2 files +# pylint: disable=R0801 + +# W0613: Unused argument 'xx' (unused-argument) +# pylint: disable=W0613 + +# TODO: Can we fix this? +# E0611: No name in module (no-name-in-module) +# pylint: disable=E0611 + +# TODO: Can we fix this? +# E1101: Instance of 'Base' has no 'X' member (no-member) +# pylint: disable=E1101 + +# TODO: Remove this disable after moving the functions with 'env' argument +# to scons_util.py. +# W0621: Redefining name 'env' from outer scope (redefined-outer-name) +# pylint: disable=W0621 + + import os -import re from platform import system -from apio import scons_util from SCons.Script import ( Builder, Action, - DefaultEnvironment, - Default, AlwaysBuild, GetOption, Exit, COMMAND_LINE_TARGETS, ARGUMENTS, - Variables, - Help, Glob, ) -from SCons.Script.SConscript import SConsEnvironment from apio.scons_util import ( get_constraint_file, error, @@ -35,6 +59,7 @@ from apio.scons_util import ( arg_str, get_verilator_param_str, get_programmer_cmd, + make_verilog_src_scanner, ) # -- Create the environment @@ -72,17 +97,8 @@ IVER_PATH = "" if isWindows or not IVL_PATH else '-B "{0}"'.format(IVL_PATH) # -- Target name TARGET = "hardware" -# -- Scan required .list files -list_files_re = re.compile(r"[\n|\s][^\/]?\"(.*\.list?)\"", re.M) - - -def list_files_scan(node, env, path): - contents = node.get_text_contents() - includes = list_files_re.findall(contents) - return env.File(includes) - - -list_scanner = env.Scanner(function=list_files_scan) +# -- Create scannenr to identify dependencies in verilog files. +verilog_src_scanner = make_verilog_src_scanner(env) # -- Get a list of all the verilog files in the src folfer, in ASCII, with # -- the full path. All these files are used for the simulation @@ -98,25 +114,28 @@ CST = get_constraint_file(env, ".cst", TOP_MODULE) # -- Synthesizing Builder synth_builder = Builder( - action='yosys -D LEDS_NR=6 -p "synth_gowin {0} -json $TARGET" {1} $SOURCES'.format( + action=( + 'yosys -D LEDS_NR=6 -p "synth_gowin {0} -json $TARGET" {1} $SOURCES' + ).format( ("-top " + TOP_MODULE) if TOP_MODULE else "", "" if VERBOSE_ALL or VERBOSE_YOSYS else "-q", ), suffix=".json", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"Synth": synth_builder}) # -- Place and route Builder. pnr_builder = Builder( - action="nextpnr-himbaechel --device {0} --json $SOURCE --write $TARGET --vopt family={5} --vopt cst={3} {4}".format( + action=( + "nextpnr-himbaechel --device {0} --json $SOURCE --write $TARGET " + "--vopt family={1} --vopt cst={2} {3}" + ).format( FPGA_MODEL, - FPGA_SIZE, - FPGA_PACK, + FPGA_TYPE, CST, "" if VERBOSE_ALL or VERBOSE_PNR else "-q", - FPGA_TYPE, ), suffix=".pnr.json", src_suffix=".json", @@ -134,32 +153,35 @@ env.Append(BUILDERS={"Bin": bitstream_builder}) # -- Time report builder # https://github.com/cliffordwolf/icestorm/issues/57 time_rpt_builder = Builder( - action='echo "Time analysis report is not impelemnted for the Gowin family." > $TARGET', + action=( + 'echo "Time analysis report is not impelemnted for the Gowin family." ' + "> $TARGET" + ), suffix=".rpt", src_suffix=".pnr.json", ) env.Append(BUILDERS={"Time": time_rpt_builder}) # -- Generate the bitstream -blif_target = env.Synth(TARGET, [src_synth]) -asc_target = env.PnR(TARGET, [blif_target, CST]) -bitstream_target = env.Bin(TARGET, asc_target) - -build_target = env.Alias("build", bitstream_target) -AlwaysBuild(build_target) - -if VERBOSE_ALL or VERBOSE_YOSYS: - AlwaysBuild(blif_target) -if VERBOSE_ALL or VERBOSE_PNR: - AlwaysBuild(asc_target) +synth_target = env.Synth(TARGET, [src_synth]) +pnr_target = env.PnR(TARGET, [synth_target, CST]) +bin_target = env.Bin(TARGET, pnr_target) +build_target = env.Alias("build", bin_target) + +if VERBOSE_YOSYS: + AlwaysBuild(synth_target) +if VERBOSE_PNR: + AlwaysBuild(pnr_target) +if VERBOSE_ALL: + AlwaysBuild(synth_target, pnr_target, build_target) # -- Upload the bitstream into FPGA programmer_cmd = get_programmer_cmd(env) -upload_target = env.Alias("upload", bitstream_target, programmer_cmd) +upload_target = env.Alias("upload", bin_target, programmer_cmd) AlwaysBuild(upload_target) # -- Target time: calculate the time -rpt_target = env.Time(asc_target) +rpt_target = env.Time(pnr_target) AlwaysBuild(rpt_target) time_target = env.Alias("time", rpt_target) @@ -168,31 +190,24 @@ time_target = env.Alias("time", rpt_target) def iverilog_generator(source, target, env, for_signature): """Constructs dynamically a commands for iverlog targets builders.""" - target_name, _ = os.path.splitext( - str(target[0]) - ) # E.g. "my_module" or"my_module_tb" - # Testbenches use the value macro VCD_OUTPUT to know the name of the waves output file. - # We also pass a dummy when the verify command to avoid a warning about the undefined macro. + # E.g. "my_module" or"my_module_tb" + target_name, _ = os.path.splitext(str(target[0])) + # Testbenches use the macro VCD_OUTPUT to know the name of the waves + # output file.We also pass a dummy when the verify command to avoid a + # warning about the undefined macro. is_testbench = target_name.upper().endswith("_TB") is_verify = "verify" in COMMAND_LINE_TARGETS vcd_output_flag = ( - f"-D VCD_OUTPUT=dummy_vcd_output" + "-D VCD_OUTPUT=dummy_vcd_output" if is_verify else f"-D VCD_OUTPUT={target_name}" if is_testbench else "" ) verbose_flag = "-v" if VERBOSE_ALL else "" - # If running a testbench with the sim command, we define the macro INTERACTIVE_SIM that - # allows the testbench to supress assertions so we can examine the waves in gtkwave. - # For example, with an assertion macro like this one that fails when running apio test. - # `define EXPECT(signal, value) \ - # if (signal !== value) begin \ - # $display("ASSERTION FAILED in %m: signal != value"); \ - # `ifndef INTERACTIVE_SIM \ - # $fatal; \ - # `endif \ - # end + # The INTERACTIVE_SIM macro allow testbenchs to distinguish between an + # automatic simulation (apio test) and interactive simulation (apio sim), + # For example, for continuing despire errors in interactive mode. is_interactive_sim = is_testbench and "sim" in COMMAND_LINE_TARGETS - interactive_sim_flag = f"-D INTERACTIVE_SIM" if is_interactive_sim else "" + interactive_sim_flag = "-D INTERACTIVE_SIM" if is_interactive_sim else "" result = 'iverilog {0} {1} -o $TARGET {2} {3} "{4}" $SOURCES'.format( IVER_PATH, verbose_flag, @@ -208,18 +223,21 @@ iverilog_builder = Builder( generator=iverilog_generator, suffix=".out", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"IVerilog": iverilog_builder}) dot_builder = Builder( - action='yosys -f verilog -p "show -format dot -colors 1 -prefix hardware {0}" {1} $SOURCES'.format( + action=( + 'yosys -f verilog -p "show -format dot -colors 1 -prefix hardware ' + '{0}" {1} $SOURCES' + ).format( TOP_MODULE if TOP_MODULE else "unknown_top", "" if VERBOSE_ALL else "-q", ), suffix=".dot", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"DOT": dot_builder}) @@ -228,7 +246,6 @@ svg_builder = Builder( action="dot -Tsvg $SOURCES -o $TARGET", suffix=".svg", src_suffix=".dot", - source_scanner=list_scanner, ) env.Append(BUILDERS={"SVG": svg_builder}) @@ -260,16 +277,18 @@ if "sim" in COMMAND_LINE_TARGETS: # Explicit testbench file name is given via --testbench. sim_testbench = TESTBENCH else: - # No --testbench flag specified. If there is exactly one testbench then pick - # it, otherwise fail. + # No --testbench flag specified. If there is exactly one testbench then + # pick it, otherwise fail. if len(list_tb) == 0: fatal_error(env, "No testbench found for simulation.") if len(list_tb) > 1: - # TODO: consider to allow specifying the default testbench in apio.ini. + # TODO: consider to allow specifying the default testbench + # in apio.ini. error( - "Found {} testbranches, please use the --testbench flag.".format( - len(list_tb) - ) + env, + ( + "Found {} testbranches, please use the --testbench flag." + ).format(len(list_tb)), ) for tb in list_tb: print("- {}".format(tb)) @@ -303,7 +322,8 @@ if "sim" in COMMAND_LINE_TARGETS: if "test" in COMMAND_LINE_TARGETS: assert "sim" not in COMMAND_LINE_TARGETS, COMMAND_LINE_TARGETS if TESTBENCH: - # Explicit testbench file name is given via --testbench. We test just that one. + # Explicit testbench file name is given via --testbench. We test just + # that one. test_tbs = [TESTBENCH] else: # No --testbench flag specified. We will test all them. @@ -312,12 +332,14 @@ if "test" in COMMAND_LINE_TARGETS: test_tbs = list_tb # All testbenches. tests = [] # Targets of all tests for test_tb in test_tbs: - # Create a list of source files. All the modules + the current testbench. + # Create a list of source files. All the modules + the current + # testbench. src_test = [] src_test.extend(src_synth) # All the .v files. src_test.append(test_tb) # Create the targets for the 'out' and 'vcd' files of the testbench. - # NOTE: Remove the two AlwaysBuild() calls below for an incremental test. Fast, correct, + # NOTE: Remove the two AlwaysBuild() calls below for an incremental + # test. Fast, correct, # but may confuse the user seeing nothing happens. test_name, _ = os.path.splitext(test_tb) # e.g. my_module_tb test_out_target = env.IVerilog(test_name, src_test) @@ -326,14 +348,16 @@ if "test" in COMMAND_LINE_TARGETS: AlwaysBuild(test_vcd_target) test_target = env.Alias(test_name, [test_out_target, test_vcd_target]) tests.append(test_target) - # Create a target for the test command that depends on all the test targets. + # Create a target for the test command that depends on all the test + # targets. tests_target = env.Alias("test", tests) AlwaysBuild(tests_target) # -- Verilator config file builder def verilator_config_func(target, source, env): - with open(target[0].get_path(), "w") as target_file: + """Creates a verilator .vlt config files.""" + with open(target[0].get_path(), "w", encoding="utf-8") as target_file: # NOTE: This config was copied from ICE40. Adjust as needed. target_file.write( "`verilator_config\n" @@ -346,33 +370,33 @@ def verilator_config_func(target, source, env): verilator_config_builder = Builder( action=Action(verilator_config_func, "Creating verilator config file."), suffix=".vlt", - source_scanner=list_scanner, ) env.Append(BUILDERS={"VerilatorConfig": verilator_config_builder}) # -- Verilator builder verilator_builder = Builder( - action='verilator --lint-only --bbox-unsup --timing -Wno-TIMESCALEMOD -Wno-MULTITOP {0} {1} {2} {3} $SOURCES "{4}"'.format( + action=( + "verilator --lint-only --bbox-unsup --timing -Wno-TIMESCALEMOD " + '-Wno-MULTITOP {0} {1} {2} {3} {4} $SOURCES "{5}"' + ).format( "-Wall" if VERILATOR_ALL else "", "-Wno-style" if VERILATOR_NO_STYLE else "", VERILATOR_PARAM_STR, "--top-module " + TOP_MODULE if TOP_MODULE else "", + TARGET + ".vlt", YOSYS_CELLS_PATH, ), src_suffix=".v", - source_scanner=list_scanner, ) env.Append(BUILDERS={"Verilator": verilator_builder}) # --- Lint lint_config_target = env.VerilatorConfig(TARGET, []) -lint_out_target = env.Verilator( - TARGET, [TARGET + ".vlt"] + src_synth + list_tb -) +lint_out_target = env.Verilator(TARGET, src_synth + list_tb) +env.Depends(lint_out_target, lint_config_target) lint_target = env.Alias("lint", lint_out_target) AlwaysBuild(lint_target) -Default(bitstream_target) # -- These is for cleaning the artifact files. if GetOption("clean"): diff --git a/apio/resources/ice40/SConstruct b/apio/resources/ice40/SConstruct index 092c47b9..3831d868 100644 --- a/apio/resources/ice40/SConstruct +++ b/apio/resources/ice40/SConstruct @@ -1,3 +1,5 @@ +"""Scons script of ICE40 FPGAs.""" + # -*- coding: utf-8 -*- # ---------------------------------------------------------------------- # -- Generic Scons script for Sintesizing hardware on an FPGA and more. @@ -7,21 +9,45 @@ # -- Licence GPLv2 # ---------------------------------------------------------------------- +# W0511: TODO: (fixme) +# pylint: disable=W0511 + +# C0103: Module name doesn't conform to snake_case naming style (invalid-name) +# pylint: disable=C0103 + +# C0209: Formatting could be an f-string (consider-using-f-string) +# pylint: disable=C0209 + +# Similar lines in 2 files +# pylint: disable=R0801 + +# W0613: Unused argument 'xx' (unused-argument) +# pylint: disable=W0613 + +# TODO: Can we fix this? +# E0611: No name in module (no-name-in-module) +# pylint: disable=E0611 + +# TODO: Can we fix this? +# E1101: Instance of 'Base' has no 'X' member (no-member) +# pylint: disable=E1101 + +# TODO: Remove this disable after moving the functions with 'env' argument +# to scons_util.py. +# W0621: Redefining name 'env' from outer scope (redefined-outer-name) +# pylint: disable=W0621 + + import os -import re from platform import system from SCons.Script import ( Builder, Action, - DefaultEnvironment, - Default, AlwaysBuild, GetOption, Exit, COMMAND_LINE_TARGETS, ARGUMENTS, - Variables, - Help, Glob, ) from apio.scons_util import ( @@ -33,6 +59,7 @@ from apio.scons_util import ( arg_str, get_verilator_param_str, get_programmer_cmd, + make_verilog_src_scanner, ) # -- Create the environment @@ -68,17 +95,8 @@ IVER_PATH = "" if isWindows or not IVL_PATH else '-B "{0}"'.format(IVL_PATH) # -- Target name TARGET = "hardware" -# -- Scan required .list files -list_files_re = re.compile(r"[\n|\s][^\/]?\"(.*\.list?)\"", re.M) - - -def list_files_scan(node, env, path): - contents = node.get_text_contents() - includes = list_files_re.findall(contents) - return env.File(includes) - - -list_scanner = env.Scanner(function=list_files_scan) +# -- Create scannenr to identify dependencies in verilog files. +verilog_src_scanner = make_verilog_src_scanner(env) # -- Get a list of all the verilog files in the src folfer, in ASCII, with # -- the full path. All these files are used for the simulation @@ -100,13 +118,16 @@ synth_builder = Builder( ), suffix=".json", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"Synth": synth_builder}) # -- Place and route Builder. pnr_builder = Builder( - action="nextpnr-ice40 --{0}{1} --package {2} --json $SOURCE --asc $TARGET --pcf {3} {4}".format( + action=( + "nextpnr-ice40 --{0}{1} --package {2} --json $SOURCE --asc $TARGET " + "--pcf {3} {4}" + ).format( FPGA_TYPE, FPGA_SIZE, FPGA_PACK, @@ -136,25 +157,25 @@ time_rpt_builder = Builder( env.Append(BUILDERS={"Time": time_rpt_builder}) # -- Generate the bitstream -blif_target = env.Synth(TARGET, [src_synth]) -asc_target = env.PnR(TARGET, [blif_target, PCF]) -bitstream_target = env.Bin(TARGET, asc_target) - -build_target = env.Alias("build", bitstream_target) -AlwaysBuild(build_target) - -if VERBOSE_ALL or VERBOSE_YOSYS: - AlwaysBuild(blif_target) -if VERBOSE_ALL or VERBOSE_PNR: - AlwaysBuild(asc_target) +synth_target = env.Synth(TARGET, [src_synth]) +pnr_target = env.PnR(TARGET, [synth_target, PCF]) +bin_target = env.Bin(TARGET, pnr_target) +build_target = env.Alias("build", bin_target) + +if VERBOSE_YOSYS: + AlwaysBuild(synth_target) +if VERBOSE_PNR: + AlwaysBuild(pnr_target) +if VERBOSE_ALL: + AlwaysBuild(synth_target, pnr_target, build_target) # -- Upload the bitstream into FPGA programmer_cmd = get_programmer_cmd(env) -upload_target = env.Alias("upload", bitstream_target, programmer_cmd) +upload_target = env.Alias("upload", bin_target, programmer_cmd) AlwaysBuild(upload_target) # -- Target time: calculate the time -rpt_target = env.Time(asc_target) +rpt_target = env.Time(pnr_target) AlwaysBuild(rpt_target) time_target = env.Alias("time", rpt_target) @@ -163,32 +184,28 @@ time_target = env.Alias("time", rpt_target) def iverilog_generator(source, target, env, for_signature): """Constructs dynamically a commands for iverlog targets builders.""" - target_name, _ = os.path.splitext( - str(target[0]) - ) # E.g. "my_module" or"my_module_tb" - # Testbenches use the value macro VCD_OUTPUT to know the name of the waves output file. - # We also pass a dummy when the verify command to avoid a warning about the undefined macro. + # E.g. "my_module" or "my_module_tb" + target_name, _ = os.path.splitext(str(target[0])) + # Testbenches use the macro VCD_OUTPUT to know the name of the waves + # output file.We also pass a dummy when the verify command to avoid a + # warning about the undefined macro. is_testbench = target_name.upper().endswith("_TB") is_verify = "verify" in COMMAND_LINE_TARGETS vcd_output_flag = ( - f"-D VCD_OUTPUT=dummy_vcd_output" + "-D VCD_OUTPUT=dummy_vcd_output" if is_verify else f"-D VCD_OUTPUT={target_name}" if is_testbench else "" ) verbose_flag = "-v" if VERBOSE_ALL else "" - # If running a testbench with the sim command, we define the macro INTERACTIVE_SIM that - # allows the testbench to supress assertions so we can examine the waves in gtkwave. - # For example, with an assertion macro like this one that fails when running apio test. - # `define EXPECT(signal, value) \ - # if (signal !== value) begin \ - # $display("ASSERTION FAILED in %m: signal != value"); \ - # `ifndef INTERACTIVE_SIM \ - # $fatal; \ - # `endif \ - # end + # The INTERACTIVE_SIM macro allow testbenchs to distinguish between an + # automatic simulation (apio test) and interactive simulation (apio sim), + # For example, for continuing despire errors in interactive mode. is_interactive_sim = is_testbench and "sim" in COMMAND_LINE_TARGETS - interactive_sim_flag = f"-D INTERACTIVE_SIM" if is_interactive_sim else "" - result = 'iverilog {0} {1} -o $TARGET {2} {3} -D NO_ICE40_DEFAULT_ASSIGNMENTS "{4}" $SOURCES'.format( + interactive_sim_flag = "-D INTERACTIVE_SIM" if is_interactive_sim else "" + result = ( + "iverilog {0} {1} -o $TARGET {2} {3} -D NO_ICE40_DEFAULT_ASSIGNMENTS " + '"{4}" $SOURCES' + ).format( IVER_PATH, verbose_flag, vcd_output_flag, @@ -203,18 +220,21 @@ iverilog_builder = Builder( generator=iverilog_generator, suffix=".out", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"IVerilog": iverilog_builder}) dot_builder = Builder( - action='yosys -f verilog -p "show -format dot -colors 1 -prefix hardware {0}" {1} $SOURCES'.format( + action=( + 'yosys -f verilog -p "show -format dot -colors 1 ' + '-prefix hardware {0}" {1} $SOURCES' + ).format( TOP_MODULE if TOP_MODULE else "unknown_top", "" if VERBOSE_ALL else "-q", ), suffix=".dot", src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"DOT": dot_builder}) @@ -223,7 +243,6 @@ svg_builder = Builder( action="dot -Tsvg $SOURCES -o $TARGET", suffix=".svg", src_suffix=".dot", - source_scanner=list_scanner, ) env.Append(BUILDERS={"SVG": svg_builder}) @@ -255,17 +274,18 @@ if "sim" in COMMAND_LINE_TARGETS: # Explicit testbench file name is given via --testbench. sim_testbench = TESTBENCH else: - # No --testbench flag specified. If there is exactly one testbench then pick - # it, otherwise fail. + # No --testbench flag specified. If there is exactly one testbench then + # pick it, otherwise fail. if len(list_tb) == 0: fatal_error(env, "No testbench found for simulation.") if len(list_tb) > 1: - # TODO: consider to allow specifying the default testbench in apio.ini. + # TODO: consider to allow specifying the default testbench in + # apio.ini. error( env, - "Found {} testbranches, please use the --testbench flag.".format( - len(list_tb) - ), + ( + "Found {} testbranches, please use the --testbench flag." + ).format(len(list_tb)), ) for tb in list_tb: print("- {}".format(tb)) @@ -299,7 +319,8 @@ if "sim" in COMMAND_LINE_TARGETS: if "test" in COMMAND_LINE_TARGETS: assert "sim" not in COMMAND_LINE_TARGETS, COMMAND_LINE_TARGETS if TESTBENCH: - # Explicit testbench file name is given via --testbench. We test just that one. + # Explicit testbench file name is given via --testbench. We test just + # that one. test_tbs = [TESTBENCH] else: # No --testbench flag specified. We will test all them. @@ -308,12 +329,14 @@ if "test" in COMMAND_LINE_TARGETS: test_tbs = list_tb # All testbenches. tests = [] # Targets of all tests for test_tb in test_tbs: - # Create a list of source files. All the modules + the current testbench. + # Create a list of source files. All the modules + the current + # testbench. src_test = [] src_test.extend(src_synth) # All the .v files. src_test.append(test_tb) # Create the targets for the 'out' and 'vcd' files of the testbench. - # NOTE: Remove the two AlwaysBuild() calls below for an incremental test. Fast, correct, + # NOTE: Remove the two AlwaysBuild() calls below for an incremental + # test. Fast, correct, # but may confuse the user seeing nothing happens. test_name, _ = os.path.splitext(test_tb) # e.g. my_module_tb test_out_target = env.IVerilog(test_name, src_test) @@ -322,14 +345,16 @@ if "test" in COMMAND_LINE_TARGETS: AlwaysBuild(test_vcd_target) test_target = env.Alias(test_name, [test_out_target, test_vcd_target]) tests.append(test_target) - # Create a target for the test command that depends on all the test targets. + # Create a target for the test command that depends on all the test + # targets. tests_target = env.Alias("test", tests) AlwaysBuild(tests_target) # -- Verilator config file builder def verilator_config_func(target, source, env): - with open(target[0].get_path(), "w") as target_file: + """Creates a verilator .vlt config files.""" + with open(target[0].get_path(), "w", encoding="utf-8") as target_file: target_file.write( "`verilator_config\n" f'lint_off -rule COMBDLY -file "{YOSYS_CELLS_PATH}"\n' @@ -341,33 +366,35 @@ def verilator_config_func(target, source, env): verilator_config_builder = Builder( action=Action(verilator_config_func, "Creating verilator config file."), suffix=".vlt", - source_scanner=list_scanner, ) env.Append(BUILDERS={"VerilatorConfig": verilator_config_builder}) # -- Verilator builder verilator_builder = Builder( - action='verilator --lint-only --bbox-unsup --timing -Wno-TIMESCALEMOD -Wno-MULTITOP -DNO_ICE40_DEFAULT_ASSIGNMENTS {0} {1} {2} {3} $SOURCES "{4}"'.format( + action=( + "verilator --lint-only --bbox-unsup --timing -Wno-TIMESCALEMOD " + "-Wno-MULTITOP -DNO_ICE40_DEFAULT_ASSIGNMENTS {0} {1} {2} {3} " + '{4} $SOURCES "{5}"' + ).format( "-Wall" if VERILATOR_ALL else "", "-Wno-style" if VERILATOR_NO_STYLE else "", VERILATOR_PARAM_STR, "--top-module " + TOP_MODULE if TOP_MODULE else "", + TARGET + ".vlt", YOSYS_CELLS_PATH, ), src_suffix=".v", - source_scanner=list_scanner, + source_scanner=verilog_src_scanner, ) env.Append(BUILDERS={"Verilator": verilator_builder}) # --- Lint lint_config_target = env.VerilatorConfig(TARGET, []) -lint_out_target = env.Verilator( - TARGET, [TARGET + ".vlt"] + src_synth + list_tb -) +lint_out_target = env.Verilator(TARGET, src_synth + list_tb) +env.Depends(lint_out_target, lint_config_target) lint_target = env.Alias("lint", lint_out_target) AlwaysBuild(lint_target) -Default(bitstream_target) # -- These is for cleaning the artifact files. if GetOption("clean"): diff --git a/apio/scons_util.py b/apio/scons_util.py index ea880a9e..0e50d5fd 100644 --- a/apio/scons_util.py +++ b/apio/scons_util.py @@ -17,9 +17,12 @@ # pylint: disable=unused-argument import os -from typing import Dict +import re +from typing import Dict, List +import SCons from SCons.Script import DefaultEnvironment from SCons.Script.SConscript import SConsEnvironment +import SCons.Node.FS import click @@ -212,3 +215,52 @@ def get_programmer_cmd(env: SConsEnvironment) -> str: prog_cmd = prog_arg.replace("${SOURCE}", "$SOURCE") return prog_cmd + + +def make_verilog_src_scanner(env: SConsEnvironment) -> SCons.Scanner: + """Creates and returns a scons Scanner object for scanning verilog + files for dependencies. + """ + # A Regex to icestudio propriaetry references for *.list files. + # Example: + # Text: ' parameter v771499 = "v771499.list"' + # Captures: 'v771499.list' + icestudio_list_re = re.compile(r"[\n|\s][^\/]?\"(.*\.list?)\"", re.M) + + # A regex to match a verilog include. + # Example + # Text: `include "apio_testing.vh" + # Capture: 'apio_testing.vh' + verilog_include_re = re.compile( + r'`\s*include\s+["]([a-zA-Z_./]+)["]', re.M + ) + + def verilog_src_scanner_func( + file_node: SCons.Node.FS.File, env: SConsEnvironment, ignored_path + ) -> List[str]: + """Given a Verilog file, scan it and return a list of references + to other files it depends on. It's not require to report dependency + on another .v file in the project since scons loads anyway + all the .v files in the project. + + Returns a list of files. + """ + # Sanity check. Should be called only to scan verilog files. + assert file_node.name.lower().endswith( + ".v" + ), f"Not a .v file: {file_node.name}" + includes_set = set() + file_text = file_node.get_text_contents() + # Get IceStudio includes. + includes = icestudio_list_re.findall(file_text) + includes_set.update(includes) + # Get Standard verilog includes. + includes = verilog_include_re.findall(file_text) + includes_set.update(includes) + # Get a deterministic list. + includes_list = sorted(list(includes_set)) + # For debugging + # info(env, f"*** {file_node.name} includes {includes_list}") + return env.File(includes_list) + + return env.Scanner(function=verilog_src_scanner_func) diff --git a/test-examples/ColorLight-5A-75B-V8/Blinky/Blinky.v b/test-examples/ColorLight-5A-75B-V8/Blinky/Blinky.v new file mode 100644 index 00000000..f662a47e --- /dev/null +++ b/test-examples/ColorLight-5A-75B-V8/Blinky/Blinky.v @@ -0,0 +1,17 @@ +//------------------------------------------------------------------ +//-- Blinking LED +//------------------------------------------------------------------ + +module Test ( + input CLK, // 25MHz clock + output led, // LED to blink +); + + reg [23:0] counter = 0; + + always @(posedge CLK) + counter <= counter + 1; + + assign led = counter[23]; + +endmodule diff --git a/test-examples/ColorLight-5A-75B-V8/Blinky/apio.ini b/test-examples/ColorLight-5A-75B-V8/Blinky/apio.ini new file mode 100644 index 00000000..0a4a03e7 --- /dev/null +++ b/test-examples/ColorLight-5A-75B-V8/Blinky/apio.ini @@ -0,0 +1,4 @@ +[env] +board = ColorLight-5A-75B-V8 +top-module = Test + diff --git a/test-examples/ColorLight-5A-75B-V8/Blinky/info b/test-examples/ColorLight-5A-75B-V8/Blinky/info new file mode 100644 index 00000000..f4dd032a --- /dev/null +++ b/test-examples/ColorLight-5A-75B-V8/Blinky/info @@ -0,0 +1 @@ +Blinking LED diff --git a/test-examples/ColorLight-5A-75B-V8/Blinky/pinout.lpf b/test-examples/ColorLight-5A-75B-V8/Blinky/pinout.lpf new file mode 100644 index 00000000..daeba267 --- /dev/null +++ b/test-examples/ColorLight-5A-75B-V8/Blinky/pinout.lpf @@ -0,0 +1,12 @@ +# -- Board: ColorLight-5A-75E-V71_(FT2232H) + +# -- CLK +LOCATE COMP "CLK" SITE "P6"; +IOBUF PORT "CLK" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; + +# -- LED +LOCATE COMP "led" SITE "T6"; +IOBUF PORT "led" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4; + + + diff --git a/tox.ini b/tox.ini index ad141350..aff40b99 100644 --- a/tox.ini +++ b/tox.ini @@ -49,12 +49,19 @@ deps = pytest==8.3.3 setenv= - LINT_DIRS = apio test test-boards + # TODO: Can we avoid specifying explicitly each SConstruct? + LINT_ITEMS = \ + apio \ + test \ + test-boards \ + apio/resources/ice40/SConstruct \ + apio/resources/ecp5/SConstruct \ + apio/resources/gowin/SConstruct commands = - black {env:LINT_DIRS} - flake8 {env:LINT_DIRS} - pylint {env:LINT_DIRS} + black {env:LINT_ITEMS} + flake8 {env:LINT_ITEMS} + pylint {env:LINT_ITEMS} # ----------------------------------------------------