之前参加&组织学校排球比赛的时候,商量到我们可以学习那些正规排球大赛一样,决赛的时候在学校体育馆进行,而且我可以在一旁准备一点EDM热歌作为暖场音乐。然后就有同学用一脸奇怪的表情问我:“你难道要现场打碟吗?”

这么多年来,身边的人一直以为,能够在自己的小房间里静坐一整天写代码/写文章/读书的人,一定都是老学究的样子。但凡玩电音的,都是不良青年。客观来看,这种成见并没有错,正是因为大多人都这么看问题,国内不论是治安情况还是初等受教育程度才能位居世界之首。但是它也造成了对年轻一代的创伤,那就是维分数与名利是从,然而学生时代数学的优异与否几乎决定了成绩高低,久而久之就形成了各种补习班/数学学校/少管学校的散漫传播。这样的状态持续到18岁,我想所谓的创新对于其中很大一部分人来说都是空谈了吧。

汉斯季末大师曾经在采访中说,不管是电影还是音乐,一个词就能评价其优劣,那就是“故事”。任何通过不少的努力与细细斟酌产生的作品,一定有其欣赏价值,一定可以在某种审美理念下被视作一种雅致的东西。所以尽管看的人不多,比起单单罗列公式代码,我愿意每篇文章开头一段添一点自己的人生感悟。


提及串口通信,最常用的就是UART协议。

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。

具体实物表现为独立的模块化芯片,或作为集成于微处理器中的周边设备。一般是RS-232C规格的,与类似Maxim的MAX232之类的标准信号幅度变换芯片进行搭配,作为连接外部设备的接口。在UART上追加同步方式的序列信号变换电路的产品,被称为USART(Universal Synchronous Asynchronous Receiver Transmitter)。

数据通信格式如下图:

其中各位的意义如下:
    起始位:先发出一个逻辑”0”信号,表示传输字符的开始。
    数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输
    校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)
    停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
    空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。

注:异步通信是按字符传输的,接收设备在收到起始信号之后只要在一个字符的传输时间内能和发送设备保持同步就能正确接收。下一个字符起始位的到来又使同步重新校准(依靠检测起始位来实现发送与接收方的时钟自同步的)。也即:

UART的工作原理:

发送数据过程:空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止位(停止位为高电位),一帧数据发送结束。

接收数据过程:空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备准备接收数据或存入缓存。

由于UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误码。一般UART一帧的数据位数为8,这样即使每个数据有一个时钟的误差,接收端也能正确地采样到数据。

UART的接收数据时序为:当检测到数据的下降沿时,表明线路上有数据进行传输,这时计数器CNT开始计数,当计数器为24=16+8时,采样的值为第0位数据;当计数器的值为40时,采样的值为第1位数据,依此类推,进行后面6个数据的采样。如果需要进行奇偶校验,则当计数器的值为152时,采样的值即为奇偶位;当计数器的值为168时,采样的值为“1”表示停止位,一帧数据接收完成。

一个标准的10位异步串行通信协议(包含1个起始位、1个停止位和8个数据位)收发时序。

当然我们现下讨论的是有关FPGA上实现UART串口通信的方法,结合上述普适的UART的相关知识,我们不难总结FPGA上实现UART的原理(这里要感谢参考了@alexdos的部分总结):

UART 主要由 UART 内核、信号监测器、移位寄存器、波特率发生器、计数器、总线选择器和奇偶校验器总共 7 个模块组成,如图:

1.UART 内核模块

UART 内核模块是整个设计的核心。在数据接收时,UART 内核模块负责控制波特率发生器和移位寄存器,使得移位寄存器在波特率时钟的驱动下同步地接收并且保存 RS-232 接收端口上的串行数据。在数据发送时,UART 内核模块首先根据待发送的数据和奇偶校验位的设置产生完整的发送序列(包括起始位、数据位、奇偶校验位和停止位),之后控制移位寄存器将序列加载到移位寄存器的内部寄存器里,最后再控制波特率发生器驱动移位寄存器将数据串行输出。

2.信号监测器模块

信号监测器用于对 RS-232 的输入信号进行实时监测,一旦发现新的数据则立即通知 UART内核。(注意:这里所说的 RS-232 输入、输出信号都指的是经过电平转换后的逻辑信号,而不是 RS-232 总线上的信号。绝对不能直接将 RS-232 总线的信号连接到 FPGA 管脚上,否则很容易造成 FPGA芯片的损坏。)

3.移位寄存器模块

移位寄存器的作用是存储输入或者输出的数据。当 UART 接收 RS-232 输入时,移位寄存器在波特率模式下采集 RS-232 输入信号,并且保存结果;当 UART 进行 RS-232 输出时,UART 内核首先将数据加载到移位寄存器内,再使移位寄存器在波特率模式下将数据输出到 RS-232 输出端口上。(注意:波特率模式指的是模块的输入时钟是符合 RS-232 传输波特率的时钟,与波特率模式对应的就是系统时钟模式,即模块是工作在系统时钟下。)

