典型片上系统FPGA_SoC在线设计仿真实践

立足在线FPGA设计仿真平台StepFPGA、MakerChip与CPUlator,借助人工智能AIIMA-Copilot-DS与MuleRun工具,使用Verilog\SystemVerilog语言,展开了一系列的典型综合逻辑电路编码设计仿真及其虚拟动态实景与波形展现的FPGA_SoC项目:简易八位机、主流RISC-V、五级流水线RISC-V、有限数字滤波FIR、Nios交互指示、RISC-V中断驱动。

1 StepFPGA--简易八位机SoC

1.1 方案规划

简易总线架构 + 定时器中断 + PWM输出,驱动LED呼吸灯效果:设计一个带简易总线的SoC系统,包含8位软核处理器、定时器外设(支持中断)、PWM输出外设和总线译码器。处理器通过总线配置定时器和PWM参数,实现LED呼吸灯效果。

|-----------|--------------|-------------------------|
| 址范围 | 外设 | 说明 |
| 0x00-0x7F | 数据RAM | 128字节通用数据 |
| 0x80 | TIMER_CTRL | 定时器控制 0使能 1中断使能 |
| 0x81 | TIMER_PRESET | 定时器预设值 (计数上限) |
| 0x82 | TIMER_COUNT | 定时器当前值 (只读) |
| 0x83 | TIMER_STATUS | 中断标志 0溢出 (写1清除) |
| 0x90 | PWM_CTRL | PWM控制 0使能 |
| 0x91 | PWM_DUTY | PWM占空比 (0-255) |

|--------------|---------------|
| 模块 | 功能 |
| simple_cpu | 8位寄存器型软核处理器 |
| bus_decoder | 地址译码与总线仲裁 |
| timer_periph | 8位定时器+中断 |
| pwm_periph | 8位PWM输出 |
| data_ram | 128×8bit数据RAM |
| pwm_soc_top | 顶层集成 |

1.2 编码实现

1 simple_cpu.v

cpp 复制代码
// ============================================
// simple_cpu.v --- 8位寄存器型软核处理器: 4个通用寄存器 R0-R3, 总线接口
// ============================================
module simple_cpu (
    input             clk,
    input             rst_n,
    input             irq,        // 中断请求    
    output reg [7:0] bus_addr,    // 总线接口
    output reg [7:0] bus_wdata,
    input      [7:0] bus_rdata,
    output reg        bus_rd,
    output reg        bus_wr
);
// 指令格式: [15:12]OP [11:10]Rd [9:8]Rs [7:0]Imm
localparam OP_NOP   = 4'h0,
           OP_LDI   = 4'h1,  // Rd = Imm
           OP_LD    = 4'h2,  // Rd = MEM[Imm]
           OP_ST    = 4'h3,  // MEM[Imm] = Rd
           OP_ADD   = 4'h4,  // Rd = Rd + Rs
           OP_SUB   = 4'h5,  // Rd = Rd - Rs
           OP_JMP   = 4'h6,  // PC = Imm
           OP_JNZ   = 4'h7,  // if Rd!=0: PC = Imm
           OP_ADDI  = 4'h8,  // Rd = Rd + Imm
           OP_SUBI  = 4'h9,  // Rd = Rd - Imm
           OP_RETI  = 4'hA;  // 中断返回
localparam S_FETCH   = 3'd0,
           S_DECODE  = 3'd1,
           S_EXECUTE = 3'd2,
           S_MEMRD   = 3'd3,
           S_MEMWR   = 3'd4;
reg [2:0]  state;
reg [7:0]  pc;
reg [7:0]  regs [0:3];   // R0-R3
reg [15:0] ir;
reg [7:0]  saved_pc;     // 中断保存PC
reg        in_irq;       // 中断中标志
wire [3:0] opcode  = ir[15:12];
wire [1:0] rd_sel  = ir[11:10];
wire [1:0] rs_sel  = ir[9:8];
wire [7:0] imm     = ir[7:0];
// 指令ROM (内嵌程序)
reg [15:0] prog_rom [0:63];
initial begin
    // 主程序: 配置Timer和PWM, 循环递增占空比
    // 0: LDI R0, 0x03      ; Timer使能+中断使能
    prog_rom[0]  = 16'h1003;
    // 1: ST  R0, 0x80      ; 写TIMER_CTRL
    prog_rom[1]  = 16'h3080;
    // 2: LDI R0, 0xFF      ; 定时器预设值255
    prog_rom[2]  = 16'h10FF;
    // 3: ST  R0, 0x81      ; 写TIMER_PRESET
    prog_rom[3]  = 16'h3081;
    // 4: LDI R0, 0x01      ; PWM使能
    prog_rom[4]  = 16'h1001;
    // 5: ST  R0, 0x90      ; 写PWM_CTRL
    prog_rom[5]  = 16'h3090;
    // 6: LDI R1, 0x00      ; R1 = 占空比初始值
    prog_rom[6]  = 16'h1400;
    // 7: ST  R1, 0x91      ; 写PWM_DUTY = R1
    prog_rom[7]  = 16'h3491;
    // 8: ADDI R1, 0x10     ; R1 += 16 (递增占空比)
    prog_rom[8]  = 16'h8410;
    // 9: JMP 0x07          ; 循环写PWM
    prog_rom[9]  = 16'h6007;
    // ISR入口 (地址 0x30): 清除Timer中断标志
    // 48: LDI R2, 0x01
    prog_rom[48] = 16'h1801;
    // 49: ST  R2, 0x83    ; 写TIMER_STATUS清标志
    prog_rom[49] = 16'h3883;
    // 50: RETI
    prog_rom[50] = 16'hA000;
end
integer i;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state    <= S_FETCH;
        pc       <= 8'd0;
        ir       <= 16'd0;
        bus_rd   <= 0;
        bus_wr   <= 0;
        in_irq   <= 0;
        saved_pc <= 0;
        for (i = 0; i < 4; i = i + 1) regs[i] <= 8'd0;
    end else begin
        bus_rd <= 0;
        bus_wr <= 0;
        case (state)
            S_FETCH: begin
                // 中断检测 (仅在非中断状态)
                if (irq && !in_irq) begin
                    saved_pc <= pc;
                    pc       <= 8'h30; // ISR入口
                    in_irq   <= 1;
                end
                ir    <= prog_rom[pc[5:0]];
                state <= S_DECODE;
            end
            S_DECODE: begin
                state <= S_EXECUTE;
            end
            S_EXECUTE: begin
                case (opcode)
                    OP_NOP: ;
                    OP_LDI: regs[rd_sel] <= imm;
                    OP_LD: begin
                        bus_addr <= imm;
                        bus_rd   <= 1;
                        state    <= S_MEMRD;
                    end
                    OP_ST: begin
                        bus_addr  <= imm;
                        bus_wdata <= regs[rd_sel];
                        bus_wr    <= 1;
                        state     <= S_MEMWR;
                    end
                    OP_ADD:  regs[rd_sel] <= regs[rd_sel] + regs[rs_sel];
                    OP_SUB:  regs[rd_sel] <= regs[rd_sel] - regs[rs_sel];
                    OP_ADDI: regs[rd_sel] <= regs[rd_sel] + imm;
                    OP_SUBI: regs[rd_sel] <= regs[rd_sel] - imm;
                    OP_JMP:  pc <= imm;
                    OP_JNZ:  if (regs[rd_sel] != 0) pc <= imm;
                    OP_RETI: begin
                        pc     <= saved_pc;
                        in_irq <= 0;
                    end
                endcase
                if (opcode != OP_LD && opcode != OP_ST) begin
                    if (opcode != OP_JMP &&
                        !(opcode == OP_JNZ && regs[rd_sel] != 0) &&
                        opcode != OP_RETI)
                        pc <= pc + 1;
                    state <= S_FETCH;
                end
            end
            S_MEMRD: begin
                regs[rd_sel] <= bus_rdata;
                pc    <= pc + 1;
                state <= S_FETCH;
            end
            S_MEMWR: begin
                pc    <= pc + 1;
                state <= S_FETCH;
            end
        endcase
    end
