diff --git a/Release.md b/Release.md index 3cd972d..db62ed1 100644 --- a/Release.md +++ b/Release.md @@ -1,5 +1,11 @@ # OneWare.CologneChip +## Release 0.7.1 +- nextpnr support (OSSCAD Suite needed) +- Global and project support for changing the toolchain +- Fixed some bugs +- Removed Release 0.7 from the oneware-extension.json (faulty version) + ## Release 0.7 - Update Dependencies for OW Studio Version 21.4.0 diff --git a/oneware-extension.json b/oneware-extension.json index f1b0fe3..ec368d9 100644 --- a/oneware-extension.json +++ b/oneware-extension.json @@ -77,7 +77,7 @@ ] }, { - "version": "0.7", + "version": "0.7.1", "targets": [ { "target": "all" diff --git a/src/OneWare.CologneChip/CologneChipLoader.cs b/src/OneWare.CologneChip/CologneChipLoader.cs index dbbbdff..78c00bd 100644 --- a/src/OneWare.CologneChip/CologneChipLoader.cs +++ b/src/OneWare.CologneChip/CologneChipLoader.cs @@ -4,6 +4,7 @@ using OneWare.UniversalFpgaProjectSystem.Models; using OneWare.UniversalFpgaProjectSystem.Parser; using OneWare.UniversalFpgaProjectSystem.Services; +using Prism.Ioc; using ILogger = OneWare.Essentials.Services.ILogger; namespace OneWare.CologneChip; @@ -111,8 +112,19 @@ public async Task DownloadAsync(UniversalFpgaProjectRoot project) var state = GetProgrammerState(properties); + var toolchain = settingsService.GetSettingValue(CologneChipConstantService.ToolChainSettingsKey); + var bitStreamPath = ""; + switch (toolchain) + { + case "p_r": + bitStreamPath = $"{CologneChipConstantService.Instance.GetBuildPath(project.RelativePath)}{topName}_00.cfg.bit"; + break; + case "nextpnr": + bitStreamPath = $"{CologneChipConstantService.Instance.GetBuildPath(project.RelativePath)}{topName}.bit"; + break; + } + List fpgaArgs = []; - var bitStreamPath = $"{CologneChipConstantService.Instance.GetBuildPath(project.RelativePath)}{topName}_00.cfg.bit"; switch (state) { @@ -151,9 +163,12 @@ public async Task DownloadAsync(UniversalFpgaProjectRoot project) throw new Exception("IllegalState"); } - if (!useWsl) { - - await childProcessService.ExecuteShellAsync("openFPGALoader", fpgaArgs, + if (!useWsl) + { + // C:\Users\sebas\OneWareStudio\Packages\NativeTools\colognechip\cc-toolchain-win + var execPath = ResolveOpenFPGALoaderPath(project); + + await childProcessService.ExecuteShellAsync(execPath, fpgaArgs, outputDir, "Running OpenFPGALoader (Short-Term)...", AppState.Loading, true); } else @@ -165,4 +180,19 @@ await childProcessService.ExecuteShellAsync("wsl", args, outputDir, "Running openFPGALoader (Short-Term)...", AppState.Loading, true); } } + + protected virtual string ResolveOpenFPGALoaderPath(UniversalFpgaProjectRoot project) + { + var src = ContainerLocator.Container.Resolve() + .GetSetting(CologneChipConstantService.YosysSourceSettingsKey, project); + + if (src == "CologneChip") + { + var path = settingsService.GetSettingValue(CologneChipConstantService.CcPathSetting); + var p = $"{path}/bin/openFPGALoader/openFPGALoader"; + logger.Log($"openFPGALoader exec path: {p}"); + return p; + } + return "openFPGALoader"; + } } \ No newline at end of file diff --git a/src/OneWare.CologneChip/OneWare.CologneChip.csproj b/src/OneWare.CologneChip/OneWare.CologneChip.csproj index 4b45ee1..e02900a 100644 --- a/src/OneWare.CologneChip/OneWare.CologneChip.csproj +++ b/src/OneWare.CologneChip/OneWare.CologneChip.csproj @@ -1,7 +1,7 @@  - 0.7 + 0.7.1 net9.0 enable enable @@ -17,6 +17,7 @@ + diff --git a/src/OneWare.CologneChip/OneWareCologneChipModule.cs b/src/OneWare.CologneChip/OneWareCologneChipModule.cs index 9d9e641..fa1c8ab 100644 --- a/src/OneWare.CologneChip/OneWareCologneChipModule.cs +++ b/src/OneWare.CologneChip/OneWareCologneChipModule.cs @@ -2,12 +2,12 @@ using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Markup.Xaml.Styling; -using Avalonia.Media; using CommunityToolkit.Mvvm.Input; using OneWare.CologneChip.Helpers; using OneWare.CologneChip.Services; using OneWare.CologneChip.ViewModels; using OneWare.CologneChip.Views; +using OneWare.Essentials.Helpers; using OneWare.Essentials.Models; using OneWare.Essentials.Services; using OneWare.Essentials.ViewModels; @@ -22,7 +22,13 @@ public class OneWareCologneChipModule : IModule { public void RegisterTypes(IContainerRegistry containerRegistry) { + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); } public void OnInitialized(IContainerProvider containerProvider) @@ -84,6 +90,15 @@ public void OnInitialized(IContainerProvider containerProvider) settingsService.RegisterSetting("Tools", "CologneChip", CologneChipConstantService.CcPathSetting, new FolderPathSetting("CologneChip Toolchain Path", defaultCologneChipPath, null, null, IsCologneChipPathValid)); + settingsService.RegisterSetting("Tools", "CologneChip", CologneChipConstantService.ToolChainSettingsKey, + new ComboBoxSetting("Place & Route", CologneChipConstantService.ToolChainDefault, CologneChipConstantService.Toolchains)); + + settingsService.RegisterSetting("Tools", "CologneChip", CologneChipConstantService.OpenFPGALoaderSourceSettingsKey, + new ComboBoxSetting("openFPGALoader Source", CologneChipConstantService.OpenFPGALoaderSourceDefault, CologneChipConstantService.BinarySources)); + + settingsService.RegisterSetting("Tools", "CologneChip", CologneChipConstantService.YosysSourceSettingsKey, + new ComboBoxSetting("Yosys Source", CologneChipConstantService.OpenFPGALoaderSourceDefault, CologneChipConstantService.BinarySources)); + settingsService.GetSettingObservable(CologneChipConstantService.CcPathSetting).Subscribe(x => { if (string.IsNullOrEmpty(x)) return; @@ -98,16 +113,43 @@ public void OnInitialized(IContainerProvider containerProvider) var pr = Path.Combine(x, "bin/p_r"); var openFpgaLoader = Path.Combine(x, "bin/openFPGALoader"); - ContainerLocator.Container.Resolve().SetPath("CC_yosys", yosys); + // ContainerLocator.Container.Resolve().SetPath("CC_yosys", yosys); ContainerLocator.Container.Resolve().SetPath("CC_p_r", pr); - ContainerLocator.Container.Resolve().SetPath("CC_openFPGALoader", openFpgaLoader); + // ContainerLocator.Container.Resolve().SetPath("CC_openFPGALoader", openFpgaLoader); }); + + var projectSettingsService = containerProvider.Resolve(); + projectSettingsService.AddProjectSetting(new ProjectSettingBuilder() + .WithSetting(new ComboBoxSetting("Place & Route", CologneChipConstantService.ProjectOverrideValue, + CologneChipConstantService.ToolchainsProject)) + .WithCategory("CologneChip") + .WithKey(CologneChipConstantService.ToolChainSettingsKey) + .Build()); + + projectSettingsService.AddProjectSetting(new ProjectSettingBuilder() + .WithSetting(new ComboBoxSetting("openFPGALoader Source", + CologneChipConstantService.ProjectOverrideValue, + CologneChipConstantService.BinarySourcesProject)) + .WithCategory("CologneChip") + .WithKey(CologneChipConstantService.OpenFPGALoaderSourceSettingsKey) + .Build()); + + projectSettingsService.AddProjectSetting(new ProjectSettingBuilder() + .WithSetting(new ComboBoxSetting("Yosys Source", + CologneChipConstantService.ProjectOverrideValue, + CologneChipConstantService.BinarySourcesProject)) + .WithCategory("CologneChip") + .WithKey(CologneChipConstantService.YosysSourceSettingsKey) + .Build()); containerProvider.Resolve().RegisterSetting("Tools", "CologneChip", CologneChipConstantService.CologneChipSettingsIgnoreGuiKey, new CheckBoxSetting("Ignore UI for HardwarePin Mapping", false)); containerProvider.Resolve().RegisterSetting("Tools", "CologneChip", CologneChipConstantService.CologneChipSettingsIgnoreSynthExitCode, new CheckBoxSetting("Ignore an exit code not equal to 0 after the synthesis", false)); + + containerProvider.Resolve().RegisterSetting("Tools", "CologneChip", + CologneChipConstantService.AutoDownloadBinariesKey, new CheckBoxSetting("Auto Download Binaries", true)); @@ -141,7 +183,16 @@ public void OnInitialized(IContainerProvider containerProvider) Command = new AsyncRelayCommand(async () => { // await projectExplorerService.SaveOpenFilesForProjectAsync(root); - await cologneChipService.PRAysnc(root, new FpgaModel(fpga!)); + await cologneChipService.PrAysnc(root, new FpgaModel(fpga!)); + }, () => fpga != null) + }, + new MenuItem() + { + Header = "Run Packing", + Command = new AsyncRelayCommand(async () => + { + // await projectExplorerService.SaveOpenFilesForProjectAsync(root); + await cologneChipService.PackAysnc(root, new FpgaModel(fpga!)); }, () => fpga != null) }, } @@ -154,7 +205,7 @@ private static bool IsCologneChipPathValid(string path) { if (!Directory.Exists(path)) return false; - if (!File.Exists(Path.Combine(path, "bin", "VERSION"))) return false; + if (!File.Exists(Path.Combine(path, "VERSION"))) return false; if (!Directory.Exists(Path.Combine(path, "bin", "yosys"))) return false; if (!Directory.Exists(Path.Combine(path, "bin", "p_r"))) return false; if (!Directory.Exists(Path.Combine(path, "bin", "openFPGALoader"))) return false; diff --git a/src/OneWare.CologneChip/Services/CcCompileStrategyBase.cs b/src/OneWare.CologneChip/Services/CcCompileStrategyBase.cs new file mode 100644 index 0000000..9666409 --- /dev/null +++ b/src/OneWare.CologneChip/Services/CcCompileStrategyBase.cs @@ -0,0 +1,228 @@ +using Avalonia.Styling; +using OneWare.Essentials.Models; +using OneWare.GhdlExtension.Services; + +namespace OneWare.CologneChip.Services; + +using Avalonia.Media; +using Avalonia.Threading; +using OneWare.CologneChip.Helpers; +using OneWare.Essentials.Enums; +using OneWare.Essentials.Services; +using OneWare.UniversalFpgaProjectSystem.Models; +using OneWare.UniversalFpgaProjectSystem.Parser; +using Prism.Ioc; + +public abstract class CcCompileStrategyBase : ICologneChipCompileStrategy +{ + protected readonly IDockService Dock; + protected readonly IChildProcessService Proc; + protected readonly ILogger Log; + protected readonly IOutputService Out; + protected readonly ISettingsService Settings; + + protected CcCompileStrategyBase() + { + Dock = ContainerLocator.Container.Resolve(); + Proc = ContainerLocator.Container.Resolve(); + Log = ContainerLocator.Container.Resolve(); + Out = ContainerLocator.Container.Resolve(); + Settings = ContainerLocator.Container.Resolve(); + } + + public async Task SynthAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + { + try + { + var properties = FpgaSettingsParser.LoadSettings(project, fpgaModel.Fpga.Name); + var topHeader = project.TopEntity?.Header ?? throw new Exception("TopEntity not set!"); + var (topName, topLang) = SplitTop(topHeader); + + var buildDir = Path.Combine(project.FullPath, "build"); + Directory.CreateDirectory(buildDir); + Dock.Show(); + + var start = DateTime.Now; + + string? preSynthVerilog = null; + if (topLang == "vhd" && !UseEmbeddedGhdl(project)) + { + preSynthVerilog = await PreSynthesizeVhdlToVerilogAsync(project, topName, topHeader); + if (preSynthVerilog is null) return false; + } + + var included = project.Files + .Where(f => GetIncludedExtensions(topLang).Contains(f.Extension)) + .Where(f => !project.CompileExcluded.Contains(f)) + .Where(f => !project.TestBenches.Contains(f)) + .Select(f => $"./../{f.RelativePath}"); + + var yosysFlags = (properties.GetValueOrDefault("yosysToolchainYosysFlags") ?? string.Empty) + .Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + var yosysSynthTool = properties.GetValueOrDefault("yosysToolchainYosysSynthTool") + ?? throw new Exception("Yosys Tool not set!"); + + var yosysArgs = BuildYosysArgs(topName, topLang, topHeader, yosysSynthTool, preSynthVerilog) + .Concat(yosysFlags) + .Concat(included) + .ToList(); + + var yosysPath = ResolveYosysPath(project); + + var (success, _) = await ExecWithOutput( + yosysPath, + yosysArgs, + $"{project.FullPath}/build", + "Running yosys..."); + + success = MaybeIgnoreSynthExitCode(success); + WritePhaseResult("Synthesis", start, success); + return success; + } + catch (Exception e) + { + Log.Error(e.Message, e); + return false; + } + } + + public async Task PrAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + { + var start = DateTime.Now; + var topHeader = project.TopEntity?.Header ?? throw new Exception("TopEntity not set!"); + var (topName, topLang) = SplitTop(topHeader); + + var ccfFile = CologneChipSettingsHelper.GetConstraintFile(project); + var (exe, args) = BuildPrCommand(topName, topLang, ccfFile); + + var success = (await Proc.ExecuteShellAsync(exe, args, + $"{project.FullPath}/build", $"Running P_R...", AppState.Loading, true, null, s => + { + Dispatcher.UIThread.Post(() => { Out.WriteLine(s); }); + return true; + })).success; + + WritePhaseResult("Place and Route", start, success); + return success; + } + + public virtual async Task PackAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + { + // default: no-op/true + return await Task.FromResult(true); + } + + // ---------- Hooks to override ---------- + + protected abstract IEnumerable BuildYosysArgs( + string topName, string topLang, string topHeader, string yosysSynthTool, string? preSynthVerilog); + + protected abstract (string exe, List args) BuildPrCommand( + string topName, string topLang, string ccfFile); + + protected virtual IEnumerable GetIncludedExtensions(string topLang) => + topLang == "v" ? new[] { ".v", ".sv" } : Array.Empty(); + + protected virtual string ResolveYosysPath(UniversalFpgaProjectRoot project) + { + var src = ContainerLocator.Container.Resolve() + .GetSetting(CologneChipConstantService.YosysSourceSettingsKey, project); + + if (src == "CologneChip") + { + var path = Settings.GetSettingValue(CologneChipConstantService.CcPathSetting); + var p = $"{path}/bin/yosys/yosys"; + Log.Log($"Yosys exec path: {p}"); + return p; + } + return "yosys"; + } + + // ---------- Utilities ---------- + + protected virtual bool UseEmbeddedGhdl(UniversalFpgaProjectRoot project) + { + var src = ContainerLocator.Container.Resolve() + .GetSetting(CologneChipConstantService.YosysSourceSettingsKey, project); + return src == "CologneChip"; + } + + protected virtual string ResolveGhdlPath() => "ghdl"; + + protected static (string name, string lang) SplitTop(string header) + { + var parts = header.Split('.'); + return (parts.First(), parts.Last()); + } + + protected bool MaybeIgnoreSynthExitCode(bool success) + { + if (success) return true; + + var ignore = Settings.GetSettingValue(CologneChipConstantService.CologneChipSettingsIgnoreSynthExitCode); + if (ignore) + { + Log.Warning("The synthesis was terminated with a non-zero exit code."); + Log.Warning($"Setting '{CologneChipConstantService.CologneChipSettingsIgnoreSynthExitCode}' is true; continuing."); + return true; + } + return false; + } + + protected async Task<(bool success, string output)> ExecWithOutput( + string exe, + IReadOnlyCollection args, + string cwd, + string title) + { + return await Proc.ExecuteShellAsync(exe, args, cwd, title, AppState.Loading, true, x => + { + if (x.StartsWith("Error:")) + { + Log.Error(x); + return false; + } + + Out.WriteLine(x); + return true; + }); + } + + protected async Task PreSynthesizeVhdlToVerilogAsync( + UniversalFpgaProjectRoot project, string topName, string topHeader) + { + var buildDir = Path.Combine(project.FullPath, "build"); + + var vhdlFiles = project.Files + .Where(f => GetVhdlExtensions().Contains(f.Extension)) + .Where(f => !project.CompileExcluded.Contains(f)) + .Where(f => !project.TestBenches.Contains(f)) + .Select(f => $"./../{f.RelativePath}") + .ToList(); + + if (vhdlFiles.Count == 0) + { + Log.Error("No VHDL sources found for external GHDL synthesis."); + return null; + } + + if (project.TopEntity is IProjectFile projectFile) + { + await ContainerLocator.Container.Resolve().SynthAsync(projectFile,"verilog", "build" ); + return $"{topName}.v"; + } + + throw new Exception($"Could not find matching operation for object type: {project.GetType().Name}"); + } + + protected virtual IEnumerable GetVhdlExtensions() => new[] { ".vhd", ".vhdl" }; + + protected void WritePhaseResult(string phase, DateTime start, bool success) + { + var t = DateTime.Now - start; + var msg = $"==================\n\n{phase} {(success ? "finished" : "failed")} after {(int)t.TotalMinutes:D2}:{t.Seconds:D2}\n"; + if (success) Out.WriteLine(msg); + else Out.WriteLine(msg, Brushes.Red); + } +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/CcCustomLogger.cs b/src/OneWare.CologneChip/Services/CcCustomLogger.cs new file mode 100644 index 0000000..b8f393d --- /dev/null +++ b/src/OneWare.CologneChip/Services/CcCustomLogger.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using OneWare.Essentials.Services; +namespace OneWare.CologneChip.Services; + +public class CcCustomLogger(ILogger logger) : ICcCustomLogger +{ + private const ConsoleColor LogMessageConsoleColor = ConsoleColor.Cyan; + + private static readonly IBrush LogMessageBrush = + (Application.Current!.GetResourceObservable("ThemeAccentBrush") as IBrush)!; + + private readonly string _assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name!; + + public void Log(string message, bool showOutput = false) + { + logger.Log("[" + _assemblyName + "]: " + message, LogMessageConsoleColor, showOutput, LogMessageBrush); + } + + public void Error(string message, bool showOutput = true) + { + logger.Error("[" + _assemblyName + "]: " + message, null, showOutput); + } +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/CcNextpnrCompileStrategy.cs b/src/OneWare.CologneChip/Services/CcNextpnrCompileStrategy.cs new file mode 100644 index 0000000..5c7e97c --- /dev/null +++ b/src/OneWare.CologneChip/Services/CcNextpnrCompileStrategy.cs @@ -0,0 +1,71 @@ +using Avalonia.Media; +using Avalonia.Threading; +using OneWare.CologneChip.Helpers; +using OneWare.Essentials.Enums; +using OneWare.Essentials.Services; +using OneWare.UniversalFpgaProjectSystem.Models; +using OneWare.UniversalFpgaProjectSystem.Parser; +using Prism.Ioc; + +namespace OneWare.CologneChip.Services; + +public class CcNextpnrCompileStrategy : CcCompileStrategyBase +{ + protected override IEnumerable BuildYosysArgs( + string topName, string topLang, string topHeader, string yosysSynthTool, string? preSynthVerilog) + { + if (topLang == "vhd") + { + if (preSynthVerilog is not null) + { + Out.WriteLine("VHDL Synthesis (external GHDL → Verilog)...\n==============="); + return new[] { + "-ql", "./synth.log", + "-p", $"read_verilog {preSynthVerilog}; " + + $"synth_gatemate -nomx8 -top {topName} -luttree -nomult; write_json {topName}.json;" + }; + } + else + { + Out.WriteLine("VHDL Synthesis (embedded GHDL)...\n==============="); + return new[] { + "-ql", "./synth.log", + "-p", $"ghdl --warn-no-binding -C --ieee=synopsys ./../{topHeader} -e {topName}; " + + $"{yosysSynthTool} -nomx8 -top {topName} -luttree -nomult; write_json {topName}.json;" + }; + } + } + + Out.WriteLine("Verilog Synthesis...\n=============="); + return new[] { + "-p", $"synth_gatemate -nomx8 -top {topName} -luttree -nomult; write_json {topName}.json;" + }; + } + + protected override (string exe, List args) BuildPrCommand(string topName, string topLang, string ccfFile) + { + return ("nextpnr-himbaechel", + new List { + "--device=CCGM1A1", + "--json", $"{topName}.json", + "-o", $"ccf=./../{ccfFile}", + "-o", $"out={topName}_impl.txt", + "--router=router2" + }); + } + + public override async Task PackAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + { + var start = DateTime.Now; + var (topName, _) = SplitTop(project.TopEntity?.Header ?? throw new Exception("TopEntity not set!")); + + var (success, _) = await ExecWithOutput( + "gmpack", + new List { $"{topName}_impl.txt", $"{topName}.bit" }, + $"{project.FullPath}/build", + "Running Pack..."); + + WritePhaseResult("Pack", start, success); + return success; + } +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/CcProprietaryCompileStrategy.cs b/src/OneWare.CologneChip/Services/CcProprietaryCompileStrategy.cs new file mode 100644 index 0000000..8553d22 --- /dev/null +++ b/src/OneWare.CologneChip/Services/CcProprietaryCompileStrategy.cs @@ -0,0 +1,46 @@ +using OneWare.UniversalFpgaProjectSystem.Models; + +namespace OneWare.CologneChip.Services; + +public class CcProprietaryCompileStrategy : CcCompileStrategyBase +{ + protected override IEnumerable BuildYosysArgs( + string topName, string topLang, string topHeader, string yosysSynthTool, string? preSynthVerilog) + { + if (topLang == "vhd") + { + if (preSynthVerilog is not null) + { + Out.WriteLine("VHDL Synthesis (external GHDL → Verilog)...\n==============="); + return new[] { + "-ql", "./synth.log", + "-p", $"read_verilog {preSynthVerilog}; " + + $"{yosysSynthTool} -nomx8 -top {topName} -vlog {topName}_synth.v" + }; + } + else + { + Out.WriteLine("VHDL Synthesis (embedded GHDL)...\n==============="); + return new[] { + "-ql", "./synth.log", + "-p", $"ghdl --warn-no-binding -C --ieee=synopsys ./../{topHeader} -e {topName}; " + + $"{yosysSynthTool} -nomx8 -top {topName} -vlog {topName}_synth.v" + }; + } + } + + Out.WriteLine("Verilog Synthesis...\n=============="); + return new[] { + "-ql", "./synth.log", + "-p", $"{yosysSynthTool} -nomx8 -top {topName} -vlog {topName}_synth.v" + }; + } + + protected override (string exe, List args) BuildPrCommand(string topName, string topLang, string ccfFile) + { + return ("p_r", new List { "-i", $"{topName}_synth.v", "-o", topName, $"-ccf ./../{ccfFile} -cCP" }); + } + + public override Task PackAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + => Task.FromResult(true); +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/CcSettingsService.cs b/src/OneWare.CologneChip/Services/CcSettingsService.cs new file mode 100644 index 0000000..8458c0c --- /dev/null +++ b/src/OneWare.CologneChip/Services/CcSettingsService.cs @@ -0,0 +1,23 @@ +using OneWare.Essentials.Services; +using OneWare.UniversalFpgaProjectSystem.Models; + +namespace OneWare.CologneChip.Services; + +public class CcSettingsService(ISettingsService settingsService) +{ + + public string GetSetting(string key, UniversalFpgaProjectRoot? root = null) + { + if (root != null) + { + if (root.Properties.ContainsKey(key)) + { + var value = root.Properties[key]!.ToString(); + if (value != CologneChipConstantService.ProjectOverrideValue) + return value; + } + } + + return settingsService.GetSettingValue(key); + } +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/CcUtilsService.cs b/src/OneWare.CologneChip/Services/CcUtilsService.cs new file mode 100644 index 0000000..62db2b2 --- /dev/null +++ b/src/OneWare.CologneChip/Services/CcUtilsService.cs @@ -0,0 +1,146 @@ +using OneWare.Essentials.Enums; +using OneWare.Essentials.Models; +using OneWare.Essentials.PackageManager; +using OneWare.Essentials.Services; + +namespace OneWare.CologneChip.Services; + +public class CcUtilsService(IPackageService packageService, ISettingsService settingsService, IApplicationStateService applicationStateService, ICcCustomLogger logger) +{ + private async Task<(bool success, bool needsRestart)> InstallDependenciesAsync() + { + ApplicationProcess checkProc = applicationStateService.AddState("Checking dependencies", AppState.Loading); + + bool restartRequired = false; + bool globalSuccess = true; + + (string id, Version minversion)[] dependencyIDs = + [ + ("OneWare.GhdlExtension", new Version(0, 10, 7)), + ("osscadsuite", new Version(2025, 01, 21)), + ("ghdl", new Version(5, 0, 1)) + ]; + + // Install osscadsuite binary between GHDL plugin and ghdl binary to allow for the addition of the ghdl binary to the store + + foreach ((string dependencyId, Version minVersion) in dependencyIDs) + { + PackageModel? dependencyModel = packageService.Packages.GetValueOrDefault(dependencyId); + Package? dependencyPackage = dependencyModel?.Package; + + if (dependencyPackage == null) + { + logger.Error( + $"Dependency with ID {dependencyId} is not available in the package manager. Please file a bug report if this issue persists"); + + globalSuccess = false; + continue; + } + + if (packageService.Packages.GetValueOrDefault(dependencyId) is + { + Status: PackageStatus.Available + }) + { + bool updatePerformed = true; + + if (settingsService.GetSettingValue(CologneChipConstantService.AutoDownloadBinariesKey)) + { + logger.Log($"Installing \"{dependencyPackage.Name}\"...", true); + + bool localSuccess = false; + + // Try to install the dependency, starting with the latest version + // If the version is not compatible or the download fails, try the previous version + foreach (PackageVersion packageVersion in dependencyPackage.Versions!.Reverse()) + { + // Skip incompatible versions + if (!(await dependencyModel!.CheckCompatibilityAsync(packageVersion)).IsCompatible) + { + continue; + } + + PackageVersion? installedVersion = dependencyModel.InstalledVersion; + + if (installedVersion == packageVersion) + { + logger.Log( + $"Failed to update {dependencyPackage.Name} from version {installedVersion.Version} to version {dependencyPackage.Versions!.Last()}", + true); + + updatePerformed = false; + localSuccess = true; + break; + } + + localSuccess = await dependencyModel.DownloadAsync(packageVersion); + + // Stop trying, if install has been successful + if (localSuccess) + { + break; + } + } + + globalSuccess = globalSuccess && localSuccess; + + if (updatePerformed) + { + if (localSuccess) + { + logger.Log($"Successfully installed \"{dependencyPackage.Name}\".", true); + } + else + { + logger.Error($"Failed to install \"{dependencyPackage.Name}\"."); + } + } + } + else + { + // Log an error if the user has not enabled automatic binary downloads + + logger.Error( + $"Extension \"{dependencyPackage.Name}\" is not installed. Please enable \"Automatically download Binaries\" under the \"Experimental\" settings or download the extension yourself"); + + globalSuccess = false; + } + + if (globalSuccess && updatePerformed) + { + restartRequired = true; + } + } + + // Check whether now the correct version is installed + if (dependencyModel!.Status is PackageStatus.Installed or PackageStatus.UpdateAvailable) + { + if (minVersion.CompareTo(Version.Parse(dependencyModel.InstalledVersion!.Version!)) <= 0) + { + logger.Log( + $"Dependency {dependencyPackage.Id} installed with version {dependencyModel.InstalledVersion.Version} greater than or equal to expected version {minVersion.ToString()}"); + } + else + { + logger.Error( + $"Installed version {dependencyModel.InstalledVersion.Version} for {dependencyPackage.Name} is below the minimum version {minVersion.ToString()}. Please update {dependencyPackage.Name}!"); + + globalSuccess = false; + } + } + else if (dependencyModel.Status is PackageStatus.NeedRestart) + { + restartRequired = true; + } + } + + if (globalSuccess && restartRequired) + { + logger.Log("Dependencies were successfully installed. Please restart OneWare Studio!", true); + } + + applicationStateService.RemoveState(checkProc); + + return (globalSuccess, restartRequired); + } +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/CologneChipConstantService.cs b/src/OneWare.CologneChip/Services/CologneChipConstantService.cs index c4e23ea..5ca66d9 100644 --- a/src/OneWare.CologneChip/Services/CologneChipConstantService.cs +++ b/src/OneWare.CologneChip/Services/CologneChipConstantService.cs @@ -19,8 +19,22 @@ private CologneChipConstantService() {} public static string CologneChipSettingsUseWsl => "cologneChip_UseWSL"; + public static string AutoDownloadBinariesKey => "cologneChip_AutoDownloadBinaries"; + public static string CologneChipDefaultConstraintFile => "project.ccf"; + public const string ToolChainSettingsKey = "cologneChipToolchain"; + public static readonly string[] Toolchains = ["p_r", "nextpnr"]; + public static readonly string[] ToolchainsProject = ["p_r", "nextpnr", ProjectOverrideValue]; + public const string ToolChainDefault = "nextpnr"; + public const string ProjectOverrideValue = "Global"; + + public const string OpenFPGALoaderSourceSettingsKey = "cologneChipOpenFPGALoaderSource"; + public const string YosysSourceSettingsKey = "cologneChipYosysSource"; + public static readonly string[] BinarySources = ["CologneChip", "OSSCAD Suite"]; + public static readonly string[] BinarySourcesProject = ["CologneChip", "OSSCAD Suite", ProjectOverrideValue]; + public const string OpenFPGALoaderSourceDefault = "CologneChip"; + public const string CcfTemplate = @"# # ##### Important ##### # Currently, the pin constraints must be added manually in this file diff --git a/src/OneWare.CologneChip/Services/CologneChipService.cs b/src/OneWare.CologneChip/Services/CologneChipService.cs index e03ae4f..27414b6 100644 --- a/src/OneWare.CologneChip/Services/CologneChipService.cs +++ b/src/OneWare.CologneChip/Services/CologneChipService.cs @@ -1,11 +1,8 @@ using Avalonia.Media; -using Avalonia.Threading; using OneWare.CologneChip.Helpers; -using OneWare.Essentials.Enums; using OneWare.Essentials.Models; using OneWare.Essentials.Services; using OneWare.UniversalFpgaProjectSystem.Models; -using OneWare.UniversalFpgaProjectSystem.Parser; using Prism.Ioc; namespace OneWare.CologneChip.Services; @@ -14,124 +11,45 @@ public class CologneChipService( IChildProcessService childProcessService, ILogger logger, IOutputService outputService, - IDockService dockService) + IDockService dockService, + ISettingsService settingsService) { public async Task SynthAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) { - try + var toolchain = settingsService.GetSettingValue(CologneChipConstantService.ToolChainSettingsKey); + return toolchain switch { - var properties = FpgaSettingsParser.LoadSettings(project, fpgaModel.Fpga.Name); - var top = project.TopEntity?.Header ?? throw new Exception("TopEntity not set!"); - - var buildDir = Path.Combine(project.FullPath, "build"); - Directory.CreateDirectory(buildDir); - - dockService.Show(); - - var start = DateTime.Now; - - var yosysSynthTool = properties.GetValueOrDefault("yosysToolchainYosysSynthTool") ?? - throw new Exception("Yosys Tool not set!"); - - var (topName, topLanguage) = (top.Split('.').First(), top.Split('.').Last()); - - List yosysArguments = []; - List includedExtensions = []; - - switch (topLanguage) - { - case "vhd": - outputService.WriteLine("VHDL Synthesis...\n==============="); - yosysArguments = ["-q","-l ./../synth.log", "-p", $"ghdl --warn-no-binding -C --ieee=synopsys ./../{top} -e {topName}; {yosysSynthTool} -nomx8 -top {topName} -vlog {topName}_synth.v"]; - includedExtensions = []; - break; - case "v": - outputService.WriteLine("Verilog Synthesis...\n=============="); - yosysArguments = ["-ql", "./../log/synth.log", "-p", $"{yosysSynthTool} -nomx8 -top {topName} -vlog {topName}_synth.v"]; - includedExtensions = [".v", ".sv"]; - break; - } - - var includedFiles = project.Files - .Where(x => includedExtensions.Contains(x.Extension)) - .Where(x => !project.CompileExcluded.Contains(x)) - .Where(x => !project.TestBenches.Contains(x)) - .Select(x => $"./../{x.RelativePath}"); - - yosysArguments.AddRange(properties.GetValueOrDefault("yosysToolchainYosysFlags")?.Split(' ', - StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) ?? []); - yosysArguments.AddRange(includedFiles); - - var (success, _) = await childProcessService.ExecuteShellAsync("yosys", yosysArguments, $"{project.FullPath}/build", - "Running yosys...", AppState.Loading, true, x => - { - if (x.StartsWith("Error:")) - { - logger.Error(x); - return false; - } - - outputService.WriteLine(x); - return true; - }); - - - if (!success) { - var ignoreSynthExitCode = ContainerLocator.Container.Resolve().GetSettingValue(CologneChipConstantService.CologneChipSettingsIgnoreSynthExitCode); - if (ignoreSynthExitCode) - { - ContainerLocator.Container.Resolve().Warning("The synthesis was terminated with an exit code other than zero"); - ContainerLocator.Container.Resolve().Warning($"Setting '{CologneChipConstantService.CologneChipSettingsIgnoreSynthExitCode}' to true"); - ContainerLocator.Container.Resolve().Warning($"Because of this setting, the route and placing tool is started anyway"); - success = true; - } - } - - - var compileTime = DateTime.Now - start; - if (success) - outputService.WriteLine( - $"==================\n\nSynthesis finished after {(int)compileTime.TotalMinutes:D2}:{compileTime.Seconds:D2}\n"); - else - outputService.WriteLine( - $"==================\n\nSynthesis failed after {(int)compileTime.TotalMinutes:D2}:{compileTime.Seconds:D2}\n", - Brushes.Red); + "p_r" => await ContainerLocator.Container.Resolve() + .SynthAsync(project, fpgaModel), + "nextpnr" => await ContainerLocator.Container.Resolve() + .SynthAsync(project, fpgaModel), + _ => throw new Exception($"Unknown toolchain: {toolchain}") + }; + } - return success; - } - catch (Exception e) + public async Task PrAysnc(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + { + var toolchain = settingsService.GetSettingValue(CologneChipConstantService.ToolChainSettingsKey); + return toolchain switch { - logger.Error(e.Message, e); - return false; - } + "p_r" => await ContainerLocator.Container.Resolve() + .PrAsync(project, fpgaModel), + "nextpnr" => await ContainerLocator.Container.Resolve() + .PrAsync(project, fpgaModel), + _ => throw new Exception($"Unknown toolchain: {toolchain}") + }; } - - public async Task PRAysnc(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) + + public async Task PackAysnc(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) { - var start = DateTime.Now; - var top = project.TopEntity?.Header ?? throw new Exception("TopEntity not set!"); - var (topName, topLanguage) = (top.Split('.').First(), top.Split('.').Last()); - var ccfFile = CologneChipSettingsHelper.GetConstraintFile(project); - - List prArguments = ["-i", $"{topName}_synth.v", "-o", topName, $"-ccf ./../{ccfFile} -cCP"]; - - var success = (await childProcessService.ExecuteShellAsync("p_r", prArguments, - $"{project.FullPath}/build", $"Running P_R...", AppState.Loading, true, null, s => - { - Dispatcher.UIThread.Post(() => { outputService.WriteLine(s); }); - return true; - })).success; - - var compileTime = DateTime.Now - start; - if (success) - outputService.WriteLine( - $"==================\n\nPlace and Route finished after {(int)compileTime.TotalMinutes:D2}:{compileTime.Seconds:D2}\n"); - else - outputService.WriteLine( - $"==================\n\nPlace and Route failed after {(int)compileTime.TotalMinutes:D2}:{compileTime.Seconds:D2}\n", - Brushes.Red); - - return success; + var toolchain = settingsService.GetSettingValue(CologneChipConstantService.ToolChainSettingsKey); + return toolchain switch + { + "p_r" => await ContainerLocator.Container.Resolve().PackAsync(project, fpgaModel), + "nextpnr" => await ContainerLocator.Container.Resolve() + .PackAsync(project, fpgaModel), + _ => throw new Exception($"Unknown toolchain: {toolchain}") + }; } public async Task CreateNetListJsonAsync(IProjectFile verilog) @@ -219,10 +137,12 @@ public void SaveConnections(UniversalFpgaProjectRoot project, FpgaModel fpga) public async Task CompileAsync(UniversalFpgaProjectRoot project, FpgaModel fpga) { var start = DateTime.Now; - outputService.WriteLine("Starting CC Toolchain...\n==============="); + outputService.WriteLine($"Starting CC Toolchain... ({ + settingsService.GetSettingValue(CologneChipConstantService.ToolChainSettingsKey)})\n==============="); var success = await SynthAsync(project, fpga); - success &= await PRAysnc(project, fpga); + success &= await PrAysnc(project, fpga); + success &= await PackAysnc(project, fpga); var endTime = DateTime.Now - start; if (success) diff --git a/src/OneWare.CologneChip/Services/ICcCustomLogger.cs b/src/OneWare.CologneChip/Services/ICcCustomLogger.cs new file mode 100644 index 0000000..16e4b2f --- /dev/null +++ b/src/OneWare.CologneChip/Services/ICcCustomLogger.cs @@ -0,0 +1,8 @@ +namespace OneWare.CologneChip.Services; + +public interface ICcCustomLogger +{ + public void Log(string message, bool showOutput = false); + + public void Error(string message, bool showOutput = true); +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/Services/ICologneChipCompileStrategy.cs b/src/OneWare.CologneChip/Services/ICologneChipCompileStrategy.cs new file mode 100644 index 0000000..d3c8b4b --- /dev/null +++ b/src/OneWare.CologneChip/Services/ICologneChipCompileStrategy.cs @@ -0,0 +1,12 @@ +using OneWare.UniversalFpgaProjectSystem.Models; + +namespace OneWare.CologneChip.Services; + +public interface ICologneChipCompileStrategy +{ + public Task SynthAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel); + + public Task PrAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel); + + public Task PackAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel); +} \ No newline at end of file diff --git a/src/OneWare.CologneChip/ViewModels/CologneChipLoaderSettingsViewModel.cs b/src/OneWare.CologneChip/ViewModels/CologneChipLoaderSettingsViewModel.cs index daf64fa..8538967 100644 --- a/src/OneWare.CologneChip/ViewModels/CologneChipLoaderSettingsViewModel.cs +++ b/src/OneWare.CologneChip/ViewModels/CologneChipLoaderSettingsViewModel.cs @@ -1,13 +1,9 @@ -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using DynamicData.Binding; using OneWare.CologneChip.Services; using OneWare.Essentials.Controls; using OneWare.Essentials.Models; using OneWare.Essentials.ViewModels; using OneWare.Settings.ViewModels; -using OneWare.Settings.ViewModels.SettingTypes; using OneWare.UniversalFpgaProjectSystem.Fpga; using OneWare.UniversalFpgaProjectSystem.Models; using OneWare.UniversalFpgaProjectSystem.Parser;