前文AXI4-Lite 写寄存器:AW/W 不同拍到达时怎样配对,并守住 B 通道背压解决"何时形成一次合法写提交";本文不重复总线通道,而是从那次提交之后开始,实现中断 pending 的 W1C 与同拍事件优先级。
中断状态位看起来只有"置1、清0"两件事,错误通常出现在软件写清除的同时又来了新事件。另一个常见问题是把enable当作事件捕获开关:屏蔽中断时,事件记录也跟着丢失。本实验只实现32位pending/status与enable寄存器核心,明确这两个竞态,不重新实现AXI4-Lite写通道。
本实验于 2026-10-21 用 Vivado Simulator 2022.1 / XSim(SW Build 3526262) 真实运行。时钟周期10ns、精度1ps;没有执行综合、STA或上板。图来自本次RTL的VCD,数据来自实际CSV,不是Python模拟RTL。
1. W1C与冲突优先级是两件事
Arm CMSIS-SVD 1.3.9的modifiedWriteValues/oneToClear定义写1清除对应位。这个写语义不替所有外设规定"硬件置位与软件清除同拍"的胜负关系。本文明确选用 set-dominant,即事件置位优先。
设某位沿前状态为P,本拍同步事件为E,有效清除条件为C,则本实验的状态转移是:
| E | C | 沿后P |
|---|---|---|
| 0 | 0 | 保持原值 |
| 0 | 1 | 0 |
| 1 | 0 | 1 |
| 1 | 1 | 1,保留同拍事件 |
向量表达为pending_next = (pending & ~clear_mask) | events。最后的事件OR使置位优先;若写成(pending | events) & ~clear_mask,冲突时就变成清除优先,改变了契约。
2. 接口与字节掩码
| 信号 | 含义 |
|---|---|
| clk / rst | 上升沿更新,同步高有效rst优先清空pending与enable |
| events31:0 | 已同步事件;高位在每个采样沿置相应pending |
| clear_we / clear_data / clear_strb | 软件W1C写脉冲、32位待清mask、4位byte使能 |
| enable_we / enable_data / enable_strb | 独立普通RW enable寄存器的写接口 |
| pending31:0 | 状态输出,读取无副作用;未使能事件也会记录 |
| enable31:0 / irq | irq为pending与enable按位与后的归约OR |
第i位有效清除条件是clear_we、clear_datai和clear_strbi/8同时为1。byte0控制位7:0。写0、clear_we=0、或所在byte的strobe=0都不清除该位。enable按相同byte lane规则更新选中字节,其余位保持;它影响irq而不抹除pending。
两套写接口可以同拍有效,这是寄存器核心契约,不是两笔总线事务的描述。地址译码、总线响应、跨域握手和软件访问原子性由外围负责。rst是同步复位:拉高但尚未到采样沿时,pending不承诺已经清零;到沿后才清空。本设计没有异步事件同步器,窄异步脉冲不能直接接入events。
enable与pending在同一沿各自更新,组合irq在该沿更新后反映新的两者。irq是电平通知,不是每个事件一个脉冲。持续为高的events会不断置位,软件清除无法使该位持续保持0。
3. 真实逐拍竞态
下面是原始TB最前面的实际输入及沿后状态。所有数值除时间外按十六进制或二进制标注。
| 上升沿/ns | 操作 | 沿后pending | 沿后enable | irq |
|---|---|---|---|---|
| 25 | event bit0=1,enable仍为0 | 00000001 | 00000000 | 0 |
| 35 | enable bit0置1 | 00000001 | 00000001 | 1 |
| 45 | event bit0与clear bit0同时有效 | 00000001 | 00000001 | 1 |
| 55 | clear_we=1,data=0,strb=0001 | 00000001 | 00000001 | 1 |
| 65 | clear_we=1,data=1,strb=0000 | 00000001 | 00000001 | 1 |
| 75 | clear_we=1,data=1,strb=0001,无事件 | 00000000 | 00000001 | 0 |

第二组在85ns置位01010101。95ns清除data=FFFFFFFF但strb=0010,因此只清byte1,pending变01010001。105ns把enable清0,仅让irq下降;115ns重新使能,已有pending使irq重新为高。125、135、145ns重复出现event bit31,它们合并为一个pending位;155ns一次有效清除即清掉bit31。165ns同步复位优先于全1事件和写操作。

