zoukankan      html  css  js  c++  java
  • 如何在FPGA上做一个没啥用的mcu

    简介

    近段时间做项目,涉及到一些传感器数据的采集,比如温度传感器DHT11,这种东西使用FPGA来做,为了实现他的时序,如果自己写的话那是真的不容易,但是对于项目来说,这个东西有需要做,怎么办?于是在FPGA或者CPLD中做一个占用资源可控,且能在各个平台下移植的可编程状态机就进入了我的视野。

    说时迟那时快,花了一天时间写了一个简单的8位mcu,在功能上仅仅只有简单的输入输出功能,加减法,逻辑运算,支持跳转,调用,比较等指令。

    主要实现功能

    目前该mcu的主要情况如下:

    • 内部16个寄存器,s0 ~ sF
    • 设计一个深度为31的程序指针栈,用于支持CALL命令
    • 支持8位加减法运算,通过设计的标志位可以实现16为,32位的加减法运算
    • 支持逻辑运算,AND,OR,XOR

    从实现功能上来看,似乎比较少,但是我们依旧可以使用这些有限的命令实现我们的初衷,就是实现与一些简单传感器的交互,甚至可以实现I2C,SPI的通信接口。

    在指令上为了不用自己开发将汇编翻译为机器指令的工具,这里直接和xilinx的picoblaze的指令保持一致,换句话说,使用xilinx的工具可以将我们的汇编代码翻译成支持这个mini-mcu的机器指令。

    有什么特有的特性

    在一些资源有限的CPLD上,完全实现一个mcu是不现实,为了契合我的初衷,并且不让资源被浪费太多,我字节写了脚本,可以根据写的汇编代码,将汇编代码中使用到的命令提取出来,并会直接将mini-mcu与这些命令有关的部分保留,裁剪掉其他没有用到的部分,但是输入输出部分将会一直保留。另外由于没有实现其他一些复杂的指令,这些指令都是在两个时钟周期中完成的,也就是说基于这个特性,可以实现精确的时钟延时。

    环境准备

    由于自己适应了linux的环境,所以实现的脚本都是使用bash写的,包括裁剪mcu,如果不会linux指令也没事,只不过不能自动化的生成这些东西,需要自己手动来,包括裁剪mcu,当然这种情况下,你可以不裁剪。

    说一说在为了支持linux指令,在windows系统上应该怎么准备环境

    step1:安装cygwin以支持bash脚本

    Cygwin就是一个windows软件,该软件就是在windows上仿真linux操作系统 ,简言之,cygwin是一个在windows平台上运行的 linux模拟环境,使用一个Dll(动态链接库)来实现 这样,我们可以开发出Cygwin下的UNIX工具,使用这个DLL运行在Windows下。
    安装方法
    1、下载cygwin安装器
    下载地址:官方地址

    然后就可以使用这个安装器进行安装了

    2、启动安装器进行安装
    安装器有三种安装模式可供选择:

    ①Install from Internet,这种模式直接从Internet安装,适合网速较快的情况;  
    ②Download Without Installing,这种模式只从网上下载Cygwin的组件包,但不安装;  
    ③Install from Local Directory,这种模式与上面第二种模式对应,当你的Cygwin组件包已经下载到本地,则可以使用此模式从本地安装Cygwin
    

    说明:当你安装过,在执行该安装程序可以选择本地安装,然后添加需要扩展的命令。
    第一次安装使用第一种方式进行安装:


    在下载的同时,建议将Cygwin安装组件也保存到了本地,以便以后能够再次安装,这一步选择安装过程中从网上下载的Cygwin组件包的保存位

    选择连接方式
    这一步选择连接的方式,选择你的连接方式,然后点击下一步,会出现选择下载站点的对话框,如下图所示

    ①Use System Proxy Settings 使用系统的代理设置  
    ②Direct Connection 一般多数用户都是这种直接连接的网络,所以都是直接使用默认设置即可  
    ③Use HTTP/FTP Proxy 使用HTTP或FTP类型的代理。如果有需要,自己选择此项后,设置对应的代理地址和端口,即可
    

    选择下载站点
    不同的镜像存放了不同的包,为了获得最快的下载速度,我们可以添加网易开源镜像http://mirrors.163.com/cygwin/或者 阿里云镜像http://mirrors.aliyun.com/cygwin/

    开始自动搜索

    选择需要下载安装的组件包
    这一步比较重要,为了之后更好的使用该软件,建议自己在这里的时候就选好需要使用的组件,或者说支持的命令。
    最核心的,记住一定要安装Devel这个部分的模块,其中包含了各种开发所用到的工具或模块。

    下面推荐推几个组件

    • fish:一个shell,具有良好的交互提示,强烈建议安装,后面的操作也和其相关
    • lynx:命令安装组件的必须工具,强烈推荐安装此项,方便之后扩展命令
    • 其他的自选,比如 gcc,curl,python,tclsh等。学习FPGA,建议安装tclsh

    组件可以在search框输入后搜索,然后选中组件,在new列双击,当看到版本号后,安装就会将此组件安装上。

    确认并开始安装

    安装好之后,将cygwin安装路径下的bin目录添加到环境变量,方便使用

    为了让我们更舒服的使用,我们先把默认的shell设为fish,当然,若果没安装fish就算了

    当我们没配置fish shell,使用默认的shell时我们打开cygwin的终端是这样的

    在终端输入以下命令后下次重启就可以了。

    echo "fish" >> /etc/profile
    

     

    当然此时要直接切换到fish可以在终端直接输入fish,切换过来就是这样的了:

    step2:安装verilog小巧的仿真工具-iverilog

    下载链接:windows版本iverilog

    下载后直接安装,当然为了之后使用方便强烈建议安装好将安装路径下的bin目录和安装目录下的gtkwave/bin目录加入环境变量。

    step3:主要工具准备完毕,在随意来个编辑器

    编辑器在这里推荐使用vscode,后面的说明也都会基于这个编辑器。
    下载链接:vscode官网
    注意,记住你的安装路径,

    我们打开他,同样为了方便使用,在这里先对其进行简单的配置:

    首先安装几个必要的插件


    在这个里面搜索,为了支持中文,你可以搜索chinese,进行安装,之后又就是中文显示了。其他的插件可以暂时不用安装,之后遇到相应的文件后,软件会自动推荐你安装,我安装的插件如下:

    关键步骤

    在搜索框搜索term


    然后配置一下:

    主要就是这几个,大家最好把这几项先配置好,省的之后一项一项配置。

    {
        "terminal.integrated.shell.windows": "D:\cygwin64\bin\fish.exe",
        "files.autoSave": "onFocusChange",
        "files.autoGuessEncoding": true,
        "editor.mouseWheelZoom": true
    }

    下载mini-mcu

    下载地址:mini-mcu

    如果你安装cygwin时也安装了git,那么在cygwin的终端中可以使用:

    git clone  https://gitee.com/yuan_hp/mini-mcu.git

    直接克隆。
    然后我们使用vscode打开我们mini-mcu的文件夹,并在打开vscode的终端。

    为了感受一下之后开发的方便,在终端中输入以下命令:

    该命令行会直接编译项目中software一级目录下的.psm文件,也就是我们的汇编代码文件,并生成对用的rom.v文件,同时裁剪mini-mcu,命令./run将会调用iverilog仿真项目并用gtkwave代开仿真的波形图

    **特别注意:**当你想开发新的功能时,你可以先不关闭gtkwave,修改software下的代码后,执行以下命令

    刷新并行文件的数据,然后在gtkwave重新加载数据:

    项目文件结构

    ├── head.v
      用于裁剪mini-mcu的宏文件
    ├── images
     存放着图片
    ├── mcu.v
     mini-mcu源码
    ├── README.md
    ├── rom.v
     编译汇编自动成成的程序存储器
    ├── run
      项目控制脚本
    ├── run.sh
    ├── sim
      生成的仿真文件
    │   ├── wave
    │   └── wave.lxt2
    ├── software
     编写的汇编代码
    │   ├── test.psm
        脚本会编译的代码
    │   ├── 第一个例子
      
    │   │   └── start.psm
    │   ├── 简单按键检测
    │   │   └── keycheck.psm
    │   ├── 流水灯程序
    │   │   └── led_water.psm
    │   └── 数码管计数
    │       └── seg_counter.psm
    ├── step_fpga
      小脚丫fpga的历程项目,执行 ./run -g 会将文件拷贝到这个目录下
    ├── tb.v
     仿真testbech文件
    ├── tmp
      执行脚本时生成的临时文件夹
    │   ├── kcpsm6.exe
    │   ├── KCPSM6_session_log.txt
    │   ├── ROM_form.v
    │   ├── test.fmt
    │   ├── test.hex
    │   ├── test.log
    │   ├── test.psm
    │   └── test.v
    ├── tools
      仿真一些工具和脚本
    │   ├── bin
    │   │   ├── compile
    │   │   ├── hex2rom
    │   │   └── msim
    │   └── kcpsm
    │       ├── kcpsm6.exe
    │       └── ROM_form.v
    ├── upCloud
    └── window.v 专门用来查看mcu内部变量的模块
    

    已经支持的指令

    • LOAD
    • JUMP
    • JUMP C
    • JUMP NC
    • JUMP Z
    • JUMP NZ
    • CALL C
    • CALL NC
    • CALL Z
    • CALL NZ
    • CALL
    • RETURN
    • RETURN C
    • RETURN NC
    • RETURN Z
    • RETURN NZ
    • AND
    • OR
    • XOR
    • INPUT
    • OUTPUT
    • ADD
    • ADDCY
    • SUB
    • SYBCY
    • COMPARE
    • TEST
    • SL0
    • SL1
    • RL
    • RR
    • SR0
    • SR1
    • SRA
    • LRA

    开发你的项目

    step1:编写代码

    脚本只会自动搜索software一级目录下的.psm文件!

    start:
        LOAD sA , 23; 加载寄存器A的值为 0x23
        ADD sA,02;寄存区A的值加上 0x02

    step2:编译

    执行命令

    ./run -c

    编译文件

    step3:仿真verilog项目

    执行命令:

    ./run


    step4:板上验证

    拷贝项目目录下的mcu.v, rom.v , head.v到实际的FPGA实际项目的目录下,进行,并编写项目的顶层文件:参考如下:

    module top
    (
        input clk_in,             //输入系统12MHz时钟
        //4bit拨码开关输入
        input [3:0] sw,
        input [3:0] key, //按键输入
        //数码管
        output [8:0] seg_led_1,
        output [8:0] seg_led_2,
        //rgb
        output reg[2:0]rgb,
        //led
        output led1,              
        output led2,
        output led3,
        output led4,
        output led5,
        output led6,
        output led7,
        output led8
    ); 
    
    wire clk ,clko,rst;
    reg [7:0] out;
    assign {led8,led7,led6,led5,led4,led3,led2,led1} = out;
    assign clk = clk_in;
    reg rst_n_in;          //复位信号
    reg [17:0]cnt ;
    always @(posedge clk) begin
            if(cnt>=18'h3ffff)begin
                rst_n_in <= 1'b1;
            end else begin
                cnt <= cnt +1;
                rst_n_in <= 0;
            end
    end 
    /*
    divide #(
        .N(1)
    ) u1 (
        .clk(clko),
        .rst_n(rst_n_in),
        .clkout(clk)
    ); 
    */
    //----------- mini-mcu 相关------------
    wire [11:0]address;
    wire [17:0] instruction;
    wire bram_enable, read_strobe, write_strobe;
    reg [7:0] in_port;
    wire [7:0] port_id, out_port;
    //----------- 数码管 相关------------
    reg[3:0] seg_data_1, seg_data_2;
    //输出引脚 
    always @(posedge clk )begin
            if(write_strobe)begin
                case(port_id)
                    8'h00:{seg_data_1,seg_data_2} <= out_port;//bcd编码的2个数码管
                    8'h01:out <= out_port;  //LED控制
                    8'h02:rgb <= out_port[2:0];  //rgb
                    default:out <=out;
                endcase
            end else out <= out;
    end
    //输入引脚
    
    always@(*)begin
        if(read_strobe) begin
            case(port_id)
                    8'h00: in_port = {key[3:0],sw[3:0]};   //按键 4bit拨码开关输入
                    
            endcase
        end 
    
    end    
    
    /**********************************
    * 例化mini-mcu
    **********************************/
    mcu mcu(
        .clk(clk),            //系统时钟
        .rst_n(    rst_n_in),          //复位  0 --> 复位
        .address(    address),       //程序取址地址
        .instruction(    instruction),   //指令输入
        .bram_enable(    bram_enable),   //程序rom使能 1-->使能    
        .in_port(    in_port),        //输入口
        .read_strobe(    read_strobe),    //输入口使能     
        .port_id( port_id),        //io口地址
        .out_port(    out_port),       //输出口
        .write_strobe(    write_strobe)   //输出口写使能  
    );
    
    rom rom(
        .clk(    clk),
        .address(    address),       //程序取址地址
        .instruction(     instruction),   //指令输入
        .enable(     bram_enable)   //程序rom使能 1-->使能 
    );
    /**********************************
    *数码管显示 是bcd码 
    **********************************/
    seg_display seg_display(
        .seg_data_1(seg_data_1),
        .seg_data_2(seg_data_2),
        .seg_led_1(seg_led_1),
        .seg_led_2(seg_led_2)
    );
    
    endmodule

    个人实验开发板

    我做实验的开发板为小脚丫FPGA,型号为STEM-MX02-C,这是U盘模式的,芯片为Lattice的,项目下已经有对应的工程,就是step_fpga,如果你的开发板也是这个,同时也安了diamond,也将diamond的可执行路径加入了环境变量,那么可以执行命令./run -g,就会编译代码,拷贝文件,综合工程,下载到开发板了,你可能需要修改的是在step_fpga下的run.tcl脚本的最后一行。

    pnmainc是diamond工具的tcl命令工具!

    几个实例

    流水灯:

    ;系统时钟为倍频到120MHz
    ;目标硬件为 小脚丫FPGA step-maxo2-c,这个型号是U盘模式,流文件会下载到mcu,每次上电由mcu配置FPGA
    ;输入
    constant sw_port,00 ;定义按键四段拨码开关  【按键 : 开关 】
    
    ;输出
    constant seg_port,00 ;定义数码管地址
    constant led_port,01 ;定义led_port为常量01
    constant rgb_port,02  ; rgb灯
    
    start:
        load sA,FE ;  led等控制
        load sB,12 ; 初始化数码管显示 12
        load sC,00000111'b  ; ' rgb 灭
        output sC,rgb_port ;rgb不量
        input sD,sw_port ; 读一次io口
        output sB, seg_port ;数码管显示
    loop:
        output sA, led_port   ;流水灯实现
        RL sA   ;循环左移
        call delay_500ms 
        jump    loop ;循环
    
    
    delay_500ms:     LOAD s2, 09      ;  500000us / (1/1.2us)  --> 计数次数
                    LOAD s1, 27
                    LOAD s0, c0
                    jump software_delay    
    
    software_delay: LOAD s0, s0             ;pad loop to make it 10 clock cycles (5 instructions), if clk 12MHz  --> 1/1.2 us
                    SUB s0, 01
                    SUBCY s1, 00
                    SUBCY s2, 00
                    JUMP NZ, software_delay
                    RETURN 

    按键检测:

    ;系统时钟为12MHz
    ;目标硬件为 小脚丫FPGA step-maxo2-c,这个型号是U盘模式,流文件会下载到mcu,每次上电由mcu配置FPGA
    
    ;实现按键检测,按一下key1,led1将会翻转一次,程序简单,因此注意手不要抖,正常按法按
    ;输入
    constant sw_port,00 ;定义按键四段拨码开关  【按键 : 开关 】
    
    ;输出
    constant seg_port,00 ;定义数码管地址
    constant led_port,01 ;定义led_port为常量01
    constant rgb_port,02  ; rgb灯
    
    
    start:
        load sA,FF ;  led等控制
        output sA,led_port
        load sB,00 ; 初始化数码管显示 
        load sC,FF  ; ' rgb 灭
        output sC,rgb_port ;rgb
        input sD,sw_port ; 读一次io口
    loop:
        input sF,sw_port
        AND sF,00010000'b ;'
        COMPARE sF,00
        JUMP Z,keycheck
        jump    loop ;循环
    
    keycheck:
        CALL delay_200ms
        input sF,sw_port
        AND sF,00010000'b ;'
        COMPARE sF,00
        CALL Z,led_sh
        JUMP loop 
    
    led_sh:
        XOR sA,00010000'b ;'
        OUTPUT sA,led_port
        RETURN 
    
    delay_200ms: LOAD s2, 03      ; 
                 LOAD s1, a9
                 LOAD s0, 80
                 jump software_delay    
    
    delay_500ms:     LOAD s2, 09      ;  500000us / (1/1.2us)  --> 计数次数
                    LOAD s1, 27
                    LOAD s0, c0
                    jump software_delay    
    
    software_delay: LOAD s0, s0             ;pad loop to make it 10 clock cycles (5 instructions), if clk 12MHz  --> 1/1.2 us
                    SUB s0, 01
                    SUBCY s1, 00
                    SUBCY s2, 00
                    JUMP NZ, software_delay
                    RETURN 

    总结

    个人水平有限,中断部分过段时间在添加,对于实现简单传感器的采集,已经足够用了,导师抓得紧,牙缝里挤出的时间写的这个小项目,收获了很多,现在这个项目只是模型,之后会逐步完善!

  • 相关阅读:
    一个泛型的单例模式
    一个将Object转化为CSV文件的类
    WSDL.EXE Error: Not enough storage is avaliable to process the command.
    一个Linq Group By 和Sum的范例
    Random Cube Algorithm
    AccessImport demo
    .net controls of FileUpload
    asp.net AJAX
    Deploy iis7.5
    C# Fibonacci Sequence
  • 原文地址:https://www.cnblogs.com/yhpbook/p/14053204.html
Copyright © 2011-2022 走看看