瞎谈指令集和寄存器读写来驱动硬件

文章目录

  • 前言
  • 一、到底什么是指令集?
  • 二、为什么现代CPU需要指令集?
  • 三、开发完指令集究竟有什么缺点?
  • 四、寄存器读写怎么验证?
  • 总结


前言

其实很早以前就想对这个话题展开来聊聊,但是对体系结构的理解也仅仅限于《量化体系结构》这一书,对底层实现也仅仅局限于做过RISC-V RV32I基本指令子集的CPU设计。此外实践深度远远不足以支撑我站在系统的角度考虑问题!因此怕讲了出现太多错误,被技术老炮们炮轰。

现在之所以敢壮起胆子来谈这个话题有3点原因和1点动机!

1、过去三年时间内读了不少架构类、嵌入开发和操作系统类的书籍,或多或少已经对系统有了大致但还是有点朦胧的了解。由于曾经接触过超级demo的CPU设计,所以对指令集的印象也相对深刻!

2、过去三年内分别体验过Vitis AI DPU(详情可以跳转之前写的Vitis AI DPU部署的博客,后续有计划补充对架构的理解,先赊个账)、自费1000+RMB购买的定制化加速器方案、读了NVIDIA开源的NVDLA加速器驱动代码和硬件代码(详情可以跳转之前写的内核态驱动、用户态驱动和架构分析的博客),对上述三个方案的SoC架构有所了解。从系统的角度对各层都有所观察、有所理解。

3、接触过Xilinx sdk开发后,对怎么引入寄存器和寄存器读写都有些许经验和感悟。

至于动机,手头这个工作结束了,就开始思考如果要做下一个工作,到底是基于指令集开发还是单纯使用寄存器开发?一直以来接触最多的是在裸机上开发寄存器,偶尔也会看到程序通过编译器得到汇编(其实就是01形式的指令集)后灌给主存来驱动硬件。一直觉得指令集开发模式很酷,就像定义了一个新的果壳宇宙,而我是建筑师。因为这样的中二病,所以手痒痒在所难免。

技术做多了,有点冷静下来了。我为什么需要指令集?指令集酷的背后有什么代价?天底下没有免费的午餐!

回答这个问题之前就需要回答这么几个问题!

1、到底什么是指令集?

2、为什么现代CPU需要指令集?然而并非所有的硬件都需要指令集!简单如自费1000+买的定制化极强的5层网络加速器根本不涉及指令集开发,复杂如NVDLA也不存在指令集(至少到现在为止我读完NV开源的架构方案、开源的KMD和UMD代码,我都没有发现指令集的存在),那么究竟是为什么现代CPU需要开发非常有条理的指令集呢?

3、开发完指令集究竟有什么样的缺点?明明限制了逻辑寄存器的数量,明明通过多bit的比较逻辑根据操作码来确定操作类型,明明需要额外设置取指译码两个看起来逻辑上很严密(好像没觉得奇怪,5级流水线嘛,教科书上都这么写)的环节但也会带来功耗和面积开销,以上种种我将会结合寄存器读写模式来谈为什么这些在相比之下是弊端

4、为什么我会提到另一种完全不咋流行的硬件驱动模式(此驱动drive而非driver)?说的就是你——“寄存器读写”,这个方式在嵌入式开发非常流行,就是使用硬件设计好的接口直接去读写寄存器,这一点还得感谢内存映射机制,还得感谢虚拟地址,感谢不完了,总之感谢整个体系结构。

基于以上四个问题,我思(xia)考(xiang)了好久,觉得是时候记录一下自己的想法了!

如果讲错了,还请各位轻锤,指出错误。

如果讲对了,欢迎大佬在评论区拓展!我权且抛砖引玉!


一、到底什么是指令集?

列举RISC指令集体系中的MIPS指令集来说明(先搬运点超标量这本书上的知识):
在这里插入图片描述
MIPS指令集类型主要分为三类。

1、和立即数相关的,rs为源寄存器,rd为目的寄存器。
2、和跳转相关的,其中26个bit用于立即数。
3、和寄存器指令相关的,rs和rt用作源寄存器,rd用作目的寄存器。由于R-Type指令种类繁多,因此需要funct域来进一步划分指令类别,同时sa专门用于移位指令。

给若干张表详细解释高op是怎么指定指令类型的!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到这里为止,MIPS指令集的基本轮廓已经显现了。
那我们还是借助超标量这本书来解释“指令集架构ISA”的概念:

ISA是规定处理器的外在行为的一系列内容的统称,它包括基本数据类型、指令、寄存器、寻址模式、存储体系、中断、异常与外部IO等内容。

换句话说,指令集其实在用有限的32bit指定好了这么几件事:

1、让处理器做什么操作?(做什么的物理实现(也就是基于cmos的电路实现)已经搭建好了),这一条由op以及其他必要信息来指定
2、给了处理器做该操作的该有的信息,比如去读哪个寄存器,比如有什么样的立即数?

第一条信息其实指明了指令集的op与其他必要的field来指定,粗糙地说是“做什么操作”,更加精确地说则是在已有的电路实现中选择哪条数据路径和控制路径来实现某条指令规定的功能,所以说白了是多选一的选择器信号或者某功能的使能信号,只不过在CPU设计中该信号会在不同流水级中挨个用上。

第二条信息指明了实现该操作必要的数据信息,这些信息的来源从指令的表现形式上来看要么是寄存器,要么是主存。但注意,不管是寄存器,还是主存,抑或是被掩盖掉看不到的3级Cache或者是磁盘和外存,得益于虚拟存储映射机制(意味着存储被统一)页面替换、cacheline内的block替换等替换策略(意味着CPU执行程序不会碍于有限的物理内存)中断异常(意味着能允许内核态、用户态线程的切换,同时允许中断现场的保存,保存所需要的资源都要从寄存器或者主存回到主存的临时空间)等的存在,物理存储在实际CPU运行(意味着这是个动态行为)中会被模糊。数据信息虽然来源纷繁复杂,但是总之一句话,就是数据信息的来源在没有操作系统的情况下是可以支持硬件工程师使用寄存器读/写的方式来获取或者赋予。

那么再回答一个问题?第二条信息我指明了是寄存器读/写,那么第一条信息的是不是也可以指明寄存器行为呢?我给一个RTLverilog代码的模板(以一个简单的加法器为例)

module add #(parameter WIDTH = 4
)
(input              clk,input              rst_n,input  [WIDTH-1:0] datain1,input  [WIDTH-1:0] datain2,input              enable_add,output [WIDTH  :0] dataout
);reg [WIDTH :0] dataout_reg;
assign dataout = dataout_reg;always @(posedge clk or negedge rst_n)
beginif (!rst_n) dataout_reg <= 0;else beginif (enable_add) dataout_reg <= datain1 + datain2;else dataout_reg <= dataout_reg;end
endendmodule

上述代码中,enable_add就是一个加法功能的使能信号,那我为什么又说这是多选一的选择信号呢?这基于CPU设计了多功能的考量,给一个粗糙的例子:

......
case (op_signal)
6'b001_001: begin ......operator_sel = <add_selection_signal>; // 赋予加法指令的控制信号......
6'b001_???: begin......operator_sel = <mul_selection_signal>; // 赋予乘法指令的控制信号
end
end
......

以上给了加法和乘法的例子,加法就是被选出来的指令。
我们再来观察加法器的代码,接口中有这么几个信号:

module add #(parameter WIDTH = 4
)
(input              clk,input              rst_n,input  [WIDTH-1:0] datain1,input  [WIDTH-1:0] datain2,input              enable_add,output [WIDTH  :0] dataout
);

clk时钟信号和rst_n复位信号是系统信号,datain1datain2就是加法的2个加数,dataout就是加法的结果,enable_add就是选中了加法的行为。在verilog中如果需要把这个加法器接入到CPU中,会考虑给这datain1datain2dataoutenable_add分配地址,对于地址的读写如果从ZYNQ的角度来考虑,其实就是sdk中的reg_readreg_write操作。

另外有没有觉得datain1和datain2就是加法的2个加数,dataout就是加法的结果,enable_add就是选中了加法的行为眼熟?这不就是指令嘛!该有的都有了

op  = &enable_add // 严格表述,&和C中一样,表示取指
rs1 = &datain1
rs2 = &datain2
rd  = &dataout

那么回到问题本身,什么是指令集?

我给出的答案是携带了操作类型的信息以及执行该操作必要的数据信息,而这些信息中操作类型被明确指定,数据信息除了立即数以外都是寄存器偏移,它的行为本质上和寄存器读写一致,不过是被高度抽象的!至于为什么需要高度抽象,看下一节。

二、为什么现代CPU需要指令集?

如果对硬件开发不熟悉的软件工程师或者算法工程师而言,直接对基于RTLverilog硬件操控寄存器来实现遥望着还有十万八千里距离的程序无疑是十分痛苦的,因为这个工作得把熟悉的基于python或者c的代码实现转换为基于寄存器的代码。

