本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!