6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun May 05, 2024 9:34 am

All times are UTC




Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: Tue May 13, 2014 6:12 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
So, although 65SPI is available as ABEL, the project on the web site won't load into ISE 14, and I thought I'd try my hand at re-implementing it as Verilog. (Yep, tried an ABEL -> verilog converter, and the results were horrid).

I figured it'd be something I could check operation by running it alongside the original jed file, and I'd get Verilog practice.

Most of it I have figured out, but I think my incomplete understanding of Verilog is tripping me up, and the tutorials don't seem to make sense in this area.

My first issue is registers in secondary modules. I have this:

Code:
module t_register(clock, reset, enable, d, q, trigger);

parameter WIDTH = 8 ;
parameter RESET = 0 ;

input clock;
input reset;
input enable;
inout [WIDTH-1:0] d;
output [WIDTH-1:0] q;
output trigger;
reg [WIDTH-1:0] q;
reg trigger;

always @ (negedge clock, posedge reset)
  begin
  if(reset)
      q <= RESET;
  else if(enable)
    begin
      q <= d;
      trigger <= 1;
    end
  end
endmodule

Looks and works fine. But, if in the top-level module I do:

t_register #(.WIDTH(8)) spi_data_reg(clock, reset_in, spi_data_reg_ce, data, spi_data, BUSY);

always (...)
BUSY <= 0; //. we are done with the 8 bit transfer...

I get an error that it's an illegal lvalue. Duh, the t_register needs trigger output defined as reg, incoming var needs to be wire, but I need to deal with it in two places. I worked around the issue by doing:

wire jim;
reg BUSY;

t_register #(.WIDTH(8)) spi_data_reg(clock, reset_in, spi_data_reg_ce, data, spi_data, jim);

always (posedge jim)
BUSY <= 1;

always (...)
BUSY <= 0; //. we are done with the 8 bit transfer...

but that seems wrong, and a waste of a flop (assuming the code does not optimize it away). I figure this is an easy "Jim does not understand Verilog" thing, but I can't seem to find the relevant tutorial. I might be able to remove the issue by pulling the module definition into the top level part of my file, but that can't be a good plan long term (and, not sure it will work anyway).

The second issue is needing to change a var from two different always blocks:

Code:
always @ (posedge sck)
if(BUSY)
begin
  if((CPHA & !CPOL) | (!CPHA & CPOL))
    if(bits == 8)
    begin
      spi_data_in <= shift_reg;
      BUSY <= 0;
      TXC <= 1;
    end
    else
    begin
      shift_reg <= {shift_reg[6:0], miso_reg};
      bits <= bits + 1;
    end
  if((!CPHA & !CPOL) | (CPHA & CPOL))
  begin
    miso_reg <= miso; 
  end
end
 
always @ (negedge sck)
if(BUSY)
begin
  if((!CPHA & !CPOL) | (CPHA & CPOL))
    if(bits == 8)
    begin
      spi_data_in <= shift_reg;
      BUSY <= 0;
      TXC <= 1;
    end
    else
    begin
      shift_reg <= {shift_reg[6:0], miso_reg};
      bits <= bits + 1;
    end
  if((CPHA & !CPOL) | (!CPHA & CPOL))
  begin
    miso_reg <= miso; 
  end
end

miso_reg gets a complaint, as does the shift register. I think I can understand why (the synthesis tool wants the flops to be rising or falling clock, but not both). I assume I need to run everything against the master clock and then just put a bunch of if statements in the always blocks, but I thought I would check before I start adding in all the extra combinatorial logic... (I think I could create a schematic version of this that would not need all the combinatorial logic, so I thought it might be possible...)

Jim

Jim


Top
 Profile  
Reply with quote  
PostPosted: Tue May 13, 2014 12:31 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1683
Location: Sacramento, CA
Andre Fachat wrote a Verilog version of the 65SPI. Try contacting him to see if he can help.

_________________
Please visit my website -> https://sbc.rictor.org/


Top
 Profile  
Reply with quote  
PostPosted: Wed May 14, 2014 4:06 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
8BIT wrote:
Andre Fachat wrote a Verilog version of the 65SPI. Try contacting him to see if he can help.


His is a VHDL version, and it's been somewhat changed, but I did ask him for any help he can provide.

I went ahead and converted the sensitivity list to reference the rising edge of the spi clock, which fixes all of my issues save one.

Since the clock can be internal (Phi2) or external, the sensitivity block uses intclk as the reference, and intclk is:

Code:
assign intclk          = (ECE ? ext_clock : clock);


