在Verilog中用for循环实现字符串长度获取、字符串连接、字符串转数字以及数字转字符串(一)

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"

【仿真截图】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值