Propeller_CPLD
Title:Propeller_CPLD
Author:ale_pacito
Published:Mon, 26 Oct 2009 08:31:57 GMT

A Propeller+CPLD project by Pacito.Sys


The goal of this project is to use a CPLD to augment the video circuitry of the propeller to obtain high resolution frame-buffer based video.

A CPLD, complex programmable logic device, provides the glue logic needed to access a external SRAM where the frame-buffer is stored and also provides a way to access the SRAM from the propeller using as few lines as possible.

A thread at Parallax' forums can be found here.

The circuit is shown below:

vgamemctrl_prop.png


CPLD and SRAM

vgamemctrl_cpld.png


The circuit uses 2 CPLD, one XC9572 and one XC9536 because is what I had laying around. If you get a XC95144(XL) or bigger you can fit both parts into the same CPLD.
The color capabilities rise to 256 simultaneous colors from a palette of the same amount using 3:3:2 for R:G:B. In a FPGA a better wider DAC could be used and a palette could be held in memory but that is "Zukunftsmusik".

How it works:

Video refresh. The propeller generates synchronism signals for vertical and horizontal as well as a pixel enable and pixel clock signal. This pixel clock signa has to be 2 times the pixel clock. The pixel enable signal has to be asserted during the visible cicle of the image. Every other cycle the memory will be ready to be read or written by the propeller. As the CPLD use is a bit of a tight fit no two consecutive cycles can be used for read or write during blanking but it could be in a bigger device.

The memory waveform explaining this interleaved process is shown below. Here the simulator and test-bench signals are shown. The software used is Xilinx WebISE 10.1 but newer or older versions work as well.

sim_9572.png

A logic analyzer hooked to the memory and clock signals show the following.

Image here



The actual board has been done as a single side PCB with few bridges. The photo process worked quite well, for being the third board I do using this process. Details can be found here. Note that removing the oil before developing is very important for the NaOH to attack the photo sensible layer!

Image here



Fully built circuit can be seen here

vgamemctrl_bot.jpg
vgamemctrl_top.jpg


The Code inside the XC9572 is shown below (Verilog), it is fixed for 640x480 i.e. 307200 pixels.

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Pacito Systems Co.
// Engineer: Pacito.Sys hppacito <@> gmail.com
//
// Create Date:    18:04:13 10/03/2009
// Design Name:
// Module Name:    top
// Project Name:
// Target Devices: XC9572(XL)
// Tool versions: WebISE 10.1 (Linux)
// Description: A VGA and memory controller for the propeller
//
// Dependencies:
//
// Revision:
// Revision 0.02 - The finite state machines are used to handle memory read and write
// Additional Comments: (c) Copyright 2009 R.A. Paz Schmidt (aka Pacito.Sys, Pacito Systems Co.)
//
//////////////////////////////////////////////////////////////////////////////////
module top(
    output [18:0] m_addr, // Memory address
    output m_nce, // memory chip enable
    output m_noe, // memory output enable
    output m_nwe, // memory write enable
     output [1:0] wstate, // only for simulation
    output i0_read_le, // latch enable when reading
     output i1_vga_le, // vga latch enable
    output i2_mem_be, // buffer enable for memory write cycle
    input in_clk, // input clock, two times desired pixel clock
    input in_pixel_en, // clock enable, pixel clock will be active when this signal is 1
    input in_dataw, // data write signal
    input in_datar, // data read signal
    input [7:0] in_data // data bus from propeller
    );
 
parameter MEM_AWIDTH = 19;
parameter MEM_DWIDTH = 8;
parameter MAX_PIXELS = 11'b01010110000;
reg [MEM_AWIDTH-1:0] r_pixel_counter;
reg [MEM_AWIDTH-1:0] r_memory_ptr;
 
reg clk_pixel;
reg [1:0] r_wstate, r_nwstate;
reg [1:0] r_rstate, r_nrstate; // not needed
reg r_pixel_en;
 
reg [1:0] r_ridx;
 
reg r_mem_noe;
reg r_mem_nwe;
 
wire w_video_noe;
 