BUt, now I have variables that generate "this signal is connected to multiple drivers" errors. It makes sense to me, since there are FFs that are set in the clock domain and then potentially set in the ext_clock domain. The problem is how best to solve them, and how did you deal with it in ABEL (looked at the code, but can't see how you handled it)

I *think* I need to build cross domain flags, but I need to create two way flags, and I am still pretty new to this.

I attached the code, since it is close to finished (for internal clock only operation

Jim


Attachments:
File comment: Almost compilable SPI65 in Verilog...
spi65.v.txt [4.57 KiB]
Downloaded 105 times
Top
 Profile  
Reply with quote  
PostPosted: Wed May 14, 2014 12:36 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1683
Location: Sacramento, CA
brain wrote:
...The problem is how best to solve them, and how did you deal with it in ABEL (looked at the code, but can't see how you handled it)


I do not know Verilog and cannot help with clock domains, but here is my description of what is going on inside my code.

Here is my clock logic:
Code:
// CK clock divider
  CKDV = (CKX == DIVX);
  CKSEL = (PHI2.pin & !ECE) # (EXTC.pin & ECE);

  CKX.clk = CKSEL;
  CKX.ar = !RES.pin # !BSY;
  when CKDV then  CKX := 0
            else  CKX := CKX + 1;

// ST state counter
   // state 0 is idle
   // states 1 thru 15 are shifing bits
   // state 16 is last bit shifted, IRQ set, BSY cleared, FIN set

  STX.clk = BSY &  DIVZ & CKDV # BSY & !DIVZ & CKSEL;
  STX.ar = !RES.pin # !BSY;
  STX := STX + 1;

  SCLK = st0 $ CPOL;


The selection from internal or external happens here:
Code:
  CKSEL = (PHI2.pin & !ECE) # (EXTC.pin & ECE);


That clock is used to drive the 5-bit state counter
Code:
  STX.clk = BSY &  DIVZ & CKDV # BSY & !DIVZ & CKSEL;


BSY gets set when you read or write the Data register. DIVZ is high when the clock divider register is not zero. CKDV is set when the clock divider counter reaches the value in the clock divider register.

Daryl

_________________
Please visit my website -> https://sbc.rictor.org/


Top
 Profile  
Reply with quote  
PostPosted: Thu May 15, 2014 12:08 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
8BIT wrote:
Here is my clock logic:
Code:
// CK clock divider
  CKDV = (CKX == DIVX);
  CKSEL = (PHI2.pin & !ECE) # (EXTC.pin & ECE);

  CKX.clk = CKSEL;
  CKX.ar = !RES.pin # !BSY;
  when CKDV then  CKX := 0
            else  CKX := CKX + 1;

// ST state counter
   // state 0 is idle
   // states 1 thru 15 are shifing bits
   // state 16 is last bit shifted, IRQ set, BSY cleared, FIN set

  STX.clk = BSY &  DIVZ & CKDV # BSY & !DIVZ & CKSEL;
  STX.ar = !RES.pin # !BSY;
  STX := STX + 1;

  SCLK = st0 $ CPOL;


Yep, saw that, and I have the same 5 bit counter in use (bits)
Quote:
The selection from internal or external happens here:
Code:
  CKSEL = (PHI2.pin & !ECE) # (EXTC.pin & ECE);


Verlig equivalent:
Code:
assign intclk          = (ECE ? ext_clock : clock);

Quote:
That clock is used to drive the 5-bit state counter
Code:
  STX.clk = BSY &  DIVZ & CKDV # BSY & !DIVZ & CKSEL;


As is mine. That part looks good to me, and my Verilog version of all of that works fine. No errors.
Quote:
BSY gets set when you read or write the Data register. DIVZ is high when the clock divider register is not zero. CKDV is set when the clock divider counter reaches the value in the clock divider register.

Daryl

BSY (BUSY in my codebase) is something to consider.

So, in the 65XX clock domain, the user writes to the data reg. You would then set BSY to 1.
But, BSY needs to be set to 0 when the state counter goes to 16. In my codebase, the set of BSY is done by the state machine, which is running in the CKSEL clock domain (called "intclk" in my code). If that domain is using the external clock, then there are two different clocks used to clock the information into the BSY FF. That is no doubt where the issue arises.

In your code, what code sets BSY to 0?

That would probably help my code.

Jim


Top
 Profile  
Reply with quote  
PostPosted: Thu May 15, 2014 4:33 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
I got mine to compile, but ISE wants to dump it into a 95288xl :-), so I thought I would check out your working code and see what I am misunderstanding.

To do that, I literally translated your code to Verilog, except for one area. You used latches for all of your registers, where I had used clocked flip flops. Was that intentional? I thought most registers in peripherals like the 6551 and 6522 were not latches, but actual clocked registers...

Anyway, the resulting file is attached, with all of your original ABEL source comments in blocks beside the Verilog. Right now, it does not compile, because the compiler (very rightly) notices that the output shift register can be clocked either by the shifting action or the load of a new value, so I have to fix that.

Edit: Missed this little piece of code, so my literal translation is incomplete:

Code:
   // ST state counter
   // state 0 is idle
   // states 1 thru 15 are shifing bits
   // state 16 is last bit shifted, IRQ set, BSY cleared, FIN set

  STX.clk = BSY &  DIVZ & CKDV # BSY & !DIVZ & CKSEL;
  STX.ar = !RES.pin # !BSY;
  STX := STX + 1;

clock is busy and counter !=0 and counter==divisor or busy and counter==0 and clock is high?

Jim


Attachments:
spi65.v.txt [8.04 KiB]
Downloaded 100 times


Last edited by brain on Thu May 15, 2014 6:04 am, edited 2 times in total.
Top
 Profile  
Reply with quote  
PostPosted: Thu May 15, 2014 4:55 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
Well, changed them to latches, and the same issue crops up, so there must be a special way to do it in Verilog that I am not seeing...


Top
 Profile  
Reply with quote  
PostPosted: Thu May 15, 2014 12:40 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1683
Location: Sacramento, CA
Here is the BSY logic:

Code:
// BSY reg operation

  BSY.d = 1;
  BSY.clk = !SOSEL & !SISEL;
  BSY.ar = st4 # !RES.pin;

So, the reading or writing of the data register sets BSY (SOSEL or SISEL).

And when bit4 of the state counter goes high (state = 16) or the RES Pin goes low, BSY is cleared.

Quote:
clock is busy and counter !=0 and counter==divisor or busy and counter==0 and clock is high?


Yes, the counter==0 test was needed to switch the source of the clock from the delay divider to the actual clock source. So, when you write 0 to the clock divider register, the counter will always == divisor and you would not get any shift clocks. When the divider register is set to 1, it counts from 0 to 1, effectively dividing the clock in two. Hope that makes sense.

Your equivalent might be:
assign STX.clk = BSY & (DIVZ ? CKDV : CKSEL);

(darn - you went and made me learn some Verilog - ;) )

Daryl

_________________
Please visit my website -> https://sbc.rictor.org/


Top
 Profile  
Reply with quote  
PostPosted: Fri May 16, 2014 5:12 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
I figured that would be the reason, after thinking about it.

I'm trying to decipher your shift register logic now, putting it back into Verilog in my original codebase.

Jim


Top
 Profile  
Reply with quote  
PostPosted: Fri May 16, 2014 12:24 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1683
Location: Sacramento, CA
I used two 8 bit registers - one for incoming data and one for outgoing data.

The outgoing register gets loaded using async set or clear. The D inputs are wired to the previous bits output. MOSI is connected to the bit 7 register (msb bit first shifting). The clock uses the lsb of the state counter. CPHA is used to select the rising edge or falling edge. I used the "any bit of the state counter high" part to ensure the first clock starts properly.

The incoming register has MISO connected to the lsb D input.. The D inputs are connected to the previous bit's output. The clock is simply the lsb of he state counter EOR'ed with CPHA.

cheers!

Daryl

_________________
Please visit my website -> https://sbc.rictor.org/


Top
 Profile  
Reply with quote  
PostPosted: Fri May 16, 2014 12:49 pm 
Offline
User avatar

Joined: Mon Apr 23, 2012 12:28 am
Posts: 760
Location: Huntsville, AL
Jim:

I have released a Verilog SPI Master interface on Github or Opencores.

You may find it useful to refer to it for some of the Verilog constructs you may be struggling with at the moment. I have attempted to target it to a CPLD, so I can't vouch for its applicability to your specific effort. However, it implements a complete master interface with a single clock domain. In the case of your project, I would say that clock would either be Phi2 or ~Phi2. I probably would select ~Phi2 if I were connecting to a 65(C)02 memory bus.

One caution though, I just reread the header on the source file, and there is room for improvement. Therefore, I suggest that you make use of the Verilog testbench provided if the header proves to be confusing or inaccurate. The testbench is also on Github.

_________________
Michael A.


Top
 Profile  
Reply with quote  
PostPosted: Sat May 17, 2014 5:15 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
MichaelM wrote:
Jim:

I have released a Verilog SPI Master interface on Github or Opencores.

You may find it useful to refer to it for some of the Verilog constructs you may be struggling with at the moment. I have attempted to target it to a CPLD, so I can't vouch for its applicability to your specific effort. However, it implements a complete master interface with a single clock domain. In the case of your project, I would say that clock would either be Phi2 or ~Phi2. I probably would select ~Phi2 if I were connecting to a 65(C)02 memory bus.

One caution though, I just reread the header on the source file, and there is room for improvement. Therefore, I suggest that you make use of the Verilog testbench provided if the header proves to be confusing or inaccurate. The testbench is also on Github.


Thanks for the codebase. I ran it on ISE with a CPLD target (just copied and pasted). It compiled to 40 MCs, 406 pterms, and 39 registers.

I was surprised your code made such heavy use of registers for things:

Quote:
always @(posedge Clk)
begin
if(Rst) begin
SCK_Inv <= #1 0;
SCK_Lvl <= #1 0;
end else if(~SS) begin
SCK_Inv <= #1 ^Mode; // Invert SCK if Mode == 1 or Mode == 2
SCK_Lvl <= #1 Mode[0]; // Set SCK idle level from LSB of Mode
end
end


For instance... I think your original not is probably read best as " I have *not* attempted to target..." Since FPGAs have significantly more FFs, this probably makes sense, capturing all signals at a specific time (when slave select goes low) and holding them...

Jim


Top
 Profile  
Reply with quote  
PostPosted: Sat May 17, 2014 5:28 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
8BIT wrote:
I used two 8 bit registers - one for incoming data and one for outgoing data.

The outgoing register gets loaded using async set or clear. The D inputs are wired to the previous bits output. MOSI is connected to the bit 7 register (msb bit first shifting). The clock uses the lsb of the state counter. CPHA is used to select the rising edge or falling edge. I used the "any bit of the state counter high" part to ensure the first clock starts properly.

The incoming register has MISO connected to the lsb D input.. The D inputs are connected to the previous bit's output. The clock is simply the lsb of he state counter EOR'ed with CPHA.

cheers!

Daryl


I understand. I thought I read in the initial thread (from way back when) you had or were goingt o try to use a third register:

//when BUSY rises, latch working register with contents of SODATA
//when done, latch SIDATA with contents of working register...

As, right now (and you note in your data sheet), changing SODATA while the transfer is happening will corrupt things. Also, I assume you can read SIDATA within the transfer time and see the bits shifting...

So, the good news is that with all the help, I now have a version of my Verilog that will compile and only uses 75 macrocells. I can probably get it down to fit into a '72.

But, it works by using three registers. When I try to squeeze it down to 2 (like you had), I run into issues with multiple clock sources on SODATA. Because, in my code, I am using the PHI2 clock to clock the databus into SODATA during a register write, and then the SODATA is getting shifted during the transfer.

Your ABEL code sidesteps the issue by using the presets and resets to "load" the SODATA register, and then the clock is only used to shift the data.

Which brings up a few questions:

  • I wonder how you replicate the .ar, .ap functionality in Verilog.
  • Is it best practice to implement 65XX peripheral registers using latches as opposed to clocked flip flops? I always used the falling edge of Phi2 to latch the data into the register.

Jim


Top
 Profile  
Reply with quote  
PostPosted: Sat May 17, 2014 5:44 am 
Offline

Joined: Tue May 05, 2009 2:49 pm
Posts: 108
Daryl,

In your codebase, you have:

Code:
SOSEL              = SPI & !A1.pin & !A0.pin & !RW.pin;
SISEL              = SPI & !A1.pin & !A0.pin &  RW.pin & FRX;

BSY.d = 1;
BSY.clk = !SOSEL & !SISEL;
BSY.ar = st4 # !RES.pin;

Should not PHI2 be in BSY.clk somewhere?

Jim


Top
 Profile  
Reply with quote  
PostPosted: Sat May 17, 2014 2:01 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1683
Location: Sacramento, CA
brain wrote:
Code:
SOSEL              = SPI & !A1.pin & !A0.pin & !RW.pin;
SISEL              = SPI & !A1.pin & !A0.pin &  RW.pin & FRX;

BSY.d = 1;
BSY.clk = !SOSEL & !SISEL;
BSY.ar = st4 # !RES.pin;

Should not PHI2 be in BSY.clk somewhere?


PHI2 is in the SPI definition:
Code:
/ SPI Address Select Register
  SPI = CS1.pin & !CS2.pin & PHI2.pin;


As far as using the async inputs to the flip-flips goes, since I don't care about any transients in the registers while PHI2 is high during a write, using the transparent latch still stores the contents of the data bus on the falling edge of PHI2 too. Now if those transients did matter, then I would have had to do something else. This way simplifies the clocking for sure.

Daryl

_________________
Please visit my website -> https://sbc.rictor.org/


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: