在Verilog中用for循环实现字符串长度获取、字符串连接、字符串转数字以及数字转字符串(一)
发布日期:2021-06-29 10:17:15 浏览次数:3 分类:技术文章

本文共 13969 字,大约阅读时间需要 46 分钟。

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循环实现一下,看看效果究竟怎么样。

(关于阻塞赋值和非阻塞赋值的区别,请参阅这篇文章:。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    endendmodule

上面的代码用组合逻辑一共实现了四个功能:获取字符串的长度、连接两个字符串、字符串转数字、数字转字符串。

经过实际硬件的测试,前三个功能都可以在一个时钟周期内(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    endendmodule

【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 / 1psmodule 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"

【仿真截图】

转载地址:https://blog.csdn.net/ZLK1214/article/details/109758359 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:在Vivado 2020.1中用MIG核读写DDR3内存,编译代码时提示Sub-optimal placement错误的解决办法
下一篇:FPGA Verilog字符串转数字,以及数字转字符串的方法

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月24日 20时14分52秒