目录
1. 简介
2. 分析
2.1 Block Design
2.2 AXI Timer
2.2.1 IP 基本信息
2.2.2 IP 地址空间
2.2.3 级联模式
2.2.4 生成/捕获模式
2.3 AXI Interrupt
2.3.1 IP 基本信息
2.3.2 IP 地址空间
2.3.3 相关概念
2.3.4 参数配置
2.3.5 中断确认寄存器
3. PYNQ 代码
3.1 AXI Timer
3.1.1 Timer 寄存器映射
3.1.2 Timer 中断信息
3.1.3 脉冲信号
3.2 PYNQ 中断示例
4. 时钟频率
4.1 KV260 PL0 频率异常
4. 总结
1. 简介
1)PYNQ 中断类
Interrupt 类表示块设计中的单个中断引脚。它通过一个阻塞的等待函数模拟 Python 的 Event,直到中断被触发。事件会在中断被清除时自动清除。要构造一个事件,需要传入块图中引脚的完全限定路径,例如,将 'my_ip/interrupt' 作为唯一参数传入。
只有在有线程或协程等待相应事件时,中断才会被启用。使用中断的推荐方法是在一个循环中等待,在继续等待之前检查并清除 IP 中的中断寄存器。例如,AxiGPIO 类使用这种方法来等待所需值的出现。
2)中断控制器
要集成到 PYNQ 框架中,专用中断必须连接到一个 AXI 中断控制器,该控制器又连接到处理系统的第一条中断线。
对于只有一个中断的 PL overlay,可以不使用 AXI 中断控制器。在这样的设计中,中断引脚必须连接到处理系统的第一条中断线。
PYNQ 仅支持最终连接到 pl_ps_irq0 的中断。
2. 分析
2.1 Block Design
2.2 AXI Timer
2.2.1 IP 基本信息
AXI Timer v2.0
AXI Timer 包括以下关键模块:
- AXI4-Lite 接口:用于访问内存映射的计时器寄存器。
- Timer Registers:一组 32 位寄存器,包括加载寄存器、计时器/计数器寄存器和控制/状态寄存器。
- 32-bit Counters:Timer 模块有两个 32 位计数器,每个计数器都可以配置为上/下计数,并且可以从加载寄存器中加载一个值。
- Interrupt Control:中断控制模块根据操作模式生成单个中断。
- 脉冲宽度调制 (PWM):PWM 模块生成一个脉冲信号,PWM0,具有指定的频率和占空比。它使用 Counter 0 作为 PWM0 周期,使用 Counter 1 作为 PWM0 输出宽度。
Counter 模块的时钟速率为 s_axi_aclk。
2.2.2 IP 地址空间
| Address Offset | Register Name | Description |
|----------------|---------------|--------------------------------------|
| 0h | TCSR0 | Timer 0 Control and Status Register |
| 04h | TLR0 | Timer 0 Load Register |
| 08h | TCR0 | Timer 0 Counter Register |
| 0Ch-0Fh | RSVD | Reserved |
| 10h | TCSR1 | Timer 1 Control and Status Register |
| 14h | TLR1 | Timer 1 Load Register |
| 18h | TCR1 | Timer 1 Counter Register |
| 1Ch-1Fh | RSVD | Reserved |
1)Control/Status Register
- ENALL:启用所有计时器,向此位写入 1 将设置 ENALL、ENT0 和 ENT1。向此寄存器写入 0 将清除 ENALL,但对 ENT0 和 ENT1 无影响。
- 0 = 对计时器无影响
- 1 = 启用所有计时器(计数器开始运行)
- T0INT:定时器0中断。表示中断条件已经发生。如果定时器模式是捕获并且定时器已启用,此位表示已发生捕获。如果模式是生成,此位表示计数器已翻转。必须通过写入1来清除。
- 读取:
0 = 没有发生中断
1 = 已发生中断 - 写入:
0 = T0INT 状态无变化
1 = 清除 T0INT(清零)
- 读取:
- Load:加载(计时器),TLR0 -> TCR0。设置此位,计数器将停止运行;因此,启动计数(TCSR0.ENT0)前,应清除此位。
- 0 = 不加载
- 1 = 用 TLR0 中的值加载计时器
2.2.3 级联模式
在级联模式下,两个定时器/计数器被级联以作为单个64位的计数器/定时器操作。级联计数器可以在生成模式和捕获模式下工作。TCSR0 充当级联计数器的控制和状态寄存器。在此模式下,TCSR1被忽略。
当需要超过32位宽的定时器/计数器时,使用此模式。级联操作要求将定时器0和定时器1作为一对一起使用。定时器1的计数事件是定时器0从所有1翻转到所有0,或者在倒数时相反。
2.2.4 生成/捕获模式
1)生成模式
- 在生成模式下,计时器从加载寄存器(Load Register)中设置的初始值开始计数,可以选择递增或递减计数。
- 当计数值溢出时,根据定时器控制寄存器(TCSR)中的自动重载/保持(ARHT)位,计数器会重新加载初始值或保持当前值。
- 如果中断使能(TINIT)位被设置,当计数器溢出时会产生一个中断信号。
2)捕获模式
- 在捕获模式下,当外部捕获触发信号(Capture Trigger)有效时,计数器的当前值会被存储到加载寄存器中。
- 根据TCSR寄存器中的递增/递减标志(UDT),计数器可以配置为递增或递减模式。
- 捕获事件发生时会产生一个中断信号,标定捕获时间。
3)总结
这两种模式分别用于不同的应用场景,生成模式通常用于定时和周期性事件,而捕获模式则用于记录特定事件发生的时间。
2.3 AXI Interrupt
2.3.1 IP 基本信息
AXI Interrupt Controller (INTC) v4.1
- 可用于扩展 PS 可用的中断输入数量,并提供一个优先级编码方案的选项。
- 支持可配置数量的软件中断,主要用于 multi-PS 中的处理器间中断。这些中断通过软件写入中断状态寄存器来触发。
- 支持多达32个中断。可级联以提供额外的中断输入。
- 支持嵌套中断。
2.3.2 IP 地址空间
| Address Offset | Register Name | Description |
|----------------|---------------|------------------------------------------------------|
| 00h | ISR | Interrupt Status Register (ISR) |
| 04h | IPR | Interrupt Pending Register (IPR) |
| 08h | IER | Interrupt Enable Register (IER) |
| 0Ch | IAR | Interrupt Acknowledge Register (IAR) |
| 10h | SIE | Set Interrupt Enables (SIE) |
| 14h | CIE | Clear Interrupt Enables (CIE) |
| 18h | IVR | Interrupt Vector Register (IVR) |
| 1Ch | MER | Master Enable Register (MER) |
| 20h | IMR | Interrupt Mode Register (IMR) |
| 24h | ILR | Interrupt Level Register (ILR) |
| 100h to 17Ch | IVAR | Interrupt Vector Address Register (IVAR) |
| 200h to 2FCh | IVEAR | Interrupt Vector Extended Address Register (IVEAR) |
1)中断状态寄存器(ISR,Interrupt Status Register)
2)中断待处理寄存器(IPR,Interrupt Pending Register)
3)中断使能寄存器(IER,Interrupt Enable Register)
4)中断确认寄存器(IAR,Interrupt Acknowledge Register)
5)设置中断使能(SIE,Set Interrupt Enables)
6)清除中断使能(CIE,Clear Interrupt Enables)
7)中断向量寄存器(IVR,Interrupt Vector Register)
8)主使能寄存器(MER,Master Enable Register)
9)中断模式寄存器(IMR,Interrupt Mode Register)
10)中断级别寄存器(ILR,Interrupt Level Register)
11)中断向量地址寄存器(IVAR,Interrupt Vector Address Register )
12)中断向量扩展地址寄存器(IVEAR,Interrupt Vector Extended Address Register )
2.3.3 相关概念
1)捕获、确认和启用中断条件
中断条件由 AXI INTC 核心捕获并保留,直到明确确认为止。中断可以全局或单独启用/禁用。当所有中断都被全局启用,并且至少有一个捕获的中断被单独启用时,处理器会收到中断条件信号。
2)边沿敏感和电平敏感捕获模式
- 边沿触发:当中断输入上出现活动边沿时,如果当前没有中断条件存在,则记录一个新的中断条件。(活动边沿的极性,上升或下降,是每个输入的选项。)无论中断是否被启用,中断都会被记录,并且保持直到得到确认。在此期间的任何活动边沿都没有影响。
- 电平触发:只要输入处于活动电平,并且当前没有中断条件存在,就记录一个中断条件。(活动电平的极性,高或低,是每个输入的选项。)无论中断是否被启用,中断都会被记录,并且即使在此期间输入电平变为非活动状态,也会保持直到得到确认。
3)中断向量寄存器
如果在 AXI INTC 模块中配置了可选的中断向量寄存器(IVR),则会在中断输入之间建立优先级关系(编号越小,优先级越高)。该寄存器返回附加到具有最高优先级的单独启用的中断的编号。这可以用来加快软件选择适当的中断服务例程的速度(软件向量化)。
4)快速中断模式控制
快速中断模式是一种在 AXI INTC IP 中可配置的中断处理方式,旨在满足对低延迟的需求。在此模式下,设备可以选择使用普通中断模式或快速中断模式,具体取决于对响应速度的要求。启用快速中断模式通过在中断模式寄存器(IMR)中设置相应的位来实现。
在快速中断模式下,当中断发生时,AXI INTC 核心会驱动中断向量地址,并将最高优先级的中断请求发送给处理器。处理器通过 Processor_ack 端口对中断进行确认。确认信号基于Processor_ack 信号更新,并且中断确认寄存器(IAR)的相应位在收到确认后更新。对 IAR 的写操作是由 AXI INTC 核心内部执行的,确保了处理过程的快速和高效。
此外,处理器在确认中断(即跳转到中断服务例程时)和执行中断服务例程中的返回中断指令时,会通过 Processor_ack 端口发送特定的信号。这些信号用于清除中断状态寄存器(ISR)中的相应位,从而管理中断的生命周期。
需要注意的是,在快速中断模式下,不允许直接写入 IAR,且中断向量寄存器(IVR)可能不会精确反映正在处理的中断。在软件初始化阶段,通常会设置中断向量地址寄存器(IVAR)和 IMR,而在任何中断启用期间,不应对这些寄存器进行写操作。这些措施有助于保障系统的稳定性和中断处理的正确性。
2.3.4 参数配置
2.3.5 中断确认寄存器
Interrupt Acknowledge Register(IAR),是一个只写寄存器,用于清除与选定中断相关的中断请求。向IAR中的某个位写入1会清除 ISR 中的相应位,并同时清除 IAR 本身中的该位。
在快速中断模式下,IAR 中的位会通过 processor_ack 端口的信息自动清除。在普通中断模式下,IAR 中的位通过 AXI 接口写入寄存器来清除。
向 IAR 中的某个位位置写入1会清除由相应中断输入生成的中断请求。一个通过向 IER 中相应位写入0而处于活动且被屏蔽的中断,将保持活动状态,直到通过确认来清除它。取消屏蔽一个活动中断会导致生成中断请求输出(如果 MER 中的 ME 位被设置)。
写入0不会产生任何效果,向不对应于活动输入的位或不存在中断的位写入1也不会产生任何效果。
3. PYNQ 代码
3.1 AXI Timer
3.1.1 Timer 寄存器映射
查看该 IP 的所有寄存器映射,非常方便。
print(timer0.register_map)
print(timer1.register_map)
---
RegisterMap {
TCSR0 = Register(MDT0=0, UDT0=1, GENT0=1, CAPT0=0, ARHT0=0, LOAD0=0, ENIT0=1, ENT0=0, T0INT=0, PWMA0=0, ENALL=0, CASC=0),
TLR0 = Register(TCLR0=100000000),
TCR0 = Register(TCR0=4294967295),
TCSR1 = Register(MDT1=0, UDT1=0, GENT1=0, CAPT1=0, ARHT1=0, LOAD1=0, ENIT1=0, ENT1=0, T1INT=0, PWMA1=0, ENALL=0),
TLR1 = Register(TCLR1=0),
TCR1 = Register(TCR1=0)
}
RegisterMap {
TCSR0 = Register(MDT0=0, UDT0=1, GENT0=0, CAPT0=0, ARHT0=0, LOAD0=0, ENIT0=1, ENT0=0, T0INT=0, PWMA0=0, ENALL=0, CASC=0),
TLR0 = Register(TCLR0=200000000),
TCR0 = Register(TCR0=4294967295),
TCSR1 = Register(MDT1=0, UDT1=0, GENT1=0, CAPT1=0, ARHT1=0, LOAD1=0, ENIT1=0, ENT1=0, T1INT=0, PWMA1=0, ENALL=0),
TLR1 = Register(TCLR1=0),
TCR1 = Register(TCR1=0)
}
3.1.2 Timer 中断信息
本例中使用了两个 Timer,可以查看各自的信息:
timer0 = ol.axi_timer_0
timer0._interrupts
---
{'interrupt': {'controller': 'axi_intc_0',
'index': 0,
'fullpath': 'axi_timer_0/interrupt'}}
timer1 = ol.axi_timer_1
timer1._interrupts
---
{'interrupt': {'controller': 'axi_intc_0',
'index': 1,
'fullpath': 'axi_timer_1/interrupt'}}
或者通过 ol.interrupt_pins 查看:
ol.interrupt_pins
---
controller:"axi_intc_0"
index:0
fullpath:"axi_timer_0/interrupt"
controller:"axi_intc_0"
index:0
fullpath:"xlconcat_0/In0"
controller:"axi_intc_0"
index:1
fullpath:"axi_timer_1/interrupt"
controller:"axi_intc_0"
index:1
fullpath:"xlconcat_0/In1"
3.1.3 脉冲信号
1)单个脉冲信号
timer0.register_map.TLR0 = 100_000_000 # Timmer Load Register
timer0.register_map.TCSR0.LOAD0 = 1 # TLR -> TCR
timer0.register_map.TCSR0.LOAD0 = 0 # 启动计数(TCSR0.ENT0)前,应清除此位
timer0.register_map.TCSR0.ARHT0 = 0 # 0 = Hold counter
timer0.register_map.TCSR0.GENT0 = 1 # 1 = Enables generate_out
timer0.register_map.TCSR0.ENIT0 = 1 # 1 = Enable Interrupt
timer0.register_map.TCSR0.UDT0 = 1 # 1 = Down Count
timer0.register_map.TCSR0.ENT0 = 1 # 1 = Enable Timmer
print('timer0 configured')
2)生成周期性的脉冲信号,参考以下代码:
timer0.register_map.TLR0 = 100_000_000 # Timmer Load Register
timer0.register_map.TCSR0.LOAD0 = 1 # TLR -> TCR
timer0.register_map.TCSR0.LOAD0 = 0 # 启动计数(TCSR0.ENT0)前,应清除此位
timer0.register_map.TCSR0.ARHT0 = 1 # 1 = Reload generate value
timer0.register_map.TCSR0.GENT0 = 1 # 1 = Enables generate_out
timer0.register_map.TCSR0.ENIT0 = 1 # 1 = Enable Interrupt
timer0.register_map.TCSR0.UDT0 = 1 # 1 = Down Count
timer0.register_map.TCSR0.ENT0 = 1 # 1 = Enable Timmer
print('timer0 configured')
3.2 PYNQ 中断示例
import pynq
import asyncio
import nest_asyncio
# 允许嵌套的事件循环
nest_asyncio.apply()
ol = pynq.Overlay('min6.bit')
ol.ip_dict
timer0 = ol.axi_timer_0
timer1 = ol.axi_timer_1
intc = ol.axi_intc_0
def intr_done():
timer0.register_map.TCSR0.T0INT = 1 # Clear T0INT,清除中断标志
timer0.register_map.TCSR0.ENT0 = 0 # 0 = Disable Timmer
timer1.register_map.TCSR0.T0INT = 1 # Clear T0INT,清除中断标志
timer1.register_map.TCSR0.ENT0 = 0 # 0 = Disable Timmer
intc.register_map.IAR = 0x03 # Clear interrupt
async def wait_for_timer0(cycles):
print('timer0 start')
timer0.register_map.TLR0 = cycles # Timmer Load Register
timer0.register_map.TCSR0.LOAD0 = 1 # TLR -> TCR
timer0.register_map.TCSR0.LOAD0 = 0 # 启动计数(TCSR0.ENT0)前,应清除此位
timer0.register_map.TCSR0.ARHT0 = 0 # 0 = Hold counter
timer0.register_map.TCSR0.GENT0 = 1 # 1 = Enable generate_out
timer0.register_map.TCSR0.ENIT0 = 1 # 1 = Enable Interrupt
timer0.register_map.TCSR0.UDT0 = 1 # 1 = Down Count
timer0.register_map.TCSR0.ENT0 = 1 # 1 = Enable Timmer
await timer0.interrupt.wait()
timer0.register_map.TCSR0.ENT0 = 0 # 0 = Disable Timmer
timer0.register_map.TCSR0.T0INT = 1 # Clear T0INT,清除中断标志
print('timer0 done')
async def wait_for_timer1(cycles):
print('timer1 start')
timer1.register_map.TLR0 = cycles # Timmer Load Register
timer1.register_map.TCSR0.LOAD0 = 1 # TLR -> TCR
timer1.register_map.TCSR0.LOAD0 = 0 # 启动计数(TCSR0.ENT0)前,应清除此位
timer1.register_map.TCSR0.ARHT0 = 0 # 0 = Hold counter
timer1.register_map.TCSR0.GENT0 = 0 # 0 = Disable generate_out
timer1.register_map.TCSR0.ENIT0 = 1 # 1 = Enable Interrupt
timer1.register_map.TCSR0.UDT0 = 1 # 1 = Down Count
timer1.register_map.TCSR0.ENT0 = 1 # 1 = Enable Timmer
await timer1.interrupt.wait()
timer1.register_map.TCSR0.ENT0 = 0 # 0 = Disable Timmer
timer1.register_map.TCSR0.T0INT = 1 # Clear T0INT,清除中断标志
print('timer1 done')
async def main():
task0 = asyncio.create_task(wait_for_timer0(100_000_000)) # 2s Max 4_294_967_295 cycles
task1 = asyncio.create_task(wait_for_timer1(200_000_000)) # 4s
await task0
await task1
intr_done()
asyncio.run(main())
PYNQ 框架中的硬件中断,不同于传统微控制器的中断服务程序(ISR),PYNQ 使用 Python 的异步编程模式来处理中断,这种方法更加灵活和高级。
PYNQ 中的中断处理机制:使用异步等待。
通过使用 asyncio 库中的 await 关键字,可以异步等待中断事件,Python 代码可以在不阻塞整个程序的情况下,等待硬件事件的发生。
await timer0.interrupt.wait()
这行代码实际上是在等待来自 timer 的中断信号。当中断发生时(计时器达到预设的计数值),这个等待状态会结束,程序将继续执行下一行代码。这与传统的中断服务程序在概念上有些相似,但在实现上更加现代和适合高级语言。
优势:
非阻塞:使用异步等待中断使得CPU可以在中断未发生时处理其他任务,这在多任务环境中非常有用。
简化的错误处理和资源管理:在 Python 的异步环境中,错误处理和资源管理可以通过现代编程实践(如上下文管理、异常处理等)来实现,这比传统的 ISR 中的错误处理和资源管理要简单和安全。
4. 时钟频率
4.1 KV260 PL0 频率异常
实际的频率输出是 13.33 MHz。
Timer 输出的脉冲:
符合预期。
4. 总结
本文分享了了 PYNQ 平台中关于中断处理和AXI Timer的配置与使用。
- PYNQ 中断类的基本概念和操作方式,包括中断的启用、等待和清除。
- AXI Timer 的关键功能,如计时器的加载、计数方式(向上或向下计数)以及中断生成,同时也提到了级联模式和生成/捕获模式的实际应用场景。
- AXI 中断控制器(INTC)的配置和中断处理机制,如何通过 PYNQ 框架使用 Python 的异步编程模式处理硬件中断。
参考:
https://pynq.readthedocs.io/en/latest/pynq_libraries/interrupt.htmlhttps://pynq.readthedocs.io/en/latest/pynq_libraries/interrupt.html