FPGA_SoC Vivado_Vitis应用编码设计仿真实践

立足典型FPGA设计仿真运行平台AMD-Xillinz-Vivado/VitisEmbeddedDevelopment,借助人工智能AIIMA-Copilot-DS与MuleRun工具,使用Verilog与C语言,展开了一系列的典型真物实操设计仿真项目演练:组合逻辑--七段数码管译码器、时序逻辑---LFSR伪随机数发生器、状态机FSM--交通灯控制器、综合UART串口通信、FPGA-SoC--MicroBlaze-V片上系统。

1 基础组合逻辑--七段数码管译码器

1.1 方案规划

BCD-to-7-Segment Decoder--将4 位BCD 输入转换为七段数码管的段选信号,驱动共阴极/共阳极数码管显示0~F。

七段数码管由a~g共7个LED段加1个小数点(dp)组成。译码器的功能是将4位BCD码(0x0~0xF)映射到8位段选信号{dp,g,f,e,d,c,b,a}。

1.2 逻辑编码

RTL源码--seg7_decoder.v

cpp 复制代码
// ============================================================
// Module: seg7_decoder
// Description: BCD to 7-segment display decoder (active HIGH)
// Target: Xilinx FPGA (Vivado Synthesis)
// ============================================================
module seg7_decoder (
    input  wire [3:0] bcd,      // 4-bit BCD input (0x0 - 0xF)
    output reg  [6:0] seg       // 7-segment output {g,f,e,d,c,b,a}
);
    // Combinational logic: BCD → segment pattern
    always @(*) begin
        case (bcd)
            4'h0: seg = 7'b111_1110;  // 0: a,b,c,d,e,f
            4'h1: seg = 7'b011_0000;  // 1: b,c
            4'h2: seg = 7'b110_1101;  // 2: a,b,d,e,g
            4'h3: seg = 7'b111_1001;  // 3: a,b,c,d,g
            4'h4: seg = 7'b011_0011;  // 4: b,c,f,g
            4'h5: seg = 7'b101_1011;  // 5: a,c,d,f,g
            4'h6: seg = 7'b101_1111;  // 6: a,c,d,e,f,g
            4'h7: seg = 7'b111_0000;  // 7: a,b,c
            4'h8: seg = 7'b111_1111;  // 8: all segments
            4'h9: seg = 7'b111_1011;  // 9: a,b,c,d,f,g
            4'hA: seg = 7'b111_0111;  // A: a,b,c,e,f,g
            4'hB: seg = 7'b001_1111;  // b: c,d,e,f,g
            4'hC: seg = 7'b100_1110;  // C: a,d,e,f
            4'hD: seg = 7'b011_1101;  // d: b,c,d,e,g
            4'hE: seg = 7'b100_1111;  // E: a,d,e,f,g
            4'hF: seg = 7'b100_0111;  // F: a,e,f,g
            default: seg = 7'b000_0000; // blank
        endcase
    end
endmodule

测试编码Testbench--tb_seg7.v

