Open lihebi opened 3 years ago
Thanks for the detailed suggestions. I'm convinced by most of them. Let me reply each inlnie.
- Definitions to circuits and functions using
function
andcircuit
Taken.
- change
#:parts
directives to lisp structure(parts ...)
Taken.
- 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)))
- 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?
- 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:
(append 20 a b c)
for horizontal or vertical appending(superimpose a b c)
for overlapping placement(pin-over base x y item)
for overlapping placement with an offset coordinate (x,y)(rotate a 3.14)
: rotate a certain degreeFor append
and superimpose
, there are some prefixes declaring the orientation and offset.
v
, h
: vertical, horizontall
, r
, t
, b
, c
: left, right, top, bottom, centerIt 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)
)
Here are my comments:
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)
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.
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):
left
and above
, with #:align
keyword with options "left", "right", "top", "bottom", "center" (the default).
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
?angle
instead of rotate
, e.g. (angle item 90)
over
in two ways:
#:align
and #:orient
keywords for specifying alignment and orientation.(over base x y item)
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.
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.
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.
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.
use singular forms for the keywords, so
pin
andpart
Sounds good
- 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.
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.
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])
)
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.
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?
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)
.
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).
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.
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.
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 functionSW_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 tomake-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
?
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.
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.
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.
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.
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.
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.
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:
(the-rc-module sw1.2 self.OUT self.GND)
, it calls the function and bind the connection.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.
This thread is about keywords and "key-syntax".
Here is my philosophy:
Hence, I propose the following changes:
Definitions to circuits and functions
Currently functions are defined as in Racket, and circuits are:
I suggest to use two keywords
function
(or some sort of abbreviations) andcircuit
(or something similar) to separate the two kinds of definitions.Reasons:
function
is used in VHDL, SPICE, and Verilog, well-known to EE users.circuit
is calledSUBCKT
(subcircuit) in SPICE,module
in Verilog, andentity
in VHDL.#
directives Can we turn blocks starting with#:vars
,#:parts
, etc. into a "typical" LISP block? LikeOr the VHDL way (from here):
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.Also, for these # directives, we have several candidate names:
a. For pins:
external-pins
,ports
,pins
,IOs
ports
nodes
, see SUBCKTinput
andoutput
.external-pins
is too long,ports
is good -- because VHDL does so, but if we useparts
below,ports
looks too similar,pins
is good too but it doesn't reflect the I/O.b. For parts:
variables
,vars
,parts
,components
variables
orvars
may not be good as they literally mean variables in VHDL and Verilog.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
port map
(but I am not sure whether it covers all connections), Verilog calls aswire
s.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
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.Keywords for different connection types
To be discussed in https://github.com/lihebi/bhdl/issues/8
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.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:
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:
as we are already doing similarly for buses.
Also for connections, the syntax is better if simplified. Can this
become somethings like the below?
Kinda like Python's list comprehension.