一,引言
此篇为笔者第一次学习FPGA技术,在学习此技术之前,有基础的电路分析,数电,模电,c语言,人工智能理论基础,此次的学习方式为跟随项目组老师先学习基础再进行项目实践重现项目开发过程。
二,verilog语言基础
模块声明
module xx(
input xx, //默认不占资源的是wire线
input reg xx , //reg声明寄存器
output xx,
output reg xx
);
endmodule
localparam //状态机声明
parameter INTERVAL_TIME = 28'd5000_0000; //设定参数
//状态名 编号
localparam S0 = 2'b00; //本地化参数
localparam S1 = 2'b01;
localparam S2 = 2'b10;
localparam S3 = 2'b11;
三,基础寄存器实现以及基础功能实现
module logic_exer( //模块声明
input clk, //时钟信号
input rst_n,
input [7:0]theta,
input wire [7:0]a_i, //输入信号 //wire是一条线(不占资源)
input wire [7:0]b_i,
output reg [9:0]sin_o,
output [7:0]c_o,
output shift_o,
output b_o, //输出信号
output reg [3:0]led
);
//reg [27:0]count; //[9:0]是线宽,10bit 2^10==1024
parameter INTERVAL_TIME = 28'd5000_0000; //设定参数
//状态名 编号
localparam S0 = 2'b00; //本地化参数
localparam S1 = 2'b01;
localparam S2 = 2'b10;
localparam S3 = 2'b11;
reg [27:0]count; //[9:0]是线宽,10bit 2^10==1024
reg [1:0] current_state;
reg [1:0] next_state;
wire work;
assign work=1'b1;
1.状态机三段式写法
//三段式写法
//第一段:刷新curren_state
always @(posedge clk or negedge rst_n)
if(~rst_n)
current_state<=S0;
else
current_state <= next_state;
//第二段
always @(*) //组合电路的另一种写法 求next_state
case(current_state)
S0:
if (count == INTERVAL_TIME-1'b1)
next_state <= S1;
S1:
if (count == INTERVAL_TIME-1'b1)
next_state <= S2;
S2:
if (count == INTERVAL_TIME-1'b1)
next_state <= S3;
S3:
if (count == INTERVAL_TIME-1'b1)
next_state <= S0;
default: next_state <= S0;
endcase
//第三段 if or case
always@(posedge clk or negedge rst_n)begin
if (~rst_n)
led<=4'b0000;
else
if (work==1'b1)begin
if(current_state == S0)begin
led[3:0] <= 4'b0001;
end
else if(current_state == S1)begin
led[3:0] <= 4'b0010;
end
else if(current_state == S2)begin
led[3:0] <= 4'b0100;
end
else if(current_state == S3)begin
led[3:0] <= 4'b1000;
end
end
end
2.D触发器
//第一个d触发器
always @(posedge clk) //上升触发时钟信号 //always是申请一个时序逻辑电路
b_t0<=a_i[0]; //d触发器表达式
//第二个d触发器,吧输出再于输入延迟一个时钟信号单位
always @(posedge clk) //上升触发时钟信号 //@表示触发条件的意思
b_t<=b_t0; //d触发器表达式
3.计数器
//计数器
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
count<=28'd0;
else
if(count<INTERVAL_TIME) //用减一的方法让计数准确
count<=count+1'b1;
else //在10的时候置零
count<=28'd0;
end
4.移位寄存器
always @(posedge clk)begin
if(count==0) //在满足count这个条件时下一个条件data刷新
//reg_shift<={reg_shift[6:0],reg_shift[7]}; //语句中的逗号是拼接前后,第一位到最后一位,其余的往前移动一位
reg_shift<={reg_shift[0],reg_shift[7:1]}; //所有向后移位移位
end
assign shift_o = reg_shift[0]; //把reg_shift[0]赋予shift_o
4.译码器(2种写法)
//译码器,用if来实现
always @(posedge clk)begin
if (theta==10'd0)
sin_o<=10'd12;
else if(theta==10'd1)
sin_o<=10'd23;
else if(theta==10'd2)
sin_o<=10'd16;
else if(theta==10'd3)
sin_o<=10'd15;
else if(theta==10'd4)
sin_o<=10'd28;
else if(theta==10'd5)
sin_o<=10'd66;
else if(theta==10'd6)
sin_o<=10'd42;
else if(theta==10'd7)
sin_o<=10'd11;
else if(theta==10'd8)
sin_o<=10'd15;
else if(theta==10'd9)
sin_o<=10'd66;
end
//译码器 查找表(sin查找表),用case来实现
always @(posedge clk)begin
case(theta)
8'd0 : sin_o<=10'd0;
8'd1 : sin_o<=10'd1;
8'd2 : sin_o<=10'd2;
8'd3 : sin_o<=10'd3;
8'd4 : sin_o<=10'd4;
8'd5 : sin_o<=10'd5;
8'd6 : sin_o<=10'd6;
8'd7 : sin_o<=10'd7;
8'd8 : sin_o<=10'd8;
8'd09 : sin_o<=10'd9;
8'd10 : sin_o<=10'd10;
8'd11 : sin_o<=10'd11;
8'd12 : sin_o<=10'd12;
8'd13 : sin_o<=10'd13;
//0到90度,查表
default:sin_o<=8'd0;
endcase
end
5.延迟机
reg a_r[2:0];
always@(posedge clk)begin
a_r[0]<=a_i;
a_r[1]<=a_r[0];
a_r[2]<=a_r[1];
end
endmodule
三,tb
代码仿真使用modelsim软件,在仿真前需要先编辑tb测试文件,添加所需输入信号,并应用对应仿真模块。
`timescale 1ns/1ns //设定时间基准
module gpu_tb();
reg clk = 1'b0; //模拟时钟,注意开始时给时钟信号设定初始值
reg [7:0]a_i,b_i;
reg [7:0]theta;
wire [7:0]c_o;
wire shift_o;
wire b_o;
wire [9:0]sin_o;
always #10 clk = ~clk; //#是延迟符号,20ns //此处生成一个模拟的时钟信号
//起一个线程
initial begin
theta = 8'd3;
a_i = 8'd2;
b_i = 8'd3; //设定ab_i的初始值
//a_i = 1'b0; //1'是位宽 b0是二进制的0
//#40 //延时40ns
//a_i = 1'b1;
//#60
//a_i = 1'b0;
end
/*
genvar i;
generate
for (i=0;i<10;i+1)begin
logic_exer #(
.INTERVAL_TIME(28'd10)
)
end
endgenerate
*/
logic_inst(
.clk(clk),
.a_i(a_i),
.b_o(b_o),
.b_i(b_i),
.c_o(c_o),
.shift_o(shift_o),
.theta(theta),
.sin_o(sin_o)
);
endmodule
//软件环境制作一个时钟