wire clk_mem = ~clk_pixel;
 
wire w_cmd_write = in_datar & in_dataw; // command write when both are asserted
wire w_dataw = in_dataw & (in_dataw ^ in_datar); // internal write signal
wire w_datar = in_datar & (in_dataw ^ in_datar); // internal read signal
 
assign wstate = r_wstate;
 
// every other clock we refresh the display if needed
always @ (posedge in_clk)
begin
   clk_pixel <= ~clk_pixel;
    if (in_pixel_en == 0) begin
        r_pixel_en <= 0;
    end else begin
        r_pixel_en <= 1;
    end
end
 
// Command write on the rising edge
always @ (posedge w_cmd_write)
begin
    r_ridx [1:0] <= in_data[1:0];
end
// Value write to the right register on the falling edge
always @ (negedge w_cmd_write)
begin
   case (r_ridx[1:0])
        2'b00: r_memory_ptr[7:0] = in_data;
        2'b01: r_memory_ptr[15:8] = in_data;
        2'b10: r_memory_ptr[MEM_AWIDTH-1:16] = in_data[MEM_AWIDTH-17:0];
        //2'b11: r_memory_ptr[MEM_AWIDTH-1:16] = in_data[MEM_AWIDTH-17:0]; // we do the same...
    endcase
end
 
// Increments pointer
always @ (posedge clk_mem)
begin
    if (r_pixel_counter[18:8] == MAX_PIXELS)
        r_pixel_counter = 0;
   else
        if (r_pixel_en == 1)
        r_pixel_counter = r_pixel_counter + 1;
end
 
// CPLD <-> Propeller comm
// Write
 
always @(*)
begin
    r_nwstate = 0;
    r_mem_nwe = (r_wstate == 2) ? 0:1;
    if (r_wstate == 0)
        if (w_dataw) r_nwstate = 1; // changes state when w_dataw goes high
    if (r_wstate == 1)
        if (clk_mem) r_nwstate = 2; // changes state when memory cycle is active
        else r_nwstate = 1; // keep current state
    if (r_wstate == 2)
        if (clk_mem) r_nwstate = 2; // keeps current state
end
 
always @ (posedge in_clk)
    r_wstate <= r_nwstate;
 
always @(*)
begin
    r_nrstate = 0;
    r_mem_noe = (r_rstate == 2) ? 0:1;
    if (r_rstate == 0)
        if (w_datar) r_nrstate = 1; // changes state when w_dataw goes high
    if (r_rstate == 1)
        if (clk_mem) r_nrstate = 2; // changes state when memory cycle is active
        else r_nrstate = 1; // keep current state
    if (r_rstate == 2)
        if (clk_mem) r_nrstate = 2; // keeps current state
end
 
always @ (posedge in_clk)
    r_rstate <= r_nrstate;
 
// Memory interface
assign w_video_noe = ~(clk_pixel & r_pixel_en);
 
assign m_addr = (w_video_noe == 0) ? r_pixel_counter:r_memory_ptr;
assign m_nce = r_mem_nwe & r_mem_noe & w_video_noe;
assign m_noe = r_mem_noe & w_video_noe;
assign m_nwe = r_mem_nwe;
 
assign i0_read_le = r_mem_noe;
assign i1_vga_le = w_video_noe;
assign i2_mem_be = !r_mem_nwe;
 
initial
begin
    clk_pixel = 0;
    r_wstate = 0;
    r_nwstate = 0;
    r_pixel_counter = 0;
    //r_mem_noe = 1;
    //r_mem_nwe = 1;
end
 
endmodule


The text code for the propeller is shown below

' VGA driver by Chip Gracey (c) 2006 Parallax Inc.
' CPLD code by me (c) 2009 Pacito.Sys
 
