基于Verilog与Quartus II的模型机设计实战:从模块构建到Cyclone II FPGA部署

news2026/3/14 3:46:14
1. 从零开始为什么我们要亲手设计一台模型机如果你是一名电子工程或计算机相关专业的学生或者是对计算机底层原理充满好奇的爱好者你可能不止一次地想过我面前的这台电脑它到底是怎么工作的那些复杂的软件指令最终是如何变成晶体管里流动的电流从而完成计算的呢这个问题听起来宏大而复杂但有一个绝佳的实践路径可以让你亲手触摸到计算的本质——那就是用硬件描述语言比如Verilog和FPGA开发工具比如Quartus II从零开始设计一台简易的CPU也就是我们常说的“模型机”。这听起来可能有点吓人感觉是芯片工程师才做的事。但我想告诉你这个过程其实非常有趣而且门槛并没有想象中那么高。我自己当年做这个项目时也是从一个“小白”开始的对着Verilog的语法发愁在Quartus里到处找编译按钮。但当你真正把一个个独立的模块像搭积木一样连接起来最后看到它能在真实的FPGA开发板上按照你的指令正确运行时那种成就感是无与伦比的。这不仅仅是完成一次作业或实验而是真正理解了从软件指令到硬件动作的完整链条。我们这个实战项目的目标很明确基于给定的指令集使用Verilog HDL在Quartus II环境中设计并实现一个简易CPU的所有核心模块最终在Altera现在是Intel的Cyclone II系列FPGA开发板上跑起来完成功能验证。整个过程会涵盖从理论设计、代码编写、功能仿真到硬件部署的全流程。你会亲手构建指令译码器、算术逻辑单元、寄存器组、程序计数器、控制器等核心部件。最终你的模型机将能够执行一段简单的机器代码程序比如完成几个数的加减运算或者控制开发板上的LED灯闪烁。别担心我们不会涉及过于复杂的流水线、缓存或者超标量设计。我们设计的是一台精简的、采用硬连线控制方式的单周期模型机。它结构清晰非常适合作为理解计算机体系结构的“第一块敲门砖”。通过这个项目你不仅能巩固数字电路和计算机组成原理的知识更能获得宝贵的现代数字系统设计实战经验掌握业界主流的EDA工具链。好了铺垫了这么多让我们卷起袖子打开Quartus II开始这场从代码到芯片的奇妙旅程吧。2. 蓝图绘制理解我们的模型机架构在动手写代码之前我们必须先想清楚我们要造一个什么样的“机器”。就像盖房子需要施工图纸一样设计CPU也需要一个清晰的顶层架构图。我们的模型机基于一个经典的、结构简单的设计它包含了CPU最核心的几个部件。我画了一个简化的框图你可以先有个直观印象------------ ------- ------------- | 程序计数器 |---| 指令存储器 |---| 指令寄存器 | | (PC) | | (ROM) | | (IR) | ------------ ------- ------------- | v --------------- | 指令译码器 | | (ins_decode) | --------------- | -------------------------------------------------------- | | | v v v ---------------- --------------------- ----------------- | 控制器 | | 寄存器组 | | 算术逻辑单元 | | (con_signal) | | (reg_group) | | (au) | ---------------- --------------------- ----------------- | | | | ------------------------------------ | | | | | | v v v v v ---------------- --------------------- ----------------- | 状态机 | | 多路选择器 | | 程序状态字 | | (sm) | | (mux2_1, mux3_1) | | (psw) | ---------------- --------------------- -----------------这个架构是如何协同工作的呢让我用一个“取指-译码-执行”的循环来给你讲讲。首先程序计数器PC就像一个指针它里面存放着下一条要执行的指令在内存中的地址。在每个时钟周期开始时PC将这个地址送给指令存储器ROMROM就像一本写满了命令的书根据地址把对应的指令代码读出来。这条指令被送到指令寄存器IR中暂时保存起来。接下来是关键的一步指令译码器ins_decode开始工作。它“解读”IR中的指令代码比如“这是一条加法指令”或者“这是一条数据移动指令”。译码器会产生一系列独热码One-Hot信号每根信号线代表一种具体的操作如mova,add,jmp等。这些信号就是告诉其他部件“现在要干什么”的命令。这些命令信号绝大部分都送到了控制器con_signal。控制器是整个模型机的大脑它根据当前要执行的操作生成控制整个数据通路的所有微操作信号。比如它要决定现在该从哪个寄存器读数据要不要写回寄存器算术单元做什么运算要不要从外部输入数据这些控制信号像一张精细的调度表确保数据在正确的时刻流向正确的地方。数据通路的核心是寄存器组reg_group和算术逻辑单元au。寄存器组好比是CPU内部的高速便签本临时存放着参与计算的数据。算术逻辑单元则是真正的“算盘”负责完成加、减等运算。数据从哪里来算完去哪里则由多路选择器mux来负责路径选择它就像一个铁路扳道工。最后状态机sm和程序状态字psw负责管理执行状态和记录运算结果的特征比如减法是否产生了借位。这样一条指令执行完毕PC更新下一个周期开始周而复始。理解了这个数据流和控制流我们写代码时就能做到心中有数知道每个模块应该放在整个系统的哪个位置承担什么职责。3. 模块化构建用Verilog打造CPU核心部件有了清晰的架构图我们就可以像组装乐高一样一个模块一个模块地用Verilog来实现它们了。这里我结合原始报告里的代码给你详细拆解几个最关键模块的设计思路和代码细节并分享一些我调试时踩过的坑。3.1 指令译码器CPU的“翻译官”指令译码器ins_decode的任务非常单纯输入是指令码ir输出是一系列控制信号。在我们的设计中采用了独热码编码也就是说对于每一种指令只有对应的一位输出是1其他都是0。这样做的好处是控制逻辑简单直接。module ins_decode( input en, // 使能信号低电平有效时清零所有输出 input [3:0] ir, // 4位指令操作码 output mova, movb, movc, movd, add, sub, jmp, jg, in1, out1, movi, halt // 12条指令控制线 ); reg [11:0] ALLOUT; // 用一个12位寄存器暂存所有输出 always (en, ir) begin if(en 0) begin ALLOUT 12b0000_0000_0000; // 使能无效全部清零 end else begin // 根据4位ir将ALLOUT对应的位置1 case(ir) 4b1111: ALLOUT 12b0000_0000_0001; // halt 4b1110: ALLOUT 12b0000_0000_0010; // movi 4b1101: ALLOUT 12b0000_0000_0100; // out1 // ... 其他指令代码映射 4b0100: ALLOUT 12b1000_0000_0000; // mova default: ALLOUT 12b0000_0000_0000; // 未定义指令输出全0 endcase end end // 将寄存器的每一位分配给具体的输出信号 assign {mova, movb, movc, movd, add, sub, jmp, jg, in1, out1, movi, halt} ALLOUT; endmodule这里有个新手容易忽略的细节always块的敏感列表。我们写的是always (en, ir)这是一个电平敏感的组合逻辑。意味着只要en或ir的值发生变化块内的语句就会重新执行。在早期的Verilog标准中这种写法很常见。但更推荐使用always (*)或者always_combSystemVerilog让综合工具自动推断敏感列表这样可以避免因列表遗漏导致的仿真与综合结果不一致的诡异问题。我一开始就曾因为敏感列表没写全导致仿真时输出不变排查了好久。3.2 算术逻辑单元核心计算引擎算术逻辑单元au是执行实际运算的地方。我们的模型机比较简单主要实现加法和减法。注意在数字电路中减法通常通过“加补码”来实现。module au( input au_en, // 运算使能 input [3:0] ac, // 运算控制码来自指令的高位 input [7:0] a, // 操作数A input [7:0] b, // 操作数B output reg [7:0] t, // 运算结果 output reg gf // 标志位例如减法借位/加法进位 ); always (*) begin if (au_en 1b0) begin t 8hZZ; // 高阻态不驱动总线 gf 1b0; end else begin gf 1b0; // 默认清零标志位 case(ac) 4b1000: begin // 加法指令 t a b; // 简单模型这里没有处理进位输出 end 4b1001: begin // 减法指令: t b - a t b (~a) 8b0000_0001; // 取反加1得到a的补码然后与b相加 // 判断是否借位如果ba则结果为正或零我们定义此时gf1 gf (t[7] 0) ? 1b1 : 1b0; // 检查结果最高位符号位 end // mova, movc等数据传输指令只是把a传到t 4b0100, 4b0101, 4b1101: begin t a; end default: begin t 8hZZ; end endcase end end endmodule关于标志位gf的设计这里是初学者常感到困惑的地方。在原始代码中gf只在减法运算且结果为正即t[7]0因为我们假设是带符号数时置1。这其实是一个简化版的“大于等于”标志。在实际的CPU如x86中会有更丰富的标志位寄存器PSW包含零标志ZF、符号标志SF、进位标志CF、溢出标志OF等。我们的模型机做了简化只保留了一个gf用于条件跳转指令jg的判断。你需要根据自己指令集的需求来定义标志位的含义。3.3 寄存器组数据的临时仓库寄存器组reg_group是CPU内部的高速存储单元。我们的设计中有4个8位通用寄存器r0, r1, r2, r3。它需要支持同时读取两个寄存器的值用于运算并在时钟沿控制下写入一个寄存器。module reg_group ( input we, // 写使能 input clk, // 时钟 input [1:0] sr, // 源寄存器选择读端口S input [1:0] dr, // 目的寄存器选择读端口D / 写地址 input [7:0] i, // 写入的数据 output reg [7:0] s, // 读出的S数据 output reg [7:0] d // 读出的D数据 ); // 内部声明4个8位寄存器并赋予初始值可选便于仿真观察 reg [7:0] r0 8h01; reg [7:0] r1 8h01; reg [7:0] r2 8h01; reg [7:0] r3 8h01; // 组合逻辑读过程根据sr和dr随时输出对应寄存器的值 always (*) begin case (sr) 2b00: s r0; 2b01: s r1; 2b10: s r2; 2b11: s r3; default: s 8b0; endcase case (dr) 2b00: d r0; 2b01: d r1; 2b10: d r2; 2b11: d r3; default: d 8b0; endcase end // 时序逻辑写过程在时钟下降沿如果写使能有效将数据i写入dr指定的寄存器 always (negedge clk) begin if (we) begin case (dr) 2b00: r0 i; 2b01: r1 i; 2b10: r2 i; 2b11: r3 i; endcase end end endmodule这里体现了数字设计中的一个重要模式同步写异步读。写操作always (negedge clk)是时序逻辑只在时钟下降沿发生这保证了数据的稳定性和可靠性。而读操作always (*)是组合逻辑只要sr或dr变化输出s和d立即更新这保证了数据读取的实时性。另外注意我们使用了非阻塞赋值这是时序逻辑的标准写法能避免仿真中出现竞争冒险。我强烈建议你遵循这个规则在always (posedge clk)或always (negedge clk)块中一律使用非阻塞赋值在组合逻辑always (*)块中一律使用阻塞赋值。这能帮你避开很多棘手的仿真bug。3.4 控制器模型机的大脑与神经中枢控制器con_signal是整个设计中最体现“设计”智慧的部分。它接收来自译码器的指令信号和来自ALU的标志位等状态信号然后产生协调所有其他模块工作的控制信号。这些信号决定了数据流的方向和操作的发生时机。module con_signal( // 输入指令信号 状态信号 input mova, movb, movc, movd, add, sub, jmp, jg, g, in1, out1, movi, halt, sm, input [7:0] ir, // 完整的指令寄存器内容包含寄存器地址等信息 // 输出一系列控制信号 output reg sm_en, ir_ld, ram_re, ram_wr, pc_in, pc_ld, reg_we, au_en, gf_en, in_en, out_en, mux_s, output reg [1:0] reg_sr, reg_dr, s, output reg [3:0] au_ac ); always (*) begin // 组合逻辑根据输入即时产生输出 // 状态机使能只要不是停机指令就允许状态机翻转 sm_en ~halt; // 指令寄存器加载当状态机sm为0时取指周期加载新指令 ir_ld ~sm; // 存储器读使能MOVC、MOVI指令需要读内存或者取指周期也需要读指令 ram_re movc | movi | (~sm); // 存储器写使能MOVB指令需要写内存 ram_wr movb; // 标志位使能减法指令需要更新标志位 gf_en sub; // 程序计数器加载和来源选择 pc_ld jmp | (jg g); // 跳转指令生效时加载PC pc_in movi | (~sm); // PC来源MOVI指令地址或顺序下一条指令地址 // 寄存器组写使能哪些指令需要写回寄存器 reg_we in1 | movi | movd | mova | movc | sub | add; // 寄存器选择直接从指令字中提取寄存器编号 reg_sr ir[1:0]; // 源寄存器通常指指令的低2位 reg_dr ir[3:2]; // 目的寄存器通常指指令的[3:2]位 // 多路选择器控制信号s选择送入ALU的B操作数来源 if (movb) s 2b10; // 来自寄存器d else if (movc) s 2b01; // 来自存储器数据 else s 2b00; // 默认来自寄存器s // 输入输出使能 in_en in1; out_en out1; // 算术单元使能和操作选择 au_en movb | mova | add | out1 | sub; // 哪些指令需要ALU工作 au_ac ir[7:4]; // 运算类型由指令高4位决定 // 另一个多路选择器控制可能选择ALU结果输出到总线 mux_s mova | movc | movi | in1 | add | sub; end endmodule设计控制器时你需要仔细推敲每一条指令的“数据通路”和“控制序列”。最好的方法是画一张“指令执行流程图”和“控制信号真值表”。比如对于ADD R1, R2这条指令假设含义是将R1和R2相加结果存回R1你需要思考在哪个时钟周期需要将R1和R2的值读出来ALU做什么操作结果写回到哪个寄存器控制器里的每一个assign语句都对应着数据通路上的一处开关。我建议你拿一张纸对照着上面的代码为mova、add、jmp这几条指令一步步画出数据流动的路径你会对控制器的理解深刻很多。这个过程可能会反复修改但这是设计的精髓所在。4. 集成、仿真与调试让模型机“活”起来当所有模块的代码都编写并单独测试过后最激动人心的时刻到了将它们集成在一起形成一个完整的顶层模块Top Module并进行仿真测试看看我们的CPU能不能正确工作。4.1 顶层模块设计与连接在Quartus II中我们需要创建一个顶层Verilog文件例如top_module.v在这个文件中实例化所有子模块并用wire型信号线将它们按照架构图连接起来。module small_computer( input clk, // 时钟信号 input rst_n, // 复位信号低电平有效 input [7:0] data_in, // 外部数据输入 output [7:0] data_out // 数据输出 ); // 定义内部连接的所有电线wire wire [7:0] pc_addr, rom_data, ir_out, reg_s_data, reg_d_data, alu_result, mux_to_alu_b, mux_to_bus; wire [3:0] opcode; wire [1:0] reg_sel_s, reg_sel_d; wire ctrl_sm_en, ctrl_ir_ld, ctrl_reg_we, ctrl_au_en, ctrl_gf_en, ctrl_pc_ld, ctrl_pc_in; wire ctrl_ram_re, ctrl_ram_wr, ctrl_in_en, ctrl_out_en; wire [1:0] ctrl_mux_s; wire [3:0] ctrl_au_ac; wire flag_g; wire state_sm; // 将指令字拆分为操作码和寄存器地址 assign opcode ir_out[7:4]; assign reg_sel_s ir_out[1:0]; assign reg_sel_d ir_out[3:2]; // 实例化程序计数器PC pc u_pc ( .in_pc(ctrl_pc_in), .clk(clk), .ld_pc(ctrl_pc_ld), .a(mux_to_bus), // PC可能从总线加载跳转地址 .c(pc_addr) ); // 实例化指令存储器ROM用Quartus的IP核或初始化寄存器实现 // 这里简化为一个同步ROM reg [7:0] rom [0:255]; initial $readmemh(program.mif, rom); // 从MIF文件加载程序 assign rom_data rom[pc_addr]; // 实例化指令寄存器IR ir u_ir ( .clk(clk), .ld_ir(ctrl_ir_ld), .a(rom_data), .x(ir_out) ); // 实例化指令译码器 ins_decode u_decoder ( .en(1b1), // 假设一直使能 .ir(opcode), .mova(mova_wire), .movb(movb_wire), // ... 其他输出信号连接到对应的wire .halt(halt_wire) ); // 实例化控制器 con_signal u_controller ( .mova(mova_wire), .movb(movb_wire), // ... 连接所有译码器输出信号 .halt(halt_wire), .ir(ir_out), .sm(state_sm), .g(flag_g), .sm_en(ctrl_sm_en), .ir_ld(ctrl_ir_ld), .ram_re(ctrl_ram_re), // ... 连接所有产生的控制信号到内部wire .mux_s(ctrl_mux_s) ); // 实例化寄存器组 reg_group u_regs ( .we(ctrl_reg_we), .clk(clk), .sr(reg_sel_s), .dr(reg_sel_d), .i(mux_to_bus), // 写入数据来自总线 .s(reg_s_data), .d(reg_d_data) ); // 实例化ALU au u_alu ( .au_en(ctrl_au_en), .ac(ctrl_au_ac), .a(reg_s_data), // A操作数通常来自寄存器s .b(mux_to_alu_b), // B操作数来源由多路选择器决定 .t(alu_result), .gf(flag_g_in) // 输出的标志位 ); // 实例化多路选择器选择ALU的B操作数 mux3_1 u_mux_alu_b ( .a(reg_s_data), .b(rom_data), // 来自存储器的立即数或数据 .c(reg_d_data), .s(ctrl_mux_s), .y(mux_to_alu_b) ); // 实例化状态机 sm u_state_machine ( .sm_en(ctrl_sm_en), .clk(clk), .sm(state_sm) ); // 实例化PSW标志位寄存器 psw u_psw ( .g(flag_g_in), .clk(clk), .g_en(ctrl_gf_en), .gf(flag_g) ); // 其他模块和数据选择器... // 输出逻辑 assign data_out (ctrl_out_en) ? reg_s_data : 8hZZ; endmodule顶层连接是最考验细心和耐心的一步。信号名非常多一个接错就会导致整个系统无法工作。我的经验是1. 画图用工具甚至手画画出顶层的连接图标清楚每个端口的来源和去向。2. 分模块验证每连接好一个模块就做一次功能仿真确保这个模块的输入输出逻辑符合预期。3. 命名规范给wire型信号起一个有意义的名字比如ctrl_开头的表示控制信号data_开头的表示数据信号这样在查看波形图时一目了然。4.2 编写测试程序与功能仿真CPU造好了得给它“灌输”思想也就是编写机器码程序。我们通过一个Memory Initialization File (.mif) 文件来初始化指令ROM。假设我们的指令集很简单比如0001: MOVI R0, 5 (将立即数5送入R0)0010: MOVI R1, 31000: ADD R0, R1 (R0 R0 R1)1111: HALT那么对应的机器码和.mif文件可能长这样假设指令格式为高4位操作码低4位寄存器或数据-- program.mif DEPTH 256; -- ROM深度 WIDTH 8; -- 数据宽度 ADDRESS_RADIX HEX; DATA_RADIX HEX; CONTENT BEGIN 00 : 15; -- MOVI R0, 5 (操作码0001寄存器00数据0101) 01 : 23; -- MOVI R1, 3 02 : 80; -- ADD R0, R1 (操作码1000源寄存器01目的寄存器00) 03 : F0; -- HALT (操作码1111) [04..FF] : 00; -- 其余地址填充0 END;在Quartus II中我们使用ModelSim-Altera进行仿真。你需要编写一个简单的测试平台Testbenchtimescale 1ns/1ns module tb_small_computer(); reg clk; reg rst_n; wire [7:0] data_out; // 实例化被测模型机 small_computer uut ( .clk(clk), .rst_n(rst_n), .data_in(8h00), // 暂时没有输入 .data_out(data_out) ); // 生成时钟信号周期20ns50MHz initial clk 0; always #10 clk ~clk; // 测试过程 initial begin rst_n 0; // 先复位 #100; rst_n 1; // 释放复位 #1000; // 运行足够多的时钟周期 // 在这里可以添加一些$display语句打印关键信号值进行判断 if (data_out 8h08) // 期待R0R1的结果8 $display(Test PASSED! Result is 8.); else $display(Test FAILED! Result is %h, data_out); $stop; end endmodule在ModelSim中运行仿真观察波形。你需要重点关注时钟和复位确保时钟在正常翻转复位信号有效。PC值是否在每个非跳转周期自动加1遇到跳转指令是否正确跳转IR值是否在正确的时刻加载了ROM中的指令码控制信号针对不同的指令reg_we,au_en,ram_re等信号是否按预期变化寄存器值观察reg_group内部的r0, r1等数据是否被正确写入和读出ALU结果计算是否正确标志位是否设置正确最终输出data_out是否在out1指令执行时输出了正确的结果仿真调试是耗时最长的阶段也是最能学到东西的阶段。你大概率会遇到结果不对的情况。别慌这是常态。我的排查步骤通常是1. 定位异常点看波形第一个出现不符合预期的信号是哪个2. 向前追溯这个错误信号的输入是谁它的输入又是否正确3. 检查代码回到对应模块的Verilog代码检查逻辑是否与设计意图一致。4. 简化测试如果整体测试太复杂就为出错的模块单独写一个更简单的Testbench进行测试。记住波形仿真工具是你的好朋友熟练使用它的放大、缩小、添加信号、设置断点等功能能极大提高调试效率。5. 硬件部署在Cyclone II FPGA上点亮你的CPU当功能仿真通过意味着我们的设计在逻辑上是正确的。接下来就要把它放到真实的FPGA芯片里运行这是从虚拟到现实的关键一步。我们以经典的Cyclone II EP2C5T144C8芯片为例。5.1 引脚分配与时序约束在Quartus II中完成综合Analysis Synthesis后我们需要进行引脚分配Pin Planner。这步是把设计中的输入输出端口映射到FPGA开发板实际物理引脚的过程。时钟clk连接到开发板的有源晶振输出脚比如PIN_2350MHz。复位rst_n连接到一个按键开关比如PIN_44并设置上拉电阻。数据输入data_in[7:0]可以连接到拨码开关比如PIN_30, PIN_31, ... PIN_37。数据输出data_out[7:0]连接到LED灯比如PIN_101到PIN_108。这样我们就可以直观地看到运算结果。其他调试信号你也可以把一些内部关键信号如pc_addr[7:0]分配到额外的LED或数码管上方便调试。引脚分配完成后必须进行时序约束。这是告诉时序分析工具你的设计需要跑在多快的时钟下。对于Cyclone II我们可以通过TimeQuest Timing Analyzer或者简单的设置来操作。一个最基本的约束是创建时钟create_clock -name clk -period 20.000 [get_ports {clk}]这表示我们定义了一个名为clk的时钟周期为20ns对应50MHz作用于端口clk上。Quartus会努力使设计在这个频率下稳定工作。如果时序分析报告出现“时序要求未满足”Timing Requirements Not Met你可能需要降低时钟频率比如改为-period 40.000或者优化代码逻辑减少组合逻辑级数。5.2 编译、下载与上板验证点击Quartus的“Start Compilation”按钮进行全流程编译包括综合、布局布线、时序分析、生成编程文件。这个过程可能会遇到很多警告Warning对于初学者需要重点关注严重警告Critical Warning比如时钟未约束、引脚未分配等。至于一般的警告比如“推断出了锁存器”Inferred latch你需要回到代码检查是否在if或case语句中缺少了else或default分支导致在特定条件下输出保持原值这通常不是我们想要的行为。编译成功后会生成一个.sof文件SRAM Object File。用USB-Blaster或其他下载器连接开发板在Quartus的Programmer工具中选择这个.sof文件点击“Start”将设计下载到FPGA的SRAM中。这种下载方式掉电后程序会丢失。上板验证是最令人兴奋也最紧张的环节。按照你的测试计划来操作给开发板上电。通过拨码开关设置一个输入值比如8‘h01。观察LED灯的显示。它应该对应你程序运行的结果比如原始报告中提到的输入01h输出EDhLED显示11101101。尝试输入不同的值或者修改.mif文件中的程序测试其他指令序列。如果结果不对不要气馁。硬件调试比软件仿真更复杂。你可以检查引脚分配确认LED和开关的引脚号是否与开发板原理图一致。检查复位和时钟用示波器测量时钟引脚是否有波形复位按键是否正常工作通常是按下低电平松开高电平。简化设计先尝试一个最简单的测试比如只让一个LED常亮确保下载流程和基本硬件是好的。使用SignalTap II这是Quartus内置的逻辑分析仪可以像仿真看波形一样在FPGA运行时抓取内部信号的实时变化是硬件调试的神器。你需要将想要观察的信号添加到SignalTap文件中重新编译下载然后触发抓取。5.3 性能分析与优化思考最后我们来看看这个模型机的“体检报告”。在Quartus的Compilation Report里可以看到资源占用情况。对于Cyclone II EP2C5T144C8这款芯片逻辑单元LEs使用量我们的简易模型机可能只用了百分之几就像原始报告说的4%。这说明FPGA的资源绰绰有余。存储器比特Memory bits我们用了很小的ROM来存程序占用也很少。时序性能看“Timing Analyzer”报告中的“Fmax”最大时钟频率。我们的设计可能能达到50MHz以上。但注意这是理想情况。实际板级运行时由于布线延迟等因素最高稳定频率会低一些。关于优化我们可以思考几个方向关键路径优化如果时序报告指出某条路径延迟太大成为关键路径可以尝试对该路径的逻辑进行流水线切割或者用寄存器打一拍提高系统最高工作频率。资源复用我们的ALU只做了加减法如果指令集扩展需要乘法器可以考虑用时序逻辑多个周期完成而不是用巨大的组合逻辑乘法器以节省面积。状态机优化当前是简单的两相状态机sm。对于更复杂的指令可以设计更精细的多周期状态机每个状态完成一个微操作从而用更简单的数据通路支持更丰富的指令。完成这个从模块构建到FPGA部署的全流程你收获的不仅仅是一个能跑的模型机。你真正走通了一条数字系统设计的标准流程需求分析、架构设计、模块编码、功能仿真、综合实现、时序约束、板级调试。这套方法论对于你今后设计更复杂的数字系统比如图像处理管道、通信协议处理器等都是完全通用的。当你看到自己编写的代码最终在真实的芯片上驱动LED闪烁出预期的图案时那种亲手创造了一个数字世界的满足感是任何理论考试都无法给予的。这就是硬件设计的魅力所在。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2409816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…