Gemmini测试test文件chisel源码详解(一)
DMACommandTrackerTest.scala
源码如下:
package gemminiimport scala.collection.mutable.ArrayBufferimport chisel3._
import chisel3.iotesters.{ChiselFlatSpec, PeekPokeTester}class DMACommandTrackerTester(c: DMAReadCommandTracker[UInt]) extends PeekPokeTester(c) {case class AllocatedCmd(id: Int, tag: Int, requestsSent: Int)var max_cycles = 100000000var cmdsToAllocate = 100val cmdIdsAllocated: ArrayBuffer[AllocatedCmd] = ArrayBuffer() // TODO use a map insteadwhile ((cmdsToAllocate > 0 || cmdIdsAllocated.size > 0) && max_cycles > 0) {val can_allocate = cmdsToAllocate > 0 && rnd.nextBoolean()poke(c.io.alloc.valid, can_allocate)val tag = rnd.nextInt(100)poke(c.io.alloc.bits.tag, tag)val can_read_cmd = rnd.nextBoolean()poke(c.io.cmd_completed.ready, can_read_cmd)if (rnd.nextBoolean() || cmdIdsAllocated.size == 0) {poke(c.io.request_returned.valid, false)} else {val cmd_buf_id = rnd.nextInt(cmdIdsAllocated.size)val id = cmdIdsAllocated(cmd_buf_id).idval requestsSent = cmdIdsAllocated(cmd_buf_id).requestsSentif (requestsSent < c.nRequests) {poke(c.io.request_returned.valid, true)poke(c.io.request_returned.bits.cmd_id, id)cmdIdsAllocated(cmd_buf_id) = cmdIdsAllocated(cmd_buf_id).copy(requestsSent=requestsSent+1)} else {poke(c.io.request_returned.valid, false)}}val alloc_fire = can_allocate && peek(c.io.alloc.ready) != 0if (alloc_fire) {cmdsToAllocate -= 1val cmd_id = peek(c.io.alloc.bits.cmd_id).toIntassert(!cmdIdsAllocated.exists(_.id == cmd_id), s"$cmd_id already allocated")cmdIdsAllocated += AllocatedCmd(cmd_id, tag, 0)}val cmd_completed_fire = can_read_cmd && peek(c.io.cmd_completed.valid) != 0if (cmd_completed_fire) {val cmd_id = peek(c.io.cmd_completed.bits.cmd_id).toIntval cmd_buf_id = cmdIdsAllocated.zipWithIndex.collectFirst { case (AllocatedCmd(id, _, _), i) if id == cmd_id => i }.getval tag = peek(c.io.cmd_completed.bits.tag).toIntif (cmdIdsAllocated(cmd_buf_id).tag != tag) {println(s"wrong tag, $tag, ${cmdIdsAllocated(cmd_buf_id).tag}")max_cycles = 0}assert(cmdIdsAllocated(cmd_buf_id).tag == tag, "tag is incorrect")assert(cmdIdsAllocated(cmd_buf_id).requestsSent == c.nRequests, "returned after wrong number of requests returned")cmdIdsAllocated.remove(cmd_buf_id)}step(1)max_cycles -= 1}assert(max_cycles > 0, "reached max_cycles")
}class DMACommandTrackerUnitTest extends ChiselFlatSpec {val testerArgs = Array("--backend-name", "treadle",// "--generate-vcd-output", "on","--target-dir", "test_run_dir/dmacommandtracker")behavior of "DMACommandTracker"it should "work" in {chisel3.iotesters.Driver.execute(testerArgs, () => new DMAReadCommandTracker(2, 16, UInt(29.W))) {c => new DMACommandTrackerTester(c)} should be (true)}
}
这段代码是用于测试 DMAReadCommandTracker 模块的功能和正确性。DMAReadCommandTracker 模块是一个用于跟踪 DMA 读取命令的状态的硬件模块,它可以分配和回收命令 id 和 tag,并在所有请求都返回后输出完成信号。
这段代码包含两个类,DMACommandTrackerTester 和 DMACommandTrackerUnitTest。DMACommandTrackerTester 类是一个用于对 DMAReadCommandTracker 模块进行输入输出操作和断言检查的测试类。
DMACommandTrackerUnitTest 类是一个用于运行 DMACommandTrackerTester 的测试类,它继承自 ChiselFlatSpec 类,并指定了一些测试参数,如后端名称,目标目录等。
这段代码的目的是验证 DMAReadCommandTracker 模块是否能正确地分配和回收命令 id 和 tag,并在所有请求都返回后输出完成信号。
-
DMACommandTrackerTester 类是一个继承自 PeekPokeTester 的测试器类,它接受一个 DMAReadCommandTracker 类型的参数 c。
- AllocatedCmd 是一个 case class,它用于存储已分配命令的 id、tag 和 requestsSent,id是命令编号、tag是标签(记录版本类型等)、requestsSent记录命令的进度。
- max_cycles 是一个整数变量,它表示最大的执行周期数,用于限制测试的时间。
- cmdsToAllocate 是一个整数变量,它表示要分配的命令数量,初始值为 100。
- cmdIdsAllocated 是一个 ArrayBuffer 类型的变量,它用于存储已分配命令的 AllocatedCmd 对象。
- while 循环是测试的主要逻辑,它在 cmdsToAllocate 大于 0 或 cmdIdsAllocated 不为空,并且 max_cycles 大于 0 的条件下执行。循环中包含以下步骤:
- 随机决定是否可以分配新的命令,并将结果赋值给 can_allocate 变量,然后将其 poke 给 c.io.alloc.valid 输入端口。
- 随机生成一个 0 到 99 之间的整数作为 tag,并将其 poke 给 c.io.alloc.bits.tag 输入端口。
- 随机决定是否可以读取已完成的命令,并将结果赋值给 can_read_cmd 变量,然后将其 poke 给 c.io.cmd_completed.ready 输入端口。
- 如果随机决定不返回请求或 cmdIdsAllocated 为空,则将 false poke 给 c.io.request_returned.valid 输入端口;否则,从 cmdIdsAllocated 中随机选择一个已分配命令,获取其 id 和 requestsSent,如果 requestsSent 小于 c.nRequests,则将 true poke 给 c.io.request_returned.valid 输入端口,并将 id poke 给 c.io.request_returned.bits.cmd_id 输入端口,同时将 cmdIdsAllocated 中对应的 AllocatedCmd 对象的 requestsSent 增加 1;否则,将 false poke 给 c.io.request_returned.valid 输入端口。
- 判断是否发生了 alloc_fire 事件,即 can_allocate 为 true 并且 c.io.alloc.ready 输出端口为 true。如果是,则将 cmdsToAllocate 减 1,并从 c.io.alloc.bits.cmd_id 输出端口读取分配的 cmd_id,断言 cmdIdsAllocated 中不存在相同的 id,并将新建的 AllocatedCmd 对象添加到 cmdIdsAllocated 中。
- 判断是否发生了 cmd_completed_fire 事件,即 can_read_cmd 为 true 并且 c.io.cmd_completed.valid 输出端口为 true。如果是,则从 c.io.cmd_completed.bits.cmd_id 和 c.io.cmd_completed.bits.tag 输出端口读取完成的 cmd_id 和 tag,并在 cmdIdsAllocated 中找到对应的 AllocatedCmd 对象和索引。如果对象中的 tag 不等于输出端口中的 tag,则打印错误信息并将 max_cycles 设为 0;否则,断言对象中的 tag 等于输出端口中的 tag,并断言对象中的 requestsSent 等于 c.nRequests,然后从 cmdIdsAllocated 中移除该对象。
- 调用 step(1) 方法执行一个时钟周期,并将 max_cycles 减 1。
- 在 while 循环结束后,断言 max_cycles 大于 0,表示测试没有超时。
-
DMACommandTrackerUnitTest 类是一个继承自 ChiselFlatSpec 的单元测试类,它定义了一些变量和方法。
- testerArgs 是一个字符串数组,它存储了一些测试参数,如后端名称、目标目录等。
- behavior of “DMACommandTracker” 是一个字符串字面量,它表示要测试的类名。
- it should “work” 是一个方法,它表示要测试的功能。方法中包含以下步骤:
- 调用 chisel3.iotesters.Driver.execute 方法,传入 testerArgs 和一个匿名函数,该函数返回一个新建的 DMAReadCommandTracker 对象,其参数为 2、16 和 UInt(29.W)。
- 在 execute 方法的第二个参数中,传入一个匿名函数,该函数接受一个 DMAReadCommandTracker 类型的参数 c,并返回一个新建的 DMACommandTrackerTester 对象,其参数为 c。
- 在 execute 方法的返回值上调用 should be (true) 方法,断言测试结果为 true。
注释版:
package gemminiimport scala.collection.mutable.ArrayBufferimport chisel3._
import chisel3.iotesters.{ChiselFlatSpec, PeekPokeTester}//测试 DMAReadCommandTracker 模块的功能和正确性。
//DMAReadCommandTracker 模块是一个用于跟踪 DMA 读取命令的状态的硬件模块,它可以分配和回收命令 id 和 tag,并在所有请求都返回后输出完成信号。
//包含两个类,DMACommandTrackerTester 和 DMACommandTrackerUnitTest。
//DMACommandTrackerTester 类是一个用于对 DMAReadCommandTracker 模块进行输入输出和断言检查的测试类。
//DMACommandTrackerUnitTest 类是一个用于运行 DMACommandTrackerTester 的测试类
//这段代码的目的是验证 DMAReadCommandTracker 模块是否能正确地分配和回收命令 id 和 tag,并在所有请求都返回后输出完成信号。class DMACommandTrackerTester(c: DMAReadCommandTracker[UInt]) extends PeekPokeTester(c) {
//用于存储已分配命令的 id、tag 和 requestsSent,id是命令编号、tag是标签(记录版本类型等)、requestsSent记录命令的进度。case class AllocatedCmd(id: Int, tag: Int, requestsSent: Int)
//最大的执行周期数var max_cycles = 100000000
//要分配的命令数量,初始值为 100;cmdIdsAllocated存储已分配命令的 AllocatedCmd 对象var cmdsToAllocate = 100val cmdIdsAllocated: ArrayBuffer[AllocatedCmd] = ArrayBuffer() // TODO use a map instead
//循环条件:1.还有剩余的周期数可执行 2.待分配命令数不是0或者已分配命令不为空while ((cmdsToAllocate > 0 || cmdIdsAllocated.size > 0) && max_cycles > 0) {
//当还有命令可以分配的时候,随机决定是否可以分配新的命令,并将结果赋值给 can_allocate 变量,然后将其 poke 给 c.io.alloc.valid 输入端口。val can_allocate = cmdsToAllocate > 0 && rnd.nextBoolean()poke(c.io.alloc.valid, can_allocate)
//随机生成一个 0 到 99 之间的整数作为 tag,并将其 poke 给 c.io.alloc.bits.tag 输入端口。val tag = rnd.nextInt(100)poke(c.io.alloc.bits.tag, tag)
//随机决定是否可以读取已完成的命令,并将结果赋值给 can_read_cmd 变量,然后将其 poke 给 c.io.cmd_completed.ready 输入端口。
//当 can_read_cmd 为 true 时,表示允许读取已完成的命令,即表明模块已准备好接收命令完成的信号。当 can_read_cmd 为 false 时,表示不允许读取已完成的命令,即表明模块不准备接收命令完成的信号。val can_read_cmd = rnd.nextBoolean()poke(c.io.cmd_completed.ready, can_read_cmd)
//如果随机决定不返回请求或 cmdIdsAllocated 为空,则将 false poke 给 c.io.request_returned.valid 输入端口if (rnd.nextBoolean() || cmdIdsAllocated.size == 0) {poke(c.io.request_returned.valid, false)} else {
//从 cmdIdsAllocated 中随机选择一个已分配命令,获取其 id 和 requestsSentval cmd_buf_id = rnd.nextInt(cmdIdsAllocated.size)val id = cmdIdsAllocated(cmd_buf_id).idval requestsSent = cmdIdsAllocated(cmd_buf_id).requestsSent//nRequests 用于表示每个已分配的命令所需的请求次数。在这个上下文中,当已分配的命令的请求次数达到 nRequests 时,该命令将被认为是完成的。
//如果 requestsSent 小于 c.nRequests,则将 true poke 给 c.io.request_returned.valid 输入端口
//将 id poke 给 c.io.request_returned.bits.cmd_id 输入端口,同时将 cmdIdsAllocated 中对应的 AllocatedCmd 对象的 requestsSent 增加 1;否则,将 false poke 给 c.io.request_returned.valid 输入端口。//说白了就是判断这个命令是否完成了,然后作相应操作if (requestsSent < c.nRequests) {poke(c.io.request_returned.valid, true)poke(c.io.request_returned.bits.cmd_id, id)cmdIdsAllocated(cmd_buf_id) = cmdIdsAllocated(cmd_buf_id).copy(requestsSent=requestsSent+1)} else {poke(c.io.request_returned.valid, false)}}
//检查是否可以分配命令,(即 can_allocate 为 true 并且 c.io.alloc.ready 输出端口为 true)
//如果是,则将 cmdsToAllocate 减 1,并从 c.io.alloc.bits.cmd_id 输出端口读取分配的 cmd_id,断言 cmdIdsAllocated 中不存在相同的 id,并将新建的 AllocatedCmd 对象添加到 cmdIdsAllocated 中。val alloc_fire = can_allocate && peek(c.io.alloc.ready) != 0if (alloc_fire) {cmdsToAllocate -= 1val cmd_id = peek(c.io.alloc.bits.cmd_id).toIntassert(!cmdIdsAllocated.exists(_.id == cmd_id), s"$cmd_id already allocated")cmdIdsAllocated += AllocatedCmd(cmd_id, tag, 0)}
//检查是否可以读取完成的命令,(即 can_read_cmd 为 true 且 c.io.cmd_completed.valid 输出端口的值不为 0)
//如果是,则读取id和tag,并在 cmdIdsAllocated 中找到对应的 AllocatedCmd 对象和索引。
//如果原tag与输出端口的tag不同就报错,相同就删除这个命令val cmd_completed_fire = can_read_cmd && peek(c.io.cmd_completed.valid) != 0if (cmd_completed_fire) {val cmd_id = peek(c.io.cmd_completed.bits.cmd_id).toIntval cmd_buf_id = cmdIdsAllocated.zipWithIndex.collectFirst { case (AllocatedCmd(id, _, _), i) if id == cmd_id => i }.getval tag = peek(c.io.cmd_completed.bits.tag).toIntif (cmdIdsAllocated(cmd_buf_id).tag != tag) {println(s"wrong tag, $tag, ${cmdIdsAllocated(cmd_buf_id).tag}")max_cycles = 0}assert(cmdIdsAllocated(cmd_buf_id).tag == tag, "tag is incorrect")assert(cmdIdsAllocated(cmd_buf_id).requestsSent == c.nRequests, "returned after wrong number of requests returned")cmdIdsAllocated.remove(cmd_buf_id)}
//每次循环周期减少step(1)max_cycles -= 1}
//判断有没有超市assert(max_cycles > 0, "reached max_cycles")
}class DMACommandTrackerUnitTest extends ChiselFlatSpec {
//存取测试参数val testerArgs = Array("--backend-name", "treadle",// "--generate-vcd-output", "on","--target-dir", "test_run_dir/dmacommandtracker")
//表示要测试的是 DMACommandTracker 模块behavior of "DMACommandTracker"it should "work" in {chisel3.iotesters.Driver.execute(testerArgs, () => new DMAReadCommandTracker(2, 16, UInt(29.W))) {c => new DMACommandTrackerTester(c)} should be (true)//测试参数与断言测试结果}
}
相关文章:
Gemmini测试test文件chisel源码详解(一)
DMACommandTrackerTest.scala 源码如下: package gemminiimport scala.collection.mutable.ArrayBufferimport chisel3._ import chisel3.iotesters.{ChiselFlatSpec, PeekPokeTester}class DMACommandTrackerTester(c: DMAReadCommandTracker[UInt]) extends Pee…...
RabbitMQ中的手动应答和自动应答
当使用RabbitMQ来处理消息时,消息确认是一个重要的概念。RabbitMQ提供了两种不同的消息确认方式:自动应答(Automatic Acknowledgment)和手动应答(Manual Acknowledgment)。这两种方式适用于不同的应用场景&…...
【C语言】文件的操作与文件函数的使用(详细讲解)
前言:我们在学习C语言的时候会发现在编写一个程序的时候,数据是存在内存当中的,而当我们退出这个程序的时候会发现这个数据不复存在了,因此我们可以通过文件把数据记录下来,使用文件我们可以将数据直接存放在电脑的硬盘…...
ROS-PX4仿真笔记_1
offbord模式测试 rosrun offboard_pkg position stablelize模式 lqr控制器实验 roslaunch px4 fast_test.launch 无人机起飞1.5-2m sh mybot_gazebo.sh#roslaunch px4 fast_racing.launch & sleep 20; roslaunch ego_planner single_run_in_gazebo.launch & sleep 1…...
使用 Python 中的小波变换信号驾驭股票价格的波动
一、简介 股票上涨和下跌,创造出像海浪一样难以预测的模式和走势。然而,就像科学家通过了解下面的水流来预测波浪的运动一样,我们也可以使用类似的工具破译股票市场的一些模式。 通过利用小波变换的力量,我们深入表面,试图揭示驱动股价的深层原因。这段旅程不仅仅涉及数字…...
AndroidStudio模拟器,没有Google Play的就有ROOT权限
正确选择版本 测试 D:\>adb shell emulator64_x86_64:/ $ su emulator64_x86_64:/ #...
复选框 前端代码
表单中复选框选项 <el-form-item label="是否公开:" hidden="true"><input type="checkbox...
每日一练 | 网络工程师软考真题Day41
1、包过滤防火墙对通过防火墙的数据包进行检查,只有满足条件的数据包才能通过,对数据包的检查内容一般不包括 。 A.源地址 B.目的地址 C.协议 D.有效载荷 2、下面关于ARP木马的描述中,错误的…...
vue使用pinia存储数据并保持数据持久化
在Vue中使用Pinia存储数据并保持数据持久化,你可以遵循以下步骤: 安装Pinia:首先,你需要安装Pinia。可以通过npm或yarn来安装它。在终端中运行以下命令: npm install pinia# 或者使用yarn yarn add pinia创建Pinia St…...
k8s - Flannel
1.Flannel概念剖析 Flannel是 CoreOS 团队针对 Kubernetes 设计的一个覆盖网络(Overlay Network)工具,其目的在于帮助每一个使用 Kuberentes 的 CoreOS 主机拥有一个完整的子网。这次的分享内容将从Flannel的介绍、工作原理及安装和配置三方…...
服务器中了balckhoues勒索病毒怎么办?勒索病毒解密,数据恢复
近日,云天数据恢复中心发现,有多位用户的服务器中了一种名为balckhoues的勒索病毒,因为绝大多数用户是第一次遇到这种情况,所以对这种类型的勒索病毒并不是很了解。那接下来我们将对balckhoues勒索病毒做一个分析。 中毒特征 服务…...
react-pdf | Warning: TextLayer styles not found.
问题描述: 使用react-pdf展示pdf,但是报警告,Warning: TextLayer styles not found. 解决方法: <Pageloading{"加载中..."}renderAnnotationLayer{false}renderTextLayer{false}/> 添加属性如上,设…...
vue上传文件MD5加密
1.下载MD5依赖 npm install crypto-js 2.在utils文件夹中新增文件md5方法文件,文件名自定义(fileMd5Sum.js) import CryptoJs from crypto-js export default {// md5值计算fileMd5Sum(file) {let CryptoJS require("crypto-js"…...
vue2 .sync 修饰符
vue2 .sync 修饰符 **创建 工程: H:\java_work\java_springboot\vue_study ctrl按住不放 右键 悬着 powershell H:\java_work\java_springboot\js_study\Vue2_3入门到实战-配套资料\01-随堂代码素材\day04\准备代码\13-sync修饰符 vue --version vue create v-sy…...
使用Tensorrt的一般步骤
使用Tensorrt的一般步骤 TensorRT的使用包括两个阶段:build and deployment。 build:该阶段主要完成模型转换(从caffe或TensorFlow到TensorRT),如下图所示,在模型转换时会完成前述优化过程中的层间融合&am…...
uniapp apple 苹果登录 离线本地打包
官方文档 uni-app官网 文档写的不全,没有写离线打包流程 加lib 签名里带 sign in with apple hbuilder开关 代码 测试代码,获取app里所有的provider uni.getProvider({service: oauth,success: function (res) {console.log(res.provider)uni.showT…...
【数据库】Sql Server数据迁移,处理自增字段赋值
给自己一个目标,然后坚持一段时间,总会有收获和感悟! 在实际项目开发中,如果遇到高版本导入到低版本,或者低版本转高版本,那么就会出现版本不兼容无法导入,此时通过程序遍历创建表和添加数据方式…...
JOSEF约瑟 矿用一般型选择性漏电继电器 LXY2-660 Φ45 JKY1-660
系列型号: JY82A检漏继电器 JY82B检漏继电器 JY82-380/660检漏继电器 JY82-IV检漏继电器 JY82-2P检漏继电器 JY82-2/3检漏继电器 JJKY检漏继电器 JD型检漏继电器 JY82-IV;JY82J JY82-II;JY82-III JY82-1P;JY82-2PA;JY82-2PB JJB-380;JJB-380/660 JD-12…...
DHCP自动分配IP原理
DHCP自动分配IP原理 1.采用UDP通信方式 2.服务器IP:255.255.255.255; 服务器端口:67, 设备接收端口:68 3.设备向服务器发送DISCOVER信息 4.设备收到服务器回应,且解析正确 5.设备向服务器发送REQUEST请求消息 6.设备接…...
读书笔记-《ON JAVA 中文版》-摘要26[第二十三章 注解]
文章目录 第二十三章 注解1. 基本语法1.1 基本语法1.2 定义注解1.3 元注解 2. 编写注解处理器2.1 编写注解处理器2.2 注解元素2.3 默认值限制 3. 使用javac处理注解4. 基于注解的单元测试5. 本章小结 第二十三章 注解 注解(也被称为元数据)为我们在代码…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...
spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
