基于AHB的四通道DMA控制器设计-test_dma.v

test_dma.v = DMA 控制器的仿真测试平台
          • 例化你前面的 dmac 顶层
          • 用 sim_ahb_task 模拟 CPU 配置 DMA
          • 模拟 外设触发 DMA 请求
          • 自动跑测试用例,看 DMA 搬数据对不对

`timescale 1ns/10ps

// ==============================
// 模块:DMA 仿真测试顶层
// 作用:给DMA供电、模拟CPU、模拟外设、跑测试用例
// ==============================
module test_dma;

// 时钟周期:100ns → 10MHz
parameter PERIOD = 100;

// DMA 寄存器基地址(和 dmac_intf 一致)
parameter BASE_ADDR= 32’h4000_0000;

// 通道使能
parameter CH0_EN = 1;
parameter CH1_EN = 1;
parameter CH2_EN = 1;
parameter CH3_EN = 1;

// 通道方向配置
parameter CH0_TGT = 32’h10;
parameter CH1_TGT = 32’h10;
parameter CH2_TGT = 32’h10;
parameter CH3_TGT = 32’h10;

// ==============================
// DMA 寄存器地址映射(和 dmac_intf 完全对应)
// ==============================
parameter CH0_CTRL = BASE_ADDR + 32’h0; // 通道0控制
parameter CH1_CTRL = BASE_ADDR + 32’h12;
parameter CH2_CTRL = BASE_ADDR + 32’h24;
parameter CH3_CTRL = BASE_ADDR + 32’h36;

parameter CH0_SOUR = BASE_ADDR + 32’h4; // 通道0源地址
parameter CH1_SOUR = BASE_ADDR + 32’h16;
parameter CH2_SOUR = BASE_ADDR + 32’h28;
parameter CH3_SOUR = BASE_ADDR + 32’h40;

parameter CH0_DEST = BASE_ADDR + 32’h8; // 通道0目标地址
parameter CH1_DEST = BASE_ADDR + 32’h20;
parameter CH2_DEST = BASE_ADDR + 32’h32;
parameter CH3_DEST = BASE_ADDR + 32’h44;

// 测试用例编号
integer testcase;

// 计数器
reg [7:0] cnt;

// 时钟、复位
reg hreset_n, hclk;

// ==============================
// AHB 主机信号(模拟 CPU)
// ==============================
reg cpu_s_hsel; // 片选
reg [31:0] cpu_s_haddr; // 地址
reg [31:0] cpu_s_hwdata; // 写数据
reg cpu_s_hwrite; // 读写
reg [1:0] cpu_s_htrans; // 传输类型
reg [2:0] cpu_s_hburst; // 突发类型
reg [2:0] cpu_s_hsize; // 位宽
reg [3:0] cpu_s_hprot; // 保护
reg cpu_s_hlock; // 锁定
reg cpu_s_hbusreq; // 总线请求

// AHB 读数据、DMA 就绪信号
wire [31:0] hrdata;
wire hreadyout_from_dmaslv;

// 临时寄存器
reg [31:0] xfr_reg;

// ==============================
// 外设 DMA 请求触发信号(Testbench 内部用)
// ==============================
reg req_0_trig;
reg req_1_trig;
reg req_2_trig;
reg req_3_trig;

// 发给 DMA 的外设请求信号(peri_req = 外设请求DMA)
reg peri_req_0;
reg peri_req_1;
reg peri_req_2;
reg peri_req_3;

// DMA 应答信号(DMA 给外设的应答)
wire dmac_ack_0;
wire dmac_ack_1;
wire dmac_ack_2;
wire dmac_ack_3;

// 测试用通道使能/方向
reg tb_ch_en_0;
reg tb_ch_en_1;
reg tb_ch_en_2;
reg tb_ch_en_3;

reg tb_ch_tgt_0;
reg tb_ch_tgt_1;
reg tb_ch_tgt_2;
reg tb_ch_tgt_3;

// 模拟外设给DMA的数据
reg [31:0] tb_hrdatain;
reg [31:0] tb_ch_datain_0;
reg [31:0] tb_ch_datain_1;
reg [31:0] tb_ch_datain_2;
reg [31:0] tb_ch_datain_3;

// ==============================
// 包含 AHB 仿真任务(写、读、空闲)
// 就是你上一个文件 sim_ahb_task.v
// ==============================
`include “sim_ahb_task.v”

