May 2006 - Posts

IQ97 Deployment Error Fixed

Get new drop: current VB9 Customer Technology Preview (CTP). Copy the database Saturn5_002.mdf and Saturn5_002_log.LDF to c:\. Zipped VB9 project directory tree.

Apologies ... the previously posted versions of IQ97 were missing a source file, specifically, {lambda}Exec, where {lambda} is the UNICODE greek character. Turns out that Winzip silently refused to copy the file, so it was just absent from the source project. The workaround was simply to comment out the three references to it that appeared in Module1.vb and delete the file from the Visual Studio project, as, no doubt, some readers discovered. The lambda execution mode is work-in-progress at this time, anyway, so there is no real harm in deleting the file. However, to prevent this kind of thing in the future, I've learned my lesson: Don't Try to be Cute When Naming Files. The drop above fixes the problem.

 

Posted Friday, May 26, 2006 1:31 PM by brianbec | 1 comment(s)

Filed under: ,

IQ97: Gas and Go!

Get new drop: current VB9 Customer Technology Preview (CTP). Copy the database Saturn5_002.mdf and Saturn5_002_log.LDF to c:\. Zipped VB9 project directory tree.

 

The Hewlett-Packard manual exhibits the following command strings (last blog I said there were 256 possible and 236 used, but here there are 250 – the explanation is that I fold 19 command strings to uppercase, e.g., LBLa = LBLA, and there are five extension command strings, 19-5+236=250; this is admittedly a tiny bit of cruft in my program). This is taken right out of the database table “InstructionSet”:

 

0        

CLX      

ENG      

GSBA     

GTO9     

LBL2     

LSTX

RCL1

S        

ST/8     

ST+8     

STO4     

X<0?     

1        

COS      

ENT^     

GSBB     

GTOA     

LBL3     

-

RCL2

SCI      

ST/9     

ST+9     

STO5     

X<>Y?    

2        

COS-1    

e^x      

GSBC     

GTOB     

LBL4      

MRG

RCL3

SF0      

ST-0     

ST*0     

STO6     

X=Y?     

3        

DEG      

F0?      

GSBD     

GTOC     

LBL5     

N!

RCL4

SF1      

ST-1     

ST*1     

STO7     

X>Y?     

4        

/        

F1?      

GSBE     

GTOD     

LBL6     

->P

RCL5

SF2      

ST-2     

ST*2     

STO8     

X<=Y?    

5        

D->R     

F2?      

GSBa     

GTOE     

LBL7     

%

RCL6

SF3      

ST-3     

ST*3     

STO9     

<X>      

6        

DSP0     

F3?      

GSBb     

GTOa      

LBL8     

%CH

RCL7

SUM+     

ST-4     

ST*4     

STOA     

X^2      

7        

DSP1     

FRC      

GSBc     

GTOb     

LBL9     

Pi

RCL8

SUM-     

ST-5     

ST*5     

STOB     

X<->I    

8        

DSP2     

FIX      

GSBd     

GTOc     

LBLA     

+

RCL9

SIN      

ST-6     

ST*6     

STOC     

X<->Y    

9        

DSP3     

GRAD     

GSBe     

GTOd     

LBLB     

PREG

RCLA

SIN-1    

ST-7     

ST*7     

STOD     

Y^x      

.        

DSP4     

GSB0     

GSBi     

GTOe     

LBLC     

PRST

RCLB

SPC      

ST-8     

ST*8     

STOE     

 

1/X      

DSP5     

GSB1     

GTO0     

GTOi     

LBLD     

PRTX

RCLC

SQRT     

ST-9     

ST*9     

STOI     

 

10^x     

DSP6     

GSB2     

GTO1     

->HMS    

LBLE     

P<->S

RCLD

ST/0     

ST+0     

ST/i     

STOi     

 

ABS      

DSP7     

GSB3     

GTO2     

HMS->    

LBLa     

PSE

RCLE

ST/1     

ST+1     

ST-i     

TAN-1    

 

CF0      

DSP8     

GSB4     

GTO3     

HMS+     

LBLb     

->R

RCLi

ST/2     

ST+2     

ST+i     

TAN      

 

CF1      

DSP9     

GSB5     

GTO4     

INT      

LBLc     

Rv

RCLi

ST/3     

ST+3     

ST*i     

*        

 

CF2      

DSPi     

GSB6     

GTO5     

ISZI     

LBLd     

R^

RCLS

ST/4      

ST+4     

STO0     

WDTA     

 

CF3      

DSZI     

GSB7     

GTO6     

ISZi     

LBLe     

RAD

RND

ST/5     

ST+5     

STO1     

X<>0?    

 

CHS      

DSZi     

