资讯动态

FPGA数字系统设计:PARWAN三段式状态机与后仿排查模板

发布时间:2026/10/28 5:35:13 来源:尧图企业网站定制
简介一份面向高校软件工程、电子信息类课程选修与FPGA入门实践的课程大作业文档聚焦数字系统设计中的PARWAN处理器架构分析与CONTROL-SECTION状态机实现适合需要完成同类实验报告、课程设计或复习VHDL开发流程的学生参考。文档围绕处理器运算、控制、寻址三部分展开梳理PC、MAR、ALU、SR、IR与CONTROLLER的协作关系并给出CONTROL-SECTION状态机的VHDL设计思路、Spartan3E XC3S500E芯片下的工程创建、测试文件编写及后仿结果说明配有仿真截图与总结便于对照理解状态迁移和取址、运算阶段的控制信号变化。资源包仅含1个doc文件约763KB轻量便于下载查阅。已有553人学习可作为课程作业模板、实验过程记录和FPGA数字系统设计入门参考。1. FPGA 数字系统设计大作业的真实难点PARWAN 架构综述与状态机后仿翻到这份 FPGA 数字系统设计大作业的第三页多数人的第一反应是把 VHDL 敲完就收工结果交上去才发现扣分点根本不在代码能不能编译——而是架构综述写成了流水账后仿截图和代码里的状态对不上号。这份材料把 PARWAN 处理器的三段式数据通路、CONTROL-SECTION 状态机的状态划分、Spartan3E XC3S500E 上的工程搭建步骤、仿真验证流程按“目的—原理—步骤—结果”四段整理清楚了是一份可以直接照着复现的课程作业模板。适合正在做 FPGA 开发入门课设、需要交一份带后仿结果的数字系统设计报告的人也适合已经写过状态机但说不清“为什么控制字要打一拍”的熟手回头补课。2. PARWAN 数据通路拆解运算、控制、寻址三段靠一根时钟串起来先看教材那张结构图左边一堆运算部件中间是控制逻辑右边是寻址和数据搬移。这个划分看起来只是排版实际决定了后面状态机怎么写、控制字怎么分。把三段边界想清楚状态转移图基本就自动浮出来了反过来如果一上来就抄状态机模板写完一定会在“这个周期到底该拉高哪根使能”上卡住。2.1 运算段、控制段、寻址段的边界怎么划运算段的职责很单一ALU 做算术逻辑运算SHU 做移位SR 记录本次运算的状态标志零标志、进位、溢出这类。这一段不关心数据从哪来、结果往哪去只要求输入在时钟沿前稳定、输出在沿后有效。所以它的设计重点是组合逻辑延迟和标志位的采样时机而不是时序控制。寻址段负责把地址送到存储器、把数据取回来。PC 保存当前指令地址MAR 保存本次访问的内存地址两者容易被混为一谈——PC 是“下一条指令在哪”MAR 是“这一拍要访问哪个单元”前者由控制段决定自增还是装载后者是每个访存周期都要重新填的寄存器。ADBUS 把地址送出去数据总线把取回的内容送进 IR 或参与运算这条路径上的每个寄存器都有自己的使能信号。控制段夹在中间是唯一同时看到指令内容、状态标志和外部启动信号的部件。它输出一组控制字决定这一拍谁能写、谁能读、ALU 做什么操作。PARWAN 的整体节奏就是“控制段发令 → 寻址段取指取数 → 运算段计算 → 控制段根据结果决定下一步”形成一个闭环。架构综述要写的就是这条闭环而不是把每个部件的名字罗列一遍。提示写架构综述时先把三段各自的输入输出列清楚再说明段与段之间靠哪几根信号握手最后落一句整体节奏。三段话就能写完比堆砌术语得分高。2.2 PC、MAR、IR、SR、ALU 的信号职责与握手把这五个部件放在一张表里对照比看结构图快得多。下面这张表是按教材结构图整理的实际写报告时可以直接改成自己版本的命名部件所属段输入来源输出去向关键使能时序要求PC寻址控制段自增/装载MAR、地址总线pc_inc / pc_load时钟沿更新MAR寻址PC 或 IR 的地址字段地址总线 ADBUSmar_load沿前稳定IR寻址数据总线控制段译码ir_load取指周期末装载SR运算ALU 标志输出控制段条件判断sr_load与 ALU 结果同拍ALU运算累加器、数据总线累加器、SRalu_op纯组合延迟要算进周期这张表里最容易被忽略的是 SR 的采样时机。ALU 是组合逻辑结果在输入变化后的传播延迟之后才稳定如果 SR 和累加器在同一个时钟沿采样而 ALU 的输入恰好也在这个沿变化采到的就是旧值或者毛刺。常见做法是让 ALU 的输入来自寄存器输出这样整个组合路径在一个周期内稳定SR 在下一个沿采样就没有歧义。这一点在综合后的时序报告里会体现为组合路径延迟如果超过时钟周期后仿就会看到标志位错拍。另一个坑是把 PC 的自增和 MAR 的装载放到同一个状态里做。逻辑上没问题但要注意 pc_inc 拉高的那一拍PC 的新值要到下一个沿才生效而 MAR 装的是 PC 的当前值——如果两个动作写反了顺序取到的就是下一条指令的地址仿真波形上表现为指令序列整体偏移一条。这种错误在前仿里往往看不出来因为功能上“看起来”还在跑只是结果全错。2.3 一条指令走完的闭环控制、取址、运算、写回把一次完整的指令执行拆成状态PARWAN 这类教学处理器的典型节奏是空闲等待启动 → 送指令地址 → 取指令 → 指令装入 IR → 送操作数地址 → 取操作数 → 执行运算 → 写回结果 → 结束回到空闲。九个状态每个状态一拍控制字在状态内保持不变。这个节奏的关键在于“每个状态只做一件事”。比如送指令地址这个状态只做 PC 值送 MAR、PC 自增两件事取指令状态只拉高存储器读使能指令装入状态只拉高 IR 装载。如果把两件事塞进一个状态控制字会变成多路同时有效后仿波形上很难判断哪根信号先到出问题也不好定位。写回阶段要注意累加器和 SR 的更新顺序。运算结果在 ALU 输出端稳定后写回使能拉高的那一拍才把它打进累加器SR 同步记录标志。如果状态机在运算状态就拉高写回使能打进累加器的可能是上一拍的结果。这类问题在波形上表现为“结果慢一拍”但只要在状态转移表里把输出列对齐到状态就不会写错。3. CONTROL-SECTION 状态机落地9 个状态的划分与三段式 VHDL 写法状态机是这份大作业的核心交付物。教材给的是时序图和状态说明落到 VHDL 要解决三个问题状态怎么划分、用哪种写法、输出怎么和状态对齐。这三件事想清楚代码量其实不大一百多行就能写完想不清楚改到半夜也对不上波形。3.1 把教材时序图翻译成状态转移表拿到时序图别急着写 case先在纸上画一张状态转移表四列当前状态、转移条件、次状态、该状态下有效的控制字。这张表是后面写代码和写报告的共同依据也是排查问题的对照物。当前状态转移条件次状态有效控制字ST_IDLEstart1ST_INST_ADDR无ST_IDLEstart0ST_IDLE无ST_INST_ADDR无条件ST_INST_FETCHpc_inc、mar_loadST_INST_FETCH无条件ST_INST_LOADmem_readST_INST_LOAD无条件ST_OP_ADDRir_loadST_OP_ADDR无条件ST_OP_FETCHmar_loadST_OP_FETCH无条件ST_ALU_OPmem_readST_ALU_OP无条件ST_STOREacc_load、sr_load、alu_opST_STORE无条件ST_ENDacc_loadST_END无条件ST_IDLE无表里只有 ST_IDLE 有分支条件其余都是无条件推进。这种“一条直线”的状态机在教学处理器里很常见因为每个状态固定一拍不存在等待握手的问题。真正需要条件分支的是遇到跳转指令或者零标志判断的时候那时要在 ST_ALU_OP 之后加一个分支状态根据 z_flag 决定回 ST_IDLE 还是回到取指。写报告时把这一点补一句说明扩展方向比只交一条直线更能体现理解。状态命名建议直接用自己的前缀别照抄教材缩写。原因很实际后面仿真波形里要认状态名字短、有规律看波形时一眼能对上名字如果和教材完全一样反而容易和别人的代码混淆答辩时说不清哪部分是自己写的。3.2 两段式、三段式与状态编码Spartan3E 上的取舍状态机写法有三种常见形态。单进程写法把所有逻辑塞进一个时序进程代码短但次态和输出混在一起改一个输出要动整个进程排查时没法单独看次态。两段式把状态寄存和次态组合分开输出仍然放在时序进程里比单进程清楚。三段式再加一个独立的输出寄存进程输出只跟状态走毛刺被时钟沿滤掉。对 Spartan3E 这种规模的器件三段式的面积代价可以忽略——状态只有九个多几个触发器不影响资源占用但对后仿的友好程度差别很大。两段式里输出由组合逻辑产生后仿会因为布线延迟出现毛刺波形上看着像“控制字闪了一下”答辩时很难解释。三段式把输出打一拍毛刺被挡在寄存器前面后仿波形干净和功能仿真的差异只剩一个周期的固定延迟。状态编码上二进制编码在 Spartan3E 的查找表结构里占资源少九个状态四位就够独热码在触发器资源充足的器件上时序更好但这里只有九个状态优势不明显。综合工具默认会对状态机重新编码如果报告里要贴波形截图建议显式指定编码方式避免综合后的状态值和仿真里的对不上——这是后面排查章节要展开的一个坑。3.3 三段式状态机的 VHDL 实现与逐段说明下面这份代码对应上面的状态转移表三段结构分得很清楚。省略号部分按自己教材的信号名补全即可library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity par_control_unit is port ( clk : in std_logic; rst : in std_logic; -- 高有效异步复位 start : in std_logic; -- 启动一次指令周期 z_flag : in std_logic; -- ALU 零标志预留分支用 pc_inc : out std_logic; mar_load : out std_logic; mem_read : out std_logic; ir_load : out std_logic; acc_load : out std_logic; sr_load : out std_logic; alu_op : out std_logic_vector(1 downto 0); state_dbg : out std_logic_vector(3 downto 0) -- 送仿真观察也可接板载 LED ); end entity par_control_unit; architecture rtl of par_control_unit is -- 状态编码按声明顺序从 0 开始state_dbg 的值可以直接对照 type state_t is (ST_IDLE, ST_INST_ADDR, ST_INST_FETCH, ST_INST_LOAD, ST_OP_ADDR, ST_OP_FETCH, ST_ALU_OP, ST_STORE, ST_END); signal cur_state : state_t : ST_IDLE; signal nxt_state : state_t : ST_IDLE; begin -- 第 1 段状态寄存器异步复位、同步翻转 p_state : process (clk, rst) begin if rst 1 then cur_state ST_IDLE; elsif rising_edge(clk) then cur_state nxt_state; end if; end process; -- 第 2 段次态组合逻辑只决定下一拍去哪不碰输出 p_next : process (cur_state, start) begin nxt_state cur_state; -- 默认保持避免推断锁存器 case cur_state is when ST_IDLE if start 1 then nxt_state ST_INST_ADDR; end if; when ST_INST_ADDR nxt_state ST_INST_FETCH; when ST_INST_FETCH nxt_state ST_INST_LOAD; when ST_INST_LOAD nxt_state ST_OP_ADDR; when ST_OP_ADDR nxt_state ST_OP_FETCH; when ST_OP_FETCH nxt_state ST_ALU_OP; when ST_ALU_OP nxt_state ST_STORE; when ST_STORE nxt_state ST_END; when ST_END nxt_state ST_IDLE; when others nxt_state ST_IDLE; end case; end process; -- 第 3 段输出寄存控制字跟随次态输出的上升沿与状态跳转同拍 p_out : process (clk, rst) begin if rst 1 then pc_inc 0; mar_load 0; mem_read 0; ir_load 0; acc_load 0; sr_load 0; alu_op 00; elsif rising_edge(clk) then -- 先全部清零再按次态置位杜绝上一拍的控制字残留 pc_inc 0; mar_load 0; mem_read 0; ir_load 0; acc_load 0; sr_load 0; alu_op 00; case nxt_state is when ST_INST_ADDR pc_inc 1; mar_load 1; when ST_INST_FETCH mem_read 1; when ST_INST_LOAD ir_load 1; when ST_OP_ADDR mar_load 1; when ST_OP_FETCH mem_read 1; when ST_ALU_OP acc_load 1; sr_load 1; alu_op 01; when ST_STORE acc_load 1; when others null; end case; end if; end process; -- 状态导出state_tpos 给出状态序号四位足够表示 0~8 state_dbg std_logic_vector(to_unsigned(state_tpos(cur_state), state_dbglength)); end architecture rtl;三段各自的职责和参数含义需要说清楚。第一段只做一件事时钟沿把次态灌进当前态复位时强制回 ST_IDLE复位极性选高有效是因为开发板按键通常上拉、按下为低取反接进来更直观。第二段是纯组合逻辑进程敏感表里只放状态和输入开头那句默认赋值是关键少了它综合工具会推断出锁存器。第三段输出用的是次态而不是当前态这样控制字和状态跳转在同一拍生效波形上看到的状态值变化和控制字变化是对齐的报告里贴图更清楚。alu_op 的两位编码要和自己 ALU 的接口对上示例里 “01” 只是占位。实际改的时候把 ALU 支持的操作列一张小表编码填进去别在代码里散落魔数。state_dbg 这根线强烈建议留着它既能接仿真波形也能在板子上接 LED 或者逻辑分析仪跑实测的时候比盲猜快得多。4. Spartan3E XC3S500E 工程配置从建工程到综合报告的三个必看数字代码写完只是完成一半工程配置错了综合能过但下载到板子上不跑或者跑起来行为诡异。这一章按“建工程—写约束—看报告”的顺序走一遍每个环节给出该填的参数和该核对的项。4.1 新建工程XC3S500E-4FG320 这几个参数填在哪打开开发工具文件菜单里新建工程弹出对话框分三页。第一页填工程名和存放路径路径里不要有中文和空格这是老工具的通病路径带空格会在综合阶段报找不到文件的错误而且报错信息不会指向路径排查起来很浪费时间。第二页是器件参数这一页填错后面全白搭。按输入资料给的型号对应填写参数项应填值说明器件族Spartan3E与开发板芯片系列一致器件型号XC3S500E50 万门规模教学板常见选型封装FG320320 脚细间距球栅封装速度等级与板卡标称一致常见为 -4按板子丝印或原理图确认综合工具XST工具自带无需额外配置仿真器ISim与工程配套支持后仿第三页确认信息后完成创建。速度等级这一项容易被随手选选快了时序报告好看但实际跑不起来选慢了资源浪费。判断方法很简单看板卡原理图或者芯片丝印上面印的完整型号里带速度等级后缀照着填就行。工程建完新建源文件时注意类型选 VHDL Module 而不是 VHDL Test Bench测试平台单独建一个文件。输入资料里给的做法是先建测试平台再关联顺序反过来也行但关联文件这一步别漏否则仿真时找不到被测实体报错信息是“entity not found”看着像代码写错了其实是工程没配对。4.2 UCF 约束时钟、复位与几个观察引脚的分配约束文件决定信号落到哪个物理引脚也决定时序分析的准确性。最少要写三组时钟、复位、观察输出。下面这段只是语法示例引脚编号必须照自己板卡的原理图逐个改直接抄会烧不进配置或者信号乱跑# 时钟周期约束必须写否则时序报告里的频率是估算值 NET clk LOC C9 | IOSTANDARD LVCMOS33 | PERIOD 20 ns; # 复位按键输入注意上下拉配置与按键电路一致 NET rst LOC D18 | IOSTANDARD LVCMOS33; # 状态观察接四个 LED 或者排针跑实测时看状态跳转 NET state_dbg0 LOC F12 | IOSTANDARD LVCMOS33; NET state_dbg1 LOC F11 | IOSTANDARD LVCMOS33; NET state_dbg2 LOC E11 | IOSTANDARD LVCMOS33; NET state_dbg3 LOC E12 | IOSTANDARD LVCMOS33; # 控制字引出方便逻辑分析仪抓时序 NET mem_read LOC G13 | IOSTANDARD LVCMOS33;PERIOD 的数值要和测试平台里的时钟周期一致。仿真里给 20 ns约束里也写 20 ns两边不一致时后仿时序报告会给出错误的裕量看着通过实际超频。复位信号的上下拉配置要与板上按键电路匹配按键一端接地、另一端上拉的电路输入默认是高电平按下变低代码里如果按高有效复位就要取反这一处不一致会导致上电后状态机一直锁在复位里波形上表现为所有输出恒为零。4.3 综合与实现报告里必须先看的三个数字综合跑完会生成一堆报告不用全看三个数字决定这份设计能不能交第一个是查找表占用。Spartan3E 的 XC3S500E 有四千多个 slice、九千多个触发器九个状态的状态机加几个寄存器占用应该很小。如果报告里 LUT 数量异常高通常是次态进程里写了复杂的条件判断或者输出进程里出现了没必要的比较逻辑回头精简 case 分支即可。第二个是最大频率。时序报告里会给出最小周期和对应的最大频率这个数字要明显高于约束里写的时钟频率留出余量。如果刚好卡在临界值说明组合路径太长重点查 ALU 那条路径和状态译码逻辑必要时把输出再打一拍。第三个是未约束路径数量。报告里会列出没有时序约束覆盖的路径数量不为零说明 UCF 漏了信号最常见的是观察用的输出引脚没写约束。这些路径不参与时序分析实际跑起来可能是稳定的也可能在温度变化时出问题交作业前补全约束比事后解释省事。注意综合警告里的“latch inferred”和“signal has no load”两类必须清零。前者意味着组合进程有分支没覆盖后者意味着有信号写了没用上通常是连线和端口名拼错。5. 后仿真对不上波形的 5 个排查点从全 X 到状态跑飞后仿真是这份作业最容易翻车的环节。功能仿真跑得通加了布局布线延迟之后就各种异常波形上看不出所以然。下面五条是按发生频率排的每条按现象、原因、解决三段写遇到问题时从上往下对照。5.1 波形整段是 U 或 X复位与初值没覆盖到现象打开波形窗口状态信号和大部分输出从头到尾都是 U 或者 X偶尔有几段跳变但看不出规律。原因有两个层面。一是测试平台里的信号声明时没给初值VHDL 里 std_logic 默认是 U时钟信号如果是 U取反之后还是 U时钟永远不起振。二是复位进程只复位了状态寄存器输出寄存器在复位分支里漏写导致复位释放后输出仍然是 U直到某个状态把它置位。解决测试平台里所有输入信号声明时给初值时钟信号写: 0复位进程里把每一个输出都列进复位分支哪怕某个输出在初始状态就是零也要显式赋值。复位保持时间给足五个时钟周期让所有内部寄存器落定再释放。改完之后重新跑波形上应该能看到复位期间所有信号为确定的低电平。5.2 前仿对后仿错delta 延迟与组合输出毛刺现象功能仿真波形干净每个状态的控制字方方正正后仿真里控制字出现窄脉冲偶尔还有一两纳秒的尖峰累加器采到的值和预期差一拍。原因功能仿真不建模布线延迟组合逻辑的输出在输入变化的同一时刻跳变看起来完美。后仿真里每根线都有延迟组合输出的多个输入不同时到达输出就会出现毛刺。如果这个毛刺正好落在下游寄存器的时钟沿附近就会被采进去。解决把输出改成寄存器输出也就是三段式里的第三段毛刺被挡在寄存器前面下游看到的永远是稳定的整拍信号。测试平台的采样点也调整一下用rising_edge(clk)触发而不是用wait for卡时间点避免采在跳变瞬间。改完后再看后仿波形控制字的边沿应该干净最多整体延迟一个周期。5.3 状态跑飞、编码对不上综合器重编码现象后仿波形里状态信号的值和代码里的枚举顺序对不上比如代码里 ST_INST_ADDR 是 1波形上却是 5或者状态在几个非法值之间跳。原因综合工具默认对状态机做重新编码选它认为面积或速度更优的方案枚举顺序和实际编码不再一一对应。如果测试平台或者观察逻辑里硬编码了状态值做判断就会全部错位。另一种情况是状态机综合时被优化掉了部分状态说明有状态不可达通常是转移条件写错。解决两种情况都先在报告里确认。看综合报告的 state machine 部分会列出实际使用的编码如果报告和预期差太多在信号上显式指定编码属性强制按二进制或者顺序码实现。测试平台里判断状态时用导出的 state_dbg 数值比对别用枚举名硬编码或者干脆在测试平台里重新声明一份相同的枚举类型做类型转换。5.4 综合报 latch 与多驱动次态进程的两个默认值现象综合警告里出现 latch inferred或者报某个信号有多个驱动源实现阶段直接失败。原因latch 来自组合进程if 或者 case 没有覆盖所有分支工具为了保持输出不变就推断出锁存器。多驱动来自同一个信号在两个进程里都被赋值最常见的是把输出既写在次态进程里又写在输出进程里。解决组合进程开头先给默认赋值case 结尾加when others两条一起用基本能消灭 latch。多驱动要看综合报告的驱动源列表把重复赋值的那个删掉输出的赋值只留在一个进程里。修完重新综合这两类警告必须为零再往下走。5.5 仿真卡住不动wait 语句与时钟源现象仿真跑了很久进度条不动或者波形上时间轴停住所有信号保持初始值。原因测试平台里某个进程没有 wait 语句或者 wait 的条件永远不会满足。比如等待一个由被测逻辑产生的信号而被测逻辑自己也需要时钟才能动两者互相等待就死锁了。另一种是时钟进程写成了clk not clk after 10 ns;放在敏感表进程里形成零延迟振荡仿真器直接挂掉。解决时钟产生语句放在进程外用并发赋值进程里只放激励。每个进程至少有一个 wait 或者敏感表纯组合的激励进程结尾加wait;让它挂起而不是无限循环。仿真时间设一个上限比如跑够二十个时钟周期就自动结束避免跑飞之后一直占着机器。6. 把波形变成回归给状态机加自检断言的三个动作波形截图是给老师看的断言是给自己看的。手动比对波形这件事做第一遍还行改一次代码重新跑就要重看一遍改到第五版基本靠运气。我后来的做法是把关键判断写成断言让仿真器自己报错。第一个动作在测试平台里加状态合法性检查。用 state_dbg 的数值判断超出 0 到 8 就报失败-- 状态合法性 关键控制字自检跑仿真时自动报错 chk : process (clk) begin if rising_edge(clk) then if rst 0 and state_dbg 1000 then assert false report 状态机进入非法编码检查次态进程的 when others 分支 severity failure; end if; -- ST_ALU_OP 的序号是 6这一拍 acc_load 必须为高 if state_dbg 0110 and acc_load / 1 then assert false report ALU_OP 状态下 acc_load 未拉高控制字表与状态机不一致 severity error; end if; end if; end process;第二个动作把仿真日志沉淀下来。跑仿真的命令行输出重定向到文件用 grep 抓 ERROR 和 FATAL出现就返回非零退出码#!/usr/bin/env bash # 一键回归编译 - 跑仿真 - 检查日志任一环节出错就中断 set -e fuse -incremental -prj par_control_unit_tb.prj -o tb.exe par_control_unit_tb ./tb.exe -tclbatch run_all.tcl sim.log 21 if grep -qE ERROR|FATAL|Failure sim.log; then echo 回归失败详见 sim.log exit 1 fi echo 回归通过状态序列与控制字自检均无异常第三个动作保存波形配置文件。把关心的信号加进波形窗口存成配置文件下次跑仿真时用命令行加载不用每次重新拖信号。这一步看着琐碎但改代码之后重新打开波形只要几秒比重新找信号快得多。这三件事做完一份作业的验证闭环才算合上。材料里给的后仿截图是结果断言和回归脚本是可复现的过程报告里把两者都放上去说明的不只是“跑通了”而是“我怎么知道它跑通了”。血泪经验是有一年赶截止时间改完状态机只看了前仿就交后仿波形没重跑结果答辩现场打开波形发现状态和代码对不上当场翻车。从那以后我每次交这种带状态机的作业都强制走一遍自检断言加日志 grep 的流程跑完看输出里那行“回归通过”才敢合上电脑。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