1 模拟14位相机时序输出
这是一个模拟相机输出时序的Verilog模块。输入信号为系统时钟信号,系统复位信号。输出信号为像素时钟,14位像素数据,帧有效信号,行有效信号,此时输出的像素在这一帧中的序号。
输出像素数量、行有效信号间隔(每行最后一个像素后多少个时钟周期开始下一行),帧有效信号间隔(例如每帧结束到下一帧开始的时钟周期数)可通过parameter调节。
系统时钟信号直接输出为像素时钟信号,14位像素数据从0开始依次增加,增加到某个可调的阈值后归零继续增加。
1.1 代码
module camera_output (
input sys_clk, // 系统时钟信号
input sys_rst_n, // 系统复位信号,低电平有效
output pixel_clk, // 像素时钟
output reg [13:0] pixel_data, // 14位像素数据
output reg frame_valid, // 帧有效信号
output reg line_valid, // 行有效信号
output reg [18:0] pixel_num // 像素序号
);
// 参数定义
parameter PIXEL_COLUMNS = 64; // 每一行的像素数量,列数
parameter PIXEL_ROWS = 51; // 每一帧有多少行,行数
parameter LINE_INTERVAL = 5; // 行有效信号间隔
parameter FRAME_INTERVAL = 10; // 帧有效信号间隔
parameter PIXEL_THRESHOLD = 16383; // 像素数据增加到这个值后归零
reg [18:0] col_counter; // 列计数器
// 像素时钟直接输出为系统时钟
assign pixel_clk = sys_clk;
// 计算此时输出的像素值所在列
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
col_counter <= 0;
end else if (pixel_counter == (PIXEL_COLUMNS + LINE_INTERVAL) * PIXEL_ROWS + FRAME_INTERVAL - 1) begin
col_counter <= 0;
end else if (col_counter == PIXEL_COLUMNS + LINE_INTERVAL - 1) begin
col_counter <= 0;
end else begin
col_counter <= col_counter + 1;
end
end
// 生成行有效信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
line_valid <= 0;
end else if (pixel_counter >= (PIXEL_COLUMNS + LINE_INTERVAL) * PIXEL_ROWS - 1) begin
line_valid <= 0;
end else if (col_counter <= PIXEL_COLUMNS - 1) begin
line_valid <= 1;
end else begin
line_valid <= 0;
end
end
reg [18:0] pixel_counter; // 一帧全部像素计数器
// 计算此时一帧中输出像素的数量
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
pixel_counter <= 0;
end else if (pixel_counter == (PIXEL_COLUMNS + LINE_INTERVAL) * PIXEL_ROWS + FRAME_INTERVAL - 1) begin
pixel_counter <= 0;
end else begin
pixel_counter <= pixel_counter + 1;
end
end
// 生成帧有效信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
frame_valid <= 0;
end else if (pixel_counter <= (PIXEL_COLUMNS + LINE_INTERVAL) * PIXEL_ROWS - 1) begin
frame_valid <= 1;
end else begin
frame_valid <= 0;
end
end
// 像素数据生成
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
pixel_data <= 0;
end else if (pixel_counter > (PIXEL_COLUMNS + LINE_INTERVAL) * PIXEL_ROWS - 1) begin
pixel_data <= 0;
end else if (pixel_data == PIXEL_THRESHOLD) begin
pixel_data <= 0;
end else if (frame_valid && line_valid) begin
pixel_data <= pixel_data + 1;
end else begin
pixel_data <= pixel_data;
end
end
// 像素序号
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
pixel_num <= 0;
end else if (pixel_counter > (PIXEL_COLUMNS + LINE_INTERVAL) * PIXEL_ROWS - 1) begin
pixel_num <= 0;
end else if (frame_valid && line_valid) begin
pixel_num <= pixel_num + 1;
end else begin
pixel_num <= pixel_num;
end
end
endmodule
1.2 仿真激励代码
`timescale 1ns / 1ps
module camera_output_tb;
reg clk;
reg rest_n;
wire pixel_clk;
wire [13:0] pixel_data;
wire frame_valid;
wire line_valid;
wire [18:0] pixel_num;
// DUT (Device Under Test)
camera_output DUT (
.sys_clk(clk),
.sys_rst_n(rest_n),
.pixel_clk(pixel_clk),
.pixel_data(pixel_data),
.frame_valid(frame_valid),
.line_valid(line_valid),
.pixel_num(pixel_num)
);
// 时钟生成
initial begin
clk = 0;
forever #10 clk = ~clk; // 产生50MHz的时钟信号
end
// 测试序列
initial begin
rest_n = 0; // 初始复位
#100; // 等待100ns以确保时钟稳定
rest_n = 1; // 释放复位
end
endmodule
1.3 测试波形
2 写入一块bram
2.1代码
`timescale 1ns / 1ps
module camera_tobram (
input rest_n,
input pixel_clk,
input [13:0] pixel_data, // 14位像素数据
input frame_valid, // 帧有效信号
input line_valid, // 行有效信号
output reg bram_we, // 写入BRAM的写使能信号,高电平有效
output reg [15:0] bram_data, // 写入BRAM的数据
output reg [18:0] bram_addr // 写入BRAM的地址
);
localparam PIXEL_SIZE = 64 * 51; //输入像素数量
reg [23:0] addr_cnt; //地址计数器
//计算地址,一个像素代表一个地址
always @(posedge pixel_clk or negedge rest_n) begin
if (!rest_n) begin
addr_cnt <= 0;
end else begin
if (frame_valid == 0) begin
addr_cnt <= 0;
end else if (addr_cnt == PIXEL_SIZE - 1) begin
addr_cnt <= 0;
end else if (frame_valid & line_valid) begin
addr_cnt <= addr_cnt + 1;
end else begin
addr_cnt <= addr_cnt;
end
end
end
//对地址进行打一拍处理
always @(posedge pixel_clk or negedge rest_n) begin
if (!rest_n) begin
bram_addr <= 0;
end else begin
bram_addr <= addr_cnt;
end
end
//设置写使能、输出数据
always @(posedge pixel_clk or negedge rest_n) begin
if (!rest_n) begin
bram_we <= 0;
bram_data <= 0;
end else begin
if (frame_valid == 0) begin
bram_we <= 0;
bram_data <= 0;
end else if (bram_addr == PIXEL_SIZE - 1) begin
bram_we <= 0;
bram_data <= 0;
end else if (frame_valid & line_valid) begin
bram_we <= 1;
bram_data <= pixel_data;
end else begin
bram_we <= 0;
bram_data <= 0;
end
end
end
endmodule
2.2 测试代码
sim_top.v
`timescale 1ns / 1ps
module sim_top (
input clk,
input rest_n,
output bram_we, // 写入BRAM的写使能信号,高电平有效
output [15:0] bram_data, // 写入BRAM的数据
output [18:0] bram_addr // 写入BRAM的地址
);
wire pixel_clk;
wire [13:0] pixel_data;
wire frame_valid;
wire line_valid;
camera_output DUT (
.sys_clk(clk),
.sys_rst_n(rest_n),
.pixel_clk(pixel_clk),
.pixel_data(pixel_data),
.frame_valid(frame_valid),
.line_valid(line_valid),
.pixel_num()
);
camera_tobram uut (
.rest_n(rest_n),
.pixel_clk(pixel_clk),
.pixel_data(pixel_data), // 14位像素数据
.frame_valid(frame_valid), // 帧有效信号
.line_valid(line_valid), // 行有效信号
.bram_we(bram_we), // 写入BRAM的写使能信号,高电平有效
.bram_data(bram_data), // 写入BRAM的数据
.bram_addr(bram_addr) // 写入BRAM的地址
);
endmodule
camera_output_tb.v
`timescale 1ns / 1ps
module camera_output_tb;
reg clk;
reg rest_n;
wire bram_we; // 写入BRAM的写使能信号,高电平有效
wire [15:0] bram_data; // 写入BRAM的数据
wire [18:0] bram_addr; // 写入BRAM的地址
// DUT (Device Under Test)
sim_top uuu (
.clk(clk),
.rest_n(rest_n),
.bram_we(bram_we), // 写入BRAM的写使能信号,高电平有效
.bram_data(bram_data), // 写入BRAM的数据
.bram_addr(bram_addr) // 写入BRAM的地址
);
// 时钟生成
initial begin
clk = 0;
forever #10 clk = ~clk; // 产生50MHz的时钟信号
end
// 测试序列
initial begin
rest_n = 0; // 初始复位
#100; // 等待100ns以确保时钟稳定
rest_n = 1; // 释放复位
end
endmodule
2.3 测试波形
3 缓存一帧,写入两块bram(只有写入部分,无输出部分)
3.1 代码
`timescale 1ns / 1ps
//帧缓存模块,可以对输入的连续摄像头数据进行一帧的缓存
module image_frame_buffer (
input reset_n,
input camera_clk,
input camera_frame_valid_i,
input camera_line_valid_i,
input [13:0] camera_data_i,
output reg [13:0] camera_data_to_bram,
output reg [18:0] write_addr,
output buffer0_write_en,
output buffer1_write_en
);
localparam PIXEL_SIZE = 64 * 51; //输入的每帧像素数量&每个bram深度
reg buffer0_write_flag, buffer1_write_flag;
reg camera_frame_valid_i_d0;
reg camera_line_valid_i_d0;
wire frame_rise_edge;
//对帧有效信号和行有效信号打一拍
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
camera_frame_valid_i_d0 <= 0;
camera_line_valid_i_d0 <= 0;
end else begin
camera_frame_valid_i_d0 <= camera_frame_valid_i;
camera_line_valid_i_d0 <= camera_line_valid_i;
end
end
//检测帧有效信号的上升沿
assign frame_rise_edge = camera_frame_valid_i && !camera_frame_valid_i_d0;
//每一帧开始换另一个bram写
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
buffer0_write_flag <= 0;
buffer1_write_flag <= 1;
end else begin
if (frame_rise_edge == 1) begin
buffer0_write_flag <= ~buffer0_write_flag;
buffer1_write_flag <= ~buffer1_write_flag;
end else begin
buffer0_write_flag <= buffer0_write_flag;
buffer1_write_flag <= buffer1_write_flag;
end
end
end
//对于每一个bram的写有效信号
assign buffer0_write_en = buffer0_write_flag && camera_frame_valid_i_d0 && camera_line_valid_i_d0;
assign buffer1_write_en = buffer1_write_flag && camera_frame_valid_i_d0 && camera_line_valid_i_d0;
//计算写入地址
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
write_addr <= 0;
end else begin
if (camera_frame_valid_i_d0 == 0) begin
write_addr <= 0;
end else if (write_addr == PIXEL_SIZE - 1) begin
write_addr <= 0;
end else if (camera_line_valid_i_d0 == 0) begin
write_addr <= write_addr;
end else begin
write_addr <= write_addr + 1;
end
end
end
//写入bram的数据要打一拍,对齐写使能和写地址
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
camera_data_to_bram <= 0;
end else begin
camera_data_to_bram <= camera_data_i;
end
end
endmodule
3.2 测试代码
sim_top.v
`timescale 1ns / 1ps
module sim_top (
input clk,
input rest_n,
output [13:0] camera_data_to_bram,
output [18:0] write_addr,
output buffer0_write_en,
output buffer1_write_en
);
wire pixel_clk;
wire [13:0] pixel_data;
wire frame_valid;
wire line_valid;
camera_output DUT (
.sys_clk(clk),
.sys_rst_n(rest_n),
.pixel_clk(pixel_clk),
.pixel_data(pixel_data),
.frame_valid(frame_valid),
.line_valid(line_valid),
.pixel_num()
);
image_frame_buffer u_image_frame_buffer (
.reset_n(rest_n),
.camera_clk(pixel_clk),
.camera_frame_valid_i(frame_valid),
.camera_line_valid_i(line_valid),
.camera_data_i(pixel_data),
.camera_data_to_bram(camera_data_to_bram),
.write_addr(write_addr),
.buffer0_write_en(buffer0_write_en),
.buffer1_write_en(buffer1_write_en)
);
endmodule
tb_top.v
`timescale 1ns / 1ps
module camera_output_tb;
reg clk;
reg rest_n;
wire [13:0] camera_data_to_bram;
wire [18:0] write_addr;
wire buffer0_write_en;
wire buffer1_write_en;
// DUT (Device Under Test)
sim_top uuu (
.clk(clk),
.rest_n(rest_n),
.camera_data_to_bram(camera_data_to_bram),
.write_addr(write_addr),
.buffer0_write_en(buffer0_write_en),
.buffer1_write_en(buffer1_write_en)
);
// 时钟生成
initial begin
clk = 0;
forever #10 clk = ~clk; // 产生50MHz的时钟信号
end
// 测试序列
initial begin
rest_n = 0; // 初始复位
#100; // 等待100ns以确保时钟稳定
rest_n = 1; // 释放复位
end
endmodule
3.3 波形图
4 缓存一帧,写入两块bram,输出像素数据和这一帧的最大值最小值
4.1代码
`timescale 1ns / 1ps
//帧缓存模块,可以对输入的连续摄像头数据进行一帧的缓存,缓存完输出数据和这一帧的最大值最小值
module image_frame_buffer (
input reset_n,
input camera_clk,
input camera_frame_valid_i,
input camera_line_valid_i,
input [13:0] camera_data_i,
output camera_frame_valid_o,
output [13:0] camera_data_o,
output [13:0] data_max,
output [13:0] data_min,
output [18:0] bram_read_addr,
output bram0_read_flag,
output bram1_read_flag
);
localparam PIXEL_SIZE = 64 * 51; //输入的每帧像素数量&每个bram深度
reg buffer0_write_flag, buffer1_write_flag;
reg camera_frame_valid_i_d0;
reg camera_line_valid_i_d0;
wire frame_rise_edge;
reg [13:0] camera_data_to_bram;
reg [18:0] write_addr;
wire buffer0_write_en;
wire buffer1_write_en;
//对帧有效信号和行有效信号打一拍
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
camera_frame_valid_i_d0 <= 0;
camera_line_valid_i_d0 <= 0;
end else begin
camera_frame_valid_i_d0 <= camera_frame_valid_i;
camera_line_valid_i_d0 <= camera_line_valid_i;
end
end
//检测帧有效信号的上升沿
assign frame_rise_edge = camera_frame_valid_i && !camera_frame_valid_i_d0;
//每一帧开始换另一个bram写
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
buffer0_write_flag <= 0;
buffer1_write_flag <= 1;
end else begin
if (frame_rise_edge == 1) begin
buffer0_write_flag <= ~buffer0_write_flag;
buffer1_write_flag <= ~buffer1_write_flag;
end else begin
buffer0_write_flag <= buffer0_write_flag;
buffer1_write_flag <= buffer1_write_flag;
end
end
end
//对于每一个bram的写有效信号
assign buffer0_write_en = buffer0_write_flag && camera_frame_valid_i_d0 && camera_line_valid_i_d0;
assign buffer1_write_en = buffer1_write_flag && camera_frame_valid_i_d0 && camera_line_valid_i_d0;
//计算写入地址
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
write_addr <= 0;
end else begin
if (camera_frame_valid_i_d0 == 0) begin
write_addr <= 0;
end else if (write_addr == PIXEL_SIZE - 1) begin
write_addr <= 0;
end else if (camera_line_valid_i_d0 == 0) begin
write_addr <= write_addr;
end else begin
write_addr <= write_addr + 1;
end
end
end
//写入bram的数据要打一拍,对齐写使能和写地址
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
camera_data_to_bram <= 0;
end else begin
camera_data_to_bram <= camera_data_i;
end
end
//两块bram对一帧进行缓存再输出
image_buffe_0 bram0 (
.clka(camera_clk), // input wire clka
.wea(buffer0_write_en), // input wire [0 : 0] wea
.addra(write_addr), // input wire [11 : 0] addra
.dina(camera_data_to_bram), // input wire [13 : 0] dina
.clkb(camera_clk), // input wire clkb
.addrb(read_addr), // input wire [11 : 0] addrb
.doutb(buffer0_read_data) // output wire [13 : 0] doutb
);
image_buffe_1 bram1 (
.clka(camera_clk), // input wire clka
.wea(buffer1_write_en), // input wire [0 : 0] wea
.addra(write_addr), // input wire [11 : 0] addra
.dina(camera_data_to_bram), // input wire [13 : 0] dina
.clkb(camera_clk), // input wire clkb
.addrb(read_addr), // input wire [11 : 0] addrb
.doutb(buffer1_read_data) // output wire [13 : 0] doutb
);
reg [18:0] read_addr;
reg buffer0_read_flag, buffer1_read_flag;
wire [13:0] buffer0_read_data;
wire [13:0] buffer1_read_data;
reg frame_valid_o;
//设置对每块bram的读标志,读标志和读地址对齐,比读出的数据和帧有效信号早一拍
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
buffer0_read_flag <= 0;
buffer1_read_flag <= 0;
end else begin
if (read_addr == PIXEL_SIZE) begin
buffer0_read_flag <= 0;
buffer1_read_flag <= 0;
end else if (write_addr == PIXEL_SIZE - 1 && buffer0_write_flag == 1) begin
buffer0_read_flag <= 1;
buffer1_read_flag <= 0;
end else if (write_addr == PIXEL_SIZE - 1 && buffer1_write_flag == 1) begin
buffer0_read_flag <= 0;
buffer1_read_flag <= 1;
end else begin
buffer0_read_flag <= buffer0_read_flag;
buffer1_read_flag <= buffer1_read_flag;
end
end
end
//计算读地址,因为bram读出数据要在地址延后一拍才出,所以要把读地址+1,让数据全部从bram出来再清零
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
read_addr <= 0;
end else begin
if (buffer0_read_flag == 0 && buffer1_read_flag == 0) begin
read_addr <= 0;
end else if (read_addr == PIXEL_SIZE) begin
read_addr <= 0;
end else begin
read_addr <= read_addr + 1;
end
end
end
//设置帧有效标志
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
frame_valid_o <= 0;
end else begin
if (buffer0_read_flag == 0 && buffer1_read_flag == 0) begin
frame_valid_o <= 0;
end else if (read_addr == PIXEL_SIZE) begin
frame_valid_o <= 0;
end else begin
frame_valid_o <= 1;
end
end
end
//输出帧有效标志
assign camera_frame_valid_o = frame_valid_o;
//设置两个bram的输出标志
wire bram0_out_flag, bram1_out_flag;
assign bram0_out_flag = (buffer0_read_flag == 1) && (frame_valid_o == 1);
assign bram1_out_flag = (buffer1_read_flag == 1) && (frame_valid_o == 1);
//输出读到的数据
assign camera_data_o = bram0_out_flag ? buffer0_read_data : (bram1_out_flag ? buffer1_read_data : 0);
reg [13:0] bram0_max, bram0_min;
reg [13:0] bram1_max, bram1_min;
reg [13:0] bram0_buffer_max, bram0_buffer_min;
reg [13:0] bram1_buffer_max, bram1_buffer_min;
//计算此时输入的图像数据的最大值最小值
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
bram0_max <= 0;
bram0_min <= 16383;
end else begin
if (buffer0_write_flag && camera_frame_valid_i_d0 == 0) begin
bram0_max <= 0;
bram0_min <= 16383;
end else begin
bram0_max <= (camera_data_to_bram > bram0_max) ? camera_data_to_bram : bram0_max;
bram0_min <= (camera_data_to_bram < bram0_min) ? camera_data_to_bram : bram0_min;
end
end
end
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
bram1_max <= 0;
bram1_min <= 16383;
end else begin
if (buffer1_write_flag && camera_frame_valid_i_d0 == 0) begin
bram1_max <= 0;
bram1_min <= 16383;
end else begin
bram1_max <= (camera_data_to_bram > bram1_max) ? camera_data_to_bram : bram1_max;
bram1_min <= (camera_data_to_bram < bram1_min) ? camera_data_to_bram : bram1_min;
end
end
end
//缓存最大值最小值
always @(posedge camera_clk or negedge reset_n) begin
if (!reset_n) begin
bram0_buffer_max <= 0;
bram0_buffer_min <= 0;
bram1_buffer_max <= 0;
bram1_buffer_min <= 0;
end else begin
if (write_addr == PIXEL_SIZE - 1) begin
bram0_buffer_max <= bram0_max;
bram0_buffer_min <= bram0_min;
bram1_buffer_max <= bram1_max;
bram1_buffer_min <= bram1_min;
end else begin
bram0_buffer_max <= bram0_buffer_max;
bram0_buffer_min <= bram0_buffer_min;
bram1_buffer_max <= bram1_buffer_max;
bram1_buffer_min <= bram1_buffer_min;
end
end
end
//输出最大值和最小值
assign data_max = bram0_out_flag ? bram0_buffer_max : bram1_out_flag ? bram1_buffer_max : 16383;
assign data_min = bram0_out_flag ? bram0_buffer_min : bram1_out_flag ? bram1_buffer_min : 0;
assign bram_read_addr = read_addr;
assign bram0_read_flag = buffer0_read_flag;
assign bram1_read_flag = buffer1_read_flag;
endmodule
4.2 测试文件
`timescale 1ns / 1ps
module sim_top (
input clk,
input rest_n,
output camera_frame_valid_o,
output [13:0] camera_data_o,
output [13:0] data_max,
output [13:0] data_min,
output [18:0] bram_read_addr,
output bram0_read_flag,
output bram1_read_flag
);
wire pixel_clk;
wire [13:0] pixel_data;
wire frame_valid;
wire line_valid;
camera_output DUT (
.sys_clk(clk),
.sys_rst_n(rest_n),
.pixel_clk(pixel_clk),
.pixel_data(pixel_data),
.frame_valid(frame_valid),
.line_valid(line_valid),
.pixel_num()
);
image_frame_buffer u_image_frame_buffer (
.reset_n(rest_n),
.camera_clk(pixel_clk),
.camera_frame_valid_i(frame_valid),
.camera_line_valid_i(line_valid),
.camera_data_i(pixel_data),
.camera_frame_valid_o(camera_frame_valid_o),
.camera_data_o(camera_data_o),
.data_max(data_max),
.data_min(data_min),
.bram_read_addr(bram_read_addr),
.bram0_read_flag(bram0_read_flag),
.bram1_read_flag(bram1_read_flag)
);
endmodule
`timescale 1ns / 1ps
module camera_output_tb;
reg clk;
reg rest_n;
wire camera_frame_valid_o;
wire [13:0] camera_data_o;
wire [13:0] data_max;
wire [13:0] data_min;
wire [18:0] bram_read_addr;
wire bram0_read_flag;
wire bram1_read_flag;
// DUT (Device Under Test)
sim_top uuu (
.clk(clk),
.rest_n(rest_n),
.camera_frame_valid_o(camera_frame_valid_o),
.camera_data_o(camera_data_o),
.data_max(data_max),
.data_min(data_min),
.bram_read_addr(bram_read_addr),
.bram0_read_flag(bram0_read_flag),
.bram1_read_flag(bram1_read_flag)
);
// 时钟生成
initial begin
clk = 0;
forever #10 clk = ~clk; // 产生50MHz的时钟信号
end
// 测试序列
initial begin
rest_n = 0; // 初始复位
#100; // 等待100ns以确保时钟稳定
rest_n = 1; // 释放复位
end
endmodule
4.3 波形图