GSB8     

GTO7     

LBL0     

LN       

R->D

R/S

ST/6     

ST+6     

STO2     

X=0?     

 

CLRG     

EEX      

GSB9     

GTO8     

LBL1     

LOG      

RCL0

RTN

ST/7     

ST+7     

STO3     

X>0?     

 


The first task is to fill the dictionary ‘gas tank’ of static delegates with pairs of command strings and delegates, preparatory to going into a loop reading command strings and executing delegates. Do this in DelExec.Init. The first ten commands are DSP0, DSP1, …, DSP9 for setting up the number of digits to display on the calculator faceplate and in console-mode printouts. In the ironic fashion presented last time, these will all dispatch to a single command delegate, dspcmd:

    Sub Init()

        Dim digits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}

        Dim labels = {"A", "B", "C", "D", "E"} REM folded to uppercase

        Dim ops = {"+", "-", "*", "/"}

       

        For Each i In digits

            staticDelegates.Add("DSP" + i, AddressOf dspcmd)

        Next

 

Here, we use + to stitch together strings; & would work just as well (I’m not sure if there is any difference). Let’s look at dspcmd:

    Function dspcmd(ByVal c As String, ByVal m As Integer) As Integer

        Savex()

        Dim q = c(3) REM fish out the digit from the command string

        Dim r As Integer = dpyPrecision

        If q = "i" Then

            r = reg(25) REM DSPI takes precision from Indirect register

        Else

            r = Val(q)

        End If

        If r >= 0 And r <= 9 Then

            dpyPrecision = r

        End If

        updateWholeUI()

        Return m + 1

    End Function

 

Sure enough, we fish out the digit pseudo-argument from the input string. The only interesting thing is the DSPI case, where the digit isn’t a digit at all, but an uncial i. The DSPi command—pronounced “Disp-Eye” or “Display Indirect”—takes the precision from the I, or Indirect, register. Cool! So we need to gas up the dictionary with one more command string for dspcmd:

        staticDelegates.Add("DSPI", AddressOf dspcmd)

 

for eleven so far (the command string is folded to uppercase). Now, let’s put in all the GTOx, GSBx, LBLx, STOx, and RCLx commands, where x can be any digit or any alphabetic label from A-E:

        For Each i In digits.Concat(labels)

            staticDelegates.Add("GTO" + i, AddressOf gtocmd)

            staticDelegates.Add("GSB" + i, AddressOf gsbcmd)

            staticDelegates.Add("LBL" + i, AddressOf lblcmd)

            staticDelegates.Add("RCL" + i, AddressOf rclcmd)

            staticDelegates.Add("STO" + i, AddressOf stocmd)

        Next

 

That takes care of 15 x 5=75 command strings for 86 so far. You can see, here, why I fold the labels to uppercase: it’s so I can clump together GTOa (uncial a) and GTOA (capital A), for instance, with RCLA (capital A), despite the fact that there is no RCLa command. This is appropriate since the pseudo-argument gets fished out of the actual command string, and all we have to do is remember to fold to uppercase for lookup—but not for copy of the command string passed to the delegate. Another way of saying this is that ONLY the insides of command functions are case-sensitive. Another excuse for this is that it helped me avoid useless ambiguities like 10^X versus 10^x. But I already admitted it was cruft; get over it.

Did you spot the opportunity for dynamic identifiers? Here is what I wanted to write:

        For Each cstr In {"GTO", "GSB", "LBL", "RCL", "STO"}

            For Each i In digits.Concat(labels)

                staticDelegates.Add(cstr + i, AddressOf (cstr + "cmd"))

            Next

        Next

 

In other words, I wanted to compute not just the command string, but the name of the delegate, too, to prevent the copy-and-paste error of, say, writing

            staticDelegates.Add("GTO" + i, AddressOf gtocmd)

            staticDelegates.Add("GSB" + i, AddressOf gtocmd)

 

Now this is the kind of annoying, stupid coding mistake that I know you never make, but, alas, I am not immune. It shows up like a land mine, much later, during calculator execution as subroutines mysteriously fail to return. But it is completely avoidable if we can write the desired code. So, what would that take? For one thing, the argument of AddressOf is not known at compile time, so the delegate can’t be constructed at compile time. So, isn’t it natural to call them late-bound delegates? We don’t have them yet, but scenarios like this point out the need, and we’re prototyping them for later development in VB.

Next, we need to fill up the tank with the storage-register arithmetic commands, STO+0, STO+1, and so on. These are a lot like the += , -=, *=, and /= operators in VB: they cumulatively side-effect the indicated registers by the indicated arithmetic operation taking the other argument from the X slot of the data stack:

        For Each i In digits

            For Each j In ops

                staticDelegates.Add("ST" + j + i, AddressOf stocmd)

            Next

        Next

        For Each j In ops

            staticDelegates.Add("ST" + j + "I", AddressOf stocmd)

        Next

 