CON
 
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000
 
 
    VGASYNCPINS = 8
    VGACLK = 27
    VGAPIXEN = 26
    DATAR = 25
    DATAW = 24
 
  hp = 640      'horizontal pixels
  vp = 240      'vertical pixels
  hf = 24        'horizontal front porch pixels
  hs = 40       'horizontal sync pixels
  hb = 128       'horizontal back porch pixels
  vf = 9        'vertical front porch lines
  vs = 3        'vertical sync lines
  vb = 28       'vertical back porch lines
  hn = 1        'horizontal normal sync state (0|1)
  vn = 1        'vertical normal sync state (0|1)
  pr = 25       'pixel rate in MHz at 80MHz system clock (5MHz granularity)
 
' Tiles
 
  xtiles = hp / 32
  ytiles = vp / 32
 
' H/V inactive states
 
  hv_inactive = (hn << 1 + vn) * $0101
 
 
OBJ
   term : "FullDuplexSerial"
 
 
VAR
    byte buffer[16]
 
 
PUB start
 
    cognew(@vgasync, 0) ' starts VGA subsystem
    cognew(@memctrl, @buffer)
    term.start(31, 30, 0, 115200)
    term.str(string("Test, new run...", 13))
    term.hex(buffer[0], 2)
    term.hex(buffer[1], 2)
    term.hex(buffer[2], 2)
    term.hex(buffer[3], 2)
    term.tx(13)
 
 
DAT
{ This COG generates the synchronismus signals needed for VGA refresh
  the clock signal and the pixel enable signals
}
 
                        org     0
 
memctrl                 mov     DIRA, c6_cnt_dira
                        mov     c6_v_buff, PAR
                        mov     c6_v_addr, #511
                        mov     c6_v_data0, #$00
                        call    #c6_writedata
                        mov     c6_v_data0, #$ff
                        mov     c6_v_addr, #1
                        call    #c6_writedata
                        mov     c6_v_addr, #511
                        mov     c6_v_data0, #$77
                        call    #c6_writedata
                        mov     c6_v_addr, #51
                        mov     c6_v_data0, #$00
                        call    #c6_writedata
 
                        mov     c6_v_addr, #511
                        call    #c6_readdata
                        wrbyte  c6_v_data0, c6_v_buff
                        add     c6_v_buff, #1
                        wrbyte  c6_v_data1, c6_v_buff
                        add     c6_v_buff, #1
                        wrbyte  c6_v_data2, c6_v_buff
                        add     c6_v_buff, #1
                        wrbyte  c6_v_data3, c6_v_buff
                        add     c6_v_buff, #1
 
                        jmp     #$
 
c6_writeaddr            or      DIRA, #$ff
                        mov     OUTA, c6_cnt_reg0
                        mov     OUTA, c6_v_addr
                        mov     OUTA, c6_cnt_reg1
                        shr     c6_v_addr, #8
                        mov     OUTA, c6_v_addr
                        mov     OUTA, c6_cnt_reg2
                        shr     c6_v_addr, #8
                        mov     OUTA, c6_v_addr
                        andn    DIRA, #$ff
c6_writeaddr_ret        ret
 
 
c6_writedata            call    #c6_writeaddr
                        or      DIRA, #$ff
                        mov     OUTA, c6_v_data0
                        or      OUTA, c6_cnt_dataw
                        andn    DIRA, #$ff          ' data has been latched
 
 
                        andn    OUTA, c6_cnt_dataw
c6_writedata_ret        ret
 
c6_readdata             call    #c6_writeaddr
 
                        or      OUTA, c6_cnt_datar
                        andn    OUTA, c6_cnt_datar
                        mov     c6_v_data0, INA
                        mov     c6_v_data1, INA
                        mov     c6_v_data2, INA
                        mov     c6_v_data3, INA
                        'andn    OUTA, c6_cnt_datar
c6_readdata_ret         ret
 
c6_cnt_cmdw
c6_cnt_reg0             long    (1<<DATAR)|(1<<DATAW)
c6_cnt_reg1             long    (1<<DATAR)|(1<<DATAW)|1
c6_cnt_reg2             long    (1<<DATAR)|(1<<DATAW)|2
 
