From 3e2467089c41d90b5433f5b92889c41db1e9f82b Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Wed, 18 Mar 2026 16:41:07 +0100 Subject: [PATCH 1/2] SAM_TC: Implement capture mode as free-running counter In capture mode (WAVE=0), the timer now runs as a free-running counter with overflow interrupts, matching real hardware behavior. Previously, capture mode logged "Unimplemented" and left the timer unconfigured. Also enables RC compare (cTimer) in capture mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Peripherals/Peripherals/Timers/SAM_TC.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs b/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs index b956ff7dc..095ee4565 100644 --- a/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs +++ b/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs @@ -571,7 +571,9 @@ private void UpdateTimer(bool start = false) if(!waveformMode) { - parent.ErrorLog("Unimplemented"); + // Capture mode: free-running counter up to MaxValue + timer.Direction = Direction.Ascending; + timer.Limit = MaxValue; } else { @@ -602,15 +604,15 @@ private void UpdateTimer(bool start = false) private void UpdateCTimer() { - if(!enabled || !waveformMode) + if(!enabled) { cTimer.Enabled = false; return; } - switch(waveformSelected) + + // In capture mode or waveform Up/UpDown, use cTimer for RC compare + if(!waveformMode || waveformSelected == WaveSelection.Up || waveformSelected == WaveSelection.UpDown) { - case WaveSelection.Up: - case WaveSelection.UpDown: var direction = timer.Direction; var value = timer.Value; var limit = timer.Limit; @@ -633,10 +635,10 @@ private void UpdateCTimer() cTimer.Limit = limit - valueC; } cTimer.Enabled = timer.Enabled; - break; - default: + } + else + { cTimer.Enabled = false; - break; } } @@ -655,7 +657,8 @@ private void LimitReached() } if(!waveformMode) { - parent.ErrorLog("Unimplemented"); + // Capture mode: free-running counter overflow + overflow = true; } else { From 31f0c510c954b04169beef0374d1354d47f6f801 Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Wed, 18 Mar 2026 16:48:49 +0100 Subject: [PATCH 2/2] SAM_TC: Add configurable counter width (16/32-bit) The SAM4E has 32-bit TC counters while the SAM4S has 16-bit. Add a counterWidth constructor parameter (default 16) to support both variants. With 16-bit counters, firmware using 32-bit overflow counting saw wildly incorrect time values. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Peripherals/Peripherals/Timers/SAM_TC.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs b/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs index 095ee4565..ff2a3bb41 100644 --- a/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs +++ b/src/Emulator/Peripherals/Peripherals/Timers/SAM_TC.cs @@ -18,13 +18,14 @@ namespace Antmicro.Renode.Peripherals.Timers { public class SAM_TC : BasicDoubleWordPeripheral, INumberedGPIOOutput, IKnownSize { - public SAM_TC(IMachine machine, ulong masterClockFrequency = 20000000) : base(machine) + public SAM_TC(IMachine machine, ulong masterClockFrequency = 20000000, int counterWidth = 16) : base(machine) { var connections = new Dictionary(); channels = new Channel[NumberOfChannels]; + var maxValue = counterWidth == 32 ? 0xFFFFFFFFUL : 0xFFFFUL; for(int i = 0; i < NumberOfChannels; i++) { - channels[i] = new Channel(machine.ClockSource, masterClockFrequency, this, i); + channels[i] = new Channel(machine.ClockSource, masterClockFrequency, this, i, maxValue); connections[i] = channels[i].IRQ; } Connections = new ReadOnlyDictionary(connections); @@ -323,14 +324,15 @@ public enum Registers private class Channel { - public Channel(IClockSource clockSource, ulong masterClockFrequency, IPeripheral owner, int channel) + public Channel(IClockSource clockSource, ulong masterClockFrequency, IPeripheral owner, int channel, ulong maxValue = DefaultMaxValue) { this.masterClockFrequency = masterClockFrequency; + this.maxValue = maxValue; this.channel = channel; parent = owner; IRQ = new GPIO(); - timer = new LimitTimer(clockSource, masterClockFrequency, owner, $"channel-{channel}", MaxValue, Direction.Ascending, eventEnabled: true, divider: 2); - cTimer = new LimitTimer(clockSource, masterClockFrequency, owner, $"channel-{channel} C capture", MaxValue, Direction.Ascending, workMode: WorkMode.OneShot, eventEnabled: true, divider: 2); + timer = new LimitTimer(clockSource, masterClockFrequency, owner, $"channel-{channel}", maxValue, Direction.Ascending, eventEnabled: true, divider: 2); + cTimer = new LimitTimer(clockSource, masterClockFrequency, owner, $"channel-{channel} C capture", maxValue, Direction.Ascending, workMode: WorkMode.OneShot, eventEnabled: true, divider: 2); timer.LimitReached += LimitReached; cTimer.LimitReached += delegate { @@ -573,7 +575,7 @@ private void UpdateTimer(bool start = false) { // Capture mode: free-running counter up to MaxValue timer.Direction = Direction.Ascending; - timer.Limit = MaxValue; + timer.Limit = maxValue; } else { @@ -581,10 +583,10 @@ private void UpdateTimer(bool start = false) { case WaveSelection.Up: timer.Direction = Direction.Ascending; - timer.Limit = MaxValue; + timer.Limit = maxValue; break; case WaveSelection.UpDown: - timer.Limit = MaxValue; + timer.Limit = maxValue; break; case WaveSelection.UpRC: timer.Direction = Direction.Ascending; @@ -672,7 +674,7 @@ private void LimitReached() compareCInterrupt = true; break; case WaveSelection.UpDown: - if(timer.Value == MaxValue) + if(timer.Value == maxValue) { parent.NoisyLog("Channel #{0} overflow", channel); overflow = true; @@ -721,7 +723,8 @@ private void LimitReached() private readonly LimitTimer timer; private readonly LimitTimer cTimer; - private const ulong MaxValue = 0xFFFF; + private readonly ulong maxValue; + private const ulong DefaultMaxValue = 0xFFFF; } private enum ClockSelection