Exemple #1
0
        /// <summary>
        /// Visit an invoke node
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="invokeNode">Invoke syntax node</param>
        /// <returns>Invoke plan</returns>
        private InvokePlanBase VisitInvoke(TestFilePlan plan, TestSetPlan testSetPlan, InvokeCodeNode invokeNode)
        {
            // --- Get start address
            var start = Eval(plan, testSetPlan, invokeNode.StartExpr);

            if (start == null)
            {
                return(null);
            }

            if (invokeNode.IsCall)
            {
                return(new CallPlan(start.AsWord()));
            }

            if (invokeNode.IsHalt)
            {
                return(new StartPlan(start.AsWord(), null));
            }

            // --- Get Stop address
            var stop = Eval(plan, testSetPlan, invokeNode.StopExpr);

            return(stop == null
                ? null
                : new StartPlan(start.AsWord(), stop.AsWord()));
        }
Exemple #2
0
        /// <summary>
        /// Visits the machine context of a test set
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="machineContext">machine context</param>
        private void VisitMachineContext(TestFilePlan plan, TestSetPlan testSetPlan, MachineContextNode machineContext)
        {
            if (machineContext == null)
            {
                return;
            }
            switch (machineContext.Id.ToUpper())
            {
            case "SPECTRUM48":
                testSetPlan.MachineType = MachineType.Spectrum48;
                return;

            case "SPECTRUM128":
                testSetPlan.MachineType = MachineType.Spectrum128;
                return;

            case "SPECTRUMP3":
                testSetPlan.MachineType = MachineType.SpectrumP3;
                return;

            case "NEXT":
                testSetPlan.MachineType = MachineType.Next;
                return;
            }
            ReportError(Errors.T0002, plan, machineContext.IdSpan);
        }
Exemple #3
0
        /// <summary>
        /// Visits a test block
        /// </summary>
        /// <param name="plan"></param>
        /// <param name="testSetPlan"></param>
        /// <param name="block"></param>
        /// <returns>Test block plan</returns>
        private TestBlockPlan VisitTestBlock(TestFilePlan plan, TestSetPlan testSetPlan, TestBlockNode block)
        {
            var testBlock = new TestBlockPlan(testSetPlan, block.TestId, block.Category, block.Span);

            if (block.TestOptions != null)
            {
                VisitTestOptions(plan, testSetPlan, block.TestOptions, out var nonmi, out var timeout);
                testBlock.DisableInterrupt = nonmi;
                testBlock.TimeoutValue     = timeout;
            }
            VisitTestParameters(plan, testBlock, block.Params);
            VisitTestCases(plan, testBlock, block.Cases);
            var invoke = VisitInvoke(plan, testSetPlan, block.Act);

            if (invoke != null)
            {
                testBlock.Act = invoke;
            }
            VisitArrange(plan, testBlock, block.Arrange);
            if (block.Breakpoints != null)
            {
                VisitBreakPoints(plan, testBlock, block.Breakpoints);
            }
            testBlock.SignMachineAvalilable();
            VisitAssert(plan, testBlock, block.Assert);
            return(testBlock);
        }
Exemple #4
0
 /// <summary>
 /// Visits test set options
 /// </summary>
 /// <param name="plan">Test file plan</param>
 /// <param name="testSetPlan">TestSetPlan to visit</param>
 /// <param name="testOptions">TestOptions syntax node</param>
 private void VisitTestOptions(TestFilePlan plan, TestSetPlan testSetPlan, TestOptionsNode testOptions)
 {
     if (testOptions == null)
     {
         return;
     }
     VisitTestOptions(plan, testSetPlan, testOptions, out var nonmi, out var timeout);
     testSetPlan.DisableInterrupt = nonmi ?? false;
     testSetPlan.TimeoutValue     = timeout ?? DEFAULT_TIMEOUT;
 }
Exemple #5
0
        /// <summary>
        /// Visits a callstub node
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">Test set plan to visit</param>
        /// <param name="nodeCallStub">Call stub ode to visit</param>
        private void VisitCallStubContext(TestFilePlan plan, TestSetPlan testSetPlan, CallStubNode nodeCallStub)
        {
            if (nodeCallStub == null)
            {
                return;
            }
            var value = Eval(plan, testSetPlan, nodeCallStub.Value);

            if (value != null)
            {
                testSetPlan.CallStubAddress = value.AsWord();
            }
        }
Exemple #6
0
        /// <summary>
        /// Visits a single test plan
        /// </summary>
        /// <param name="plan">Test plan to emit</param>
        /// <param name="node">TestSetNode to use</param>
        private TestSetPlan VisitTestSet(TestFilePlan plan, TestSetNode node)
        {
            var testSetPlan = new TestSetPlan(node.TestSetId, node.Span);

            VisitMachineContext(plan, testSetPlan, node.MachineContext);
            VisitSourceContext(plan, testSetPlan, node.SourceContext);
            VisitTestOptions(plan, testSetPlan, node.TestOptions);
            VisitDataBlock(plan, testSetPlan, node.DataBlock);
            if (node.Init != null)
            {
                foreach (var asgn in node.Init.Assignments)
                {
                    var asgnPlan = VisitAssignment(plan, testSetPlan, asgn);
                    if (asgnPlan != null)
                    {
                        testSetPlan.InitAssignments.Add(asgnPlan);
                    }
                }
            }
            if (node.Setup != null)
            {
                testSetPlan.Setup = VisitInvoke(plan, testSetPlan, node.Setup);
            }
            if (node.Cleanup != null)
            {
                testSetPlan.Cleanup = VisitInvoke(plan, testSetPlan, node.Cleanup);
            }

            foreach (var block in node.TestBlocks)
            {
                var blockPlan = VisitTestBlock(plan, testSetPlan, block);
                if (blockPlan != null)
                {
                    if (testSetPlan.TestBlocks.Any(tb =>
                                                   string.Compare(tb.Id, blockPlan.Id, StringComparison.InvariantCultureIgnoreCase) == 0))
                    {
                        ReportError(Errors.T0007, plan, block.TestIdSpan, block.TestId);
                        continue;
                    }
                    testSetPlan.TestBlocks.Add(blockPlan);
                }
            }
            return(testSetPlan);
        }
Exemple #7
0
        /// <summary>
        /// Visits a value data member
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="valueMember">Value member syntax node</param>
        private void VisitValueMember(TestFilePlan plan, TestSetPlan testSetPlan, ValueMemberNode valueMember)
        {
            // --- Check ID duplication
            var id = valueMember.Id;

            if (testSetPlan.ContainsSymbol(id))
            {
                ReportError(Errors.T0006, plan, valueMember.IdSpan, id);
                return;
            }

            // --- Evaluate the expression
            var value = Eval(plan, testSetPlan, valueMember.Expr);

            if (value != null)
            {
                testSetPlan.SetDataMember(id, value);
            }
        }
Exemple #8
0
        /// <summary>
        /// Vistis test set and test node options
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="testOptions">TestOptions syntax node</param>
        /// <param name="disableInterrupt">Disable interrupt flag</param>
        /// <param name="timeout">TIMEOUT value</param>
        private void VisitTestOptions(TestFilePlan plan, TestSetPlan testSetPlan, TestOptionsNode testOptions, out bool disableInterrupt, out int timeout)
        {
            // --- Set default values
            disableInterrupt = false;
            timeout          = 0;
            if (testOptions?.Options == null)
            {
                return;
            }

            // --- Process options
            var interruptModeFound = false;
            var timeoutFound       = false;

            foreach (var option in testOptions.Options)
            {
                if (option is InterruptOptionNodeBase intNode)
                {
                    if (interruptModeFound)
                    {
                        ReportError(Errors.T0005, plan, intNode.Span, "DI or EI");
                        return;
                    }
                    interruptModeFound = true;
                    disableInterrupt   = intNode is DiTestOptionNode;
                }
                else if (option is TimeoutTestOptionNode timeoutNode)
                {
                    if (timeoutFound)
                    {
                        ReportError(Errors.T0005, plan, timeoutNode.Span, "TIMEOUT");
                        return;
                    }
                    timeoutFound = true;
                    var value = Eval(plan, testSetPlan, timeoutNode.Expr);
                    if (value != null)
                    {
                        timeout = (int)value.AsNumber();
                    }
                }
            }
        }