4.波特率发生器模块

由于 RS-232 传输必定是工作在某种波特率下,比如 9600,为了便于和 RS-232 总线进行同步,需要产生符合 RS-232 传输波特率的时钟,这就是波特率发生器的功能。

5.奇偶校验器模块

奇偶校验器的功能是根据奇偶校验的设置和输入数据计算出相应的奇偶校验位,它是通过纯组合逻辑实现的。

6.总线选择模块

总线选择模块用于选择奇偶校验器的输入是数据发送总线还是数据接收总线。在接收数据时,总线选择模块将数据接收总线连接到奇偶校验器的输入端,来检查已接收数据的奇偶校验位是否正确;而在发送数据时,总线选择模块将数据发送总线连接到奇偶校验器的输入端,UART内核模块就能够获取并且保存待发送序列所需的奇偶校验位了。

7.计数器模块

计数器模块的功能是记录串行数据发送或者接收的数目,在计数到某数值时通知 UART 内核模块。

UART的优点和缺点

没有任何一种通信协议是完美的,以下是一些优点和缺点,可帮助您确定它们是否符合您项目的需求:

1. 优点

只使用两根电线

不需要时钟信号

有一个奇偶校验位

只要双方设置后,就可以改变数据包的结构

有完整的文档并且具有广泛的使用

2. 缺点

数据帧的大小限制为最多9位

不支持多个从属或多个主系统

每个UART的波特率必须在10%之内

有了上述的总结后,我们便可以开始在Robei中添加适当的代码进行实现了。官方给出的代码仍然是有一些问题的,debug完成后,确认可以产生模块文件的代码如下所示:

//UART接收模块
module uart(clk16x,rst_n,rx,DataReady,DataReceived);//---Ports declearation: generated by Robei---input clk16x;input rst_n;input rx;output DataReady;output [7:0] DataReceived;wire clk16x;wire rst_n;wire rx;reg DataReady;reg [7:0] DataReceived;//----Code starts here: integrated by Robei-----reg[7:0] cnt;reg trigger_r0;reg [3:0] count;wire neg_tri;always @ (posedge clk16x or negedge rst_n)beginif(!rst_n)begintrigger_r0<=0;endelsebegintrigger_r0<=rx;endendassign neg_tri=trigger_r0&~rx;reg cnt_en;always @ (posedge clk16x or negedge rst_n)beginif(!rst_n)cnt_en<=0;else if(neg_tri==1)cnt_en<=1;else if(cnt==8'd152)cnt_en<=0;endalways @ (posedge clk16x or negedge rst_n)beginif(!rst_n)cnt<=8'd0;else    if(cnt_en)cnt<=cnt+1;elsecnt<=8'd0;endalways @ (posedge clk16x or negedge rst_n)beginif(!rst_n)beginDataReceived<=8'b0;count<=0;endelse if(cnt_en)case(cnt)8'd24:begin DataReceived[0]<=rx;count<=count+1;end8'd40:begin DataReceived[1]<=rx;count<=count+1;end8'd56:begin DataReceived[2]<=rx;count<=count+1;end8'd72:begin DataReceived[3]<=rx;count<=count+1;end8'd88:begin DataReceived[4]<=rx;count<=count+1;end8'd104:begin DataReceived[5]<=rx;count<=count+1;end8'd120:begin DataReceived[6]<=rx;count<=count+1;end8'd136:begin DataReceived[7]<=rx;count<=count+1;endendcaseendalways@(posedge clk16x or negedge rst_n)beginif(!rst_n)DataReady<=1'b0;else if(cnt==8'd152)DataReady<=1'b1;elseDataReady<=1'b0;end
endmodule    //uart
//UART发送模块
module uartsend(clk16x,rst_n,TransEn,DataToTrans,BufFull,tx);//---Ports declearation: generated by Robei---input clk16x;input rst_n;input TransEn;input [7:0] DataToTrans;output BufFull;output tx;wire clk16x;wire rst_n;wire TransEn;wire [7:0] DataToTrans;reg BufFull;reg tx;//----Code starts here: integrated by Robei-----reg [7:0] cnt;reg TransEn_r;wire pos_tri;reg cnt_en;always@(posedge clk16x or negedge rst_n)beginif(!rst_n)TransEn_r <= 1'b0;elseTransEn_r <= TransEn;endassign pos_tri = ~TransEn_r & TransEn;reg [7:0] ShiftReg;always @ (posedge pos_tri or negedge rst_n)beginif(!rst_n)ShiftReg <= 8'b0;elseShiftReg <= DataToTrans;endalways @ (posedge clk16x or negedge rst_n)beginif(!rst_n)begincnt_en <= 1'b0;BufFull <= 1'b0;endelse if(pos_tri==1'b1)begincnt_en <=1'b1;BufFull <= 1'b1;endelse if(cnt==8'd160)begincnt_en<=1'b0;BufFull <= 1'b0;endendalways @ (posedge clk16x or negedge rst_n)beginif(!rst_n)cnt<=8'd0;else if(cnt_en)cnt<=cnt+1;elsecnt<=8'd0;endalways @ (posedge clk16x or negedge rst_n)beginif(!rst_n)begintx <= 1'b1;endelse if(cnt_en)case(cnt)8'd0   :  tx <= 1'b0;8'd16  :  tx <= ShiftReg[0];8'd32  :  tx <= ShiftReg[1];8'd48  :  tx <= ShiftReg[2];8'd64  :  tx <= ShiftReg[3];8'd80  :  tx <= ShiftReg[4];8'd96  :  tx <= ShiftReg[5];8'd112 :  tx <= ShiftReg[6];8'd128 :  tx <= ShiftReg[7];8'd144 :  tx <= 1'b1;endcaseelsetx <= 1'b1;endendmodule    //uartsend
//UART testbench代码
module uart_tb();reg clk16x;reg rst_n;reg rx;wire DataReady;wire [7:0] DataReceived;//----Code starts here: integrated by Robei-----initial beginclk16x=0;rst_n=0;rx=0;#2rst_n=1;#2rst_n=0;#2rst_n=1;#2rx=1;#32rx=0;#64rx=1;#128rx=0;#64rx=1;#32rx=0;#32rx=1;#100$finish;endalways #1 clk16x=~clk16x;initial begin$dumpfile ("D:/Robei/RobeiJoey_Project/UART/uart_tb.vcd");$dumpvars;end
endmodule    //uart_tb
//UARTSEND testbench代码
module uartsend_tb();reg clk16x;reg rst_n;reg TransEn;reg [7:0] DataToTrans;wire BufFull;wire tx;//----Code starts here: integrated by Robei-----initial beginclk16x=0;rst_n=1;TransEn=0;DataToTrans=0;#2rst_n=0;#2DataToTrans=8'b10110010;#2rst_n=1;#2TransEn=1;#1000$finish;endalways #1 clk16x=~clk16x;initial begin$dumpfile ("D:/Robei/RobeiJoey_Project/UART/uartsend_tb.vcd");$dumpvars;end
endmodule    //uartsend_tb

具体运行后仿真一切顺利,设计成功。其它的步骤我们大可交给QuartusⅡ完成,如前面提到的方法一样,主要是绑定管脚和查验实际电路图即可。完成后即可下载到FPGA开发板进行验证。


终于,在千辛万苦的入门学习之后,我们迎来了《7天搞定FPGA精录&总结》专栏文章的终极BOSS:Natalius 8位RISC处理器设计实例。以下分为六点循序渐进地从入门RISC处理器的单元开始,深入理解在FPGA上开发建议处理器模块的方法,此处感谢中国电子网的@EDA的总结,对我的思考启发很大。

一、RISC单元设计思路:

1、从RISC是什么说起:

精简指令集计算机RISC(Reduced Instruction Set Computer)是针对复杂指令集计算机CISC(Complex Instruction Set Computer)提出的,具备如下特征1)一个有限的简单的指令集; 2)强调寄存器的使用或CPU配备大量的能用的寄存器;3)强调对指令流水线的使用。

大家都了解过ASIC专用集成电路,但是对RSIC的熟悉程度还不是很高,因为绝大多数非本专业的学生都只是使用CPU,而没有具体深入到CPU的架构内部去思考问题。SoC(System on a Chip)以其高集成度,低功耗等优点越来越受欢迎。开发人员不必从单个逻辑门开始去设计ASIC,而是应用己有IC芯片的功能模块,称为核(core),或知识产权(IP)宏单元进行快速设计,效率大为提高。CPU 的IP核是SoC技术的核心,开发出具有自主知识产权的CPU IP核对我国在电子技术方面跟上世界先进的步伐,提高信息产业在世界上的核心竟争力有重大意义。

2、CPU_ip核的组成:

尽管各种CPU的性能指标和结构细节不同,但所要完成的基本功能相同,从整体上可分为八个基本的部件:时钟发生器、指令寄存器、累加器、RISC CPU算术逻辑运算单元、数据控制器、状态控制器、程序控制器、程序计数器、地址多路器。状态控制器负责控制每一个部件之间的相互操作关系,具体的结构和逻辑关系如图1所示。

时钟发生器利用外部时钟信号,经过分频生成一系列时钟信号给CPU中的各个部件使用。为了保证分频后信号的跳变性能,在设计中采用了同步状态机的方法。

指令寄存器在触发时钟clk1的正跳变触发下,将数据总线送来的指令存入寄存器中。数据总线分时复用传递数据和指令,由状态控制器的load_ir信号负责判别。load_ir信号通过使能信号ena口线输入到指令寄存器。复位后,指令寄存器被清为零。每条指令为两个字节16位,高3位是操作码,低13位是地址线。CPU的地址总线为是13位,位寻址空间为8K 字节。本设计的数据总线是8位,每条指令取两次,每次由变量state控制。

累加器用于存放当前的运算结果,是双目运算中的一个数据来源。复位后,累加器的值为零。当累加器通过使能信号ena 口线收到来自CPU状态控制器load_acc 信号后,在clk1时钟正跳沿时就接收来自数据总线的数据。

算术逻辑运算单元根据输入的不同的操作码分别实现相应的加、与、异或、跳转等基本运算。

数据控制器其作用是控制累加器的数据输出,由于数据总线是各种操作传送数据的公共通道,分时复用,有时传输指令,有时要传送数据。其余时候,数据总线应呈高阻态,以允许其他部件使用。所以,任何部件向总线上输出数据时,都需要一个控制信号的,而此控制信号的启、停则由CPU状态控制器输出的各信号控制决定。控制信号datactl_ena决定何时输出累加器中的数据。

地址多路器用于输出的地址是PC(程序计数器)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM中读取指令,输出的应是PC地址,后4个时钟周期用于对RAM或端口的读写,该地址由指令给出,地址的选择输出信号由时钟信号的8分频信号fecth提供。

程序计数器用于提供指令地址,以便读取指令,指令按地址顺序存放在存储器中,有两种途径可形成指令地址,一是顺序执行程序的情况,二是执行JMP指令后,获得新的指令地址。

状态机控制器接受复位信号RST,当RST有效时,能通过信号ena使其为0 ,输入到状态机中以停止状态机的工作。状态机是CPU 的控制核心,用于产生一系列的控制信号,启动或停止某些部件,CPU何时进行读指令来读写I/O端口及RAM区等操作,都是由状态机来控制的。状态机的当前状态,由变量state记录,state的值就是当前这个指令周期中已经过的时钟数。指令周期是由8 个时钟组成,每个时钟都要完成固定的操作。

3、系统时序的安排:

RISC CPU的复位和启动操作是通过rst引脚的信号触发执行的,当rst信号一进入高电平,RISC CPU就会结束现行操作,并且只要rst停留在高电平状态,CPU就维持在复位状态,CPU各状态寄存器都设为无效状态。当信号rst回到低电平,接着到来的第一个fetch 上升沿将启动RISC CPU开始工作,从ROM的000处的开始读取指令并 执行相应的操作。

读指令时序,每个指令的前3个时钟周期用于读指令,4~6周期读信号rd有效,第7 个周期读信号无效,第8个周期地址总线输出PC地址,为下一个指令作准备。

写指令时序,每个指令的第3.5个时钟周期建立写地址,第四个周期输出数据,第5个时钟周期输出写信号,第6个时钟结束,第7.5个时钟周期输出为PC地址,为下个指令做准备。

4、微处理器指令:

数据处理指令:数据处理指令完成寄存器中数据的算术和逻辑操作,其他指令只是传送数据和控制程序执行的顺序.因此,数据处理指令是唯一可以修改数据值的指令,数据处理指令一般需两个源操作数,产生单个结果.所有的操作数都是8位宽,或者来自寄存器,或者来自指令中定义的立即数.每一个源操作数寄存器和结果寄存器都在指令中独立的指定。

数据传送和控制转移类指令:共有17条,不包括按布尔变量控制程序转移的指令。其中有全存储空间的长调用、长转移和按2KB分块的程序空间内的绝对调用和绝对转移;全空间的长度相对转移及一页范围内的短相对转移;还有条件转移指令。这类指令用到的助记符有ACALL, AJMP, LCALL, LJMP, SJMP, M, JZ, JNZ, ONE,DJNZ。控制转移类指令主要用来修改1x指针从而达到对程序流的控制,所用到的寄存器主要有sp, pc, ir等寄存器。指令由操作码和操作数组成,取指令电路的目的就是把指令码和操作数分开。组成电路由如图3所示。取指令电路由程序指针,程序指针解析模块、ROM, IR(指令寄存器),控制器状态寄存器组成。取指令指令的过程如下:PC指针的值经过pc_mux模块赋值,把ROM中的指令取出来,送到指令寄存器的数据输入口。指令寄存器受状态寄存器的控制,当取指令信号有效时,ROM中的指令码被保存在指令寄存器中,然后经控制器译码,产生控制信号,对PC指针的增量加以控制取出下一条指令。

5、汇编程序:

汇编程序是为了调试软核而开发的,手工编写机器码很容易出错并且工作量很大。在调试过程中修改指令集时,汇编程序也要作相应的修改。所以要求编译器的结构简单性能可靠,在程序中必要的地方可以用堆叠代码方法实现,不必考虑编程技巧和汇编器效率问题。汇编程序用于测试RISC CPU的基本指令集,如果CPU的各条指令执行正确,停止在HLT指令处。如果程序在其它地址暂停运行,则有一个指令出错。程序中,@符号后的十六进制表示存储器的地址,每行的//后表示注释。下面是一小段程序代码,编译好的汇编机器代码装入虚拟ROM,要参加运算的数据装入虚拟RAM就可以开始进行仿真。