end
endmodule

2 bus_coder.v

cpp 复制代码
// ============================================
// bus_decoder.v --- 地址译码器
// ============================================
module bus_decoder (
    input      [7:0] addr,
    input      [7:0] wdata,
    input             rd,
    input             wr,
    output reg [7:0] rdata,
    // RAM
    output            ram_cs,
    input      [7:0] ram_rdata,
    // Timer
    output            timer_cs,
    input      [7:0] timer_rdata,
    // PWM
    output            pwm_cs,
    input      [7:0] pwm_rdata
);
// 地址译码
assign ram_cs   = (addr[7] == 1'b0);              // 0x00-0x7F
assign timer_cs = (addr[7:4] == 4'h8);              // 0x80-0x8F
assign pwm_cs   = (addr[7:4] == 4'h9);            // 0x90-0x9F
// 读数据多路选择
always @(*) begin
    case (1'b1)
        ram_cs:   rdata = ram_rdata;
        timer_cs: rdata = timer_rdata;
        pwm_cs:   rdata = pwm_rdata;
        default:  rdata = 8'h00;
    endcase
end
endmodule

3 temer_periph.v

cpp 复制代码
// ============================================
// timer_periph.v --- 8位定时器外设 (带中断), 寄存器: CTRL(+0) PRESET(+1) COUNT(+2) STATUS(+3)
// ============================================
module timer_periph (
    input            clk,
    input            rst_n,
    input            cs,          // 片选
    input      [1:0] addr,        // 寄存器偏移
    input      [7:0] wdata,
    output reg [7:0] rdata,
    input            rd,
    input            wr,
    output           irq          // 中断输出
);
reg [7:0] ctrl;     // [0]使能 [1]中断使能
reg [7:0] preset;   // 预设计数值
reg [7:0] counter;  // 当前计数值
reg       ovf_flag; // 溢出标志
wire timer_en  = ctrl[0];
wire irq_en    = ctrl[1];
assign irq     = ovf_flag & irq_en;
// 寄存器写
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        ctrl     <= 8'd0;
        preset   <= 8'hFF;
        counter  <= 8'd0;
        ovf_flag <= 0;
    end else begin
        // 写寄存器
        if (cs && wr) begin
            case (addr)
                2'd0: ctrl   <= wdata;
                2'd1: preset <= wdata;
                2'd3: ovf_flag <= ovf_flag & ~wdata[0]; // 写1清除
            endcase
        end
        // 计数器逻辑
        if (timer_en) begin
            if (counter >= preset) begin
                counter  <= 8'd0;
                ovf_flag <= 1'b1;
            end else
                counter <= counter + 8'd1;
        end
    end
end
// 寄存器读
always @(*) begin
    rdata = 8'd0;
    if (cs && rd) begin
        case (addr)
            2'd0: rdata = ctrl;
            2'd1: rdata = preset;
            2'd2: rdata = counter;
            2'd3: rdata = {7'd0, ovf_flag};
        endcase
    end
end
endmodule

4 pwm_periph.v

cpp 复制代码
// ============================================
// pwm_periph.v --- 8位PWM输出外设, 寄存器: CTRL(+0) DUTY(+1)
// ============================================
module pwm_periph (
    input            clk,
    input            rst_n,
    input            cs,
    input      [0:0] addr,      // 1位偏移
    input      [7:0] wdata,
    output reg [7:0] rdata,
    input            rd,
    input            wr,
    output           pwm_out    // PWM输出
);
reg [7:0] ctrl;    // [0]使能
reg [7:0] duty;    // 占空比 (0=全灭, 255=全亮)
reg [7:0] counter; // 自由运行计数器

wire pwm_en = ctrl[0];
assign pwm_out = pwm_en && (counter < duty);
// PWM计数器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        ctrl    <= 8'd0;
        duty    <= 8'd0;
        counter <= 8'd0;
    end else begin
        counter <= counter + 8'd1; // 自由运行,自然溢出
        if (cs && wr) begin
            case (addr)
                1'd0: ctrl <= wdata;
                1'd1: duty <= wdata;
            endcase
        end
    end
end
// 读寄存器
always @(*) begin
    rdata = 8'd0;
    if (cs && rd) begin
        case (addr)
            1'd0: rdata = ctrl;
            1'd1: rdata = duty;
        endcase
    end
end
endmodule

5 data_ram.v

cpp 复制代码
// ============================================
// data_ram.v --- 128×8bit数据RAM
// ============================================
module data_ram (
    input            clk,
    input            cs,
    input      [6:0] addr,
    input      [7:0] wdata,
    output reg [7:0] rdata,
    input            rd,
    input            wr
);
reg [7:0] mem [0:127];
always @(posedge clk) begin
    if (cs && wr)
        mem[addr] <= wdata;
end
always @(*) begin
    rdata = 8'd0;
    if (cs && rd)
        rdata = mem[addr];
end
endmodule

6 pwm_soc_top.v

cpp 复制代码
// ============================================
// pwm_soc_top.v --- Timer/PWM SoC顶层
// ============================================
module pwm_soc_top (
    input        clk_12m,
    input        rst_n,
    output       pwm_led      // PWM驱动LED
);
wire [7:0] bus_addr, bus_wdata, bus_rdata;
wire       bus_rd, bus_wr;
wire       irq;
// 外设片选
wire ram_cs, timer_cs, pwm_cs;
wire [7:0] ram_rdata, timer_rdata, pwm_rdata;
simple_cpu u_cpu (
    .clk(clk_12m), .rst_n(rst_n), .irq(irq),
    .bus_addr(bus_addr), .bus_wdata(bus_wdata),
    .bus_rdata(bus_rdata), .bus_rd(bus_rd), .bus_wr(bus_wr)
);
bus_decoder u_bus (
    .addr(bus_addr), .wdata(bus_wdata), .rd(bus_rd), .wr(bus_wr),
    .rdata(bus_rdata),
    .ram_cs(ram_cs), .ram_rdata(ram_rdata),
    .timer_cs(timer_cs), .timer_rdata(timer_rdata),
    .pwm_cs(pwm_cs), .pwm_rdata(pwm_rdata)
);
data_ram u_ram (
    .clk(clk_12m), .cs(ram_cs),
    .addr(bus_addr[6:0]),
    .wdata(bus_wdata), .rdata(ram_rdata),
    .rd(bus_rd), .wr(bus_wr)
);
timer_periph u_timer (
    .clk(clk_12m), .rst_n(rst_n), .cs(timer_cs),
    .addr(bus_addr[1:0]),
    .wdata(bus_wdata), .rdata(timer_rdata),
    .rd(bus_rd), .wr(bus_wr), .irq(irq)
);
pwm_periph u_pwm (
    .clk(clk_12m), .rst_n(rst_n), .cs(pwm_cs),
    .addr(bus_addr[0]),
    .wdata(bus_wdata), .rdata(pwm_rdata),
    .rd(bus_rd), .wr(bus_wr), .pwm_out(pwm_led)
);
endmodule

1.3 仿真测试

1 测试文件pwm_soc_tb.v

cpp 复制代码
// ============================================
// pwm_soc_tb.v --- Timer/PWM SoC仿真
// ============================================
`timescale 1ns / 1ps
module pwm_soc_tb;
reg  clk;
reg  rst_n;
wire pwm_led;

pwm_soc_top uut (
    .clk_12m (clk),
    .rst_n   (rst_n),
    .pwm_led (pwm_led)
);
// 12MHz 时钟
initial clk = 0;
always #41.67 clk = ~clk;
initial begin
    $dumpfile("pwm_wave.vcd");
    $dumpvars(0, pwm_soc_tb);
    rst_n = 0;
    #300;
    rst_n = 1;
    // 运行足够长观察多次PWM占空比变化
    // PWM周期 = 256 clk = 21.3μs @12MHz
    // 程序循环周期 ≈ 9条指令×3~4clk ≈ 30clk
    #200_000;  // 200μs

    $display("=== PWM SoC Simulation Complete ===");
    $finish;
end
// 监控PWM占空比变化
always @(uut.u_pwm.duty) begin
    $display("[%0t] PWM Duty changed to: %d (%.1f%%)",
             $time, uut.u_pwm.duty,
             uut.u_pwm.duty * 100.0 / 255.0);
end
// 监控定时器中断
always @(posedge uut.u_timer.irq)
    $display("[%0t] *** TIMER IRQ fired! counter=%d ***",
             $time, uut.u_timer.counter);
// 监控CPU指令执行
always @(posedge clk) begin
    if (rst_n && uut.u_cpu.state == 3'd2)
        $display("[%0t] CPU: PC=%02h OP=%01h R0=%02h R1=%02h",
                 $time, uut.u_cpu.pc,
                 uut.u_cpu.opcode,
                 uut.u_cpu.regs[0], uut.u_cpu.regs[1]);
end
endmodule

2 仿真波形

仿真运行步骤

A.创建项目:新建项目 "pwm_soc_demo",选择 EG4S20 芯片型号

B.添加6个设计文件:创建 simple_cpu.v、bus_decoder.v、timer_periph.v、pwm_periph.v、data_ram.v、pwm_soc_top.v

C.添加测试平台:创建pwm_soc_tb.v,仿真时间设为200μs以上

D.运行仿真:添加信号: clk, rst_n, bus_addr, bus_wdata, bus_wr, irq, pwm_out, duty, counter。可将 pwm_out 设为模拟显示模式观察占空比。

E.波形验证:观察 PWM duty 值从 0x00 递增 (每次+0x10): 0x00→0x10→0x20→...→0xF0→0x00(溢出)循环。PWM输出方波的高电平宽度应随之增加

展开一系列过程:逻辑综合à管脚分配àGPGA映射à文件下载。其中管脚分配过程如下图所示。在线系统没有EG4S20,这里以LCMXO2-4000HC替代,以演示相应进程。

2 MarkChip---FIR滤波器SoC

2.1 设计规划

|-----------------|------------------------|----------------------------------|
| 模块 | 功能 | 关键设计点 |
| fir_filter_8tap | 8阶FIR低通滤波器 | 16-bit数据/系数,40-bit累加器,组合MAC树 |
| regfile | 配置寄存器文件 | 8个寄存器,组合读/时序写,自清零位 |
| dma_ctrl | DMA数据搬运控制器 | 6态FSM,读→喂入FIR→写回 |
| axi_lite_slave | AXI-Lite从机总线接口 | 写通道3态FSM + 读通道2态 FSM |
| dsp_soc | SoC顶层集成 | AXI + test port 双路径 mux,1KB BRAM |
| top | MakerChip封装+ testbench | 复位同步,周期计数器,pass/fail 判定 |

2.2 编码实现

1 FIR 滤波器-- 直接型MAC 结构

关键实现:

  1. 系数通过case(ca)逐地址写入8个独立寄存器(避免RAM推断)
  2. 延迟线用展开赋值x0<=din; x1<=x0; ... 而非 for 循环(避免Verilator警告)
  3. MAC 用组合逻辑assign m = h0*x0 + ... + h7*x7(避免for循环累加的非阻塞赋值错误)

2 寄存器文件-- 内存映射

关键设计:

  1. 组合逻辑读:always @(*)实现零等待读
  2. 时序写:always @(posedge clk)单块内处理,无部分赋值冲突
  3. 自清零:cwe在写后下一个周期自动归零

3 DMA 控制器--6 态有限状态机

每个样本经历 5 个周期:读存储器 → 等待数据 → 喂入 FIR → 写回 → 循环。

4 AXI-Lite Slave-- 独立读写通道

关键特性:

  1. 支持 AW+W 同步到达(一个周期完成握手)
  2. 也支持 AW 先于 W 的分步握手
  3. 地址只取低 8 位(匹配寄存器文件地址空间)

5 程序编码

cpp 复制代码
// ============================================================================
// DSP SoC: 8-tap FIR + DMA + AXI-Lite --- MakerChip Compatible
// ============================================================================
/* verilator lint_off TIMESCALEMOD */
/* verilator lint_off UNUSEDSIGNAL */
/* verilator lint_off UNOPTFLAT */
/* verilator lint_off BLKANDNBLK */
/* verilator lint_off WIDTH */
/* verilator lint_off PINCONNECTEMPTY */
/* verilator lint_off DECLFILENAME */
/* verilator lint_off INITIALDLY */
/* verilator lint_off CASEINCOMPLETE */
/* verilator lint_off UNSIGNED */
/* verilator lint_off CASEOVERLAP */
`timescale 1ns/1ps
// ============================================================================
// 1. 8-TAP FIR FILTER
// ============================================================================
module fir_filter_8tap (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        en,
    input  logic        dv_in,
    input  logic [15:0] din,
    input  logic [2:0]  ca,
    input  logic [15:0] cd,
    input  logic        cwe,
    output logic        dv_out,
    output logic [39:0] dout
);
    logic signed [15:0] h0,h1,h2,h3,h4,h5,h6,h7;
    logic signed [15:0] x0,x1,x2,x3,x4,x5,x6,x7;
    logic signed [39:0] m;
    always @(posedge clk)
        if (cwe)
            case (ca)
                0: h0<=cd; 1: h1<=cd; 2: h2<=cd; 3: h3<=cd;
                4: h4<=cd; 5: h5<=cd; 6: h6<=cd; 7: h7<=cd;
            endcase
    always @(posedge clk)
        if (!rst_n) begin
            x0<=0; x1<=0; x2<=0; x3<=0; x4<=0; x5<=0; x6<=0; x7<=0;
        end else if (en && dv_in) begin
            x0<=din; x1<=x0; x2<=x1; x3<=x2;
            x4<=x3; x5<=x4; x6<=x5; x7<=x6;
        end
    assign m = h0*x0 + h1*x1 + h2*x2 + h3*x3
             + h4*x4 + h5*x5 + h6*x6 + h7*x7;
    always @(posedge clk)
        if (!rst_n) begin
            dout   <= 0;
            dv_out <= 0;
        end else begin
            dv_out <= 0;
            if (en && dv_in) begin
                dout   <= m;
                dv_out <= 1;
            end
        end
endmodule
// ============================================================================
// 2. REGISTER FILE
// ============================================================================
module regfile (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        wr,
    input  logic [7:0]  addr,
    input  logic [31:0] wd,
    output logic [31:0] rd,
    output logic [31:0] dma_src,
    output logic [31:0] dma_dst,
    output logic [15:0] dma_len,
    output logic        fir_en,
    output logic        dma_start,
    output logic [2:0]  ca,
    output logic [15:0] cd,
    output logic        cwe,
    output logic        irq_en,
    output logic        irq_out,
    input  logic        dma_done,
    input  logic        dma_busy,
    input  logic        fir_done
);
    logic [31:0] r_ctrl, r_irq, r_dma_src, r_dma_dst;
    logic [15:0] r_dma_len;
    assign dma_src   = r_dma_src;
    assign dma_dst   = r_dma_dst;
    assign dma_len   = r_dma_len;
    assign fir_en    = r_ctrl[0];
    assign dma_start = r_ctrl[1];
    assign irq_en    = r_irq[1];
    always @(*) begin
        rd = 32'h0;
        case (addr)
            8'h00: rd = r_ctrl;
            8'h04: rd = {28'b0, dma_busy, fir_done, dma_done, r_ctrl[0]};
            8'h08: rd = r_irq;
            8'h10: rd = r_dma_src;
            8'h14: rd = r_dma_dst;
            8'h18: rd = {16'b0, r_dma_len};
            8'h20: rd = {29'b0, ca};
            8'h24: rd = {16'b0, cd};
        endcase
    end
    always @(posedge clk)
        if (!rst_n) begin
            r_ctrl    <= 0;
            r_irq     <= 0;
            r_dma_src <= 0;
            r_dma_dst <= 0;
            r_dma_len <= 0;
            ca        <= 0;
            cd        <= 0;
            cwe       <= 0;
            irq_out   <= 0;
        end else begin
            cwe <= 0;
            if (wr) begin
                case (addr)
                    8'h00: r_ctrl    <= wd;
                    8'h08: r_irq     <= wd;
                    8'h10: r_dma_src <= wd;
                    8'h14: r_dma_dst <= wd;
                    8'h18: r_dma_len <= wd[15:0];
                    8'h20: ca        <= wd[2:0];
                    8'h24: begin cd <= wd[15:0]; cwe <= 1; end
                endcase
            end
            if ((dma_done || fir_done) && r_irq[1])
                irq_out <= 1;
            else if (r_irq[0])
                irq_out <= 0;
        end
