分类 💻电子&计算机 下的文章 - 我的学记|刘航宇的博客
首页
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🖼️相册
🎵音乐
🏞️壁纸
搜 索
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
10,656 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
6,175 阅读
3
【高数】形心计算公式讲解大全
5,595 阅读
4
【1】基于STM32CubeMX-STM32GPIO端口开发
4,284 阅读
5
如何判断运放是工作在线性区还是非线性区
4,076 阅读
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
电路&嵌入式
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
搜 索
标签搜索
嵌入式
ASIC/FPGA
VLSI
SOC设计
机器学习
天线设计
C/C++
EDA&虚拟机
小实验
软件算法
信号处理
电子线路
通信&射频
随笔
笔试面试
硬件算法
Verilog
软件无线电
Python
DL/ML
刘航宇Hangyu Liu
嵌入式系统&数字IC爱好者博客
累计撰写
296
篇文章
累计收到
520
条评论
首页
栏目
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
电路&嵌入式
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
页面
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🖼️相册
🎵音乐
🏞️壁纸
用户登录
登录
💻电子&计算机(共25篇)
找到
25
篇与
💻电子&计算机
相关的结果
Microsemi Libero SOC使用示例—建立点灯工程
嵌入式开发中的Hello World,点灯是再也基础不过的实验了,通过点灯实验,可以了解芯片GPIO的控制和开发环境IDE新建工程的流程,对于FPGA来说,每个IO口几乎一样,所以本篇文章主要学习一下如何基于Microsemi Libero集成开发环境建立一个示例工程,让一个LED以500ms的频率闪烁,以Microsemi SmartFusion系列FPGA——A2F200M3F为例,Microsemi其他系列FPGA芯片过程类似。准备工作工欲利其事,必先利其器,充分的准备工作很有必要。软件准备:Microsemi Libero SoC集成开发环境,并已经成功注册,软件版本推荐V11.8或更高版本。硬件准备:Microsemi FPGA开发板,主控芯片A2F200M3F-PQ208,其他型号芯片类似。Flash Pro 4或Flash Pro5下载器,用于给FPGA芯片下载程序和调试。新建工程的主要步骤新建工程,选择芯片型号等新建设计,使用Verilog编写点灯模块。仿真验证,对编写的点灯模块进行时序仿真,来验证是否满足设计需求。综合、管脚分配、布局、布线。生成程序文件,连接开发板,使用FlashPro下载程序到芯片内,观察现象是否和设计的一致。1.新建工程和大多数IDE一样,选择Project -> New Project,新建一个工程。输入工程名称LED_Blink,选择工程存放的路径,工程名称和路径不要有中文字符和空格,选择源文件的类型Verilog或者VHDL。选择芯片型号,这里选择Microsemi SmartFusion系列下的A2F200M3F芯片,PQ208封装,把鼠标放在所选芯片上,可以查看芯片的详细参数:封装、速度等级、温度范围,内核电压、Flash ROM大小、用户IO数目、RAM大小、Flash ROM大小,ARM Cortex-M3 SoC的外设配置等详细的参数。选择IO的电平标准,不同的电平标准,高低电平的电压范围是不同的,这里选择默认的LVTTL。是否创建MSS模块,MSS里有PLL和ARM Cortex-M3的使用,以后用到PLL和ARM核时再添加,这里先不选择,以后需要也可以再创建。是否导入已经存在的HDL文件,如果已经有一些写好的模块,可以在这里直接导入。是否导入已经存在的管脚约束文件,这里选择不添加,我们会在后面通过图形化工具来指定管脚。到这里,工程就创建完成了,然后会在存储路径下生成一个和工程名称一样的文件夹,工程相关的所以文件都存放在这里。主要包括以下几个文件夹:具体每个文件夹存放的是什么文件,我们在以后的文章再详细介绍。以上的工程配置在创建完工程之后,也可以再次更改,可以通过Project->Project Setting查看或更改配置:或者通过点击如下图标来进入配置界面:弹出如下窗口,和新建工程是一样的,可以更改FPGA的型号,但只限于同一个系列内。2.添加设计文件Microsemi Libero开发环境支持HDL方式和SmarDesign方式来创建设计,HDL方式支持VerilogHDL和VHDL两种硬件描述语言,而SmartDesign方式和Xilinx的Schematic原理图方式是一样的,是通过图形化的方式来对各个模块之间的连接方式进行编辑,两种方式都可以完成设计。由于本实验功能简单,所以以使用Verilog文件为例。创建Verilog文件创建Verilog文件有多种方式,可以直接双击左侧菜单中的Create Design->Create HDL或者点击File->New->HDL,这两种方式都可以创建一个Verilog设计文件,这里选择Verilog文件。输入模块名称:led_driver,不用添加.v后缀名,Libero软件会自动添加。源代码:module led_driver( //input input clk, //clk=2MHz input rst_n, //0=reset //output output reg led ); parameter T_500MS = 999999; //1M reg [31:0] cnt; always @ (posedge clk) begin if(!rst_n) cnt <= 32'b0; else if(cnt >= T_500MS) cnt <= 32'b0; else //cnt < T_500MS cnt <= cnt + 32'b1; end always @ (posedge clk) begin if(!rst_n) led <= 1'b1; else if(cnt >= T_500MS) led <= ~led; end endmodule可以看到,代码非常的简单,定义一个计数器,系统时钟为2MHz=500ns,500ms=1M个时钟周期,当计数到500ms时,LED翻转闪烁。3.仿真验证编写完成,之后,点击对号进行语法检查,如果没有语法错误就可以进行时序仿真了。新建Testbench文件底部切换到Design Hierarchy选项卡,在led模块上右键选择Create Testbechch创建仿真文件,选择HDL格式。给创建的testbench文件名一般为模块名后加_tb,这里为:led_driver_tb,因为我们的板子外部晶体为2M,所以这里系统时钟周期为500ns,这个也可以在文件中更改。点击OK之后,可以看到,Libero软件已经为我们生成了一些基本代码,包括输入端口的定义,系统时钟的产生,输入信号的初始化等等。我们只需要再增加几行即可。`timescale 1ns/100ps module led_driver_tb; parameter SYSCLK_PERIOD = 500;// 2MHZ reg SYSCLK; reg NSYSRESET; wire led; //add output reg initial begin SYSCLK = 1'b0; NSYSRESET = 1'b0; end initial begin #(SYSCLK_PERIOD * 10 ) NSYSRESET = 1'b0; //add system reset #(SYSCLK_PERIOD * 100 ) NSYSRESET = 1'b1; //add system set end always @(SYSCLK) //generate system clock #(SYSCLK_PERIOD / 2.0) SYSCLK <= !SYSCLK; led_driver led_driver_0 ( // Inputs .clk(SYSCLK), .rst_n(NSYSRESET), // Outputs .led(led ) //add port // Inouts ); endmodule仿真代码也非常简单,输入信号初始化,NSYSRESET在10个时钟周期之后拉低,100个时钟周期之后拉高。使用ModelSim进行时序仿真仿真代码语法检查无误后,可以进行ModelSim自动仿真,在安装Libero时,已经默认安装了ModelSim仿真软件,并和Libero进行了关联。直接双击Simulate,Libero会自动打开ModelSim。可以看到输入输出信号,已经为我们添加好了:先点击复位按钮,复位系统,然后设置要运行的时间,由于设计的是500ms闪烁一次,这里我们先运行2s,即2000ms,在ModelSim中2秒已经算是很长的时间了,然后点击时间右边的运行按钮,耐心等待,停止之后就会看到led按500ms变化一次的波形了,如下图所示,可以再添加一个cnt信号到波形观察窗口,可以看到cnt周期性的变化。使用2个光标的精确测量,可以看出,led每隔500ms翻转一次,说明程序功能是正确的。4.管脚分配与STM32等MCU不同,FPGA的引脚配置非常灵活,如STM32只有固定的几个引脚才能作为定时器PWM输出,而FPGA通过管脚分配可以设置任意一个IO口输出PWM,而且使用起来非常灵活,这也是FPGA和MCU的一个区别,当然其他的功能,如串口外设,SPI外设等等,都可以根据需要自己用HDL代码来实现,非常方便。时序仿真正常之后,就可以进行管脚分配了,即把模块的输入输出端口,真正的分配到芯片实际的引脚上,毕竟我们的代码是要运行在真正的芯片上的。打开引脚配置图形化界面双击Create/Edit I/O Attributes,打开图形化配置界面,在打开之前,Libero会先进行综合(Synthesize)、编译(Complie),当都运行通过时,才会打开配置界面。分配管脚管脚可视化配置工具使用起来非常简单:引脚号指定、IO的电平标准,内部上下拉等等,非常直观。把时钟、复位、LED这些管脚分配到开发板原理图中对应的引脚,在分配完成之后,可以点击左上角的commit and check进行检查。在分配完成之后,为了以后方便查看已经分配的引脚,可以导出一个pdc引脚约束文件,选择Designer窗口下的File->Export->Constraint File,会导出一个led_driver.pdc文件,保存在工程目录下的constraint文件夹。一些特殊管脚的处理SmartFusion系列的FPGA芯片,在分配个别引脚,如35-39、43-47这些引脚时,直接不能分配,这些引脚属于MSS_FIO特殊引脚,具体怎么配置为通用IO,可以查看下一篇文章。而新一代的SmartFusion 2系列的FPGA芯片则没有这种情况。5.程序下载管脚分配完成之后,连接FlashPro下载器和开发板的JTAG接口,关闭Designer窗口,选择Program Device,耐心等待几分钟,如果连接正常,会在右侧输出编程信息:擦除、验证、编程等操作,下载完成之后,就会看到板子上的LED闪烁起来了。Microsemi FPGA的Flash结构和Altera、Xilinx不同,Microsemi FPGA在下载程序时,并不是下载程序到SPI Flash,而是直接下载到FPGA内部的。目前,FPGA 市场占有率最高的两大公司Xilinx和Altera 生产的 FPGA 都是基于 SRAM 工艺的,需要在使用时外接一个片外存储器以保存程序。上电时,FPGA 将外部存储器中的数据读入片内 RAM,完成配置后,进入工作状态;掉电后 FPGA 恢复为白片,内部逻辑消失。这样 FPGA 不仅能反复使用,还无需专门的 FPGA编程器,只需通用的 EPROM、PROM 编程器即可。而Microsemi的SmartFusion、SmartFusion2、ProASICS3、ProASIC3E系列基于Flash结构,具备反复擦写和掉电后内容非易失性, 因此基于Flash结构的FPGA同时具备了SRAM结构的灵活性和反熔丝结构的可靠性,这种技术是最近几年发展起来的新型FPGA实现工艺,目前实现的成本还偏高,没有得到大规模的应用。示例工程下载基于Libero V11.8.2.4的工程下载:
2023年06月09日
475 阅读
0 评论
2 点赞
2023-05-29
Windows安装Jupyter Notebook及机器学习的库
Jupyter Notebook安装(Windows)下载Jupyter Notebook(1)打开cmd(如果没有把Python安装目录添加到Path,需要切换到Python安装目录的Scripts目录下);(2)输入pip install jupyter;(推荐在外网环境下安装)2.库安装下载下面文件然后执行:pip3 install -r requirements.txt启动Juypter Notebook(1)命令行窗口输入jupyter notebook;浏览器会打开Jupyter Notebook窗口,说明Jupyter Notebook安装成功。(2) 配置Jupyter Notebook1、行窗口输入jupyter notebook --generate-config,会发现C:\Users\用户名\ .jupyter下多出了一个配置文件jupyter_notebook_config.py;2、这个配置文件,找到下面这句#c.NotebookApp.notebook_dir = ''。可以把它修改成c.NotebookApp.notebook_dir = 'D:\jupyter-notebook',当然具体的目录由自己创建的文件夹决定(需要自己创建)。配置文件修改完成后,以后在jupyter notebook中写的代码都会保存在该目录下。现在重新启动jupyter notebook,就进入了新的工作目录;5.添加代码自动补全功能(可选)(1)打开cmd,输入pip install jupyter_contrib_nbextensions,等待安装成功;(2)安装完之后需要配置nbextension(配置前要确保已关闭jupyter notebook),在cmd中输入jupyter contrib nbextension install --user --skip-running-check,等待配置成功;(3)在前两步成功的情况下,启动jupyter notebook,会发现在选项栏中多出了Nbextension的选项,点开该选项,并勾选Hinterland,即可添加代码自动补全功能。
2023年05月29日
152 阅读
0 评论
1 点赞
FPGA&Matlab联合开发之滤波器模块(带通滤波器为例)
在通信或者信号处理中,数字滤波器是非常重要的模块,前面有关博文中提到FIR滤波器的一步步Verilog设计,如https://ee.ac.cn/index.php/archives/511.html本文以带通滤波器为例,利用Matlab进行高效开发MATLAB生成低通滤波器设计步骤:(1)在MATLAB命令窗口中输入“filterDesigner”或“fdatool”出现如下对话框设置FIR滤波器为和需要的阶数滤波器,选择窗函数的类型为海明窗函数,海明窗函数可以得到旁瓣更小的效果,能量更加集中在主瓣中设置带通滤波器的上下截至频率分别为4MHz 和 5MHz(2)量化输入输出,点击工作栏左边的量化选项,即“set quantization parameters”选项,选择定点,设置输入字长为8,其他选择默认,如下图示:(3)根据自己需求,细化一些配置。这里不难探索设置完成后,点击Targets中Generate HDL,选择生成Verilog 代码,设置路径,MATLAB即可生成设计好的滤波器Verilog HDL 代码以及测试文件:(4)根据需求,配置输出.v文件的全局信号、测试文件,点击生成,生成后,Matlab主页面会提示.v生成的文件路径Modelsim仿真上述文件可以看到输入信号在4MHZ~5MHZ备保留,设计无误。需要注意一点,一般Modelsim仿真输出波形都是离散的01信号,这里需要配置一下,在上图被选中的信号中,在左侧右键鼠标。右击,format,analog(automatic);右击,radix,decimal;这两个步骤完成之后,就出现上图模拟信号的效果
2023年05月21日
156 阅读
0 评论
2 点赞
2023-05-15
机器学习代码实现:线性回归与岭回归
1.线性回归模型线性回归模型是最简单的一种线性模型,模型的形式就是:$y=W^T x+b$我们可以通过对原本的输入向量x扩增一个为1的维度将参数W和b统一成一个参数W,即模型变成了$y=W^T x$这里的W是原本两个参数合并之后的而其损失函数的形式是残差平方损失RSS$L=\frac \sum_^m\left(W^T x_i-y_i\right)^2=\frac\left(W^T X-y\right)^T\left(W^T X-y\right)$我们很容易就可以通过求导得到线性回归模型的关于W的梯度$\nabla_W L=\frac \sum_^m\left(W^T x_i-y_i\right) x_i=\frac X^T\left(W^T X-y\right)$这样一来我们就可以通过梯度下降的方式来训练参数W,可以用下面的公式表示$W:=W-\alpha \frac X^T\left(W^T X-y\right)$但实际上线性模型的参数W可以直接求解出,即:$W=\left(X^T X\right)^ X^T y$2.线性回归的编程实现具体代码中的参数的形式可能和上面的公式推导略有区别,我们实现了一个LinearRegression的类,包含fit,predict和loss三个主要的方法,fit方法就是求解线性模型的过程,这里我们直接使用了正规方程来解class LinearRegression: def fit(self, X: np.ndarray, y: np.ndarray) -> float: N, D = X.shape # 将每个样本的特征增加一个维度,用1表示,使得bias和weight可以一起计算 # 这里在输入的样本矩阵X末尾增加一列来给每个样本的特征向量增加一个维度 # 现在X变成了N*(D+1)维的矩阵了 expand_X = np.column_stack((X, np.ones((N, 1)))) self.w = np.matmul(np.matmul(np.linalg.inv(np.matmul(expand_X.T, expand_X)), expand_X.T), y) return self.loss(X, y)predict实际上就是将输入的矩阵X放到模型中进行计算得到对应的结果,loss给出了损失函数的计算方式:def loss(self, X: np.ndarray, y: np.ndarray): """ 线性回归模型使用的是RSS损失函数 :param X:需要预测的特征矩阵X,维度是N*D :param y:标签label :return: """ delta = y - self.predict(X) total_loss = np.sum(delta ** 2) / X.shape[0] return total_loss3.岭回归Ridge Regression与代码实现岭回归实际上就是一种使用了正则项的线性回归模型,也就是在损失函数上加上了正则项来控制参数的规模,即:$L=\frac \sum_^m\left(W^T x_i-y_i\right)^2+\lambda\|W\|_2=\frac\left(W^T X-y\right)^T\left(W^T X-y\right)+\lambda W^T W$因此最终的模型的正规方程就变成了:$W=\left(X^T X+\lambda I\right)^ X^T y$这里的\lambda是待定的正则项参数,可以根据情况选定,岭回归模型训练的具体代码如下class RidgeRegression: def fit(self, X: np.ndarray, y: np.ndarray): N, D = X.shape I = np.identity(D + 1) I[D][D] = 0 expand_X = np.column_stack((X, np.ones((N, 1)))) self.W = np.matmul(np.matmul(np.linalg.inv(np.matmul(expand_X.T, expand_X) + self.reg * I), expand_X.T), y) return self.loss(X, y)4.数据集实验这里使用了随机生成的二位数据点来对线性模型进行测试,测试结果如下:线性模型测试结果岭回归也使用同样的代码进行测试。
2023年05月15日
81 阅读
0 评论
1 点赞
联发科2024年数字IC设计验证实习生考题解析
1、(20分)逻辑化简:(1)列出真值表(2)列出其卡诺图(3)写出Z的最简表达式答:卡诺图:卡诺图画完后勾1就完事了提示:约束项的一般形式为:与或式 = 0 (如果不是此种形式,化为此种形式);如此题的BC = 0;或者AB +CD = 0;ABC + CD = 0;等等。BC=0(即B=1,且C=1)对应的格子画X。2、(5分)ASIC flow 中综合工具的作用是什么?综合的时候需要SDC文件进行约束,请列举3条SDC的语法。答:ASIC flow 中综合工具的作用是将RTL级的硬件描述语言转换为与特定工艺库相匹配的门级网表,同时进行优化以满足时序、面积和功耗等约束。综合的时候需要SDC文件进行约束,SDC文件是一种基于Tcl的格式,用于指定设计的时序约束34。SDC文件中的常用时序约束语法有:create_clock -name <clock_name> -period <clock_period> [get_ports <clock_port>] 用于创建时钟源并指定时钟周期。 set_input_delay -clock <clock_name> <delay_value> [get_ports <input_port>] 用于指定输入端口相对于时钟源的延迟。 set_output_delay -clock <clock_name> <delay_value> [get_ports <output_port>] 用于指定输出端口相对于时钟源的延迟。 set_clock_uncertainty -setup <setup_value> -hold <hold_value> <clock_name> 用于指定时钟源的不确定性,包括建立时间和保持时间。 set_false_path -from [get_ports <source_port>] -to [get_ports <destination_port>] 用于指定不需要进行时序分析的路径。 set_multicycle_path -setup -from [get_clocks <source_clock>] -to [get_clocks <destination_clock>] <cycle_number> 用于指定多周期路径,即源时钟和目标时钟之间有多个周期的时间差。3、(10分)智力题(1)2 12 1112 3112 132112 ,下一个数?给理由;答:第一个数是2,第二个数是12,表示前一个数有1个2;第三个数是1112,表示前一个数有1个1和1个2;以此类推。所以,下一个数是1113122112,表示前一个数有1个1,1个3,2个1和2个2(2)有一个小偷费劲力气进入到了银行的金库里。在金库里他找到了一百个箱子,每一个箱子里都装满了金币。不过,只有一个箱子里装的是真的金币,剩下的99个箱子里都是假的。真假金币的外形和质感完全一样,任何人都无法通过肉眼分辨出来。它们只有一个区别:真金币每一个重量为101克,而假金币的重量是100克。在金库里有一个电子秤,它可以准确地测量出任何物品的重量,精确到克。但很不幸的是,这个电子秤和银行的报警系统相连接,只要被使用一次就会立刻失效。请问,小偷怎么做才能只使用一次电子秤就找到装着真金币的箱子呢?答:小偷可以这样做:从第一个箱子里拿出1个金币,从第二个箱子里拿出2个金币,从第三个箱子里拿出3个金币,以此类推,直到从第一百个箱子里拿出100个金币。然后,把所有拿出来的金币放在电子秤上,测量它们的总重量。如果所有的金币都是假的,那么总重量应该是5050克(等于1+2+3+…+100)。如果有一个箱子里是真的金币,那么总重量会比5050克多出一些。这个多出来的部分就是真金币的数量乘以1克。例如,如果第十一个箱子里是真的金币,那么总重量会比5050克多出11克,因为从第十一个箱子里拿出了11个真金币。所以,小偷只要看电子秤上显示的数字减去5050,就能知道哪个箱子里是真的金币了。4、(10分)选择参与过的任一个项目,简述项目内容以及流程,讲述您在项目中承担的任务,挑一项你认为难的地方并阐述解决方案。答:优先答ASIC的设计与验证项目,其次是FPGA项目(如基于FPGA的图像处理、天线阵、雷达、加速器等等),其它项目不要答。5、(5分)用python写一个冒泡排序的函数以及测试程序。# 定义冒泡排序函数 def bubble_sort(lst): # 获取列表长度 n = len(lst) # 遍历列表n-1次 for i in range(n-1): # 设置一个标志,用于判断是否发生交换 swapped = False # 遍历未排序的部分 for j in range(n-1-i): # 如果前一个元素大于后一个元素,交换位置 if lst[j] > lst[j+1]: lst[j], lst[j+1] = lst[j+1], lst[j] # 标志设为True,表示发生了交换 swapped = True # 如果没有发生交换,说明列表已经有序,提前结束循环 if not swapped: break # 返回排序后的列表 return lst # 定义测试程序 # 创建一个乱序的列表 lst = [5, 3, 8, 2, 9, 1, 4, 7, 6] # 打印原始列表 print("Original list:", lst) # 调用冒泡排序函数,对列表进行排序 lst = bubble_sort(lst) # 打印排序后的列表 print("Sorted list:", lst)结果图6、(15分)用Verilog 写一个 Round Robin 仲裁器。模块端口如下:input clock; input reset_b; input [N-1:0] request; input [N-1] lock; output [N-1] grant; //one-hot此处的 lock 输入信号,表示请求方收到了仲裁许可,在对应的lock拉低之前,仲裁器不可以开启新的仲裁。(可简单理解为仲裁器占用)该题要求参数化编程,在模块例化时可调整参数。也即是说你不能写一个固定参数,比如N=8的模块。参考波形图:答:// 功能: // -1- Round Robin 仲裁器 // -2- 仲裁请求个数N可变 // -3- 加入lock机制(类似握手) // -4- 复位时的最高优先级定为 0 ,次优先级:1 -> 2 …… -> N-2 -> N-1 `timescale 1ns / 1ps module RoundRobinArbiter #( parameter N = 4 //仲裁请求个数 )( input clock, input reset_b, input [N-1:0] request, input [N-1:0] lock, output reg [N-1:0] grant//one-hot ); // 模块内部参数 localparam IDLE = 3'b001;// 复位进入空闲状态,接收并处理系统的初次仲裁请求 localparam WAIT_REQ_GRANT = 3'b010;// 等待后续仲裁请求到来,并进行仲裁 localparam WAIT_LOCK = 3'b100;// 等待LOCK拉低 // 模块内部信号 reg [2:0] R_STATUS; //请求状态 reg [N-1:0] R_MASK; //掩码 wire [N-1:0] W_REQ_MASKED; assign W_REQ_MASKED = request & R_MASK; //屏蔽低位 always @ (posedge clock) begin if(~reset_b) begin R_STATUS <= IDLE; R_MASK <= 0; grant <= 0; end else begin case(R_STATUS) IDLE: begin if(|request) //首次仲裁请求,不全为0 begin R_STATUS <= WAIT_LOCK; //首先需要找到request中优先级最高的比特位,对优先级最高的比特位给出许可信号。 //这一步可以通过request和它的2的补码按位与。这是因为一个数和它的补码相与,得到的结果是一个独热码,独热码为1的那一位是这个数最低的1 grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); //得到掩码的方法是,对第一步的许可信号grant-1,再与grant本身相或,相或的结果再取反。 end else begin R_STATUS <= IDLE; end end WAIT_REQ_GRANT://处理后续的仲裁请求 begin if(|request) begin R_STATUS <= WAIT_LOCK; //在下一轮仲裁中,已经被仲裁许可的比特位变成了最低优先级,而未被仲裁许可的比特位将会被仲裁。 //因此对第一步中给出许可的比特位(假设是第2位)以及它的低比特位进行屏蔽,对request中的第5位到第3位进行保持 //这个操作可以利用掩码111000和request相与实现得到。 if(|(request & R_MASK))//不全为零 begin grant <= W_REQ_MASKED & ((~W_REQ_MASKED)+1); R_MASK <= ~((W_REQ_MASKED & ((~W_REQ_MASKED)+1))-1 | (W_REQ_MASKED & ((~W_REQ_MASKED)+1))); end else begin grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); end end else begin R_STATUS <= WAIT_REQ_GRANT; grant <= 0; R_MASK <= 0; end end //通过第二步得到第2位到第0位被屏蔽的request_new信号, //判断request_new是否为全0信号,如果是全0信号,代表此时不存在需要被仲裁的比特位,则返回第一步:找到request中优先级最高的比特位, //对优先级最高的比特位给出许可信号,然后进行第二步。如果request_new不是全0信号,代表存在未被仲裁的比特位, //则找到request_new中优先级最高的比特位,对优先级最高的比特位给出许可信号,然后进行第二步。 WAIT_LOCK: begin if(|(lock & grant)) //未释放仲裁器 begin R_STATUS <= WAIT_LOCK; end else if(|request) //释放的同时存在仲裁请求 begin R_STATUS <= WAIT_LOCK; if(|(request & R_MASK))//不全为零 begin grant <= W_REQ_MASKED & ((~W_REQ_MASKED)+1); R_MASK <= ~((W_REQ_MASKED & ((~W_REQ_MASKED)+1))-1 | (W_REQ_MASKED & ((~W_REQ_MASKED)+1))); end else begin grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); end end else begin R_STATUS <= WAIT_REQ_GRANT; grant <= 0; R_MASK <= 0; end end default: begin R_STATUS <= IDLE; R_MASK <= 0; grant <= 0; end endcase end end endmodule测试代码`timescale 1ns / 1ps module RoundRobinArbiter_tb; parameter N = 4; // 可以在测试时调整参数 // 定义测试信号 reg clock; reg reset_b; reg [N-1:0] request; reg [N-1:0] lock; wire [N-1:0] grant; // 定义时钟信号 initial clock = 0; always #10 clock = ~clock; // 实例化仲裁器模块 RoundRobinArbiter #( .N(N) ) inst_RoundRobinArbiter ( .clock (clock), .reset_b (reset_b), .request (request), .lock (lock), .grant (grant) ); // 定义时钟周期和初始值 initial begin reset_b <= 1'b0; request <= 0; lock <= 0; end // 定义请求和锁定信号的变化 initial begin #20; reset_b <= 1'b1; @(posedge clock) request <= 2; lock <= 2; @(posedge clock) request <= 0; @(posedge clock) request <= 5; lock <= 7; @(posedge clock) lock <= 5; @(posedge clock) request <= 1; @(posedge clock) lock <= 1; @(posedge clock) request <= 0; @(posedge clock) lock <= 0; #1000 $stop; // 测试结束 end // 显示测试结果和波形图 initial begin $monitor("Time=%t, clock=%b, reset_b=%b, request=%b, lock=%b, grant=%b", $time, clock, reset_b, request, lock, grant); $dumpfile("RoundRobinArbiter_tb.vcd"); $dumpvars(0,RoundRobinArbiter_tb); end endmodule结果:如果对波形图无法理解可以看此博文https://blog.csdn.net/m0_49540263/article/details/1149674437、(15分)关于DMA寄存器配置,DMA寄存器(地址 0x81050010)表:Type 表示读写类型。Reset 表示复位值。写一个C函数 void dma_driver(void),按步骤完成以下需求:分配DMA所需的源地址(0x30)分配DMA所需的目的地址(0x300)设置传输128 Byte 数据开始DMA传输等待DMA传输结束答:// 假设有以下宏定义 #define DMA_REG 0x81050010 // DMA控制寄存器的地址 #define DMA_SRC_ADDR 0x30 // DMA源地址 #define DMA_DST_ADDR 0x300 // DMA目的地址 #define DMA_SIZE 128 // DMA传输大小 #define DMA_START 1 // DMA开始传输的标志位 // 定义C函数 void dma_driver(void) void dma_driver(void) { // 定义一个指向DMA控制寄存器的指针 volatile uint32_t *dma_reg = (volatile uint32_t *)DMA_REG; // 清空DMA控制寄存器的值 *dma_reg = 0; // 设置DMA源地址,目的地址和传输大小 *dma_reg |= (DMA_SRC_ADDR << 2) | (DMA_DST_ADDR << 13) | (DMA_SIZE << 24); // 开始DMA传输 *dma_reg |= DMA_START; // 等待DMA传输结束 while (*dma_reg & DMA_START) { // 可以在这里做一些其他的事情,比如打印日志或者检查错误 // printf("Waiting for DMA to finish...\n"); // check_error(); } }8、(20分)二阶带通滤波器,利用RC组件搭建,通带范围 1kHz~30kHz ,两个电阻 R 均为10kΩ ,问两个电容容值多少?答:第一步首得知道二阶带通(RC)滤波器的电路长啥样,高、低通组合一下就是带通,自己思考一下高、低通组合:如串联或并联,会得到带通还是带组?电路图:这个一看就是总传递函数=A1*A2(模电二阶有源或无源滤波器绝对有)然后化简根据推导得到的表达式,对于 jwRC2 ,这一项,当 w 趋于无穷大时,uo/ui 趋于零。那么高频的临界点就是 wRC2 = 1+2C2/C1;(此时忽略低频项1/jwRC1)同理,对于低频项 1 /jwRC1, w 趋于无穷小时,uo/ui 趋于零 ,那么低频的临界点就是 1/wRC1 = 1+2C2/C1;然后解二元一次方程两个电容就被解出来了 这里提供一种更简单方法: 二阶带通滤波器的中心频率 f0 和品质因数 Q 可以用下面的公式计算:已知 R1 = R2 = 10kΩ,f0 = (1kHz + 30kHz) / 2 = 15.5kHz,Q = f0 / (30kHz - 1kHz) = 0.54,代入上面的公式,可以求得:这是一个二元一次方程组,可以用任意方法求解,例如消元法或代入法。为了方便起见,我们假设 C1 和 C2 的值相近,那么可以近似地认为 C1 = C2 = 3.45nF。这样就得到了两个电容的容值。当然,也可以选择其他的电容值,只要满足上面的方程组即可。
2023年04月23日
606 阅读
0 评论
5 点赞
【硬件算法进阶】Verilog实现802.3 CRC-32校验运算电路
循环冗余校验(Cyclic Redundancy Check,CRC)是通信中常用的差错检测编码方式,其基本工作原理是根据输入的信息位(信息码元),按照给定的生成多项式产生校验位(校验码元),并一起传送到接收端。在接收端,接收电路按照相同的规则对接收数据进行计算并生成本地的校验位,然后与收到的校验位进行对比,如果二者不同,则说明传输过程中发生了错误,否则说明传输是正确的。带有CRC校验结果的数据帧结构如表1-2所示。CRC检验位生成与检测工作包括以下基本步骤。图1-6是一个并行CRC-32校验运算电路。图中的d[7:0]是输入的用户数据,它是按照字节的方式输入的。load_ini是在对一个新的数据包开始校验计算之前对电路进行初始化的控制信号,经过初始化后,电路内部32比特寄存器的值改变为全1。calc是电路运算指示信号,在整个数据帧输入和CRC校验结果输出的过程中其都应该保持有效(高电平有效)。d_valid为1时表示当前输入的是需要进行校验运算的有效数据。crc[7:0]是电路输出的CRC校验运算结果,它是按照字节方式,在有效数据输入完成后开始输出的,一共有4个有效字节。crc_reg[31:0]是内部寄存器的值,具体使用时不需要该输出。并行计算的思想,输入数据S要并行输入到G(x)系数为1的支路中,输入数据从输入端按高到低逐bit输入,就可以实现。假如被除数是2位的数据S[1:0]=01,多项式是10011,x4 +x+1。在CRC校验里面,习惯省略最高位的1,多项式用0011表示。那么S除以0011的模二运算数字电路结构为:其中d1~ d4是寄存器输入;q1~q4是寄存器输出。寄存器需要赋初值,一般赋全1或全0。d1=S[1]^q4;d2= S[1]^ q1^q4;d3=q2;d4=q3。经过一次移位后:q1=d1= S[1]^q4;q2= d2= S[1]^ q1^q4;q3= d3=q2;q4= d4=q3。此时有:d1=S[0]^q3;d2= S[0]^ S[1]^ q4^q3;d3= S[1]^ q1^q4;d4= q2。令c[3:0]=,d[3:0]=,那么d就是最终的运算结果表达式,如下d[3]=c[1];d[2]= S[1]^ c[0]^c[3];d[1]= S[0]^ S[1]^ c[3]^ c[2];d[0]= S[0]^ c[2]。令c的初值为0,则01对0011的模二除法的余数为0011。再比如多项式为x5 +x3 +x+1,简记式为01011,其数字电路结构为:输入数据S要全部输入完,寄存器得到的结果才是最后的结果。同理可推导出其他多项式和输入数据的情况。对于循环检验,这里举个例子,如果数据是10bit*100个包,则每次输入10bit得到校验码后,该检验码为下次数据计算时寄存器D的初值,如此反复计算得到最后的检验码添加到整个数据后面即可,而不需要每个数据包后面都添加检验码。下面是以太网循环冗余校验电路的设计代码:module crc32_8023( clk, reset, d, load_init, calc, d_valid, crc_reg, crc ); input clk; input reset; input [7:0] d; input load_init; input calc; input d_valid; output reg [31:0] crc_reg; output reg [7:0] crc; wire [2:0] ctl; wire [31:0] next_crc; wire [31:0] i; assign i = crc_reg; assign ctl = ; always @(posedge clk or posedge reset) begin if(reset) crc_reg <= 32'hffffffff; else begin case (ctl) // 3'b000,3'b010: begin crc_reg <= crc_reg; crc <= crc;end 3'b001: begin crc_reg <= ; crc <= ~; //crc <= ~ crc_reg[16:23]; end 3'b011: begin crc_reg <= next_crc[31:0]; crc <= ~; //crc <= ~ next_crc[24:31]; end 3'b100,3'b110: begin crc_reg <= 32'hffffffff; crc <= crc; end 3'b101: begin crc_reg <= 32'hffffffff; crc <= ~; //crc <= ~ crc_reg[16:23]; end 3'b111: begin crc_reg <= 32'hffffffff; crc <= ~; //crc <= ~ next_crc[24:31]; end endcase end end assign next_crc[0] = d[7]^i[24]^d[1]^i[30]; //d+i=31 assign next_crc[1] = d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[30]^i[31]; assign next_crc[2] = d[5]^d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[26]^i[30]^i[31]; assign next_crc[3] = d[4]^d[5]^d[6]^d[0]^i[25]^i[26]^i[27]^i[31]; assign next_crc[4] = d[3]^d[4]^d[5]^d[7]^d[1]^i[24]^i[26]^i[27]^i[28]^i[30]; assign next_crc[5] = d[0]^d[1]^d[2]^d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[29]^i[30]^i[31]; assign next_crc[6] = d[0]^d[1]^d[2]^d[3]^d[5]^d[6]^i[25]^i[26]^i[28]^i[29]^i[30]^i[31]; assign next_crc[7] = d[0]^d[2]^d[4]^d[5]^d[7]^i[24]^i[26]^i[27]^i[29]^i[31]; assign next_crc[8] = d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[0]; //每项多出i[i],i=0、1、2...23 assign next_crc[9] = d[2]^d[3]^d[5]^d[6]^i[1]^i[25]^i[26]^i[28]^i[29]; assign next_crc[10] =d[2]^d[4]^d[5]^d[7]^i[2]^i[24]^i[26]^ i[27]^i[29]; assign next_crc[11] =i[3]^d[3]^i[28]^d[4]^i[27]^d[6]^i[25]^d[7]^i[24]; assign next_crc[12] =d[1]^d[2]^d[3]^d[5]^d[6]^d[7]^i[4]^i[24]^i[25]^i[26]^i[28]^i[29]^i[30]; assign next_crc[13] =d[0]^d[1]^d[2]^d[4]^d[5]^d[6]^i[5]^i[25]^i[26]^i[27]^i[29]^i[30]^i[31]; assign next_crc[14] =d[0]^d[1]^d[3]^d[4]^d[5]^i[6]^i[26]^i[27]^i[28]^i[30]^i[31]; assign next_crc[15] =d[0]^d[2]^d[3]^d[4]^i[7]^i[27]^i[28]^i[29]^i[31]; assign next_crc[16] =d[2]^d[3]^d[7]^i[8]^i[24]^i[28]^i[29]; assign next_crc[17] =d[1]^d[2]^d[6]^i[9]^i[25]^i[29]^i[30]; assign next_crc[18] =d[0]^d[1]^d[5]^i[10]^i[26]^i[30]^i[31]; assign next_crc[19] =d[0]^d[4]^i[11]^i[27]^i[31]; assign next_crc[20] =d[3]^i[12]^i[28]; assign next_crc[21] =d[2]^i[13]^i[29]; assign next_crc[22] =d[7]^i[14]^i[24]; assign next_crc[23] =d[1]^d[6]^d[7]^i[15]^i[24]^i[25]^i[30]; assign next_crc[24] =d[0]^d[5]^d[6]^i[16]^i[25]^i[26]^i[31]; assign next_crc[25] =d[4]^d[5]^i[17]^i[26]^i[27]; assign next_crc[26] =d[1]^d[3]^d[4]^d[7]^i[18]^i[28]^i[27]^i[24]^i[30]; assign next_crc[27] =d[0]^d[2]^d[3]^d[6]^i[19]^i[29]^i[28]^i[25]^i[31]; assign next_crc[28] =d[1]^d[2]^d[5]^i[20]^i[30]^i[29]^i[26]; assign next_crc[29] =d[0]^d[1]^d[4]^i[21]^i[31]^i[30]^i[27]; assign next_crc[30] =d[0]^d[3]^i[22]^i[31]^i[28]; assign next_crc[31] =d[2]^i[23]^i[29]; endmodule测试代码`timescale 1ns/1ns module crc_test(); reg clk, reset; reg [7:0] d; reg load_init; reg calc; reg data_valid; wire [31:0] crc_reg; wire [7:0] crc; initial begin clk=0; reset=0; load_init=0; calc=0; data_valid=0; d=0; end always begin #10 clk=1; #10 clk=0; end always begin crc_reset; crc_cal; end task crc_reset; begin reset=1; repeat(2)@(posedge clk); #5; reset=0; repeat(2)@(posedge clk); end endtask task crc_cal; begin repeat(5) @ (posedge clk); //通过losd_init=1 对CRC计算电路进行初始化 #5; load_init= 1; repeat(1)@ (posedge clk); //设置1oad_init=0,data_valid= 1,calc=1 //开始对输人数据进行CRC校验运算 #5; load_init= 0; data_valid=1; calc=1; d=8'haa; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hbb; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hcc; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hdd; repeat(1)@ (posedge clk); //设置load_init=0,data_valid=1,calc=0 //停止对数据进行CRC校验运算,开始输出 //计算结果 #5; data_valid=1; calc=0; d=8'haa; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hbb; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hee; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hdd; repeat(1)@ (posedge clk); #5; data_valid=0; repeat(10)@ (posedge clk); end endtask crc32_8023 my_crc_test(.clk(clk),.reset(reset),.d(d),.load_init(load_init),.calc(calc),.d_valid(data_valid),.crc_reg(crc_reg),.crc(crc)); endmodule图1-7是电路的仿真结果。图中①是电路进行CRC校验计算之前对电路进行初始化操作的过程,经过初始化之后,crc_reg内部数值为全1。②是对输入数据aa-> bb-> cc-> dd进行运算操作的过程,此时calc和data_valid均为1。③是输出计算结果的过程,CRC校验运算结果a7、01、b4和55先后被输出。在接收方向上,可以采用相同的电路进行校验检查,判断是否在传输过程中发生了差错。具体工作时,可以边接收用户数据边进行校验运算,当一个完整的MAC帧接收完成后(此时接收数据帧中的校验结果也参加了校验运算),如果当前校验电路的crc_reg值为0xC704DD7B(对于以太网中使用的CRC-32校验,无论原始数据是什么,正确接收时校验和都是此固定数值),说明没有发生错误,否则说明MAC帧有错。CRC-32校验值的作用是用于检测数据传输或存储中的错误。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成校验和并将其与发送的校验和进行比较。如果两者相等,则没有数据损坏。如果两者不相等,则说明数据在传输或存储过程中发生了改变,可能是由于噪声、干扰、故障或恶意篡改等原因造成的。CRC-32校验值可以有效地检测出数据中的随机错误,但是不能保证检测出所有的错误。例如,如果数据中有偶数个比特发生了翻转,那么CRC-32校验值可能不会改变,从而无法发现错误。因此,CRC-32校验值只能作为一种辅助的错误检测手段,不能完全依赖它来保证数据的正确性和完整性。相关工具如果不理解推导过程的话,可以由相关工具帮忙计算出结果和得到Verilog代码:CRC校验Verilog代码生成链接:http://outputlogic.com/?page_id=321CRC校验计算工具链接:http://www.ip33.com/crc.html,这个工具只能计算16bit为一个数据包的数据,如果数据包为10bit等之类的就不太适用在线计算器使用举例报文 : 1011001 (0x59)生成多项式 : g(x) = x^4 + x^3 + 1CRC : 1010 ( 0xa)CRC计算结果截图:参考文献Verilog HDL算法与电路设计-乔庐峰
2023年04月12日
906 阅读
0 评论
2 点赞
【IC/CPU设计】极简的RISC_CPU设计
CPU为SOC系统的核心环节,该项目来自于夏宇闻老师的经典教材——《Verilog 数字系统设计教程》,通过此练习方便数字ICer更好的入门本次项目实践环境:前仿: Modelsim综合: Design CompileCPU简介CPU(Central Processing Unit),中文全称中央处理器,作为四大U之首(CPU/GPU/TPU/NPU),是计算机系统的运算和控制核心,也是当今数字系统中不可或缺的组成部分。CPU自诞生到如今发展超过50年,借助冯诺依曼体系,CPU掀起一股又一股的科技浪潮。RISC作为精简了指令集的CPU,除了指令更加简洁,还拥有简单合理的内部结构,从而提高了运算速度。CPU工作的5个阶段:(1)取指(IF,Instruction Fetch),将指令从存储器取出到指令寄存器。每取一条指令,程序计数器自加一。(2)译指(ID,Instruction Decode),对取出的指令按照规定格式进行拆分和译码。(3)执行(EX,Execute),执行具体指令操作。(4)访问存储(MEM,Memory),根据指令访问存储、完成存储和读取。(5)写回(WB,Write Back),将计算结果写回到存储器。CPU内部关键结构:(1)算术逻辑运算器(ALU);(2)累加器;(3)程序计数器;(4)指令寄存器和译码器;(5)时序和控制部件。RISC_CPU内部结构和Verilog实现本项目中的RISC_CPU一共有9个模块组成,具体如下:(1)时钟发生器;(2)指令寄存器;(3)累加器;(4)算术逻辑运算单元;(5)数据控制器;(6)状态控制器;(7)主状态机;(8)程序计数器;(9)地址多路器。整体结构时钟发生器模块图:端口描述:reset是高电平复位信号;clk是外部时钟信号;fetch是控制信号,是clk的八分频信号;fetch为高电平时,触发执行指令以及地址多路器输出指令地址和数据地址。alu_ena是算术逻辑运算单元的使能信号。可以看到alu_ena提前fetch高电平一个clk周期,fetch是clk的8分频信号。Verilog代码:// Description: RISC——CPU 时钟发生器 // ----------------------------------------------------------------------------- module clk_gen ( input clk , // Clock input reset , // High level reset output reg fetch , // 8 frequency division output reg alu_ena // Arithmetic enable ); reg [7:0] state; //One-piece state machine parameter S1 = 8'b0000_0001, S2 = 8'b0000_0010, S3 = 8'b0000_0100, S4 = 8'b0000_1000, S5 = 8'b0001_0000, S6 = 8'b0010_0000, S7 = 8'b0100_0000, S8 = 8'b1000_0000, idle = 8'b0000_0000; always@(posedge clk)begin if(reset)begin fetch <= 0; alu_ena <= 0; state <= idle; end else begin case(state) S1: begin alu_ena <= 1; state <= S2; end S2: begin alu_ena <= 0; state <= S3; end S3: begin fetch <= 1; state <=S4; end S4: begin state <= S5; end S5: state <= S6; S6: state <= S7; S7: begin fetch <= 0; state <= S8; end S8: begin state <= S1; end idle: state <= S1; default: state <=idle; endcase end end endmodule指令寄存器模块图:端口描述:寄存器是将数据总线送来的指令存入高8位或低8位寄存器中。ena信号用来控制是否寄存。每条指令为两个字节,16位,高3位是操作码,低13位是地址(CPU地址总线为13位,寻址空间为8K字节)。本设计的数据总线为8位,每条指令需要取两次,先取高8位,再取低8位。Verilog代码:// Description: RISC—CPU 指令寄存器 // ----------------------------------------------------------------------------- module register ( input [7:0] data , input clk , input rst , input ena , output reg [15:0] opc_iraddr ); reg state ; // always@( posedge clk ) begin if( rst ) begin opc_iraddr <= 16'b 0000_0000_0000_0000; state <= 1'b 0; end // if rst // If load_ir from machine actived, load instruction data from rom in 2 clock periods. // Load high 8 bits first, and then low 8 bits. else if( ena ) begin case( state ) 1'b0 : begin opc_iraddr [ 15 : 8 ] <= data; state <= 1; end 1'b1 : begin opc_iraddr [ 7 : 0 ] <= data; state <= 0; end default : begin opc_iraddr [ 15 : 0 ] <= 16'bxxxx_xxxx_xxxx_xxxx; state <= 1'bx; end endcase // state end // else if ena else state <= 1'b0; end endmodule 累加器模块图:端口描述:累加器用于存放当前结果,ena信号有效时,在clk上升沿输出数据总线的数据。// Description: RISC-CPU 累加器模块 // ----------------------------------------------------------------------------- module accum ( input clk , // Clock input ena , // Enable input rst , // Asynchronous reset active high input [7:0] data , // Data bus output reg [7:0] accum ); always@(posedge clk)begin if(rst) accum <= 8'b0000_0000;//Reset else if(ena) accum <= data; end endmodule 算术运算器模块图:端口描述:算术逻辑运算单元可以根据输入的操作码分别实现相应的加、与、异或、跳转等基本操作运算。本单元支持8种操作运算。opcode用来选择计算模式data是数据输入accum是累加器输出alu_ena是模块使能信号clk是系统时钟Verilog代码:// Description: RISC-CPU 算术运算器 // ----------------------------------------------------------------------------- module alu ( input clk , // Clock input alu_ena , // Enable input [2:0] opcode , // High three bits are used as opcodes input [7:0] data , // data input [7:0] accum , // accum out output reg [7:0] alu_out , output zero ); parameter HLT = 3'b000 , SKZ = 3'b001 , ADD = 3'b010 , ANDD = 3'b011 , XORR = 3'b100 , LDA = 3'b101 , STO = 3'b110 , JMP = 3'b111 ; always @(posedge clk) begin if(alu_ena) begin casex(opcode)//操作码来自指令寄存器的输出 opc_iaddr(15..0)的第三位 HLT: alu_out <= accum ; SKZ: alu_out <= accum ; ADD: alu_out <= data + accum ; ANDD: alu_out <= data & accum ; XORR: alu_out <= data ^ accum ; LDA : alu_out <= data ; STO : alu_out <= accum ; JMP : alu_out <= accum ; default: alu_out <= 8'bxxxx_xxxx ; endcase end end assign zero = !accum; endmodule 数据控制器模块图:端口描述:数据控制器的作用是控制累加器的数据输出,数据总线是分时复用的,会根据当前状态传输指令或者数据。数据只在往RAM区或者端口写时才允许输出,否则呈现高阻态。in是8bit数据输入data_ena是使能信号data是8bit数据输出Verilog代码:// Description: RISC-CPU 数据控制器 // ----------------------------------------------------------------------------- module datactl ( input [7:0] in , // Data input input data_ena , // Data Enable output wire [7:0] data // Data output ); assign data = (data_ena )? in: 8'bzzzz_zzzz ; endmodule 地址多路器模块图:端口描述:用于选择输出地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM种读取指令,输出的是PC地址;后四个时钟周期用于对RAM或端口读写。地址多路器和数据控制器实现的功能十分相似。fetch信号用来控制地址输出,高电平输出pc_addr ,低电平输出ir_addr ;pc_addr 指令地址;ir_addr ram或端口地址。Verilog代码:// Description: RISC-CPU 地址多路器 // ----------------------------------------------------------------------------- module adr ( input fetch , // enable input [12:0] ir_addr , // input [12:0] pc_addr , // output wire [12:0] addr ); assign addr = fetch? pc_addr :ir_addr ; endmodule 程序计数器模块图:端口描述:程序计数器用来提供指令地址,指令按照地址顺序存放在存储器中。包含两种生成途径:(1)顺序执行的情况(2)需要改变顺序,例如JMP指令rst复位信号,高电平时地址清零;clock 时钟信号,系统时钟;ir_addr目标地址,当加载信号有效时输出此地址;pc_addr程序计数器地址load地址装载信号Verilog代码:// Description: RISC-CPU 程序计数器 // ----------------------------------------------------------------------------- module counter ( input [12:0] ir_addr , // program address input load , // Load up signal input clock , // CLock input rst , // Reset output reg [12:0] pc_addr // insert program address ); always@(posedge clock or posedge rst) begin if(rst) pc_addr <= 13'b0_0000_0000_0000; else if(load) pc_addr <= ir_addr; else pc_addr <= pc_addr + 1; end endmodule 状态控制器&主状态机模块图:(图左边)状态机端口描述:状态控制器接收复位信号rst,rst有效,控制输出ena为0,fetch有效控制ena为1。// Description: RISC-CPU 状态控制器 // ----------------------------------------------------------------------------- module machinectl ( input clk , // Clock input rst , // Asynchronous reset input fetch , // Asynchronous reset active low output reg ena // Enable ); always@(posedge clk)begin if(rst) ena <= 0; else if(fetch) ena <=1; end endmodule (图右边)主状态端口描述:主状态机是CPU的控制核心,用于产生一系列控制信号。指令周期由8个时钟周期组成,每个时钟周期都要完成固定的操作。(1)第0个时钟,CPU状态控制器的输出rd和load_ir 为高电平,其余为低电平。指令寄存器寄存由ROM送来的高8位指令代码。(2)第1个时钟,与上一个时钟相比只是inc_pc从0变为1,故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位指令代码。(3)第2个时钟,空操作。(4)第3个时钟,PC增1,指向下一条指令。操作符为HLT,输出信号HLT为高。操作符不为HLT,除PC增1外,其余控制线输出为0.(5)第4个时钟,操作。操作符为AND,ADD,XOR或LDA,读取相应地址的数据;操作符为JMP,将目的地址送给程序计数器;操作符为STO,输出累加器数据。(6)第5个时钟,若操作符为ANDD,ADD或者XORR,算术运算器完成相应的计算;操作符为LDA,就把数据通过算术运算器送给累加器;操作符为SKZ,先判断累加器的值是否为0,若为0,PC加1,否则保持原值;操作符为JMP,锁存目的地址;操作符为STO,将数据写入地址处。(7)第6个时钟,空操作。(8)第7个时钟,若操作符为SKZ且累加器为0,则PC值再加1,跳过一条指令,否则PC无变化。// Description: RISC-CPU 主状态机 // ----------------------------------------------------------------------------- module machine ( input clk , // Clock input ena , // Clock Enable input zero , // Asynchronous reset active low input [2:0] opcode , // OP code output reg inc_pc , // output reg load_acc , // output reg load_pc , // output reg rd , // output reg wr , // output reg load_ir , // output reg datactl_ena , // output reg halt ); reg [2:0] state ; //parameter parameter HLT = 3'b000 , SKZ = 3'b001 , ADD = 3'b010 , ANDD = 3'b011 , XORR = 3'b100 , LDA = 3'b101 , STO = 3'b110 , JMP = 3'b111 ; always@(negedge clk) begin if(!ena) //收到复位信号rst,进行复位操作 begin state <= 3'b000; <= 4'b0000; <= 4'b0000; end else ctl_cycle; end //------- task ctl_cycle ------- task ctl_cycle; begin casex(state) 3'b000: //load high 8bits in struction begin <= 4'b0001; <= 4'b0100; state <= 3'b001; end 3'b001://pc increased by one then load low 8bits instruction begin <= 4'b1001; <= 4'b0100; state <= 3'b010; end 3'b010: //idle begin <= 4'b0000; <= 4'b0000; state <= 3'b011; end 3'b011: //next instruction address setup 分析指令开始点 begin if(opcode == HLT)//指令为暂停HLT begin <= 4'b1000; <= 4'b0001; end else begin <= 4'b1000; <= 4'b0000; end state <= 3'b100; end 3'b100: //fetch oprand begin if(opcode == JMP) begin <= 4'b0010; <= 4'b0000; end else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA) begin <= 4'b0001; <= 4'b0000; end else if(opcode == STO) begin <= 4'b0000; <= 4'b0010; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b101; end 3'b101://operation begin if(opcode == ADD || opcode == ANDD ||opcode ==XORR ||opcode == LDA)//过一个时钟后与累加器的内存进行运算 begin <= 4'b0101; <= 4'b0000; end else if(opcode == SKZ && zero == 1)// & and && begin <= 4'b1000; <= 4'b0000; end else if(opcode == JMP) begin <= 4'b1010; <= 4'b0000; end else if(opcode == STO) begin//过一个时钟后吧wr变为1,写到RAM中 <= 4'b0000; <= 4'b1010; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b110; end 3'b110: begin if(opcode == STO) begin <= 4'b0000; <= 4'b0010; end else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA) begin <= 4'b0001; <= 4'b0000; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b111; end 3'b111: begin if(opcode == SKZ && zero == 1) begin <= 4'b1000; <= 4'b0000; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b000; end default: begin <= 4'b0000; <= 4'b0000; state <= 3'b000; end endcase end endtask endmodule 外围模块为了对RISC-CPU进行测试,需要对ROM、RAM和地址译码器进行设计。地址译码器模块说明:地址译码器用于产生选通信号,选通ROM或者RAM1FFFH —— 1800H RAM(范围):1_1xxx_xxxx_xxxx17FFH —— 0000H ROM(范围):0_xxxx_xxxx_xxxx+1_0xxx_xxxx_xxxxVerilog代码:// Description: RISC-CPU 地址译码器 // ----------------------------------------------------------------------------- module addr_decode ( input [12:0] addr , // Address output reg ram_sel , // Ram sel output reg rom_sel // Rom sel ); always@(addr)begin casex(addr) 13'b1_1xxx_xxxx_xxxx: <= 2'b01; 13'b0_xxxx_xxxx_xxxx: <= 2'b10; 13'b1_0xxx_xxxx_xxxx: <= 2'b10; default: <= 2'b00; endcase end endmodule RAM模块说明:RAM用于存放临时数据,可读可写。Verilog代码:// Description: RISC-CPU RAM模块 // ----------------------------------------------------------------------------- module ram ( input ena , // Enable input read , // read Enable input write , // write Enable inout wire [7:0] data , // data input [9:0] addr // address ); reg [7:0] ram [10'h3ff:0] ; assign data = (read && ena )? ram[addr]:8'h zz; always@(posedge write) begin ram[addr] <= data; end endmodule ROM模块说明:RAM用于存放只读数据。Verilog代码:// Description: RISC-CPU ROM模块 // ----------------------------------------------------------------------------- module rom ( input [12:0] addr , input read , input ena , output wire [7:0] data ); reg [7:0] memory [13'h1ff:0]; assign data = (read && ena)? memory[addr]:8'b zzzz_zzzz; endmodule 顶层模块模块图:Verilog代码:// Description: RISC-CPU 顶层模块 // ----------------------------------------------------------------------------- //`include "clk_gen.v" //`include "accum.v" //`include "adr.v" //`include "alu.v" //`include "machine.v" //`include "counter.v" //`include "machinectl.v" //`iclude "machine.v" //`include "register.v" //`include "datactl.v" module RISC_CPU ( input clk , input reset , output wire rd , output wire wr , output wire halt , output wire fetch , //addr output wire [12:0] addr , output wire [12:0] ir_addr , output wire [12:0] pc_addr , inout wire [7:0] data , //op output wire [2:0] opcode ); wire [7:0] alu_out ; wire [7:0] accum ; wire zero ; wire inc_pc ; wire load_acc ; wire load_pc ; wire load_ir ; wire data_ena ; wire contr_ena ; wire alu_ena ; //inst clk_gen mclk_gen( .clk (clk ), .reset (reset ), .fetch (fetch ), .alu_ena (alu_ena ) ); register m_register( .data (data ), .ena (load_ir ), .rst (reset ), .clk (clk ), .opc_iraddr ( ) ); accum m_accum( .data (alu_out ), .ena (load_acc ), .clk (clk ), .rst (reset ), .accum (accum ) ); alu m_alu( .data (data ), .accum (accum ), .clk (clk ), .alu_ena (alu_ena ), .opcode (opcode ), .alu_out (alu_out ), .zero (zero ) ); machinectl m_machinectl( .clk (clk ), .rst (reset ), .fetch (fetch ), .ena (contr_ena ) ); machine m_machine( .inc_pc (inc_pc ), .load_acc (load_acc ), .load_pc (load_pc ), .rd (rd ), .wr (wr ), .load_ir (load_ir ), .clk (clk ), .datactl_ena(data_ena ), .halt (halt ), .zero (zero ), .ena (contr_ena ), .opcode (opcode ) ); datactl m_datactl( .in (alu_out ), .data_ena (data_ena ), .data (data ) ); adr m_adr( .fetch (fetch ), .ir_addr (ir_addr ), .pc_addr (pc_addr ), .addr (addr ) ); counter m_counter( .clock (inc_pc ), .rst (reset ), .ir_addr (ir_addr ), .load (load_pc ), .pc_addr (pc_addr ) ); endmodule TestbenchTestbench包含三个测试程序,这个部分不能综合。Test1程序TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。若各条指令正确,应该在地址2E(hex)处,在执行HLT时刻停止。若程序在任何其他位置停止,则必有一条指令运行错误,可以按照注释找到错误的指令。test1汇编程序:(.pro文件/存放于ROM)//机器码-地址-汇编助记符-注释 @00 //address statement 111_0000 //00 BEGIN: JMP TST_JMP 0011_1100 000_0000 //02 HLT //JMP did not work 0000_0000 000_00000 //04 HLT //JMP did not load PC skiped 0000_0000 101_1100 //06 JMP_OK: LDA DATA 0000_0000 001_00000 //08 SKZ 0000_0000 000_0000 //0a HLT 0000_0000 101_11000 //0C LDA DATA_2 0000_0001 001_00000 //0E SKZ 0000_0000 111_0000 //10 JMP SKZ_OK 001_0100 000_0000 //12 HLT 0000_0000 110_11000 //14 SKZ_OK: STO TEMP 0000_0010 101_11000 //16 LDA DATA_1 0000_0000 110_11000 //18 STO TEMP 0000_0010 101_11000 //1A LDA TEMP 0000_0010 001_00000 //1C SKZ 0000_0000 000_00000 //1E HLT 0000_0000 100_11000 //20 XOR DATA_2 0000_0001 001_00000 //22 SKZ 0000_0000 111_00000 //24 JMP XOR_OK 0010_1000 000_00000 //26 HLT 0000_0000 100_11000 //28 XOR_OK XOR DATA_2 0000_0001 001_00000 //2A SKZ 0000_0000 000_00000 //2C HLT 0000_0000 000_0000 //2E END 0000_0000 111_00000 //30 JMP BEGIN 0000_0000 @3c 111_00000 //3c TST_JMP IMR OK 0000_0110 000_00000 //3E HLT test1数据文件:(.dat/存放于RAM)/----------------------------------- @00 ///address statement at RAM 00000000 //1800 DATA_1 11111111 //1801 DATA_2 10101010 //1082 TEMPTest2程序TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。这个程序是用来测试RISC-CPU的高级指令集,若执行正确,应在地址20(hex)处在执行HLT时停止。test2汇编程序:@00 101_11000 //00 BEGIN 0000_0001 011_11000 //02 AND DATA_3 0000_0010 100_11000 //04 XOR DATA_2 0000_0001 001_00000 //06 SKZ 0000_0000 000_00000 //08 HLT 0000_0000 010_11000 //0A ADD DATA_1 0000_0000 001_00000 //0C SKZ 0000_0000 111_00000 //0E JMP ADD_OK 0001_0010 111_00000 //10 HLT 0000_0000 100_11000 //12 ADD_OK XOR DATA_3 0000_0010 010_11000 //14 ADD DATA_1 0000_0000 110_11000 //16 STO TEMP 0000_0011 101_11000 //18 LDA DATA_1 0000_0000 010_11000 //1A ADD TEMP 0000_0001 001_00000 //1C SKZ 0000_0000 000_00000 //1E HLT 0000_0000 000_00000 //END HLT 0000_0000 111_00000 //JMP BEGIN 0000_0000test2数据文件:@00 00000001 //1800 DATA_1 10101010 //1801 DATA_2 11111111 //1802 DATA_3 00000000 //1803 TEMPTest3程序TEST3程序是一个计算0~144的斐波那契数列的程序,用来验证CPU整体功能。test3汇编程序:@00 101_11000 //00 LOOP:LDA FN2 0000_0001 110_11000 //02 STO TEMP 0000_0010 010_11000 //04 ADD FN1 0000_0000 110_11000 //06 STO FN2 0000_0001 101_11000 //08 VLDA TEMP 0000_0010 110_11000 //0A STO FN1 0000_0000 100_11000 //0C XOR LIMIT 0000_0011 001_00000 //0E SKZ 0000_0000 111_00000 //10 JMP LOOP 0000_0000 000_00000 //12 DONE HLT 0000_0000test3数据文件:@00 00000001 //1800 FN1 00000000 //1801 FN2 00000000 //1802 TEMP 10010000 //1803 LIMIT完整的testbenchVerilog代码:// Description: RISC-CPU 测试程序 // ----------------------------------------------------------------------------- `include "RISC_CPU.v" `include "ram.v" `include "rom.v" `include "addr_decode.v" `timescale 1ns/1ns `define PERIOD 100 // matches clk_gen.v module cputop_tb; reg [( 3 * 8 ): 0 ] mnemonic; // array that holds 3 8 bits ASCII characters reg [ 12 : 0 ] PC_addr, IR_addr; reg reset_req, clock; wire [ 12 : 0 ] ir_addr, pc_addr; // for post simulation. wire [ 12 : 0 ] addr; wire [ 7 : 0 ] data; wire [ 2 : 0 ] opcode; // for post simulation. wire fetch; // for post simulation. wire rd, wr, halt, ram_sel, rom_sel; integer test; //-----------------DIGITAL LOGIC---------------------- RISC_CPU t_cpu (.clk( clock ),.reset( reset_req ),.halt( halt ),.rd( rd ),.wr( wr ),.addr( addr ),.data( data ),.opcode( opcode ),.fetch( fetch ),.ir_addr( ir_addr ),.pc_addr( pc_addr )); ram t_ram (.addr ( addr [ 9 : 0 ]),.read ( rd ),.write ( wr ),.ena ( ram_sel ),.data ( data )); rom t_rom (.addr ( addr ),.read ( rd ), .ena ( rom_sel ),.data ( data )); addr_decode t_addr_decoder (.addr( addr ),.ram_sel( ram_sel ),.rom_sel( rom_sel )); //-------------------SIMULATION------------------------- initial begin clock = 0; // display time in nanoseconds $timeformat ( -9, 1, "ns", 12 ); display_debug_message; sys_reset; test1; $stop; test2; $stop; test3; $finish; // simulation is finished here. end // initial task display_debug_message; begin $display ("\n************************************************" ); $display ( "* THE FOLLOWING DEBUG TASK ARE AVAILABLE: *" ); $display ( "* \"test1;\" to load the 1st diagnostic program. *"); $display ( "* \"test2;\" to load the 2nd diagnostic program. *"); $display ( "* \"test3;\" to load the Fibonacci program. *"); $display ( "************************************************\n"); end endtask // display_debug_message task test1; begin test = 0; disable MONITOR; $readmemb ("test1.pro", t_rom.memory ); $display ("rom loaded successfully!"); $readmemb ("test1.dat", t_ram.ram ); $display ("ram loaded successfully!"); #1 test = 1; #14800; sys_reset; end endtask // test1 task test2; begin test = 0; disable MONITOR; $readmemb ("test2.pro", t_rom.memory ); $display ("rom loaded successfully!"); $readmemb ("test2.dat", t_ram.ram ); $display ("ram loaded successfully!"); #1 test = 2; #11600; sys_reset; end endtask // test2 task test3; begin test = 0; disable MONITOR; $readmemb ("test3.pro", t_rom.memory ); $display ("rom loaded successfully!"); $readmemb ("test3.dat", t_ram.ram ); $display ("ram loaded successfully!"); #1 test = 3; #94000; sys_reset; end endtask // test1 task sys_reset; begin reset_req = 0; #( `PERIOD * 0.7 ) reset_req = 1; #( 1.5 * `PERIOD ) reset_req = 0; end endtask // sys_reset //--------------------------MONITOR-------------------------------- always@( test ) begin: MONITOR case( test ) 1: begin // display results when running test 1 $display("\n*** RUNNING CPU test 1 - The Basic CPU Diagnostic Program ***"); $display("\n TIME PC INSTR ADDR DATA "); $display(" ------ ---- ------- ------ ------ "); while( test == 1 )@( t_cpu.pc_addr ) begin // fixed if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed #60 PC_addr <= t_cpu.pc_addr - 1; IR_addr <= t_cpu.ir_addr; #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1 end // while test == 1 @ t_cpu.pc_addr end 2: begin // display results when running test 2 $display("\n*** RUNNING CPU test 2 - The Basic CPU Diagnostic Program ***"); $display("\n TIME PC INSTR ADDR DATA "); $display(" ------ ---- ------- ------ ------ "); while( test == 2 )@( t_cpu.pc_addr ) begin // fixed if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed #60 PC_addr <= t_cpu.pc_addr - 1; IR_addr <= t_cpu.ir_addr; #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1 end // while test == 2 @ t_cpu.pc_addr end 3: begin // display results when running test 3 $display("\n*** RUNNING CPU test 3 - An Executable Program **************"); $display("***** This program should calculate the fibonacci *************"); $display("\n TIME FIBONACCI NUMBER "); $display(" ------ -----------------_ "); while( test == 3 ) begin wait( t_cpu.opcode == 3'h 1 ) // display Fib. No. at end of program loop $strobe("%t %d", $time, t_ram.ram [ 10'h 2 ]); wait( t_cpu.opcode != 3'h 1 ); end // while test == 3 end endcase // test end // MONITOR: always@ test //-------------------------HALT------------------------------- always@( posedge halt ) begin // STOP when HALT intruction decoded #500 $display("\n******************************************"); $display( "** A HALT INSTRUCTION WAS PROCESSED !!! **"); $display( "******************************************"); end // always@ posedge halt //-----------------------CLOCK & MNEMONIC------------------------- always#(`PERIOD / 2 ) clock = ~ clock; always@( t_cpu.opcode ) begin // get an ASCII mnemonic for each opcode case( t_cpu.opcode ) 3'b 000 : mnemonic = "HLT"; 3'b 001 : mnemonic = "SKZ"; 3'b 010 : mnemonic = "ADD"; 3'b 011 : mnemonic = "AND"; 3'b 100 : mnemonic = "XOR"; 3'b 101 : mnemonic = "LDA"; 3'b 110 : mnemonic = "STO"; 3'b 111 : mnemonic = "JMP"; default : mnemonic = "???"; endcase end endmodule $ readmemb ( "test1. pro" ,t_ rom. . memory ); $ readmemb ( "testl. dat",t_ ram_ . ram); 即可把编译好的汇编机器码装人虚拟ROM,把需要参加运算的数据装人虚拟RAM就可以开始仿真。上面语句中的第一项为打开的文件名,后一项为系统层次管理下的ROM模块和RAM模块中的存储器memory和ram。源代码&脚本隐藏内容,请前往内页查看详情前仿真结果test1 test2 test3 DC后仿真采用SMIC180工艺在典型环境下进行测试 时序报告: 面积报告: 功耗报告: 综合电路图: 总结该项目更加偏向于教学练习,CPU也是数字IC的重要研究方向,对此感兴趣的同学可以找点论文和开源资料进行学习。可以进一步优化如流水线、运算单元,扩展成SOC系统等。
2023年04月08日
571 阅读
5 评论
5 点赞
2023-04-06
【硬件算法】Verilog之FPGA实现信号移相法
实现信号移相可以用FPGA控制信号0-360度连续可调,对于高频信号(1GHZ以上)超出了FPGA工作频率还有一种办法是FPGA-》DA-》射频前端-》移相器-》阻抗匹配-》天线。本案例直接采用FPGA对数字中频信号处理(kHZ、MHZ)本质是边沿检测与分频现象:代码:/* Function : Phase Shift Interface : clk_fre---unit(MHZ) din_fre---unit(KHz) phase_angle---unit(0-360 Angle) Date: 2023/04/05 Description: Phase shift is carried out on the input square wave. The phase Angle unit is Angle (0-360), or the input can be greater than 360. The system clock unit is MHz and the input signal clock is KHz. */ module PhaseShift( input clk, //clk input rst_n, //rest input [7:0] clk_fre, //system clock frequency,MHZ input [15:0] din_fre, //input signal clock frequency,KHZ input [8:0] phase_angle, //phase shift angle input din, //input signal output reg dout //output signal ); reg [31:0] posedge_counter; reg [31:0] negedge_counter; reg [31:0] delay_counter; reg in_posedge_flg; reg in_negedge_flg; reg out_posedge_flg; reg out_negedge_flg; reg old_din; reg init_data=1'b1; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin posedge_counter <= 1'b0; negedge_counter <= 1'b0; in_posedge_flg <= 1'b0; in_negedge_flg <= 1'b0; out_posedge_flg <= 1'b0; out_negedge_flg <= 1'b0; end else begin if(~old_din & din) in_posedge_flg = 1'b1; if(old_din & ~din) in_negedge_flg = 1'b1; old_din <= din; if(init_data) begin delay_counter <= ((1000000000)/din_fre*clk_fre)/360*(phase_angle%360)/1000000; dout <= din ; init_data <= 1'b0; end if(in_posedge_flg && posedge_counter <= delay_counter) begin posedge_counter <= posedge_counter + 1'b1 ; out_posedge_flg <= 1'b0; end else begin posedge_counter <= 1'b0 ; in_posedge_flg <= 1'b0 ; if(~out_posedge_flg) begin dout <= 1'b1 ; out_posedge_flg <= 1'b1 ; end end if (in_negedge_flg && negedge_counter <= delay_counter) begin negedge_counter <= negedge_counter + 1'b1 ; out_negedge_flg <= 1'b0 ; end else begin negedge_counter <= 1'b0 ; in_negedge_flg <= 1'b0 ; if(~out_negedge_flg) begin dout <= 1'b0 ; out_negedge_flg <= 1'b1; end end end end endmodule测试代码:`timescale 100ps/10ps // module test_PhaseShift; reg clk; reg rst_n; reg [7:0] clk_fre; //system clock frequency,MHZ reg [15:0] din_fre ; //input signal clock frequency,KHZ reg [8:0] phase_angle; //phase shift angle reg din ; //input signal wire dout ; //output signal initial begin clk = 0; din = 0; din_fre = 4000; phase_angle = 90; clk_fre = 100; rst_n = 1; #12 rst_n = 0; #4 rst_n = 1; #10000 $stop; //end end always #5 clk = ~clk; always #125 din = ~din; PhaseShift u1( .clk(clk), .rst_n(rst_n), .clk_fre(clk_fre), .din_fre(din_fre), .phase_angle(phase_angle), .din(din), .dout(dout) ); endmodule
2023年04月06日
567 阅读
0 评论
2 点赞
使用Verilog实现与仿真AMBA--AHB总线协议(三)
1、AHB从机AHB从机应答来自系统主主机发起的传输。从机使用从译码器输出的 HSELx 信号来决定它什么时候作应答。其它传输需要的信号,如地址与控制信息由主机产生。下图为一个AHB从机的接口框图:下面是一个AHB接口的SRAM控制器的程序://------------------------------------------------------------------------------ // File : ahb_sram.v // Editor : VSCode, Tab Size(4) // Description : Top Module of AHB SRAM Controller. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module ahb_sram #( parameter SYNC_RESET = 1, parameter AHB_DWIDTH = 32, parameter AHB_AWIDTH = 32, parameter SIZE_IN_BYTES = 2048, parameter ADD_WIDTH = $clog2(SIZE_IN_BYTES) ) ( //---------------------------------- // IO Declarations //---------------------------------- // Inputs input HCLK, input HRESETN, input HSEL, input HREADYIN, input [1:0] HTRANS, input [2:0] HBURST, input [2:0] HSIZE, input [AHB_DWIDTH-1:0] HWDATA, input [AHB_AWIDTH-1:0] HADDR, input HWRITE, // Outputs output [AHB_DWIDTH-1:0] HRDATA, output [1:0] HRESP, output HREADYOUT ); //---------------------------------- // Variable Declarations //---------------------------------- wire [ADD_WIDTH-1:0] HADDR_cal; wire [2:0] ahbsram_size; wire [ADD_WIDTH-1:0] ahbsram_addr; wire [31:0] ahbsram_wdata; wire ahbsram_write; wire [31:0] sramahb_rdata; wire sramahb_ack; //---------------------------------- // Start of Main Code //---------------------------------- assign HADDR_cal = HADDR[ADD_WIDTH-1:0]; // Instantiations ahb_sram_if #( .AHB_DWIDTH (AHB_DWIDTH), .AHB_AWIDTH (AHB_AWIDTH), .ADD_WIDTH (ADD_WIDTH), .SYNC_RESET (SYNC_RESET) ) u_ahb_sram_if ( .HCLK (HCLK), .HRESETN (HRESETN), .HSEL (HSEL), .HTRANS (HTRANS), .HBURST (HBURST), .HWRITE (HWRITE), .HWDATA (HWDATA), .HSIZE (HSIZE), .HADDR (HADDR_cal), .HREADYIN (HREADYIN), // From SRAM Control signals .sramahb_ack (sramahb_ack), .sramahb_rdata (sramahb_rdata), // Outputs .HREADYOUT (HREADYOUT), .HRESP (HRESP), // To SRAM Control signals .ahbsram_req (ahbsram_req), .ahbsram_write (ahbsram_write), .ahbsram_wdata (ahbsram_wdata), .ahbsram_size (ahbsram_size), .ahbsram_addr (ahbsram_addr), .HRDATA (HRDATA) ); sram_ctrl_if #( .ADD_WIDTH (ADD_WIDTH), .SYNC_RESET (SYNC_RESET) ) u_sram_ctrl_if ( .HCLK (HCLK), .HRESETN (HRESETN), // From AHB Interface signals .ahbsram_req (ahbsram_req), .ahbsram_write (ahbsram_write), .ahbsram_wdata (ahbsram_wdata), .ahbsram_size (ahbsram_size), .ahbsram_addr (ahbsram_addr), // Outputs // To AHB Interface signals .sramahb_ack (sramahb_ack), .sramahb_rdata (sramahb_rdata) ); endmodule //------------------------------------------------------------------------------ // File : ahb_sram_if.v // Editor : VSCode, Tab Size(4) // Description : // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module ahb_sram_if #( parameter AHB_DWIDTH = 32, parameter AHB_AWIDTH = 32, parameter ADD_WIDTH = 11, parameter SYNC_RESET = 0 ) ( //---------------------------------- // IO Declarations //---------------------------------- // Inputs input HCLK, input HRESETN, input HSEL, input HREADYIN, input [1:0] HTRANS, input [2:0] HBURST, input [2:0] HSIZE, input [ADD_WIDTH-1:0] HADDR, input [AHB_DWIDTH-1:0] HWDATA, input HWRITE, input sramahb_ack, input [AHB_DWIDTH-1:0] sramahb_rdata, // Outputs output HREADYOUT, output [1:0] HRESP, output reg [AHB_DWIDTH-1:0] HRDATA, output ahbsram_req, output ahbsram_write, output [AHB_AWIDTH-1:0] ahbsram_wdata, output [2:0] ahbsram_size, output [ADD_WIDTH-1:0] ahbsram_addr ); //---------------------------------- // Local Parameter Declarations //---------------------------------- // State Machine parameters localparam IDLE = 2'b00; localparam AHB_WR = 2'b01; localparam AHB_RD = 2'b10; parameter RESP_OKAY = 2'b00; parameter RESP_ERROR = 2'b01; // AHB HTRANS definition parameter TRN_IDLE = 2'b00; parameter TRN_BUSY = 2'b01; parameter TRN_SEQ = 2'b11; parameter TRN_NONSEQ = 2'b10; parameter SINGLE = 3'b000; parameter INCR = 3'b001; parameter WRAP4 = 3'b010; parameter INCR4 = 3'b011; parameter WRAP8 = 3'b100; parameter INCR8 = 3'b101; parameter WRAP16 = 3'b110; parameter INCR16 = 3'b111; //---------------------------------- // Variable Declarations //---------------------------------- reg [1:0] HTRANS_d; reg [2:0] HBURST_d; reg [2:0] HSIZE_d; reg [ADD_WIDTH-1:0] HADDR_d; reg [AHB_DWIDTH-1:0] HWDATA_d; reg HWRITE_d; reg HSEL_d; reg HREADYIN_d; reg [1:0] ahbcurr_state; reg [1:0] ahbnext_state; reg latchahbcmd; reg ahbsram_req_int; reg ahbsram_req_d1; reg [AHB_DWIDTH-1:0] HWDATA_cal; reg [4:0] burst_count; reg [4:0] burst_count_reg; reg [4:0] count; wire aresetn; wire sresetn; //---------------------------------- // Start of Main Code //---------------------------------- assign aresetn = (SYNC_RESET==1) ? 1'b1 : HRESETN; assign sresetn = (SYNC_RESET==1) ? HRESETN : 1'b1; // Generation of valid AHB Command which triggers the AHB Slave State Machine assign validahbcmd = HREADYIN & HSEL & (HTRANS == TRN_NONSEQ); // Generation of HRESP assign HRESP = RESP_OKAY; always @(*) begin HWDATA_cal = HWDATA; end // Latch all the AHB signals always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin HADDR_d <= }; HWDATA_d <= }; HTRANS_d <= 2'b00; HSIZE_d <= 2'b00; HBURST_d <= 3'b000; HWRITE_d <= 1'b0; HSEL_d <= 1'b0; HREADYIN_d <= 1'b0; end else if (HREADYIN == 1'b1 & HSEL == 1'b1 & HREADYOUT == 1'b1) begin HADDR_d <= HADDR; HTRANS_d <= HTRANS; HSIZE_d <= HSIZE; HBURST_d <= HBURST; HWRITE_d <= HWRITE; HWDATA_d <= HWDATA_cal; HSEL_d <= HSEL; HREADYIN_d <= HREADYIN; end end // Current State generation always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin ahbcurr_state <= IDLE; end else begin ahbcurr_state <= ahbnext_state; end end // Next State and output decoder logic always @(*) begin latchahbcmd = 1'b0; ahbsram_req_int = 1'b0; ahbnext_state = ahbcurr_state; case (ahbcurr_state) IDLE : begin if (HREADYIN == 1'b1 && HSEL == 1'b1 && ((HTRANS == TRN_NONSEQ) || HTRANS == TRN_SEQ)) begin latchahbcmd = 1'b1; if (HWRITE == 1'b1) begin ahbnext_state = AHB_WR; end else begin ahbnext_state = AHB_RD; end end else begin ahbnext_state = IDLE; end end AHB_WR : begin latchahbcmd = 1'b0; ahbsram_req_int = 1'b1; if (sramahb_ack == 1'b1) begin if (count == burst_count_reg) begin ahbnext_state = IDLE; end else begin ahbsram_req_int = 1'b0; end end end AHB_RD : begin latchahbcmd = 1'b0; ahbsram_req_int = 1'b1; if (sramahb_ack == 1'b1) begin ahbnext_state = IDLE; end end default : begin ahbnext_state = IDLE; end endcase end // LOGIC FOR BURST COUNT always @(*) begin burst_count = burst_count_reg; if (HSEL == 1'b1 && HTRANS == TRN_NONSEQ && HREADYIN == 1'b1 && HREADYOUT == 1'b1) begin case (HBURST) SINGLE : burst_count = 5'b00001; WRAP4,INCR4 : burst_count = 5'b00100; WRAP8,INCR8 : burst_count = 5'b01000; WRAP16,INCR16 : burst_count = 5'b10000; default : burst_count = 4'b0001; endcase end end always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin burst_count_reg <= 'h0; end else begin burst_count_reg <= burst_count; end end always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin count <= 5'h0; end else begin if (count == burst_count_reg) begin count <= 5'h0; end else if (ahbsram_req == 1'b1) begin count <= count + 1'b1; end else begin count <= count; end end end assign HREADYOUT = !ahbsram_req_int; // Generation of signals required for SRAM assign ahbsram_write = ahbsram_req ? HWRITE_d : 1'b0; assign ahbsram_wdata = HWDATA; assign ahbsram_addr = ahbsram_req ? HADDR_d : HADDR_d; assign ahbsram_size = ahbsram_req ? HSIZE_d : HSIZE_d; always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin ahbsram_req_d1 <= 1'b0; end else begin ahbsram_req_d1 <= ahbsram_req_int; end end // Generate the request to the SRAM contol logic when there is AHB read or write request assign ahbsram_req = ahbsram_req_int & !ahbsram_req_d1; // HRDATA generation always @(*) begin if (HREADYOUT && HREADYIN) begin HRDATA = sramahb_rdata; end else begin HRDATA = sramahb_rdata; end end endmodule //------------------------------------------------------------------------------ // File : sram_ctrl_if.v // Editor : VSCode, Tab Size(4) // Description : // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module sram_ctrl_if #( parameter AHB_DWIDTH = 32, parameter ADD_WIDTH = 11, parameter SYNC_RESET = 0 ) ( //---------------------------------- // IO Declarations //---------------------------------- // Inputs input HCLK, input HRESETN, input ahbsram_req, input ahbsram_write, input [2:0] ahbsram_size, input [ADD_WIDTH-1:0] ahbsram_addr, input [AHB_DWIDTH-1:0] ahbsram_wdata, // Outputs output sramahb_ack, output reg [AHB_DWIDTH-1: 0] sramahb_rdata ); //---------------------------------- // Local Parameter Declarations //---------------------------------- // State Machine parameters localparam S_IDLE = 2'b00; localparam S_WR = 2'b01; localparam S_RD = 2'b10; //---------------------------------- // Variable Declarations //---------------------------------- reg [3:0] sram_wen_mem; reg [1:0] sramcurr_state; reg [1:0] sramnext_state; reg sram_wen; reg sram_ren; reg sramahb_ack_int; reg sram_ren_d; reg sram_done; wire [AHB_DWIDTH-1:0] ram_rdata; wire aresetn; wire sresetn; //---------------------------------- // Start of Main Code //---------------------------------- assign aresetn = (SYNC_RESET == 1) ? 1'b1 : HRESETN; assign sresetn = (SYNC_RESET == 1) ? HRESETN : 1'b1; // Current State generation always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sramcurr_state <= S_IDLE; end else begin sramcurr_state <= sramnext_state; end end // Next State and output decoder logic always @(*) begin sramahb_ack_int = 1'b0; sram_wen = 1'b0; sram_ren = 1'b0; sramnext_state = sramcurr_state; case (sramcurr_state) S_IDLE : begin if (ahbsram_req == 1'b1) begin if (ahbsram_write == 1'b1) begin sramnext_state = S_WR; sram_wen = 1'b1; end else begin sram_ren = 1'b1; sramnext_state = S_RD; end end end S_WR : begin if (sram_done == 1'b1) begin sramnext_state = S_IDLE; sramahb_ack_int = 1'b1; end end S_RD : begin if (sram_done == 1'b1) begin sramnext_state = S_IDLE; sramahb_ack_int = 1'b1; end end default : begin sramnext_state = S_IDLE; end endcase end always @(*) begin sram_wen_mem = 4'b0000; if (ahbsram_size == 3'b010) begin sram_wen_mem = }; end else if (ahbsram_size == 3'b001) begin case (ahbsram_addr[1]) 1'b0 : begin sram_wen_mem[0] = sram_wen; sram_wen_mem[1] = sram_wen; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = 1'b0; end 1'b1 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = sram_wen; sram_wen_mem[3] = sram_wen; end endcase end else if (ahbsram_size == 3'b000) begin case (ahbsram_addr[1:0]) 2'b00 : begin sram_wen_mem[0] = sram_wen; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = 1'b0; end 2'b01 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = sram_wen; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = 1'b0; end 2'b10 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = sram_wen; sram_wen_mem[3] = 1'b0; end 2'b11 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = sram_wen; end endcase end else begin sram_wen_mem = }; end end // SRAM Instantiations sram_model #( .SYNC_RESET (SYNC_RESET), .ADDR_WIDTH (ADD_WIDTH) ) u_sram_model ( .writedata (ahbsram_wdata), .readdata (ram_rdata[31:0]), .wren (sram_wen_mem), .rden (sram_ren), .writeaddr (ahbsram_addr[ADD_WIDTH-1:2]), .readaddr (ahbsram_addr[ADD_WIDTH-1:2]), .clk (HCLK), .resetn (HRESETN) ); always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sramahb_rdata <= 32'h0; end else if (sram_ren_d == 1'b1) begin sramahb_rdata <= ram_rdata; end else begin sramahb_rdata <= sramahb_rdata; end end always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sram_ren_d <= 32'h0; end else begin sram_ren_d <= sram_ren; end end // Generate the SRAM done when the SRAM wren/rden is done always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sram_done <= 1'b0; end else if (sram_wen || sram_ren) begin sram_done <= 1'b1; end else begin sram_done <= 1'b0; end end // Generate the SRAM ack assign sramahb_ack = sramahb_ack_int; endmodule //------------------------------------------------------------------------------ // File : sram_model.v // Editor : GVIM, Tab Size(4) // Description : FPGA Block Ram/Onchip SRAM. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module sram_model #( parameter SYNC_RESET = 0, parameter ADDR_WIDTH = 16 ) ( //---------------------------------- // IO Declarations //---------------------------------- input [31:0] writedata, output [31:0] readdata, input [3:0] wren, input rden, input [ADDR_WIDTH-1:2] writeaddr, input [ADDR_WIDTH-1:2] readaddr, input clk, input resetn ); //---------------------------------- //--Local Parameter Declarations //---------------------------------- localparam AWT = ((1<<(ADDR_WIDTH-2))-1); //---------------------------------- // Variable Declarations //---------------------------------- reg [7:0] bram0[AWT:0]; reg [7:0] bram1[AWT:0]; reg [7:0] bram2[AWT:0]; reg [7:0] bram3[AWT:0]; reg [ADDR_WIDTH-3:0] addr_q1; reg rden_r = 1'b0; wire [31:0] readdata_i; wire aresetn; wire sresetn; //---------------------------------- // Start of Main Code //---------------------------------- assign aresetn = (SYNC_RESET == 1) ? 1'b1 : resetn; assign sresetn = (SYNC_RESET == 1) ? resetn : 1'b1; always @(posedge clk) begin rden_r <= rden; end // Infer Block RAM always @(posedge clk) begin if (wren[0]) bram0[writeaddr] <= writedata[7:0]; if (wren[1]) bram1[writeaddr] <= writedata[15:8]; if (wren[2]) bram2[writeaddr] <= writedata[23:16]; if (wren[3]) bram3[writeaddr] <= writedata[31:24]; end always @(posedge clk) begin addr_q1 <= readaddr[ADDR_WIDTH-1:2]; end assign readdata_i = ; assign readdata = rden_r ? readdata_i : }; endmodule下面是测试用例代码://------------------------------------------------------------------------------ // File : top_tb.v // Editor : VSCode, Tab Size(4) // Description : Testbench of AHB SRAM Controller. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module top_tb(); //---------------------------------- // Local Parameter Declarations //---------------------------------- localparam AHB_CLK_PERIOD = 5; // Assuming AHB CLK to be 100MHz localparam SIZE_IN_BYTES =2048; //---------------------------------- // Variable Declarations //---------------------------------- reg HCLK = 0; wire HWRITE; wire [1:0] HTRANS; wire [2:0] HSIZE; wire [2:0] HBURST; wire HREADYIN; wire [31:0] HADDR; wire [31:0] HWDATA; wire HREADYOUT; wire [1:0] HRESP; wire [31:0] HRDATA; reg HRESETn; //---------------------------------- // Start of Main Code //---------------------------------- //----------------------------------------------------------------------- // Generate HCLK //----------------------------------------------------------------------- always #AHB_CLK_PERIOD HCLK <= ~HCLK; //----------------------------------------------------------------------- // Generate HRESETn //----------------------------------------------------------------------- initial begin HRESETn = 1'b0; repeat(5) @(posedge HCLK); HRESETn = 1'b1; end ahb_master #( .START_ADDR (32'h0), .DEPTH_IN_BYTES (SIZE_IN_BYTES) ) u_ahb_master ( .HRESETn (HRESETn), .HCLK (HCLK), .HADDR (HADDR), .HTRANS (HTRANS), .HWRITE (HWRITE), .HSIZE (HSIZE), .HBURST (HBURST), .HWDATA (HWDATA), .HRDATA (HRDATA), .HRESP (HRESP), .HREADY (HREADYOUT) ); ahb_sram # ( .AHB_AWIDTH (32), .AHB_DWIDTH (32), .SIZE_IN_BYTES (SIZE_IN_BYTES), .SYNC_RESET (1) ) u_ahb_sram ( .HCLK (HCLK), .HRESETN (HRESETn), .HSEL (1'b1), .HWRITE (HWRITE), .HADDR (HADDR), .HWDATA (HWDATA), .HRDATA (HRDATA), .HSIZE (HSIZE), .HTRANS (HTRANS), .HBURST (HBURST), .HRESP (HRESP), .HREADYIN (1'b1), .HREADYOUT (HREADYOUT) ); `ifdef VCS initial begin $fsdbDumpfile("top_tb.fsdb"); $fsdbDumpvars; end initial begin `ifdef DUMP_VPD $vcdpluson(); `endif end `endif endmodule这个模块主要是产生了时钟和复位信号,例化了待测模块,以及一个AHB Master的功能模型。具体见下面的代码://------------------------------------------------------------------------------ // File : ahb_master.v // Editor : VSCode, Tab Size(4) // Description : AHB Master Simulation Model. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps `define SINGLE_TEST `define BURST_TEST module ahb_master #( //---------------------------------- // Paramter Declarations //---------------------------------- parameter START_ADDR = 0, parameter DEPTH_IN_BYTES = 32'h100, parameter END_ADDR = START_ADDR+DEPTH_IN_BYTES-1 ) ( //---------------------------------- // IO Declarations //---------------------------------- input wire HRESETn, input wire HCLK, output reg [31:0] HADDR, output reg [1:0] HTRANS, output reg HWRITE, output reg [2:0] HSIZE, output reg [2:0] HBURST, output reg [31:0] HWDATA, input wire [31:0] HRDATA, input wire [1:0] HRESP, input wire HREADY ); //---------------------------------- // Variable Declarations //---------------------------------- reg [31:0] data_burst[0:1023]; //---------------------------------- // Start of Main Code //---------------------------------- initial begin HADDR = 0; HTRANS = 0; HWRITE = 0; HSIZE = 0; HBURST = 0; HWDATA = 0; while(HRESETn === 1'bx) @(posedge HCLK); while(HRESETn === 1'b1) @(posedge HCLK); while(HRESETn === 1'b0) @(posedge HCLK); `ifdef SINGLE_TEST repeat(3) @(posedge HCLK); memory_test(START_ADDR, END_ADDR, 1); memory_test(START_ADDR, END_ADDR, 2); memory_test(START_ADDR, END_ADDR, 4); `endif `ifdef BURST_TEST repeat(5) @(posedge HCLK); memory_test_burst(START_ADDR, END_ADDR, 1); memory_test_burst(START_ADDR, END_ADDR, 2); memory_test_burst(START_ADDR, END_ADDR, 4); memory_test_burst(START_ADDR, END_ADDR, 6); memory_test_burst(START_ADDR, END_ADDR, 8); memory_test_burst(START_ADDR, END_ADDR, 10); memory_test_burst(START_ADDR, END_ADDR, 16); memory_test_burst(START_ADDR, END_ADDR, 32); memory_test_burst(START_ADDR, END_ADDR, 64); memory_test_burst(START_ADDR, END_ADDR, 128); memory_test_burst(START_ADDR, END_ADDR, 255); repeat(5) @(posedge HCLK); `endif $finish(2); end //----------------------------------------------------------------------- // Single transfer test //----------------------------------------------------------------------- task memory_test; input [31:0] start; // start address input [31:0] finish; // end address input [2:0] size; // data size: 1, 2, 4 integer i; integer error; reg [31:0] data; reg [31:0] gen; reg [31:0] got; reg [31:0] reposit[START_ADDR:END_ADDR]; begin $display("%m: read-after-write test with %d-byte access", size); error = 0; gen = $random(7); for (i = start; i < (finish-size+1); i = i + size) begin gen = $random & ~32'b0; data = align(i, gen, size); ahb_write(i, size, data); ahb_read(i, size, got); got = align(i, got, size); if (got !== data) begin $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data); error = error + 1; end end if (error == 0) $display("[%10d] %m OK: from %x to %x", $time, start, finish); $display("%m read-all-after-write-all with %d-byte access", size); error = 0; gen = $random(1); for (i = start; i < (finish-size+1); i = i + size) begin gen = & ~32'b0; data = align(i, gen, size); reposit[i] = data; ahb_write(i, size, data); end for (i = start; i < (finish-size+1); i = i + size) begin data = reposit[i]; ahb_read(i, size, got); got = align(i, got, size); if (got !== data) begin $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data); error = error + 1; end end if (error == 0) $display("[%10d] %m OK: from %x to %x", $time, start, finish); end endtask //----------------------------------------------------------------------- // Burst transfer test //----------------------------------------------------------------------- task memory_test_burst; input [31:0] start; // start address input [31:0] finish; // end address input [7:0] leng; // burst length integer i; integer j; integer k; integer r; integer error; reg [31:0] data; reg [31:0] gen; reg [31:0] got; reg [31:0] reposit[0:1023]; integer seed; begin $display("%m: read-all-after-write-all burst test with %d-beat access", leng); error = 0; seed = 111; gen = $random(seed); k = 0; if (finish > (start+leng*4)) begin for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin for (j = 0; j < leng; j = j + 1) begin data_burst[j] = $random; reposit[j+k*leng] = data_burst[j]; end @(posedge HCLK); ahb_write_burst(i, leng); k = k + 1; end gen = $random(seed); k = 0; for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin @(posedge HCLK); ahb_read_burst(i, leng); for (j = 0; j < leng; j = j + 1) begin if (data_burst[j] != reposit[j+k*leng]) begin error = error+1; $display("%m A=%hh D=%hh, but %hh expected",i+j*leng, data_burst[j], reposit[j+k*leng]); end end k = k + 1; r = $random & 8'h0F; repeat(r) @(posedge HCLK); end if (error == 0) $display("%m %d-length burst read-after-write OK: from %hh to %hh",leng, start, finish); end else begin $display("%m %d-length burst read-after-write from %hh to %hh ???",leng, start, finish); end end endtask //----------------------------------------------------------------------- // As AMBA AHB bus uses non-justified data bus scheme, data should be // aligned according to the address. //----------------------------------------------------------------------- function [31:0] align; input [ 1:0] addr; input [31:0] data; input [ 2:0] size; // num of bytes begin `ifdef BIG_ENDIAN case (size) 1 : case (addr[1:0]) 0 : align = data & 32'hFF00_0000; 1 : align = data & 32'h00FF_0000; 2 : align = data & 32'h0000_FF00; 3 : align = data & 32'h0000_00FF; endcase 2 : case (addr[1]) 0 : align = data & 32'hFFFF_0000; 1 : align = data & 32'h0000_FFFF; endcase 4 : align = data&32'hFFFF_FFFF; default : $display($time,,"%m ERROR %d-byte not supported for size", size); endcase `else case (size) 1 : case (addr[1:0]) 0 : align = data & 32'h0000_00FF; 1 : align = data & 32'h0000_FF00; 2 : align = data & 32'h00FF_0000; 3 : align = data & 32'hFF00_0000; endcase 2 : case (addr[1]) 0 : align = data & 32'h0000_FFFF; 1 : align = data & 32'hFFFF_0000; endcase 4 : align = data&32'hFFFF_FFFF; default : $display($time,,"%m ERROR %d-byte not supported for size", size); endcase `endif end endfunction `include "ahb_transaction_tasks.v" endmodule这个模块主要是实现了两个testcase,一个是测试single传输,另一个测试burst传输。AHB读写传输任务被封装在ahb_transaction_tasks.v文件中://------------------------------------------------------------------------------ // File : ahb_transaction_tasks.v // Editor : VSCode, Tab Size(4) // Description : AHB Transaction Tasks. // //------------------------------------------------------------------------------ `ifndef __AHB_TRANSACTION_TASKS_V__ `define __AHB_TRANSACTION_TASKS_V__ //----------------------------------------------------------------------- // AHB Read Task //----------------------------------------------------------------------- task ahb_read; input [31:0] address; input [2:0] size; output [31:0] data; begin @(posedge HCLK); HADDR <= #1 address; HTRANS <= #1 2'b10; // NONSEQ; HBURST <= #1 3'b000; // SINGLE; HWRITE <= #1 1'b0; // READ; case (size) 1 : HSIZE <= #1 3'b000; // BYTE; 2 : HSIZE <= #1 3'b001; // HWORD; 4 : HSIZE <= #1 3'b010; // WORD; default : $display($time,, "ERROR: unsupported transfer size: %d-byte", size); endcase @(posedge HCLK); while (HREADY !== 1'b1) @(posedge HCLK); HTRANS <= #1 2'b0; // IDLE @(posedge HCLK); while (HREADY === 0) @(posedge HCLK); data = HRDATA; // must be blocking if (HRESP != 2'b00) $display($time,, "ERROR: non OK response for read"); @(posedge HCLK); end endtask //----------------------------------------------------------------------- // AHB Write Task //----------------------------------------------------------------------- task ahb_write; input [31:0] address; input [2:0] size; input [31:0] data; begin @(posedge HCLK); HADDR <= #1 address; HTRANS <= #1 2'b10; // NONSEQ HBURST <= #1 3'b000; // SINGLE HWRITE <= #1 1'b1; // WRITE case (size) 1 : HSIZE <= #1 3'b000; // BYTE 2 : HSIZE <= #1 3'b001; // HWORD 4 : HSIZE <= #1 3'b010; // WORD default : $display($time,, "ERROR: unsupported transfer size: %d-byte", size); endcase @(posedge HCLK); while (HREADY !== 1) @(posedge HCLK); HWDATA <= #1 data; HTRANS <= #1 2'b0; // IDLE @(posedge HCLK); while (HREADY === 0) @(posedge HCLK); if (HRESP != 2'b00) $display($time,, "ERROR: non OK response write"); @(posedge HCLK); end endtask //----------------------------------------------------------------------- // AHB Read Burst Task //----------------------------------------------------------------------- task ahb_read_burst; input [31:0] addr; input [31:0] leng; integer i; integer ln; integer k; begin k = 0; @(posedge HCLK); HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR ln = leng; end HWRITE <= #1 1'b0; // READ HSIZE <= #1 3'b010; // WORD @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); while (leng > 0) begin for (i = 0; i < ln-1; i = i + 1) begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b11; // SEQ; @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); data_burst[k%1024] <= HRDATA; k = k + 1; end leng = leng - ln; if (leng == 0) begin HADDR <= #1 0; HTRANS <= #1 0; HBURST <= #1 0; HWRITE <= #1 0; HSIZE <= #1 0; end else begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR1 ln = leng; end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); data_burst[k%1024] = HRDATA; // must be blocking k = k + 1; end end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); data_burst[k%1024] = HRDATA; // must be blocking end endtask //----------------------------------------------------------------------- // AHB Write Burst Task // It takes suitable burst first and then incremental. //----------------------------------------------------------------------- task ahb_write_burst; input [31:0] addr; input [31:0] leng; integer i; integer j; integer ln; begin j = 0; ln = 0; @(posedge HCLK); while (leng > 0) begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR ln = leng; end HWRITE <= #1 1'b1; // WRITE HSIZE <= #1 3'b010; // WORD for (i = 0; i < ln-1; i = i + 1) begin @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); HWDATA <= #1 data_burst[(j+i)%1024]; HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b11; // SEQ; while (HREADY == 1'b0) @(posedge HCLK); end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); HWDATA <= #1 data_burst[(j+i)%1024]; if (ln == leng) begin HADDR <= #1 0; HTRANS <= #1 0; HBURST <= #1 0; HWRITE <= #1 0; HSIZE <= #1 0; end leng = leng - ln; j = j + ln; end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); if (HRESP != 2'b00) begin // OKAY $display($time,, "ERROR: non OK response write"); end `ifdef DEBUG $display($time,, "INFO: write(%x, %d, %x)", addr, size, data); `endif HWDATA <= #1 0; @(posedge HCLK); end endtask `endif使用VCS仿真,打印信息如下:整体仿真波形如下:不喜欢用DVE的话,也可以选择用Verdi查看波形,这种比较简单的调试,DVE也足够用了。下面贴一张突发传输的细节图:
2023年03月16日
169 阅读
0 评论
1 点赞
2023-03-04
AMBA--AHB总线协议介绍(二)
1、突发提前终止在特定情况下,一个突发传输会被提前终止,此时,要求从机能根据突发信息采取正确的动作。从机能够通过监控HTRANS信号一个突发传输是否被提前终止。在突发传输开始后,从机每次传输都会监测HTRANS信号是否为SEQ或BUSY,当HTRANS信号上出现NONSEQ或者IDLE时,则表明前一次突发传输已经终止。当一个主机因为仲裁失败导致突发传输未能完成,则在该主机下一次获得总线所有权时,必须能够保证重建该突发完成剩余传输。例如,当主机只完成一个4拍突发传输的一拍,那么它必须使用一个未定长度的突发来完成剩下的三拍传输。下图表示了一个四拍的回环突发操作,从机在第一次传输插入了一个等待状态,该突发传输大小为4Byte,为4拍传输,所以地址将在16字节边界回环,因此传输到0x3C后,下一次的传输地址将返回到0x30:下图表示了一个4拍增量突发,在第一次传输时从机也插入了一个等待状态,地址是连续的,跨过了16字节地址地址边界:下图表示了一个8拍的回环突发传输,传输大小为4Byte,所以地址将在32字节边界回环,回环之后的地址为0x20:下图表示8拍增量突发,传输大小为2Byte,所以地址增量为2,同时传输会跨过16字节的地址边界:下图表示了未定义长度的突发传输,从图中可以看出,该图包含两个未定义长度的突发传输,第一个位起始地址为0x20,传输大小为2Byte;第二个突发起始地址为0x5C,传输大小为4Byte,地址增量为4:2、控制信号和传输类型可突发类型信号一样,每次传输都会有一组控制信号来提供传输的附加信息。这些控制信号和地址总线具有相同的时序,和地址信号不同的是,这些控制信号在突发传输的过程中,必须保持不变。2.1 传输方向HWRITE为高时,表示写传输,并且主机需要数据广播到写数据总线(HWDATA)上;该信号为低时,表示读传输,从机必须产生数据到读数据总线(HRDATA)。2.1 传输大小HSIZE[2:0]表示传输大小,见下表:注:传输大小和传输类型一起被用于计算回环突发传输的地址边界。2.3 保护控制HPROT[3:0]为总线协议保护信号,用于定义存取的型态与特性,表示传输是否是:一次预取址或者数据访问;特权模式访问或者用户模式访问;对于带有存储器管理单元的主机来说这些信号也表示当前访问是带高速缓存(cache)的或是带缓冲(buffer)的;HPROT[3]高速缓存HPROT[2]带缓冲的HPROT[1]特权模式HPROT[0]数据/预取指描述---0预取指---1数据访问--0-用户模式访问--1-特权模式访问-0--无缓冲-1--带缓冲0---无高速缓存1---带高速缓存注意:并非所有的主机都能产生正确的保护控制信号,因此建议从机没有严格必要的情况下,不要使用HPROT信号。3、地址译码AHB总线中,会使用一个中央译码器来产生HSELx信号,用于选择从机,选择信号由地址高位信号组合译码产生。建议使用简单的译码方案来保证高速操作。从机只能在HREADY信号为高时,采样地址和控制信号以及HSELx,当HREADY信号为高时,表示传输完成。AHB有几个特别的规定:(1)每个从机至少都有 1KB 的内存空间。(2)每个主机每次存取的空间不可超过 1KB。(3)如果在 NONSEQ 或 SEQ 型态下存取到不存在的地址时,会有一个预设的从机发出 ERROR 的响应信号。(4)如果在 IDLE 或 BUSY 型态下存取到不存在的地址时,会有 OKAY 的响应信号。(5)预设的从机是中央译码器的一部分。(6)根据系统设计,使用地址的高位来产生从机选择信号。(7)地址的低位送给从机以寻址其的内部存储器或缓存器4、从机传输响应主机发起传输后,由从机决定传输如何进行。一旦传输发起,协议不允许主机取消传输。被访问的从机必须提供一个传输状态的响应。从机使用HREADY信号和HRESP[1:0]信号的组合,来提供传输状态。从机可以提供多种方式来完成传输:(1)立刻完成传输;(2)插入一个或多个等待状态以获得充分时间来完成传输;(3)发出一个错误应答表示传输失败;(4)延时传输的完成,允许主机和从机放弃总线,让其他主机先完成传输,再完成剩下的传输。4.1 传输完成HREADY信号用于扩展AHB传输中的数据相位,HREADY为低表示传输被扩展,为高表示传输完成。注意:每个从必须预先设定它放弃总线之前能插入的最大等待周期,用来计算访问总线的延时。推荐但不必须的是, 等待状态最多只可使用 16 个周期,已防止任何访问长时间占用总线。从机需要 HREADY 是双向信号(HREADY 做为总线上的信号,它是主机和从机的输入;同时每个从机自己的 HREADY 信号是输出。所以对于从机会有两个 HREADY 信号,一个来自总线的输入,一个自己给到多路器的输出。4.2 传输响应HRESP[1:0]的编码及传输响应信号如下表:HRESP[1:0]ReponseDescription00OKAY从机可以用HREADY (拉高)与OKAY 的信号响应,代表传送已成功完成。HREADY 被拉低,加上OKAY的信号可插入在任何的响应信号(ERROR、 RETRY 或SPLIT) 之间,也就是当从机在未能正确的选择ERROR、RETRY 或SPLIT时,可以插入HREADY (拉低)与OKAY信号,但是等待状态最多只可使用16个周期01ERROR此次数据传送发生错误,该信号必须维持两个时钟周期10RETRY传输未完成,主机应尝试再次发起传输,直到传输完毕,该信号必须维持两个时钟周期11SPLIT此信号必须维持两个周期,从机响应此信号表示此次的传送无法完成,要以分段传送方式来达成。等到从机可以完成时,会知会仲裁器,让重提数据传送要求的主机,完成传送4.3 双周期响应仅有OKAY响应可以在单周期给出,ERROR、RETRY、SPLIT响应至少需要两个周期。为了完成这些响应,从机会在倒数第二个周期驱动HRESP为ERROR或RETRY或SPLIT,同时拉低HREADY以获得一个额外的扩展周期。在最后一个周期,HREADY拉高,表示传输完成,同时保持HRESP。如果从机需要两个以上的周期来完成ERROR、RETRY、SPLIT响应,则从机可能在传输开始时,插入等待状态。在这段周期内,从机拉低HREADY,同时只能给出OKAY响应。需要双周期响应是AHB流水线传输的本质决定的,当从机发起ERROR、RETRY、SPLIT响应时,下一次传输的地址已经被广播到地址总线上了,双周期响应允许主机有足够的时间来取消该地址,并在下一次传输开始之前驱动HTRANS为IDLE。下图表示了一个RETRY应答,从图中可知,传输的起始地址为A,传输大小为4Byte,主机在获得响应之前,已经将地址设置为A+4,从机在地址A不能立即完成传输,因此给出RETRY响应,这个响应指示主机地址A的传输未完成,并且地址A+4的传输被IDLE传输类型取代。下图表示了传输中,从机需要一个周期(HRESP为OKAY)来决定给出何种响应的例子,之后从机给出双周期的ERROR响应结束了传输。4.4 错误响应当主机收到一个 ERROR响应时,它可以选择结束当前的批量数据传送,也可以继续传送剩下的批量数据,但通常不这样做。在从机送出 ERROR的信号之前必须先送出OKEY + HREADY (LOW)的信号,而 ERROR 信号至少需要维持两个周期。如上图所示。(1)在第二个周期 S 送出 OKAY+ low HREADY 信号,使从机有充分的时间决定是否需要发出 ERROR的信号;(2)主机收到ERROR应答后,立刻结束当前的数据传送;注意:从机必须维持ERROR至少两个周期4.5 分块和重试分块和重试响应给从机提供了在无法立刻给传输提供数据时释放总线的机制。这两种响应都允许在总线上结束传输,因此允许更高优先级的主机获得总线访问权。SPLIT和RETRY的不同之处在于仲裁器在发生SPLIT和RETRY后分配总线的方式:(1)对RETRY而言,仲裁器将继续使用常规优先级方案,因此只有拥有更高优先级的主机能获得总线访问权;(2)对SPLIT而言,仲裁器将调整优先级方案,以便其他任何主机申请总线即能获取总线,即使这个主机拥有更低的优先级。为了完成SPLIT传输,从机必须通知仲裁器数据何时可用,以便相应主机完成传输。SPLIT传输机制增加了仲裁器和从机的复杂度,却拥有完全释放总线给其他主机使用的有点;RETRY相应就只允许更高优先级的主机使用总线。主机使用相同的方式处理SPLIT和RETRY相应。主机应继续请求总线,并尝试传输,知道传输完成或收到ERROR相应为止。5、数据总线为了避免使用三态数据总线,AHB协议将数据总线分开为读数据总线和写数据总线。最小的数据位宽为32位,且位宽可增加。5.1 写数据总线HWDATA[31:0]1、写出数据由主机发出;2、主机必须将数据总线上的数据维持住,直到从机完成数据的接收 (HREADY HIGH);3、所有传送所对应的地址都必须对齐,例如: 32 位的传送地址 Addr[1:0] = 00;4、大端摆放法(big endian)或小端摆放法(little endian)都可使用4.11小端摆放法(以 32 位为例)(1)低字节在 HWDATA.的低字节地址(2)Byte 0 (address offset = 0) 在 HWDATA[7:0](3)Byte 1 (address offset = 1) 在 HWDATA[15:8](4)Byte 2 (address offset = 2) 在 HWDATA[23:16](5)Byte 3 (address offset = 3) 在 HWDATA[31:24]4.12大端摆放法(1)低字节在 HWDATA.的高字节地址(2)Byte 0 (address offset = 0) 在 HWDATA[31:24](3)Byte 1 (address offset = 1) 在 HWDATA[23:16](4)Byte 2 (address offset = 2) 在 HWDATA[15:8](5)Byte 3 (address offset = 3) 在 HWDATA[7:0]5.2 读数据总线HRDATA[31:0]读数据总线在读数据期间,由被选中的从机驱动,如果从机通过拉低HREADY信号来扩展传输,则从机只需在最后一个周期提供有效数据,由HREADY信号为高表示。端结构与写数据总线相同。从机只需要在OKAY响应周期提供有效数据,SPLIT、RETRY以及ERROR响应不需要提供有效的读数据。6、总线仲裁机制仲裁机制确保了任何时刻只有一个主机可以访问总线。仲裁器的功能如下:(1)通过观察不同的总线请求信号,通过仲裁算法来决定哪个主机拥有最高优先级;(2)接收从机的完成分段传送的请求仲裁信号描述如下表所示:NameWidthDescriptionHBUSREQx1bit由主机到仲裁器,主机通过此信号对仲裁器提出使用总线的要求。主机可在任何时间要求仲裁器对于固定长度的传送,只要发起一次请求即可, 仲裁器会依据HBURST[2:0]来判断传送的长度。对于未定的传送长度,主机必须持续提出请求,直到完成全部的传送为止。当所有主机都不需要使用总线时,必须在HTRANS.上送出IDLE信号,仲裁器就会将总线的使用权交给预设主机(default M)HLOCKx1bit此信号必须与HBUSREQx配合,表示一个不可分段的传送动作,HLOCKx必须再指定地址之前使能一个周期HGRANTx1bit由仲裁器输出到主机,仲裁器通知某个主机表示此主机得到总线的使用权.主机在HGRANT信号为高且HREADY信号也为高的那个HCLK上升沿处获得总线使用权 HMASTER4bit仲裁器以此信号表示当前是哪一个主机在使用总线,此信号也可用来控制地址多任务器HMASTERLOCK1bit仲裁器以此信号表示当前的总线是被锁住的,此信号与地址和控制信号具有相同的时序HSPLIT16bit一个可执行分段传送的从机以此信号让仲裁器知道要让哪一个主机完成其未完成的分段传送6.1 总线访问请求主机可以在任何周期内使用HBUSREQx信号来请求总线。仲裁器会在时钟上升沿根据内部的优先级仲裁算法决定接下来哪一个主机将获得总线访问权。通常仲裁器会在一个突发传输完成后才会授权另一个主机,但是如果需要的话,仲裁器也可以提前终止突发,将总线访问权授予更高优先级的主机。如果一个主机需要锁定总线,那么它必须声明HLOCKx信号,告知仲裁器其他主机不应该被授予总线。当主得到总线授权,并且正在执行一个定长的突发传输,它不需要继续总线请求即可完成这个 burst。仲裁器观察 突发的进程,并使用 HBURST[2:0]来决定主机有多少个传送。如果主机希望在当前正在进行的突发之后继续 1 次传输,它应在当前突发期间重新声明请求信号。如果主机在突发传输过程中失去了总线访问权,那么它必须重新声明HBUSREQx信号以获得重新授权。对于未定长度的突发,主机需持续声明HBUSREQx,知道最后一个传输已经开始。因为在未定长度的突发中,仲裁器不知道何时可以改变总线授权。主机没有请求,也可能获得了总线控制权。这是因为当没有主机去请求总线时,总线会授权给一个默认的主机。 所以当某个主机不需要总线访问,它需要把传送类型 HTRANS 设置为 IDLE,这点很重要。6.2 授予总线访问仲裁器通过声明HGRANTx信号来表示当前拥有最高优先级的主机,HREADY信号为高时,主机将被授予总线,同时仲裁器改变HMASTER[3:0]来表示主机序号。下图表示一个零等待的总线授予访问:下图表示了有等待状态总线授予访问:数据总线的拥有权必须比地址总线的拥有权晚释放出去, 这是因为数据周期发生在地址周期之后。(1)在 T5 正沿,从机送出 HREADY 信号,表示 M1成功传送最后一次传输的地址及控制信号(2)地址总线在 T5 正沿开始交换使用者,当 HGRANT_M2 AND HREADY = HIGH,表示地址总线交换成功(3)仲裁器必须知道 M1还有多少笔数据要传送,才可正确的发出 HGRANT_M2 信号(4)当仲裁器收到最后一个传输地址后,仲裁器就会改变 HGRANTx 信号(5)T5 刚开始时, M2 送出地址与控制信号(6)数据总线的交换只有在上一笔数据传送结束后才会开始(7)在 T7 正沿,开始数据总线的交换下图表示了仲裁器在一次突发结束后移交总线的例子:(1)仲裁器在倒数第二个地址被采样时(T5),改变HGRANTx信号;(2)新的HGRANTx信息将在最后一个地址被采样时(T7)同时被采样。下图表示了HGRANTx和HMASTER信号在系统中是如何被使用的:!注意:由于使用了中央地址译码器,每个主机可以立刻输出它想被执行的地址,而不用等到被授予总线。HGRANTx信号仅被主机用来决定它何时拥有总线,并考虑何时地址被选中的从机采样。HMASTER的延迟信号被用来控制写数据总线数据选择器。6.3 锁定传输仲裁必须从观察每一个主机的 HLOCKx 信号, 来决定什么时候主机希望执行一个锁定顺序传输。之后仲裁器负责确保没有其他主机被授权,直到这个锁定传输完成。在一个连续锁定传输之后,仲裁将总是还会保持当前的主机的授权来传一个附加的传送来保证锁定传输的序列的最后一个传送已经成功完成,并且没有收到 SPLIT or RETRY 的应答之一。所以推荐但并不是强制要求的做法是,主机在任何锁定传输后插入一个 IDLE 传输,在开始其它突传输之前,来提供一个仲裁切换的机会。仲裁器也负责声明HMASTERLOCK信号,该信号与地址和控制信号具有相同的时序。该信号指示从机当前传输时锁定的,因此必须在其他主机被授予总线之前处理完成。6.4 默认总线主机每个系统都必须包含一个默认总线主机,当其他主机都不能使用总线时,该主机被授予总线。默认主机在获得授予总线时,只能执行IDLE传输。如果所有主机都还在等待 SPLIT传输的完成,也会将总线使用权交给默认主机。7、分段传输从机可以用 SPLIT 来解决延迟太长的传输。仲裁器必须观察响应信号并且遮掉(mask)已被分段传送的主机,使其等候 HSPLIT 信号的通知后才授予其总线使用权。仲裁器必须在 HMASTER[3:0] 上产生一个标签表示哪一个主机正在执行数据传送。当从机要执行分段传输的动作时,这个从机必须存下主机序号。当从机可以完成被分段的传输时会在 HSPLITx[15:0]信号上,送出要完成传送的主机序号。仲裁器以 HSPLITx[15:0] 来决定哪一个主机 可以重新获得 bus 的使用权。仲裁器会在每个周期去观察 HSPLITx 信号。当有多个 HSPLITx 信号时,会被逻辑或在一起传送给仲裁器。7.1 分段传输顺序1、主机送出地址与控制信号,开始传送。2、被寻址到的从机立即提供数据,或者送出SPLIT应答信号(如果需要等些周期来获得数据)。在每次的传输,仲裁器会广播出当前是哪一个主机在使用总线。 从机需要记录主机序列号,以便后面重传使用。3、有 SPLIT 信号响应时,仲裁器会将总线使用权交给别的主机使用,如果所有其它主机都收到SPLIT 信号,则移交给默认主机。4、当从机要完成分段传送时, 从机会在 HSPLITx 上设定对应的主机 ID,来指示哪个主机可以被允许再次访问总线。5、仲裁器会在每个周期观察 HSPLITx 信号,如果有变化,就更改正确的主机的优先权。6、仲裁器将会允许重新要求使用权的主机,但如果有更高优先级的主机在用总线,可能会延迟。7、从机会响应 OKAY 信号表示传送完成。7.2 多重分段传输只要一个主机有额外的 HBUSREQ 与 HGRANT 线, 即可发出超过一个以上的传送要求。仲裁器将一组信号 (REQ and GNT) 视为一个主机。可分段传输的从机可被设计去处理额外的传送要求而不需纪录额外的地址与控制信号,但要记录主机序号。从机可利用 HSPLITx 来表示当前正在处理的是哪一个主机要完成分段传输。仲裁器可重新仲裁这些主机的请求,高优先权的主机可被重新给予使用权。7.3 预防死锁Deadlock可能发生在很多不同的主机对同一个发出 SPLIT 和 RETRY 的从机身上, 而该从机又无法处理那么多 SPLIT and RETRY 时,便会发生 deadlock。Deadlock 可以透过限制分段传送的数目(最多 16 个)来避免。从机并不会记录下每个要求的地址与控制讯息,如果要作 SPLIT 的话,只会记录下当前传送对象的 ID 讯息。当 S 可完成分段传送时,才会锁定要求的地址与控制讯息。从机会依序地让仲裁器知道从机要服务哪些要求。从机可以任何顺序处理要求的服务。重传:当从机无法在很短的时间内完成传送的动作时, 它可以要求主机重传。 从机一次只可对一个主机发出 RETRY信号,确定所服务的主机是同一个时, RETRY 的动作才能完成。如果 RETRY 是不同主机时可以采取下列措施:(1)给一个 error 回应(2)通知仲裁器(3)产生中断(4)重置系统7.4 分块传输的总线移交当主机收到 SPLIT 或 RETRY 响应信号时,必须先进入 IDLE 状态并且重新请求总线使用权,一个新的主机会在 IDLE 周期结束前由仲裁器决定出来。主机在收到 SPLIT 或 RETRY 信号后须立刻进入 IDLE 状态。(1)在 T2 与 T3, 从机会传两个周期的 SPILT 信号。(2)该主机在 T3 正沿时发现有 SPLIT 响应信号,于是进入 IDLE 状态。(3)仲裁器在 T3 正沿时,决定下一个总线的使用者。(4)在 T4,新的主机送出地址与控制信号
2023年03月04日
183 阅读
0 评论
1 点赞
AMBA--AHB总线协议介绍(一)
1、AHB总线概述AHB:Advanced High-performance Bus,即高级高性能总线。AHB总线是SOC芯片中应用最为广泛的片上总线。下图是一个典型的基于AMBA AHB总线的微控制器系统:SOC架构基于AMBA AHB的设计中可以包含一个或多个总线主机,通常一个系统里至少包含一个处理器和一个测试接口;DMA和DSP作为总线主机同样是比较常见的。典型的AHB总线设计包括一下几个部分:(1)AHB主机:主机可以通过提供地址和控制信息发起读写操作;同一时刻总线上只允许一个主机占用总线。(2)AHB从机:从机需在给定的地址空间范围内响应总线上的读或写操作;从机通过信号将成功、失败、等待数据传输等信息返回至有效的主机。(3)AHB仲裁器:总线仲裁器确保同一时刻只有一个主机被允许发起传输。所有的AHB总线都必须包含一个仲裁器,即使是在单主机总线系统中。(4)AHB译码器:译码器的作用是对传输中的地址信号进行译码,并提供给从机一个选择信号。所有的AHB总线中都必须包含一个中央译码器。2、总线互联AMBA AHB总线协议被设计为一个使用中央多路选择器的互联方案,如下表:基于这种方案,所有的主机在需要发起传输时,都可以驱动地址和控制信号,由仲裁器决定哪一个主机的地址和控制信号(写传输时包含写数据信号)连通到总线上的所有从机。同时总线需要一个中央译码器,用于控制读数据和应答信号的多路数据选择器,该多路数据选择器用于选择传输中涉及的从机的相关信号。3、AHB信号AHB信号以字母H作为前缀,如下表所示NameWidthSourceDescriptionHCLK1bit时钟源总线所有传输都基于此时钟,所有信号的时序都与时钟上升沿有关HRESETn1bit复位控制器总线复位信号,总线上唯一低电平有效的信号HADDR32bit主机32位地址总线HTRANS2bit主机表明当前传输的类型HSIZE2bit主机表明传输的大小,典型的是字节(8-bit)、 半字(16-bit)、字(32-bit), 协议允许最大传输大小是1024 bitHWRITE1bit主机传输写信号,高电平表示写传输,低电平表示读传输HBURST3bit主机有自己的HBURST信号有自己的HBURST信号HPROT4bit主机保护控制信号。主要应用于期望实现某些程度的保护级别的模块中HWDATA32bit主机写传输数据。要求最低位宽位32位HSELx1bit译码器从机选择信号,每个AHB从机都有自己的HSEL信号,该信号有效时,表明选中相应从机HRDATA32bit从机读数据总线,用于读操作期间从机向主机传输数据HREADY1bit从机该信号为高时,表明总线传输完成。也可以拉低该信号用以扩展传输HRESP2bit从机传输响应信号。提供四种响应OKAY、ERROR、RETRY、SPLITAMBA AHB也需要一些信号用于支持多主机总线操作,其中一些仲裁信号需要点对点连接:NameWidthSourceDescriptionHBUSREQx1bit主机主机x发送至仲裁器的总线请求信号,总线系统中最多有16个主机,每个主机都有一个HBUSREQ信号HLOCKx1bit主机该信号为高时,表示主机x发起锁定传输HGRANTx1bit仲裁器表明主机x在当前总线上拥有最高的优先级,当HREADY信号为高时,地址/控制信号的所有权将发生变化,故主机x需在HREADY与HGRANTx信号同时为高时,获得总线控制权HMASTER4bit仲裁器表明哪一个主机当前拥有地址/控制信号的所有权HMASTLOCK1bit仲裁器表明当前传输为锁定顺序传输,此信号与HMASTER具有相同时序HSPLITx16bit从机用于指示仲裁器,哪一个主机可以继续完成分块传输4、AMBA AHB操作概述在一次AHB传输开始之前,主机必须被授予总线访问权。这个过程起始于主机向仲裁器发出一个总线请求,由仲裁器决定该主机何时被授予总线使用权。被授权的主机通过驱动地址和控制信号来发起一次AHB传输,这些信号提供了地址、传输方向、传输宽度等信息,以及会表明当前传输是否为突发传输的一部分。AHB支持两种形式的突发传输:(1)增量突发,在地址边界不进行回环;(2)回环突发,在特定地址边界回环写数据总线用于将数据从主机发送到从机,读数据总线用于将数据从从机传输到主机。每次数据传输包含:(1)一个地址和控制周期;(2)一个或多个数据周期由于地址不支持扩展,所以所有的从机必须在地址周期内采样地址,而数据可以通过HREADY信号进行扩展延长,当HREADY信号为低,总线将插入等待状态,以此提供从机额外的采样数据或者提供数据的时间。在传输中,从机使用应答信号HRESP[1:0]来表示传输状态:(1)OKAY:OKAY响应表示传输正常,且当HREADY信号为高时,表示传输成功;(2)ERROR:ERROR响应表示发生了传输错误,并且传输失败;(3)RETRY and SPLIT:RETRY和SPLIT都表示当前传输未能即刻完成,但是主机应继续尝试传输一般而言,仲裁器授权另一个主机前,允许当前被授权主机完成突发传输。然而,为了避免过多的仲裁延迟(当前主机占用过多总线周期),总线可以打断一个突发传输,在这种情况下,主机必须重新申请总线,以完成后续的突发传输。5、基本传输一个AHB传输包含两个部分:(1)地址相位,仅包含一个时钟周期;(2)数据相位,可以使用HREADY信号,维持多个时钟周期下图表示了一个无等待状态的简单传输:在无等待的简单传输中,主机在HCLK的上升沿驱动地址和控制信号到总线上,在下一个时钟上升沿时,从机对地址和控制信号进行采样,从机采样得到地址和控制信息后,可以驱动应答信号给与主机适当的响应,主机在第三个时钟的上升沿对应答信号进行采样。这个简单的例子演示了在不同的时钟周期地址和数据相位是怎样产生的。实际上,当前传输的地址相位都对应于前一次传输的数据相位,这种地址和数据交叠现象,是总线能够进行流水线传输的基础,这样不仅能够获得更高的传输性能,也能为从机进行响应提供充足的时间。从机可以在数据相位中,插入等待状态,以此获得更多得时间来完成传输:注意:(1)在写传输中,主机需要在整个扩展周期内保持写数据信号稳定(2)在读传输中,从机没必要提供有效数据,知道传输结束时当传输以这种方式做扩展时,将对下一个传输的地址相位产生一个扩展的副作用,如下图所示:该图表示了三个不相关的地址A、B、C上的传输,图中地址A和C的传输都没有等待状态,地址B的传输通过HREADY信号的拉低插入了一个等待的数据相位,这样导致了地址C传输的地址相位进行了扩展。6、传输类型AHB传输类型可以分为四种,通过HTRANS[1:0]的取值来划分:HTRANSTypeDescription00IDLE表明没有数据传输的要求。IDLE 用于主机被授予总线,但不希望进行数据传输的情况。对于IDLE传输,从机必须提供一个零等待的OKAY应答,并且忽略该传输01BUSYBUSY传输类型允许主机在突发传输中插入空闲周期。表明主机正在进行突发传输,但下次传输不能立即有效。当主机使用BUSY传输类型时,地址和控制信号必须对应突发中的下一次传输。从机必须提供一个零等待的OKAY应答,并且忽略该传输。10NONSEQ表示突发传输中的第一次传输或者非突发的单次传输,地址和控制信号与前次传输无关.11SEQ突发传输中剩余的传输时连续的,地址和前一次传输是相关的,当前地址值等于前一次传输的地址值加上传输大小(字节), 控制信息和前一次传输相同。在回环的突发传输中,传输的地址在地址边界处回环,回环值的大小等于传输大小(字节) 乘以传输次数(4、8或16) )下图是一个不同传输类型的例子:从图中可以看出:第一个传输为以此突发传输的开始(T1),所以其传输类型为NONSEQ;主机不能立刻执行突发传输中的第二次传输,所以使用了BUSY的传输类型(T2)来延迟下一次传输的开始,注意此时地址已经时下次传输的地址,控制信号和下次传输保持一致,主机只插入了一个BUSY,所以T3执行第二次传输;主机执行第三次传输(T4),由于从机将HREADY信号拉低,插入了一个等待周期,引起地址相位的扩展;T6周期完成第三次传输,T7周期完成第四次传输(图中T8边沿)。7、突发操作AMBA AHB协议定义了4、8和16拍的突发,未定义长度的突发传输以及单次传输;协议支持增量和回环的突发方式。增量突发方式访问连续地址空间,每次传输的地址是前一次传输地址增加一个增量偏移;回环突发中,如果传输的其实地址并未和突发中的字节总数对齐,则突发传输地址将在达到边界处回环。例如:一个4拍回环突发的字(4字节)访问将在16字节的边界处回环,如果传输的起始地址为0x34,那么突发中将包含4个地址:0x34,0x38,0x3C,0x30。AMBA AHB 有8种突发操作,使用信号HBRUST[2:0]表示:HTRANSTypeDescription000SINGLE单一传输001INCR未指定长度的增量突发010WRAP44拍回环突发011INCR44拍增量突发100WRAP88拍回环突发101INCR88拍增量突发110WRAP1616拍回环突发111INCR1616拍增量突发突发不能超过1K的地址边界,所以主机尽量不要发起将要跨过地址边界的定长的增量突发。一次突发传输的数据总量,等于节拍数乘以每拍包含的字节数。所以突发传输必须将地址边界和数据大小对齐,例如,字传输必须对齐到字地址边界,即A[1:0]=00;半字传输必须对齐到半字地址边界,即A[0]=0。
2023年03月03日
290 阅读
0 评论
1 点赞
AMBA--APB总线协议及Verilog实现与仿真
1、APB总线简介APB:Advanced Peripheral Bus,高级外设总线,具备以下特性:(1)低功耗;(2)接口协议简单;(3)总线传输使用时钟上升沿进行,便于时序分析;(4)应用广泛,支持多种外设。所有的APB模块均是APB从机。2、APB信号列表所有的APB总线信号都以字母P作为前缀,下表列出了APB信号的名称以及对信号的描述:NameWidthI/ODescriptionPCLK1bitInAPB总线时钟,所有传输只能发生在PCLK的上升沿PRESETn1bitInAPB总线复位信号,低电平有效PADDR32bitInAPB地址总线PSEL1bitInAPB从机片选信号PENABLE1bitInAPB选通信号,高电平表示APB传输的第二个周期PWRITE1bitInAPB读写控制信号,高表示写,低表示读PRDATA32bitOutAPB读数据信号,最高为32位PWDATA32bitInAPB写数据信号,最高为32位3、APB总线时序(1)写时序写传输开始于T2时刻,在改时钟上升沿时刻,地址、写信号、PSEL、写数据信号同时发生变化,T2时钟,即传输的第一个时钟被称为SETUP周期。在下个时钟上升沿T3,PENABLE信号拉高,表示ENABLE周期,在该周期内,数据、地址以及控制信号都必须保持有效。整个写传输在这个周期结束时完成。(2)读时序读传输开始于T2时刻,在改时钟上升沿时刻,地址、写信号、PSEL信号同时发生变化,在下个时钟上升沿T3,PENABLE信号拉高,从机必须在ENABLE周期内提供读数据,读数据信号将在T4上升沿时刻被采样。经历2个cycle就读数据,不需要握手4、Verilog实现下面编写一个简单的基于APB接口的memory读写控制程序供读数据,读数据信号将在T4上升沿时刻被采样。`timescale 1ns / 1ps module apb_sram #( parameter SIZE_IN_BYTES = 1024 ) ( //---------------------------------- // IO Declarations //---------------------------------- input PRESETn, input PCLK, input PSEL, input [31:0] PADDR, input PENABLE, input PWRITE, input [31:0] PWDATA, output reg [31:0] PRDATA ); //---------------------------------- // Local Parameter Declarations //---------------------------------- localparam A_WIDTH = clogb2(SIZE_IN_BYTES); //---------------------------------- // Variable Declarations //---------------------------------- reg [31:0] mem[0:SIZE_IN_BYTES/4-1]; wire wren; wire rden; wire [A_WIDTH-1:2] addr; //---------------------------------- // Function Declarations //---------------------------------- function integer clogb2; input [31:0] value; reg [31:0] tmp; reg [31:0] rt; begin tmp = value - 1; for (rt = 0; tmp > 0; rt = rt + 1) tmp = tmp >> 1; clogb2 = rt; end endfunction //---------------------------------- // Start of Main Code //---------------------------------- // Create read and write enable signals using APB control signals assign wren = PWRITE && PENABLE && PSEL; // Enable Period assign rden = ~PWRITE && ~PENABLE && PSEL; // Setup Period assign addr = PADDR[A_WIDTH-1:2]; // Write mem always @(posedge PCLK) begin if (wren) mem[addr] <= PWDATA; end // Read mem always @(posedge PCLK) begin if (rden) PRDATA <= mem[addr]; else PRDATA <= 'h0; end endmodule测试代码:`timescale 1ns / 1ps `ifndef CLK_FREQ `define CLK_FREQ 50000000 `endif module top_tb(); //---------------------------------- // Local Parameter Declarations //---------------------------------- parameter SIZE_IN_BYTES = 1024; localparam CLK_FREQ = `CLK_FREQ; localparam CLK_PERIOD_HALF = 1000000000/(CLK_FREQ*2); //---------------------------------- // Variable Declarations //---------------------------------- reg PRESETn = 1'b0; reg PCLK = 1'b0; reg PSEL; reg [31:0] PADDR; reg PENABLE; reg PWRITE; reg [31:0] PWDATA; wire [31:0] PRDATA; reg [31:0] reposit[0:1023]; //---------------------------------- // Start of Main Code //---------------------------------- apb_sram #( .SIZE_IN_BYTES (SIZE_IN_BYTES) ) u_apb_sram ( .PRESETn (PRESETn), .PCLK (PCLK), .PSEL (PSEL), .PADDR (PADDR), .PENABLE (PENABLE), .PWRITE (PWRITE), .PWDATA (PWDATA), .PRDATA (PRDATA) ); // generate PCLK always #CLK_PERIOD_HALF begin PCLK <= ~PCLK; end // generate PRESETn initial begin PRESETn <= 1'b0; repeat(5) @(posedge PCLK); PRESETn <= 1'b1; end // test memory initial begin PSEL = 1'b0; PADDR = ~32'h0; PENABLE = 1'b0; PWRITE = 1'b0; PWDATA = 32'hffff_ffff; wait(PRESETn == 1'b0); wait(PRESETn == 1'b1); repeat(3) @(posedge PCLK); memory_test(0, SIZE_IN_BYTES/4-1); repeat(5) @(posedge PCLK); $finish(2); end // memory test task task memory_test; // starting address input [31:0] start; // ending address, inclusive input [31:0] finish; reg [31:0] dataW; reg [31:0] dataR; integer a; integer b; integer err; begin err = 0; // read-after-write test for (a = start; a <= finish; a = a + 1) begin dataW = $random; apb_write(4*a, dataW); apb_read (4*a, dataR); if (dataR !== dataW) begin err = err + 1; $display($time,,"%m Read after Write error at A:0x%08x D:0x%x, but 0x%x expected", a, dataR, dataW); end end if (err == 0) $display($time,,"%m Read after Write 0x%x-%x test OK", start, finish); err = 0; // read_all-after-write_all test for (a = start; a <= finish; a = a + 1) begin b = a - start; reposit[b] = $random; apb_write(4*a, reposit[b]); end for (a = start; a <= finish; a = a + 1) begin b = a - start; apb_read(4*a, dataR); if (dataR !== reposit[b]) begin err = err + 1; $display($time,,"%m Read all after Write all error at A:0x%08x D:0x%x, but 0x%x expected", a, dataR, reposit[b]); end end if (err == 0) $display($time,,"%m Read all after Write all 0x%x-%x test OK", start, finish); end endtask // APB write task task apb_write; input [31:0] addr; input [31:0] data; begin @(posedge PCLK); PADDR <= #1 addr; PWRITE <= #1 1'b1; PSEL <= #1 1'b1; PWDATA <= #1 data; @(posedge PCLK); PENABLE <= #1 1'b1; @(posedge PCLK); PSEL <= #1 1'b0; PENABLE <= #1 1'b0; end endtask // APB read task task apb_read; input [31:0] addr; output [31:0] data; begin @(posedge PCLK); PADDR <= #1 addr; PWRITE <= #1 1'b0; PSEL <= #1 1'b1; @(posedge PCLK); PENABLE <= #1 1'b1; @(posedge PCLK); PSEL <= #1 1'b0; PENABLE <= #1 1'b0; data = PRDATA; // it should be blocking end endtask `ifdef VCS initial begin $fsdbDumpfile("top_tb.fsdb"); $fsdbDumpvars; end initial begin `ifdef DUMP_VPD $vcdpluson(); `endif end `endif endmodule该测试用例,主要实现了APB读和写的task,用于产生APB读写时序,对memory的测试分成连个部分,一个是每进行一次写传输后,紧接着进行同地址的读传输,让后对比读写结果一致性;另一个测试是在连续写一段地址后,再全部读出改地址段的数据,完成读操作后进行数据比对。下面是仿真打印信息APB写传输时序的仿真波形如下:APB写传输时序的仿真波形如下:
2023年02月28日
875 阅读
0 评论
2 点赞
1
2
3