最近做FPGA笔试题,题目要求用Verilog写一个AXI4-Lite从机,支持4个32位寄存器读写。我地址译码和握手信号都按协议写了,仿真时读数据没问题,但连续写两个不同地址时,前一个寄存器的值总被后一个覆盖。检查了AWVALID和WVALID的握手顺序,感觉没毛病。是写通道和地址通道没做同步,还是寄存器更新条件写错了?校招笔试这种题到底考什么?
2026年FPGA校招笔试遇到「用Verilog实现AXI4-Lite从机寄存器读写」的编程题,为什么地址译码写对了但写数据总被覆盖?
8 个回答

这种情况八成不是地址译码写错了,而是你把寄存器更新绑在了 WVALID 上。AXI4-Lite 的写通道里 AW 和 W 是两条独立的通道,各自握手,从机必须等 AWVALID 和 WVALID 同时有效、并且自己把 AWREADY 和 WREADY 都拉高,才能认为这一笔写事务真正成立。很多人图省事写成 WVALID && WREADY 就 wr_en=1,然后拿 WSTRB 直接去写某个固定寄存器,地址那一路只在 AW 握手时锁一下,结果连着写两个地址时,第二次的 W 数据就按上一次锁存的地址写进去了,看起来就是前一个被后一个覆盖。稳妥点的做法是先用一个简单状态机或者两个 valid 打拍,把 AW 的地址和 W 的数据都暂存到各自的 holding 寄存器里,等两者都到位再产生一拍 wr_en 和 wr_addr,统一更新寄存器组。仿真时建议单独跑一笔 AW 先到、W 后到的激励,再跑一笔 W 先到、AW 后到的,覆盖一下乱序到达。你现在仿真里 AW 和 W 是同一个时钟沿一起拉高的吗?如果是,那正好掩盖了这个问题。

先把协议捋清楚再谈代码,不然你会一直在「明明写对了」和「仿真不对」之间打转。AXI4-Lite 的写事务由两条独立通道构成:写地址通道 AW 和写数据通道 W。它们各有自己的 VALID/READY 握手,协议上并不保证谁先到,也不保证同一拍到。从机要做的事情是:只有当这一笔事务的 AW 和 W 都完成握手,才认为写操作有效,然后把 W 通道上的数据按 WSTRB 写进 AW 通道给出的地址对应的寄存器。很多人踩的坑就出在「什么时候算写有效」这个判断上。常见的错误写法是:AW 握手时把 awaddr 锁进一个 addr_reg,然后 W 握手时直接用 wr_en 更新寄存器,但更新时用的地址可能是上一次残留的 addr_reg,或者反过来,数据没锁住就跟着下一拍变了。连续写两个不同地址时,第一笔的 W 数据被第二笔的地址配上,或者第二笔的数据把第一笔的寄存器又写了一遍,表现出来就是「前一个被覆盖」。
正确的骨架其实不复杂。你可以用一个很轻的状态机:IDLE 状态下等 AWVALID 和 WVALID 都有效,把 awaddr 和 wdata、wstrb 一起锁存进 holding 寄存器,同时把 AWREADY 和 WREADY 拉高完成握手;下一拍产生一个单周期的 wr_en,用锁存好的地址、数据、掩码去更新寄存器组,然后回到 IDLE。如果不想用状态机,也可以用两个 valid 打拍加一个联合判断,但状态机在笔试和工程里都更好讲清楚,面试官一看就知道你想过时序。
再往深一层,有几个取舍值得在校招阶段想明白。第一,AWREADY 和 WREADY 要不要一直拉高?一直拉高实现简单,但遇到慢速寄存器或者需要等待的场景就不合适;笔试里一般直接拉高可以接受,但你要能说出「如果需要背压,我会把它们和内部 ready 关联」。第二,写地址和数据要不要做深度缓冲?AXI4-Lite 通常一笔一笔来,不做 outstanding 也能过,但如果你写了多笔流水,就要考虑地址和数据队列的配对问题,这也是覆盖 bug 的高发区。第三,WSTRB 一定要用,别默认 32 位全写,否则字节写场景会错。
从笔试和面试的角度看,这道题真正考的不是你能不能把寄存器写对,而是你懂不懂「通道独立、握手成对」这个 AXI 的核心思想。面试官常追问的延伸点包括:AW 和 W 到达顺序不同怎么处理、写响应 B 通道什么时候给、读写同时来怎么仲裁、地址译码是字对齐还是字节对齐。你仿真时最好把 AW 先到、W 先到、同时到这三种激励都跑一遍,覆盖到了,这类题基本就稳了。顺便问一句,你现在的仿真激励里 AW 和 W 是同一个时钟沿拉高的吗?如果是,建议改成错开一两拍再看结果。

