我是桂林理工大三苦逼通信学子🐕。
在前些日子,学习计算机网络课程的时候,接触到了数据传输和以太网传输协议,并且还要去写一个以太网传输的作业,我当时是使用了c去解决这个问题。

其中在这个大作业中,CRC校验程序的编写,无疑是重中之重。碰巧,我这个假期自学了verilog,那就让我来试试可不可以用Verilog来写一段CRC校验吧。
什么是CRC?
循环冗余校验码简称CRC(循环码),是一种能力相当强的检错、纠错码,并且实现编码和检码的电路比较简单,常用于串行传送的辅助存储器与主机的数据通信和计算机网络中。
将要传送的信息M(X)表示为一个多项式L,用L除以一个预先确定的多项式G(X),得到的余式就是所需的循环冗余校验码。
CRC如何计算?
计算CRC,我们先要明确我们使用什么CRC算法,不同的CRC算法,会导致其参与计算的多项式公式不同。
例如常见的有:CRC-8:x8+x2+x+1;
CRC-32: x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1;
等等。
在此,我们以CRC-5/ITU为例子。CRC-5/ITU的多项式为x5+x4+x^2+1;转换为二进制,可得除数为110101;
其次,除了多项式的确定以外,我们还需要会模2算法。二进制的模2算法,即减法不借位、加法不进位,可以认为这是一种异或操作。
例:传输的有效数据为111101011001。除数为110101。可得,在传输数据后面添加5个0,CRC的校验位为00000。校验位的长度应比除数的长度短1。
所以可得,我们最终要传送的码组为11110101100100000。
CRC的校验
CRC码存储或传送后,在接收方进行校验过程,以判断数据是否有错,若有错则进行纠错。一个CRC码一定能被生成多项式整除,所以在接收方对码字用同样的生成多项式相除,如果余数为0,则码字没有错误;若余数不为0,则说明某位出错,不同的出错位置余数不同。对于(n,k)码制,在生成多项式确定时,出错位置和余数的对应关系是确定的。
接上文所述,我们传送的码组为11110101100100000。若传送没有出错,在接收端,该传送码组定能被110101整除。
verilog设计思路
弄明白了CRC的原理,设计也就很简单了。关键就是在于“按位异或”,因为模2算法说白了就是一个异或操作,我们可以利用verilog去写逐位异或,逐位输出,最后进行一个接收端的异或判断即可。
为什么接收端做异或运算?
上文所述,要进行验证,接收端应该去做一个除法,为什么设计思路中的接收端却是做一个异或运算?
这是因为在做除法的时候,CRC的除法就是使用模2的减法,其最后实现的结果就是两个值的异或,我们可以按照除法的顺序进行一个计算,而除法中的减法的步骤,我们可以通过异或去进行实现,相当于我们可以套用发送端的程序。这大大的缩小了我们的编程复杂度,使这个程序变得更加简单。
整体定义
module crc(datasend,datain,dataout,datareceive,resending,readys,readyr,clk,err,reset);
output readys,resending;//readys是编码准备信号,设置为高电平有效;resending是重发信号输出信号,设置为高电平有效;
//而resending信号的变化也是我们波形分析的一大指标;
parameter width=1,amount=12;//数据的位宽用width来表示,,amount可以用来表示数据码组中信息位部分中输入数据的个数。
output [width*amount+4:0] datasend;//crc的循环码输出,位宽其为17.(0~16有17个存储单元,所以位宽可认为是17.)
output [width-1:0] dataout;//译码模块的数据输出,位宽为1.
input reset,err,clk,readyr;
//reset为编码模块计数器预置信号的输入,且上升沿有效。
//err为迫使接收端接受错误的信号(总不能不接受吧),高电平有效;
//reayr为译码模块准备信号,高电平有效。
input [width-1:0]datain;//编码的数据输入,位宽为一
input [width*amount+4:0] datareceive;//接收的crc码组;
crcsend send1(datasend,readys,datain,reset,clk);
crcreceive receive1(dataout,resending,datasend,readyr,clk,err);
//上述是对两个部分进行一个端口的定义。
endmodule
这个部分我们先对发送端和接收端做一个端口的定义,对于数组,我们也要提供一个定义,去规定数组的长度。
并且,对于我们设计的电路,心里面需要有一个设计的草图,因为是行为级建模,对于具体的电路不作太多的要求,但对于端口该如何定义,需要哪些端口,实现哪些功能,提供什么信号,都需要知道。

