赞
踩
在之前STM32的学习中,认识到无论实现什么功能,有几个基础功能是必不可少的,缺失这些功能则无法将代码以我们想要的逻辑运行下去,STM32因为STM32CubeMX的存在,使得这些功能能够通过图形化配置,建立代码框架,但我们知道,FPGA是硬件电路代码化,其本身并没有已经固化好的功能,比如串口、IIC、SPI等,甚至连RAM和ROM都没有,但这也就是FPGA可以是任何东西的原因,其并未将功能固化到引脚上的同时,各个功能也没有了像STM32一样的引脚束缚。
但由于上述功能的概念大众较为认可,Vivado同样也集成了类似的图形化配置功能,其命名为IP核。
故该章主要讲述的就是IP核的三个大功能,分别是:
MMCM/PLL通俗的将就是将晶振的主频提高或降低的核心部件,为不同的功能提供不同的时钟信号,可以理解为STM32CubeMX的时钟树配置的部分,只是STM32CubeMX的时钟树需要兼顾多个部件的时钟供应,故存在诸多限制,但MMCM/PLL则不存在此限制,直接配置即可
使用Clocking Wizard IP核产生4个时钟100MHz、100MHz_180deg、50M、25M,连接到U20、T20、P19、N18四个引脚。
新建Vivado项目后,点击Project Manager下的IP Catalog,即可进入IP核的配置界面
在IP核配置界面搜索clock,找到Clocking Wizard
首先配置时钟源,时钟源为FPGA的板上晶振提供的时钟信号,我是用的板子时钟为50MHz晶振,则将时钟源配置为50MHz
配置完成时钟源后,进行输出时钟配置,我们的配置目标是100MHz、100MHz_180deg、50M、25M4个时钟,其中180deg的意义为相位后移180°
Clock_Wizard的IP核存在一个控制信号为locked,在Port Renaming中可对此信号重命名,若无特殊要求则默认即可
MMCM Settings为而配置总览
完成上一步后,若确认无异常,则可直接点击OK,点击后会出现Generate Output Products界面,直接点击Generate即可
之后在Source串口的IP Source菜单即可找到clk_wiz_0.veo,双击打开后可找到clk_wiz_0的例化接口。
接口如下:
clk_wiz_0 instance_name
(
// Clock out ports
.clk_out1_100m(clk_out1_100m), // output clk_out1_100m
.clk_out2_100m_180(clk_out2_100m_180), // output clk_out2_100m_180
.clk_out3_50m(clk_out3_50m), // output clk_out3_50m
.clk_out4_25m(clk_out4_25m), // output clk_out4_25m
// Status and control signals
.reset(reset), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(clk_in1)); // input clk_in1
为验证内核功能,需要建立一个verilog文件将ip_wiz_0功能例化,名称为MMCM_PLL.v内容如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/11 23:13:37 // Design Name: // Module Name: MMCM_PLL // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module MMCM_PLL( input sys_clk , input sys_rst_n , output clk_100m , output clk_100m_180deg, output clk_50m , output clk_25m ); /*************************************************** main code ***************************************************/ wire locked; //MMCM-PLL example clk_wiz_0 clk_wiz_0 ( // Clock out ports .clk_out1_100m (clk_100m), // output clk_out1_100m .clk_out2_100m_180 (clk_100m_180deg), // output clk_out2_100m_180 .clk_out3_50m (clk_50m), // output clk_out3_50m .clk_out4_25m (clk_25m), // output clk_out4_25m // Status and control signals .reset (~sys_rst_n), // input reset .locked (locked), // output locked // Clock in ports .clk_in1 (sys_clk) // input clk_in1 ); endmodule
除了逻辑代码外,还需要添加约束代码【用于堆芯逻辑输出和硬件物理引脚】
约束文件命名为MMCM_PLL.xdc
若各位码哥有逻辑分析仪,则直接生成代码下载测试,如果没有的话则需要在Vivado或者Modelsim中进行仿真,仿真的玩法基本一致,这里图方便就在Vivado实现了;
首先建立仿真verilog文件:
将新建立的仿真文件命名为tb_MMCM_PLL
需要建立一个verilog文件进行仿真,名称为tb_MMCM_PLL.v,代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/11 23:27:08 // Design Name: // Module Name: tb_MMCM_PLL // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module tb_MMCM_PLL(); reg sys_clk; reg sys_rst_n; wire clk_100m; wire clk_100m_180deg; wire clk_50m; wire clk_25m; always #10 sys_clk = ~sys_clk; initial begin sys_clk = 1'b0; sys_rst_n = 1'b0; #200 sys_rst_n = 1'b1; end MMCM_PLL u_MMCM_PLL( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ), .clk_100m (clk_100m ), .clk_100m_180deg (clk_100m_180deg ), .clk_50m (clk_50m ), .clk_25m (clk_25m ) ); endmodule
该仿真程序的核心就是仿真出时钟信号sys_clk和复位信号sys_rst_n。
接下来就是进行仿真,首先左键点击项目管理的Run Simulation,在弹出的菜单中点击第一项RUN Behavioral Simulation即可开始仿真
按照下图红框中配置好仿真的时长,并开始仿真
由图可见生成的各个时钟的波形,与预期一致,到此,IP的内核功能已经实现!
RAM随机存储器,它可以随时把数据写入任一指定地 址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。可以理解为STM32在编写代码时,定义的变量在赋值或使用时所对应的硬件地址所在的存储表。
定义一块RAM空间,对RAM空间内的数据进行写入的读取;仅使用时钟输入和复位输入;
新建Vivado工程,在IP核的配置界面搜索Block Memory
配置如下图所示,配置总线类型为Native、存储器类型为单端口RAM【读写只能通过一个端口实现】
接下来配置端口A,读取数据宽度8bit深度32,写入数据宽度8bit深度32,读写模式使用不变模式No Change【不允许同时读写】,取消Primitives Output Register,防止在仿真时,数据后滞一个时钟周期,不利于学习过程的分析,选中该选项是打开输出流水寄存器,可改善时序性能,正常使用时一般选择打开;
Other Options在该实验暂时不用修改,故跳过,之后在Summary中进行全局检查,无问题则直接点击OK。
跳出生成界面直接点击生成(Generate)即可
在与MMCM_PLL的IP核相同的位置可找到RAM的例化端口
复制例化端口后进行编码;
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
blk_mem_gen_0 your_instance_name (
.clka(clka), // input wire clka
.ena(ena), // input wire ena
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [4 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.douta(douta) // output wire [7 : 0] douta
);
// INST_TAG_END ------ End INSTANTIATION Template ---------
建立使用RAM的功能模块,命名为ram_rw.v,该模块和IP核为同层级;
代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/12 22:40:36 // Design Name: // Module Name: ram_rw // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module ram_rw( input clk ,//时钟信号 input rst_n ,//复位信号,低电平有效 output ram_en ,//ram 使能信号 output ram_wea ,//ram 读写选择 output reg [4:0] ram_addr ,//ram 读写地址 output reg [7:0] ram_wr_data,//ram 写数据 input [7:0] ram_rd_data //ram 读数据 ); //reg define reg [5:0] rw_cnt; /***************************************** main code ******************************************/ //控制RAM使能信号 assign ram_en = rst_n; //rw_cnt计数器在0~31写入数据,32~63读取数据 assign ram_wea = (rw_cnt <= 6'd31 && ram_en == 1'b1) ? 1'b1 : 1'b0; //读写控制计数器,计数范围0~63 always @ (posedge clk or negedge rst_n)begin if(rst_n == 1'b0) rw_cnt <= 1'b0; else if(rw_cnt == 6'd63) rw_cnt <= 1'b0; else rw_cnt <= rw_cnt + 1'b1; end //产生RAM写数据 always @ (posedge clk or negedge rst_n)begin if(rst_n == 1'b0) ram_wr_data <= 1'b0; else if(rw_cnt <= 6'd31)//对应数据随地址变化而变化 ram_wr_data <= ram_wr_data + 1'b1; else ram_wr_data <= 1'b0; end //读写地址信号 0~31 always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) ram_addr <= 1'b0; else if(rw_cnt == 6'd31) ram_addr <= 1'b0; else ram_addr <= ram_addr + 1'b1; end endmodule
新建高层级的模块,调用IP核和刚刚写好的模块ip_ram.v,端口仅为时钟信号和复位信号,代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/12 23:00:27 // Design Name: // Module Name: ip_ram // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module ip_ram( input sys_clk , //系统时钟 input sys_rst_n //系统复位,低电平有效 ); //wire define wire ram_en ; //RAM 使能 wire ram_wea ; //ram 读写使能信号,高电平写入,低电平读出 wire [4:0] ram_addr ; //ram 读写地址 wire [7:0] ram_wr_data ; //ram 写数据 wire [7:0] ram_rd_data ; //ram 读数据 //***************************************************** //** main code //***************************************************** //ram 读写模块例化 ram_rw u_ram_rw( .clk (sys_clk ), .rst_n (sys_rst_n ), .ram_en (ram_en ), .ram_wea (ram_wea ), .ram_addr (ram_addr ), .ram_wr_data(ram_wr_data), .ram_rd_data(ram_rd_data) ); //ram ip核模块例化 blk_mem_gen_0 blk_mem_gen_0( .clka (sys_clk ) ,// input wire clka .ena (ram_en ) ,// input wire ena .wea (ram_wea ) ,// input wire [0 : 0] wea .addra (ram_addr ) ,// input wire [4 : 0] addra .dina (ram_wr_data) ,// input wire [7 : 0] dina .douta (ram_rd_data) // output wire [7 : 0] douta ); endmodule
接下来建立约束文件,命名为ip_ram.xdc,代码如下:
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
RAM的读写由于与物理引脚无关,故需要进行仿真验证,确定RAM的IP核功能是否正确,新建访问文件为tb_ip_ram.v,具体代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/12 23:09:44 // Design Name: // Module Name: tb_ip_ram // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module tb_ip_ram(); reg sys_clk; reg sys_rst_n; always#10 sys_clk = ~sys_clk; initial begin sys_clk = 1'b0; sys_rst_n = 1'b0; #200 sys_rst_n = 1'b1; end ip_ram u_ip_ram( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n ) ); endmodule
之后与仿真MMCM_PLL一样的操作进行仿真:
进行仿真
仿真结果放大后,结果如下:
当ram_wea信号拉高后进行写操作,写入数据与地址一一对应;
当ram_wea信号拉低后进行读操作,读取数据与地址一一对应;
至此测试完成。
使用异步方式将数据先写入FIFO区,再从FIFO区读取;
其大致思路如下图所示,建立一个FIFO的写入模块u_fifo_wr、建立一个FIFO的读取模块u_fifo_rd、建立一个FIFO的IP核fifo_generator_0,若要下载到FPGA测试则还需要建立一个虚拟逻辑分析仪IP核ila_0:
与之前的IP核方法一样,在IP核搜索栏搜索FIFO,找到FIFO_Generator
选在Native接口的独立时钟块的RAM“Independent Clocks Block RAM”
对FIFO接口进行配置,本次实验读写数据为8位,故数据宽度都为8位,数据深度暂定为256,读取写入每次都顺序操作255个数据,由于仅观察FIFO读写,故ResetPin可以取消勾选。
状态标记配置需要把快满和快空的标记勾选上
配置数据计数器,读写数据计数器均使能,且宽度定义为8
完成上述操作后直接点击OK,则会跳出IP核生成器,直接点击生成即可;
FIFO的IP核例化端口可在下图所示位置实现
顶层模块代码文件命名为ip_fifo.v,代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/13 20:48:50 // Design Name: // Module Name: ip_fifo // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module ip_fifo( input sys_clk, input sys_rst_n ); wire fifo_wr_en ; wire fifo_rd_en ; wire [7:0] fifo_din ; wire [7:0] fifo_dout ; wire almost_full ; wire almost_empty; wire fifo_full ; wire fifo_empty ; wire [7:0] fifo_wr_data_count; wire [7:0] fifo_rd_data_count; //***************************************************** //******* mian code //***************************************************** //例化FIFO IP核 fifo_generator_0 fifo_generator_0( .wr_clk (sys_clk ), .rd_clk (sys_clk ), .wr_en (fifo_wr_en ), .rd_en (fifo_rd_en ), .din (fifo_din ), .dout (fifo_dout ), .almost_full (almost_full ), .almost_empty (almost_empty ), .full (fifo_full ), .empty (fifo_empty ), .wr_data_count (fifo_wr_data_count), .rd_data_count (fifo_rd_data_count) ); //例化FIFO写入模块 fifo_wr u_fifo_wr( .clk ( sys_clk ), // 写时钟 .rst_n ( sys_rst_n ), // 复位信号 .fifo_wr_en ( fifo_wr_en ) , // fifo 写请求 .fifo_wr_data ( fifo_din ) , // 写入 FIFO 的数据 .almost_empty ( almost_empty ), // fifo 将空信号 .almost_full ( almost_full ) // fifo 将满信号 ); //例化FIFO读取模块 fifo_rd u_fifo_rd( .clk ( sys_clk ), // 读时钟 .rst_n ( sys_rst_n ), // 复位信号 .fifo_rd_en ( fifo_rd_en ), // fifo 读请求 .fifo_dout ( fifo_dout ), // 从 FIFO 输出的数据 .almost_empty ( almost_empty ), // fifo 将空信号 .almost_full ( almost_full ) // fifo 将满信号 ); //例化ILA IP核,虚拟逻辑分析仪 ila_0 ila_0( .clk (sys_clk ), .probe0 (fifo_wr_en ), .probe1 (fifo_rd_en ), .probe2 (fifo_din ), .probe3 (fifo_dout ), .probe4 (fifo_empty ), .probe5 (almost_empty ), .probe6 (fifo_full ), .probe7 (almost_full ), .probe8 (fifo_wr_data_count), .probe9 (fifo_rd_data_count) ); endmodule
新建约束文件,命名为ip_dido.xdc,代码如下:
create_clock -period 20.000 -name clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
编写FIFO写入模块,命名为fifo_wr.v,代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/13 21:04:32 // Design Name: // Module Name: fifo_wr // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module fifo_wr( input clk , input rst_n , input almost_empty, input almost_full , output reg fifo_wr_en , output reg [7:0]fifo_wr_data ); //reg define reg [1:0] state ; reg almost_empty_d0 ; reg almost_empty_syn; reg [3:0] dly_cnt ; //***************************************************** //** main code //***************************************************** //将almost_empty同步到写时钟域 always@(posedge clk)begin if(!rst_n)begin almost_empty_d0 <= 1'b0; almost_empty_syn <= 1'b0; end else begin almost_empty_d0 <= almost_empty; almost_empty_syn <= almost_empty_d0; end end //向FIFO写入数据 always @(posedge clk)begin if(!rst_n)begin fifo_wr_en <= 1'b0; fifo_wr_data <= 8'b0; state <= 2'b0; dly_cnt <= 4'd0; end else begin case(state) 2'd0:begin if(almost_empty_syn) begin //如果检测到 FIFO 将被读空(下一拍就会空) state <= 2'd1; //就进入延时状态 end else state <= state; end 2'd1:begin if(dly_cnt == 4'd10) begin //延时 10 拍,等待状态信号完成更新 dly_cnt <= 4'd0; state <= 2'd2; //开始写操作 fifo_wr_en <= 1'b1; //打开写使能 end else dly_cnt <= dly_cnt + 4'd1; end 2'd2:begin if(almost_full) begin //等待 FIFO 将被写满(下一拍就会满) fifo_wr_en <= 1'b0; //关闭写使能 fifo_wr_data <= 8'd0; state <= 2'd0; //回到第一个状态 end else begin //如果 FIFO 没有被写满 fifo_wr_en <= 1'b1; //则持续打开写使能 fifo_wr_data <= fifo_wr_data + 1'd1; //且写数据值持续累加 end end default : state <= 2'd0; endcase end end endmodule
编写FIFO的读取模块,命名为fifo_rd.v,代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/13 21:04:32 // Design Name: // Module Name: fifo_rd // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module fifo_rd( input clk , input rst_n , input [7:0]fifo_dout, input almost_full , input almost_empty, output reg fifo_rd_en ); //reg define reg [1:0] state ; reg almost_full_d0 ; reg almost_full_syn; reg [3:0] dly_cnt ; //***************************************************** //** main code //***************************************************** //将almost_full同步到读时钟域 always@(posedge clk)begin if(!rst_n)begin almost_full_d0 <= 1'b0; almost_full_syn <= 1'b0; end else begin almost_full_d0 <= almost_full; almost_full_syn <= almost_full_d0; end end //向FIFO读取数据 always @(posedge clk)begin if(!rst_n)begin fifo_rd_en <= 1'b0; state <= 2'b0; dly_cnt <= 4'd0; end else begin case(state) 2'd0:begin if(almost_full_syn) begin //如果检测到 FIFO 将被写满(下一拍就会满) state <= 2'd1; //就进入延时状态 end else state <= state; end 2'd1:begin if(dly_cnt == 4'd10) begin //延时 10 拍,等待状态信号完成更新 dly_cnt <= 4'd0; state <= 2'd2; //开始读操作 end else dly_cnt <= dly_cnt + 4'd1; end 2'd2:begin if(almost_empty) begin //等待 FIFO 将被读空(下一拍就会空) fifo_rd_en <= 1'b0; //关闭读使能 state <= 2'd0; //回到第一个状态 end else //如果 FIFO 没有被写满 fifo_rd_en <= 1'b1; //则持续打开写使能 end default : state <= 2'd0; endcase end end endmodule
因为读写FIFO的动作下载到FPGA上后,实验现象难以观测,故需要启动虚拟逻辑分析仪内核ILA实现。
首先还是在IP核中搜索ILA,选中下图所示的IP核
由于我们需要观测的数据为读写过程中的使能、数据、存满、读空、将满将空等数据,具体内容如下,则需要配置10路虚拟逻辑分析仪探针,数据深度设置为1024【深度越深,采集的过程信息越多,占用FPGA空间越多,需要适可而止】
对虚拟逻辑分析仪配置的10个端口的数据宽度,针对读取写入的具体数据需要配置的数据宽度为8,其余布尔量信号宽度为1即可。
配置完成之后直接点击OK,生成即可。
编辑仿真代码,命名为tb_ip_fifo.v,代码如下:
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/13 21:47:10 // Design Name: // Module Name: tb_ip_fifo // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module tb_ip_fifo(); reg sys_clk; reg sys_rst_n; ip_fifo u_ip_fifo( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n) ); parameter PERIOD = 20; always begin sys_clk = 1'b0; #(PERIOD/2) sys_clk = 1'b1; #(PERIOD/2); end initial begin sys_rst_n = 0; #100 sys_rst_n = 1; end endmodule
完成代码编辑后在Vivado直接进行仿真,完成写操作位置为下图黄线所示位置;
完成读操作,下图黄线位置为写完成
故测试成功;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。