Exemple #9
0
        /// <summary>
        /// Vistis test set and test node options
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="testOptions">TestOptions syntax node</param>
        /// <param name="nonmi">NONMI value</param>
        /// <param name="timeout">TIMEOUT value</param>
        private void VisitTestOptions(TestFilePlan plan, TestSetPlan testSetPlan, TestOptionsNode testOptions, out bool?nonmi, out int?timeout)
        {
            // --- Set default values
            nonmi   = null;
            timeout = null;
            if (testOptions?.Options == null)
            {
                return;
            }

            // --- Process options
            var nonmiFound   = false;
            var timeoutFound = false;

            foreach (var option in testOptions.Options)
            {
                if (option is NoNmiTestOptionNode nonmiNode)
                {
                    if (nonmiFound)
                    {
                        ReportError(Errors.T0005, plan, nonmiNode.Span, "NONMI");
                        return;
                    }
                    nonmiFound = true;
                    nonmi      = true;
                }
                else if (option is TimeoutTestOptionNode timeoutNode)
                {
                    if (timeoutFound)
                    {
                        ReportError(Errors.T0005, plan, timeoutNode.Span, "TIMEOUT");
                        return;
                    }
                    timeoutFound = true;
                    var value = Eval(plan, testSetPlan, timeoutNode.Expr);
                    if (value != null)
                    {
                        timeout = (int)value.AsNumber();
                    }
                }
            }
        }
Exemple #10
0
        /// <summary>
        /// Visits the source context of a test set
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="sourceContext">Machine context</param>
        private void VisitSourceContext(TestFilePlan plan, TestSetPlan testSetPlan, SourceContextNode sourceContext)
        {
            if (sourceContext == null)
            {
                return;
            }

            // --- Prepare predefined symbols for Z80 compilation
            var options = new AssemblerOptions();

            foreach (var symbol in sourceContext.Symbols)
            {
                options.PredefinedSymbols.Add(symbol.Id);
            }
            var assembler = new Z80Assembler();

            // --- Check filename existence
            var filename = sourceContext.SourceFile;

            if (!Path.IsPathRooted(filename))
            {
                filename = Path.Combine(DefaultSourceFolder ?? "", filename);
            }
            if (!File.Exists(filename))
            {
                ReportError(Errors.T0003, plan, sourceContext.SourceFileSpan, filename);
                return;
            }

            // --- Compile the Z80 source file
            var output = assembler.CompileFile(filename, options);

            if (output.ErrorCount == 0)
            {
                testSetPlan.CodeOutput = output;
                return;
            }

            // --- Issue cZ80 ompilation error
            ReportError(Errors.T0004, plan, sourceContext.SourceFileSpan, filename, output.ErrorCount);
        }
Exemple #11
0
        /// <summary>
        /// Visits an assignment of a TestSet
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="asgn">Assignment syntax node</param>
        /// <returns>Assignment plan</returns>
        private AssignmentPlanBase VisitAssignment(TestFilePlan plan, TestSetPlan testSetPlan, AssignmentNode asgn)
        {
            if (asgn is RegisterAssignmentNode regAsgn)
            {
                var value = Eval(plan, testSetPlan, regAsgn.Expr);
                return(value != null
                    ? new RegisterAssignmentPlan(regAsgn.RegisterName, value.AsWord())
                    : null);
            }

            if (asgn is FlagAssignmentNode flagAsgn)
            {
                return(new FlagAssignmentPlan(flagAsgn.FlagName));
            }

            if (asgn is MemoryAssignmentNode memAsgn)
            {
                var address = Eval(plan, testSetPlan, memAsgn.Address);
                var value   = Eval(plan, testSetPlan, memAsgn.Value);
                if (address == null || value == null)
                {
                    return(null);
                }
                ExpressionValue length = null;
                if (memAsgn.Length != null)
                {
                    length = Eval(plan, testSetPlan, memAsgn.Length);
                    if (length == null)
                    {
                        return(null);
                    }
                }

                return(length == null
                    ? new MemoryAssignmentPlan(address.AsWord(), value.AsByteArray())
                    : new MemoryAssignmentPlan(address.AsWord(), value.AsByteArray().Take(length.AsWord()).ToArray()));
            }

            return(null);
        }
Exemple #12
0
 /// <summary>
 /// Visit a data block
 /// </summary>
 /// <param name="plan">Test file plan</param>
 /// <param name="testSetPlan">TestSetPlan to visit</param>
 /// <param name="dataBlock">DataBlock syntax node</param>
 private void VisitDataBlock(TestFilePlan plan, TestSetPlan testSetPlan, DataBlockNode dataBlock)
 {
     if (dataBlock == null)
     {
         return;
     }
     foreach (var dataMember in dataBlock.DataMembers)
     {
         if (dataMember is ValueMemberNode valueMember)
         {
             VisitValueMember(plan, testSetPlan, valueMember);
         }
         else if (dataMember is MemoryPatternMemberNode mempatMember)
         {
             VisitMemoryPatternMember(plan, testSetPlan, mempatMember);
         }
         else if (dataMember is PortMockMemberNode portMockMember)
         {
             VisitPortMockMember(plan, testSetPlan, portMockMember);
         }
     }
 }
Exemple #13
0
        /// <summary>
        /// Visits a test block
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">Test set plan to visit</param>
        /// <param name="block">Block to visit</param>
        /// <returns>Test block plan</returns>
        private TestBlockPlan VisitTestBlock(TestFilePlan plan, TestSetPlan testSetPlan, TestBlockNode block)
        {
            var testBlock = new TestBlockPlan(testSetPlan, block.TestId, block.Category, block.Span);

            if (block.TestOptions != null)
            {
                VisitTestOptions(plan, testSetPlan, block.TestOptions, out var disableInterrupt, out var timeout);
                testBlock.DisableInterrupt = disableInterrupt;
                testBlock.TimeoutValue     = timeout;
            }
            if (block.Setup != null)
            {
                testBlock.Setup = VisitInvoke(plan, testSetPlan, block.Setup);
            }
            VisitTestParameters(plan, testBlock, block.Params);
            VisitTestCases(plan, testBlock, block.Cases);
            var invoke = VisitInvoke(plan, testSetPlan, block.Act);

            if (invoke != null)
            {
                testBlock.Act = invoke;
            }
            VisitArrange(plan, testBlock, block.Arrange);
            if (block.Breakpoints != null)
            {
                VisitBreakPoints(plan, testBlock, block.Breakpoints);
            }
            testBlock.MachineContext = new CompileTimeMachineContext();
            VisitAssert(plan, testBlock, block.Assert);
            if (block.Cleanup != null)
            {
                testBlock.Cleanup = VisitInvoke(plan, testSetPlan, block.Cleanup);
            }

            return(testBlock);
        }
