EDA365电子论坛网

标题: linux学习之路_or1200第一个裸机程序(上) [打印本页]

作者: mutougeda    时间: 2021-4-22 17:38
标题: linux学习之路_or1200第一个裸机程序(上)
% p. u" L! k& q6 T
经过前面千辛万苦、爬山涉水、纠结了好久才弄好的环境,现在我们终于可以把FPGA当成个单片机使了,稍微比单片机猛一点,但是绝对比单片机贵一个数量级,FPGA现在能完成由CPU做的事,我们就把它当成一个大价钱买回来的单片机,不过相比于S3C24XX的片子来说,那么贵的一个FPGA板卡跑40M的一个CPU绝对的奢侈,不过仅限于理解和学习嵌入式的过程,管他的。
3 i& C9 X' n  B. H" P0 J! ~/ T        但是如果是这样呢,把or1200资源优化好,例化两个或者多个CPU,是不是可以做成SMP架构?又或者说现有的产品上有FPGA芯片,但是里面的资源还足以容得下一个or1200,是不是可以做个协处理器在里面?我想应该可以做的事情还很多的······吧。
; _3 Y8 k6 `5 u- ]5 a. ~6 m; `, Q' A; `+ c/ J( l: C
        按照惯例,硬件代码第一个无非是点亮LED灯;软件代码无非是写个hello world,有同感木有。9 d  G- \. @6 W- i' u/ @0 N
% z5 B( P$ I9 j2 w' \
        但是想想,我们现在的SOC只有什么?CPU、RAM、Debug、UART、BUS,我们没有把GPIO外设加进去,点个灯貌似行不通了,但是能操作的毕竟外设蛮多的,但是又想看到有现象输出的,那我们只好选择去写个UART的代码咯。
/ Y, _) y4 z2 c3 }6 f
( B5 B: q7 Q" l  G4 ]. j) z( W5 k& f2 Y        我们在后面完善or1200_basic_soc时把其他外设添加进去后,上了linux系统后,在编写GPIO字符驱动时再去把灯点亮吧,现在我们就当FPGA是个单片机去编写所谓的“裸机”程序。
; N# m3 W5 D: h0 _' B- o% w2 ^" a, K6 ]/ X% d0 T! o, b; a
        我们现在手头上有两本书对吧,《开源软核处理器OPENRISC的SOPC设计》、《CPU源代码分析与芯片设计及Linux移植》,那我们就先看看接下来要写的UART16550外设的说明文档先咯,uart16550_latest.tar.gz源码包里的doc文件夹有该ipcore的说明文档,其实这就是相当于看芯片手册一样了,是不是很熟悉,那就不继续吐槽了,或者《开源软核处理器OPENRISC的SOPC设计》里面第7章也有UART16550的中文翻译版本,感觉这本书就是opencores上面大堆文档的翻译大杂烩,算了,自己写不出的书还是不吐槽人家的了。0 {9 X1 B9 E) Y- D# ?# V
4 t9 Z7 d2 }! }# x; p. N2 S
        大概浏览一下UART16550的结构后我们就来咯,敲代码。0 N+ d6 M! G, ^( d8 ~% M& I6 ]
