基于双口RAM的CPLD与DSP通讯

发布于:2025-03-13 ⋅ 阅读:(14) ⋅ 点赞:(0)

摘要:本文主要介绍DSP(TMS320F28335)与CPLD(EPM570T100C5)的通讯,本文主要基于CPLD的双口RAM通过XINTF实现与DSP的通讯,与前面文章中的基于寄存器映射相比,寄存器映射是一种通过特定地址访问硬件寄存器的通信方式。每个寄存器都有一个唯一的地址,通过地址总线和数据总线进行读写操作。而双口RAM是一种允许同时进行两组独立读写操作的存储器。双口RAM有两个独立的端口,每个端口都有自己的地址总线、数据总线和控制信号。此次文章主要通过配置简单的双口RAM实现DSP访问写端口,FPGA访问读端口。并在FPGA中通过数码管显示收到的数。

CPLD中简单双口RAM配置:

文中的双口RAM配置采用的是新起点之FPG开发指南V3.1中的配置步骤,可以自行下载阅读详细解释,这里不再赘述,只是介绍如何配置双口RAM的IP核:

1、新建一个工程,命名为ip_2port_ram2(任意), 然后在右边🔍中输入RAM,找到RAM:2-PORT。(注意,在不同的版本可能有所不同,在我的13.1版本中则是下面图2的方法)

图1
图2

        

2、双击2-port,并保存在路径中,并命名为RAM_2PORT(任意)

3、指定端口类型和指定存储器大小,maxii 只能是简单双口RAN,选择以word为单位

4、设置储存器深度,此处为32,数据位宽设置为16位,因为XINTF只能传输16位数据,

5、设置双口RAM时钟,并勾选读写使能信号,不勾选则一直再读写

6、取消"q"输出,勾选读出数据则会多延时一个时钟周期

7、点next

8、勾选下图中的选项,方便后面例化,然后点finish

9、点击yes

10、这样就配置完成!上述只是简单的配置过程,具体详细的解释请参考新起点之FPG开发指南V3.1

FPGA部分verilog代码

1、顶层模块代码

module ip_2port_ram2 (
    input [19:0] DSP_Add,
    input CS0,
    input WR,
    input RD,
    input RST,
    input CLK, // 外部时钟信号
    output RESET,
    output led, // 状态显示
    input [15:0] DSP_Data, // DSP与CPLD之间的数据线定义为16位输入
    //output [15:0] rd_data,
    output [7:0] seg, // 数码管显示输出
    output [7:0] an,  // 8位数码管使能
    
);

/*变量寄存器的定义*/
wire [15:0] DSP_Data_out; // 用于输出数据给数码管
wire [15:0] DSP_Data_out_dac; // 用于输出数据给DAC
wire [15:0] write_data_ram; // 定义一个写入数据用于RAM
reg  [15:0] write_data; // 定义一个最终写入数据

/* 双口RAM实例 */
wire [15:0] ram_rd_data;
wire [5:0] ram_wr_addr;
wire [5:0] ram_rd_addr;
wire ram_wr_en;
wire ram_rd_en;

ram_2port ram_2port_inst (
    .data(write_data),
    .rdaddress(ram_rd_addr),
    .rdclock(CLK),
    .rden(ram_rd_en),
    .wraddress(ram_wr_addr),
    .wrclock(CLK),
    .wren(ram_wr_en),
    .q(ram_rd_data)
);

/* 写模块实例 */
ram_wr u_ram_wr(
    .clk(CLK),
    .rst_n(RST),
    .DSP_Add(DSP_Add),
    .CS0(CS0),
    .WR(WR),
    .ram_wr_en(ram_wr_en),
    .ram_wr_addr(ram_wr_addr),
    .DSP_Data(DSP_Data), // 将DSP_Data作为输入信号传递
    .write_data(write_data_ram)
);

/* 读模块实例 */
ram_rd u_ram_rd(
    .clk(CLK),
    .rst_n(RST),
    .ram_rd_en(ram_rd_en),
    .ram_rd_data(ram_rd_data),
    .ram_rd_addr(ram_rd_addr),
    .q_out(DSP_Data_out),
    .q_out_dac(DSP_Data_out_dac),

);

