长刃在手枪出如龙 —— 探索Verilog-AXI开源库

乐活   2024-08-28 12:04   北京  

一个全面的Verilog和SystemVerilog开源库basic_verilog

Github Verilog“兵器谱”第四把交椅:DarkRISCV

3049行RTL Github最高星开源verilog项目:PicoRV32

截止目前的三篇文章中,我们已经盘点了Github Verilog排行榜上的8个开源项目,重点介绍了其中的picorv32、darkriscv和basic_verilog:

那么话不多说,我们还是顺着stars数量往后继续学习,榜单的第9和第10位分别是RTL-to-GDS Flow全流程工具OpenROAD工程和国际半导体大厂ADI的开源代码库:

OpenROAD是一个开源的EDA平台,由The OpenROAD Project提供支持。它旨在为数字IC设计提供一个基础应用,实现从RTL(寄存器传输级)到GDSII(图形数据库系统II)的完整设计流程。OpenROAD的设计理念是提供一个无需人工干预的自动化流程,以加快设计探索和物理设计实现的速度。对于初学者来说,OpenROAD提供了OpenROAD-flow-scripts,这是一个即用型的原型和tapeout流程。此外,OpenROAD还提供了一个图形用户界面,可以帮助用户更直观地理解和控制设计流程。

正好最近不是也在更lawliet的专栏文章:

【芯片设计】从RTL到GDS(一):Introduction

感兴趣的话,正好可以借助这个开源工具进行一下独立自主的设计和尝试全流程跑通,毕竟这个经历也不是谁都能拥有的哈。更多的信息请查阅工程README.md说明文件吧:

https://github.com/The-OpenROAD-Project/OpenROAD

而后面的analogdeviceinc/hdl其实就更没有太多介绍的必要了,毕竟是业内大名鼎鼎公司的开源库:

https://github.com/analogdevicesinc/hdl

Analog Devices Inc.为多种参考设计和原型系统提供了硬件描述语言(HDL)库和项目。这个代码库包含了硬件描述语言代码(Verilog或VHDL)以及所需的Tcl脚本,用于使用赛灵思(Xilinx)和/或英特尔(Intel)工具链创建和构建特定的FPGA示例设计。

于是来到今天内容的主角,榜单上的第11名:Verilog-AXI。真的,敢给自己的开源库起这种名字并且获得高stars的工程,必然是实力颇深的。咱们顺便就看下榜单的第11名~15名分别是什么:

emmm除了verilog-axi外,其他四个项目都是处理器相关其中三个是riscv核处理器,还有一个我们很熟悉的e203。看的出来,现在大家对处理器领域的热情的确很高。


众所周知,对于ASIC以及FPGA设计而言,AXI协议的使用以及相关组件一直扮演着重要的作用,相信没有哪家芯片公司是没有自己的AXI处理与适配公共代码库的。此外呢AXI总线(当然也包括其他的AMBA总线)相关知识考察也总是在各种校招社招面试中被提及。因此通过开源的代码库学习下一些典型操作的实现自然也是非常有意义的事情。
https://github.com/alexforencich/verilog-axi

verilog-axi库本身是一个针对FPGA实现的Verilog组件集合(项目介绍中自述),涵盖了与AXI协议有关从基础的RAM和FIFO到复杂的DMA各类组件。这些组件不仅支持 AXI4 协议,也支持其精简版本 AXI4-Lite,以满足不同设计的需求。尽管其主要为FPGA平台打造,但是作为ASIC的从业者而言也非常的具有学习价值。不过要注意,在阅读中还是会有很多地方有差异,比如FPGA代码中的REG通常会赋初始值,而该操作在ASIC中是极为少见的(甚至是被禁止的)。因此我们更得的去理解学习他的处理操作思路就好,不要直接搬运代码:

reg [CYCLE_COUNT_WIDTH-1:0] output_cycle_count_reg = {CYCLE_COUNT_WIDTH{1'b0}}, output_cycle_count_next;

项目的verilog含量还是可以,55%剩下的Tcl占比为0.9%,这部分主要是约束文件,不过他的约束针对的是vivado平台可能对我们的参考意义不太高Makefile占比3.3%,很好理解项目里为每个module都做了验证环境每个验证环境都是通过Makefile来启动的。

最后Python的占比为41.1%,这是什么原因呢这在于其验证环境是通过python cocotb库中的cocotbext.axi库进行搭建的。使用cocotb进行芯片验证一样是流行很久方法了,但是个人感觉在国内好像铺的不是很广,相较而言老美用的更多些。我也是在20年左右关注了一下这个库,但是后来发现在工作中确实用不到,就没有继续学习。如果有对cocotb感兴趣的小伙伴可以顺带把工程中的验证环境也研究研究:

import itertoolsimport loggingimport osimport random
import cocotb_test.simulatorimport pytest
import cocotbfrom cocotb.clock import Clockfrom cocotb.triggers import RisingEdge, Timerfrom cocotb.regression import TestFactory
from cocotbext.axi import AxiBus, AxiMaster, AxiRam

class TB(object): def __init__(self, dut): self.dut = dut
self.log = logging.getLogger("cocotb.tb") self.log.setLevel(logging.DEBUG)
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
self.axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst) self.axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**16)...