Exemple #14
0
        /// <summary>
        /// Invokes the code and waits for its completion within the specified
        /// timeout limits.
        /// </summary>
        /// <param name="testItem">Test item that invokes this method</param>
        /// <param name="invokePlan">Invokation plan</param>
        /// <param name="timeout">Timeout in milliseconds</param>
        /// <param name="token">Token to cancel test run</param>
        /// <param name="watch">Optional stopwatch for diagnostics</param>
        /// <returns>True, if code completed; otherwise, false</returns>
        private async Task <bool> InvokeCodeAsync(TestItemBase testItem, InvokePlanBase invokePlan, int timeout, CancellationToken token,
                                                  Stopwatch watch = null)
        {
            if (invokePlan == null)
            {
                return(true);
            }
            if (!(Package.MachineViewModel.SpectrumVm is ISpectrumVmRunCodeSupport runCodeSupport))
            {
                return(false);
            }

            // --- Prepare code invocation
            ExecuteCycleOptions runOptions;
            bool removeFromHalt = false;
            var  spectrumVm     = Package.MachineViewModel.SpectrumVm;
            var  timeoutTacts   = timeout * spectrumVm.BaseClockFrequency
                                  * spectrumVm.ClockMultiplier / 1000;

            if (invokePlan is CallPlan callPlan)
            {
                // --- Obtain Call stub address
                TestSetPlan testSetPlan = null;
                switch (testItem)
                {
                case TestSetItem set:
                    testSetPlan = set.Plan;
                    break;

                case TestItem item:
                    testSetPlan = item.Plan.TestSet;
                    break;

                case TestCaseItem caseItem:
                    testSetPlan = caseItem.Plan.TestBlock.TestSet;
                    break;
                }

                var callStubAddress = testSetPlan?.CallStubAddress ?? DEFAULT_CALL_STUB_ADDRESS;

                // --- Create CALL stub
                Package.MachineViewModel.SpectrumVm.Cpu.Registers.PC = callStubAddress;
                runCodeSupport.InjectCodeToMemory(callStubAddress, new byte[] { 0xCD, (byte)callPlan.Address, (byte)(callPlan.Address >> 8) });
                runOptions = new ExecuteCycleOptions(EmulationMode.UntilExecutionPoint,
                                                     terminationPoint: (ushort)(callStubAddress + 3),
                                                     fastVmMode: true,
                                                     timeoutTacts: timeout * spectrumVm.BaseClockFrequency
                                                     * spectrumVm.ClockMultiplier / 1000);
            }
            else if (invokePlan is StartPlan startPlan)
            {
                spectrumVm.Cpu.Registers.PC = startPlan.Address;
                if (startPlan.StopAddress == null)
                {
                    // --- Start and run until halt
                    runOptions     = new ExecuteCycleOptions(EmulationMode.UntilHalt, fastVmMode: true, timeoutTacts: timeoutTacts);
                    removeFromHalt = true;
                }
                else
                {
                    // --- Start and run until the stop address is reached
                    runOptions = new ExecuteCycleOptions(EmulationMode.UntilExecutionPoint,
                                                         terminationPoint: startPlan.StopAddress.Value,
                                                         fastVmMode: true,
                                                         timeoutTacts: timeoutTacts);
                }
            }
            else
            {
                return(false);
            }

            // --- Prepare the machine to run the code
            var initialTacts = Package.MachineViewModel.SpectrumVm.Cpu.Tacts;
            var machine      = Package.MachineViewModel.Machine;
            var cpuSupport   = Package.MachineViewModel.SpectrumVm.Cpu as IZ80CpuTestSupport;

            cpuSupport.ExecutionFlowStatus.ClearAll();
            cpuSupport.MemoryReadStatus.ClearAll();
            cpuSupport.MemoryWriteStatus.ClearAll();
            Package.MachineViewModel.NoToolRefreshMode = true;

            // --- Start the machine
            machine.Start(runOptions);
            ReportTimeDetail("Start VM:", testItem, watch);

            // --- Waith for completion
            var   completion = machine.CompletionTask;
            await completion;

            // --- Report outcome
            ReportTimeDetail("Complete VM:", testItem, watch);
            var endTacts = Package.MachineViewModel.SpectrumVm.Cpu.Tacts;

            if (Package.Options.TestTStateExecutionLogging)
            {
                testItem.Log($"#of T-States consumed: {endTacts - initialTacts}");
            }

            // --- Check if code ran successfully
            var success = !completion.IsFaulted &&
                          !completion.IsCanceled &&
                          !token.IsCancellationRequested &&
                          machine.ExecutionCycleResult;

            // --- Take care the VM is paused
            await machine.Pause();

            ReportTimeDetail("Pause VM:", testItem, watch);

            // --- If the CPU was halted, it should be removed from this state for the next run
            if (removeFromHalt)
            {
                var cpu = Package.MachineViewModel.SpectrumVm.Cpu as IZ80CpuTestSupport;
                cpu?.RemoveFromHaltedState();
            }

            // --- Handle user cancellation/Timeout
            if (!success)
            {
                if (token.IsCancellationRequested)
                {
                    throw new TaskCanceledException();
                }
                testItem.State = TestState.Aborted;
                testItem.Log("Timeout expired. Test aborted.", LogEntryType.Fail);
            }
            return(success);
        }