cpp 复制代码
// ============================================================
// Testbench: tb_seg7 --- exhaustive test of all 16 BCD inputs
// ============================================================
`timescale 1ns/1ps
module tb_seg7;
    reg  [3:0] bcd;
    wire [6:0] seg;
    // DUT instantiation
    seg7_decoder uut (
        .bcd (bcd),
        .seg (seg)
    );
    initial begin
        bcd = 4'd0;         // 明确初始化
        $display("===== Test =====");        
        for (bcd = 4'd0; bcd <= 4'd15; bcd = bcd + 1) begin
            #10;
            $display("bcd=%h  seg=%b", bcd, seg);
        end        
        $finish;
    end
endmodule

1.3 仿真测试

Vivado 仿真执行步骤

  1. 创建工程:File → Project → New → 选择目标器件 (如 Artix-7 XC7A35T) → 完成。
  2. 添加设计源文件:Add Sources → Add Design Sources → 选择seg7_decoder.v
  3. 添加仿真源文件:Add Sources → Add Simulation Sources → 选择tb_seg7.v
  4. 运行行为仿真:Flow Navigator → SIMULATION → Run Behavioral Simulation → 观察波形。
  5. Tcl 控制台命令

1.4 综合执行

添加并编写XDC约束à综合Run Synthesisà实现Run Implementationà生成位流Generate Bitstreamà下载à运行。

cpp 复制代码
## ============================================================
## Project: seg7_decoder
## Board:   Digilent Basys3 (XC7A35T-1CPG236C)
## File:    constraints_seg7.xdc
## ============================================================
## ── 系统时钟 (100 MHz) ──
set_property PACKAGE_PIN W5        [get_ports clk]
set_property IOSTANDARD LVCMOS33   [get_ports clk]
create_clock -add -name sys_clk -period 10.00 [get_ports clk]
## ── 拨码开关输入 SW[3:0] → BCD[3:0] ──
## SW0 → bcd[0], SW1 → bcd[1], SW2 → bcd[2], SW3 → bcd[3]
set_property PACKAGE_PIN V17       [get_ports {bcd[0]}]
set_property IOSTANDARD LVCMOS33   [get_ports {bcd[0]}]
set_property PACKAGE_PIN V16       [get_ports {bcd[1]}]
set_property IOSTANDARD LVCMOS33   [get_ports {bcd[1]}]
set_property PACKAGE_PIN W16       [get_ports {bcd[2]}]
set_property IOSTANDARD LVCMOS33   [get_ports {bcd[2]}]
set_property PACKAGE_PIN W17       [get_ports {bcd[3]}]
set_property IOSTANDARD LVCMOS33   [get_ports {bcd[3]}]
## ── 七段数码管 段选 seg[6:0] = {g,f,e,d,c,b,a} ──
## 共阳极: seg=0 点亮, seg=1 熄灭 (active LOW)
set_property PACKAGE_PIN W7        [get_ports {seg[0]}]  ## a
set_property IOSTANDARD LVCMOS33   [get_ports {seg[0]}]
set_property PACKAGE_PIN W6        [get_ports {seg[1]}]  ## b
set_property IOSTANDARD LVCMOS33   [get_ports {seg[1]}]
set_property PACKAGE_PIN U8        [get_ports {seg[2]}]  ## c
set_property IOSTANDARD LVCMOS33   [get_ports {seg[2]}]
set_property PACKAGE_PIN V8        [get_ports {seg[3]}]  ## d
set_property IOSTANDARD LVCMOS33   [get_ports {seg[3]}]
set_property PACKAGE_PIN U5        [get_ports {seg[4]}]  ## e
set_property IOSTANDARD LVCMOS33   [get_ports {seg[4]}]
set_property PACKAGE_PIN V5        [get_ports {seg[5]}]  ## f
set_property IOSTANDARD LVCMOS33   [get_ports {seg[5]}]
set_property PACKAGE_PIN U7        [get_ports {seg[6]}]  ## g
set_property IOSTANDARD LVCMOS33   [get_ports {seg[6]}]
## ── 七段数码管 位选 an[3:0] ──
## 共阳极 PNP 驱动: an=0 选中, an=1 关闭 (active LOW)
## 仅使用第 0 位数码管显示 BCD 输入
set_property PACKAGE_PIN V4        [get_ports {an[0]}]   ## DIG0 --- 使用中
set_property IOSTANDARD LVCMOS33   [get_ports {an[0]}]
set_property PACKAGE_PIN U4        [get_ports {an[1]}]   ## DIG1 --- 关闭
set_property IOSTANDARD LVCMOS33   [get_ports {an[1]}]
set_property PACKAGE_PIN U2        [get_ports {an[2]}]   ## DIG2 --- 关闭
set_property IOSTANDARD LVCMOS33   [get_ports {an[2]}]
set_property PACKAGE_PIN U3        [get_ports {an[3]}]   ## DIG3 --- 关闭
set_property IOSTANDARD LVCMOS33   [get_ports {an[3]}]
## ── 约束: 未使用的数码管位保持关闭 ──
## 在 RTL 中: assign an = 4'b1110; (仅 DIG0 有效)

2 基础时序逻辑--LFSR伪随机发生器

2.1 方案规划

Linear Feedback Shift Register--基于XOR反馈抽头的最大长度序列伪随机数发生器,可配置位宽 (默认 8-bit),产生2N−1个不重复伪随机值。

LFSR(线性反馈移位寄存器),通过将输出位的线性组合(XOR)反馈到输入端,产生最大长度伪随机序列。

关键概念:

  • 抽头位置 (Taps):决定序列长度,需满足本原多项式条件
  • Fibonacci 结构:反馈从高位抽头 XOR 后注入 LSB
  • Galois 结构:每级独立 XOR,效率更高
  • 种子 (Seed):初始值不能为全 0,否则锁定

伪随机数发生常见LFSR抽头配置列表

|----------|----------|----------------------------------|--------------------------|
| 位宽 N | 序列长度 | Fibonacci 抽头 (XOR 位) | 本原多项式 |
| 4 | 15 | 4,3 | x⁴ + x³ + 1 |
| 8 | 255 | 8,6,5,4 | x⁸ + x⁶ + x⁵ + x⁴ + 1 |
| 16 | 65535 | 16,15,13,4 | x¹⁶ + x¹⁵ + x¹³ + x⁴ + 1 |
| 32 | 4.29×10⁹ | 32,22,2,1 | x³² + x²² + x² + x + 1 |

2.2 逻辑编码

1 RTL 源码(Fibonacci)--lfsr_fib.v

cpp 复制代码
// ============================================================
// Module: lfsr_fib
// Fibonacci LFSR with configurable width
// Default: 8-bit, taps [8,6,5,4]
// ============================================================
module lfsr_fib #(
    parameter integer N    = 8,
    parameter [31:0] SEED = 8'hA5
)(
    input  wire        clk,
    input  wire        rst_n,    // active-low reset
    input  wire        en,       // shift enable
    output wire [N-1:0] rnd      // pseudo-random output
);
    reg [N-1:0] lfsr_q;
    // Feedback bit: XOR of tap positions
    // For N=8: taps at bits [7,5,4,3] (0-indexed)
    wire feedback;
    generate
        if (N == 8)
            assign feedback = lfsr_q[7] ^ lfsr_q[5] ^ lfsr_q[4] ^ lfsr_q[3];
        else if (N == 16)
            assign feedback = lfsr_q[15] ^ lfsr_q[14] ^ lfsr_q[12] ^ lfsr_q[3];
        else if (N == 4)
            assign feedback = lfsr_q[3] ^ lfsr_q[2];
        else
            assign feedback = lfsr_q[N-1] ^ lfsr_q[N-2]; // fallback
    endgenerate
    // Sequential logic
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            lfsr_q <= SEED[N-1:0];
        else if (en)
            lfsr_q <= {lfsr_q[N-2:0], feedback};
    end
    assign rnd = lfsr_q;
endmodule

2 RTL 源码(Galois)--lfsr_gal.v

cpp 复制代码
// ============================================================
// Module: lfsr_gal
// Galois LFSR --- each stage XOR independently
// More efficient for synthesis (shorter critical path)
// ============================================================
module lfsr_gal #(
    parameter integer N    = 8,
    parameter [31:0] SEED = 8'hA5
)(
    input  wire        clk,
    input  wire        rst_n,
    input  wire        en,
    output wire [N-1:0] rnd
);
    reg [N-1:0] lfsr_q;
    // Galois: MSB directly feeds back, XOR at tap positions
    // Mask for N=8: taps at bit positions 5,4,3 (from MSB)
    wire msb = lfsr_q[N-1];
    wire [N-1:0] next_val;
    generate
        if (N == 8) begin
            assign next_val[N-1]     = lfsr_q[N-2];
            assign next_val[5]        = lfsr_q[4] ^ msb;
            assign next_val[4]        = lfsr_q[3] ^ msb;
            assign next_val[3]        = lfsr_q[2] ^ msb;
            assign next_val[2]        = lfsr_q[1];
            assign next_val[1]        = lfsr_q[0];
            assign next_val[0]        = msb;
        end
        else begin
            // Simplified: generic fallback
            assign next_val = {lfsr_q[N-2:0], msb};
        end
    endgenerate
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            lfsr_q <= SEED[N-1:0];
        else if (en)
            lfsr_q <= next_val;
    end
    assign rnd = lfsr_q;
endmodule

3 Testbench--tb_lfsr.v

cpp 复制代码
`timescale 1ns/1ps
module tb_lfsr;
    reg        clk = 0;
    reg        rst_n;
    reg        en;
    wire [7:0] rnd;
    // 100 MHz clock
    always #5 clk = ~clk;
    // DUT
    lfsr_fib #(.N(8), .SEED(8'hA5)) uut (
        .clk   (clk),
        .rst_n (rst_n),
        .en    (en),
        .rnd   (rnd)
    );
    // Track unique values to verify max-length
    reg [255:0] seen; // bitmap: 1 bit per possible value
    integer count;
    initial begin
        //$dumpfile("lfsr_wave.vcd");
        //$dumpvars(0, tb_lfsr);
        seen  = 0;
        count = 0;
        rst_n = 0;
        en    = 1;
        // Hold reset for 3 clocks
        #30;
        rst_n = 1;
        // Run for full period (255 clocks for 8-bit)
        repeat (256) @(posedge clk);
        $display("LFSR period verification complete.");
        #20;
        $finish;
    end
    // Count unique outputs
    always @(posedge clk) begin
        if (rst_n && en) begin
            seen[rnd] = 1'b1;
            count = count + 1;
            if (count <= 10)
                $display("t=%0t  rnd = 0x%0h", $time, rnd);
        end
    end
