面某芯片公司FPGA岗,二面面试官直接甩了一道手写代码题:设计一个支持单通道、带中断的简易DMA控制器,要求写出状态机、地址寄存器和数据通路。我平时项目只做过UART、SPI这类外设驱动,完全没碰过总线主设备,当场卡壳。想问问有经验的前辈,这类题面试官到底在考什么?如果没做过DMA,面试时该怎么把思路往自己熟悉的方向靠?
2026年FPGA校招面试,面试官让我现场用Verilog写一个「简易DMA控制器」,只做过UART和SPI的应届生该怎么拆解思路?
10 个回答

这道题其实考的不是你有没有写过DMA,而是你有没有『总线主设备』这个思维模型。你做过UART、SPI,那是从设备视角:等片选、等时钟、被寄存器读写。DMA反过来,是它主动发起读写、去抢总线、去告诉CPU『我搬完了』。
现场拆解可以这样:先画一张框图——主机接口(寄存器配置:源地址、目的地址、长度、启动位)、数据通路(读通道+写通道+一个FIFO或直接bypass)、控制状态机、中断产生逻辑。画完框图,面试官基本就知道你懂架构了,代码只是填空。
状态机别一上来写五六个状态,先写最小可用版本:IDLE(等start)→ REQ(向总线发请求、等grant)→ READ(读一个beat)→ WRITE(写一个beat)→ 判断地址自增后长度是否归零 → 归零进DONE,否则回REQ。地址自增就是两个计数器:src_addr <= src_addr + 1(或+4),dst同理,len递减。握手用valid/ready这组经典对:主设备拉valid,从设备ready时数据才算被接收,别写成单拍假设——这是应届生最常翻车的地方。中断条件就一句:len归零且最后一拍握手完成,拉irq,等CPU写clear位再撤。
没做过的项目怎么靠?你可以说:UART的发送状态机我是这么分的,DMA本质是把『数据源』从波特率定时器换成总线,状态跳转思想是通的。这样既诚实又展示迁移能力,比硬编强。
风险提醒:别在面试现场纠结时序细节(比如跨时钟域同步),先把功能框架讲清楚。真要深挖,面试官会追问:如果读比写慢怎么办?这时候一个FIFO加反压就是加分项。
你手头项目里有没有哪个模块是你自己从零画过状态机的?那个模块的状态划分,其实就是你答这题的底子。

先说个容易被忽略的点:面试官让你手写DMA,八成不是想看你能不能默出完整可综合代码,而是想看你面对一个没做过的模块时,会不会先建模再动手。你可以先把DMA翻译成你熟悉的东西——它本质上就是个『不用CPU盯着搬数据的小工』,只不过这个工人有主动发起读写的能力,而你的UART是被动等别人来读写它。
现场可以这样开口:我先不写代码,先画个框图确认理解对不对。左边是主机接口,CPU通过它写源地址、目的地址、长度和启动位;中间是控制状态机;右边是总线主接口,负责向总线发读写请求。数据通路里放一个FIFO或者直接bypass。画完这一步,面试官通常会点头,因为这说明你脑子里有架构。
然后讲状态跳转:IDLE等start,收到后进REQ去要总线授权,拿到授权后READ一个beat、WRITE一个beat,地址各自自增,长度减一,减到零就进DONE并拉中断。这里的关键是地址自增要用两个独立计数器,别想着共用一个。
一个风险提醒:如果你完全没碰过总线握手,别硬编valid/ready细节,容易露怯。可以主动说『握手时序我按经验这样处理,具体协议要对着手册再核』。这样反而显得你有工程边界感。你目前项目里有没有涉及过中断或状态机?如果有,哪怕是很小的模块,也可以往这上面引。

关键就一句:DMA是总线主设备,会主动抢总线,而不是像UART那样等人来读写。你只要把『谁发起、谁应答、搬完怎么通知CPU』这三件事讲清楚,代码写不完整也没关系。先画框图再写状态机,面试官看的就是这个。

我当年也是外设背景,第一次被问DMA同样懵。后来复盘发现,真正卡住我的不是没写过,而是没建立起『主从』这个分类意识。
外设驱动写多了,思维会固定成:我配好寄存器,然后等数据来、等中断、读走。整个过程中CPU是发起者,我永远是被动方。而DMA反过来——它自己决定什么时候去读内存、什么时候去写外设,CPU只是事先给了它一张任务单。这个视角一换,很多设计细节就顺了。
顺着这条线往下推,为什么DMA要有状态机?因为它得管住『什么时候能抢总线、抢到了搬几个beat、搬完怎么收尾』。为什么要有地址自增?因为任务单上写的是起始地址加长度,实际搬运要一个beat一个beat地推进,源和目的各一个计数器。为什么要有中断?因为CPU不能一直轮询,搬完必须有个信号告诉它『活干完了』。你把这个因果链讲出来,比背状态名更有说服力。
再说个取舍:现场时间有限,别追求写全。优先保证三样东西——状态划分合理、地址自增逻辑对、中断产生条件清楚。握手信号valid/ready能提一句就提一句,提不了就说明你知道这是协议层的事,需要查手册。
准备层面,我建议你回去补一个最小DMA的仿真练手,哪怕只是把状态机跑通、打印波形。有了这一次动手,面试时你讲的就是『我做过』而不是『我猜』。你现在的课设或竞赛项目里,有没有能改造成带搬运逻辑的模块?从那儿切入可能比从零学DMA更快。