/* 数码管显示实例 */
seg_display_8digit u_seg_display_8digit(
    .clk(CLK),
    .rst_n(RST),
    .digit0(DSP_Data_out[3:0]),
    .digit1(DSP_Data_out[7:4]),
    .digit2(DSP_Data_out[11:8]),
    .digit3(DSP_Data_out[15:12]),
//    .digit4(q_out_combined[19:16]),
//    .digit5(q_out_combined[23:20]),
//    .digit6(q_out_combined[27:24]),
//    .digit7(q_out_combined[31:28]),
    .seg(seg),
    .an(an)
);



/* 多路复用器选择写数据 */
always @(*) begin
    write_data = write_data_ram;
end

assign rd_data = DSP_Data_out;

endmodule

2、读模块

module ram_rd (
    input clk,
    input rst_n,
    output reg ram_rd_en,
    input [15:0] ram_rd_data,
    output reg [5:0] ram_rd_addr,
    output reg [15:0] q_out,
    output reg [15:0] q_out_dac,
);

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        ram_rd_en <= 1'b0;
        ram_rd_addr <= 6'b0;
    end else begin
        ram_rd_en <= 1'b1;
        case (ram_rd_addr)
            6'h00: ram_rd_addr <= 6'h01; // 读取地址 6'h01
            6'h01: ram_rd_addr <= 6'h02; // 读取地址 6'h02
            default: ram_rd_addr <= 6'h00; // 默认地址 6'h00
        endcase
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        q_out <= 16'b0;
        q_out_dac <= 16'b0;
    end else if (ram_rd_en) begin
        case (ram_rd_addr)
            6'h01: q_out <= ram_rd_data; // 地址 6'h01 的数据用于 q_out
            6'h02: q_out_dac <= ram_rd_data; // 地址 6'h02 的数据用于 q_out_dac

        endcase
    end
end

endmodule

3、写模块

module ram_wr (
    input clk,
    input rst_n,
    input [19:0] DSP_Add,
    input CS0,
    input WR,
    input [15:0] DSP_Data, // 将inout改为input
    output reg ram_wr_en,
    output reg ram_wr_we,
    output reg [5:0] ram_wr_addr,
    output reg [15:0] write_data
);

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        ram_wr_en <= 1'b0;
        ram_wr_we <= 1'b0;
        ram_wr_addr <= 6'b0;
        write_data <= 16'b0;
    end else if (CS0 == 1'b0 && WR == 1'b0) begin
        ram_wr_en <= 1'b1;
        ram_wr_we <= 1'b1;
        case (DSP_Add)
            20'h200010: begin ram_wr_addr <= 6'h00; write_data <= DSP_Data; end
            20'h200011: begin ram_wr_addr <= 6'h01; write_data <= DSP_Data; end
            default: begin ram_wr_addr <= 6'h3F; write_data <= 16'h0000; end // 默认情况下,未定义的地址
        endcase
    end else begin
        ram_wr_en <= 1'b0;
        ram_wr_we <= 1'b0;
    end
end

endmodule

4、数码管模块

module seg_display_8digit (
    input clk,                  // 时钟输入信号
    input rst_n,                // 复位信号,低电平有效
    input [3:0] digit0,         // 数码管第0位的输入数据
    input [3:0] digit1,         // 数码管第1位的输入数据
    input [3:0] digit2,         // 数码管第2位的输入数据
    input [3:0] digit3,         // 数码管第3位的输入数据
    input [3:0] digit4,         // 数码管第4位的输入数据
    input [3:0] digit5,         // 数码管第5位的输入数据
    input [3:0] digit6,         // 数码管第6位的输入数据
    input [3:0] digit7,         // 数码管第7位的输入数据
    output reg [7:0] seg,       // 数码管段位输出信号
    output reg [7:0] an         // 数码管使能信号
);

reg [16:0] clkdiv;              // 时钟分频寄存器
reg [3:0] digit;                // 当前显示的数字
reg [2:0] digit_select;         // 当前选择的数码管位

// 时钟分频,用于多路复用8位数码管
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        clkdiv <= 17'b0;        // 复位时将时钟分频寄存器清零
    end else begin
        clkdiv <= clkdiv + 1;   // 时钟上升沿时,时钟分频寄存器加1
    end
end

// 数码管位选择
always @(posedge clkdiv[16] or negedge rst_n) begin
    if (!rst_n) begin
        digit_select <= 3'b0;   // 复位时将数码管选择寄存器清零
    end else begin
        digit_select <= digit_select + 1; // 时钟分频后的时钟上升沿时,数码管选择寄存器加1
    end
end

