Sunday, 22 April 2018

Softcore CPU, assembler and xorshift pseudorandom on an FPGA

I thought it might be nice to make some kind of simplistic 32-bit softcore CPU using an FPGA. The most natural opcodes (for me) would correspond to common C operations such arithmetic +, -, *, /, %, =, comparisons (and conditional jumps) <, >, <=, >=, !=, == and bit operations &, |, ^, ~, <<, >>. This is why I decided to call it CCPU. A simple assembler was written using C, a softcore CPU using verilog and a xorshift pseudorandom program using assembler.

This all put together outputs white noise.
--

 0 MOV A, 1     ; this program will do xorshift32
 2 MOV B, 0     ; displayed as noise on CCPUs VGA out
 4 MOV C, 1
 6 MOV D, 13
 8 MOV E, 17
10 MOV F, 5
12 MOV H, 256
14 MOV I, 64000
16 SHL G, A, D
17 XOR A, A, G  ; A ^= A << 13
18 SHR G, A, E
19 XOR A, A, G  ; A ^= A >> 17
20 SHL G, A, F
21 XOR A, A, G  ; A ^= A << 5
22 ADD B, B, C  ; B = B + 1
23 STO B, A     ; MEM[B] = A
24 JL B, I, 16  ; if(B==64000) goto 16;
26 JMP 2        ; goto 2;

--

Lossless Rayleigh scattering (from things smaller than the wavelength of the light) from air makes laser beams visible if you have enough power. Approximately 1:100000 of the beam power will be scattered per meter at this frequency. Redder colors scatter less. This is the same mechanism which is responsible for the sky being blue. I don't know exactly what my power level here is, probably slightly above 50 mW. The beam is rather bright and when focused capable of lighting matches.
--

module xorshift(input clock, output VGA_HS, VGA_VS, VGA_B);

wire cpu_clock;
wire pixel_clock;

/* VGA 640x400 70Hz */
parameter H = 640;  /* width of visible area */
parameter HFP = 16; /* unused time before hsync */
parameter HS = 96;  /* width of hsync */
parameter HBP = 48; /* unused time after hsync */
parameter V = 400;  /* height of visible area */
parameter VFP = 12; /* unused time before vsync */
parameter VS = 2;   /* width of vsync */
parameter VBP = 35; /* unused time after vsync */
reg [9:0] h_cnt;    /* horizontal pixel counter */
reg [9:0] v_cnt;    /* vertical pixel counter */

reg hs, vs, pixel;
reg vmem [320*200-1:0];
reg [15:0] video_counter;

/* horizontal pixel counter */
always@(posedge pclk)
begin
  if(h_cnt == H+HFP+HS+HBP-1) h_cnt <= 0;
  else h_cnt <= h_cnt + 1;
  if(h_cnt == H+HFP) hs <= 0;
  if(h_cnt == H+HFP+HS) hs <= 1;
end

/* vertical pixel counter */
always@(posedge pclk)
begin
  if(h_cnt == H+HFP)
  begin
    if(v_cnt == VS+VBP+V+VFP-1) v_cnt <= 0;
    else v_cnt <= v_cnt + 1;
    if(v_cnt == V+VFP) vs <= 1;
    if(v_cnt == V+VFP+VS) vs <= 0;
  end
end

/* RAMDAC */
always@(posedge pclk)
begin
  if((v_cnt < V) && (h_cnt < H))
  begin
  if(h_cnt[0] == 1)
    video_counter <= video_counter + 1;
    pixel <= vmem[video_counter];
  end else
  begin
    if(h_cnt == H+HFP)
    begin
      if(v_cnt == V+VFP)
        video_counter <= 0;
      else
        if((v_cnt < V) && (v_cnt[0] != 1))
          video_counter <= video_counter - 320;
    end
    pixel <= 0;
  end
end

assign VGA_HS = hs;
assign VGA_VS = vs;
assign VGA_B = pixel;
/* VGA */

reg [31:0] mem[255:0];
reg [31:0] D;
reg [31:0] pc;
reg [31:0] cnt;
reg [31:0] r[31:0], data;
reg [7:0] o, a, b, c;
reg pclk, cclk;

initial $readmemh("ram.hex", mem);

initial
begin
  pc = 0;
  o = 0;
  a = 0;
  b = 0;
  c = 0;
end

always @(posedge clock)
begin
  cnt <= cnt + 1;
  pclk <= ~pclk; /* 25 MHz */
  cclk <= ~cclk;
end

always @(posedge cpu_clock)
begin
  D <= mem[pc];
  o <= D[31:24];
  a <= D[23:16];
  b <= D[15:8];
  c <= D[7:0];
end

always @(negedge cpu_clock)
begin
  data <= mem[pc+1];
  case(o)
    1:begin /* JMP */
        pc <= data;
      end
    6:begin /* JL */
        if(r[a]<r[b])
          pc <= data;
        else
          pc <= pc + 1;
      end
    8:begin /* MOV */
        r[a] <= data;
        pc <= pc + 2;
      end
   11:begin /* STO */
        vmem[r[a]] <= r[b];
        pc <= pc + 1;
      end
   12:begin /* SHL */
        r[a] <= r[b] << r[c];
        pc <= pc + 1;
      end
   13:begin /* SHR */
        r[a] <= r[b] >> r[c];
        pc <= pc + 1;
      end
   17:begin /* XOR */
        r[a] <= r[b] ^ r[c];
        pc <= pc + 1;
      end
   18:begin /* ADD */
        r[a] <= r[b] + r[c];
        pc <= pc + 1;
      end
  endcase
end

assign pixel_clock = pclk;
assign cpu_clock = cclk;