机器码 地址 汇编助记符 注释

@00 //地址声明

101_11000 //00 BEGIN: LDA DATA_2

0000_0001

011_11000 //02 AND DATA_3

0000_0010

100_11000 //04 XOR DATA_2

0000_0001001_00000 //06 SKZ

0000_0000

000_00000 //08 HLT //AND does't work

6、调试:

最基本的调试手段 是基于FPGA 厂商提供的开发和仿真环境,用硬件描述语言编写TESTBENCH,构成一个最小运行环境。TESTBENCH产生对目标软核的激励,同时记录软核的输出,和预期值进行比对,可以确定核的设计错误。这种方法的好处是实现容易,结果准确,但硬件描述语言编码量较大。为了仿真结果的准确性,无论功能仿真还是时序仿真,仿真的步长都不能太小,结果导致整个系统仿真时间太长。本设计中先对RISC CPU的各个子模块进行了分别综合,检查正确性,如果发现错误可以在较小的范围内来检查并验证。子模块综合完毕后,把要综合的RISC CPU的模块与外围器件以及测试模块分离出来组成一个大模块,综合后的的RISC CPU模块如图4所示,这是Xilinx ISE7.1 所综合生成的技术原理图。

综合的结果只是通用的门级网表,只是一些与、或、非门的逻辑关系,和芯片实际的配置情况还有差距。此时应该使用FPGA/CPLD厂商提供的实现与布局布线工具,根据所选芯片的型号,进行芯片内部功能单元的实际连接与映射。这种实现与布局布线工具一般要选用所选器件的生产商开发的工具,因为只有生产者最了解器件内部的结构,如在ISE的集成环境中完成实现与布局布线的工具是Flow Engine。

STA(Static Timing Analysis)静态时序分析,完成FPGA设计时必须的一个步骤。在FPGA加约束、综合、布局布线后,在ISE中可以运行Timing Analyzer生成详细的时序报告,本设计中Minimum period: 12.032ns (Maximum Frequency: 83.112MHz),Minimum input arrival time before clock: 6.479ns,Maximum output required time after clock: 9.767ns。然后,设计人员检查时序报告,根据工具的提示找出不满足Setup/Hold time的路径,以及不符合约束的路径,进行修改保证数据能被正确的采样。在后仿真中将布局布线的时延反标到设计中去,使仿真既包含门延时,又包含线延时信息。这种后仿真是最准确的仿真,能真实地反映芯片的实际工作情况。

ALU的设计在之前的文章中已经提到过了,或者可以参考下面的ALU模块的代码实际地去分析,此略。

二、实际代码的编写实现RISC:

1、ALU模块:

reg [7:0] resu;
wire [7:0] result;
always@(a or b)
case (opalu)
0: resu <= ~a;
1: resu <= a & b;
2: resu <= a ^ b;
3: resu <= a | b;
4: resu <= a;
5: resu <= a + b;
6: resu <= a - b;
default: resu <= a + 1;
endcase
assign zero=(resu==0);
assign result=resu;
assign carry=(a<b);
always@(result)
case (sh)
0: dshift <= {result[6:0], “0”};
1: dshift <= {result[6:0], result[7]};
2: dshift <= {“0”, result[7:1]};
3: dshift <= {result[0], result[7:1]};
4: dshift <= result;
5: dshift <= {result[6:0], “1”};
6: dshift <= {“1”, result[7:1]};
default: dshift <= result;
endcase

2、stack模块:

reg [7:0] resu;
wire [7:0] result;
reg [3:0] addr;
reg [10:0] ram [15:0];
wire [10:0] dout;
wire [10:0] din;
always@(posedge clk or posedge rst)
begin
if (rst)
PC<=0;
else
if (ldpc)    
if(selpc)
PC<=ninst_addr;
else
PC<=PC+1;
end
assign din = PC;
always@(posedge clk)
begin
if (rst)
addr<=0;
else
begin
if (wr_en0 && rd_en1)
if (addr>0)
addr<=addr-1;
if (wr_en1 && rd_en0)
if (addr<15)
addr<=addr+1;
end
end
always @(posedge clk)
if (wr_en)
ram[addr] <= din;
assign dout = ram[addr];
assign out = dout + 1;

3、data_supply:

wire [7:0] portB;
wire [7:0] regmux, muxkte;
reg [7:0] mem [7:0];
always@(posedge clk)
begin
mem[0]<=0;
if(we)
mem[wa]<=regmux;
end
assign portA=mem[raa];
assign portB=mem[rab];
assign regmux=insel? shiftout : muxkte;
assign muxkte=selk? kte : data_in;
assign muximm=selimm? imm : portB;