These loops add 44 command strings, for 130 so far. We need four more case-folded indirect commands:

        staticDelegates.Add("GTOI", AddressOf gtocmd)

        staticDelegates.Add("GSBI", AddressOf gsbcmd)

        staticDelegates.Add("RCLI", AddressOf rclcmd)

        staticDelegates.Add("STOI", AddressOf stocmd)

 

for 134. There are exactly 100 more commands, accounting for the 236 required minus the two unimplemented (MRG and WDATA). The program does a quick check of this at the end of DelExec.Init, counting the command strings in the database and the command strings in the Dictionary, and diffing them both ways. If a string is in the database and not the dictionary, it’s unimplemented. Vice-versa, it’s an extension. See the source for all that.

Ok, now, sit down. Take a deep breath, because I am about to put on my best Halloween face and try to scare the pants off you. Remember dspcmd? Have you noticed that it is never called statically? Go ahead, to the source, find dspcmd in DelExec, highlight it with the mouse, and press Alt-F12. That will find all references to the symbol. Just its definition and its insertion into staticDelegates at command strings DSP+i and DSPI. No calls. So what’s the use of it? Answer: the REPL loop or the UI faceplate sends one of those 11 strings into the system, which looks up the delegate in the dictionary and dispatches to it through the delegate.

Ok, great, but since it’s not used directly in the simulator program, why should it be stored directly in the simulator program? Why not just read it out of the database, just like the calculator program that uses it, not to mention along with all the state variables and what not? Well, the answer is, of course we should. Just a matter of finding a way to store code in the database, that is, a way to convert code to data. Now, what I want to do is just cut the VB code and paste it into the database, and read it back in at dictionary-creation time. We call that code literals, and this scenario points out the need for it, and we’re prototyping it for later development in VB. In the mean time, we have to use more grotesque means. The only way of saving code as data in the current CLR is to disassemble it into IL, and the only ways to restore code from data is Reflection.Emit and Lightweight CodeGen (the last two links are superb; thanks, Joel Pobar). So, I wrote a little disassembler, and write back all the command functions into the database table “Disassemblies,” with foreign keys to another table called “CmdImplementations,” which stores byte-code streams for safety and redundancy. Oh, and, of course, I have a little assembler that writes the code back out into dynamic delegates in module DynExec. If you’ll forgive the length, dspcmd ends up, in “Disassemblies,” as

0

Nop

1

Call

Savex

6

Nop

7

ldarg.0

8

ldc.i4.3

9

callvirt

get_Chars

14

stloc.1

15

Ldsfld

dpyPrecision

20

stloc.2

21

ldloc.1

22

Call

ToString

27

Ldstr

"i"

32

ldc.i4.0

33

Call

CompareString

38

ldc.i4.0

39

Ceq

41

stloc.3

42

ldloc.3

43

brfalse.s

17

45

Ldsfld

reg

50

ldc.i4.s

25

52

ldelem.r8

53

call

Round

58

conv.ovf.i4

59

stloc.2

60

br.s

8

62

nop

63

ldloc.1

64

call

Val

69

stloc.2

70

nop

71

ldloc.2

72

ldc.i4.0

73

clt

75

ldc.i4.0

76

ceq

78

ldloc.2

79

ldc.i4.s

9

81

cgt

83

ldc.i4.0

84

ceq

86

and

87

stloc.3

88

ldloc.3

89

brfalse.s

6

91

ldloc.2

92

stsfld

dpyPrecision

97

nop

98

call

updateWholeUI

103

nop

104

ldarg.1

105

ldc.i4.1

106

add.ovf

107

stloc.0

108

br.s

0

110

ldloc.0

111

ret


 To execute all calculator commands in dynamic-delegate mode, enter the “R” command at the console prompt, then execute the Diagnostics or Primes programs as before. And don’t blame me if it goes into the weeds: it’s only proof-of-concept code.

Posted Thursday, May 18, 2006 9:45 PM by brianbec | 3 comment(s)

Endoskeleton of the IQ97

Get new drop: current VB9 Customer Technology Preview (CTP). Copy the database Saturn5_002.mdf and Saturn5_002_log.LDF to c:\. Zipped VB9 project directory tree.

 