endmodule
// ============================================================================
// 3. DMA CONTROLLER
// ============================================================================
module dma_ctrl (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        start,
    output logic        busy,
    output logic        done,
    input  logic [31:0] cfg_src,
    input  logic [31:0] cfg_dst,
    input  logic [15:0] cfg_len,
    output logic        mem_re,
    output logic [31:0] mem_ra,
    input  logic [15:0] mem_rd,
    input  logic        mem_rv,
    output logic        mem_we,
    output logic [31:0] mem_wa,
    output logic [15:0] mem_wd,
    output logic        fir_vld,
    output logic [15:0] fir_dat,
    input  logic        fir_rdy
);
    logic [2:0]  s;
    logic [15:0] cnt;
    logic [31:0] sp, dp;
    logic [15:0] db;
    always @(posedge clk)
        if (!rst_n) begin
            s<=0; busy<=0; done<=0; cnt<=0; sp<=0; dp<=0; db<=0;
            mem_re<=0; mem_ra<=0; mem_we<=0; mem_wa<=0; mem_wd<=0;
            fir_vld<=0; fir_dat<=0;
        end else begin
            done<=0; mem_re<=0; mem_we<=0; fir_vld<=0;
            case (s)
                0: if (start) begin
                       sp<=cfg_src; dp<=cfg_dst; cnt<=cfg_len;
                       busy<=1; s<=1;
                   end else busy<=0;
                1: if (cnt==0) s<=5;
                   else begin mem_re<=1; mem_ra<=sp; s<=2; end
                2: if (mem_rv) begin db<=mem_rd; s<=3; end
                3: begin fir_vld<=1; fir_dat<=db;
                       if (fir_rdy) s<=4; end
                4: begin mem_we<=1; mem_wa<=dp; mem_wd<=db;
                       sp<=sp+2; dp<=dp+2; cnt<=cnt-1; s<=1; end
                5: begin done<=1; busy<=0; s<=0; end
            endcase
        end
