第五课:模块化设计与层次结构

🎓 第五课:模块化设计与层次结构

上节回顾:我们学会了用状态机控制复杂时序。这节课学习模块化设计——像搭乐高积木一样组装电路!


🧱 5.1 为什么要模块化?

🎯 生活比喻:组装电脑 vs 焊接电路板

不模块化的设计(全焊死在一块板上):

❌ CPU坏了 → 整块板报废
❌ 想升级内存 → 无法实现
❌ 电路复杂 → 找不到故障点

模块化设计(用标准接口连接):

✅ 坏了哪个换哪个
✅ 可以随时升级
✅ 每个模块独立测试

📊 硬件模块化的优势

优势说明类比
可复用一次设计,多处使用像函数库
易调试单独测试每个模块像单元测试
协同开发多人并行工作像Git分支
层次清晰顶层只看接口像API文档

🔌 5.2 模块实例化基础

📚 基本语法结构

模块名 实例名(
    .端口名(连接的信号),
    .端口名(连接的信号)
);

比喻:像"插插座"

  • 模块 = 电器(有固定插头规格)
  • 实例化 = 把电器插到插座上
  • 端口连接 = 电线连接

🎯 例题9:用半加器组装全加器

📐 设计思路

全加器功能:A + B + Cin = {Cout, Sum}

半加器1: A + B = {C1, S1}
半加器2: S1 + Cin = {C2, Sum}
或门:    Cout = C1 | C2

电路示意图

A ──┐
    ├→[半加器1]──S1──┐
B ──┘      ↓        ├→[半加器2]─→ Sum
          C1        │      ↓
           │   Cin─┘      C2
           └────┬──────────┘
                ↓
               [或门]─→ Cout

📝 完整代码实现

底层模块:半加器

module half_adder(
    input wire a,
    input wire b,
    output wire sum,
    output wire cout
);
    assign sum = a ^ b;   // 异或
    assign cout = a & b;  // 与
endmodule

顶层模块:全加器

module full_adder(
    input wire a,       // 🔢 被加数
    input wire b,       // 🔢 加数
    input wire cin,     // ⬆️ 低位进位输入
    output wire sum,    // 📝 和
    output wire cout    // ⬆️ 进位输出
);

    // 🔌 内部连线(像电路板上的走线)
    wire s1;     // 半加器1的和
    wire c1;     // 半加器1的进位
    wire c2;     // 半加器2的进位
    
    // 🧩 实例化第1个半加器
    half_adder ha1(
        .a(a),        // 端口名 .a 连接到信号 a
        .b(b),        // 端口名 .b 连接到信号 b
        .sum(s1),     // 输出连到内部线 s1
        .cout(c1)     // 输出连到内部线 c1
    );
    
    // 🧩 实例化第2个半加器
    half_adder ha2(
        .a(s1),       // 用第1个的输出作输入
        .b(cin),      // 加上低位进位
        .sum(sum),    // 最终的和
        .cout(c2)     // 第2个进位
    );
    
    // 🔌 或门:合并两个进位
    assign cout = c1 | c2;
    
endmodule

🔍 关键语法解析

1️⃣ 端口连接的两种方式

按位置连接(不推荐)

half_adder ha1(a, b, s1, c1);  // ❌ 容易出错
//             ↑  ↑  ↑   ↑
//            必须严格对应声明顺序

按名称连接(推荐)

half_adder ha1(
    .a(a),      // ✅ 一目了然
    .b(b),      // 顺序可以打乱
    .sum(s1),
    .cout(c1)
);

2️⃣ 内部连线的作用
wire s1;  // 声明内部信号
//   ↑
//  像电路板上的铜箔走线

硬件对应

模块A的输出引脚 ──[s1这根线]── 模块B的输入引脚

3️⃣ 实例命名规范
half_adder ha1(...);
//         ↑
//        实例名(必须唯一)

命名建议

  • u_前缀:u_adder, u_counter
  • 用功能缩写:ha1, ha2(半加器)
  • 用层级标识:u_top_ctrl

🧪 测试全加器