The nice thing about this project is that we can explore technologies quite fully, but in the context of a concrete, small, completely understandable application: a programmable calculator. Anyone who can understand a Hewlett-Packard calculator can understand VB9, LINQ, ADO.NET, comprehensions, lambda expressions, and all the rest of these beautiful new technologies. Many ‘package’ samples are too large, offering no way to understand them piece-by-piece; or too artificial or abstract, leaving too big a gap to what one wants to learn to do; or too mundane to keep you awake. IQ97 is just big enough, just small enough, concrete, and fun.

Because people reason better from the concrete to the abstract rather than the other way around, many generalizations to other kinds of projects will be quite obvious, some so obvious that they don’t need pointing out, others I’ll point out when I notice them. Here’s one right off the bat: every technique we employ to simulate the HP97 can carry over to implementing any other virtual-machine (VM) specification. Any important VMs come to mind? How about the CLR itself? IQ97 will give you lots of ideas toward creating your own CLR / CLI implementation. Later on, we will talk about a virtual machine for executing LINQ comprehensions: a VM that can be implemented over a relational database, over XML, over objects in memory, over any kind of collections.

Because IQ97 is small and constrained, it’s practical to do some crazy things with it. Now, you’re not supposed to resist, saying “why would anyone ever do THAT?” You’re supposed to say “wow, I never thought of that!” then let your imagination go, trying to think of ways to use them in your own work. For instance, I now have planned out the following FIVE execution modes

1.        Delegates to static functions

a.       Development Mode: Includes disassembly and dehydration into the database

b.      Deployment Mode: When development is completely done, the static functions can be erased from the simulator, relying entirely on 2b

2.       Delegates to dynamic functions via Lightweight Code Gen (LCG)

a.       generated in parallel with disassembly and dehydration of the static functions

b.      or, in a completely separate phase, dynamically rehydrated, re-assembled, and generated from the database

3.       Dynamically generated lambda expressions

4.      Rehydrated lambda expressions

5.       Stored procedures in VB through SQLCLR in the database

The first mode is the easiest to understand and implement, and is the basis for all the other modes. In the current drop, 1a and 2a are implemented. 2B just takes courage, but will be the last thing I ever do with this. Some scaffolding for 3 is in place, and the rest are just dreams at present. Let’s dig into mode 1.

The first task is to establish some variables to hold the durable state—that is, persisted to the database—of the machine, in a module named DelExec, short for Delegate Execution. There is a 4-position data stack, a 4-position address stack for GSB (go subroutine) instructions, 4 flags, 224 program-memory slots, 26 registers, and a special last-x register for storing whatever was last in the X slot of the stack. VB9 requires the largest-index value in the declaration of the array bounds, not the length of the array, which would be one more. So an array of length 4 is declared with a 3 in round brackets, and the index will range from 0 to 3, inclusive.

Public Module DelExec

    Public dStk(3) As Double  ' Data Stack

    Public aStk(3) As Integer ' Address Stack

    Public flags(3) As Boolean

    Public pgm(224) As String ' Program Memory

    Public reg(25) As Double

    Public lastX As Double

 

We’re using just ordinary VB Doubles for values in the calculator. This is not precisely correct, as the actual calculator uses binary-coded decimal (BCD) and it’s easy to detect the difference, for instance, in different overflow behavior. However, VB Doubles are good enough for ordinary circumstances, in particular, for the Diagnostics and Primes programs. Furthermore, our goal is to simulate the calculator software, not its hardware. It would be a different project to precisely simulate the calculator numerics, and even the exact microcode (someone else has written such a simulation of the microcode for the HP45). But that would take us away from the point of the exercise: to show off fancy language and programming techniques.

Other aspects of durable state include the current display precision, trig mode and display style, which are loaded with the defaults below when the calculator powers up:

    Public dpyPrecision As Integer = 2

    Public dpyStyle As String = "FIX"

    Public trigMode As String = "RAD"

 

For executing stored programs, we need an instruction pointer (ip), and we shall add a couple more instrumentation variables to count the total number of instructions executed and the real time taken. These are not in the original calculator, of course, but are just too good not to add to our sim. Since we can execute the sim out of the database, these will need to be durable, too:

    Public ip As Integer

    Public ic As Integer

    Public it As New Stopwatch REM from System.Diagnostics

 

We’ll need exactly these things in the database. Use “Open Table Definition” and “Show Table Data” in Visual Studio’s Server Explorer under the connection to Saturn5_002.mdf and look over the tables “Stack,” “Registers,” “Flags,” and “Controls” (I don’t see a way to copy-paste the database schema here, so just take a look). We interact with these tables using LINQ / Dataset as follows:

    Sub initVmdb()

        dInstructionSet = New DataPackage("InstructionSet")

        dFlags = New DataPackage("Flags")

        dControls = New DataPackage("Controls")

        dStack = New DataPackage("Stack")

        dRegisters = New DataPackage("Registers")

    End Sub

 

