摘要:本文主要介绍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的方法)


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收到数据后将收到的数据通过数码管显示。