// 分配当前显示的数字
always @(*) begin
    case (digit_select)
        3'b000: begin
            digit = digit0;     // 选择第0位数码管显示的数据
            an = 8'b11111110;   // 使能第0位数码管
        end
        3'b001: begin
            digit = digit1;     // 选择第1位数码管显示的数据
            an = 8'b11111101;   // 使能第1位数码管
        end
        3'b010: begin
            digit = digit2;     // 选择第2位数码管显示的数据
            an = 8'b11111011;   // 使能第2位数码管
        end
        3'b011: begin
            digit = digit3;     // 选择第3位数码管显示的数据
            an = 8'b11110111;   // 使能第3位数码管
        end
        3'b100: begin
            digit = digit4;     // 选择第4位数码管显示的数据
            an = 8'b11101111;   // 使能第4位数码管
        end
        3'b101: begin
            digit = digit5;     // 选择第5位数码管显示的数据
            an = 8'b11011111;   // 使能第5位数码管
        end
        3'b110: begin
            digit = digit6;     // 选择第6位数码管显示的数据
            an = 8'b10111111;   // 使能第6位数码管
        end
        3'b111: begin
            digit = digit7;     // 选择第7位数码管显示的数据
            an = 8'b01111111;   // 使能第7位数码管
        end
        default: begin
            digit = 4'b0000;    // 默认显示0
            an = 8'b11111111;   // 关闭所有数码管
        end
    endcase
end

// 8段数码管译码器
always @(*) begin
    case (digit)
        4'h0: seg = 8'b11000000; // 显示0
        4'h1: seg = 8'b11111001; // 显示1
        4'h2: seg = 8'b10100100; // 显示2
        4'h3: seg = 8'b10110000; // 显示3
        4'h4: seg = 8'b10011001; // 显示4
        4'h5: seg = 8'b10010010; // 显示5
        4'h6: seg = 8'b10000010; // 显示6
        4'h7: seg = 8'b11111000; // 显示7
        4'h8: seg = 8'b10000000; // 显示8
        4'h9: seg = 8'b10010000; // 显示9
        4'hA: seg = 8'b10001000; // 显示A
        4'hB: seg = 8'b10000011; // 显示B
        4'hC: seg = 8'b11000110; // 显示C
        4'hD: seg = 8'b10100001; // 显示D
        4'hE: seg = 8'b10000110; // 显示E
        4'hF: seg = 8'b10001110; // 显示F
        default: seg = 8'b11111111; // 默认关闭所有段
    endcase
end

endmodule

DSP部分代码

#include "DSP2833x_Device.h"     // DSP2833x芯片设备头文件
#include "DSP2833x_Examples.h"   // DSP2833x示例代码文件
#include <math.h>                // 包含数学库以生成正弦波

// 定义控制 LED 的宏
#define LED1_ON   GpioDataRegs.GPASET.bit.GPIO11 = 1   // 点亮 LED1
#define LED1_OFF  GpioDataRegs.GPACLEAR.bit.GPIO11 = 1 // 熄灭 LED1
#define LED2_ON   GpioDataRegs.GPASET.bit.GPIO10 = 1   // 点亮 LED2
#define LED2_OFF  GpioDataRegs.GPACLEAR.bit.GPIO10 = 1 // 熄灭 LED2

//定义起始地址
Uint16 *ExRamStart = (Uint16 *)0x200000; // 外部 RAM 起始地址,16 位数据存储

#define SentADD0    *(volatile Uint16 *)0x200010  // 发送地址
#define SentADD1    *(volatile Uint16 *)0x200011  // 发送地址
#define SentADD2    *(volatile Uint16 *)0x200012  // 发送地址
#define SentADD3    *(volatile Uint16 *)0x200013  // 发送地址
#define SentADD4    *(volatile Uint16 *)0x200014  // 发送地址
#define SentADD5    *(volatile Uint16 *)0x200015  // 发送地址
#define SentADD6    *(volatile Uint16 *)0x200016  // 发送地址
#define SentADD7    *(volatile Uint16 *)0x200017  // 发送地址

// 全局变量
volatile Uint16 sent_data0 = 0;
volatile Uint16 sent_data1 = 0;
volatile Uint16 sent_data2 = 0;
volatile Uint16 sent_data3 = 0;
volatile Uint16 sent_data4 = 0;
volatile Uint16 sent_data5 = 0;
volatile Uint16 sent_data6 = 0;
volatile Uint16 sent_data7 = 0;