DataPackage is my abstraction over the LINQ / Dataset stuff, capturing the patterns I use over and over:

    Public Class DataPackage

        Implements IDisposable

 

        Public ds As DataSet

        Public da As SqlDataAdapter

        Public dt As DataTable

        Public dq As IQueryable

        Public cb As SqlCommandBuilder

 

Every one of my data packages will need all five of these things. I make them public for my own convenience. Yeah, I know the conventional wisdom of private fields, public properties, yadda yadda, but this is for me, only, and I don’t need to overdo that discipline. A couple of trivial constructors:

        Sub New()

            'Nothing interesting here, but must exist

        End Sub

 

        Sub New(ByVal nym As String)

            Me.create(nym)

        End Sub

 

The following is where the real stuff occurs: this is how we handle each table in the database. Given a name, first we make a DataSet (I frequently use the pseudonym “nym” for variables containing names, because there are too many other names of names named name in a typical system). We also must make a data adapter, then just have it query up the whole table into the DataSet, which I always want to do. We also must have a SQL command builder, even though it’s not directly used. Since each of my DataSets contains just one table, for simplicity, I can set a variable of type DataTable always to the 0-th element of ds.Tables:

        Public Sub create(ByVal nym As String)

            ds = New DataSet(nym)

            da = New SqlDataAdapter("SELECT * FROM " + nym, conn)

            cb = New SqlCommandBuilder(da) REM must exist, but not used

            da.Fill(ds)

            dt = ds.Tables(0)

 

Now, we have another pattern: In every one of my tables (with one exception, later), the first column and only the first column contains the primary key, which sometimes happens to be one of those SQL autoincrement is-identity kinds. I found the following patterns to work, partly by trial-and-error and partly by asking around. I don’t understand the TableMappings, but I need them:

            Dim fudge(1) As DataColumn

            fudge(0) = dt.Columns.Item(0)

            dt.PrimaryKey = fudge

            dq = dt.ToQueryable()

            da.TableMappings.Add(nym, "Table")

        End Sub

 

        Public Function update(ByVal nym As String) As Integer

            Return da.Update(ds, nym)

        End Function

 

The rest of the class is just standard IDisposable boilerplate support for releasing non-garbage-collected resources:

        Protected Overrides Sub finalize()

            Me.Dispose()

            MyBase.Finalize()

        End Sub

 

        Private disposedValue As Boolean = False  ' To detect redundant calls

       

        Protected Overridable Sub Dispose(ByVal disposing As Boolean)

            If Not Me.disposedValue Then

                If disposing Then

                    cb.Dispose()

                    ds.Dispose()

                    da.Dispose()

                    dt.Dispose()

                    Me.disposedValue = True

                End If

                ' TODO: free shared unmanaged resources

            End If

            Me.disposedValue = True

        End Sub

 

    End Class

 

The next seven variables are NON-durable, and track modes in the simulator rather than any documented modes in the calculator. Numeric entry, for instance, requires us to track whether the decimal or EEX key has been pressed. This isn’t the time to get into those vagaries, the point here being that just a handful of extra variables take care of them.

    Public eex(1) As Integer

    Public currexp As Double = 0

    Public liftP As Boolean = True ' Will next entry lift the stack?

    Public traceP As Boolean = True

    Public eexmode As Boolean = False

    Public efrac As Boolean = False ' Entering a fractional part?

    Public progP As Boolean = False

 

Notice the Public access for most everything. Since this application is standalone, I break things up into modules and classes just for design modularity, and access control is not something I need to worry much about, so I am completely whimsical about it.

I’ll use the term command to mean a calculator function, whether executed by clicking a button on the faceplate, or by typing a shortcut key with the faceplate up, or by typing a calculator command in the console window, or by running a stored calculator program. Every command has a static function in the DelExec module (all functions in a VB module happen to be static functions). Executing a command works by first presenting a command string to the simulator. IQ97 commands do not take arguments, but variable information is often encoded in the command string. For instance, the STO4 command means “Store a copy of the value in the X slot of the data stack into register 4.” Likewise, STO3 means “Store a copy of the value in the X slot into register 3.” Today, it would be considered a more mainstream design, perhaps, to give the destination register as an argument to a generic STO command. However, that was not quite the way Hewlett-Packard did it back then.

There is a bit of irony to the story: the way to key in the STO4 command on the calculator faceplate is first to press the STO key and then to press the 4 key. However, internally, this combination is stored as a single byte in program memory. In fact, this merged keystroke encoding was a big improvement over the ancestral HP65, which, in fact, stored commands and arguments sequentially. However, that led to twisted code as programmers strove to save precious memory.