时代进步了,指令集把对控制信号和数据信号的信息做了个极大化的抽象,在RISC中被编码为32bit的宽度。好处在于,一次性给出2个或者3个或者4个寄存器信息,把基于python或者c的代码实现转为基于指令集的代码的工程量砍了至少一半以上,这个工程我们称之为基于指令集的汇编开发。这个量说大也不大,毕竟计科和微电子系的同行们试过直接手撸汇编,大名鼎鼎的雷军先生在金山开发时用的也是汇编,说小还真不小,毕竟用过c或者更高级的python的同志们想再回到汇编时代肯定是不乐意的。

时代接着进步,编译器Runtime的出现极大化地促进高级编程语言的发展。编译器前端对接高级程序,后端对接指令集。用过c或者更高级的python的同志们可以无所畏惧地开发软件或者算法是因为有了编译器runtime这一层隔膜,事实上也得感谢操作系统将必要的硬件抽象为内核态驱动(KMD,Kernel Mode Driver),而驱动是底层将寄存器行为进一步封装的高级方案。runtime编译器在我熟悉的NVDLA的方案里面被统一归到用户态驱动(UMD,User Mode Driver)中,而在另一套我熟悉的Vitis AI DPU的方案中分别列举。因此关于UMD的归类各家有各家看法。

我们以上提到的所有,其实本质上是出于用户的考量,使用者怎么方便,计算机系统工作者就怎么考虑。毕竟使用者越多,整个框架卖得越多,当然得到的反馈也越多,同时促进了生产力的发展。所以,理所应当地,越是底层的,越不容易开源,嗐。

三、开发完指令集究竟有什么缺点?

第二节我们讨论到基于指令集的工作,提到一个词用户。但事实上,硬件设计的指标并非远在天边的用户,而是PPA (Performance, Power, Area),和针对具体场景而提出的成本抗辐射可用性等等。

我们还是以前述的加法操作为例。在CPU中五级流水线——取指、译码、执行、访存、写回中,取指和译码负责从ICache中取回指令和将指令破译。而在我提到的加法操作的verilog设计中其实只需要考虑将数据信息从寄存器中获取到就行,所以就只局限于当前这个操作,寄存器操作可以省略取指、译码的很大一部分工作。执行一条指令所需要的周期数减少、功能部件数量减少,接着功耗减少,在芯片上的占用面积也减少。

对于硬件加速器如是,我举一个Vitis AI DPU的架构:
在这里插入图片描述
在这里插入图片描述
在这个硬件加速器中还是照着CPU的流水线来开发,从APU中以100MHz的频率给指令,指令通过Bus进入由时钟生成器生成325MHz高频时钟和100MHz低频时钟的PL,指令进入指令分配器中依次经过取指、译码和分发,同时从片上Memory中获取必要的数据信息,或者在data miss时从片外Memory中获取数据信息。随后将数据和指令的控制信息传送到矩阵乘加的计算阵列中,将得到的中间结果通过BRAM读写控制器放到片上Memory或者Data Mover(这个应该是FIFO)中,一旦存储不下了,就把数据从片上调到片外(当然这个过程是隐藏在计算流程后面的,减少串行执行带来的延迟以提升计算性能)。

如果从寄存器读写的角度来说,取指、译码和分发的功能部件可以做极大的简化。但是缺点也是明显的!就是debug Verilog的时候没有那么容易,在sdk开发的时候也没那么容易!

这个方案还用到了中断,主要起2方面作用:1、通知CPU,矩阵乘加计算完了,以便于系统发送新的矩阵乘加指令;2、通知CPU,所有有关矩阵乘加的操作都计算完了,为了保证基于CNN的图像分类可以接着执行,该把执行softmax的指令发送一下了,所以作者设计了2个softmax的固化IP。

类似基于五级流水的加速方案还有很多,比如Vortex GPGPU(基于RISC-V指令集扩展)(具体不做解读,大概能看得明白,学了超标量处理器设计的思路,4路x发射乱序的GPGPU):
在这里插入图片描述

所以其实从硬件开发的角度来说,全部功能模块都用指令集实现无疑是对PPA的损害,所以要考虑到并非所有功能模块都使用指令集开发。

我个人认为基于寄存器开发的方案是PPA大哥!但是如果从基于寄存器开发的例子NVDLA来说,想接着做二次开发的难度还是比较大,代码逻辑混乱,不容易理清楚。以下是NVDLA IP架构,从架构内容可以看出不具备指令开发的特点,其中Configuration interface block用于以硬件方式配置寄存器,随后该模块接着被拉出寄存器在KMD层面抽象。
在这里插入图片描述
在这里插入图片描述
看完以上几个方案,我们如果从基于ZYNQ+FPGA的模式来考虑,很多人可能觉得如果要开发基于自定义指令集的方案应该怎么做?毕竟前面提到和RISC-V指令集相关的GPGPU,这个可以让CPU来识别是不是host (CPU)或者device (GPU)负责,类似NVIDIA的方案。那是不是就没法儿搞自定义指令集这一工程了呢?不然!给个方案实现:

1、RTL层面依然使用自定义指令集来完成取指、译码、执行、访存、写回等五个步骤的加速器;

2、RTL层面有了这么几个要素:1)取指令的起始地址;2)加速器开始运行的使能信号

3、在2中提到的2个寄存器可以明显被拉出来(在完成axi类型的IP封装和ZYNQ搭建SoC以后、经历综合实现bitstream导出后会给出寄存器memory中的偏移地址),使用寄存器的偏移地址可以在Xilinx Vitis sdk中对起始地址处赋予指令的内容,也就是需要手写汇编

4、至于手写汇编能不能被编译器代替,这得需要专门的编译器工具了。不过对CNN或者Transformer这类矩阵乘占据大多数的,往往开发数据级并行DLP,因此指令数量在模型规模不大的情况下其实并不会很多。

四、寄存器读写怎么验证?

我以NVDLA为例,这个问题在回答如何在裸机层面驱动层面去验证,驱动层面的细节太多,请移步我之前写过关于NVDLA的驱动代码解读。以下回答裸机层面

最后封装的时候是这样的:
在这里插入图片描述
NVDLA IP寄存器被拉到AMBA总线中,注意,给寄存器分配偏移地址是在SoC搭完以后,所以换句话说,没有AMBA总线来做中介是不可能在系统层面进行验证的。

我拿一段代码来说明(下面是axi-lite slave内的一段代码):