module tb_full_adder;
    reg a, b, cin;
    wire sum, cout;
    
    full_adder uut(
        .a(a),
        .b(b),
        .cin(cin),
        .sum(sum),
        .cout(cout)
    );
    
    integer i;
    
    initial begin
        $display("A B Cin | Sum Cout | 验算(十进制)");
        $display("-----------------------------------");
        
        // 🔄 遍历所有8种输入组合(2³=8)
        for (i = 0; i < 8; i = i + 1) begin
            {a, b, cin} = i;  // 🎨 并行赋值技巧
            #10;
            $display("%b %b  %b  |  %b   %b   | %d+%d+%d=%d%b", 
                     a, b, cin, sum, cout,
                     a, b, cin, cout, sum);
        end
        
        $finish;
    end
endmodule

📊 预期输出

A B Cin | Sum Cout | 验算(十进制)
-----------------------------------
0 0  0  |  0   0   | 0+0+0=00 ✓
0 0  1  |  1   0   | 0+0+1=01 ✓
0 1  0  |  1   0   | 0+1+0=01 ✓
0 1  1  |  0   1   | 0+1+1=10 ✓ (进位!)
1 0  0  |  1   0   | 1+0+0=01 ✓
1 0  1  |  0   1   | 1+0+1=10 ✓
1 1  0  |  0   1   | 1+1+0=10 ✓
1 1  1  |  1   1   | 1+1+1=11 ✓ (3的二进制)

🎯 例题10:4位行波进位加法器

📚 设计思路

目标:实现 A[3:0] + B[3:0] = Sum[3:0], Cout

方案:用4个全加器级联

A[0]+B[0] → FA0 → Sum[0]
   ↓进位
A[1]+B[1] → FA1 → Sum[1]
   ↓进位
A[2]+B[2] → FA2 → Sum[2]
   ↓进位
A[3]+B[3] → FA3 → Sum[3], Cout

📝 代码实现

module adder_4bit(
    input wire [3:0] a,      // 4位被加数
    input wire [3:0] b,      // 4位加数
    input wire cin,          // 最低位进位输入
    output wire [3:0] sum,   // 4位和
    output wire cout         // 最高位进位输出
);

    // 🔌 内部进位线(连接相邻全加器)
    wire c1, c2, c3;
    
    // 🧩 实例化4个全加器
    full_adder fa0(
        .a(a[0]),
        .b(b[0]),
        .cin(cin),     // 外部进位输入
        .sum(sum[0]),
        .cout(c1)      // 传递给下一级
    );
    
    full_adder fa1(
        .a(a[1]),
        .b(b[1]),
        .cin(c1),      // 接收上一级进位
        .sum(sum[1]),
        .cout(c2)
    );
    
    full_adder fa2(
        .a(a[2]),
        .b(b[2]),
        .cin(c2),
        .sum(sum[2]),
        .cout(c3)
    );
    
    full_adder fa3(
        .a(a[3]),
        .b(b[3]),
        .cin(c3),
        .sum(sum[3]),
        .cout(cout)    // 最终进位输出
    );
    
endmodule

🔍 向量切片技巧

wire [3:0] a;     // 声明4位向量
     ↑   ↑
    最高 最低

a[0]  // 访问第0位(最低位)
a[3]  // 访问第3位(最高位)
a[2:1] // 访问第2,1位(切片)

硬件对应

a[3] a[2] a[1] a[0]
 ↓    ↓    ↓    ↓
[像4根独立的电线捆在一起]

🧪 测试代码

module tb_adder_4bit;
    reg [3:0] a, b;
    reg cin;
    wire [3:0] sum;
    wire cout;
    
    adder_4bit uut(
        .a(a), .b(b), .cin(cin),
        .sum(sum), .cout(cout)
    );
    
    initial begin
        $display("  A  +  B  + Cin = Cout Sum | 十进制验算");
        $display("------------------------------------------");
        
        // 🎲 测试案例
        a=4'd5;  b=4'd3;  cin=0; #10;
        $display("%d + %d +  %d =   %d   %d  | %d (✓)", 
                 a, b, cin, cout, sum, a+b+cin);
        
        a=4'd15; b=4'd1;  cin=0; #10;
        $display("%d + %d +  %d =   %d   %d  | %d (溢出!)", 
                 a, b, cin, cout, sum, a+b+cin);
        
        a=4'd7;  b=4'd8;  cin=1; #10;
        $display("%d + %d +  %d =   %d   %d  | %d (✓)", 
                 a, b, cin, cout, sum, a+b+cin);
        
        $finish;
    end