补一个容易被忽略的角度:就算你握手逻辑写对了,寄存器更新条件本身也可能埋雷。我见过一种写法是 always @(posedge clk) 里用 if (WVALID && WREADY) 去更新,同时地址用组合逻辑从 awaddr 直接接过来,没有锁存。仿真时如果 AW 和 W 刚好同拍,看着没问题;一旦激励错开,awaddr 可能已经变了,数据就写到别的寄存器上去了。
所以判断标准可以简化成一句话:寄存器更新必须发生在「AW 和 W 都已握手完成」之后的那一拍,而且用的地址和数据都得是那一刻锁存下来的稳定值,不能是还在变化的组合信号。你可以先按这个标准检查自己的代码,大概率能定位到问题。
替代做法上,如果笔试时间紧,用状态机最稳,逻辑清晰也方便讲;如果只想快速验证,也可以把 AW 和 W 的 valid 各打一拍,做一个联合 valid,再配一个 ready 回压,但要小心打拍带来的延迟和 B 通道响应的对齐。风险在于:打拍多了容易漏掉 BVALID 的时序,或者出现 ready 和 valid 不同步导致仿真挂死。
小建议是仿真时加一个自检,写完之后立刻回读四个寄存器,打印出来对比,比盯着波形快得多。

这题大概率是写使能只看了 WVALID 或 WREADY,没把 AW 的握手也算进去。AW 和 W 是两条独立通道,只有两者都有效、从机都回 READY 的那一拍才算这笔写成立,否则地址和数据会错配,前一个寄存器就被覆盖了。先查你的 wr_en 条件。

个人感觉你卡的不是译码,而是「一笔写事务什么时候算数」这个认知。AXI4-Lite 里 AW 和 W 各有各的 VALID/READY,协议不保证同拍,也不保证顺序,所以从机必须自己等两边都到齐。常见的翻车写法是:W 通道一握手就直接拿 WSTRB 往寄存器里写,地址用的是 AW 那边锁存下来的 awaddr。单笔写看着没事,连续写两个地址时,第二笔的 W 可能比第二笔的 AW 先到,于是数据就按上一次残留的地址写进去了,表现出来就是前一个寄存器被后一个覆盖。
建议在仿真里加个探针,把 awvalid、awready、wvalid、wready、awaddr 和当前 wr_en 一起打到波形上看,重点观察「wr_en 拉高那一拍,awaddr 是不是这笔事务对应的地址」。如果对不上,基本就实锤了。改法上,笔试时间紧的话直接上一个小状态机最省事:IDLE 等 AW 和 W 都握手,锁存地址和数据,再进 WRITE 拍更新寄存器。

笔试里这类题真正想看的,其实不是你能不能背出 AXI 的握手定义,而是你有没有「事务边界」这根弦。地址译码写对,只说明你知道 4 个寄存器各自的偏移;但写数据被覆盖,说明你把「通道握手」和「事务成立」混为一谈了。协议层面 AW 和 W 是解耦的,从机这一侧必须自己把它们重新对齐。
给你一个具体的小场景:假设寄存器 0 在 0x00,寄存器 1 在 0x04。第一笔写 0x00 的数据先到,你按 WVALID 触发,写进了 addr_reg 里存的 0x00,没问题。第二笔写 0x04 时,如果 W 比这次 AW 先来,你的 addr_reg 还是上一笔的 0x00,数据就又被写回寄存器 0 了,看起来就是被覆盖。
所以判断标准可以简化成:寄存器更新必须发生在 AW 和 W 都完成握手之后的那一拍,而且用的地址和数据都得是那一刻锁存好的稳定值,不能是还在变化的组合信号。替代做法上,状态机最稳,逻辑清晰面试也好讲;想省事也可以给 AWVALID、WVALID 各打一拍,用两个 holding 寄存器攒齐了再产生 wr_en。另外别忽略 WSTRB,笔试常顺手考你按字节写使能。
顺便说一句,面试官多半还会追问「如果 AW 和 W 不是同一拍来,你的 READY 怎么给」,提前想好怎么答。你现在是自己写仿真激励验证的,还是只跑了题目给的 testbench?