`timescale 1 ns / 1 psmodule ppv3_preprocess_accelerator_v1_0_S00_AXI #(// Users to add parameters hereparameter DATA_WIDTH_AXI = 32,parameter PRIOR_DATA_WIDTH        =   1024    , // 64prior * int8 * 2 (xc,yc)parameter GT_DATA_WIDTH           =   256     , // 8gt * int8 * 4 (xleft,xright,yupper,ybottom)parameter NUMBER_OF_GT            =   8       ,parameter NUMBER_OF_PRIOR         =   64      ,parameter DATA_WIDTH_INT8         =   8       ,parameter HPIC                    =   160     , // 160 or 120parameter WPIC                    =   160     ,parameter NUMBER_OF_CLS           =   32      , // cls numberparameter DATA_WIDTH_INT4         =   4       ,parameter CLS_DATA_WIDTH          =   8192    , // 64prior * int4 * 32(cls)parameter CONF_DATA_WIDTH         =   512     , // 64prior confidence * int8parameter CONF_INT8               =   8       , // int8 confidenceparameter CONF_THRESHOLD          =   8       , // int-itize confidence threshold // alignmentparameter DATA_WIDTH_INT12        =   12      ,// uppersumparameter DATA_WIDTH_INT16        =   16      ,// User parameters ends// Do not modify the parameters beyond this line// Width of S_AXI data busparameter integer C_S_AXI_DATA_WIDTH	= 32,// Width of S_AXI address busparameter integer C_S_AXI_ADDR_WIDTH	= 6)(// Users to add ports hereoutput 							o_run,output  [DATA_WIDTH_AXI-2:0]	o_num_cnt, input   						i_idle,input   						i_read,input   						i_write,input							i_done,output                           train_or_test,output                           nms_enable,output                           faster_enable,output                           dsla_enable,output                           yolo_enable,input		                    end_signal,// User ports ends// Do not modify the ports beyond this line// Global Clock Signalinput wire  S_AXI_ACLK,// Global Reset Signal. This Signal is Active LOWinput wire  S_AXI_ARESETN,// Write address (issued by master, acceped by Slave)input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR,// Write channel Protection type. This signal indicates the// privilege and security level of the transaction, and whether// the transaction is a data access or an instruction access.input wire [2 : 0] S_AXI_AWPROT,// Write address valid. This signal indicates that the master signaling// valid write address and control information.input wire  S_AXI_AWVALID,// Write address ready. This signal indicates that the slave is ready// to accept an address and associated control signals.output wire  S_AXI_AWREADY,// Write data (issued by master, acceped by Slave) input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA,// Write strobes. This signal indicates which byte lanes hold// valid data. There is one write strobe bit for each eight// bits of the write data bus.    input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB,// Write valid. This signal indicates that valid write// data and strobes are available.input wire  S_AXI_WVALID,// Write ready. This signal indicates that the slave// can accept the write data.output wire  S_AXI_WREADY,// Write response. This signal indicates the status// of the write transaction.output wire [1 : 0] S_AXI_BRESP,// Write response valid. This signal indicates that the channel// is signaling a valid write response.output wire  S_AXI_BVALID,// Response ready. This signal indicates that the master// can accept a write response.input wire  S_AXI_BREADY,// Read address (issued by master, acceped by Slave)input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR,// Protection type. This signal indicates the privilege// and security level of the transaction, and whether the// transaction is a data access or an instruction access.input wire [2 : 0] S_AXI_ARPROT,// Read address valid. This signal indicates that the channel// is signaling valid read address and control information.input wire  S_AXI_ARVALID,// Read address ready. This signal indicates that the slave is// ready to accept an address and associated control signals.output wire  S_AXI_ARREADY,// Read data (issued by slave)output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA,// Read response. This signal indicates the status of the// read transfer.output wire [1 : 0] S_AXI_RRESP,// Read valid. This signal indicates that the channel is// signaling the required read data.output wire  S_AXI_RVALID,// Read ready. This signal indicates that the master can// accept the read data and response information.input wire  S_AXI_RREADY);// AXI4LITE signalsreg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_awaddr;reg  	axi_awready;reg  	axi_wready;reg [1 : 0] 	axi_bresp;reg  	axi_bvalid;reg [C_S_AXI_ADDR_WIDTH-1 : 0] 	axi_araddr;reg  	axi_arready;reg [C_S_AXI_DATA_WIDTH-1 : 0] 	axi_rdata;reg [1 : 0] 	axi_rresp;reg  	axi_rvalid;// Example-specific design signals// local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH// ADDR_LSB is used for addressing 32/64 bit registers/memories// ADDR_LSB = 2 for 32 bits (n downto 2)// ADDR_LSB = 3 for 64 bits (n downto 3)localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;localparam integer OPT_MEM_ADDR_BITS = 1;//----------------------------------------------//-- Signals for user logic register space example//------------------------------------------------//-- Number of Slave Registers 4reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg0;reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg1;reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg2;reg [C_S_AXI_DATA_WIDTH-1:0]	slv_reg3;wire	 slv_reg_rden;wire	 slv_reg_wren;reg [C_S_AXI_DATA_WIDTH-1:0]	 reg_data_out;integer	 byte_index;reg	 aw_en;// I/O Connections assignmentsassign S_AXI_AWREADY	= axi_awready;assign S_AXI_WREADY	= axi_wready;assign S_AXI_BRESP	= axi_bresp;assign S_AXI_BVALID	= axi_bvalid;assign S_AXI_ARREADY	= axi_arready;assign S_AXI_RDATA	= axi_rdata;assign S_AXI_RRESP	= axi_rresp;assign S_AXI_RVALID	= axi_rvalid;// Implement axi_awready generation// axi_awready is asserted for one S_AXI_ACLK clock cycle when both// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is// de-asserted when reset is low.always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_awready <= 1'b0;aw_en <= 1'b1;end elsebegin    if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)begin// slave is ready to accept write address when // there is a valid write address and write data// on the write address and data bus. This design // expects no outstanding transactions. axi_awready <= 1'b1;aw_en <= 1'b0;endelse if (S_AXI_BREADY && axi_bvalid)beginaw_en <= 1'b1;axi_awready <= 1'b0;endelse           beginaxi_awready <= 1'b0;endend end       // Implement axi_awaddr latching// This process is used to latch the address when both // S_AXI_AWVALID and S_AXI_WVALID are valid. always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_awaddr <= 0;end elsebegin    if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en)begin// Write Address latching axi_awaddr <= S_AXI_AWADDR;endend end       // Implement axi_wready generation// axi_wready is asserted for one S_AXI_ACLK clock cycle when both// S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is // de-asserted when reset is low. always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_wready <= 1'b0;end elsebegin    if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )begin// slave is ready to accept write data when // there is a valid write address and write data// on the write address and data bus. This design // expects no outstanding transactions. axi_wready <= 1'b1;endelsebeginaxi_wready <= 1'b0;endend end       // Implement memory mapped register select and write logic generation// The write data is accepted and written to memory mapped registers when// axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to// select byte enables of slave registers while writing.// These registers are cleared when reset (active low) is applied.// Slave register write enable is asserted when valid address and data are available// and the slave is ready to accept the write address and write data.assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginslv_reg0 <= 0;slv_reg1 <= 0;slv_reg2 <= 0;slv_reg3 <= 0;end else beginif (slv_reg_wren)begincase ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )2'h0:for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )if ( S_AXI_WSTRB[byte_index] == 1 ) begin// Respective byte enables are asserted as per write strobes // Slave register 0slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];end  2'h1:for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )if ( S_AXI_WSTRB[byte_index] == 1 ) begin// Respective byte enables are asserted as per write strobes // Slave register 1slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];end  2'h2:for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )if ( S_AXI_WSTRB[byte_index] == 1 ) begin// Respective byte enables are asserted as per write strobes // Slave register 2slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];end  2'h3:for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )if ( S_AXI_WSTRB[byte_index] == 1 ) begin// Respective byte enables are asserted as per write strobes // Slave register 3slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];end			    default : beginslv_reg0 <= slv_reg0;slv_reg1 <= slv_reg1;slv_reg2 <= slv_reg2;slv_reg3 <= slv_reg3;endendcaseendendend    // Implement write response logic generation// The write response and response valid signals are asserted by the slave // when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted.  // This marks the acceptance of address and indicates the status of // write transaction.always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_bvalid  <= 0;axi_bresp   <= 2'b0;end elsebegin    if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID)begin// indicates a valid write response is availableaxi_bvalid <= 1'b1;axi_bresp  <= 2'b0; // 'OKAY' response end                   // work error responses in futureelsebeginif (S_AXI_BREADY && axi_bvalid) //check if bready is asserted while bvalid is high) //(there is a possibility that bready is always asserted high)   beginaxi_bvalid <= 1'b0; end  endendend   // Implement axi_arready generation// axi_arready is asserted for one S_AXI_ACLK clock cycle when// S_AXI_ARVALID is asserted. axi_awready is // de-asserted when reset (active low) is asserted. // The read address is also latched when S_AXI_ARVALID is // asserted. axi_araddr is reset to zero on reset assertion.always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_arready <= 1'b0;axi_araddr  <= 32'b0;end elsebegin    if (~axi_arready && S_AXI_ARVALID)begin// indicates that the slave has acceped the valid read addressaxi_arready <= 1'b1;// Read address latchingaxi_araddr  <= S_AXI_ARADDR;endelsebeginaxi_arready <= 1'b0;endend end       // Implement axi_arvalid generation// axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both // S_AXI_ARVALID and axi_arready are asserted. The slave registers // data are available on the axi_rdata bus at this instance. The // assertion of axi_rvalid marks the validity of read data on the // bus and axi_rresp indicates the status of read transaction.axi_rvalid // is deasserted on reset (active low). axi_rresp and axi_rdata are // cleared to zero on reset (active low).  always @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_rvalid <= 0;axi_rresp  <= 0;end elsebegin    if (axi_arready && S_AXI_ARVALID && ~axi_rvalid)begin// Valid read data is available at the read data busaxi_rvalid <= 1'b1;axi_rresp  <= 2'b0; // 'OKAY' responseend   else if (axi_rvalid && S_AXI_RREADY)begin// Read data is accepted by the masteraxi_rvalid <= 1'b0;end                endend    // Implement memory mapped register select and read logic generation// Slave register read enable is asserted when valid address is available// and the slave is ready to accept the read address.assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;always @(*)begin// Address decoding for reading registerscase ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )2'h0   : reg_data_out <= slv_reg0;2'h1   : reg_data_out <= {{27{1'b0}}, end_signal, i_done, i_idle, i_read, i_write};2'h2   : reg_data_out <= slv_reg2;2'h3   : reg_data_out <= slv_reg3;default : reg_data_out <= 777;  // To debugendcaseend// Output register or memory read dataalways @( posedge S_AXI_ACLK )beginif ( S_AXI_ARESETN == 1'b0 )beginaxi_rdata  <= 0;end elsebegin    // When there is a valid read address (S_AXI_ARVALID) with // acceptance of read address by the slave (axi_arready), // output the read dada if (slv_reg_rden)beginaxi_rdata <= reg_data_out;     // register read dataend   endend    // Add user logic here// tick gen o_runreg r_run;always @(posedge S_AXI_ACLK) begin if(!S_AXI_ARESETN) begin // sync reset_nr_run <= 1'b0;  end else beginr_run <= slv_reg0[31];end endassign o_run 		= (r_run == 1'b0) && (slv_reg0[DATA_WIDTH_AXI-1] == 1'b1) ; // Posedge 1 tickassign o_num_cnt 	= slv_reg0[DATA_WIDTH_AXI-2:0];assign {dsla_enable, yolo_enable, faster_enable, nms_enable, train_or_test} = slv_reg3[4:0]; //	wire reset_n = S_AXI_ARESETN;
//	wire clk = S_AXI_ACLK;reg r_done; // to keep done status, i_done is a 1 tick.always @(posedge S_AXI_ACLK) beginif(!S_AXI_ARESETN) begin  // sync reset_nr_done <= 1'b0;  end else if (i_done) beginr_done <= 1'b1;end else if (o_run) beginr_done <= 1'b0;end  // else. keep statusend
/*always @(posedge S_AXI_ACLK) begin if(!S_AXI_ARESETN) begin // sync reset_nslv_reg1 <= 32'b0;  end else beginslv_reg1[0] <= i_idle;slv_reg1[1] <= i_read;slv_reg1[2] <= i_write;slv_reg1[3] <= r_done;end end
*/// User logic endsendmodule

其中

......assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;always @(*)begin// Address decoding for reading registerscase ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )2'h0   : reg_data_out <= slv_reg0;2'h1   : reg_data_out <= {{27{1'b0}}, end_signal, i_done, i_idle, i_read, i_write};2'h2   : reg_data_out <= slv_reg2;2'h3   : reg_data_out <= slv_reg3;default : reg_data_out <= 777;  // To debugendcaseend
......

和这段代码

	assign o_run 		= (r_run == 1'b0) && (slv_reg0[DATA_WIDTH_AXI-1] == 1'b1) ; // Posedge 1 tickassign o_num_cnt 	= slv_reg0[DATA_WIDTH_AXI-2:0];assign {dsla_enable, yolo_enable, faster_enable, nms_enable, train_or_test} = slv_reg3[4:0]; 

把一个已经定义好的IP的接口信号怎么关联到axi-lite bus内做了一个样板操作,通过封装为axi-lite类型的IP后和ZYNQ接在一起以后就有了寄存器的偏移地址

随后的验证在sdk层面可以移步我之前写的sdk代码解读。


总结

简单谈了谈指令集和寄存器读写怎么驱动硬件,以及指令集开发的优缺点。详细谈了寄存器读写和指令集开发的流程与验证流程。其实本来还有一堆细节要讲,比如下一个工作应该怎么考虑,but考虑到目前某些部件的硬件代码还不熟悉暂时先搁置,最近在赶进度,后期会分析这个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/38048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应急响应:应急响应流程,常见应急事件及处置思路

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

交通气象站:保障道路畅通的守护者

随着现代社会的飞速发展&#xff0c;交通网络日益密集&#xff0c;人们的出行越来越依赖于公路、铁路和航空等交通方式。然而&#xff0c;多变的天气条件常常给交通安全带来隐患&#xff0c;如大雾、雨雪、强风等恶劣天气不仅影响行车视线&#xff0c;还可能造成路面湿滑、结冰…

第十四届蓝桥杯省赛C++B组D题【飞机降落】题解(AC)

解题思路 这道题目要求我们判断给定的飞机是否都能在它们的油料耗尽之前降落。为了寻找是否存在合法的降落序列&#xff0c;我们可以使用深度优先搜索&#xff08;DFS&#xff09;的方法&#xff0c;尝试所有可能的降落顺序。 首先&#xff0c;我们需要理解题目中的条件。每架…

【MotionCap】pycharm 远程在wsl2 ubuntu20.04中root的miniconda3环境

pycharm wsl2 链接到pycharmsbin 都能看到内容,/root 下内容赋予了zhangbin 所有,pycharm还是看不到/root 下内容。sudo 安装了miniconda3 引发了这些问题 由于是在 root 用户安装的miniconda3 所以安装路径在/root/miniconda3 里 这导致了环境也是root用户的,会触发告警 WA…

冲击试样缺口拉刀V2U2U3U5

拉刀性能介绍 冲击试样缺口拉刀采用进口高速工具钢W18Cr4V材质&#xff0c;特殊工艺精密加工制造&#xff0c;硬度高&#xff0c;耐磨性好&#xff0c;使用寿命长&#xff0c;每把拉刀可加工试样达20&#xff0c;000多个。拉刀共54个齿&#xff08;深度5mm缺口拉刀为74个齿&am…

抖音本地生活服务商条件太高怎么办?低门槛方法来了!

随着本地生活赛道的潜力不断显现&#xff0c;本地生活服务商的数量也在与日俱增。而在所有开通本地生活服务板块的互联网平台中&#xff0c;日活跃用户数约8亿的抖音往往是众多创业者优先考虑的对象&#xff0c;以抖音本地生活服务商如何申请为代表的相关问题也因此常出现在多个…

排序算法(1)之插入排序----直接插入排序和希尔排序

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 排序之插入排序----直接插入排序和希尔排序(1) 收录于专栏【数据结构初阶】 本专栏旨在分享学习数据结构学习的一点学习笔记&#xff0c;欢迎大家在评论区交流讨…

页面加载503 Service Temporarily Unavailable异常

最近发现网页刷新经常503&#xff0c;加载卡主&#xff0c;刷新页面就正常了。 研究之后发现是页面需要的js文件等加载失败了。 再研究之后发现是nginx配置的问题。 我之前为了解决一个漏洞检测到目标主机可能存在缓慢的HTTP拒绝服务攻击 把nginx的连接设置了很多限制&#…

PHP传奇游戏推广信息发布站程序源码带会员发布

这是一个游戏导航网站程序。可以做任何一款游戏的推广发布&#xff0c;会员注册发布&#xff0c;后台审核通过&#xff0c;前台就可以展示&#xff0c;非常不错的游戏发布平台

一个项目学习Vue3---响应式基础

观察下面一段代码&#xff0c;学习响应式基础的全部内容 <template><div><div>将下面的msg属性放到上面来:{{ msg }}</div><button click"count">{{ count }}</button><button click"object.count.value">{{ o…

【Carsim】Carsim2019与Matlab2015b联合仿真测试

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Carsim2019与Matlab2015b联合仿真测试。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c…

python-糖果俱乐部(赛氪OJ)

[题目描述] 为了庆祝“华为杯”的举办&#xff0c;校园中开展了许多有趣的热身小活动。小理听到这个消息非常激动&#xff0c;他赶忙去参加了糖果俱乐部的活动。 该活动的规则是这样的&#xff1a;摊位上有 n 堆糖果&#xff0c;第 i 堆糖果有 ai​ 个&#xff0c;参与的同学可…

面向工业化的多类电子元件自动计数系统测试报告

目录 1、项目描述 2、登录注册测试 2、主界面测试 2.1、在线计数测试 2.2、离线计数测试 2.3、浏览数据测试 1、项目描述 该系统利用机器视觉平台采集电子元件图像&#xff0c;设计并实现了适应不同形态分布的电子元件计数模型&#xff0c;能够快速且准确地进行计数和分类&…

昇思25天学习打卡营第九天|使用静态图加速

背景 提供免费算力支持&#xff0c;有交流群有值班教师答疑的华为昇思训练营进入第九天了。 今天是第九天&#xff0c;前八天的学习内容可以看链接 昇思25天学习打卡营第一天|快速入门 昇思25天学习打卡营第二天|张量 Tensor 昇思25天学习打卡营第三天|数据集Dataset 昇思25天…

高效的向量搜索算法——分层可导航小世界图(HNSW)

最近在接触大模型相关内容&#xff0c;发现一种高效的向量搜索算法HNSW&#xff0c;这里做一下记录。 在之前自己也接触过一段时间的复杂网络&#xff08;网络科学&#xff09;&#xff0c;没想到&#xff0c;将网络科学的思想引入到向量搜索算法中&#xff0c;可以产生令人眼前…

如何实现公网环境远程连接本地局域网宝塔FTP服务远程管理文件

文章目录 前言1. Linux安装Cpolar2. 创建FTP公网地址3. 宝塔FTP服务设置4. FTP服务远程连接小结 5. 固定FTP公网地址6. 固定FTP地址连接 &#x1f4a1;推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。…

Python28-5 k-means算法

k-means 算法介绍 k-means 算法是一种经典的聚类算法&#xff0c;其目的是将数据集分成 ( k ) 个不同的簇&#xff0c;每个簇内的数据点尽可能接近。算法的基本思想是通过反复迭代优化簇中心的位置&#xff0c;使得每个簇内的点与簇中心的距离之和最小。k-means 算法的具体步骤…

S7-1500轴工艺对象105报文安装(硬件目录的支持包 HSP)

S7-1500PLC里硬件组态没法组态到105报文是因为对应的HSP文件没有安装&#xff0c;首先需要安装对应的HSP文件。 1、HSP文件安装 V19版本的HSP安装链接如下 https://download.csdn.net/download/m0_46143730/89503735 2、安装HSP文件 3、需要将博途软件关闭才能完成安装 4、拖…

猫头虎博主全栈前沿AI技术领域矩阵社群

猫头虎博主全栈前沿AI技术领域矩阵社群 &#x1f44b;大家好&#xff0c;我是猫头虎&#xff01;今天我要向大家介绍一个非常重要的社群矩阵——专为全栈前沿AI技术领域的朋友们打造的各种技术交流和资源互助的社群。这些社群不仅能帮助大家快速提升技术水平&#xff0c;还能拓…

【MySQL备份】Percona XtraBackup全量备份实战篇

目录 1. 前言 2.准备工作 2.1.环境信息 2.2.创建备份目录 2.3.配置/etc/my.cnf文件 2.4.授予root用户BACKUP_ADMIN权限 3.全量备份 4.准备备份 5.数据恢复 6.总结 "实战演练&#xff1a;利用Percona XtraBackup执行MySQL全量备份操作详解" 1. 前言 本文…