看下工程的目录结构,极其清晰明了。rtl目录中是基于verilog语言的AXI模块,syn目录中是Tcl时序约束文件,tb中是每个模块的验证环境。

然后呢咱们一起看下代码,通过前面的推送大家也能看得出,我评价代码的好坏一般就从注释、对齐、易读性、代码风格等几个方面看,主要就是评估下可读性如何。而verilog-axi库的对齐注释和代码风格还是很不错的,每个文件上来都是语言版本说明和timescale设置

// Language: Verilog 2001
`resetall`timescale 1ns / 1ps`default_nettype none

而后每个参数的作用都有注释:

module axi_cdma #(    // Width of data bus in bits    parameter AXI_DATA_WIDTH = 32,    // Width of address bus in bits    parameter AXI_ADDR_WIDTH = 16,    // Width of wstrb (width of data bus in words)    parameter AXI_STRB_WIDTH = (AXI_DATA_WIDTH/8),    // Width of AXI ID signal    parameter AXI_ID_WIDTH = 8,    // Maximum AXI burst length to generate    parameter AXI_MAX_BURST_LEN = 16,    // Width of length field    parameter LEN_WIDTH = 20,    // Width of tag field    parameter TAG_WIDTH = 8,    // Enable support for unaligned transfers    parameter ENABLE_UNALIGNED = 0)

接口以及其他代码对齐工整:

(    input  wire                       clk,    input  wire                       rst,
/* * AXI descriptor input */ input wire [AXI_ADDR_WIDTH-1:0] s_axis_desc_read_addr, input wire [AXI_ADDR_WIDTH-1:0] s_axis_desc_write_addr, input wire [LEN_WIDTH-1:0] s_axis_desc_len, input wire [TAG_WIDTH-1:0] s_axis_desc_tag, input wire s_axis_desc_valid, output wire s_axis_desc_ready,
/* * AXI descriptor status output */ output wire [TAG_WIDTH-1:0] m_axis_desc_status_tag, output wire [3:0] m_axis_desc_status_error, output wire m_axis_desc_status_valid,
localparam [1:0]    AXI_RESP_OKAY = 2'b00,    AXI_RESP_EXOKAY = 2'b01,    AXI_RESP_SLVERR = 2'b10,    AXI_RESP_DECERR = 2'b11;
localparam [3:0] DMA_ERROR_NONE = 4'd0, DMA_ERROR_TIMEOUT = 4'd1, DMA_ERROR_PARITY = 4'd2, DMA_ERROR_AXI_RD_SLVERR = 4'd4,

整个模块的区域划分也是一种比较经典的划分方式:

module axi_cdma #...-input--output--parameter--localparam--reg--wire--assign--always-

我之前写RTL时也喜欢这种区域划分的方式,不过后面转变代码风格之后就主要以寄存器描述为骨骼搭建代码了,当然这种方式还是很不错的。

而下面项目的代码风格不是我很能接收的地方,但是就像前面说的,不排除是FPGA和ASIC RTL的组织方式天然就有区别,而这个库是为FPGA平台搭建的。模块中的主要逻辑多用状态机,以及大的always@* block和always@clk block,其中把众多的线和寄存器汇聚在一起处理,层次一般比较深:

always @* begin    state_next = STATE_IDLE;
id_next = id_reg; addr_next = addr_reg; data_next = data_reg; resp_next = resp_reg; ruser_next = ruser_reg; burst_next = burst_reg; burst_size_next = burst_size_reg; master_burst_next = master_burst_reg; master_burst_size_next = master_burst_size_reg;
s_axi_arready_next = 1'b0; m_axi_arid_next = m_axi_arid_reg; m_axi_araddr_next = m_axi_araddr_reg; m_axi_arlen_next = m_axi_arlen_reg; m_axi_arsize_next = m_axi_arsize_reg; m_axi_arburst_next = m_axi_arburst_reg; m_axi_arlock_next = m_axi_arlock_reg; m_axi_arcache_next = m_axi_arcache_reg; m_axi_arprot_next = m_axi_arprot_reg; m_axi_arqos_next = m_axi_arqos_reg; m_axi_arregion_next = m_axi_arregion_reg; m_axi_aruser_next = m_axi_aruser_reg; m_axi_arvalid_next = m_axi_arvalid_reg && !m_axi_arready; m_axi_rready_next = 1'b0;
if (SEGMENT_COUNT == 1) begin // master output is same width; direct transfer with no splitting/merging s_axi_rid_int = id_reg; s_axi_rdata_int = m_axi_rdata; s_axi_rresp_int = m_axi_rresp; s_axi_rlast_int = m_axi_rlast; s_axi_ruser_int = m_axi_ruser; s_axi_rvalid_int = 0;
case (state_reg) STATE_IDLE: begin // idle state; wait for new burst s_axi_arready_next = !m_axi_arvalid;
if (s_axi_arready && s_axi_arvalid) begin s_axi_arready_next = 1'b0; id_next = s_axi_arid; m_axi_arid_next = s_axi_arid; m_axi_araddr_next = s_axi_araddr; m_axi_arlen_next = s_axi_arlen; m_axi_arsize_next = s_axi_arsize; m_axi_arburst_next = s_axi_arburst; m_axi_arlock_next = s_axi_arlock; m_axi_arcache_next = s_axi_arcache; m_axi_arprot_next = s_axi_arprot; m_axi_arqos_next = s_axi_arqos; m_axi_arregion_next = s_axi_arregion;
always @(posedge clk) begin    rd_ptr_reg <= rd_ptr_next;    rd_addr_reg <= rd_ptr_next;
mem_read_data_valid_reg <= mem_read_data_valid_next;
if (read) begin mem_read_data_reg <= mem[rd_addr_reg[FIFO_ADDR_WIDTH-1:0]]; end
if (rst) begin rd_ptr_reg <= {FIFO_ADDR_WIDTH+1{1'b0}}; mem_read_data_valid_reg <= 1'b0; endend

所以可能对于一部分像我这样的人来讲,阅读起来会有一定的困难。这点大家如果真的有需求的话,就只好克服一下了。


项目中包含了以下这些常用模块:
axi_adapter:
AXI宽度适配器模块,具有可参数化的数据和地址接口宽度。支持INCR突发类型和窄突发。是axi_adapter_rd和axi_adapter_wr的包装器。
axi_adapter_rd:
AXI宽度适配器模块,具有可参数化的数据和地址接口宽度。支持INCR突发类型和窄突发。
axi_adapter_wr:
AXI宽度适配器模块,具有可参数化的数据和地址接口宽度。支持INCR突发类型和窄突发。
axi_axil_adapter:
AXI到AXI Lite转换器和宽度适配器模块,具有可参数化的数据和地址接口宽度。支持INCR突发类型和窄突发。是axi_axil_adapter_rd和axi_axil_adapter_wr的包装器。
axi_axil_adapter_rd:
AXI到AXI Lite转换器和宽度适配器模块,具有可参数化的数据和地址接口宽度。支持INCR突发类型和狭窄突发。
axi_axil_adapter_wr
AXI到AXI Lite转换器和宽度适配器模块,具有可参数化的数据和地址接口宽度。支持INCR突发类型和窄突发。
axi_cdma
AXI到AXI CDMA,具有可参数化的数据和地址接口宽度。只生成全宽INCR突发,最大突发长度可参数化。支持非对齐传输,可以通过参数配置以节省资源消耗。
axi_cdma_desc_mux
AXI CDMA模块的多路复用器/解复用器。允许在多个请求源之间共享AXI DMA模块,交错请求并分发响应。
axi_crossbar
AXI非阻塞交叉互,具有可参数化的数据和地址接口宽度以及主从接口数量。支持所有突发类型。完全非阻塞,具有完全独立的读写路径;基于ID的事务排序保护逻辑;每个端口的地址解码、准入控制和解码错误处理。是axi_crossbar_rd和axi_crossbar_wr的包装器。
可以使用axi_crossbar_wrap.py生成包装器。
axi_crossbar_addr
AXI非阻塞交叉互联的地址解码和准入控制模块。
axi_crossbar_rd
AXI非阻塞交叉互联,具有可参数化的数据和地址接口宽度以及主从接口数量。仅限读接口。支持所有突发类型。完全非阻塞,具有完全独立的读写路径;基于ID的事务排序保护逻辑;每个端口的地址解码、准入控制和解码错误处理。
axi_crossbar_wr
AXI非阻塞交叉互联,具有可参数化的数据和地址接口宽度以及主从接口数量。仅限写接口。支持所有突发类型。完全非阻塞,具有完全独立的读写路径;基于ID的事务排序保护逻辑;每个端口的地址解码、准入控制和解码错误处理。
axi_dma
AXI到AXI DMA,具有可参数化的数据和地址接口宽度。只生成全宽INCR突发,最大突发长度可参数化。支持非对齐传输,可以通过参数配置以节省资源消耗。是axi_dma_rd和axi_dma_wr的包装器。
axi_dma_desc_mux
AXI DMA模块的多路复用器/解复用器。允许在多个请求源之间共享AXI DMA模块,交错请求并分发响应。
axi_dma_rd
AXI到AXI DMA子模块,具有可参数化的数据和地址接口宽度。只生成全宽INCR突发,最大突发长度可参数化。支持对齐传输,可以通过参数配置以节省资源消耗。
axi_dma_wr
AXI到AXI DMA子模,具有可参数化的数据和地址接口宽度。只生成全宽INCR突发,最大突发长度可参数化。支持对齐传输,可以通过参数配置以节省资源消耗。
axi_dp_ram
AXI双端口RAM,具有可参数化的数据和地址接口宽度。支持FIXED和INCR突发类型以及窄突发。
axi_fifo
AXI FIFO,具有可参数化的数据和地址接口宽度。支持所有突发类型。可以选择性地延迟地址通道,直到写数据完全移位到FIFO中,或者读数据FIFO有足够的容量容纳整个突发。是axi_fifo_rd和axi_fifo_wr的包装器。
axi_fifo_rd
AXI FIFO,具有可参数化的数据和地址接口宽度。仅限AR和R通道。支持所有突发类型。可以选择性地延迟地址通道,直到读数据FIFO为空或者有足够的容量容纳整个突发。
axi_fifo_wr
AXI FIFO,具有可参数化的数据和地址接口宽度。仅限WR、W和B通道。支持所有突发类型。可以选择性地延迟地址通道,直到写数据完全移位到写数据FIFO中,或者当前突发完全填满写数据FIFO。
axi_interconnect
AXI共享互连,具有可参数化的数据和地址接口宽度以及主从接口数量。支持所有突发类型。面积小,但不支持并发操作。
可以使用axi_interconnect_wrap.py生成包装器。
axi_ram
AXI RAM,具有可参数化的数据和地址接口宽度。支持FIXED和INCR突发类型以及窄突发。
axi_ram_rd_if
AXI RAM读接口,具有可参数化的数据和地址接口宽度。处理突发并提供一个简化的内部存储器接口。支持FIXED和INCR突发类型以及窄突发。
axi_ram_wr_if
AXI RAM写接口,具有可参数化的数据和地址接口宽度。处理突发并提供一个简化的内部存储器接口。支持FIXED和INCR突发类型以及窄突发。
axi_ram_wr_rd_if
AXI RAM读写接口,具有可参数化的数据和地址接口宽度。处理突发并提供一个简化的内部存储器接口。支持FIXED和INCR突发类型以及窄突发。是axi_ram_rd_if和axi_ram_wr_if的包装器。
axi_register
AXI寄存器,具有可参数化的数据和地址接口宽度。支持所有突发类型。在所有通道中插入简单缓冲区或滑移缓冲区。通道寄存器类型可以单独更改或绕过。是axi_register_rd和axi_register_wr的包装器。
axi_register_rd
AXI寄存器,具有可参数化的数据和地址接口宽度。仅限AR和R通道。支持所有突发类型。在所有通道中插入简单缓冲区或滑移缓冲区。通道寄存器类型可以单独更改或绕过。
axi_register_wr
AXI寄存器,具有可参数化的数据和地址接口宽度。仅限WR、W和B通道。支持所有突发类型。在所有通道中插入简单缓冲区或滑移缓冲区。通道寄存器类型可以单独更改或绕过。
axil_adapter
AXI Lite宽度适配器模块,具有可参数化的数据和地址接口宽度。是axi_adapter_rd和axi_adapter_wr的包装器。
axil_adapter_rd
AXI Lite宽度适配器模块,具有可参数化的数据和地址接口宽度。
axil_adapter_wr
AXI Lite宽度适配器模块,具有可参数化的数据和地址接口宽度。
axil_cdc
AXI Lite时钟域交叉模块,具有可参数化的数据和地址接口宽度。是axi_cdc_rd和axi_cdc_wr的包装器。
axil_cdc_rd
AXI Lite时钟域交叉模块,具有可参数化的数据和地址接口宽度。
axil_cdc_wr
AXI Lite时钟域交叉模块,具有可参数化的数据和地址接口宽度。
axil_crossbar
AXI Lite非阻塞交叉互联,具有可参数化的数据和地址接口宽度以及主从接口数量。完全非阻塞,具有完全独立的读写路径;基于FIFO的事务排序保护逻辑;每个端口的地址解码、准入控制和解码错误处理。是axil_crossbar_rd和axil_crossbar_wr的包装器。
可以使用axil_crossbar_wrap.py生成包装器。
axil_crossbar_addr
AXI Lite非阻塞交叉互联的地址解码和准入控制模块。
axil_crossbar_rd
AXI Lite非阻塞交叉互联,具有可参数化的数据和地址接口宽度以及主从接口数量。仅限读接口。完全非阻塞,具有完全独立的读写路径;基于FIFO的事务排序保护逻辑;每个端口的地址解码、准入控制和解码错误处理。
axil_crossbar_wr
AXI Lite非阻塞交叉互联,具有可参数化的数据和地址接口宽度以及主从接口数量。仅限写接口。完全非阻塞,具有完全独立的读写路径;基于FIFO的事务排序保护逻辑;每个端口的地址解码、准入控制和解码错误处理。
axil_interconnect
AXI Lite共享互连,具有可参数化的数据和地址接口宽度以及主从接口数量。面积小,但不支持并发操作。
可以使用axil_interconnect_wrap.py生成包装器。
axil_ram
AXI LiteRAM,具有可参数化的数据和地址接口宽度。
axil_reg_if
AXI Lite寄存器接口,具有可参数化的数据和地址接口宽度。可以用来在多个模块和层级上组装一组控制寄存器,而不需要复杂的仲裁逻辑。是axil_reg_if_rd和axil_reg_if_wr的包装器。
axil_reg_if_rd
AXI Lite寄存器接口,具有可参数化的数据和地址接口宽度。仅限读方向。可以用来在多个模块和层级上组装一组控制寄存器,而不需要复杂的仲裁逻辑。
axil_reg_if_wr
AXI Lite寄存器接口,具有可参数化的数据和地址接口宽度。仅限写方向。可以用来在多个模块和层级上组装一组控制寄存器,而不需要复杂的仲裁逻辑。
axil_register
AXI Lite寄存器,具有可参数化的数据和地址接口宽度。在所有通道中插入滑移缓冲区。通道寄存器可以单独绕过。是axil_register_rd和axil_register_wr的包装器。
axil_register_rd
AXI Lite寄存器,具有可参数化的数据和地址接口宽度。仅限AR和R通道。在所有通道中插入简单缓冲区。通道寄存器可以单独绕过。
axil_register_wr
AXI Lite寄存器,具有可参数化的数据和地址接口宽度。仅限WR、W和B通道。在所有通道中插入简单缓冲区。通道寄存器可以单独绕过。

系列文章入口——

【芯片设计】SoC 101(一):绪论
【芯片设计】FIFO漫谈(零)从无处不在的FIFO开始说起
【芯片设计】计算机体系结构(一)虚拟内存

【芯片设计】深入理解AMBA总线(零)绪论

【芯片设计】握手协议的介绍与时序说明
【芯片设计】复位那些小事 —— 复位消抖
【芯片设计】快速入门数字芯片设计(一)Introduction
【芯片验证】UVM源码计划(零)下定决心读源码前的自测环节
【芯片设计】异步电路碎碎念(一) 到底什么是异步电路
【芯片设计】从RTL到GDS(一):Introduction
其他文章链接——
【芯片验证】sva_assertion: 15道助力飞升的断言练习
【芯片验证】可能是RTL定向验证的巅峰之作
【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起
【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析
【芯片设计】verilog中有符号数和无符号数的本质探究
【芯片设计】论RTL中always语法的消失术
【芯片设计】代码即注释,注释即代码
【芯片设计】700行代码的risc处理器你确实不能要求太多了
入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢
如何计算系统的outstanding 和 burst length?
芯片搬砖日常·逼死强迫症的关键词不对齐事件
熟人社会里,一群没有社会价值的局外人





































芯时代青年
专心数字前端全流程,芯时代有为青年的自我修养
 最新文章