endmodule
// ============================================================================
// 4. AXI-LITE SLAVE
// ============================================================================
module axi_lite_slave (
    input  logic        clk,
    input  logic        rst_n,
    // Write address channel
    input  logic [31:0] awaddr,
    input  logic        awvalid,
    output logic        awready,
    // Write data channel
    input  logic [31:0] wdata,
    input  logic [3:0]  wstrb,
    input  logic        wvalid,
    output logic        wready,
    // Write response channel
    output logic [1:0]  bresp,
    output logic        bvalid,
    input  logic        bready,
    // Read address channel
    input  logic [31:0] araddr,
    input  logic        arvalid,
    output logic        arready,
    // Read data channel
    output logic [31:0] rdata,
    output logic [1:0]  rresp,
    output logic        rvalid,
    input  logic        rready,
    // Internal register interface
    output logic        o_wr_en,
    output logic [7:0]  o_wr_addr,
    output logic [31:0] o_wr_data,
    output logic        o_rd_en,
    output logic [7:0]  o_rd_addr,
    input  logic [31:0] i_rd_data
);
    // Write channel FSM
    logic [1:0] ws;
    localparam WS_IDLE=0, WS_W=1, WS_B=2;
    logic [7:0]  wa_lat;
    always @(posedge clk)
        if (!rst_n) begin
            ws         <= WS_IDLE;
            awready    <= 0;
            wready     <= 0;
            bvalid     <= 0;
            bresp      <= 2'b00;
            o_wr_en    <= 0;
            o_wr_addr  <= 0;
            o_wr_data  <= 0;
            wa_lat     <= 0;
        end else begin
            o_wr_en <= 0;
            case (ws)
                WS_IDLE: begin
                    awready <= 1;
                    wready  <= 1;
                    if (awvalid && wvalid) begin
                        wa_lat    <= awaddr[7:0];
                        o_wr_addr <= awaddr[7:0];
                        o_wr_data <= wdata;
                        o_wr_en   <= 1;
                        awready   <= 0;
                        wready    <= 0;
                        ws        <= WS_B;
                    end else if (awvalid) begin
                        wa_lat  <= awaddr[7:0];
                        awready <= 0;
                        ws      <= WS_W;
                    end else if (wvalid) begin
                        o_wr_data <= wdata;
                        o_wr_en   <= 1;
                        wready    <= 0;
                        ws        <= WS_B;
                    end
                end
                WS_W: begin
                    wready <= 1;
                    if (wvalid) begin
                        o_wr_addr <= wa_lat;
                        o_wr_data <= wdata;
                        o_wr_en   <= 1;
                        wready    <= 0;
                        ws        <= WS_B;
                    end
                end
                WS_B: begin
                    bvalid <= 1;
                    bresp  <= 2'b00;
                    if (bready) begin
                        bvalid <= 0;
                        ws     <= WS_IDLE;
                    end
                end
            endcase
        end

    // Read channel FSM
    logic [1:0] rs;
    localparam RS_IDLE=0, RS_R=1;
    always @(posedge clk)
        if (!rst_n) begin
            rs         <= RS_IDLE;
            arready    <= 0;
            rvalid     <= 0;
            rdata      <= 0;
            rresp      <= 2'b00;
            o_rd_en    <= 0;
            o_rd_addr  <= 0;
        end else begin
            o_rd_en <= 0;
            case (rs)
                RS_IDLE: begin
                    arready <= 1;
                    if (arvalid) begin
                        o_rd_addr <= araddr[7:0];
                        o_rd_en   <= 1;
                        arready   <= 0;
                        rs        <= RS_R;
                    end
                end
                RS_R: begin
                    rdata  <= i_rd_data;
                    rresp  <= 2'b00;
                    rvalid <= 1;
                    if (rready) begin
                        rvalid <= 0;
                        rs     <= RS_IDLE;
                    end
                end
            endcase
        end
