HDLbits中Conwaylife题目解答,以及从中得到关于HLS的一些思考

HDLbits中Conwaylife题目解答,以及从中得到关于HLS的一些思考

导言

笔者最近刚刚刷完HDLbits的所有题目,个人认为其中难度较大的题目,除了状态机的设计外,便是Conwaylife(康威生命游戏),这是一个二维的细胞自动机。这也是为数不多HDLbits中用到for循环语句解答的题,笔者在解题过程中不由得想到了之前利用HLS完成的卷积操作,认为其中有较大的关联性,写下此文用于记录,如果思考有误,也欢迎大家一同探讨。

Conwaylife题目的Verilog HDL解答及思路

首先先贴出题目的中文翻译及笔者的Verilog代码,可以顺利通过仿真,之后简单介绍一下设计思路。
Problem Statement
康威的《生命游戏》是一个二维的细胞自动机。

“游戏”在二维单元格网格上进行,其中每个单元格为1(生存)或0(死亡)。在每个时间步长,每个小区都会根据其具有的邻居数量来更改状态:

0-1个邻居:单元格变为0。
2个邻居:单元格状态不变。
3个邻居:单元格变成1。
4个以上邻居:单元格变为0。

该游戏适用于无限网格。在此电路中,我们将使用16x16的网格。为了使事情变得更有趣,我们将使用16x16的环形面,其侧面环绕在网格的另一侧。例如,角单元格(0,0)有8个邻居:(15,1),(15,0),(15,15),(0,1),(0,15),(1,1) ,(1,0)和(1,15)。 16x16网格由长度为256的矢量表示,其中16个单元格的每一行由子矢量表示:q [15:0]是第0行,q [31:16]是第1行,依此类推。(此工具接受SystemVerilog,因此你可以根据需要使用2D向量。)

qoad:在下一个时钟沿将数据加载到q中,以加载初始状态。
q:游戏的16x16当前状态,每个时钟周期更新一次。

游戏状态应在每个时钟周期前移一个时间步长。

数学家,生命游戏细胞自动机的创造者约翰·康威(John Conway)于2020年4月11日因COVID-19逝世。

module top_module(
    input clk,
    input load,
    input [255:0] data,
    output [255:0] q ); 
    
    reg [15:0] q_2d [15:0]; //2-d q
    reg [15:0] q_next [15:0]; //2-d q_next
    reg [3:0] sum;
    integer i,j;
    always@(*)begin
        for(i=0;i<16;i++)begin
            for(j=0;j<16;j++)begin
                if(i==0 && j==0)//左上角
                    sum=q_2d[15][1]+q_2d[15][0]+q_2d[15][15]+q_2d[0][1]+q_2d[0][15]+q_2d[1][0]+q_2d[1][1]+q_2d[1][15];
                else if(i==0 && j==15)//右上角
                    sum=q_2d[0][0]+q_2d[0][14]+q_2d[15][0]+q_2d[15][14]+q_2d[15][15]+q_2d[1][0]+q_2d[1][14]+q_2d[1][15];
                else if(i==15 && j==0)//左下角
                    sum=q_2d[15][1]+q_2d[15][15]+q_2d[14][0]+q_2d[14][15]+q_2d[14][1]+q_2d[0][0]+q_2d[0][1]+q_2d[0][15];
                else if(i==15 && j==15)//右下角
                    sum=q_2d[15][0]+q_2d[15][14]+q_2d[14][15]+q_2d[14][0]+q_2d[14][14]+q_2d[0][0]+q_2d[0][15]+q_2d[0][14];
                else if(i==0)//上边界
                    sum=q_2d[0][j-1]+q_2d[0][j+1]+q_2d[1][j-1]+q_2d[1][j]+q_2d[1][j+1]+q_2d[15][j-1]+q_2d[15][j]+q_2d[15][j+1];
                else if(i==15)//下边界
                    sum=q_2d[15][j-1]+q_2d[15][j+1]+q_2d[0][j-1]+q_2d[0][j]+q_2d[0][j+1]+q_2d[14][j-1]+q_2d[14][j]+q_2d[14][j+1];
                else if(j==0)//左边界
                    sum=q_2d[i][1]+q_2d[i][15]+q_2d[i-1][0]+q_2d[i-1][15]+q_2d[i-1][1]+q_2d[i+1][0]+q_2d[i+1][1]+q_2d[i+1][15];
                else if(j==15)//右边界
                    sum=q_2d[i][0]+q_2d[i][14]+q_2d[i-1][0]+q_2d[i-1][14]+q_2d[i-1][15]+q_2d[i+1][0]+q_2d[i+1][14]+q_2d[i+1][15];
                else  //中间元素
                    sum=q_2d[i-1][j]+q_2d[i-1][j-1]+q_2d[i-1][j+1]+q_2d[i][j-1]+q_2d[i][j+1]+q_2d[i+1][j]+q_2d[i+1][j-1]+q_2d[i+1][j+1];

                case(sum)
                    2:q_next[i][j]=q_2d[i][j];
                    3:q_next[i][j]=1'b1;
                    default:q_next[i][j]=0;
                endcase
                
                //q_2d = q_next;
                
        end
    end
