ucisc 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -1
- data/core/stdlib.ucisc +101 -0
- data/examples/everest_12bit.ucisc +195 -0
- data/examples/factorial.ucisc +24 -22
- data/examples/fib.ucisc +40 -35
- data/examples/hello_world.ucisc +59 -68
- data/examples/paint_image.ucisc +261 -0
- data/exe/png_to_hex +38 -0
- data/exe/ucisc +8 -5
- data/lib/micro_cisc.rb +30 -7
- data/lib/micro_cisc/compile/compiler.rb +10 -2
- data/lib/micro_cisc/compile/instruction.rb +40 -13
- data/lib/micro_cisc/compile/statement.rb +118 -34
- data/lib/micro_cisc/version.rb +1 -1
- data/lib/micro_cisc/vm/color_lcd_display.rb +115 -0
- data/lib/micro_cisc/vm/device.rb +32 -21
- data/lib/micro_cisc/vm/processor.rb +20 -9
- data/lib/micro_cisc/vm/term_device.rb +4 -8
- data/ucisc.gemspec +2 -0
- data/ucisc.vim +14 -8
- metadata +36 -4
- data/examples/image.ucisc +0 -543
- data/lib/micro_cisc/vm/video.rb +0 -151
data/exe/ucisc
CHANGED
@@ -6,14 +6,17 @@ require "byebug"
|
|
6
6
|
|
7
7
|
if ARGV.length < 1
|
8
8
|
puts "Usage:"
|
9
|
-
puts " ucisc <file_name>"
|
9
|
+
puts " ucisc <file_name> [<file_name> ...]"
|
10
10
|
exit(0)
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
puts "Reading #{
|
15
|
-
compiler = MicroCisc.load(
|
16
|
-
|
13
|
+
file_names = ARGV.reject { |name| name.start_with?("-") }
|
14
|
+
puts "Reading #{file_names.join(" ")}"
|
15
|
+
compiler = MicroCisc.load(file_names)
|
16
|
+
|
17
|
+
instructions = compiler.command_count
|
18
|
+
size = compiler.serialize.size
|
19
|
+
puts "Running program with #{instructions} instructions compiled to #{size} words"
|
17
20
|
|
18
21
|
begin
|
19
22
|
MicroCisc.run(compiler.serialize)
|
data/lib/micro_cisc.rb
CHANGED
@@ -13,25 +13,48 @@ require "micro_cisc/vm/device"
|
|
13
13
|
require "micro_cisc/vm/processor"
|
14
14
|
require "micro_cisc/vm/term_device"
|
15
15
|
require "micro_cisc/vm/empty_device"
|
16
|
+
require "micro_cisc/vm/color_lcd_display"
|
16
17
|
|
17
18
|
module MicroCisc
|
18
19
|
class Error < StandardError; end
|
19
20
|
|
20
|
-
def self.load(
|
21
|
-
text =
|
21
|
+
def self.load(file_names)
|
22
|
+
text = ""
|
23
|
+
file_names.each do |file_name|
|
24
|
+
text += File.read(file_name)
|
25
|
+
end
|
22
26
|
MicroCisc::Compile::Compiler.new(text)
|
23
27
|
end
|
24
28
|
|
25
29
|
def self.run(data)
|
26
|
-
|
27
|
-
|
30
|
+
blocks = data.size / 256 + 1
|
31
|
+
rom_blocks = []
|
32
|
+
(0...blocks).each do |block|
|
33
|
+
rom = Array.new(256).map { 0 }
|
34
|
+
size = data.size - block * 256
|
35
|
+
size = 256 if size > 256
|
36
|
+
rom[0...size] = data[(block * 256)...((block + 1) * 256)]
|
37
|
+
rom_blocks << rom
|
38
|
+
end
|
28
39
|
terminal = MicroCisc::Vm::TermDevice.new(5)
|
29
|
-
|
30
|
-
|
40
|
+
screen = MicroCisc::Vm::EmptyDevice.new
|
41
|
+
init_screen = ARGV.include?('-s')
|
42
|
+
if(init_screen)
|
43
|
+
screen = MicroCisc::Vm::ColorLcdDisplay.new(
|
44
|
+
6, # Device ID
|
45
|
+
40, # 10k words of memory (40 blocks)
|
46
|
+
128, # screen pixel width
|
47
|
+
72, # screen pixel height
|
48
|
+
MicroCisc::Vm::ColorLcdDisplay::COLOR_MODE_12BIT
|
49
|
+
)
|
50
|
+
end
|
51
|
+
devices = Array.new(17).map { MicroCisc::Vm::EmptyDevice.new }
|
31
52
|
devices[15] = terminal # first banked device
|
32
|
-
|
53
|
+
devices[16] = screen
|
54
|
+
processor = MicroCisc::Vm::Processor.new(1, 256, rom_blocks)
|
33
55
|
processor.devices = devices
|
34
56
|
processor.start(ARGV.include?('-d'))
|
57
|
+
screen.join if(init_screen)
|
35
58
|
processor
|
36
59
|
end
|
37
60
|
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module MicroCisc
|
2
2
|
module Compile
|
3
3
|
class Compiler
|
4
|
+
attr_reader :command_count
|
5
|
+
|
4
6
|
def initialize(text)
|
5
7
|
@text = text
|
8
|
+
@command_count = 0
|
6
9
|
parse
|
7
10
|
end
|
8
11
|
|
@@ -11,17 +14,21 @@ module MicroCisc
|
|
11
14
|
@instructions = []
|
12
15
|
address = 0
|
13
16
|
@labels = {}
|
14
|
-
@
|
17
|
+
@indexed_vars = {}
|
18
|
+
@equivalents = {}
|
15
19
|
errors = []
|
16
20
|
lgen = MicroCisc::Compile::LabelGenerator.new
|
21
|
+
@command_count = 0
|
17
22
|
@text.each_line do |line|
|
18
23
|
begin
|
19
|
-
statement = MicroCisc::Compile::Statement.new(lgen, line, @
|
24
|
+
statement = MicroCisc::Compile::Statement.new(lgen, line, @indexed_vars, @equivalents)
|
25
|
+
command = false
|
20
26
|
statement.parse.each do |instruction|
|
21
27
|
if instruction.label?
|
22
28
|
@labels[instruction.label] = address
|
23
29
|
elsif instruction.instruction?
|
24
30
|
@instructions << [line_number, instruction]
|
31
|
+
command = true
|
25
32
|
address += 1
|
26
33
|
elsif instruction.data?
|
27
34
|
@instructions += instruction.data.map { |d| [line_number, d] }
|
@@ -38,6 +45,7 @@ module MicroCisc
|
|
38
45
|
address += word_counts.sum
|
39
46
|
end
|
40
47
|
end
|
48
|
+
@command_count += 1 if command
|
41
49
|
rescue ArgumentError => e
|
42
50
|
MicroCisc.logger.error("Error on line #{line_number}: #{e.message}\n #{line}")
|
43
51
|
errors << [line_number, e, line]
|
@@ -1,19 +1,20 @@
|
|
1
1
|
module MicroCisc
|
2
2
|
module Compile
|
3
3
|
class Instruction
|
4
|
-
|
4
|
+
COMPONENT_WITH_QUOTES_REGEX = /(?<component>[^\s"']+|"((\\"|[^"])*)")/
|
5
|
+
attr_reader :label, :operation, :data, :immediates, :sign, :dir, :src, :dest, :original, :minimal, :inc
|
5
6
|
|
6
|
-
def initialize(label_generator, minimal, original,
|
7
|
+
def initialize(label_generator, minimal, original, statement)
|
7
8
|
@label_generator = label_generator
|
8
9
|
@original = original
|
9
10
|
@label = nil
|
10
11
|
@operation = nil
|
11
12
|
@sign = nil
|
12
13
|
@dir = nil
|
13
|
-
@
|
14
|
+
@immediates = []
|
14
15
|
@src = nil
|
15
16
|
@dest = nil
|
16
|
-
@
|
17
|
+
@statement = statement
|
17
18
|
@data = nil
|
18
19
|
@inc = nil
|
19
20
|
@eff = nil
|
@@ -38,7 +39,7 @@ module MicroCisc
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def parse_ucisc(minimal_instruction)
|
41
|
-
@components = minimal_instruction.
|
42
|
+
@components = minimal_instruction.scan(COMPONENT_WITH_QUOTES_REGEX).flatten
|
42
43
|
|
43
44
|
return if @components.empty?
|
44
45
|
|
@@ -57,6 +58,28 @@ module MicroCisc
|
|
57
58
|
|
58
59
|
if @components.first == '%'
|
59
60
|
@components.shift
|
61
|
+
@components = @components.map do |component|
|
62
|
+
if component =~ /"(\\"|[^"])*"/
|
63
|
+
# Remove surrounding quotes
|
64
|
+
component = component[1...(component.length - 1)]
|
65
|
+
component = component.gsub("\\n","\n")
|
66
|
+
component = component.gsub("\\\"","\"")
|
67
|
+
hex = []
|
68
|
+
offset = 0
|
69
|
+
while(offset < component.length)
|
70
|
+
pair = component[offset...(offset + 2)]
|
71
|
+
pair = pair.bytes
|
72
|
+
pair << 0 if pair.length < 2
|
73
|
+
word = pair.pack("C*").unpack("S>").last
|
74
|
+
hex_word = "%04X" % word
|
75
|
+
hex << hex_word
|
76
|
+
offset += 2
|
77
|
+
end
|
78
|
+
component = ["%04X" % component.length] + hex
|
79
|
+
else
|
80
|
+
component
|
81
|
+
end
|
82
|
+
end.flatten
|
60
83
|
@data = @components.map do |component|
|
61
84
|
if match = /(?<name>[^\s]+)\.(?<type>imm|disp)/.match(component)
|
62
85
|
[match['name'], match['type']]
|
@@ -98,7 +121,7 @@ module MicroCisc
|
|
98
121
|
raise ArgumentError, "Duplicate #{component.last} value" if current
|
99
122
|
code = component.first
|
100
123
|
unless code >= 0 && code < 16
|
101
|
-
raise ArgumentError, "Value of #{component.last} must be between 0x0 and 0xF instead of #{component.first.to_s(16).upcase}"
|
124
|
+
raise ArgumentError, "Value of #{component.last} must be between 0x0 and 0xF instead of 0x#{component.first.to_s(16).upcase}"
|
102
125
|
end
|
103
126
|
component.first
|
104
127
|
end
|
@@ -135,8 +158,10 @@ module MicroCisc
|
|
135
158
|
while components.size > 0
|
136
159
|
to_parse = components.shift
|
137
160
|
if(to_parse.start_with?('$') || to_parse.start_with?('&'))
|
138
|
-
raise ArgumentError, "Missing ref #{to_parse}" unless @
|
139
|
-
to_parse = @
|
161
|
+
raise ArgumentError, "Missing ref #{to_parse}" unless @statement.get_var(to_parse)
|
162
|
+
to_parse = @statement.get_var(to_parse)
|
163
|
+
components = to_parse.split(/\s/) + components
|
164
|
+
to_parse = components.shift
|
140
165
|
end
|
141
166
|
parsed = parse_component(to_parse)
|
142
167
|
if ['val', 'reg', 'mem'].include?(parsed.last)
|
@@ -198,7 +223,9 @@ module MicroCisc
|
|
198
223
|
end
|
199
224
|
end
|
200
225
|
|
201
|
-
if @immediates.last != 0 &&
|
226
|
+
if @immediates.last != 0 && @operation == 'alu'
|
227
|
+
raise ArgumentError, "Destination immediate is not allowed for compute instructions"
|
228
|
+
elsif @immediates.last != 0 && (!@source_is_mem || !@dest_is_mem)
|
202
229
|
raise ArgumentError, "Destination immediate is only allowed when both arguments are mem args"
|
203
230
|
end
|
204
231
|
|
@@ -208,18 +235,18 @@ module MicroCisc
|
|
208
235
|
|
209
236
|
def validate_immediate(value, index)
|
210
237
|
width = @bit_width
|
211
|
-
width = width / 2 if @source_is_mem && @dest_is_mem
|
238
|
+
width = width / 2 if @operation == 'copy' && @source_is_mem && @dest_is_mem
|
212
239
|
if index == 0 && @source_is_mem || index == 1 && @dest_is_mem
|
213
240
|
min = 0
|
214
|
-
max = (
|
241
|
+
max = (1 << width) - 1
|
215
242
|
else
|
216
|
-
magnitude = 1 << (
|
243
|
+
magnitude = 1 << (width - 1)
|
217
244
|
min = magnitude * -1
|
218
245
|
max = magnitude - 1
|
219
246
|
end
|
220
247
|
if (value < min || value > max)
|
221
248
|
signed = @source_is_mem ? 'unsigned' : 'signed'
|
222
|
-
raise ArgumentError, "Immediate max bits is #{
|
249
|
+
raise ArgumentError, "Immediate max bits is #{width} #{signed}; value must be between #{min} and #{max} instead of #{value}"
|
223
250
|
end
|
224
251
|
end
|
225
252
|
|
@@ -1,16 +1,17 @@
|
|
1
1
|
module MicroCisc
|
2
2
|
module Compile
|
3
3
|
class Statement
|
4
|
-
SUGAR_REGEX = /(?<
|
4
|
+
SUGAR_REGEX = /(?<names>(\$|&)[^\s\[\]]+(\s*,\s*(\$|&)[^\s\[\]]+)*)\s+(?<op>as|=)\s+(?<param>.+)/
|
5
5
|
FUNCTION_REGEX = /(?<stack>[^\s\[\]]+)\s*(\[(?<words>[0-9]+)\]){0,1}\s+<=\s+(?<label>[a-zA-Z_][a-zA-Z0-9_\-@$!%]*)\s*\(\s*(?<args>[^)]*)/
|
6
|
-
IMM_REGEX = / (0x){0,1}
|
6
|
+
IMM_REGEX = / (?<imm_val>-{0,1}(0x){0,1}[0-9A-Fa-f])\.imm/
|
7
7
|
attr_reader :original, :minimal
|
8
8
|
|
9
|
-
def initialize(label_generator, statement,
|
9
|
+
def initialize(label_generator, statement, indexed_vars, equivalents)
|
10
10
|
@label_generator = label_generator
|
11
11
|
@original = statement
|
12
12
|
@minimal = filter_comments(statement)
|
13
|
-
@
|
13
|
+
@indexed_vars = indexed_vars
|
14
|
+
@equivalents = equivalents
|
14
15
|
end
|
15
16
|
|
16
17
|
def filter_comments(instruction)
|
@@ -24,44 +25,124 @@ module MicroCisc
|
|
24
25
|
instruction.gsub(/\s+/, ' ')
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
-
|
28
|
+
def normalize(arg, delta)
|
29
|
+
imm_matches = arg.scan(IMM_REGEX).flatten
|
30
|
+
imm_val = imm_matches.map(&:to_i).sum + delta
|
31
|
+
arg = arg.gsub(IMM_REGEX, '').strip
|
32
|
+
[arg, imm_val]
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_variable(name, arg, delta)
|
36
|
+
unless arg.include?("mem") || arg.include?("reg")
|
37
|
+
raise ArgumentError, "Indexed variable reference is not allowed for non-register arguments, use 'as' instead"
|
38
|
+
end
|
39
|
+
|
40
|
+
arg = normalize(arg, delta)
|
41
|
+
imm_val = arg.last
|
42
|
+
# remove mem/reg part
|
43
|
+
arg_num = arg.first[0..-4]
|
44
|
+
name = name[1..-1]
|
45
|
+
|
46
|
+
mem_name = "$#{name}"
|
47
|
+
ref_name = "&#{name}"
|
48
|
+
@equivalents.delete(mem_name)
|
49
|
+
@equivalents.delete(ref_name)
|
50
|
+
@indexed_vars[mem_name] = ["#{arg_num}mem", imm_val]
|
51
|
+
@indexed_vars[ref_name] = ["#{arg_num}reg", imm_val]
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_equivalent(name, arg, delta)
|
55
|
+
arg = normalize(arg, delta)
|
56
|
+
if arg.first.include?("mem") || arg.first.include?("reg")
|
57
|
+
imm_val = arg.last
|
58
|
+
# remove mem/reg part
|
59
|
+
arg_num = arg[0][0..-4]
|
29
60
|
name = name[1..-1]
|
30
|
-
arg = arg[0..-4]
|
31
61
|
|
32
|
-
|
33
|
-
|
62
|
+
mem_name = "$#{name}"
|
63
|
+
ref_name = "&#{name}"
|
64
|
+
@indexed_vars.delete(mem_name)
|
65
|
+
@indexed_vars.delete(ref_name)
|
66
|
+
|
67
|
+
imm_str = " #{imm_val}.imm" if imm_val > 0
|
68
|
+
@equivalents[mem_name] = "#{arg_num}mem#{imm_str}"
|
69
|
+
@equivalents[ref_name] = "#{arg_num}reg#{imm_str}"
|
34
70
|
else
|
35
|
-
@
|
71
|
+
@indexed_vars.delete(name)
|
72
|
+
@equivalents[name] = arg.first
|
36
73
|
end
|
37
74
|
end
|
38
75
|
|
76
|
+
def update_variable(arg, delta)
|
77
|
+
arg = arg.sub('reg', 'mem')
|
78
|
+
['mem', 'reg'].each do |type|
|
79
|
+
variable = arg.sub('mem', type)
|
80
|
+
@indexed_vars.each do |name, value|
|
81
|
+
value[1] += delta if value.first == variable
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_var(name)
|
87
|
+
val = @equivalents[name]
|
88
|
+
val ||=
|
89
|
+
begin
|
90
|
+
pair = @indexed_vars[name]
|
91
|
+
"#{pair.first} #{pair.last}.imm" if pair
|
92
|
+
end
|
93
|
+
val || name
|
94
|
+
end
|
95
|
+
|
39
96
|
def parse
|
97
|
+
instruction = nil
|
98
|
+
names = []
|
99
|
+
op = nil
|
40
100
|
if FUNCTION_REGEX =~ @minimal
|
41
|
-
parse_function_call
|
101
|
+
return parse_function_call
|
42
102
|
elsif SUGAR_REGEX =~ @minimal
|
43
103
|
match = SUGAR_REGEX.match(@minimal)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@minimal
|
50
|
-
instruction = Instruction.new(@label_generator, minimal, original, @sugar)
|
51
|
-
dest = instruction.dest
|
52
|
-
if [1, 2, 3].include?(dest)
|
53
|
-
create_variable(name, "#{dest}.mem")
|
54
|
-
else
|
55
|
-
dest -= 4 if dest > 4
|
56
|
-
create_variable(name, "#{dest}.reg")
|
57
|
-
end
|
58
|
-
[instruction]
|
104
|
+
op = match['op']
|
105
|
+
names = match['names'].split(',').map(&:strip)
|
106
|
+
|
107
|
+
@minimal = match['param']
|
108
|
+
if minimal.start_with?('copy') || minimal.start_with?('compute')
|
109
|
+
instruction = Instruction.new(@label_generator, @minimal, original, self)
|
59
110
|
else
|
60
|
-
|
111
|
+
var = match['param']
|
112
|
+
imm_match = IMM_REGEX.match(var)
|
113
|
+
imm_val = imm_match ? imm_match['imm_val'].to_i : 0
|
114
|
+
var = var.to_s.gsub(IMM_REGEX, '').strip
|
115
|
+
@minimal = "#{get_var(var)}#{" #{imm_val}.imm" if imm_val != 0}"
|
61
116
|
end
|
62
117
|
else
|
63
|
-
|
118
|
+
instruction = Instruction.new(@label_generator, @minimal, original, self)
|
64
119
|
end
|
120
|
+
if instruction && instruction.instruction?
|
121
|
+
dest = instruction.dest
|
122
|
+
if instruction.inc && instruction.inc > 0
|
123
|
+
if dest > 4
|
124
|
+
# pop
|
125
|
+
update_variable("#{dest - 4}.mem", -1)
|
126
|
+
else
|
127
|
+
# push
|
128
|
+
update_variable("#{dest}.mem", 1)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if minimal.start_with?('copy') && dest > 4 && instruction.src == dest
|
132
|
+
# Manually modifying a register
|
133
|
+
update_variable("#{dest - 4}.mem", -1 * instruction.immediates.first)
|
134
|
+
end
|
135
|
+
dest -= 4 if dest > 4
|
136
|
+
@minimal = "#{dest}.mem"
|
137
|
+
end
|
138
|
+
names.each_with_index do |name, index|
|
139
|
+
if op == 'as'
|
140
|
+
create_equivalent(name, @minimal, index)
|
141
|
+
elsif op == '='
|
142
|
+
create_variable(name, @minimal, index)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
[instruction].compact
|
65
146
|
end
|
66
147
|
|
67
148
|
def parse_function_call
|
@@ -69,7 +150,7 @@ module MicroCisc
|
|
69
150
|
label = match['label']
|
70
151
|
|
71
152
|
stack = match['stack']
|
72
|
-
stack =
|
153
|
+
stack = get_var(stack)
|
73
154
|
raise ArgumentError, "Invalid stack param, mem register expected: #{stack}" unless stack =~ /^[1-3]\.mem$/
|
74
155
|
stackp = stack.sub('mem', 'reg')
|
75
156
|
|
@@ -79,15 +160,15 @@ module MicroCisc
|
|
79
160
|
instructions = []
|
80
161
|
if return_words > 0
|
81
162
|
instruction = "copy #{stackp} -#{return_words}.imm #{stackp}"
|
82
|
-
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return vars - #{original}",
|
163
|
+
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return vars - #{original}", self)
|
83
164
|
end
|
84
165
|
|
85
166
|
instruction = "copy 0.reg #{args.size + 2}.imm #{stack} push"
|
86
|
-
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return addr - #{original}",
|
167
|
+
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # return addr - #{original}", self)
|
87
168
|
|
88
169
|
stack_delta = 1 + return_words
|
89
170
|
args = args.each do |arg|
|
90
|
-
arg = arg.split(' ').map { |a|
|
171
|
+
arg = arg.split(' ').map { |a| get_var(a) || a }.join(' ')
|
91
172
|
is_stack = arg.start_with?(stack)
|
92
173
|
if is_stack
|
93
174
|
offset = stack_delta
|
@@ -102,10 +183,13 @@ module MicroCisc
|
|
102
183
|
end
|
103
184
|
instruction = "copy #{arg} #{stack} push"
|
104
185
|
stack_delta += 1
|
105
|
-
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # push arg - #{original}",
|
186
|
+
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # push arg - #{original}", self)
|
106
187
|
end
|
107
188
|
instruction = "copy 0.reg #{label}.disp 0.reg"
|
108
|
-
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # call - #{original}",
|
189
|
+
instructions << Instruction.new(@label_generator, instruction, " #{instruction} # call - #{original}", self)
|
190
|
+
if return_words > 0
|
191
|
+
update_variable(stack, return_words)
|
192
|
+
end
|
109
193
|
instructions
|
110
194
|
end
|
111
195
|
end
|