写在前面
本项目为MCDF数据整形器设计,所有的参考代码见我的github
https://github.com/SuperiorLQF/verilog_ALL/tree/master/MCDF
其中设计的参考文档见github文件中的MCDF修订版.docx
文件。选择的工具链是Vscode & iverilog & gtkwave
,相关工具的安装与环境配置就不介绍了,可以参考其他文章。
注意:不同的工具链代码可能有区别,例如一些集成开发环境不需要书写`include,而是直接将代码加入工程作为替代
MCDF模块按照子模块划分,总共分为了control_register
、slvae_FIFO
、arbiter
、formatter
这几个子模块和MCDF
这个顶层模块。本章首先从控制寄存器control_register
开始介绍,这样的安排顺序更容易对于模块整体有系统性的理解。
下面我们正式开始介绍。
第一节 control_register
文档理解
将设计文档中控制寄存器control_register
的设计文档相关内容引用如下
1.接口描述及其时序
控制寄存器接口时序如图所示。在每个时钟周期根据cmd
命令完成指定操作。当cmd
为写指令时,将数据cmd_data_i
写入cmd_addr
指定的寄存器中。当cmd
为读指令时,从cmd_addr
指定的寄存器中的数据读出,并在下一个时钟周期送到cmd_data_o
端口。当cmd
为其它指令时不进行任何操作。
2.接口信号
见设计文档,这里不在赘述。
首先我们看到,cmd_i[1:0]
是一个2位的命令输入,但是设计里没有规定WR
,RD
,idle
各自的对应二进制码,因此这里进行自己定义:选择按照one-hot
为RD
和WR
进行编码
cmd_i[1:0] | 对应命令 |
---|---|
01 | RD |
10 | WR |
00/11 | idle |
接着,我们观察时序发现,一次只能执行一种指令,因此不同于同时读写的存储器写法,只需要简单的 always-case 语句就可以实现,这里需要注意。
此外,需要关注电路的同步性,电路的特性类似于D触发器。cmd_addr
和cmd
在一个时钟周期内是对应的,即在时钟上升沿到来之前(本时钟周期)给定,在始终上升沿触发时(下一时钟周期)完成对应的操作。因此,在图中cmd_addr=0x04,cmd=RD
时,在下一时钟周期才会读出数据D2
弄清了基本时序之后,下面来观察控制寄存器内部的具体存储结构。文档中给出说明,三个通道每个通道都对应2个寄存器单元**:控制寄存器单元和状态寄存器单元**。每个单元都是标准的32位寄存器。
control_register
中的寄存器安排如下(这里相比于设计文档有小的调整)
地址 | 寄存器单元描述 |
---|---|
0x00 | slave0的控制寄存器 |
0x04 | slave1的控制寄存器 |
0x08 | slave2的控制寄存器 |
0x0C | slave0的状态寄存器 |
0x10 | slave1的状态寄存器 |
0x14 | slave2的状态寄存器 |
对于控制寄存器和状态寄存器有如下规定:
控制寄存器
,32bit,可读写,位定义为:
- bit[0] :通道使能信号。1为打开,0为关闭。复位值为1。
- bit[2:1] :优先级。0为最高,3为最低。复位值为3。
- bit[5:3] :数据包长度。0对应长度4,1对应8,2对应16,3对应32。其它数值均暂时对应32。复位值为0。
- bit[31:6] :保留位,不能写入。复位值为0。
状态寄存器
,32bit,只读,位定义为:
- bit[ 7: 0] :从端FIFO0的可写余量,实时同步FIFO0的余量,复位值为FIFO深度值。
- bit[31:8] :保留位,复位值为0。
重点关注其复位值,因为需要在always-reset
中进行描述.
由于3个控制寄存器的复位值是一样的,3个状态寄存器亦如此,因此可以在模块中设置参数parameter
来进行赋值复位,以增强代码可读性和可维护性。
parameter CTRL_REG_DEFAULT='b111,
STATE_REG_DEFAULT=32'd63//FIFO深度63
这里FIFO深度规定为63而不是64是因为:64=26,表示0-64需要[6:0]共7位二进制数,而文档中虽然在状态寄存器这里给了8位存储空间,但是在margin这里只给了[5:0]共6位位宽,因此这里将深度改为63,就可以在6位位宽的margin中进行传输。
输出对应:前面已经说了数据、地址、指令这些输入输出,但我们发现,control_register
中还有一些IO,例如slvx_margin_i[5:0]
,slvx_en_o
,slvx_pkglen_o[2:0]
,slvx_prio_o[1:0]
,
这些都是什么含义?观察文档中这些IO信号的注释,我们发现,它们就是控制寄存器和状态寄存器对应位的描述。
举例而言,控制寄存器bit[2:1]
描述是该通道的优先级,因此slvx_prio_o[2:1]
应当就是该寄存器对应位的值。那么实际上这些信号就是寄存器对应位的信号通过wire直接引出来罢了。
综合以上,我们对control_register
设计有了具体把握,下面就进行verilog设计代码编写。
第二节 control_register
代码实现
模块文件名:control_register.v
注释见代码内
/*************************<MCDF控制寄存器>*********************/
//存储器仅在时钟周期完成读或写,并不完成同时读写
`timescale 1ns/100ps
/*************************<端口声明>*********************/
module control_register
#(
parameter CTRL_REG_DEFAULT='b111,
STATE_REG_DEFAULT=32'd63//FIFO深度63
)
(
input clk_i,
rstn_i,
input wire [1:0] cmd_i ,
input wire [5:0] cmd_addr_i,
input wire [31:0] cmd_data_i,
input wire [5:0] slv0_margin_i,
slv1_margin_i,
slv2_margin_i,
output slv0_en_o,
slv1_en_o,
slv2_en_o,
output reg [31:0] cmd_data_o,
output wire [1:0] slv0_prio_o,
slv1_prio_o,
slv2_prio_o,
output wire [2:0] slv0_pkglen_o,
slv1_pkglen_o,
slv2_pkglen_o
);
/*************************<中间信号>*********************/
reg [31:0] Register [5:0];//6个寄存器构成的存储单元
integer i;
/*************************<时序电路>*********************/
always @(posedge clk_i or negedge rstn_i) begin
if(!rstn_i)begin //给寄存器赋复位值
for(i=0;i<3;i=i+1)begin
Register[i]<=CTRL_REG_DEFAULT;
end
for(i=3;i<6;i=i+1)begin
Register[i]<=STATE_REG_DEFAULT;
end
cmd_data_o<='d0; //!!!不加这句不给综合
end
else begin
case (cmd_i)
//!!!这里没有加入状态寄存器不能写的约束,所有寄存器均可读写,并且读取的通道余量是延迟一个时钟的
2'b01:cmd_data_o<=Register[cmd_addr_i>>2];//RD
2'b10:Register[cmd_addr_i>>2]<=cmd_data_i;//WR
default: //IDLE
; //不做任何操作但是要加上,防止综合出锁存器
endcase
//!!!锁存更新通道余量,不是实时的,会延迟一个时钟
Register[3][5:0]<=slv0_margin_i;
Register[4][5:0]<=slv1_margin_i;
Register[5][5:0]<=slv2_margin_i;
end
end
/*************************<组合逻辑电路>*********************/
//通道使能信号
assign slv0_en_o=Register[0][0];
assign slv1_en_o=Register[1][0];
assign slv2_en_o=Register[2][0];
//通道优先级
assign slv0_prio_o=Register[0][2:1];
assign slv1_prio_o=Register[1][2:1];
assign slv2_prio_o=Register[2][2:1];
//数据包长度
assign slv0_pkglen_o=Register[0][5:3];
assign slv1_pkglen_o=Register[1][5:3];
assign slv2_pkglen_o=Register[2][5:3];
endmodule
对其进行编译,通过,下面进行testbench编写
第三节 control_register testbench
代码实现
这里根据参考波形给出相应的激励,需要注意的主要有2点
- tb中的输入reg信号都要用非阻塞赋值
<=
- 与时钟沿同步的激励信号必须用
@(posedge/negedge clk_i)
,否则仿真时会出现时序错误
tb文件名:control_register_tb.v
注释见代码内
`timescale 1ns/100ps
`include "control_register.v"
/*************************<端口声明>*********************/
module control_register_tb;
reg clk_i,
rstn_i;
reg [1:0] cmd_i;
reg [5:0] cmd_addr_i;
reg [31:0] cmd_data_i;
reg [5:0] slv0_margin_i,
slv1_margin_i,
slv2_margin_i;
wire slv0_en_o,
slv1_en_o,
slv2_en_o;
wire [31:0] cmd_data_o;
wire [1:0] slv0_prio_o,
slv1_prio_o,
slv2_prio_o;
wire [2:0] slv0_pkglen_o,
slv1_pkglen_o,
slv2_pkglen_o;
/*************************<原件例化>*********************/
control_register cr1(
clk_i,
rstn_i,
cmd_i,
cmd_addr_i,
cmd_data_i,
slv0_margin_i,
slv1_margin_i,
slv2_margin_i,
slv0_en_o,
slv1_en_o,
slv2_en_o,
cmd_data_o,
slv0_prio_o,
slv1_prio_o,
slv2_prio_o,
slv0_pkglen_o,
slv1_pkglen_o,
slv2_pkglen_o
);
/*************************<激励信号>*********************/
initial begin
clk_i=0;
rstn_i=0;
cmd_i='b00;
cmd_addr_i='b00;
cmd_data_i='h00;
slv0_margin_i='d21;
slv1_margin_i='d33;
slv2_margin_i='d45;
$dumpfile("control_register.vcd");
$dumpvars;
#25 rstn_i=1;
@(posedge clk_i)begin
cmd_i<='b10;
cmd_addr_i<='d00;//特别注意要非阻塞赋值
cmd_data_i<='hD1;
end
@(posedge clk_i)begin
cmd_i<='b10;
cmd_addr_i<='d04;
cmd_data_i<='hD2;
end
@(posedge clk_i)begin
cmd_i<='b10;
cmd_addr_i<='d08;
cmd_data_i<='hD3;
end
@(posedge clk_i)begin
cmd_i<='b00;
cmd_addr_i<='d08;
cmd_data_i<='hD3;
end
@(posedge clk_i)begin
cmd_i<='b10;
cmd_addr_i<='d00;
cmd_data_i<='hD4;
end
@(posedge clk_i)begin
cmd_i<='b01;
cmd_addr_i<='d04;
cmd_data_i<='hD4;
end
@(posedge clk_i)begin
cmd_i<='b01;
cmd_addr_i<='d00;
cmd_data_i<='hD4;
end
#500 $finish;
end
always #10
clk_i<=~clk_i;
endmodule
成功编译之后,生成vcd波形文件,下面使用gtkwave观察波形
第四节 control_register
vcd波形观察
在terminal中cd到当前文件夹下,输入
gtkwave control_register.vcd
然后手动查看需要看的信号,如下图所示
与参考波形相同,简单验证完成。
补充
波形保存:当我们需要保存波形时,点击Write Save File就可以保存波形文件,格式为.gtkw
,当下次需要查看时,就可以直接导入gtkw
文件,而直接导入vcd
文件就不会看到之前已经拉出的波形。
至此,control_register
设计及检查完毕,下一章将从slave_FIFO
开始详述设计过程。