Skip to content

Commit 8a6890f

Browse files
authored
version of GPTimer without excessive autoformatting (#32)
1 parent 8d47664 commit 8a6890f

File tree

3 files changed

+239
-1
lines changed

3 files changed

+239
-1
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from ._platform_timer import PlatformTimer
22
from ._soc_id import SoCID
3+
from .gptimer import GPTimer
34

4-
__all__ = ['PlatformTimer', 'SoCID']
5+
__all__ = ['PlatformTimer', 'SoCID', 'GPTimer']
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from amaranth import Module, Signal, unsigned
2+
from amaranth.lib.wiring import Component, In, Out, connect, flipped
3+
4+
from amaranth_soc import csr
5+
6+
"""
7+
General-Purpose Timer:
8+
- 32-bit up-counter
9+
- 8-bit prescaler
10+
- compare match + auto-reload
11+
- level-high IRQ on match
12+
"""
13+
14+
15+
__all__ = ["GPTimer"]
16+
17+
18+
class GPTimer(Component):
19+
class Ctrl(csr.Register, access="rw"):
20+
"""CTRL: [0]=EN, [1]=RST, [2]=AR, [3]=IRQ_EN
21+
EN = Enables the counter
22+
RST = Resets the counter
23+
AR = Enable auto reload
24+
IRQ_EN = Enable interrupt on match
25+
"""
26+
27+
en: csr.Field(csr.action.RW, unsigned(1))
28+
rst: csr.Field(csr.action.RW, unsigned(1))
29+
ar: csr.Field(csr.action.RW, unsigned(1))
30+
irq_en: csr.Field(csr.action.RW, unsigned(1))
31+
# bits [4:8) reserved
32+
33+
class Presc(csr.Register, access="rw"):
34+
"""Prescaler divisor (0 => /1, 255 => /256)"""
35+
36+
val: csr.Field(csr.action.RW, unsigned(8))
37+
38+
class Compare(csr.Register, access="rw"):
39+
"""32-bit compare value"""
40+
41+
val: csr.Field(csr.action.RW, unsigned(32))
42+
43+
class Count(csr.Register, access="r"):
44+
"""32-bit free-running counter (read-only)"""
45+
46+
val: csr.Field(csr.action.R, unsigned(32))
47+
48+
class Status(csr.Register, access="rw"):
49+
"""STATUS: [0]=MATCH (W1C)"""
50+
51+
match: csr.Field(csr.action.RW1C, unsigned(1))
52+
# bits [1:8) reserved
53+
54+
def __init__(self):
55+
# CSR bank: 5-bit address, 8-bit data bus
56+
regs = csr.Builder(addr_width=5, data_width=8)
57+
self._ctrl = regs.add("ctrl", self.Ctrl(), offset=0x00)
58+
self._presc = regs.add("presc", self.Presc(), offset=0x04)
59+
self._count = regs.add("count", self.Count(), offset=0x08)
60+
self._compare = regs.add("compare", self.Compare(), offset=0x0C)
61+
self._status = regs.add("status", self.Status(), offset=0x10)
62+
63+
self._bridge = csr.Bridge(regs.as_memory_map())
64+
65+
super().__init__(
66+
{
67+
"bus": In(
68+
csr.Signature(
69+
addr_width=regs.addr_width, data_width=regs.data_width
70+
)
71+
),
72+
"irq": Out(1),
73+
}
74+
)
75+
self.bus.memory_map = self._bridge.bus.memory_map
76+
77+
def elaborate(self, platform):
78+
m = Module()
79+
m.submodules.bridge = self._bridge
80+
connect(m, flipped(self.bus), self._bridge.bus)
81+
82+
# shorthands to the field-ports
83+
ctrl = self._ctrl.f
84+
presc = self._presc.f
85+
cmp_ = self._compare.f
86+
cnt = self._count.f
87+
status = self._status.f
88+
89+
# Internal timer signals:
90+
p_cnt = Signal(8, name="prescaler_cnt")
91+
cnt_r = Signal(32, name="counter")
92+
mflag = Signal(1, name="match_pulse")
93+
94+
# prescaler & counter logic
95+
with m.If(ctrl.rst.data):
96+
m.d.sync += [p_cnt.eq(0), cnt_r.eq(0), mflag.eq(0)]
97+
with m.Elif(ctrl.en.data):
98+
with m.If(p_cnt == presc.val.data):
99+
m.d.sync += [p_cnt.eq(0), cnt_r.eq(cnt_r + 1)]
100+
with m.Else():
101+
m.d.sync += p_cnt.eq(p_cnt + 1)
102+
103+
# default no pulse match
104+
m.d.sync += mflag.eq(0)
105+
106+
# compare & auto-reload, set match-flag
107+
with m.If((cnt_r == cmp_.val.data) & ctrl.en.data):
108+
m.d.sync += mflag.eq(1)
109+
with m.If(
110+
ctrl.ar.data
111+
): # if auto reload isn't enabled the counter keeps incrementing after hitting the compare match value
112+
m.d.sync += cnt_r.eq(0)
113+
114+
m.d.comb += status.match.set.eq(
115+
mflag
116+
) # drive the RW1C “set” port from that pulse
117+
m.d.comb += cnt.val.r_data.eq(cnt_r) # mirror into COUNT CSR
118+
m.d.comb += self.irq.eq(
119+
status.match.data & ctrl.irq_en.data
120+
) # irq follows the *CSR storage
121+
122+
return m

tests/test_gptimer.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# amaranth: UnusedElaboratable=no
2+
3+
# SPDX-License-Identifier: BSD-2-Clause
4+
5+
from amaranth import *
6+
from amaranth.sim import Simulator
7+
from chipflow_digital_ip.base import GPTimer
8+
import unittest
9+
10+
11+
class _GPTimerHarness(Elaboratable):
12+
def __init__(self):
13+
self.timer = GPTimer()
14+
15+
def elaborate(self, platform):
16+
m = Module()
17+
m.submodules.timer = self.timer
18+
return m
19+
20+
21+
class TestGPTimer(unittest.TestCase):
22+
# CSR register offsets:
23+
REG_CTRL = 0x00
24+
REG_PRESC = 0x04
25+
REG_COUNT = 0x08
26+
REG_COMPARE = 0x0C
27+
REG_STATUS = 0x10
28+
29+
async def _write_reg(self, ctx, bus, addr, value, width=1): # width in bytes
30+
for i in range(width):
31+
ctx.set(bus.addr, addr + i) # addr to write to
32+
ctx.set(bus.w_data, (value >> (8 * i)) & 0xFF) # exact byte value to write
33+
ctx.set(bus.w_stb, 1) # write strobe (write data on bus)
34+
await ctx.tick() # on the next clock edge
35+
36+
ctx.set(bus.w_stb, 0) # done, you can stop writing
37+
await ctx.tick() # on the next edge
38+
39+
async def _read_reg(self, ctx, bus, addr, width=1):
40+
result = 0
41+
for i in range(width):
42+
ctx.set(bus.addr, addr + i) # set the bus to this address
43+
ctx.set(bus.r_stb, 1) # read from bus
44+
await ctx.tick() # on the next clock edge
45+
result |= ctx.get(bus.r_data) << (8 * i) # use bitwise or to assemble read
46+
ctx.set(bus.r_stb, 0)
47+
await ctx.tick()
48+
return result
49+
50+
def test_prescaler_and_count(self):
51+
"""Counter should increment once per (PRESC+1) cycles."""
52+
dut = _GPTimerHarness()
53+
54+
async def testbench(ctx):
55+
bus = dut.timer.bus
56+
57+
# prescaler = 2 → increment every 3 cycles & enable timer
58+
await self._write_reg(ctx, bus, self.REG_PRESC, 2, 1)
59+
await self._write_reg(ctx, bus, self.REG_CTRL, 1 << 0, 1)
60+
61+
# run 10 cycles → floor(10/3) = 3
62+
for _ in range(10):
63+
await ctx.tick()
64+
65+
cnt = await self._read_reg(ctx, bus, self.REG_COUNT, width=4)
66+
self.assertEqual(cnt, 3) # generater error message if this test fails
67+
68+
sim = Simulator(dut)
69+
sim.add_clock(1e-6) # generate clock signal for ctx
70+
sim.add_testbench(testbench)
71+
with sim.write_vcd("gptimer_presc_test.vcd", "gptimer_presc_test.gtkw"):
72+
sim.run() # run simulation
73+
74+
def test_match_and_irq_and_auto_reload(self):
75+
"""COMPARE match should set STATUS.MATCH, assert IRQ, then auto-reload."""
76+
dut = _GPTimerHarness()
77+
78+
async def testbench(ctx):
79+
bus = dut.timer.bus
80+
81+
# setup: prescaler 0, compare = 5, enable + auto-reload + irq enable
82+
await self._write_reg(ctx, bus, self.REG_PRESC, 0, 1)
83+
await self._write_reg(ctx, bus, self.REG_COMPARE, 5, 4)
84+
ctrl_bits = (1 << 0) | (1 << 2) | (1 << 3)
85+
await self._write_reg(ctx, bus, self.REG_CTRL, ctrl_bits, 1)
86+
87+
# step up to compare
88+
for _ in range(5):
89+
await ctx.tick()
90+
91+
# on next tick, match → STATUS.MATCH=1 and irq=1
92+
await ctx.tick()
93+
94+
cnt = await self._read_reg(ctx, bus, self.REG_COUNT, 4)
95+
self.assertEqual(cnt, 0)
96+
97+
status = await self._read_reg(ctx, bus, self.REG_STATUS, 1)
98+
99+
self.assertEqual(status, 1)
100+
self.assertEqual(ctx.get(dut.timer.irq), 1)
101+
102+
# clear the match bit
103+
await self._write_reg(ctx, bus, self.REG_STATUS, 1, 1)
104+
status = await self._read_reg(ctx, bus, self.REG_STATUS, 1)
105+
self.assertEqual(status, 0)
106+
107+
sim = Simulator(dut)
108+
sim.add_clock(1e-6)
109+
sim.add_testbench(testbench)
110+
with sim.write_vcd("gptimer_match_test.vcd", "gptimer_match_test.gtkw"):
111+
sim.run()
112+
113+
114+
if __name__ == "__main__":
115+
unittest.main()

0 commit comments

Comments
 (0)