ucisc 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|