endmodule

2.3 仿真测试

Vivado 仿真执行步骤

新建工程并添加源文件:添加lfsr_fib.v作为Design Source,tb_lfsr.v作为Simulation Source。

运行 Behavioral Simulation:观察rnd7:0的变化模式,验证序列不重复。

验证最大长度:8-bit LFSR应在255个时钟后回到种子值0xA5,期间不出现全0。

综合验证:Run Synthesis → 检查Utilization报告和Timing Summary。

2.4 综合执行

添加并编写XDC约束à综合Run Synthesisà实现Run Implementationà生成位流Generate Bitstreamà下载à运行。

XDC-2LFSR 伪随机数发生器--constraints_lfsr.xdc

端口定义:8位LED7:0实时显示LFSR输出值。按键BTN0控制使能/暂停,BTN1复位。

cpp 复制代码
## ============================================================
## Project: lfsr_fib
## Board:   Digilent Basys3 (XC7A35T-1CPG236C)
## File:    constraints_lfsr.xdc
## ============================================================
## ── 系统时钟 (100 MHz) ──
set_property PACKAGE_PIN W5        [get_ports clk]
set_property IOSTANDARD LVCMOS33   [get_ports clk]
create_clock -add -name sys_clk -period 10.00 [get_ports clk]
## ── 控制按键 ──
set_property PACKAGE_PIN U18       [get_ports btn_en]     ## BTN0 --- 使能/暂停
set_property IOSTANDARD LVCMOS33   [get_ports btn_en]
set_property PACKAGE_PIN T18       [get_ports btn_rst]    ## BTN1 --- 复位
set_property IOSTANDARD LVCMOS33   [get_ports btn_rst]
## ── LED 输出 [7:0] ← LFSR rnd[7:0] ──
set_property PACKAGE_PIN U16       [get_ports {led[0]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[0]}]
set_property PACKAGE_PIN E19       [get_ports {led[1]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[1]}]
set_property PACKAGE_PIN U19       [get_ports {led[2]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[2]}]
set_property PACKAGE_PIN V19       [get_ports {led[3]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[3]}]
set_property PACKAGE_PIN W18       [get_ports {led[4]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[4]}]
set_property PACKAGE_PIN U15       [get_ports {led[5]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[5]}]
set_property PACKAGE_PIN U14       [get_ports {led[6]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[6]}]
set_property PACKAGE_PIN V14       [get_ports {led[7]}]
set_property IOSTANDARD LVCMOS33   [get_ports {led[7]}]

3 基础FSM--交通灯控制器

3.1 方案规划

Finite State Machine Traffic Light Controller--基于Moore型有限状态机实现十字路口交通灯控制,含东西 / 南北两组红黄绿灯,支持定时切换。

1 状态机设计

采用Moore型FSM,输出仅与当前状态相关。定义4个状态:

🔴S0: EW_RED:东西红 | 南北绿 25s

🟡S1: EW_YEL:东西红 | 南北黄 5s

🔴S2: NS_RED:东西绿 | 南北红 25s

🟡S3: NS_YEL:东西黄 | 南北红 5s

2 状态转换

S0 → S1 → S2 → S3 → S0 (循环)