end
    
  
    always@(posedge clk)begin
        if(load)begin
            for(i=0;i<16;i++)begin
                for(j=0;j<16;j++)begin
                	q_2d[i][j] <=data[i*16+j]; 
            end 
        end
    end
        else 
            q_2d <= q_next;    
    end
    
    
    genvar m,n;
    generate
        for(m = 0; m < 16; m = m + 1) begin : line_reverse
            for(n = 0; n < 16; n = n + 1) begin : list_reverse
                assign q[m*16+n] = q_2d[m][n];
        end
       end
        
    endgenerate

        
endmodule

该段代码仿真结果正确。书写代码用了一个组合逻辑描述变化规律,一个时序逻辑用于赋值,还有一个generate-for语句用于转换输出格式,个人认为将代码细分的比较合理,类似于三段式状态机的描述方法,便于读者阅读。
1、定义部分:
笔者在这里定义了两个二维的数组q_2d、q_next,用于存储数据的当前时刻与下一时刻的值,存储为二维数组的原因从题目中可以很轻易的得出,该细胞自动机也是二维的,在Verilog可以用这样的语句表达一个二维数组:

    reg [15:0] q_2d [15:0]; //2-d q
    reg [15:0] q_next [15:0]; //2-d q_next

在Verilog的语法书中是这样解释的,前一个[15:0]表示数据长度16位,后一个则表示共有16个这样的数据,学过硬件的朋友们很容易就可以在脑海中构建二维的数据存储图。在本题中,利用这样的方法将输入的256位数据(0-255)转换为二维数组((0,0),(0,1)······(0,15),(1,0),(1,1)······(15,15)),这样就如同题目表述的意思相同了。
2、组合逻辑部分
在转换为二维数组之后,笔者用了if~else if~else语句来将二维数组分类,方便定义sum记录数据,用于下面判断下一时刻的输出值q_next。当然,由于要判断每一个数据的变换,我们需要定义两个嵌套的for循环遍历整个二维数组。

        for(i=0;i<16;i++)begin
            for(j=0;j<16;j++)begin

3、时序逻辑部分
在时序逻辑中,如同状态机的时序部分,判断load信号是否有效,进而决定是否载入输入数据,并且每一个时刻刷新一次输出。相对比较简单,这里不再赘述。
4、输出维度转换
这里笔者采用了generate-for语句,将二维数组转换为一维数据。采用这一语句的原因是因为这里存在着复用模块的需求,因此直接再for循环里,对每一个二维数组进行赋值,便可一一对应得到最终的输出。

    genvar m,n;
    generate
        for(m = 0; m < 16; m = m + 1) begin : line_reverse
            for(n = 0; n < 16; n = n + 1) begin : list_reverse
                assign q[m*16+n] = q_2d[m][n];
        end
       end
        
    endgenerate

关于generate-for语句的用法,大家可以自行查询,在这里简单说一下,如果有着对于某个模块复用的需求,便可以采用这一语句,个人对于其与for循环语句的理解是。for循环语句无法嵌套模块(例如本题中用的assign),只能进行单纯的赋值运算(在解答代码中也有for循环的使用)。并且generate-for循环语句包含着并行处理的硬件思想,而for循环则是软件中顺序执行的语句。

HLS中卷积操作

在解答此题之后,笔者开始对于这一部分进行了一些思考,该题并不像是利用Verilog编写的题目,如果采用软件模拟的方法,似乎更容易完成细胞自动机,这就让人不由得想到利用高层次语言完成硬件编程,恰巧笔者在之前也有做过类似的项目,利用C语言完成卷积神经网络的前向推导(PL端),通过AXI总线传输数据,再利用PS端调度,输出预测结果,完成数据集的识别,这一项目基于Ultra96 V2板子完成的,其中个人编写的卷积操作如下:

void convolution_c1(
			  DTYPE X[C1_X_DMNIN][C1_X_DMNIN][C1_N_CHAN],
		const DTYPE W[C1_W_DMNIN][C1_W_DMNIN][C1_N_CHAN][C1_N_FILTERS],
			  DTYPE out[C1_OUT_DMNIN][C1_OUT_DMNIN][C1_N_FILTERS],
		const DTYPE bias[C1_N_FILTERS])
{
	convolution_c1_label9:for(uint8_t f = 0 ; f < C1_N_FILTERS; f++)
	{
		convolution_c1_label10:for (uint8_t r = 0; r < C1_OUT_DMNIN ; r++)
		{
			convolution_c1_label11:for (uint8_t c = 0; c < C1_OUT_DMNIN ; c++)
			{
				out[r][c][f] = bias[f];
			}
		}
	}
	printf("the conv1 output is :");
	for(uint8_t f = 0 ; f < C1_N_FILTERS; f++)
	{
		for (uint8_t r = 0; r < C1_OUT_DMNIN ; r+=STRIDE)
		{
			for (uint8_t c = 0; c < C1_OUT_DMNIN ; c+=STRIDE)
			{
				for(uint8_t ch = 0 ; ch < C1_N_CHAN; ch++)
				{
					for (uint8_t i = 0; i < C1_W_DMNIN ; i++)
					{
						for (uint8_t j = 0; j < C1_W_DMNIN ; j++)
						{
							out[r][c][f] = out[r][c][f] + W[i][j][ch][f] * X[r+i][c+j][ch];
						}
					}
				}
				printf("%d,",out[r][c][f]);
			}
		}
	}
	printf("\n");
}

可以看出,卷积操作由于是在二维图像中进行,也是利用许多for循环嵌套完成的,这一部分的乘加操作在C语言编写中十分简单快捷,六个for循环嵌套便可完成一次卷积操作。
笔者在完成其他部分的函数中,封装IP后在Vivado中调用,成功完成了这一网络的部署,得到输出结果。

一些思考

笔者在对比了两部分内容后,确实觉得在某些问题的求解中,利用C、Python这样的软件语言解决硬件问题时会更加便捷,对于HLS工具的诞生感到钦佩,在一些需要顺序执行语句的问题中,利用HDL语言书写代码怎么写怎么别扭,如果转换编程思维,采用C语言进行编程却能很好的完成任务。掌握软件编程确实是必要的,正如同现在流行的说法“软硬件协同”,这确实也是一名硬件工程师需要掌握的部分。
“软硬件协同这东西只有搞硬件的会搞,搞软件、算法的同学懒得搞这些。”也希望以后可以多多从遇到的一些问题中学习,多多感悟。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"HLS Book"的文翻译是《高音调系统手册》。HLS即高音调系统(High-Level Synthesis),是一种硬件描述语言和自动转换工具,用于将高级代码(如C、C++等)转换为硬件描述语言(如VHDLVerilog等),以便在FPGA等硬件平台上实现。《高音调系统手册》是一本介绍HLS技术和方法的参考书籍。 该书首先介绍了高音调系统的基本概念和原理。它解释了HLS技术的背景和关键特点,例如高级抽象、自动化转换和快速原型开发等。读者将了解到HLS在硬件设计领域的重要性和应用。 《高音调系统手册》还详细介绍了HLS的工作流程和方法。它描述了将高级代码转换为硬件描述代码的过程,包括源代码分析、优化和综合等步骤。书提供了丰富的示例和实践案例,帮助读者理解和掌握HLS的工作原理和技术细节。 此外,该书还介绍了HLS工具的使用和调试技巧。读者将学会如何使用HLS工具进行代码编写、仿真和调试,以及如何优化和验证生成的硬件电路。这些技巧和经验对于高效、准确地实现硬件设计非常重要。 总而言之,《高音调系统手册》是一本全面介绍HLS技术和方法的参考书籍。它适合从事硬件设计和开发的工程师、研究人员和学生阅读。读者通过学习该书,将能够掌握HLS的基本概念、工作原理和应用方法,提高硬件设计的效率和质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值