// 三角波、正弦波和方波生成参数
volatile Uint16 tri_wave_counter = 0;
volatile Uint16 sine_wave_counter = 0;
volatile Uint16 square_wave_counter = 0;
volatile int wave_step = 1; // 修改为 int 类型以避免符号变化

// 函数声明
void configtestled(void);
void init_zone7(void);

void main(void)
{
    // 初始化系统
    InitSysCtrl();
    InitXintf16Gpio();

    // 禁用所有中断
    DINT;
    InitPieCtrl();
    IER = 0x0000;
    IFR = 0x0000;
    InitPieVectTable();

    init_zone7();
    // 配置 LED GPIO
    configtestled();

    // 初始化外部 RAM
    Uint16 *addr;
    for (addr = ExRamStart; addr < (ExRamStart + 0xFFFF); addr++)
    {
        *addr = 0x0000; // 将区域7的所有地址初始化为0
    }

    while (1)
    {
        // 写入数据到外设
        SentADD0 = sent_data0;
        SentADD1 = sent_data1;
        SentADD2 = sent_data2;
        SentADD3 = sent_data3;
        SentADD4 = sent_data4;
        SentADD5 = sent_data5;
        SentADD6 = sent_data6;
        SentADD7 = sent_data7;

        DELAY_US(1000);  // 确保写入操作稳定
        asm(" NOP"); asm(" NOP"); asm(" NOP");
        sent_data0 = (sent_data0 + 1) & 0xFFFFFFFF;
        DELAY_US(10000); // 延时10毫秒

        // 生成方波
        square_wave_counter = (square_wave_counter + 1) % 16;
        if (square_wave_counter < 8)
        {
            sent_data1 = 0xFFFF;
        }
        else
        {
            sent_data1 = 0x0000;
        }

        // 其他数据保持为0
        sent_data2 = 0x0000;
        sent_data3 = 0x0000;
        sent_data4 = 0x0000;
        sent_data5 = 0x0000;
        sent_data6 = 0x0000;
        sent_data7 = 0x0000;
    }
}

// 配置 LED GPIO
void configtestled(void)
{
    EALLOW;
    GpioCtrlRegs.GPAMUX1.bit.GPIO11 = 0;
    GpioCtrlRegs.GPADIR.bit.GPIO11 = 1;
    GpioDataRegs.GPACLEAR.bit.GPIO11 = 1;  // 熄灭 LED1

    GpioCtrlRegs.GPAMUX1.bit.GPIO10 = 0;
    GpioCtrlRegs.GPADIR.bit.GPIO10 = 1;
    GpioDataRegs.GPACLEAR.bit.GPIO10 = 1;  // 熄灭 LED2
    EDIS;
}

// 初始化外部存储接口
void init_zone7(void)
{
    EALLOW; // 允许访问受保护寄存器
    SysCtrlRegs.PCLKCR3.bit.XINTFENCLK = 1; // 确保 XINTF 时钟已启用
    EDIS; // 禁止受保护寄存器访问

    InitXintf16Gpio(); // 配置 XINTF 的 GPIO,为 16 位数据总线

    EALLOW;
    XintfRegs.XINTCNF2.bit.XTIMCLK = 1; // 设置 XTIMCLK = SYSCLKOUT/2
    XintfRegs.XINTCNF2.bit.WRBUFF = 3;  // 最多缓冲 3 次写操作
    XintfRegs.XINTCNF2.bit.CLKOFF = 0;  // 启用 XCLKOUT
    XintfRegs.XINTCNF2.bit.CLKMODE = 1; // 设置 XCLKOUT = XTIMCLK/2

    // 配置 Zone 7 的写操作时序
    XintfRegs.XTIMING7.bit.XWRLEAD = 3;    // 写前导时间
    XintfRegs.XTIMING7.bit.XWRACTIVE = 7; // 写有效时间
    XintfRegs.XTIMING7.bit.XWRTRAIL = 3;  // 写后延时间

    // 配置 Zone 7 的读操作时序
    XintfRegs.XTIMING7.bit.XRDLEAD = 3;
    XintfRegs.XTIMING7.bit.XRDACTIVE = 7;
    XintfRegs.XTIMING7.bit.XRDTRAIL = 3;

    EDIS;
}

上述文章,完成了简单双口RAM的配置,上述代码,实现了DSP发送数据给FPGA,FPGA收到数据后将收到的数据通过数码管显示。