3.2 逻辑编码

RTL 源码--traffic_light.v

cpp 复制代码
// ============================================================
// Module: traffic_light
// Moore FSM Traffic Light Controller
// Clock: 1 Hz (use a divider from system clock)
// ============================================================
module traffic_light
 #(
    parameter T_GREEN  = 25,   // green light duration (seconds)
    parameter T_YELLOW = 5     // yellow light duration (seconds)
)(
    input  wire       clk,       // 1 Hz clock
    input  wire       rst_n,     // active-low reset
    // East-West lights
    output reg        ew_red,
    output reg        ew_yellow,
    output reg        ew_green,
    // North-South lights
    output reg        ns_red,
    output reg        ns_yellow,
    output reg        ns_green
);
    // State encoding
    localparam [1:0]
        S0_EW_RED   = 2'b00,   // EW=Red,    NS=Green
        S1_EW_YEL   = 2'b01,   // EW=Red,    NS=Yellow
        S2_NS_RED   = 2'b10,   // EW=Green,  NS=Red
        S3_NS_YEL   = 2'b11;   // EW=Yellow, NS=Red
    reg [1:0] state, next_state;
    reg [7:0] counter;
    // ===== Sequential: state register =====
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= S0_EW_RED;
        else
            state <= next_state;
    end
    // ===== Sequential: timer counter =====
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            counter <= 8'd0;
        else if (state != next_state)
            counter <= 8'd0;      // reset counter on state change
        else
            counter <= counter + 8'd1;
    end
    // ===== Combinational: next-state logic =====
    always @(*) begin
        next_state = state;  // default: hold
        case (state)
            S0_EW_RED: if (counter >= T_GREEN  - 1) next_state = S1_EW_YEL;
            S1_EW_YEL: if (counter >= T_YELLOW - 1) next_state = S2_NS_RED;
            S2_NS_RED: if (counter >= T_GREEN  - 1) next_state = S3_NS_YEL;
            S3_NS_YEL: if (counter >= T_YELLOW - 1) next_state = S0_EW_RED;
        endcase
    end
    // ===== Combinational: output logic (Moore) =====
    always @(*) begin
        // defaults: all off
        {ew_red, ew_yellow, ew_green} = 3'b000;
        {ns_red, ns_yellow, ns_green} = 3'b000;
        case (state)
            S0_EW_RED: begin ew_red=1; ns_green=1; end
            S1_EW_YEL: begin ew_red=1; ns_yellow=1; end
            S2_NS_RED: begin ew_green=1; ns_red=1; end
            S3_NS_YEL: begin ew_yellow=1; ns_red=1; end
        endcase
    end
endmodule

时钟分频--clk_divider.v

cpp 复制代码
// 100 MHz → 1 Hz 分频器
module clk_divider #(
    parameter CLK_IN   = 100_000_000,
    parameter CLK_OUT  = 1
)(
    input  wire clk_in,
    input  wire rst_n,
    output reg  clk_out
);
    localparam DIV = CLK_IN / (CLK_OUT * 2);
    reg [25:0] cnt;
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            cnt     <= 0;
            clk_out <= 0;
        end else if (cnt >= DIV - 1) begin
            cnt     <= 0;
            clk_out <= ~clk_out;
        end else
            cnt <= cnt + 1;
    end
endmodule

Testbench--tb_traffic.v