第1条:
我理解你当场卡壳的那种感觉,但你换个角度想:面试官其实不太可能指望一个只写过UART/SPI的应届生,现场默出一份能综合、能跑通时序的完整DMA。真要看完整代码,笔试或机试环节更合适。二面现场手写,考的是你能不能把一个陌生模块拆成自己熟悉的构件,讲清楚数据怎么流、控制怎么走。
所以第一步不是写代码,是建模。你可以先跟面试官说:我先画个框图确认理解。DMA本质是个总线主设备——这一点是你和UART最大的认知差。UART是你去读写它的寄存器,它被动应答;DMA反过来,它自己发起读写请求,CPU只是事先给它一张任务单:源地址、目的地址、搬多少字节、搬完要不要中断。
框图四块就够了:主机接口(CPU写配置寄存器)、控制状态机、数据通路(读通道+写通道,中间可以放个FIFO也可以bypass)、中断产生逻辑。画完这四块,面试官基本就知道你不是在硬背。
然后讲状态机,别一上来堆六七个状态。最小可用版本:IDLE等start,收到后进REQ去申请总线,拿到grant进READ读一个beat,再进WRITE写一个beat,然后判断长度计数器是否归零——没归零回REQ,归零进DONE并拉中断。地址自增就是两个计数器,src_addr和dst_addr各自加beat宽度。
这里有个容易翻车的点:valid/ready握手。读的时候你发读请求,要等从设备把valid拉高你才能收数据;写的时候你拉valid,要等对方ready。别把这两拍搞混,很多人栽在这。
你写不完整没关系,把状态跳转和握手时序讲清楚,比默代码有说服力。
你现在的项目里,有没有哪块是你自己设计过状态机的?那个经历其实可以直接迁移过来讲。
第2条:
核心就一句:先别想DMA,先想「谁主动」。你写过UART,UART是别人来读写它,它被动;DMA是它主动去抢总线搬数据。抓住这个主从反转,整道题就顺了。
现场这么走:画框图(主机接口+状态机+数据通路+中断),然后口述状态跳转IDLE→REQ→READ→WRITE→DONE,地址用两个计数器自增,搬完拉中断。代码写关键几个always块就够,别硬凑完整。面试官要的是你面对陌生模块时的拆解能力,不是背诵能力。
第3条:
别慌,这题本质是考你有没有「总线主设备」这根弦。你只要说清DMA是主动发起方、UART是被动应答方,再画个框图把状态机走一遍,就已经赢过一半人了。代码写不全真没事。

先说一个反常识的判断:面试官让你现场写DMA,大概率不是想看你默出一份能综合的完整代码。二面手写题通常只有十几分钟,真要跑通时序、验证握手,那是机试的活。所以他在看的是另一件事——你面对一个没做过的模块,第一反应是慌,还是先把它拆成自己认识的东西。
你做过UART和SPI,这其实是很值钱的底子,别急着否定自己。你缺的不是Verilog能力,而是一个分类视角:你之前写的都是从设备,CPU来读写你的寄存器,你被动应答;DMA是主设备,它自己发起读写、去抢总线、搬完再通知CPU。这个『谁发起、谁应答、搬完怎么通知』的框架一旦立起来,你会发现DMA没那么神秘,它只是把你在UART里做的『等中断、读数据、写数据』换了个主动方。
现场我建议你开口第一句就是:我先不写代码,画个框图跟您确认理解。然后画四块——主机接口(CPU写源地址、目的地址、长度、启动位)、控制状态机、数据通路(读通道加写通道,中间可以放FIFO也可以bypass)、中断产生逻辑。画完这一步,面试官基本就知道你脑子里有架构了,代码只是填空。
状态机别一上来堆五六个状态,先给最小可用版本:IDLE等start,收到进REQ去要总线授权,拿到grant后READ一个beat、WRITE一个beat,地址各自自增,长度递减,减到零进DONE拉中断,否则回REQ继续。地址自增就是两个计数器,源和目的各一个,每个beat加一次。中断条件很朴素:长度归零且最后一个写完成。
有一个细节特别能加分:握手。总线上的valid/ready是主从之间的礼节,你的请求发出去了不代表对方马上接,所以READ和WRITE里要能等ready,不能假设一拍就完。你如果主动提一句『这里要处理背压,不能死等』,面试官会知道你写过真实接口,不是背的。
再往深一层,你可以聊聊取舍:FIFO放不放?放了能吸收读写速率不匹配,代价是面积和延迟;bypass省资源但要求源和目的节奏对齐。这种『为什么这么选』的讨论,比多写二十行代码更能体现工程思维。
最后说准备方法。别再刷题式地背DMA代码,去做一件事:把AXI或Avalon的握手时序图找出来,自己画一遍valid/ready的波形,理解背压。然后拿你现有的UART项目,试着把它改成一个主动发起读写的版本,哪怕只是仿真。这个过程比看十篇博客有用。
你现在手头有能跑仿真的环境吗,还是只有学校实验箱?这决定了我建议你练到什么颗粒度。

