Verilog中的for循环和disable语句是可综合的,利用for循环,可以像编写软件代码那样实现一些算法。
字符串长度获取很简单,一个for循环就搞定了。对于字符串的连接,关键是要去掉中间的“0”字符。比如用两个reg[6*8-1:0]寄存器来存储"hello"、"word"这两个字符串:
reg [47:0] a = "hello";
reg [47:0] b = "word";
存进去之后,寄存器a开头(h字符前面)有一个'\0',寄存器b开头(w字符前面)有两个'\0',如果直接用{a, b}语句连接,则得到的内容是"\0hello\0\0word",中间有两个\0,不符合我们预期的效果。因此我们需要设法去掉中间的\0。
在单片机中,我们经常用“/10”“%10”这样的方法获取数字的每一个数位,然后加上'0'(0x30)即可转换为字符串。字符串转数字可以反过来用“*10”的方法实现。在FPGA中这样做的效率并不高,不过我们也可以尝试用for循环实现一下,看看效果究竟怎么样。
(关于阻塞赋值和非阻塞赋值的区别,请参阅这篇文章:https://blog.csdn.net/ZLK1214/article/details/109661981。for循环的括号里面一定只能用阻塞赋值,循环体里面一般也是用阻塞赋值)
测试环境:Vivado 2020.1 + XC7A35T-2FGG484I,晶振50MHz。
笔者也在ISE 14.7 + XC6SLX45-2FGG484C上面试了一下,但是因为第四个算法(数字转字符串)过于复杂,没有编译通过,在Implementation的Map阶段就失败了。
【StringManipulator.vh】
`define STRMANIP_STRLEN 1
`define STRMANIP_STRCAT 2
`define STRMANIP_ATOI 3
`define STRMANIP_ITOA 4
【StringManipulator.v】
`include "StringManipulator.vh"
`define MAX_BIT (MAX_SIZE * 8 - 1)
module StringManipulator
#(parameter MAX_SIZE = 16)
(
input [3:0] request,
input [`MAX_BIT:0] str1_in,
input [`MAX_BIT:0] str2_in,
output reg [`MAX_BIT:0] str_out,
input signed [31:0] num_in,
output reg signed [31:0] num_out
);
integer i;
reg [7:0] ch1, ch2;
always @(*) begin
num_out = 0;
str_out = 0;
i = 0;
ch1 = 0;
ch2 = 0;
case (request)
`ifdef STRMANIP_STRLEN
`STRMANIP_STRLEN: begin
// 求字符串的长度
// 规定"MMM"、"\0\0MMM"的长度均为3
// "MM\0"、"\0MM\0"和"\0M\0M"的长度均为2
// 也就是寄存器空间中非NULL字符的个数
num_out = 0;
for (i = 0; i < MAX_SIZE; i = i + 1) begin
ch1 = str1_in >> (i << 3);
if (ch1 != 0)
num_out = num_out + 1;
end
end
`endif
`ifdef STRMANIP_STRCAT
`STRMANIP_STRCAT: begin
// 连接两个字符串
str_out = str1_in;
for (i = MAX_SIZE - 1; i >= 0; i = i - 1) begin
ch1 = str2_in >> (i << 3);
if (ch2 == 0 && ch1 != 0)
ch2 = ch1;
if (ch2 != 0)
str_out = {str_out[`MAX_BIT - 8:0], ch1};
end
end
`endif
`ifdef STRMANIP_ATOI
`STRMANIP_ATOI: begin
// 字符串转数字
begin: ATOI
for (i = MAX_SIZE - 1; i >= 0; i = i - 1) begin
ch1 = str1_in >> (i << 3);
if (ch1 >= "0" && ch1 <= "9") begin
if (ch2 == 0)
ch2 = "+"; // 正号
num_out = num_out * 10 + (ch1 - "0");
end
else if (ch1 == "-") begin
if (ch2 == 0)
ch2 = "-"; // 负号
else
disable ATOI; // 负号出现的位置不对, 中止循环
end
end
end
// 补充符号位
if (ch2 == "-")
num_out = -num_out;
end
`endif
`ifdef STRMANIP_ITOA
`STRMANIP_ITOA: begin
// 数字转字符串
if (num_in == 0)
str_out = "0";
else begin
if (num_in > 0) begin
// 正数
ch2 = "+";
num_out = num_in;
end
else begin
// 负数
ch2 = "-";
num_out = -num_in;
end
for (i = 0; i < MAX_SIZE; i = i + 1) begin
if (num_out != 0) begin
// 从个位开始提取数字
ch1 = "0" + num_out % 10;
num_out = num_out / 10;
end
else if (ch2 == "-") begin
// 添加负号
ch1 = "-";
ch2 = 0;
end
else begin
// 字符串靠右对齐
ch1 = 0;
end
str_out = {ch1, str_out[`MAX_BIT:8]};
end
end
end
`endif
default: begin
end
endcase
end
endmodule
上面的代码用组合逻辑一共实现了四个功能:获取字符串的长度、连接两个字符串、字符串转数字、数字转字符串。
经过实际硬件的测试,前三个功能都可以在一个时钟周期内(20ns)完成。但是第四个功能在for循环中用到了除法器,所以一个周期内无法完成计算,实际测试是经过五个时钟周期(100ns)才能得到结果。
实际上最后两个算法并不是最优的,因为在for循环中分别用到了乘法器和除法器,占用了大量的FPGA资源。可以考虑使用BCD码与BIN码互相转换的算法,只需要加减法和移位就可以搞定。
其余的测试代码:
【config.vh】
`define SYSCLK 50000000 // 系统时钟频率
【main.v】
`include "StringManipulator.vh"
`define N 14
`define M (`N * 8 - 1)
module main(
input clock,
output uart_tx
);
wire uart_tx_request;
wire [7:0] uart_tx_data;
wire uart_tx_ready;
wire uart_sent;
UARTTransmitter uart_transmitter(clock, uart_tx, 24'd115200, uart_tx_request, uart_tx_data, uart_tx_ready, uart_sent);
reg uart_bytearray_tx_mode;
reg [`M:0] uart_bytearray_tx_data;
reg uart_bytearray_tx_request = 0;
reg [7:0] uart_bytearray_tx_size;
wire uart_bytearray_tx_ready;
wire uart_bytearray_sent;
ByteArrayTransmitter #(`N) uart_bytearray_transmitter(clock, uart_tx_request, uart_tx_data, uart_tx_ready, uart_sent,
uart_bytearray_tx_mode, uart_bytearray_tx_request, uart_bytearray_tx_data, uart_bytearray_tx_size, uart_bytearray_tx_ready, uart_bytearray_sent);
reg [3:0] strmanip_request;
reg [`M:0] strmanip_str1_in;
reg [`M:0] strmanip_str2_in;
reg signed [31:0] strmanip_num_in;
wire [`M:0] strmanip_str_out;
wire signed [31:0] strmanip_num_out;
StringManipulator #(`N) string_manipulator(strmanip_request, strmanip_str1_in, strmanip_str2_in, strmanip_str_out, strmanip_num_in, strmanip_num_out);
reg signed [15:0] i = 0;
reg [3:0] state = 0;
always @(posedge clock) begin
case (state)
0: begin
// 下载程序时, 串口电平浮空, 电脑端容易收到0x00
// 开机后先延时, 避免串口空闲时间过短而造成后续字符乱码
if (i == 16'd399) begin
i <= 0;
state <= 1;
end
else
i <= i + 1'b1;
end
1: begin
if (uart_bytearray_tx_ready) begin
// 字符串未开始发送
if (!uart_bytearray_tx_request) begin
// 串口空闲
case (i)
0: begin
strmanip_str1_in <= "hello";
strmanip_request <= `STRMANIP_STRLEN; // 求字符串的长度
end
1, 3, 9, 11: begin
uart_bytearray_tx_mode <= 0; // 二进制发送模式
uart_bytearray_tx_data <= strmanip_num_out;
uart_bytearray_tx_size <= 4;
uart_bytearray_tx_request <= 1;
end
2: begin
strmanip_str1_in <= "iomanip";
strmanip_request <= `STRMANIP_STRLEN;
end
4: begin
strmanip_str1_in <= "Hello ";
strmanip_str2_in <= "World!";
strmanip_request <= `STRMANIP_STRCAT;
end
5, 7, 17, 23: begin
//uart_bytearray_tx_mode <= 1; // 字符串发送模式
uart_bytearray_tx_data <= strmanip_str_out;
uart_bytearray_tx_size <= `N;
uart_bytearray_tx_request <= 1;
end
6: begin
strmanip_str1_in <= "Vivado";
strmanip_str2_in <= " 2020.1";
strmanip_request <= `STRMANIP_STRCAT;
end
8: begin
strmanip_str1_in <= "-2020-11";
strmanip_request <= `STRMANIP_ATOI;
end
10: begin
strmanip_str1_in <= "2023406814";
strmanip_request <= `STRMANIP_ATOI;
end
12: begin
strmanip_num_in <= 727565;
strmanip_request <= `STRMANIP_ITOA;
end
18: begin
strmanip_num_in <= -15392;
strmanip_request <= `STRMANIP_ITOA;
end
endcase
if (i >= 0)
i <= i + 1'b1;
end
end
else begin
// 字符串已开始发送
state <= 2;
end
end
2: begin
if (uart_bytearray_tx_ready) begin
// 字符串已发送
uart_bytearray_tx_request <= 0; // 关闭发送请求
state <= 1;
end
end
endcase
end
endmodule
【UARTTransmitter.v】
`include "config.vh"
module UARTTransmitter(
input clock, // 系统时钟
output reg tx = 1, // 串口发送引脚
input [23:0] baud_rate, // 波特率
input request, // 请求发送字符 (ready=1后应该及时撤销请求, 否则会再次发送同样的字符)
input [7:0] data, // 发送的字符内容 (ready=0时必须保持不变)
output ready, // 是否可以送入新字符
output sent // 是否发送完毕
);
integer counter = 0; // 波特率计数器
reg [3:0] bit_i = 10; // 当前正在发送第几位 (0为起始位, 1-8为数据位, 9为停止位, 10为空闲)
wire bit_start = (counter == 0); // 位开始信号
wire bit_sample = (counter == `SYSCLK / baud_rate / 2 - 1); // 接收端采样点信号
wire bit_end = (counter == `SYSCLK / baud_rate - 1); // 位结束信号
/*
ready引脚的真值表:
bit_i request | ready
-------------------------------------------------------------
0-8 X | 0 (正在发送起始位和数据位)
9 X | 1 (正在发送停止位, 允许送入新字符)
10 0 | 1 (空闲, 允许送入新字符)
10 1 | 0 (已请求发送字符, 但还没开始发送)
*/
assign ready = (bit_i == 9 || sent);
assign sent = (bit_i == 10 && !request);
always @(posedge clock) begin
if (bit_i <= 9) begin
if (bit_start) begin
counter <= 1;
if (bit_i == 0)
tx <= 0; // 起始位
else if (bit_i >= 1 && bit_i <= 8)
tx <= data[bit_i - 1]; // 数据位
else
tx <= 1; // 停止位
end
else if (bit_end) begin
counter <= 0;
if (bit_i == 9 && request)
bit_i <= 0; // 继续发送下一字符, 中间无停顿
else
bit_i <= bit_i + 1'b1; // 继续发送下一位, 或停止发送
end
else
counter <= counter + 1;
end
else if (request)
bit_i <= 0; // 开始发送
else
counter <= 0; // 空闲
end
endmodule
【ByteArrayTransmitter.v】
`define MAX_BIT (MAX_SIZE * 8 - 1)
module ByteArrayTransmitter #(
parameter MAX_SIZE = 16
)(
input clock,
output reg byte_request = 0,
output reg [7:0] byte,
input byte_ready,
input byte_sent,
input mode, // 0:二进制模式, 1:字符串模式
input request,
input [`MAX_BIT:0] data,
input [7:0] size,
output ready,
output sent
);
localparam STATE_LOAD = 0;
localparam STATE_REQUESTED = 1;
localparam STATE_SENDING = 2;
reg [`MAX_BIT:0] buffer;
reg [7:0] count = 0;
reg [1:0] state = STATE_LOAD;
assign ready = (count == 0 && ((byte_ready && !byte_sent) || sent));
assign sent = (byte_sent && !request);
always @(posedge clock) begin
case (state)
STATE_LOAD: begin
if (byte_ready) begin
if (count == size)
count <= 0; // 发送结束
else if ((count == 0 && byte_sent && request && size > 0 && size <= MAX_SIZE) || count != 0) begin
// 开始发送
if (count == 0)
buffer = data << (8 * (MAX_SIZE - size)); // 载入请求发送的数据, 并靠左对齐
else
buffer = buffer << 8; // 继续发送下一个字节
count <= count + 1'b1;
byte = buffer[`MAX_BIT:`MAX_BIT - 7];
if (mode == 0 || byte != 0) begin
byte_request <= 1;
state <= STATE_REQUESTED;
end
end
end
end
STATE_REQUESTED: begin
// 等待发送开始
if (!byte_ready)
state <= STATE_SENDING;
end
STATE_SENDING: begin
// 等待发送结束
if (byte_ready) begin
byte_request <= 0;
state <= STATE_LOAD;
end
end
endcase
end
endmodule
Test bench(仅用于仿真的代码):
【test.v】
`timescale 1ns / 1ps
module test;
// Inputs
reg clock;
// Outputs
wire uart_tx;
// Instantiate the Unit Under Test (UUT)
main uut (
.clock(clock),
.uart_tx(uart_tx)
);
initial begin
// Initialize Inputs
clock = 0;
// Wait 100 ns for global reset to finish
#100;
// Add stimulus here
end
always begin
#12.5 clock = !clock;
end
endmodule
【程序运行结果(串口输出)】
00000005 字符串长度为5
00000007 字符串长度为7
000048656c6c6f20576f726c6421 "Hello World!"
0056697661646f20323032302e31 "Vivado 2020.1"
fffff81c -2020
789abcde 2023406814
0000000000000000373237353635 "727565"
00000000000000002d3135333932 "-15392"
【仿真截图】