// ==============================
// 例化 DMA 顶层(被测试的模块)
// ==============================
dmac u_dmac(
.HCLK (hclk),
.HRESETn (hreset_n),

// AHB 从机:CPU 配置接口
.HSEL_SLV (cpu_s_hsel),
.HREADYIN_SLV (1’b1),
.HTRANS_SLV (cpu_s_htrans),
.HSIZE_SLV (cpu_s_hsize),
.HWRITE_SLV (cpu_s_hwrite),
.HADDR_SLV (cpu_s_haddr),
.HWDATA_SLV (cpu_s_hwdata),
.HREADYOUT_SLV (hreadyout_from_dmaslv),
.HRESP_SLV (),
.HRDATA_SLV (hrdata),

// AHB 主机:DMA 访问总线(仿真中悬空不接)
.HSEL (),
.HTRANS (),
.HSIZE (),
.HWRITE (),
.HADDR (),
.HWDATA (),
.HREADY_IN (1’b1), // 模拟从机永远就绪
.HRESP (),
.HRDATA (tb_hrdatain), // 模拟外设给DMA的数据

// 外设请求 & DMA 应答
.req_0 (peri_req_0),
.req_1 (peri_req_1),
.req_2 (peri_req_2),
.req_3 (peri_req_3),
.ack_0 (dmac_ack_0),
.ack_1 (dmac_ack_1),
.ack_2 (dmac_ack_2),
.ack_3 (dmac_ack_3)
);

// ==============================
// 外设请求生成逻辑:trig 触发 → 产生 peri_req → 收到 ack 清除
// 作用:模拟外设“我需要DMA服务”
// ==============================
always @(posedge hclk or negedge hreset_n)
if (!hreset_n) peri_req_0 <= 0;
else if (req_0_trig)peri_req_0 <= 1;
else if (dmac_ack_0)peri_req_0 <= 0;

always @(posedge hclk or negedge hreset_n)
if (!hreset_n) peri_req_1 <= 0;
else if (req_1_trig)peri_req_1 <= 1;
else if (dmac_ack_1)peri_req_1 <= 0;

always @(posedge hclk or negedge hreset_n)
if (!hreset_n) peri_req_2 <= 0;
else if (req_2_trig)peri_req_2 <= 1;
else if (dmac_ack_2)peri_req_2 <= 0;

always @(posedge hclk or negedge hreset_n)
if (!hreset_n) peri_req_3 <= 0;
else if (req_3_trig)peri_req_3 <= 1;
else if (dmac_ack_3)peri_req_3 <= 0;

// ==============================
// 模拟外设发送自增数据(每触发一次+1)
// 作用:让DMA搬的数据有规律,方便看对不对
// ==============================
always @(posedge hclk or negedge hreset_n)
if (!hreset_n) tb_ch_datain_0 <= 0;
else if (~tb_ch_en_0)tb_ch_datain_0 <= 0;
else if (req_0_trig)tb_ch_datain_0 <= tb_ch_datain_0 + 1;

always @(posedge hclk or negedge hreset_n)
if (!hreset_n) tb_ch_datain_1 <= 0;
else if (~tb_ch_en_1)tb_ch_datain_1 <= 0;
else if (req_0_trig)tb_ch_datain_1 <= tb_ch_datain_1 + 1;

always @(posedge hclk or negedge hreset_n)
if (!hreset_n) tb_ch_datain_2 <= 0;
else if (~tb_ch_en_2)tb_ch_datain_2 <= 0;
else if (req_0_trig)tb_ch_datain_2 <= tb_ch_datain_2 + 1;

always @(posedge hclk or negedge hreset_n)
if (!hreset_n) tb_ch_datain_3 <= 0;
else if (~tb_ch_en_3)tb_ch_datain_3 <= 0;
else if (req_0_trig)tb_ch_datain_3 <= tb_ch_datain_3 + 1;

