bhdl / bhdl

A Programming Language and System for making PCBs
https://bhdl.org
Other
5 stars 2 forks source link

BHDL keywords #9

Open lihebi opened 3 years ago

forrestbao commented 3 years ago

This thread is about keywords and "key-syntax".

Here is my philosophy:

  1. let newbies pick up BHDL quickly, and let lazy users type less and read less
  2. but allow advanced users take full scale of function programming in Racket
  3. make the development of BHDL easier by taking advantage of what's already in Racket's ecosystem.

Hence, I propose the following changes:

  1. Definitions to circuits and functions

    Currently functions are defined as in Racket, and circuits are:

    (define three-button
    (make-circuit 

    I suggest to use two keywords function (or some sort of abbreviations) and circuit (or something similar) to separate the two kinds of definitions.

    Reasons:

    1. Keyword function is used in VHDL, SPICE, and Verilog, well-known to EE users.
    2. Our circuit is called SUBCKT (subcircuit) in SPICE, module in Verilog, and entity in VHDL.
    3. It increases the readability of code.
    4. It makes the program shorter, and indentations fewer.
  2. # directives Can we turn blocks starting with #:vars, #:parts, etc. into a "typical" LISP block? Like

    (parts (
        (R (R 1k))
        (U1 (ESP-01))
    )
    )

    Or the VHDL way (from here):

    entity Mux8to1 is
    port (
        Inputs : in Std_Logic_Vector(7 downto 0);
        Select_s : in Std_Logic_Vector(2 downto 0);
        Output : out Std_Logic
        );
    end Mux8to1;

    I just wanna reduce the varieties of syntax. I love LISP because the entire program is just nested brackets.

    Verilog used # when refering to parameters.

  3. Also, for these # directives, we have several candidate names:

    a. For pins: external-pins, ports, pins, IOs

    • VHDL calls them ports
    • SPICE calls (external) nodes, see SUBCKT
    • Verilog calls them input and output.
    • To me, external-pins is too long, ports is good -- because VHDL does so, but if we use parts below, ports looks too similar, pins is good too but it doesn't reflect the I/O.
    • Another issue is shall we treat the pins as arguments of a circuit -- to be functional programming, and Verilog does the same.

    b. For parts: variables, vars, parts, components

    • variables or vars may not be good as they literally mean variables in VHDL and Verilog.
    • And it is confusing with real variables in a function.
    • VHDL calls them component. Verilog does not use a keyword but use typed declaration See Slides 17-18. This doesn't apply for SPICE.

    c. For wires: connect, hook, wiring, wire, netlist

    • VHDL calls as port map (but I am not sure whether it covers all connections), Verilog calls as wires.
    • netlist, which I started using in onebutton project, may not be good as netlist is grounded.
    • wire maybe the best. Short, clear, and an existing HDL uses it.

    d. For layouts: layout, place

  4. Layout keywords

    We discussed about them in this issue: https://github.com/lihebi/bhdl/issues/4

    Can we replace the keywords/operators from Pict into more circuit-oriented ones? Like over, left, facing. etc.

  5. Keywords for different connection types

    To be discussed in https://github.com/lihebi/bhdl/issues/8

  6. Syntax to help pass paramters and/or arguments between instances or definitions of circuits.

    To be discussed in a new thread. In Verilog, this is called parameter. They use the # to separate parameters and ports when calling a module.

  7. Syntax for list comprehension

    There are two cases needing list comprehension: those for ports/buses/signals/wires (arrays of signals/wires), and those for arrays of parts.

    For an array of parts, currently in BHDL:

    [buttons (for/list ([row (in-range 3)])
                            (SW_Push))]

    The amount of brackets is excessive.

    In VHDL, this could be achieved in for-loops. Verilog uses something like defining a bus.

    I wish we could support something like Verilog:

    [buttons (SW_Push[3])] ; for 1D array
    [buttons (SW_Push[3][3])] ; for 2D arrays

    as we are already doing similarly for buses.

    Also for connections, the syntax is better if simplified. Can this

    #:connect
          (for*/list ([i (in-range 4)] [j (in-range 5)])
                         (*- self.col[j](list-ref (list-ref matrix i) j)self.row[i])
          )

    become somethings like the below?

    #:connect
    (    (*- self.col[j] self.row[i])  
                (for i in [0:4] )
                (for j in [0:5])
    )

    Kinda like Python's list comprehension.

lihebi commented 3 years ago

Thanks for the detailed suggestions. I'm convinced by most of them. Let me reply each inlnie.

  1. Definitions to circuits and functions using function and circuit

Taken.

  1. change #:parts directives to lisp structure (parts ...)

Taken.

  1. Also, for these # directives, we have several candidate names: a. For pins: external-pins, ports, pins, IOs b. For parts: variables, vars, parts, components c. For wires: connect, hook, wiring, wire, netlist d. For layouts: layout, place

Taken. Based on your analysis, let's use these:

(function (delay-push-fixed [btn #f])
  (circuit
     (pins VCC GND Ctrl)
     (parts [RC1 (RC-fixed)]
            [D1 (Diode '1N4148)]
            [R4 (R '1k)]
            [SW1 (if btn btn (SW_Push))]
            [Q1 (MOSFET '2N7002)]
            [R2 (R '10k)]
            [D2 (LED)])
     (wire (*- RC1.Out D1.anode Q1.1)
           (*- RC1.GND Q1.2)
           (*- Q1.3 self.Ctrl))
     (layout (inset SW1 50)))
lihebi commented 3 years ago
  • Another issue is shall we treat the pins as arguments of a circuit -- to be functional programming, and Verilog does the same.

Do you mean this?

(function (delay-push-fixed btn-name [btn #f])
  (circuit
     (if btn
         (pins VCC GND Ctrl)
         (pins VCC GND Ctrl btn-name))
     (parts ...)
     (wire ...)
     (layout ...))

This could be interesting. This seems to be useful in the context of inheritance #6?

lihebi commented 3 years ago
  1. Layout keywords

I'm summarizing our current design and your suggestions in #4 in this cell, and make comments in the next cell.


The current keywords are the same as racket/pict library, summarizing below:

For append and superimpose, there are some prefixes declaring the orientation and offset.

It looks something like these:

vl-append
vc-append
hbl-append
lt-superimpose
lb-superimpose
lc-superimpose
ltl-superimpose
lbl-superimpose

I'm also pasting your code in #4 here, with keywords canvas, left, right, above, below, and angle

#:layout ( 
             ; Only the following keywords: canvas, left, right, above, below, and angle. 

             (canvas rectangle 50 40) 
             (left Rh Rv 10); horizontal spacing between Rh and Rv is 10mm 
             (above U C)     ; the chip must be above the C  

             (left (left_edge canvas) (left_edge U) 4) 

             (angle Rh 90) ; Rh is vertical
             (angle Rv 0)  ; Rv is horizontal
             (angle (left_edge canvas) (left_edge U) 0) 
           )
lihebi commented 3 years ago

Here are my comments:

  1. your original design is to build a constraint solving system, and all specified are constraints. As I commented in #4, that will introduce some overhead integrating a constraint solver like Z3, and I prefer to postpone this decision.
  2. Your left, right, above, below is in the append family, the h-append and v-append. We probably just need left and above too. The more important problem is, there's another degree of freedom, the alignment, e.g. either vl-append, vc-append, or vr-append.

If we want to avoid too many functions like vl-append, vc-append, and vr-append, we could do this:

(left a b c)                ; default is center
(left #:align "center"
        a b c)
(above #:align "left"
        a b c)
(above #:align "right"
        a b c)
  1. the angle is description, and rotate is an action. This is similar that left is description, and append is action. We could use one set. I think left and right is more clear that we are talking about placement/layout, while append does not convey that information.

  2. We need pin-over and superimpose because sometimes we do want to overlap parts. E.g. an Arduino Uno footprint and parts to be placed on top. For that, probably we add an over to unify previous pin-over and superimpose.


To sum up, here are my proposals (with question marks for discussion):

lihebi commented 3 years ago
  1. Syntax for list comprehension [PART 1/3]

    [buttons (for/list ([row (in-range 3)])
                           (SW_Push))]

    The amount of brackets is excessive.

The list comprehension in racket is very similar to the one in python.

Lisp is prefix and use a lot of parenthesis, and thus:

(for/list ([i (in-range 3)])
    (SW_Push))

The python syntax would be:

[SW_Push() for i in range(3)]

They are basically the same, but I totally agree lisp is verbose.

lihebi commented 3 years ago
  1. Syntax for list comprehension [PART 2/3]

    I wish we could support something like Verilog:

    [buttons (SW_Push[3])] ; for 1D array
    [buttons (SW_Push[3][3])] ; for 2D arrays

I think this is a good syntax, and we are in lisp so we can implement anything! The only thing is that SW_Push is a function, and we have to call it somewhere three times. If we just write SW_Push[3], it looks like we are creating an array of functions.

So maybe this:

[buttons [3] (SW_Push)]

which expands to exactly this:

(for/list ([i (in-range 3)])
    (SW_Push))

This code still has some semantic misleading problems: users might think we are creating ONE instance at (SW_Push), and think that buttons is a list of 3 references to the same object.

Thus we might want to consider this as well, it doesn't introduce new syntax, just a make-list-f function:

[buttons (make-list-f 3 SW_Push)]

where make-list-f returns a list by calling the function multiple times. This is similar to racket's make-list library, but that one only for values, we need to make function calls, thus the -f postfix.

lihebi commented 3 years ago
  1. Syntax for list comprehension [PART 3/3]

    Also for connections, the syntax is better if simplified. Can this

    #:connect
         (for*/list ([i (in-range 4)]
                     [j (in-range 5)])
           (*- self.col[j]
               (list-ref (list-ref matrix i) j)
               self.row[i])
         )

    become somethings like the below?

    #:connect
    (    (*- self.col[j] self.row[i])  
               (for i in [0:4] )
               (for j in [0:5])
    )

I view this as the prefix vs. postfix list comprehension. I think it's OK, and we don't need to change syntax and semantic of racket. A postfix list comprehension actually looks weird in the context of the racket code.

The verbose part is (list-ref (list-ref matrix i) j) which is due to racket's lack of array indexing syntax. The common way for high-dimensional data is to use (list-ref* matrix i j). I prefer to use this for now.

One final note: by implementing the array indexing and postfix dot annotation, I feel we are deviating a lot from racket's syntax. Racket is powerful in making syntax abstractions, but only inside lisp's syntax family (which is basically structural parenthesis). In the long run, we would probably better write a separate parser (reader) instead of using racket's default. I'll look into that possibility. Previously I've checked it, and found the brackets and dot syntax are written in low-level C code.

forrestbao commented 3 years ago

Taken. Based on your analysis, let's use these:

* pins

* parts

* wire

* layout/place TBD

A minor comment, let's use singular forms for the keywords, so pin and part, to be consistent with the convention of VHDL and Verilog.

lihebi commented 3 years ago

use singular forms for the keywords, so pin and part

Sounds good

forrestbao commented 3 years ago
  • Another issue is shall we treat the pins as arguments of a circuit -- to be functional programming, and Verilog does the same.

Do you mean this?

(function (delay-push-fixed btn-name [btn #f])
  (circuit
     (if btn
         (pins VCC GND Ctrl)
         (pins VCC GND Ctrl btn-name))
     (parts ...)
     (wire ...)
     (layout ...))

This could be interesting. This seems to be useful in the context of inheritance #6?

No, i mean moving current pin section to a list of arguments of a circuit definition. So when reusing the small circuit in a big circuit, we just need to call the small circuit with variables defined in the big circuit. For example, say we have an RC circuit defined as:

(circuit RC Vin Vout GND)  ;  Vin, Vout, and GND are two pins of the circuit RC. Now in the form of two arguments of the function RC. 

Now we want to build a RC circuit connected with a pushbutton in series:

(circuit RC_w_btn VCC GND OUT
    (part  (sw1 pushbutton) ;  sw1 is an instance of the pushbutton
             (rc1   (RC  sw1.2 self.OUT self.GND) ) ; Hook the 2nd pin of sw1 to RC's Vin, self.OUT to RC's Vout, and self.GND to RC's GND 
    ) ; end part 
    ...
)

In this way, every circuit is indeed a function. And we can eliminate the pin and wire sections. It will save a lot of typing. Most programmers should be familiar with this, as long as they treat every circuit as a function. At the end of the day, a function call is about hooking variables in current scope to arguments of the function under calling.

forrestbao commented 3 years ago

Regarding layout keywords:

Of course we can postpone the syntax needing constraint solving for later. I just wanna replace Pict keywords like vc-append or rc-superimpose to those more intuitive in PCB design context. Your new keywords left, over, angle, etc. sound much better and good for now.

Answers to your proposal:

To sum up, here are my proposals (with question marks for discussion):

* `left` and `above`, with `#:align` keyword with options "left", "right", "top", "bottom", "center" (the default).

We may be able to combine alignment and orientation. How about just six key words left, right and middle for vertical layout and top, bottom and center for horizontal layouts?

({left, right, middle/default} {distance} obj1 obj2 ... )
({top, bottom, center/default} {distance} obj1 obj2 ...)

Distance is optional; default is 0. Supporting distances doesn't need a constraint support. E.g., just create a dummy block whose width or height is the distance. For example, (left Rh Rv 10); horizontal spacing between Rh and Rv is 10mm, in default/center alignment can be implemented as

    (define block ((rectangle 1 10) ) ; create a dummy block of width 10
    (define Rh_and_block (hc-append Rh block)) ; combine Rh with the dummy block first
    (hc-append Rh_and_block Rv)  ; finally Rh + dummy bock + Rv

This can well support aligning parts with edges or corners of a canvas as well.

  * We probably don't need `right` and `below`?
  * `left` might not be the good choice, as it corresponds to `top`, not `above`. And we have "left" inside the alignment options. Maybe `besides`?

* Use `angle` instead of `rotate`, e.g. `(angle item 90)`

Sounds good. But can angle be binary, e.g., (angle item1 item2 90)?

* Use `over` in two ways:

  * with both `#:align` and `#:orient` keywords for specifying alignment and orientation.
  * with coordinate (x,y) for absolute positioning, e.g. `(over base x y item)`

I like the second choice as I don't like too many keywords especially when they start with #. I'd assume base is a circuit instance and item is another? And, it's the absolute position between the centers of the two circuit instances?

I'd prefer offset over over because of the ambiguity of the word "over". It could also mean "on top of" which maybe confused with vertical alignments above.

forrestbao commented 3 years ago

Regarding the for-loops and lists:

After studying the for-loop syntax of Racket, I feel it's not too bad. Just too many brackets, and most programmers are familiar with prefix expressions.

Is the make-list-f you mentioned the same as make-list? if so, i am very happy with the solution below:

[buttons (make-list 3 SW_Push)]

just tell people make-list's meaning.

Minor: what's the difference between make-list and build-list?

In terms of wires,

#:connect
      (for*/list ([i (in-range 4)]
                  [j (in-range 5)])
        (*- self.col[j]
            (list-ref (list-ref matrix i) j)
            self.row[i])
      )

I found out that we can use shorthand [i 4] for [i (in-range 4)] that will reduce a lot of brackets. (See section 11.1 of this)

If we already have dot notation (e.g., self.row[i]), can we change (list-ref (list-ref matrix i) j) to something like matrix[i][j]?

Can we combine for*/list and for/list to one keyword? The syntax for the two seem to be the same.

Then we can re-write the code above into:

(repeat ([i 4]  [j 5])
            (*- self.col[j]   matrix[i][j] self.row[i])
)
lihebi commented 3 years ago

every circuit is indeed a function

(circuit RC_w_btn VCC GND OUT
    (part  (sw1 pushbutton) ;  sw1 is an instance of the pushbutton
             (rc1   (RC  sw1.2 self.OUT self.GND) ) ; Hook the 2nd pin of sw1 to RC's Vin, self.OUT to RC's Vout, and self.GND to RC's GND 
    ) ; end part 
    ...
)

Gotcha, I like this, this is very useful. Essentially a call with the circuit (RC sw1.2 self.OUT self.GND) establishes connections in addition to creating an instance of the circuit. Let me think about how to implement this.

lihebi commented 3 years ago

We may be able to combine alignment and orientation. How about just six key words left, right and middle for vertical layout and top, bottom and center for horizontal layouts?

But should left (of) be horizontal layout?

lihebi commented 3 years ago

Minor: what's the difference between make-list and build-list?

[buttons (make-list 3 SW_Push)]

SW_Push is a function. Thus (make-list 3 SW_Push) could not work, because it will be a list of 3 functions, not a list of three objects. We need to call the function SW_Push three times, thus the (make-list-f 3 SW_Push) (not in racket library). The (build-list 3 SW_Push) seems to be very similar to make-list-f, but it applies the function with an argument (SW_Push 0/1/2).

lihebi commented 3 years ago

Can we combine for*/list and for/list to one keyword? The syntax for the two seem to be the same.

I prefer not. The semantic is different, and this is quite standard in racket.

The for* will nest the loop variables, e.g.

(for* ([i 4]
       [j 5]) ...)

will iterate 4*5 times, and is equivalent to

(for ([i 4])
  (for ([j 5])
    ...))

In contrast, the for loop will iterate loop variable at the same time, e.g.

(for ([i 4]
      [j 5]) ..)

would iterate 4 times until the end of i (or whichever list is shorter).

lihebi commented 3 years ago

If we already have dot notation (e.g., self.row[i]), can we change (list-ref (list-ref matrix i) j) to something like matrix[i][j]?

This is a little challenging, let me think about how to implement this.

forrestbao commented 3 years ago

We may be able to combine alignment and orientation. How about just six key words left, right and middle for vertical layout and top, bottom and center for horizontal layouts?

But should left (of) be horizontal layout?

I am thinking implicitly implying that any horizontal layout goes from left to right, any vertical layout goes from top to bottom. The new six keywords are just for alignments. E.g., (left [R2 R1 R3]) means R2 on top, R1 in the middle, and R3 at the bottom, and the 3 resistors are left-aligned.

forrestbao commented 3 years ago

SW_Push is a function. Thus (make-list 3 SW_Push) could not work, because it will be a list of 3 functions, not a list of three objects. We need to call the function SW_Push three times, thus the (make-list-f 3 SW_Push) (not in racket library). The (build-list 3 SW_Push) seems to be very similar to make-list-f, but it applies the function with an argument (SW_Push 0/1/2).

Can we give make-list-f a more intuitive name? Like copy-circuit or circuit-array?

forrestbao commented 3 years ago

If we already have dot notation (e.g., self.row[i]), can we change (list-ref (list-ref matrix i) j) to something like matrix[i][j]?

This is a little challenging, let me think about how to implement this.

I'd be happier with a different keyword than list-ref such as index or element, which I'd argue more intuitive. At the end of the day, accessing an element of an array is like a function giving the array name and the index.

forrestbao commented 3 years ago

Can we combine for*/list and for/list to one keyword? The syntax for the two seem to be the same.

I prefer not. The semantic is different, and this is quite standard in racket.

I understand it's standard in Racket. But to BHDL users, it's complexity. I object the distinction between single-layer loops and nested loops in Racket. I'd argue that although it makes the compiler's life easier and increases the explicity, it introduces complexity with little benefit to the users.

But for now, I am okay with using Racket's syntax. It's not too bad to users. Just tell them the Racket way. We have time to think.

lihebi commented 3 years ago

I'd be happier with a different keyword than list-ref such as index or element

This is very simple to do.

I still think matrix[i][j] should be the way to go. I'll think about how to implement this, we need a proper parser somewhere in racket's macro expansion.

forrestbao commented 3 years ago

Gotcha, I like this, this is very useful. Essentially a call with the circuit (RC sw1.2 self.OUT self.GND) establishes connections in addition to creating an instance of the circuit. Let me think about how to implement this.

Thanks for your consensus on this. Do you wanna use this in the paper?

I really want this feature because it shows the convenience of BHDL''s core idea: every circuit is a function. Verilog and VHDL do almost exactly the same in instantiation, although both of them use a special syntax like our part, instead of function arguments, to define pins of a module (Verilog) or a component(VHDL) definitions.

PS: VHDL has a more explicit syntax for instantiation, e.g.,

esp01_A: esp01 port map (vcc => VCC, gnd => GND, gpio_1 => button_1, gpio_2 => email_LED, gpio_1 => battery_charging_LED);
esp01_B: esp01 port map (vcc => VCC, gnd => GND, gpio_1 => button_2, gpio_2 => buzzer_in, gpio_1 => vibrator_control);

where esp01_A and esp01_B are two instances of esp01, and the signals before => are pins of esp01 whereas those after are signals of the outer circuit, which contains buttons, LEDs, a buzzer and a vibrator.

lihebi commented 3 years ago

I understand it's standard in Racket. But to BHDL users, it's complexity.

I agree, and I also disagree with many racket's decisions.

However, one of the key advantage of BHDL is that it is a DSL inside host language, allowing us to introduce minimal new syntax, only the circuit and the connection syntax. It's very friendly for users to learn (though they need to learn the host language). If we turn out to change much of the host language's semantic, we are losing that advantage.

Also, the for loop is really NOT a BHDL syntax, it's whatever loop constructs the host language provides. BHDL can technically be embedded in another language.

lihebi commented 3 years ago

Do you wanna use this in the paper?

Yes, I'll add a paragraph and possibly an example to talk about this in the paper. I'll implement this along the way, it's not hard.

lihebi commented 3 years ago

it shows the convenience of BHDL''s core idea: every circuit is a function

So I agree with the idea. But the implementation will deviate a little. The BHDL circuit is an object of type "Circuit". In racket, an object cannot be used as a function. That's why when we want to reuse a circuit, we always define a function to wrap around the circuit, e.g.

(function (RC r c)
  (circuit (part ..) (pin ..) ...))

So in this example, we cannot just write:

(define the-rc-module (circuit (pin A B C) ...))
(the-rc-module sw1.2 self.OUT self.GND)

Instead we could write:

(SOME-MAGIC-KEYWORD the-rc-module sw1.2 self.OUT self.GND)

which expands to:

(circuit (pin ..) (part ..)
   (wire (BUS (the-rc-module [A B C])
                     ([sw1.2 self.OUT self.GND]))))

I have an idea if we really want to avoid the SOME-MAGIC-KEYWORD and use the-rc-module as the function. The trick is to make (circuit ...) returns not just a Circuit object, but a function that:

forrestbao commented 3 years ago

it shows the convenience of BHDL''s core idea: every circuit is a function

So I agree with the idea. But the implementation will deviate a little.

Sounds good. I will let you decide how to implement.

I think we should still have a pin section. It increases the readability, and aligns well with VHDL and Verilog's practices. They just "call" the component or module in the instantiation stage. We should only replace part and wire with the new function call syntax.