endmodule

📐 5.3 参数化设计(Parameter)

🎯 问题场景

如果要设计8位、16位、32位加法器,难道要写3遍代码?

答案:用parameter参数化!


📝 参数化模块示例

module adder_nbit #(
    parameter WIDTH = 4  // 📊 可配置参数(默认4位)
)(
    input wire [WIDTH-1:0] a,    // 位宽自适应
    input wire [WIDTH-1:0] b,
    input wire cin,
    output wire [WIDTH-1:0] sum,
    output wire cout
);

    // 🧮 直接用算术运算(综合器会优化)
    assign {cout, sum} = a + b + cin;
    //     ↑拼接运算符,将进位和结果合并
    
endmodule

🔧 实例化时覆盖参数

// 🔹 方法1:按位置传参
adder_nbit #(8) u_adder8(...);  // 8位加法器

// 🔹 方法2:按名称传参(推荐)
adder_nbit #(
    .WIDTH(16)
) u_adder16(...);  // 16位加法器

🧪 测试不同位宽

module tb_adder_nbit;
    // 🔹 测试8位加法器
    reg [7:0] a8, b8;
    wire [7:0] sum8;
    wire cout8;
    
    adder_nbit #(.WIDTH(8)) u8(
        .a(a8), .b(b8), .cin(1'b0),
        .sum(sum8), .cout(cout8)
    );
    
    // 🔹 测试16位加法器
    reg [15:0] a16, b16;
    wire [15:0] sum16;
    wire cout16;
    
    adder_nbit #(.WIDTH(16)) u16(
        .a(a16), .b(b16), .cin(1'b0),
        .sum(sum16), .cout(cout16)
    );
    
    initial begin
        // 测试8位
        a8 = 8'd200; b8 = 8'd100; #10;
        $display("8位: %d + %d = %d", a8, b8, sum8);
        
        // 测试16位
        a16 = 16'd50000; b16 = 16'd20000; #10;
        $display("16位: %d + %d = %d", a16, b16, sum16);
        
        $finish;
    end
endmodule

🏗️ 5.4 层次化设计实战:数字时钟

📐 系统架构

顶层模块(top)
├── 分频器(divider) → 1Hz时钟
├── 秒计数器(counter_sec) → 0~59
├── 分计数器(counter_min) → 0~59
├── 时计数器(counter_hour) → 0~23
└── 数码管驱动(seg7_driver)

📝 底层模块1:计数器

module counter #(
    parameter MAX_VAL = 59  // 最大计数值
)(
    input wire clk,
    input wire rst_n,
    input wire en,          // 使能信号
    output reg [$clog2(MAX_VAL):0] count,  // 自动计算位宽
    output wire carry       // 进位输出
);

    assign carry = (count == MAX_VAL) && en;  // 计满产生进位
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            count <= 0;
        end 
        else if (en) begin
            if (count >= MAX_VAL) begin
                count <= 0;
            end 
            else begin
                count <= count + 1;
            end
        end
    end
    
endmodule

📝 底层模块2:分频器

module clock_divider #(
    parameter DIV_FACTOR = 50_000_000  // 分频系数
)(
    input wire clk_in,      // 输入时钟(如50MHz)
    input wire rst_n,
    output reg clk_out      // 输出时钟(如1Hz)
);

    reg [$clog2(DIV_FACTOR)-1:0] cnt;
    
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            cnt <= 0;
            clk_out <= 0;
        end 
        else begin
            if (cnt >= DIV_FACTOR/2 - 1) begin
                cnt <= 0;
                clk_out <= ~clk_out;  // 翻转输出
            end 
            else begin
                cnt <= cnt + 1;
            end
        end
    end
    
endmodule

📝 顶层模块组装

module digital_clock(
    input wire clk_50m,     // 板载50MHz时钟
    input wire rst_n,
    output wire [5:0] sec,  // 秒输出
    output wire [5:0] min,  // 分输出
    output wire [4:0] hour  // 时输出
);

    // 🔌 内部信号
    wire clk_1hz;           // 1Hz时钟
    wire carry_sec;         // 秒进位
    wire carry_min;         // 分进位
    
    // 🧩 分频器:50MHz → 1Hz
    clock_divider #(
        .DIV_FACTOR(50_000_000)
    ) u_div (
        .clk_in(clk_50m),
        .rst_n(rst_n),
        .clk_out(clk_1hz)
    );
    
    // 🧩 秒计数器(0~59)
    counter #(
        .MAX_VAL(59)
    ) u_sec (
        .clk(clk_1hz),
        .rst_n(rst_n),
        .en(1'b1),          // 始终使能
        .count(sec),
        .carry(carry_sec)
    );
    
    // 🧩 分计数器(0~59)
    counter #(
        .MAX_VAL(59)
    ) u_min (
        .clk(clk_1hz),
        .rst_n(rst_n),
        .en(carry_sec),     // 秒进位时才计数
        .count(min),
        .carry(carry_min)
    );
    
    // 🧩 时计数器(0~23)
    counter #(
        .MAX_VAL(23)
    ) u_hour (
        .clk(clk_1hz),
        .rst_n(rst_n),
        .en(carry_min),     // 分进位时才计数
        .count(hour),
        .carry()            // 悬空(不需要)
    );
    
endmodule

🔍 关键设计要点

1️⃣ $clog2函数
$clog2(59)  // 计算log₂(59)并向上取整 = 6
//  ↑
// 自动计算需要的位宽

用途:避免手动计算位宽出错


2️⃣ 进位信号级联
秒满60 → carry_sec=1 → 触发分计数器
分满60 → carry_min=1 → 触发时计数器

硬件意义:像机械齿轮的咬合传动


3️⃣ 端口悬空
.carry()  // 不连接任何信号(合法)

场景:当某个输出不需要时


⚠️ 模块化设计常见错误

❌ 错误1:忘记声明内部连线

module top;
    half_adder ha1(.sum(s1), ...);  // ❌ s1未声明
endmodule

正确做法

wire s1;  // ✅ 先声明
half_adder ha1(.sum(s1), ...);

❌ 错误2:端口位宽不匹配

wire [3:0] data;
module_8bit u(.in(data));  // ❌ 4位连到8位

后果:综合警告,高位补0或截断


❌ 错误3:循环实例化

module A;
    B u_b(...);  // A调用B
endmodule

module B;
    A u_a(...);  // ❌ B又调用A,形成死循环
endmodule

🎓 本课核心总结

📋 模块化设计原则

原则说明类比
单一职责每个模块只做一件事Unix哲学
接口清晰端口命名明确API设计
参数化用parameter增强复用泛型编程
层次分明顶层只连接,底层做计算MVC架构

✅ 设计检查清单

□ 模块功能是否单一?
□ 端口命名是否清晰?
□ 是否使用参数化?
□ 内部信号是否都声明?
□ 是否有未连接的端口?
□ 层次是否合理(不超过5层)?

🚀 综合大作业:简易CPU

💡 项目目标

设计一个4位微处理器,包含:

  • ALU模块:加减与或运算
  • 寄存器堆:4个通用寄存器
  • 指令译码器:解析4位指令
  • 控制器:状态机控制执行流程

📐 模块划分建议

cpu_top
├── u_alu (算术逻辑单元)
├── u_regfile (寄存器堆)
├── u_decoder (指令译码)
└── u_controller (主控制器)

📌 下节预告

第六课:仿真进阶与调试技巧
学习内容:

  • 高级testbench编写
  • $monitor$strobe等系统任务
  • 波形调试技巧
  • 覆盖率分析
  • 实战:UART协议仿真验证

💬 本课学习检查
✅ 理解模块实例化的语法
✅ 会用内部连线连接模块
✅ 掌握按名称端口连接
✅ 能用parameter参数化设计
✅ 理解层次化设计思想
✅ 会组装多模块系统
✅ 知道如何分解复杂功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值