-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
2574 lines (2349 loc) · 254 KB
/
index.html
1
2
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
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/font-awesome.min.css">
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"yoursite.com","root":"/","scheme":"Muse","version":"7.7.2","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":true},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
</script>
<meta name="description" content="技术的高低不是取决于你C/C++写得多么牛逼,而是取决于你掌握的资源有多少。">
<meta property="og:type" content="website">
<meta property="og:title" content="liangkangnan的博客">
<meta property="og:url" content="http://yoursite.com/index.html">
<meta property="og:site_name" content="liangkangnan的博客">
<meta property="og:description" content="技术的高低不是取决于你C/C++写得多么牛逼,而是取决于你掌握的资源有多少。">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="liangkangnan">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="http://yoursite.com/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false
};
</script>
<title>liangkangnan的博客</title>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<div>
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">liangkangnan的博客</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-fw fa-home"></i>首页</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section"><i class="fa fa-fw fa-tags"></i>标签</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="fa fa-fw fa-th"></i>分类</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-fw fa-archive"></i>归档</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about/" rel="section"><i class="fa fa-fw fa-user"></i>关于</a>
</li>
<li class="menu-item menu-item-contact">
<a href="/contact/" rel="section"><i class="fa fa-fw fa-envelope"></i>联系</a>
</li>
</ul>
</nav>
</div>
</header>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
<a href="https://gitee.com/liangkangnan" class="github-corner" title="Follow me on Gitee" aria-label="Follow me on Gitee" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content">
<div class="posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2020/04/29/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99RISC-V%E5%A4%84%E7%90%86%E5%99%A8/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/me.png">
<meta itemprop="name" content="liangkangnan">
<meta itemprop="description" content="技术的高低不是取决于你C/C++写得多么牛逼,而是取决于你掌握的资源有多少。">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="liangkangnan的博客">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a href="/2020/04/29/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99RISC-V%E5%A4%84%E7%90%86%E5%99%A8/" class="post-title-link" itemprop="url">从零开始写RISC-V处理器</a>
</h1>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2020-04-29 20:44:58" itemprop="dateCreated datePublished" datetime="2020-04-29T20:44:58+08:00">2020-04-29</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-check-o"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2021-01-31 21:28:36" itemprop="dateModified" datetime="2021-01-31T21:28:36+08:00">2021-01-31</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/RISC-V/" itemprop="url" rel="index"><span itemprop="name">RISC-V</span></a>
</span>
</span>
<br>
<span class="post-meta-item" title="本文字数">
<span class="post-meta-item-icon">
<i class="fa fa-file-word-o"></i>
</span>
<span class="post-meta-item-text">本文字数:</span>
<span>60k</span>
</span>
<span class="post-meta-item" title="阅读时长">
<span class="post-meta-item-icon">
<i class="fa fa-clock-o"></i>
</span>
<span class="post-meta-item-text">阅读时长 ≈</span>
<span>55 分钟</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>第一次听到RISC-V这个词大概是两年前,当时觉得它也就是和MIPS这些CPU架构没什么区别,因此也就不以为然了。直到去年,RISC-V这个词开始频繁地出现在微信和其他网站上,此时我再也不能无动于衷了,于是开始在网上搜索有关它的资料,开始知道有SiFive这个网站,知道SiFive出了好几款RISC-V的开发板。可是最便宜的那一块开发板都要700多RMB,最后还是忍痛出手了一块。由于平时上班比较忙,所以玩这块板子的时间并不多,也就是晚上下班后和周末玩玩,自己照着芯片手册写了几个例程在板子上跑跑而已。</p>
<p>再后来发现网上已经有如何设计RISC-V处理器的书籍卖了,并且这个处理器是开源的,于是果断买了一本来阅读并浏览了它的开源代码,最后表示看不懂。从那之后一个“从零开始写RISC-V处理器”的想法开始不断地出现在我的脑海里。我心里是很想学习、深入研究RISC-V的,但是一直以来都没有verilog和FPGA的基础,可以说是CPU设计领域里的门外汉,再加上很少业余时间,为此一度犹豫不决。但是直觉告诉我已近不能再等了,我决定开始自学verilog和FPGA,用简单易懂的方式写一个RISC-V处理器并且把它开源出来,在提高自身的同时希望能帮助到那些想入门RISC-V的同学,于是tinyriscv终于在2019年12月诞生了。</p>
<p>tinyriscv是一个采用三级流水线设计,顺序、单发射、单核的32位RISC-V处理器,全部代码都是采用verilog HDL语言编写,核心设计思想是简单、易懂。</p>
<h1 id="绪论"><a href="#绪论" class="headerlink" title="绪论"></a>绪论</h1><h2 id="RISC-V是什么"><a href="#RISC-V是什么" class="headerlink" title="RISC-V是什么"></a>RISC-V是什么</h2><p>RISC,即精简指令集处理器,是相对于X86这种CISC(复杂指令集处理器)来说的。RISC-V中的V是罗马数字,也即阿拉伯数字中的5,就是指第5代RISC。</p>
<p>RISC-V是一种指令集架构,和ARM、MIPS这些是属于同一类东西。RISC-V诞生于2010年,最大的特点是开源,任何人都可以设计RISC-V架构的处理器并且不会有任何版权问题。</p>
<h2 id="既生ARM,何生RISC-V"><a href="#既生ARM,何生RISC-V" class="headerlink" title="既生ARM,何生RISC-V"></a>既生ARM,何生RISC-V</h2><p>ARM是一种很优秀的处理器,这一点是无可否认的,在RISC处理器中是处于绝对老大的地位。但是ARM是闭源的,要设计基于ARM的处理器是要交版权费的,或者说要购买ARM的授权,而且这授权费用是昂贵的。</p>
<p>RISC-V的诞生并不是偶然的,而是必然的,为什么?且由我从以下两大领域进行说明。</p>
<p>先看开源软件领域(或者说是操作系统领域),Windows是闭源的,Linux是开源的,Linux有多成功、对开源软件有多重要的意义,这个不用多说了吧。再看手机操作系统领域,iOS是闭源的,Android是开源的,Android有多成功,这个也不用多说了吧。对于RISC处理器领域,由于有了ARM的闭源,必然就会有另外一种开源的RISC处理器。RISC-V之于CPU的意义,就好比Linux之于开源软件的意义。</p>
<p>或者你会说现在也有好多开源的处理器架构啊,比如MIPS等等,为什么偏偏是RISC-V?这个在这里我就不细说了,我只想说一句:大部分人能看到的机遇不会是一个好的机遇,你懂的。</p>
<p>可以说未来十年乃至更长时间内不会有比RISC-V更优秀的开源处理器架构出现。错过RISC-V,你注定要错过一个时代。</p>
<h2 id="浅谈Verilog"><a href="#浅谈Verilog" class="headerlink" title="浅谈Verilog"></a>浅谈Verilog</h2><p>verilog,确切来说应该是verilog HDL(Hardware Description Language ),从它的名字就可以知道这是一种硬件描述语言。首先它是一种语言,和C语言、C++语言一样是一种编程语言,那么verilog描述的是什么硬件呢?描述电阻?描述电容?描述运算放大器?都不是,它描述的是数字电路里的硬件,比如与、非门、触发器、锁存器等等。</p>
<p>既然是编程语言,那一定会有它的语法,学过C语言的同学再来看verilog得代码,会发现有很多地方是相似的。</p>
<p>verilog的语法并不难,难的是什么时候该用wire类型,什么时候该用reg类型,什么时候该用assign来描述电路,什么时候该用always来描述电路。assign能描述组合逻辑电路,always也能描述组合逻辑电路,两者有什么区别呢?</p>
<h2 id="用always描述组合逻辑电路"><a href="#用always描述组合逻辑电路" class="headerlink" title="用always描述组合逻辑电路"></a>用always描述组合逻辑电路</h2><p>我们知道数字电路里有两大类型的电路,一种是组合逻辑电路,另外一种是时序逻辑电路。组合逻辑电路不需要时钟作为触发条件,因此输入会立即(不考虑延时)反映到输出。时序逻辑电路以时钟作为触发条件,时钟的上升沿到来时输入才会反映到输出。</p>
<p>在verilog中,assign能描述组合逻辑电路,always也能描述组合逻辑电路。对于简单的组合逻辑电路的话两者描述起来都比较好懂、容易理解,但是一旦到了复杂的组合逻辑电路,如果用assign描述的话要么是一大串要么是要用好多个assign,不容易弄明白。但是用always描述起来却是非常容易理解的。</p>
<p>既然这样,那全部组合逻辑电路都用always来描述好了,呵呵,既然assign存在就有它的合理性。</p>
<p>用always描述组合逻辑电路时要注意避免产生锁存器,if和case的分支情况要写全。</p>
<p>在tinyriscv中用了大量的always来描述组合逻辑电路,特别是在译码和执行阶段。</p>
<h2 id="数字电路设计中的时序问题"><a href="#数字电路设计中的时序问题" class="headerlink" title="数字电路设计中的时序问题"></a>数字电路设计中的时序问题</h2><p>要分析数字电路中的时序问题,就一定要提到以下这个模型。</p>
<p><img src="/2020/04/29/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99RISC-V%E5%A4%84%E7%90%86%E5%99%A8/1_0.jpg" alt="时序模型"></p>
<p>其中对时序影响最大的是上图中的组合逻辑电路。所以要避免时序问题,最简单的方法减小组合逻辑电路的延时。组合逻辑电路里的串联级数越多延时就越大,实在没办法减小串联级数时,可以采用流水线的方式将这些级数用触发器隔开。</p>
<h2 id="流水线设计"><a href="#流水线设计" class="headerlink" title="流水线设计"></a>流水线设计</h2><p>要设计处理器的话,流水线是绕不开的。当然你也可以抬杠说:”用状态机也可以实现处理器啊,不一定要用流水线。”</p>
<p>采用流水线设计方式,不但可以提高处理器的工作频率,还可以提高处理器的效率。但是流水线并不是越长越好,流水线越长要使用的资源就越多、面积就越大。</p>
<p>在设计一款处理器之前,首先要确定好所设计的处理器要达到什么样的性能(或者说主频最高是多少),所使用的资源的上限是多少,功耗范围是多少。如果一味地追求性能而不考虑资源和功耗的话,那么所设计出来的处理器估计就只能用来玩玩,或者做做学术研究。</p>
<p>tinyriscv采用的是三级流水线,即取指、译码和执行,设计的目标就是要对标ARM的Cortex-M3系列处理器。</p>
<h2 id="代码风格"><a href="#代码风格" class="headerlink" title="代码风格"></a>代码风格</h2><p>代码风格其实并没有一种标准,但是并不代表代码风格不重要。好的代码风格可以让别人看你的代码时有一种赏心悦目的感觉。哪怕代码只是写给自己看,也一定要养成好的代码风格的习惯。tinyriscv的代码风格在很大程度上沿用了写C语言代码所采用的风格。</p>
<p>下面介绍tinyriscv的一些主要的代码风格。</p>
<h3 id="缩进"><a href="#缩进" class="headerlink" title="缩进"></a>缩进</h3><p>统一使用4个空格。</p>
<h3 id="if语句"><a href="#if语句" class="headerlink" title="if语句"></a>if语句</h3><p>不管if语句下面有多少行语句,if下面的语句都由begin…end包起来,并且begin在if的最后,如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">if (a == 1'b1) begin</span><br><span class="line"> c <= b;</span><br><span class="line">end else begin</span><br><span class="line"> c <= d;</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<h3 id="case语句"><a href="#case语句" class="headerlink" title="case语句"></a>case语句</h3><p>对于每一个分支情况,不管有多少行语句,都由begin…end包起来,如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">case (a)</span><br><span class="line"> c: begin</span><br><span class="line"> e = g;</span><br><span class="line"> end</span><br><span class="line"> default: begin</span><br><span class="line"> b = t;</span><br><span class="line"> end</span><br><span class="line">endcase</span><br></pre></td></tr></table></figure>
<h3 id="always语句"><a href="#always语句" class="headerlink" title="always语句"></a>always语句</h3><p>always语句后跟begin,如下所示:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">always @ (posedge clk) begin</span><br><span class="line"> a <= b;</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>=、==、<=、>=、+、-、*、/、@等符号左右各有一个空格。</p>
<p>,和:符号后面有一个空格。</p>
<p>对于模块的输入信号,不省略wire关键字。</p>
<p>每个文件的最后留一行空行。</p>
<p>if、case、always后面都有一个空格。</p>
<h1 id="硬件篇"><a href="#硬件篇" class="headerlink" title="硬件篇"></a>硬件篇</h1><p>硬件篇主要介绍tinyriscv的verilog代码设计。</p>
<p>tinyriscv整体框架如图2_1所示。</p>
<p><img src="/2020/04/29/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99RISC-V%E5%A4%84%E7%90%86%E5%99%A8/2_0.jpg" alt></p>
<p>图2_1 tinyriscv整体框架</p>
<p>可见目前tinyriscv已经不仅仅是一个内核了,而是一个小型的SOC,包含一些简单的外设,如timer、uart_tx等。</p>
<p>tinyriscv SOC输入输出信号有两部分,一部分是系统时钟clk和复位信号rst,另一部分是JTAG调试信号,TCK、TMS、TDI和TDO。</p>
<p>上图中的小方框表示一个个模块,方框里面的文字表示模块的名字,箭头则表示模块与模块之间的的输入输出关系。</p>
<p>下面简单介绍每个模块的主要作用。</p>
<p>jtag_top:调试模块的顶层模块,主要有三大类型的信号,第一种是读写内存的信号,第二种是读写寄存器的信号,第三种是控制信号,比如复位MCU,暂停MCU等。</p>
<p>pc_reg:PC寄存器模块,用于产生PC寄存器的值,该值会被用作指令存储器的地址信号。</p>
<p>if_id:取指到译码之间的模块,用于将指令存储器输出的指令打一拍后送到译码模块。</p>
<p>id:译码模块,纯组合逻辑电路,根据if_id模块送进来的指令进行译码。当译码出具体的指令(比如add指令)后,产生是否写寄存器信号,读寄存器信号等。由于寄存器采用的是异步读方式,因此只要送出读寄存器信号后,会马上得到对应的寄存器数据,这个数据会和写寄存器信号一起送到id_ex模块。</p>
<p>id_ex:译码到执行之间的模块,用于将是否写寄存器的信号和寄存器数据打一拍后送到执行模块。</p>
<p>ex:执行模块,纯组合逻辑电路,根据具体的指令进行相应的操作,比如add指令就执行加法操作等。此外,如果是lw等访存指令的话,则会进行读内存操作,读内存也是采用异步读方式。最后将是否需要写寄存器、写寄存器地址,写寄存器数据信号送给regs模块,将是否需要写内存、写内存地址、写内存数据信号送给rib总线,由总线来分配访问的模块。</p>
<p>div:除法模块,采用试商法实现,因此至少需要32个时钟才能完成一次除法操作。</p>
<p>ctrl:控制模块,产生暂停流水线、跳转等控制信号。</p>
<p>clint:核心本地中断模块,对输入的中断请求信号进行总裁,产生最终的中断信号。</p>
<p>rom:程序存储器模块,用于存储程序(bin)文件。</p>
<p>ram:数据存储器模块,用于存储程序中的数据。</p>
<p>timer:定时器模块,用于计时和产生定时中断信号。目前支持RTOS时需要用到该定时器。</p>
<p>uart_tx:串口发送模块,主要用于调试打印。</p>
<p>gpio:简单的IO口模块,主要用于点灯调试。</p>
<p>spi:目前只有master角色,用于访问spi从机,比如spi norflash。</p>
<h2 id="PC寄存器"><a href="#PC寄存器" class="headerlink" title="PC寄存器"></a>PC寄存器</h2><p>PC寄存器模块所在的源文件:rtl/core/pc_reg.v</p>
<p>PC寄存器模块的输入输出信号如下表所示:</p>
<table>
<thead>
<tr>
<th align="center">序号</th>
<th align="center">信号名</th>
<th align="center">输入/输出</th>
<th align="center">位宽(bits)</th>
<th align="center">说明</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">clk</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">时钟输入信号</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">rst</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">复位输入信号</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">jump_flag_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">跳转标志</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">jump_addr_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">跳转地址,即跳转到该地址</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">hold_flag_i</td>
<td align="center">输入</td>
<td align="center">3</td>
<td align="center">暂停标志,即PC寄存器的值保持不变</td>
</tr>
<tr>
<td align="center">6</td>
<td align="center">jtag_reset_flag_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">复位标志,即设置为复位后的值</td>
</tr>
<tr>
<td align="center">7</td>
<td align="center">pc_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">PC寄存器值,即从该值处取指</td>
</tr>
</tbody></table>
<p>PC寄存器模块代码比较简单,直接贴出来:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">always @ (posedge clk) begin</span><br><span class="line"> // 复位</span><br><span class="line"> if (rst == `RstEnable || jtag_reset_flag_i == 1'b1) begin</span><br><span class="line"> pc_o <= `CpuResetAddr;</span><br><span class="line"> // 跳转</span><br><span class="line"> end else if (jump_flag_i == `JumpEnable) begin</span><br><span class="line"> pc_o <= jump_addr_i;</span><br><span class="line"> // 暂停</span><br><span class="line"> end else if (hold_flag_i >= `Hold_Pc) begin</span><br><span class="line"> pc_o <= pc_o;</span><br><span class="line"> // 地址加4</span><br><span class="line"> end else begin</span><br><span class="line"> pc_o <= pc_o + 4'h4;</span><br><span class="line"> end</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<p>第3行,PC寄存器的值恢复到原始值(复位后的值)有两种方式,第一种不用说了,就是复位信号有效。第二种是收到jtag模块发过来的复位信号。PC寄存器复位后的值为CpuResetAddr,即32’h0,可以通过改变CpuResetAddr的值来改变PC寄存器的复位值。</p>
<p>第6行,判断跳转标志是否有效,如果有效则直接将PC寄存器的值设置为jump_addr_i的值。因此可以知道,所谓的跳转就是改变PC寄存器的值,从而使CPU从该跳转地址开始取指。</p>
<p>第9行,判断暂停标志是否大于等于Hold_Pc,该值为3’b001。如果是,则保持PC寄存器的值不变。这里可能会有疑问,为什么Hold_Pc的值不是一个1bit的信号。因为这个暂停标志还会被if_id和id_ex模块使用,如果仅仅需要暂停PC寄存器的话,那么if_id模块和id_ex模块是不需要暂停的。当需要暂停if_id模块时,PC寄存器也会同时被暂停。当需要暂停id_ex模块时,那么整条流水线都会被暂停。</p>
<p>第13行,将PC寄存器的值加4。在这里可以知道,tinyriscv的取指地址是4字节对齐的,每条指令都是32位的。</p>
<h2 id="通用寄存器"><a href="#通用寄存器" class="headerlink" title="通用寄存器"></a>通用寄存器</h2><p>通用寄存器模块所在的源文件:rtl/core/regs.v</p>
<p>一共有32个通用寄存器x0~x31,其中寄存器x0是只读寄存器并且其值固定为0。</p>
<p>通用寄存器的输入输出信号如下表所示:</p>
<table>
<thead>
<tr>
<th align="center">序号</th>
<th align="center">信号名</th>
<th align="center">输入/输出</th>
<th align="center">位宽(bits)</th>
<th align="center">说明</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">clk</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">时钟输入</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">rst</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">复位输入</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">we_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">来自执行模块的写使能</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">waddr_i</td>
<td align="center">输入</td>
<td align="center">5</td>
<td align="center">来自执行模块的写地址</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">wdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">来自执行模块的写数据</td>
</tr>
<tr>
<td align="center">6</td>
<td align="center">jtag_we_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">来自jtag模块的写使能</td>
</tr>
<tr>
<td align="center">7</td>
<td align="center">jtag_addr_i</td>
<td align="center">输入</td>
<td align="center">5</td>
<td align="center">来自jtag模块的写地址</td>
</tr>
<tr>
<td align="center">8</td>
<td align="center">jtag_data_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">来自jtag模块的写数据</td>
</tr>
<tr>
<td align="center">9</td>
<td align="center">raddr1_i</td>
<td align="center">输入</td>
<td align="center">5</td>
<td align="center">来自译码模块的寄存器1读地址</td>
</tr>
<tr>
<td align="center">10</td>
<td align="center">rdata1_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">寄存器1读数据</td>
</tr>
<tr>
<td align="center">11</td>
<td align="center">raddr2_i</td>
<td align="center">输入</td>
<td align="center">5</td>
<td align="center">来自译码模块的寄存器2读地址</td>
</tr>
<tr>
<td align="center">12</td>
<td align="center">rdata2_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">寄存器2读数据</td>
</tr>
<tr>
<td align="center">13</td>
<td align="center">jtag_data_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">jtag读数据</td>
</tr>
</tbody></table>
<p>注意,这里的寄存器1不是指x1寄存器,寄存器2也不是指x2寄存器。而是指一条指令里涉及到的两个寄存器(源寄存器1和源寄存器2)。一条指令可能会同时读取两个寄存器的值,所以有两个读端口。又因为jtag模块也会进行寄存器的读操作,所以一共有三个读端口。</p>
<p>读寄存器操作来自译码模块,并且读出来的寄存器数据也会返回给译码模块。写寄存器操作来自执行模块。</p>
<p>先看读操作的代码,如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">// 读寄存器1</span><br><span class="line">always @ (*) begin</span><br><span class="line"> if (rst == `RstEnable) begin</span><br><span class="line"> rdata1_o = `ZeroWord;</span><br><span class="line"> end else if (raddr1_i == `RegNumLog2'h0) begin</span><br><span class="line"> rdata1_o = `ZeroWord;</span><br><span class="line"> // 如果读地址等于写地址,并且正在写操作,则直接返回写数据</span><br><span class="line"> end else if (raddr1_i == waddr_i && we_i == `WriteEnable) begin</span><br><span class="line"> rdata1_o = wdata_i;</span><br><span class="line"> end else begin</span><br><span class="line"> rdata1_o = regs[raddr1_i];</span><br><span class="line"> end</span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">// 读寄存器2</span><br><span class="line">always @ (*) begin</span><br><span class="line"> if (rst == `RstEnable) begin</span><br><span class="line"> rdata2_o = `ZeroWord;</span><br><span class="line"> end else if (raddr2_i == `RegNumLog2'h0) begin</span><br><span class="line"> rdata2_o = `ZeroWord;</span><br><span class="line"> // 如果读地址等于写地址,并且正在写操作,则直接返回写数据</span><br><span class="line"> end else if (raddr2_i == waddr_i && we_i == `WriteEnable) begin</span><br><span class="line"> rdata2_o = wdata_i;</span><br><span class="line"> end else begin</span><br><span class="line"> rdata2_o = regs[raddr2_i];</span><br><span class="line"> end</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<p>可以看到两个寄存器的读操作几乎是一样的。因此在这里只解析读寄存器1那部分代码。</p>
<p>第5行,如果是读寄存器0(x0),那么直接返回0就可以了。</p>
<p>第8行,这涉及到数据相关问题。由于流水线的原因,当前指令处于执行阶段的时候,下一条指令则处于译码阶段。由于执行阶段不会写寄存器,而是在下一个时钟到来时才会进行寄存器写操作,如果译码阶段的指令需要上一条指令的结果,那么此时读到的寄存器的值是错误的。比如下面这两条指令:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">add x1, x2, x3</span><br><span class="line">add x4, x1, x5</span><br></pre></td></tr></table></figure>
<p>第二条指令依赖于第一条指令的结果。为了解决这个数据相关的问题就有了第8~9行的操作,即如果读寄存器等于写寄存器,则直接将要写的值返回给读操作。</p>
<p>第11行,如果没有数据相关,则返回要读的寄存器的值。</p>
<p>下面看写寄存器操作,代码如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// 写寄存器</span><br><span class="line">always @ (posedge clk) begin</span><br><span class="line"> if (rst == `RstDisable) begin</span><br><span class="line"> // 优先ex模块写操作</span><br><span class="line"> if ((we_i == `WriteEnable) && (waddr_i != `RegNumLog2'h0)) begin</span><br><span class="line"> regs[waddr_i] <= wdata_i;</span><br><span class="line"> end else if ((jtag_we_i == `WriteEnable) && (jtag_addr_i != `RegNumLog2'h0)) begin</span><br><span class="line"> regs[jtag_addr_i] <= jtag_data_i;</span><br><span class="line"> end</span><br><span class="line"> end</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<p>第5~6行,如果执行模块写使能并且要写的寄存器不是x0寄存器,则将要写的值写到对应的寄存器。</p>
<p>第7~8行,jtag模块的写操作。</p>
<p>CSR寄存器模块(csr_reg.v)和通用寄存器模块的读、写操作是类似的,这里就不重复了。</p>
<h2 id="取指"><a href="#取指" class="headerlink" title="取指"></a>取指</h2><p><strong>目前tinyriscv所有外设(包括rom和ram)、寄存器的读取都是与时钟无关的,或者说所有外设、寄存器的读取采用的是组合逻辑的方式</strong>。这一点非常重要!</p>
<p>tinyriscv并没有具体的取指模块和代码。PC寄存器模块的输出pc_o会连接到外设rom模块的地址输入,又由于rom的读取是组合逻辑,因此每一个时钟上升沿到来之前(时序是满足要求的),从rom输出的指令已经稳定在if_id模块的输入,当时钟上升沿到来时指令就会输出到id模块。</p>
<p>取到的指令和指令地址会输入到if_id模块(if_id.v),if_id模块是一个时序电路,作用是将输入的信号打一拍后再输出到译码(id.v)模块。</p>
<h2 id="译码"><a href="#译码" class="headerlink" title="译码"></a>译码</h2><p>译码模块所在的源文件:rtl/core/id.v</p>
<p>译码(id)模块是一个纯组合逻辑电路,主要作用有以下几点:</p>
<p>1.根据指令内容,解析出当前具体是哪一条指令(比如add指令)。</p>
<p>2.根据具体的指令,确定当前指令涉及的寄存器。比如读寄存器是一个还是两个,是否需要写寄存器以及写哪一个寄存器。</p>
<p>3.访问通用寄存器,得到要读的寄存器的值。</p>
<p>译码模块的输入输出信号如下表所示:</p>
<table>
<thead>
<tr>
<th align="center">序号</th>
<th align="center">信号名</th>
<th align="center">输入/输出</th>
<th align="center">位宽(bits)</th>
<th align="center">说明</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">rst</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">复位信号</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">inst_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">指令内容</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">inst_addr_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">指令地址</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">reg1_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">寄存器1输入数据</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">reg2_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">寄存器2输入数据</td>
</tr>
<tr>
<td align="center">6</td>
<td align="center">csr_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">CSR寄存器输入数据</td>
</tr>
<tr>
<td align="center">7</td>
<td align="center">ex_jump_flag_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">跳转信号</td>
</tr>
<tr>
<td align="center">8</td>
<td align="center">reg1_raddr_o</td>
<td align="center">输出</td>
<td align="center">5</td>
<td align="center">读寄存器1地址,即读哪一个通用寄存器</td>
</tr>
<tr>
<td align="center">9</td>
<td align="center">reg2_raddr_o</td>
<td align="center">输出</td>
<td align="center">5</td>
<td align="center">读寄存器2地址,即读哪一个通用寄存器</td>
</tr>
<tr>
<td align="center">10</td>
<td align="center">csr_raddr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">读csr寄存器地址,即读哪一个CSR寄存器</td>
</tr>
<tr>
<td align="center">11</td>
<td align="center">mem_req_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">向总线请求访问内存信号</td>
</tr>
<tr>
<td align="center">12</td>
<td align="center">inst_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">指令内容</td>
</tr>
<tr>
<td align="center">13</td>
<td align="center">inst_addr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">指令地址</td>
</tr>
<tr>
<td align="center">14</td>
<td align="center">reg1_rdata_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">通用寄存器1数据</td>
</tr>
<tr>
<td align="center">15</td>
<td align="center">reg2_rdata_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">通用寄存器2数据</td>
</tr>
<tr>
<td align="center">16</td>
<td align="center">reg_we_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">通用寄存器写使能</td>
</tr>
<tr>
<td align="center">17</td>
<td align="center">reg_waddr_o</td>
<td align="center">输出</td>
<td align="center">5</td>
<td align="center">通用寄存器写地址,即写哪一个通用寄存器</td>
</tr>
<tr>
<td align="center">18</td>
<td align="center">csr_we_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">CSR寄存器写使能</td>
</tr>
<tr>
<td align="center">19</td>
<td align="center">csr_rdata_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">CSR寄存器读数据</td>
</tr>
<tr>
<td align="center">20</td>
<td align="center">csr_waddr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">CSR寄存器写地址,即写哪一个CSR寄存器</td>
</tr>
</tbody></table>
<p>以add指令为例来说明如何译码。下图是add指令的编码格式:</p>
<p><img src="/2020/04/29/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99RISC-V%E5%A4%84%E7%90%86%E5%99%A8/inst_add.png" alt="add"></p>
<p>可知,add指令被编码成6部分内容。通过第1、4、6这三部分可以唯一确定当前指令是否是add指令。知道是add指令之后,就可以知道add指令需要读两个通用寄存器(rs1和rs2)和写一个通用寄存器(rd)。下面看具体的代码:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">case (opcode)</span><br><span class="line">...</span><br><span class="line"> `INST_TYPE_R_M: begin</span><br><span class="line"> if ((funct7 == 7'b0000000) || (funct7 == 7'b0100000)) begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_ADD_SUB, `INST_SLL, `INST_SLT, `INST_SLTU, `INST_XOR, `INST_SR, `INST_OR, `INST_AND: begin</span><br><span class="line"> reg_we_o = `WriteEnable;</span><br><span class="line"> reg_waddr_o = rd;</span><br><span class="line"> reg1_raddr_o = rs1;</span><br><span class="line"> reg2_raddr_o = rs2;</span><br><span class="line"> end</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>第1行,opcode就是指令编码中的第6部分内容。</p>
<p>第3行,`INST_TYPE_R_M的值为7’b0110011。</p>
<p>第4行,funct7是指指令编码中的第1部分内容。</p>
<p>第5行,funct3是指指令编码中的第4部分内容。</p>
<p>第6行,到了这里,第1、4、6这三部分已经译码完毕,已经可以确定当前指令是add指令了。</p>
<p>第7行,设置写寄存器标志为1,表示执行模块结束后的下一个时钟需要写寄存器。</p>
<p>第8行,设置写寄存器地址为rd,rd的值为指令编码里的第5部分内容。</p>
<p>第9行,设置读寄存器1的地址为rs1,rs1的值为指令编码里的第3部分内容。</p>
<p>第10行,设置读寄存器2的地址为rs2,rs2的值为指令编码里的第2部分内容。</p>
<p>其他指令的译码过程是类似的,这里就不重复了。译码模块看起来代码很多,但是大部分代码都是类似的。</p>
<p>译码模块还有个作用是当指令为加载内存指令(比如lw等)时,向总线发出请求访问内存的信号。这部分内容将在总线一节再分析。</p>
<p>译码模块的输出会送到id_ex模块(id_ex.v)的输入,id_ex模块是一个时序电路,作用是将输入的信号打一拍后再输出到执行模块(ex.v)。</p>
<h2 id="执行"><a href="#执行" class="headerlink" title="执行"></a>执行</h2><p>执行模块所在的源文件:rtl/core/ex.v</p>
<p>执行(ex)模块是一个纯组合逻辑电路,主要作用有以下几点:</p>
<p>1.根据当前是什么指令执行对应的操作,比如add指令,则将寄存器1的值和寄存器2的值相加。</p>
<p>2.如果是内存加载指令,则读取对应地址的内存数据。</p>
<p>3.如果是跳转指令,则发出跳转信号。</p>
<p>执行模块的输入输出信号如下表所示:</p>
<table>
<thead>
<tr>
<th align="center">序号</th>
<th align="center">信号名</th>
<th align="center">输入/输出</th>
<th align="center">位宽(bits)</th>
<th align="center">说明</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">rst</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">复位信号</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">inst_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">指令内容</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">inst_addr_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">指令地址</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">reg_we_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">寄存器写使能</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">reg_waddr_i</td>
<td align="center">输入</td>
<td align="center">5</td>
<td align="center">通用寄存器写地址,即写哪一个通用寄存器</td>
</tr>
<tr>
<td align="center">6</td>
<td align="center">reg1_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">通用寄存器1读数据</td>
</tr>
<tr>
<td align="center">7</td>
<td align="center">reg2_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">通用寄存器2读数据</td>
</tr>
<tr>
<td align="center">8</td>
<td align="center">csr_we_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">CSR寄存器写使能</td>
</tr>
<tr>
<td align="center">9</td>
<td align="center">csr_waddr_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">CSR寄存器写地址,即写哪一个CSR寄存器</td>
</tr>
<tr>
<td align="center">10</td>
<td align="center">csr_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">CSR寄存器读数据</td>
</tr>
<tr>
<td align="center">11</td>
<td align="center">int_assert_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">中断信号</td>
</tr>
<tr>
<td align="center">12</td>
<td align="center">int_addr_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">中断跳转地址,即中断发生后跳转到哪个地址</td>
</tr>
<tr>
<td align="center">13</td>
<td align="center">mem_rdata_i</td>
<td align="center">输入</td>
<td align="center">32</td>
<td align="center">内存读数据</td>
</tr>
<tr>
<td align="center">14</td>
<td align="center">div_ready_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">除法模块是否准备好信号,即是否可以进行除法运算</td>
</tr>
<tr>
<td align="center">15</td>
<td align="center">div_result_i</td>
<td align="center">输入</td>
<td align="center">64</td>
<td align="center">除法结果</td>
</tr>
<tr>
<td align="center">16</td>
<td align="center">div_busy_i</td>
<td align="center">输入</td>
<td align="center">1</td>
<td align="center">除法模块忙信号,即正在进行除法运算</td>
</tr>
<tr>
<td align="center">17</td>
<td align="center">div_op_i</td>
<td align="center">输入</td>
<td align="center">3</td>
<td align="center">具体的除法运算,即DIV、DIVU、REM和REMU中的哪一种</td>
</tr>
<tr>
<td align="center">18</td>
<td align="center">div_reg_waddr_i</td>
<td align="center">输入</td>
<td align="center">5</td>
<td align="center">除法运算完成后要写的通用寄存器地址</td>
</tr>
<tr>
<td align="center">19</td>
<td align="center">mem_wdata_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">内存写数据</td>
</tr>
<tr>
<td align="center">20</td>
<td align="center">mem_raddr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">内存读地址</td>
</tr>
<tr>
<td align="center">21</td>
<td align="center">mem_waddr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">内存写地址</td>
</tr>
<tr>
<td align="center">22</td>
<td align="center">mem_we_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">内存写使能</td>
</tr>
<tr>
<td align="center">23</td>
<td align="center">mem_req_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">请求访问内存信号</td>
</tr>
<tr>
<td align="center">24</td>
<td align="center">reg_wdata_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">通用寄存器写数据</td>
</tr>
<tr>
<td align="center">25</td>
<td align="center">reg_we_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">通用寄存器写使能</td>
</tr>
<tr>
<td align="center">26</td>
<td align="center">reg_waddr_o</td>
<td align="center">输出</td>
<td align="center">5</td>
<td align="center">通用寄存器写地址</td>
</tr>
<tr>
<td align="center">27</td>
<td align="center">csr_wdata_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">CSR寄存器写数据</td>
</tr>
<tr>
<td align="center">28</td>
<td align="center">csr_we_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">CSR寄存器写使能</td>
</tr>
<tr>
<td align="center">29</td>
<td align="center">csr_waddr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">CSR寄存器写地址,即写哪一个CSR寄存器</td>
</tr>
<tr>
<td align="center">30</td>
<td align="center">div_start_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">开始除法运算</td>
</tr>
<tr>
<td align="center">31</td>
<td align="center">div_dividend_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">除法运算中的被除数</td>
</tr>
<tr>
<td align="center">32</td>
<td align="center">div_divisor_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">除法运算中的除数</td>
</tr>
<tr>
<td align="center">33</td>
<td align="center">div_op_o</td>
<td align="center">输出</td>
<td align="center">3</td>
<td align="center">具体的除法运算,即DIV、DIVU、REM和REMU中的哪一种</td>
</tr>
<tr>
<td align="center">34</td>
<td align="center">div_reg_waddr_o</td>
<td align="center">输出</td>
<td align="center">5</td>
<td align="center">除法运算完成后要写的通用寄存器地址</td>
</tr>
<tr>
<td align="center">35</td>
<td align="center">hold_flag_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">暂停流水线信号</td>
</tr>
<tr>
<td align="center">36</td>
<td align="center">jump_flag_o</td>
<td align="center">输出</td>
<td align="center">1</td>
<td align="center">跳转信号</td>
</tr>
<tr>
<td align="center">37</td>
<td align="center">jump_addr_o</td>
<td align="center">输出</td>
<td align="center">32</td>
<td align="center">跳转地址</td>
</tr>
</tbody></table>
<p>下面以add指令为例说明,add指令的作用就是将寄存器1的值和寄存器2的值相加,最后将结果写入目的寄存器。代码如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">`INST_TYPE_R_M: begin</span><br><span class="line"> if ((funct7 == 7'b0000000) || (funct7 == 7'b0100000)) begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_ADD_SUB: begin</span><br><span class="line"> jump_flag = `JumpDisable;</span><br><span class="line"> hold_flag = `HoldDisable;</span><br><span class="line"> jump_addr = `ZeroWord;</span><br><span class="line"> mem_wdata_o = `ZeroWord;</span><br><span class="line"> mem_raddr_o = `ZeroWord;</span><br><span class="line"> mem_waddr_o = `ZeroWord;</span><br><span class="line"> mem_we = `WriteDisable;</span><br><span class="line"> if (inst_i[30] == 1'b0) begin</span><br><span class="line"> reg_wdata = reg1_rdata_i + reg2_rdata_i;</span><br><span class="line"> end else begin</span><br><span class="line"> reg_wdata = reg1_rdata_i - reg2_rdata_i;</span><br><span class="line"> end</span><br><span class="line"> ...</span><br><span class="line"> end</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>第2~4行,译码操作。</p>
<p>第5行,对add或sub指令进行处理。</p>
<p>第6~12行,当前指令不涉及到的操作(比如跳转、写内存等)需要将其置回默认值。</p>
<p>第13行,指令编码中的第30位区分是add指令还是sub指令。0表示add指令,1表示sub指令。</p>
<p>第14行,执行加法操作。</p>
<p>第16行,执行减法操作。</p>
<p>其他指令的执行是类似的,需要注意的是没有涉及的信号要将其置为默认值,if和case情况要写全,避免产生锁存器。</p>
<p>下面以beq指令说明跳转指令的执行。beq指令的编码如下:</p>
<p><img src="/2020/04/29/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99RISC-V%E5%A4%84%E7%90%86%E5%99%A8/inst_beq.png" alt="beq"></p>
<p>beq指令的作用就是当寄存器1的值和寄存器2的值相等时发生跳转,跳转的目的地址为当前指令的地址加上符号扩展的imm的值。具体代码如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">`INST_TYPE_B: begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_BEQ: begin</span><br><span class="line"> hold_flag = `HoldDisable;</span><br><span class="line"> mem_wdata_o = `ZeroWord;</span><br><span class="line"> mem_raddr_o = `ZeroWord;</span><br><span class="line"> mem_waddr_o = `ZeroWord;</span><br><span class="line"> mem_we = `WriteDisable;</span><br><span class="line"> reg_wdata = `ZeroWord;</span><br><span class="line"> if (reg1_rdata_i == reg2_rdata_i) begin</span><br><span class="line"> jump_flag = `JumpEnable;</span><br><span class="line"> jump_addr = inst_addr_i + {{20{inst_i[31]}}, inst_i[7], inst_i[30:25], inst_i[11:8], 1'b0};</span><br><span class="line"> end else begin</span><br><span class="line"> jump_flag = `JumpDisable;</span><br><span class="line"> jump_addr = `ZeroWord;</span><br><span class="line"> end</span><br><span class="line"> ...</span><br><span class="line">end</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>第2~4行,译码出beq指令。</p>
<p>第5~10行,没有涉及的信号置为默认值。</p>
<p>第11行,判断寄存器1的值是否等于寄存器2的值。</p>
<p>第12行,跳转使能,即发生跳转。</p>
<p>第13行,计算出跳转的目的地址。</p>
<p>第15、16行,不发生跳转。</p>
<p>其他跳转指令的执行是类似的,这里就不再重复了。</p>
<h2 id="访存"><a href="#访存" class="headerlink" title="访存"></a>访存</h2><p>由于tinyriscv只有三级流水线,因此没有访存这个阶段,访存的操作放在了执行模块中。具体是这样的,在译码阶段如果识别出是内存访问指令(lb、lh、lw、lbu、lhu、sb、sh、sw),则向总线发出内存访问请求,具体代码(位于id.v)如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">`INST_TYPE_L: begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_LB, `INST_LH, `INST_LW, `INST_LBU, `INST_LHU: begin</span><br><span class="line"> reg1_raddr_o = rs1;</span><br><span class="line"> reg2_raddr_o = `ZeroReg;</span><br><span class="line"> reg_we_o = `WriteEnable;</span><br><span class="line"> reg_waddr_o = rd;</span><br><span class="line"> mem_req = `RIB_REQ;</span><br><span class="line"> end</span><br><span class="line"> default: begin</span><br><span class="line"> reg1_raddr_o = `ZeroReg;</span><br><span class="line"> reg2_raddr_o = `ZeroReg;</span><br><span class="line"> reg_we_o = `WriteDisable;</span><br><span class="line"> reg_waddr_o = `ZeroReg;</span><br><span class="line"> end</span><br><span class="line"> endcase</span><br><span class="line">end</span><br><span class="line">`INST_TYPE_S: begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_SB, `INST_SW, `INST_SH: begin</span><br><span class="line"> reg1_raddr_o = rs1;</span><br><span class="line"> reg2_raddr_o = rs2;</span><br><span class="line"> reg_we_o = `WriteDisable;</span><br><span class="line"> reg_waddr_o = `ZeroReg;</span><br><span class="line"> mem_req = `RIB_REQ;</span><br><span class="line"> end</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>第2~4行,译码出内存加载指令,lb、lh、lw、lbu、lhu。</p>
<p>第5行,需要读寄存器1。</p>
<p>第6行,不需要读寄存器2。</p>
<p>第7行,写目的寄存器使能。</p>
<p>第8行,写目的寄存器的地址,即写哪一个通用寄存器。</p>
<p>第9行,发出访问内存请求。</p>
<p>第19~21行,译码出内存存储指令,sb、sw、sh。</p>
<p>第22行,需要读寄存器1。</p>
<p>第23行,需要读寄存器2。</p>
<p>第24行,不需要写目的寄存器。</p>
<p>第26行,发出访问内存请求。</p>
<p>问题来了,为什么在取指阶段发出内存访问请求?这跟总线的设计是相关的,这里先不具体介绍总线的设计,只需要知道如果需要访问内存,则需要提前一个时钟向总线发出请求。</p>
<p>在译码阶段向总线发出内存访问请求后,在执行阶段就会得到对应的内存数据。</p>
<p>下面看执行阶段的内存加载操作,以lb指令为例,lb指令的作用是访问内存中的某一个字节,代码(位于ex.v)如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">`INST_TYPE_L: begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_LB: begin</span><br><span class="line"> jump_flag = `JumpDisable;</span><br><span class="line"> hold_flag = `HoldDisable;</span><br><span class="line"> jump_addr = `ZeroWord;</span><br><span class="line"> mem_wdata_o = `ZeroWord;</span><br><span class="line"> mem_waddr_o = `ZeroWord;</span><br><span class="line"> mem_we = `WriteDisable;</span><br><span class="line"> mem_raddr_o = reg1_rdata_i + {{20{inst_i[31]}}, inst_i[31:20]};</span><br><span class="line"> case (mem_raddr_index)</span><br><span class="line"> 2'b00: begin</span><br><span class="line"> reg_wdata = {{24{mem_rdata_i[7]}}, mem_rdata_i[7:0]};</span><br><span class="line"> end</span><br><span class="line"> 2'b01: begin</span><br><span class="line"> reg_wdata = {{24{mem_rdata_i[15]}}, mem_rdata_i[15:8]};</span><br><span class="line"> end</span><br><span class="line"> 2'b10: begin</span><br><span class="line"> reg_wdata = {{24{mem_rdata_i[23]}}, mem_rdata_i[23:16]};</span><br><span class="line"> end</span><br><span class="line"> default: begin</span><br><span class="line"> reg_wdata = {{24{mem_rdata_i[31]}}, mem_rdata_i[31:24]};</span><br><span class="line"> end</span><br><span class="line"> endcase</span><br><span class="line"> end</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>第2~4行,译码出lb指令。</p>
<p>第5~10行,将没有涉及的信号置为默认值。</p>
<p>第11行,得到访存的地址。</p>
<p>第12行,由于访问内存的地址必须是4字节对齐的,因此这里的mem_raddr_index的含义就是32位内存数据(4个字节)中的哪一个字节,2’b00表示第0个字节,即最低字节,2’b01表示第1个字节,2’b10表示第2个字节,2’b11表示第3个字节,即最高字节。</p>
<p>第14、17、20、23行,写寄存器数据。</p>
<h2 id="回写"><a href="#回写" class="headerlink" title="回写"></a>回写</h2><p>由于tinyriscv只有三级流水线,因此也没有回写(write back,或者说写回)这个阶段,在执行阶段结束后的下一个时钟上升沿就会把数据写回寄存器或者内存。</p>
<p>需要注意的是,在执行阶段,判断如果是内存存储指令(sb、sh、sw),则向总线发出访问内存请求。而对于内存加载(lb、lh、lw、lbu、lhu)指令是不需要的。因为内存存储指令既需要加载内存数据又需要往内存存储数据。</p>
<p>以sb指令为例,代码(位于ex.v)如下:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">`INST_TYPE_S: begin</span><br><span class="line"> case (funct3)</span><br><span class="line"> `INST_SB: begin</span><br><span class="line"> jump_flag = `JumpDisable;</span><br><span class="line"> hold_flag = `HoldDisable;</span><br><span class="line"> jump_addr = `ZeroWord;</span><br><span class="line"> reg_wdata = `ZeroWord;</span><br><span class="line"> mem_we = `WriteEnable;</span><br><span class="line"> mem_req = `RIB_REQ;</span><br><span class="line"> mem_waddr_o = reg1_rdata_i + {{20{inst_i[31]}}, inst_i[31:25], inst_i[11:7]};</span><br><span class="line"> mem_raddr_o = reg1_rdata_i + {{20{inst_i[31]}}, inst_i[31:25], inst_i[11:7]};</span><br><span class="line"> case (mem_waddr_index)</span><br><span class="line"> 2'b00: begin</span><br><span class="line"> mem_wdata_o = {mem_rdata_i[31:8], reg2_rdata_i[7:0]};</span><br><span class="line"> end</span><br><span class="line"> 2'b01: begin</span><br><span class="line"> mem_wdata_o = {mem_rdata_i[31:16], reg2_rdata_i[7:0], mem_rdata_i[7:0]};</span><br><span class="line"> end</span><br><span class="line"> 2'b10: begin</span><br><span class="line"> mem_wdata_o = {mem_rdata_i[31:24], reg2_rdata_i[7:0], mem_rdata_i[15:0]};</span><br><span class="line"> end</span><br><span class="line"> default: begin</span><br><span class="line"> mem_wdata_o = {reg2_rdata_i[7:0], mem_rdata_i[23:0]};</span><br><span class="line"> end</span><br><span class="line"> endcase</span><br><span class="line"> end</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>第2~4行,译码出sb指令。</p>