endmodule
// ============================================================================
// 5. DSP SOC TOP
// ============================================================================
module dsp_soc (
    input  logic        clk,
    input  logic        rst_n,
    // AXI-Lite slave port
    input  logic [31:0] s_awaddr,
    input  logic        s_awvalid,
    output logic        s_awready,
    input  logic [31:0] s_wdata,
    input  logic [3:0]  s_wstrb,
    input  logic        s_wvalid,
    output logic        s_wready,
    output logic [1:0]  s_bresp,
    output logic        s_bvalid,
    input  logic        s_bready,
    input  logic [31:0] s_araddr,
    input  logic        s_arvalid,
    output logic        s_arready,
    output logic [31:0] s_rdata,
    output logic [1:0]  s_rresp,
    output logic        s_rvalid,
    input  logic        s_rready,
    // Test port (bypasses AXI for direct register access)
    input  logic        test_wr,
    input  logic [7:0]  test_addr,
    input  logic [31:0] test_wd,
    output logic [31:0] test_rd,
    output logic        irq
);
    // AXI slave outputs
    logic        axi_wr_en;
    logic [7:0]  axi_wr_addr;
    logic [31:0] axi_wr_data;
    logic        axi_rd_en;
    logic [7:0]  axi_rd_addr;
    // Mux: test port has priority over AXI
    logic        rf_wr;
    logic [7:0]  rf_addr;
    logic [31:0] rf_wd;
    assign rf_wr   = test_wr | axi_wr_en;
    assign rf_addr = test_wr ? test_addr : axi_wr_addr;
    assign rf_wd   = test_wr ? test_wd   : axi_wr_data;
    // Internal wires
    logic        fir_en, dma_start, irq_en;
    logic        dma_done, dma_busy, fir_done;
    logic [31:0] dma_src, dma_dst;
    logic [15:0] dma_len;
    logic [2:0]  ca;
    logic [15:0] cd;
    logic        cwe;
    logic        irq_out;
    logic        dma_mre;
    logic [31:0] dma_mra;
    logic [15:0] dma_mrd;
    logic        dma_mrv;
    logic        dma_mwe;
    logic [31:0] dma_mwa;
    logic [15:0] dma_mwd;
    logic        fir_vld;
    logic [15:0] fir_dat;
    logic [31:0] rf_rd;
    // BRAM 1KB
    logic [15:0] bram [0:511];
    wire [8:0] ri = dma_mra[9:1];
    wire [8:0] wi = dma_mwa[9:1];
    always @(posedge clk)
        if (dma_mwe && wi < 512) bram[wi] <= dma_mwd;
    assign dma_mrd = bram[ri];
    assign dma_mrv = dma_mre;
    // AXI-Lite slave
    axi_lite_slave u_axi (
        .clk(clk), .rst_n(rst_n),
        .awaddr(s_awaddr), .awvalid(s_awvalid), .awready(s_awready),
        .wdata(s_wdata),   .wstrb(s_wstrb),     .wvalid(s_wvalid),   .wready(s_wready),
        .bresp(s_bresp),   .bvalid(s_bvalid),   .bready(s_bready),
        .araddr(s_araddr), .arvalid(s_arvalid), .arready(s_arready),
        .rdata(s_rdata),   .rresp(s_rresp),     .rvalid(s_rvalid),   .rready(s_rready),
        .o_wr_en(axi_wr_en), .o_wr_addr(axi_wr_addr), .o_wr_data(axi_wr_data),
        .o_rd_en(axi_rd_en), .o_rd_addr(axi_rd_addr), .i_rd_data(rf_rd)
    );
    // Register file
    regfile u_rf (
        .clk(clk), .rst_n(rst_n),
        .wr(rf_wr), .addr(rf_addr), .wd(rf_wd), .rd(rf_rd),
        .dma_src(dma_src), .dma_dst(dma_dst), .dma_len(dma_len),
        .fir_en(fir_en), .dma_start(dma_start),
        .ca(ca), .cd(cd), .cwe(cwe), .irq_en(irq_en), .irq_out(irq_out),
        .dma_done(dma_done), .dma_busy(dma_busy), .fir_done(fir_done)
    );
    // DMA controller
    dma_ctrl u_dma (
        .clk(clk), .rst_n(rst_n),
        .start(dma_start), .busy(dma_busy), .done(dma_done),
        .cfg_src(dma_src), .cfg_dst(dma_dst), .cfg_len(dma_len),
        .mem_re(dma_mre), .mem_ra(dma_mra), .mem_rd(dma_mrd), .mem_rv(dma_mrv),
        .mem_we(dma_mwe), .mem_wa(dma_mwa), .mem_wd(dma_mwd),
        .fir_vld(fir_vld), .fir_dat(fir_dat), .fir_rdy(fir_en)
    );
    // FIR filter
    fir_filter_8tap u_fir (
        .clk(clk), .rst_n(rst_n),
        .en(fir_en), .dv_in(fir_vld), .din(fir_dat),
        .ca(ca), .cd(cd), .cwe(cwe),
        .dv_out(fir_done), .dout()
    );
    assign irq     = irq_out;
    assign test_rd = rf_rd;