/ l$ Z% n" s. d5 B
        在敲代码之前先搞清楚一些流程,当内嵌在FPGA的or1200上电复位时,它在做些什么,在硬件环境搭建时,我们把CPU的复位地址设置为0x100,这个地址可以在or1200_defines.v中任意指定的,什么是0x100呢,我们在CPU体系手册《openrisc-arch-1.0-rev0.pdf》中16章操作系统接口有所描述,0x100是复位后取指令的第一个地址,虽然是这样说,但是复位地址可以是任意指定的,以后我们固化代码在Flash时,会将这个复位地址设置为Flash的起始地址,目前我们先指定在0x100,所以,CPU复位时就会从0x100取出第一条执行的指令,OK。! z3 k( L; J; v

3 f: q( b6 G/ {& A& X 5 }8 [7 G' n1 M& ]% d

7 C$ R7 F/ M5 |0 E, z! H# D        对于其他向量地址来说,是CPU设计时规定好的异常向量地址,好比0x100是复位异常向量,我们在写C51时熟悉的对比一下看还有什么?0x500是否熟悉?只是时钟中断异常向量。还有什么咧?0x800,外中断异常向量,所以,当我们在CPU将计数器中断enable后,当时钟计满中断时,就会跳转到0x500执行指令。对于外中断而言,前面我们在修改or1200_defines.v把PIC的中断个数设置为20个,所以,当这20个外中断enable后,中断源触发后,CPU就跳转到0x800地址来执行指令啦,至于其他的异常向量都是类似的,可能以前我们接触得比较少,但在对于上了操作系统后,这每个异常向量都会有对应的处理过程。现在对于or1200_basic_soc来说,只使用了复位、时钟、中断异常向量。
# F* A: X" v+ f% r, _& p1 f# [3 z1 s, H+ N4 P/ i/ z/ ?
        所以我们敲代码的第一步就是编写这些异常向量的处理程序,即异常向量表,因为刚上电时处理器的C环境还没有建立好,所以这些代码都是要用汇编去写的,我们现在至少要用到reset复位异常向量,硬着头皮码吧。
' p( k( J4 ]* W6 k5 v) {8 Z& M: L0 q3 C
1 S) u! f1 @$ N7 {/ t0 N        我理解的reset复位异常向量是通常说的启动代码,在用Keil MDK创建S3C2440的工程时也有对应的启动代码文件s3c2440.S,看起来蛮多的,1k多行,但是往下看,800多行的宏定义,那启动代码就200多行了。再来吐槽下or1200在linux系统下的关于启动代码的文件entry.S、head.S,这两个文件加起来2k5多行,那我们手动去敲那么多代码么,开玩笑吧。5 v7 Z9 r: l2 g, q, |
. ?5 {, C( _6 K" A/ @/ P% e$ T$ g
        好在我们现在用到的外设不多,就一个UART,而且我们现在还不需要用到时钟计数,还没需要到中断,所以复位向量用到的代码大概只有50行,50的汇编对于我这种级别        来说简直小菜一碟,好,码字。& f% ~& i, X$ z# s+ V

  V/ \' L7 K  m3 O  L( d        至于这个简单的UART代码我都打包好在附件中了,那我们简单分析一下这50多行reset向量需要的代码。+ N9 r: Q/ D' X8 J

" d! x+ {) M% r5 r: p+ G! N9 L        把工程都扔进sourceinsight,方便查看嘛,打开reset.S,对于前面的宏定义先跳过,用到的时候再说吧。
( _5 H+ ~$ O$ n+ e! P& s" I5 ]2 X# Y' X
/ u0 o' k; j' c) F
        先看0x100的异常向量,3行代码,是不是很有成就感。8 A( _# s% i+ R0 P% U3 z
3 }8 U# c  v) [% a' ]. z8 c' D
/ t8 h: U- X9 b) P( f% G& W* q# `6 ^

# O* k) X4 o5 ]' x' Y2 a" H* b  h/ I2 j2 q+ U
        LOAD_SYMBOL_2_GPR看名字就知道意思,作用是将标号的地址存入CPU的寄存器。5 q% k: k  }; C  ?

  D# Z0 A& j; W& e 1 O/ @$ }/ j8 q, ^' l

5 S# m6 P8 X$ e# H+ H2 O. B5 r. o# C) G
& w: e4 T; f, V6 W& C) V        18行l.movhi是or1200处理器的逻辑指令,作用是把立即数零拓展左移16bit1 \5 y0 f# x. C- T5 \5 z

0 G7 P0 h, g7 i0 E( t# b, [  S/ al.movhi rD,K
: _( S. P# z. v3 F$ F+ e( R7 k9 Y- X/ D  w2 T: k$ ?: ^7 g8 M$ ~
rD[31:0] < -extz(Immediate) < < 165 t+ t4 E" L( |3 w8 }
3 M6 ]5 e& X$ m* \
        所以18行将symbol的高16位存入gpr中。3 n2 f; d+ z4 M1 a8 I8 S. z

9 o4 }- U2 I7 d( z1 u% G: k8 [        l.or是or1200处理器的逻辑指令按位或,作用不用讲了吧,
+ h/ D0 T' p/ S" e9 S
% V* l9 H# X* K$ Q! [( Cl.or rD,rA,rB1 U* f; {& l& v" U) `0 r

' D) `+ F6 t' F; I' arD[31:0] < -rA[31:0] OR rB[31:0]
2 C6 w! V7 l6 d- m. @
: s1 E3 {+ W/ ~; ~& r        所以19行将symbol的低16位存入gpr中。# u2 Q$ f0 j7 W/ d2 S

" Y% Z' [- ~& M* {        继续reset向量,l.jr r3,即跳转到r3寄存器的地址上,所以reset向量前3行就是跳转到_reset标号地址上。
$ a+ m9 {, e" k7 o- Q7 q4 E2 w& D
        貌似55行还有个l.nop指令,只是or1200的空操作指令。跳转指令之后紧接着的指令称为延时槽,在or1200体系中,在跳转指令之后的紧接着的一条指令是规定、必须、肯定会执行的,所以在现在or1200的gcc编译器中,在跳转指令之后都会自动补上一个nop指令,貌似在新版本的编译器会在跳转指令后执行别的有效指令代替nop操作,提高效率。- a+ S& g; _* @- O3 M  E, ]1 e. M: g

  U; ?5 x( |  U' f        好,continue······2 b$ T+ a" P  v' ^4 R1 j
9 Q, P/ m& U& M2 y2 J

/ f: y+ J9 y7 `3 z! V& E. ^        Sourceinsight中一跳,转到_reset标号上* Y$ R% Z# v! p2 G& g& u5 G" d& u

+ B7 D/ S- h3 L$ n
5 A, u9 T# M' e* D; c. e1 J4 ^& t
) A; e+ Y; Y& ]) U* T" U9 s
        l.movhi,l.ori很熟悉啊,目的将r0寄存器清零。
( A* X2 ]# v0 n
2 j/ ^% Z1 Z6 X! C% K+ f$ C        l.addi作用是将r0和SPR_SR_SM相加后存入r3中,至于SPR_SR_SM又是什么?跟踪下就可以发现在spr-defs.h有其定义。; `! L/ M5 j, j1 R+ h
% Z# ]' \( a; E0 t/ Z  a5 @

; O: p% R2 R6 w# e4 n+ n' Q& _
1 R+ i4 {! `: L) S. e0 c
7 d) h# R2 V) W, \        这是supervisor模式,这个linux的内核态和用户态概念很像,or1200存在两种模式,supervisor和user,区别是操作CPU内部寄存器组时权限不同。! q( S: B6 c0 R; \; u5 A! B
5 t7 ~* z6 V8 Z0 W
继续,l.mtspr就是操作or1200内特殊寄存器组的专用指令,专用的,将r0和r3的寄存器的值相加后送入SPR_SR寄存器内1 W$ g: z0 A1 K, o5 W3 y
; T% c! [, a0 ~
; ~- s" s3 D( D  [( V" t8 k

. G) R4 B1 _. Y5 c: y* w1 U: V0 L  `0 G1 V" Z
        在《openrisc-arch-1.0-rev0.pdf》中可以看到,SPR_SR的first bit就是SM位7 |" {  k' L& W. N0 t4 i7 r
: B0 v* u7 M) m; x3 {4 i
        为1时就是Supervisor Mode- O( V; a; h/ s$ I- m4 w: S

; p! G, ~; \' W6 U  _        继续往下看,有个CLEAR_GPR的宏,目的是将gpr设置成r0的值
3 v7 ~* e* B' c& l! r# p. R, v9 e* F: ]& U" m1 D- U" d
/ |- Y. ~7 _& }
# m. v  M: f0 Q7 E# C: _
+ P- r% U" \& e
        在or1200中,r0被设置成常数0,做与这个宏的作用就是清零
0 ^1 V9 [( R9 Z+ _# a* ]: g( i0 N9 [
好,继续
* `6 G5 J( w- A0 _$ y- g$ C% n: S0 s; Z6 }7 C
- @7 r, [8 D- y( s% _
  V& V& M% G% z# \
* T. T4 K! c* e8 ~& r9 a  o0 a

. l3 X, f4 g$ n9 V5 g- I        SPR_TTMR是关于or1200内Timer的寄存器,将r0的值赋入SPR_TTMR中,至于SPR_TTMR的解释如下表。' q! q+ x" M) e

: z4 |: J5 \! y) U7 x3 u' g6 A4 u
, X  m# q7 M3 @6 J- X6 [1 a" e2 c6 [0 @( m

" T  O& c& \8 N" B: P; z# ^        目的就是把执行计数单元(简称定时器吧)的中断和使能都禁用了。
+ y, u# \1 K' h5 h2 }  b. t
) s5 f8 d) k. @        好,继续······貌似完了,200行吧_stack的值赋给r1,_stack是CPU堆栈的值,_stack这个值在lds文件内定义,这个文件稍后再解释。
* l; n8 \8 u: G: o8 u" U2 ~" W9 A/ O) v
. [5 l/ m8 a' \7 y- z3 r7 h. N  B7 i        第203,204行是不是很熟悉,跳转到main函数执行,堆栈建立好之后下面main函数就可以用C来码了。6 E$ Q1 E% j2 K8 j) g
. r8 |4 c6 `; ^: h
        Reset向量就需要这些代码了,至于reset.S剩下的代码是关于中断处理的,比如在
+ b3 _3 A% @6 ]4 J
" y; n# Z" P1 J( Q" W' X/ @  @ 7 {# n6 P, W' v6 i& D4 z
( O2 V8 m. _7 p' L7 E# n
* P7 P1 b% N  s- Q
        在定时器中断和外中断会先执行EXCEPTION_HANDLER宏,目的是执行中断处理之前的现场保护,跳转至中断服务函数,执行完中断服务函数之后的现场恢复,中断的概念不用多解释了,应该是很熟悉的了。2 H: l+ f# D* K* @0 Y. M5 R

4 T0 `9 E3 t  }% M) ~1 g! S        关于or1200中断的流程可以参考《CPU源代码分析与芯片设计及Linux移植》,里面讲得蛮详细的,虽然大部分也是翻译手册的,至于附件的代码关于reset.S剩余部分不是特别难,就不解释了,不解释咯······7 h/ }1 e7 e! T9 m6 \
7 b" P* I7 K+ a7 S% @; P
        突然发现,reset的异常向量我们就敲完了,在reset向量里会跳转至main函数,那就开始了,main函数熟悉了吧,
- _+ Y) Z0 t: b' Y* \  U
0 T; t5 _1 ]) S3 J: y+ Z        附件中的uart.c和uart.h关于UART16550的串口驱动和头文件,程序不难,对比着uart16550_latest.tar.gz的说明文档、《开源软核处理器OPENRISC的SOPC设计》和源码的注释5分钟就可以看懂了,不详细说了,因为大家都有C语言功底嘛。
9 D" W. T4 ~6 }. o, e& L8 K
1 B4 J3 I1 @$ ]9 B. [. E% n4 ^         至于附件driver和include的其他文件可以先不管,因为现在还用不上,有兴趣可以自行翻阅下,是关于中断二级向量表的实现和timer的驱动,大部分都是参考u-boot下openrisc架构修修改改过来的,所以最好还是稍微浏览下,然后在下次移植u-boot时能快速上手。! k9 a& |7 ~" n1 x! x4 Q( c
% D& f6 f) `3 Z0 A
        Main函数更不用多说了,手痒自己随意打的,随便修改吧,至于里面的函数用sourceinsight追一下就可以了,都是比较简单的东西了。, Y" B8 T* j, k$ Y7 g9 R: p( q% ]: v# ?
9 d& j. r* t: ]; k3 `. A* [" W  X* o
        UART的代码到这里我们就敲完了,最后就是把程序下到我们的SDRAM中运行了,好,在下到SDRAM之前呢,还需要编写一个连接脚本文件,告诉编译器编译出来的可执行程序各个段的存储位置,入口地址,在学校学汇编和计算机原理的时候都应该讲过这些的了,就是编译源码后得到的可执行bin文件的在RAM的存储位置,例如在link.lds文件中6 S" @- C& w6 z# H# _' Q1 V$ o+ j

* `" P8 b6 C# ~' o) ~% q4 ]& q
# n: z) O- E) u8 @
- S7 ~4 f5 x; s; ~( g- k) x& i" y
; G7 R1 h" g9 d2 A        vectors段,即我们之前编写的异常向量表的存放位置,从0x0开始到0x2000,除or1200结构本身定义的异常向量表之外,还保留空的异常向量允许自定义操作。
  |) |# W7 G: l# K* o) x
' r) v8 Y* {! J& g" k( R3 z6 J7 @2 h  }: O5 ]" s" ~# i
        ram段就是开始存放我们uart的程序代码啦,继续看······. W( b! R4 i: U- l

+ n2 n, b1 Z' \0 Y% T% o7 d' y( I % _  l9 H! _+ z" B# {% `, Z5 @

: v4 ?; x" j* e( R
+ V2 ~8 J: S8 A' {, m1 Y9 f        这里定义了一个_min_stack的变量,大小为8K,记得我们在写reset向量时设置CPU堆栈的值吗?4 `) T" K- z# J  B

7 ^. W/ s. U, r' ]
% w: u8 o4 T4 I9 B" r5 Q) w2 A
6 b9 c' K/ \0 q. [
% Z! y, q" \4 N8 ~7 }4 c+ @, m- m& O        在这里_stack的值在这里取出,这里我说的不是很明白,关于lds连接文件附件中的ARM79出品-u-boot移植手册.pdf的附录一有详细说明,参考参考很容易就明白啦,或者可以google一下,再或者看看别人的技术博客就行了,这里不再详细解释了,因为码字是在是很累人的一件事。, M+ G: a; w$ Y1 ^
! x0 y. A8 T) Y# b3 Z) c; b

  f# g. [# h$ G3 B) w        一切OK准备就绪,但是在linux环境下编译我们还要写一个makefile,哇天啊,好吧,认栽吧,写吧。- _7 E- F/ u; H" ~% }# b

+ N7 P7 J: _% o  D% d        ······
2 ?7 Q7 ~; C: `& N& k3 s, A
* o- z/ Y, e/ `% T8 ~         现在可以打开virtualbox,先浏览一下放在桌面的那些文件,比如, I; F# x4 g  A; W" B

2 B  c5 E  |. {% y9 D4 ] ( ^' ~8 q6 U4 `( j& r

& s1 u! x' g8 B2 b& P
$ \; L2 _' X: O! q        可大概看看这个环境可以做些什么东西。) ^+ W; G9 u0 ]" j! a

. b5 R: h) S$ I. x: N9 ]0 c. x. \: p4 p+ V1 z* a+ a8 D
        然后打开home folder,点啊点,点啊点3 P5 ]& H  X; }2 m, _0 k. F
2 h/ g( s+ n% c
- N7 D9 B! M8 c+ |' s0 `. |5 h

" P) G3 `  ~! }5 i" H3 s# c
8 G% x& {6 o1 F- |        在orpmon文件夹中包含一个opencores社区针对or1200做的bootloader,而且,我在写makefile的时候都是参考这个bootloader的makefile写的,至于怎么去写一个makefile,参考《跟我一起写 Makefile.pdf》和《GNU+makefile中文手册.pdf》这两个手册咯。7 M) c" K  i1 d" L

作者: CCxiaom    时间: 2021-4-22 18:31
or1200第一个裸机程序(上)
作者: twel2e    时间: 2021-4-23 18:06
现在我们终于可以把FPGA当成个单片机使了,稍微比单片机猛一点,但是绝对比单片机贵一个数量级,FPGA现在能完成由CPU做的事
0 A: {" ?( {* r0 w8 S




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2