发送端
//the part of encoding
module crcsend(datasend,readys,datain,reset,clk);
parameter width=1,amount=12;
//parameter可以用来定义一个参数,一般是要多次使用的数字或字符。
output [width*amount+4:0] datasend;
output readys;
input clk,reset;
input [width-1:0] datain;
reg [width*amount+4:0] datasend;
reg [width*amount:0] bufin;//reg类型为寄存器类型。该数值为缓冲区数值输入
reg [width*amount+4:0] bufdatasending;//该变量为缓冲器数值输出。
reg readys ;
integer n;//这也是一种寄存器类型,与reg不同的是:reg没有符号,而这个是有符号的。
always@(posedge reset or posedge clk)
//利用always语句进行触发事件,需要注意的是posedge是上升沿,而negedge是下降沿。
begin
if(reset)
n=0;
else
if(n < amount-1)
begin
readys<=0;//发送模块没有准备好
bufin=bufin << width;//数据左移
bufin[width-1:0]=datain;
n=n+1;//逐个写入数据
end
else
begin
bufin=bufin << width;
bufin[width-1:0]=datain;
bufdatasending[width*amount+4:5]=bufin;
if(bufin[11])//第十二位有效数据
bufin[11:6]=bufin[11:6]^6'b110101;//与多项式进行异或;
if(bufin[10])//异或完要退位,就顺延下去异或
bufin[10:5]=bufin[10:5]^6'b110101;
if(bufin[9])
bufin[9:4]=bufin[9:4]^6'b110101;
if(bufin[8])
bufin[8:3]=bufin[8:3]^6'b110101;
if(bufin[7])
bufin[7:2]=bufin[7:2]^6'b110101;
if(bufin[6])
bufin[6:1]=bufin[6:1]^6'b110101;
if(bufin[5])
bufin[5:0]=bufin[5:0]^6'b110101;
//if语句同时也是一个判断条件,把0开头的情况也考虑进去了。
bufdatasending=bufin[4:0];//得到校验位。
datasend[width*amount+4:0]=bufdatasending[width*amount+4:0];//数据加入校验位
n=0;
readys<=1;//发送模块准备好了
end
end
endmodule
发送端的关键点主要在于缓冲器的设置,暂时存储数据作为一个中转站和副本。在后面译码端还可以起到一个比对的作用。

接收端
//the part of decoding
module crcreceive(dataout,resending,datareceive,readyr,err,clk);//端口定义
parameter width=1,amount=12;
output [width-1:0] dataout;
output resending;
input readyr,clk,err;
input [width*amount+4:0] datareceive;//接收传送的数据
reg resending;//错误重发
reg correct;//正确
reg [width-1:0] dataout;
reg [width*amount+4:0] bufreceive;
reg [width*amount:0] bufdatareceive;
integer g;
always@(posedge clk)//时钟信号电平变化为触发条件
begin
if(readyr)//译码模块准备好,可以开始了!
begin
bufreceive=datareceive;
if(err)
bufreceive[16]=~bufreceive[16];//按位取反
bufdatareceive[width*amount:0]=bufreceive[width*amount+4:5];
if(bufdatareceive[11])
bufdatareceive[11:6]=bufdatareceive[11:6]^6'b110101;
if(bufdatareceive[10])
bufdatareceive[10:5]=bufdatareceive[10:5]^6'b110101;
if(bufdatareceive[9])
bufdatareceive[9:4]=bufdatareceive[9:4]^6'b110101;
if(bufdatareceive[8])
bufdatareceive[8:3]=bufdatareceive[8:3]^6'b110101;
if(bufdatareceive[7])
bufdatareceive[7:2]=bufdatareceive[7:2]^6'b110101;
if(bufdatareceive[6])
bufdatareceive[6:1]=bufdatareceive[6:1]^6'b110101;
if(bufdatareceive[5])
bufdatareceive[5:0]=bufdatareceive[5:0]^6'b110101;
if (!(bufdatareceive[4:0]^bufreceive[4:0]))//异或为0,此时的bufdatareceive的数据正是校验位,若为正确,异或后应为0.
begin
correct=1;resending=0;
dataout=bufreceive[16];
bufreceive=bufreceive << width;//左移
g=1;//输出
end
else
begin
correct=0;
resending=1;
dataout='bz;//错误的情况下,dataout设置为高阻态
end
end
else if(correct)
begin
if(g< amount)
begin
dataout=bufreceive[width*amount+4:width*amount+5-width];//利用这种移位输出,有点像堆栈数据输出的原理。
bufreceive=bufreceive << width;
g=g+1;//逐位输出
end
end
end
endmodule
接收端还是写异或,可以减少代码的规模。

text bench部分
module crc_tsst;
reg datain,reset,clk,err;
reg [15:0] shift;
wire [16:0] datasend;
wire ready;
always #50 clk=~clk;
initial
begin
clk=1;
err=0;
shift=16'h8000;
reset=0;
#10 reset=1;
#20 reset=0;
#9600 err =1;
#100 err=0;
#500000 $stop;
end
always@(posedge clk)
begin
datain=shift[0];
shift=shift>>1;
shift[15]=shift[14]^shift[11];
if(!shift[15:12])
shift[15:12]=4'b1000;
end
crcsend send(datasend,ready,datain,reset,clk);
crcreceive receive(dataout,resending,datasend,ready,clk,err);
endmodule
tb说白了就是写:信号定义,模块接口,功能代码三个部分。
最终电路的波形(使用modelist)

可以实现预期的功能。
1996

被折叠的 条评论
为什么被折叠?



