Eobj is a Perl programming environment. Well, technically speaking, it’s a Perl module. Eobj stands for Easy Objects.
Idea: Object oriented programming is supported very well in Perl, but it requires some rather advanced programming techniques: Perl modules and namespaces, references, blessings and some syntactic sugars. In addition, the awareness of some special variables is required.
Eobj is intended to bring object oriented programming to the layman Perl programmer’s doorstep. “Object oriented for dummies”, if you like. It supports an easy way to create classes, and a comfortable to handle object’s properties (a.k.a. member variables). The code looks clean, and is easy to understand, even if you’re not so good in Perl.
Most of the criticism I’ve gotten regarding Eobj is that I’ve done things differently from Java and C++. That only makes me feel better. Others claim that Eobj is not needed. Those who say that usually know more than ten ways to access a referenced hash. Most Perl programmers don’t want to know what a reference is. And I understand them.
For someone who understands the basic principles of object oriented programming and “normal” Perl script programming, it should take no longer than 15 minutes before being able to get started. There are a few quite simple rules to follow, and off you are.
Almost all features of “classic” Perl OO programming is supported. The main cost of using Eobj is some overhead. But if you’re worried about memory and speed, why are you using Perl?
Eobj is derived from the Perlilog project. In fact, Perlilog’s object environment is very similar to Eobj’s, there are minor differences. Eobj is intended as a general-purpose environment, while Perlilog’s object environment is adapted to its certain purpose.
Eobj is released on CPAN, and can be downloaded from any of its mirrors. In particular, it’s available at http://search.cpan.org/~billauer/ (you may browse the manual page at http://search.cpan.org/~billauer/Eobj-0.23/Eobj.pm)
Somewhere in 2006 TI (Texas Instruments) released a clock synthesizer, CDCE906, which is pretty much everything one can look for in a system clock generator: Several (up to 6) clock outputs derived from a single reference clock or crystal with excellent multiplication and division capabilities, not to mention low jitter, slew rate control, spread spectrum and more. On top of all, it’s surprisingly low-priced. So it’s a chip you want to consider, even if all you (think you) need for now, is one single system clock with a fixed frequency. It will save you, when you’ll discover three weeks before release that you actually need another clock with a weird frequency. Believe me.
Except for the non-E devices, (that is, CDC906 and CDC706, which TI seems to neglect) the devices’ setting can be preprogrammed into an on-chip EEPROM, before or after the chip is put on the PCB board. If this is enough for you, frankly, you have nothing to look for on this page. On the other hand, if you want to set the current configuration regardless of the EEPROM, it’s possible over the SMBus.
Now, SMBus is more or less like I2C (also known as IIC), so if you have a controller on the board with I2C/SMBus cababilities, you may want to use it (if it has I2C, I suggest knowing the differences). If there is no controller around, or you prefer the FPGA to set up the synthesizer, the Verilog code below is what you need.
It should be pointed out, that TI also have another very similar device, CDCE706, which is surprisingly enough more capable than its “little brother” CDCE906. Their register structure is identical however, so the code below can be used for CDCE706 as well.
The only downside about both devices is that being very versatile, they’re also complicated. I’m not going to describe how to run these chips. There is no way out of reading the extensive datasheet, and I promise finding some hidden treasures there. The only thing this module will do for you, is get the right values to the right register.
So let’s get to business. Before doing anything else, be sure to set the parameter “clk_freq” to the frequency of the module’s input clock “clk” (in MHz, nearest integer). This is crucial, because the SMBus is derived from this clock. If several frequencies are possible, make “clk_freq” the highest one.
Important notes:
- Pullup resistors for sclk and sdata are mandatory. Nothing will work without them. When tied to a 3.3V supply, they should be between 10 kOhm and 33 kOhm.
- This module acts as an SMBus master. It must therefore not share the sclk and sdata lines with another master, unless it’s assured that they won’t attempt to take control at the same time. The simplest solution is to make one-on-one connections of sclk and sdata between the chip and the FPGA (don’t forget the pullups!).
- Verify that the FPGA outputs are indeed 3.3V (this is usually something you should set). Modern FPGA’s take a lower voltage by default.
Now the basic I/O’s:
clk |
Input clock, which drives the module’s logic and from which the SMBus clock is derived. Preferably with a known and stable frequency. The module is sensitive to this clock’s rising edge only. |
reset |
Asynchronous reset, active high (input). May be tied to ’0′ if the state variable “state” can be assured to wake up as “st_idle” (don’t trust the syntheizer to do this implicitly) |
send_data |
Input, synchronous with “clk”: Start send data to chip command. Set to ’1′ to update all registers of the chip. This line should be ’1′ for at least one “clk” cycle, but not more than the time it takes for the full write to complete. |
busy |
Output, synchronous with “clk”: This line is high as long as the register data is being written to the chip over the SMBus. |
error |
Output, synchronous with “clk”: This line goes high if an acknowledgement from the chip is missing, which indicates some kind of error. The line remains high until the next initiation with “send_data”. |
sclk |
Output: To be directly connected to the chip’s pin #10 (SCLOCK). This pin toggles between ’0′ and high-Z. Do verify that your development tools indeed create a high-Z condition on the output when the ‘z’ value is assigned to this line (Xilinx FPGA tools are OK with this). Don’t forget the pullup resistor |
sdata |
Output: To be directly connected to the chip’s pin #9 (SDATA). This pin toggles between ’0′ and high-Z. Do verify that your development tools indeed create a high-Z condition on the output when the ‘z’ value is assigned to this line. Don’t forget the pullup resistor |
S0 |
Output: To be directly connected to the chip’s pin #1 (S0/A0/CLK_SEL). Will be tied to constant ’1′ (see datasheet for meaning). |
S1 |
Output: To be directly connected to the chip’s pin #2 (S1/A1). Will be tied to constant ’1′ (see datasheet for meaning). |
Next we have the actual synthesizer parameters, whose meaning should be obvious to whoever has read the datasheet well. The only thing note is that the term “PLL” has been exchanged for “VCO”, which I find more intuitive. So VCO1_M and VCO1_N are the M and N for PLL1, using the datasheet’s terminology, etc. The comments in the module itself may help as well.
All these inputs can be constants (which will shrink the synthesized logic) or you can wire them to whatever you like, but these inputs must be steady as long as their content is flushed to the chip ( = as long as the “busy” output is high).
So all we have left is the module itself. I’ve lifted the copyright restrictions for it, so you can use it in any way you like.
(And by the way: I have no special relations with TI whatsoever. Not even as a freelancer).
———————————–
module cdce906
(
clk, reset, send_data,
busy, error,
sclk, sdata, S0, S1,
VCO1_M, VCO1_N, VCO1_bypass, VCO1_hi_freq_range,
VCO2_M, VCO2_N, VCO2_bypass, VCO2_hi_freq_range,
VCO3_M, VCO3_N, VCO3_bypass, VCO3_hi_freq_range,
P0_VCO_select, P0_div, P1_VCO_select, P1_div,
P2_VCO_select, P2_div, P3_VCO_select, P3_div,
P4_VCO_select, P4_div, P5_VCO_select, P5_div,
Y0_en, Y0_inv, Y0_slew_rate, Y0_div_select,
Y1_en, Y1_inv, Y1_slew_rate, Y1_div_select,
Y2_en, Y2_inv, Y2_slew_rate, Y2_div_select,
Y3_en, Y3_inv, Y3_slew_rate, Y3_div_select,
Y4_en, Y4_inv, Y4_slew_rate, Y4_div_select,
Y5_en, Y5_inv, Y5_slew_rate, Y5_div_select,
SSC_mod_select, SSC_freq_select
);
input clk;
input reset;
output sclk;
inout sdata;
output S0, S1;
output busy, error;
input send_data;
input [8:0] VCO1_M;
input [11:0] VCO1_N;
input VCO1_bypass;
input VCO1_hi_freq_range;
input [8:0] VCO2_M;
input [11:0] VCO2_N;
input VCO2_bypass;
input VCO2_hi_freq_range;
input [8:0] VCO3_M;
input [11:0] VCO3_N;
input VCO3_bypass;
input VCO3_hi_freq_range;
input [2:0] P0_VCO_select;
input [6:0] P0_div;
input [2:0] P1_VCO_select;
input [6:0] P1_div;
input [2:0] P2_VCO_select;
input [6:0] P2_div;
input [2:0] P3_VCO_select;
input [6:0] P3_div;
input [2:0] P4_VCO_select;
input [6:0] P4_div;
input [2:0] P5_VCO_select;
input [6:0] P5_div;
input Y0_en;
input Y0_inv;
input [1:0] Y0_slew_rate;
input [2:0] Y0_div_select;
input Y1_en;
input Y1_inv;
input [1:0] Y1_slew_rate;
input [2:0] Y1_div_select;
input Y2_en;
input Y2_inv;
input [1:0] Y2_slew_rate;
input [2:0] Y2_div_select;
input Y3_en;
input Y3_inv;
input [1:0] Y3_slew_rate;
input [2:0] Y3_div_select;
input Y4_en;
input Y4_inv;
input [1:0] Y4_slew_rate;
input [2:0] Y4_div_select;
input Y5_en;
input Y5_inv;
input [1:0] Y5_slew_rate;
input [2:0] Y5_div_select;
input [2:0] SSC_mod_select;
input [3:0] SSC_freq_select;
reg SMBus_en, pre_en;
reg kickoff;
reg [2:0] state;
reg get_bit;
reg next_bit, this_is_ack, wait_for_ack;
reg sdata_d;
reg error;
reg [4:0] word_addr;
reg [3:0] bit_pos;
reg no_more_bits;
reg [7:0] SMBus_word;
reg [11:0] div_counter;
reg sclk_logic, sdata_logic;
parameter clk_freq = 10;
parameter st_idle = 0,
st_start0 = 1,
st_start1 = 2,
st_bit0 = 3,
st_bit1 = 4,
st_bit2 = 5,
st_stop0 = 6,
st_stop1 = 7;
assign sclk = sclk_logic ? 1'bz : 1'b0 ;
assign sdata = sdata_logic ? 1'bz : 1'b0 ;
assign S0 = 1;
assign S1 = 1;
always @(posedge clk)
begin
SMBus_en <= pre_en;
sdata_d <= sdata;
if (div_counter == ((clk_freq * 10) - 1))
begin
div_counter <= 0;
pre_en <= 1;
end
else
begin
div_counter <= div_counter + 1;
pre_en <= 0;
end
end
always @(posedge clk or posedge reset)
if (reset)
kickoff <= 0;
else if (send_data)
kickoff <= 1;
else if (SMBus_en)
kickoff <= 0;
assign busy = kickoff || (state != st_idle);
always @(posedge clk or posedge reset)
if (reset)
begin
state <= st_idle;
error <= 0;
end
else if (SMBus_en)
case (state)
st_idle: begin
sclk_logic <= 1;
sdata_logic <= 1;
get_bit <= 0;
if (kickoff)
state <= st_start0;
end
st_start0: begin
sclk_logic <= 1;
sdata_logic <= 0;
state <= st_start1;
error <= 0;
end
st_start1: begin
sclk_logic <= 0;
state <= st_bit0;
end
st_bit0: begin
sclk_logic <= 0;
sdata_logic <= next_bit;
wait_for_ack <= this_is_ack;
get_bit <= 1;
state <= st_bit1;
end
st_bit1: begin
sclk_logic <= 1;
get_bit <= 0;
state <= st_bit2;
end
st_bit2: begin
sclk_logic <= 0;
if (wait_for_ack && !sdata_d && !kickoff)
state <= st_bit2;
else if (no_more_bits)
state <= st_stop0;
else
state <= st_bit0;
if (wait_for_ack && sdata_d && sclk_logic)
error <= 1;
end
st_stop0: begin
sclk_logic <= 0;
sdata_logic <= 0;
state <= st_stop1;
end
st_stop1: begin
sclk_logic <= 1;
state <= st_idle;
end
endcase
always @(posedge clk)
if (SMBus_en)
begin
if (kickoff)
begin
word_addr <= 0;
bit_pos <= 7;
no_more_bits <= 0;
end
else if (get_bit)
begin
bit_pos <= (bit_pos == 0) ? 8 : bit_pos - 1;
if (bit_pos == 0)
word_addr <= word_addr + 1;
if ((word_addr == 29) &&
(bit_pos == 8))
no_more_bits <= 1;
end
if (bit_pos == 8)
begin
next_bit <= 1;
this_is_ack <= 1;
end
else
begin
next_bit <= SMBus_word[ bit_pos[2:0] ];
this_is_ack <= 0;
end
end
always @(posedge clk)
if (SMBus_en)
case (word_addr)
0: SMBus_word <= 8'b1101001_0 ;
1: SMBus_word <= 0 ;
2: SMBus_word <= 26 ;
3: SMBus_word <= 0 ;
4: SMBus_word <= VCO1_M[7:0] ;
5: SMBus_word <= VCO1_N[7:0] ;
6: SMBus_word <= { VCO1_bypass, VCO2_bypass, VCO3_bypass, VCO1_N[11:8], VCO1_M[8] } ;
7: SMBus_word <= VCO2_M[7:0] ;
8: SMBus_word <= VCO2_N[7:0] ;
9: SMBus_word <= { VCO1_hi_freq_range, VCO2_hi_freq_range,
VCO3_hi_freq_range, VCO2_N[11:8], VCO2_M[8] } ;
10: SMBus_word <= VCO3_M[7:0] ;
11: SMBus_word <= VCO3_N[7:0] ;
12: SMBus_word <= { P0_VCO_select, VCO3_N[11:8], VCO3_M[8] } ;
13: SMBus_word <= { P1_VCO_select, 1'b0, 2'b00, 2'b00 } ;
14: SMBus_word <= { 2'b00, P3_VCO_select, P2_VCO_select } ;
15: SMBus_word <= { 2'b00, P5_VCO_select, P4_VCO_select } ;
16: SMBus_word <= { 1'b0, P0_div } ;
17: SMBus_word <= { 1'b0, P1_div } ;
18: SMBus_word <= { 1'b0, P2_div } ;
19: SMBus_word <= { 1'b0, P3_div } ;
20: SMBus_word <= { 1'b0, P4_div } ;
21: SMBus_word <= { 1'b0, P5_div } ;
22: SMBus_word <= { 1'b0, Y0_inv, Y0_slew_rate, Y0_en, Y0_div_select } ;
23: SMBus_word <= { 1'b0, Y1_inv, Y1_slew_rate, Y1_en, Y1_div_select } ;
24: SMBus_word <= { 1'b0, Y2_inv, Y2_slew_rate, Y2_en, Y2_div_select } ;
25: SMBus_word <= { 1'b0, Y3_inv, Y3_slew_rate, Y3_en, Y3_div_select } ;
26: SMBus_word <= { 1'b0, Y4_inv, Y4_slew_rate, Y4_en, Y4_div_select } ;
27: SMBus_word <= { 1'b0, Y5_inv, Y5_slew_rate, Y5_en, Y5_div_select } ;
28: SMBus_word <= { 1'b0, SSC_mod_select, SSC_freq_select } ;
default: SMBus_word <= 0 ;
endcase
endmodule
Perlilog is a command-line tool which generates Verilog modules from a set of files, which come in several other formats. It was originally designed to integrate Verilog IP cores. It’s released as free software (GPL).
Motivation: Anyone who has needed to integrate a few Verilog modules into something working, knows it’s a headache. It’s not only a mechanic task, but also a source of bugs. Since Verilog modules’ interface is basically wires, the integration task requires an accurate understanding of what each wire stands for, and how it’s expected to behave. Having many details to keep track on, it’s common that undesidered
Perlilog’s main concept is that handling IP cores (and Verilog modules in general) should be intuitive. We think about IP cores as black boxes, so we should also relate to them as black boxes when connecting them. When connecting cores, we imagine that as drawing a thick line between them. So should the connection be done, when actually integrating the system.
Perlilog introduces an alternative concepts for connecting between cores. The main issues are:
- Rather than connecting wires (Verilog inputs and outputs), connect ports. A port is a loosely defined object, which holds one simple rule: Connecting port A with port B should mean what one would normally expect from drawing a line between two blocks in a schematic block diagram. Surprisingly enough, it’s almost always possible to properly define this meaning.
- Cores should be black boxes, and they must be easy to configure. The meaning of each attribute should be obvious (as opposed to editing define files)
Perlilog is also useful for much simpler tasks, such as massive connection of Verilog module’s inputs and outputs to pads, and other tasks that are readily performed with Perl scripts. It’s a good starting point for writing a Perl script which handles Verilog code, even for the simpler tasks (and hence the project’s name, as a “mixture” between Perl and Verilog).
Most Linux distributions have Perlilog packaged, so the easiest way to obtain it is with yum, apt-get, or whatever applies. Or you may download the project and its guide or directly from this site: Perlilog-1.0.tar.gz and perlilog-guide-1.0.pdf.