c6_cnt_pe               long    (1<<VGAPIXEN)
c6_cnt_dataw            long    (1<<DATAW)
c6_cnt_datar            long    (1<<DATAR)
c6_cnt_dira             long    (1<<DATAR)|(1<<DATAW)|(1<<VGAPIXEN)
c6_v_addr               long    0
c6_v_buff               long    0
c6_v_data0              long    0
c6_v_data1              long    0
c6_v_data2              long    0
c6_v_data3              long    0
 
DAT
{ This COG generates the synchronismus signals needed for VGA refresh
  the clock signal and the pixel enable signals
}
 
                        org     0
 
vgasync                 mov     DIRA, c7_cnt_dira
 
                        movi    ctra, #%00001_101        'enable PLL in ctra (VCO runs at 4x)
                        movi    frqa, #(pr / 5) << 3     'set pixel rate
 
                        mov     ctrb, #VGACLK          ' clock output
                        movi    ctrb, #%00010_110        ' PLL 2 times pixel rate
 
                        movi    frqb, #(pr / 5) << 3     'set pixel rate
                        mov     vcfg,reg_vcfg           'set video configuration
 
' Main loop, display field and do invisible sync lines
 
field                   mov     color_ptr,color_base    'reset color pointer
                        mov     pixel_ptr,pixel_base    'reset pixel pointer
                        mov     y,#ytiles               'set y tiles
:ytile                  mov     yl,#32                  'set y lines per tile
:yline                  mov     yx,#2                   'set y expansion
:yexpand                mov     x,#xtiles               'set x tiles
                        mov     vscl,vscl_pixel         'set pixel vscl
 
:xtile                  rdword  color,#0                'get color word
                        mov     color,hv                'set h/v inactive states
                        rdlong  pixel,#0                'get pixel long
                        waitvid color,#0                'pass colors and pixels to video
                        djnz    x,#:xtile               'another x tile?
 
                        mov     x,#1                    'do horizontal sync
                        call    #hsync
 
                        djnz    yx,#:yexpand            'y expand?
 
                        djnz    yl,#:yline              'another y line in same tile?
 
                        djnz    y,#:ytile               'another y tile?
 
                        'wrlong   colormask,par          'visible done, write non-0 to sync
 
                        mov     x,#vf                   'do vertical front porch lines
                        call    #blank
                        mov     x,#vs                   'do vertical sync lines
                        call    #vsync
                        mov     x,#vb                   'do vertical back porch lines
                        call    #vsync
 
                        jmp     #field                  'field done, loop
 
 
' Subroutine - do blank lines
 
vsync                   xor     hvsync,#$101            'flip vertical sync bits
 
blank                   mov     vscl,hvis               'do blank pixels
                        waitvid hvsync,#0
hsync                   mov     vscl,#hf                'do horizontal front porch pixels
                        waitvid hvsync,#0
                        mov     vscl,#hs                'do horizontal sync pixels
                        waitvid hvsync,#1
                        mov     vscl,#hb                'do horizontal back porch pixels
                        waitvid hvsync,#0
                        djnz    x,#blank                'another line?
hsync_ret
blank_ret
vsync_ret               ret
 
 
' Data
 
reg_dira                long    0                       'set at runtime
reg_dirb                long    0                       'set at runtime
reg_vcfg                long    $200002ff                       'set at runtime
 
color_base              long    0                       'set at runtime (2 contiguous longs)
pixel_base              long    0                       'set at runtime
 
vscl_pixel              long    1 << 12 + 32            '1 pixel per clock and 32 pixels per set
colormask               long    $FCFC                   'mask to isolate R,G,B bits from H,V
hvis                    long    hp                      'visible pixels per scan line
hv                      long    hv_inactive             '-H,-V states
hvsync                  long    hv_inactive ^ $200      '+/-H,-V states
 
c7_cnt_dira             long    (1<<VGACLK)|(1<<VGAPIXEN)|(3<<VGASYNCPINS)
 
' Uninitialized data
 
color_ptr               res     1
pixel_ptr               res     1
color                   res     1
pixel                   res     1
x                       res     1
y                       res     1
yl                      res     1
yx                      res     1
 
 
 
An image on a TFT monitor at 640x480 can be seen below. Note the use of graphics not possible with a bare propeller.