Patrick McNamara wrote:
After looking at the lines above, I would probably go for this as a
general format:
CMD OP1 OP2 [flags] for two operand instructions
CMD OP1 [flags] for one operand instructions
CMD [flags] for zero operand instructions
Though really, this is a kind of academic discussion. For as simple a
syntax as this will have, setting up a compiler that access a
configurable definition for line syntax would not be terribly hard.
Still, having a single syntax will make it much easier for people
other than the author to read and understand programs.
Patrick M
_______________________________________________
Open-graphics mailing list
[email protected]
http://lists.duskglow.com/mailman/listinfo/open-graphics
List service provided by Duskglow Consulting, LLC (www.duskglow.com)
So I sat down this afternoon and flushed this out a bit. Here is
Timothy's original program (bugs and all, plus my new ones :) ) in the
new instruction syntax. This assumes a sync polarity register. Flags
are asserted if specified.
; Simple 640x480 program
; hfp = 8
; hsync = 8
; hbp = 8
; vfp = 3
; vsync = 3
; vbp = 3
; vsync,hsync negative-assertion, data-enable positive assertion
; Entry point
.addr = 0
; Vertical program
ADDR 0 [vhd] ; vid addr = start of framebuffer
CALL vsync+1 1
CALL vsync 2
CALL vblank 2 [v] ; back porch
FETCH 640 [v] ; fetch first scanline
CALL vbp+1 1 [v]
CALL vbp 1 [v]
CALL vactive 479 [v]
NOOP [v] ; Already fetched last scanline
CALL vactive+1 1 [v]
CALL vblank 2 [v] ; front porch
CALL vblank_short 1 [v]
JUMP 0 [vh]
vsync:
NOOP 1 ; last 4 pixels of hsync
NOOP 2 [h] ; back porch
NOOP 640 [h] ; active period, but vblank
NOOP 2 [h] ; front porch
vblank:
NOOP 1 [v] ; last 4 pixels of hsync
NOOP 2 [vh] ; back porch
NOOP 640 [vh] ; active period, but vblank
NOOP 2 [vhr] ; front porch
vblank_short:
NOOP 1 [v] ; last 4 pixels of hsync
NOOP 2 [vh] ; back porch
NOOP 640 [vh] ; active period, but vblank
NOOP 1 [vhr] ; front porch
vactive:
FETCH 1 [v] ; last 4 pixels of hsync
NOOP 2 [vh] ; back porch
SEND 640 [vh] ; active period, but vblank
NOOP 2 [vhr] ; front porch
.end
Now for the fun part. What follows is a compiler for the defined
assembly. It will produce raw binary byte strings suitable for loading
into the video controller program memory. You should be able to modify
the memory module (RAMB16_S36_36.v) in SVN as follows to populate the
memory at startup with the compiled program. Please pardon my sad
attempt at Verilog. I am NOT a Verilog programmer by any stretch. The
final argument is the offset, if you want to start somewhere other than
0, change accordingly.
reg [35:0] mem [0:511];
initial begin
$readmemb("program.bin", mem, 0);
end
And now, here is the compiler code. It's all Perl and it's ugly. It
has bugs, I just haven't bothered to find them yet. I have not done
more than a cursory validation of the output byte strings to see that I
got all the bit positions in the opcodes correct. If enough people like
it, I'll commit it to SVN.
----BEGIN COMPILER----
#!/usr/bin/perl
###################################################################
#
# Sample video controller assembly langyuage compiler
#
# Copyright (c) 2006, Patrick McNamara
#
# This code is public domain and may be freely copied, modified
# and redistributed under this or other licenses.
#
# usage: compiler.pl <input file> <output file>
#
###################################################################
#start with our opcode definitions.
#fields are:
# 1, opcode value
# 2, argument count
# 3, argument 1 word position. A leading - means that the
# argument will be subtracted from the max possible
# 4, argument 2 word position, same format as argument 1
%opcode=(
"JUMP" => [0, 1, "8:0", ""],
"CALL" => [1, 2, "8:0", "-20:9"],
"NOOP" => [2, 1, "-20:9", ""],
"SEND" => [3, 1, "-20:9", ""],
#"UNDEF" => [4, 0, "", ""],
"FETCH" => [5, 1, "20:9", ""],
"ADDR" => [6, 1, "20:0", ""],
"INC" => [7, 1, "20:0", ""],
);
#The bit positions of the flags in the upper byte
%flag_bit=(
"z" => 0x01,
"i" => 0x02,
"r" => 0x04,
"x" => 0x08,
"y" => 0x10,
"h" => 0x20,
"v" => 0x40,
"d" => 0x80,
);
my %labels=();
my @binary=();
my @src=();
#slurp in the source file
open(SRC, $ARGV[0]);
@src=<SRC>;
chomp(@src);
close(SRC);
#pass 1, figure out what the labels, and their offsets are.
%labels=map_labels([EMAIL PROTECTED]);
#compile the code
@binary=compile([EMAIL PROTECTED], \%labels);
#and dump the results to disk
write_out($ARGV[1], [EMAIL PROTECTED]);
exit(0);
sub compile {
local *src=$_[0];
local *labels=$_[1];
my $line="";
my $flags;
my @flag_chars=();
my $char;
my $op, $arg1, $arg2;
my @op_info=();
my $binary;
my $bith, $bitl;
my $line_num=0;
my @output=();
my $op_count;
my $comp=0;
for $line (@src) {
#our running line count for errors and other human stuff
$line_num++;
print "$line_num: $line\n";
#by default, if not specified, the flags will all be 0;
$flags=0;
#zero out our final compiled byte string
$binary=0;
#if the line contains a comment ';' take everything before it
$line=(split(";", $line))[0] if($line=~/;/);
#if the line begins with a lable ':' take everything after it
$line=(split(":", $line))[1] if($line=~/:/);
#if we got a blank line, skip it.
next if($line=~/^\s*$/);
#if the line if a compiler macro, skip it
next if($line=~/^\s*\./);
#strip any leading blanks space
[EMAIL PROTECTED]@@g;
#check for flags in the command
if ($line=~/\[\w*\]/) {
#match the flags and extract
$line=~m/\[(\w*)\]/;
$char=$1;
#now remove them from the original line since we don't need
#them there anymore
[EMAIL PROTECTED]@@;
#split them into single characters
@flag_chars=split("", $char);
#and set any flag specified
for $char (@flag_chars) {
$flags|=$flag_bit{$char};
}
}
#insert the flags into the compiled bytestring
$binary |= 0xFF000000 & ($flags << 24);
$opcount=0;
#split the line into it's component pieces
($op, $arg1, $arg2)=split(" ", $line);
#and figure out how many operands we have.
$opcount++ if(defined($arg1));
$opcount++ if(defined($arg2) && $arg2 ne "");
#Make sure we have a valid operation and error if not.
if(!defined($opcode{$op})) {
print "ERROR: line $line_num - opcode not defined: $op\n";
next;
}
#retrieve the opcode definition
@[EMAIL PROTECTED];
#validate the number of arguments against what we expect. For the time
#being we will accept too many and just throw a warning.
if($opcount < $op_info[1]) {
print "ERROR: line $line_num - expected $op_info[1] arguments,
found $opcount\n";
next;
}
if($opcount > $op_info[1]) {
print "WARNING: line $line_num - expected $op_info[1] arguments,
found $opcount. Ignoring extra arguments\n";
next;
}
#add the opcode to the compiled byte string
$binary |= 0x00E00000 & ($op_info[0] << 21);
#if we have any arguments we need to process them
if ($op_info[1]>0) {
$comp=0;
#determine whether the argument will be complemented and get
#rid of the flag if it exists
if($op_info[2]=~/^-/) {
$comp=1;
$op_info[2]=~s/^-//;
}
#get the high a low bit positions
($bith, $bitl)=split(":", $op_info[2]);
#if the argument is not all numeric then we assume it is a label
#and translate it. If it won't translate, move on to the next line
#since the translation function was nice and printed the error
#message already
if($arg1=~/\D/) {
$arg1=translate_label($arg1, \%labels, $line_num);
next unless(defined($arg1));
$arg1=$labels{$arg1};
}
#if the argument is to be complemented, do so.
if($comp) {
$arg1=(2**($bith-$bitl))+1 - $arg1;
}
#Add the argument into the compiled bit string
$binary |= ((0xFFFFFFFF >> (31-$bith)) & (0xFFFFFFFF <<
($bitl))) & ($arg1 << $bitl);
}
#if there are two args we need to process the second one. See
comments for the first
#argument for explanation. Yes this chunk of code could probably be
made into a
#function for better maintenance and readability.
if ($op_info[1]>1) {
$comp=0;
if($op_info[3]=~/^-/) {
$comp=1;
$op_info[3]=~s/^-//;
}
if($arg2=~/\D/) {
$arg2=translate_label($arg2, \%labels, $line_num);
next unless(defined($arg2));
$arg2=$labels{$arg2};
}
($bith, $bitl)=split(":", $op_info[3]);
if($comp) {
$arg2=(2**($bith-$bitl))+1 - $arg2;
}
$binary |= ((0xFFFFFFFF >> (31-$bith)) & (0xFFFFFFFF <<
($bitl))) & ($arg2 << $bitl);
}
#print the compiled output for the user
printf "bin=0x%08X\n", $binary;
push(@output, $binary);
}
return(@output);
}
sub translate_label {
my $label_str=$_[0];
local *labels=$_[1];
my $line_num=$_[2];
my $label_name;
my $offset;
my $dir=1;
#it is possible that a label may have an offset associated with it
#which could be positive or negative. If the label name has a -
#in it we assume that it will have negative offset
$dir=-1 if($label_str=~/-/);
#Now, split off any offset that does exist
($label_name, $offset)=split("[+-]", $label_str);
#if there wasn't an offset, we will define it as zero to make the
#math simpler.
$offset=0 unless (defined($offset));
#make sure the label we have has a defined offset, otherwise print
#an error
unless (defined($labels{$label_name})) {
print "ERROR: line $line_num - label undefined: $label_name\n";
return(undef);
}
#return the address for the translated label and offset
return($labels{$label_name}+($offset*$dir));
}
sub map_labels {
local *src=$_[0];
my %labels={};
my $start=-1;
my $offset=0;
my $label="";
#parse throught the source looking for labels
for $line (@src) {
#skip empty lines
next if($line=~/^\s*$/);
#skip comment only lines
next if($line=~/^\s*;/);
#if we found the .addr macro, save the start address for
#label relocation later
if($line=~/^\s*\.addr\s+/) {
[EMAIL PROTECTED](\d+)@;
$start=$1;
next;
}
#skip any other compiler macro line
next if($line=~/^\s*\./);
#if we found a line that has a label definition, we need to
#process it.
if($line=~/^\s*\w+\s*:/) {
#match the label and extract it.
[EMAIL PROTECTED]([\w_]+)\s*:.*@;
$label=$1;
#since we are working with a fixed length instruction set
#we can simply count the executable lines we've parse and
#use that count as the label offset.
$labels{$label}=$offset;
}
$offset++;
}
#if the programmer didn't specify an entry point, chastise him, but
#continue anyway.
if($start==-1) {
print "Warning: No define entry point, assuming 0.\n";
$start=0;
}
#if we have a none 0 entry point, we need to relocat all our labels
#based on that entry point.
if($start) {
print "Relocating program to offset: $start\n";
for $label (keys(%labels)) {
$labels{$label}+=$start;
}
}
return(%labels);
}
sub write_out {
my $file=$_[0];
local *binary=$_[1];
my $out_string="";
my $count;
#output file
open(FILE, "> $file");
#process each compiled statement
for ($count=0; $count<scalar(@binary); $count++) {
#We are writing out BIG ENDIEN, mainly because it is easiest
#to load into a hex editor and verify against what is expected.
#This could easily be changed by changing the pack flag below.
$out_string=pack("N", $binary[$count]);
print FILE $out_string;
}
close(FILE);
}
_______________________________________________
Open-graphics mailing list
[email protected]
http://lists.duskglow.com/mailman/listinfo/open-graphics
List service provided by Duskglow Consulting, LLC (www.duskglow.com)