项目概述
在学习sdram的使用的时候需要用到FIFO,不想重头写一个了,决定直接用安路的FIFO IP,之前没有用过,对它的功能用法还不太了解,所以写一个小工程来学习一下。项目功能设定为:一秒的时钟脉冲内向FIFO发送数据,从0开始一直到255,然后直接从FIFO中读出数据,发送到数码管进行显示,并单独引出一个一秒亮一秒灭的LED与其进行对比,判断功能完整性。
开发板:安路EG4S20BG256,cortex-M0内核。
编译环境:安路TD5.6.2版本。
为了简便,采用同步FIFO,输入输出共用一个时钟,输入输出都采用8位宽,1024深度。
程序层级
顶层代码
module fifo_ctrl(
input clk,
input rst, //用于复位,连接key1
output [3:0] digitalCS,
output [7:0] digitalDATA,
output LED
);
wire empty_flag;
wire full_flag;
wire we,re;
reg [8:0] di;
reg [8:0] do;
// 1秒周期时钟
reg [25:0] counter;
wire clk_1Hz;
reg clk_1Hz_reg;
// 计数器逻辑
always @(posedge clk or posedge rst) begin
if (rst) begin
counter <= 26'd0;
clk_1Hz_reg <= 1'b0;
end else if (counter == 26'd49_999_999) begin // 50,000,000 - 1
counter <= 26'd0;
clk_1Hz_reg <= ~clk_1Hz_reg; // 翻转时钟输出
end else begin
counter <= counter + 1'b1;
end
end
assign clk_1Hz = clk_1Hz_reg;
//半秒时钟脉冲
reg [24:0] counter_half;
wire clk_2Hz;
reg clk_2Hz_reg;
// 计数器逻辑
always @(posedge clk or posedge rst) begin
if (rst) begin
counter_half <= 26'd0;
clk_2Hz_reg <= 1'b0;
end else if (counter_half == 25'd24_999_999) begin // 25,000,000 - 1
counter_half <= 26'd0;
clk_2Hz_reg <= ~clk_2Hz_reg; // 翻转时钟输出
end else begin
counter_half <= counter_half + 1'b1;
end
end
assign clk_2Hz = clk_2Hz_reg;
// 数据生成器
reg [7:0] data_to_write;
always @(posedge clk_2Hz or posedge ret) begin
if (rst)
data_to_write <= 8'd0;
else
data_to_write <= data_to_write + 8'd1;
end
wire [7:0] fifo_data_in, fifo_data_out;
// assign we = clk_1Hz & ~full_flag; // 每秒写入一次,如果FIFO不满
assign we = (counter_half == 25'd24_999_999) & ~full_flag; // 每秒写入一次,如果FIFO不满
assign re = ~empty_flag; // 只要FIFO不空就持续读取
assign LED = clk_1Hz; // 1Hz闪烁LED
fifo u_fifo(
.rst (rst),
.di (data_to_write),
.clk (clk),
.we (we),
.do (fifo_data_out),
.re (re),
.empty_flag (empty_flag),
.full_flag (full_flag)
);
// 三位数码管显示控制
Digitron_NumDisplay display_controller (
.CLK(clk),
.Result(fifo_data_out),
.Digitron_Out(digitalDATA),
.DigitronCS_Out(digitalCS)
);
endmodule
这里必须要强调一下,在程序初期,我试图直接将1Hz的时钟脉冲进行数据发送,但这样的结果是数码管2秒才会变换一个数字,是因为程序里设定1Hz脉冲上升沿才会发送数据,这中间的间隔实际上就变成了2s,因此,后来我又写了一个2Hz的脉冲,这时在上升沿发送数据就对了。
三位数码管显示代码
module Digitron_NumDisplay
(
input wire CLK,
input wire [7:0] Result,
output wire [7:0] Digitron_Out,
output wire [3:0] DigitronCS_Out
);
parameter T250K = 16'd16666; // 调整为3kHz刷新率 (50MHz / 3000Hz ≈ 16666)
reg [15:0] Count;
reg [1:0] cnt;
reg [3:0] W_DigitronCS_Out;
always @(posedge CLK)
begin
if (Count == T250K)
begin
Count <= 16'd0;
cnt <= cnt + 1'b1;
end
else
Count <= Count + 1'b1;
end
always @(cnt)
begin
case (cnt)
2'b00: W_DigitronCS_Out = 4'b1110; // 个位
2'b01: W_DigitronCS_Out = 4'b1101; // 十位
2'b10: W_DigitronCS_Out = 4'b1011; // 百位
default: W_DigitronCS_Out = 4'b1111; // 全不选
endcase
end
reg [3:0] SingleNum;
reg [7:0] W_Digitron_Out;
parameter _0 = 8'b0011_1111, _1 = 8'b0000_0110, _2 = 8'b0101_1011,
_3 = 8'b0100_1111, _4 = 8'b0110_0110, _5 = 8'b0110_1101,
_6 = 8'b0111_1101, _7 = 8'b0000_0111, _8 = 8'b0111_1111,
_9 = 8'b0110_1111;
wire [7:0] hundreds;
wire [7:0] tens;
wire [7:0] ones;
assign hundreds = Result / 100;
assign tens = (Result % 100) / 10;
assign ones = Result % 10;
always @(*)
begin
case(W_DigitronCS_Out)
4'b1110: SingleNum = ones;
4'b1101: SingleNum = tens;
4'b1011: SingleNum = hundreds;
default: SingleNum = 4'd0;
endcase
case(SingleNum)
0: W_Digitron_Out = _0;
1: W_Digitron_Out = _1;
2: W_Digitron_Out = _2;
3: W_Digitron_Out = _3;
4: W_Digitron_Out = _4;
5: W_Digitron_Out = _5;
6: W_Digitron_Out = _6;
7: W_Digitron_Out = _7;
8: W_Digitron_Out = _8;
9: W_Digitron_Out = _9;
default: W_Digitron_Out = 8'b0000_0000;
endcase
end
assign Digitron_Out = W_Digitron_Out;
assign DigitronCS_Out = W_DigitronCS_Out;
endmodule
运行结果
程序顺利运行。这里用到了三位数码管以及一个LED,数码管一秒一计数,LED两秒闪灭一周期,用来与FIFO读出的数据进行对比,判断FIFO读出的效果。经过实际测试,数码管正常显示,计数到255即发生反转,功能正常。
总结
哦!万能的Chatgpt以及Claude!他们几乎能帮我写出全部的代码!
另外,总的来看,同步FIFO IP核的使用并不复杂,这里不再考虑内部RAM的构建,因此带来了极大的方便。数据读出也很迅速,毕竟在50MHz时钟下人眼也几乎无法判断LED与数码管之间是否有延迟。
在写程序之前,我对FIFO的输入时钟clk不太理解,认为对FIFO的读入与读出会与这个clk信号有直接联系,但实测来看,如果仅用IP的话看起来并没有什么关系,这里只要把它和系统时钟连在一起就好了,不过要注意,如果自己写FIFO,时钟还是很重要的,尤其对于异步FIFO而言。