碎碎念:
向关注的朋友们道个歉,不好意思这一期鸽了这么久。( ̄(工) ̄)
这是一个懒狗不想写1000行的状态机,所以写了1000行的脚本的故事。
虽然本期内容与FOC的直接相关度并不大,由于是在整个项目中的一个小环节,因此还是放在这个专栏里面了。在FPGA片上调试数据收发时,多字节串口收发始终是我个人比较讨厌的环节,状态机的编写实在是让我苦不堪言(重复劳动过多)。
于是乎,在一晚上手写了1400多行状态机代码后,我实在受不了了,最后咨询J师兄,决定用Python写一个脚本,来实现自动读取Excel中的指令接收以及信息发送定义信息,利用字符串操作自动生成对应的System Verilog文件。
本文主要介绍一下简单的实现逻辑,并给出Python部分的代码,源码文件获取方法写在了文章结尾。
2023.04.17更:修正了send脚本在Modelsim中缺少tx_flag的问题。
2023.04.17更:receive脚本en信号有效时,不接收最后一字节数据,暂未修正。(可以最后一位不放有效数据来缓解)
目录
Verilog多字节串口收发最优方案
1 主要思路
本文的思路其实很清晰,但是读者最好首先对UART串口单字节收发时序有一定了解,之后阅读本文代码时,思路也会清晰很多。
目前默认大家已经了解单字节收发的原理啦,如果读者有需要的话,可以留言告诉我,我会单独写一篇介绍单字节收发时序的内容,也可以算是对本文的底层基础的进一步说明~
对应到FOC任务上,有以下两个具体需求(注意,下面的主语都是FPGA开发板,而不是上位机):
- 接收指令:当识别到指令头FF时,进一步开始识别指令的具体类别,之后按照类别将对应字节数的数据存储到寄存器中。这里需要注意的是,不同类型的指令所具有的字节数是不同的。
- 发送数据:由于不同数据具有的字节数不同,当需要发送多字节数据时,需要将之拆分成单字节,利用状态机以及单字节发送模块实现对上位机的发送。
有了具体需求,再看看实现可能性:
Python有一个很方便的库叫做xlwings,用来读取Excel中的信息;多字节收发的状态机编写过程中又具有较多的机械重复性(三段式状态机结构固定),因此必定是可以实现的。
2 Receive与Send模块的输入输出端口设计
在正式开始设计前,首先应该定义具体的端口类型(即使是可变的),这对整体时序逻辑非常重要。为了便于说明,我以脚本最终生成的端口为例进行展示。
2.1 Receive模块的端口设计
这一模块的输入输出端口数量是固定的,下面分别进行说明:
- sys_clk_50m:50MHz的系统时钟
- sys_rst_n:系统复位信号
- uart_rx:串口接收信号
- Comm_type:表示当前指令的类型,宽度以8为倍数会自动改变,会结合Excel中对应字段的宽度进行修改(这样的好处是不会限制指令类型数量在128)
- Comm_content:表示指令的内容,这一字段是复用的,因此其宽度仅取决于内容最长的指令宽度,也是自动改变的。
- Comm_en:指令内容有效信号,当其为高电平,表示当前指令内容存储完毕。
2.2 Send模块的端口设计
用来将设定需要发送的内容传递给模块,内部利用状态机将数据按顺序逐字节发送给上位机。为了便于阅读,因此输入端口数量是自动改变的,下面进行分别说明:
- sys_clk_50m:50MHz的系统时钟
- sys_rst_n:系统复位信号
- uart_tx:串口发送信号
- Start_trans:高电平来临,表示开始发送数据
- 02部分,表示需要输入的7类数据(请忽略我乱起的名字,开始精神错乱受到Rick启发哈哈哈哈)
3 控制台:Excel文件部分
在已知需求的情况下,我对Excel表格进行了如下设计:
上述两个图是同一个Excel文件中的两个sheet,分别对应了FPGA Receive以及FPGA Send两个需求部分。
值得注意的是,表格的行数是可变的,可以随时增减行数,代码会进行自动识别。
3.1 Receive
这部分是为了对应需求1,实现:识别指令头-识别指令类型-接收指令内容三个部分的内容。
因此我设计了如下的几个属性列:
- 序号:用来便于表格的可读性,同时在实际工程应用中,便于工程文档与代码的对应。
- 指令名称(英文):用于在代码中方便给状态机起名字,增加代码的可读性。
- 指令名称(中文):不影响生成的代码,为了增加Excel可读性。
- 固定指令头(二进制,数值要相同):当识别到这部分时,表示开始接收指令了,作为状态机的Idle跳出条件,这个字段的字节长度是不可变的,但是内容可以自己修改。为了方便我就默认为了FF(注意其中的下划线时可有可无的,在Python进行了鲁棒性处理)。
- 指令类型(二进制):识别到指令来临,利用这一字段来判断具体指令类型。为了增加复用性,增大指令的表示数量,可以自行增加其到多字节(二字节宽度需为8的倍数)。
- 指令字节数:表示当前指令具体包含多少字节,需要为整型,可以为0。0时表示直接输出一个指令有效信号,但是指令类型就是本行对应的指令类型。可以用来执行特殊操作,这里我设置为了数据清零(本文不体现,在工程别的模块里)。
- 备注:为了提高Excel的可读性,不影响代码的生成。
- 模块名称:决定了脚本生成的System Verilog代码的模块名称。
可能有读者会问,指令头FF会不会受到指令内容中的FF影响呢?其实是不会的,因为是利用状态机来实现的,当进入对应状态的时候,就只会识别为对应位的指令内容,而不是指令头。
3.2 Send
这部分是为了需求2,实现:接收信息输入-发送数据两个部分的内容。
因此我设计了如下的属性列:
- 序号:用来便于表格的可读性,同时在实际工程应用中,便于工程文档与代码的对应。
- 信息名称(英文):用于在代码中方便给状态机起名字,以及决定模块输入接口的名字,增加代码的可读性。
- 信息名称(中文):不影响生成的代码,为了增加Excel可读性。
- 信息字节数:对应每一类信息的字节数,影响代码中变量宽度。
- 备注:为了提高Excel的可读性,不影响代码的生成。
- 模块名称:决定了脚本生成的System Verilog代码的模块名称。
感觉说得有些混乱,有问题的可以随时给我提出~
4 程序主体:Python实现部分
在确定了具体的模块需求以及Excel模块,就可以开始愉快(bushi)地编写Python脚本代码啦,下面我将会先提出两个部分代码中需要注意的地方以及编写思路,之后给出代码~
这部分是利用Python中的字符串操作来实现的,需要读者对Verilog逻辑有比较深入的理解,反而是Python语言的基础要求不高。
4.1 Receive
这一部分代码的tips如下:
- 在读取Excel数据时,需要注意读取到的字符串还是数字,对于读取类似“1111_1111”这种数据时,需要添加鲁棒性处理(删除“_”)。
- Excel数据需要首先定位一共有多少行是有效信息,这里当读取到None时,表示已经跳出了有效信息行范围。
- 指令的总字节数会影响状态机的总状态数,因此要先计数总数,判断一共需要多少个状态,同时确定状态变量的宽度。
- 指令内容的最大字节数,影响指令内容输出寄存器的宽度,因此也要进行确定。
- 当指令类型超过一字节可以表示范围时,指令类型确定就不能只使用一个状态实现了,因此需要对状态机的生成逻辑进行对应修改(对完美主义者不友好啊,这真是牵一发而动全身,这里我修改了整一天)。
- 当指令内容没有完全占用整个输出寄存器的时候,从高位到地位逐个占用。这里需要注意存储的对应到底是哪一位。
- 为了提高输出System Verilog代码的可读性以及易用性,同步输出了数据对应的位置、模块使用案例、以及状态机的图案。
目前就想到了这么多,之后在使用过程中,我也随时来更新这部分细节。其实很多地方我在代码中也有清晰的注释。
以上面Excel中的内容为例子,代码运行过程,控制台打印出下面的内容:
代码如下:
1# -*- coding: utf-8 -*-
"""
Created on Sat Jul 9 22:05:06 2022
@author: Alex_1
用途:读取Excel指令信息,自动生成状态机,实现不同指令的读取任务
"""
# 打开对应的表格
import xlwings as xw
wb = xw.Book(".\\uart.xlsx")
receive = wb.sheets["receive"]
#send = wb.sheets["send"]
print("表格文件已打开")
# 定义需要的列的名称
column_signal=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
column_signal.extend(['AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ'])
column_signal.extend(['BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN'])
# 获取当前时间
import datetime as dt
now_time = dt.datetime.now().strftime('%F %T')
print("代码文件创建时间:"+now_time)
# 获取模块名称
Module_name = ""
Module_name = receive.range(column_signal[7] + str(2)).value
print("模块名称:"+Module_name)
# 确定指令的最大数量以及对应的行列范围
row_max= 0
temp = "temp"
while(1):
row_max = row_max + 1
temp = receive.range(column_signal[0] + str(row_max)).value
if(temp == None):
break
row_max = row_max - 1
print("表格有效数据范围:1-"+str(row_max))
# 获取指令头
Command_Head = receive.range(column_signal[3] + str(2)).value
print("指令头:" + str(Command_Head))
# 获取全部的指令名称(英文)
Command_Type = []
for i in range(2,row_max+1):
temp = receive.range(column_signal[1] + str(i)).value
Command_Type.append(temp)
print("指令名称:" + str(Command_Type))
# 获取全部的指令类型(二进制)
Command_Type_binary = []
for i in range(2,row_max+1):
temp = receive.range(column_signal[4] + str(i)).value
Command_Type_binary.append(temp)
print("指令类型列表:" + str(Command_Type_binary))
# 获取输出指令类型的宽度(需要多少个字节)
Command_Type_Length = 0
Style_example = str(receive.range(column_signal[4] + str(2)).value).replace("_","")
Style_example_len = len(Style_example)
for i in range(0,64):
if(i*8 >= Style_example_len):
Command_Type_Length = i
break
print("指令类型字节数:" + str(Command_Type_Length))
#Command_Type_Length = 3
#计算所需要的全部状态数
State_num = 1 #(包含了Idle)
State_num += Command_Type_Length #算上Idle,如果这个是两个字节的,就增加状态
for i in range(2,row_max+1):
State_num += receive.range(column_signal[5] + str(i)).value
if(receive.range(column_signal[5] + str(i)).value == 0):
State_num += 1
State_num = int(State_num)
print("状态机状态数量:"+str(State_num))
#利用状态数量,判断需要多少位存储状态数
State_bit = 0
for i in range(0,64):
if(2**i >= State_num):
State_bit = i
break
print("状态变量位宽:" + str(State_bit))
# 获取输出指令的宽度(根据最大字节长度判断),同时获取每个Type的字节数量
Max_Command_length = 0
Command_Type_num = []
for i in range(2,row_max+1):
temp = receive.range(column_signal[5] + str(i)).value
Command_Type_num.append(int(str(temp).replace(".0","")))
if(temp > Max_Command_length):
Max_Command_length = temp
Max_Command_length_Byte = Max_Command_length
Max_Command_length_Bit = Max_Command_length_Byte * 8
print("最大指令字节数:" + str(int(Max_Command_length_Byte)))
# 根据当前的数量,建立状态机状态列表
State_name = ["Idle"]
if(Command_Type_Length == 1): #根据指令类型变量的宽度,确定这个部分需要几个状态
State_name.append("Get_Type")
else:
for j in range(Command_Type_Length-1,-1,-1):
State_name.append("Get_Type_"+str(j))
for i in range(0,row_max-1):
if(Command_Type_num[i] == 0):
State_name.append(Command_Type[i])
elif(Command_Type_num[i] == 1):
State_name.append(Command_Type[i])
else:
for j in range(Command_Type_num[i]-1,-1,-1):
State_name.append(Command_Type[i]+"_"+str(j))
print("状态机状态列表:" + str(State_name))
# 获取每个指令状态循环的起点索引
State_Start_index = []
for i in range(0,len(Command_Type_binary)):
s = 1 + Command_Type_Length #这里就考虑到了类别字节不是1字节的情况
for j in range(0,i):
if(Command_Type_num[j] == 0):
s += 1
else:
s += Command_Type_num[j]
State_Start_index.append(s)
print("各指令状态起点索引:" + str(State_Start_index))
# 单独创建列表存储Type部分的循环序列索引
State_Type_list_index = []
if(Command_Type_Length > 1):
for i in range(0,Command_Type_Length):
State_Type_list_index.append(i+1)
State_Type_list_index.append(0)
print("Type部分转移路径:" + str(State_Type_list_index))
# 获取每个指令状态循环的序列索引
State_list_index = []
for i in range(0,len(Command_Type_binary)): #默认产生不包括Type部分的循环
single_list_index = []
single_list_index.append(State_Start_index[i])
if(Command_Type_num[i] == 0):
single_list_index.append(0)
else:
for j in range(1,Command_Type_num[i]):
single_list_index.append(State_Start_index[i] + j)
single_list_index.append(0)
State_list_index.append(single_list_index)
print("各指令转移路径:" + str(State_list_index))
# 便于可视化,绘制状态转移图
print()
print("******************************************************状态转移图(简)******************************************************")
for i in range(0,len(State_list_index)):
temp = "#" + str(i)+ " "*(6-len(str(i))) + Command_Type[i] + " "*(16-len(Command_Type[i])) + ": 0 ("+ State_name[0] +")→"
for j in range(0,len(State_list_index[i])):
temp += str(State_list_index[i][j])
temp = temp +" (" + State_name[State_list_index[i][j]] + ")"
if(j < len(State_list_index[i])-1):
temp += "→"
print(temp)
# 编写代码
with open(Module_name + ".sv","w") as f:
#打印模块版权信息
f.write("`timescale 1ns / 1ps \n")
f.write("//\n")
f.write("// Create Date: " + str(now_time) + "\n")
f.write("// Module Name: " + Module_name + "\n")
f.write("// Description: Test uart auto Machine \n")
f.write("// ********** Powered By Alex_1 in 2022.07 ********** \n")
f.write("// ****************** wangy.fun :)******************* \n")
f.write("//\n")
f.write("// ****************** Comm_content ****************** \n")
#打印输出的指令各自对应的是Comm_content中的哪几位,便于复制使用
for i in range(0,len(Command_Type)):
temp = "// "
temp += Command_Type[i] + " "*(20-len(Command_Type[i])) + ": Comm_content ["
temp += str(int(Max_Command_length_Byte*8)-1)
temp += ":"
temp += str(int(Max_Command_length_Byte-len(State_list_index[i])+1)*8)
temp += "]\n"
f.write(temp)
f.write("//\n")
f.write("// ****************** User Example ****************** \n")
# 打印模块的使用案例
f.write("// " + Module_name + " " + Module_name + "(\n")
f.write("// .sys_clk_50m ( ), // System Clock\n")
f.write("// .sys_rst_n ( ), // System Reset\n")
f.write("// .uart_rx ( ), // Uart RX\n")
f.write("// .Comm_type ( ), // " + str(Command_Type_Length) + " Bytes \n")
f.write("// .Comm_content ( ), // " + str(int(Max_Command_length_Byte)) + " Bytes \n")
f.write("// .Comm_en ( ) // Comment Enable\n")
f.write("// );\n")
f.write("//\n")
f.write("//******************************************************State Transition Diagram******************************************************\n")
for i in range(0,len(State_list_index)):
temp = "// #" + str(i)+ " "*(6-len(str(i))) + Command_Type[i] + " "*(16-len(Command_Type[i])) + ": 0 ("+ State_name[0] +") --- "
for j in range(0,len(State_list_index[i])):
temp += str(State_list_index[i][j])
temp = temp +" (" + State_name[State_list_index[i][j]] + ")"
if(j < len(State_list_index[i])-1):
temp += " --- "
temp += "\n"
f.write(temp)
f.write("//\n\n")
#打印模块接口定义部分
f.write("module " + Module_name + "(\n")
f.write("//00 System Clock and Reset-----------------------------------------------------------------------------------------------------------------\n")
f.write("input wire sys_clk_50m, // 50MHz\n")
f.write("input wire sys_rst_n, // Reset Signal\n\n")
f.write("//01 UART RX\n")
f.write("input wire uart_rx, // UART_RX\n\n")
f.write("//02 Command Output\n")
f.write("output reg ["+str(int(Command_Type_Length*8-1))+":0] Comm_type, // Command Type\n")
f.write("output reg ["+str(Max_Command_length_Bit-1).replace(".0","")+":0] Comm_content, // Command Content\n")
f.write("output reg Comm_en // Command Enable\n")
f.write(");\n\n")
#打印模块状态定义部分
f.write("// Define Needed State----------------------------------------------------------------------------------------------------------------\n")
for i in range(0,State_num):
temp = "parameter "
temp += State_name[i]
temp += " "*(30-len(State_name[i]))
temp += "= "
temp += str(State_bit)
temp += "\'d"
temp += str(i)
temp += ";\n"
f.write(temp)
f.write("\n\n")
#打印需要的寄存器与导线
f.write("// Define Registers and Conductor----------------------------------------------------------------------------------------------------------------\n")
f.write("reg [" + str(State_bit-1) + ":0] current_state;\n")
f.write("reg [" + str(State_bit-1) + ":0] next_state;\n")
f.write("wire [7:0] uart_data;\n")
f.write("wire uart_done;\n\n")
f.write("reg uart_done_d0;\n")
f.write("reg uart_done_d1;\n")
f.write("wire uart_posedge;\n")
f.write("assign uart_posedge = (uart_done_d0)&&(~uart_done_d1);\n\n\n")
#检测uart_done的上升沿信号
f.write("// Detect rising edges of uart_done----------------------------------------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" begin\n")
f.write(" uart_done_d0 <= 1'b0;\n")
f.write(" uart_done_d1 <= 1'b0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" uart_done_d0 <= uart_done;\n")
f.write(" uart_done_d1 <= uart_done_d0;\n")
f.write(" end\n")
f.write("end\n\n\n")
#三段式第一段:状态跳转
f.write("//----------------------------------------------------------Three-stage---------------------------------------------------------------------------\n")
f.write("//First part: statement transition----------------------------------------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" current_state <= Idle;\n")
f.write(" else\n")
f.write(" current_state <= next_state;\n")
f.write("end\n\n\n")
#三段式第二段:
f.write("//Second part: combination logic, judge statement transition condition-----------------------------------------------------------------------------------\n")
f.write("always @(*)\n")
f.write("begin\n")
f.write(" case(current_state)\n")
f.write(" Idle:\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" if(uart_data == "+str(len(str(Command_Head).replace("_","")))+"'b" + str(Command_Head) + ")\n")
f.write(" begin\n")
if(Command_Type_Length > 1): #当状态转移需要多个字节
f.write(" next_state <= "+ str(State_name[State_Type_list_index[0]]) +";\n")
else:
f.write(" next_state <= Get_Type;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= Idle;\n")
f.write(" end\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= Idle;\n")
f.write(" end\n")
f.write(" end\n\n")
f.write("//Get_Type\n")
if(Command_Type_Length > 1): #当指令类型字节数大于等于2
for i in range(0,len(State_Type_list_index)-1):
start_state = State_name[State_Type_list_index[i]]
next_state = State_name[State_Type_list_index[i+1]]
f.write(" " + start_state + ":\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
if(i+1 < len(State_Type_list_index)-1): #当还处于数据存储阶段
f.write(" next_state <= "+next_state+";\n")
else:#处于跳转判断阶段
#由于差一个周期,因此这里根据符号跳转部分,需要利用Comm_type的高几位以及实时uart_data结合组成
f.write(" case({Comm_type["+str(int(Command_Type_Length*8-1))+":8],uart_data[7:0]})\n")
for i in range(0,len(Command_Type_binary)):
temp = " "
temp += str(Command_Type_Length*8)
temp += "\'b"
temp += str(Command_Type_binary[i])
temp += ": next_state <= "
temp += State_name[State_Start_index[i]]
temp += ";\n"
f.write(temp)
f.write(" default: next_state <= Idle;\n")
f.write(" endcase\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= "+start_state+";\n")
f.write(" end\n")
f.write(" end\n\n")
else: #当指令类型字节数等于1
f.write(" Get_Type:\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" case(uart_data)\n")
for i in range(0,len(Command_Type_binary)):
temp = " "
temp += str(len(str(Command_Type_binary[i]).replace("_","")))
temp += "\'b"
temp += str(Command_Type_binary[i])
temp += ": next_state <= "
temp += State_name[State_Start_index[i]]
temp += ";\n"
f.write(temp)
f.write(" default: next_state <= Idle;\n")
f.write(" endcase\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= Get_Type;\n")
f.write(" end\n")
f.write(" end\n\n")
for i in range(0,len(Command_Type_binary)): #遍历全部的指令
temp = State_list_index[i]
f.write("//"+ str(i) + " " + Command_Type[i] +"\n")
#print(temp)
if(Command_Type_num[i] == 0): #0字节指令
#print("该状态直接跳回起点,使用简单模板")
f.write(" " + State_name[temp[0]] + ":\n")
f.write(" begin\n")
f.write(" next_state <= Idle;\n")
f.write(" end\n\n")
else: #非0字节指令
#print("使用常规模板")
for j in range(0,len(temp)-1):
#print("j:" + str(j))
start_state = State_name[temp[j]]
next_state = State_name[temp[j+1]]
#print("start_state:" + start_state)
#print("next_state:" + next_state)
f.write(" "+start_state+":\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" next_state <= "+next_state+";\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= "+start_state+";\n")
f.write(" end\n")
f.write(" end\n\n")
f.write(" default:\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" next_state <= Idle;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= Idle;\n")
f.write(" end\n")
f.write(" end\n")
f.write(" endcase\n")
f.write("end\n\n\n")
#三段式第三段
f.write("//Last part: output data-----------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" begin\n")
f.write(" Comm_type <= "+str(Command_Type_Length*8)+"'d0;\n")
f.write(" Comm_content <= "+str(Max_Command_length_Bit).replace(".0","")+"'d0;\n")
f.write(" Comm_en <= 1'd0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" case(current_state)\n")
f.write(" Idle:\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" Comm_type <= "+str(Command_Type_Length*8)+"'d0;\n")
f.write(" Comm_content <= "+str(Max_Command_length_Bit).replace(".0","")+"'d0;\n")
f.write(" Comm_en <= 1'd0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" Comm_type <= "+str(Command_Type_Length*8)+"'d0;\n")
f.write(" Comm_content <= "+str(Max_Command_length_Bit).replace(".0","")+"'d0;\n")
f.write(" Comm_en <= 1'd0;\n")
f.write(" end\n")
f.write(" end\n\n")
f.write("//Get_Type\n")
if(Command_Type_Length > 1): #当指令类型字节数大于等于2
for i in range(0,len(State_Type_list_index)-1):
start_state = State_name[State_Type_list_index[i]]
next_state = State_name[State_Type_list_index[i+1]]
f.write(" " + start_state + ":\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
#按字节存储对应数据
#print(i)
index_x = int((Command_Type_Length-i)*8)-1
index_y = str(index_x - 7)
index_x = str(index_x)
f.write(" Comm_type["+index_x+":"+index_y+"] <= uart_data;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" Comm_type <= Comm_type;\n")
f.write(" end\n")
f.write(" end\n\n")
else: #指令字节数是1
f.write(" Get_Type:\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" case(uart_data)\n")
for i in range(0,len(Command_Type_binary)):
temp = " "
temp += str(len(str(Command_Type_binary[i]).replace("_","")))
temp += "\'b"
temp += str(Command_Type_binary[i])
temp += ": Comm_type <= "+str(Command_Type_Length*8)+"'d"
temp += str(i)
temp += ";\n"
f.write(temp)
f.write(" default: Comm_type <= "+str(Command_Type_Length*8)+"'d0;\n")
f.write(" endcase\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" Comm_type <= Comm_type;\n")
f.write(" end\n")
f.write(" end\n\n")
for i in range(0,len(Command_Type_binary)): #遍历全部的指令
temp = State_list_index[i]
f.write("//"+ str(i) + " " + Command_Type[i] +"\n")
#print(temp)
if(Command_Type_num[i] == 0): #0字节指令
#print("该状态直接跳回起点,使用简单模板")
f.write(" " + State_name[temp[0]] + ":\n")
f.write(" begin\n")
f.write(" Comm_content <= "+str(Max_Command_length_Bit).replace(".0","")+"'d0;\n")
f.write(" Comm_en <= 1'b1;\n")
f.write(" end\n\n")
else: #非0字节指令
#print("使用常规模板")
for j in range(0,len(temp)-1):
#print("j:" + str(j))
start_state = State_name[temp[j]]
next_state = State_name[temp[j+1]]
#print("start_state:" + start_state)
#print("next_state:" + next_state)
f.write(" "+start_state+":\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
#需要根据i计算Comm_content的索引
index_x = int((Max_Command_length_Byte-j)*8)-1
index_y = str(index_x - 7)
index_x = str(index_x)
f.write(" Comm_content["+index_x+":"+index_y+"] <= uart_data;\n")
if(next_state == "Idle"):
f.write(" Comm_en <= 1'b1;\n")
else:
f.write(" Comm_en <= 1'b0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" Comm_content <= Comm_content;\n")
f.write(" Comm_en <= 1'b0;\n")
f.write(" end\n")
f.write(" end\n\n")
f.write(" default:\n")
f.write(" begin\n")
f.write(" if(uart_posedge)\n")
f.write(" begin\n")
f.write(" Comm_type <= "+str(Command_Type_Length*8)+"'d0;\n")
f.write(" Comm_content <= "+str(Max_Command_length_Bit).replace(".0","")+"'d0;\n")
f.write(" Comm_en <= 1'd0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" Comm_type <= "+str(Command_Type_Length*8)+"'d0;\n")
f.write(" Comm_content <= "+str(Max_Command_length_Bit).replace(".0","")+"'d0;\n")
f.write(" Comm_en <= 1'd0;\n")
f.write(" end\n")
f.write(" end\n")
f.write(" endcase\n")
f.write(" end\n")
f.write("end\n\n")
#实例化单字节接收模块
f.write("uart_recv uart_recv(\n")
f.write(".sys_clk ( sys_clk_50m ),\n")
f.write(".sys_rst_n ( sys_rst_n ),\n")
f.write(".uart_rxd ( uart_rx ),\n")
f.write(".uart_data ( uart_data ),\n")
f.write(".uart_done ( uart_done )\n")
f.write(");\n\n")
f.write("endmodule\n")
print("\n"+Module_name+".sv 生成完成!")
4.2 Send
对比Receive部分,Send的思路就简单了很多,无非就是将接收到到数据按顺序发送出去。一部分代码的tips如下:
- Excel数据需要首先定位一共有多少行是有效信息,这里当读取到None时,表示已经跳出了有效信息行范围。
- 需要结合信息名称以及信息的数量,字节宽度决定生成的模块的对应的接口数量以及位宽。
- 指令的总字节数会影响状态机的总状态数,因此要先计数总数,判断一共需要多少个状态,同时确定状态变量的宽度。
- 为了提高输出System Verilog代码的可读性以及易用性,同步输出了模块使用案例、以及状态机的图案(这个图就很简单,因为只有一个状态循环)。
以上面Excel中的内容为例子,代码运行过程,控制台打印出下面的内容: 代码如下:
# -*- coding: utf-8 -*-
"""
Created on Mon Jul 11 09:14:38 2022
@author: Alex_1
用途:读取Excel指令信息,自动生成状态机,实现不同指令的读取任务
修正:修正了部分问题(2023.04.17)
"""
# 打开对应的表格
import xlwings as xw
wb = xw.Book(".\\uart.xlsx")
send = wb.sheets["send"]
print("表格文件已打开")
# 定义需要的列的名称
column_signal=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
column_signal.extend(['AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ'])
column_signal.extend(['BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN'])
# 获取当前时间
import datetime as dt
now_time = dt.datetime.now().strftime('%F %T')
print("代码文件创建时间:"+now_time)
# 获取模块名称
Module_name = ""
Module_name = send.range(column_signal[5] + str(2)).value
print("模块名称:"+Module_name)
# 确定指令的最大数量以及对应的行列范围
row_max= 0
temp = "temp"
while(1):
row_max = row_max + 1
temp = send.range(column_signal[0] + str(row_max)).value
if(temp == None):
break
row_max = row_max - 1
print("表格有效数据范围:1-"+str(row_max))
# 获取信息的英文名称
Data_name = []
for i in range(2,row_max+1):
temp = send.range(column_signal[1] + str(i)).value
Data_name.append(temp)
print("信息名称:" + str(Data_name))
# 获取每类信息的字节数
Data_Byte = []
Data_Byte_sum = 0
for i in range(2,row_max+1):
temp = int(send.range(column_signal[3] + str(i)).value)
Data_Byte.append(temp)
Data_Byte_sum += int(temp)
print("各信息字节数:" + str(Data_Byte))
print("总字节数:" + str(Data_Byte_sum))
# 总状态数
State_num = 2 #原始包含Idle 以及Savedata
State_num += Data_Byte_sum
print("总状态数:" + str(State_num))
#利用状态数量,判断需要多少位存储状态数
State_bit = 0
for i in range(0,64):
if(2**i >= State_num):
State_bit = i
break
print("状态变量位宽:" + str(State_bit))
# 定义全部的状态名称
State_name = ["Idle", "Save_Data"]
for i in range(0, len(Data_name)):
if(Data_Byte[i] == 1):
temp = "State_" + Data_name[i]
State_name.append(temp)
else:
for j in range(Data_Byte[i]-1,-1,-1):
temp = "State_" + Data_name[i]
temp += "_"
temp += str(j)
State_name.append(temp)
print("状态机状态列表:" + str(State_name))
# 定义全部的输入接口,
interface = []
for i in range(0, len(Data_name)):
index_start = str(Data_Byte[i]*8-1)
temp = ""
temp += "["
temp += index_start
temp += ":"
temp += "0]"
interface.append(temp)
print("输入接口宽度列表:" + str(interface))
# 计算存储时需要的分段情况
save_part = []
for i in range(0, len(Data_name)):
for j in range(Data_Byte[i]*8-1,-1,-8):
temp = Data_name[i]
temp += "_Buff"
temp += "["
temp += str(j)
temp += ":"
temp += str(j-7)
temp += "]"
save_part.append(temp)
print("存储分段情况:"+str(save_part))
# 便于可视化,绘制状态转移图
print()
print("******************************************************状态转移图(简)******************************************************")
temp = ""
for i in range(0,len(State_name)):
temp += str(i)
temp += "("
temp += State_name[i]
temp += ")"
if(i < len(State_name)-1):
temp += " -- "
print(temp)
# 输入一堆数据,会改变模块的端口,这里就需要提供能够用来实例化复制的部分了!
# 编写代码
with open(Module_name + ".sv","w") as f:
#打印模块版权信息
f.write("`timescale 1ns / 1ps \n")
f.write("//\n")
f.write("// Create Date: " + str(now_time) + "\n")
f.write("// Module Name: " + Module_name + "\n")
f.write("// Description: Test uart auto Machine \n")
f.write("// ********** Powered By Alex_1 in 2023.04 ********** \n")
f.write("// ****************** wangy.fun :)******************* \n")
f.write("//\n")
f.write("// ****************** User Example ****************** \n")
#打印模块的使用案例
f.write("// " + Module_name + " " + Module_name + "(\n")
f.write("// .sys_clk_50m ( ), // System Clock\n")
f.write("// .sys_rst_n ( ), // System Reset\n")
f.write("// .uart_tx ( ), // Uart TX\n")
f.write("// .Start_trans ( ), // Start Send Data To Computer\n")
for i in range(0, len(Data_name)):
temp = "// ."
temp += Data_name[i]
temp += (20-len(Data_name[i]))*" "
temp += "( )"
if(i < len(Data_name)-1):
temp += ", // "
else:
temp += " // "
temp += str(Data_Byte[i])
temp += " Bytes\n"
f.write(temp)
f.write("// );\n")
f.write("//\n")
#打印状态转移图
f.write("// ********************************************State Transition Diagram********************************************\n")
temp = "// "
for i in range(0,len(State_name)):
temp += str(i)
temp += "("
temp += State_name[i]
temp += ")"
if(i < len(State_name)-1):
temp += " -- "
temp += "\n"
f.write(temp)
f.write("//\n\n")
#打印模块接口定义部分
f.write("module " + Module_name + "(\n")
f.write("//00 System Clock and Reset-----------------------------------------------------------------------------------------------------------------\n")
f.write("input wire sys_clk_50m, // 50MHz\n")
f.write("input wire sys_rst_n, // Reset Signal\n\n")
f.write("//01 UART TX\n")
f.write("output wire uart_tx, // UART_TX\n")
f.write("input wire Start_trans, // Start Send Data To Computer\n\n")
f.write("//02 Information for UART\n")
for i in range(0, len(Data_name)):
temp = "input wire "
temp += interface[i]
temp += " "*(15-len(interface[i]))
temp += Data_name[i]
if(i < len(Data_name)-1):
temp += ",\n"
else:
temp += "\n"
f.write(temp)
f.write(");\n\n")
#打印模块状态定义部分
f.write("// Define Needed State----------------------------------------------------------------------------------------------------------------\n")
for i in range(0,State_num):
temp = "parameter "
temp += State_name[i]
temp += " "*(20-len(State_name[i]))
temp += "= "
temp += str(State_bit)
temp += "\'d"
temp += str(i)
temp += ";\n"
f.write(temp)
f.write("\n")
#定义存储状态的变量
f.write("// Define State ----------------------------------------------------------------------------------------------------------------\n")
f.write("reg [" + str(State_bit-1) + ":0] current_state;\n")
f.write("reg [" + str(State_bit-1) +":0] next_state;\n\n")
#定义存储输入数据的寄存器
f.write("// Define Data Buffer----------------------------------------------------------------------------------------------------------------\n")
for i in range(0, len(Data_name)):
temp = "reg "
temp += interface[i]
temp += " "*(15-len(interface[i]))
temp += Data_name[i]
temp += "_Buff"
temp += ";\n"
f.write(temp)
f.write("\n")
#定义一些中间寄存器以及导线,并定义连接关系
f.write("// Define intermediate variables and wires-------------------------------------------------------------------------------------------------\n")
f.write("reg tx_flag_d0;\n")
f.write("reg tx_flag_d1;\n")
f.write("reg [7:0] uart_data_buff;\n")
f.write("reg [1:0] clk_cnt;\n")
f.write("reg pause_done;\n\n")
f.write("wire [7:0] uart_data;\n")
f.write("wire send_done;\n")
f.write("wire uart_en_wire;\n")
f.write("wire tx_flag;\n\n")
f.write("assign send_done = tx_flag_d1 & (~tx_flag_d0);\n")
f.write("assign uart_data = uart_data_buff;\n")
f.write("assign uart_en_wire = (clk_cnt == 2'd2)? 1'b1: 1'b0;\n\n")
#获取发送完成后的下降沿信号
f.write("// Detect dropping edges of tx_flag----------------------------------------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" begin\n")
f.write(" tx_flag_d0 <= 1'd0;\n")
f.write(" tx_flag_d1 <= 1'd0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" tx_flag_d0 <= tx_flag;\n")
f.write(" tx_flag_d1 <= tx_flag_d0;\n")
f.write(" end\n")
f.write("end\n\n")
#进行数据存储
f.write("// Save Data----------------------------------------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" begin\n")
for i in range(0, len(Data_name)):
temp = " " + Data_name[i]
temp += "_Buff <= "
temp += str(Data_Byte[i]*8)
temp += "'d0"
temp += ";\n"
f.write(temp)
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" if(current_state == Save_Data)\n")
f.write(" begin\n")
for i in range(0, len(Data_name)):
temp = " " + Data_name[i]
temp += "_Buff <= "
temp += Data_name[i]
temp += ";\n"
f.write(temp)
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
for i in range(0, len(Data_name)):
temp = " " + Data_name[i]
temp += "_Buff <= "
temp += Data_name[i]
temp += "_Buff;\n"
f.write(temp)
f.write(" end\n")
f.write(" end\n")
f.write("end\n\n")
#三段式第一段:状态跳转
f.write("//----------------------------------------------------------Three-stage---------------------------------------------------------------------------\n")
f.write("//First part: statement transition----------------------------------------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" current_state <= Idle;\n")
f.write(" else\n")
f.write(" current_state <= next_state;\n")
f.write("end\n\n\n")
#三段式第二段:
f.write("//Second part: combination logic, judge statement transition condition-----------------------------------------------------------------------------------\n")
f.write("always @(*)\n")
f.write("begin\n")
f.write(" case(current_state)\n\n")
f.write(" Idle:\n")
f.write(" begin\n")
f.write(" if (Start_trans)\n")
f.write(" next_state <= Save_Data;\n")
f.write(" else\n")
f.write(" next_state <= Idle;\n")
f.write(" end\n\n")
f.write(" Save_Data:\n")
f.write(" begin\n")
f.write(" next_state <= " + State_name[2] + ";\n")
f.write(" end\n\n")
for i in range(2,len(State_name)-1):
start_state = State_name[i]
next_state = State_name[i+1]
f.write(" "+start_state+":\n")
f.write(" begin\n")
f.write(" if(clk_cnt == 2'd3)\n")
f.write(" begin\n")
f.write(" if(send_done == 1'd1)\n")
f.write(" next_state <= "+next_state+";\n")
f.write(" else\n")
f.write(" next_state <= "+start_state+";\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= "+start_state+";\n")
f.write(" end\n")
f.write(" end\n\n")
f.write(" "+State_name[-1]+":\n")
f.write(" begin\n")
f.write(" if(clk_cnt == 2'd3)\n")
f.write(" begin\n")
f.write(" if(send_done == 1'd1)\n")
f.write(" next_state <= "+State_name[0]+";\n")
f.write(" else\n")
f.write(" next_state <= "+State_name[-1]+";\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" next_state <= "+State_name[-1]+";\n")
f.write(" end\n")
f.write(" end\n\n")
f.write(" default:\n")
f.write(" next_state <= Idle;\n\n")
f.write(" endcase\n")
f.write("end\n\n")
#三段式第三段
f.write("//Last part: output data-----------------------------------------------------------------------------------\n")
f.write("always @(posedge sys_clk_50m or negedge sys_rst_n)\n")
f.write("begin\n")
f.write(" if (!sys_rst_n)\n")
f.write(" begin\n")
f.write(" clk_cnt <= 2'd0;\n")
f.write(" pause_done <= 1'b0;\n")
f.write(" uart_data_buff <= 8'd0;\n")
f.write(" end\n")
f.write(" else\n")
f.write(" begin\n")
f.write(" case(current_state)\n\n")
for i in range(0,2):
f.write(" "+State_name[i]+":\n")
f.write(" begin\n")
f.write(" clk_cnt <= 2'd0;\n")
f.write(" pause_done <= 1'b0;\n")
f.write(" uart_data_buff <= 8'd0;\n")
f.write(" end\n\n")
for i in range(2,len(State_name)):
f.write(" "+State_name[i]+":\n")
f.write(" begin\n")
f.write(" uart_data_buff <= "+save_part[i-2]+";\n")
f.write(" if((clk_cnt <= 2'd2) && (pause_done == 1'd0))\n")
f.write(" begin\n")
f.write(" clk_cnt <= clk_cnt + 2'd1;\n")
f.write(" end\n")
f.write(" if(clk_cnt == 2'd3)\n")
f.write(" begin\n")
f.write(" pause_done <= 1'd1;\n")
f.write(" end\n")
f.write(" if((pause_done == 1'd1) && (send_done == 1'd1))\n")
f.write(" begin\n")
f.write(" pause_done <= 1'd0;\n")
f.write(" clk_cnt <= 2'd0;\n")
f.write(" end\n")
f.write(" end\n\n")
f.write(" default:\n")
f.write(" begin\n")
f.write(" clk_cnt <= 2'd0;\n")
f.write(" pause_done <= 1'd0;\n")
f.write(" uart_data_buff <= 8'd0;\n")
f.write(" end\n\n")
f.write(" endcase\n")
f.write(" end\n")
f.write("end\n\n")
#实例化单字节模块
f.write("uart_send uart_send(\n")
f.write(" .sys_clk ( sys_clk_50m ),\n")
f.write(" .sys_rst_n ( sys_rst_n ),\n")
f.write(" .uart_din ( uart_data ),\n")
f.write(" .uart_en ( uart_en_wire ),\n")
f.write(" .uart_txd ( uart_tx ),\n")
f.write(" .tx_flag ( tx_flag )\n")
f.write(");\n\n")
f.write("endmodule")
print("\n"+Module_name+".sv 生成完成!")
5 运行结果:System Verilog部分
这部分其实就是Python脚本的输出结果啦,下面简单介绍一下两个输出的文件,具体的时序逻辑就不详细介绍了,本文的重点还是在Python的实现上。
值得注意的是,生成的代码文件都包含了单字节收发的子模块,这两个子模块我是从正点原子的视频中参考修改得来的,读者可以自行搜索。
5.1 Recive
运行结果的文件头如下图所示:
包含了信息介绍、指令信息与寄存器索引关系、模块实例化例子、状态转移图。在用户体验上还是非常不错的。
后面就是比较常规的寄存器定义以及三段式逻辑啦,如果对串口时序比较熟悉,还是容易理解的,这里就不再赘述了。
经过Vivado仿真,也证明了模块的使用是正常的,下图给出Vivado的仿真结果:
5.2 Send
运行结果的文件头如下图所示:
这里包含了信息介绍、模块实例化例子、状态转移图。
后面的内容同理,也是三段式状态机来完成,不过这个包含了一些简单的子模块,用来检测当前字节是否发送完成。
经过Vivado仿真,也证明了模块的使用是正常的,下图给出Vivado的仿真结果:
如果有想要子模块源码文件以及对应Excel文件的小伙伴,可以在公众号“Alex的书桌与实验室”回复“2SV”获取下载链接~
这就是本期的全部内容啦,如果你喜欢我的文章,不要忘了点赞收藏,分享给身边的朋友哇~