We need a CLR type, cmdel, for command delegates and a dictionary, staticDelegates, to look up command delegates given command strings:

    Delegate Function cmdel(ByVal cmd As String, ByVal ip As Integer) _

    As Integer

    Private staticDelegates As New Dictionary(Of String, cmdel)

 

So, in our sim, we pick up the STO key, then a numeric key, and build a combined command string, “STO4,” and the static function for all STO commands then fishes the register back out of the string, again. This explains why every command function takes the actual command string as an argument—it often needs to fish out hidden pseudo-argument information.

Every command function also takes an integer representing the current instruction pointer, and is responsible for returning the next value of the instruction pointer, which is almost always just one more than the current ip, exceptions including branch and subroutine-call instructions. This is another advantage of the merged keystroke encoding: since all instructions are the same length, all command functions can increment ip the same way.

So, as you can probably guess, there are 256 possible opcodes. The calculator defines 236 of them, and I implemented all but WDATA and MRG, which are for writing data to mag cards. You can see all the command strings and their key scan codes in the table “InstructionSet.” The numeric opcodes are not used in IQ97, so we don’t record or store them anywhere.

It might seem most straightforward to start off by showing the numeric-entry commands. However, they are a bit messy due to internal modalities concerning the decimal point and EEX key, so let’s delay presenting them for a bit. The numeric operation commands are a good place to start. Consider the following, which implements the plus command, whenever the + key is pressed:

    Function plus(ByVal c As String, ByVal m As Integer) As Integer

        Savex()

        dStk(0) += dStk(1)

        dStk(1) = dStk(2)

        dStk(2) = dStk(3)

        updateStackUI()

        Return m + 1

    End Function

 

So, clearly, dStk(0) represents the X slot of the data stack, dStk(1) the Y slot, and so on. Savex is a ubiquitous utility; most of the command functions call it:

    Sub Savex()

        lastX = dStk(0)

        EndInput()

    End Sub

 

which just saves the last value of the X slot of the data stack in the special register lastX, where it can be fetched with the LSTX command. EndInput interacts with the numeric input modes, so we’ll talk about it later.

UpdateStackUI is one of a bunch of functions for operating the blinking lights. We get to all that later.

So there you go: we have laid out the endoskeleton of the calculator implementation. Next installment, we’ll fill up the dictionary with command delegates and start executing functions!

Posted Thursday, May 18, 2006 8:25 AM by brianbec | 2 comment(s)

State of the IQ97

I’ll call my simulator of the HP97 the IQ97 (see if you can decipher the pun). Despite my effort at self-restraint, this has bloomed into a full-blown obsessive-compulsive sample work, as I can’t stop thinking of new things to do with it. So, rather than waiting until it’s all done, since there is no way to know when that will be, I’ll blog it out as I go along. A tremendous amount of interesting technology that deserves to be shown off has already gone into it. You’ll forgive the bits that get finished or undone later in the spirit of joyful bodging, and you’ll overlook messy bits that were just hacked together to get to something more interesting. And bugs? Please post or email ‘em when you find ‘em.

You’ll need the current VB9 Customer Technology Preview (CTP). Then you will need to copy the database Saturn5_002.mdf file and Saturn5_002_log.LDF file to c:\ , the root of your C drive. (This pun is too obscure to guess: one version of the chip in the HP calculators was called the ‘Saturn’ and the rocket used to launch the Apollo-Soyuz mission was the mighty Saturn 5). Finally, you will need this zipped VB9 project directory tree. You should be able to open up the solution, hit F5, and follow along these blogs. At this point, if you have things properly installed, you will see quite a lot of console output fly by, ending with the last few lines of the calculator’s diagnostic program:

...

219 SCI

220 PRTX

221 DSP1

222 FIX

223 PRTX

224 R/S

=000:=>

 

Most of the console output consists in disassembled calculator-key functions, about which we will have more to say later on.

There are lots of things to do in console mode, but, for now, just type m, which stands for ‘monitor,’ and <Enter>, and you should see the faceplate of the IQ97. You have now exited console mode: all keyboard and mouse entry go into the UI until you type q at the faceplate. That will send you back to console mode. For some reason I don’t understand, you must force the UI into focus the first time you type ‘m<Enter>;’ the easiest way to do that is just click anything on the faceplate or just Alt-tab over to its window. After the first time, you can m<Enter> and q back and forth and focus will track correctly.

Hewlett-Packard’s diagnostic program is preloaded, so if you click the ‘A’ button on the extreme left center or just type a (no <Enter> on the faceplate), you should see blinking lights for about 5 to 10 seconds, halting with 0.00 in the main display at top-left; -8.889e-088 twice and 0.00 on the paper tape, right-center top; and, in the machine-state display in green, left-center top, ip = 224, cmd = R/S, ic = 1018, TIME = something reasonable around 5 to 10 seconds, and ips (instructions per second) = something reasonable around 250 or below. If you get all that, you’ve passed the basic diagnostics and you are ready to use the calculator.

The UI mode responds to mouse clicks, of course, but also to several standard computer keyboard presses, like the number keys, the arithmetic keys, <End> for the calculator ENTER key (because the computer <Enter> key automatically mouse clicks whatever button has the focus, it was difficult to use that for the calculator ENTER key), <Backspace> for CLX, A-E for the A-E keys, F for the calculator f-shift key (the glaring yellow one), and R for  Rv, which rolls the stack down one.

To get off the ground, type 1 <End> 2 +, and you should see 3.00 in the main display and the X slot of the stack display center-top. The main display is always an echo of the X slot. If you just finished the diagnostics, the rest of the stack will contain Y=0.00, Z=300.00, and T=300.00. For those not familiar with the famous Hewlett-Packard Reverse-Polish Notation (RPN), it’s just postfix immediate. Numeric keys build up inputs, so, when we type the first 1, we start input mode. When we type <End>, we end input mode. Typing 2 causes an automatic push (HP calls it stack lift) of 1 into the Y slot, and puts the machine back into buildup mode . + Terminates buildup mode and immediately executes Y+XàX, dropping the stack down, autocopying T into Z. Most of the rest of the calculator functions operate this way, and a little bit of playing around will get you familiar and at-ease with it if you aren’t already.