你这个现象基本可以锁定成一句话:寄存器更新只看了一半的握手。AXI4-Lite 的写事务是 AW 和 W 两条独立通道,各自有 VALID/READY,协议不承诺同拍、也不承诺谁先到。所以从机必须自己把「这一笔写成立」定义清楚:AWVALID 与 WVALID 同时有效、并且你这一侧 AWREADY 和 WREADY 都拉高的那一拍,才算数。
很多人写成 wr_en = WVALID && WREADY,地址用 AW 握手时锁存的 awaddr。单笔写看不出问题,连着写两个地址就露馅:第二笔的 W 可能比第二笔 AW 先到,此时 addr_reg 还是上一笔的地址,数据就写回到前一个寄存器,看起来就是被覆盖。反过来也有坑:地址锁存了但数据没锁,更新那一拍 wdata 已经跟着总线变了。
笔试里想拿分,思路比代码量重要。你可以先用一个很小的状态机:IDLE 等 AWVALID 和 WVALID 都到,分别把 awaddr 和 wdata 打进 holding 寄存器,同时把两个 READY 拉高,下一拍产生一拍 wr_en 去更新寄存器,然后回 IDLE。这样地址和数据天然对齐,面试官问起来也好讲。如果不想写状态机,用两个 valid 打拍做联合判断也行,但一定要保证更新用的地址和数据是同一笔事务锁存下来的稳定值,不能是还在变的组合信号。
顺带说一句,这题考的往往不是译码,而是你有没有「事务边界」这个概念。地址译码对了只说明你知道四个寄存器的偏移,写数据覆盖说明你还没把 AW 和 W 当成一笔事务来看。面试官多半会追问:如果 AW 和 W 长时间不同拍怎么办?WSTRB 怎么处理?写和读同时来会不会冲突?提前想清楚这些,比多写几行代码有用。你现在仿真是自己搭的 testbench 还是用现成的 VIP 激励?这个会影响你观察握手顺序的方式。

先说结论:你八成是把「写通道握手」当成了「写事务成立」。这两个在 AXI4-Lite 里不是一回事,而校招笔试这道题,考的就是你有没有把这条边界划清楚。
AXI4-Lite 的写事务由两条通道拼起来:AW 带地址和控制信息,W 带数据和字节选通 WSTRB。协议给这两条通道各自配了一套 VALID/READY 握手,而且明确不要求它们同拍、不规定谁先谁后。也就是说,从机的视角里,地址可能先到,数据可能先到,甚至中间隔好几拍。那「这一笔写到底什么时候算数」这件事,协议不管,得你自己定义。
大多数翻车写法长这样:AW 握手时把 awaddr 存进 addr_reg,然后 wr_en 直接用 WVALID && WREADY 去拉。单笔写,AW 和 W 凑巧同拍,仿真波形漂漂亮亮;一连写两个地址,第二笔的 W 比第二笔的 AW 早到一拍,此时 addr_reg 还留着上一笔的地址,新数据就按旧地址写进去了,现象就是前一个寄存器被后一个覆盖。反过来,如果地址锁了、数据没锁,更新那一拍 wdata 已经跟着总线翻到下一个值,同样错。
所以正确的判断条件是:AWVALID 和 WVALID 同时有效,并且你这一侧 AWREADY、WREADY 都拉高的那一拍,才产生一拍 wr_en,用的地址和数据必须都是那一刻锁存下来的稳定值,不能是还在变的组合信号。
写法上有两条路。一条是状态机:IDLE 等到 AW 和 W 都到齐,跳到 WRITE,拉一拍写使能,回 bvalid,再等 bready,回 IDLE。逻辑清晰,面试讲起来也好讲,笔试里最稳。另一条是更省面积的 holding 寄存器法:AW 握手时把地址存进 awaddr_hold 并置一个 aw_done,W 握手时把数据存进 wdata_hold 并置 w_done,两个 done 同时为 1 的那一拍做寄存器的实际更新,然后清标志。两种思路本质一样,都是把两条解耦的通道在从机侧重新对齐。
再往深一层想,这道题真正想筛的不是你会不会背握手定义,而是你有没有「事务边界」这个概念。地址译码写对,只说明你知道 4 个寄存器在 0x00、0x04、0x08、0x0C;但写数据被覆盖,说明你把通道握手和事务成立混为一谈了。这类题在笔试里出现频率很高,因为它同时考协议理解、时序意识和边界条件,比让你写个加法器有区分度得多。
建议你现在就做一件事:在仿真里把 awvalid、awready、wvalid、wready、awaddr、wdata 和你的 wr_en 一起打到波形上,专门看 wr_en 拉高那一拍,awaddr 是不是这笔事务对应的地址。对不上,实锤;对得上,再回头查 WSTRB 有没有正确按字节使能。另外顺手把 AW 先到、W 先到、同拍三种激励都跑一遍,能过这三种,基本上就稳了。
顺便问一句,你现在的实现是纯组合逻辑判断还是有状态机?这决定了改动量是加两行还是重写一块。
发表回答
登录后可在本页底部提交回答