// ==============================
// 根据当前哪个外设请求,把对应数据送给DMA的HRDATA
// ==============================
always @(posedge hclk or negedge hreset_n)
if (!hreset_n) tb_hrdatain <= 1;
else if (peri_req_0) tb_hrdatain <= tb_ch_datain_0;
else if (peri_req_1) tb_hrdatain <= tb_ch_datain_1;
else if (peri_req_2) tb_hrdatain <= tb_ch_datain_2;
else if (peri_req_3) tb_hrdatain <= tb_ch_datain_3;

// ==============================
// 时钟生成:100ns 周期
// ==============================
initial begin
forever #(PERIOD/2) hclk = ~hclk;
end

// ==============================
// 主测试流程(核心!)
// ==============================
initial begin
// 初始化信号
testcase = 0;
hreset_n = 1;
hclk = 1;
xfr_reg = 0;
req_0_trig= 0;
req_1_trig= 0;
req_2_trig= 0;
req_3_trig= 0;

tb_ch_en_0=0;
tb_ch_en_1=0;
tb_ch_en_2=0;
tb_ch_en_3=0;

tb_ch_tgt_0=0;
tb_ch_tgt_1=0;
tb_ch_tgt_2=0;
tb_ch_tgt_3=0;

// 复位
#(PERIOD*20) hreset_n = 0;
#(PERIOD*20) hreset_n = 1;

// ————————–
// 测试用例 1:内存 → 外设
// ————————–
testcase = 1;

// 配置通道0源地址
#(PERIOD*1) AHB_SIGNLE_WR(CH0_SOUR, 32’h4001_1000);
// 配置通道0目标地址
#(PERIOD*1) AHB_SIGNLE_WR(CH0_DEST, 32’h4001_0000);
// 配置通道0控制寄存器:长度+使能
#(PERIOD*1) AHB_SIGNLE_WR(CH0_CTRL, 32’h900 + CH0_EN);

// 打开通道0
tb_ch_en_0 = 1;
tb_ch_tgt_0 = 0;

#(PERIOD*60);

// 触发9次 DMA 请求
repeat (9) begin
#(PERIOD*1) req_0_trig = 1;
#(PERIOD*1) req_0_trig = 0;
#(PERIOD*10);
end

#(PERIOD*300);

// 复位
#(PERIOD*20) hreset_n = 0;
#(PERIOD*20) hreset_n = 1;

// ————————–
// 测试用例 2:外设 → 内存
// ————————–
testcase = 2;

// 配置通道0
#(PERIOD*1) AHB_SIGNLE_WR(CH0_SOUR, 32’h4001_0000);
#(PERIOD*1) AHB_SIGNLE_WR(CH0_DEST, 32’h4001_1000);
#(PERIOD*1) AHB_SIGNLE_WR(CH0_CTRL, 32’ha00 + CH0_TGT + CH0_EN);

tb_ch_en_0 = 1;
tb_ch_tgt_0 = 0;

// 触发10次DMA请求
repeat (10) begin
#(PERIOD*1) req_0_trig = 1;
#(PERIOD*1) req_0_trig = 0;
#(PERIOD*20);
end

#(PERIOD*300);

// 结束仿真
$finish;
end

// ==============================
// 波形dump:保存波形文件,用于Verdi查看
// ==============================
initial begin
$fsdbDumpfile(“test_dma.fsdb”);
$fsdbDumpvars(0,”test_dma”);
$fsdbDumpMDA();
end

endmodule

三、场景化大白话(最关键)
把 test_dma.v 想象成:
DMA 的“考场 + 监考老师 + 假学生”
• dmac:考生(DMA 控制器)
• test_dma:考场
• AHB 任务:假 CPU(老师发卷子)
• peri_req:假外设(学生要交作业)
• 波形文件:监控录像

整个考试流程:
1. 复位 → 考生坐好
2. CPU 配置 DMA(发卷子):
◦ 源地址
◦ 目标地址
◦ 传输长度
◦ 启动通道
3. 模拟外设发请求:我要传数据
4. DMA 应答 + 搬数据
5. 看波形 对不对

留言

您的邮箱地址不会被公开。 必填项已用 * 标注