FPGA入门(7):IP核调用(一)+https://developer.aliyun.com/article/1556568
rom_ctrl.v
`timescale 1ns/1ns module rom_ctrl #( parameter CNT_MAX = 24'd9_999_999; //0.2s计数器最大值 ) ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低有效 input wire key1_flag , //按键1消抖后有效信号 input wire key2_flag , //按键2消抖后有效信号 output reg [7:0] addr //输出读ROM地址 ); //reg define reg addr_flag1 ; //特定地址1标志信号 reg addr_flag2 ; //特定地址2标志信号 reg [23:0] cnt_200ms ; //0.2s计数器 //产生特定地址1标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) addr_flag1 <= 1'b0; else if(key2_flag == 1'b1) addr_flag1 <= 1'b0; else if(key1_flag == 1'b1) addr_flag1 <= ~addr_flag1; //产生特定地址2标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) addr_flag2 <= 1'b0; else if(key1_flag == 1'b1) addr_flag2 <= 1'b0; else if(key2_flag == 1'b1) addr_flag2 <= ~addr_flag2; //0.2s循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_200ms <= 24'd0; else if(cnt_200ms == CNT_MAX || addr_flag1 == 1'b1 || addr_flag2 == 1'b1) cnt_200ms <= 24'd0; else cnt_200ms <= cnt_200ms + 1'b1; //让地址从0~255循环,其中两个按键控制两个特定地址的跳转 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) addr <= 8'd0; else if(addr == 8'd255 && cnt_200ms == CNT_MAX) addr <= 8'd0; else if(addr_flag1 == 1'b1) addr <= 8'd99; else if(addr_flag2 == 1'b1) addr <= 8'd199; else if(cnt_200ms == CNT_MAX) addr <= addr + 1'b1; endmodule
rom.v
`timescale 1ns/1ns module rom ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire [1:0] key , //输入按键信号 output wire stcp , //输出数据存储器时钟 output wire shcp , //移位寄存器的时钟输入 output wire ds , //串行数据输入 output wire oe //输出使能信号 ); //wire define wire [7:0] addr ; //地址线 wire [7:0] rom_data ; //读出ROM数据 wire key1_flag ; //按键1消抖信号 wire key2_flag ; //按键2消抖信号 //----------------rom_ctrl_inst---------------- rom_ctrl rom_ctrl_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低有效 .key1_flag (key1_flag ), //按键1消抖后有效信号 .key2_flag (key2_flag ), //按键2消抖后有效信号 .addr (addr ) //输出读ROM地址 ); //----------------key1_filter_inst-------------- key_filter key1_filter_inst ( .sys_clk (sys_clk ), //系统时钟50Mhz .sys_rst_n (sys_rst_n ), //全局复位 .key_in (key[0] ), //按键输入信号 .key_flag (key1_flag ) //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //----------------key2_filter_inst-------------- key_filter key2_filter_inst ( .sys_clk (sys_clk ), //系统时钟50Mhz .sys_rst_n (sys_rst_n ), //全局复位 .key_in (key[1] ), //按键输入信号 .key_flag (key2_flag ) //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //----------------seg_595_dynamic_inst-------------- seg_595_dynamic seg_595_dynamic_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低有效 .data ({12'd0,rom_data}), //数码管要显示的值 .point (0 ), //小数点显示,高电平有效 .seg_en (1'b1 ), //数码管使能信号,高电平有效 .sign (0 ), //符号位,高电平显示负号 .stcp (stcp ), //输出数据存储寄时钟 .shcp (shcp ), //移位寄存器的时钟输入 .ds (ds ), //串行数据输入 .oe (oe ) //输出使能信号 ); //----------------rom_256x8_inst--------------- rom_256x8 rom_256x8_inst ( .address (addr ), .clock (sys_clk ), .q (rom_data ) ); endmodule
第27讲:RAM-IP核的调用
RAM是随机存取存储器(Random Access Memory)的简称,是一个易失性存储器;其工作时可以随时对任何一个指定的地址写入或读出数据。这是ROM所并不具备的功能。
波形图绘制
子功能模块绘制
系统框图
key_filter
`timescale 1ns/1ns module key_filter #( parameter CNT_MAX = 20'd999_999 //计数器计数最大值 ) ( input wire sys_clk , //系统时钟50Mhz input wire sys_rst_n , //全局复位 input wire key_in , //按键输入信号 output reg key_flag //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //reg define reg [19:0] cnt_20ms ; //计数器 //cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_20ms <= 20'b0; else if(key_in == 1'b1) cnt_20ms <= 20'b0; else if(cnt_20ms == CNT_MAX && key_in == 1'b0) cnt_20ms <= cnt_20ms; else cnt_20ms <= cnt_20ms + 1'b1; //key_flag:当计数满20ms后产生按键有效标志位 //且key_flag在999_999时拉高,维持一个时钟的高电平 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) key_flag <= 1'b0; else if(cnt_20ms == CNT_MAX - 1'b1) key_flag <= 1'b1; else key_flag <= 1'b0; endmodule
ram_crtl
`timescale 1ns/1ns module ram_ctrl ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低有效 input wire key1_flag , //按键1消抖后有效信号,作为写标志信号 input wire key2_flag , //按键2消抖后有效信号,作为读标志信号 output reg wr_en , //输出写RAM使能,高点平有效 output reg rd_en , //输出读RAM使能,高电平有效 output reg [7:0] addr , //输出读写RAM地址 output wire [7:0] wr_data //输出写RAM数据 ); //parameter define parameter CNT_MAX = 9_999_999; //0.2s计数器最大值 //reg define reg [23:0] cnt_200ms ; //0.2s计数器 //让写入的数据等于地址数,即写入数据0~255 assign wr_data = (wr_en == 1'b1) ? addr : 8'd0; //wr_en:产生写RAM使能信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) wr_en <= 1'b0; else if(addr == 8'd255) wr_en <= 1'b0; else if(key1_flag == 1'b1) wr_en <= 1'b1; //rd_en:产生读RAM使能信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) rd_en <= 1'b0; else if(key2_flag == 1'b1 && wr_en == 1'b0) rd_en <= 1'b1; else if(key1_flag == 1'b1) rd_en <= 1'b0; else rd_en <= rd_en; //0.2s循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_200ms <= 24'd0; else if(cnt_200ms == CNT_MAX || key2_flag == 1'b1) cnt_200ms <= 24'd0; else if(rd_en == 1'b1) cnt_200ms <= cnt_200ms + 1'b1; //写使能有效时, always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) addr <= 8'd0; else if((addr==8'd255 && cnt_200ms==CNT_MAX) || (addr==8'd255 && wr_en==1'b1) || key2_flag==1'b1 || key1_flag==1'b1) addr <= 8'd0; else if((wr_en == 1'b1) || (rd_en == 1'b1 && cnt_200ms == CNT_MAX)) addr <= addr + 1'b1; endmodule
ram
`timescale 1ns/1ns module ram ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire [1:0] key , //输入按键信号 output wire stcp , //输出数据存储器时钟 output wire shcp , //移位寄存器的时钟输入 output wire ds , //串行数据输入 output wire oe //输出使能信号 ); //wire define wire wr_en ; //写使能 wire rd_en ; //读使能 wire [7:0] addr ; //地址线 wire [7:0] wr_data ; //写数据 wire [7:0] rd_data ; //读出RAM数据 wire key1_flag ; //按键1消抖信号 wire key2_flag ; //按键2消抖信号 //----------------ram_ctrl_inst---------------- ram_ctrl ram_ctrl_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低有效 .key1_flag (key1_flag ), //按键1消抖后有效信号,作为写标志信号 .key2_flag (key2_flag ), //按键2消抖后有效信号,作为读标志信号 .wr_en (wr_en ), //输出写RAM使能,高点平有效 .rd_en (rd_en ), //输出读RAM使能,高电平有效 .addr (addr ), //输出读写RAM地址 .wr_data (wr_data ) //输出写RAM数据 ); //----------------key1_filter_inst---------------- key_filter key1_filter_inst ( .sys_clk (sys_clk ), //系统时钟50Mhz .sys_rst_n (sys_rst_n ), //全局复位 .key_in (key[0] ), //按键输入信号 .key_flag (key1_flag ) //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //----------------key2_filter_inst---------------- key_filter key2_filter_inst ( .sys_clk (sys_clk ), //系统时钟50Mhz .sys_rst_n (sys_rst_n ), //全局复位 .key_in (key[1] ), //按键输入信号 .key_flag (key2_flag ) //key_flag为1时表示消抖后检测到按键被按下 //key_flag为0时表示没有检测到按键被按下 ); //----------------seg_595_dynamic_inst---------------- seg_595_dynamic seg_595_dynamic_inst ( .sys_clk (sys_clk ), //系统时钟,频率50MHz .sys_rst_n (sys_rst_n ), //复位信号,低有效 .data ({12'd0,rd_data} ), //数码管要显示的值 .point (0 ), //小数点显示,高电平有效 .seg_en (1'b1 ), //数码管使能信号,高电平有效 .sign (0 ), //符号位,高电平显示负号 .stcp (stcp ), //输出数据存储寄时钟 .shcp (shcp ), //移位寄存器的时钟输入 .ds (ds ), //串行数据输入 .oe (oe ) //输出使能信号 ); //---------------rom_256x8_inst-------------- ram_256x8 ram_256x8_inst ( .aclr (~sys_rst_n ), //异步清零信号 .address (addr ), //读写地址线 .clock (sys_clk ), //使用系统时钟作为读写时钟 .data (wr_data ), //输入写入RAM的数据 .rden (rd_en ), //读RAM使能 .wren (wr_en ), //写RAM使能 .q (rd_data ) //输出读RAM数据 ); endmodule
第28讲:FIFO-IP核的调用
FIFO(First in First out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写方式。
FIFO存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用;如:多比特数据做跨时钟域处理、前后带宽不同步等都用到了FIFO。
跨时钟域处理
同步FIFO
fifo.v
`timescale 1ns/1ns module fifo ( input wire sys_clk , //系统时钟50Mhz input wire [7:0] pi_data , //输入顶层模块的数据 //要写入到FIFO中的数据 input wire pi_flag , //输入数据有效标志信号 //也作为FIFO的写请求信号 input wire rdreq , //FIFO读请求信号 output wire [7:0] po_data , //FIFO读出的数据 output wire empty , //FIFO空标志信号,高有效 output wire full , //FIFO满标志信号,高有效 output wire [7:0] usedw //FIFO中存在的数据个数 ); //---------------scfifo_256x8_inst------------------- scfifo_256x8 scfifo_256x8_inst( .clock (sys_clk ), //input clock .data (pi_data ), //input [7:0] data .rdreq (rdreq ), //input rdreq .wrreq (pi_flag ), //input wrreq .empty (empty ), //output empty .full (full ), //output full .q (po_data ), //output [7:0] q .usedw (usedw ) //output [7:0] usedw ); endmodule
tb_fifo.v
`timescale 1ns/1ns module tb_fifo(); //reg define reg sys_clk ; reg [7:0] pi_data ; reg pi_flag ; reg rdreq ; reg sys_rst_n ; reg [1:0] cnt_baud ; //wire define wire [7:0] po_data ; wire empty ; wire full ; wire [7:0] usedw ; //初始化系统时钟、复位 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #100; sys_rst_n <= 1'b1; end //sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz always #10 sys_clk = ~sys_clk; //cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_baud <= 2'b0; else if(&cnt_baud == 1'b1) cnt_baud <= 2'b0; else cnt_baud <= cnt_baud + 1'b1; //pi_flag:输入数据有效标志信号,也作为FIFO的写请求信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pi_flag <= 1'b0; //每4个时钟周期且没有读请求时产生一个数据有效标志信号 else if((cnt_baud == 2'd0) && (rdreq == 1'b0)) pi_flag <= 1'b1; else pi_flag <= 1'b0; //pi_data:输入顶层模块的数据,要写入到FIFO中的数据 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pi_data <= 8'b0; //pi_data的值为0~255依次循环 else if((pi_data == 8'd255) && (pi_flag == 1'b1)) pi_data <= 8'b0; else if(pi_flag == 1'b1) //每当pi_flag有效时产生一个数据 pi_data <= pi_data + 1'b1; //rdreq:FIFO读请求信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) rdreq <= 1'b0; else if(full == 1'b1) //当FIFO中的数据存满时,开始读取FIFO中的数据 rdreq <= 1'b1; else if(empty == 1'b1) //当FIFO中的数据被读空时停止读取FIFO中的数据 rdreq <= 1'b0; //------------------------fifo_inst------------------------ fifo fifo_inst( .sys_clk (sys_clk ), //input sys_clk .pi_data (pi_data ), //input [7:0] pi_data .pi_flag (pi_flag ), //input pi_flag .rdreq (rdreq ), //input rdreq .po_data (po_data ), //output [7:0] po_data .empty (empty ), //output empty .full (full ), //output full .usedw (usedw ) //output [7:0] usedw ); endmodule
异步FIFO
fifo.v
`timescale 1ns/1ns module fifo ( //如果端口信号较多,我们可以将端口信号进行分组 //把相关的信号放在一起,使代码更加清晰 //FIFO写端 input wire wrclk , //同步于FIFO写数据的时钟50MHz input wire [7:0] pi_data , //输入顶层模块的数据,要写入到FIFO中 //的数据同步于wrclk时钟 input wire pi_flag , //输入数据有效标志信号,也作为FIFO的 //写请求信号,同步于wrclk时钟 //FIFO读端 input wire rdclk , //同步于FIFO读数据的时钟25MHz input wire rdreq , //FIFO读请求信号,同步于rdclk时钟 //FIFO写端 output wire wrempty , //FIFO写端口空标志信号,高有效, //同步于wrclk时钟 output wire wrfull , //FIFO写端口满标志信号,高有效, //同步于wrclk时钟 output wire [7:0] wrusedw , //FIFO写端口中存在的数据个数, //同步于wrclk时钟 //FIFO读端 output wire [15:0] po_data , //FIFO读出的数据,同步于rdclk时钟 output wire rdempty , //FIFO读端口空标志信号,高有效, //同步于rdclk时钟 output wire rdfull , //FIFO读端口满标志信号,高有效, //同步于rdclk时钟 output wire [6:0] rdusedw //FIFO读端口中存在的数据个数, //同步于rdclk时钟 ); //----------------------dcfifo_256x8to128x16_inst----------------------- dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst ( .data (pi_data), //input [7:0] data .rdclk (rdclk ), //input rdclk .rdreq (rdreq ), //input rdreq .wrclk (wrclk ), //input wrclk .wrreq (pi_flag), //input wrreq .q (po_data), //output [15:0] q .rdempty(rdempty), //output rdempty .rdfull (rdfull ), //output rdfull .rdusedw(rdusedw), //output [6:0] rdusedw .wrempty(wrempty), //output wrempty .wrfull (wrfull ), //output wrfull .wrusedw(wrusedw) //output [7:0] wrusedw ); endmodule
tb_fifo.v
`timescale 1ns/1ns module tb_fifo(); //reg define reg wrclk ; reg [7:0] pi_data ; reg pi_flag ; reg rdclk ; reg rdreq ; reg sys_rst_n ; reg [1:0] cnt_baud ; reg wrfull_reg0 ; reg wrfull_reg1 ; //wire define wire wrempty ; wire wrfull ; wire [7:0] wrusedw ; wire [15:0] po_data ; wire rdempty ; wire rdfull ; wire [6:0] rdusedw ; //初始化时钟、复位 initial begin wrclk = 1'b1; rdclk = 1'b1; sys_rst_n <= 1'b0; #100; sys_rst_n <= 1'b1; end //wrclk:模拟FIFO的写时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz always #10 wrclk = ~wrclk; //rdclk:模拟FIFO的读时钟,每20ns电平翻转一次,周期为40ns,频率为25MHz always #20 rdclk = ~rdclk; //cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔 always@(posedge wrclk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_baud <= 2'b0; else if(&cnt_baud == 1'b1) cnt_baud <= 2'b0; else cnt_baud <= cnt_baud + 1'b1; //pi_flag:输入数据有效标志信号,也作为FIFO的写请求信号 always@(posedge wrclk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pi_flag <= 1'b0; //每4个时钟周期且没有读请求时产生一个数据有效标志信号 else if((cnt_baud == 2'd0) && (rdreq == 1'b0)) pi_flag <= 1'b1; else pi_flag <= 1'b0; //pi_data:输入顶层模块的数据,要写入到FIFO中的数据 always@(posedge wrclk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pi_data <= 8'b0; pi_data的值为0~255依次循环 else if((pi_data == 8'd255) && (pi_flag == 1'b1)) pi_data <= 8'b0; else if(pi_flag == 1'b1) //每当pi_flag有效时产生一个数据 pi_data <= pi_data + 1'b1; //将同步于rdclk时钟的写满标志信号wrfull在rdclk时钟下打两拍 always@(posedge rdclk or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin wrfull_reg0 <= 1'b0; wrfull_reg1 <= 1'b0; end else begin wrfull_reg0 <= wrfull; wrfull_reg1 <= wrfull_reg0; end //rdreq:FIFO读请求信号同步于rdclk时钟 always@(posedge rdclk or negedge sys_rst_n) if(sys_rst_n == 1'b0) rdreq <= 1'b0; //如果wrfull信号有效就立刻读,则不会看到rd_full信号拉高, //所以此处使用wrfull在rdclk时钟下打两拍后的信号 else if(wrfull_reg1 == 1'b1) rdreq <= 1'b1; else if(rdempty == 1'b1)//当FIFO中的数据被读空时停止读取FIFO中的数据 rdreq <= 1'b0; //------------------------fifo_inst------------------------ fifo fifo_inst( //FIFO写端 .wrclk (wrclk ), //input wclk .pi_data(pi_data), //input [7:0] pi_data .pi_flag(pi_flag), //input pi_flag //FIFO读端 .rdclk (rdclk ), //input rdclk .rdreq (rdreq ), //input rdreq //FIFO写端 .wrempty(wrempty), //output wrempty .wrfull (wrfull ), //output wrfull .wrusedw(wrusedw), //output [7:0] wrusedw //FIFO读端 .po_data(po_data), //output [15:0] po_data .rdempty(rdempty), //output rdempty .rdfull (rdfull ), //output rdfull .rdusedw(rdusedw) //output [6:0] rdusedw ); endmodule