Exemple #15
0
        /// <summary>
        /// Visits a memory pattern member
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="mempatMember">Memory pattern member syntax node</param>
        private void VisitMemoryPatternMember(TestFilePlan plan, TestSetPlan testSetPlan, MemoryPatternMemberNode mempatMember)
        {
            // --- Check ID duplication
            var id = mempatMember.Id;

            if (testSetPlan.ContainsSymbol(id))
            {
                ReportError(Errors.T0006, plan, mempatMember.IdSpan, id);
                return;
            }

            // --- Evaluate byte array
            var bytes      = new List <byte>();
            var errorFound = false;

            foreach (var mempat in mempatMember.Patterns)
            {
                if (mempat is BytePatternNode bytePattern)
                {
                    foreach (var byteExpr in bytePattern.Bytes)
                    {
                        var value = Eval(plan, testSetPlan, byteExpr);
                        if (value != null)
                        {
                            bytes.Add((byte)value.AsNumber());
                        }
                        else
                        {
                            errorFound = true;
                        }
                    }
                }
                else if (mempat is WordPatternNode wordPattern)
                {
                    foreach (var byteExpr in wordPattern.Words)
                    {
                        var value = Eval(plan, testSetPlan, byteExpr);
                        if (value != null)
                        {
                            var word = value.AsWord();
                            bytes.Add((byte)word);
                            bytes.Add((byte)(word >> 8));
                        }
                        else
                        {
                            errorFound = true;
                        }
                    }
                }
                else if (mempat is TextPatternNode textPattern)
                {
                    foreach (var val in SyntaxHelper.SpectrumStringToBytes(textPattern.String))
                    {
                        bytes.Add(val);
                    }
                }
            }

            if (errorFound)
            {
                return;
            }
            testSetPlan.SetDataMember(id, new ExpressionValue(bytes.ToArray()));
        }
Exemple #16
0
        /// <summary>
        /// Visit a port mock member
        /// </summary>
        /// <param name="plan">Test file plan</param>
        /// <param name="testSetPlan">TestSetPlan to visit</param>
        /// <param name="portMockMember">Port mock syntax node</param>
        private void VisitPortMockMember(TestFilePlan plan, TestSetPlan testSetPlan, PortMockMemberNode portMockMember)
        {
            // --- Check ID duplication
            var id = portMockMember.Id;

            if (testSetPlan.ContainsSymbol(id))
            {
                ReportError(Errors.T0006, plan, portMockMember.IdSpan, id);
                return;
            }

            // --- Get port address
            var portAddress = Eval(plan, testSetPlan, portMockMember.Expr);
            var portMock    = portAddress != null ? new PortMockPlan(portAddress.AsWord()) : null;

            // --- Get pulses
            var pulsesOk       = true;
            var nextPulseStart = 0L;

            foreach (var pulse in portMockMember.Pulses)
            {
                // --- Get pulse expression values
                var value  = Eval(plan, testSetPlan, pulse.ValueExpr);
                var pulse1 = Eval(plan, testSetPlan, pulse.Pulse1Expr);
                if (value == null || pulse1 == null)
                {
                    pulsesOk = false;
                    continue;
                }

                ExpressionValue pulse2 = null;
                if (pulse.Pulse2Expr != null)
                {
                    pulse2 = Eval(plan, testSetPlan, pulse.Pulse2Expr);
                    if (pulse2 == null)
                    {
                        pulsesOk = false;
                        continue;
                    }
                }

                // --- Create a new pulse
                PortPulsePlan pulsePlan;
                if (pulse2 == null)
                {
                    // --- We have only value and length
                    pulsePlan = new PortPulsePlan(value.AsByte(), nextPulseStart,
                                                  nextPulseStart + pulse1.AsNumber() - 1);
                }
                else
                {
                    // --- We have pulse value, start, and end
                    pulsePlan = new PortPulsePlan(value.AsByte(), pulse1.AsNumber(),
                                                  pulse.IsInterval ? pulse2.AsNumber() : pulse1.AsNumber() + pulse2.AsNumber() - 1);
                }
                nextPulseStart = pulsePlan.EndTact + 1;
                portMock?.AddPulse(pulsePlan);
            }

            // --- Create the entire port mock
            if (portMock != null && pulsesOk)
            {
                testSetPlan.SetPortMock(id, portMock);
            }
        }