1988年,Jürg Gutknecht和我一起完成并出版了Oberon编程语言[1,2],其为我职业生涯早期开发的另外两种编程语言Pascal和Modula-2的后续版本。与Modula-2相比,我们最初设计Oberon编程语言力求更加精简和高效,以便能够更好地帮助教学人员为计算机科学专业的学生教授系统编程。我们再接再励,于1990年针对可采用windows系统和具有字处理能力的工作站开发了Oberon操作系统(OS),以此作为工作站的现代实现方式。接着,我们出版了一本书,名为《Project Oberon》,详细讲解了Oberon编译器以及与之同名的操作系统。此外,书中还包括详细的指令和源代码。
几年前,我的朋友Paul Reed建议我重新修订并再次出版这本书,不仅因为这本书对系统设计教学具有重要价值,同时还因为这本书可以作为良好的切入点,帮助那些想要成为创新者的人们从零基础构建可靠的系统。
然而,我当时实际上是遇到了很大的困难。我最初开发的编译器是将已基本消失了的处理器作为目标。因此,我的解决办法就是为现代处理器重新编写编译器。但在做了不少研究之后,我无法找到一款能够在清晰度、规律性和简洁性上符合我标准的处理器。因此,我自己设计了这款编译器。而我之所以能够将该想法付诸实现,都是因为现代FPGA能够帮助我设计硬件以及系统软件。
选择赛灵思 FPGA能帮助我更新系统,同时让设计尽可能接近自1990年以来的原始版本。更重要的是,选择赛灵思FPGA 能帮助我更新系统,同时让设计尽可能接近自1990年以来的原始版本。
实现在低成本Digilent Spartan®-3开发板上的新型处理器RISC负责托管1MB静态RAM (SRAM)内存。我唯一添加的系统硬件就是一个鼠标接口和一个用来替换旧系统中硬盘驱动器的SD卡。
这本书和面向整个系统的源代码可在projectoberon.com[3,4,5]中查阅,也可在该网站上名为 S3RISCinstall.zip.的单个文件中进行查阅。该文件包含指令、SD卡文件系统图像和FPGA配置比特文件(对于Spartan-3开发板的 Platform Flash,此为PROM文件形式),以及SD卡/鼠标接口硬件的构造详图。
RISC处理器
该处理器由算术逻辑单元、由16个32位寄存器组成的阵列和带指令寄存器、IR及程序计数器PC的控制单元组成。Verilog模块RISC5就是该处理器的典型代表。
该处理器具有20种指令:4种用于移动、偏移和旋转;4种用于逻辑运算;4种用于整数运算;4种用于浮点运算;2种用于存储器访问;2种用于分支。
RISC5通过运行环境RISC5Top导入。该运行环境包括到各种(内存映射)器件和SRAM(256M×32 位)的接口。
整个系统(图1)包括以下Verilog模块(见行统计)
RISC5Top | 运行环境 | 194 |
RISC5 | 处理器 | 201 |
乘法器 | 整数运算 | 47 |
除法器 | 24 | |
FP加法器 | 浮点运算 | 98 |
FP乘法器 | 33 | |
FP除法器 | 35 | |
SPI | SD卡和发送器/接收器 | 25 |
VID | 1024×768 视频控制器 | 73 |
PS2 | 键盘 | 25 |
鼠标 | 鼠标 | 95 |
RS232T | RS232 发送器 | 23 |
RS232R | RS232 接收器 | 25 |
图1 – 该系统及其所含Verilog模块的方框图
我将内存映射到黑白VGA显示器中,这样它只占用1024×768×1 位/像素=98304字节,基本上
占 1 MB可用主内存的10%。该SD卡将取代初始系统中80MB的硬盘驱动器,其可通过能够接受并序列化字节或32位字的标准SPI接口进行存取。键盘和鼠标通过标准PS-2串行接口连接。此外,还提供一根串行异步的RS-232线和一个通用8位并行的I/O接口。模块RISC5Top还带有一个每毫秒采用增量式计数的计数器。
OBERON操作系统
该操作系统软件由包括内存分配器(带垃圾回收器)的内核、文件系统以及引导载入程序、文本系统、浏览器系统和文本编辑器组成。
名为“Oberon”的模块是中心任务调度程序,而“System”是基础命令模块。通过点击显示器上任何浏览器文本“M.P”上的中间按钮即可触发动作,其中P是模块M声明的程序名。如果M不存在,则会自动加载。但是,大多数文本编辑命令是通过简单的鼠标点击触发的。
其中,左边一栏按钮用来设置"脱字"符,标记文本位置,右边一栏按钮用来选择文本字段(text stretch)。
“Kernel”模块包括磁盘存储管理和垃圾回收器。我保证观察浏览器是平铺的,不重叠。标准布局显示了多个浏览器的两条垂直轨迹。只需拖动标题栏,就可以放大、缩小或移动它们。图2显示了在显示器上运行的用户界面以及Spartan-3开发板、键盘及鼠标。
图2 - 显示用户界面的显示器(Spartan-3开发板在右侧)
加载时系统占用模块空间112640字节(21%),占堆(heap)的16128字节(3%)。系统包括所以下几个模块(见行统计),如图3所示:
内核 | 271 | (内核) |
文件目录 | 352 | |
文件 | 505 | |
模块(引导载入程序) | 226 | |
浏览器 | 216 | (外核) |
文本 | 532 | |
Oberon | 411 | |
菜单浏览器 | 208 | |
文本帧 | 874 | |
系统 | 420 | |
编辑 | 233 | |
图3 - 系统及其模块
值得注意的是,该系统在加电或重置时,完成初始化仅需两秒钟。这包括文件目录中垃圾回收扫描。
OBERON编译器
系统自带的编译器采用简单的自上而下递归下降分析法。用户使用ORP.Compile @命令即可激活模块选定的源文本上的编译器。
包解析器通过扫描仪输入各种符号,包括识别符、数字和特殊符号(如BEGIN、END、+等)。该方案已被证明在许多应用中有效且均表现不凡。这点在我著的书《编译器结构》[6,7]中有详细说明。
该包解析器调用代码生成程序模块中的程序。这些程序直接将指令添加在代码阵列上。如果已知所有分支目的地,向前跳转指令(forward-branch instructions)在模块编译结束时则会提供跳转地址(修正)。
所有可变地址都与基址寄存器关联。这就是用于局部变量(运行时设置在程序输入)的R14(堆栈指示器)或用于全局和输入变量的R13。
基址地址按要求通过地址保存在寄存器R12内的系统全局模块表载入。R15用于RISC架构确定的返回地址(链接)。因此,R0-R11可用于表达式评估和传递过程参数。
整个编译器由4个相对较小的有效模块组成(见行统计):
ORP | 包解析器 | 968 |
ORG | 代码生成器 | 1120 |
ORB | base def | 435 |
ORS | 扫描仪 | 311 |
编译器占用115912字节(22%)的模块空间和17508字节(4%)的堆空间(编译之前)。其源代码长约65KB。编译器自身的编译在25 MHz RISC处理器上只需几秒钟[8]。
Lola HDL及Verilog翻译
名为Lola的硬件描述语言(HDL)于1990年作为硬件设计基础教学的一种方式开发。这一时期,文本定义开始替代电路图,首个FPGA开始应用,尽管尚未达到主流设计。Lola由生成位流文件(被加载到FPGA)的编译器执行。位流文件格式由Algotronix公司和 Concurrent Logic 公司共同开发。这两家公司提供的存储单元结构都比较简单,看起来好像是自动布局布线的最佳选择。
在我的项目之后,紧接着是在FPGA上重新实现Oberon系统,现在突然出现这种想法以唤醒Lola。由于赛灵思 FPGA存储单元结构相当简单,所以我们没有冒险实现布局布线,更何况赛灵思因专利原因而拒绝透露其位流文件格式这一事实。
很明显,要建立不生成专有位流文件但翻译成赛灵思能为其提供综合工具的语言的Lola编译器。我们选择了Verilog。这种解决方法意味着迂回绕路相当浪费:首先,Lola模块已经经过包解析,然后翻译,然后再次包解析。通过所有这些措施,我们确信Lola编译器具有适当的错误报告和类型一致性检查功能。
为促进Lola-2的开发,我们决心用Lola重新定义所有RISC5处理器模块。现在,这已经实现了。
LOLA语言
Lola是一种Oberon风格的短小而精炼的程序语言(见http://www.inf.ethz.ch/personal/wirth/Lola/Lola2.pdf)。为简单起见,我们在这里只展示一个简单的Lola文本实例。源文本单元被称为模块。其报头规定了名称及其输入和输出参数,以及各参数的名称和类型。
MODULE Counter0 (IN CLK50M, rstIn: BIT;
IN swi: BYTE; OUT leds: BYTE);
TYPE IBUFG: = MODULE (IN I: BIT; OUT O: BIT)^; VAR clk, tick0, tick1: BIT;
clkInBuf: IBUFG; REG (clk) rst: BIT;
cnt0: [16] BIT; (*半毫秒*)
cnt1: [10] BIT; (*半秒*)
cnt2: BYTE;
BEGIN leds:= swi.7 ->swi : swi.0 -> cnt1[9:2]:cnt2;
tick0:= (cnt0 = 49999);
tick1:= tick0 & (cnt1 =499); rst:= ~rstIn;
cnt0:= ~rst -> 0: tick0 ->0 : cnt0 + 1; cnt1 := ~rst -> 0 : tick1 -> 0 : cnt1 +tick0; cnt2 := ~rst -> 0 : cnt2 + tick1;
clkInBuf (CLK50M,clk) END Counter0.
编译器通常使用值为NIL的指示检查数组索引和基准值。如果违反规定,这会造成陷阱。这种技术具有高度的安全性,防止错误和崩溃。事实上,只能通过采用伪模块SYSTEM中的操作,即PUT和COPY,才能防碍系统的完整性。
这些操作必须局限于接入器件接口的驱动模块。在输入列表中,通过SYSTEM很容易识别它们。整个系统采用Oberon编程,无需使用汇编码。
我选择Digilent Spartan-3 开发板是由于其成本低、操作简便,这使其适于教育机构,以获得整套课堂教学套件。
一个重大优势就是该开发板上有静态RAM,使得接口连接非常简单直观(甚至用于字节选择)。遗憾的是,所有新开发板均采用动态RAM,虽然存储空间更大,但接口连接复杂得多,因此刷新和初始化(校准)需要电路。这一电路与带静态RAM的整个处理器一样复杂。即使控制器以单片式提供,这有悖于我们的开放检查原则。
报头之后是一段包含本地对象(如变量和寄存器)的声明。之后一段通过赋值规定变量和寄存器数值。BYTE表示一个8位阵列。
每一个变量和寄存器通过唯一一种表达式(组合电路)进行定义。多重赋值没有任何意义。可以想象,每个HDL程序包含在一个大的永远重复子句中,因为寄存器和变量赋值会在每一个同步时钟脉冲周期内重复。
LOLA编译器
编译器采用简单的自上而下递归下降分析法。通过LSC.Compile @命令在所选Lola源文本上激活该编译器。包分析器包解析器通过扫描仪输入各种符号,包括识别符、数字和特殊符号(如BEGIN、END、+等)。该方案已被证明在许多应用中有效且表现不凡。这在我著的书《编译器结构》 (第1部分和第2部分)中有详细说明。
而不是直接即时生成Verilog文本,作为语法分析的一种意外结果,包分析器中会出现语句,从而生成树形结构,以适当方式表示输入文本,以便进一步处理。这种结构具有一个优点,即通过调用不同翻译程序很容易生成各种不同的输出。其中一个就是Verilog翻译程序。命令为 LSV. List outputfile.v。另一个命令可能翻译至VHDL或简单地列出树形结构。但也有可能生成网表,以便通过布局器和布线器进行进一步处理。
因此,整个编译器至少包括4个相对较小的高效模块组成(线数显示):
扫描仪 | 159 | ||
LSB | 基址 | 52 | |
LSC | 编译器/包解析器 | 503 | |
LSV | Verilog 生成器 | 215 | |
可在http://www.inf.ethz.ch/personal/wirth/Lola/Lola-Compiler.pdf查阅从Lola翻译至Verilog的指令。
软件与硬件‘程序’之间的差异
在过去,已进行许多努力使HDL看起来像“普通”编程语言(PL)。我们还注意到,HDL在编程语言集中通常有一个对应程序,各自采用不同风格。因此,Verilog在C、VHDL在Ada,以及Lola在 Oberon中都有其各自的原型。我们认为,认识到两个类别之间的根本差异是非常重要的,特别是存在句法相似或几乎相同的时候。这些根本差异是什么?
为了简化说明,我们仅分析同步电路 - 即,所有寄存器与时钟同样发出滴答声的电路。总体来说,这确实是一种良好的设计模式,如果可能的话,附在同步电路上。那么,很明显,一个电路的所有元件同时(或几乎同时)工作。
这是John von Neumann 提出带定序器的处理器体系结构的巧妙构思。定序器含有指令寄存器,据此,(各个周期中)某些电路会被选用而其他电路会被忽略,从而巧妙地重新利用(ALU)不同部件。现在,周期或步骤已成为固有顺序,并有可能将数值重新分配到同一个变量,因为程序计数器将它们与程序中和指令序列中的某些次序关联起来。真是这种定序器构思使得通过相对简单电路执行庞大程序成为可能。
总之,Lola-2是PL Oberon风格下的HDL。这里所述编译器将Lola模块翻译为Verilog模块。Lola的优点在于语言的简单、规则结构以及编译器对类型检查和改进错误诊断的突出。整个RISC处理器模块已通过Lola表示 (见http://www.inf.ethz.ch/personal/wirth/Lola/index.html)。
— Niklaus Wirth
最后的想法
40多年前,C.A.R. Hoare说,在科学技术的所有分支学科中,学生在被要求用实验尝试自己的想法之前,容易受到许多示范性设计实例的影响。编程和软件设计与这种明智模式形成了鲜明对比。这里,在阅读任何实例之前,要求学生从一开始就编写程序。
这种可怕事实的原因就是:几乎不存在大小合适的示范实例。因此,我决定稍微修正这种情形,我在1975年编写了《算法与数据结构》一书。随后(与J. Gut-knecht)负责操作系统教学任务,我设计了Oberon系统(1986-88)。
此后,编程教学并没有明显提高,而系统的规模和复杂性显著增加。虽然开源的努力颇受欢迎,但它并没有真正改变这种形势,因为大多数程序已经建成“运行”,但却没有真正投入使用。
我继续大胆提议,应重新设计所有程序,不光是计算机,还有读取方式。这些程序应是可发行的。即便这种方法正确而有效,但这是一项比创建可执行程序更为艰难的任务。这意味着,没有任何部分必须用汇编码指定。
忽略在这种“人为因素”的结果就是,在许多地方,新的应用没有得到精心设计,而是通过消除程序中错误来实现,其结果有时是令人担忧的。要做到“易懂”的关键就是坚持简单性和规律性,放弃不必要的修饰,避免花里胡哨,正确区分传统与便捷。
该系统所占空间小,是小资源实现大作为的见证。虽然Oberon系统包含一个文件系统、文本编辑器和浏览器(Windows)管理程序,但相比多数现代操作系统而言,其占用的空间少得可怜。意外的是它仅仅依靠一些简单规则,因此便于学习如何使用。
最后,这种简单的系统的另一大优势就是,用户可以在该基础系统上安全构建所需程序,而不必担心是否存在未知特征与功能,如后门程式(back door)。从系统完整性易受到攻击的角度看,这是一个基本属性,是关键安全型应用不可或缺的。值得注意的是,我们的系统硬件没有这种隐藏元件。毕竟,在如此之大而又没人能够完全了解的基础上构建的任何系统也不能予以保证。
致谢
我衷心感谢Paul Reed所做的宝贵贡献。他建议我重新编撰《Oberon项目》一书,还建议在FPGA上重新实现整个系统。Paul一直激励着我。他想到过用SD卡替代磁盘,他为SPI、PS-2和Verilog中的VID接口做出了卓越贡献。
参考文献
1. http://www.inf.ethz.ch/personal/ wirth/Oberon/Oberon07.Report.pdf
2. http://www.inf.ethz.ch/personal/ wirth/Oberon/PIO.pdf
3. www.inf.ethz.ch/personal/wirth/ProjectOberon/index.html
4. www.inf.ethz.ch/personal/wirth/Oberon/PIO.pdf
5. www.inf.ethz.ch/personal/wirth/ Oberon/Oberon07.Report.pdf
6. http://www.inf.ethz.ch/personal/ wirth/CompilerConstruction/CompilerConstruction1.pdf
7. http://www.inf.ethz.ch/personal/ wirth/CompilerConstruction/Compil-erConstruction2.pdf
8. http://www.inf.ethz.ch/personal/ wirth/ProjectOberon/PO.Applica- tions.pdf(Ch.12)