cpp 复制代码
`timescale 1ns/1ps
module tb_traffic;
    reg  clk_1hz = 0;
    reg  rst_n;
    wire ew_red, ew_yellow, ew_green;
    wire ns_red, ns_yellow, ns_green;
    // Simulated 1 Hz clock (for fast simulation)
    always #500 clk_1hz = ~clk_1hz; // 1μs = 1Hz scaled
    traffic_light #(.T_GREEN(5), .T_YELLOW(2)) uut (
        .clk       (clk_1hz),
        .rst_n     (rst_n),
        .ew_red    (ew_red),
        .ew_yellow (ew_yellow),
        .ew_green  (ew_green),
        .ns_red    (ns_red),
        .ns_yellow (ns_yellow),
        .ns_green  (ns_green)
    );
    initial begin
        //$dumpfile("traffic_wave.vcd");
        //$dumpvars(0, tb_traffic);
        rst_n = 0;
        #100;
        rst_n = 1;
        // Simulate one full cycle: 5+2+5+2 = 14 ticks
        repeat (30) @(posedge clk_1hz);
        $display("Traffic light simulation complete.");
        $finish;
    end
endmodule

3.3 仿真测试

Vivado 仿真执行步骤

  1. 添加源文件并仿真:将traffic_light.v和tb_traffic.v加入工程。运行Behavioral Simulation。
  2. 验证状态转换时序:在波形窗口确认 S0(5s绿) → S1(2s黄) → S2(5s绿) → S3(2s黄) 循环。
  3. 可选:添加 RTL 分析:RTL Analysis → 查看综合后状态机原理图,确认状态转换逻辑正确。

3.4 综合执行

添加并编写XDC约束à综合Run Synthesisà实现Run Implementationà生成位流Generate Bitstreamà下载à运行。

FSM 交通灯控制器 ---constraints_traffic.xdc

端口定义:6 个 LED 模拟两组交通灯 (东西 / 南北)。EW 方向: LED0(红) LED1(黄) LED2(绿);NS 方向: LED3(红) LED4(黄) LED5(绿)。

cpp 复制代码
## ============================================================
## Project: traffic_light (FSM)
## Board:   Digilent Basys3 (XC7A35T-1CPG236C)
## File:    constraints_traffic.xdc
## ============================================================
## ── 系统时钟 (100 MHz) ──
set_property PACKAGE_PIN W5        [get_ports clk]
set_property IOSTANDARD LVCMOS33   [get_ports clk]
create_clock -add -name sys_clk -period 10.00 [get_ports clk]
## ── 复位按键 ──
set_property PACKAGE_PIN U18       [get_ports rst_n]      ## BTN0 --- active LOW
set_property IOSTANDARD LVCMOS33   [get_ports rst_n]
## ── 东西方向 (East-West) ──
set_property PACKAGE_PIN U16       [get_ports ew_red]     ## LED0 --- 红
set_property IOSTANDARD LVCMOS33   [get_ports ew_red]
set_property PACKAGE_PIN E19       [get_ports ew_yellow]  ## LED1 --- 黄
set_property IOSTANDARD LVCMOS33   [get_ports ew_yellow]
set_property PACKAGE_PIN U19       [get_ports ew_green]   ## LED2 --- 绿
set_property IOSTANDARD LVCMOS33   [get_ports ew_green]
## ── 南北方向 (North-South) ──
set_property PACKAGE_PIN V19       [get_ports ns_red]     ## LED3 --- 红
set_property IOSTANDARD LVCMOS33   [get_ports ns_red]
set_property PACKAGE_PIN W18       [get_ports ns_yellow]  ## LED4 --- 黄
set_property IOSTANDARD LVCMOS33   [get_ports ns_yellow]
set_property PACKAGE_PIN U15       [get_ports ns_green]   ## LED5 --- 绿
set_property IOSTANDARD LVCMOS33   [get_ports ns_green]

4 综合应用--UART串口通信

4.1 方案规划

Universal Asynchronous Receiver-Transmitter--实现完整的UART发送器和接收器,内部环回(Loopback)验证数据正确性,支持可配置波特率。

UART帧格式(8N1: 8 数据位, 无校验, 1 停止位):

图6-9 UART帧格式(8N1)示意图

波特率 = 系统时钟 / 分频系数。例:100 MHz / 115200 ≈ 868 → 每个 bit 持续 868 个时钟周期。

4.2 逻辑编码

UART 发送器--uart_tx.v

cpp 复制代码
// ============================================================
// Module: uart_tx
// UART Transmitter: 8N1 format
// ============================================================
module uart_tx #(
    parameter CLK_FREQ = 100_000_000,
    parameter BAUD     = 115200
)(
    input  wire       clk,
    input  wire       rst_n,
    input  wire [7:0] tx_data,
    input  wire       tx_start,
    output reg        tx_out,
    output wire       tx_busy
);
    localparam DIV = CLK_FREQ / BAUD;
    localparam CNT_W = $clog2(DIV);
    localparam [3:0]
        IDLE  = 4'd0,
        START = 4'd1,
        DATA  = 4'd2,
        STOP  = 4'd3;
    reg [3:0]  state;
    reg [CNT_W-1:0] clk_cnt;
    reg [2:0]  bit_idx;
    reg [7:0]  shift_reg;
    assign tx_busy = (state != IDLE);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state    <= IDLE;
            tx_out   <= 1'b1;
            clk_cnt  <= 0;
            bit_idx  <= 0;
            shift_reg<= 8'd0;
        end else case (state)
            IDLE: begin
                tx_out <= 1'b1;
                if (tx_start) begin
                    shift_reg <= tx_data;
                    state     <= START;
                    clk_cnt   <= 0;
                end
            end
            START: begin
                tx_out <= 1'b0;
                if (clk_cnt == DIV - 1) begin
                    clk_cnt <= 0;
                    state   <= DATA;
                    bit_idx <= 0;
                end else
                    clk_cnt <= clk_cnt + 1;
            end
            DATA: begin
                tx_out <= shift_reg[0];
                if (clk_cnt == DIV - 1) begin
                    clk_cnt   <= 0;
                    shift_reg <= {1'b0, shift_reg[7:1]};
                    if (bit_idx == 7)
                        state <= STOP;
                    else
                        bit_idx <= bit_idx + 1;
                end else
                    clk_cnt <= clk_cnt + 1;
            end
            STOP: begin
                tx_out <= 1'b1;
                if (clk_cnt == DIV - 1)
                    state <= IDLE;
                else
                    clk_cnt <= clk_cnt + 1;
            end
        endcase
    end
endmodule

UART 接收器--uart_rx.v

cpp 复制代码
// ============================================================
// Module: uart_rx
// UART Receiver: 8N1 format, 16x oversampling midpoint
// ============================================================
module uart_rx #(
    parameter CLK_FREQ = 100_000_000,
    parameter BAUD     = 115200
)(
    input  wire       clk,
    input  wire       rst_n,
    input  wire       rx_in,
    output reg  [7:0] rx_data,
    output reg        rx_valid
);
    localparam DIV = CLK_FREQ / BAUD;
    localparam HALF = DIV / 2;
    localparam CNT_W = $clog2(DIV);
    localparam [3:0]
        IDLE  = 4'd0,
        START = 4'd1,
        DATA  = 4'd2,
        STOP  = 4'd3;
    reg [3:0]       state;
    reg [CNT_W-1:0] clk_cnt;
    reg [2:0]       bit_idx;
    reg [7:0]       shift_reg;
    // 2-stage synchronizer for rx_in (meta-stability guard)
    reg rx_sync1, rx_sync2;
    always @(posedge clk) begin
        rx_sync1 <= rx_in;
        rx_sync2 <= rx_sync1;
    end
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state     <= IDLE;
            clk_cnt   <= 0;
            bit_idx   <= 0;
            shift_reg <= 8'd0;
            rx_data   <= 8'd0;
            rx_valid  <= 1'b0;
        end else begin
            rx_valid <= 1'b0;  // pulse
            case (state)
                IDLE: begin
                    if (!rx_sync2) begin  // falling edge detected
                        state   <= START;
                        clk_cnt <= 0;
                    end
                end
                START: begin
                    if (clk_cnt == HALF - 1) begin
                        if (!rx_sync2) begin  // confirm start bit at midpoint
                            clk_cnt <= 0;
                            state   <= DATA;
                            bit_idx <= 0;
                        end else
                            state <= IDLE;  // false start
                    end else
                        clk_cnt <= clk_cnt + 1;
                end
                DATA: begin
                    if (clk_cnt == DIV - 1) begin
                        clk_cnt          <= 0;
                        shift_reg[bit_idx] <= rx_sync2;
                        if (bit_idx == 7)
                            state <= STOP;
                        else
                            bit_idx <= bit_idx + 1;
                    end else
                        clk_cnt <= clk_cnt + 1;
                end
                STOP: begin
                    if (clk_cnt == DIV - 1) begin
                        state    <= IDLE;
                        rx_data  <= shift_reg;
                        rx_valid <= 1'b1;
                    end else
                        clk_cnt <= clk_cnt + 1;
                end
            endcase
        end
    end
endmodule

环回测试顶层--uart_loopback.v

cpp 复制代码
// ============================================================
// Module: uart_loopback
// Connects TX output directly to RX input for verification
// ============================================================
module uart_loopback #(
    parameter CLK_FREQ = 100_000_000,
    parameter BAUD     = 115200
)(
    input  wire       clk,
    input  wire       rst_n,
    input  wire [7:0] tx_data,
    input  wire       tx_start,
    output wire [7:0] rx_data,
    output wire       rx_valid,
    output wire       tx_busy
);
    wire loop_wire;  // internal loopback wire
    uart_tx #(.CLK_FREQ(CLK_FREQ), .BAUD(BAUD)) u_tx (
        .clk      (clk),
        .rst_n    (rst_n),
        .tx_data  (tx_data),
        .tx_start (tx_start),
        .tx_out   (loop_wire),
        .tx_busy  (tx_busy)
    );
    uart_rx #(.CLK_FREQ(CLK_FREQ), .BAUD(BAUD)) u_rx (
        .clk      (clk),
        .rst_n    (rst_n),
        .rx_in    (loop_wire),   // loopback: tx_out → rx_in
        .rx_data  (rx_data),
        .rx_valid (rx_valid)
    );
endmodule

Testbench--tb_uart_loopback.v

cpp 复制代码
`timescale 1ns/1ps
module tb_uart_loopback;
    // Use smaller divider for faster simulation
    localparam CLK_FREQ = 10_000_000;  // 10 MHz
    localparam BAUD     = 9600;
    reg        clk = 0;
    reg        rst_n;
    reg  [7:0] tx_data;
    reg        tx_start;
    wire [7:0] rx_data;
    wire       rx_valid;
    wire       tx_busy;
    // 10 MHz clock
    always #50 clk = ~clk;
    uart_loopback #(.CLK_FREQ(CLK_FREQ), .BAUD(BAUD)) uut (
        .clk      (clk),
        .rst_n    (rst_n),
        .tx_data  (tx_data),
        .tx_start (tx_start),
        .rx_data  (rx_data),
        .rx_valid (rx_valid),
        .tx_busy  (tx_busy)
    );
    integer pass_cnt, fail_cnt;
    initial begin
        $dumpfile("uart_loopback.vcd");
        $dumpvars(0, tb_uart_loopback);
        rst_n    = 0;
        tx_data  = 0;
        tx_start = 0;
        pass_cnt = 0;
        fail_cnt = 0;
        #200;
        rst_n = 1;
        #500;
        // Send test patterns
        send_byte(8'h55);  // 0101_0101
        send_byte(8'hAA);  // 1010_1010
        send_byte(8'h00);
        send_byte(8'hFF);
        send_byte(8'hA5);
        send_byte(8'h3C);
        #1_000_000;  // wait for last byte
        $display("\n===== UART Loopback Results =====");
        $display("PASS: %0d   FAIL: %0d", pass_cnt, fail_cnt);
        $finish;
    end
    task send_byte;
        input [7:0] data;
        begin
            while (tx_busy) @(posedge clk);
            @(posedge clk);
            tx_data  = data;
            tx_start = 1;
            @(posedge clk);
            tx_start = 0;
            // Wait for rx_valid
            @(posedge rx_valid);
            if (rx_data == data) begin
                $display("PASS: sent 0x%0h  recv 0x%0h", data, rx_data);
                pass_cnt = pass_cnt + 1;
            end else begin
                $display("FAIL: sent 0x%0h  recv 0x%0h", data, rx_data);
                fail_cnt = fail_cnt + 1;
            end
            #50_000;  // gap between bytes
        end
    endtask
endmodule

4.3 仿真测试

Vivado 仿真执行步骤

  1. 添加4个源文件:uart_tx.v、uart_rx.v为Design Sources;uart_loopback.v为Design Source + 模块例化;tb_uart_loopback.v为Simulation Source。
  2. 运行Behavioral Simulation:观察tx_out的UART帧波形,确认start bit → 8 data bits (LSB first) → stop bit。
  3. 检查环回结果:Tcl Console 查看$display 输出,验证所有6个测试字节均PASS。
  4. 硬件验证(可选):添加Constraints → 分配 UART TX/RX 引脚 → Generate Bitstream → 使用串口终端 115200 baud测试。

4.4 综合执行

添加并编写XDC约束à综合Run Synthesisà实现Run Implementationà生成位流Generate Bitstreamà下载à运行。

UART串口通信--constraints_uart.xdc

端口定义:USB-UART桥接芯片(115200 baud, 8N1)。LED0指示TX busy,LED1指示 X valid。SW0手动触发发送测试字节。

UART 串口通信--constraints_uart.xdc

端口定义:USB-UART桥接芯片 (115200 baud, 8N1)。LED0 指示TX busy,LED1指示 RX valid。SW0手动触发发送测试字节。

cpp 复制代码
## ============================================================
## Project: uart_loopback
## Board:   Digilent Basys3 (XC7A35T-1CPG236C)
## File:    constraints_uart.xdc
## ============================================================
## ── 系统时钟 (100 MHz) ──
set_property PACKAGE_PIN W5        [get_ports clk]
set_property IOSTANDARD LVCMOS33   [get_ports clk]
create_clock -add -name sys_clk -period 10.00 [get_ports clk]
## ── USB-UART 引脚 (通过 MicroUSB 口连接 PC) ──
set_property PACKAGE_PIN A18       [get_ports uart_tx]    ## FPGA TX → PC RX
set_property IOSTANDARD LVCMOS33   [get_ports uart_tx]
set_property PACKAGE_PIN B18       [get_ports uart_rx]    ## PC TX → FPGA RX
set_property IOSTANDARD LVCMOS33   [get_ports uart_rx]
## ── 状态指示 LED ──
set_property PACKAGE_PIN U16       [get_ports led_tx_busy]  ## LED0 --- TX 忙
set_property IOSTANDARD LVCMOS33   [get_ports led_tx_busy]
set_property PACKAGE_PIN E19       [get_ports led_rx_valid] ## LED1 --- RX 有效
set_property IOSTANDARD LVCMOS33   [get_ports led_rx_valid]
## ── 手动触发发送 ──
set_property PACKAGE_PIN V17       [get_ports tx_trigger]   ## SW0 --- 触发发送
set_property IOSTANDARD LVCMOS33   [get_ports tx_trigger]

5 MicroBlaze-V SoC

5.1 方案规划

MicroBlaze-V SoC--按键输入 / LED 指示:基于MicroBlaze-V软核处理器搭建SoC系统,在Vivado中完成Block Design,在Vitis中编写嵌入式C程序实现按键读取与LED控制。

MicroBlaze-V是Xilinx 32位RISC软核处理器,在Vivado Block Design中搭建如下图。

5.2 系统设计

++Vivado Block Design++ ++创建步骤++

1 创建新工程:File → New Project → RTL Project → 选择目标FPGA(如XC7A35T)。勾选Do not specify source。

2 创建Block Design:Flow Navigator → IP INTEGRATOR → Create Block Design → 命名为mb_soc

3 添加MicroBlaze-V处理器:点击Add IP → 搜索MicroBlaze-V→ 双击添加。

4 运行Block Automation:点击顶部 Run Block Automation → 勾选 Clock → 确认。系统自动添加 Clock Wizard、Clock & Reset_Wizard、MDMDebug,用于JTAG下载和在线调试、ILMB/DLDM模块。进而双击Clock Wizard,选择single clock。

5 添加AXI GPIO (按键输入):Add IP → AXI GPIO → 双击配置:GPIO Width = 4,勾选 All Inputs。

6 添加AXI GPIO (LED 输出):再添加一个 AXI GPIO → 配置:GPIO Width = 4,勾选 All Outputs。

7 连接 AXI 总线:点击 Run Connection Automation → 勾选所有模块选项 → 确认。系统自动完成各个模块连接线,如图6-14所示。

8 生成输出产品 & 封装

右键Block Design → Generate Output Products → 全局 → Generate。

然后在sources下的mb_soc.bd上,右键菜单,选择Create HDL Wrapper → Let Vivado manage → OK。

5.3 综合实现

1 添加约束文件

Add Sources → Add Constraints → 添加constraints.xdc

MicroBlaze-V SoC--constraints_mb_soc.xdc

端口定义:与Block Design中GPIO端口名对应。btn_tri_i3:0→ AXI GPIO输入(按键),led_tri_o3:0→ AXI GPIO输出(LED)。顶层wrapper端口名需与Block Design一致。

cpp 复制代码
# ============================================================
## Project: MicroBlaze-V SoC (按键输入 / LED 指示)
## Board:   Digilent Basys3 (XC7A35T-1CPG236C)
## File:    constraints_mb_soc.xdc
## ============================================================
## 注意: 端口名必须与 Block Design wrapper 一致
## 如果 wrapper 自动生成,请根据实际端口名调整
## ── 系统时钟 (100 MHz) ──
set_property PACKAGE_PIN W5        [get_ports clk]
set_property IOSTANDARD LVCMOS33   [get_ports clk]
create_clock -add -name sys_clk -period 10.00 [get_ports clk]
## ── 复位 (CPU_RESETN 按键,低电平有效) ──
set_property PACKAGE_PIN R2        [get_ports rst_n]
set_property IOSTANDARD LVCMOS33   [get_ports rst_n]
## ── 按键输入 BTN[3:0] → AXI GPIO (gpio_btn_io) ──
set_property PACKAGE_PIN U18       [get_ports {btn_tri_i[0]}]  ## BTN0
set_property IOSTANDARD LVCMOS33   [get_ports {btn_tri_i[0]}]
set_property PACKAGE_PIN T18       [get_ports {btn_tri_i[1]}]  ## BTN1
set_property IOSTANDARD LVCMOS33   [get_ports {btn_tri_i[1]}]
set_property PACKAGE_PIN W19       [get_ports {btn_tri_i[2]}]  ## BTN2
set_property IOSTANDARD LVCMOS33   [get_ports {btn_tri_i[2]}]
set_property PACKAGE_PIN T17       [get_ports {btn_tri_i[3]}]  ## BTN3
set_property IOSTANDARD LVCMOS33   [get_ports {btn_tri_i[3]}]
## ── LED 输出 LED[3:0] ← AXI GPIO (gpio_led_io) ──
set_property PACKAGE_PIN U16       [get_ports {led_tri_o[0]}]  ## LED0
set_property IOSTANDARD LVCMOS33   [get_ports {led_tri_o[0]}]
set_property PACKAGE_PIN E19       [get_ports {led_tri_o[1]}]  ## LED1
set_property IOSTANDARD LVCMOS33   [get_ports {led_tri_o[1]}]
set_property PACKAGE_PIN U19       [get_ports {led_tri_o[2]}]  ## LED2
set_property IOSTANDARD LVCMOS33   [get_ports {led_tri_o[2]}]
set_property PACKAGE_PIN V19       [get_ports {led_tri_o[3]}]  ## LED3
set_property IOSTANDARD LVCMOS33   [get_ports {led_tri_o[3]}]

2 综合实现

Run Synthesis → Run Implementation → Generate Bitstream

综合后检查 Utilization → 实现后检查 Timing → 生成比特流。

3 导出硬件到Vitis

File → Export → Export Hardware → 勾选Include bitstream→ Export。

5.4 嵌入应用

1 Vitis Embadded Development 嵌入式开发

通过FPGAs_AdaptiveSoCs_Unified_SDI_2025.2_1114_2157_Win64.exe,选择安装Vitis Embadded Development而不是Vitis,特别说明。

操作步骤:

A **.**启动Vitis开始菜单 → AMD Xilinx → Vitis (或从Vivado: Tools → Launch Vitis IDE,通常会自动启动Vitis)。

B **.**创建 Platform Component(生成BSP):File → New → Platform Component。

C **.**创建Application Component:File → New → Application Component。

D **.**编写C代码(main.c)

注意:出现include头文件找不到,可以通过build完成构建,就引入了相应头文件,相应C程序中的错误提示也没有了。

cpp 复制代码
/* ============================================================
 * Enhanced: Button toggles between LED display modes
 * BTN0: cycle mode  |  BTN1: manual bit 0  |  BTN2-3: reserved
 * Mode 0: all off  |  Mode 1: all on  |  Mode 2: walking LED
 * ============================================================ */
#include "xparameters.h"
#include "xgpio.h"
#include "xil_printf.h"
XGpio btn_gpio, led_gpio;
void delay_ms(u32 ms) {
    volatile u32 cnt = ms * 5000;
    while (cnt--) __asm__ volatile("nop");
}
int main() {
    u32 btn, prev_btn = 0;
    u8  mode = 0;
    u8  walk_bit = 0;
    XGpio_Initialize(&btn_gpio, XPAR_AXI_GPIO_0_BASEADDR);
    XGpio_Initialize(&led_gpio, XPAR_AXI_GPIO_1_BASEADDR);
    XGpio_SetDataDirection(&btn_gpio, 1, 0xF);
    XGpio_SetDataDirection(&led_gpio, 1, 0x0);
    xil_printf("Enhanced Button-LED Demo\r\n");
    xil_printf("BTN0: cycle mode | BTN1: manual mode\r\n");
    while (1) {
        btn = XGpio_DiscreteRead(&btn_gpio, 1) & 0xF;
        // Detect button press (rising edge)
        if ((btn & 0x1) && !(prev_btn & 0x1)) {
            mode = (mode + 1) % 4;
            xil_printf("Mode: %d\r\n", mode);
        }
        // Execute current mode
        switch (mode) {
            case 0:  // all off
                XGpio_DiscreteWrite(&led_gpio, 1, 0x0);
                break;
            case 1:  // all on
                XGpio_DiscreteWrite(&led_gpio, 1, 0xF);
                break;
            case 2:  // walking LED
                XGpio_DiscreteWrite(&led_gpio, 1, (1 << walk_bit));
                walk_bit = (walk_bit + 1) % 4;
                break;
            case 3:  // mirror buttons
                XGpio_DiscreteWrite(&led_gpio, 1, btn);
                break;
        }
        prev_btn = btn;
        delay_ms(250);  // ~4 Hz update for walking LED
    }
}

2 下载与硬件运行

A **.**连接硬件并下载比特流

USB 连接 Basys3 → 在 Vivado 中: Open Target → Auto Connect → Program Device → 选择生成的.bit文件 → Program。

B 在Vitis中下载并运行程序:右键mb_soc_app→ Run As → Launch on Hardware (System Debugger)。

程序将自动下载至 BRAM 并开始执行。

C **.**验证结果:按下 BTN0~BTN3,对应的LED0~LED3应实时点亮。

相关推荐
kaizq5 小时前
典型片上系统FPGA_SoC在线设计仿真实践
片上系统soc·mulerun·makerchip·ima-copilot-ds·stepfpga·cpulator·fpga在线开发
kaizq15 天前
在线设计模仿平台StepFPGA应用实践
fpga开发·verilog编程·在线设计仿真·小脚丫stepfpga·图形化设计·risc-v_soc·ima-copilot-ds
kaizq23 天前
MuleRun助力MakerChip-FPGA在线编程模拟仿真操练
fpga开发·verilog·龙虾机器人·mulerun·makerchip·在线模拟仿真
cuguanren3 个月前
MuleRun vs OpenClaw vs 网页服务:云端安全与本地自由的取舍之道
安全·大模型·llm·agent·智能体·openclaw·mulerun