├── .gitignore ├── .gitlab-ci.yml ├── AUTHORS ├── LICENSE ├── README.md ├── build.sbt ├── build.sh ├── project ├── build.properties └── plugins.sbt ├── reports ├── .gitignore ├── .latexmkrc ├── assets │ ├── bd.png │ ├── board.jpg │ ├── nf.jpg │ └── overall-pic.jpg └── presentation.tex ├── scalastyle-config.xml ├── scalastyle-test-config.xml ├── scripts ├── cuckoo.c └── cuckoo_simulator.py ├── src ├── main │ └── scala │ │ ├── acceptor │ │ └── acceptor.scala │ │ ├── adapter │ │ └── adapter.scala │ │ ├── arp │ │ ├── arp.scala │ │ └── result.scala │ │ ├── cuckoo │ │ └── cuckoo.scala │ │ ├── data │ │ ├── arp.scala │ │ ├── axi.scala │ │ ├── axis.scala │ │ ├── eth.scala │ │ ├── ip.scala │ │ ├── packet.scala │ │ ├── pactype.scala │ │ └── status.scala │ │ ├── encoder │ │ └── encoder.scala │ │ ├── forward │ │ ├── cuckoo.scala │ │ ├── linear.scala │ │ └── result.scala │ │ ├── main.scala │ │ ├── router │ │ ├── ctrl.scala │ │ ├── router.scala │ │ └── top.scala │ │ ├── transmitter │ │ └── transmitter.scala │ │ └── util │ │ ├── asyncBridge.scala │ │ ├── config.scala │ │ ├── consts.scala │ │ └── crc.scala └── test │ └── scala │ ├── acceptor.scala │ ├── cuckoo.scala │ ├── forward │ └── linear.scala │ ├── main.scala │ └── top.scala └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project Specific stuff 2 | test_run_dir/* 3 | ### XilinxISE template 4 | # intermediate build files 5 | *.bgn 6 | *.bit 7 | *.bld 8 | *.cmd_log 9 | *.drc 10 | *.ll 11 | *.lso 12 | *.msd 13 | *.msk 14 | *.ncd 15 | *.ngc 16 | *.ngd 17 | *.ngr 18 | *.pad 19 | *.par 20 | *.pcf 21 | *.prj 22 | *.ptwx 23 | *.rbb 24 | *.rbd 25 | *.stx 26 | *.syr 27 | *.twr 28 | *.twx 29 | *.unroutes 30 | *.ut 31 | *.xpi 32 | *.xst 33 | *_bitgen.xwbt 34 | *_envsettings.html 35 | *_map.map 36 | *_map.mrp 37 | *_map.ngm 38 | *_map.xrpt 39 | *_ngdbuild.xrpt 40 | *_pad.csv 41 | *_pad.txt 42 | *_par.xrpt 43 | *_summary.html 44 | *_summary.xml 45 | *_usage.xml 46 | *_xst.xrpt 47 | 48 | # project-wide generated files 49 | *.gise 50 | par_usage_statistics.html 51 | usage_statistics_webtalk.html 52 | webtalk.log 53 | webtalk_pn.xml 54 | 55 | # generated folders 56 | iseconfig/ 57 | xlnx_auto_0_xdb/ 58 | xst/ 59 | _ngo/ 60 | _xmsgs/ 61 | ### Eclipse template 62 | *.pydevproject 63 | .metadata 64 | .gradle 65 | bin/ 66 | tmp/ 67 | *.tmp 68 | *.bak 69 | *.swp 70 | *~.nib 71 | local.properties 72 | .settings/ 73 | .loadpath 74 | 75 | # Eclipse Core 76 | .project 77 | 78 | # External tool builders 79 | .externalToolBuilders/ 80 | 81 | # Locally stored "Eclipse launch configurations" 82 | *.launch 83 | 84 | # CDT-specific 85 | .cproject 86 | 87 | # JDT-specific (Eclipse Java Development Tools) 88 | .classpath 89 | 90 | # Java annotation processor (APT) 91 | .factorypath 92 | 93 | # PDT-specific 94 | .buildpath 95 | 96 | # sbteclipse plugin 97 | .target 98 | 99 | # TeXlipse plugin 100 | .texlipse 101 | ### C template 102 | # Object files 103 | *.o 104 | *.ko 105 | *.obj 106 | *.elf 107 | 108 | # Precompiled Headers 109 | *.gch 110 | *.pch 111 | 112 | # Libraries 113 | *.lib 114 | *.a 115 | *.la 116 | *.lo 117 | 118 | # Shared objects (inc. Windows DLLs) 119 | *.dll 120 | *.so 121 | *.so.* 122 | *.dylib 123 | 124 | # Executables 125 | *.exe 126 | *.out 127 | *.app 128 | *.i*86 129 | *.x86_64 130 | *.hex 131 | 132 | # Debug files 133 | *.dSYM/ 134 | ### SBT template 135 | # Simple Build Tool 136 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 137 | 138 | target/ 139 | lib_managed/ 140 | src_managed/ 141 | project/boot/ 142 | .history 143 | .cache 144 | ### Emacs template 145 | # -*- mode: gitignore; -*- 146 | *~ 147 | \#*\# 148 | /.emacs.desktop 149 | /.emacs.desktop.lock 150 | *.elc 151 | auto-save-list 152 | tramp 153 | .\#* 154 | 155 | # Org-mode 156 | .org-id-locations 157 | *_archive 158 | 159 | # flymake-mode 160 | *_flymake.* 161 | 162 | # eshell files 163 | /eshell/history 164 | /eshell/lastdir 165 | 166 | # elpa packages 167 | /elpa/ 168 | 169 | # reftex files 170 | *.rel 171 | 172 | # AUCTeX auto folder 173 | /auto/ 174 | 175 | # cask packages 176 | .cask/ 177 | ### Vim template 178 | [._]*.s[a-w][a-z] 179 | [._]s[a-w][a-z] 180 | *.un~ 181 | Session.vim 182 | .netrwhist 183 | *~ 184 | ### JetBrains template 185 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 186 | 187 | *.iml 188 | 189 | ## Directory-based project format: 190 | .idea/ 191 | # if you remove the above rule, at least ignore the following: 192 | 193 | # User-specific stuff: 194 | # .idea/workspace.xml 195 | # .idea/tasks.xml 196 | # .idea/dictionaries 197 | 198 | # Sensitive or high-churn files: 199 | # .idea/dataSources.ids 200 | # .idea/dataSources.xml 201 | # .idea/sqlDataSources.xml 202 | # .idea/dynamic.xml 203 | # .idea/uiDesigner.xml 204 | 205 | # Gradle: 206 | # .idea/gradle.xml 207 | # .idea/libraries 208 | 209 | # Mongo Explorer plugin: 210 | # .idea/mongoSettings.xml 211 | 212 | ## File-based project format: 213 | *.ipr 214 | *.iws 215 | 216 | ## Plugin-specific files: 217 | 218 | # IntelliJ 219 | /out/ 220 | 221 | # mpeltonen/sbt-idea plugin 222 | .idea_modules/ 223 | 224 | # JIRA plugin 225 | atlassian-ide-plugin.xml 226 | 227 | # Crashlytics plugin (for Android Studio and IntelliJ) 228 | com_crashlytics_export_strings.xml 229 | crashlytics.properties 230 | crashlytics-build.properties 231 | ### C++ template 232 | # Compiled Object files 233 | *.slo 234 | *.lo 235 | *.o 236 | *.obj 237 | 238 | # Precompiled Headers 239 | *.gch 240 | *.pch 241 | 242 | # Compiled Dynamic libraries 243 | *.so 244 | *.dylib 245 | *.dll 246 | 247 | # Fortran module files 248 | *.mod 249 | 250 | # Compiled Static libraries 251 | *.lai 252 | *.la 253 | *.a 254 | *.lib 255 | 256 | # Executables 257 | *.exe 258 | *.out 259 | *.app 260 | ### OSX template 261 | .DS_Store 262 | .AppleDouble 263 | .LSOverride 264 | 265 | # Icon must end with two \r 266 | Icon 267 | 268 | # Thumbnails 269 | ._* 270 | 271 | # Files that might appear in the root of a volume 272 | .DocumentRevisions-V100 273 | .fseventsd 274 | .Spotlight-V100 275 | .TemporaryItems 276 | .Trashes 277 | .VolumeIcon.icns 278 | 279 | # Directories potentially created on remote AFP share 280 | .AppleDB 281 | .AppleDesktop 282 | Network Trash Folder 283 | Temporary Items 284 | .apdisk 285 | ### Xcode template 286 | # Xcode 287 | # 288 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 289 | 290 | ## Build generated 291 | build/ 292 | DerivedData 293 | 294 | ## Various settings 295 | *.pbxuser 296 | !default.pbxuser 297 | *.mode1v3 298 | !default.mode1v3 299 | *.mode2v3 300 | !default.mode2v3 301 | *.perspectivev3 302 | !default.perspectivev3 303 | xcuserdata 304 | 305 | ## Other 306 | *.xccheckout 307 | *.moved-aside 308 | *.xcuserstate 309 | ### Scala template 310 | *.class 311 | *.log 312 | 313 | # sbt specific 314 | .cache 315 | .history 316 | .lib/ 317 | dist/* 318 | target/ 319 | lib_managed/ 320 | src_managed/ 321 | project/boot/ 322 | project/plugins/project/ 323 | 324 | # Scala-IDE specific 325 | .scala_dependencies 326 | .worksheet 327 | ### Java template 328 | *.class 329 | 330 | # Mobile Tools for Java (J2ME) 331 | .mtj.tmp/ 332 | 333 | # Package Files # 334 | *.jar 335 | *.war 336 | *.ear 337 | 338 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 339 | hs_err_pid* 340 | 341 | # Bloop & Metal 342 | /.bloop 343 | /.metals 344 | /project/.bloop -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | key: ${CI_JOB_NAME} 3 | paths: 4 | - "sbt-cache/.ivy/cache" 5 | - "sbt-cache/.boot" 6 | - "sbt-cache/.sbtboot" 7 | - "sbt-cache/target" 8 | 9 | variables: 10 | GIT_SUBMODULE_STRATEGY: none 11 | SBT_VERSION: "0.13.9" 12 | SBT_OPTS: "-Dsbt.global.base=sbt-cache/.sbtboot -Dsbt.boot.directory=sbt-cache/.boot -Dsbt.ivy.home=sbt-cache/.ivy -Dsbt.repository.config=.gitlab-ci/repositories -Dsbt.override.build.repos=true" 13 | 14 | stages: 15 | - test 16 | 17 | test: 18 | stage: test 19 | image: chisel 20 | script: 21 | - chmod +x ./test.sh 22 | - ./test.sh 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ao Shen 2 | Chenyao Lou 3 | Linkai Zheng 4 | Xiaoyi Liu 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MeowRouter developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MeowRouter 2 | ======================= 3 | 4 | > Routing for cats 5 | 6 | MeowRouter is a hardware accelerated IP packet forwarder running on programmable ICs. MeowRouter utilizes the Cuckoo hashing algorithm, and supports external memory accesses through AXI interface, so it is capable of handling large amount of network traffic / pps with huge routing tables. 7 | 8 | This repository contains the RTL source code for the data plane, and is intended to be used alongside an CPU. See [MeowRouter-top](https://github.com/meow-chip/MeowRouter-top) for an example. 9 | 10 | ## Authors 11 | 12 | See `AUTHORS` file 13 | 14 | ## Directory Structure 15 | 16 | MeowRouter's directory structure follows the Scala convention, which is: 17 | - `src/main/scala` contains all the source code 18 | - `src/main/test` contains all the unit tests 19 | 20 | `src/main/scala/router.scala` contains the source for the main router module 21 | Source code is divided into the following components: 22 | - `acceptor`: Packet receptor + parser 23 | - `data`: Shared data definations 24 | - `arp`: ARP cache/matcher 25 | - `forward`: Forwarding table 26 | - `encoder`: Packet serializer 27 | - `transmitter`: Packet transmitter 28 | - `adapter`: CPU rx/tx buf 29 | 30 | ## License 31 | All code under this repository is released under the MIT license. See `LICENSE` file. 32 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | // See README.md for license details. 2 | 3 | def scalacOptionsVersion(scalaVersion: String): Seq[String] = { 4 | Seq() ++ { 5 | // If we're building with Scala > 2.11, enable the compile option 6 | // switch to support our anonymous Bundle definitions: 7 | // https://github.com/scala/bug/issues/10047 8 | CrossVersion.partialVersion(scalaVersion) match { 9 | case Some((2, scalaMajor: Long)) if scalaMajor < 12 => Seq() 10 | case _ => Seq("-Xsource:2.11") 11 | } 12 | } 13 | } 14 | 15 | def javacOptionsVersion(scalaVersion: String): Seq[String] = { 16 | Seq() ++ { 17 | // Scala 2.12 requires Java 8. We continue to generate 18 | // Java 7 compatible code for Scala 2.11 19 | // for compatibility with old clients. 20 | CrossVersion.partialVersion(scalaVersion) match { 21 | case Some((2, scalaMajor: Long)) if scalaMajor < 12 => 22 | Seq("-source", "1.7", "-target", "1.7") 23 | case _ => 24 | Seq("-source", "1.8", "-target", "1.8") 25 | } 26 | } 27 | } 28 | 29 | name := "meow-router" 30 | 31 | version := "0.0.1" 32 | 33 | scalaVersion := "2.11.12" 34 | 35 | crossScalaVersions := Seq("2.11.12", "2.12.4") 36 | 37 | resolvers ++= Seq( 38 | Resolver.sonatypeRepo("snapshots"), 39 | Resolver.sonatypeRepo("releases") 40 | ) 41 | 42 | // Provide a managed dependency on X if -DXVersion="" is supplied on the command line. 43 | val defaultVersions = Map( 44 | "chisel3" -> "3.2.+", 45 | "chisel-iotesters" -> "1.3.+" 46 | ) 47 | 48 | libraryDependencies ++= Seq("chisel3","chisel-iotesters").map { 49 | dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) } 50 | 51 | scalacOptions ++= scalacOptionsVersion(scalaVersion.value) 52 | 53 | javacOptions ++= javacOptionsVersion(scalaVersion.value) 54 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sbt 'runMain Main --target-dir build --top-name Top --no-dce' -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.1.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /reports/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !assets 3 | !*.tex 4 | !.gitignore 5 | !.latexmkrc 6 | -------------------------------------------------------------------------------- /reports/.latexmkrc: -------------------------------------------------------------------------------- 1 | $pdf_mode = 1; 2 | $dvi_mode = $postscript_mode = 0; 3 | $pdflatex = "xelatex %O %S"; 4 | @default_files = ('presentation.tex'); 5 | -------------------------------------------------------------------------------- /reports/assets/bd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meow-chip/MeowRouter/1b7d05305ad85a455bca37b34b8b6cad967e14a7/reports/assets/bd.png -------------------------------------------------------------------------------- /reports/assets/board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meow-chip/MeowRouter/1b7d05305ad85a455bca37b34b8b6cad967e14a7/reports/assets/board.jpg -------------------------------------------------------------------------------- /reports/assets/nf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meow-chip/MeowRouter/1b7d05305ad85a455bca37b34b8b6cad967e14a7/reports/assets/nf.jpg -------------------------------------------------------------------------------- /reports/assets/overall-pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meow-chip/MeowRouter/1b7d05305ad85a455bca37b34b8b6cad967e14a7/reports/assets/overall-pic.jpg -------------------------------------------------------------------------------- /reports/presentation.tex: -------------------------------------------------------------------------------- 1 | \documentclass[UTF-8]{ctexbeamer} 2 | \usetheme{Boadilla} 3 | 4 | \usepackage{listings} 5 | 6 | \title{MeowRouter} 7 | \subtitle{路由器的可扩展性及内卷} 8 | 9 | \author{Grp.3 - 刘晓义\hspace{.2em} 娄晨耀 \hspace{.2em} 郑林楷 \hspace{.2em} 申奥} 10 | \date{2019.12.25 - Merry Christmas} 11 | 12 | \begin{document} 13 | \begin{frame} 14 | \titlepage 15 | \end{frame} 16 | \begin{frame} 17 | \frametitle{Repository \& Contribution} 18 | 19 | \textbf{https://github.com/meow-chip} 20 | 21 | \begin{description} 22 | \item[MeowRouter] \textbf{数据平面 RTL} in Chisel3 23 | \item[MeowV64] \textbf{CPU RTL} in Chisel3 24 | \item[RouterSoftware] \textbf{控制平面固件} in Rust, asm, C++ 25 | \item[MeowRouter\-top] \textbf{thinpad\_top} in admiration of 杰哥、橙橙和宇翔 26 | \end{description} 27 | 28 | \vspace{1em} 29 | 30 | \begin{description} 31 | \item[刘晓义] CPU 总体结构,数据平面流水设计,接口设计,Block Design 连连看 32 | \item[娄晨耀] 算法,转发表查找实现 33 | \item[郑林楷] 路由控制协议实现 34 | \item[申奥] CPU 执行单元,BPU 35 | \end{description} 36 | \end{frame} 37 | \begin{frame} 38 | \frametitle{Design Motivation} 39 | 40 | 数据平面的核心工作: 41 | \begin{itemize} 42 | \item 解析、组装包 43 | \only<1>{\item 查找下一跳} 44 | \only<2->{\item \textbf{查找下一跳}} 45 | \item 填入 MAC 46 | \end{itemize} 47 | \vspace{1em} 48 | 49 | \pause 50 | \pause 51 | 52 | Currently(2019): 53 | 54 | \begin{itemize} 55 | \item 路由表可能很大 (813242 BGP prefixes @ 12-24\footnote{https://www.cidr-report.org/as2.0}) 56 | \item 相比片上存储和 SRAM,大量的 DRAM 比较便宜,速度比较慢 (~20ns cmp. to ~5ns) 57 | \end{itemize} 58 | 59 | \pause 60 | \vspace{1em} 61 | 62 | 核心目标:\textbf{减少访存} 63 | \end{frame} 64 | \begin{frame} 65 | \frametitle{Prior Arts} 66 | \begin{block}{Linux Netfilter Offload\footnote{https://www.kernel.org/doc/Documentation/networking/nf\_flowtable.txt}} 67 | \begin{center} 68 | \includegraphics[width=0.6\textwidth]{assets/nf.jpg} 69 | \end{center} 70 | \end{block} 71 | \end{frame} 72 | \begin{frame} 73 | \frametitle{Prior Arts} 74 | \begin{block}{Luleå algorithm} 75 | Trie Tree, 为通用计算硬件设计(General-purpose CPU + Cache) 76 | 77 | 单次查找 \textasciitilde 8 次访存 78 | 79 | 有针对快速更新的优化 80 | \end{block} 81 | \pause 82 | \begin{block}{CuckooSwitch} 83 | 使用 Cuckoo Hashtable 查询交换规则 84 | \end{block} 85 | \end{frame} 86 | 87 | \begin{frame} 88 | \frametitle{Algorithm} 89 | 90 | 转发表由 /32 -> /32 的转发规则构成,使用软件计算后填充到硬件内 91 | 92 | \begin{itemize} 93 | \item 存储可以使用哈希表 94 | \item 硬件可以通过直接按位比较处理 95 | \item 方便添加额外信息: NAT etc. 96 | \end{itemize} 97 | 98 | \pause 99 | \vspace{1em} 100 | 101 | 如何修改? 102 | 103 | \pause 104 | 105 | \vspace{1em} 106 | 107 | 哈希表的设计? 108 | \begin{description} 109 | \item[Identity] 需要 $2^{32} \times 32\text{bit}$ = 16GB RAM,保证单次访存 110 | \item[Open addressing] 访存数量不定,需要用利用率换平均访存速度: Swisstable 111 | \item[Chaining] 硬件没法实现 112 | \end{description} 113 | \end{frame} 114 | \begin{frame} 115 | \frametitle{Cuckoo Hashtable} 116 | 117 | \begin{itemize} 118 | \item 哈希函数得到两个值 119 | \item 每个 Key 对应两个 Index,如果插入的时候两行都满了,可以通过将某一位置的内容移动到它对应的另一行中 120 | \item 查询保证两次访存,硬件实现简单 121 | \item 可以达到 95\% 的利用率 122 | \item 插入逻辑较复杂,访存可能比较多: CPU Cache 123 | \end{itemize} 124 | \end{frame} 125 | 126 | \begin{frame} 127 | \frametitle{Memory Access} 128 | 129 | 使用标准总线: \textbf{AXI} 130 | 131 | \pause 132 | 133 | \vspace{1em} 134 | Pros: 135 | 136 | \begin{itemize} 137 | \item 有很多的 IP 可以用: UART, BRAM, EMC, ... 138 | \item 扩展性高:Interconnect 上 Master 和 Slave 数量不定,可以级联 139 | \end{itemize} 140 | 141 | \vspace{1em} 142 | Cons: 143 | \begin{itemize} 144 | \item 延迟高: At least 10 Cycles 145 | \item 占用片上面积很大: tot. 10K LUT 146 | \end{itemize} 147 | \end{frame} 148 | 149 | \begin{frame} 150 | \frametitle{Overall picture} 151 | 152 | \begin{center} 153 | \includegraphics[width=0.95\textwidth]{assets/overall-pic.jpg} 154 | \end{center} 155 | \end{frame} 156 | 157 | \begin{frame} 158 | \frametitle{Optimization} 159 | 160 | 为了保证数据平面的处理速度最大化,进行了以下优化: 161 | \begin{itemize} 162 | \item 采用两级 AXI Interconnect,第二级连接 EMC,使用数据平面的 125M 时钟 163 | \item 发送读取请求时,采用 Request Interleaving: 减小 Interconnect 带来的延迟 164 | \item 移除所有空转的自动机状态 165 | \item 增大 CPU 的 rx/tx buffer,减少短期 Burst 带来的丢包,让一轮能够计算得到的转发规则更多 166 | \end{itemize} 167 | \end{frame} 168 | 169 | \begin{frame} 170 | \frametitle{Performance} 171 | \begin{description} 172 | \item[单口单工] \textasciitilde 94 Mbps 173 | \item[单口双工] \textasciitilde 187 Mbps 174 | \item[双口双工] \textasciitilde 330 Mbps 175 | \item[小包] \textasciitilde 148kpps 176 | \end{description} 177 | \end{frame} 178 | 179 | \begin{frame} 180 | \frametitle{CPU brief intro.} 181 | 182 | \begin{itemize} 183 | \item 70M, 默认配置使用 \textasciitilde 50K LUT 184 | \pause 185 | \item 64bit RISC-V, 实现 ISA 为 \textbf{RV64IMAC -Zifencei -Zicsr} 186 | \begin{description} 187 | \item[I] 整数指令集 188 | \item[M] 乘除法 189 | \item[A] Atomic Primitives 190 | \item[C] 压缩指令集 191 | \item[-Zifencei] FENCE.I 192 | \item[-Zicsr] CSR 及指令 193 | \end{description} 194 | \pause 195 | \item 流水线: \textbf{多发射 + 预测执行(BHT + RAS) + 乱序执行(Tomasulo)} 196 | \begin{itemize} 197 | \item ALU 1 + Branch + CSR + Bypass 198 | \item ALU 2 + Mul + Div 199 | \item LSBuf + LSU 200 | \end{itemize} 201 | \pause 202 | \item Cache: \textbf{L1 Instr/Data, L2 + MSI Directory, 支持多核} 203 | \item 可配置 204 | \end{itemize} 205 | \end{frame} 206 | 207 | \begin{frame} 208 | \frametitle{Firmware} 209 | 210 | \begin{itemize} 211 | \item 汇编编写固件入口,Trap Vector 入口,初始化栈指针,传入 mhartid 212 | \item Rust 编写 Firmware 的转发表项计算、填入,ARP 表项填入,发包、收包逻辑,HAL 后端 213 | \item C 编写 RIP 协议部分 214 | \end{itemize} 215 | 216 | Linker Script 将 .text 和 .rodata 放到 Flash 中,将 .data 放到 SRAM 中 217 | 218 | 不使用 BSS 段: 懒得清空 219 | \end{frame} 220 | 221 | \begin{frame} 222 | \frametitle{RIP} 223 | 224 | 基础是 Router-Lab,方便脱离其他部分调试 225 | 226 | 实现了 Split horizon, Poisoned reverse 227 | 228 | \pause 229 | 230 | \vspace{1em} 231 | 232 | 尝试解决 RIP 处理速度慢的问题: 233 | 234 | 根据 UDP Checksum 随机丢包 235 | 236 | \pause 237 | 238 | \vspace{1em} 239 | 240 | 由于和 RIP 无关的固件问题,目前最多存储大约 1500 条表项,之后会栈溢出 241 | 242 | \end{frame} 243 | 244 | \begin{frame} 245 | \frametitle{Tools} 246 | 247 | \textbf{Chisel3} 248 | 249 | 用 Scala 的 DSL 做 HDL,更强的编译期检查,可配置性 250 | 251 | https://github.com/freechipsproject/chisel3 252 | 253 | \pause 254 | 255 | \vspace{1em} 256 | 257 | \textbf{Block Design} 258 | 259 | Vivado 提供的可视化编程(?)工具 260 | 261 | 连连看,方便配置 IP 262 | \end{frame} 263 | 264 | \begin{frame} 265 | \includegraphics[width=\textwidth]{assets/bd.png} 266 | \end{frame} 267 | 268 | \begin{frame} 269 | \frametitle{Thanks!} 270 | 271 | \begin{center} 272 | \includegraphics[height=0.7\textheight]{assets/board.jpg} 273 | \end{center} 274 | \end{frame} 275 | \end{document} 276 | -------------------------------------------------------------------------------- /scalastyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | Scalastyle standard configuration 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | No lines ending with a ; 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | |\|\||&&|:=|<>|<=|>=|!=|===|<<|>>|##|unary_(~|\-%?|!))$]]> 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /scalastyle-test-config.xml: -------------------------------------------------------------------------------- 1 | 2 | Scalastyle configuration for Chisel3 unit tests 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | No lines ending with a ; 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | |\|\||&&|:=|<>|<=|>=|!=|===|<<|>>|##|unary_(~|\-%?|!))$]]> 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /scripts/cuckoo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define ROW_NUM (1024) // ROW_NUM must be the power of 2, and the maximum value is 65536 8 | #define ROW_NUM_MASK (ROW_NUM - 1) 9 | #define SLOT_NUM (4) 10 | 11 | #define KEY_TYPE uint32_t 12 | #define VALUE_TYPE uint8_t 13 | 14 | /* cuckoo hash table 15 | 16 | Usage: 17 | - struct Table t; 18 | create a hash table instance 19 | 20 | - insert(&t, key, value); 21 | insert (key, value) into the table t. 22 | It returns 1 if the insertation is successful. 23 | If the key already exists, it still returns 1 but updates the value. 24 | 25 | - lookup(&t, key, &result); 26 | lookup the key from the table t. 27 | It returns 1 if the key is found, and sets the result to the correspond value. 28 | 29 | - remove_key(&t, key); 30 | delete key from the table t. 31 | It returns 1 if the delete is successful, namely, the key was in the table. 32 | 33 | Note: The keys cannot be zero, since cuckoo uses zero as the invalid key internally. 34 | */ 35 | 36 | #define boolean uint8_t 37 | 38 | struct Row { 39 | KEY_TYPE keys[SLOT_NUM]; 40 | VALUE_TYPE values[SLOT_NUM]; 41 | }; 42 | 43 | struct Table { 44 | struct Row rows[ROW_NUM]; 45 | }; 46 | 47 | 48 | // a fake hash implementation 49 | // TODO: replace it with a real implementation 50 | uint32_t hash(KEY_TYPE k) { 51 | return k; 52 | } 53 | 54 | // lookup the key k at row r. 55 | // it returns the index of the key if it exists 56 | int lookup_at_row(struct Row *r, KEY_TYPE k, VALUE_TYPE *result) { 57 | for (int i = 0; i < SLOT_NUM; i++) { 58 | if (r->keys[i] == k) { 59 | *result = r->values[i]; 60 | return i; 61 | } 62 | } 63 | return -1; 64 | } 65 | 66 | boolean lookup(struct Table *t, KEY_TYPE k, VALUE_TYPE *result) { 67 | assert(k != 0); 68 | int h = hash(k); 69 | int r1_id = h & ROW_NUM_MASK; 70 | int r2_id = (h >> 16) & ROW_NUM_MASK; 71 | 72 | if (lookup_at_row(&t->rows[r1_id], k, result) != -1) { 73 | return 1; 74 | } 75 | 76 | if (lookup_at_row(&t->rows[r2_id], k, result) != -1) { 77 | return 1; 78 | } 79 | 80 | return 0; 81 | } 82 | 83 | 84 | // empty_slot returns empty slot index if there is an empty slot, 85 | // otherwise returns -1. 86 | int empty_slot(struct Row *r) { 87 | for (int i = 0; i < SLOT_NUM; i++) { 88 | if (r->keys[i] == 0) { 89 | return i; 90 | } 91 | } 92 | return -1; 93 | } 94 | 95 | // insert the (k, v) pair into the row r. 96 | boolean insert_into_row(struct Row *r, KEY_TYPE k, VALUE_TYPE v) { 97 | int slot_id = empty_slot(r); 98 | if (slot_id >= 0) { 99 | r->keys[slot_id] = k; 100 | r->values[slot_id] = v; 101 | return 1; 102 | } 103 | return 0; 104 | } 105 | 106 | // insert the (k, v) pair into the table r. 107 | boolean insert(struct Table *t, KEY_TYPE k, VALUE_TYPE v) { 108 | assert(k != 0); 109 | int h = hash(k); 110 | int r1_id = h & ROW_NUM_MASK; 111 | int r2_id = (h >> 16) & ROW_NUM_MASK; 112 | fflush(stdout); 113 | 114 | // check if the key is alread in the table. If it is, modify the value. 115 | VALUE_TYPE tmp_result; 116 | int tmp_index; 117 | tmp_index = lookup_at_row(&t->rows[r1_id], k, &tmp_result); 118 | if (tmp_index != -1) { 119 | t->rows[r1_id].values[tmp_index] = v; 120 | return 1; 121 | } 122 | tmp_index = lookup_at_row(&t->rows[r2_id], k, &tmp_result); 123 | if (tmp_index != -1) { 124 | t->rows[r2_id].values[tmp_index] = v; 125 | return 1; 126 | } 127 | 128 | // try to insert into the first(r1_id) row 129 | if (insert_into_row(&t->rows[r1_id], k, v)) { 130 | return 1; 131 | } 132 | 133 | // try to insert into the second(r2_id) row 134 | if (insert_into_row(&t->rows[r2_id], k, v)) { 135 | return 1; 136 | } 137 | 138 | // TODO: shift the tuples in the row r1_id and r2_id to other rows, to reserve an empty slot 139 | 140 | return 0; 141 | } 142 | 143 | boolean remove_key_from_row(struct Row *r, KEY_TYPE k) { 144 | for (int i = 0; i < SLOT_NUM; i++) { 145 | if (r->keys[i] == k) { 146 | r->keys[i] = 0; 147 | return 1; 148 | } 149 | } 150 | return 0; 151 | } 152 | 153 | boolean remove_key(struct Table *t, KEY_TYPE k) { 154 | assert(k != 0); 155 | int h = hash(k); 156 | int r1_id = h & ROW_NUM_MASK; 157 | int r2_id = (h >> 16) & ROW_NUM_MASK; 158 | 159 | if (remove_key_from_row(&t->rows[r1_id], k)) { 160 | return 1; 161 | } 162 | 163 | if (remove_key_from_row(&t->rows[r2_id], k)) { 164 | return 1; 165 | } 166 | 167 | return 0; 168 | } 169 | 170 | void test_smoke() { 171 | struct Table t; 172 | memset(&t, 0, sizeof(t)); 173 | 174 | // insert (42, 1) and (43, 2) 175 | insert(&t, 42, 1); 176 | insert(&t, 43, 2); 177 | 178 | VALUE_TYPE v; 179 | // lookup the key 42 180 | assert(lookup(&t, 42, &v)); 181 | assert(v == 1); 182 | // lookup the key 43 183 | assert(lookup(&t, 43, &v)); 184 | assert(v == 2); 185 | 186 | // remove the key 42 187 | assert(remove_key(&t, 42)); 188 | assert(lookup(&t, 42, &v) == 0); 189 | 190 | // keep modifying the key 42 191 | for (int i = 3; i <= 50; i++) { 192 | assert(insert(&t, 42, i)); 193 | assert(lookup(&t, 42, &v)); 194 | assert(v == i); 195 | } 196 | 197 | printf("smoke: Passed\n"); 198 | } 199 | 200 | void test_utilization() { 201 | float factor = 0.4; 202 | int keys[SLOT_NUM*ROW_NUM]; 203 | 204 | struct Table t; 205 | memset(&t, 0, sizeof(t)); 206 | for (int i = 0; i < SLOT_NUM * ROW_NUM * factor; i++) { 207 | keys[i] = (rand() << 16) ^ rand(); 208 | assert(insert(&t, keys[i], i)); 209 | } 210 | 211 | // verify all keys are in the table 212 | for (int i = 0; i < SLOT_NUM * ROW_NUM * factor; i++) { 213 | VALUE_TYPE v; 214 | assert(lookup(&t, keys[i], &v)); 215 | assert(v == (VALUE_TYPE)i); 216 | } 217 | 218 | printf("utilization: Passed\n"); 219 | } 220 | 221 | void test() { 222 | test_smoke(); 223 | test_utilization(); 224 | } 225 | 226 | int main(int argc, char *argv[]) { 227 | test(); 228 | return 0; 229 | } -------------------------------------------------------------------------------- /scripts/cuckoo_simulator.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import random 4 | import crcmod 5 | 6 | crc32_func1 = crcmod.mkCrcFun(0x104c11db7, initCrc=0, xorOut=0xFFFFFFFF) 7 | crc32_func2 = crcmod.mkCrcFun(0x1cf2420b5, initCrc=0, xorOut=0xFFFFFFFF) 8 | 9 | class Cuckoo(object): 10 | def __init__(self, buckets_num=1024, slots_num=4, shift_times=3): 11 | self.buckets_num = buckets_num 12 | self.slots_num = slots_num 13 | self.shift_times = shift_times 14 | self.buckets = [[None] * slots_num for _ in range(buckets_num)] 15 | 16 | def bucket_id(self, key): 17 | #return key % self.buckets_num, key // self.buckets_num % self.buckets_num 18 | return crc32_func1(str(key).encode()) % self.buckets_num, crc32_func2(str(key).encode()) % self.buckets_num 19 | 20 | def put_into_bucket(self, key, bucket): 21 | assert(len(bucket) == self.slots_num) 22 | for i in range(self.slots_num): 23 | if bucket[i] == None: 24 | bucket[i] = key 25 | return True 26 | return False 27 | 28 | def shift(self, bucket, depth = 3): 29 | if depth == 0: 30 | return False 31 | 32 | for i in range(self.slots_num): 33 | k = bucket[i] 34 | assert(k != None) 35 | id1, id2 = self.bucket_id(k) 36 | # swap to make sure k is currently in self.bucket[id1] 37 | if k in self.buckets[id2]: 38 | id1, id2 = id2, id1 39 | other_b = self.buckets[id2] 40 | 41 | if None not in other_b and id1 != id2: 42 | self.shift(other_b, depth-1) 43 | 44 | for j in range(self.slots_num): 45 | if other_b[j] == None: 46 | other_b[j] = k 47 | bucket[i] = None 48 | return True 49 | return False 50 | 51 | def put(self, key): 52 | id1, id2 = self.bucket_id(key) 53 | b1 = self.buckets[id1] 54 | b2 = self.buckets[id2] 55 | 56 | if self.put_into_bucket(key, b1): 57 | return True 58 | if self.put_into_bucket(key, b2): 59 | return True 60 | if self.shift(b1, depth=self.shift_times): 61 | assert(self.put_into_bucket(key, b1)) 62 | return True 63 | if self.shift(b2, depth=self.shift_times): 64 | assert(self.put_into_bucket(key, b2)) 65 | return True 66 | 67 | return False 68 | 69 | def find(self, key): 70 | id1, id2 = self.bucket_id(key) 71 | b1 = self.buckets[id1] 72 | b2 = self.buckets[id2] 73 | return key in b1 or key in b2 74 | 75 | def sim1(): 76 | buckets_num = 1024 77 | slots_num = 4 78 | shift_times = 2 79 | factor = 0.8 80 | for round in range(300): 81 | c = Cuckoo(buckets_num=buckets_num, slots_num=slots_num, shift_times=shift_times) 82 | keys = [] 83 | for i in range(int(buckets_num*slots_num*factor)): 84 | k = random.randint(0, 2000000) 85 | keys.append(k) 86 | if not c.put(k): 87 | print(f"Failed on inserting {i}-th key") 88 | assert(False) 89 | 90 | for k in keys: 91 | assert(c.find(k)) 92 | 93 | print("sim1: Pass") 94 | 95 | if __name__ == "__main__": 96 | sim1() -------------------------------------------------------------------------------- /src/main/scala/acceptor/acceptor.scala: -------------------------------------------------------------------------------- 1 | package acceptor; 2 | 3 | import chisel3._; 4 | import data._; 5 | import chisel3.util._ 6 | import chisel3.experimental._ 7 | import _root_.util._ 8 | import encoder.EncoderUnit 9 | 10 | class Acceptor(PORT_COUNT: Int) extends MultiIOModule { 11 | // Header Length = MAC * 2 + VLAN + EtherType 12 | val HEADER_LEN = 6 * 2 + 4 + 2 13 | 14 | val macs = IO(Input(Vec(PORT_COUNT+1, UInt(48.W)))) 15 | 16 | val io = IO(new Bundle { 17 | val rx = Flipped(new AXIS(8)) 18 | 19 | val writer = Flipped(new AsyncWriter(new Packet(PORT_COUNT))) 20 | val payloadWriter = Flipped(new AsyncWriter(new EncoderUnit)) 21 | }) 22 | 23 | val cnt = RegInit(0.asUInt(12.W)) 24 | 25 | object State extends ChiselEnum { 26 | val eth, ip, body = Value 27 | } 28 | 29 | val state = RegInit(State.eth) 30 | 31 | val header = Reg(Vec(HEADER_LEN, UInt(8.W))) 32 | val ip = Reg(Vec(IP.HeaderLength/8, UInt(8.W))) 33 | val fusedHeader = Wire(header.cloneType) 34 | fusedHeader := header 35 | fusedHeader(17.U - cnt) := io.rx.tdata 36 | 37 | val pactype = PacType.parse(header.slice(0, 2)) 38 | val fusedPactype = PacType.parse(fusedHeader.slice(0, 2)) 39 | 40 | val output = Wire(new Packet(PORT_COUNT)) 41 | 42 | val dropped = RegInit(false.B) 43 | 44 | val emit = Wire(Bool()) 45 | emit := false.B 46 | val emitted = RegNext(emit) 47 | 48 | io.rx.tready := true.B 49 | 50 | output.eth.sender := (header.asUInt >> (18 - 12) * 8) 51 | output.eth.dest := (header.asUInt >> (18 - 6) * 8) 52 | output.eth.vlan := header(2) 53 | output.eth.pactype := pactype 54 | output.ip := ip.asTypeOf(output.ip) 55 | val destMatch = ( 56 | output.eth.dest === 0xFFFFFFFFFFFFl.U // Broadcast 57 | || output.eth.dest === macs(output.eth.vlan) // Unicast 58 | || output.eth.dest(47, 24) === 0x01005E.U // Multicast 59 | ) 60 | 61 | io.payloadWriter.clk := this.clock 62 | io.payloadWriter.data := DontCare 63 | io.payloadWriter.en := false.B 64 | 65 | switch(state) { 66 | is(State.eth) { 67 | header(17.U - cnt) := io.rx.tdata 68 | 69 | when(io.rx.tvalid) { 70 | when(cnt < (HEADER_LEN-1).U) { 71 | cnt := cnt +% 1.U 72 | }.otherwise { 73 | when(fusedPactype === PacType.ipv4) { 74 | cnt := 0.U 75 | state := State.ip 76 | }.otherwise { 77 | emit := true.B 78 | dropped := io.writer.full || io.payloadWriter.progfull || !destMatch 79 | state := State.body 80 | } 81 | } 82 | } 83 | } 84 | 85 | is(State.ip) { 86 | ip((IP.HeaderLength/8 - 1).U - cnt) := io.rx.tdata 87 | 88 | when(io.rx.tvalid) { 89 | when(cnt < (IP.HeaderLength/8-1).U) { 90 | cnt := cnt +% 1.U 91 | }.otherwise { 92 | emit := true.B 93 | dropped := io.writer.full || io.payloadWriter.progfull || !destMatch 94 | state := State.body 95 | } 96 | } 97 | } 98 | 99 | is(State.body) { 100 | io.payloadWriter.en := io.rx.tvalid && !dropped 101 | io.payloadWriter.data.data := io.rx.tdata 102 | io.payloadWriter.data.last := io.rx.tlast 103 | 104 | when(io.rx.tvalid && io.rx.tlast) { 105 | state := State.eth 106 | cnt := 0.U 107 | dropped := false.B 108 | } 109 | } 110 | } 111 | 112 | io.writer.en := emitted && !dropped 113 | io.writer.data := output 114 | 115 | io.writer.clk := this.clock 116 | } 117 | -------------------------------------------------------------------------------- /src/main/scala/adapter/adapter.scala: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import chisel3._ 4 | import chisel3.experimental._ 5 | import chisel3.experimental.ChiselEnum 6 | import chisel3.util._ 7 | import _root_.util.AsyncWriter 8 | import _root_.util.Consts 9 | import data._ 10 | import encoder.EncoderUnit 11 | 12 | class BufPort extends Bundle { 13 | val clk = Output(Clock()) 14 | val addr = Output(UInt(32.W)) 15 | val din = Output(UInt(8.W)) 16 | val dout = Input(UInt(8.W)) 17 | val we = Output(Bool()) 18 | } 19 | 20 | object AdapterReq extends ChiselEnum { 21 | val incoming, arpMiss, forwardMiss, unknown = Value 22 | } 23 | 24 | class Adapter extends MultiIOModule { 25 | val toBuf = IO(new BufPort) 26 | 27 | val fromEnc = IO(new Bundle { 28 | val input = Input(UInt(8.W)) 29 | val valid = Input(Bool()) 30 | val last = Input(Bool()) 31 | 32 | val req = Input(AdapterReq()) 33 | 34 | val stall = Output(Bool()) 35 | }) 36 | 37 | val toEnc = IO(new Bundle { 38 | val writer = Flipped(new AsyncWriter(new EncoderUnit)) 39 | }) 40 | 41 | object State extends ChiselEnum { 42 | val rst, rstWait, pollHead, pollZero, incoming, outgoing = Value 43 | } 44 | 45 | object Status extends ChiselEnum { 46 | val idle = Value(0.U) 47 | val incoming = Value(1.U) 48 | val outgoing = Value(2.U) 49 | val forwardMiss = Value(3.U) 50 | val arpMiss = Value(4.U) 51 | } 52 | 53 | toBuf.clk := this.clock 54 | // By default, don't write 55 | toBuf.we := false.B 56 | toBuf.din := DontCare 57 | 58 | fromEnc.stall := true.B 59 | toEnc.writer := DontCare 60 | toEnc.writer.en := false.B 61 | toEnc.writer.clk := this.clock 62 | 63 | val state = RegInit(State.rst) 64 | val nstate = Wire(State()) 65 | nstate := state 66 | state := nstate 67 | 68 | val head = RegInit(1.U(log2Ceil(Consts.CPUBUF_COUNT).W)) 69 | val tail = RegInit(1.U(log2Ceil(Consts.CPUBUF_COUNT).W)) 70 | 71 | def step(sig: UInt): UInt = { 72 | Mux(sig === (Consts.CPUBUF_COUNT-1).U, 1.U, sig +% 1.U) 73 | } 74 | 75 | val bufAddrShift = log2Ceil(Consts.CPUBUF_SIZE) 76 | val statusOffset = (Consts.CPUBUF_SIZE - 1).U(bufAddrShift.W) 77 | val lenOffset = (Consts.CPUBUF_SIZE - 4).U(bufAddrShift.W) 78 | 79 | val sendingSlot = Reg(UInt(log2Ceil(Consts.CPUBUF_COUNT).W)) 80 | val dropping = RegInit(false.B) 81 | 82 | val cnt = RegInit(0.U(bufAddrShift.W)) 83 | val totCnt = RegInit(0.U(bufAddrShift.W)) 84 | 85 | object TransferState extends ChiselEnum { 86 | val payload, status, cntLo, cntHi, fin = Value 87 | } 88 | 89 | val transferState = RegInit(TransferState.payload) 90 | 91 | val raddr = Wire(UInt(32.W)) 92 | raddr := DontCare 93 | toBuf.addr := raddr 94 | 95 | val rstCnt = RegInit(0.U(log2Ceil(Consts.CPUBUF_COUNT).W)) 96 | 97 | switch(state) { 98 | is(State.rst) { 99 | head := 1.U 100 | tail := 1.U 101 | 102 | toBuf.addr := rstCnt ## statusOffset 103 | toBuf.din := 0.U 104 | toBuf.we := true.B 105 | 106 | rstCnt := rstCnt + 1.U 107 | 108 | when(rstCnt === (Consts.CPUBUF_COUNT-1).U) { 109 | nstate := State.rstWait 110 | } 111 | } 112 | 113 | is(State.rstWait) { 114 | raddr := statusOffset 115 | nstate := State.pollZero 116 | } 117 | 118 | is(State.pollZero) { 119 | when(toBuf.dout === Status.outgoing.asUInt()) { 120 | nstate := State.outgoing 121 | transferState := TransferState.cntLo 122 | raddr := lenOffset 123 | cnt := 0.U 124 | sendingSlot := 0.U 125 | }.otherwise { 126 | nstate := State.pollHead 127 | raddr := head ## statusOffset 128 | } 129 | } 130 | 131 | is(State.pollHead) { 132 | when(toBuf.dout === Status.outgoing.asUInt()) { 133 | nstate := State.outgoing 134 | transferState := TransferState.cntLo 135 | sendingSlot := head 136 | raddr := head ## lenOffset 137 | cnt := 0.U 138 | }.elsewhen(fromEnc.valid && !dropping) { 139 | when(step(tail) =/= head) { 140 | nstate := State.incoming 141 | transferState := TransferState.payload 142 | cnt := 0.U 143 | // Recv slot is always tail 144 | }.otherwise { 145 | dropping := true.B 146 | nstate := State.pollZero 147 | raddr := statusOffset 148 | } 149 | }.otherwise { 150 | nstate := State.pollZero 151 | raddr := statusOffset 152 | } 153 | 154 | when(toBuf.dout === Status.idle.asUInt()) { 155 | when(head =/= tail) { // Ring buf not empty 156 | head := step(head) 157 | } 158 | } 159 | } 160 | 161 | is(State.incoming) { 162 | switch(transferState) { 163 | is(TransferState.payload) { 164 | raddr := tail ## cnt 165 | toBuf.din := fromEnc.input 166 | fromEnc.stall := false.B 167 | 168 | when(fromEnc.valid) { 169 | toBuf.we := true.B 170 | cnt := cnt +% 1.U 171 | 172 | when(fromEnc.last) { 173 | transferState := TransferState.cntLo 174 | } 175 | } 176 | } 177 | 178 | is(TransferState.cntLo) { 179 | raddr := tail ## lenOffset 180 | toBuf.we := true.B 181 | toBuf.din := cnt(7, 0) 182 | transferState := TransferState.cntHi 183 | } 184 | 185 | is(TransferState.cntHi) { 186 | raddr := tail ## lenOffset + 1.U 187 | toBuf.we := true.B 188 | toBuf.din := cnt >> 8 189 | transferState := TransferState.status 190 | } 191 | 192 | is(TransferState.status) { 193 | raddr := tail ## statusOffset 194 | toBuf.we := true.B 195 | toBuf.din := Status.incoming.asUInt() 196 | 197 | when(fromEnc.req === AdapterReq.arpMiss) { 198 | toBuf.din := Status.arpMiss.asUInt() 199 | }.elsewhen(fromEnc.req === AdapterReq.forwardMiss) { 200 | toBuf.din := Status.forwardMiss.asUInt() 201 | } 202 | transferState := TransferState.fin 203 | } 204 | 205 | is(TransferState.fin) { 206 | tail := step(tail) 207 | 208 | nstate := State.pollZero 209 | raddr := statusOffset 210 | } 211 | } 212 | } 213 | 214 | is(State.outgoing) { 215 | switch(transferState) { 216 | is(TransferState.cntLo) { 217 | raddr := sendingSlot ## lenOffset + 1.U 218 | transferState := TransferState.cntHi 219 | totCnt := toBuf.dout 220 | } 221 | 222 | is(TransferState.cntHi) { 223 | raddr := sendingSlot ## cnt 224 | transferState := TransferState.payload 225 | totCnt := toBuf.dout ## totCnt(7, 0) 226 | } 227 | 228 | is(TransferState.payload) { 229 | val isLast = (cnt +% 1.U) === totCnt 230 | toEnc.writer.data.data := toBuf.dout 231 | toEnc.writer.data.last := isLast 232 | toEnc.writer.en := true.B 233 | 234 | raddr := sendingSlot ## cnt 235 | 236 | when(toEnc.writer.en && !toEnc.writer.full) { 237 | cnt := cnt +% 1.U 238 | raddr := sendingSlot ## (cnt +% 1.U) 239 | 240 | when(isLast) { 241 | transferState := TransferState.status 242 | } 243 | } 244 | } 245 | 246 | is(TransferState.status) { 247 | raddr := sendingSlot ## statusOffset 248 | toBuf.we := true.B 249 | toBuf.din := Status.idle.asUInt() 250 | transferState := TransferState.fin 251 | } 252 | 253 | is(TransferState.fin) { 254 | head := step(head) 255 | 256 | nstate := State.pollZero 257 | raddr := statusOffset 258 | } 259 | } 260 | } 261 | } 262 | 263 | when(dropping) { 264 | fromEnc.stall := false.B 265 | when(fromEnc.valid && fromEnc.last) { 266 | dropping := false.B 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/main/scala/arp/arp.scala: -------------------------------------------------------------------------------- 1 | package arp 2 | import chisel3._ 3 | import chisel3.util._ 4 | import forward.ForwardOutput 5 | import data._ 6 | import _root_.util.Consts 7 | import _root_.top.Cmd 8 | import top.Op 9 | 10 | class ARPTable(PORT_COUNT: Int, SIZE: Int) extends MultiIOModule { 11 | class ARPEntry extends Bundle { 12 | val ip = UInt(32.W) 13 | 14 | val valid = Bool() 15 | 16 | val mac = UInt(48.W) 17 | val at = UInt(log2Ceil(PORT_COUNT+1).W) 18 | 19 | def |(that: ARPEntry) : ARPEntry = (this.asUInt() | that.asUInt()).asTypeOf(this) 20 | } 21 | 22 | object ARPEntry { 23 | def apply(): ARPEntry = 0.U.asTypeOf(new ARPEntry) 24 | } 25 | 26 | val io = IO(new Bundle { 27 | val input = Input(new ForwardOutput(PORT_COUNT)) 28 | val status = Input(Status()) 29 | 30 | val stall = Output(Bool()) 31 | val pause = Input(Bool()) 32 | 33 | val output = Output(new ARPOutput(PORT_COUNT)) 34 | val outputStatus = Output(Status()) 35 | }) 36 | 37 | val macs = IO(Input(Vec(PORT_COUNT+1, UInt(48.W)))) 38 | val ips = IO(Input(Vec(PORT_COUNT+1, UInt(32.W)))) 39 | 40 | val storeInit = Seq.fill(SIZE)(ARPEntry()) 41 | 42 | val store = RegInit(VecInit(storeInit)) 43 | val ptr = RegInit(0.U(log2Ceil(SIZE).W)) 44 | 45 | val (found, entry) = store.foldLeft((false.B, 0.U.asTypeOf(new ARPEntry)))((acc, cur) => { 46 | val found = cur.valid && cur.ip === io.input.lookup.nextHop 47 | 48 | (found || acc._1, Mux(found, cur, 0.U.asTypeOf(cur)) | acc._2) 49 | }) 50 | 51 | io.stall := false.B // ARPTable never stalls 52 | 53 | val pipe = Reg(new ARPOutput(PORT_COUNT)) 54 | val pipeStatus = RegInit(Status.vacant) 55 | 56 | when(!io.pause) { 57 | pipeStatus := io.status 58 | pipe.packet := io.input.packet 59 | pipe.forward := io.input.lookup 60 | pipe.arp := DontCare 61 | 62 | when(io.status === Status.normal) { 63 | pipe.arp.found := found 64 | pipe.arp.at := entry.at 65 | pipe.arp.mac := entry.mac 66 | pipe.packet.eth.vlan := entry.at 67 | pipe.packet.eth.dest := entry.mac 68 | pipe.packet.eth.sender := macs(entry.at) 69 | } 70 | } 71 | 72 | io.outputStatus := pipeStatus 73 | io.output := pipe 74 | 75 | // Handling commands 76 | 77 | val cmd = IO(Input(new Cmd)) 78 | when(cmd.fired()) { 79 | switch(cmd.op) { 80 | is(Op.writeNCEntIP) { 81 | store(cmd.idx).ip := cmd.data 82 | } 83 | 84 | is(Op.writeNCEntMAC) { 85 | store(cmd.idx).mac := cmd.data 86 | } 87 | 88 | is(Op.writeNCEntPort) { 89 | store(cmd.idx).at := cmd.data 90 | } 91 | 92 | is(Op.disableNCEnt) { 93 | store(cmd.idx).valid := false.B 94 | } 95 | 96 | is(Op.enableNCEnt) { 97 | store(cmd.idx).valid := true.B 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/scala/arp/result.scala: -------------------------------------------------------------------------------- 1 | package arp 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import forward.ForwardLookup 6 | import data._ 7 | 8 | class ARPLookup(val PORT_COUNT: Int) extends Bundle { 9 | val found = Bool() 10 | val mac = UInt(48.W) 11 | val at = UInt(log2Ceil(PORT_COUNT+1).W) 12 | } 13 | 14 | class ARPOutput(val PORT_COUNT: Int) extends Bundle { 15 | val arp = new ARPLookup(PORT_COUNT) 16 | val forward = new ForwardLookup 17 | val packet = new Packet(PORT_COUNT) 18 | } -------------------------------------------------------------------------------- /src/main/scala/cuckoo/cuckoo.scala: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import _root_.util.CRC 6 | 7 | class HashTable(val KeySize: Int, val ValueSize: Int) extends Module { 8 | class KeyValuePair(val KeySize: Int, val ValueSize: Int) extends Bundle { 9 | val key = UInt(KeySize.W) 10 | val value = UInt(ValueSize.W) 11 | val valid = Bool() 12 | } 13 | 14 | class Bucket() extends Bundle { 15 | val pairs = Vec(4, new KeyValuePair(KeySize, ValueSize)) 16 | } 17 | 18 | val io = IO(new Bundle { 19 | val key = Input(UInt(KeySize.W)) 20 | val value = Input(UInt(ValueSize.W)) 21 | // Both of setEn and queryEn are true is the undefined behavior 22 | // when setEn is true, this module will set the "key" to "the value" 23 | // when queryEn is true, this module will return the value associted to the "key" 24 | val setEn = Input(Bool()) 25 | val queryEn = Input(Bool()) 26 | 27 | val answerValue = Output(UInt(ValueSize.W)) 28 | val answerFound = Output(Bool()) 29 | val setSucc = Output(Bool()) // is set operation successful 30 | val stall = Output(Bool()) 31 | val dbg = Output(new Bucket()) 32 | val dbg_keyhash = Output(UInt(KeySize.W)) 33 | }); 34 | 35 | val mem = Mem(1024, new Bucket()) 36 | val sIDLE :: sReading :: sComparing :: sSetting :: sSetting1 :: sSetting2 :: sSetting3 :: Nil = Enum(7) 37 | val state = RegInit(sIDLE) 38 | 39 | val key = Reg(UInt(KeySize.W)) // querying on key 40 | val keyHash = Reg(UInt(log2Ceil(1024).W)) 41 | val bucket = Reg(new Bucket()) 42 | val p = RegInit(0.asUInt(8.W)) 43 | val pair = Reg(new KeyValuePair(KeySize, ValueSize)) 44 | val found = RegInit(false.B) 45 | val setSucc = RegInit(false.B) 46 | 47 | // connecting io wires 48 | io.answerValue := pair.value 49 | io.answerFound := found 50 | io.setSucc := setSucc 51 | io.stall := state =/= sIDLE 52 | io.dbg := bucket 53 | io.dbg_keyhash := keyHash 54 | 55 | switch (state) { 56 | is (sIDLE) { 57 | key := io.key 58 | keyHash := CRC(CRC.CRC_8F_6, io.key, KeySize) 59 | setSucc := false.B 60 | found := false.B 61 | p := 0.U 62 | when (io.queryEn) { 63 | state := sReading 64 | } .elsewhen (io.setEn) { 65 | pair.key := io.key 66 | pair.value := io.value 67 | pair.valid := true.B 68 | state := sSetting 69 | } 70 | } 71 | 72 | // ******* 73 | // * the states for query 74 | // ******* 75 | is (sReading) { 76 | bucket := mem.read(keyHash) 77 | state := sComparing 78 | } 79 | is (sComparing) { 80 | for (i <- 0 until 4) { 81 | when (bucket.pairs(i).key === key) { 82 | found := true.B 83 | pair := bucket.pairs(i) 84 | } 85 | } 86 | state := sIDLE 87 | } 88 | 89 | // ******* 90 | // * the state for insert 91 | // ******* 92 | is (sSetting) { 93 | bucket := mem.read(keyHash) 94 | state := sSetting1 95 | } 96 | is (sSetting1) { 97 | val firstEmptys = Array.tabulate(4) { bucket.pairs(_).valid === false.B } 98 | for (i <- 1 until 4) { 99 | for (j <- 0 until i) { 100 | firstEmptys(i) = firstEmptys(i) && bucket.pairs(j).valid 101 | } 102 | } 103 | for (i <- 0 until 4) { 104 | when (firstEmptys(i)) { 105 | bucket.pairs(i) := pair 106 | setSucc := true.B 107 | } 108 | } 109 | state := sSetting2 110 | } 111 | is (sSetting2) { 112 | mem.write(keyHash, bucket) 113 | state := sIDLE 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/scala/data/arp.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3._; 4 | 5 | object ARP { 6 | val HtypeEth = 0x0001.U(16.W) 7 | val PtypeIPV4 = 0x0800.U(16.W) 8 | 9 | val OperRequest = 0x0001.asUInt(16.W) 10 | val OperReply = 0x0002.asUInt(16.W) 11 | } 12 | 13 | // Reversed due to endian 14 | class ARP(val HASIZE: Int, val PASIZE: Int) extends Bundle { 15 | var htype = UInt(16.W) 16 | var ptype = UInt(16.W) 17 | 18 | var hlen = UInt(8.W) 19 | var plen = UInt(8.W) 20 | 21 | var oper = UInt(16.W) 22 | 23 | var sha = UInt(HASIZE.W) 24 | var spa = UInt(PASIZE.W) 25 | 26 | var tha = UInt(HASIZE.W) 27 | var tpa = UInt(PASIZE.W) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/data/axi.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3._ 4 | 5 | // AXI master port, without ACLK / ARSESTn 6 | class AXI( 7 | val DATA_WIDTH: Int, 8 | val ADDR_WIDTH: Int = 48, 9 | val ID_WIDTH: Int = 4 10 | ) extends Bundle { 11 | if(DATA_WIDTH == 32) throw new Error() 12 | // TODO: asserts DATA_wIDTH % 8 === 0 13 | val AWID = Output(UInt(ID_WIDTH.W)) 14 | val AWADDR = Output(UInt(ADDR_WIDTH.W)) 15 | val AWLEN = Output(UInt(8.W)) 16 | val AWSIZE = Output(UInt(3.W)) 17 | val AWBURST = Output(UInt(2.W)) 18 | // AXI4 removes AWLOCK 19 | val AWCACHE = Output(UInt(4.W)) 20 | val AWPROT = Output(UInt(3.W)) 21 | val AWQOS = Output(UInt(3.W)) 22 | val AWREGION = Output(UInt(4.W)) 23 | // We ignore user signals 24 | val AWVALID = Output(Bool()) 25 | val AWREADY = Input(Bool()) 26 | 27 | // AXI4 removes WID 28 | val WDATA = Output(UInt(DATA_WIDTH.W)) 29 | val WSTRB = Output(UInt((DATA_WIDTH/8).W)) 30 | val WLAST = Output(Bool()) 31 | val WVALID = Output(Bool()) 32 | val WREADY = Input(Bool()) 33 | 34 | val BID = Input(UInt(ID_WIDTH.W)) 35 | val BRESP = Input(UInt(2.W)) 36 | val BVALID = Input(Bool()) 37 | val BREADY = Output(Bool()) 38 | 39 | val ARID = Output(UInt(ID_WIDTH.W)) 40 | val ARADDR = Output(UInt(ADDR_WIDTH.W)) 41 | val ARLEN = Output(UInt(8.W)) 42 | val ARSIZE = Output(UInt(3.W)) 43 | val ARBURST = Output(UInt(2.W)) 44 | val ARCACHE = Output(UInt(4.W)) 45 | val ARPROT = Output(UInt(3.W)) 46 | val ARQOS = Output(UInt(3.W)) 47 | val ARREGION = Output(UInt(4.W)) 48 | val ARVALID = Output(Bool()) 49 | val ARREADY = Input(Bool()) 50 | 51 | val RID = Input(UInt(ID_WIDTH.W)) 52 | val RDATA = Input(UInt(DATA_WIDTH.W)) 53 | val RRESP = Input(UInt(2.W)) 54 | val RLAST = Input(Bool()) 55 | val RVALID = Input(Bool()) 56 | val RREADY = Output(Bool()) 57 | } 58 | 59 | object AXI { 60 | object Constants { 61 | object Resp { 62 | val OKAY = 0 63 | val EXOKAY = 1 64 | val SLVERR = 2 65 | val DECERR = 3 66 | } 67 | 68 | object Size { 69 | val S1 = 0 70 | val S2 = 1 71 | val S4 = 2 72 | val S8 = 3 73 | val S16 = 4 74 | val S32 = 5 75 | val S64 = 6 76 | val S128 = 7 77 | 78 | def from(width: Int) : Int = width match { 79 | case 1 => S1 80 | case 2 => S2 81 | case 4 => S4 82 | case 8 => S8 83 | case 16 => S16 84 | case 32 => S32 85 | case 64 => S64 86 | case 128 => S128 87 | } 88 | } 89 | 90 | object Burst { 91 | val FIXED = 0 92 | val INCR = 1 93 | val WRAP = 2 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/scala/data/axis.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3._ 4 | 5 | // AXI-S producer 6 | class AXIS(val WIDTH: Int) extends Bundle { 7 | var tdata = Output(UInt(WIDTH.W)) 8 | var tvalid = Output(Bool()) 9 | var tlast = Output(Bool()) 10 | var tready = Input(Bool()) 11 | 12 | def split(n: Int) : List[AXIS] = { 13 | var ready = false.B 14 | val coll = for(_ <- (0 until n)) yield { 15 | val y = Wire(new AXIS(WIDTH)) 16 | 17 | y.tdata := tdata 18 | y.tvalid := tvalid 19 | y.tlast := tlast 20 | 21 | ready = y.tlast || ready 22 | 23 | y 24 | } 25 | 26 | tready := ready 27 | 28 | coll.toList 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/scala/data/eth.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3._; 4 | import chisel3.util.log2Ceil; 5 | import chisel3.util.Cat 6 | 7 | class Eth(val VLAN_COUNT: Int) extends Bundle { 8 | val dest = UInt(48.W) 9 | val sender = UInt(48.W) 10 | val pactype = PacType() 11 | val vlan = UInt(log2Ceil(VLAN_COUNT).W) 12 | 13 | def toBits() : UInt = Cat( 14 | dest, 15 | sender, 16 | 0x810000.U(24.W), 17 | vlan.asTypeOf(UInt(8.W)), 18 | PacType.serialize(pactype) 19 | ) 20 | 21 | def asVec : Vec[UInt] = toBits().asTypeOf(Vec(18, UInt(8.W))) 22 | } -------------------------------------------------------------------------------- /src/main/scala/data/ip.scala: -------------------------------------------------------------------------------- 1 | package data 2 | import chisel3._ 3 | 4 | object IP { 5 | val HeaderLength = 160 6 | 7 | val ICMP_PROTO = 1 8 | val TCP_PROTO = 6 9 | val UDP_PROTO = 17 10 | } 11 | 12 | /** 13 | * TODO: add TCP Source/Target port 14 | */ 15 | class IP extends Bundle { 16 | val version = UInt(4.W) 17 | val ihl = UInt(4.W) 18 | val dscp = UInt(6.W) 19 | val ecn = UInt(2.W) 20 | 21 | val len = UInt(16.W) 22 | 23 | val id = UInt(16.W) 24 | val flags = UInt(3.W) 25 | val foff = UInt(13.W) 26 | 27 | val ttl = UInt(8.W) 28 | val proto = UInt(8.W) 29 | 30 | val chksum = UInt(16.W) 31 | val src = UInt(32.W) 32 | val dest = UInt(32.W) 33 | } -------------------------------------------------------------------------------- /src/main/scala/data/packet.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3._ 4 | 5 | class Packet(val PORT_COUNT: Int) extends Bundle { 6 | val eth = new Eth(PORT_COUNT + 1) // Port = 0 should refer to localhost 7 | val ip = new IP 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/data/pactype.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3._; 4 | import chisel3.experimental._; 5 | import chisel3.util.Enum; 6 | 7 | object PacType extends ChiselEnum { 8 | val unknown, ipv4, arp = Value 9 | 10 | def parse(v: IndexedSeq[UInt]) : PacType.Type = { 11 | var result = Wire(PacType()) 12 | 13 | when((v(1) === 8.U) && (v(0) === 0.U)) { 14 | result := PacType.ipv4 15 | }.elsewhen((v(1) === 8.U) && (v(0) === 6.U)) { 16 | result := PacType.arp 17 | }.otherwise { 18 | result := PacType.unknown 19 | } 20 | 21 | result 22 | } 23 | 24 | def serialize(v: PacType.Type): UInt = { 25 | val result = Wire(UInt()) 26 | when(v === PacType.ipv4) { 27 | result := 0x0800.U(16.W) 28 | } .elsewhen(v === PacType.arp) { 29 | result := 0x0806.U(16.W) 30 | } .otherwise { 31 | result := 0.U(16.W) 32 | } 33 | 34 | result 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/data/status.scala: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import chisel3.util.Enum 4 | import chisel3.experimental.ChiselEnum 5 | 6 | /** 7 | * Pipeline status 8 | * vacant: No valid packet here 9 | * normal: This is a normal packet. Encoder should decide its 10 | * behavior based on pactype and Forward Table/ARP lookup result 11 | * dropped: This is a dropped packet. Encoder should empty related IP content FIFO if needed 12 | */ 13 | object Status extends ChiselEnum { 14 | val vacant, normal, toLocal, dropped = Value 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/encoder/encoder.scala: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import data._ 6 | import _root_.util.AsyncWriter 7 | import _root_.util.AsyncReader 8 | import arp.ARPOutput 9 | import _root_.util.Consts 10 | import chisel3.experimental.ChiselEnum 11 | import forward.ForwardLookup 12 | import adapter.AdapterReq 13 | 14 | class EncoderUnit extends Bundle { 15 | val data = UInt(8.W) 16 | val last = Bool() 17 | } 18 | 19 | class Encoder(PORT_COUNT: Int) extends MultiIOModule { 20 | val io = IO(new Bundle{ 21 | val input = Input(new ARPOutput(PORT_COUNT)) 22 | val status = Input(Status()) 23 | 24 | val stall = Output(Bool()) 25 | val pause = Input(Bool()) 26 | 27 | val writer = Flipped(new AsyncWriter(new EncoderUnit)) 28 | val payloadReader = Flipped(new AsyncReader(new EncoderUnit)) 29 | }) 30 | 31 | val toAdapter = IO(new Bundle { 32 | val input = Output(UInt(8.W)) 33 | val valid = Output(Bool()) 34 | val last = Output(Bool()) 35 | 36 | val req = Output(AdapterReq()) 37 | 38 | val stall = Input(Bool()) 39 | }) 40 | 41 | val fromAdapter = IO(new Bundle { 42 | val writer = new AsyncWriter(new EncoderUnit) 43 | }) 44 | 45 | val writing = RegInit(false.B) 46 | val cnt = RegInit(0.U) 47 | 48 | object State extends ChiselEnum { 49 | val idle, eth, ip, ipPipe, ipDrop, local, localIp, localPipe, localSend = Value 50 | } 51 | 52 | val state = RegInit(State.idle) 53 | 54 | val sending = Reg(new ARPOutput(PORT_COUNT)) 55 | val ipView = sending.packet.ip.asUInt.asTypeOf(Vec(IP.HeaderLength/8, UInt(8.W))) 56 | val headerView = sending.packet.eth.asVec 57 | 58 | io.payloadReader.clk := this.clock 59 | io.payloadReader.en := false.B 60 | 61 | io.writer.data.last := false.B 62 | io.writer.data.data := 0.asUInt.asTypeOf(io.writer.data.data) 63 | io.writer.en := false.B 64 | io.writer.clk := this.clock 65 | 66 | toAdapter.input := DontCare 67 | toAdapter.last := DontCare 68 | toAdapter.valid := false.B 69 | 70 | val localReq = Reg(AdapterReq()) 71 | toAdapter.req := localReq 72 | 73 | fromAdapter.writer.full := true.B 74 | fromAdapter.writer.progfull := true.B 75 | 76 | switch(state) { 77 | is(State.idle) { 78 | when(fromAdapter.writer.en) { 79 | state := State.localSend 80 | }.elsewhen( 81 | !io.pause 82 | && io.status === Status.normal 83 | && io.input.arp.found 84 | && io.input.forward.status === ForwardLookup.forward 85 | ) { 86 | sending := io.input 87 | state := State.eth 88 | cnt := 17.U 89 | }.elsewhen(!io.pause && (io.status =/= Status.dropped && io.status =/= Status.vacant)) { 90 | sending := io.input 91 | state := State.local 92 | cnt := 17.U 93 | 94 | when(io.status === Status.toLocal) { 95 | localReq := AdapterReq.incoming 96 | }.elsewhen(io.input.forward.status === ForwardLookup.notFound) { 97 | localReq := AdapterReq.forwardMiss 98 | }.elsewhen(!io.input.arp.found) { 99 | localReq := AdapterReq.arpMiss 100 | }.otherwise { 101 | localReq := AdapterReq.unknown 102 | } 103 | } 104 | } 105 | 106 | is(State.eth) { 107 | // Sending ETH packet 108 | io.writer.data.data := headerView(cnt) 109 | io.writer.en := true.B 110 | 111 | when(!io.writer.full) { 112 | when(cnt > 0.U) { 113 | cnt := cnt - 1.U 114 | }.otherwise { 115 | // For all packets other than IP, local is asserted 116 | assert(sending.packet.eth.pactype === PacType.ipv4) 117 | // Is IP 118 | state := State.ip 119 | cnt := (IP.HeaderLength/8-1).U 120 | } 121 | } 122 | } 123 | 124 | is(State.local) { 125 | toAdapter.input := headerView(cnt) 126 | toAdapter.valid := true.B 127 | toAdapter.last := false.B 128 | 129 | when(!toAdapter.stall) { 130 | when(cnt > 0.U) { 131 | cnt := cnt - 1.U 132 | }.elsewhen(sending.packet.eth.pactype === PacType.ipv4) { 133 | state := State.localIp 134 | cnt := (IP.HeaderLength/8-1).U 135 | }.otherwise { 136 | state := State.localPipe 137 | } 138 | } 139 | } 140 | 141 | is(State.ip) { 142 | io.writer.data.data := ipView(cnt) 143 | io.writer.data.last := false.B 144 | io.writer.en := true.B 145 | 146 | when(!io.writer.full) { 147 | when(cnt > 0.U) { 148 | cnt := cnt - 1.U 149 | } .otherwise { 150 | state := State.ipPipe 151 | } 152 | } 153 | } 154 | 155 | is(State.localIp) { 156 | toAdapter.input := ipView(cnt) 157 | toAdapter.valid := true.B 158 | toAdapter.last := false.B 159 | 160 | when(!toAdapter.stall) { 161 | when(cnt > 0.U) { 162 | cnt := cnt - 1.U 163 | } .otherwise { 164 | state := State.localPipe 165 | } 166 | } 167 | } 168 | 169 | is(State.ipPipe) { 170 | io.writer.data := io.payloadReader.data 171 | val transfer = (!io.payloadReader.empty) && (!io.writer.full) 172 | io.writer.en := transfer 173 | io.payloadReader.en := transfer 174 | 175 | when(io.payloadReader.data.last && transfer) { 176 | state := State.idle 177 | } 178 | } 179 | 180 | is(State.localPipe) { 181 | toAdapter.input := io.payloadReader.data.data 182 | toAdapter.valid := !io.payloadReader.empty 183 | toAdapter.last := io.payloadReader.data.last 184 | io.payloadReader.en := !io.payloadReader.empty && !toAdapter.stall 185 | 186 | when(io.payloadReader.data.last && io.payloadReader.en) { 187 | state := State.idle 188 | } 189 | } 190 | 191 | is(State.ipDrop) { 192 | io.payloadReader.en := true.B 193 | when(io.payloadReader.data.last) { state := State.idle } 194 | } 195 | 196 | // TODO: ipDrop + localSend 197 | 198 | is(State.localSend) { 199 | io.writer <> fromAdapter.writer 200 | io.writer.clk := this.clock // Avoid clock mux 201 | when(io.writer.en && io.writer.data.last && !io.writer.full) { 202 | state := State.idle 203 | } 204 | } 205 | } 206 | 207 | io.stall := state =/= State.idle 208 | } 209 | -------------------------------------------------------------------------------- /src/main/scala/forward/cuckoo.scala: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import chisel3.experimental._ 6 | import data._ 7 | import _root_.util.Consts 8 | 9 | 10 | /** 11 | * Forward table with Cuckoo hashtable stored in external memory 12 | */ 13 | class CuckooFT(PORT_COUNT: Int) extends MultiIOModule { 14 | val io = IO(new Bundle { 15 | val input = Input(new Packet(PORT_COUNT)) 16 | val status = Input(Status()) 17 | 18 | val stall = Output(Bool()) 19 | val pause = Input(Bool()) 20 | 21 | val output = Output(new ForwardOutput(PORT_COUNT)) 22 | val outputStatus = Output(Status()) 23 | 24 | val axi = new AXI(64) 25 | }) 26 | 27 | val ips = IO(Input(Vec(PORT_COUNT+1, UInt(32.W)))) 28 | 29 | val working = Reg(new Packet(PORT_COUNT)) 30 | val lookup = Reg(new ForwardLookup) 31 | val addr = Reg(UInt(32.W)) 32 | val status = RegInit(Status.vacant) 33 | 34 | io.output.packet := working 35 | io.output.lookup := lookup 36 | io.outputStatus := status 37 | 38 | object State extends ChiselEnum { 39 | val idle, waiting = Value 40 | } 41 | 42 | val state = RegInit(State.idle) 43 | 44 | io.stall := state =/= State.idle 45 | 46 | class CuckooRow extends Bundle { 47 | val values = Vec(4, UInt(32.W)) 48 | val keys = Vec(4, UInt(32.W)) 49 | } 50 | 51 | val lines = Reg(Vec(2, Vec(4, UInt(64.W)))) 52 | val cnts = RegInit(VecInit(Seq.fill(2)(0.U(log2Ceil(4).W)))) 53 | val views = lines.asTypeOf(Vec(2, new CuckooRow())) 54 | val lineReady = RegInit(VecInit(Seq(false.B, false.B))) 55 | val sending = RegInit(0.U(2.W)) 56 | val hashes = VecInit(CuckooFT.hash(working.ip.dest)) 57 | 58 | io.axi := DontCare 59 | io.axi.AWVALID := false.B 60 | io.axi.WVALID := false.B 61 | io.axi.BREADY := false.B 62 | io.axi.ARVALID := false.B 63 | io.axi.RREADY := false.B 64 | 65 | switch(state) { 66 | is(State.idle) { 67 | when(!io.pause) { 68 | status := io.status 69 | working := io.input 70 | 71 | when(io.status =/= Status.normal) { 72 | state := State.idle 73 | }.elsewhen(io.input.eth.pactype =/= PacType.ipv4) { 74 | state := State.idle 75 | status := Status.toLocal 76 | }.elsewhen( 77 | io.input.ip.dest === ips(io.input.eth.vlan) 78 | || io.input.ip.dest.andR() 79 | || io.input.ip.dest(31, 24) === 224.U 80 | ) { 81 | state := State.idle 82 | status := Status.toLocal 83 | // lookup doesn't matter now 84 | }.otherwise { 85 | lineReady := VecInit(Seq(false.B, false.B)) 86 | cnts := VecInit(Seq.fill(2)(0.U)) 87 | sending := 0.U 88 | 89 | state := State.waiting 90 | } 91 | } 92 | } 93 | 94 | is(State.waiting) { 95 | assume(new CuckooRow().getWidth == 64 * 4) 96 | // AR 97 | io.axi.ARADDR := Consts.CUCKOO_ADDR_BASE.U + hashes(sending) * Consts.CUCKOO_LINE_WIDTH.U 98 | io.axi.ARBURST := AXI.Constants.Burst.INCR.U 99 | io.axi.ARCACHE := 0.U 100 | io.axi.ARID := sending 101 | io.axi.ARLEN := 3.U // 4 slot per line 102 | io.axi.ARPROT := 0.U 103 | io.axi.ARQOS := 0.U 104 | io.axi.ARREGION := 0.U 105 | io.axi.ARSIZE := AXI.Constants.Size.from(io.axi.DATA_WIDTH / 8).U 106 | io.axi.ARVALID := sending < 2.U 107 | 108 | assert(sending <= 2.U) 109 | 110 | when(io.axi.ARREADY && io.axi.ARVALID) { 111 | sending := sending + 1.U 112 | } 113 | 114 | // R 115 | io.axi.RREADY := true.B 116 | when(io.axi.RVALID) { 117 | val idx = io.axi.RID 118 | lines(idx)(cnts(idx)) := io.axi.RDATA 119 | cnts(idx) := cnts(idx) +% 1.U 120 | 121 | when(io.axi.RLAST) { 122 | lineReady(idx) := true.B 123 | } 124 | } 125 | 126 | when(lineReady.reduce(_ && _)) { 127 | // Start matching 128 | // TODO: filter broadcast & 0.0.0.0 129 | // TODO: match localhost 130 | val hits = views.map(row => VecInit(row.keys.map(_ === working.ip.dest)).asUInt().orR()) 131 | val values = views.map(row => Mux1H( 132 | row.keys.zip(row.values).map({ case (k, v) => (k === working.ip.dest) -> v }) 133 | )) 134 | 135 | val hit = VecInit(hits).asUInt.orR() 136 | val value = Mux1H(hits.zip(values)) 137 | 138 | lookup.status := Mux(hit, ForwardLookup.forward, ForwardLookup.notFound) 139 | lookup.nextHop := value 140 | 141 | state := State.idle 142 | } 143 | } 144 | } 145 | } 146 | 147 | object CuckooFT { 148 | def hash(addr: UInt): Seq[UInt] = { 149 | val HASH_WIDTH = log2Ceil(Consts.CUCKOO_LINE_COUNT) 150 | val hash1 = addr(HASH_WIDTH-1, 0) 151 | val hash2 = addr(16 + HASH_WIDTH-1, 16) 152 | Seq(hash1, hash2) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/scala/forward/linear.scala: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import data._ 6 | import _root_.util.Consts 7 | 8 | /** 9 | * LLFT stands for Linear Lookup Forward Table 10 | * Using chisel memory here (becaues, reasons) 11 | * 12 | * This impl doesn't support NAT. so io.output.lookup.status is never natInbound or natOutbound 13 | */ 14 | class LLFT(PORT_COUNT: Int) extends MultiIOModule { 15 | private class Entry extends Bundle { 16 | val prefix = UInt(32.W) 17 | val len = UInt(log2Ceil(32+1).W) 18 | val dest = UInt(32.W) 19 | } 20 | 21 | private object Entry { 22 | def apply(ip: List[Int], len: Int, dest: List[Int]) : Entry = { 23 | val output = Wire(new Entry) 24 | 25 | val pFirst :: pRest = ip.map(_.U(8.W)) 26 | val dFirst :: dRest = dest.map(_.U(8.W)) 27 | 28 | output.prefix := (Cat(pFirst, pRest :_*)) >> (32 - len) 29 | output.len := len.U 30 | output.dest := Cat(dFirst, dRest :_*) 31 | 32 | output 33 | } 34 | } 35 | 36 | val io = IO(new Bundle { 37 | val input = Input(new Packet(PORT_COUNT)) 38 | val status = Input(Status()) 39 | 40 | val stall = Output(Bool()) 41 | val pause = Input(Bool()) 42 | 43 | val output = Output(new ForwardOutput(PORT_COUNT)) 44 | val outputStatus = Output(Status()) 45 | }) 46 | 47 | val ips = IO(Input(Vec(PORT_COUNT+1, UInt(32.W)))) 48 | 49 | private val store = VecInit(Array( 50 | Entry(List(10, 0, 1, 0), 24, List(10, 0, 1, 2)), 51 | Entry(List(10, 0, 2, 0), 24, List(10, 0, 2, 2)), 52 | Entry(List(10, 0, 3, 0), 24, List(10, 0, 3, 2)), 53 | Entry(List(10, 0, 4, 0), 24, List(10, 0, 4, 2)) 54 | )) 55 | 56 | val cnt = Reg(UInt(log2Ceil(store.length + 1).W)) 57 | val shiftCnt = Reg(UInt(log2Ceil(32+1).W)) 58 | 59 | val working = Reg(new Packet(PORT_COUNT)) 60 | val lookup = Reg(new ForwardLookup) 61 | val addr = Reg(UInt(32.W)) 62 | val status = RegInit(Status.vacant) 63 | 64 | io.output.packet := working 65 | io.output.lookup := lookup 66 | io.outputStatus := status 67 | 68 | val sIDLE :: sMATCHING :: Nil = Enum(2) 69 | val state = RegInit(sIDLE) 70 | 71 | io.stall := state =/= sIDLE 72 | 73 | switch(state) { 74 | is(sIDLE) { 75 | when(!io.pause) { 76 | status := io.status 77 | working := io.input 78 | when(io.status =/= Status.vacant) { 79 | when(io.input.eth.pactype === PacType.ipv4) { 80 | when(io.input.ip.dest === ips(io.input.eth.vlan) || io.input.ip.dest.andR()) { 81 | // Broadcast or to self 82 | status := Status.toLocal 83 | lookup.status := ForwardLookup.invalid 84 | }.otherwise { 85 | addr := io.input.ip.dest 86 | cnt := 0.U 87 | shiftCnt := 32.U 88 | state := sMATCHING 89 | } 90 | } .otherwise { 91 | status := Status.toLocal 92 | lookup.status := ForwardLookup.invalid 93 | } 94 | } 95 | } 96 | } 97 | 98 | is(sMATCHING) { 99 | when(cnt === store.length.U) { 100 | state := sIDLE 101 | lookup.status := ForwardLookup.notFound 102 | } .elsewhen(shiftCnt =/= store(cnt).len) { 103 | shiftCnt := shiftCnt - 1.U 104 | addr := addr >> 1 105 | } .otherwise { 106 | when(store(cnt).prefix === addr) { 107 | lookup.status := ForwardLookup.forward 108 | lookup.nextHop := store(cnt).dest 109 | state := sIDLE 110 | } .otherwise { 111 | cnt := cnt + 1.U 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/scala/forward/result.scala: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import chisel3.experimental._ 6 | import data._ 7 | 8 | object ForwardLookup extends ChiselEnum { 9 | val invalid, notFound, forward = Value 10 | } 11 | 12 | /** 13 | * The forward table lookup result 14 | * nextHop is valid only if status =/= ForwardLookup.notFound && status =/= ForwardLookup.invalid 15 | * 16 | * status = ForwardLookup.invalid if the input packet is not an IP packet (ARP, etc.) 17 | * (For now) 18 | * 19 | */ 20 | class ForwardLookup extends Bundle { 21 | val status = ForwardLookup() 22 | val nextHop = UInt(32.W) 23 | } 24 | 25 | /** 26 | * Output for the forward table stage. 27 | * Contains the lookup result and the packet. 28 | * 29 | * 30 | * For nat packets, TCP should be asserted 31 | * If lookup.status === ForwardLookup.natOutbound, then the packet besides TCP checksum is modified 32 | * If lookup.status === ForwardLookup.natInbound, then the packet besides TCP checksum is 33 | * also modified, and it's guaranteed that lookup.nextHop === packet.ip.dest 34 | * TCP checksum computation is delayed until the TCP Checksum stage 35 | */ 36 | class ForwardOutput(val PORT_COUNT: Int) extends Bundle { 37 | val packet = new Packet(PORT_COUNT) 38 | val lookup = new ForwardLookup 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/main.scala: -------------------------------------------------------------------------------- 1 | import top.Top 2 | 3 | object Main extends App { 4 | chisel3.Driver.execute(args, () => new Top) 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/router/ctrl.scala: -------------------------------------------------------------------------------- 1 | package top 2 | 3 | import chisel3._ 4 | import chisel3.experimental._ 5 | import chisel3.util._ 6 | import _root_.data._ 7 | 8 | /** 9 | * Stall signal from/to a single stage 10 | * 11 | * stall: The stage request a stall 12 | * pause: The stage should hold its output and pause 13 | * This means that this stage is probably stalled by the succeeding stage(s) 14 | */ 15 | class StageStall extends Bundle { 16 | val stall = Input(Bool()) 17 | val pause = Output(Bool()) 18 | } 19 | 20 | /** 21 | * Command interface 22 | * 23 | * A command sent by the CPU 24 | * 25 | * Command is applied on the raising edge of op 26 | * All fields are given in reversed order comparing to the software repr 27 | * because we are using big-endian here. 28 | */ 29 | class Cmd extends Bundle { 30 | val data = UInt(48.W) 31 | val idx = UInt(8.W) 32 | val op = Op() 33 | 34 | def fired() = RegNext(op) === Op.nop && op =/= Op.nop 35 | } 36 | 37 | object Op extends ChiselEnum { 38 | val nop = Value(0.U) 39 | val setIP = Value(1.U) 40 | val setMAC = Value(2.U) // Actually, we don't know if MAC can be altered during runtime 41 | val writeNCEntIP = Value(3.U) // NCEnt = Neighboor cache entry 42 | val writeNCEntMAC = Value(4.U) 43 | val writeNCEntPort = Value(5.U) 44 | val enableNCEnt = Value(6.U) 45 | val disableNCEnt = Value(7.U) 46 | 47 | val inval = Value(0xFF.U) // Pad op length to 8 48 | } 49 | 50 | /** 51 | * Stall signal controller. 52 | * Currently only presents a naive impl: 53 | * Pause the entire pipeline if any stage is stalled 54 | */ 55 | class Ctrl extends MultiIOModule { 56 | val io = IO(new Bundle { 57 | val inputWait = Output(Bool()) // controls acceptor.io.read.en 58 | val forward = new StageStall 59 | val arp = new StageStall 60 | val encoder = new StageStall 61 | }); 62 | 63 | val anyStalled = io.forward.stall || io.arp.stall || io.encoder.stall 64 | io.inputWait := anyStalled 65 | io.forward.pause := anyStalled 66 | io.arp.pause := anyStalled 67 | io.encoder.pause := anyStalled 68 | 69 | val macStore = RegInit(VecInit(Seq( 70 | 0x000000000000L.U(48.W), 71 | 0x000000000001L.U(48.W), 72 | 0x000000000002L.U(48.W), 73 | 0x000000000003L.U(48.W), 74 | 0x000000000004L.U(48.W) 75 | ))) 76 | 77 | val ipStore = RegInit(VecInit(Seq( 78 | 0x0A010001.U(32.W), 79 | 0x0A000101.U(32.W), 80 | 0x0A000201.U(32.W), 81 | 0x0A000301.U(32.W), 82 | 0x0A000401.U(32.W) 83 | ))) 84 | 85 | val macs = IO(Output(Vec(macStore.length, UInt(48.W)))) 86 | macs := macStore 87 | 88 | val ips = IO(Output(Vec(ipStore.length, UInt(32.W)))) 89 | ips := ipStore 90 | 91 | val cmd = IO(Input(new Cmd)) 92 | 93 | assert(cmd.getWidth == 64) 94 | assert(cmd.op.getWidth == 8) 95 | 96 | when(cmd.fired()) { 97 | switch(cmd.op) { 98 | is(Op.setIP) { 99 | ipStore(cmd.idx) := cmd.data 100 | } 101 | 102 | is(Op.setMAC) { 103 | macStore(cmd.idx) := cmd.data 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/scala/router/router.scala: -------------------------------------------------------------------------------- 1 | package top 2 | 3 | import chisel3._; 4 | 5 | import acceptor.Acceptor 6 | import encoder.Encoder 7 | import encoder.EncoderUnit 8 | import chisel3.core.withClock 9 | import data._ 10 | import ch.qos.logback.core.helpers.Transform 11 | import _root_.util.AsyncBridge 12 | import transmitter.Transmitter 13 | import forward._ 14 | import arp.ARPTable 15 | import _root_.util.Consts 16 | import adapter._ 17 | 18 | /** 19 | * The router module 20 | * Pipeline: 21 | * (Eth & CPU) -> Forward Table Lookup -> ARP Cache Lookup -> Encoder -> (Eth) 22 | * | 23 | * ---> CPU (Forward table miss, ARP cache miss, or dest === localhost) 24 | */ 25 | class Router(PORT_NUM: Int) extends Module { 26 | val io = IO(new Bundle { 27 | val rx_clk = Input(Clock()) 28 | val tx_clk = Input(Clock()) 29 | 30 | val rx = Flipped(new AXIS(8)) 31 | val tx = new AXIS(8) 32 | 33 | val buf = new BufPort 34 | 35 | val cmd = Input(new Cmd) 36 | 37 | val axi = new AXI(64) 38 | }) 39 | 40 | val acceptorBridge = Module(new AsyncBridge(new Packet(PORT_NUM))) 41 | acceptorBridge.io.read.clk := this.clock 42 | 43 | val transmitterBridge = Module(new AsyncBridge(new EncoderUnit)) 44 | transmitterBridge.io.write.clk := this.clock 45 | transmitterBridge.io.write.progfull := DontCare 46 | 47 | val payloadBridge = Module(new AsyncBridge(new EncoderUnit, Consts.PAYLOAD_BUF, Consts.MAX_MTU)) 48 | 49 | val ctrl = Module(new Ctrl()) 50 | acceptorBridge.io.read.en := !ctrl.io.inputWait 51 | ctrl.cmd := io.cmd 52 | 53 | withClock(io.rx_clk) { 54 | val acceptor = Module(new Acceptor(PORT_NUM)) 55 | 56 | acceptor.io.rx <> io.rx 57 | acceptorBridge.io.write <> acceptor.io.writer 58 | acceptor.macs := RegNext(RegNext(ctrl.macs)) 59 | payloadBridge.io.write <> acceptor.io.payloadWriter 60 | } 61 | 62 | val forward = Module(new CuckooFT(PORT_NUM)) 63 | forward.ips := ctrl.ips 64 | ctrl.io.forward.stall <> forward.io.stall 65 | ctrl.io.forward.pause <> forward.io.pause 66 | forward.io.input := acceptorBridge.io.read.data 67 | forward.io.status := Mux(acceptorBridge.io.read.empty, Status.vacant, Status.normal) 68 | forward.io.axi <> io.axi 69 | 70 | val arp = Module(new ARPTable(PORT_NUM, 8)) 71 | arp.ips := ctrl.ips 72 | arp.macs := ctrl.macs 73 | arp.cmd := io.cmd 74 | ctrl.io.arp.stall <> arp.io.stall 75 | ctrl.io.arp.pause <> arp.io.pause 76 | forward.io.output <> arp.io.input 77 | forward.io.outputStatus <> arp.io.status 78 | 79 | val encoder = Module(new Encoder(PORT_NUM)) 80 | ctrl.io.encoder.stall <> encoder.io.stall 81 | ctrl.io.encoder.pause <> encoder.io.pause 82 | 83 | val packet = acceptorBridge.io.read.data 84 | 85 | encoder.io.input := arp.io.output 86 | encoder.io.status := arp.io.outputStatus 87 | encoder.io.writer <> transmitterBridge.io.write 88 | encoder.io.payloadReader <> payloadBridge.io.read 89 | 90 | val adapter = Module(new Adapter) 91 | adapter.toBuf <> io.buf 92 | encoder.toAdapter <> adapter.fromEnc 93 | adapter.toEnc <> encoder.fromAdapter 94 | 95 | withClock(io.tx_clk) { 96 | val transmitter = Module(new Transmitter) 97 | transmitter.io.reader <> transmitterBridge.io.read 98 | transmitter.io.tx <> io.tx 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/scala/router/top.scala: -------------------------------------------------------------------------------- 1 | package top 2 | 3 | import chisel3._ 4 | import data._ 5 | 6 | class Top extends Module { 7 | val io = IO(new Bundle { 8 | // Clk and rst are implicit 9 | 10 | val rx_clk = Input(Clock()) 11 | val tx_clk = Input(Clock()) 12 | 13 | val rx_tdata = Input(UInt(8.W)) 14 | val rx_tvalid = Input(Bool()) 15 | val rx_tlast = Input(Bool()) 16 | // Ignore user 17 | 18 | val tx_tdata = Output(UInt(8.W)) 19 | val tx_tvalid = Output(Bool()) 20 | val tx_tlast = Output(Bool()) 21 | val tx_tready = Input(Bool()) 22 | val tx_tuser = Output(Bool()) 23 | 24 | val buf_clk = Output(Clock()) 25 | val buf_addr = Output(UInt(32.W)) 26 | val buf_din = Output(UInt(8.W)) 27 | val buf_dout = Input(UInt(8.W)) 28 | val buf_we = Output(UInt(1.W)) 29 | 30 | val cmd = Input(UInt(64.W)) 31 | 32 | val axi = new AXI(64) 33 | }) 34 | 35 | val router = Module(new Router(4)) 36 | 37 | router.io.rx_clk := io.rx_clk 38 | router.io.tx_clk := io.tx_clk 39 | 40 | router.io.rx.tdata := io.rx_tdata 41 | router.io.rx.tvalid := io.rx_tvalid 42 | router.io.rx.tlast := io.rx_tlast 43 | router.io.rx.tready := DontCare 44 | 45 | io.tx_tdata := router.io.tx.tdata; 46 | io.tx_tlast := router.io.tx.tlast; 47 | io.tx_tvalid := router.io.tx.tvalid; 48 | router.io.tx.tready := io.tx_tready 49 | 50 | io.buf_addr := router.io.buf.addr 51 | io.buf_clk := router.io.buf.clk 52 | io.buf_din := router.io.buf.din 53 | router.io.buf.dout := io.buf_dout 54 | io.buf_we := router.io.buf.we.asUInt 55 | 56 | io.tx_tuser := false.B 57 | 58 | router.io.cmd := io.cmd.asTypeOf(router.io.cmd) 59 | 60 | router.io.axi <> io.axi 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/transmitter/transmitter.scala: -------------------------------------------------------------------------------- 1 | package transmitter 2 | 3 | import chisel3._ 4 | import data._ 5 | import _root_.util.AsyncReader 6 | import encoder.EncoderUnit 7 | 8 | class Transmitter extends Module { 9 | val io = IO(new Bundle { 10 | val reader = Flipped(new AsyncReader(new EncoderUnit)) 11 | val tx = new AXIS(8) 12 | }) 13 | 14 | io.tx.tdata := io.reader.data.data 15 | io.tx.tlast := io.reader.data.last 16 | io.tx.tvalid := !io.reader.empty 17 | io.reader.en := io.tx.tready 18 | io.reader.clk := this.clock 19 | } -------------------------------------------------------------------------------- /src/main/scala/util/asyncBridge.scala: -------------------------------------------------------------------------------- 1 | package util 2 | import chisel3._ 3 | import chisel3.core.withClock 4 | import chisel3.util._ 5 | import chisel3.core.StringParam 6 | import chisel3.core.IntParam 7 | import chisel3.core.Reset 8 | import scala.math.max 9 | 10 | class InnerBridge(val width: Int, val depth: Int, val thresh: Int) extends BlackBox(Map( 11 | "FIFO_MEMORY_TYPE" -> StringParam("distributed"), 12 | "FIFO_WRITE_DEPTH" -> IntParam(depth), 13 | "WRITE_DATA_WIDTH" -> IntParam(width), 14 | "READ_DATA_WIDTH" -> IntParam(width), 15 | "READ_MODE" -> StringParam("fwft"), 16 | "FIFO_READ_LATENCY" -> IntParam(0), 17 | "PROG_FULL_THRESH" -> IntParam(thresh) 18 | )) { 19 | val io = IO(new Bundle { 20 | val rst = Input(Reset()) 21 | 22 | val wr_clk = Input(Clock()) 23 | val wr_en = Input(Bool()) 24 | val din = Input(UInt(width.W)) 25 | val full = Output(Bool()) 26 | 27 | val rd_clk = Input(Clock()) 28 | val rd_en = Input(Bool()) 29 | val dout = Output(UInt(width.W)) 30 | val empty = Output(Bool()) 31 | 32 | val prog_full = Output(Bool()) 33 | }) 34 | 35 | override def desiredName: String = "xpm_fifo_async" 36 | } 37 | 38 | class AsyncReader[+Type <: Data](t: Type) extends Bundle { 39 | val clk = Input(Clock()) 40 | val en = Input(Bool()) 41 | val data = Output(t) 42 | val empty = Output(Bool()) 43 | 44 | override def cloneType: this.type = new AsyncReader(t).asInstanceOf[this.type] 45 | } 46 | 47 | class AsyncWriter[+Type <: Data](t: Type) extends Bundle { 48 | val clk = Input(Clock()) 49 | val en = Input(Bool()) 50 | val data = Input(t) 51 | val full = Output(Bool()) 52 | 53 | val progfull = Output(Bool()) 54 | 55 | override def cloneType: this.type = new AsyncWriter(t).asInstanceOf[this.type] 56 | } 57 | 58 | class AsyncBridge[+Type <: Data](t: Type, depth: Int = 16, spaceThresh: Int = -1) extends Module { 59 | val io = IO(new Bundle { 60 | // Implict reset 61 | val write = new AsyncWriter(t.cloneType) 62 | val read = new AsyncReader(t) 63 | }) 64 | 65 | def xpm_fifo_async_impl = { 66 | val thresh = if(spaceThresh < 0) { 7 } else { depth - spaceThresh + 1 } 67 | 68 | val width = t.getWidth 69 | val inner = Module(new InnerBridge(width, depth, thresh)) 70 | 71 | inner.io.rst := this.reset 72 | 73 | inner.io.wr_clk := io.write.clk 74 | inner.io.wr_en := io.write.en 75 | inner.io.din := io.write.data.asUInt 76 | io.write.full := inner.io.full 77 | io.write.progfull := inner.io.prog_full 78 | 79 | inner.io.rd_clk := io.read.clk 80 | inner.io.rd_en := io.read.en 81 | io.read.data := inner.io.dout.asTypeOf(t) 82 | io.read.empty := inner.io.empty 83 | } 84 | 85 | def fake_register_impl = { 86 | // We ignore read.clk and write.clk, and use the default clock to make our life easier. 87 | 88 | var buf = Mem(depth, t) 89 | 90 | var start = RegInit(0.U(log2Ceil(depth ).W)) 91 | val end = RegInit(0.U(log2Ceil(depth).W)) 92 | 93 | // the maximum size is actually depth - 1 since we have to distinguish between the status of empty and full 94 | val full = start === end + 1.U 95 | val empty = start === end 96 | val size = end - start 97 | 98 | buf.write(end, io.write.data) 99 | io.write.full := full 100 | io.write.progfull := (size + max(0, spaceThresh).U) > (depth - 1).U || full // TODO: we haven't considered the overflow yet 101 | when(io.write.en && !full) { 102 | end := end + 1.U 103 | } 104 | 105 | io.read.data := buf.read(start) 106 | io.read.empty := empty 107 | when(io.read.en && !empty) { 108 | start := start + 1.U 109 | } 110 | } 111 | 112 | if (Configs.isTesting) { 113 | fake_register_impl 114 | } else { 115 | xpm_fifo_async_impl 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/scala/util/config.scala: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | object Configs { 4 | var isTesting: Boolean = sys.env.getOrElse("CHISEL_TYPE", "RELEASE").toUpperCase == "TEST" 5 | var isRelease: Boolean = !isTesting 6 | } -------------------------------------------------------------------------------- /src/main/scala/util/consts.scala: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | 6 | object Consts { 7 | val PAYLOAD_BUF = 8192 8 | val MAX_MTU = 1600 9 | 10 | val CPUBUF_SIZE = 2048 11 | val CPUBUF_COUNT = 64 12 | 13 | val CUCKOO_LINE_COUNT = 1024 14 | val CUCKOO_ADDR_BASE = 0x400000 15 | val CUCKOO_LINE_WIDTH = 8 * 4 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/util/crc.scala: -------------------------------------------------------------------------------- 1 | 2 | // See https://github.com/chipsalliance/rocket-chip/blob/master/LICENSE.SiFive for license details. 3 | 4 | package util 5 | 6 | import chisel3._ 7 | import chisel3.util._ 8 | 9 | object CRC 10 | { 11 | // A divisor of 0x1d5 is interpretted to be x^8 + x^7 + x^6 + x^4 + x^2 + 1 12 | // Let n be the highest term in the divisor; n=8 in 0x1d5. 13 | // Then, this function calculates c mod d, returning an n-bit UInt. 14 | // coefficient.width must be <= width 15 | def apply(divisor: BigInt, coefficient: UInt, width: Integer): UInt = { 16 | require (divisor > 0 && divisor.testBit(0)) 17 | require (width > 0) 18 | assert (coefficient >> width === 0.U) 19 | val n = log2Floor(divisor) 20 | val m = width 21 | if (m <= n) return coefficient 22 | 23 | // Initialize the reduction matrix 24 | val array = Array.tabulate(m) { BigInt(1) << _ } 25 | // Reduce the matrix of terms larger than n 26 | for { 27 | i <- (n until m).reverse 28 | j <- 0 to n 29 | if divisor.testBit(j) 30 | } array(i-(n-j)) ^= array(i) 31 | // Construct the circuit 32 | Cat(Seq.tabulate(n) { i => (array(i).U & coefficient).xorR } .reverse) 33 | } 34 | 35 | // Find more great CRC polynomials here: https://users.ece.cmu.edu/~koopman/crc/ 36 | val CRC_16F_4_2 = BigInt(0x1a2eb) // HD=4 for <32751 bits and HD=6 for <93 bits 37 | val CRC_8F_6 = BigInt(0x137) // https://users.ece.cmu.edu/~koopman/crc/hd6.html 38 | } 39 | -------------------------------------------------------------------------------- /src/test/scala/acceptor.scala: -------------------------------------------------------------------------------- 1 | package acceptor 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import chisel3.iotesters.{Driver, PeekPokeTester} 6 | 7 | import data.Packet 8 | import data.AXIS 9 | 10 | class AcceptorWrapper(PORT_COUNT: Int) extends Module { 11 | val io = IO(new Bundle { 12 | val rx = Flipped(new AXIS(8)) 13 | 14 | val packet = Output(new Packet(PORT_COUNT)) 15 | val valid = Output(new Bool()) 16 | }) 17 | 18 | val c = Module(new Acceptor(PORT_COUNT)) 19 | c.io.rx <> io.rx 20 | c.io.writer <> DontCare 21 | c.io.payloadWriter <> DontCare 22 | io.valid := c.io.writer.en 23 | io.packet := c.io.writer.data 24 | } 25 | 26 | class AcceptorTest(PORT_COUNT: Int, c: AcceptorWrapper) extends PeekPokeTester(c) { 27 | val packetsStr = List( 28 | // ARP request, local IP for port 1 is 10.0.1.1, expected to emit an ARP Response 29 | "FF FF FF FF FF FF 00 12 17 CD DD 12 81 00 00 01 08 06 00 01 08 00 06 04 00 01 00 12 17 CD DD 12 0A 00 01 02 00 00 00 00 00 00 0A 00 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 61 84 A4 4D", 30 | // IP ping 10.0.1.2 -> 10.0.2.3 (With incorrect FCS). This should route to 10.0.2.2, and thus cause a ARP miss. Expected to emit four ARP Request 31 | "00 00 00 00 00 01 00 12 17 CD DD 12 81 00 00 01 08 00 45 00 00 54 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 02 03 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4", 32 | // ARP response from 10.0.3.1 33 | "00 00 00 00 00 03 06 05 04 03 02 01 81 00 00 03 08 06 00 01 08 00 06 04 00 02 06 05 04 03 02 01 0A 00 03 02 00 00 00 00 00 03 0A 00 03 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 61 84 A4 4D", 34 | // IP ping 10.0.1.2 -> 10.0.3.6 (With incorrect FCS). This should route to 10.0.3.2 35 | "00 00 00 00 00 01 00 12 17 CD DD 12 81 00 00 01 08 00 45 00 00 54 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 03 06 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4", 36 | // Very large IP packet (Fake size), should drop 37 | "00 00 00 00 00 01 00 12 17 CD DD 12 81 00 00 01 08 00 45 00 FF FF 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 03 06 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4" 38 | ) 39 | 40 | val results = List( 41 | 2, // ARP 42 | 1, // IP 43 | 2, // ARP 44 | 1, // IP 45 | -1 // error packet 46 | ) 47 | 48 | for (p_id <- 0 until packetsStr.length) { 49 | val p = packetsStr(p_id) 50 | val sendBuf = p.split(" ").map(x => Integer.parseInt(x, 16)) 51 | var packetType = BigInt(-1) 52 | println("sending packet: " + p) 53 | for (i <- 0 until sendBuf.length) { 54 | poke(c.io.rx.tdata, sendBuf(i)) 55 | poke(c.io.rx.tvalid, true) 56 | poke(c.io.rx.tlast, i == sendBuf.length - 1) 57 | step(1) 58 | //println("packtype: " + peek(c.io.packet.eth.pactype)) 59 | //println("iplen: " + peek(c.io.packet.ip.len)) 60 | //println("ttl: " + peek(c.io.packet.ip.ttl)) 61 | if (peek(c.io.valid) == BigInt(1)) { 62 | packetType = peek(c.io.packet.eth.pactype) 63 | } 64 | } 65 | assert(packetType.toInt == results(p_id)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/scala/cuckoo.scala: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import chisel3.iotesters.{Driver, TesterOptionsManager, PeekPokeTester} 6 | 7 | class TestCuckoo(c: HashTable, KeySize: Int, ValueSize: Int) extends PeekPokeTester(c) { 8 | // gerenate keys and val 9 | // TODO: When the #keys is larger than the 250, the hash table may fail 10 | val keys = for (i <- 0 until 200) yield scala.util.Random.nextInt(2047483647) 11 | val vals = for (i <- 0 until 200) yield scala.util.Random.nextInt(255) 12 | 13 | def waitWhenStalled(): Unit = { 14 | var cnt = 0 15 | while (peek(c.io.stall) == BigInt(1)) { 16 | cnt = cnt + 1 17 | assert(cnt <= 10) 18 | step(1) 19 | } 20 | // println(s"Waited $cnt steps") 21 | } 22 | 23 | // insert 24 | for (i <- 0 until keys.length) { 25 | poke(c.io.key, keys(i)) 26 | poke(c.io.value, vals(i)) 27 | poke(c.io.setEn, true) 28 | poke(c.io.queryEn, false) 29 | step(1) 30 | waitWhenStalled() 31 | println("i = " + i + " key_hash = " + peek(c.io.dbg_keyhash) + " key = " + keys(i)) 32 | // for (j <- 0 until 4) { 33 | // println("key = " + peek(c.io.dbg.pairs(j).key) + ", value = " + peek(c.io.dbg.pairs(j).value) + ", valid = " + peek(c.io.dbg.pairs(j).valid)) 34 | // } 35 | assert(peek(c.io.setSucc) == BigInt(1)) 36 | } 37 | 38 | // query 39 | for (i <- 0 until keys.length) { 40 | poke(c.io.key, keys(i)) 41 | poke(c.io.setEn, false) 42 | poke(c.io.queryEn, true) 43 | step(1) 44 | waitWhenStalled() 45 | //println(peek(c.io.answerValue).toString) 46 | assert(peek(c.io.answerFound) == BigInt(1)) 47 | assert(peek(c.io.answerValue) == BigInt(vals(i))) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/scala/forward/linear.scala: -------------------------------------------------------------------------------- 1 | package forward 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import chisel3.iotesters.PeekPokeTester 6 | 7 | import data._ 8 | 9 | class LLFTTestModule(PORT_COUNT: Int) extends Module { 10 | val io = IO(new Bundle { 11 | val cnt = Input(UInt(8.W)) 12 | val idle = Output(Bool()) 13 | val pass = Output(Bool()) 14 | }) 15 | 16 | val c = Module(new LLFT(PORT_COUNT)) 17 | val INPUTS = VecInit(Seq( 18 | 0x0A0001AA.U(32.W), 19 | 0x0A000319.U(32.W) 20 | )) 21 | val OUTPUTS = VecInit(Seq( 22 | 0x0A000102.U(32.W), 23 | 0x0A000302.U(32.W) 24 | )) 25 | 26 | //val cnt = RegInit(0.U) 27 | 28 | c.io <> DontCare 29 | 30 | c.io.input.eth.pactype := PacType.ipv4 31 | c.io.input.ip.dest := INPUTS(io.cnt) 32 | c.io.status := Status.normal 33 | c.io.pause := 0.U 34 | 35 | io.idle := !c.io.stall 36 | io.pass := OUTPUTS(io.cnt) === c.io.output.lookup.nextHop 37 | } 38 | 39 | class LLFTTest(c: LLFTTestModule, PORT_COUNT: Int) extends PeekPokeTester(c) { 40 | for (cnt <- 0 until 2) { 41 | var done = false 42 | do { 43 | poke(c.io.cnt, cnt.U(8.W)) 44 | step(1) 45 | done = peek(c.io.idle) == BigInt(1) 46 | } while (!done) 47 | expect(c.io.pass, true) 48 | } 49 | } -------------------------------------------------------------------------------- /src/test/scala/main.scala: -------------------------------------------------------------------------------- 1 | import chisel3.iotesters.Driver 2 | import forward.{LLFTTestModule, LLFTTest} 3 | import acceptor.{AcceptorWrapper, AcceptorTest} 4 | import cuckoo.{HashTable, TestCuckoo} 5 | 6 | 7 | object TestMain { 8 | def main(args: Array[String]): Unit = { 9 | println("🧪 TestCuckoo...") 10 | if (!Driver(() => new HashTable(32, 8))(c => new TestCuckoo(c, 32, 8))) System.exit(1) 11 | println("🧪 LLFTTest...") 12 | if (!Driver(() => new LLFTTestModule(4))(c => new LLFTTest(c, 4))) System.exit(1) 13 | println("🧪 AcceptorTest...") 14 | if (!Driver(() => new AcceptorWrapper(4))(c => new AcceptorTest(4, c))) System.exit(1) 15 | println("🧪 TopTest...") 16 | if (!Driver(() => new TopWrap())(c => new TopTest(c))) System.exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/scala/top.scala: -------------------------------------------------------------------------------- 1 | import scala.util.control.Breaks._ 2 | import chisel3._ 3 | import chisel3.util._ 4 | import chisel3.iotesters.{Driver, TesterOptionsManager, PeekPokeTester} 5 | import _root_.top.Top 6 | 7 | class TopWrap extends Module { 8 | val io = IO(new Bundle { 9 | // Clk and rst are implicit 10 | val rx_clk = Input(Clock()) 11 | val tx_clk = Input(Clock()) 12 | 13 | val rx_tdata = Input(UInt(8.W)) 14 | val rx_tvalid = Input(Bool()) 15 | val rx_tlast = Input(Bool()) 16 | // Ignore user 17 | 18 | val tx_tdata = Output(UInt(8.W)) 19 | val tx_tvalid = Output(Bool()) 20 | val tx_tlast = Output(Bool()) 21 | val tx_tready = Input(Bool()) 22 | val tx_tuser = Output(Bool()) 23 | }) 24 | 25 | val top = Module(new Top()) 26 | top.io <> io 27 | top.io.rx_clk := this.clock 28 | top.io.tx_clk := this.clock 29 | } 30 | 31 | class TopTest(c: TopWrap) extends PeekPokeTester(c) { 32 | val packetsStr = List( 33 | // ARP request, local IP for port 1 is 10.0.1.1, expected to emit an ARP Response 34 | "FF FF FF FF FF FF 00 12 17 CD DD 12 81 00 00 01 08 06 00 01 08 00 06 04 00 01 00 12 17 CD DD 12 0A 00 01 02 00 00 00 00 00 00 0A 00 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 61 84 A4 4D", 35 | // IP ping 10.0.1.2 -> 10.0.2.3 (With incorrect FCS). This should route to 10.0.2.2, and thus cause a ARP miss. Expected to emit four ARP Request 36 | "00 00 00 00 00 01 00 12 17 CD DD 12 81 00 00 01 08 00 45 00 00 54 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 02 03 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4", 37 | // ARP response from 10.0.3.1 38 | "00 00 00 00 00 03 06 05 04 03 02 01 81 00 00 03 08 06 00 01 08 00 06 04 00 02 06 05 04 03 02 01 0A 00 03 02 00 00 00 00 00 03 0A 00 03 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 61 84 A4 4D", 39 | // IP ping 10.0.1.2 -> 10.0.3.6 (With incorrect FCS). This should route to 10.0.3.2 40 | "00 00 00 00 00 01 00 12 17 CD DD 12 81 00 00 01 08 00 45 00 00 54 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 03 06 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4", 41 | // Very large IP packet (Fake size), should drop 42 | "00 00 00 00 00 01 00 12 17 CD DD 12 81 00 00 01 08 00 45 00 FF FF 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 03 06 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4" 43 | ) 44 | 45 | val expectedPackets = List( 46 | List("00 12 17 CD DD 12 00 00 00 00 00 01 81 00 00 01 08 06 00 01 08 00 06 04 00 02 00 00 00 00 00 01 0A 00 01 01 00 12 17 CD DD 12 0A 00 01 02"), 47 | List( 48 | "FF FF FF FF FF FF 00 00 00 00 00 01 81 00 00 01 08 06 00 01 08 00 06 04 00 01 00 00 00 00 00 01 0A 00 01 01 00 00 00 00 00 00 0A 00 02 02", 49 | "FF FF FF FF FF FF 00 00 00 00 00 02 81 00 00 02 08 06 00 01 08 00 06 04 00 01 00 00 00 00 00 02 0A 00 02 01 00 00 00 00 00 00 0A 00 02 02", 50 | "FF FF FF FF FF FF 00 00 00 00 00 03 81 00 00 03 08 06 00 01 08 00 06 04 00 01 00 00 00 00 00 03 0A 00 03 01 00 00 00 00 00 00 0A 00 02 02", 51 | "FF FF FF FF FF FF 00 00 00 00 00 04 81 00 00 04 08 06 00 01 08 00 06 04 00 01 00 00 00 00 00 04 0A 00 04 01 00 00 00 00 00 00 0A 00 02 02" 52 | ), 53 | List(), 54 | List("06 05 04 03 02 01 00 00 00 00 00 03 81 00 00 03 08 00 45 00 00 54 32 22 00 00 40 01 34 85 0A 00 01 02 0A 00 03 06 08 00 80 D5 7B 43 00 03 5C BF EC 2E 00 03 C7 EF 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 26 7F 6C E4"), 55 | List() 56 | ) 57 | 58 | var recvBuf = Vector[Int]() 59 | 60 | def peekData(pid: Int, rid: Int): Int = { 61 | val valid = peek(c.io.tx_tvalid) == BigInt(1) 62 | val last = peek(c.io.tx_tlast) == BigInt(1) 63 | val data = peek(c.io.tx_tdata).toInt 64 | val user = peek(c.io.tx_tuser) == BigInt(1) 65 | 66 | if (valid) { 67 | recvBuf = recvBuf :+ data 68 | } 69 | 70 | if (user) { 71 | println("User Package") 72 | } 73 | 74 | if (last && recvBuf.length > 0) { 75 | val recivedPacketStr = recvBuf.map(x => "%02x".format(x).toUpperCase).mkString(" ") 76 | println("recived packet: " + recivedPacketStr) 77 | assert(recivedPacketStr == expectedPackets(pid)(rid)) 78 | recvBuf = Vector[Int]() 79 | rid + 1 80 | } else { 81 | rid 82 | } 83 | } 84 | 85 | for (pid <- 0 until packetsStr.length) { 86 | var recivedPacketCnt = 0 87 | val p = packetsStr(pid) 88 | val sendBuf = p.split(" ").map(x => Integer.parseInt(x, 16)) 89 | println("sending packet: " + p) 90 | for (i <- 0 until sendBuf.length) { 91 | poke(c.io.rx_tdata, sendBuf(i)) 92 | poke(c.io.rx_tvalid, true) 93 | poke(c.io.rx_tlast, i == sendBuf.length - 1) 94 | 95 | poke(c.io.tx_tready, true) 96 | step(1) 97 | recivedPacketCnt = peekData(pid, recivedPacketCnt) 98 | } 99 | breakable { 100 | for (i <- 0 until 1000) { 101 | poke(c.io.rx_tvalid, false) 102 | poke(c.io.tx_tready, true) 103 | 104 | step(1) 105 | recivedPacketCnt = peekData(pid, recivedPacketCnt) 106 | //println("recivedPacketCnt: " + recivedPacketCnt) 107 | if (recivedPacketCnt == expectedPackets(pid).length && expectedPackets(pid).length != 0) { 108 | break 109 | } 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export CHISEL_TYPE=TEST 4 | 5 | sbt 'test:runMain TestMain' --------------------------------------------------------------------------------