其实面试官多半不是要你默出一份能综合的DMA,而是看你会不会先建模。DMA跟你写过的UART最大的差别就一句话:它是总线主设备,会主动去抢总线搬数据,CPU只负责下任务单。你开口先说这句,再画个框图,基本就稳了。你当时是卡在状态机怎么分,还是卡在握手信号上?

我换个角度说,别把它当DMA,把它当你写过的东西拼起来。搬数据这件事,你UART里其实干过:读一个字节,写一个字节,只不过发起方是CPU。DMA就是把这套动作交给一个状态机自己发起——IDLE等start,收到就进REQ去要总线授权,拿到grant后READ一个beat、WRITE一个beat,源地址和目的地址各自自增,长度计数器减一,减到零就进DONE拉中断。你看,READ/WRITE这两个状态跟你收发寄存器没本质区别,新东西只是前面多了个『要总线』的REQ,后面多了个『通知CPU』的DONE。至于数据通路,中间放个FIFO或者直接bypass都行,面试时讲清楚你为什么这么选就够了。这么一拆,写起来就是填空,不是从零发明。

真正容易踩的坑,是把DMA写成一个大号状态机,然后卡在握手信号上出不来。我建议你先分清两条线:控制流和数据流。控制流是状态机加地址寄存器、长度寄存器,管『什么时候搬、搬到哪、还剩多少』;数据流是读通道和写通道之间的valid/ready握手,管『这一拍数据到底成没成』。很多人写崩就是因为把这两件事揉在一起,状态机里又判valid又改地址,自己都绕晕。
举个具体的:READ状态里你发出读请求,总线不一定这一拍就回数据,所以要有个等ready或者等rvalid的子状态;数据真正到手了,再进WRITE把它写出去。地址自增也别写成state里的一句a <= a + 4,最好单独拎出来,源和目的各一个计数器,长度减到零时统一判断跳DONE,这样逻辑干净,面试官一看就知道你脑子里有层次。
还有个替代做法值得提一句:如果现场时间紧,你可以先说『我先写一个bypass版本,不加FIFO,保证功能对;如果带宽要求高,我再在读写之间插一级FIFO做缓冲』。这等于主动展示了工程取舍,比闷头写一个又长又错的版本强得多。风险在于,你要是连REQ和DONE都讲不清,光堆复杂度反而暴露短板,所以先保最小可用版本。你项目里的UART波特率是怎么算的,这个说不定也能借来类比一下时钟域的问题?

先给你一个挺实用的心态:面试官让你现场写DMA,多半不是要一份能跑综合的完整代码,而是看你面对陌生模块会不会先建模。你只做过UART和SPI,其实底子够用,缺的只是「总线主设备」这个分类意识——UART是你去读写它,它被动应答;DMA反过来,它自己发起读写、去抢总线,CPU只丢一张任务单给它。
开场别急着敲键盘,先画框图确认理解:主机接口(CPU写源地址、目的地址、长度、启动位)、控制状态机、数据通路(读通道加写通道,中间可放FIFO也可bypass)、中断产生逻辑。画完再讲跳转:IDLE等start,进REQ要总线授权,拿到grant后READ一个beat、WRITE一个beat,源和目的地址各自自增,长度减到零进DONE拉中断。
这里有个容易翻车的风险点:很多人把控制流和数据流揉在一起,状态机里又判valid又改地址,最后自己绕晕。稳妥做法是分开——状态机只管「什么时候搬、还剩多少」,valid/ready握手只管「这一拍数据成没成」,地址自增单独拎出来,源和目的各一个计数器。面试官一看就知道你脑子里有层次。
另外提醒一句,别把DMA讲成「大号UART」,主动方和被动方这个差别一定要点出来。你当时是卡在状态机怎么分,还是卡在握手信号上?
发表回答
登录后可在本页底部提交回答