1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System;
9 using System.Linq;
10 
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Exceptions;
14 using Antmicro.Renode.Peripherals.CPU;
15 using Antmicro.Renode.Peripherals.Bus;
16 using Antmicro.Renode.Logging;
17 using Antmicro.Renode.Time;
18 
19 namespace Antmicro.Renode.Peripherals.Timers
20 {
21     public class MSP430_Timer : BasicWordPeripheral
22     {
MSP430_Timer(IMachine machine, MSP430X cpu, int acknowledgeInterrupt, long baseFrequency = 32768, int captureCompareCount = 7)23         public MSP430_Timer(IMachine machine, MSP430X cpu, int acknowledgeInterrupt, long baseFrequency = 32768, int captureCompareCount = 7) : base(machine)
24         {
25             if(captureCompareCount <= 0 || captureCompareCount > 7)
26             {
27                 throw new ConstructionException("captureCompareCount should be between 1 and 7");
28             }
29 
30             cpu.InterruptAcknowledged += (interruptIndex) =>
31             {
32                 if(interruptIndex == acknowledgeInterrupt)
33                 {
34                     timerInterruptPending[0].Value = false;
35                     UpdateInterrupts();
36                 }
37             };
38 
39             TimersCount = captureCompareCount;
40 
41             mainTimer = new LimitTimer(machine.ClockSource, baseFrequency, this, "clk", limit: ushort.MaxValue, workMode: WorkMode.OneShot, eventEnabled: true);
42             internalTimers = Enumerable.Range(0, TimersCount)
43                 .Select(idx =>
44                 {
45                     var timer = new LimitTimer(machine.ClockSource, baseFrequency, this, $"compare {idx}", limit: ushort.MaxValue, workMode: WorkMode.OneShot, eventEnabled: true, direction: Direction.Ascending);
46                     var index = idx;
47                     timer.LimitReached += delegate
48                     {
49                         timerInterruptPending[index].Value = true;
50                         UpdateInterrupts();
51                     };
52                     return timer;
53                 })
54                 .ToArray()
55             ;
56 
57             timerInterruptEnabled = new IFlagRegisterField[TimersCount];
58             timerInterruptPending = new IFlagRegisterField[TimersCount];
59             timerCompare = new IValueRegisterField[TimersCount];
60 
61             mainTimer.LimitReached += LimitReached;
62 
63             DefineRegisters();
64         }
65 
66         [ConnectionRegionAttribute("interruptVector")]
WriteWordToInterruptVector(long offset, ushort value)67         public void WriteWordToInterruptVector(long offset, ushort value)
68         {
69             if(offset != 0)
70             {
71                 this.Log(LogLevel.Warning, "Illegal write access at non-zero offset (0x{0:X}) to interruptVector region", offset);
72             }
73             // NOTE: This region is single word wide, so we are ignoring offset argument
74             WriteWord((long)Registers.InterruptVector, value);
75         }
76 
77         [ConnectionRegionAttribute("interruptVector")]
ReadWordFromInterruptVector(long offset)78         public ushort ReadWordFromInterruptVector(long offset)
79         {
80             if(offset != 0)
81             {
82                 this.Log(LogLevel.Warning, "Illegal read access at non-zero offset (0x{0:X}) to interruptVector region", offset);
83             }
84             // NOTE: This region is single word wide, so we are ignoring offset argument
85             return ReadWord((long)Registers.InterruptVector);
86         }
87 
88         public int TimersCount { get; }
89 
90         public GPIO IRQ0 { get; } = new GPIO();
91         public GPIO IRQ_IV { get; } = new GPIO();
92 
LimitReached()93         private void LimitReached()
94         {
95             if(timerMode.Value == Mode.UpDown)
96             {
97                 mainTimer.Direction = mainTimer.Direction == Direction.Ascending ? Direction.Descending : Direction.Ascending;
98                 mainTimer.Value = mainTimer.Direction == Direction.Ascending ? 0 : mainTimer.Limit;
99             }
100 
101             if(timerMode.Value != Mode.Stop)
102             {
103                 mainTimer.Enabled = true;
104                 RecalculateCompareTimers();
105             }
106 
107             interruptOverflowPending.Value |= timerMode.Value == Mode.Up;
108             UpdateInterrupts();
109         }
110 
RecalculateCompareTimers()111         private void RecalculateCompareTimers()
112         {
113             var currentCount = mainTimer.Direction == Direction.Ascending ? mainTimer.Value : mainTimer.Limit - mainTimer.Value;
114             foreach(var entry in internalTimers.Select((Timer, Index) => new { Timer, Index }))
115             {
116                 var newLimit = mainTimer.Direction == Direction.Ascending ? timerCompare[entry.Index].Value : mainTimer.Limit - timerCompare[entry.Index].Value;
117                 entry.Timer.Value = currentCount;
118                 entry.Timer.Limit = newLimit;
119                 entry.Timer.Enabled |= mainTimer.Enabled && entry.Timer.Value <= entry.Timer.Limit;
120             }
121         }
122 
UpdateInterrupts()123         private void UpdateInterrupts()
124         {
125             var interrupt = timerInterruptPending[0].Value && timerInterruptEnabled[0].Value;
126             this.Log(LogLevel.Debug, "IRQ0: {0}", interrupt);
127             IRQ0.Set(interrupt);
128 
129             var interruptVectorIndex = Enumerable
130                 .Range(1, internalTimers.Length - 1)
131                 .FirstOrDefault(index => timerInterruptPending[index].Value && timerInterruptEnabled[index].Value);
132 
133             var interruptVector = interruptVectorIndex > 0;
134             interruptVector |= interruptOverflowEnabled.Value && interruptOverflowPending.Value;
135             this.Log(LogLevel.Debug, "IRQ_IV: {0}", interruptVector);
136             IRQ_IV.Set(interruptVector);
137         }
138 
UpdateDivider()139         private void UpdateDivider()
140         {
141             Divider = (1 << (int)clockDivider.Value) * ((int)clockDividerExtended.Value + 1);
142         }
143 
UpdateMode()144         private void UpdateMode()
145         {
146             switch(timerMode.Value)
147             {
148                 case Mode.Stop:
149                     Enabled = false;
150                     return;
151                 case Mode.Up:
152                     mainTimer.Direction = Direction.Ascending;
153                     mainTimer.Limit = timerCompare[0].Value;
154                     break;
155                 case Mode.Continuous:
156                     mainTimer.Direction = Direction.Ascending;
157                     var bits = timerWidth.Value == 0 ? 16 : 16 - 2 * ((int)timerWidth.Value + 1);
158                     mainTimer.Limit = (1UL << bits) - 1;
159                     break;
160                 case Mode.UpDown:
161                     mainTimer.Limit = timerCompare[0].Value;
162                     break;
163                 default:
164                     throw new Exception("unreachable");
165             }
166 
167             mainTimer.Enabled = true;
168         }
169 
DefineRegisters()170         private void DefineRegisters()
171         {
172             Registers.Control.Define(this)
173                 // NOTE: Interrupt flag
174                 .WithFlag(0, out interruptOverflowPending, name: "TBIFG")
175                 // NOTE: Interrupt enable
176                 .WithFlag(1, out interruptOverflowEnabled, name: "TBIE")
177                 // NOTE: Interrupt clear
178                 .WithFlag(2, FieldMode.WriteOneToClear, name: "TBCLR",
179                     writeCallback: (_, value) =>
180                     {
181                         clockDivider.Value = 0;
182                         UpdateDivider();
183 
184                         if(timerMode.Value == Mode.UpDown)
185                         {
186                             mainTimer.Direction = Direction.Ascending;
187                         }
188                         mainTimer.Value = mainTimer.Direction == Direction.Ascending ? 0 : mainTimer.Limit;
189                     })
190                 .WithReservedBits(3, 1)
191                 // NOTE: Mode
192                 .WithEnumField(4, 2, out timerMode, name: "MC",
193                     changeCallback: (_, __) => UpdateMode())
194                 // NOTE: Divider, (1 << value)
195                 .WithValueField(6, 2, out clockDivider, name: "ID",
196                     changeCallback: (_, __) => UpdateDivider())
197                 // NOTE: Clock select
198                 .WithTag("TBSSEL", 8, 2)
199                 .WithReservedBits(10, 1)
200                 // NOTE: Counter length,
201                 //       00b=16, 01b=12,
202                 //       10b=10, 11b= 8
203                 .WithValueField(11, 2, out timerWidth, name: "CNTL",
204                     changeCallback: (_, __) => UpdateMode())
205                 // NOTE: Group select
206                 .WithTag("TBCLGRP", 13, 2)
207                 .WithReservedBits(15, 1)
208                 .WithWriteCallback((_, __) => UpdateInterrupts())
209             ;
210 
211             Registers.CaptureCompareControl0.DefineMany(this, (uint)TimersCount, (register, index) =>
212             {
213                 register
214                     .WithFlag(0, out timerInterruptPending[index], name: "CCIFG")
215                     .WithTaggedFlag("COV", 1)
216                     .WithTaggedFlag("OUT", 2)
217                     .WithTaggedFlag("CCI", 3)
218                     .WithFlag(4, out timerInterruptEnabled[index], name: "CCIE")
219                     .WithTag("OUTMOD", 5, 3)
220                     .WithTaggedFlag("CAP", 8)
221                     .WithReservedBits(9, 1)
222                     .WithTaggedFlag("SCCI", 10)
223                     .WithTaggedFlag("SCS", 11)
224                     .WithTag("CCIS", 12, 2)
225                     .WithTag("CM", 14, 2)
226                     .WithWriteCallback((_, __) => UpdateInterrupts())
227                 ;
228             });
229 
230             Registers.Counter.Define(this)
231                 .WithValueField(0, 16, name: "TxAR",
232                     valueProviderCallback: _ => mainTimer.Value,
233                     writeCallback: (_, value) => mainTimer.Value = value)
234                 .WithWriteCallback((_, __) =>
235                 {
236                     RecalculateCompareTimers();
237                     UpdateInterrupts();
238                 })
239             ;
240 
241             Registers.CaptureCompare0.DefineMany(this, (uint)TimersCount, (register, index) =>
242             {
243                 register
244                     .WithValueField(0, 16, out timerCompare[index], name: $"TAxCCR{index}",
245                         changeCallback: (_, __) =>
246                         {
247                             UpdateMode();
248                             RecalculateCompareTimers();
249                         })
250                 ;
251             });
252 
253             Registers.InterruptVector.Define(this)
254                 .WithValueField(0, 16, name: "TAIV",
255                     valueProviderCallback: _ =>
256                     {
257                         int? index = Enumerable.Range(0, internalTimers.Length - 1).FirstOrDefault(idx => timerInterruptPending[idx].Value);
258                         if(!index.HasValue && interruptOverflowPending.Value)
259                         {
260                             index = internalTimers.Length + 1;
261                             interruptOverflowPending.Value = false;
262                         }
263                         else
264                         {
265                             timerInterruptPending[index.Value].Value = false;
266                         }
267 
268                         UpdateInterrupts();
269                         return (ulong)(index << 1);
270                     },
271                     writeCallback: (_, value) =>
272                     {
273                         if(value != 0)
274                         {
275                             // NOTE: Writes other than zero are no-op
276                             return;
277                         }
278 
279                         int? firstIndex = Enumerable.Range(0, internalTimers.Length - 1).FirstOrDefault(index => timerInterruptPending[index].Value);
280                         if(firstIndex.HasValue)
281                         {
282                             timerInterruptPending[firstIndex.Value].Value = false;
283                         }
284                         else
285                         {
286                             interruptOverflowEnabled.Value = false;
287                         }
288                         UpdateInterrupts();
289                     })
290             ;
291 
292             Registers.Expansion0.Define(this)
293                 .WithValueField(0, 3, out clockDividerExtended, name: "TAIDEX",
294                     changeCallback: (_, __) => UpdateDivider())
295                 .WithReservedBits(3, 13)
296             ;
297         }
298 
299         private bool Enabled
300         {
301             get => mainTimer.Enabled;
302             set
303             {
304                 mainTimer.Enabled = value;
305                 foreach(var timer in internalTimers)
306                 {
307                     timer.Enabled = value;
308                 }
309             }
310         }
311 
312         private long Frequency
313         {
314             get => mainTimer.Frequency;
315             set
316             {
317                 mainTimer.Frequency = value;
318                 foreach(var timer in internalTimers)
319                 {
320                     timer.Frequency = value;
321                 }
322             }
323         }
324 
325         private int Divider
326         {
327             get => mainTimer.Divider;
328             set
329             {
330                 mainTimer.Divider = value;
331                 foreach(var timer in internalTimers)
332                 {
333                     timer.Divider = value;
334                 }
335             }
336         }
337 
338         private IFlagRegisterField interruptOverflowEnabled;
339         private IFlagRegisterField interruptOverflowPending;
340 
341         private IFlagRegisterField[] timerInterruptEnabled;
342         private IFlagRegisterField[] timerInterruptPending;
343         private IValueRegisterField[] timerCompare;
344 
345         private IValueRegisterField clockDivider;
346         private IValueRegisterField clockDividerExtended;
347 
348         private IEnumRegisterField<Mode> timerMode;
349         private IValueRegisterField timerWidth;
350 
351         private readonly LimitTimer mainTimer;
352         private readonly LimitTimer[] internalTimers;
353 
354         private enum Mode
355         {
356             Stop,
357             Up,
358             Continuous,
359             UpDown,
360         }
361 
362         private enum Registers
363         {
364             Control = 0x00,
365             CaptureCompareControl0 = 0x02,
366             Counter = 0x10,
367             CaptureCompare0 = 0x12,
368             InterruptVector = 0x2E,
369             Expansion0 = 0x20
370         }
371     }
372 }
373