endmodule
--
#include <stdio.h>
#include <stdlib.h>


/* Branching */
#define NOP  0
#define JMP  1
#define JE   2
#define JNE  3
#define JG   4
#define JGE  5
#define JL   6
#define JLE  7
/* Loading */
#define MOV  8
#define LDA 10
#define STO 11
/* Logic */
#define SHL 12
#define SHR 13
#define NOT 14
#define AND 15
#define OR  16
#define XOR 17
/* Arithmetic */
#define ADD 18
int isOpcode(char *opc, char *cmp) {
  int i, j = 0;
  for(i=0; i<3; i++)
    if(opc[i] == cmp[i])
      j++;
    if(j==3)
      return 1;
    else
      return 0;
}
int main() {
  FILE *in = fopen("xorshift.asm", "r");
  FILE *out = fopen("ram.bin", "wb");
  FILE *outh = fopen("ram.hex", "w");
  unsigned char buf[256], nbuf[64], op[3], regs[3];
  int i, j, k, line = 0;
  while(fgets(buf, 256, in)!=NULL) {
    /* find opcode */
    for(i=0; i<3; i++)
      op[i] = buf[i];
    for(i=0; i<64; i++)
      nbuf[i] = 0;
    i = 3;
    j = 0;
    while(buf[i]!='\n' && buf[i]!=';') {
      if(buf[i]>47 && buf[i]<58)
        nbuf[j++] = buf[i];
      i++;
    }
    j = atoi(nbuf);
    /* find registers */
    i = 3;
    k = 0;
    while(buf[i]!='\n' && buf[i]!=';') {
      if(buf[i]>64 && buf[i]<91)
        regs[k++] = buf[i]-'A';
      i++;
    }
    /* handle opcodes */
    if(isOpcode(op, "NOP")) {
      i = (NOP<<24);
      fwrite(&i, 4, 1, out);
      printf("%d\tNOP\n", line);
      fprintf(outh, "%.8x\n", &i);
      line += 1;
    }
    if(isOpcode(op, "JMP")) {
      i = JMP<<24;
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJMP %d\n", line, j):
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "JE ")) {
      i = (JE<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJE  %c, %c, %d\n", line, regs[0]+'A', regs[1]+'A', j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "JNE")) {
      i = (JNE<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJNE %c, %c, %d\n", line, regs[0]+'A', regs[1]+'A', j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "JG ")) {
      i = (JG<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJG %c, %c, %d\n", line, regs[0]+'A', regs[1]+'A', j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "JGE")) {
      i = (JGE<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJGE %c, %c, %d\n", line, regs[0]+'A', regs[1]+'A',j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "JL ")) {
      i = (JL<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJL  %c, %c, %d\n", line, regs[0]+'A', regs[1]+'A', j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "JLE")) {
      i = (JLE<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tJLE %c, %c, %d\n", line, regs[0]+'A', regs[1]+'A', j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "MOV")) {
      i = (MOV<<24)+(regs[0]<<16);
      fwrite(&i, 4, 1, out);
      fwrite(&j, 4, 1, out);
      printf("%d\tMOV %c, %d\n", line, regs[0]+'A', j);
      fprintf(outh, "%.8x\n", i);
      fprintf(outh, "%.8x\n", j);
      line += 2;
    }
    if(isOpcode(op, "LDA")) {
      i = (LDA<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      printf("%d\tLDA %c, %c\n", line, regs[0]+'A', regs[1]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "STO")) {
      i = (STO<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      printf("%d\tSTO %c, %c\n", line, regs[0]+'A', regs[1]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "SHL")) {
      i = (SHL<<24)+(regs[0]<<16)+(regs[1]<<8)+regs[2];
      fwrite(&i, 4, 1, out);
      printf("%d\tSHL %c, %c, %c\n", line, regs[0]+'A', regs[1]+'A', regs[2]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "SHR")) {
      i = (SHR<<24)+(regs[0]<<16)+(regs[1]<<8)+regs[2];
      fwrite(&i, 4, 1, out);
      printf("%d\tSHR %c, %c, %c\n", line, regs[0]+'A', regs[1]+'A', regs[2]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "NOT")) {
      i = (NOT<<24)+(regs[0]<<16)+(regs[1]<<8);
      fwrite(&i, 4, 1, out);
      printf("%d\tNOT %c, %c\n", line, regs[0]+'A', regs[1]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "AND")) {
      i = (AND<<24)+(regs[0]<<16)+(regs[1]<<8)+regs[2];
      fwrite(&i, 4, 1, out);
      printf("%d\tAND %c, %c, %c\n", line, regs[0]+'A', regs[1]+'A', regs[2]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "OR ")) {
      i = (OR<<24)+(regs[0]<<16)+(regs[1]<<8)+regs[2];
      fwrite(&i, 4, 1, out);
      printf("%d\tOR  %c, %c, %c\n", line, regs[0]+'A', regs[1]+'A', regs[2]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "XOR")) {
      i = (XOR<<24)+(regs[0]<<16)+(regs[1]<<8)+regs[2];
      fwrite(&i, 4, 1, out);
      printf("%d\tXOR %c, %c, %c\n", line, regs[0]+'A', regs[1]+'A', regs[2]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
    if(isOpcode(op, "ADD")) {
      i = (ADD<<24)+(regs[0]<<16)+(regs[1]<<8)+regs[2];
      fwrite(&i, 4, 1, out);
      printf("%d\tADD %c, %c, %c\n", line, regs[0]+'A', regs[1]+'A', regs[2]+'A');
      fprintf(outh, "%.8x\n", i);
      line += 1;
    }
  }
  fflush(out);
  fflush(outh);
  fclose(in);
  fclose(out);
  fclose(outh);
}