While RPN eventually lost out to so-called ‘algebraic mode’ with parentheses in the calculator mass market, presumably because students preferred to slavishly copy formulas from textbooks to their calculators rather than to read and understand the formulas, RPN really is technically superior and much easier to grasp, not to mention much easier to edit and correct. Yes, there is a teeny-tiny learning molehill, but the benefits massively outweigh the cost, especially for anyone who has actually studied computers for a little while. Be that as it may, RPN was introduced by HP for pure expediency: it required less precious, on-board memory than the algebraic style. RPN only requires the value stack because all operations execute immediately. An algebraic-mode calculator must store a complete expression tree as it is built up, and would thus have been intolerably limited in 1976, when 25 memory cells was considered lavishly, luxuriously, many. With algebraic mode, users had to resort to pencil-and-paper to break up big formulas, while, with a little practice, an experienced RPN user could key in the most hairy formula right the first time (hint: start from the innermost subexpressions and build OUT, just as with applicative order or call-by-value in programming languages.

If you want to play with the primes program, go to the drop-down combo box just under the main display and select “Primes” (don’t bother with “Moon Lander” – it’s not working yet). Now TAB out of the combo box to remove focus from it (another UI booger – sorry). Click or type E to enter Prime’s Auto Mode, and type 100 B 200 C D to generate all primes from 100 to 200. This should go for about 20 to 40 seconds, with 4788 instructions executed at 125-250 ips, and leave results on the scrollable paper tape. If you want to heat up your computer for a few hours, type 1000000000 B 1000001000 C D and it will spit out one prime about once every 10 to 20 minutes. I suggest you leave that for overnight, though, and try out the console mode first, without the UI, because console mode has about 1000 times greater throughput.

Type q to quit the UI (you can get it back with another m<Enter>), and enter the following at the console prompt, each line terminated with <Enter> (don’t press <Enter> too early – it single-steps the program in the calculator, but, if you do, I’ll let you guess how to get started again):

read.prim

an extended command, not really part of the HP97 command set

T

Turns of trace mode

E

Turns on auto-print mode, lest the calculator stop after each prime

1

Simulates pressing of the 1 key on the calculator

0

Yes, we have to enter each digit on its own

0

As if it were an individual calculator key press

0

Each followed by the Enter key, so, yes, console mode is clumsy

0

But it will execute much, much faster, when we run the program

0

So hang in there for four more zeros, ok?

0

Three …

0

Two …

0

One …

0

Liftoff!  Yay, we’re done (and it sounds like a rocket launch)

B

Tells the calculator that we have the starting prime

1

Oh, no, here we go again!

0

Ok, go with me for five zeros

0

Four …

0

Three …

0

Two …

0

One …

1

A break from the monotony

0

Just three more and we’re done for good, I promise

0

But now I’m just funning with you because you got the idea

0

Way back there

C

Tells the calculator that we have the ending prime

D

Let ‘er rip!

 

This should start producing primes immediately, starting with 1000000007, and ending with 1000000993, at the rate of 1 every few seconds, executing exactly 8,628,918 instructions at the rate of up to 225,000 instructions per second. If you did the same in UI mode, the number of instructions will be exactly the same, but the ips will be more like 225, taking well over one hour.

The primes program has another nifty feature: it will also factor any number in the X register if you click the A key (or type A<Enter> at the console-mode prompt, or just A at the UI). Make sure you’re in auto-print mode, that is, that you have toggled E until 1 shows in the X slot and the main display.

At the console prompt, you can also type preg to print out the registers and prst to print out the stack, or go back into trace mode with t, all followed by <Enter>, of course, which I won’t bother to mention for console mode from now on.

If something goes wrong and the program becomes unresponsive (called ‘going into the weeds’ – a metaphor from automobile racing) just kill it from Visual Studio and start it over. It’s just a blogging sample, don’t you know, and hasn’t been subjected to anything like product-level QA.

Ok, but guess what? You’ve stuck with me through all that and we are ready for our first REAL TREAT of VB9 programming! Look at the code for intercepting computer keystrokes in the UI panel. It’s in Monitor.vb and is called Monitor_KeyPress. This is the ordinary winforms event handler for KeyPress, which gets forwarded to the topmost control—the window itself—from whatever subcontrol happens to have the focus.

    Private Sub Monitor_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles Me.KeyPress

 

        Dim k = Char.ToUpperInvariant(e.KeyChar)

 

        If k >= "0"c And k <= "9"c Then

            CObj(Me).("Num" & k & "_Click")(Nothing, Nothing)

        ElseIf k >= "A"c And k <= "E"c Then

            CObj(Me).(k & "Key_Click")(Nothing, Nothing)

        ElseIf k = "F"c Then

            fShiftKey_Click(Nothing, Nothing)

        ElseIf k = "+"c Then

            Me.PlusKey_Click(Nothing, Nothing)

        ElseIf k = "-"c Then

            Me.MinusKey_Click(Nothing, Nothing)

        ElseIf k = "*"c Then

            Me.TimesKey_Click(Nothing, Nothing)

        ElseIf k = "/"c Then

            Me.DivKey_Click(Nothing, Nothing)

        ElseIf k = "R"c Then

            Me.RollDownKey_Click(Nothing, Nothing)

        ElseIf k = "Q"c Then

            fMonitor.Dispose()

            fMonitor = Nothing

        End If

 

    End Sub

 

We’ll convert the key to uppercase and start testing its value. The first branch runs when you press any key in the 0 through 9 range, and … what’s that weirdness? Method call through dynamic Identifiers! The idea is rather than specifying the method to call for each digit key statically, like this

        If k = "0"c Then

            Me.Num0_Click(Nothing, Nothing)

        ElseIf k = "1"c Then

            Me.Num1_Click(Nothing, Nothing)

        ElseIf k = "2"c Then

            Me.Num2_Click(Nothing, Nothing)

        ...

 

and so on, let’s just calculate the name of the method to call from the keycode. It only works through late binding (can’t statically bind to a method name that is not known until run time), so we first must convert the method receiver to something of static type Object, that is, convert Me to an object (hopefully, this syntax noise will be eliminated in the next drop of VB9):

CObj(Me)

Then, let’s calculate the name of the button-click event-handler method to call:

"Num" & k & "_Click"

(the ampersand glues strings – and things automatically convertible to strings – together), put that method name inside parentheses to the right of the dot:

CObj(Me).("Num" & k & "_Click")

(that's the magic syntax that means “dynamic identifier!”), supply the arguments to finish our first method-call through dynamic identifier:

CObj(Me).("Num" & k & "_Click")(Nothing, Nothing)

 

So, pressing a digit key on the computer keyboard just forwards execution to the exact code that process a digit-key button click through the computer mouse, just as we want. There is another method call through dynamic identifiers for handling A through E. Notice we cannot make the all-too-easy typing error of getting the digit wrong in the method name, so the dynamic identifiers also give us prophylaxis against cut-and-paste coding bugs!

We could not conveniently do likewise with the arithmetic keys. While I would have liked to say

    Private Sub [+_Click](...) Handles PlusKey.Click

 

which VB9 accepts, by the way, because of the bracket escape syntax, the CLR balks at a name such as +_Click for a method and it fails to link. But if I had been able to write that, then the four lines for processing arithmetic keyboard input would have collapsed to just one method call through dynamic identifiers.

That’s enough for today, but in future installments, you will see much more use of this feature, as well as calls through dynamic-method delegates and through lambda-expression delegates. Also in the future, I will update the IQ97 source solution as I go along, and each blog will tell you whether there is a  new drop to pick up.

Posted Tuesday, May 16, 2006 10:56 PM by brianbec | 11 comment(s)

More Posts