4、zc_control代码:

always @ (posedge clk or posedge rst)
begin
if (rst)
begin
z<=0;
c<=0;
end
else
if (ldflag)
begin
z<=zero;
c<=carry;
end
end

5、data_path(上述模块的)顶层信号连接模块设计:

6、instruction memory 模型设计:

reg[15:0] rom[2047:0]; wire we; assign we=0;

always @(posedge clk)
if(we)
rom[address]<=0;
else
instruction <= rom[address];

7、 control unit模型设计:

parameter fetch=5’d0;
parameter decode=5’d1;
parameter ldi=5’d2;
parameter ldm=5’d3;
parameter stm=5’d4;
parameter cmp=5’d5;
parameter add=5’d6;
parameter sub=5’d7;
parameter andi=5’d8;
parameter oor=5’d9;
parameter xori=5’d10;
parameter jmp=5’d11;
parameter jpz=5’d12;
parameter jnz=5’d13;
parameter jpc=5’d14;
parameter jnc=5’d15;
parameter csr=5’d16;
parameter ret=5’d17;
parameter adi=5’d18;
parameter csz=5’d19;
parameter cnz=5’d20;
parameter csc=5’d21;
parameter cnc=5’d22;
parameter sl0=    5’d23;
parameter sl1=    5’d24;
parameter sr0=5’d25;
parameter sr1=5’d26;
parameter rrl=5’d27;
parameter rrr=5’d28;
parameter noti=5’d29;
parameter nop=5’d30;
wire [4:0] opcode;
reg [4:0] state;
assign opcode=instruction[15:11];
always@(posedge clk or posedge rst)
begin
if (rst)
state<=decode;
else
case (state)
fetch: state<=decode;
decode:
case (opcode)
2: state<=ldi;
3: state<=ldm;
4: state<=stm;
5: state<=cmp;
6: state<=add;
7: state<=sub;
8: state<=andi;
9: state<=oor;
10: state<=xori;
11: state<=jmp;
12: state<=jpz;
13: state<=jnz;
14: state<=jpc;
15: state<=jnc;
16: state<=csr;
17: state<=ret;
18: state<=adi;
19: state<=csz;
20: state<=cnz;
21: state<=csc;
22: state<=cnc;
23: state<=sl0;
24: state<=sl1;
25: state<=sr0;
26: state<=sr1;
27: state<=rrl;
28: state<=rrr;
29: state<=noti;
default: state<=nop;
endcase
ldi:state<=fetch;
ldm:state<=fetch;
stm:state<=fetch;
cmp:state<=fetch;
add:state<=fetch;
sub:state<=fetch;
andi:state<=fetch;
oor:state<=fetch;
xori:state<=fetch;
jmp:state<=fetch;
jpz:state<=fetch;
jnz:state<=fetch;
jpc:state<=fetch;
jnc:state<=fetch;
csr:state<=fetch;
ret:state<=fetch;
adi:state<=fetch;
csz:state<=fetch;
cnz:state<=fetch;
csc:state<=fetch;
cnc:state<=fetch;
sl0:state<=fetch;
sl1:state<=fetch;
sr0:state<=fetch;
sr1:state<=fetch;
rrl:state<=fetch;
rrr:state<=fetch;
noti:state<=fetch;
nop:state<=fetch;
endcase
end
always @ (state)
begin
port_addr<=0;
write_e<=0;
read_e<=0;
insel<=0;
we<=0;
raa<=0;
rab<=0;
wa<=0;
opalu<=4;
sh<=4;
selpc<=0;
ldpc<=1;
ldflag<=0;
naddress<=0;
selk<=0;
KTE<=0;
wr_en<=0;
rd_en<=0;
imm<=0;
selimm<=0;
case (state)
fetch: ldpc<=0;
decode:
begin
ldpc<=0;
if (opcodestm)
begin
raa<=instruction[10:8];
port_addr<=instruction[7:0];
end
else if (opcodeldm)
begin
wa<=instruction[10:8];
port_addr<=instruction[7:0];
end
else if (opcode==ret)
begin
rd_en<=1;
end
end
ldi:
begin
selk<=1;
KTE<=instruction[7:0];
we<=1;
wa<=instruction[10:8];
end
ldm:
begin
wa<=instruction[10:8];
we<=1;
read_e<=1;
port_addr<=instruction[7:0];
end
stm:    
begin
raa<=instruction[10:8];
write_e<=1;
port_addr<=instruction[7:0];
end
cmp:
begin
ldflag<=1;
raa<=instruction[10:8];
rab<=instruction[7:5];
opalu<=6;
end
add:    
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=5;
we<=1;
end
sub:    
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=6;
we<=1;
end
andi:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=1;
we<=1;
end
oor:    
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=3;
we<=1;
end
xori:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=2;
we<=1;
end
jmp:
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jpz:
if (z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jnz:    
if (!z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jpc:    
if ©
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jnc:    
if (!c)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
csr:    
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
ret:    
begin
naddress<=stack_addr;
selpc<=1;
ldpc<=1;
end
adi:    
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
imm<=instruction[7:0];
selimm<=1;
insel<=1;
opalu<=5;
we<=1;
end    
csz:    
if (z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
cnz:    
if (!z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
csc:    
if ©
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
cnc:    
if (!c)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
sl0:    
begin    
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=0;
we<=1;
end
sl1:    
begin    
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=5;
we<=1;
end
sr0:    
begin    
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=2;
we<=1;
end
sr1:    
begin    
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=6;
we<=1;
end
rrl:    
begin    
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=1;
we<=1;
end                        
rrr:    
begin    
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=3;
we<=1;
end
noti:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
opalu<=0;
we<=1;
end
nop: opalu<=4;
endcase
end

8、Natalius processor模型设计,也就是总模块:

9、testbench测试模块设计:

注意:这是modelsim的测试文件代码,这个模块因为太大且含有$readmemh()暂时不太适合在Robei中测试。

`timescale 1ns / 1ps
module testbench_processor();
reg clk_tb;
reg rst_tb;
reg [7:0] data_in_tb;
wire [7:0] port_addr_tb;
wire read_e_tb;
wire write_e_tb;
wire [7:0] data_out_tb;
processor processor_i(
.clk(clk_tb),
.rst(rst_tb),
.port_addr(port_addr_tb),
.read_e(read_e_tb),
.write_e(write_e_tb),
.data_in(data_in_tb),
.data_out(data_out_tb)
);
initial begin
clk_tb = 0;
rst_tb = 1;
data_in_tb = 0;
#5 rst_tb = 0;
#2000 $finish;
end
always #2 clk_tb = ~clk_tb;
endmodule

具体使用的测试样例“test.asm”如下
ldi r1, 22
ldi r2, 80
ldi r3, 36
ldi r4, 45
add r1, r2
stm r1, 11
add r3, r4
stm r3, 25
add r1, r3
stm r1, 32

经过Modelsim的仿真验证,知设计无误,成功。

三、具体导入工程中:

将所有的子模块和顶层模块导入QuartusⅡ中,然后进行分析综合设计,最后下载到FPGA开发板进行验证。在fitter中我们还可以查看设计得到的电路图实物模拟,的确是十分的庞大。目前我所查找到的资料中,2020年3月27日发表的,由Junya MIURA†a), Nonmember, Hiromu MIYAZAKI†b), Student Member, and Kenji KISE†c), 各位Members合作研究的一文A portable and Linux capable RISC-V computer system in Verilog HDL明确指出他们发明了一种5000行左右完整功能的RISC开发的VerilogHDL代码,下图是它们的设计思路总图,截取自文献原文。这虽然使得产生电路的复杂性和工程文件的复杂性大大降低,但是也至少需要数十模块互相协同工作,对芯片的物理空间的占用还是不低的。更何况暂不了解这种技术是否运用在了实际芯片的开发中,如果没有,则当前的设计方案对芯片的空间利用率要求更多。而一个浅显的道理是,单元数越多的芯片,在散热足够支持的情况下通常是更强大的。这也是为什么在突破设计难题的同时,各国各公司争先恐后地努力研发精度更高的光刻机,当然,如果可能,也会尽力去提高FPGA或者时下大热的ZYNQ芯片的集成度。

文末我会将文献资料作为本文的资源信息分享,有兴趣的朋友还可以进一步深入了解学习。


至此,从第一次下载安装Robei软件,从不知道如何使用EDA,如何编写VerilogHDL代码,一步步分析硬件开发的方式,一直到最终完成一份RSIC架构的CPU处理器单元的硬件设计,我们用了六篇文章,总结了其中的核心要点,第七篇文章是本系列的终结,我将会介绍根据这些书籍、软件、文献的学习之后,用来完成我们疫情在家的开发大任务的自选课题和设计思路。这段时间,我读了书,有些想法,便会记录下来。这不一定是需要多少人阅读的,也是我今后复习的一份宝贵的资料。能自己写的一定自己写,不能自己独立完成的也是参考了很多大佬的文章后用我自己的风格汇总提炼。想来这样的事情是很有意义的,在别的学科领域方面,只要我愿意去学的,我都还会坚持做下去。

7天搞定FPGA精录总结Episode.6 串口通信,系统设计【基于Robei、Altera QuartusⅡ与Python】相关推荐

  1. 《7天搞定FPGA》—Robei与Xilinx实战之前言介绍

    <7天搞定FPGA>-Robei与Xilinx实战 思想前言 集成电路设计软件目前在世界上只有几家公司在做,普遍分布在欧美等国家,中国的集成电路设计软件长期依赖于盗版和进口."工 ...

  2. Python程序设计与科学计算精录总结Episode.4 Python进阶:自动化办公应用(基于Michael导师Python课程与VS2019)

    听说, 星空从不耀白. 所以, 凡是夜, 我都低头疾走. 宁静的夏, 望着猎户天际划破, 你许下丝缕的心愿, 愿与我恒久. 可是, 哪里有长守,只不过是慰藉呓语几片. 你悄悄地离开, 捎去美丽的心愿, ...

  3. 录ppt的时候录光标_使用 PowerPoint 轻松搞定 Windows 电脑录屏丨一日一技

    此前给大家介绍过许多 在移动设备上录制屏幕 的方法,却很少提及桌面端的屏幕录制.但实际上在学习和工作中偶尔还是会碰到需要进行桌面端屏幕录制的情况.而近日我无意中发现了一个简便的录制 PC 电脑桌面的方 ...

  4. Python程序设计与科学计算精录总结Episode.2 Python基础语法:函数、模块、内置数据结构、面向对象知识总结(基于Michael导师Python课程与VS2019)

    Jupyter Notebook是基于网页的用于交互计算的应用程序.其可被应用于全过程计算:开发.文档编写.运行代码和展示结果.这个名字就很讲究,Jupyter改自木星的英文单词Jupiter,其中包 ...

  5. 数据结构精录总结Episode.6 数据结构入门之树(基于Visual C++)

    好久(可能一周)没有更新博文了吧. 国内的疫情控制情况已经接近尾声,同样地我们的这一学期也快接近尾声.其实感觉宅家学习虽然远远没有学校那么紧张了,但是效率有所降低,生活规律性也降低了.早晨很晚才起床但 ...

  6. Python程序设计与科学计算精录总结Episode.3 Python高级语法:文件、异常、标准库和问题解决模式知识总结(基于Michael导师Python课程与VS2019)

    今天是2020年5月20日,这个对于博主来说平凡的一天,但社交媒体上早就炸开锅了. 博主认为,有些人受过伤之后,就爱的小心翼翼,迟迟不敢去再次追求自己的另一半,就这样,渐渐的过了能够大胆去爱的年纪,一 ...

  7. 【Linux】一篇文章搞定 CPP模拟实现TCP协议下socket通信

    CPP模拟实现TCP协议下socket通信 1. TCP 编程流程图 2. 数据收发阶段使用的API 2.1 send接口 2.2 recv接口 3. 两个队列 4. 总结TCP 编程双端流程 5. ...

  8. FPGA学习笔记(四)——串口通信之接收数据(调试过程)

    本学习笔记主要参考小梅哥B站教学视频,网址如下: https://www.bilibili.com/video/BV1va411c7Dz?p=1 使用的编译器为Vivado,HDL语言为verilog ...

  9. mac电脑怎么录屏?2招轻松搞定!

    案例:怎样对mac电脑进行屏幕录制? [之前一直使用的是Windows电脑,对Windows电脑比较熟悉.最近换了一台苹果电脑,不知道使用它怎么进行电脑录屏.求一个好用的苹果电脑录屏方法!] 在我们的 ...

最新文章

  1. Service 和 doGet 和 doPost 方法的区别
  2. Base64的编码规则和C#实现
  3. 计算机视觉:数据预处理-图像增广方法
  4. WebRTC 的 log 系统实现分析
  5. 解决 Unable to load native-hadoop library for your platform方法之一
  6. [jQuery] jQuery.fn的init方法返回的this指的是什么对象?为什么要返回this
  7. 如何在Java中对Collection对象进行排序?
  8. STM32F103_步进电机
  9. JPA还是JDBC?
  10. ascii码01100001_ASCII码对照表以及各个字符的解释(精华版)
  11. 5款最好用的免费3D建模软件(附下载链接)
  12. PageOffice 5.2 试用版注册使用
  13. K8s 开先河、技能全栈、业务“无感”,深度解读云原生的这一年
  14. 谈谈对儒家与道家的一些小小看法
  15. 只做了delete操作,为啥 ORACLE-01466表定义已更改
  16. 火焰焰心matlab,火焰的形貌-中性焰、碳化焰、氧化焰
  17. 解决电脑速度慢的问题
  18. 读书笔记---货币战争
  19. RISC-V的ARTY工程实现
  20. Python课程第十一天_下午_课程笔记(包和模块)

热门文章

  1. 最新浏览器调查:Firefox、Safari、Chrome市场占有率上升
  2. java对象数组和Array List
  3. [IPHONE]游戏引擎剖析
  4. 如何养成读书的好习惯
  5. ArcGis之JavaScript
  6. java协议手机qq批量登录软件_手把手分享:日常大量账号批量登录,批量点击软件...
  7. matplot三维画图
  8. react中做proxy代理解决options的问题
  9. 剑灵电五服务器位置,剑灵都有哪些区合区?
  10. 服务器在raid5下做系统ghost备份,服务器在RAID5下做系统ghost备份