4. 软件为什么应直接写mask
软件应直接写希望确认的位,例如要清bit0,就向状态寄存器写00000001,并使能对应byte。不要读出整个pending、再用普通RW寄存器的读改写方式写回;读到的其他1位也会被当成清除请求。例如读到00000003,为了"保留bit1"而把bit1的1写回,实际会把它清掉。
set-dominant只能保证新事件恰好与清除同拍时留下1。若同一位原本已为1,在软件读取之后、真正执行清除之前又发生事件,这些事件仍可能合并;状态位没有能力记下事件个数或先后次序。需要逐事件计数或完全可追溯交付时,应设计计数器、队列或明确的确认协议,不能从W1C规则推导出无损事件统计。
5. 完整RTL
保存为src/irq_w1c.sv。正文代码块由最终实际测试源码直接嵌入。
systemverilog
`timescale 1ns/1ps
module irq_w1c (
input wire clk, rst,
input wire [31:0] events,
input wire clear_we, input wire [31:0] clear_data,
input wire [3:0] clear_strb,
input wire enable_we, input wire [31:0] enable_data,
input wire [3:0] enable_strb,
output reg [31:0] pending, enable,
output wire irq
);
wire [31:0] clear_mask = clear_data &
{{8{clear_strb[3]}}, {8{clear_strb[2]}},
{8{clear_strb[1]}}, {8{clear_strb[0]}}} & {32{clear_we}};
integer lane;
assign irq = |(pending & enable);
always @(posedge clk) begin
if (rst) begin
pending <= 0; enable <= 0;
end else begin
pending <= (pending & ~clear_mask) | events;
if (enable_we)
for (lane=0; lane<4; lane=lane+1)
if (enable_strb[lane])
enable[8*lane +: 8] <= enable_data[8*lane +: 8];
end
end
endmodule
6. 完整原始自检TB
保存为src/tb_irq_w1c.sv。TB在下降沿驱动,在上升沿后1ns检查状态,避免与非阻塞赋值更新竞态。CSV的time_ns是被检查的采样沿时间,状态列为该沿后1ns读取的结果。
每位64组穷举覆盖旧pending、事件、clear_we、清除data、该byte的strobe、enable;每组先建立前态,再验证目标沿。另有16×16种clear/enable byte mask组合、1024个固定种子混合随机周期及复位冲突。随机种子固定,测试不依赖现场随机时间。
systemverilog
`timescale 1ns/1ps
module tb_irq_w1c;
reg clk=0,rst=1,clear_we=0,enable_we=0;
reg [31:0] events=0,clear_data=0,enable_data=0;
reg [3:0] clear_strb=0,enable_strb=0;
wire [31:0] pending,enable;
wire irq;
irq_w1c dut(.*);
always #5 clk=~clk;
reg [31:0] ref_pending=0,ref_enable=0,mask;
reg [31:0] random_state=32'h218badf0;
integer tests=0,truth_cases=0,fd,i,b,v,c;
function automatic [31:0] rng;
begin
random_state=random_state^(random_state<<13);
random_state=random_state^(random_state>>17);
random_state=random_state^(random_state<<5);rng=random_state;
end
endfunction
initial begin
fd=$fopen("trace.csv","w");
$fdisplay(fd,"time_ns,rst,events,clear_we,clear_data,clear_strb,enable_we,enable_data,enable_strb,pending,enable,irq");
end
always @(posedge clk) begin
if(rst) begin ref_pending=0;ref_enable=0;end
else begin
mask=0;
for(integer q=0;q<32;q=q+1) begin
mask[q]=clear_we && clear_strb[q/8] && clear_data[q];
if(enable_we && enable_strb[q/8]) ref_enable[q]=enable_data[q];
end
ref_pending=(ref_pending & ~mask) | events;
end
#1;
if(pending!==ref_pending || enable!==ref_enable || irq!==( |(ref_pending & ref_enable)))
$fatal(1,"W1C_ASSERT case=%0d pending=%h/%h enable=%h/%h irq=%b",tests,pending,ref_pending,enable,ref_enable,irq);
tests=tests+1;
$fdisplay(fd,"%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d",$time-1,rst,events,clear_we,clear_data,clear_strb,enable_we,enable_data,enable_strb,pending,enable,irq);
end
task automatic step(input bit r,input [31:0] ev,input bit cw,
input [31:0] cd,input [3:0] cs,input bit ew,input [31:0] ed,input [3:0] es);
begin
@(negedge clk);rst=r;events=ev;clear_we=cw;clear_data=cd;clear_strb=cs;
enable_we=ew;enable_data=ed;enable_strb=es;
@(posedge clk);#2;
end
endtask
initial begin
step(1,0,0,0,0,0,0,0);
step(0,1,0,0,0,0,0,0); // event while disabled is still recorded
step(0,0,0,0,0,1,1,15);
step(0,1,1,1,1,0,0,0); // set dominates simultaneous clear
step(0,0,1,0,1,0,0,0); // write zero retains
step(0,0,1,1,0,0,0,0); // missing strobe retains
step(0,0,1,1,1,0,0,0); // now clears
step(0,32'h01010101,0,0,0,1,32'hffffffff,15);
step(0,0,1,32'hffffffff,2,0,0,0);
step(0,0,0,0,0,1,0,15); // disable without erasing pending
step(0,0,0,0,0,1,32'hffffffff,15);
repeat(3) step(0,32'h80000000,0,0,0,0,0,0);
step(0,0,1,32'h80000000,8,0,0,0);
step(1,32'hffffffff,1,32'hffffffff,15,1,32'hffffffff,15);
// Per-bit 64-case truth table: old P, event, WE, data, strobe, enable.
for(b=0;b<32;b=b+1) for(v=0;v<64;v=v+1) begin
step(0,((v&1)!=0)?(32'b1<<b):0,1,32'hffffffff,15,
1,((v&32)!=0)?(32'b1<<b):0,15);
step(0,((v&2)!=0)?(32'b1<<b):0,(v&4)!=0,
((v&8)!=0)?(32'b1<<b):0,((v&16)!=0)?(4'b1<<(b/8)):0,0,0,0);
truth_cases=truth_cases+1;
end
// All pairs of clear/enable byte strobes with different lane contents.
for(i=0;i<16;i=i+1) for(c=0;c<16;c=c+1) begin
step(0,32'hffffffff,0,0,0,1,32'h12345678,15);
step(0,0,1,32'h0ff055aa,i,1,32'h87654321,c);
end
for(i=0;i<1024;i=i+1)
step(0,rng(),rng()&1,rng(),rng(),rng()&1,rng(),rng());
step(1,32'hffffffff,1,0,15,1,32'hffffffff,15);
step(0,0,0,0,0,0,0,0);
$display("TEST_PASS W1C original checked_cycles=%0d truth_cases=%0d strobe_pairs=256 random=1024",tests,truth_cases);
$fclose(fd);$finish;
end
initial begin #1000000;$fatal(1,"WATCHDOG W1C");end
endmodule
7. 独立复核与故意错误
独立reviewer先根据契约编写review/tb_w1c_review.sv,当时未读取主RTL或主TB。它逐位执行"先看事件、否则看有效清除"的决策,不复制DUT位向量表达式;逐位归约irq,并检查同步复位不会提前发生。
| 实际测试 | 结果 |
|---|---|
| 原始TB | PASS,5651个检查周期,2048个逐位真值用例,256种双strobe组合,1024个随机周期 |
| 独立review TB | PASS,35302个检查周期,16384个逐位真值用例,256种双strobe组合,2000个随机周期 |
| 实际CSV二次检查 | 两套结果全部正确;从review记录重建并确认16384个不同逐位真值向量 |
独立真值表每位包含9个布尔维度:旧pending、event、clear_we、clear_data位、clear_strb位、旧enable、enable_we、enable_data位、enable_strb位,因此为32×2^9=16384。这个数字不是所有32位系统状态与输入组合的完整穷举;逐位组合逻辑之外,用多位事件、byte组合和随机测试补充验证。
| 故意错误 | 实际功能断言失败位置 |
|---|---|
| 改为clear-dominant | review cycle=5,pending应1却变0 |
| 忽略clear byte strobe | cycle=41,不应被选中的位被清除 |
| 用enable掩掉pending | cycle=2,禁用期间事件记录丢失 |
| 把写1清除改成写0清除 | cycle=4,写0错误地清除了pending |
四个变体均完成xvlog与xelab,随后被W1C_REVIEW_ASSERT捕获;不能仅凭xsim进程退出码判断,因为$fatal后工具仍可能返回0。
开发过程中,独立reviewer首次编译发现初稿clear_mask拼接少了一个右花括号。这是真实初稿语法错误,已修正并重新编译、展开、仿真通过;失败记录保留在review_runs_w1c。它没有被算入上表的故意负例。修正后的正常RTL未发现契约内功能缺陷。
8. 独立复制运行
将上述两个源码保存后,在新的空目录运行:
powershell
New-Item -ItemType Directory run_w1c_manual
Set-Location ./run_w1c_manual
xvlog --sv ../src/irq_w1c.sv ../src/tb_irq_w1c.sv
xelab tb_irq_w1c -s sim -debug typical
xsim sim -runall
Set-Location ..
工具不在PATH时使用Vivado安装目录的.bat绝对路径;本次为D:\tools\Xilinx\Vivado\2022.1\bin。下面两条命令仅供持有作者完整复现归档时使用,不是复制本文两个文件后的必需步骤;归档内提供统一脚本,输出目录必须尚不存在:
powershell
python run_tests.py --suite original --out rerun_original
python run_tests.py --suite negative --out rerun_negative
归档中的这两条命令分别运行两篇原始TB与共8个故意变体。独立W1C复核可改用review/tb_w1c_review.sv和tb_w1c_review顶层手动编译。真实正常记录在runs_original/w1c_original与review_runs_w1c_02,后者VCD只保留前1000ns作为代表窗口,仿真随后run all完成全部检查,CSV覆盖全部35302个检查周期。主TB VCD保留全程;两张文章图均来自主TB。
本模块处理同步状态,不处理异步脉冲采集、总线协议或CPU中断控制器。读取无副作用不等于软件能无损计数,enable关闭也不等于事件消失。这些边界应写进硬件接口文档和驱动约定。
留一个集成问题
假设其余 pending/enable 位均为 0,未提及的事件与写使能均为 0。当前 pending9=1、enable9=0,软件置 clear_we=1、clear_data9=1,但 clear_strb1=0,同拍又有 events9=1:沿后 pending 与 IRQ 各是什么?下一拍仅使能 bit 9 后,软件应看到什么?