Nios实验入门——用Verilog编程方式完成LED流水灯显示并使用串口输出“Hello Nios-II”字符到笔记本电脑

Nios实验入门——用Verilog编程方式完成LED流水灯显示并使用串口输出“Hello Nios-II”字符到笔记本电脑

码农世界 2024-05-19 前端 58 次浏览 0个评论

文章目录

  • 前言
  • 一、Verilog编程方式完成LED流水灯显示
    • 1.1 新建工程并添加FPGA芯片
    • 1.2 新建.v文件并添加至顶层实体
    • 1.3 引脚分配
    • 1.4 编译(包含分析与综合)
    • 1.5 选择烧录器
    • 1.6 添加烧录文件
    • 1.7 下载
    • 1.8 实验现象
    • 二、Verilog编程方式实现串口
      • 2.1 uart_tx.v文件
      • 2.2 test.v文件
      • 2.3 top.v顶层文件
      • 2.5 串口代码讲解
      • 2.4 引脚分配
      • 2.5 实验现象
      • 总结
      • 参考

        前言

        主要目的:

        (1)学习 Quartus Prime 、Platform Designer、Nios II SBT 的基本操作;

        (2)初步了解 SOPC 的开发流程,基本掌握 Nios II 软核的定制方法;

        (3)掌握 Nios II 软件的开发流程,软件的基本调式方法。

        主要内容

        • 在DE2-115开发板上用Nios软件编程方式完成LED流水灯显示
        • 用Verilog编程方式通过DE2-115开发板串口输出“Hello Nios-II”字符到笔记本电脑串口助手

          一、Verilog编程方式完成LED流水灯显示

          1.1 新建工程并添加FPGA芯片

          打开quartus新建工程:

          一直next,到选择型号界面。

          选择芯片型号,根据实际情况选择

          点击finish

          1.2 新建.v文件并添加至顶层实体

          代码如下:

          module led_flow #(parameter TIME_0_5S = 25_000_000)(
              input               sys_clk     ,
              input               sys_rst_n   ,
              output  reg [7:0]   led     
          );
              reg     [24:0]      cnt     ;
              wire                add_cnt ;
              wire                end_cnt ;
              reg     [2:0]       cnt1;
              wire                add_cnt1;
              wire                end_cnt1;
              always @(posedge sys_clk or negedge sys_rst_n)begin
                  if(!sys_rst_n) begin
                      cnt <= 25'b0;
                  end
                  else if(add_cnt) begin
                      if(end_cnt) begin
                          cnt <= 25'b0;
                      end
                      else begin
                          cnt <= cnt+1'b1;
                      end
                  end
                  else begin
                      cnt <= cnt;
                  end
              end
              // 异步复位
              always @(posedge sys_clk or negedge sys_rst_n) begin
                  if(!sys_rst_n)begin
                      cnt1 <= 3'b0;
                  end
                  else if(add_cnt1) begin
                      if(end_cnt1)begin
                          cnt1 <= 3'b0;
                      end
                      else begin
                          cnt1 <= cnt1 + 1'b1;
                      end
                  end
              end
              always @(posedge sys_clk or negedge sys_rst_n)begin
                  if(!sys_rst_n)begin
                      led <= 8'b0;
                  end
                  else begin
                      case (cnt1)
                          3'b000 : led <= 8'b0000_0001;
                          3'b001 : led <= 8'b0000_0010;
                          3'b010 : led <= 8'b0000_0100;
                          3'b011 : led <= 8'b0000_1000;
                          3'b100 : led <= 8'b0001_0000;
                          3'b101 : led <= 8'b0010_0000;
                          3'b110 : led <= 8'b0100_0000;
                          3'b111 : led <= 8'b1000_0000;
                          default: led <= led;
                      endcase
                  end
              end
              assign add_cnt = 1'b1;
              assign end_cnt = add_cnt && cnt == TIME_0_5S - 1;
              assign add_cnt1 = (cnt == TIME_0_5S-1);
              assign end_cnt1 = add_cnt1 && cnt1 == 3'b111;
          endmodule
          

          1.3 引脚分配

          引脚如下:

          1.4 编译(包含分析与综合)

          1.5 选择烧录器

          1.6 添加烧录文件

          1.7 下载

          1.8 实验现象

          Verilog编程方式完成LED流水灯显示

          二、Verilog编程方式实现串口

          项目创建同第一章的LED流水灯

          示例串口代码如下:

          2.1 uart_tx.v文件

          uart_tx.v 代码如下:

          //波特率为115200bps,即每秒传送115200bit的数据,传送1bit数据需要434个时钟周期
          //tx内部是并行数据,需要串行传出去,一般数据格式是1bit的起始位,8bit的数据位,1bit的停止位
          //所以需要一个8bit的计数器,计算传送了多少个bit,起始位是低电平有效,停止位是持续的高电平
          //需要接收8bit的数据
          //需要1bit的传送出去
          module uart_tx(
              input               clk     ,
              input               rst_n   ,
          //in
              input    [7:0]      din ,//要发送的数据
              input               din_vld,//数据有效
          //out
              output  reg [3:0]   cnt_byte,//现在输出第几个byte了
              output  reg         tx    //串口数据
          );
          parameter    Baud = 434;
          //波特率计时器
          reg         [8:0]       cnt_baud     ;
          wire            add_cnt_baud ;
          wire            end_cnt_baud ;
          reg                     flag;
          always @(posedge clk or negedge rst_n)begin 
              if(!rst_n)begin
                   cnt_baud <= 0;
              end 
              else if(add_cnt_baud)begin 
                  if(end_cnt_baud)begin
                      cnt_baud <= 0;
                  end
                  else begin
                      cnt_baud<=cnt_baud+1;
                  end
              end 
              else begin 
                  cnt_baud <= cnt_baud;
              end 
          end
          assign add_cnt_baud = flag;
          assign end_cnt_baud = add_cnt_baud && cnt_baud == Baud - 1;
          always @(posedge clk or negedge rst_n)begin
              if(!rst_n)begin
                  flag <= 1'b0;
              end
              else if(din_vld)begin
                  flag <= 1'b1;
              end
              else if(end_cnt_bit)begin
                  flag <= 1'b0;
              end
              else begin
                  flag <= flag;
              end
          end
          //波特率计数完成,就可以发送下一个bit
          //表示需要把第几位发送出去
          reg               [3:0]      cnt_bit;//最多是8
          wire            add_cnt_bit;
          wire            end_cnt_bit;
          always @(posedge clk or negedge rst_n)begin 
              if(!rst_n)begin
                   cnt_bit <= 0;
              end 
              else if(add_cnt_bit)begin 
                  if(end_cnt_bit)begin
                      cnt_bit <= 0;
                  end
                  else begin
                      cnt_bit <= cnt_bit + 1;
                  end
              end 
              else begin 
                  cnt_bit <= cnt_bit;
              end 
          end
          assign  add_cnt_bit = end_cnt_baud;
          assign  end_cnt_bit = add_cnt_bit && cnt_bit == 8;
          //发送到第几个字符,总共要发15个字符
          wire                     add_cnt_byte ;
          wire                     end_cnt_byte ;
          always @(posedge clk or negedge rst_n)begin 
              if(!rst_n)begin
                   cnt_byte <= 0;
              end 
              else if(add_cnt_byte)begin 
                  if(end_cnt_byte)begin
                      cnt_byte <= 0;
                  end
                  else begin
                      cnt_byte <= cnt_byte + 1;
                  end
              end 
              else begin 
                  cnt_byte <= cnt_byte;
              end 
          end
          assign      add_cnt_byte = end_cnt_bit;//发送完8bit后
          assign      end_cnt_byte = add_cnt_byte && cnt_byte == 14;
          //发送数据的逻辑,先加上起始位
          reg         [8:0]      data     ;
          always @(posedge clk or negedge rst_n)begin
              if(!rst_n)begin
                  data <= 9'h1ff;
              end
              else if(din_vld)begin
                  data <= {din,1'b0};  //数据加上起始位
              end
              else begin
                  data <= data;
              end
          end
          //并行转串行逻辑
          always @(posedge clk or negedge rst_n)begin 
              if(!rst_n)begin
                   tx <= 0;
              end 
              else if(cnt_baud == 1)begin //每发送完1bit,就发送一个tx;
                  tx <= data[cnt_bit];//LSP,低位先发
              end 
              else if(end_cnt_bit)begin
                  //处理停止位
                  tx <= 1'b1;
              end
              else begin 
                  tx <= tx;
              end
          end
          endmodule
          

          2.2 test.v文件

          test.v 代码如下:

          module test(
              input               clk     ,
              input               rst_n   ,
              input  wire [3:0]   cnt_byte,//现在输出第几个byte了
              output   reg           dout_vld,//表示200us间隔实现
              output   reg [7:0]  led_data//表示输出的数据
          );
          //总共需要发送15个字符,所以需要15的计数器
          //200us计数器
          parameter   TIME_200uS = 1_000_0;
          reg         [13:0]      cnt_200uS;
          wire                    add_cnt_200uS;
          wire                    end_cnt_200uS;
          always @(posedge clk or negedge rst_n)begin 
              if(!rst_n)begin
                  cnt_200uS <= 0;
              end 
              else if(add_cnt_200uS)begin 
                  if(end_cnt_200uS)begin 
                      cnt_200uS <= 0;
                  end
                  else begin 
                      cnt_200uS <= cnt_200uS + 1;
                  end 
              end
              else begin
                  cnt_200uS <= 0;
              end
          end 
          assign add_cnt_200uS = 1'b1;
          assign end_cnt_200uS = add_cnt_200uS && cnt_200uS ==  TIME_200uS - 1;
              //定义输出数据
              //Hello Nios-II到串口
              always @(posedge clk or negedge rst_n)begin
                  if(!rst_n)begin
                      dout_vld <= 1'b0;
                  end
                  else if(end_cnt_200uS)begin
                      dout_vld <= 1'b1;
                      case(cnt_byte)
                           0     :   led_data = 8'b01001000;//H
                           1     :   led_data = 8'b01100101;//e
                           2     :   led_data = 8'b01101100;//l
                           3     :   led_data = 8'b01101100;//l
                           4     :   led_data = 8'b01101111;//o
                           5     :   led_data = 8'b00100000;//space
                           6     :   led_data = 8'b01001110;//N
                           7     :   led_data = 8'b01101001;//i
                           8     :   led_data = 8'b01101111;//o
                           9      :   led_data = 8'b01110011;//s
                          10      :   led_data = 8'b00101101;//-
                          11      :   led_data = 8'b01001001;//I
                          12      :   led_data = 8'b01001001;//I
                          13      :   led_data = 8'b00001101;//\r
                          14      :   led_data = 8'b00001010;//\n
                          default :   led_data = 8'b0;
                      endcase
                  end
                  else begin
                      dout_vld <= 1'b0;
                  end
              end
          endmodule
          

          2.3 top.v顶层文件

          **top.v**顶层文件代码:

          module top(
              input           clk     ,
              input           rst_n   ,
              output          tx      
          );
              wire    [7:0]           led_data    ;
              wire    [3:0]           cnt_byte   ;
              wire                   din_vld   ;
              uart_tx             inst_uart_tx(
                  .clk            (clk      ),
                  .rst_n          (rst_n    ),
          //in
                  .din            (led_data),//如果串口占用时,uart_data
                  .din_vld        (din_vld),
          //out
                  .cnt_byte       (cnt_byte),
                  .tx           (tx     ) 
              );
              
              
              test                inst_test(
                  .clk            (clk        ),
                  .rst_n          (rst_n      ),
          //in
                  .cnt_byte       (cnt_byte),
          //out
                  .led_data       (led_data   ),
                  .dout_vld       (din_vld)
              );
          endmodule
          

          2.5 串口代码讲解

          波特率(Band Rate):

          串口协议中很重要的一点就是波特率,波特率的概念是每秒钟传送码元的个数,就是一秒钟传输了几个二进制的个数,他的单位是Bit/s和bps两种。常见的串口速度有115200bps 9600bps等等,串口(RS232)的最大传输速率是 115200bps,表示一秒钟传输了115200个二进制 。

          波特率和字节的关系

          1GB=1024MB

          1MB=1024KB

          1KB=1024B(字节)

          我们需要串口接收的数据数每秒512字节,串口的波特率是115200位/秒

          波特率115200=115200(位/秒)

          如果没有校验位,就应该除以10,得到的是每秒字节数:波特率115200=115200(位/秒)=11520(字节/秒)

          再除以1024,就是每秒KB数:波特率115200=115200(位/秒)=11.25(KB/秒)也就是满足每秒可以接收512字节。

          在Verilog代码中,我们只需要理解计算这两个值就可以完成串口代码的梳理, 假设我们FPGA使用的是50MHZ的系统时钟 波特率使用的是9600bps 传输一个bit需要的时钟周期个数是50_000_000/9600个个数,得到个数之后再用这个个数乘以周期的时间便是传输1bit需要的时间50_000_000/9600*20便是1bit需要的时间。

          Uart通信协议

          1.串口通信的信号线只需要两条线就可以完成,TX和RX TX发送端 RX为接收端。

          2.起始位,数据线从高变低,低有效为0,数据传输开始。

          3.数据位,起始位传输之后便是数据位开始,一般为8位,传输时低位(LSB)在前,高位(MSB)在后。

          4.校验位,校验位可以认为是一个特殊的数据位,通常使用的是奇偶校验,使用串口协议时通常取消奇偶校验位。

          5.停止位,停止位高有效为1,他表示这一个个字节传输结束。

          6.位时间,起始位、数据位、校验位的位宽度是一致的,停止位有0.5位、1位、1.5位格式,一般为1位。

          7.空闲位,持续的高电平。

          7.帧:从起始位开始到停止位结束的时间间隔称之为一帧。

          将8位或者多位数据拆分为一位一位的发送出去的过程称为并转串。将一位一位接收的数据合并为8位或者多位数据的过程称为串转并。

          对于串行通信设备来说,发送方都是在执行并转串,接收方都是在执行串转并。

          UART设备为串行通信设备。

          2.4 引脚分配

          指定gpio口为tx和rx,编程实现硬件逻辑

          2.5 实验现象

          程序代码的编译及运行同LED流水灯显示一样

          实现效果如下:


          总结

          在比较Verilog和Nios-II两种硬件描述语言(HDL)时,我们可以从它们的编程方式、易用性、灵活性以及对细节的要求等方面进行阐述。

          Verilog编程

          • 选择性实现:Verilog提供了高度的灵活性,允许开发者根据需要选择性地实现特定的硬件功能,例如只编写串口通信部分的代码。
          • 时序敏感:Verilog编程需要对时序有非常精确的控制和理解,这要求开发者在编写代码时非常细心,以避免潜在的bug。
          • 简洁性:虽然Verilog代码可以非常简洁,但这也意味着开发者需要对硬件设计有深入的理解,才能确保设计的准确性。

            Nios-II编程

            • 模块化设计:Nios-II采用模块化的IP核,使得开发者可以通过组合现成的模块来构建复杂的系统,类似于构建一个微型计算机。
            • 易用性:对于初学者来说,Nios-II相对容易上手,因为它提供了一种类似于拼图的编程方式,通过软件编程来实现硬件逻辑。
            • 深入理解的挑战:尽管Nios-II在表面上看起来简单,但要深入理解其内部工作原理和优化性能,仍然需要相当的专业知识和经验。

              综合比较

              • 灵活性与控制:Verilog提供了更多的控制和灵活性,但这也意味着需要更高的专业知识和对细节的关注。
              • 易用性与构建速度:Nios-II通过提供预构建的模块和软件编程接口,简化了硬件开发过程,使得构建速度更快,但可能牺牲了一定的定制性。
              • 学习曲线:Verilog的学习曲线可能更陡峭,因为它要求开发者对硬件设计有更深入的理解;而Nios-II则提供了一个较为平缓的学习曲线,使得新手也能较快地开始项目开发。

                总的来说,Verilog和Nios-II各有优势,选择哪一种取决于项目的具体需求、开发者的专业知识以及对开发时间和定制性的要求。

                参考

                Nios-II编程入门实验

                Quartus II 17.1新建一个流水灯

转载请注明来自码农世界,本文标题:《Nios实验入门——用Verilog编程方式完成LED流水灯显示并使用串口输出“Hello Nios-II”字符到笔记本电脑》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,58人围观)参与讨论

还没有评论,来说两句吧...

Top