endmodule
// ============================================================================
// 6. MAKERCHIP TOP
// ============================================================================
module top (
    input  logic clk,
    input  logic reset_async,
    output logic passed,
    output logic failed
);
    // Reset sync
    logic rst_n;
    logic [3:0] rchain;
    always @(posedge clk)
        if (reset_async) begin
            rchain <= 4'b0000;
            rst_n  <= 1'b0;
        end else begin
            rchain <= {rchain[2:0], 1'b1};
            rst_n  <= rchain[3];
        end
    // Cycle counter
    logic [9:0] cyc;
    always @(posedge clk)
        if (reset_async || !rst_n)
            cyc <= 0;
        else
            cyc <= cyc + 1;
    // Test signals
    logic        t_wr;
    logic [7:0]  t_addr;
    logic [31:0] t_wd;
    logic [31:0] t_rd;
    // AXI signals (tied off for now)
    logic [31:0] ax_awaddr, ax_araddr, ax_wdata;
    logic        ax_awvalid, ax_wvalid, ax_arvalid, ax_bready, ax_rready;
    logic [31:0] ax_rdata;
    logic [1:0]  ax_bresp, ax_rresp;
    logic        ax_awready, ax_wready, ax_bvalid, ax_arready, ax_rvalid;
    logic        dut_irq;
    // Testbench: t_wr stays high from cycle 6 onwards (proven approach)
    always @(posedge clk)
        if (reset_async || !rst_n) begin
            t_wr   <= 0;
            t_addr <= 0;
            t_wd   <= 0;
            passed <= 0;
            failed <= 0;
        end else begin
            if (cyc >= 10'd6) begin
                t_wr <= 1;
                if (cyc == 10'd6) begin
                    t_addr <= 8'h10;       // DMA_SRC
                    t_wd   <= 32'hDEAD_BEEF;
                end
            end
            // Check at cycle 10
            if (cyc == 10'd10) begin
                if (t_rd == 32'hDEAD_BEEF)
                    passed <= 1;
                else
                    failed <= 1;
            end
        end
    // Tie off AXI (not used in this test)
    assign ax_awaddr  = 0;
    assign ax_araddr  = 0;
    assign ax_wdata   = 0;
    assign ax_awvalid = 0;
    assign ax_wvalid  = 0;
    assign ax_arvalid = 0;
    assign ax_bready  = 0;
    assign ax_rready  = 0;
    // DUT
    dsp_soc u_dut (
        .clk(clk),
        .rst_n(rst_n),
        // AXI-Lite port
        .s_awaddr(ax_awaddr), .s_awvalid(ax_awvalid), .s_awready(ax_awready),
        .s_wdata(ax_wdata),   .s_wstrb(4'hF),         .s_wvalid(ax_wvalid),  .s_wready(ax_wready),
        .s_bresp(ax_bresp),   .s_bvalid(ax_bvalid),   .s_bready(ax_bready),
        .s_araddr(ax_araddr), .s_arvalid(ax_arvalid), .s_arready(ax_arready),
        .s_rdata(ax_rdata),   .s_rresp(ax_rresp),     .s_rvalid(ax_rvalid),  .s_rready(ax_rready),
        // Test port
        .test_wr(t_wr),
        .test_addr(t_addr),
        .test_wd(t_wd),
        .test_rd(t_rd),
        .irq(dut_irq)
    );
endmodule

2.3 模拟仿真

1 仿真测试

3 CPUIator NiosII交互指示SoC

3.1 系统及其创建

1 应用系统创建

  1. 浏览器访问CPUlator网站
  2. 选择Nios II处理器架构
  3. 加载预设配置(或使用自定义.gpj项目文件)

2 Nios II SoC 系统架构概览

3 外设地址映射表(CPUlator 预设配置)

|--------------------------|------------|--------|--------|------------------------|
| 外设模块 | 基地址 | 宽度 | 方向 | 功能说明 |
| jtag_uart_0 | 0x10001000 | --- | --- | JTAG 调试串口 |
| onchip_memory | 0x00000000 | 128KB | --- | 片上 RAM |
| sys_clk_timer | 0x10002000 | --- | --- | 系统时钟定时器 |
| pio_keys (按键) | 0x10000060 | 4 bit | 输入 | 4 个按键 KEY3:0 |
| pio_switches (开关) | 0x10000050 | 10 bit | 输入 | 10 位拨码开关 SW9:0 |
| pio_leds (LED) | 0x10000040 | 10 bit | 输出 | 10 个 LED 灯 LEDR9:0 |
| pio_hex_low (低两位数码管) | 0x10000020 | 32 bit | 输出 | HEX3~HEX0 |
| | | | | |

4 PIO 寄存器偏移

每个PIO模块包含以下寄存器(偏移相对于基地址):

|--------|----------------|--------------|------------------------|
| 偏移 | 寄存器 | 读/ | 说明 |
| 0x00 | Data | R/W | 数据寄存器:输入=读引脚状态,输出=写输出值 |
| 0x04 | Direction | R/W | 方向寄存器:0=输入,1=输出 |
| 0x08 | Interrupt Mask | R/W | 中断屏蔽寄存器 |
| 0x0C | Edge Capture | R/W | 边沿捕获寄存器 |

3.2 功能编码实现

完整的C语言编码,包括:头文件与宏定义、七段数码管编码表、外设驱动函数、主程序实现多模式交互

cpp 复制代码
/*==========================================================================
 * 文件: nios2_soc_io.c
 * 平台: CPUlator 在线仿真 --- Nios II (nios-de1soc 预设)
  * 功能: 按键/开关输入 + LED/七段数码管输出,4种运行模式
 *   模式0: SW[9:0] → LEDR[9:0] 镜像 + HEX2~0 显示开关值
 *   模式1: KEY0=+1, KEY1=-1 计数器,LED+HEX显示
 *   模式2: LED流水灯,KEY2=暂停/继续
 *   模式3: HEX5~0 十六进制自动递增计数器
 *   切换:  KEY3 下降沿切换模式
 *========================================================================*/
/* ========================== 基础类型 ========================== */
typedef unsigned int    uint32_t;
typedef unsigned short  uint16_t;
typedef unsigned char   uint8_t;
/* ========================== PIO 寄存器偏移 ========================== */
#define PIO_DATA_OFFSET       0x00
#define PIO_DIRECTION_OFFSET  0x04
#define PIO_IRQ_MASK_OFFSET   0x08
#define PIO_EDGE_CAP_OFFSET   0x0C
/* ========================== 外设基地址 (nios-de1soc) ========================== */
#define PIO_LEDS_BASE         0xff200000   /* LEDR[9:0] 输出           */
#define PIO_HEX_LOW_BASE      0xff200020   /* HEX3~HEX0 七段数码管     */
#define PIO_HEX_HIGH_BASE     0xff200030   /* HEX5~HEX4 七段数码管     */
#define PIO_SWITCHES_BASE     0xff200040   /* SW[9:0]  输入            */
#define PIO_KEYS_BASE         0xff200050   /* KEY[3:0] 输入            */
#define JTAG_UART_BASE        0xff201000   /* JTAG UART 调试串口       */
#define SYS_TIMER_BASE        0xff202000   /* 系统定时器               */
/* ========================== JTAG UART 寄存器偏移 ========================== */
#define JTAG_UART_DATA        0x00
#define JTAG_UART_CONTROL     0x04
/* ======================================================================
 *  I/O 访问 --- 内联汇编精确控制指令类型 *
 *  ldwio/stwio → 仅用于外设地址 (0xFF20xxxx, 绕过 cache)
 *  ldw/stw     → 编译器对普通 C 变量自动生成 (经过 cache) *
/* ======================================================================*/
static inline uint32_t IORD(uint32_t base, uint32_t reg) {
    uint32_t val;
    __asm__ volatile (
        "ldwio %0, 0(%1)"
        : "=r"(val)
        : "r"(base + reg)
    );
    return val;
}
static inline void IOWR(uint32_t base, uint32_t reg, uint32_t val) {
    __asm__ volatile (
        "stwio %0, 0(%1)"
        :
        : "r"(val), "r"(base + reg)
        : "memory"
    );
}
/* ======================================================================
 *  七段数码管段码表 (共阳极, 低电平点亮)-- 0 = 段亮, 1 = 段灭
 *    aaa         bit0 = a段     bit4 = e段
 *   f   b        bit1 = b段     bit5 = f段
 *    ggg         bit2 = c段     bit6 = g段
 *   e   c        bit3 = d段     bit7 = dp
 *    ddd dp
 * ======================================================================*/
static const uint8_t SEG7_TABLE[16] = {
    /* 0 */ 0xC0,  /* 1100 0000  a,b,c,d,e,f 亮        */
    /* 1 */ 0xF9,  /* 1111 1001  b,c 亮                */
    /* 2 */ 0xA4,  /* 1010 0100  a,b,d,e,g 亮          */
    /* 3 */ 0xB0,  /* 1011 0000  a,b,c,d,g 亮          */
    /* 4 */ 0x99,  /* 1001 1001  b,c,f,g 亮            */
    /* 5 */ 0x92,  /* 1001 0010  a,c,d,f,g 亮          */
    /* 6 */ 0x82,  /* 1000 0010  a,c,d,e,f,g 亮        */
    /* 7 */ 0xF8,  /* 1111 1000  a,b,c 亮              */
    /* 8 */ 0x80,  /* 1000 0000  全部亮                */
    /* 9 */ 0x90,  /* 1001 0000  a,b,c,d,f,g 亮        */
    /* A */ 0x88,  /* 1000 1000  a,b,c,e,f,g 亮        */
    /* b */ 0x83,  /* 1000 0011  c,d,e,f,g 亮          */
    /* C */ 0xC6,  /* 1100 0110  a,d,e,f 亮            */
    /* d */ 0xA1,  /* 1010 0001  b,c,d,e,g 亮          */
    /* E */ 0x86,  /* 1000 0110  a,d,e,f,g 亮          */
    /* F */ 0x8E   /* 1000 1110  a,e,f,g 亮            */
};
/* ========================== 全局变量 (无 volatile) ==================== */
typedef enum {
    MODE_SWITCH_MIRROR = 0,   /* 开关→LED 镜像            */
    MODE_KEY_COUNTER,          /* 按键计数器               */
    MODE_LED_SCAN,             /* 流水灯                   */
    MODE_HEX_COUNTER,          /* HEX 计数器               */
    MODE_COUNT
} run_mode_t;
static run_mode_t g_mode       = MODE_SWITCH_MIRROR;
static uint32_t   g_key_count  = 0;
static uint32_t   g_led_pat    = 0x001;
static uint32_t   g_hex_count  = 0;
static uint8_t    g_scan_pause = 0;
/* ==========================工具函数=====================================*/
static void delay(uint32_t count) {  // 延时
    while (count--) {
        __asm__ volatile ("nop");
    }
}
static uint32_t read_keys(void) {   // 按键读取 (低电平有效: 按下=0)
    return IORD(PIO_KEYS_BASE, PIO_DATA_OFFSET) & 0x0F;
}
static uint32_t read_switches(void) { //开关读取
    return IORD(PIO_SWITCHES_BASE, PIO_DATA_OFFSET) & 0x3FF;
}
static void write_leds(uint32_t val) { //LED 写入
    IOWR(PIO_LEDS_BASE, PIO_DATA_OFFSET, val & 0x3FF);
}
static uint32_t led_shift_left(uint32_t cur) { //LED 流水: 单灯左移
    uint32_t next = (cur << 1) & 0x3FF;
    if (next == 0) next = 0x001;
    return next;
}
/* ======================七段数码管驱动===================================
/* 在指定数码管位置显示一个十六进制数字
 *   pos:   0~5  (0=HEX0最右, 5=HEX5最左)
 *   value: 0~15
 * 布局:
 *   PIO_HEX_LOW  [31:0]  = HEX3(31:24) | HEX2(23:16) | HEX1(15:8) | HEX0(7:0)
 *   PIO_HEX_HIGH [31:0]  = (高位未用)   | HEX5(15:8)  | HEX4(7:0)
 */
static void hex_display_digit(uint8_t pos, uint8_t value) {
    uint32_t seg = SEG7_TABLE[value & 0x0F];
    uint32_t shift, mask, cur;
    if (pos < 4) {
        /* HEX0~3 → PIO_HEX_LOW */
        shift = pos * 8;
        mask  = ~(0xFFu << shift);
        cur   = IORD(PIO_HEX_LOW_BASE, PIO_DATA_OFFSET);
        cur   = (cur & mask) | (seg << shift);
        IOWR(PIO_HEX_LOW_BASE, PIO_DATA_OFFSET, cur);
    } else {
        /* HEX4~5 → PIO_HEX_HIGH */
        shift = (pos - 4) * 8;
        mask  = ~(0xFFu << shift);
        cur   = IORD(PIO_HEX_HIGH_BASE, PIO_DATA_OFFSET);
        cur   = (cur & mask) | (seg << shift);
        IOWR(PIO_HEX_HIGH_BASE, PIO_DATA_OFFSET, cur);
    }
}
/* 6位数码管显示24位十六进制数 */
static void hex_display_value(uint32_t value) {
    int i;
    for (i = 0; i < 6; i++) {
        hex_display_digit((uint8_t)i, (uint8_t)((value >> (i * 4)) & 0x0F));
    }
}
/* 清除全部数码管 (消隐: 全1) */
static void hex_display_clear(void) {
    IOWR(PIO_HEX_LOW_BASE,  PIO_DATA_OFFSET, 0xFFFFFFFF);
    IOWR(PIO_HEX_HIGH_BASE, PIO_DATA_OFFSET, 0xFFFFFFFF);
} 
/* ======================JTAG UART 调试输出============================*/
static void jtag_putchar(char c) {
    /* 等待发送 FIFO 有空位: control[31:16] = WSPACE > 0 */
    while ((IORD(JTAG_UART_BASE, JTAG_UART_CONTROL) & 0xFFFF0000) == 0) ;
    IOWR(JTAG_UART_BASE, JTAG_UART_DATA, (uint32_t)(uint8_t)c);
}
static void jtag_puts(const char *s) {
    while (*s) {
        if (*s == '\n') jtag_putchar('\r');
        jtag_putchar(*s++);
    }
}
static void jtag_put_hex(uint32_t value) {
    static const char hexch[] = "0123456789ABCDEF";
    int i; int started = 0;
    jtag_putchar('0');
    jtag_putchar('x');
    for (i = 28; i >= 0; i -= 4) {
        uint8_t nibble = (uint8_t)((value >> i) & 0x0F);
        if (nibble || started || i == 0) {
            jtag_putchar(hexch[nibble]);
            started = 1;
        }
    }
}
/* ========================各模式处理函数=================================*/
/* HEX5 始终显示当前模式编号 */
static void show_mode_indicator(void) {
    hex_display_digit(5, (uint8_t)g_mode);
}
/* ---- 模式0: 开关→LED 镜像 ---- */
static void mode_switch_mirror(void) {
    uint32_t sw = read_switches();
    write_leds(sw);
    /* HEX0=低4位, HEX1=中间4位, HEX2=SW9~8 */
    hex_display_digit(0, (uint8_t)(sw & 0x0F));
    hex_display_digit(1, (uint8_t)((sw >> 4) & 0x0F));
    hex_display_digit(2, (uint8_t)((sw >> 8) & 0x03));
}
/* ---- 模式1: 按键计数 ---- */
static void mode_key_counter(void) {
    uint32_t keys = read_keys();
    if ((keys & 0x01) == 0) { g_key_count++; delay(200000); } /* KEY0 +1 */
    if ((keys & 0x02) == 0) { g_key_count--; delay(200000); } /* KEY1 -1 */
    write_leds(g_key_count & 0x3FF);
    hex_display_digit(0, (uint8_t)(g_key_count & 0x0F));
    hex_display_digit(1, (uint8_t)((g_key_count >> 4) & 0x0F));
    jtag_puts("CNT=");
    jtag_put_hex(g_key_count);
    jtag_puts("\n");
}

/* ---- 模式2: 流水灯 ---- */
static void mode_led_scan(void) {
    /* KEY2 按下 → 暂停/继续 */
    if ((read_keys() & 0x04) == 0) {
        g_scan_pause = !g_scan_pause;
        delay(200000);
    }
    if (!g_scan_pause) {
        write_leds(g_led_pat);
        g_led_pat = led_shift_left(g_led_pat);
        hex_display_digit(0, (uint8_t)(g_led_pat & 0x0F));
    }
    delay(100000);
}

/* ---- 模式3: 十六进制计数器 ---- */
static void mode_hex_counter(void) {
    hex_display_value(g_hex_count);
    write_leds(g_hex_count & 0x3FF);
    g_hex_count++;
    if (g_hex_count > 0xFFFFFF) g_hex_count = 0;
    delay(150000);
}

/* ==========================系统初始化=================================*/
static void system_init(void) {
    /* 清除输出 */
    write_leds(0x000);
    hex_display_clear();
    /* PIO 方向: 输入=0x0, 输出=全1 */
    IOWR(PIO_KEYS_BASE,     PIO_DIRECTION_OFFSET, 0x00000000);
    IOWR(PIO_SWITCHES_BASE, PIO_DIRECTION_OFFSET, 0x00000000);
    IOWR(PIO_LEDS_BASE,     PIO_DIRECTION_OFFSET, 0x000003FF);
    IOWR(PIO_HEX_LOW_BASE,  PIO_DIRECTION_OFFSET, 0xFFFFFFFF);
    IOWR(PIO_HEX_HIGH_BASE, PIO_DIRECTION_OFFSET, 0xFFFFFFFF);
    /* 清除按键边沿捕获 + 禁止中断 */
    IOWR(PIO_KEYS_BASE, PIO_EDGE_CAP_OFFSET, 0x0F);
    IOWR(PIO_KEYS_BASE, PIO_IRQ_MASK_OFFSET, 0x00);
    /* 启动信息 */
    jtag_puts("\n========================================\n");
    jtag_puts("  Nios II SoC IO Demo (nios-de1soc)\n");
    jtag_puts("========================================\n");
    jtag_puts("  Mode 0: SW -> LED Mirror\n");
    jtag_puts("  Mode 1: Key Counter\n");
    jtag_puts("  Mode 2: LED Scan (KEY2=Pause)\n");
    jtag_puts("  Mode 3: Hex Counter\n");
    jtag_puts("  KEY3: Switch Mode\n");
    jtag_puts("----------------------------------------\n");
}
/* =============================主函数===================================*/
int main(void) {
    uint32_t prev_key3 = 1;  /* KEY3 上次状态, 未按下=1 */
    system_init();
    jtag_puts("[MAIN] Entering loop.\n");
    while (1) {
        /* ---- KEY3 下降沿检测 (1→0 = 按下) ---- */
        uint32_t key3_now = read_keys() & 0x08;
        if (prev_key3 == 1 && key3_now == 0) {
            g_mode = (run_mode_t)((g_mode + 1) % MODE_COUNT);
            jtag_puts("MODE=");
            jtag_put_hex((uint32_t)g_mode);
            jtag_puts("\n");
            delay(200000);
        }
        prev_key3 = key3_now;
        /* ---- 模式指示 ---- */
        show_mode_indicator();
        /* ---- 执行当前模式 ---- */
        switch (g_mode) {
        case MODE_SWITCH_MIRROR: mode_switch_mirror(); break;
        case MODE_KEY_COUNTER:   mode_key_counter();   break;
        case MODE_LED_SCAN:      mode_led_scan();      break;
        case MODE_HEX_COUNTER:   mode_hex_counter();   break;
        default:                 g_mode = MODE_SWITCH_MIRROR; break;
        }
    }
    return 0;
}

3.3 仿真测试跟踪

1 编译运行

编码、确认外设地址、编译与下载、运行,进入各种模式跟踪测试。

2 跟踪测试

++A++ ++.测试模式0:++ ++开关镜像++

跟踪寄存器变化:
r4 ← IORD(0x10000050, 0x00) ; 读到 SW 值

r5 ← r4 & 0x3FF ; 掩码

IOWR(0x10000040, 0x00, r5) ; 写入 LED

++B++ ++.测试模式1:++ ++按键计数(KEY3++ ++切换到此模式)++

调试技巧:

  1. 使用 "Step" 单步执行到 delay() 前
  2. 在 Registers 面板观察 r4(按键值) 的变化
  3. 在 Memory 面板观察 g_key_count 变量地址处的值

++C++ ++.测试模式2:++ ++流水灯(KEY3++ ++再次切换)++

++D++ ++.测试模式3:++ ++十六进制计数器(KEY3++ ++再次切换)++

++E++ ++.寄存器级调试跟踪++

在 CPUlator 的 RegistersMemory 面板中,可以进行指令级跟踪。

++F++ ++.JTAG UART++ ++调试跟踪++

CPUlator 的 JTAG UART 窗口实时显示串口输出

相关推荐
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
亚图跨际2 年前
单板计算机(SBC)-片上系统(SOC)嵌入式C++和FPGA(VHDL)
c++·qt·mqtt·esp8266·raspberry pi·单板计算机sbc·片上系统soc